CIS 97YT Index > Reaching to Other Parts of Documents

Reaching to Other Parts of Documents

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&#237;as</phrase>
    <phrase lang="fr">Bonjour</phrase>
    <phrase lang="de">Guten Tag</phrase>
</phrase-list>
</document>

Here's what we want as our output:

Saying Hello

"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.

Reaching with XPath

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() &gt;= 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.

Line 24

Save the lang attribute from the current <phrase> element and in the seek_lang variable.

Lines 25-26

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.

Lines 29 and 31

We use the variable $language to access the node that was stored in lines 25-26. We do this for three reasons:

  1. To prove the point that XSLT variables can contain nodes and node sets, not just numbers and strings.
  2. To make the XSLT easier to read: we could have left out lines 25 and 26, but that would make line 29 look like this: <xsl:value-of select="/document/language[@abbrev=$seek_lang]/name"/>, and we would have to do something similar in line 31.
  3. To make the XSLT more efficient; the complex XPath expression needs to be evaluated only once (line 26), not twice (lines 29-31).

Lines 35-48

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.

Reaching with Keys

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"/>

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.