A Stylesheet That Uses the id Function

page 85 • We need to process any seealso elements, as well. These elements are handled similarly to the xref elements, the main difference being that the refids attribute of the seealso element can refer to more than one glossary entry. Figure 5-1. HTML document with generated cross-references Heres the template that takes care of our first task, generating the HTML title and the h1 : xsl:template match=glossary html head title xsl:textGlossary 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:textGlossary Listing: xsl:text xsl:value-of select=glentry[1]term xsl:text - xsl:text xsl:value-of select=glentry[last]term h1 xsl:apply-templates select=glentry body html xsl:template We generate the title and h1 using the XPath expressions glentry[1]term for the first term in the document, and using glentry[last]term for the last term. Our next step is to process all the glentry elements. Well generate an HTML paragraph for each one, and then well generate a named anchor point, using the id attribute as the name of the anchor. Heres the 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 page 86 p xsl:template In this template, were using an attribute value template to generate the name attribute of the HTML a element. The XPath expression id retrieves the id attribute of the glentry element were currently processing. We use this attribute to generate a named anchor. We then write the term itself in bold and apply the template for the defn element. In our output document, each glossary entry contains a paragraph with the highlighted term and its definition. The name attribute of this HTML a element is generated with an attribute value template. See Section 3.3 for more information. Our next step is to process the cross-reference. Heres the template for the xref element: xsl:template match=xref a href={refid} xsl:choose xsl:when test=idrefidxreftext xsl:value-of select=idrefidxreftext xsl:when xsl:otherwise xsl:value-of select=idrefid xsl:otherwise xsl:choose a xsl:template We create the a element in two steps: • Create the href attribute. It must refer to the correctly named anchor in the HTML document. • Create the text of the link. This text is the word or phrase that appears in the browser; clicking on the link should take the user to the referenced term. 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 . Now all thats left is for us to retrieve the text. This retrieval is the most complicated part of the process relatively speaking, anyway. Remember that we want to use the xreftext attribute of the term element, if there is one, and use the text of the term element, otherwise. To implement an if-then-else statement, we use the xsl:choose element. In the previous sample, we used a test expression of idrefidxreftext to see if the xreftext attribute exists. Remember, an empty node-set is considered false. If the attribute doesnt exist, the node-set will be empty and the xsl:otherwise element will be evaluated. If the test is true, we use idrefidxreftext to retrieve the cross-reference text. The first part of the XPath expression idrefid returns the node that has an ID that matches the value refid ; the second part xreftext retrieves the xreftext attribute of that node. We insert the text of the xreftext attribute inside the a element. Finally, we handle any seealso elements. The difference here is that the refids attribute can reference any number of glossary terms, so well use the id function differently. page 87 Heres the template for seealso : xsl:template match=seealso b xsl:textSee also: xsl:text b xsl:for-each select=idrefids 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=notposition=last xsl:text, xsl:text xsl:if xsl:for-each xsl:text. xsl:text xsl:template There are a couple of important differences here. First, we call the id function in an xsl:for-each element. Calling the id function with an attribute of type IDREFS returns a node-set; each node in the node-set is the match for one of the ID s in the attribute. The second difference is that referencing the correctly named anchor is more difficult. When we processed the xref element, we knew that the correct anchor name was the value of the refid attribute. When processing seealso , the refids attribute doesnt do us any good because it may contain any number of ID s. All is not lost, however. What we did previously was use the id attribute of each node returned by the id function—a minor inconvenience, but another difference in processing an attribute of type IDREFS instead of IDREF . The final difference is that we want to add commas after all items except the last. The xsl:if element shown previously does just this. If the position of the current item is the last, we dont output the comma and space defined here with the xsl:text element. We formatted all references here as a sentence; as an exercise, feel free to process the items in a more sophisticated way. For example, you could generate an HTML list from the IDREFS , or maybe format things differently if the refids attribute only contains a single ID . Weve done several useful things with the id function. Weve been able to use attributes of type ID to discover the links between related pieces of information, and weve 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, thats great. Unfortunately, there are times when we need to do more, and on those occasions, the id function doesnt quite cut it. Well mention the limitations of the id function briefly, then well discuss XSLT functions that let us overcome them.

5.1.4 Limitations of IDs

To this point, weve been able to generate cross-references easily. There are some limitations of the ID datatype and the id function, though: • If you want to use the ID datatype, you have to declare the attributes that use that datatype in your DTD or schema. Unfortunately, if your DTD is defined externally to your XML document, the XML parser isnt required to read it. If the DTD isnt read, then the parser has no idea that a given attribute is of type ID . page 88 • You must define the ID and IDREF relationship in the XML document. It would be nice to have the XML document define the data only, with the relationships between parts of the document defined externally say, in a stylesheet. That way, if you needed to define a new relationship between parts of the document, you could do it by creating a new stylesheet, and you wouldnt have to modify your XML document. Requiring the XML document structure to change every time you need to define a new relationship between parts of the document will become unwieldy quickly. • An element can have at most one attribute of type ID . If youd like to refer to the same element in more than one way, you cant use the id function. • Any given ID value can be found on at most one element. If youd like to refer to more than one element with a single value, you cant use the id function for that, either. • Only one set of ID s exists for the entire document. In other words, if you declare the attributes customer_number , part_number , and order_number to be of type ID , the value of a customer_number must be unique across all the attributes of type ID . It is illegal in this case for a customer_number to be the same as a part_number , even though those attributes might belong to different elements. • An ID can only be an attribute of an XML element. The only way you can use the id function to refer to another element is through its attribute of type ID . If you want to find another element based on an attribute that isnt an ID , based on the elements content, based on the elements children, etc., the id function is of no use whatsoever. • The value of an ID must be an XML name. In other words, it cant contain spaces, it cant start with a number, and its subject to the other restrictions of XML names. Section 2.3 of the XML Recommendation defines these restrictions; see http:www.w3.orgTRREC-xml if youd like more information. To get around all of these limitations, XSLT defines the key function. Well discuss that function in the next section.

5.2 Generating Links with the key Function

Now that weve covered the id function in great detail, well move on to XSLTs 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 peoples 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 dont have to search the entire database. Well discuss the details of how the key function works, then well compare it to the id function.