XSLT transformations from C#

You can perform a transformation using the C# Saxon.Api interface as follows (the same classes can also be used, of course, from other languages supported on .NET, but the description here is oriented to C#):

  1. Create a Processor and set any global configuration options on the Processor. It is recommended to create a single Processor for the entire application.

    var processor = new Processor();
  2. Call NewXsltCompiler() to create an XsltCompiler object, and set any options that are local to a specific compilation (for example, the destination of error messages).

    var compiler = processor.NewXsltCompiler();
  3. Decide how you want to handle compile-time errors. Some options are:

    • If your application will only be used to execute stylesheets that have been thoroughly tested elsewhere (for example, in a development environment such as Oxygen), then you can simply rely on the exception thrown by the compiler if an error occurs. However, the exception won't contain detailed diagnostics to say what is wrong.

    • If your application runs from the command line, then in normal circumstances any error messages will be written to the console, and no special action is needed.

    • If however the application runs in a web server, or within a GUI framework, then you will need to think about where to direct the error messages. For example, you could send them to a log file, or you could display them on screen. Either way, you will want to register an ErrorReporter that deals with the messages. For example, you could capture the messages in a list (for later analysis) like this:

      compiler.ErrorReporter = err => errorList.Add(err)

      Or you could send them to a log file like this:

      compiler.ErrorReporter = err => Console.Error.WriteLine(err.Message)

      The error object that is passed to the ErrorReporter delegate is not an exception (it cannot be thrown). It is an object of class Error, and its main properties are:

      • Message: an error message
      • ErrorCode: the error code
      • Location: the location in the stylesheet where the error was detected
      • IsWarning: distinguishes warnings from fatal error conditions
  4. Call the Compile() method to compile a stylesheet. The result is an XsltExecutable, which can be used as often as you like, in the same thread or in different threads.

    var executable = compiler.Compile(...)

    This method has a number of overloads, allowing the stylesheet to be supplied in different ways. It can be supplied as a URI, as a Stream, as a TextReader, or as an XmlReader.

    Except for trivial stylesheets containing a single module, it's important that the stylesheet should have a known base URI. The base URI is used at compile time for resolving any relative URI reference appearing in an xsl:include or xsl:import declaration; it's also used at run-time when calling functions such as document(), unparsed-text(), or json-doc(). You can supply a base URI either by setting the BaseUri property on the XsltCompiler, or as an (implicit or explicit) parameter on the call to the Compile method.

    Compiling a stylesheet can often be an expensive process in comparison with the actual execution (especially when the stylesheet is large and the source document is small). It's therefore desirable to reuse the XsltExecutable for multiple transformations where possible. Once created, the XsltExecutable is read-only and thread-safe.

  5. The next step is to create an XsltTransformer, or Xslt30Transformer object, by calling the Load() or Load30() method on the XsltExecutable.

    var transformer = executable.Load(); var transformer = executable.Load30();

    Either of these can be used to run the transformation (regardless of which XSLT version you are using), but they have different capabilities:

    • The XsltTransformer is geared towards the traditional way of running an XSLT transformation, by supplying a principal source document as input. When you call the Run() method, the template rule that best matches this source document is located and executed.

      The input document is supplied as a Stream (containing lexical XML), using the method SetInputStream. This method also expects a URI, which is used as the base URI of the source document in case it contains relative references to other documents.

      The destination for the result is supplied as the argument to the Run() method. This can be any implementation of the interface IDestination; the most common destinations are Serializer (which causes the transformation output to be serialized, typically as XML or HTML), and XdmDestination which delivers the result as an XDM node tree. (Read the xdmDestination.XdmNode property on completion to access the root of the tree.)

      Options for serializing the result can be set either on the Serializer object, or using <xsl:output> declarations in the stylesheet. Options set via the API take precedence.

      Another option is to send the result of a transformation to another XsltTransformer. The class XsltTransformer implements the IDestination interface, so the output of one transformation can form the input to another, making it easy to construct a pipeline of transformations.

      The XsltTransformer also allows you to start executing the stylesheet with a named template as the entry point. In this case there will not necessarily be a source document, though you can still supply one, and it will be used as the context item for global variables.

      You can use methods on the XsltTransformer to set values for global stylesheet parameters, but not for parameters declared at the level of a particular xsl:template element.

      Typical usage:

      var transformer = executable.Load(); transformer.SetInputStream(...); transformer.SetParameter(new QName("debug"), new XdmAtomicValue(true)); var writer = new StringWriter(); var serializer = processor.NewSerializer(writer); serializer.SetOutputProperty(Serializer.INDENT, "yes"); transformer.Run(serializer); Console.WriteLine(writer.ToString());
    • The Xslt30Transformer class was a later introduction, and as its name suggests, it provides new ways of executing stylesheet code that are defined in the XSLT 3.0 specification, though Saxon also allows you to use the same entry points with 1.0 or 2.0 stylesheets. Among the new capabilities are:

      • You can invoke a specific stylesheet function, with parameters, rather than invoking a named template or template rule.
      • If you start execution with a named template or template rule, you can supply values for the parameters defined on that template, as well as the global stylesheet parameters.
      • Whether you execute a template or a function, you can return the results in raw form rather than wrapping them in a result tree. For example, a function (or template) might return a sequence of strings, or a single boolean, or a map, or even a function.
      • There is no longer any necessary relationship between the "principal source document" (if it still exists) and the context item for evaluating global variables. The two things are quite independent of each other.

      It is still possible to wrap the output in a result tree and send it to a IDestination (which might be a Serializer), but this is no longer mandatory.

      The Xslt30Transformer does not implement the IDestination interface so it is not quite as convenient as XsltTransformer when constructing a pipeline.

    Both XsltTransformer and Xslt30Transformer can be serially reused, but they must not be shared across multiple threads. Both allow you to set any options required for the specific transformation: for example, the global context item, the stylesheet parameters, and callbacks for resolving URIs used in functions such as doc(), unparsed-text(), and json-doc().

Examples of Saxon.Api transformations are included in the saxon-resources download file, see the sample application Examples.cs, and API examples for .NET for more information.

For instance, XsltSimple2 in Examples.cs runs as follows:

// Create a Processor instance. Processor processor = new(); // Load the source document XdmNode input = processor.NewDocumentBuilder().Build(new Uri(samplesDir, "data/books.xml")); // Create a transformer for the stylesheet Xslt30Transformer transformer = processor.NewXsltCompiler().Compile(new Uri(samplesDir, "styles/identity.xsl")).Load30(); // Create a serializer const string outfile = "OutputFromXsltSimple2.xml"; Serializer serializer = processor.NewSerializer(); serializer.OutputStream = new FileStream(outfile, FileMode.Create, FileAccess.Write); // Transform the source XML and serialize the result to the output file. transformer.ApplyTemplates(input, serializer); Console.WriteLine("\nOutput written to " + outfile + "\n");