Using XPath and XSLT in .NET

 
Chapter 11 - Manipulating XML
bySimon Robinsonet al.
Wrox Press 2002
  

In this section, we are going to look at support for XPath and XSL Transforms (XSLT) in the .NET Framework. XPath support is through the System.Xml.XPath namespace, and XSLT through the System.Xml.Xsl namespace. The reason that we are looking at them together is that the XPathNavigator class of the System.XPath namespace provides a very performance-oriented way of performing XSL Transforms in .NET.

XPath is the query language for XML. You would use XPath to select a subset of elements based on element text values or perhaps based on attribute values. XSLT is used to transform a base document into another document of different structure or type.

We will first look at System.XPath and then discuss how it is used to feed the System.Xml.Xsl classes.

The System.XPath Namespace

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

Here is a table that lists the key classes in System.XPath , and gives a short description of the purpose of each class:

Class Name

Description

XPathDocument

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. XPath equivalent to a nodeset in Xpath .

XPathExpression

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

XPathException

XPath exception class.

XPathDocument

XPathDocument doesn't offer any of the functionality of the XmlDocument class. If you need editing capabilities, then XmlDocument is the choice to go for; if you're utilizing ADO.NET, then XmlDataDocument (we will see this later in the chapter) is what you'll use. However, if speed is of concern, then use XPathDocument as your store. It has four overloads allowing you to open an XML document from a file and path string, a TextReader object, an XmlReader object, or a Stream -based object.

XPathNavigator

XPathNavigator contains all of the methods for moving and selecting elements that you need. Some of the "move" methods defined in this class are:

Method Name

Description

MoveTo()

Takes an XPathNavigator as a parameter. Move the current position to be the same as that passed in to XPathNavigator .

MoveToAttribute()

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

MoveToFirstAttribute()

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

MoveToNextAttribute()

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

MoveToFirst()

Move to the first sibling in the current node. Returns true if successful, otherwise returns false .

MoveToLast()

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

MoveToNext()

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

MoveToPrevious()

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

MoveToFirstChild()

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

MoveToId()

Move 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()

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

MoveToRoot()

Move to the root node of the document.

There several Select() methods for selecting a subset of nodes to work with. All of these Select() methods return an XPathNodeIterator object.

There are also SelectAncestors() and SelectChildren() methods that can be used. Both return an XPathNodeIterator . While Select() takes an XPath expression as a parameter, the other select methods take an XPathNodeType as a parameter.

You can extend XPathNavigator to use such things as the file system or Registry as the store instead of an XPathDocument .

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

Using Classes from the XPath Namespace

The best way to see how these classes are used is to look at some code. Let's load up the books.xml document and move around in it so that you can see how the navigation works. In order to use the examples, we 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 we are using the file booksxpath.xml . It is similar to the books.xml that we have been using previously, except there are a couple of extra books added. Here's the form code, which can be found in the XPathXSLSample1 folder:

   private void button1_Click(object sender, System.EventArgs e)     {     //modify to match your path structure     XPathDocument doc=new XPathDocument("..\..\..\booksxpath.xml");     //create the XPath navigator     XPathNavigator nav=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())     {     LoadBook(iter.Current);     }     }     private void LoadBook(XPathNavigator lstNav)     {     //We are passed an XPathNavigator of a particular book node     //we will select all of the descendents and     //load the list box with the names and values     XPathNodeIterator iterBook=lstNav.SelectDescendants     (XPathNodeType.Element,false);     while(iterBook.MoveNext())     listBox1.Items.Add(iterBook.Current.Name + ": "     + iterBook.Current.Value);     }   

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

 XPathNavigator nav = doc.CreateNavigator(); 

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

To load the data into the listbox, we use the XPathNodeIterator.Current property. This will create a new XPathNavigator object based on just the node that the XPathNodeIterator is pointing at. In this case, we are creating an XPathNavigator for one book node in the document.

The LoadBook() method takes this XPathNavigator and creates another XPathNodeIterator by issuing another type of select method, the SelectDescendants() method. This will give us an XPathNodeIterator of all of the child nodes and children of the child nodes of the book node that we passed to the LoadBook() method.

Then we do another MoveNext() loop on the XPathNodeIterator and load the listbox with the element names and element values.

This is what the screen looks like after running the code. Notice that novels are the only books listed now:

click to expand

What if we 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 changes are highlighted below (this version of the code can be found in XPathXSLSample2 ):

 private void button1_Click(object sender, System.EventArgs e)  {    //modify to match your path structure    XPathDocument doc = new XPathDocument("..\..\..\booksxpath.XML");    //create the XPath navigator    XPathNavigator nav = 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())     {       LoadBook(iter.Current.Clone());    }   //add a break line and calculate the sum     listBox1.Items.Add("========================");     listBox1.Items.Add("Total Cost = "     + nav.Evaluate("sum(/bookstore/book[@genre='novel']/price)"));   } 

This time, we see the total cost of the books evaluated in the listbox:

click to expand

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 we will 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 can be found in the XPathXSLSample3 folder.) We 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 button1_Click(object sender, System.EventArgs e)     {     //create the new XPathDocument     XPathDocument doc = new XPathDocument("..\..\..\booksxpath.xml");     //create a new XslTransForm     XslTransform transForm = new XslTransform();     transForm.Load("..\..\..\books.xsl");     //this FileStream will be our output     FileStream fs=new FileStream("..\..\..\booklist.html",     FileMode.Create);     //Create the navigator     XPathNavigator nav = doc.CreateNavigator();     //Do the transform. The output file is created here     transForm.Transform(nav, null, fs);     }   

This is about as simple a transform as can be. We create an XPathDocument -based object and an XslTransform -based object. We load the booksxpath.xml file into the XPathDocument , and books.xsl file into the XslTransform .

In this example, we also create a FileStream object to write the new HTML document to disk. If this were an ASP.NET application, we would have used a TextWriter object and passed it into the HttpResponse object instead. If we were transforming to another XML document we would have used an XmlWriter based object.

After the XPathDocument and XslTransform objects are ready, we 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 later), and IO streams. 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 books.xsl document is a fairly straightforward stylesheet. 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

Earlier we mentioned XsltArgumentList . 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. Let's look at an example to see how this works (found in XPathXSLSample4 ). Add the highlighted code to your sample code:

 private void button1_Click(object sender, System.EventArgs e) {    //new XPathDocument    XPathDocument doc=new XPathDocument("..\..\..\booksxpath.xml");    //new XslTransform    XslTransform transForm=new XslTransform();   transForm.Load("..\..\..\booksarg.xsl");     //new XmlTextWriter since we are creating a new XML document     XmlWriter xw=new XmlTextWriter("..\..\..\argSample.xml",null);     //create the XsltArgumentList and new BookUtils object     XsltArgumentList argBook=new XsltArgumentList();     BookUtils bu=new BookUtils();     //this tells the argumentlist about BookUtils     argBook.AddExtensionObject("urn:ProCSharp",bu);   //new XPathNavigator    XPathNavigator nav=doc.CreateNavigator();    //do the transform    transForm.Transform(nav,argBook,xw);    xw.Close(); }   //simple test class     public class BookUtils     {     public BookUtils(){}         public string ShowText()     {     return "This came from the ShowText method!";     }     }   

This is what the output of the transform looks like ( argSample.xml ):

   <?xml version="1.0"?>     <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, we define a new class, BookUtils . In this class we have one rather useless method that returns the string " This came from the ShowText method! ". In the button1_Click() event, we create the XPathDocument and XslTransform just as we did before, with a couple of exceptions. This time we are going to create an XML document, so we use the XmlWriter instead of the FileStream that we used before. The next change is here:

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

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

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

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   xmlns:bookUtil="urn:ProCSharp">   <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 we add the namespace that we created when we added the object to the XsltArgumentList . Then when we want to make the method call, we use standard XSLT namespace prefixing syntax and make the method call.

Another way we could have accomplished this is with XSLT scripting. You can include C#, VB, and JavaScript code in the stylesheet. 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, much the same way that ASP.NET works.

Let's modify the previous XSLT file in this way. First we add the script to the stylesheet. You can see these changes below 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. We 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 stylesheet. 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. The process is the same regardless.

  


Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

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