You want to generate XSLT from a different XML representation. Alternatively, you want to transform XSLT or pseudo-XSLT into real XSLT.
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 1.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 some day:
<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 destiniation --> <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 outter 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 recusion 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>
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:
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.
If this feature were in XSLT, it would probably require awkward extensions to the processing model akin to a C program’s preprocessor.
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 XML—by generating XSLT from the table specification and then running that XSLT over the data—than it is to interpret the table specification within the same stylesheet you use to process the data.
See Oliver Becker’s XSLT loop compiler for a similar example that also validates (http://www.informatik.hu-berlin.de/~obecker/XSLT/#loop-compiler).