Book HomeXSLSearch this book

4.2. Branching Elements of XSLT

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.

4.2.1. The <xsl:if> Element

The <xsl:if> element looks like this:

<xsl:if test="count(zone) &gt; 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 &gt; instead of > in the attribute value. You're always safe using &gt; 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 &lt; 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.

4.2.1.1. Converting to boolean values

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:

number
If a number is positive or negative zero, it is false. If a numeric value is NaN (not a number; if I try to use the string "blue" as a number, the result is NaN), it is false. If a number has any other value, it is true.

node-set
An empty node-set is false, a non-empty node-set is true.

string
A zero-length string is false; a string whose length is not zero is true.

These rules are defined in Section 4.3 of the XPath specification.

4.2.1.2. Boolean examples

Here are some examples that illustrate how boolean values evaluate the test attribute:

<xsl:if test="count(zone) &gt;= 2">
This is a boolean expression because it uses the greater-than-or-equal boolean operator. If the count() function returns a value greater than or equal to 2, the test attribute is true. Otherwise, the test attribute is false.

<xsl:if test="$x">
The variable x is evaluated. If it is a string, then the test attribute is true only if the string has a length greater than zero. If it is a node-set, then the test attribute is true only if the node-set has at least one member. If it is a number, then the test attribute is true only if the number is anything other than positive zero, negative zero, or NaN. (Of course, if x is a boolean value, true is true and false is false.)

<xsl:if test="true()">
The boolean function true() always returns the boolean value true. Therefore, this test attribute is always true.

<xsl:if test="true">
This example is a trick. This test attribute is true only if there is at least one <true> element in the current context. The XSLT processor interprets the value true as an XPath expression that specifies all <true> elements in the current context. The strings true and false don't have any special significance in XSLT.

<xsl:if test="'true'">
This test attribute is always true. Notice that in this case we used single quotes inside double quotes to specify that this is a literal string, not an element name. This test attribute is always true because the string has a length greater than zero, not because its value happens to be the word "true."

<xsl:if test="'false'">
Another trick example; this test attribute is always true. As before, we used single quotes inside double quotes to specify that this is a literal string. Because the string has a length greater than zero, the test attribute is always true. The value of the nonempty string, confusing as it is, doesn't matter.

<xsl:if test="not(3)">
This test attribute is always false. The literal 3 evaluates to true, so its negation is false. On the other hand, the expressions not(0) and not(-0) are always true.

<xsl:if test="false()">
This test attribute is always false. The boolean function false() always returns the boolean value false.

<xsl:if test="section/section">
The XPath expression section/section returns a node-set. If the current context contains one or more <section> elements that contain a <section> element in turn, the test attribute is true. If no such elements exist in the current context, the test attribute is false.

4.2.2. The <xsl:choose> Element

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:

4.2.2.1. <xsl:choose> example

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.)

4.2.3. The <xsl:for-each> Element

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.

4.2.3.1. <xsl:for-each> example

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.



Library Navigation Links

Copyright © 2002 O'Reilly & Associates. All rights reserved.