Using Xpathnavigators


An XPathNavigator is used to select, iterate, and sometimes edit data from an xml document. An XPathNavigator can be created from an XmlDocument to allow editing capabilities or from an XPathDocument for read-only use. Since the XPathDocument is read-only it performs very well. Unlike the XmlReader, the XPathNavigator isn't a streaming model, so the same document can be used without having to re-read and parse.

The XPathNavigaor is part of the System.Xml.XPath namespace. XPath is a query language used to select specific nodes or elements from an xml document for processing.

The System.Xml.XPath Namespace

The System.Xml.XPath namespace is built for speed. It provides a read-only view of your XML documents, so there are no editing capabilities. Classes in this namespace are built to do fast iteration and selections on the XML document in a cursor fashion.

The following table lists the key classes in System.Xml.XPath and gives a short description of the purpose of each class.

Class Name

Description

XPathDocument

Provides a view of the entire XML document. Read-only.

XPathNavigator

Provides the navigation capabilities to an XPathDocument.

XPathNodeIterator

Provides iteration capabilities to a node set.

XPathExpression

Represents a compiled XPath expression. Used by SelectNodes, SelectSingleNodes, Evaluate, and Matches.

XPathException

An XPath exception class.

XPathDocument

XPathDocument doesn't offer any of the functionality of the XmlDocument class. Its sole purpose is to create XPathNavigators. As a matter of fact, that is the only method available on the XPathDocument class (other then those provided by Object).

An XPathDocument can be created a number of different ways. You can pass in an XmlReader, a file name of an xml document or a Stream based object ti the constructor. This allows a great deal of flexibility. For example, you could use the XmlValidatingReader to validate the xml and the use that same object to create the XPathDocument.

XPathNavigator

XPathNavigator contains all of the methods for moving and selecting elements that you need. The following table lists some of the "move" methods defined in this class.

Method Name

Description

MoveTo()

Takes XPathNavigator as a parameter. Moves the current posi- tion to be the same as that passed in to XPathNavigator.

MoveToAttribute()

Moves to the named attribute. Takes the attribute name and namespace as parameters.

MoveToFirstAttribute()

Moves to the first attribute in the current element. Returns true if successful.

MoveToNextAttribute()

Moves to the next attribute in the current element. Returns true if successful.

MoveToFirst()

Moves to the first sibling in the current node. Returns true if successful; otherwise it returns false.

MoveToLast()

Moves to the last sibling in the current node. Returns true if successful.

MoveToNext()

Moves to the next sibling in the current node. Returns true if successful.

MoveToPrevious()

Moves to the previous sibling in the current node. Returns true if successful.

MoveToFirstChild()

Moves to the first child of the current element. Returns true if successful.

MoveToId()

Moves to the element with the ID supplied as a parameter. There needs to be a schema for the document, and the data type for the element must be of type ID.

MoveToParent()

Moves to the parent of the current node. Returns true if successful.

MoveToRoot()

Moves to the root node of the document.

In order to select a subset of the document you can use one of the Select methods:

Method Name

Description

Select()

Select a node set using an XPath expression.

SelectAncestors`()

Selects all of the ancestors of the current node based on an XPath expression.

SelectChildren()

Select all of the children of the current node based on an XPath expression.

SelectDescendants()

Select all of the descendants of the current node based on an XPath expression.

SelectSingleNode()

Select one node based on an XPath expression.

If the XPathNavigator was created from an XPathDocument, it is read-only. If it is created from an XmlDocument, the XPathNavigator can be used to edit the document. This can be verified by checking the CanEdit property. If true, you can use one of the Insert methods. InsertBefore and InsertAfter will create a new node either before or after the current node. The source of the new node can be from an XmlReader or a string. Optionally an XmlWriter can be returned and used to write the new node information.

Strongly typed values can be read form the nods using the ValueAs properties. Notice that this is different from XmReader, which used ReadValue methods.

XPathNodeIterator

XPathNodeIterator can be thought of as the equivalent of a NodeList or a NodeSet in XPath. This object has three properties and two methods:

  • Clone — Creates a new copy of itself

  • Count — Number of nodes in the XPathNodeIterator object

  • Current — Returns an XPathNavigator pointing to the current node

  • CurrentPosition() — Returns an integer with the current position

  • MoveNext() — Moves to the next node that matches the XPath expression that created the XPathNodeIterator

The XPathNodeIterator is returned by the XPathNavigators select methods. You use it to iterate over the set of nodes returned by a Select method of the XPathNavigator. Using the MoveNext method of the XPathNodeIterator does not change the location of the XPathNavigator that created it.

Using classes from the XPath namespace

The best way to see how these classes are used is to look at some code that iterates through the books.xml document. This will allow you to see how the navigation works. In order to use the examples, you first add a reference to the System.Xml.Xsl and System.Xml.XPath namespaces:

 using System.Xml.XPath;  using System.Xml.Xsl; 

For this example, you are using the file booksxpath.xml. It is similar to the books.xml that you have been using, except there are a couple of extra books added. Here's the form code, which is part of the XmlSample project:

 private void button1_Click(object sender, EventArgs e) { //modify to match your path structure XPathDocument doc = new XPathDocument("books.xml"); //create the XPath navigator XPathNavigator nav = ((IXPathNavigable)doc).CreateNavigator(); //create the XPathNodeIterator of book nodes // that have genre attribute value of novel XPathNodeIterator iter = nav.Select("/bookstore/book[@genre='novel']"); while (iter.MoveNext()) { XPathNodeIterator newIter = iter.Current.SelectDescendants(XPathNodeType.Element, false); while(newIter.MoveNext()) listView1.Items.Add(newIter.Current.Name + ": " + newIter.Current.Value); } } 

The first thing you do in the button1_Click() method is to create the XPathDocument (called doc), passing in the file and path string of the document you want opened. The next line is where the XPathNavigator is created:

XPathNavigator nav = doc.CreateNavigator();

In the example, you can see that you use the Select() method to retrieve a set of nodes that all have novel as the value of the genre attribute. You then use the MoveNext() method to iterate through all of the novels in the book list.

To load the data into the list box, you use the XPathNodeIterator.Current property. This creates a new XPathNavigator object based on just the node that the XPathNodeIterator is pointing to. In this case, you are creating an XPathNavigator for one book node in the document.

The next loop takes this XPathNavigator and creates another XPathNodeIterator by issuing another type of select method, the SelectDescendants() method. This gives you an XPathNodeIterator of all of the child nodes and children of the child nodes of the book node.

Then you do another MoveNext() loop on the XPathNodeIterator and load the list box with the element names and element values.

Figure 21-3 shows what the screen looks like after running the code. Note that novels are the only books listed now.

image from book
Figure 21-3

What if you wanted to add up the cost of these books? XPathNavigator includes the Evaluate() method for just this reason. Evaluate() has three overloads. The first one contains a string that is the XPath function call. The second overload uses the XPathExpression object as a parameter, and the third uses XPathExpression and an XPathNodeIterator as parameters. The following code is similar to the previous example, except this time all of the nodes in the document are iterated. The Evaluate at the end totals up the cost of all of the books:

 private void button2_Click(object sender, EventArgs e) { //modify to match your path structure XPathDocument doc = new XPathDocument("books.xml"); //create the XPath navigator XPathNavigator nav = ((IXPathNavigable)doc).CreateNavigator(); //create the XPathNodeIterator of book nodes // that have genre attribute value of novel XPathNodeIterator iter = nav.Select("/bookstore/book"); while (iter.MoveNext()) { XPathNodeIterator newIter = iter.Current.SelectDescendants(XPathNodeType.Element, false); while (newIter.MoveNext()) listView1.Items.Add(newIter.Current.Name + ": " + newIter.Current.Value); } listView1.Items.Add("========================="); listView1.Items.Add("Total Cost = " + nav.Evaluate("sum(/bookstore/book/price)")); } 

This time, you see the total cost of the books evaluated in the list box (see Figure 21-4).

image from book
Figure 21-4

Now let's say that you need to add a node for discount. Using the InsertAfter method can get this done fairly easily. Here is the code:

 private void button3_Click(object sender, EventArgs e) { XmlDocument doc = new XmlDocument(); doc.Load("books.xml"); XPathNavigator nav = doc.CreateNavigator(); if (nav.CanEdit) { XPathNodeIterator iter = nav.Select("/bookstore/book/price"); while (iter.MoveNext()) { iter.Current.InsertAfter("<disc>5</disc>"); } } doc.Save("newbooks.xml"); } 

Here we add the <disc>5</disc> element after the price elements. First all of the price nodes are selected. The XPathNodeIterator is used to iterate over the nodes and the new node is inserted. The modified document is saved with a new name, newbooks.xml. Here is what the new version looks like:

 <?xml version="1.0"?> <!-- This file represents a fragment of a book store inventory database --> <bookstore> <book genre="autobiography" publicationdate="1991" ISBN="1-861003-11-0"> <title>The Autobiography of Benjamin Franklin</title> <author> <first-name>Benjamin</first-name> <last-name>Franklin</last-name> </author> <price>8.99</price> <disc>5</disc> </book> <book genre="novel" publicationdate="1967" ISBN="0-201-63361-2"> <title>The Confidence Man</title> <author> <first-name>Herman</first-name> <last-name>Melville</last-name> </author> <price>11.99</price> <disc>5</disc> </book> <book genre="philosophy" publicationdate="1991" ISBN="1-861001-57-6"> <title>The Gorgias</title> <author> <name>Plato</name> </author> <price>9.99</price> <disc>5</disc> </book> </bookstore> 

Nodes can be inserted before or after a selected node. The nodes can also be changed and they can be deleted. If you have changes that have to be done to large numbers of nodes, using the XPathNavigator created from an XmlDocument may be your best choice.

The System.Xml.Xsl Namespace

The System.Xml.Xsl namespace contains the classes that the .NET Framework uses to support XSL Transforms. The contents of this namespace are available to any store whose classes implement the IXPathNavigable interface. In the .NET Framework that would currently include XmlDocument, XmlDataDocument, and XPathDocument. Again, just as with XPath, use the store that makes the most sense. If you plan to create a custom store, such as one using the file system and you want to be able to do transforms, be sure to implement the IXPathNavigable interface in your class.

XSLT is based on a streaming pull model. Because of this, you can chain several transforms together. You could even apply a custom reader between transforms if needed. This allows a great deal of flexibility in design.

Transforming XML

The first example you look at takes the books.xml document and transforms it into a simple HTML document for display using the XSLT file books.xsl. (This code is in the XPathXSLSample3 folder.) You will need to add the following using statements:

 using System.IO;  using System.Xml.Xsl;  using System.Xml.XPath; 

Here is the code to perform the transform:

 private void transformtoHTMLToolStripButton_Click(object sender, EventArgs e) { XslTransform trans = new XslTransform(); trans.Load("books.xsl"); trans.Transform("books.xml", "out.html"); webBrowser1.Url = AppDomain.CurrentDomain.BaseDirectory + "//out.html"; } 

A transform doesn't get any simpler than this. First a new XmlTransform object is created. It loads the books.xsl transform document and then performs the transform. In this example, a string with the file name is used as the input. The output is out.html. This file is then loaded into the web browser control used on the form. Instead of the file name books.xml as the input document, you can also use an IXPathNavigable based object. This would be any object that can create an XPathNavigator. An XPathNavigator can also be used as the source of the transform.

If this were an ASP.NET application, you would have used a TextWriter object and passed it to the HttpResponse object instead. If you were transforming to another XML document, you would have used an XmlWriter-based object.

After the XslTransform object is ready, you create the XPathNavigator on the XPathDocument, and pass the XPathNavigator and the FileStream into the Transform() method of the XslTransform object. Transform() has several overloads, passing in combinations of navigators, XsltArgumentList (more on this shortly), IO streams, and XmlResolvers. The navigator parameter can be XPathNavigator or anything that implements the IXPathNavigable interface. The IO streams can be a TextWriter, Stream, or XmlWriter-based object. The XmlResolver is used to manage the process of getting a resource from an external source. The XmlResolver handles the security, opening the data source and returning the data or stream. In the .Net Framework 1.0 the XmlResolver parameter was not a requirement. All of those versions of the Transform method have been deprecated and now the XmlResolver parameter is required; however, you can pass null if the features of an XmlResolver, namely security and credential management, are not needed.

The books.xsl document is a fairly straightforward style sheet. The document looks like this:

 <xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <html> <head> <title>Price List</title> </head> <body> <table> <xsl:apply-templates/> </table> </body> </html> </xsl:template> <xsl:template match="bookstore"> <xsl:apply-templates select="book"/> </xsl:template> <xsl:template match="book"> <tr><td> <xsl:value-of select="title"/> </td><td> <xsl:value-of select="price"/> </td></tr> </xsl:template> </xsl:stylesheet> 

Using XsltArgumentList

XsltArgumentList was mentioned earlier. This is a way that you can bind an object with methods to a namespace. Once this is done, you can invoke the methods during the transform. Here's an example:

 private void button3_Click(object sender, EventArgs e) { //new XPathDocument XPathDocument doc = new XPathDocument("books.xml"); //new XslTransform XslCompiledTransform trans = new XslCompiledTransform(); trans.Load("booksarg.xsl"); //new XmlTextWriter since we are creating a new xml document XmlWriter xw = new XmlTextWriter("argSample.xml", null); //create the XslArgumentList and new BookUtils object XsltArgumentList argBook = new XsltArgumentList(); BookUtils bu = new BookUtils(); //this tells the argumentlist about BookUtils argBook.AddExtensionObject("urn:XslSample", bu); //new XPathNavigator XPathNavigator nav = doc.CreateNavigator(); //do the transform trans.Transform(nav, argBook, xw); xw.Close(); webBrowser1.Navigate(AppDomain.CurrentDomain.BaseDirectory + "//argSample.xml"); } 

This is the code for the BooksItil class. This is the class that will be called from the transform.

 namespace XslSample { class BookUtils { public BookUtils() { } public string ShowText() { return "This came from the ShowText method!"; } } } 

This is what the output of the transform looks like; the output has been formatted for easier viewing (argSample.xml):

 <books> <discbook> <booktitle>The Autobiography of Benjamin Franklin</booktitle> <showtext>This came from the ShowText method!</showtext> </discbook> <discbook> <booktitle>The Confidence Man</booktitle> <showtext>This came from the ShowText method!</showtext> </discbook> <discbook> <booktitle>The Gorgias</booktitle> <showtext>This came from the ShowText method!</showtext> </discbook> <discbook> <booktitle>The Great Cookie Caper</booktitle> <showtext>This came from the ShowText method!</showtext> </discbook> <discbook> <booktitle>A Really Great Book</booktitle> <showtext>This came from the ShowText method!</showtext> </discbook> </books> 

In this example, you define a new class, BookUtils. In this class you have one rather useless method that returns the string "This came from the ShowText method!" In the button1_Click() event, you create the XPathDocument and XslTransform objects. Before we loaded the xml document and the transform document directly into the XslComiledTransform object. This time you will use the XPathNavigator to load the documents.

Next you need to do the following:

XsltArgumentList argBook=new XsltArgumentList();  BookUtils bu=new BookUtils(); argBook.AddExtensionObject("urn:XslSample",bu);

This is where you create the XsltArgumentList object. You create an instance of the BookUtils object, and when you call the AddExtensionObject() method, you pass in a namespace for your extension and the object that you want to be able to call methods from. When you make the Transform() call, you pass in the XsltArgumentList (argBook) along with the XPathNavigator and the XmlWriter object you made.

Here is the booksarg.xsl document (based on books.xsl):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:bookUtil="urn:XslSample">    <xsl:output method="xml" indent="yes"/>    <xsl:template match="/">       <xsl:element name="books">          <xsl:apply-templates/>       </xsl:element>      </xsl:template>    <xsl:template match="bookstore">       <xsl:apply-templates select="book"/>    </xsl:template>    <xsl:template match="book">       <xsl:element name="discbook">          <xsl:element name="booktitle">             <xsl:value-of select="title"/>          </xsl:element>          <xsl:element name="showtext"> <xsl:value-of select="bookUtil:ShowText()"/>          </xsl:element>       </xsl:element>       </xsl:template> </xsl:stylesheet>

The two important new lines are highlighted. First, you add the namespace that you created when you added the object to XsltArgumentList. Then when you want to make the method call, you use standard XSLT namespace prefixing syntax and make the method call.

Another way you could have accomplished this is with XSLT scripting. You can include C#, Visual Basic, and JavaScript code in the style sheet. The great thing about this is that unlike current non-.NET implementations, the script is compiled at the XslTransform.Load() call; this way, you are executing already compiled scripts.

Go ahead and modify the previous XSLT file in this way. First, you add the script to the style sheet. You can see the following changes in booksscript.xsl:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:user="http://wrox.com"> <msxsl:script language="C#" implements-prefix="user"> string ShowText() { return "This came from the ShowText method!"; } </msxsl:script>    <xsl:output method="xml" indent="yes"/>       <xsl:template match="/">    <xsl:element name="books">       <xsl:apply-templates/>    </xsl:element>         </xsl:template>    <xsl:template match="bookstore">       <xsl:apply-templates select="book"/>    </xsl:template>       <xsl:template match="book">       <xsl:element name="discbook">       <xsl:element name="booktitle"> <xsl:value-of select="title"/>       </xsl:element>       <xsl:element name="showtext">         <xsl:value-of select="user:ShowText()"/>       </xsl:element>     </xsl:element>       </xsl:template> </xsl:stylesheet>

Once again, the changes are highlighted. You set the scripting namespace, add the code (which was copied and pasted in from the Visual Studio .NET IDE), and make the call in the style sheet. The output looks the same as that of the previous example.

To summarize, the key thing to keep in mind when performing transforms is to remember to use the proper XML data store. Use XPathDocument if you don't need edit capabilities, XmlDataDocument if you're getting your data from ADO.NET, and XmlDocument if you need to be able to edit the data. In each case you are dealing with the same process.




Professional C# 2005
Pro Visual C++ 2005 for C# Developers
ISBN: 1590596080
EAN: 2147483647
Year: 2005
Pages: 351
Authors: Dean C. Wills

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net