Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1

.pdf
Скачиваний:
70
Добавлен:
16.08.2013
Размер:
24.18 Mб
Скачать

588 C H A P T E R 1 3 X M L

Updating a DOM Tree

The process of updating a DOM tree is as simple as finding the correct node and changing the appropriate values. Finally, after all of the changes are made, save the changes.

In Listing 13-10, you continue to recursively navigate the DOM tree of Listing 13-1, but this time you’re looking for a Goblin node that was mistakenly given a Dagger. The Goblin was supposed to have a Saber. The trick is that you can just globally change all Daggers to Sabers because the Succubus node also has a Dagger, so you have to verify that it is the Goblin node’s Dagger. There are many ways of doing this, and I can think of a couple (better ones) using flags, but the method in Listing 13-10 shows the implementation of the largest number of different methods to find a node (without being redundant).

Listing 13-10. Updating the Monster DOM Tree

using namespace System; using namespace System::Xml;

void Navigate(XmlNode ^node)

{

if (node == nullptr) return;

if (node->Value != nullptr && node->Value->Equals("Dagger"))

{

if (node->ParentNode->ParentNode["Name"]->FirstChild->Value-> Equals("Goblin"))

{

node->Value = "Saber"; node->ParentNode->Attributes["Damage"]->Value = "1d8";

}

}

Navigate(node->FirstChild); Navigate(node->NextSibling);

}

void main()

{

XmlDocument ^doc = gcnew XmlDocument();

try

{

doc->Load("Monsters.xml");

XmlNode ^root = doc->DocumentElement;

C H A P T E R 1 3 X M L

589

// Recursive navigation of the DOM tree Navigate(root);

doc->Save("New_Monsters.xml");

}

catch (Exception ^e)

{

Console::WriteLine("Error Occurred: {0}", e->Message );

}

}

The main method looks familiar enough. The primary difference is that you will write out the DOM tree when you are done to make sure the change actually occurred:

doc->Save("New_Monsters.xml");

The recursive function is pretty similar. Let’s look closely at the if statement that does the update. First, you make sure the node has a value, as not all nodes have one. Calling the Equals() method on a node that doesn’t have a value will cause an exception to be thrown:

if (node->Value != nullptr && node->Value->Equals("Dagger"))

So you now know that you have a node with a value of Dagger. How do you check to make sure it belongs to a Goblin node? You do this by checking the current node’s grandparent’s Name element for the value of Goblin:

if (node->ParentNode->ParentNode["Name"]->FirstChild->Value->Equals("Goblin"))

What I really want you to focus on in the preceding statement is ParentNode["Name"]. The default indexed property of a ParentNode contains a collection of its child elements. This collection can be either an indexed property (as previously) or an array property where it is passed the numeric index of the child: ParentNode[0].

To change the value of a node, you simply assign it a new value:

node->Value = "Saber";

The damage done by a Saber differs from a Dagger, so you need to change the Damage attribute of the Weapon node. Notice that it is the Weapon node, not the Saber node. The Saber node is an XmlText node. You need to navigate to the Saber node’s parent first and then to its attributes. Notice that Attributes also has a default indexed property.

node->ParentNode->Attributes["Damage"]->Value = "1d8";

Figure 13-6 shows the new copy of the XML monster file created by UpdateXMLDOM.exe in the Visual Studio 2005 editor.

590 C H A P T E R 1 3 X M L

Figure 13-6. The updated XML monster file

Writing XmlNodes in a DOM Tree

You can truly get a good understanding of how a DOM tree is stored in memory by building a few XmlNodes manually. The basic process is to create a node and then append all its children on it. Then for each of the children, append all their children, and so on.

The last example (see Listing 13-11) before you get to XPaths shows how to add a new monster (a Skeleton node) after the Goblin node.

Listing 13-11. Adding a New Monster to the DOM Tree

using namespace System; using namespace System::Xml;

XmlElement ^CreateMonster(XmlDocument ^doc)

{

XmlElement ^skeleton = doc->CreateElement("Monster");

// <Name>Skeleton</Name>

XmlElement ^name = doc->CreateElement("Name"); name->AppendChild(doc->CreateTextNode("Skeleton")); skeleton->AppendChild(name);

// <HitDice Dice="1/2 d12" Default="3" />

XmlElement ^hitdice = doc->CreateElement("HitDice"); XmlAttribute ^att = doc->CreateAttribute("Dice"); att->Value = "1/2 d12"; hitdice->Attributes->Append(att);

att = doc->CreateAttribute("Default"); att->Value = "3"; hitdice->Attributes->Append(att); skeleton->AppendChild(hitdice);

C H A P T E R 1 3 X M L

591

// <Weapon Number="2" Damage="1d3-1">Claw</Weapon> XmlElement ^weapon = doc->CreateElement("Weapon"); att = doc->CreateAttribute("Number");

att->Value = "2"; weapon->Attributes->Append(att);

att = doc->CreateAttribute("Damage"); att->Value = "1d3-1"; weapon->Attributes->Append(att);

weapon->AppendChild(doc->CreateTextNode("Claw")); skeleton->AppendChild(weapon);

return skeleton;

}

void main()

{

XmlDocument ^doc = gcnew XmlDocument();

try

{

doc->Load("Monsters.xml");

XmlNode ^root = doc->DocumentElement;

// Skip comment and goblin

XmlNode ^child = root->FirstChild->NextSibling;

// Insert new monster root->InsertAfter(CreateMonster(doc), child);

doc->Save("New_Monsters.xml");

}

catch (Exception ^e)

{

Console::WriteLine("Error Occurred: {0}", e->Message );

}

}

The method of inserting XmlNodes, though not difficult, needs a quick explanation. I first wondered why you needed to pass a pointer to the XmlNode that you are going to place on the new XmlNode before or after. Why not just call the Insert method for this node instead, like this:

childNode->InsertBefore(newNode); // wrong childNode->InsertAfter(newNode); // wrong

Then I realized that I am not actually inserting after the child node. Instead I am inserting into the parent node after or before the child node. Thus the correct syntax:

parentNode->InsertBefore(newNode, childNode); parentNode->InsertAfter(newNode, childNode);

or as in the previous code:

root->InsertAfter(CreateMonster(doc), child);

Like the writing methods of forward-only access, it seems a lot of effort is required to create such a simple XmlElement. You need to remember that the correct way to do this is without hardcoding, thus making it reusable.

592 C H A P T E R 1 3 X M L

The first issue with creating nodes dynamically is that you need access to the XmlDocument, as all the XmlNode creation methods are found in it. You have two choices: pass XmlDocument as a parameter as was done in this example, or make XmlDocument a private member variable that all classes can access.

Now that you have access to the creation methods, it is a simple matter to create the element:

XmlElement ^skeleton = doc->CreateElement("Monster");

Then you create and append any of its child elements:

XmlElement ^weapon = doc->CreateElement("Weapon"); skeleton->AppendChild(weapon);

Of course, to create these child elements, you need to create and append the child elements attribute(s) and body text (which might have to create grandchildren nodes, and so on):

XmlAttribute ^att = doc->CreateAttribute("Number"); att->Value = "2";

weapon->Attributes->Append(att);

att = doc->CreateAttribute("Damage"); att->Value = "1d3-1"; weapon->Attributes->Append(att);

weapon->AppendChild(doc->CreateTextNode("Claw"));

Figure 13-7 shows the resulting new copy of the XML monster file from WriteXMLDOM.exe with the new inserted monster in the Visual Studio 2005 editor.

Figure 13-7. The XML monster file with a new monster

C H A P T E R 1 3 X M L

593

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 XmlReader discussed previously, but without the restriction of forwardonly access? You do. It’s called the XPathNavigator class.

If you were comfortable with the XmlReader 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 class that represents the node type (see Table 13-2) of the current node.

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

ValueAs<data type> is a <data type> representing the value of the current node. Some examples are ValueAsBoolean and ValueAsInt32.

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 whether the specified XPathNavigator is a descendant of the current XPathNavigator.

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

Matches() determines whether 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.

MoveToChild() moves to the child node specified.

MoveToDescendant() moves to the descendant node specified.

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.

594C H A P T E R 1 3 X M L

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.

ValueAs() returns the current node value as the type specified.

As you can see by the list of methods made available by 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 XPathNavigator.

Listing 13-12. Navigating a DOM Tree Using XPathNavigator

using namespace System; using namespace System::Xml;

using namespace System::Xml::XPath;

String ^indent(int depth)

{

String ^ind = "";

return ind->PadLeft(depth*4, ' ');

}

void Navigate(XPathNavigator ^nav, int depth)

{

Console::WriteLine("{0}: Name='{1}' Value='{2}'", String::Concat(indent(depth), nav->NodeType.ToString()), nav->Name, nav->Value);

if (nav->HasAttributes)

{

nav->MoveToFirstAttribute(); do {

Console::WriteLine("{0} Attribute: Name='{1}' Value='{2}'", indent(depth+1),nav->Name, nav->Value);

}

while(nav->MoveToNextAttribute()); nav->MoveToParent();

}

C H A P T E R 1 3 X M L

595

if (nav->MoveToFirstChild())

{

Navigate(nav, depth+1); nav->MoveToParent();

}

if (nav->MoveToNext()) Navigate(nav, depth);

}

void main()

{

XmlDocument ^doc = gcnew XmlDocument(); try

{

doc->Load("Monsters.xml");

XPathNavigator ^nav = doc->CreateNavigator(); nav->MoveToRoot();

Navigate(nav, 0);

}

catch (Exception ^e)

{

Console::WriteLine("Error Occurred: {0}", e->Message);

}

}

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:

nav->MoveToRoot();

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 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. In other words, to find out if you successfully moved to the next node, you need to check whether 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.

596 C H A P T E R 1 3 X M L

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 console dump, created by ReadXPathNav.exe, of all the nodes and attributes that make up the monster DOM tree.

Figure 13-8. A console list 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 hardly seems 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).

C H A P T E R 1 3 X M L

597

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,

/MonsterList/Monster/Name

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("/MonsterList/Monster/Name");

Console::WriteLine("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("/MonsterList/Monster/Name");

Console::WriteLine("Monsters\n--------"); while (list->MoveNext())

{

XPathNavigator ^n = list->Current; n->MoveToFirstChild(); Console::WriteLine(n->Value);

}

*/

}

Figure 13-9 presents the output of the snippet.

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.