[XSL-LIST Mailing List Archive Home] [By Thread] [By Date]

Re: [xsl] XSLT vs javascript performance


Subject: Re: [xsl] XSLT vs javascript performance
From: Robert Koberg <rob@xxxxxxxxxx>
Date: Sun, 7 Feb 2010 18:34:06 -0800

(ufff...  might show up twice. I originally sent with wrong email address)

It also depends a great deal on what XML you working with and what you have to
do with it.

There has been improvement in JS libraries selectors so for some tasks, JS
DOM/Selectors might make more sense, performance-wise.

There is still no equal to XSL when dealing with *repeatedly* (i.e. you can
reuse the browser's XSL processor object) transforming mixed content XML.

I have an example you can test for yourself. It takes a REST application's
WADL and transforms it to JSON for use in a client javascript app. I think
this is a good test because it's end result is a JSON object, which should
give the edge to JS in building it, right?

I have pasted a JS function that can be used as a convenient way to transform
XML with XSL, the XSL, an XML instance document and the JSON output. You
recreate the transformation in JS. Insert some start time end time vars and
see which is fastest. The biggest 'bottleneck' in XSL (client-side or
server-side) is building the XSL's XML document into a processor (or Templates
in java) object, I would either discard the initial transform as it has to
build the processor object or indicate it as an outlier that might have skewed
the result (at least in a multiple reuse situation).

I started to write the JS function after recent threads on JS and XSL (so take
with a grain of salt). For example:

var optons = {
   xslPath: "relative/path/to.xsl",
   xmlDoc: someXmlHttpResult
 },

 jsonWadlStr = lsb.asString(optons),
 jsonWadlElem = lsb.asElement(optons)
 jsonWadlDocument = lsb.asDocument(optons);

They take a simple JS object:

* xslPath: "relative/path/to.xsl"

* set either:
 - xmlDoc: a DOM Document
 - xmlPath: "relative/path/to.xml"
 - neither - when neither of the above two properties are sent, a cached empty
document is used as the main source of the transform. It is expected that you
would pass a param(s) indicated the path of document(s) to load with the
xsl:document function. This leads to better performance because you bypass
creating the W3 DOM document for the source XML.

* resetParams: sets the xsl's top level params back to their original state.
This is a slight performance hit in IE on processor load because you have to
query the XSL's DOM Document to find the original values. *If you do not do
this, the browser's processor object will use the last existing values from
the last or previous transformations*. This might be perfectly fine and
desirable even.




(strangely, this combination crashes Opera for me, but that is another issue)

The XML (a WADL document):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<application xmlns="http://research.sun.com/wadl/2006/10">
 <doc xmlns:jersey="http://jersey.dev.java.net/" jersey:generatedBy="Jersey:
1.1.5 01/20/2010 03:55 PM"/>
 <resources base="http://localhost:8080/lsb/rest/">
   <resource path="metadata">
     <resource path="lsb/metadata/{type}/{siteId}">
       <param xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"
style="template" name="siteId"/>
       <param xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"
style="template" name="type"/>
       <method name="GET" id="get">
         <response>
           <representation mediaType="application/xml"/>
         </response>
       </method>
     </resource>
   </resource>
   <resource path="index">
     <resource path="lsb/site.xml/{siteId}">
       <param xmlns:xs="http://www.w3.org/2001/XMLSchema" default="site_root"
type="xs:string" style="template" name="siteId"/>
       <method name="POST" id="indexSiteTree">
         <response>
           <representation mediaType="application/json"/>
         </response>
       </method>
     </resource>
   </resource>
   <resource path="site-tree">
     <resource path="lsb/site.xml/{siteId}">
       <param xmlns:xs="http://www.w3.org/2001/XMLSchema" default="site_root"
type="xs:string" style="template" name="siteId"/>
       <method name="GET" id="get">
         <response>
           <representation mediaType="application/json"/>
         </response>
       </method>
       <method name="PUT" id="put">
         <request>
           <representation mediaType="application/json"/>
         </request>
         <response>
           <representation mediaType="application/json"/>
         </response>
       </method>
     </resource>
   </resource>
 </resources>
</application>

The XSL:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 version="1.0"
 xmlns:x="http://www.w3.org/1999/xhtml"
 xmlns:wadl="http://research.sun.com/wadl/2006/10"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 exclude-result-prefixes="x wadl xs">

 <xsl:output method="text" omit-xml-declaration="yes" standalone="yes"
encoding="UTF-8"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/">
   <xsl:apply-templates/>
 </xsl:template>

 <xsl:template match="wadl:application">
   <xsl:text>{</xsl:text>
   <xsl:apply-templates/>
   <xsl:text>}</xsl:text>
 </xsl:template>

 <xsl:template match="wadl:resources">
   <xsl:text>"base":"</xsl:text>
   <xsl:value-of select="@base"/>
   <xsl:text>", "root-resources": {</xsl:text>
   <xsl:apply-templates select="wadl:resource" mode="root-resource"/>
   <xsl:text>}</xsl:text>
 </xsl:template>


 <xsl:template match="wadl:resource" mode="root-resource">
   <xsl:if test="not(position() = 1)">,</xsl:if>
   <xsl:text>"</xsl:text>
   <xsl:value-of select="@path"/>
   <xsl:text>": {</xsl:text>
   <xsl:apply-templates select="wadl:resource"/>
   <xsl:text>}</xsl:text>
 </xsl:template>

 <xsl:template match="wadl:resource">
   <xsl:if test="wadl:method">
     <xsl:if test="not(position() = 1)">,</xsl:if>
     <xsl:variable name="resource-path">
       <xsl:apply-templates select="." mode="resource-path"/>
     </xsl:variable>
     <xsl:text>"path": "</xsl:text>
     <xsl:value-of select="$resource-path"/>
     <xsl:text>", "params": [</xsl:text>
     <xsl:apply-templates select="wadl:param"/>
     <xsl:text>], "methods": {</xsl:text>
     <xsl:apply-templates select="wadl:method"/>
     <xsl:text>}</xsl:text>
   </xsl:if>
   <xsl:apply-templates select="wadl:resource"/>
 </xsl:template>

 <xsl:template match="wadl:param">
   <xsl:if test="not(position() = 1)">,</xsl:if>
   <xsl:text>{</xsl:text>
   <xsl:apply-templates select="@*" mode="attr-as-field"/>
   <xsl:text>}</xsl:text>
 </xsl:template>

 <xsl:template match="wadl:method">
   <xsl:if test="not(position() = 1)">,</xsl:if>
   <xsl:text>"</xsl:text>
   <xsl:value-of select="@name"/>
   <xsl:text>": {</xsl:text>
   <xsl:text>"request": [</xsl:text>
   <xsl:choose>
     <xsl:when test="wadl:request">
       <xsl:apply-templates select="wadl:request"/>
     </xsl:when>
     <xsl:otherwise>"application/x-www-form-urlencoded"</xsl:otherwise>
   </xsl:choose>

   <xsl:text>], "response": [</xsl:text>
   <xsl:apply-templates select="wadl:response"/>
   <xsl:text>]}</xsl:text>
 </xsl:template>

 <xsl:template match="wadl:response | wadl:request">
   <xsl:apply-templates/>
 </xsl:template>

 <xsl:template match="wadl:representation">
   <xsl:if test="not(position() = 1)">,</xsl:if>
   <xsl:text>"</xsl:text>
   <xsl:value-of select="@mediaType"/>
   <xsl:text>"</xsl:text>
 </xsl:template>

 <xsl:template match="wadl:resource" mode="resource-path">
   <xsl:apply-templates select="parent::wadl:resource" mode="resource-path"/>
   <xsl:if test="not(parent::wadl:resources)">
     <xsl:text>/</xsl:text>
   </xsl:if>
   <xsl:value-of select="@path"/>
 </xsl:template>

 <xsl:template match="@*" mode="attr-as-field">
   <xsl:if test="not(position() = 1)">,</xsl:if>
   <xsl:text>"</xsl:text>
   <xsl:value-of select="local-name()"/>
   <xsl:text>":</xsl:text>
   <xsl:text>"</xsl:text>
   <xsl:value-of select="."/>
   <xsl:text>"</xsl:text>
 </xsl:template>

</xsl:stylesheet>



The JavaScript:

// set the namepsace for the JS
var lsb = {};


if ((document.implementation === null &&
document.implementation.createDocument === null)) {

var ieUtil = (function() {
 var ieImpls = {
   xsl: ["Msxml2.XSLTemplate.6.0", "MSXML2.XSLTemplate.3.0"],
   ftDom: ["MSXML2.FreeThreadedDOMDocument.6.0",
"MSXML2.FreeThreadedDOMDocument.3.0"],
   dom: ["Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.3.0",
"MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"],
   http: ["Msxml2.XMLHTTP.6.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP",
"Microsoft.XMLHTTP"]/*,
   writer: ["Msxml2.MXXMLWriter.6.0", "Msxml2.MXXMLWriter.3.0",
"MSXML2.MXXMLWriter", "MSXML.MXXMLWriter", "Microsoft.XMLDOM"]*/
 };

 return {
   newInstance: function(key) {
     var i, a = ieImpls[key],
       exists = false,
       impl,
       version;
     for(i=0; i < a.length && !exists; i++){
       try{
         impl = new ActiveXObject(a[i]);
         version = a[i];
         exists = true;
       }catch (e){}
     }
     if (!exists) {
       alert("Transformat cannot continue. Could not find IE ActiveX
implementation for: " + key);
       return null;
     }
     return new ActiveXObject(version);
   }
 };
})();

}



lsb.xslt = (function() {

 var isModern = (!(document.implementation === null &&
document.implementation.createDocument === null)),

   cache = {},

   cachedDocument = isModern ? document.implementation.createDocument("",
"default", null) : ieUtil.newInstance("dom"),

   setParam = function(processor, xmlns, name, value) {
    if (isModern) {
      processor.setParameter(xmlns, name, value);
    } else {
      processor.setParameter(name, value);
    }
   },

   setParams = function(processor, params) {
     if (!params) { return; }
     var paramName, val;
//console.log("setParams params: ", params);
     for (paramName in params) {
       if (params.hasOwnProperty(paramName)) {
         val = params[paramName];
 //console.log("setParams " + paramName + ": ", val);
         if (val) {
           if (val.constructor.toString().indexOf("Array") === -1) {
             setParam(processor, val[0], paramName, val[1]);
           } else {
             setParam(processor, null, paramName, val);
           }
         }
       }
     }
   },

   getModernProc = function(xslPath) {
     var processor = new XSLTProcessor(),
       xmlhttp = new XMLHttpRequest();
     xmlhttp.open("GET", xslPath, false);
     xmlhttp.send('');
     processor.importStylesheet(xmlhttp.responseXML);
     cache[xslPath] = {processor: processor};
     return processor;
   },

   loadOriginalParams = function(xslDoc) {
     xslDoc.setProperty("SelectionLanguage", "XPath");
     xslDoc.setProperty("SelectionNamespaces",
"xmlns:xsl='http://www.w3.org/1999/XSL/Transform'");
     var xslParams = xslDoc.selectNodes("/xsl:stylesheet/xsl:param"),
       i, paramElem,
       origParams = {};
     if (xslParams.length > 0) {
       for (i=0; i < xslParams.length; i++) {
         paramElem = xslParams[i];
         origParams[paramElem.getAttribute("name")] =
paramElem.getAttribute("select");
       }
     }
     return origParams;
   },

   getIeProc = function(xslPath) {
     var xslDoc = ieUtil.newInstance("ftDom"),
       xslt = ieUtil.newInstance("xsl"),
       processor;

     xslDoc.async = false;
     xslDoc.load(xslPath);
     xslt.stylesheet = xslDoc;
     processor = xslt.createProcessor();

     cache[xslPath] = {
       processor: processor,
       params: loadOriginalParams(xslDoc)
     };
   },


   getProcessor = function(xslPath, params, resetParams) {
//console.log("getProcessor xslPath: ", xslPath);
     if (!xslPath) {
       alert("Transformation cannot proceed without a path to the XSL.");
       return null;
     }

     var templates = cache[xslPath],
       processor = (templates === undefined || templates === null) ? null :
templates.processor;

     if (!processor) {
       if (isModern) {
         processor = getModernProc(xslPath);
       } else {
         processor = getIeProc(xslPath);
       }
     } else if (resetParams) {
       if (isModern) {
         processor.clearParameters();
       } else {
        var entry = cache[xslPath],
          propName,
          origParams = entry.params;
        if (origParams) {
          for (propName in origParams) {
            if (params.hasOwnProperty(propName)) {
              processor.addParameter(propName, origParams[propName]);
            }
          }
        }
      }
     }

     setParams(processor, params);

     return processor;
   };
//console.log("lsb.xslt this:", this);
 return {

  asDocument: function(options) {
    var proc = getProcessor(options.xslPath, options.params,
(options.resetParams || false)),
      inDoc = options.xmlDoc || options.xmlPath || cachedDocument,
      outDoc = null;
    if (isModern) {
      outDoc = proc.transformToDocument(inDoc);
    } else {
      proc.input = inDoc;
      outDoc = ieUtil.newInstance("dom");
      proc.output = outDoc;
      proc.transform();
    }
    return outDoc;
  },

  asElement: function(options) {
    var proc = getProcessor(options.xslPath, options.params,
(options.resetParams || false)),
      inDoc = options.xmlDoc || options.xmlPath || cachedDocument,
      outDoc = null,
      outElem = null;
    if (isModern) {
      outElem = proc.transformToFragment(inDoc, (options.ownerDocument ||
cachedDocument));
    } else {
      proc.input = inDoc;
      outDoc = ieUtil.newInstance("dom");
      proc.output = outDoc;
      proc.transform();
      outElem = outDoc.documentElement;
    }
    return outElem;
  },

  asString: function(options) {
//console.log("lsb.xslt.asString options: ", options);
    var proc = getProcessor(options.xslPath, options.params,
(options.resetParams || false)),
      inDoc = options.xmlDoc || options.xmlPath || cachedDocument;
//console.log("lsb.xslt.asString proc: ", proc);
//console.log("lsb.xslt.asString inDoc: ", inDoc);
    if (isModern) {
      return (new
XMLSerializer()).serializeToString(proc.transformToFragment(inDoc,
document));
    } else {
      proc.input = inDoc;
      proc.transform();
      return proc.output;
    }
  }
 };
})();





On Feb 7, 2010, at 2:03 PM, Michael Kay wrote:

>
> When comparing the performance of a high-level language (XSLT) to a
> lower-level language (Javascript), the first rule is that the ratio depends
> more than anything else on the skill of the programmer writing in the
> lower-level language. Unless you factor that as a variable into your
> comparison, the results are fairly meaningless.
>
> Regards,
>
> Michael Kay
> http://www.saxonica.com/
> http://twitter.com/michaelhkay
>
>> -----Original Message-----
>> From: Rob Belics [mailto:rob_belics@xxxxxxxxxxx]
>> Sent: 07 February 2010 21:38
>> To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
>> Subject: [xsl] XSLT vs javascript performance
>>
>> I'm wondering if anyone has any information on performance of
>> xslt transformation speed in the browser vs letting
>> javascript work on the DOM from data fetched from the web.
>> For example, if the browser already has the xslt file or
>> javascript code and some xml data is fetched from a remote
>> server over the internet due to the user clicking on a link.
>> Which could finish processing that data faster?
>>
>> After some Googling, I've only found one article that claims
>> xslt would be "many times faster" but without any reason for
>> saying that. Browsers, recently, have sped up performance of
>> their javascript engines quite a bit. Particularly Firefox,
>> Webkit and, now, Opera.


Current Thread
Keywords