Book HomeJava and XSLTSearch this book

Chapter 3. XSLT Part 2 -- Beyond the Basics

Contents:

Conditional Processing
Parameters and Variables
Combining Multiple Stylesheets
Formatting Text and Numbers
Schema Evolution
Ant Documentation Stylesheet

As you may have guessed, this chapter is a continuation of the material presented in the previous chapter. The basic syntax of XSLT should make sense by now. If not, it is probably a good idea to sit down and write a few stylesheets to gain some basic familiarity with the technology. What we have seen so far covers the basic mechanics of XSLT but does not take full advantage of the programming capabilities this language has to offer. In particular, this chapter will show how to write more reusable, modular code through features such as named templates, parameters, and variables.

The chapter concludes with a real-world example that uses XSLT to produce HTML documentation for Ant build files. Ant is a Java build tool that uses XML files instead of Makefiles to drive the compilation process. Since XML is used, XSLT is a natural choice for producing documentation about the build process.

3.1. Conditional Processing

In the previous chapter, we saw a template that output the name of a president or vice president. Its basic job was to display the first name, middle name, and last name. A nonbreaking space was printed between each piece of data so the fields did not run into each other. What we did not see was that many presidents do not have middle names, so our template ended up printing the first name, followed by two spaces, followed by the last name. To fix this, we need to check for the existence of a middle name before simply outputting its content and a space. This requires conditional logic, a feature found in just about every programming language in existence.

XSLT provides two mechanisms that support conditional logic: <xsl:if> and <xsl:choose>. These allow a stylesheet to produce different output depending on the results of a boolean expression, which must yield true or false as defined by the XPath specification.

3.1.1. <xsl:if>

The behavior of the <xsl:if> element is comparable to the following Java code:

if (boolean-expression) {
  // do something
}

In XSLT, the syntax is as follows:

<xsl:if test="boolean-expression">
  <!-- Content: template -->
</xsl:if>

The test attribute is required and must contain a boolean expression. If the result is true, the content of this element is instantiated; otherwise, it is skipped. The code in Example 3-1 illustrates several uses of <xsl:if> and related XPath expressions. Code that is highlighted will be discussed in the next several paragraphs.

Example 3-1. <xsl:if> examples

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html"/>
  <!--******************************************************
      ** "/" template
      ***************************************************-->
  <xsl:template match="/">
    <html>
      <body>
        <h1>Conditional Processing Examples</h1>
        <xsl:apply-templates select="presidents"/>
      </body>
    </html>
  </xsl:template>
  <!--******************************************************
      ** "presidents" template
      ***************************************************-->
  <xsl:template match="presidents">
    <h3>
      List of
        <xsl:value-of select="count(president)"/>
      Presidents
    </h3>
    <ul>
      <xsl:for-each select="president">
        <li>
          <!-- display every other row in bold -->
          <xsl:if test="(position( ) mod 2) = 0">
            <xsl:attribute name="style">
              <xsl:text>font-weight: bold;</xsl:text>
            </xsl:attribute>
          </xsl:if>
          <xsl:apply-templates select="name"/>
          <!-- display some text after the last element -->
          <xsl:if test="position() = last( )">
            <xsl:text> (current president)</xsl:text>
          </xsl:if>
        </li>
      </xsl:for-each>
    </ul>
  </xsl:template>
  <!--******************************************************
      ** "name" template
      ***************************************************-->
  <xsl:template match="name">
    <xsl:value-of select="last"/>
    <xsl:text>, </xsl:text>
    <xsl:value-of select="first"/>
    <xsl:if test="middle">
      <xsl:text> disable-output-escaping="yes">&amp;nbsp;</xsl:text>
      <xsl:value-of select="middle"/>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

The first thing the match="presidents" template outputs is a heading that displays the number of presidents:

List of
  <xsl:value-of select="count(president)"/>
Presidents

The count( ) function is an XPath node set function and returns the number of elements in a node set. In this case, the node set is the list of <president> elements that are direct children of the <presidents> element, so the number of presidents in the XML file is displayed. The next block of code does the bulk of the work in this stylesheet, outputting each president as a list item using a loop:

<xsl:for-each select="president">
  <li>
    <!-- display every other row in bold -->
    <xsl:if test="(position( ) mod 2) = 0">
      <xsl:attribute name="style">
        <xsl:text>font-weight: bold;</xsl:text>
      </xsl:attribute>
    </xsl:if>

In this example, the <xsl:for-each> loop first selects all <president> elements that are immediate children of the <presidents> element. As the loop iterates over this node set, the position( ) function returns an integer representing the current node position within the current node list, beginning with index 1. The mod operator computes the remainder following a truncating division, just as Java and ECMAScript do for their % operator. The XPath expression (position( ) mod 2) = 0 will return true for even numbers; therefore the style attribute will be added to the <li> tag for every other president, making that list item bold.

This template continues as follows:

<xsl:apply-templates select="name"/>
<!-- display some text after the last element -->
<xsl:if test="position() = last( )">
  <xsl:text> (current president)</xsl:text>
</xsl:if>
</li>
</xsl:for-each>

The last( ) function returns an integer indicating the size of the current context; in this case, it returns the number of presidents. When the position is equal to this count, the additional text (current president) is appended to the result tree. Java programmers should note that XPath uses a single = character for comparisons instead of ==, as Java does. A portion of the HTML for our list ends up looking like this:

<li>Washington, George</li>
<li style="font-weight: bold;">Adams, John</li>
<li>Jefferson, Thomas</li>
<li style="font-weight: bold;">Madison, James</li>
<li>Monroe, James</li>
<li style="font-weight: bold;">Adams, John&nbsp;Quincy</li>
<li>Jackson, Andrew</li>
...remaining HTML omitted
<li>Bush, George (current president)</li>

The name output has been improved from the previous chapter and now uses <xsl:if> to determine if the middle name is present:

<xsl:template match="name">
  <xsl:value-of select="last"/>
  <xsl:text>, </xsl:text>
  <xsl:value-of select="first"/>
  <xsl:if test="middle">
    <xsl:text> disable-output-escaping="yes">&amp;nbsp;</xsl:text>
    <xsl:value-of select="middle"/>
  </xsl:if>
</xsl:template>

In this case, <xsl:if test="middle"> checks for the existence of a node set rather than for a boolean value. If any <middle> elements are found, the content of <xsl:if> is instantiated. The test does not have to be this simplistic; any of the XPath location paths from the previous chapter would work here as well.

As written here, if any <middle> elements are found, the first one is printed. Later, in Example 3-7, <xsl:for-each> will be used to print all middle names for presidents, such as George Herbert Walker Bush.

Checking for the existence of an attribute is very similar to checking for the existence of an element. For example:

<xsl:if test="@someAttribute">
  ...execute this code if "someAttribute" is present
</xsl:if>

Unlike most programming languages, <xsl:if> does not have a corresponding else or otherwise clause. This is only a minor inconvenience[9] because the <xsl:choose> element provides this functionality.

[9] <xsl:choose> requires a lot of typing.

3.1.2. <xsl:choose>, <xsl:when>, and <xsl:otherwise>

The XSLT equivalent of Java's switch statement is <xsl:choose> , which is virtually identical[10] in terms of functionality. <xsl:choose> must contain one or more <xsl:when> elements followed by an optional <xsl:otherwise> element. Example 3-2 illustrates how to use this feature. This example also uses <xsl:variable>, which will be covered in the next section.

[10] Java's switch statement only works with char, byte, short, or int.

Example 3-2. <xsl:choose>

<xsl:template match="presidents">
  <h3>Color Coded by Political Party</h3>
  <ul>
    <xsl:for-each select="president">
      <xsl:variable name="color">
        <!-- define the color value based on political party -->
        <xsl:choose>
          <xsl:when test="party = 'Democratic'">
            <xsl:text>blue</xsl:text>
          </xsl:when>
          <xsl:when test="party = 'Republican'">
            <xsl:text>green</xsl:text>
          </xsl:when>
          <xsl:when test="party = 'Democratic Republican'">
            <xsl:text>purple</xsl:text>
          </xsl:when>
          <xsl:when test="party = 'Federalist'">
            <xsl:text>brown</xsl:text>
          </xsl:when>
          <xsl:when test="party = 'Whig'">
            <xsl:text>black</xsl:text>
          </xsl:when>
          <!-- never executed in this example -->
          <xsl:otherwise>
            <xsl:text>red</xsl:text>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:variable>
      <li>
        <font color="{$color}">
          <!-- show the party name -->
          <xsl:apply-templates select="name"/>
          <xsl:text> - </xsl:text>
          <xsl:value-of select="party"/>
        </font>
      </li>
    </xsl:for-each>
  </ul>
</xsl:template>

In this example, the list of presidents is displayed in order along with the political party of each president. The <xsl:when> elements test for each possible party, setting the value of a variable. This variable, color, is then used in a font tag to set the current color to something different for each party. The <xsl:otherwise> element is never executed because all of the political parties are listed in the <xsl:when> elements. If a new president affiliated with some other political party is ever elected, then none of the <xsl:when> conditions would be true, and the font color would be red.

One difference between the XSLT approach and a pure Java approach is that XSLT does not require break statements between <xsl:when> elements. In XSLT, the <xsl:when> elements are evaluated in the order in which they appear, and the first one with a test expression resulting in true is evaluated. All others are skipped. If no <xsl:when> elements match, then <xsl:otherwise>, if present, is evaluated.

Since <xsl:if> has no corresponding <xsl:else>, <xsl:choose> can be used to mimic the desired functionality as shown here:

<xsl:choose>
  <xsl:when test="condition">
    <!-- if condition -->
  </xsl:when>
  <xsl:otherwise>
    <!-- else condition -->
  </xsl:otherwise>
</xsl:choose>

As with other parts of XSLT, the XML syntax forces a lot more typing than Java programmers are accustomed to, but the mechanics of if/else are faithfully preserved.



Library Navigation Links

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