Solution 2: Use the XPath contains function

page 96 Weve done a couple of things here: First, weve saved the value of the refids attribute of the seealso element in the variable id_list . Thats because we cant access it within the for- each element. We can find a given seealso element from within a given term element, but its too difficult to find that element generically from every term element. The simplest way to find the element is to save the value in a variable. Second, we look at all of the term elements in the document. For each one, if our variable containing the refids attribute of the seealso element contains the value of the current term elements id attribute, then we process that term element. Here are the results our stylesheet generates: html head titleGlossary Listing: applet - wildcard charactertitle head body h1Glossary Listing: applet - wildcard characterh1 p ba name=appletaapplet: 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 applets access rights are limited in two ways: the applet cannot access the file system of the client upon which it is executing, and the applets communication across the network is limited to the server from which it was downloaded. Contrast with a href=servletservleta. bSee also: ba href=DMZlongdemilitarized zonea, a href=DMZ DMZa, a href=pattern-matchingpattern-matching charactera, a href=wildcard-charwildcard charactera. p ... There are a couple of problems here. The most mundane is that in our stylesheet, we dont know how many term elements have id attributes contained in our variable. That means its difficult to insert commas correctly between the matching term s. In the output here, we were lucky that the last match was in fact the last term, so the results here are correct. For any seealso element whose refid attribute doesnt contain the id attribute of the last term element in the document, this stylesheet wont work. The more serious problem is that one of the matches is, in fact, wrong. If you look closely at the output, we get a match for the term DMZ , even though there isnt an exact match for its id in our variable. Thats because the XPath contains function says correctly that the value DMZlong contains the id s DMZlong and DMZ . So our second attempt at solving this problem doesnt require us to change the structure of the XML document, but in this case, we have to change some of our ID s so that the problem we just mentioned doesnt occur. Thats probably going to be a maintenance nightmare and a serious drawback to this approach. page 97

5.2.3.4 Solution 3: Use recursion to process the IDREFS datatype

Here we use a recursive template to tokenize the refids attribute into individual IDs, then process each one individually. This style of programming takes a while to get used to, but it can be fairly simple. Heres the crux of our stylesheet: xsl:template match=seealso b xsl:textSee 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=concatnormalize-spacestringToTokenize, xsl:variable xsl:choose xsl:when test=normalizedString= xsl:variable name=firstOfString select=substring-beforenormalizedString, xsl:variable name=restOfString select=substring-afternormalizedString, a href={firstOfString} xsl:choose xsl:when test=keyterm-ids, firstOfString[1]xreftext xsl:value-of select=keyterm-ids, firstOfString[1]xreftext xsl:when xsl:otherwise xsl:value-of select=keyterm-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 first thing we did was invoke the named template resolveIDREFS in the template for the seealso element. While invoking the template, we pass in the value of the refids attribute and let recursion work its magic. The resolveIDREFS template works like this: • Break the string into two parts: the first ID and the rest of the string. If there is no first ID i.e., the string contains only whitespace, were done. • Resolve the cross-reference for the first ID. • Invoke the template with the rest of the string. page 98 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=concatnormalize-spacestringToTokenize, xsl:variable The normalize-space function removes all leading and trailing whitespace from a string and replaces internal whitespace characters with a single space. Remember that whitespace inside an attribute isnt significant; our seealso element could be written like this: seealso refids= wildcard-char DMZlong pattern-matching When we pass this attribute to normalizeSpace , the returned value is wildcard-char DMZlong pattern-matching . All whitespace at the start and end of the value has been removed and all the whitespace between characters has been replaced with a single space. Because were using the substring-before and substring-after functions to find the first token and the rest of the string, its important that there be at least one space in the string. Its 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 were done. Although this approach is more tedious, it does everything we need it to do. We dont have to change our XML document, and we correctly resolve all the ID s in the IDREFS datatype.

5.2.3.5 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. Well cover extension functions in Chapter 8 , but for now, heres what the stylesheet looks like: ?xml version=1.0? xsl:stylesheet version=1.0 xmlns:xsl=http:www.w3.org1999XSLTransform xmlns:java=http:xml.apache.orgxsltjava 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