XSLTBy Doug TidwellAugust 2001 0-596-00053-7, Order Number: 0537 473 pages, $39.95 |
Chapter 5
Creating Links and Cross-ReferencesContents:
Generating Links with the id() Function
If you're creating a web site, publishing a book, or creating an XML transaction, chances are many pieces of information will refer to other things. This chapter discusses a several ways to link XML elements. It reviews three techniques:
Generating Links with the key() Function
Generating Links in Unstructured Documents
Summary
Using the id() function
Doing more advanced linking with the key() function
Generating links in unstructured documents
Generating Links with the id() Function
Our first attempt at linking will be with the XPath id() function.
The ID, IDREF, and IDREFs Datatypes
Three of the basic datatypes supported by XML Document Type Definitions (DTDs) are ID, IDREF, and IDREFS. Here's a simple DTD that illustrates these datatypes:
<!--glossary.dtd--> <!--The containing tag for the entire glossary--> <!ELEMENT glossary (glentry+) > <!--A glossary entry--> <!ELEMENT glentry (term,defn+) > <!--The word being defined--> <!ELEMENT term (#PCDATA) > <!--The id is used for cross-referencing, and the xreftext is the text used by cross-references.--> <!ATTLIST term id ID #REQUIRED xreftext CDATA #IMPLIED > <!--The definition of the term--> <!ELEMENT defn (#PCDATA | xref | seealso)* > <!--A cross-reference to another term--> <!ELEMENT xref EMPTY > <!--refid is the ID of the referenced term--> <!ATTLIST xref refid IDREF #REQUIRED > <!--seealso refers to one or more other definitions--> <!ELEMENT seealso EMPTY> <!ATTLIST seealso refids IDREFS #REQUIRED >
Each value of the id attribute must be unique.
Each value of the refid attribute must match a value of an id attribute elsewhere in the document.
An XML Document in Need of Links
<?xml version="1.0" ?> <!DOCTYPE glossary SYSTEM "glossary.dtd"> <glossary> <glentry> <term id="applet">applet</term> <defn> An application program, written in the Java programming language, that can be retrieved from a web server and executed by a web browser. A reference to an applet appears in the markup for a web page, in the same way that a reference to a graphics file appears; a browser retrieves an applet in the same way that it retrieves a graphics file. For security reasons, an applet's access rights are limited in two ways: the applet cannot access the file system of the client upon which it is executing, and the applet's communication across the network is limited to the server from which it was downloaded. Contrast with <xref refid="servlet"/>. <seealso refids="wildcard-char DMZlong pattern-matching"/> </defn> </glentry> <glentry> <term id="DMZlong" xreftext="demilitarized zone">demilitarized zone (DMZ)</term> <defn> In network security, a network that is isolated from, and serves as a neutral zone between, a trusted network (for example, a private intranet) and an untrusted network (for example, the Internet). One or more secure gateways usually control access to the DMZ from the trusted or the untrusted network. </defn> </glentry> <glentry> <term id="DMZ">DMZ</term> <defn> See <xref refid="DMZlong"/>. </defn> </glentry> <glentry> <term id="pattern-matching">pattern-matching character</term> <defn> A special character such as an asterisk (*) or a question mark (?) that can be used to represent zero or more characters. Any character or set of characters can replace a pattern-matching character. </defn> </glentry> <glentry> <term id="servlet">servlet</term> <defn> An application program, written in the Java programming language, that is executed on a web server. A reference to a servlet appears in the markup for a web page, in the same way that a reference to a graphics file appears. The web server executes the servlet and sends the results of the execution (if there are any) to the web browser. Contrast with <xref refid="applet" />. </defn> </glentry> <glentry> <term id="wildcard-char">wildcard character</term> <defn> See <xref refid="pattern-matching"/>. </defn> </glentry> </glossary>A Stylesheet That Uses the id() Function
Let's look at our desired output. What we want is an HTML document, such as that shown in Figure 5-1, that displays the various definitions in an easy-to-read format, with the cross-references formatted as hyperlinks.
In the HTML document, we'll need to address several things in our stylesheet:
Figure 1-1. HTML document with generated cross-references
Here's the template that takes care of our first task, generating the HTML <title> and the <h1>:
<xsl:template match="glossary"> <html> <head> <title> <xsl:text>Glossary Listing: </xsl:text> <xsl:value-of select="glentry[1]/term"/> <xsl:text> - </xsl:text> <xsl:value-of select="glentry[last()]/term"/> </title> </head> <body> <h1> <xsl:text>Glossary Listing: </xsl:text> <xsl:value-of select="glentry[1]/term"/> <xsl:text> - </xsl:text> <xsl:value-of select="glentry[last()]/term"/> </h2> <xsl:apply-templates select="glentry"/> </body> </html> </xsl:template><xsl:template match="glentry"> <p> <b> <a name="{@id}"/> <xsl:value-of select="term"/> <xsl:text>: </xsl:text> </b> <xsl:apply-templates select="defn"/> </p> </xsl:template>The name attribute of this HTML <a> element is generated with an attribute value template.
Our next step is to process the cross-reference. Here's the template for the <xref> element:
<xsl:template match="xref"> <a href="#{@refid}"> <xsl:choose> <xsl:when test="id(@refid)/@xreftext"> <xsl:value-of select="id(@refid)/@xreftext"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="id(@refid)"/> </xsl:otherwise> </xsl:choose> </a> </xsl:template>We create the <a> element in two steps:
For the first step, we know that the href attribute must contain a hash mark (#) followed by the name of the anchor point. Because we generated all the named anchors from the id attributes of the various <glentry> elements, we know the name of the anchor point is the same as the id.
<xsl:template match="seealso"> <b> <xsl:text>See also: </xsl:text> </b> <xsl:for-each select="id(@refids)"> <a href="#{@id}"> <xsl:choose> <xsl:when test="@xreftext"> <xsl:value-of select="@xreftext"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="."/> </xsl:otherwise> </xsl:choose> </a> <xsl:if test="not(position()=last())"> <xsl:text>, </xsl:text> </xsl:if> </xsl:for-each> <xsl:text>. </xsl:text> </xsl:template>We've done several useful things with the id() function. We've been able to use attributes of type ID to discover the links between related pieces of information, and we've converted the XML into HTML links, renderable in an ordinary household browser. If this is the only kind of linking and referencing you need to do, that's great. Unfortunately, there are times when we need to do more, and on those occasions, the id() function doesn't quite cut it. We'll mention the limitations of the id() function briefly, then we'll discuss XSLT functions that let us overcome them.
Limitations of IDs
To this point, we've been able to generate cross-references easily. There are some limitations of the ID datatype and the id() function, though:
The value of an ID must be an XML name. In other words, it can't contain spaces, it can't start with a number, and it's subject to the other restrictions of XML names. (Section 2.3 of the XML Recommendation defines these restrictions; see http://www.w3.org/TR/REC-xml if you'd like more information.)
To get around all of these limitations, XSLT defines the key() function. We'll discuss that function in the next section.
Generating Links with the key() Function
Now that we've covered the id() function in great detail, we'll move on to XSLT's key() function. Each key() function effectively creates an index of the document. You can then use that index to find all elements that have a particular property. Conceptually, key() works like a database index. If you have a database of (U.S. postal) addresses, you might want to index that database by the people's last names, by the states in which they live, by their Zip Codes, etc. Each index takes a certain amount of time to build, but it saves processing time later. If you want to find all the people who live in the state of Idaho, you can use the index to find all those people directly; you don't have to search the entire database.
Defining a key()
You define a key() function with the <xsl:key> element:
<xsl:key name="language-index" match="defn" use="@language"/>A Slightly More Complicated XML Document in Need of Links
<glentry> <term id="DMZlong" xreftext="demilitarized zone">demilitarized zone (DMZ)</term> <defn topic="security" language="en"> In network security, a network that is isolated from, and serves as a neutral zone between, a trusted network (for example, a private intranet) and an untrusted network (for example, the Internet). One or more secure gateways usually control access to the DMZ from the trusted or the untrusted network. </defn> <defn topic="security" language="it"> [Pretend this is an Italian definition of DMZ.] </defn> <defn topic="security" language="es"> [Pretend this is a Spanish definition of DMZ.] </defn> <defn topic="security" language="jp"> [Pretend this is a Japanese definition of DMZ.] </defn> <defn topic="security" language="de"> [Pretend this is a German definition of DMZ.] </defn> </glentry> <glentry> <term id="DMZ" acronym="yes">DMZ</term> <defn topic="security" language="en"> See <xref refid="DMZlong"/>. </defn> </glentry><!--The word being defined--> <!ELEMENT term (#PCDATA) > <!--The id is used for cross-referencing, and the xreftext is the text used by cross-references.--> <!ATTLIST term id ID #REQUIRED xreftext CDATA #IMPLIED acronym (yes|no) "no"> <!--The definition of the term--> <!ELEMENT defn (#PCDATA | xref | seealso)* > <!--The topic defines the subject of the definition, the language code defines the language of this definition, and the acronym is yes or no (default is no).--> <!ATTLIST defn topic (Java|general|security) "general" language (en|de|es|it|jp) "en">
We can find all <defn> elements that apply to a particular topic.
We can find all <term> elements that are acronyms.
Stylesheets That Use the key() Function
<xsl:key name="language-index" match="defn" use="@language"/><xsl:param name="targetLanguage"/>java org.apache.xalan.xslt.Process -in moreterms.xml -xsl crossref2.xsl -param targetLanguage itIf you use Michael Kay's Saxon processor, the syntax looks like this:
java com.icl.saxon.StyleSheet moreterms.xml crossref2.xsl targetLanguage=it<xsl:template match="glossary"> <html> <head> <title> <xsl:text>Glossary Listing: </xsl:text> <xsl:value-of select="key('language-index', $targetLanguage)[1]/preceding-sibling::term"/> <xsl:text> - </xsl:text> <xsl:value-of select="key('language-index', $targetLanguage)[last()]/preceding-sibling::term"/> </title> </head> <body> <h1> <xsl:text>Glossary Listing: </xsl:text> <xsl:value-of select="key('language-index', $targetLanguage)[1]/ancestor::glentry/term"/> <xsl:text> - </xsl:text> <xsl:value-of select="key('language-index', $targetLanguage)[last()]/ancestor::glentry/term"/> </h2> <xsl:for-each select="key('language-index', $targetLanguage)"> <xsl:apply-templates select="ancestor::glentry"/> </xsl:for-each> </body> </html> </xsl:template><h1> <xsl:text>Glossary Listing: </xsl:text> <xsl:value-of select="key('language-index', $targetLanguage)[1]/ancestor::glentry/term"/> <xsl:text> - </xsl:text> <xsl:value-of select="key('language-index', $targetLanguage)[last()]/ancestor::glentry/term"/> </h2><xsl:for-each select="key('language-index', $targetLanguage)"> <xsl:apply-templates select="ancestor::glentry"/> </xsl:for-each><xsl:apply-templates select="defn[@language=$targetLanguage]"/>Here's the complete stylesheet:
<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:key name="language-index" match="defn" use="@language"/> <xsl:param name="targetLanguage"/> <xsl:template match="/"> <xsl:apply-templates select="glossary"/> </xsl:template> <xsl:template match="glossary"> <html> <head> <title> <xsl:text>Glossary Listing: </xsl:text> <xsl:value-of select="key('language-index', $targetLanguage)[1]/preceding-sibling::term"/> <xsl:text> - </xsl:text> <xsl:value-of select="key('language-index', $targetLanguage)[last()]/preceding-sibling::term"/> </title> </head> <body> <h1> <xsl:text>Glossary Listing: </xsl:text> <xsl:value-of select="key('language-index', $targetLanguage)[1]/ancestor::glentry/term"/> <xsl:text> - </xsl:text> <xsl:value-of select="key('language-index', $targetLanguage)[last()]/ancestor::glentry/term"/> </h2> <xsl:for-each select="key('language-index', $targetLanguage)"> <xsl:apply-templates select="ancestor::glentry"/> </xsl:for-each> </body> </html> </xsl:template> <xsl:template match="glentry"> <p> <b> <a name="{term/@id}"/> <xsl:value-of select="term"/> <xsl:text>: </xsl:text> </b> <xsl:apply-templates select="defn[@language=$targetLanguage]"/> </p> </xsl:template> <xsl:template match="defn"> <xsl:apply-templates select="*|comment()|processing-instruction()|text()"/> </xsl:template> <xsl:template match="xref"> <a href="#{@refid}"> <xsl:choose> <xsl:when test="id(@refid)/@xreftext"> <xsl:value-of select="id(@refid)/@xreftext"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="id(@refid)"/> </xsl:otherwise> </xsl:choose> </a> </xsl:template> <xsl:template match="seealso"> <b> <xsl:text>See also: </xsl:text> </b> <xsl:for-each select="id(@refids)"> <a href="#{@id}"> <xsl:choose> <xsl:when test="@xreftext"> <xsl:value-of select="@xreftext"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="."/> </xsl:otherwise> </xsl:choose> </a> <xsl:if test="not(position()=last())"> <xsl:text>, </xsl:text> </xsl:if> </xsl:for-each> <xsl:text>. </xsl:text> </xsl:template> </xsl:stylesheet>Given our sample document and a targetLanguage of en, we get these results:
<html> <head> <title>Glossary Listing: applet - wildcard character</title> </head> <body> <h1>Glossary Listing: applet - wildcard character</h2> <p> <b><a name="applet"></a>applet: </b> An application program, written in the Java programming language, that can be retrieved from a web server and executed by a web browser. A reference to an applet appears in the markup for a web page, in the same way that a reference to a graphics file appears; a browser retrieves an applet in the same way that it retrieves a graphics file. For security reasons, an applet's access rights are limited in two ways: the applet cannot access the file system of the client upon which it is executing, and the applet's communication across the network is limited to the server from which it was downloaded. Contrast with <a href="#servlet">servlet</a>. ...Changing the targetLanguage to it, the results are now different:
<html> <head> <title>Glossary Listing: applet - servlet</title> </head> <body> <h1>Glossary Listing: applet - servlet</h2> <p> <b><a name="applet"></a>applet: </b> [Pretend this is an Italian definition of applet.] </p> <p> <b><a name="DMZlong"></a>demilitarized zone (DMZ): </b> [Pretend this is an Italian definition of DMZ.] </p> <p> <b><a name="servlet"></a>servlet: </b> [Pretend this is an Italian definition of servlet.] </p> </body> </html><xsl:template match="xref"> <a href="#{@refid}"> <xsl:choose> <xsl:when test="key('term-ids', @refid)[1]/@xreftext"> <xsl:value-of select="key('term-ids', @refid)[1]/@xreftext"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="key('term-ids', @refid)[1]"/> </xsl:otherwise> </xsl:choose> </a> </xsl:template>The key() function and the IDREFS datatype
For all its flexibility, the key() function doesn't support anything like the IDREFS datatype. We can try to use the key() function the same way we used id():
<xsl:template match="seealso"> <b> <xsl:text>See also: </xsl:text> </b> <xsl:for-each select="key('term-ids', @refids)"> <a> ...There are several ways to deal with this problem; we'll go through our choices next.
Solution #1: Replace the IDREFS datatype
<seealso> <item refid="wildcard-character"/> <item refid="DMZlong"/> <item refid="pattern-matching"/> </seealso>Solution #2: Use the XPath contains() function
A second approach is to leave the structure of the XML document unchanged, then use the XPath contains() function to find all <term> elements whose id attributes are contained in the value of the refids attribute of the <seealso> element. Here's how that would work:
<xsl:template match="seealso"> <b> <xsl:text>See also: </xsl:text> </b> <xsl:variable name="id_list" select="@refids"/> <xsl:for-each select="//term"> <xsl:if test="contains($id_list, @id)"> <a href="#{@id}"> <xsl:choose> <xsl:when test="@xreftext"> <xsl:value-of select="@xreftext"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="."/> </xsl:otherwise> </xsl:choose> </a> <xsl:if test="not(position()=last())"> <xsl:text>, </xsl:text> </xsl:if> </xsl:if> </xsl:for-each> <xsl:text>. </xsl:text> </xsl:template>Here are the results our stylesheet generates:
<html> <head> <title>Glossary Listing: applet - wildcard character</title> </head> <body> <h1>Glossary Listing: applet - wildcard character</h2> <p> <b><a name="applet"></a>applet: </b> An application program, written in the Java programming language, that can be retrieved from a web server and executed by a web browser. A reference to an applet appears in the markup for a web page, in the same way that a reference to a graphics file appears; a browser retrieves an applet in the same way that it retrieves a graphics file. For security reasons, an applet's access rights are limited in two ways: the applet cannot access the file system of the client upon which it is executing, and the applet's communication across the network is limited to the server from which it was downloaded. Contrast with <a href="#servlet">servlet</a>. <b>See also: </b><a href="#DMZlong">demilitarized zone</a>, <a href="#DMZ"> DMZ</a>, <a href="#pattern-matching">pattern-matching character</a>, <a href="#wildcard-char">wildcard character</a>. </p> ...So our second attempt at solving this problem doesn't require us to change the structure of the XML document, but in this case, we have to change some of our IDs so that the problem we just mentioned doesn't occur. That's probably going to be a maintenance nightmare and a serious drawback to this approach.
Solution #3: Use recursion to process the IDREFS datatype
<xsl:template match="seealso"> <b> <xsl:text>See also: </xsl:text> </b> <xsl:call-template name="resolveIDREFS"> <xsl:with-param name="stringToTokenize" select="@refids"/> </xsl:call-template> </xsl:template> <xsl:template name="resolveIDREFS"> <xsl:param name="stringToTokenize"/> <xsl:variable name="normalizedString"> <xsl:value-of select="concat(normalize-space($stringToTokenize), ' ')"/> </xsl:variable> <xsl:choose> <xsl:when test="$normalizedString!=' '"> <xsl:variable name="firstOfString" select="substring-before($normalizedString, ' ')"/> <xsl:variable name="restOfString" select="substring-after($normalizedString, ' ')"/> <a href="#{$firstOfString}"> <xsl:choose> <xsl:when test="key('term-ids', $firstOfString)[1]/@xreftext"> <xsl:value-of select="key('term-ids', $firstOfString)[1]/@xreftext"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="key('term-ids', $firstOfString)[1]"/> </xsl:otherwise> </xsl:choose> </a> <xsl:if test="$restOfString!=''"> <xsl:text>, </xsl:text> </xsl:if> <xsl:call-template name="resolveIDREFS"> <xsl:with-param name="stringToTokenize" select="$restOfString"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:text>.</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:template>The resolveIDREFS template works like this:
One technique in particular is worth mentioning here: the way we handled whitespace in the attribute value. We pass the string we want to tokenize as a parameter to the template, but we need to normalize the whitespace. We use two XPath functions to do this: normalize-space() and concat(). The call looks like this:
<xsl:template name="resolveIDREFS"> <xsl:param name="stringToTokenize"/> <xsl:variable name="normalizedString"> <xsl:value-of select="concat(normalize-space($stringToTokenize), ' ')"/> </xsl:variable><seealso refids=" wildcard-char DMZlong pattern-matching "/>Because we're using the substring-before() and substring-after() functions to find the first token and the rest of the string, it's important that there be at least one space in the string. (It's possible, of course, that an IDREFS attribute contains only one ID.) We use the concat() function to add a space to the end of the string. When the string contains only that space, we know we're done.
Although this approach is more tedious, it does everything we need it to do. We don't have to change our XML document, and we correctly resolve all the IDs in the IDREFS datatype.
Solution #4: Use an extension function
The final approach is to write an extension function that tokenizes the refids attribute and returns a node-set containing all id values we need to search for. Xalan ships with an extension that does just that. We invoke the extension function on the value of the refids attribute, then use a <xsl:for-each> element to process all items in the node-set. We'll cover extension functions in , but for now, here's what the stylesheet looks like:
<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:java="http://xml.apache.org/xslt/java" exclude-result-prefixes="java"> <xsl:output method="html" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:key name="term-ids" match="term" use="@id"/> <xsl:template match="/"> <xsl:apply-templates select="glossary"/> </xsl:template> <xsl:template match="glossary"> <html> <head> <title> <xsl:text>Glossary Listing: </xsl:text> <xsl:value-of select="glentry[1]/term"/> <xsl:text> - </xsl:text> <xsl:value-of select="glentry[last()]/term"/> </title> </head> <body> <h1> <xsl:text>Glossary Listing: </xsl:text> <xsl:value-of select="glentry[1]/term"/> <xsl:text> - </xsl:text> <xsl:value-of select="glentry[last()]/term"/> </h2> <xsl:apply-templates select="glentry"/> </body> </html> </xsl:template> <xsl:template match="glentry"> <p> <b> <a name="{term/@id}"/> <xsl:value-of select="term"/> <xsl:text>: </xsl:text> </b> <xsl:apply-templates select="defn"/> </p> </xsl:template> <xsl:template match="defn"> <xsl:apply-templates select="*|comment()|processing-instruction()|text()"/> </xsl:template> <xsl:template match="xref"> <a href="#{@refid}"> <xsl:choose> <xsl:when test="key('term-ids', @refid)[1]/@xreftext"> <xsl:value-of select="key('term-ids', @refid)[1]/@xreftext"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="key('term-ids', @refid)[1]"/> </xsl:otherwise> </xsl:choose> </a> </xsl:template> <xsl:template match="seealso"> <b> <xsl:text>See also: </xsl:text> </b> <xsl:for-each select="java:org.apache.xalan.lib.Extensions.tokenize(@refids)"> <a href="{key('term-ids', .)/@id}"> <xsl:choose> <xsl:when test="key('term-ids', .)/@xreftext"> <xsl:value-of select="key('term-ids', .)/@xreftext"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="key('term-ids', .)"/> </xsl:otherwise> </xsl:choose> </a> <xsl:if test="not(position()=last())"> <xsl:text>, </xsl:text> </xsl:if> </xsl:for-each> <xsl:text>.</xsl:text> </xsl:template> </xsl:stylesheet>Hopefully at this point you're convinced of at least one of the following two things:
Advantages of the key() Function
The value we use to look up elements in the key function isn't constrained to be an XML name. If we use the ID datatype, its value can't contain spaces, among other constraints.
Generating Links in Unstructured Documents
Before we leave the topic of linking, we'll discuss one more useful technique. So far, all of this chapter's examples have been structured nicely. When there was a relationship between two pieces of information, we had an id and refid pair to match them. What happens if the XML document you're transforming isn't written that way? Fortunately, we can use the key() function and a new function, generate-id(), to create structure where there isn't any.
An Unstructured XML Document in Need of Links
<?xml version="1.0" ?> <!DOCTYPE glossary SYSTEM "unstructuredglossary.dtd"> <glossary> <glentry> <term>applet</term> <defn> An application program, written in the Java programming language, that can be retrieved from a web server and executed by a web browser. A reference to an applet appears in the markup for a web page, in the same way that a reference to a graphics file appears; a browser retrieves an applet in the same way that it retrieves a graphics file. For security reasons, an applet's access rights are limited in two ways: the applet cannot access the file system of the client upon which it is executing, and the applet's communication across the network is limited to the server from which it was downloaded. Contrast with <refterm>servlet</refterm>. </defn> </glentry> <glentry> <term>demilitarized zone</term> <defn> In network security, a network that is isolated from, and serves as a neutral zone between, a trusted network (for example, a private intranet) and an untrusted network (for example, the Internet). One or more secure gateways usually control access to the DMZ from the trusted or the untrusted network. </defn> </glentry> <glentry> <term>DMZ</term> <defn> See <refterm>delimitarized zone</refterm>. </defn> </glentry> <glentry> <term>pattern-matching character</term> <defn> A special character such as an asterisk (*) or a question mark (?) that can be used to represent zero or more characters. Any character or set of characters can replace a pattern-matching character. </defn> </glentry> <glentry> <term>servlet</term> <defn> An application program, written in the Java programming language, that is executed on a web server. A reference to a servlet appears in the markup for a web page, in the same way that a reference to a graphics file appears. The web server executes the servlet and sends the results of the execution (if there are any) to the web browser. Contrast with <refterm>applet</refterm>. </defn> </glentry> <glentry> <term>wildcard character</term> <defn> See <refterm>pattern-matching character</refterm>. </defn> </glentry> </glossary>We'll go through the relevant parts of the stylesheet. First, we define the key:
<xsl:key name="terms" match="term" use="."/>Second, we need to generate a named anchor point for each <term> element:
<xsl:template match="glentry"> <p> <b> <a name="{generate-id(term)}"> <xsl:value-of select="term"/> <xsl:text>: </xsl:text> </a> </b> <xsl:apply-templates select="defn"/> </p> </xsl:template><xsl:template match="refterm"> <a href="#{generate-id(key('terms', .))}"> <xsl:value-of select="."/> </a> </xsl:template>Our generated HTML output creates cross-references similar to those in our earlier stylesheets:
<h1>Glossary Listing: applet - wildcard character</h2> <p> <b><a name="N11">applet: </a></b> An application program, written in the Java programming language, that can be retrieved from a web server and executed by a web browser. A reference to an applet appears in the markup for a web page, in the same way that a reference to a graphics file appears; a browser retrieves an applet in the same way that it retrieves a graphics file. For security reasons, an applet's access rights are limited in two ways: the applet cannot access the file system of the client upon which it is executing, and the applet's communication across the network is limited to the server from which it was downloaded. Contrast with <a href="#N53">servlet</a>. </p> ... <p> <b><a name="N53">servlet: </a></b> An application program, written in the Java programming language, that is executed on a web server. A reference to a servlet appears in the markup for a web page, in the same way that a reference to a graphics file appears. The web server executes the servlet and sends the results of the execution (if there are any) to the web browser. Contrast with <a href="#N11">applet</a>. </p>The generate-id() Function
TIP: The generate-id() function is not required to check if an ID it generates duplicates an ID that's already in the document. In other words, if your document has an attribute of type ID with a value of sdk3829a, there's a possibility that an ID returned by generate-id() will also be sdk3829a. It's not likely, but be aware that it could happen.If you invoke generate-id() against two different nodes, the two generated IDs will be different.
If no node-set is passed in (you invoke generate-id()), the function generates an ID for the context node.
Summary
In this chapter, we've examined a several ways to generate links and cross-references between different parts of a document. If your XML document has a reasonable amount of structure, you can use the id() and key() functions to define many different relationships between the parts of a document. Even if your XML document isn't structured, you may be able to use key() and generate-id() to create simple references. In the next chapter, we'll look at sorting and grouping, two more ways to organize the information in our XML documents.
Back to: XSLT
© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com