You have seen how to create and annotate XML content using the LINQ to XML API. Whenever we have XML available in memory, we can also navigate and eventually modify it. To navigate XML content, we can use methods and properties of X* classes, or we can also rely on LINQ queries over an X* object. In this section, we will see the former way of working, and in the next section we will see the latter.
Every XNode provides some methods and properties to navigate its hierarchy. For instance, we can use IsAfter or IsBefore methods to compare ordinal positioning of nodes in the document. These methods internally use the CompareDocumentOrder static method of the XNode class and return a numeric index, of type Integer, that represents the “distance" of two XNode instances in the containing XDocument. It is also used by XNodeDocumentOrderComparer, and it is useful when ordering nodes in LINQ to XML queries. Every XNode also provides a couple of properties, called NextNode and PreviousNode, that map to the next and previous nodes in the graph, as their names indicate. Pay attention to the relative cost of these properties. NextNode returns a reference to an internal field and is relatively cheap; PreviousNode requires a partial scan of the tree branch containing the current node and is a little bit more expensive. XContainer also provides LastNode and FirstNode properties. Finally, every XObject offers a Parent property that represents the parent node in the graph.
Whenever you find a node traversing the document using one of these techniques and you want to modify it, you can use methods such as Remove or ReplaceWith, which are available for any XNode, to remove the node itself from the graph or to replace it with a new fragment. There are also RemoveAttributes, ReplaceAttributes, and ReplaceAll for objects of type XElement, which work with their respective attributes or with the full set of child nodes. Finally, XElement also offers SetAttributeValue, SetElementValue, and SetValue to change the value of an attribute, a child element, or the entire current element, respectively.
In Listing 6-20, you can see how to replace one tag with another.
Listing 6-20: Example of tag replacement using the XElement ReplaceWith method
XElement customer = new XElement("customer", new XAttribute("id", "C01"), new XElement("firstName", "Paolo"), new XElement("lastName", "Pialorsi")); // Do something in the meantime ... customer.LastNode.ReplaceWith( new XElement("nickName", "PaoloPia"));
The preceding code block changes this XML:
<?xml version="1.0" encoding="utf-8"?> <customer > <firstName>Paolo</firstName> <lastName>Pialorsi</lastName> </customer>
into this XML:
<?xml version="1.0" encoding="utf-8"?> <customer > <firstName>Paolo</firstName> <nickName>PaoloPia</nickName> </customer>
Listing 6-21 shows you how to change attribute and element values.
Listing 6-21: Example of attribute and child element management using XElement methods
customer.SetAttributeValue("id", "C02"); customer.SetElementValue("notes", "Notes about this customer");
By calling these methods, the API creates attributes or elements that do not yet exist or changes the values of ones that already exist. When the value provided to these methods is NULL and the nodes already exist, they are removed.
While traversing the XML, keep in mind that the navigation technique you use influences the result. The methods and properties shown until now work directly in memory and determine their results at the time that you invoke them. If you ask to remove or replace a node, the action is taken instantly within the in-memory structure. In the case of queries over XML, based on the LINQ to XML query engine, modification methods are applied to query expression results that will be evaluated only when they are effectively used, like the ones you saw in Chapter 4.