<?xml version="1.0"?>
<bookstore>
<book isbn="1-56592-235-2" lang="en">
<author>David Flannagan</author>
<title>JavaScript: The Definitive Guide</title>
</book>
<book isbn="1-56592-235-1" lang="en">
<author>David Flannagan</author>
<title>JavaScript: The Definitive Guide</title>
</book>
<book isbn="0-471-40399-7" lang="en">
<author>Dan Margulis</author>
<title>Photoshop 6 for Professionals</title>
</book>
</bookstore>
The document describes several books in a bookstore, providing the ISBN number, a language code, author, and title for each book. <?xml version="1.0" ?>
<titles>
<title>JavaScript: The Definitive Guide</title>
<title>JavaScript: The Definitive Guide</title>
<title>Photoshop 6 for Professionals</title>
</titles>
A flow-driven XSLT stylesheet example might look like this: <?xml version="1.0" ?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/bookstore">
<titles>
<xsl:for-each select="book">
<xsl:call-template name="process-book"/>
</xsl:for-each>
</titles>
</xsl:template>
<xsl:template name="process-book">
<xsl:copy-of select="title"/>
</xsl:template>
</xsl:stylesheet>
The stylesheet matches the root node right away (<xsl:template match="/bookstore">), and then enforces the control flow afterwards by pointing to each <book> node using the combination of the for-each construct and the call-template function. <?xml version="1.0"?>
<titles><title>JavaScript: The Definitive Guide</title>
<title>JavaScript: The Definitive Guide</title><title
>Photoshop 6 for Professionals</title></titles>
To format it nicely, you have to add one more statement to the XSLT stylesheet: <xsl:output indent="yes" encoding="utf-8"/>
The indent="yes" activates the indentation. It is also wise to specify an output encoding explicitly, even though UTF-8 is the default encoding for XSLT. <?xml version="1.0"?>
<bookstore>
<section num="1">
<row num="1">
<book isbn="1-56592-235-2" lang="en">
<author>David Flannagan</author>
<title>JavaScript: The Definitive Guide</title>
</book>
<book isbn="1-56592-235-1" lang="en">
<author>David Flannagan</author>
<title>JavaScript: The Definitive Guide</title>
</book>
<book isbn="0-471-40399-7" lang="en">
<author>Dan Margulis</author>
<title>Photoshop 6 for Professionals</title>
</book>
</row>
</section>
</bookstore>
If you try to continue in the flow-driven way, the XSLT must grow considerably (and as you'll see, needlessly) to adapt to the format change, adding templates to iterate over and process the <section> and <row> elements: <?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:template match="/bookstore">
<titles>
<xsl:for-each select="section">
<xsl:call-template name="process-section"/>
</xsl:for-each>
</titles>
</xsl:template>
<xsl:template name="process-section">
<xsl:for-each select="row">
<xsl:call-template name="process-row"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="process-row">
<xsl:for-each select="book">
<xsl:call-template name="process-book"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="process-book">
<xsl:copy-of select="title"/>
</xsl:template>
</xsl:stylesheet>
Event-driven XSLT
Fortunately, you can make the transformation much simpler by using matched templates. A matched template is one the XSLT processor triggers when its "match" attribute matches the current (context) node, whether that's simply the name of a tag or a more complex XPath expression. For example, the processor will trigger the following template whenever the context node is a "lang" attribute (the ampersand denotes an attribute node rather than an element node).
<xsl:template match="*[@lang]">
<xsl:text>This element has the follwing language id:<xsl:value-of select="@lang"></xsl:text>
</xsl:template>
By processing the file through matched templates, the code makes as few assumptions as possible about the format of the input file. For example, the following stylesheet outputs exactly the same result for both input files, even though their hierarchical formats differ significantly. Here's the revised stylesheet: <?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:template match="/">
<xsl:element name="titles">
<xsl:apply-templates select="node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="book">
<xsl:copy-of select="title"/>
</xsl:template>
</xsl:stylesheet>
This event-driven version matches the root elementregardless of its nameby using the single backslash (/) syntax. Next, it outputs the root <titles> tag, and instructs the stylesheet to continue the iteration over the contents of the current or context node (the root node in this case) with the apply-templates call. <?xml version="1.0"?>
<titles>
<title>JavaScript: The Definitive Guide</title>
<title>JavaScript: The Definitive Guide</title>
<title>Photoshop 6 for Professionals</title>
</titles>
The output is indeed the same as for the first input file, except for one minor annoyance. There are some gratuitous carriage returns before and after the <title> tags that cause the extra white space in the output. <xsl:template match="text()">
<xsl:value-of select="."/>
</xsl:template>
In the example above, the carriage returns stem from the inside of the <section>, <row>, and <book> tags of the input document, one for each tag. <xsl:template match="text()"/>
That line gets rid of the carriage returns by overriding the built-in text template using a custom version that produces no output. <xsl:template match="*|/">
<xsl:apply-templates/>
</xsl:template>
The built-in template for nodes copies nothing to the output, but by invoking the <xsl:apply-templates/> call, allows other templates to match children of the current tag. In other words, any XSLT stylesheet processes all the nodes in the input document by default.| Author's Note: You can gain fine-grained control over extra whitespace characters in the XSLT output by using the <xsl:preserve-space> and <xsl:strip-space> constructs in the stylesheet, or by using the xml:space attribute on XML tags in the input files. |
Imperative XSLT
Unlike most programming languages, XSLT does not favor sequential execution. This is manifested by the verbosity of the related language constructs such as switch and for-each, and by weak support of side-effects (no variables in the traditional sense)
This common example illustrates the verbosity of the imperative approach, which constructs an HTML table, placing the book names in rows and alternating colors on odd and even rows from the input document:
<?xml version="1.0" encoding="utf-8"?>
<table>
<tr>
<td style="color:red;">David Flannagan</td>
</tr>
<tr>
<td style="color:blue;">David Flannagan</td>
</tr>
<tr>
<td style="color:red;">Dan Margulis</td>
</tr>
</table>
![]() | |
| Figure 1. Table with Alternating Colors: The figure shows how alternating red and blue rows of content might render in a browser. |
<?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:strip-space elements="*"/>
<xsl:template match="row">
<table>
<xsl:for-each select="book[1]">
<xsl:call-template name="process-book">
<xsl:with-param name="even" select="false()"/>
</xsl:call-template>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="process-book">
<xsl:param name="even"/>
<xsl:choose>
<xsl:when test="$even">
<tr><td style="color:blue;" >
<xsl:value-of select="author"/></td></tr>
</xsl:when>
<xsl:otherwise>
<tr><td style="color:red;" >
<xsl:value-of select="author"/></td></tr>
</xsl:otherwise>
</xsl:choose>
<xsl:for-each select="following-sibling::book[1]">
<xsl:call-template name="process-book">
<xsl:with-param name="even" select="not($even)"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
The stylesheet creates one table for each <row> element in the input, so it first matches the row tag. Then, it uses the for-each construct to change the execution context to the first book node, calling the process-book template for each with a parameter that controls the row color in the HTML table. The process-book template then outputs the row, with either a red or a blue color depending on the value of the parameter, and calls itself to process the next book element with the opposite parameter value.| Author's Note: The preceding rule applies except in cases of conflict resolution where order is the last decision criteria. |
<?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:strip-space elements="*"/>
<xsl:template match="/">
<table>
<xsl:apply-templates select="@*|node()"/>
</table>
</xsl:template>
<xsl:template match="book[(position() mod 2)=0]">
<tr><td style="color:red;">
<xsl:value-of select="author"/></td></tr>
</xsl:template>
<xsl:template match="book[(position() mod 2)=1]">
<tr><td style="color:blue;" >
<xsl:value-of select="author"/></td></tr>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
In contrast to the procedural approach, this version doesn't define any algorithm. Instead, it specifies two templates for the processor to match: one for even-numbered rows and one for odd-numbered rows. The processor outputs the contents in red for even-numbered elements and in blue for odd-numbered elements.
Key Indexing in XSLT
You can simplify a fair portion of XSLT processing if you understand how to use keys. Keys in XSLT have more or less the same meaning that indexes have in relational databases, except that in XSLT, keys index hierarchical structure rather than relational structure. It's easiest to explain keys with an example.
Imagine that you need to count the number of book copies available for each book title and display them in an HTML table, where each row looks like this:
...
<tr>
<td>JavaScript: The Definitive Guide</td>
<td>2</td>
</tr>
...
Here's a possible solution that illustrates the use of keys: <?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="title"/>
<xsl:template match="/">
<table>
<xsl:apply-templates select="node()|@*"/>
</table>
</xsl:template>
<xsl:template match="book">
<tr>
<td>
<xsl:value-of select="title"/>
</td><td>
<xsl:value-of
select="count(key('kbook',title))"/>
</td>
</tr>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
In the preceding example, the key declaration has three parts: the name of the key, used to refer to it later in the code, the match, that is, the element or attribute of the input data to be indexed, and the use which is an XPath expression that defines the key itself. XPath is a language for addressing parts of an XML document, designed to be used by XSLT and XPointer. See the full language specification for more information. <?xml version="1.0" encoding="utf-8"?>
<table>
<tr>
<td>JavaScript: The Definitive Guide</td>
<td>2</td>
</tr>
<tr>
<td>JavaScript: The Definitive Guide</td>
<td>2</td>
</tr>
<tr>
<td>Photoshop 6 for Professionals</td>
<td>1</td>
</tr>
</table>
That leads to another common XSLT problem: removing duplicates. <?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="title"/>
<xsl:template match="/">
<table>
<xsl:apply-templates select="node()|@*"/>
</table>
</xsl:template>
<xsl:template match="book">
<xsl:if test="generate-id()=
generate-id(key('kbook',title)[1])">
<tr>
<td>
<xsl:value-of select="title"/>
</td><td>
<xsl:value-of
select="count(key('kbook',title))"/>
</td>
</tr>
</xsl:if>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Notice that the key declaration in this example is identical to the previous example. You use the generate-id() function to obtain a unique id for each node, which ensures that every time you pass in the same <book> node, you get the same ID. The ID value depends on which XSLT processor implementation you're using, but typically, ID would be something like n1n1 or d1md1 or some other meaningless string. This example uses the key in a conditional expression that compares the ID of the current node with the ID of the first node returned by the key that matches the title of the current node. In other words, the key that matches "JavaScript: The Definitive Guide" returns two nodes ordered as 1 and 2. During execution, the template matching <book> passes in those same two nodes. When processing node 1, the ID of the node is the same as returned by the key key('kbook','JavaScript: The Definitive Guide')[1]; but when processing node 2, the condition is false. Thus, the stylesheet processes only one book that matches the title "JavaScript: The Definitive Guide."
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. <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 <?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.
Extending XSLT
Sometimes XSLT turns to be too lexically poor to do complex transformations. Two viable options then exist:
<xsl:variable name="foo" select="/"/>
In contrast, if you create a variable with an embedded statement, it returns a result tree fragment: <xsl:variable name="foo">
<xsl:copy-of select="/">
</xsl:variable>
Because XSLT allows more operations on node-sets, it is wise to use the select statement when possible instead of embedded statements. Otherwise, the node-set extension function would come to the rescue. <?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
version="1.0">
<xsl:variable name="all">
<xsl:copy-of select="child::*[1]">
</xsl:variable>
<xsl:template select="/">
<root>
<xsl:copy-of select="ext:node-set($all)">
</root>
</xsl:template>
</xsl:stylesheet>
As a sign of EXSLT's popularity, even Microsoft supports some of the EXSLT functions. However, Microsoft uses a different namespace: urn:schemas-microsoft-com:xslt.
| DevX is a division of Internet.com. © Copyright 2010 Internet.com. All Rights Reserved. Legal Notices |