CIT041X Index > Advanced XSLT

XSLT / Advanced XSLT

Modes

Here is another example, in addition to the one given in the book. Let's take our movie database and generate an HTML page with an index at beginning that links to the long description later on in the page. Here's what the result will look like, and here's some of the HTML:

<h2>Movie List</h2>
<ul>
<li><a href="#movie_1">The Maltese Falcon (1931)</a></li>
<li><a href="#movie_2">The Maltese Falcon (1941)</a></li>
<li><a href="#movie_3">Trouble In Paradise (1932)</a></li>
</ul>
<hr>
<h3>
<a name="movie_1">The Maltese Falcon (1931)</a>
</h3>
<!-- etc. -->

Ths means that we have to process each <movie> element twice; once to put it in the index list, and once to insert its long description. A first attempt at a template for the <movie-list> element might look like this:

<xsl:template match="movie-list">
    <html>
    <head>
        <title>Movie List</title>
    </head>
    <body style="font-size:12pt;">
        <h2>Movie List</h2>
        <ul>
        <xsl:apply-templates select="movie"/>
        </ul>
        
        <hr />
        
        <xsl:apply-templates select="movie"/>
    </body>
    </html>
</xsl:template>

<xsl:template match="movie">
    <!-- output for list here -->
</xsl:template>

<xsl:template match="movie">
    <!-- output for description here -->
</xsl:template>

But this won't work. Since both the <xsl:template match="movie"> templates are equally specific (see the rules on pages 329-331 of XML: The Complete Reference), Xalan will use the last one in the file, and use it for both <xsl:apply-templates>.

What we really want to say is that there are two different templates to handle the <movie> element; one when we're in list-building mode, and one when we're in description-output mode. Here's our revised outline:

<xsl:template match="movie-list">
    <html>
    <head>
        <title>Movie List</title>
    </head>
    <body style="font-size:12pt;">
        <h2>Movie List</h2>
        <ul>
        <xsl:apply-templates select="movie" mode="make_list"/>
        </ul>
        
        <hr />
        
        <xsl:apply-templates select="movie" mode="long_desc"/>
    </body>
    </html>
</xsl:template>

<xsl:template match="movie" mode="make_list">
    <!-- output for list here -->
</xsl:template>

<xsl:template match="movie" mode="long_desc">
    <!-- output for description here -->
</xsl:template>

Here's the entire stylesheet, with line numbers for reference.

 1  <xsl:stylesheet version="1.0"
 2      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 3  
 4  <xsl:output method="html"/>
 5  
 6  <xsl:template match="movie-list">
 7      <html>
 8      <head>
 9          <title>Movie List</title>
10      </head>
11      <body style="font-size:12pt;">
12          <h2>Movie List</h2>
13          <ul>
14          <xsl:apply-templates select="movie" mode="make_list"/>
15          </ul>
16          
17          <hr />
18          
19          <xsl:apply-templates select="movie" mode="long_desc"/>
20      </body>
21      </html>
22  </xsl:template>
23  
24  <xsl:template match="movie" mode="make_list">
25      <li><a href="#movie_{position()}">
26      <xsl:value-of select="heading/title"/>
27      (<xsl:value-of select=".//year"/>)</a></li>
28  </xsl:template>
29  
30  <xsl:template match="movie" mode="long_desc">
31      <h3>
32      <a name="movie_{position()}"><xsl:value-of select="heading/title"/>
33      (<xsl:value-of select=".//year"/>)</a></h3>
34      <div style="border-bottom: 1px dashed black;">
35      <xsl:apply-templates select="synopsis"/>
36      <xsl:apply-templates select="credits/actors"/>
37      </div>
38  </xsl:template>
39  
40  
41  <xsl:template match="synopsis">
42      <div style="margin-left: 2em; margin-right: 2em;">
43      <xsl:for-each select="para">
44          <p><xsl:apply-templates/></p>
45      </xsl:for-each>
46      </div>
47  </xsl:template>
48  
49  
50  <xsl:template match="credits/actors">
51  <h4>The Cast:</h4>
52  <ul>
53      <xsl:apply-templates select="actor"/>
54  </ul>
55  </xsl:template>
56  
57  <xsl:template match="actor[@special='yes']">
58      <li><span style="color:green;">
59      <xsl:value-of select="."/></span></li>
60  </xsl:template>
61  
62  <xsl:template match="actor">
63      <li><xsl:value-of select="."/></li>
64  </xsl:template>
65          
66  </xsl:stylesheet>
Line 25

We generate the link name by appending the movie's position in the list to the word movie. Notice that we need the curly braces to force evaluation of an expression. That is much shorter than the equivalent:

<a>
    <xsl:attribute name="href">#movie_<xsl:value-of select="position()"/></xsl:attribute>
Line 27 and Line 33

Laziness reigns supreme. Rather than use heading/technical-details/year as a selector, I decided to use .//year to find the <year> descendant of the current node.

Line 53; Lines 57-64

You might think that this won't work either; how can I have two templates that match <actor>? The reason it puts special actors in green and non-special actors in black is this XSLT rule: “A pattern with a successful test expression in square brackets overrides a pattern with no test expression that is otherwise identical.”

By the way, my initial approach to this was 100% wrong. I wanted to use an <xsl:if> twice, once to put in a preceding <span> and once to put in a closing </span>. This is wrong, because the template is not well-formed!

<xsl:template match="actor">
	<li>
	<xsl:if test="@special='yes'">
		<span style="color: green;">
	</xsl:if>
	<xsl:value-of select="."/>
	<xsl:if test="@special='yes'">
		</span>
	</xsl:if>
	</li>
</xsl:template>