Consider the following document:
<document> <language abbrev="fr"> <name>French</name> <place>France</place> <place>Canada</place> <place>Switzerland</place> </language> <language abbrev="de"> <name>German</name> <place>Germany</place> <place>Austria</place> <place>Switzerland</place> </language> <language abbrev="sp"> <name>Spanish</name> <place>Spain</place> <place>Mexico</place> <place>Argentina</place> </language> <language abbrev="it"> <name>Italian</name> <place>Italy</place> </language> <phrase-list> <phrase lang="it">Bon giorno</phrase> <phrase lang="sp">Buenos días</phrase> <phrase lang="fr">Bonjour</phrase> <phrase lang="de">Guten Tag</phrase> </phrase-list> </document>
Here's what we want as our output:
"Bon giorno" is Italian, spoken in Italy.
"Buenos días" is Spanish, spoken in Spain, Mexico, and Argentina.
"Bonjour" is French, spoken in France, Canada, and Switzerland.
"Guten Tag" is German, spoken in Germany, Austria, and Switzerland.
This will involve “reaching” to other parts of the
document. That is, when we process a <phrase>
element, we have to “reach” back to the corresponding
<language>
element to get the list of countries
that speak a language. We can accomplish this with XPath, as in
the following XSLT stylesheet:
1 <?xml version="1.0"?> 2 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 3 version="1.0"> 4 5 <xsl:output method="html" indent="yes"/> 6 7 <xsl:template match="/"> 8 <xsl:apply-templates select="document/phrase-list"/> 9 </xsl:template> 10 11 <xsl:template match="phrase-list"> 12 <html> 13 <head> 14 <title>Saying Hello</title> 15 </head> 16 <body> 17 <h2 align="center">Saying Hello</h2> 18 <xsl:apply-templates select="phrase"/> 19 </body> 20 </html> 21 </xsl:template> 22 23 <xsl:template match="phrase"> 24 <xsl:variable name="seek_lang" select="@lang"/> 25 <xsl:variable name="language" 26 select="/document/language[@abbrev=$seek_lang]"/> 27 <p> 28 "<xsl:value-of select="."/>" is 29 <xsl:value-of select="$language/name"/>, 30 spoken in 31 <xsl:apply-templates select="$language/place"/>. 32 </p> 33 </xsl:template> 34 35 <xsl:template match="place"> 36 <xsl:value-of select="."/> 37 <xsl:choose> 38 <xsl:when test="position() = last()-1"> 39 <xsl:if test="last() >= 3"> 40 <xsl:text>,</xsl:text> 41 </xsl:if> 42 <xsl:text> and </xsl:text> 43 </xsl:when> 44 <xsl:when test="position() != last()"> 45 <xsl:text>, </xsl:text> 46 </xsl:when> 47 </xsl:choose> 48 </xsl:template> 49 </xsl:stylesheet>
The area of greatest interest to us is in lines 24 through 31.
Save the lang
attribute from the current
<phrase>
element and in the
seek_lang
variable.
Line 26 selects the <language>
element
whose abbrev
attribute is equal to the language
we are seeking. This node is stored into variable
language
.
You may wonder why we needed line 24—why didn’t we just
say select="/document/language[@abbrev=@lang]"
? That
won’t work, because the moment we get to the language
step in the path, we are in the <language>
context, and
those nodes do not have a lang
attribute.
We use the variable $language
to access the node
that was stored in lines 25-26. We do this for three reasons:
<xsl:value-of select="/document/language[@abbrev=$seek_lang]/name"/>
, and we would have to do something similar in line 31.
This template has nothing to do with reaching or variables; it just shows what you have to go through to get the punctuation correct when you create a comma-separated list of items.
While this approach does work, it becomes inefficient as the
paths become more complex and the number of nodes to search becomes
greater. If we could build a “lookup table” where
the key is the abbreviation and the value is the corresponding
language node, then we wouldn’t need to do an expensive
XPath evaluation. This is exactly what XSLT’s
<xsl:key>
element and key()
functions do.
The first step is to insert this line at the beginning of the stylesheet (between lines 6 and 7 in the listing above).
<xsl:key name="langs" match="language" use="@abbrev"/>
name
gives the name of the index; you choose the
name.match
tells which nodes will be indexeduse
is an XPath expression (in the context of the
match
nodes) that gives the key for the index.Now we replace the template starting on line 23 with this:
<xsl:template match="phrase"> <p> "<xsl:value-of select="."/>" is <xsl:value-of select="key('langs',@lang)/name"/>, spoken in <xsl:apply-templates select="key('langs',@lang)/place"/>. </p> </xsl:template>
The text in bold shows the use of the key()
function,
which takes two arguments: the name of the index to use for lookup,
and the key to use for the lookup.