Contents


Save time and code with XPath 2.0 and XSLT 2.0

Create easy-to-maintain stylesheets with the to operator, the item data type, and sequences

One of the major new concepts in XPath 2.0 and XSLT 2.0 is that everything is a sequence. In XPath 1.0 and XSLT 1.0, you typically worked with trees of nodes. The parsed XML document was a tree that contained the document node and its descendants. Using that tree of nodes, you could find the node for the root element, along with all of the root element's descendants, attributes, and siblings. (Any comments or processing instructions outside the root element of the XML file are considered siblings of the root element.)

When you work with an XML document in XPath 2.0 and XSLT 2.0, you use the sequence in the same way as the tree structure in XPath 1.0 and XSLT 1.0. The sequence contains a single item (the document node), and you use it the same way you always have. However, you can create sequences of atomic values. Listing 1, taken from the upcoming sample application in which you'll manage the data for a 16-team single-elimination tournament, shows an example of a sequence of atomic values.

Listing 1. A sequence of atomic values
<xsl:variable name="seeds" as="xs:integer*">
  <xsl:sequence 
    select="(1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15)"/>
</xsl:variable>

This code defines the variable $seeds. The new <xsl:sequence> element defines, as you might expect, a sequence of items. In this case, the items are XML Schema xs:integers. The new as attribute defines the data type of the variable, and the asterisk (xs:integer*) means that the sequence contains zero or more integers. In XPath 1.0 and XSLT 1.0, you would create 16 different text nodes and then group those nodes into a variable. In XPath 2.0 and XSLT 2.0, the sequence acts as a one-dimensional array of numbers, which is exactly what you want for the sample application.

Sequences follow a couple of rules. First, they can't contain other sequences. If you create a new sequence from a sequence of three items, followed by another sequence of three items, the result will be a new sequence of six items. Second, sequences allow you to mix nodes and items. You can create a sequence that contains the atomic values shown in Listing 1, plus all of the <contestant> elements, as shown in Listing 2.

Listing 2. A sequence of nodes and atomic values
<xsl:variable name="seeds" as="item()*">
  <xsl:for-each select="/bracket/contestants/contestant">
    <xsl:copy-of select="."/>
  </xsl:for-each>
  <xsl:sequence 
    select="(1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15)"/>
</xsl:variable>

The variable $seeds contains all of the contestant nodes and the 16 atomic values you used earlier. Notice that the data type of the variable is item()*. An item is a node or an atomic value, so this variable can contain anything.

Now that you know the basics of sequences and items, let's look at the to operator, which is new in XPath 2.0 and XSLT 2.0. It allows you to select a range of integers. For example, you might create a sequence as shown in Listing 3.

Listing 3. A sequence of integers created with the to operator
<xsl:variable name="range" as="item()*">
  <xsl:sequence select="1 to 16"/>
</xsl:variable>

This code creates a variable named $range that contains the integers 1 through 16. In the sample application, you can use the to operator as a looping mechanism, as shown in Listing 4.

Listing 4. Using the to operator for looping
<xsl:for-each select="1 to 32">
  <!-- Do something useful here -->
</xsl:for-each>

Before you build the stylesheet, look at the sample application in more detail.

Understanding the sample application

In sample application for this article, you'll manage the data for a 16-team single-elimination tournament. As you'd expect, the tournament data is represented in XML. You'll create an XSLT 2.0 stylesheet that transforms the XML data into an HTML table that demonstrates the results of the tournament. Listing 5 shows the XML document format.

Listing 5. XML document with tournament data
<?xml version="1.0" encoding="UTF-8"?>
<!-- tourney.xml -->
<bracket>
   <title>RSDC Smackdown</title>
   <contestants>
      <contestant seed="1" image="images/homerSimpson.png">Donuts</contestant>
      <contestant seed="2" image="images/caffeine.png">Caffeine</contestant>
      <contestant seed="3" image="images/fearlessFreep.png">Fearless Freep</contestant>
      <contestant seed="4" image="images/wmd.jpg">Weapons of Mass Destruction</contestant>
      <contestant seed="5" image="images/haroldPie.jpg">Pie</contestant>
      <contestant seed="6" image="images/adamAnt.png">Adam Ant</contestant>
      <contestant seed="7" image="images/georgeWBush.jpg">Misunderestimated</contestant>
      <contestant seed="8" image="images/sillyPutty.jpg">Silly Putty</contestant>
      <contestant seed="9" image="images/krazyGlue.jpg">Krazy Glue</contestant>
      <contestant seed="10" image="images/snoopDogg.png">Biz-Implification</contestant>
      <contestant seed="11" image="images/atomAnt.png">Atom Ant</contestant>
      <contestant seed="12" image="images/ajaxcan.png">AJAX</contestant>
      <contestant seed="13" image="images/darthVader.jpg">Darth Vader</contestant>
      <contestant seed="14" image="images/nastyCanasta.png">Nasty Canasta</contestant>
      <contestant seed="15" image="images/jcp.png">Java Community Process</contestant>
      <contestant seed="16" image="images/andre.png">Andre the Giant</contestant>
   </contestants>
   <results>
      <result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
      <result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
      <result round="1" firstSeed="5" secondSeed="12" winnerSeed="5"/>
      <result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
      <result round="1" firstSeed="6" secondSeed="11" winnerSeed="11"/>
      <result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
      <result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
      <result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
      <result round="2" firstSeed="1" secondSeed="9" winnerSeed="1"/>
      <result round="2" firstSeed="5" secondSeed="4" winnerSeed="5"/>
      <result round="2" firstSeed="11" secondSeed="3" winnerSeed="3"/>
      <result round="2" firstSeed="10" secondSeed="2" winnerSeed="2"/>
      <result round="3" firstSeed="1" secondSeed="5" winnerSeed="1"/>
      <result round="3" firstSeed="3" secondSeed="2" winnerSeed="2"/>
      <result round="4" firstSeed="1" secondSeed="2" winnerSeed="2"/>
   </results>
</bracket>

In the <title> element, you can see the name of the tournament, RSDC Smackdown. This document represents the actual results from a session at this year's IBM Rational Software Developer Conference.

A bracket contains 16 <contestant> elements, each of which has a name (its text), a seed, and an image. A 16-team tournament consists of 15 matchups. Each matchup is represented by a <result> element. Four pieces of data are associated with each matchup: the round of the tournament in which the matchup occurred (the round attribute), the seeds of the two contestants (stored in the firstSeed and secondSeed attributes), and the seed of the winner (the winnerSeed attribute). Your task is to take this XML document and transform it into an HTML table that illustrates the results, as shown in Figure 1.

Figure 1. The tournament results in an HTML table
Tournament results in an HTML table
Tournament results in an HTML table

The table has 32 rows and five columns. Using an XSLT 1.0 approach, you might build the HTML table one row at a time, as shown in Listing 6.

Listing 6. Building the HTML table one row at a time
<!-- Row 1 -->
<tr>
  <td style="border: none; border-top: solid; border-right: solid;">
    <xsl:text>[1] </xsl:text>
    <xsl:value-of select="$contestants[@seed='1']/>
  </td>
  <td style="border: none;>
    &nbsp;
  </td>
  <td style="border: none;>
    &nbsp;
  </td>
  <td style="border: none;>
    &nbsp;
  </td>
  <td style="border: none;>
    &nbsp;
  </td>
</tr>
<!-- Row 2 -->
. . .

That would work, but the stylesheet would be difficult to maintain. The code for each row and column is repeated throughout the stylesheet. The main problem here is that you need 32 rows in the output table. Each of the 32 rows contains data from an element in the XML document (either a <contestant> or a <result>). Unfortunately, you don't have 32 elements you can iterate through. You might use <xsl:for-each select="contestants/contestant|results/result">, but those elements don't appear in the order in which you need them in the table. XSLT 1.0 doesn't have many tools to help you with this.

You can refactor the code to make the stylesheet much simpler. There are patterns in the style and content of the cells, and you can use the new features of XPath 2.0 and XSLT 2.0 to iterate through those patterns. The final stylesheet is roughly 70% smaller than the (admittedly clumsy) original version. Before you build that stylesheet, let's look at how to refactor the code.

Refactoring the code - Calculating table cell styles

To refactor the code, start moving the style information to a CSS stylesheet. As you can see in Figure 2, the HTML bracket table has five different cell styles: None, MatchupStart, MatchupMiddle, MatchupEnd, and Solid.

Figure 2. Cell border styles in the HTML table
Cell border styles in the HTML table
Cell border styles in the HTML table

Listing 7 shows what the CSS code looks like.

Listing 7. The CSS stylesheet
.None          { width: 20%; }
.MatchupStart  { width: 20%; 
                 border: none; border-top: solid; 
                 border-right: solid; }
.MatchupMiddle { width: 20%; 
                 border: none; border-right: solid; }
.MatchupEnd    { width: 20%; 
                 border: none; border-bottom: solid; 
                 border-right: solid; }
.Solid         { width: 20%; 
                 border: solid; }

The border styles make it easy to see the results of the tournament. The horizontal lines joining two cells indicate that those two competitors faced each other; the cell with the solid border in the following column indicates the winner of the matchup. Looking at the border styles, you can see a definite pattern: For each pair of competitors, the cells before the first competitor have no border (style None), the cells for the two competitors have a solid border (style Solid), the cells between the two competitors have a border on the right (style MatchupMiddle), and the cells after the last competitor have no border (style None). This is slightly different for the first column. Because the pairs of competitors in the first column are so close together, the cell for the first competitor has a border on the top and right (style MatchupStart), and the cell for the second competitor has a border on the bottom and right (style MatchupEnd).

Notice that the pairs of competitors are farther apart in each column. A one-cell gap is between the competitors in column 1, a three-cell gap in column 2, a seven-cell gap in column 3, and a 15-cell gap in column 4. The size of each gap is one less than a power of two, so there's a definite pattern here.

The generalized pattern across the table is that each column contains a repeating group of cells. The sizes of the repeating groups (which I'll call the period of the column, for lack of a better term) are 4, 8, 16, 32, and 64 for the five columns. Each repeating group contains two competitors, the cells between the two competitors and the cells before and after the two competitors.

Within each column, use two values in your calculations: $period, which represents the size of the repeating group, and $oneQuarter, which is one quarter of the period size. (Storing $period div 4 in a variable makes the code cleaner.) Table 1 shows the generalized rules for the cell border styles.

Table 1. Cell border styles in the HTML table
FormulaColumn 1 (period 4)Column 2 (period 8)Column 3 (period 16)Column 4 (period 32)Column 5 (period 64)
$row < $oneQuarter or $row > $period - $oneQuarterN/ARow 1, style NoneRows 1-3, style NoneRows 1-7, style NoneRows 1-15, style None
$row = $oneQuarterRow 1, style MatchupStartRow 2, style SolidRow 4, style SolidRow 8, style SolidRow 16, style Solid
$row > $oneQuarter and $row < $period - $oneQuarterRow 2, style MatchupMiddleRows 3-5, style MatchupMiddleRows 5-11, style MatchupMiddleRows 9-23, style MatchupMiddleRows 17-32, style None
$row = $period - $oneQuarterRow 3, style MatchupEndRow 6, style SolidRow 12, style SolidRow 24, style SolidN/A
$row < $oneQuarter or $row > $period - $oneQuarterRow 4, style NoneRows 7-8, style NoneRows 13-16, style NoneRows 25-32, style NoneN/A

Now you've created an elegant way to figure out the style of any given cell in the table. Given the column number and the row number, the formula works like a charm.

Refactoring the code - Calculating table cell content

When you create a cell in the table, of course, you also need to put the appropriate content in that cell. That has a nice pattern as well. Anything with a style of None or MatchupMiddle doesn't have any content at all. That means you only have to worry about finding the right content for the cells with the other three styles.

In Table 1, you can see that the cells with content have the property $row = $oneQuarter or $row = $period - $oneQuarter. For the first column, you simply write the seed and the name of the appropriate contestant. The matchups are based on the seedings and feature the pairings shown in Table 2.

Table 2. Matchups based on seedings
Matched pairs based on seedings
[1] versus [16]
[8] versus [9]
[5] versus [12]
[4] versus [13]
[6] versus [11]
[3] versus [14]
[7] versus [10]
[2] versus [15]

The matchups are arranged so that if the higher seed always wins, the top-seeded team will always play the lowest-seeded team remaining, the second-seeded team will always play the second-lowest-seeded team remaining, and so forth. Looking at the seeds in this order (1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15), you can seey they match the values in the sequence $seeds. The competitors are displayed in column 1 in this order.

A competitor appears in every other row, which means row 1 has the first seed in the sequence, row 3 has the second seed in the sequence, row 5 has the third seed in the sequence, and so on. The pattern here is $row + 1 div 2 = $index. In other words, take the row number, add 1, and divide it by 2 to get the index of the seed within the $seeds sequence. The content of the table cell is the seed number followed by the name of the contestant with that seed.

That takes care of the content for column 1. Columns 2 through 5, as you'd expect, are more complicated. Instead of looking at the <contestant> elements, you need to look at the results, which are displayed in the 15 <result> elements.

Column 2 contains the winners of round 1. That means you need to look at the <result> elements with the attribute round="1". To keep things simpler, the winner of the first matchup (the 1 versus 16 seed) is stored in the first <result> element, the winner of the second matchup (the 8 versus 9 seed) is stored in the second <result> element, and so on. The winners in column 2 are displayed in rows 2, 6, 10, 14, 18, 22, 26, and 30. Looking for a pattern, row 2 uses the first <result> element, row 6 uses the second, and row 10 uses the third. For this column, if you take the row number, add 2, and divide it by 4, you get the position of the appropriate <result round="1"> element.

Columns 3 and 4 are handled similarly. The winners in column 3 are displayed in rows 4, 12, 20, and 28 (the pattern here is to add 4 and divide by 8). The winners in column 4 are displayed in rows 8 and 24 (add 8 and divide by 16). Column 5 displays only one contestant—the winner of the entire tournament. If you're in row 16, you display the winner from the single <result round="4"> element.

The refactored way to find the position of the value you seek is ($row + $oneQuarter) div ($oneQuarter * 2). Using the increasing size of the repeating pattern makes the code simple. For column 1, the calculated position is the index into the sequence of seeds; for the other columns, the calculated position is the position of the appropriate <result> element.

Now you have an elegant way to determine both the content and the style of each cell in the table. Given the row and column number, you can figure out everything you need to know.

Harnessing the power of XPath 2.0 and XSLT 2.0

Now you can use the new techniques available in XPath 2.0 and XSLT 2.0 to streamline the stylesheet. To start, use the to operator. If you built the table with a procedural programming language, you might do something similar to Listing 8.

Listing 8. Procedural approach to the stylesheet
      for (int row=1; row<=32; row++)
        for (int column=1; column<=5; column++)
          // Build each cell in the table here

With XPath 2.0 and XSLT 2.0, you use <xsl:for-each> to replace the for loops that you use in a procedural language. Listing 9 shows you how.

Listing 9. Doing for loops with the to operator
<xsl:for-each select="1 to 32">
  <xsl:variable name="outerIndex" select="."/>
    <tr>
      <xsl:for-each select="1 to 5">

You need to address a couple of complications here. First, in XPath 1.0 and XSLT 1.0, using <xsl:for-each> changed the context for each iteration. For example, if you used <xsl:for-each select="contestants/contestant>, the context node is the latest <contestant> during each iteration. As you use the to operator to iterate through various integers, the context item (it's the context item in 2.0) is undefined. As you can see in Listing 9, you need to save the current value of the outer <xsl:for-each> because it's not available to you in the inner <xsl:for-each>.

But it gets worse. If the context item is undefined, you have no way to select nodes from the document. If you know you're in row 1, column 1, you can get the first item from the $seeds sequence because $seeds is a global variable. That tells you that you need to find the <contestant seed="1"> element. Unfortunately, you can't get to anything in the document. Even using an absolute XPath expression such as /bracket/contestants/contestant[@seed='1'] doesn't work. For that reason, you need to store the nodes that you care about as global variables. Listing 10 shows you how to access the global variables whenever and wherever you need to.

Listing 10. Global variables that store the nodes you need
<xsl:variable name="results" select="/bracket/results"/>

<xsl:variable name="contestants" select="/bracket/contestants"/>

If you need to get the name of the 16th-seeded contestant, the XPath expression is $contestants/contestant[@seed="16"]. You access the <result> elements similarly; if you need to get the winner of the second matchup in round 2, the expression is $results/result[@round="2"][2]/@winnerSeed. Listing 11 shows two more global variables you can use to generate the HTML table.

Listing 11. Other useful global variables
<xsl:variable name="periods" as="xs:integer*">
  <xsl:sequence select="(4, 8, 16, 32, 64)"/>
</xsl:variable>

<xsl:variable name="backgroundColors" as="xs:string*">
  <xsl:sequence 
    select="('background: #CCCCCC;', 'background: #9999CC;',
             'background: #99CCCC;', 'background: #CC99CC;',
             'background: #CCCC99;')"/>
</xsl:variable>

These variables store the value of the period for each column and the background color for each column. To use each variable, you simply use the column number as the index of the single value you want.

Another nice enhancement in XPath 2.0 and XSLT 2.0 is the <xsl:function> element. Let's create two functions in the stylesheet: cellStyle and getResults. The first function returns the border style for each cell, while the second function returns the results (if any) for a given matchup. The parameters to both functions are the row and column numbers of the cell. Listing 12 demonstrates the code for the cellStyle function.

Listing 12. The cellStyle function
<xsl:function name="bracket:cellStyle" as="xs:string">
  <xsl:param name="row" as="xs:integer"/>
  <xsl:param name="column" as="xs:integer"/>
  
  <xsl:variable name="period" as="xs:integer"
    select="subsequence($periods, $column, 1)"/>
  <xsl:variable name="oneQuarter" as="xs:integer"
    select="$period div 4"/>
  <xsl:variable name="lastColumn" as="xs:boolean"
    select="$oneQuarter = count($contestants/contestant)"/>
  <xsl:variable name="position" select="$row mod $period"/>
  
  <xsl:value-of 
    select="if ($position = $oneQuarter) then
                (if ($column = 1) then 'MatchupStart'
                    else 'Solid')
            else if ($position = $period - $oneQuarter) then
                     (if ($column = 1) then 'MatchupEnd'
                         else 'Solid')
            else if ($lastColumn) then 'None'
            else if ($position &lt; $oneQuarter or 
                     $position &gt; $period - $oneQuarter) then 'None'
            else 'MatchupMiddle'"/>
</xsl:function>

Before you determine the cell style, you use four variables. You retrieve the value of $period from the global variable $periods. As I mentioned before, $oneQuarter is simply $period div 4. The Boolean value $lastColumn simply compares $oneQuarter to the count of contestants in the tournament. If those values are equal, you're processing the last column. Finally, the variable $position indicates the position of the current row within the pattern. In other words, row 9 in the table is the first row of the repeating group for columns 1 and 2. Use the position of the row within the pattern to figure out the cell style.

XPath 2.0 and XSLT 2.0 feature an if operator, which has an expression (in parentheses), followed by a then and an else. All of this goes into the select attribute of <xsl:value-of>. In this example, you replace the much more verbose <xsl:choose> element with a single expression. The code here is pretty straightforward, given the discussion of the table formulas; if the logic were more complicated, it might be more maintainable to use <xsl:choose>, even though it requires more typing.

Some syntax notes for <xsl:function>: First, you need to declare a new namespace for the functions. If you invoke bracket:cellStyle() in an XPath expression, the namespace will tell the XSLT 2.0 processor how to find the function. Second, notice that you used the as="xs:string" attribute to indicate that this function returns a string. The <xsl:value-of> element returns one of the five style names; that's the output of the function.

The getResults function is a little more complicated, but not by much, as you can see in Listing 13.

Listing 13. The getResults function
<xsl:function name="bracket:getResults" as="xs:string">
  <xsl:param name="row" as="xs:integer"/>
  <xsl:param name="column" as="xs:integer"/>
  
  <xsl:variable name="period" as="xs:integer"
    select="subsequence($periods, $column, 1)"/>
  <xsl:variable name="oneQuarter" as="xs:integer"
    select="$period div 4"/>
  <xsl:variable name="position" select="$row mod $period"/>
  
  <xsl:choose>
    <xsl:when test="$position = $oneQuarter
                    or
                    $position = $period - $oneQuarter">
      <xsl:variable name="round" select="$column - 1"/>
      <xsl:variable name="index" 
        select="($row + $oneQuarter) div ($oneQuarter * 2)"/>
      <xsl:variable name="currentSeed"
        select="if ($column = 1) then 
                  subsequence($seeds, $index, 1)
                else
                  $results/result[@round=$round][$index]/@winnerSeed"/>
      <xsl:choose>
        <xsl:when test="string-length(string($currentSeed))">
          <xsl:value-of 
            select="concat('[', $currentSeed, '] ', 
                    $contestants/contestant[@seed=$currentSeed])"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:text>&#160;</xsl:text>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise>
      <xsl:text>&#160;</xsl:text>
    </xsl:otherwise>
  </xsl:choose>
</xsl:function>

You have the same parameters as before. You have to do some special processing here for column 1, which contains data from the <contestant> elements, while the other columns contain data from the <result> elements. As with the cellStyle function, you calculate $period and $position, and you use the $oneQuarter variable to simplify the formulas.

If the cell contains a contestant, you calculate three more variables. The $round variable is one less than the column (column 2 contains results from round 1), and you calculate the $index variable according to the formula discussed earlier.

You need to follow two steps to find the appropriate data. First, set the value of the $currentSeed variable. If this is round 1, you use the new subsequence function to select a value from the $seeds variable. For the other rounds, you get the winnerSeed attribute from the appropriate <result> element.

Second, in addition to processing column 1 differently, you need to account for the possibility of a <result> element that doesn't have a winner (winnerSeed=""). If that happens, return a nonbreaking space (&#160;). Because of the way XSLT 2.0 handles data types (this is a subject for a future article), you need to convert $currentSeed to a string, then test the length of the string. If the length of the string is zero, return a nonbreaking space; otherwise, return the seed and the name of the contestant.

Finally, notice that you use <xsl:choose> here. Although you can do everything with a single XPath if statement, the code will be unwieldy. Even though <xsl:choose> is more verbose, the code is cleaner and easier to understand.

Now that you've set up the global variables and functions that you need, the heart of the stylesheet is simple and elegant, as you can see in Listing 14.

Listing 14. The heart of the stylesheet
<xsl:for-each select="1 to 32">
  <xsl:variable name="outerIndex" select="."/>
  <tr>
    <xsl:for-each select="1 to 5">
      <td style="{subsequence($backgroundColors, ., 1)}"
          class="{bracket:cellStyle($outerIndex, .)}">
        <xsl:value-of 
          select="bracket:getResults($outerIndex, .)"/>
      </td>
    </xsl:for-each>
  </tr>
</xsl:for-each>

You have the two loops that create the five columns for the 32 rows of the table. For each cell, the background color is based on the current column number. The cellStyle function determines the border style (class="x"), and the getResults function determines the value of the cell.

Using the stylesheet

As you might expect, you need an XSLT 2.0 processor to use this stylesheet. I won't reprint the entire stylesheet here, but you must use <xsl:stylesheet version="2.0"> so the processor will run in XSLT 2.0 mode. I highly recommend Michael Kay's Saxon processor (see Related topics for more details). Dr. Kay was the editor of the XSLT 2.0 spec, and Saxon was in some ways a test case as the spec was being developed. If you use Saxon, use this command to transform the XML file tourney.xml using the stylesheet results-html.xsl and write the output to the results.html file:

java net.sf.saxon.Transform -o results.html tourney.xml results-html.xsl

To illustrate the flexibility of the stylesheet, take out some of the results in the XML file and run the transform. Listing 15 shows what the <result> elements look like.

Listing 15. XML document with incomplete tournament data
<?xml version="1.0" encoding="UTF-8"?>
<!-- incomplete-tourney.xml -->
<bracket>
. . .
   <results>
      <result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
      <result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
      <result round="1" firstSeed="5" secondSeed="12" winnerSeed="5"/>
      <result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
      <result round="1" firstSeed="6" secondSeed="11" winnerSeed="11"/>
      <result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
      <result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
      <result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
      <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="3" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="3" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="4" firstSeed="" secondSeed="" winnerSeed=""/>
   </results>
</bracket>

Listing 15 represents the state of the tournament after the first round. All of the <result> elements for rounds 2, 3, and 4 have an empty winnerSeed attribute. Despite this, the stylesheet generates the correct bracket, as you can see in Figure 3.

Figure 3. The bracket for an incomplete tournament
An incomplete bracket
An incomplete bracket

Summary

This article demonstrated many of the new features of XPath 2.0 and XSLT 2.0. In the sample application, you took an unwieldy stylesheet and refactored it into a much smaller and more maintainable piece of code. Part of your effort went into analyzing the code to find patterns, but without the to operator, <xsl:function>, and sequences of items, streamlining the stylesheet as much as you did would have been difficult.

Most significantly, you created generic functions that can handle any number of contestants. To change the stylesheet to handle a 32-team tournament, for example, you need to change the sequences $seeds and $periods. You also need to replace select="1 to 5" with select="1 to $rounds", where $rounds represents the number of rounds in the tournament. The most elegant solution, of course, is to create XSLT 2.0 functions that calculate the values for any generic bracket, including the number of rounds, the sequence of seeds (a 32-team bracket features matchups between 1 and 32, 2 and 31, and so forth), and the periods for the various columns. This challenge is left as an exercise for the reader.

The core of the problem is that you need to iterate through 32 rows of the table, but XSLT 1.0 offers you no practical way to do that. The new features of XPath 2.0 and XSLT 2.0 help you solve this problem.


Downloadable resources


Related topics

  • What kind of language is XSLT? (Michael Kay, developerWorks, April 2005): Put the XSLT language in context with this article about the design philosophies behind it, where it comes from, what it's good at, and why to use it.
  • Planning to upgrade XSLT 1.0 to 2.0 (David Marston, Joanne Tong, and Henry Zongaro; developerWorks, July 2007): In this series, learn how to move your stylesheets from XSLT 1.0 to the new standard.
  • Extensible Stylesheet Language Family page: Find all the new recommendations for XSLT 2.0, XPath 2.0, XQuery 1.0, and other specifications that became official recommendations of the World Wide Web Consortium (W3C) on 23 Jan 2007. The entire language definition is spread across several specs.
  • Saxon XSLT 2.0 processor: Find Michael Kay's processor on SourceForge.
  • The Altova XML page on the Altova Web site: Get information and try the Altova XSLT processor. Altova, the maker of XMLSpy and other popular products, makes its XSLT processor available free of charge. It supports XSLT 1.0, XSLT 2.0, and XQuery 1.0. Although it's not open source, the license does allow you to embed the XSLT engine in your products.
  • XML technical library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks.
  • IBM trial software: Build your next development project with trial software available for download directly from developerWorks.
static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML
ArticleID=252136
ArticleTitle=Save time and code with XPath 2.0 and XSLT 2.0
publish-date=09042007