Navigating with XPathNavigator

Wouldn't it be nice to have easy sequential access through an XML file and the concept of a current location like you have with XmlTextReader discussed previously, but without the restriction of forward-only access? You do. It's called the XPathNavigator class.

If you were comfortable with the XmlTextReader class, then you should have no trouble adapting to the XPathNavigator class, as many of its properties and methods are very similar. Also, if you were comfortable with XmlDocument, you should have few problems with XPathNavigator because you will find a lot of overlap between them. The following are some of the more common XPathNavigator properties:

  • HasAttributes is a Boolean that is true if the current node has attributes; otherwise, it is false.

  • HasChildren is a Boolean that is true if the current node has children; otherwise, it is false.

  • IsEmptyElement is a Boolean that is true if the current node is an empty element or, in other words, the element ends in />.

  • LocalName is a String representing the name of the current node without the namespace prefix.

  • Name is a String representing the qualified name of the current node.

  • NodeType is an XmlNodeType enum that represents the node type (see Table 13-2) of the current node.

  • Value is a String representing the value of the current node.

Here are some of the more commonly used XPathNavigator class methods:

  • ComparePosition() compares the position of the current navigator with another specified navigator.

  • Compile() compiles an XPath String into an XPathExpression.

  • Evaluate() evaluates an XPath expression.

  • GetAttribute() gets the attribute with the specified LocalName.

  • IsDescendant() determines if the specified XPathNavigator is a descendant of the current XPathNavigator.

  • IsSamePosition() determines if the current and a specified XPathNavigator share the same position.

  • Matches() determines if the current node matches a specified expression.

  • MoveTo() moves to the position of a specified XPathNavigator.

  • MoveToAttribute() moves to the attribute that matches a specified LocalName.

  • MoveToFirst() moves to the first sibling of the current node.

  • MoveToFirstAttribute() moves to the first attribute of the current node.

  • MoveToFirstChild() moves to the first child of the current node.

  • MoveToId() moves to the node that has a specified String ID attribute.

  • MoveToNext() moves to the next sibling of the current node.

  • MoveToNextAttribute() moves to the next attribute of the current node.

  • MoveToParent() moves to the parent of the current node.

  • MoveToPrevious() moves to the previous sibling of the current node.

  • MoveToRoot() moves to the root node of the current node.

  • Select() selects a collection of nodes that match an XPath expression.

  • SelectAncestor() selects a collection of ancestor nodes that match an XPath expression.

  • SelectChildren() selects a collection of children nodes that match an XPath expression.

  • SelectDescendants() selects a collection of descendant nodes that match an XPath expression.

As you can see by the list of methods made available by the XPathNavigator, it does what its name suggests: navigates. The majority of the methods are for navigating forward, backward, and, as you will see when you add XPath expressions, randomly through the DOM tree.

Basic XPathNavigator

Let's first look at the XPathNavigator class without the XPath functionality or simply its capability to move around a DOM tree. The example in Listing 13-12 is your third and final read through the monster XML file. This time you are going to use the XPathNavigator.

Listing 13-12: Navigating a DOM Tree Using XPathNavigator

start example
 using namespace System::Xml; using namespace System::Xml::XPath; String *indent(Int32 depth) {     String *ind = "";     return ind->PadLeft(depth*4, ' ');  } void BuildListBox() {     XmlDocument *doc = new XmlDocument();     try     {         doc->Load(S"Monsters.xml");         XPathNavigator *nav = doc->CreateNavigator();         nav->MoveToRoot();         Navigate(nav, 0);     }     catch (Exception *e)     {         MessageBox::Show(e->Message, S"Navigate Aborted");     } } void Navigate(XPathNavigator *nav, Int32 depth) {     Output->Items->Add(String::Format(         S"{0}: Name='{1}' Value='{2}'",         String::Concat(indent(depth),__box(nav->NodeType)->ToString()),         nav->Name, nav->Value));     if (nav->HasAttributes)     {         nav->MoveToFirstAttribute();         do {             Output->Items->Add(String::Format(                 S"{0}Attribute: Name='{1}' Value='{2}'",                 indent(depth+1),nav->Name, nav->Value));         }         while(nav->MoveToNextAttribute());         nav->MoveToParent();     }     if (nav->MoveToFirstChild())     {         Navigate(nav, depth+1);         nav->MoveToParent();     }     if (nav->MoveToNext())         Navigate(nav, depth); } 
end example

The first thing you have to remember when working with the XPathNavigator class is that you need to import the namespace System::Xml::XPath using the following command:

 using namespace System::Xml::XPath; 

I personally think of the XPathNavigator as a token that I move around that shows where I currently am in the DOM tree. In the preceding program I use only one XPathNavigator object pointer that gets passed around. This pointer eventually passes by every node of the DOM tree.

You create an XPathNavigator from any class that inherits from the XmlNode class using the CreateNavigator() method:

 XPathNavigator *nav = doc->CreateNavigator(); 

At this point, your navigator is pointing to the location of the node that you created it from. To set it at the first element of the DOM tree, you need to call the navigator's MoveToRoot() method:


Using recursion still holds true for XPathNavigator navigation as it does for standard XmlDocument navigation. You will probably notice that it has many similarities to the XmlDocument reader example. The biggest difference, though, is that with an XPathNavigator you need to navigate back out of a child branch before you can enter a new branch. Therefore, you see the use of the MoveToParent() method used much more frequently.

Something that you have to get used to if you have been using XmlDocument and XmlNode navigation is that the move methods return Boolean success values. What this means is that to find out if you successfully moved to the next node, you need to check to see if the move method returned true. If the move method can't successfully move to the next node, then it returns false. The move ends up changing an internal pointer in the XPathNavigator. This is considerably different than navigating with XmlNodes, where the nodes return the value of the next node or null if they can't navigate as requested.

One other thing you'll probably notice is that the Value property returns a concatenation of all its child node Value properties, and not just its own Value. You might not think it helpful, but I'll show how you can use this feature as a shortcut in the next example.

Figure 13-8 shows the ListBox dump, created by ReadXPathNav.exe, of all the nodes and attributes that make up the monster DOM tree.

click to expand
Figure 13-8: A ListBox of all nodes of the XML monster file

XPathNavigator using XPath Expressions

Using any of the methods in the previous section to navigate an XML file or DOM tree is hardly trivial. If you're trying to get specific pieces of information out of your XML files, going through the trouble of writing all that code seems hardly worth the effort. If there wasn't a better way, I'm sure XML would lose its popularity. The better way is the XPath expression.

With XPath expressions, you can quickly grab one particular piece of information out of the DOM tree or a list of information. The two most common ways of implementing XPath expressions are via the XPathNavigator class's Select() method and the XmlNode class's SelectNodes() method.

The XPath expression syntax is quite large and beyond the scope of this book. If you want to look into the details of the XPath language, then I recommend you start with the documentation on XPath provided by the .NET Framework.

For now, let's make do with some simple examples that show the power of the XPath (almost wrote "Force" there—hmmm... I must have just seen Star Wars).

The first example is the most basic form of XPath. It looks very similar to how you would specify a path or a file. It is simply a list of nodes separated by the forward slash (/), which you want to match within the document. For example,


specifies that you want to get a list of all "Name" nodes that have a parent node of Monster and MonsterList. The starting forward slash specifies that MonsterList be at the root. Here is a method that will execute the preceding XPath expression:

 void GetMonsters(XPathNavigator *nav) {     XPathNodeIterator *list =         nav->Select(S"/MonsterList/Monster/Name");     Console::WriteLine(S"Monsters\n-----");     while (list->MoveNext())     {         XPathNavigator *n = list->Current;         Console::WriteLine(n->Value);     } //  The required code to do the same as above if no //  XPathNavigator concatenation occurred. /*     list = nav->Select(S"/MonsterList/Monster/Name");     Console::WriteLine(S"Monsters\n----");     while (list->MoveNext())     {         XPathNavigator *n = list->Current;         n->MoveToFirstChild();         Console::WriteLine(n->Value);     } */ } 

Figure 13-9 presents the output of the snippet.

click to expand
Figure 13-9: Output for the XPath expression MonsterList/Monster/Name

As promised earlier, this example shows how the concatenation of child values by the XPathNavigator can come in handy. Remember that the XmlText node is a child of the XmlElement node, so without the concatenation of the XPathNavigator class, the dumping of the values of the "Name" nodes will produce empty strings, because XmlElement nodes have no values.

That was simple enough. Let's look at something a little more complex. It is possible to specify that you don't care what the parents are by prefixing with a double forward slash (//). For example,


would get you all "Name" nodes in the document. Be careful, though: If you use the "Name" element start tag in different places, you will get them all.

Along the same lines, if you don't actually care what the parent is but you want only a node at specific depth, you would use the asterisk (*) to match any element. For example,


would get all the names with a grandparent of MonsterList, but it would matter who the parent was.

Conditional expressions are possible. You enclose conditionals in square brackets ([ ]). For example,


would result in all monsters that have "Name" node (which would be all of them, as Name is a mandatory element—but that is another story). It is possible to specify an exact value for the conditional node or specify what values it cannot be. For example,

 //Monster[Name = ''Goblin''] //Monster[Name != ''Succubus''] 

would result in the first expression grabbing the Monster node Goblin and the second expression grabbing every monster but the Succubus.

Here is a method that will execute a combination of the expressions you covered previously. Also notice that just to be different, the example uses the XmlNode class's SelectNodes() method. Because XmlNodes don't concatenate child values, you need to navigate to the child to get the desired value:

 void GetDragonsWeapons(XmlNode *node) {     XmlNodeList *list =         node->SelectNodes(S"//Monster[Name='Red Dragon']/Weapon");     Console::WriteLine(S"\nDragon's Weapons\n-------");     IEnumerator *en = list->GetEnumerator();     while (en->MoveNext())     {         XmlNode *n = dynamic_cast<XmlNode*>(en->Current);         Console::WriteLine(n->FirstChild->Value);     } } 

Figure 13-10 shows the output of the snippet.

click to expand
Figure 13-10: Output for the XPath expression //Monster[Name='Red Dragon']/Weapon

Let's expand on this expression just a little more. It is also possible to have conditionals with logical operators such as and, or, and not().

The following method shows the logical operator in practice. It also shows how to grab an attribute value out of the navigator:

 void GetGoblinSuccubusHitDice(XPathNavigator *nav) {     XPathNodeIterator *list =         nav->Select(S"//Monster[Name='Goblin' or Name='Succubus']/HitDice");     Console::WriteLine(S"\nGoblin & Succubus HD\n-----------");     while (list->MoveNext())     {         XPathNavigator *n = list->Current;         n->MoveToFirstAttribute();         Console::WriteLine(n->Value);     } } 

Figure 13-11 shows the output of the snippet.

click to expand
Figure 13-11: Output for the XPath expression //Monster[Name='Goblin' or Name= 'Succubus']/HitDice

To match attributes in an XPath expression, use the "at" sign (@) in front of the attribute's name. For example,

 void GetGoblinSuccubusHitDice(XPathNavigator *nav) {     XPathNodeIterator *list =       nav->Select(S"//Monster[Name='Goblin' or Name='Succubus']/HitDice/@Dice");     Console::WriteLine(S"\nGoblin & Succubus HD\n----------");     while (list->MoveNext())     {         XPathNavigator *n = list->Current;         Console::WriteLine(n->Value);    } } 

results in the same output as the previous example. Notice that you no longer have to move to the attribute before displaying it.

As a final example, the following snippet shows that you can make numeric comparisons. In this example, I grab all Weapon elements with a Number attribute of less than or equal to 1:

 void GetSingleAttackWeapons(XPathNavigator *nav) {     XPathNodeIterator *list =         nav->Select(S"//Weapon[@Number <= 1]");     Console::WriteLine(S"\nSingle Attack Weapons\n----------");     while (list->MoveNext())     {         XPathNavigator *n = list->Current;         Console::WriteLine(n->Value);     } } 

Figure 13-12 shows the output of the snippet.

click to expand
Figure 13-12: Output for the XPath expression //Weapon[@Number <= 1]

Managed C++ and. NET Development
Managed C++ and .NET Development: Visual Studio .NET 2003 Edition
ISBN: 1590590333
EAN: 2147483647
Year: 2005
Pages: 169 © 2008-2017.
If you may any questions please contact us: