Home » Articles » Unique Product Features » Compiled Templates for XSLT 2.0

Compiled Templates for XSLT 2.0

By Mark Joseph - September 3, 2008 @ 7:43 pm

A problem of performance

One common concern with the use of XSLT has been performance. The need to compile an XSLT template, with its embedded XPath expressions each time a Web page is to be displayed is wasteful. Part of P6R’s implementation of the XSLT 2.0 standard includes an extension that helps address this performance problem. Our XLST processor (see XJR SDK) compiles an XSLT template into an essentially static data structure that can be used over and over again by multiple threads at the same time. This is achieved by removing most of the dynamic, run-time state out of the compiled templates. This article describes our approach in detail.

Compiled XSLT templates as objects

P6R’s XSLT processor (p6IXSLT) can be used in a simple mode of execution where a template is first compiled (via the compileTemplates() method) and then used in the transformation of a source document. This is a typical run-time model for XSLT processors and for some applications this mode of execution will be acceptable.

However, for high performance applications the same template will need to be executed concurrently by several threads.   Two XSLT interfaces support this complex mode of execution. First, the p6IXSLTTemplate interface allows the caller to extract a compiled template object from a running XSLT processor instance. Second, the compiled template object is represented by the p6IXSLTCompiled interface (which allows the template to be marked with a name for caching purposes).  The compiled template contains almost no execution state and thus can be shared with multiple instances of XSLT processors at the same time.

A compiled template contains all XSLT elements in a tree of nodes along with compiled, embedded XPath expressions, and compiled forms of any regular expressions that might appear in both XPath expressions and XSLT elements (i.e., the Non-deterministic Finite State Automation that represents the regex is its compiled form).   Only compiled regular expressions contain run-time state in a compiled XSLT template and each is protected with its own lock.   During run time all other XSLT execution state (e.g., global variables, keys, result document, source document) are all associated with the instance of the XSLT processor object and not with the compiled XSLT template object.

The simple mode of execution involves the following sequence of statements:

p6IDataStream*  pOutStream;  // output results to a stream (a file or socket)
p6IXSLT* pXSLT;               // instance of the XSLT processor

// call class factory to get an XSLT processor instance
p6CreateInstance(... IID_p6IXSLT, &pXSLT );
// . . . . 
// first compile an XSLT template 
pXSLT->compileTemplates( <buffer containing XSLT template>, pOutStream );

// transform the source document, place output in pOutStream
pXSLT->startTransform( pOutStream, <buffer with XML (or JSON) data as source document> );

In the complex mode of execution, one or more “compiled” template objects are generated by performing the following sequence of statements:

p6IDataStream* pOutStream;    // output results or error messages
p6IXSLT* pXSLT;               // instance of the XSLT processor
p6IXSLT* pAnotherXSLT;
p6IXSLT* pThirdXSLT;
p6IXSLTTemplate* pTemplate1;  // compiled template interface
p6IXSLTTemplate* pTemplate2; 
p6IXSLTTemplate* pTemplate3; 
p6IXSLTCompiled* pCompiled1;  // compiled template object
p6IXSLTCompiled* pCompiledN;

// call class factory to get XSLT processor instances
p6CreateInstance(... IID_p6IXSLT, &pXSLT );
p6CreateInstance(... IID_p6IXSLT, &pAnotherXSLT );
p6CreateInstance(... IID_p6IXSLT, &pThirdXSLT );

// get interfaces that support compiled templates
pXSLT->queryInterface( p6IXSLTTemplate, &pTemplate1 );
pAnotherXSLT->queryInterface( p6IXSLTTemplate, &pTemplate2 );
pThirdXSLT->queryInterface( p6IXSLTTemplate, &pTemplate3 );

// extract the compiled template from the XSLT processor instance
pXSLT->compileTemplates( <buffer containing XSLT template>, pOutStream  );
pTemplate1->getTemplate( &pCompiled1 );
. . .
pXSLT->compileTemplates( <buffer containing another XSLT template>, pOutStream );
pTemplate1->getTemplate( &pCompiledN );
. . .
// load the compiled template into other instances of the XSLT processor for execution
pTemplate2->setTemplate( pCompiled1 );
pAnotherXSLT->startTransform( pOutStream, <source document in XML or JSON> );
pTemplate3->setTemplate( pCompiled1 );
pThirdXSLT->startTransform( pOutStream3, <source document in XML or JSON> );
pCompiled1->release();
// . . .

An application can generate a compiled template for each output “page” it needs to generate.   Each compiled template (i.e., a p6IXSLTCompiled interface) can then be placed into a RAM cache and used over and over again in several transformations.

Next in the complex mode of execution, the compiled templates can be run concurrently across several instances of the XSLT processor.   The p6IXSLTTemplate interface also allows a compiled template to be “loaded” into any instance of an XSLT processor for execution.   In fact, the same compiled template could be loaded to any number of XSLT processors at the same time, where each XSLT processor executes with its own thread.

When a compiled template is no longer needed the caller simply invokes the “release()” method on the object to free it. All P6R objects are reference counted, so when the last holder of a compiled template releases it the template will free itself.

Application defined variables and functions

XPath is described as an “embedded” expression language.   That means that it typically does not live on its own but is part of another parent application, such as XSLT.  When XPath detects a variable or function which it does not recognize it calls out to its parent application to ask it to resolve the reference.   P6R has implemented this in a very flexible and powerful way.   Our XPath processor contains a method that allows its parent application to register a special “connector” object.   This connector object has two methods: lookupvaraible() and executeExternalFunction().   The connector object can be reset at any time between XPath expression evaluations.   So for example, in an XSLT style sheet all references to XSLT variables that appear in a “select” attribute will force the XPath processor to call to its XSLT parent to obtain the variables’ value (by calling a registered connector’s lookupvariable() method).

This connector object design gets extended one step further.  P6R’s XSLT processor allows its calling application to also register its own connector object.   The key point here is that all connector objects are chained.   So as an example, if the XPath processor detects a variable it does not recognize it calls out to its XSLT parent.   However, if the XSLT processor also does not recognize the variable it will then call out to its parent (i.e., the calling application) via the second connector object.   The same thing occurs if the unrecognized reference is to a function rather than to a variable.

This chaining of connector objects allows an application running an XSLT processor to define its own global variables and external functions. These global variables are different from normal XSLT variables in that they can change their values at any time since they are under control of the calling application.  (Note that the connector interface passes back and forth only XPath supported types.)

This feature by itself is a powerful P6R extension, but it also supports compiled templates as discussed in the next section.

Parametrized compiled templates

P6R’s XSLT processor has restricted support for Attribute Value Templates because of our compiled templates feature.  Many XSLT elements allow some of their attributes to be passed in as part of the “environment” before template compilation.   This way an XSLT element can be parametrized.  For example, the XSLT sort element allows many of its attributes (e.g., order = { ascending | descending },  and collation = { uri }) to be determined at compile time via parameters to the compile/transform call.

The problem with this type of value expansion is that it can only happen once with compiled templates.   Using the XSLT sort element as an example, the same template would have to be compiled with both values for the “order” attribute just in case that attribute could be set by a user at a Web UI.   Instead of using these “hard coded” values we allow the calling application to use the XSLT processor’s connector objects described in the previous section above.  These application controlled variables can change at any time and can be defined differently for each instance of a running XSLT processor.  (For example, different values can be returned by a variable based on the type of operating system that is running.)

Again using the sort element for an example, we would change all appearances of the “collation” attribute to reference a global variable that is defined by a connector object.  If the sort element is being used to generate an HTML page per HTTP request, then the collation value returned could be based on the browser’s value returned in the “Accept-language” header.  The key point is that now these once hard coded values can take on dynamic values thus allowing the same compiled template to be used for a wider range of requests.

Caching the source document

P6R’s XSLT processor has two main interfaces to perform a transformation.   The first interface takes the XML (or JSON) source data as a stream of data.   Once the stream is read the XML (or JSON) is compiled into a DOM tree.   If most or all of the source document is static then repeated parsing of the source is again a waste of time.   Thus the second interface to perform a transformation takes a P6R DOM tree as input (i.e., p6IDOMXML interface).

All or parts of a source document can be cached in RAM and used over and over again with compiled templates to improve processing speed.   Now what if part of the source document needs to change?   Well we support that requirement by provided a DOM tree merge function.   Given DOM trees A and B, this feature allows DOM tree B to be grafted onto any part of DOM tree A.   So for example, DOM tree A could be a cached part of a source document while DOM tree B could a dynamic portion of a page to be generated.  (Later DOM tree B can be removed from tree A as well.)

Summary

P6R’s XSLT extensions provide enhanced functionality (e.g., the connector objects described in Section 3) and address known performance issues.  With our XSLT processor a developer can either use our extensions or just use the processor in a simple mode of operation with a easy to use API.   Our goal is to provide a flexible and powerful XSLT 2.0 solution to all developers.

"Compiled Templates for XSLT 2.0" was published on September 3rd, 2008 and is listed in Unique Product Features.

Follow comments via the RSS Feed | Leave a comment | Trackback URL


Leave Your Comment