Out of the Angle Brackets
This article is not finished. Please do not copy its content, make a link instead.
To improve XSLT execution performance in the .NET Framework version 2.0, the XslTransform class has been replaced with a new XSLT 1.0 implementation: the XslCompiledTransform class. XslCompiledTransform compiles XSLT stylesheets to Microsoft Intermediate Language (MSIL) methods and then executes them. Execution time of the new processor is on average 4 times better than XslTransform and matches the speed of MSXML, the native XML processor.
XslTransform
XslCompiledTransform
Although XslCompiledTransform is designed to be as compatible with XslTransform as possible, differences between the two classes nonetheless exist. The purpose of this document is to cover known differences between XslTransform to XslCompiledTransform and to simplify migration from the former to the latter.
Before we can talk about migration, it's worth briefly outlining how XslCompiledTransform works.
When the Load method is called, XslCompiledTransform reads stylesheet modules from XmlReader and creates an abstract syntax tree (AST) of the principal stylesheet module and all its imports and includes. It then compiles all script blocks to assemblies (one assembly per script namespace per script language). Finally it compiles the AST to a separate assembly.
Load
XmlReader
Once the stylesheet is fully loaded, XslCompiledTransform can transform the input document. Transformation of the input document to output involves the following steps:
To load the cache, XslCompiledTransform uses XmlReader to read the input document. At this time, XslCompiledTransform applies whitespace stripping rules.
If the input document was specified as a Uniform Resource Identifier (URI) string, XslCompiledTransform creates an XmlReader to read the XML document from that URI using default XmlReaderSettings, but allowing Document Type Definition (DTD):
XmlReaderSettings
XmlReaderSettings rs = new XmlReaderSettings(); rs.ProhibitDtd = false; XmlReader.Create(inputUri, rs);
If the input document was specified as IXPathNavigable, XslCompiledTransform doesn't reload the cache. It calls IXPathNavigable.CreateNavigator() and uses this navigator as its cache. In this case, if the stylesheet contains xsl:strip-space or xsl:preserve-space instructions, XslCompiledTransform raises an error because it can't properly apply whitespace stripping rules.
IXPathNavigable
IXPathNavigable.CreateNavigator()
xsl:strip-space
xsl:preserve-space
Transformation is started by applying all template rules to the current node in the XML tree. In most cases the current node will be the root node (one that matches pattern "/"). XslCompiledTransform allows a client application to choose what node should be current at transformation start by providing input document as XPathNavigator positioned to this node. Global variables are calculated "lazily" (on first use) and the context node for them is always the root node of the input document even when the transformation has been started from a different node.
XPathNavigator
This behavior is key to allowing to rerun parts of the transformation. XslTransform, by contrast, did not support this feature. It always starts transformation from the root node.
Depending on the Transform overload called, a client application may receive transformation results in one of these forms: Stream, TextWriter, XmlWriter, or XmlReader. Internally, Transform always generates output as a set of XmlWriter events and then serializes these events to the requested form.
Transform
Stream
TextWriter
XmlWriter
The stylesheet may contain one or more xsl:output instructions that specify how the transformation output should be serialized. These instructions are compiled into an instance of XmlWriterSettings class, which can be accessed via the OutputSettings property of XslCompiledTransform. A client application may receive the output tree directly by providing an instance of the XmlWriter class to the Transform method as the 'results' argument. In this case the client is responsible for output serialization. When Transform is called with Stream or TextWriter as the 'result' argument, XslCompiledTransform internally creates XmlWriter by calling XmlWriter.Create(result, outputSettings) and then uses this writer to serialize transformation output.
xsl:output
XmlWriterSettings
OutputSettings
XmlWriter.Create(result, outputSettings)
Here is a WinDiff style comparison of XslTransform and XslCompiledTransform APIs:
public sealed class XslTransform { public sealed class XslCompiledTransform { // Constructors public XslTransform(); public XslCompiledTransform() { } // New constructor allows you to debug stylesheets public XslCompiledTransform(bool enableDebug); // Properties public XmlResolver XmlResolver { set ; } // This property was not thread-safe, and thus removed from XslCompiledTransform public XmlWriterSettings OutputSettings { get; } public TempFileCollection TemporaryFiles { get; } // Load stylesheet from XmlReader public void Load(XmlReader stylesheet); public void Load(XmlReader stylesheet, XmlResolver resolver); public void Load(XmlReader stylesheet, XmlResolver resolver, Evidence evidence); public void Load(XmlReader stylesheet, XsltSettings settings, XmlResolver stylesheetResolver); // Load stylesheet from IXPathNavigable public void Load(IXPathNavigable stylesheet); public void Load(IXPathNavigable stylesheet, XmlResolver resolver); public void Load(IXPathNavigable stylesheet, XmlResolver resolver, Evidence evidence); public void Load(IXPathNavigable stylesheet, XsltSettings settings, XmlResolver stylesheetResolver); // Load stylesheet from XPathNavigator public void Load(XPathNavigator stylesheet); public void Load(XPathNavigator stylesheet, XmlResolver resolver); public void Load(XPathNavigator stylesheet, XmlResolver resolver, Evidence evidence); // In .NET Framework 2.0 XPathNavigator implements IXPathNavigable, so these overloads are not needed // Load stylesheet from URI public void Load(string url); public void Load(string url, XmlResolver resolver); public void Load(string stylesheetUri); public void Load(string stylesheetUri, XsltSettings settings, XmlResolver stylesheetResolver); // Transform document read from XmlReader public void Transform(XmlReader input, XmlWriter results); public void Transform(XmlReader input, XsltArgumentList arguments, XmlWriter results); public void Transform(XmlReader input, XsltArgumentList arguments, TextWriter results); public void Transform(XmlReader input, XsltArgumentList arguments, Stream results); public void Transform(XmlReader input, XsltArgumentList arguments, XmlWriter results, XmlResolver documentResolver); // Transform IXPathNavigable to XmlReader public XmlReader Transform(IXPathNavigable input, XsltArgumentList args); public XmlReader Transform(IXPathNavigable input, XsltArgumentList args, XmlResolver resolver); // Not implemented in XslCompiledTransform // Transform IXPathNavigable to XmlWriter public void Transform(IXPathNavigable input, XsltArgumentList args, XmlWriter output); public void Transform(IXPathNavigable input, XsltArgumentList args, XmlWriter output, XmlResolver resolver); public void Transform(IXPathNavigable input, XmlWriter results); public void Transform(IXPathNavigable input, XsltArgumentList arguments, XmlWriter results); // Transform IXPathNavigable to TextWriter public void Transform(IXPathNavigable input, XsltArgumentList args, TextWriter output); public void Transform(IXPathNavigable input, XsltArgumentList args, TextWriter output, XmlResolver resolver); public void Transform(IXPathNavigable input, XsltArgumentList arguments, TextWriter results); // Transform IXPathNavigable to Stream public void Transform(IXPathNavigable input, XsltArgumentList args, Stream output); public void Transform(IXPathNavigable input, XsltArgumentList args, Stream output, XmlResolver resolver); public void Transform(IXPathNavigable input, XsltArgumentList arguments, Stream results); // Transform XPathNavigator public XmlReader Transform(XPathNavigator input, XsltArgumentList args, XmlResolver resolver); public XmlReader Transform(XPathNavigator input, XsltArgumentList args); public void Transform(XPathNavigator input, XsltArgumentList args, XmlWriter output, XmlResolver resolver); public void Transform(XPathNavigator input, XsltArgumentList args, XmlWriter output); public void Transform(XPathNavigator input, XsltArgumentList args, Stream output, XmlResolver resolver); public void Transform(XPathNavigator input, XsltArgumentList args, Stream output); public void Transform(XPathNavigator input, XsltArgumentList args, TextWriter output, XmlResolver resolver); public void Transform(XPathNavigator input, XsltArgumentList args, TextWriter output); // In .NET Framework 2.0 XPathNavigator implements IXPathNavigable, so these overloads are not needed // Transform document read from URI public void Transform(String inputfile, String outputfile); public void Transform(String inputfile, String outputfile, XmlResolver resolver); public void Transform(string inputUri, string resultsFile); public void Transform(string inputUri, XmlWriter results); public void Transform(string inputUri, XsltArgumentList arguments, XmlWriter results); public void Transform(string inputUri, XsltArgumentList arguments, TextWriter results); public void Transform(string inputUri, XsltArgumentList arguments, Stream results); }
As you can see, the APIs of the classes are very similar. The major differences are:
Evidence
XsltSettings
TemporaryFiles
enableDebug
Let's discuss each of these in turn.
The XslTransform class had several Transform overloads that return transformation results in form of an XmlReader object. These overloads were mostly used to load the transformation results into an in-memory representation (such as XmlDocument or XPathDocument) without incurring the overhead of serialization and deserialization of the resulting XML tree:
XmlDocument
XPathDocument
// Load the stylesheet XslTransform xslt = new XslTransform(); xslt.Load("MyStylesheet.xsl"); // Transform input document to XmlDocument for further processing XmlDocument doc = new XmlDocument(); doc.Load(xslt.Transform(input, (XsltArgumentList)null));
Due to architectural and performance reasons, XslCompiledTransform does not support transforming to XmlReader. However, in .NET Framework 2.0 the XPathNavigator class introduces new methods, which allow loading an XML tree directly from XmlWriter. The same task may be accomplished now this way:
XmlDocument doc = new XmlDocument(); using (XmlWriter writer = doc.CreateNavigator().AppendChild()) { xslt.Transform(input, (XsltArgumentList)null, writer); }
Another way to obtain the transformation results in form of an XmlReader object in an efficient way is using XslReader class, which implements XmlReader by running XslCompiledTransform on a separate thread and resuming that thread each time XmlReader.Read needs a next node. XslReader code is available as a part of Mvp.Xml project.
XslReader
XmlReader.Read
Instead of Evidence, XslCompiledTransform's API uses a new XsltSettings class to control stylesheet rights. This class exposes two properties, EnableScript and EnableDocumentFunction:
EnableScript
EnableDocumentFunction
public sealed class XsltSettings { public XsltSettings(); public XsltSettings(bool enableDocumentFunction, bool enableScript); public bool EnableDocumentFunction { get; set; } public bool EnableScript { get; set; } public static XsltSettings Default { get { return new XsltSettings(false, false); } } public static XsltSettings TrustedXslt { get { return new XsltSettings(true, true); } } }
Script blocks and the document function may introduce security risks and are thus disabled by default. If you trust the stylesheet and want to use those features, you need to explicitly enable them by passing an appropriately set XsltSettings object, as shown below:
document
XslCompiledTransform xslt = new XslCompiledTransform(); // Disable script blocks and the document() function // if a stylesheet came from an untrusted source string untrustedUri = @"http://www.untrusted-site.com/meow.xsl"; XmlResolver secureResolver = new XmlSecureResolver(new XmlUrlResolver(), untrustedUri); xslt.Load(untrustedUri, XsltSettings.Default, secureResolver); // Enable script blocks and the document() function // if a trusted stylesheet needs them xslt.Load(@"C:\MyProject\purr.xsl", XsltSettings.TrustedXslt, new XmlUrlResolver());
Overloads of the Load method that do not take an XsltSettings argument, use XsltSettings.Default.
XsltSettings.Default
If a stylesheet contains script blocks, or it is compiled with the enableDebug flag set to true, XslCompiledTransform may create some temporary files in the %TEMP% folder. When the finalizer of a TempFileCollection object (which keeps track of created temporary files), tries to remove all of them, some files may be in use by the current AppDomain (*.DLL files) or by the debugger (*.PDB files). Those files cannot be deleted until unloading the AppDomain or detaching the debugger. If you frequently compile XSLT stylesheets with script blocks, the %TEMP% folder may become overcrowded with temporary files left undeleted. To ensure that all temporary files will be removed, an advanced client may use the TemporaryFiles property to save paths of temporary files for a further cleanup.
true
TempFileCollection
AppDomain
If a client application uses a Transform overload that takes an XmlWriter as the output object, XslCompiledTransform ignores all xsl:output settings specified in the stylesheet. In this case the client is responsible for output serialization. It may control serialization process by either passing a custom implementation of XmlWriter or adjusting the properties of the standard XmlTextWriter. If the client needs to know serialization settings specified in the stylesheet, it may obtain them through the XslCompiledTransform.OutputSettings property. Suppose, for example, that the client needs to turn character checking off, but respect other xsl:output settings. It may use the following code to achieve that:
XmlTextWriter
XslCompiledTransform.OutputSettings
// Load the stylesheet XslCompiledTransform xslt = new XslCompiledTransform(); xslt.Load("MyStylesheet.xsl"); // Clone its output settings and turn character checking off XmlWriterSettings ws = xslt.OutputSettings.Clone(); ws.CheckCharacters = false; // Run the transformation with changed output settings xslt.Transform("MyDocument.xml", XmlWriter.Create(Console.Out, ws));
For a number of error cases, the XSLT specification leaves XSLT processors flexibility to either signal an error, or recover by taking certain recovery action. The table below summarizes behaviors of XslTransform and XslCompiledTransform in such cases.
xsl:element
xsl:attribute
xmlns
xsl:processing-instruction
xsl:comment
cdata-section-elements
xsl:number
1 Behavior depends on the document resolver passed to the Transform method. See the section devoted to the document function.
2 The default resolver, XmlUrlResolver, always ignores fragment identifiers. However, custom resolvers may regard them.
XmlUrlResolver
3 XmlUrlResolver will pass the relative URI reference to Path.GetFullPath and resolve it relative to the current directory. Custom resolvers may behave differently.
Path.GetFullPath
In XSLT both stylesheet modules and source documents are XML files. There is a lot of similarity in loading them. Here we summarize how XslCompiledTransform loads stylesheet modules and source documents and then we will see where its behavior is different from XslTransform.
XslCompiledTransform uses XmlReader to read both stylesheets and source documents. In both cases more than one XML file can be used. A stylesheet module may import or include other stylesheet modules; to load additional source documents, the document function can be used.
XslCompiledTransform delegates all work related to external URIs access to XmlResolver. To load the principal stylesheet and all includes/imports, XslCompiledTransform uses instance of XmlResolver which was passed to the Load method. To load the source document and all subsequent documents, XslCompiledTransform uses XmlResolver passed to the Transform method.
XmlResolver
When the overload of the Load/Transform method that doesn't have XmlResolver argument is called, XmlUrlResolver is used to load subsequent stylesheets and documents. When null is passed as an XmlResolver argument to the Load/Transform method, loading of subsequent stylesheets/documents is disabled.
null
A client application may pass XmlReader, URI string or IXPathNavigable to Load/Transform methods. System.Uri will be passed to XmlResolver and resolved to XmlReader, Stream or IXPathNavigable.
string
System.Uri
For subsequent stylesheets/documents XmlResolver returns to Proccessor XmlReader, Stream or IXPathNavigable.
In case when input is Stream to load XML Processor creates XmlTextReader. XmlTextReader has set of properties which control reader's behavior. (Most important properties are Normalization, ProhibitDtd, WhitespaceHandling, XmlResolver; see documentation for XmlTextReader on meaning of this properies.)
XmlTextReader
Normalization
ProhibitDtd
WhitespaceHandling
When XslCompiledTransform creates the first reader, one that is used for reading data passed directly to Load/Transform methods, it sets the reader setting the following way:
XmlReaderSettings rs = new XmlReaderSettings(); rs.ConformanceLevel = ConformanceLevel.Document; rs.XmlResolver = null; rs.ProhibitDtd = true; rs.CloseInput = true;
For each subsequent stylesheet/document XslCompiledTransform creates XmlTextReader with the same reader settings that the first XmlReader has. This guarantees consistency in loading stylesheets and source documents.
If you need to use specific reader settings in stylesheet/document load process, you have to pass XmlReader created with these settings to Load/Transform methods. If you want XmlReader for subsequent stylesheet/document document to have different reader settings, you should implement a custom XmlResolver, which returns an XmlReader with required reader settings, and pass it to a Load/Transform method.
In contrast, XslTransform always created helper XmlTextReader objects with default settings. If a client application provided the Load/Transform method with an XmlReader that has non-default reader settings, those reader settings were not regarded on subsequent document loads.
XslTransform swallowed any exceptions thrown during execution of the document function, returning an empty node-set. All possible run-time errors (e.g., network share cannot be accessed) were masked, and clients had no means to control that.
In contrast, XslCompiledTransform class does not swallow exceptions, but rethrows them wrapped in an XslTransformException. However, you can still implement XslTransform's behavior—if the GetEntity method of your custom XmlResolver returns null, it is treated as an empty node-set, and no errors will be reported.
XslTransformException
GetEntity
When you used XslTransform, the GetEntity method of your XmlResolver had to return either Stream or XPathNavigator. With XslCompiledTransform, a resolver can return either Stream, XmlReader, or IXPathNavigable (and, therefore, XPathNavigator). The option of returning XmlReader is useful if you want to have precise control of loading documents, for example, to control whitespace handling, DTD handling (prohibit or allow processing a DTD), compatibility issues (use an old XmlTextReader or a new reader created by XmlReader.Create), etc.
XmlReader.Create
It is common to use document('') to refer to the document node of the containing stylesheet module. Please note that the base URI of the stylesheet module must be known in order to make it work. For example, if you create an XmlReader over a stream without specifying its base URI, XSLT engine will not resolve the empty string reference correctly unless you have provided a custom XmlResolver that handles such a reference in a special way.
document('')
Both XslTransform and XslCompiledTransform allow extending XSLT language through script blocks (ms:script elements) included in the stylesheet and extension objects passed added to XsltArgumentList. Here we outline differences between XslTransform and XslCompiledTransform in implementing these features.
ms:script
XsltArgumentList
Both XSLT engines use CodeDom to compile script blocks. All script blocks implementing the same namespace are concatenated and compiled into a single class. Methods of that class may be called from within XPath expressions contained in the stylesheet.
XslCompiledTransform introduces two new restrictions on use of scripts:
In XslCompiledTransform a binding (method name lookup) to script functions happens at compile time, and stylesheets that worked with XslTranform may cause an exception when loaded with XslCompiledTransform.
XslTranform
XslCompiledTransform allows the ms:script element to start with ms:using and ms:assembly child elements. The former declares an additional namespace, the latter refers an additional assembly for use in a script block.
ms:using
ms:assembly
The only difference in implementation of extension objects is that multiple overloads with the same number of arguments are prohibited now.
XslTransform allowed returning a node-set from an extension function as an object of the XPathNodeIterator class. With XslCompiledTransform you have more alternatives: a node-set may be returned as XPathNodeIterator, XPathNavigator[], or XPathNavigator (if the node-set consists of a single node).
XPathNodeIterator
XPathNavigator[]
XslCompiledTransform makes it easier to migrate from MSXML to managed XSLT implementation by supporting some MSXML extensions. Everywhere in this section the ms prefix denotes the urn:schemas-microsoft-com:xslt namespace URI. Another frequently used prefix for this namespace URI is msxsl. Of course, you may use any other prefix in your stylesheet provided it is bound to the same URI.
ms
urn:schemas-microsoft-com:xslt
msxsl
XslTransform required the argument of the ms:node-set function to be an RTF (result tree fragment), throwing an exception for an argument of any other type. Behavior of this function in XslCompiledTransform is aligned with the behavior of the same function in MSXML and exsl:node-set, which allow converting a value of an arbitrary type into a text node containing a string representation of that value. For example, ms:node-set(1 div 0) creates a text node with the value 'Infinity'.
ms:node-set
exsl:node-set
ms:node-set(1 div 0)
'Infinity'
XslTransform did not recognize the ms:version property, returning an empty string. XslCompiledTransform supports the ms:version system property. For example, system-property('ms:version') returns a string representing the version of the assembly implementing XslCompiledTransform in the same format as returned by Assembly.ImageRuntimeVersion property ('v2.0.50727' for .NET Framework 2.0).
ms:version
system-property('ms:version')
Assembly.ImageRuntimeVersion
'v2.0.50727'
MSXML provides a number of XPath extension functions to facilitate developing XSLT stylesheets. XslTransform did not support them. XslCompiledTransform supports all those functions except schema-related ones:
The following schema-related XPath extension functions are not supported natively by XslCompiledTransform, but may be implemented as extension functions:
If you migrate from MSXML, and your stylesheets contain script blocks, you will likely have to rework them, because the System.Xml assembly uses a different object model to represent nodes and node-sets than MSXML does. Moreover, managed JScript .NET and Visual Basic .NET, supported by XslCompiledTransform, differ to some extent from their native counterparts, JScript and VBScript.
In the .NET Framework 2.0, this gap is narrowed a bit. XPathNodeIterator implements the IEnumerable interface. This allows iterating node-set objects with JScript's foreach instruction.
IEnumerable
foreach
With MSXML you can specify an initial mode, which the transformation would start in, using the setStartMode method of the IXSLProcessor interface. Both XslTransform and XslCompiledTransform do not provide means to specify a start mode, however you may usually simulate that by passing the name of the start mode as an additional parameter to your stylesheet and adding a new 'start template' containing a switch table:
setStartMode
IXSLProcessor
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- Import your main stylesheet here --> <xsl:import href="main.xsl"/> <!-- Set this parameter to simulate setting a start mode --> <xsl:param name="start-mode" select="''"/> <xsl:template match="/"> <!-- Switch mode according to the value of $start-mode --> <xsl:choose> <xsl:when test="$start-mode=''"> <!-- Continue processing in the default mode --> <xsl:apply-imports/> </xsl:when> <xsl:when test="$start-mode='foo'"> <!-- Switch to mode foo --> <xsl:apply-templates select="/" mode="foo"/> </xsl:when> <xsl:when test="$start-mode='bar'"> <!-- Switch to mode bar --> <xsl:apply-templates select="/" mode="bar"/> </xsl:when> ... <xsl:otherwise> <xsl:message terminate="yes">Invalid start-mode</xsl:message> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet>
You may be surprised at seeing System.Data.SqlXml assembly on a call stack. System.Data.SqlXml is a helper assembly containing a part of XslCompiledTransform implementation. It is not supposed to be used directly, and there is no guarantee that its API will not be changed in future .NET Framework releases.
Authors: Sergey Dubinets, Anton Lapounov