XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

Conditional Execution

Conditional Execution

April 02, 2003

Most programming languages provide some means of conditional execution, which allows a program to execute an instruction or block of instructions only if a particular condition is true. Many programming languages do this with if statements; the XSLT equivalent is the xsl:if instruction.

There's ultimately only one instruction that can or can't happen in XSLT based on the boolean value of an xsl:if element's test expression: nodes get added to the result tree or they don't. The addition of nodes to the result tree is the only end result of any XSLT activity, and the xsl:if element gives you greater control over which nodes get added than template rule match conditions can give you. For example, an xsl:if instruction can base its behavior on document characteristics such as attribute values and the existence (or lack of) specific elements in a document.

Many programming languages also offer case or switch statements. These list a series of conditions to check and the actions to perform when finding a true condition. XSLT's xsl:choose element lets you do this in your stylesheets.

xsl:if

The xsl:if instruction adds its contents to the result tree if the expression in its test attribute evaluates to a boolean value of true. For example, imagine that the template rule for the following document's poem element must know various things about that element before it can add certain text nodes to the result tree.

<poem author="jm" year="1667">
<verse>Seest thou yon dreary Plain, forlorn and wild,</verse>
<verse>The seat of desolation, void of light,</verse>
<verse>Save what the glimmering of these livid flames</verse>
<verse>Casts pale and dreadful?</verse>
</poem>

The poem template rule below has six xsl:if instructions. Each adds a text node to the result tree if the test condition is true. They could add any kind of node -- elements, attributes, or whatever you like -- but the example is easier to follow if we stick with simple text messages. (All sample stylesheets, input, and output are available in this zip file.)

<!-- xq286.xsl: converts xq285.xml into xq287.txt -->

<xsl:template match="poem">

  --- Start of "if" tests. ---

  <xsl:if test="@author='jm'">
    1. The poem's author is jm.
  </xsl:if>

  <xsl:if test="@author">
    2. The poem has an author attribute.
  </xsl:if>

  <xsl:if test="@flavor">
    3. The poem has a flavor attribute.
  </xsl:if>

  <xsl:if test="verse">
    4. The poem has at least one verse child element.
  </xsl:if>

  <xsl:if test="shipDate">
    5. The poem has at least one shipDate child element.
  </xsl:if>

  <xsl:if test="count(verse) > 3">
    6. The poem has more than 3 verse child elements.
  </xsl:if>

  <xsl:if test="count(verse) < 3">
    7. The poem has less than 3 verse child elements.
  </xsl:if>

  <xsl:if test="(@author = 'bd') or (@year='1667')">
    8. Either the author is "bd" or the year is "1667".
  </xsl:if>

  <xsl:if test="@year < '1850'">
    9a. The poem is old.

    <xsl:if test="@year < '1700'">
      9b. The poem is very old.
    </xsl:if>

    <xsl:if test="@year < '1500'">
      9c. The poem is very, very old.
    </xsl:if>

  </xsl:if>

  --- End of "if" tests. 

</xsl:template>

The first xsl:if element tests whether the poem element's author attribute has a value of "jm". Note the use of single quotation marks around "jm" in the template to enclose the literal string within the double quotation marks of the test attribute value. You could also use the "&quot;" or "&apos;" entity references (for example, test="@author=&quot;jm&quot;") to delimit the string value.

When the stylesheet is applied to the source XML, the XSLT processor adds the text node beginning with "1" to the result along with several other text nodes from the stylesheet, depending on the boolean values of each xsl:if instruction's test attribute:

  --- Start of "if" tests. ---

  
    1. The poem's author is jm.
  
    2. The poem has an author attribute.
  
    4. The poem has at least one verse child element.
  
    6. The poem has more than 3 verse child elements.
  
    8. Either the author is "bd" or the year is "1667".
  
    9a. The poem is old.

    
      9b. The poem is very old.
    

  --- End of "if" tests. 

Some of these expressions don't look as obviously boolean as @author="jm", but the XSLT processor can still treat them as boolean expressions. For example, the stylesheet's second xsl:if instruction has a test value that only says "@author". This tells the XSLT processor to add the xsl:if element's contents to the result tree if the context node (in this case, the poem element node listed as the xsl:template element's match attribute value) has an attribute specified with that name. The third xsl:if element does the same for a flavor attribute, and because the poem element has an author attribute but no flavor attribute, the result of applying the stylesheet to the input shown includes the text node beginning with "2" but not the one beginning "3".

A similar test can check whether an element has a particular child subelement. The fourth and fifth xsl:if elements check for verse and shipDate subelements of the poem element; the output shows that it has at least one verse subelement but no shipDate subelements.

How many verse subelements does it have? The count() function makes it easy to find out. The sixth and seventh xsl:if elements check whether this count is more or less than 3. The sixth is true, and its text is added to the output, but the seventh is not. (If there were exactly 3 verse child elements, neither of these conditions would be true.)

Note When you want to use the less-than symbol in an xsl:if element's test attribute, remember to use the entity reference "&lt;". The actual < character in the test attribute value (or in any attribute value) makes the stylesheet an ill-formed XML document, so the XML parser will choke on it and not pass it along to the XSLT processor.

You can use parentheses and the boolean operators and and or to build more complex boolean expressions. For example, the eighth xsl:if element in the stylesheet above adds the text node string beginning with "8" if either the author attribute equals "bd" or the year attribute equals "1667". Because the latter condition is true for the sample document's poem element, an XSLT processor will add the text. The parentheses in the test attribute's expression are not necessary in this case; but as with similar expressions in other programming languages, they make it easier to see the expression's structure.

For more complex conditional logic, you can put xsl:if elements inside of other ones. In the example, the xsl:if element with the line beginning "9a" contains xsl:if elements 9b and 9c. The output shows that, because the poem element's year attribute has a value less than 1850, the text string beginning with "9a" gets added to the output. Because it's also less than 1700, the text string beginning with "9b" in the first nested xsl:if element is also added. The date value is not less than 1500, so the second nested xsl:if element's text node does not get added to the output.

Before getting too fancy with your xsl:if elements, note the flexibility that that the xsl:choose instruction gives you. It offers the equivalent of an "else" or "otherwise" section, which lets you specify nodes to add to the result tree if the test conditions aren't true. The xsl:if element offers no equivalent of this, so programmers accustomed to if-else constructs in other programming languages may find the xsl:choose element better for certain situations where xsl:if first seems like the obvious choice.

xsl:choose

XSLT's xsl:choose instruction is similar to xsl:if but has a few key differences:

  • One xsl:choose element can test for more than one condition and add different nodes to the result tree based on which condition is true.

  •     

    Also in Transforming XML

    Automating Stylesheet Creation

    Appreciating Libxslt

    Push, Pull, Next!

    Seeking Equality

    The Path of Control

  • An xsl:choose element can have a default template to add to the result tree if none of the conditions are true. (Compare xsl:if, which has no equivalent of an "else" condition.)

  • The xsl:choose element has specific subelements that are necessary for it to work, while you can put any well-formed elements you want inside of an xsl:if element.

When an XSLT processor sees an xsl:choose element, it checks the test attribute value of each xsl:when element that it finds as a child of the xsl:choose element. When it finds a true test expression, it adds that xsl:when element's contents to the result tree and then skips the rest of the xsl:choose element. If it finds no xsl:when element with a true test expression, it checks for the optional xsl:otherwise element at the end of the xsl:choose element. If it finds one, it adds its contents to the result tree.

For example, let's say we want a template rule to check the date of the following poem and add a message to the result tree saying whether it was one of Milton's early, middle period, or later works.

<poem author="jm" year="1667">
  <verse>Seest thou yon dreary Plain, forlorn and wild,</verse>
  <verse>The seat of desolation, void of light,</verse>
  <verse>Save what the glimmering of these livid flames</verse>
  <verse>Casts pale and dreadful?</verse>
</poem>

The XSLT processor will skip over the xsl:choose element's first two xsl:when elements in the following template because their test expressions are false for the year value of "1667". It will add the text "The poem is one of Milton's later works" to the result tree when it finds that the condition "@year &lt; 1668" is true. (Note again the use of "&lt;" instead of a "<" to keep the xsl:when element well-formed.)

<!-- xq290.xsl: converts xq289.xml into xq291.txt -->

<xsl:template match="poem">

  <xsl:choose>
    <xsl:when test="@year < 1638">
   The poem is one of Milton's earlier works.
    </xsl:when>

    <xsl:when test="@year < 1650">
   The poem is from Milton's middle period.
    </xsl:when>

    <xsl:when test="@year < 1668">
   The poem is one of Milton's later works.
    </xsl:when>

    <xsl:when test="@year < 1675">
   The poem is one of Milton's last works.
    </xsl:when>

    <xsl:otherwise>
      The poem was written after Milton's death.
    </xsl:otherwise>

  </xsl:choose>

</xsl:template>

Although the sample poem element's year value of "1667" also makes the last xsl:when element's test expression ("@year &lt; 1675") true, the XSLT processor will not continue checking for more true test expressions after it finds one. The result only has the text result node from the first xsl:when element with a true test expression:

   The poem is one of Milton's later works.  

Like xsl:if instructions, xsl:when elements can have more elaborate contents between their start- and end-tags—for example, literal result elements, xsl:element elements, or even xsl:if and xsl:choose elements—to add to the result tree. Their test expressions can also use all the tricks and operators that the xsl:if element's test attribute can use, such as and, or, and function calls, to build more complex boolean expressions. (For many more demonstrations of the xsl:if and xsl:choose instructions, see my book XSLT Quickly.)