xsl-list
[Top] [All Lists]

Re: [xsl] XSLT vs javascript performance

2010-02-07 20:34:39
(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(_at_)charter(_dot_)net] 
Sent: 07 February 2010 21:38
To: xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com
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.


--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: 
<mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--



--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: 
<mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--



--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--

<Prev in Thread] Current Thread [Next in Thread>