Using Complex Keys in XSLT
Because the
use attribute of the key definition is an XPath expression, it's possible to create quite elaborate indexes that rely upon complex XPath statements. As an example,
generate-id() makes a unique key for every
<book> node.
<xsl:key name="kbook" match="book" use="generate-id()"/>
The International Standard Book Number, or ISBN (sometimes pronounced "is-ben"), is a unique identifier for books, intended to be used commercially. The following declaration calculates the checksum of an ISBN number by returning
true if the checksum passes the test and
false otherwise.
You can find the check digit of an ISBN by first multiplying each digit of the ISBN by that digit's place in the number sequence, with the leftmost digit being multiplied by 1, the next digit by 2, and so on. Next, take the sum of these multiplications and calculate the sum modulo 11, with "10" represented by the character "X". As an example, for the ISBN 1-56592-235-2, the calculation would be
(1*1 + 2*5 + 3*6 + 4*5 + 5*9 + 6*2 + 7*2 + 8*3 + 9*5) mod 11. The
translate function deletes all the dash (-) characters from the
@isbn attribute value. The following example uses the
substring function to extract each character from the string returned by translate.
<xsl:key
name="kbook"
match="book"
use="boolean(
(substring(translate(@isbn, '-',''), 1,1) * 1 +
substring(translate(@isbn, '-',''), 2,1) * 2 +
substring(translate(@isbn, '-',''), 3,1) * 3 +
substring(translate(@isbn, '-',''), 4,1) * 4 +
substring(translate(@isbn, '-',''), 5,1) * 5 +
substring(translate(@isbn, '-',''), 6,1) * 6 +
substring(translate(@isbn, '-',''), 7,1) * 7 +
substring(translate(@isbn, '-',''), 8,1) * 8 +
substring(translate(@isbn, '-',''), 9,1) * 9)
mod 11 -
substring(translate(@isbn, '-',''),10,1))
or
(boolean(
(substring(translate(@isbn, '-',''), 1,1) * 1 +
substring(translate(@isbn, '-',''), 2,1) * 2 +
substring(translate(@isbn, '-',''), 3,1) * 3 +
substring(translate(@isbn, '-',''), 4,1) * 4 +
substring(translate(@isbn, '-',''), 5,1) * 5 +
substring(translate(@isbn, '-',''), 6,1) * 6 +
substring(translate(@isbn, '-',''), 7,1) * 7 +
substring(translate(@isbn, '-',''), 8,1) * 8 +
substring(translate(@isbn, '-',''), 9,1) * 9)
mod 11 = 10)
and
(substring(translate(@isbn, '-',''),10,1)) = 'X')"/>
Branching vs. Modes in XSLT
XSLT's branching powers are weak compared to the branching statements of conventional languages. Instead, you can use the powerful mechanism of modesoften unexplored by occasional XSLT programmers.
Suppose you have to print all the titles and their respective ISBN codes, checking for the ISBN code validity at the same time. You could represent the desired result as follows:
<?xml version="1.0" encoding="utf-8"?>
<table>
<th>ISBN number check failed</th>
<tr>
<td class="color:red;">1-56592-235-1</td>
</tr>
</table><table>
<th>ISBN number check passed</th>
<tr>
<td>1-56592-235-2</td>
</tr>
<tr>
<td>0-471-40399-7</td>
</tr>
</table>
Without knowing how to use keys and modes, you might implement the solution with the following stylesheet logic:
<xsl:choose>
<xsl:when test="">
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
This example would construct the table by iterating on the ISBN nodes and choosing whether to output
class="color:red;" on each pass. This would be easy if you weren't obliged to group the result and output all the failed ISBN codes first. For the purpose of grouping, the use of keys and modes leads to much simpler code, the alternatives being extension functions or chaining of two different XSLT stylesheets.
<?xml version="1.0" ?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes" encoding="utf-8"/>
<xsl:key
name="kbook"
match="book"
use="boolean(
(substring(translate(@isbn, '-',''), 1,1) * 1 +
substring(translate(@isbn, '-',''), 2,1) * 2 +
substring(translate(@isbn, '-',''), 3,1) * 3 +
substring(translate(@isbn, '-',''), 4,1) * 4 +
substring(translate(@isbn, '-',''), 5,1) * 5 +
substring(translate(@isbn, '-',''), 6,1) * 6 +
substring(translate(@isbn, '-',''), 7,1) * 7 +
substring(translate(@isbn, '-',''), 8,1) * 8 +
substring(translate(@isbn, '-',''), 9,1) * 9) mod
11 - substring(translate(@isbn, '-',''),10,1))
or
(boolean(
(substring(translate(@isbn, '-',''), 1,1) * 1 +
substring(translate(@isbn, '-',''), 2,1) * 2 +
substring(translate(@isbn, '-',''), 3,1) * 3 +
substring(translate(@isbn, '-',''), 4,1) * 4 +
substring(translate(@isbn, '-',''), 5,1) * 5 +
substring(translate(@isbn, '-',''), 6,1) * 6 +
substring(translate(@isbn, '-',''), 7,1) * 7 +
substring(translate(@isbn, '-',''), 8,1) * 8 +
substring(translate(@isbn, '-',''), 9,1) * 9) mod
11 = 10)
and
(substring(translate(@isbn, '-',''),10,1)) = 'X')"/>
<xsl:template match="/">
<table>
<th>ISBN number check failed</th>
<xsl:apply-templates
select="key('kbook',true())"
mode="failed"/>
</table>
<table>
<th>ISBN number check passed</th>
<xsl:apply-templates
select="key('kbook',false())"
mode="passed"/>
</table>
</xsl:template>
<xsl:template match="book" mode="failed">
<tr>
<td class="color:red;">
<xsl:value-of select="@isbn"/>
</td>
</tr>
</xsl:template>
<xsl:template match="book" mode="passed">
<tr>
<td>
<xsl:value-of select="@isbn"/>
</td>
</tr>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
This version processes both types of
book nodesthose that did not pass checksum verification for their ISBN codes, and those that didusing separate templates for the
failed and
passed modes. The stylesheet outputs ISBN values in red for books with ISBN codes that fail the checksum test.