XML Traversal

XML traversal is primarily accomplished with 4 properties and 11 methods. In this section, I try to mostly use the same code example for each property or method, except I change a single argument on one line when possible. The example in Listing 7-41 builds a full XML document.

Example. A Base Example Subsequent Examples May Be Derived From
//  I will use this to store a reference to one of the elements in the XML tree.
XElement firstParticipant;

XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

Console.WriteLine(xDocument);

First, notice that I am saving a reference to the first BookParticipant element I construct. I do this so that I can have a base element from which to do all the traversal. While I will not be using the firstParticipant variable in this example, I will in the subsequent traversal examples. The next thing to notice is the argument for the Console.WriteLine method. In this case, I output the document itself. As I progress through these traversal examples, I change that argument to demonstrate how to traverse the XML tree. So here is the output showing the document from the previous example:

<!DOCTYPE BookParticipants SYSTEM "BookParticipants.dtd">
<?BookCataloger out-of-print?>
<BookParticipants>
  <BookParticipant type="Author">
    <FirstName>Joe</FirstName>
    <LastName>Rattz</LastName>
  </BookParticipant>
  <BookParticipant type="Editor">
    <FirstName>Ewan</FirstName>
    <LastName>Buckingham</LastName>
  </BookParticipant>
</BookParticipants>

Traversal Properties

I will begin my discussion with the primary traversal properties. When directions (up, down, etc.) are specified, they are relative to the element the method is called on. In the subsequence examples, I save a reference to the first BookParticipant element, and it is the base element used for the traversal.

Forward with XNode.NextNode

Traversing forward through the XML tree is accomplished with the NextNode property. Listing 7-42 is an example.

Example. Traversing Forward from an XElement Object via the NextNode Property
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

Console.WriteLine(firstParticipant.NextNode);

Since the base element is the first BookParticipant element, firstParticipant, traversing forward should provide me with the second BookParticipant element. Here are the results:

<BookParticipant type="Editor">
  <FirstName>Ewan</FirstName>
  <LastName>Buckingham</LastName>
</BookParticipant>

Based on these results I would say I am right on the money. Would you believe me if I told you that if I had accessed the PreviousNode property of the element it would have been null since it is the first node in its parent's node list? It's true, but I'll leave you the task of proving it to yourself.

Backward with XNode.PreviousNode

If you want to traverse the XML tree backward, use the PreviousNode property. Since there is no previous node for the first participant node, I'll get tricky and access the NextNode property first, obtaining the second participant node, as I did in the previous example, from which I will obtain the PreviousNode. If you got lost in that, I will end up back to the first participant node. That is, I will go forward with NextNode to then go backward with PreviousNode, leaving me where I started. If you have ever heard the expression "taking one step forward and taking two steps back," with just one more access of the PreviousNode property, you could actually do that. LINQ makes it possible. Listing 7-43 is the example.

Example. Traversing Backward from an XElement Object via the PreviousNode Property
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

Console.WriteLine(firstParticipant.NextNode.PreviousNode);

If this works as I expect, I should have the first BookParticipant element's XML:

<BookParticipant type="Author">
  <FirstName>Joe</FirstName>
  <LastName>Rattz</LastName>
</BookParticipant>

LINQ to XML actually makes traversing an XML tree fun.

Up to Document with XObject.Document

Obtaining the XML document from an XElement object is as simple as accessing the Document property of the element. So please notice my change to the Console.WriteLine method call, shown in Listing 7-44.

Example. Accessing the XML Document from an XElement Object via the Document Property
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

Console.WriteLine(firstParticipant.Document);

This will output the document, which is the same output as Listing 7-41, and here is the output to prove it:

<!DOCTYPE BookParticipants SYSTEM "BookParticipants.dtd">
<?BookCataloger out-of-print?>
<BookParticipants>
  <BookParticipant type="Author">
    <FirstName>Joe</FirstName>
    <LastName>Rattz</LastName>
  </BookParticipant>
  <BookParticipant type="Editor">
    <FirstName>Ewan</FirstName>
    <LastName>Buckingham</LastName>
  </BookParticipant>
</BookParticipants>

Up with XObject.Parent

If you need to go up one level in the tree, it will probably be no surprise that the Parent property will do the job. Changing the node passed to the WriteLine method to what's shown in Listing 7-45 changes the output (as you will see).

Example. Traversing Up from an XElement Object via the Parent Property
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

Console.WriteLine(firstParticipant.Parent);

The output is changed to this:

<BookParticipants>
  <BookParticipant type="Author">
    <FirstName>Joe</FirstName>
    <LastName>Rattz</LastName>
  </BookParticipant>
  <BookParticipant type="Editor">
    <FirstName>Ewan</FirstName>
    <LastName>Buckingham</LastName>
  </BookParticipant>
</BookParticipants>

Don't let that fool you either. This is not the entire document. Notice it is missing the document type and processing instruction.

Traversal Methods

To demonstrate the traversal methods, since they return sequences of multiple nodes, I must now change that single Console.WriteLine method call to a foreach loop to output the potential multiple nodes. This will result in the former call to the Console.WriteLine method looking basically like this:

foreach(XNode node in firstParticipant.Nodes())
{
    Console.WriteLine(node);
}

From example to example, the only thing changing will be the method called on the firstParticipant node in the foreach statement.

Down with XContainer.Nodes()

No, I am not expressing my disdain for nodes. Nor am I stating I am all in favor of nodes, as in being "down for" rock climbing, meaning being excited about the prospect of going rock climbing. I am merely describing the direction of traversal I am about to discuss.

Traversing down an XML tree is easily accomplished with a call to the Nodes method. It will return a sequence of an object's child XNode objects. In case you snoozed through some of the earlier chapters, a sequence is an IEnumerable<T>, meaning an IEnumerable of some type T. Listing 7-46 is the example.

Example. Traversing Down from an XElement Object via the Nodes Method
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

foreach (XNode node in firstParticipant.Nodes())
{
  Console.WriteLine(node);
}

Here is the output:

<FirstName>Joe</FirstName>
<LastName>Rattz</LastName>

Don't forget, that method is returning all child nodes, not just elements. So any other nodes in the first participant's list of child nodes will be included. This could include comments (XComment), text (XText), processing instructions (XProcessingInstruction), document type (XDocumentType), or elements (XElement). Also notice that it does not include the attribute because an attribute is not a node.

To provide a better example of the Nodes method, let's look at the code in Listing 7-47. It is similar to the base example with some extra nodes thrown in.

Example. Traversing Down from an XElement Object via the Nodes Method with Additional Node Types
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XComment("This is a new author."),
      new XProcessingInstruction("AuthorHandler", "new"),
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

foreach (XNode node in firstParticipant.Nodes())
{
  Console.WriteLine(node);
}

This example is different than the previous in that there is now a comment and processing instruction added to the first BookParticipant element. Pressing Ctrl+F5 displays the following:

<!--This is a new author.-->
<?AuthorHandler new?>
<FirstName>Joe</FirstName>
<LastName>Rattz</LastName>

We can now see the comment and the processing instruction. What if you only want a certain type of node, though, such as just the elements? Do you recall from Chapter 4 the OfType operator? I can use that operator to return only the nodes that are of a specific type, such as XElement. Using the same basic code as Listing 7-47, to return just the elements, I will merely change the foreach line, as shown in Listing 7-48.

Example. Using the OfType Operator to Return Just the Elements
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XComment("This is a new author."),
      new XProcessingInstruction("AuthorHandler", "new"),
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

foreach (XNode node in firstParticipant.Nodes().OfType<XElement>())
{
  Console.WriteLine(node);
}

As you can see, the XComment and XProcessingInstruction objects are still being created. But since I am now calling the OfType operator, the code produces these results:

<FirstName>Joe</FirstName>
<LastName>Rattz</LastName>

Are you starting to see how cleverly all the new C# language features and LINQ are coming together? Isn't it cool that we can use that Standard Query Operator to restrict the sequence of XML nodes this way? So if you want to get just the comments from the first BookParticipant element, could you use the OfType operator to do so? Of course you could, and the code would look like Listing 7-49.

Example. Using the OfType Operator to Return Just the Comments
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XComment("This is a new author."),
      new XProcessingInstruction("AuthorHandler", "new"),
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

foreach (XNode node in firstParticipant.Nodes().OfType<XComment>())
{
  Console.WriteLine(node);
}

Here is the output:

<!--This is a new author.-->

Just to be anticlimactic, can you use the OfType operator to get just the attributes? No, you cannot. This is a trick question. Remember that unlike the W3C XML DOM API, with the LINQ to XML API, attributes are not nodes in the XML tree. They are a sequence of name-value pairs hanging off the element. To get to the attributes of the first BookParticipant node, I would change the code to that in Listing 7-50.

Example. Accessing an Element's Attributes Using the Attributes Method
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XComment("This is a new author."),
      new XProcessingInstruction("AuthorHandler", "new"),
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

foreach (XAttribute attr in firstParticipant.Attributes())
{
  Console.WriteLine(attr);
}

Notice I had to change more than just the property or method of the first BookParticipant element that I was accessing. I also had to change the enumeration variable type to XAttribute, because XAttribute doesn't inherit from XNode. Here are the results:

type="Author"

Down with XContainer.Elements()

Because the LINQ to XML API is so focused on elements, and that is what we are working with most, Microsoft provides a quick way to get just the elements of an element's child nodes using the Elements method. It is the equivalent of calling the OfType<XElement> method on the sequence returned by the Nodes method.

Listing 7-51 is an example that is logically the same as Listing 7-48.

Example. Accessing an Element's Child Elements Using the Elements Method
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XComment("This is a new author."),
      new XProcessingInstruction("AuthorHandler", "new"),
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

foreach (XNode node in firstParticipant.Elements())
{
  Console.WriteLine(node);
}

This code produces the exact same results as Listing 7-48:

<FirstName>Joe</FirstName>
<LastName>Rattz</LastName>

The Elements method also has an overloaded version that allows you to pass the name of the element you are looking for, as in Listing 7-52.

Example. Accessing Named Child Elements Using the Elements Method
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XComment("This is a new author."),
      new XProcessingInstruction("AuthorHandler", "new"),
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

foreach (XNode node in firstParticipant.Elements("FirstName"))
{
  Console.WriteLine(node);
}

This code produces the following:

<FirstName>Joe</FirstName>

Down with XContainer.Element()

You may obtain the first child element matching a specified name using the Element method. Instead of a sequence being returned requiring a foreach loop, I will have a single element returned, as shown in Listing 7-53.

Example. Accessing the First Child Element with a Specified Name
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XComment("This is a new author."),
      new XProcessingInstruction("AuthorHandler", "new"),
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

Console.WriteLine(firstParticipant.Element("FirstName"));

This code outputs the following:

<FirstName>Joe</FirstName>

Up Recursively with XNode.Ancestors()

While you can obtain the single parent element using a node's Parent property, you can get a sequence of the ancestor elements using the Ancestors method. This is different in that it recursively traverses up the XML tree instead of stopping one level up, and it only returns elements, as opposed to nodes.

To make this demonstration more clear, I will add some child nodes to the first book participant's FirstName element. Also, instead of enumerating through the ancestors of the first BookParticipant element, I use the Element method to reach down two levels to the newly added NickName element. This provides more ancestors to provide greater clarity. The code is shown in Listing 7-54.

Example. Traversing Up from an XElement Object via the Ancestors Method
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XComment("This is a new author."),
      new XProcessingInstruction("AuthorHandler", "new"),
      new XAttribute("type", "Author"),
      new XElement("FirstName",
        new XText("Joe"),
        new XElement("NickName", "Joey")),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));
foreach (XElement element in firstParticipant.
							  Element("FirstName").Element("NickName").Ancestors())
{
  Console.WriteLine(element.Name);
}

Again, please notice I add some child nodes to the first book participant's FirstName element. This causes the first book participant's FirstName element to have contents that include an XText object equal to the string "Joe", and to have a child element, NickName. I retrieve the first book participant's FirstName element's NickName element for which to retrieve the ancestors. In addition, notice I used an XElement type variable instead of an XNode type for enumerating through the sequence returned from the Ancestors method. This is so I can access the Name property of the element. Instead of displaying the element's XML as I have done in past examples, I am only displaying the name of each element in the ancestor's sequence. I do this because it would be confusing to display each ancestor's XML, because each would include the previous and it would get very recursive, thereby obscuring the results. That all said, here they are:

FirstName
BookParticipant
BookParticipants

Just as expected, the code recursively traverses up the XML tree.

Up Recursively with XElement.AncestorsAndSelf()

This method works just like the Ancestors method, except it includes itself in the returned sequence of ancestors. Listing 7-55 is the same example as before, except it calls the AncestorsAndSelf method.

Example. Traversing Up from an XElement Object via the AncestorsAndSelf Method
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XComment("This is a new author."),
      new XProcessingInstruction("AuthorHandler", "new"),
      new XAttribute("type", "Author"),
      new XElement("FirstName",
        new XText("Joe"),
        new XElement("NickName", "Joey")),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

foreach (XElement element in firstParticipant.
							  Element("FirstName").Element("NickName").AncestorsAndSelf())
{
  Console.WriteLine(element.Name);
}

The results should be the same as when calling the Ancestors method, except I should also see the NickName element's name at the beginning of the output:

NickName
FirstName
BookParticipant
BookParticipants

Down Recursively with XContainer.Descendants()

In addition to recursively traversing up, you can recursively traverse down with the Descendants method. Again, this method only returns elements. There is an equivalent method named DescendantNodes that will return all descendant nodes. Listing 7-56 is the same code as the previous, except I call the Descendants method on the first book participant element.

Example. Traversing Down from an XElement Object via the Descendants Method
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XComment("This is a new author."),
      new XProcessingInstruction("AuthorHandler", "new"),
      new XAttribute("type", "Author"),
      new XElement("FirstName",
        new XText("Joe"),
        new XElement("NickName", "Joey")),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

foreach (XElement element in firstParticipant.Descendants())
{
  Console.WriteLine(element.Name);
}

The results are the following:

FirstName
NickName
LastName

As you can see, it traverses all the way to the end of every branch in the XML tree.

Down Recursively with XElement.DescendantsAndSelf()

Just as the Ancestors method has an AncestorsAndSelf method variation, so too does the Descendants method. The DescendantsAndSelf method works just like the Descendants method, except it also includes the element itself in the returned sequence. Listing 7-57 is the same example that I used for the Descendants method call, with the exception that now it calls the DescendantsAndSelf method.

Example. Traversing Down from an XElement Object via the DescendantsAndSelf Method
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants", firstParticipant =
    new XElement("BookParticipant",
      new XComment("This is a new author."),
      new XProcessingInstruction("AuthorHandler", "new"),
      new XAttribute("type", "Author"),
      new XElement("FirstName",
        new XText("Joe"),
        new XElement("NickName", "Joey")),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham"))));

foreach (XElement element in firstParticipant.DescendantsAndSelf())
{
  Console.WriteLine(element.Name);
}

So does the output also include the firstParticipant element's name?

BookParticipant
FirstName
NickName
LastName

Of course it does.

Forward with XNode.NodesAfterSelf()

For this example, in addition to changing the foreach call, I add a couple of comments to the BookParticipants element to make the distinction between retrieving nodes and elements more evident, since XComment is a node but not an element. Listing 7-58 is what the code looks like for this example.

Example. Traversing Forward from the Current Node Using the NodesAfterSelf Method
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants",
    new XComment("Begin Of List"), firstParticipant =
    new XElement("BookParticipant",
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham")),
    new XComment("End Of List")));

foreach (XNode node in firstParticipant.NodesAfterSelf())
{
  Console.WriteLine(node);
}

Notice that I added two comments that are siblings of the two BookParticipant elements. This modification to the constructed XML document will be made for the NodesAfterSelf, ElementsAfterSelf, NodesBeforeSelf, and ElementsBeforeSelf examples.

This causes all sibling nodes after the first BookParticipant node to be enumerated. Here are the results:

<BookParticipant type="Editor">
  <FirstName>Ewan</FirstName>
  <LastName>Buckingham</LastName>
</BookParticipant>
<!--End Of List-->

As you can see, the last comment is included in the output because it is a node. Don't let that output fool you. The NodesAfterSelf method only returns two nodes: the BookParticipant element whose type attribute is Editor and the End Of List comment. Those other nodes, FirstName and LastName are merely displayed because the ToString method is being called on the BookParticipant node.

Keep in mind that this method returns nodes, not just elements. If you want to limit the type of nodes returned, you could use the TypeOf operator as I have demonstrated in previous examples. But if the type you are interested in is elements, there is a method just for that called ElementsAfterSelf.

Forward with XNode.ElementsAfterSelf()

This example uses the same modifications to the XML document made in Listing 7-58 concerning the addition of two comments.

To get a sequence of just the sibling elements after the referenced node, you call the ElementsAfterSelf method, as shown in Listing 7-59.

Example. Traversing Forward from the Current Node Using the ElementsAfterSelf Method
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants",
    new XComment("Begin Of List"), firstParticipant =
    new XElement("BookParticipant",
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham")),
    new XComment("End Of List")));

foreach (XNode node in firstParticipant.ElementsAfterSelf())
{
  Console.WriteLine(node);
}

The example code with these modifications produces the following results:

<BookParticipant type="Editor">
  <FirstName>Ewan</FirstName>
  <LastName>Buckingham</LastName>
</BookParticipant>

Notice that the comment is excluded this time because it is not an element. Again, the FirstName and LastName elements are only displayed because they are the content of the BookParticipant element that was retrieved and because the ToString method was called on the element.

Backward with XNode.NodesBeforeSelf()

This example uses the same modifications to the XML document made in Listing 7-58 concerning the addition of two comments.

This method works just like NodesAfterSelf except it retrieves the sibling nodes before the referenced node. In the example code, since the initial reference into the document is the first BookParticipant node, I obtain a reference to the second BookParticipant node using the NextNode property of the first BookParticipant node so that there are more nodes to return, as shown in Listing 7-60.

Example. Traversing Backward from the Current Node
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants",
    new XComment("Begin Of List"), firstParticipant =
    new XElement("BookParticipant",
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham")),
    new XComment("End Of List")));

foreach (XNode node in firstParticipant.NextNode.NodesBeforeSelf())
{
  Console.WriteLine(node);
}

This modification should result in the return of the first BookParticipant node and the first comment. Here are the results:

<!--Begin Of List-->
<BookParticipant type="Author">
  <FirstName>Joe</FirstName>
  <LastName>Rattz</LastName>
</BookParticipant>

Interesting! I was expecting the two nodes that were returned, the comment and the first BookParticipant, to be in the reverse order. I expected the method to start with the referenced node and build a sequence via the PreviousNode property. Perhaps it did indeed do this but then called the Reverse or InDocumentOrder operator. I cover the InDocumentOrder operator in the next chapter. Again, don't let the FirstName and LastName nodes confuse you. The NodesBeforeSelf method did not return those. It is only because the ToString method was called on the first BookParticipant node, by the Console.WriteLine method, that they are displayed.

Backward with XNode.ElementsBeforeSelf()

This example uses the same modifications to the XML document made in Listing 7-58 concerning the addition of two comments.

Just like the NodesAfterSelf method has a companion method named ElementsAfterSelf to return only the elements, so too does the NodesBeforeSelf method. The ElementsBeforeSelf method returns only the sibling elements before the referenced node, as shown in Listing 7-61.

Example. Traversing Backward from the Current Node
XElement firstParticipant;

//  A full document with all the bells and whistles.
XDocument xDocument = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"),
  new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null),
  new XProcessingInstruction("BookCataloger", "out-of-print"),
  //  Notice on the next line that I am saving off a reference to the first
  //  BookParticipant element.
  new XElement("BookParticipants",
    new XComment("Begin Of List"), firstParticipant =
    new XElement("BookParticipant",
      new XAttribute("type", "Author"),
      new XElement("FirstName", "Joe"),
      new XElement("LastName", "Rattz")),
    new XElement("BookParticipant",
      new XAttribute("type", "Editor"),
      new XElement("FirstName", "Ewan"),
      new XElement("LastName", "Buckingham")),
    new XComment("End Of List")));

foreach (XNode node in firstParticipant.NextNode.ElementsBeforeSelf())
{
  Console.WriteLine(node);
}

Notice that again I obtain a reference to the second BookParticipant node via the NextNode property. Will the output contain the comment?

<BookParticipant type="Author">
  <FirstName>Joe</FirstName>
  <LastName>Rattz</LastName>
</BookParticipant>

Of course not, because it is not an element.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset