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:
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.
<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:
<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.
using (StreamWriter streamWriter =
new StreamWriter("books.html", false, Encoding.UTF8))
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("books.xml");
XmlTextWriter xmlWriter = new XmlTextWriter(streamWriter);
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.
using (StreamWriter streamWriter =
new StreamWriter("books_xq.html", false, Encoding.UTF8))
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("books.xml");
XmlTextWriter xmlWriter = new XmlTextWriter(streamWriter);
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