Recipe12.9.Generating XSLT from XSLT


Recipe 12.9. Generating XSLT from XSLT

Problem

You want to generate XSLT from a different XML representation. Alternatively, you want to transform XSLT or pseudo-XSLT into real XSLT.

Solution

Two things about the control structure of XSLT sometimes annoy me. The first is the absence of an if-then-elsif-else construct; the second is the absence of a true looping construct. Of course, I am aware of xsl:choose and xsl:for-each, but each is lacking to some extent. I find xsl:choose annoying because the choose element serves practically no function, except to force an extra level of nesting. The xsl:for-each is not really a looping construct but an iteration construct. To emulate loops with counters, you have to use recursion or the Piez method (see Recipe 2.5), which is awkward.

This example illustrates an XSLT-to-XSLT generation by pretending that XSLT has the elements xslx:elsif, xslx:else, and xslx:loop. Since it really does not, you will create a stylesheet that generates true XSLT from the following pseudo-XSLT. Having an xsl:if and an xslx:if is awkward, but it would be wrong to use the standard XSLT namespace for your extended elements; these elements might be defined in standard XSLT someday:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  xmlns:xslx="http://www.ora.com/XSLTCookbook/ExtendedXSLT" >     <xsl:output method="text"/>       <xsl:template match="foo">   <xslx:if test="bar">     <xsl:text>You often will find a bar in the neighborhood of foo!</xsl:text>   </xslx:if>   <xslx:elsif test="baz">     <xsl:text>A baz is a sure sign of geekdom.</xsl:text>   </xslx:elsif>   <xslx:else>     <xslx:loop param="i" init="0" test="$i &lt; 10" incr="1">       <xsl:text>Hmmm, nothing to say here but I'll say it 10 times.</xsl:text>     </xslx:loop>   </xslx:else>   <xslx:loop param="i" init="10" test="$i >= 0" incr="-1">     <xslx:loop param="j" init="10" test="$j >= 0" incr="-1">       <xsl:text>&#xa;</xsl:text>       <xsl:value-of select="$i * $j"/>     </xslx:loop>   </xslx:loop>   <xslx:if test="foo">        <xsl:text>foo foo! Nobody says foo foo!</xsl:text>   </xslx:if>   <xslx:else>        <xsl:text>Well, okay then!</xsl:text>   </xslx:else> </xsl:template>     </xsl:stylesheet>

Here is a transformation that generates true XSLT from pseudo-XSLT. To keep things simple, this example does not include semantic checking such as checks for multiple xsl:else clauses with a single xsl:if or checks for duplication of parameters in nested loops:

<xsl:stylesheet version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  xmlns:xslx="http://www.ora.com/XSLTCookbook/ExtendedXSLT"  xmlns:xso="dummy" >     <!-- Reuse the identity transform to copy --> <!-- regular XSLT form source to destination --> <xsl:import href="../util/copy.xslt"/>     <!-- DO NOT let the processor do the formatting via indent = yes --> <!-- Because this could screw up xsl:text nodes                  --> <xsl:output method="xml" version="1.0" encoding="UTF-8" />     <!--We use xso as a alias when we need to output literal xslt elements --> <xsl:namespace-alias stylesheet-prefix="xso" result-prefix="xsl"/>       <xsl:template match="xsl:stylesheet | xsl:transform">    <xso:stylesheet>     <!--The first pass handles the if-elsif-else translation -->     <!--and the conversion of xslx:loop to named template calls -->     <xsl:apply-templates select="@* | node( )"/>          <!--The second pass handles the conversion of xslx:loop -->     <!-- to recusive named templates -->     <xsl:apply-templates mode="loop-body" select="//xslx:loop"/>         </xso:stylesheet> </xsl:template>          <!--We look for xslx:if's that have matching xslx:elsif or xslx:else --> <xsl:template match="xslx:if[following-sibling::xslx:else or                               following-sibling::xslx:elsif]">   <xsl:variable name="idIf" select="generate-id( )"/>   <xso:choose>     <xso:when test="{@test}">       <xsl:apply-templates select="@* | node( )"/>     </xso:when>     <!-- We process the xsl:eslif and xslx:else in a special mode -->     <!-- as part of the xsl:choose. We must make sure to only pick -->     <!-- up the ones whose preceding xslx:if is this xslx:if -->     <xsl:apply-templates       select="following-sibling::xslx:else[                       generate-id(preceding-sibling::xslx:if[1]) = $idIf] |               following-sibling::xslx:elsif[                       generate-id(preceding-sibling::xslx:if[1]) = $idIf]"       mode="choose"/>   </xso:choose> </xsl:template>     <!--Ignore xslx:elsif and xslx:else in normal mode --> <xsl:template match="xslx:elsif | xslx:else"/>     <!--An xslx:elsif becomes a xsl:when --> <xsl:template match="xslx:elsif"  mode="choose">  <xso:when test="{@test}">    <xsl:apply-templates select="@* | node( )"/>  </xso:when> </xsl:template>     <!--An xslx:else becomes a xsl:otherwise --> <xsl:template match="xslx:else" mode="choose">  <xso:otherwise>    <xsl:apply-templates/>  </xso:otherwise> </xsl:template>         <!-- An xslx:loop becomes a call to a named template --> <xsl:template match="xslx:loop">   <!-- Each template is given the name loop-N where N is position -->   <!-- of this loop relative to previous loops at any level -->   <xsl:variable name="name">     <xsl:text>loop-</xsl:text>     <xsl:number count="xslx:loop" level="any"/>   </xsl:variable>       <xso:call-template name="{$name}">     <xsl:for-each select="ancestor::xslx:loop">       <xso:with-param name="{@param}" select="${@param}"/>     </xsl:for-each>     <xso:with-param name="{@param}" select="{@init}"/>   </xso:call-template>     </xsl:template>     <!-- Mode loop-body is used on the 2nd pass. --> <!-- Here recursive templates are generated to do the looping.  -->     <xsl:template match="xslx:loop" mode="loop-body">   <xsl:variable name="name">     <xsl:text>loop-</xsl:text>     <xsl:value-of select="position( )"/>   </xsl:variable>       <xso:template name="{$name}">     <!--If this loop is nested in another it must -->     <!--"see" the outer loop parameters so we generate these here -->     <xsl:for-each select="ancestor::xslx:loop">       <xso:param name="{@param}"/>     </xsl:for-each>     <!--The local loop parameter -->     <xso:param name="{@param}"/>     <!--Generate the recursion control test -->     <xso:if test="{@test}">       <!-- Apply template in normal mode to handle -->       <!-- calls to nested loops while copying everything else. -->       <xsl:apply-templates/>       <!--This is the recursive call that applies -->       <!--the incr to the loop param -->       <xso:call-template name="{$name}">         <xsl:for-each select="ancestor::xslx:loop">           <xso:with-param name="{@param}" select="${@param}"/>         </xsl:for-each>         <xso:with-param name="{@param}" select="${@param} + {@incr}"/>       </xso:call-template>     </xso:if>   </xso:template> </xsl:template>     </xsl:stylesheet>

Here is the result of the generation:

<xso:stylesheet xmlns:xso="http://www.w3.org/1999/XSL/Transform" xmlns:xsl="http:// www.w3.org/1999/XSL/Transform" xmlns:xslx="http://www.ora.com/XSLTCookbook/ ExtendedXSLT" version="1.0">   <xsl:output method="text"/>   <xsl:template match="foo">     <xso:choose>       <xso:when test="bar">         <xsl:text>You often will find a bar in the neighborhood of foo!</xsl:text>       </xso:when>       <xso:when test="baz">         <xsl:text>A baz is a sure sign of geekdom.</xsl:text>       </xso:when>       <xso:otherwise>         <xso:call-template name="loop-1">           <xso:with-param name="i" select="0"/>         </xso:call-template>       </xso:otherwise>     </xso:choose>     <xso:call-template name="loop-2">       <xso:with-param name="i" select="10"/>     </xso:call-template>     <xso:choose>       <xso:when test="foo">         <xsl:text>foo foo! Nobody says foo foo!</xsl:text>       </xso:when>       <xso:otherwise>         <xsl:text>Well, okay then!</xsl:text>       </xso:otherwise>     </xso:choose>   </xsl:template>   <xso:template name="loop-1">     <xso:param name="i"/>     <xso:if test="$i &lt; 10">       <xsl:text>Hmmm, nothing to say here but I'll say it 10 times.</xsl:text>       <xso:call-template name="loop-1">         <xso:with-param name="i" select="$i + 1"/>       </xso:call-template>     </xso:if>   </xso:template>   <xso:template name="loop-2">     <xso:param name="i"/>     <xso:if test="$i &gt;= 0">       <xso:call-template name="loop-3">         <xso:with-param name="i" select="$i"/>         <xso:with-param name="j" select="10"/>       </xso:call-template>       <xso:call-template name="loop-2">         <xso:with-param name="i" select="$i + -1"/>       </xso:call-template>     </xso:if>   </xso:template>   <xso:template name="loop-3">     <xso:param name="i"/>     <xso:param name="j"/>     <xso:if test="$j &gt;= 0">       <xsl:text> </xsl:text>       <xsl:value-of select="$i * $j"/>       <xso:call-template name="loop-3">         <xso:with-param name="i" select="$i"/>         <xso:with-param name="j" select="$j + -1"/>       </xso:call-template>     </xso:if>   </xso:template> </xso:stylesheet>

Discussion

The xsl:namespace-alias element is the key to generating XSLT with XSLT. Without it, the processor would not be able to distinguish actual XSLT content from content that is meant to be output as literal result elements. Generation of XSLT with XSLT is useful in more contexts then this recipe will cover. Some additional examples include:


Facilitation of literate programming

Literate programming embeds code fragments in human-readable documentation (rather than the usual reverse situation) so that information is presented in the order that best suits people, rather than the order that best suits compilers.


Provision of conditional includes/imports

If this feature were in XSLT, it would probably require awkward extensions to the processing model akin to a C program's preprocessor.


Enabling the dynamic evaluation of XPaths

This category refers to a stylesheet that generates XPaths from an import source and embeds them in another stylesheet that evaluates them statically. The extra level of indirection thus emulates dynamic behavior. Often those XPaths are embedded in a document. For example, you might see a table specification like:

<table of="person">   <column label="Firstname" content="name/firstname" />   <column label="Surname" content="name/surname" />   <column label="Age" content="@age" type="number" />   <sort select="Surname, Firstname, Age" />  </table>

It is easier to generate the table described by this XMLby generating XSLT from the table specification and then running that XSLT over the datathan it is to interpret the table specification within the same stylesheet you use to process the data.

See Also

See Oliver Becker's XSLT loop compiler for a similar example that also validates (http://www.informatik.hu-berlin.de/~obecker/XSLT/#loop-compiler).




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net