Jesper Tverskov, April 22, 2008

XSLT 2.0 Saxon in ASP.NET

XSLT 2.0 became standard in 2007 and we want to use it in ASP.NET. Microsoft has not made an XSLT 2.0 processor yet, but who really cares. It is easy to use the .net version of the Saxon XSLT 2.0 processor instead. This is a tutorial to get you started.

It is my feeling that Microsoft is not going to make an XSLT 2.0 processor.[1] I don't know if this is good or bad, but the good news is that it is easy to use Saxon's free XSLT 2.0 processor in .Net or Saxon's schema-aware XSLT 2.0 processor. Even if Microsoft makes an XSLT 2.0 processor, it is difficult to imagine that it would be better than Saxon. Why not just let Saxon be the XSLT 2.0 processor for .NET?

1. Saxon’s own documentation

Saxon’s .NET API Documentation is not easy reading unless you do .NET programming every day. The About Saxon webpage mentions that saxon-resources9-n.zip (the number will get higher one day) contains additional documentation. The file is a little difficult to find at the Saxonica download page (it is there), or from the main project page for Saxon at sourceforge.net (could have been fixed). It can also be found here: http://saxon.sourceforge.net/.

Inside saxon-resources9-n.zip is a "samples/cs" directory and the file XsltExamples.cs. The best way to open the "cs" file is from inside software knowing what "cs" means like "Visual Studio .Net", "Visual Web Developer 2008 Express Edition", etc.

The proper place to get help for Saxon in ASP.NET is at the Saxon Discussion Forums: Help.

2. What I do in the tutorial

In this tutorial I have simply taken the examples in Saxon’s own XsltExamples.cs file, and I have modified them to get them working in ASP.NET using C# and "code behind".

I only recreate the simple transformations for Saxon in ASP.NET. The first few steps are always the most difficult. I hope the simple examples will make it easier to get also the more advanced examples in Saxon’s own documentation working the day you need similar code.

3. How make ASP.NET find Saxon

Here is the easy way. Let us use the free edition, Saxon-B. Download and unzip "saxonb9-0-0-4n.zip" (the number will get higher one day). It contains a "bin" directory. Simply copy the files in the "bin" directory to a "bin" directory at the root of your ASP.NET website.

At the top of the codebehind file, you can now insert:

using Saxon.Api;

That is it!

4. XML input and XSLT stylesheet

The input XML file, test.xml, contains a handful of products, and the XSLT stylesheet, test.xsl, transform them into a table in an XHTML page, test.html.

To make use of some of the new functions in XSLT 2.0, we generate date and time in the stylesheet using current-dateTime(), format it with format-dateTime(), and we use the min() and max() functions for "price" and "stock".

In the last example we use two parameters to turn the min/max section on/off, and to sort the table using "prices" in ascending order or "stock" in descending order. We can only choose by changing the parameters in the code behind file by hand. You can put in the proper form elements on the webpage yourself.

5. BaseOutputUri

In the documentation for "Saxon API for .NET" we read: "The base output URI, which acts as the base URI for resolving the "href" attribute of xsl:result-document."

When you use xsl:result-document with a "href" attribute, the document will end up all sorts of strange places in your file system, if you don’t use:

transformer.BaseOutputUri = new Uri("dir of your output doc");

Even when you transform to screen or to file it happens that you also make use of xsl:result-document to get some files created in the same process. For that reason I recommend always to use "BaseOutputUri" so you don’t forget it. I am doing that in all code examples but it is only necessary if you use xsl:result-document.

The "Uri" can look like:

transformer.BaseOutputUri = new Uri("c:\\Inetpub\\wwwroot\\etc\\");
or
transformer.BaseOutputUri = new Uri(Server.MapPath("aFile"));

In all the examples the "Uri" looks like:

transformer.BaseOutputUri = new Uri(xsltUri);

We are using the directory of the XSLT stylesheet just to make it easy. "xsltUri" is a variable set to Server.MapPath("test.xsl").

6. Parameters

In XSLT 1.0 we used parameters a lot as the standard method to overcome the limitations of XSLT 1.0. An example: In ASP.NET we could find the date and time and pass them into the XSLT stylesheet as parameters.

In XSLT 2.0 most of the common limitations are gone. In XSLT 2.0 we can still use parameters to overcome remaining limitations but we now mostly use parameters as input to switches in the XSLT stylesheet.

7. Simple transformation to screen

In Saxon's documentation file, XsltExamples.cs, the first example is called "Show the simplest possible transformation from URI of source document to output stream."

I have reused the code exactly as it is except that the word "static" is dropped from "public static void" and serializer.SetOutputWriter(Console.Out) has been changed to serializer.SetOutputWriter(Response.Output).

I have added transformer.BaseOutputUri = new Uri(xsltUri). It is only necessary if xsl:result-document is used but it is nice always to have the code in place for the day you need it.

Also I have added and out-commented two parameters and serializer.SetOutputFile(Server.MapPath("test.html")) just to have the code in place.

7.1 The aspx file (ExampleSimple1.aspx)

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ExampleSimple1.aspx.cs" Inherits="Saxon1" %>

7.2 The codebehind file (ExampleSimple1.aspx.cs)

using System;
using System.IO;
using System.Collections;
using System.Xml;
using Saxon.Api;

public partial class Saxon1 : System.Web.UI.Page
  {

  /**
   * Show the simplest possible transformation from URI of source document
   * to output stream.
   */

  public void ExampleSimple1(String sourceUri, String xsltUri)
    {
    // Create a Processor instance.
    Processor processor = new Processor();

    // Load the source document.
    XdmNode input = processor.NewDocumentBuilder().Build(new Uri(sourceUri));

    // Create a transformer for the stylesheet.
    XsltTransformer transformer = processor.NewXsltCompiler().Compile(new Uri(xsltUri)).Load();

    // Set the root node of the source document to be the initial context node.
    transformer.InitialContextNode = input;

    // BaseOutputUri is only necessary for xsl:result-document.
    transformer.BaseOutputUri = new Uri(xsltUri);

    // transformer.SetParameter(new QName("", "", "a-param"), new XdmAtomicValue("hello to you!"));
    // transformer.SetParameter(new QName("", "", "b-param"), new XdmAtomicValue(someVariable));

    // Create a serializer.
    Serializer serializer = new Serializer();
    serializer.SetOutputWriter(Response.Output); //for screen
    // serializer.SetOutputFile(Server.MapPath("test.html")); //for file

    // Transform the source XML to System.out.
    transformer.Run(serializer);
  }

  protected void Page_Load(object sender, EventArgs e)
    {
      ExampleSimple1(Server.MapPath("test.xml"), Server.MapPath("test.xsl"));
    }
}

8. Simple transformation to file

In Saxon's documentation file, XsltExamples.cs, the second example is called "Show the simplest possible transformation from File to a File."

I have reused the code exactly as it is except that the word "static" is dropped from "public static void" and serializer.SetOutputStream(new FileStream("ExampleSimple2.out", FileMode.Create, FileAccess.Write)) has been replaced by serializer.SetOutputFile(Server.MapPath("test.html"));.

I have added transformer.BaseOutputUri = new Uri(xsltUri). It is only necessary if xsl:result-document is used but it is nice always to have the code in place for the day you need it. Also I have added and out-commented two parameters and serializer.SetOutputWriter(Response.Output) just to have the code in place.

To avoid ending up with a blank screen when transforming to a file, we simply add a Response.Redirect or a Server.Transfer after the code doing the transformation. In the example I redirect to the created output file to see if it is there, but normally we continue to some other webpage.

8.1 The aspx file (ExampleSimple2.aspx)

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ExampleSimple2.aspx.cs" Inherits="Saxon2" %>

8.2 The codebehind file (ExampleSimple2.aspx.cs)

using System;
using System.IO;
using System.Collections;
using System.Xml;
using Saxon.Api;

public partial class Saxon2 : System.Web.UI.Page
  {
  /**
   * Show the simplest possible transformation from File
   * to a File.
   */
    public void ExampleSimple2(String sourceUri, String xsltUri)
      {
        // Create a Processor instance.
        Processor processor = new Processor();

        // Load the source document.
        XdmNode input = processor.NewDocumentBuilder().Build(new Uri(sourceUri));

        // Create a transformer for the stylesheet.
        XsltTransformer transformer = processor.NewXsltCompiler().Compile(new Uri(xsltUri)).Load();

        // Set the root node of the source document to be the initial context node.
        transformer.InitialContextNode = input;

        // BaseOutputUri is only necessary for xsl:result-document.
        transformer.BaseOutputUri = new Uri(xsltUri);

        // transformer.SetParameter(new QName("", "", "a-param"), new XdmAtomicValue("hello to you!"));
        // transformer.SetParameter(new QName("", "", "b-param"), new XdmAtomicValue(someVariable));

        // Create a serializer.
        Serializer serializer = new Serializer();
        //serializer.SetOutputWriter(Response.Output);
        serializer.SetOutputFile(Server.MapPath("test.html"));

        // Transform the source XML to System.out.
        transformer.Run(serializer);
      }

    protected void Page_Load(object sender, EventArgs e)
      {
        ExampleSimple2(Server.MapPath("test.xml"), Server.MapPath("test.xsl"));
        Response.Redirect("test.html"); //Just to see if it is there.
        // Server.Transfer("test.html"); //if you don't want the address line to change.
      }
}

9. Using parameters

To make things just a little bit more advanced we will do a transformation to screen making use of two parameters. The aspx file and the "code behind" file are exactly like the transformation to screen example above except that we now use the two parameters and have added the code to pass them on.

XSLT code to pick up the parameters have been present in test.xsl from the beginning. In the first two examples we simply used default values.

9.1 The aspx file (ExampleSimple3.aspx)

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ExampleSimple3.aspx.cs" Inherits="Saxon3" %>

9.2 The codebehind file (ExampleSimple3.aspx.cs)

using System;
using System.IO;
using System.Collections;
using System.Xml;
using Saxon.Api;

  public partial class Saxon3 : System.Web.UI.Page
    {
      /**
       * Show the simplest possible transformation from
       * URI of source document to output stream
       * using parameters.
       */

      public void ExampleSimple3(String sourceUri, String xsltUri, String param1, String param2)
        {
          // Create a Processor instance.
          Processor processor = new Processor();

          // Load the source document.
          XdmNode input = processor.NewDocumentBuilder().Build(new Uri(sourceUri));

          // Create a transformer for the stylesheet.
          XsltTransformer transformer = processor.NewXsltCompiler().Compile(new Uri(xsltUri)).Load();

          // Set the root node of the source document to be the initial context node.
          transformer.InitialContextNode = input;

          transformer.SetParameter(new QName("", "", "maxmin"), new XdmAtomicValue(param1));
          transformer.SetParameter(new QName("", "", "pricestock"), new XdmAtomicValue(param2));

          // BaseOutputUri is only necessary for xsl:result-document.
          transformer.BaseOutputUri = new Uri(xsltUri);

          // Create a serializer.
          Serializer serializer = new Serializer();
          serializer.SetOutputWriter(Response.Output); //for screen
          // serializer.SetOutputFile(Server.MapPath("test.html")); //for file

          // Transform the source XML to System.out.
          transformer.Run(serializer);
        }

      protected void Page_Load(object sender, EventArgs e)
        {
          String maxmin = "yes"; //yes|no
          String pricestock = "stock"; //price|stock

          ExampleSimple3(Server.MapPath("test.xml"), Server.MapPath("test.xsl"), maxmin, pricestock);
        }
}

Footnotes

[1]

In 2007-11-16, Interview with Chris Lovett at the Microsoft XML Team's WebLog:

"As for XSLT 2.0 - we’ve heard from customers and understand the improvements in XSLT 2.0 over XSLT 1.0, but right now we’re in the middle of a big strategic investment in LINQ and EDM for the future of the data programming platform which we think will create major improvements in programming against all types of data. But we are always re-evaluating our technology investments so if your readers want to ramp up their volume on XSLT 2.0 please ask them to drop us a line with their comments."

Updated 2009-08-06