|Resources | Buyer's Guide | Newsletter | Safari Bookshelf|
|Download Stylus Studio - The World's Best XML Editor|
What is XML?
What is XSLT?
What is XSL-FO?
What is XLink?
What is XML Schema?
What is XQuery?
What is RDF?
What is RSS?
What are Topic Maps?
What are Web Services?
What are XForms?
XSLT Recipe of the Day
Arbitrary Repetition with Named Template Recursion
XSLT offers no specific element for repeating a group of instructions an arbitrary number of times or until a given condition is true. To understand why requires a little historical background.
Just as XML got its start as a simplified version of SGML, XSLT and XSL began as simplified, XML-based versions of a stylesheet language developed for SGML called DSSSL (Document Style Semantics and Specification Language -- rhymes with "whistle"). Like SGML, DSSSL is an ISO standard, but its actual use in the real world has been limited.
Why didn't it catch on? One problem was its descent from Scheme, a programming language in the LISP family. "LISP" stands for "LISt Processing" language, but some programmers say that it stands for "Lots of Irritating Silly Parentheses". LISP, Scheme, and DSSSL use parentheses heavily for syntax, and the parenthesized expressions get nested at such deep levels that expressions ending with "))))" are common in all three languages. Both data structures and control structures are parenthesized expressions in these languages, and this can make code difficult to read, especially without using a LISP-aware editor like Emacs.
XSL and XSLT remedy this by applying many of DSSSL's principles using XML. Expressions can be deeply nested, but instead of being nested within dozens of parentheses and indentation conventions, they're nested inside of regular XML elements that have names right in their tags describing their purpose -- for example, xsl:if, xsl:number, and xsl:comment. This makes XSLT stylesheets more readable than DSSSL stylesheets for many people.
XSLT did inherit a related aspect of its ancestors that some view as a blessing and others as a curse: there's no concept of a series of instructions being executed sequentially. (The technical term is that XSLT is a "side effect free" language.) A stylesheet gets applied to a source tree to create a result tree, and while the structure of the result tree is important, the order in which it's created is irrelevant. Since you can't have a series of instructions, you certainly don't have a way to repeat a series of instructions.
This doesn't prevent you from doing something a specific number of times or until a given condition is true in XSLT. You just have to do so using recursion, a fundamental LISP/Scheme/DSSSL technique Using recursive named templates, you can get the benefits of both "for" loops and "while" loops.
To demonstrate this, we'll use this simple input document:
<sample>the facile gates of hell too slightly barred</sample>
The following template rule shows how to repeat something a specific number of times. It has a named template called "hyphens" that can call itself as many times as necessary to add the specified number of hyphens to the result tree. To demonstrate the use of this named template, the "sample" template calls the "hyphens" template four times, asking it to add a different number of hyphens to the result tree each time. First, the "sample" template calls it with no value overriding the howMany parameter so that we can see the template's default behavior, and then it calls the template three more times with the values 3, 20, and 0 to override the parameter's default value of 1.
<xsl:template name="hyphens"> <xsl:param name="howMany">1</xsl:param> <xsl:if test="$howMany > 0"> <!-- Add 1 hyphen to result tree. --> <xsl:text>-</xsl:text> <!-- Print remaining ($howMany - 1) hyphens. --> <xsl:call-template name="hyphens"> <xsl:with-param name="howMany" select="$howMany - 1"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template match="sample"> Print 1 hyphen: <xsl:call-template name="hyphens"/> Print 3 hyphens: <xsl:call-template name="hyphens"> <xsl:with-param name="howMany" select="3"/> </xsl:call-template> Print 20 hyphens: <xsl:call-template name="hyphens"> <xsl:with-param name="howMany" select="20"/> </xsl:call-template> Print 0 hyphens: <xsl:call-template name="hyphens"> <xsl:with-param name="howMany" select="0"/> </xsl:call-template> </xsl:template>
This creates the following result:
Print 1 hyphen: - Print 3 hyphens: --- Print 20 hyphens: -------------------- Print 0 hyphens:
The "hyphens" named template first declares the howMany parameter with an xsl:param element that sets this parameter's default value to 1. The rest of the template is a single xsl:if element whose contents get added to the result tree if howMany is set to a value greater than zero. If howMany passes this test, a single hyphen is added to the result tree and an xsl:call-template instruction calls the "hyphens" named template with a value of howMany that is one less than its current setting. If howMany is set to 1, xsl:call-template calls it with a value of 0, so no more hyphens will be added to the result tree. If howMany is set to 3, the named template will be called with a howMany value of 2 after adding the first of the 3 requested hyphens, and the process will be repeated until the template is called with a value of 0.
Recursion: Multiple calls to the same named template
This is what we mean by "recursion". When a template calls itself, it's a recursive call. You don't want it to call itself forever, so the recursive template needs a terminating condition -- in the case above, an xsl:if element that won't perform the recursive call unless howMany is greater than 0.
You must also be sure that the terminating condition will eventually occur. If the terminating condition was "$howMany = 0" and the recursive call subtracted 2 from the current value of howMany before calling the "hyphens" template again, calling it with a value of 3 would then mean making subsequent calls with howMany values of 1, -1, -3, -5, and so forth without ever hitting 0. The recursive calls would never stop. (The actual outcome of such an endless loop depends on the XSLT processor being used.)
The example above simulates the "for" loops used by many other programming languages because its recursive template performs an action a specific number of times, with the exact number passed to it at runtime. A "while" loop typically repeats an action or actions as long as a certain condition is true, and guess what -- the example above is really a "while" loop. The condition is the "$howMany > 0" test in the named template's xsl:if start-tag. You can put any condition you want there, and the template will make recursive calls to itself as long as the condition is true.
|Copyright © 2004 O'Reilly Media, Inc.|