Table of Contents |
Recipe 12.9. Generating XSLT from XSLTProblemYou want to generate XSLT from a different XML representation. Alternatively, you want to transform XSLT or pseudo-XSLT into real XSLT. SolutionTwo 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 < 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>
</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 < 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 >= 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 >= 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> DiscussionThe 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:
See AlsoSee Oliver Becker's XSLT loop compiler for a similar example that also validates (http://www.informatik.hu-berlin.de/~obecker/XSLT/#loop-compiler). |
Table of Contents |