Three XSLT elements are used for branching: <xsl:if>, <xsl:choose>, and <xsl:for-each>. The first two are much like the if and case statements you may be familiar with from other languages, while the for-each element is significantly different from the for or do-while structures in other languages. We'll discuss all of them here.
The <xsl:if> element looks like this:
<xsl:if test="count(zone) > 2"> <xsl:text>Applicable zones: </xsl:text> <xsl:apply-templates select="zone"/> </xsl:if>
The <xsl:if> element, surprisingly enough, implements an if statement. The element has only one attribute, test. If the value of test evaluates to the boolean value true, then all elements inside the <xsl:if> are processed. If test evaluates to false, then the contents of the <xsl:if> element are ignored. (If you want to implement an if-then-else statement, check out the <xsl:choose> element described in the next section.)
Notice that we used > instead of > in the attribute value. You're always safe using > here, although some XSLT processors process the greater-than sign correctly if you use > instead. If you need to use the less-than operator (<), you'll have to use the < entity. The same holds true for the less-than-or-equal operator (<=) and the greater-than-or-equal (>=) operators. See Section B.4.2, "Boolean Operators" for more information on this topic.
The <xsl:if> element is pretty simple, but it's the first time we've had to deal with boolean values. These values will come up later, so we might as well discuss them here. Attributes like the test attribute of the <xsl:if> element convert whatever their values happen to be into a boolean value. If that boolean value is true, the <xsl:if> element is processed. (The <xsl:when> element, which we'll discuss in just a minute, has a test attribute as well.)
Here's the rundown of how various datatypes are converted to boolean values:
These rules are defined in Section 4.3 of the XPath specification.
Here are some examples that illustrate how boolean values evaluate the test attribute:
The <xsl:choose> element is the equivalent of a case or switch statement in other programming languages. You can also use it to implement an if-then-else statement. An <xsl:choose> contains at least one <xsl:when> element (logically equivalent to an <xsl:if> element), with an optional <xsl:otherwise> element. The test attribute of each <xsl:when> element is evaluated until the XSLT processor finds one that evaluates to true. When that happens, the contents of that <xsl:when> element are evaluated. If none of the <xsl:when> elements have a test that is true, the contents of the <xsl:otherwise> element (if there is one) are processed.
Here's how these XSLT elements compare to the switch or select/case statements you might know from other languages:
The C, C++, and Java switch statement is roughly equivalent to the <xsl:choose> element. The one exception is that procedural languages tend to use fallthrough processing. In other words, if a branch of the switch statement evaluates to true, the runtime executes everything until it encounters a break statement, even if some of that code is part of other branches. The <xsl:choose> element doesn't work that way. If a given <xsl:when> evaluates to true, only the statements inside that <xsl:when> are evaluated.
The Java case statement is equivalent to the <xsl:when> element. In Java, if a given case statement does not end with a break statement, the following case is executed as well. Again, this is not the case with XSLT; only the contents of the first <xsl:when> element that is true are processed.
The Java and C++ default statement is equivalent to the <xsl:otherwise> element.
Here's a sample <xsl:choose> element that sets the background color of the table's rows. If the bgcolor attribute is coded on the <table-row> element, the value of that attribute is used as the color; otherwise, the sample uses the position() function and the mod operator to cycle the colors between papayawhip, mintcream, lavender, and whitesmoke.
<xsl:template match="table-row"> <tr> <xsl:attribute name="bgcolor"> <xsl:choose> <xsl:when test="@bgcolor"> <xsl:value-of select="@bgcolor"/> </xsl:when> <xsl:when test="position() mod 4 = 0"> <xsl:text>papayawhip</xsl:text> </xsl:when> <xsl:when test="position() mod 4 = 1"> <xsl:text>mintcream</xsl:text> </xsl:when> <xsl:when test="position() mod 4 = 2"> <xsl:text>lavender</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>whitesmoke</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:attribute> <xsl:apply-templates select="*"/> </tr> </xsl:template>
In this sample, we use <xsl:choose> to generate the value of the bgcolor attribute of the <tr> element. Our first test is to see if the bgcolor attribute of the <table-row> element exists; if it does, we use that value for the background color and the <xsl:otherwise> and other <xsl:when> elements are ignored. (If the bgcolor attribute is coded, the XPath expression @bgcolor returns a node-set containing a single attribute node.)
The next three <xsl:when> elements check the position of the current <table-row> element. The use of the mod operator here is the most efficient way to cycle between the various options. Finally, we use an <xsl:otherwise> element to specify whitesmoke as the default case. If position() mod 4 = 3, the background color will be whitesmoke.
A couple of minor details: in this example, we could replace the <xsl:otherwise> element with <xsl:when test="position() mod 4 = 3">; that is logically equivalent to the example as coded previously. For obfuscation bonus points, we could code the second <xsl:when> element as <xsl:when test="not(position() mod 4)">. (Remember that the boolean negation of zero is true.)
If you want to process all the nodes that match a certain criteria, you can use the <xsl:for-each> element. Be aware that this isn't a traditional for loop; you can't ask the XSLT processor to do something like this:
for i = 1 to 10 do
The <xsl:for-each> element lets you select a set of nodes, then do something with each of them. Let me mention again that this is not the same as a traditional for loop. Another important point is that the current node changes with each iteration through the <xsl:for-each> element. We'll go through some examples to illustrate this.
Here's a sample that selects all <section> elements inside a <tutorial> element and then uses a second <xsl:for-each> element to select all the <panel> elements inside each <section> element:
<xsl:template match="tutorial"> <xsl:for-each select="section"> <h1> <xsl:text>Section </xsl:text> <xsl:value-of select="position()"/> <xsl:text>. </xsl:text> <xsl:value-of select="title"/> </h1> <ul> <xsl:for-each select="panel"> <li> <xsl:value-of select="position()"/> <xsl:text>. </xsl:text> <xsl:value-of select="title"/> </li> </xsl:for-each> </ul> </xsl:for-each> </xsl:template>
Given this XML document:
<tutorial> <section> <title>Gene Splicing for Young People</title> <panel> <title>Introduction</title> <!-- ... --> </panel> <panel> <title>Discovering the secrets of life and creation</title> <!-- ... --> </panel> <panel> <title>"I created him for good, but he's turned out evil!"</title> <!-- ... --> </panel> <panel> <title>When angry mobs storm your castle</title> <!-- ... --> </panel> </section> </tutorial>
The previous template produces these results:
<h1>Section 1. Gene Splicing for Young People</h1> <ul> <li>1. Introduction</li> <li>2. Discovering the secrets of life and creation</li> <li>3. "I created him for good, but he's turned out evil!"</li> <li>4. When angry mobs storm your castle</li> </ul>
Each time a select attribute is processed, it is evaluated in terms of the current node. As the XSLT processor cycles through all the <xsl:section> and <xsl:panel> elements, each of them in turn becomes the current node. By using iteration, we've generated a table of contents with a very simple template.
Copyright © 2002 O'Reilly & Associates. All rights reserved.