Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XSLT

XSL 2.0 and XQuery 1.0 in .NET

5.00/5 (12 votes)
18 Oct 2018Ms-PL4 min read 5   4.4K  
Using the open source Saxon library, .NET programmers can benefit from XSL 2.0 and XQuery 1.0.

Note: you must download both parts in order for the SaxonWrapper to work.

Introduction

This small project started when I wanted to use XSL 2.0 and XPath 2.0 in my .NET code. The problem is that the .NET framework only provides XSL 1.0 and XPath 1.0 out of the box. The only implementation of XSL 2.0 and XPath 2.0 that I am aware of is Saxon. In my experience, Saxon has not experienced wide spread adoption in the .NET community because of the considerable difference between Saxon interfaces and .NET XML interfaces. The aim of the project has been to write very simple interface adapters so that .NET programmers can use Saxon in the same way that they use System.Xml.

The reader should have a basic understanding of XML. The W3Schools website provides highly readable tutorials about XSL, XPath, and XQuery.

Motivation for Using Saxon

XPath 2.0 is richer and more flexible than XPath 1.0. In XPath 2.0, everything is a sequence rather than a "node set", and there is support for conditional logic and looping. For example, the following is a valid XPath 2.0 statement which returns a sequence:

XML
for $x in /order/item return $x/price * $x/quantity

XML.com has a very good article on XPath 2.0. The article was written in early 2002, which shows how long the technology has been around for.

XQuery 1.0 is an extension of XPath 2.0, and is a very capable query language. XQuery can be used to produce XML-based output in place of XSL. In fact, it is far easier to write XQuery than it is to write XSL. The main construct of XQuery is the FLOWR statement. FLOWR stands for "For, Let, Order by, Where, Return", and is similar to LINQ.

XML
<pricing>
for $x in doc("NorthWind.xml")/order/item
where $x/price>

XSL 2.0 uses XPath 2.0, whereas XSL 1.0 uses XPath 1.0. There are numerous benefits to using XSL 2.0 over XSL 1.0. In XSL 2.0, you can write a function in XSL and access the function from within XPath using the xsl:function element. This is an improvement from the previous situation where you write the XPath functions in .NET code and end up with platform specific XSL. Moreover, the development cycle is faster when the custom functions are written in XSL.

XSL 2.0 also features grouping using the xsl:for-each-group element. The following code from the XML.com article is a good example of this:

XML
<xsl:for-each-group select="cities/city" group-by="@country">
  <tr>
    <td><xsl:value-of select="@country"/></td>
    <td>
      <xsl:value-of select="current-group()/@name" separator=", "/>
    </td>
    <td><xsl:value-of select="sum(current-group()/@pop)"/></td>
  </tr>
</xsl:for-each-group>

Detailed information about XSL 2.0 can be found at XML.com.

Using the Code

As discussed, the API for Saxon is somewhat different to the standard XML API for .NET. This project addresses the issue using the adapter pattern. The classes Xsl2Processor and XQueryProcessor present the Saxon API to the .NET programmer in a similar way to the presentation of XslCompiledTransform in System.Xml. The main difference is that XslCompiledTransform accepts IXPathNavigable as XML source, whereas the wrappers accept the less generalized XmlNode. This is due to the Saxon API using XmlNode.

The code incorporates Saxon and its related libraries as a Visual Studio project called SaxonWrappers. There are two options for usage. The first is to add the SaxonWrappers project to your solution. This allows you to customize the wrappers. Alternatively, you can reference all the assemblies in the root directory of the zip file. Ensure that the saxon9api.netmodule file is in the same directory as the DLL files.

The following code performs an XSL 2.0 transformation books.xsl against books.xml and writes the resulting output to books.html. The input files are included in the download.

C#
using (StreamWriter streamWriter = 
       new StreamWriter("books.html", false, Encoding.UTF8))
{            
    // Load the input doc
    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.Load("books.xml");

    // Create the output XmlWriter
    XmlTextWriter xmlWriter = new XmlTextWriter(streamWriter);

    // Do the transformation
    Xsl2Processor processor = new Xsl2Processor();
    processor.Load("books.xsl");
    processor.Transform(xmlDoc, xmlWriter);
}

The following code performs an XQuery 1.0 in books-to-html.xq against books.xml, and writes the resulting output to books_xq.html. The input files are included in the download.

C#
using (StreamWriter streamWriter = 
       new StreamWriter("books_xq.html", false, Encoding.UTF8))
{
    // Load the input doc
    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.Load("books.xml");

    // Create the output XmlWriter
    XmlTextWriter xmlWriter = new XmlTextWriter(streamWriter);

    // Do the transformation
    XQueryProcessor xp = new XQueryProcessor();
    xp.LoadFromFile("books-to-html.xq");
    xp.RunQuery(xmlDoc, xmlWriter);    
}

The XQueryProcessor class also has a Load method to load an XQuery statement from a string.

More about Saxon, Java, and .NET

Saxon is written and maintained by Michael Kay, who is also the editor of the XSL 2.0 standard. Saxon is written in Java, and Michael has made the work available to .NET using IKVM. Saxon links to the Java core libraries using IKVM's compilation of GNU Class Path. All of these libraries are available under permissive Open Source licenses, including Saxon which is under the Mozilla Public License.

Further Reading

History

  • 25/03/2008 - First release

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)