We've emulated a for loop now, but what about a stylesheet that generates another stylesheet that emulates the for loop? As we beat this dead horse one more time, we'll create a stylesheet that generates the iteration for us, along with an XML syntax that automates the process.
Here's the XML template we'll use to generate the stylesheet:
<?xml version="1.0"?> <html xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <head> <title>Text generated by our for loop processor</title> </head> <body> <h1>Text generated by our for loop processor</h1> <table border="1"> <tr> <th>Iteration #</th> <th>Value of <i>i</i></th> </tr> <for-loop index-variable="0" increment="1" operator="<=" test-value="10"> <tr> <td align="center"> <xsl:value-of select="$iteration"/> </td> <td align="center"> <xsl:value-of select="$i"/> </td> </tr> </for-loop> </table> </body> </html>
The design of our stylesheet-generating stylesheet is as follows:
Output the <xsl:stylesheet> element.
Generate the for-loop template. This will be a named template that we'll invoke while processing the rest of the document.
Generate the root element template. To do this, everything except the <for-loop> element is copied to the output document. The <for-loop> element will be converted into a call to the for-loop template we generated in the previous step.
Close out the <xsl:stylesheet> element.
There are a couple of complications in producing our stylesheet-generating stylesheet. First, we need to have some way to distinguish among the XSLT elements in the stylesheet being processed and the XSLT elements we're generating. Here's one way to do it:
<xsl:element name="xsl:template" namespace="http://www.w3.org/1999/XSL/Transform"> <xsl:attribute name="name">for-loop</xsl:attribute> <xsl:element name="xsl:param" namespace="http://www.w3.org/1999/XSL/Transform"> <xsl:attribute name="name">i</xsl:attribute> <xsl:attribute name="select"> <xsl:value-of select="@index-variable"/> </xsl:attribute> </xsl:element> <xsl:element name="xsl:param" namespace="http://www.w3.org/1999/XSL/Transform"> <xsl:attribute name="name">increment</xsl:attribute> <xsl:attribute name="select"> <xsl:value-of select="@increment"/> </xsl:attribute> </xsl:element> <xsl:element name="xsl:param" namespace="http://www.w3.org/1999/XSL/Transform"> <xsl:attribute name="name">operator</xsl:attribute> <xsl:attribute name="select"> <xsl:text>'</xsl:text> <xsl:value-of select="@operator"/> <xsl:text>'</xsl:text> </xsl:attribute> </xsl:element> <xsl:element name="xsl:param" namespace="http://www.w3.org/1999/XSL/Transform"> <xsl:attribute name="name">testValue</xsl:attribute> <xsl:attribute name="select"> <xsl:value-of select="@test-value"/> </xsl:attribute> </xsl:element> <xsl:element name="xsl:param" namespace="http://www.w3.org/1999/XSL/Transform"> <xsl:attribute name="name">iteration</xsl:attribute> <xsl:attribute name="select">1</xsl:attribute> </xsl:element> ...
This lengthy listing generates this simple XML fragment:
<ns1:template name="for-loop"> <ns1:param name="i" select="0"/> <ns1:param name="increment" select="1"/> <ns1:param name="operator" select="'<='"/> <ns1:param name="testValue" select="10"/> <ns1:param name="iteration" select="1"/> ...
This approach works, but we're doing an awful lot of work to create some fairly simple elements. For all the XSLT elements we're generating with <xsl:element> elements, we have to declare the namespace for each one. The obvious way of handling this would be to generate a namespace declaration on the <xsl:stylesheet> element:
<xsl:attribute name="xmlns:xsl"> http://www.w3.org/1999/XSL/Transform </xsl:attribute>
Unfortunately, the XSLT specification states (in section 7.1.3) that this isn't legal. What we did in our previous example was add the namespace attribute to all XSLT elements we need to generate. (The XSLT processor is not required to use the namespace prefix we specified in the <xsl:element>, by the way.) To help us get around this awkward problem, the XSLT specification provides the <xsl:namespace-alias> element. This provision allows us to define an alias for the XSLT namespace (or any other namespace we want to use); we'll use the normal XSLT namespace for the stylesheet elements we use, and we'll use the alias for the stylesheet elements we generating. Here's how our new stylesheet looks:
<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xslout="can be anything, doesn't matter"> <xsl:output method="xml" indent="yes"/> <xsl:namespace-alias stylesheet-prefix="xslout" result-prefix="xsl"/> <xsl:template match="*|@*|text()|comment()|processing-instruction()"> <xsl:copy> <xsl:apply-templates select="*|@*|text()|comment()|processing-instruction()"/> </xsl:copy> </xsl:template> <xsl:template match="for-loop"> <xslout:call-template name="for-loop"> <xslout:with-param name="i" select="{@index-variable}"/> <xslout:with-param name="increment" select="{@increment}"/> <xslout:with-param name="operator"> <xsl:attribute name="select"> <xsl:text>'</xsl:text> <xsl:value-of select="@operator"/> <xsl:text>'</xsl:text> </xsl:attribute> </xslout:with-param> <xslout:with-param name="testValue" select="{@test-value}"/> </xslout:call-template> </xsl:template> <xsl:template match="for-loop" mode="generate-template"> <xslout:variable name="newline"> <xslout:text> </xslout:text> </xslout:variable> <xslout:template name="for-loop"> <xslout:param name="i" select="@index-variable"/> <xslout:param name="increment" select="@increment"/> <xslout:param name="operator" select="@operator"/> <xslout:param name="testValue" select="@test-value"/> <xslout:param name="iteration" select="1"/> <xslout:variable name="testPassed"> <xslout:choose> <xslout:when test="starts-with($operator, '!=')"> <xslout:if test="$i != $testValue"> <xslout:text>true</xslout:text> </xslout:if> </xslout:when> <xslout:when test="starts-with($operator, '<=')"> <xslout:if test="$i <= $testValue"> <xslout:text>true</xslout:text> </xslout:if> </xslout:when> <xslout:when test="starts-with($operator, '>=')"> <xslout:if test="$i >= $testValue"> <xslout:text>true</xslout:text> </xslout:if> </xslout:when> <xslout:when test="starts-with($operator, '=')"> <xslout:if test="$i = $testValue"> <xslout:text>true</xslout:text> </xslout:if> </xslout:when> <xslout:when test="starts-with($operator, '<')"> <xslout:if test="$i < $testValue"> <xslout:text>true</xslout:text> </xslout:if> </xslout:when> <xslout:when test="starts-with($operator, '>')"> <xslout:if test="$i > $testValue"> <xslout:text>true</xslout:text> </xslout:if> </xslout:when> <xslout:otherwise> <xslout:message terminate="yes"> <xslout:text>Sorry, the for-loop emulator only </xslout:text> <xslout:text>handles six operators </xslout:text> <xslout:value-of select="$newline"/> <xslout:text>(< | > | = | <= | >= | !=). </xslout:text> <xslout:text>The value </xslout:text> <xslout:value-of select="$operator"/> <xslout:text> is not allowed.</xslout:text> <xslout:value-of select="$newline"/> </xslout:message> </xslout:otherwise> </xslout:choose> </xslout:variable> <xslout:if test="$testPassed='true'"> <xslout:comment>From your stylesheet:</xslout:comment> <xsl:apply-templates select="*"/> <xslout:comment>End of text from your stylesheet</xslout:comment> <xslout:call-template name="for-loop"> <xslout:with-param name="i" select="$i + $increment"/> <xslout:with-param name="increment" select="$increment"/> <xslout:with-param name="operator" select="$operator"/> <xslout:with-param name="testValue" select="$testValue"/> <xslout:with-param name="iteration" select="$iteration + 1"/> </xslout:call-template> </xslout:if> </xslout:template> </xsl:template> <xsl:template match="/"> <xslout:stylesheet version="1.0"> <xsl:apply-templates select="//for-loop" mode="generate-template"/> <xslout:template match="/"> <xsl:apply-templates select="*"/> </xslout:template> </xslout:stylesheet> </xsl:template> </xsl:stylesheet>
Throughout our stylesheet, we used the usual xsl namespace for the stylesheet elements we use, and the xslout namespace for the stylesheet elements we generate. Notice that though we define the xslout namespace on the <xsl:namespace-alias> element, we still have to declare it on the <xsl:stylesheet> element. Also note that the value we define for the xslout namespace doesn't matter; the value referred to by the <xsl:namespace-alias> is used instead.
Here is the stylesheet generated by our stylesheet-generating stylesheet:
<?xml version="1.0" encoding="UTF-8"?> <xslout:stylesheet xmlns:xslout="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xslout:variable name="newline"> <xslout:text/> </xslout:variable> <xslout:template name="for-loop"> <xslout:param select="@index-variable" name="i"/> <xslout:param select="@increment" name="increment"/> <xslout:param select="@operator" name="operator"/> <xslout:param select="@test-value" name="testValue"/> <xslout:param select="1" name="iteration"/> <xslout:variable name="testPassed"> <xslout:choose> <xslout:when test="starts-with($operator, '!=')"> <xslout:if test="$i != $testValue"> <xslout:text>true</xslout:text> </xslout:if> </xslout:when> <xslout:when test="starts-with($operator, '<=')"> <xslout:if test="$i <= $testValue"> <xslout:text>true</xslout:text> </xslout:if> </xslout:when> <xslout:when test="starts-with($operator, '>=')"> <xslout:if test="$i >= $testValue"> <xslout:text>true</xslout:text> </xslout:if> </xslout:when> <xslout:when test="starts-with($operator, '=')"> <xslout:if test="$i = $testValue"> <xslout:text>true</xslout:text> </xslout:if> </xslout:when> <xslout:when test="starts-with($operator, '<')"> <xslout:if test="$i < $testValue"> <xslout:text>true</xslout:text> </xslout:if> </xslout:when> <xslout:when test="starts-with($operator, '>')"> <xslout:if test="$i > $testValue"> <xslout:text>true</xslout:text> </xslout:if> </xslout:when> <xslout:otherwise> <xslout:message terminate="yes"> <xslout:text>Sorry, the for-loop emulator only </xslout:text> <xslout:text>handles six operators </xslout:text> <xslout:value-of select="$newline"/> <xslout:text>(< | > | = | <= | >= | !=). </xslout:text> <xslout:text>The value </xslout:text> <xslout:value-of select="$operator"/> <xslout:text> is not allowed.</xslout:text> <xslout:value-of select="$newline"/> </xslout:message> </xslout:otherwise> </xslout:choose> </xslout:variable> <xslout:if test="$testPassed='true'"> <xslout:comment>From your stylesheet:</xslout:comment> <tr xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <td align="center"> <xsl:value-of select="$iteration"/> </td> <td align="center"> <xsl:value-of select="$i"/> </td> </tr> <xslout:comment>End of text from your stylesheet</xslout:comment> <xslout:call-template name="for-loop"> <xslout:with-param select="$i + $increment" name="i"/> <xslout:with-param select="$increment" name="increment"/> <xslout:with-param select="$operator" name="operator"/> <xslout:with-param select="$testValue" name="testValue"/> <xslout:with-param select="$iteration + 1" name="iteration"/> </xslout:call-template> </xslout:if> </xslout:template> <xslout:template match="/"> <html xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <head> <title>Text generated by our for loop processor</title> </head> <body> <h1>Text generated by our for loop processor</h1> <table border="1"> <tr> <th>Iteration #</th> <th>Value of <i>i</i> </th> </tr> <xslout:call-template name="for-loop"> <xslout:with-param select="0" name="i"/> <xslout:with-param select="1" name="increment"/> <xslout:with-param name="operator" select="'<='"/> <xslout:with-param select="10" name="testValue"/> </xslout:call-template> </table> </body> </html> </xslout:template> </xslout:stylesheet>
When we execute the generated stylesheet, it produces the following HTML document. (When rendered in a browser, the document generated by the stylesheet generated by our other stylesheet looks like Figure 4-1.)
<html> <head> <META http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Text generated by our for loop processor</title> </head> <body> <h1>Text generated by our for loop processor</h1> <table border="1"> <tr> <th>Iteration #</th><th>Value of <i>i</i></th> </tr> <tr> <td align="center">1</td><td align="center">0</td> </tr> <tr> <td align="center">2</td><td align="center">1</td> </tr> <tr> <td align="center">3</td><td align="center">2</td> </tr> <tr> <td align="center">4</td><td align="center">3</td> </tr> <tr> <td align="center">5</td><td align="center">4</td> </tr> <tr> <td align="center">6</td><td align="center">5</td> </tr> <tr> <td align="center">7</td><td align="center">6</td> </tr> <tr> <td align="center">8</td><td align="center">7</td> </tr> <tr> <td align="center">9</td><td align="center">8</td> </tr> <tr> <td align="center">10</td><td align="center">9</td> </tr> <tr> <td align="center">11</td><td align="center">10</td> </tr> </table> </body> </html>
Notice in the generated document that the HTML <title> and <h1> values come directly from the XML template, as do the table headings and the definition of the HTML table itself.
Copyright © 2002 O'Reilly & Associates. All rights reserved.