Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a technique to combine a pipeline of XSL transformations into a single transformation?

I have written an application which uses a pipeline of 15 XSL stylesheets, and I'm beginning to work on tuning its performance. It's designed to be portable, so that it can be run in both the web browser environment, and on the desktop. On the desktop, I think it may make sense to keep the stylesheets separated out as a pipeline of multiple transformations, as this allows each individual transformation to be run in its own thread, which can be very efficient on CPUs with multiple cores. However, not only is the browser environment is single-threaded, in most browsers, the XSL processing API exposed to JavaScript requires parsing the result of each individual transformation back into a DOM object, which seems inefficient. I think it would therefore be advantageous to combine all of the stylesheets into a single stylesheet when running in the context of the browser environment, if that is possible. I have an idea of how this may be accomplished with exsl:node-set (which most browsers support), but it's not clear to me if the technique I'm imagining is generalizable. Is there a general technique for transforming a pipeline of XSL stylesheets into a single XSL stylesheet, such that the semantics of the complete pipeline are preserved? An automated solution would be ideal.

like image 637
jbeard4 Avatar asked Nov 05 '22 06:11

jbeard4


1 Answers

There is a technique that allows independent transformations to be chained together where the output of the k-th transformation is the input of the (k+1)-th transformation.

Here is a simple example:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ext="http://exslt.org/common"
    exclude-result-prefixes="ext xsl">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="node()|@*" name="identity">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="/">
  <xsl:variable name="vrtfPass1">
   <xsl:apply-templates select="node()"/>
  </xsl:variable>

  <xsl:apply-templates mode="pass2"
   select="ext:node-set($vrtfPass1)/node()"/>
 </xsl:template>

 <xsl:template match="/*">
     <xsl:copy>
       <xsl:copy-of select="@*"/>
       <one/>
     <xsl:apply-templates/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="node()|@*" mode="pass2">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*" mode="pass2"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="/*/one" mode="pass2" >
     <xsl:call-template name="identity"/>
      <two/>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the following XML document:

<doc/>

the wanted result (the first pass addds the element <one/> as a child of the top element, then the second pass adds another child, , immediately after the element` that was created in the first pass) is produced:

<doc>
   <one/>
   <two/>
</doc>

There is a very suitable template/function in FXSL to do this: this is the compose-flist template. It takes as parameters an initial data argument and N functions (templates) and produces the chained composition of these functions/templates.

Here is the test example from the FXSL library:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:myFun1="f:myFun1"
xmlns:myFun2="f:myFun2" 
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="xsl f ext myFun1 myFun2"
>
  <xsl:import href="compose.xsl"/>
  <xsl:import href="compose-flist.xsl"/>

  <!-- to be applied on any xml source -->

  <xsl:output method="text"/>
  <myFun1:myFun1/>
  <myFun2:myFun2/>


  <xsl:template match="/">

    <xsl:variable name="vFun1" select="document('')/*/myFun1:*[1]"/>
    <xsl:variable name="vFun2" select="document('')/*/myFun2:*[1]"/>
    Compose:
    (*3).(*2) 3 = 
    <xsl:call-template name="compose">
      <xsl:with-param name="pFun1" select="$vFun1"/>
      <xsl:with-param name="pFun2" select="$vFun2"/>
      <xsl:with-param name="pArg1" select="3"/>
    </xsl:call-template>

    <xsl:variable name="vrtfParam">
      <xsl:copy-of select="$vFun1"/>
      <xsl:copy-of select="$vFun2"/>
      <xsl:copy-of select="$vFun1"/>
    </xsl:variable>

    Multi Compose:
    (*3).(*2).(*3) 2 = 
    <xsl:call-template name="compose-flist">
      <xsl:with-param name="pFunList" select="ext:node-set($vrtfParam)/*"/>
      <xsl:with-param name="pArg1" select="2"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="myFun1:*" mode="f:FXSL">
    <xsl:param name="pArg1"/>

    <xsl:value-of select="3 * $pArg1"/>
  </xsl:template>

  <xsl:template match="myFun2:*" mode="f:FXSL">
    <xsl:param name="pArg1"/>

    <xsl:value-of select="2 * $pArg1"/>
  </xsl:template>
</xsl:stylesheet>

when this transformation is applied on any xml document (not used), the wanted, correct result is produced:

Compose:
(*3).(*2) 3 = 
18

Multi Compose:
(*3).(*2).(*3) 2 = 
36

Do note: In XSLT 2.0 and later no xxx:node-set() extension is necessary, and any of the chained transformations can be contained in a real function.

like image 157
Dimitre Novatchev Avatar answered Nov 17 '22 08:11

Dimitre Novatchev