Solution 3: Use recursion to process the IDREFS datatype

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 page 99 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 xsl:template match=glentry p b a name={termid} 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=keyterm-ids, refid[1]xreftext xsl:value-of select=keyterm-ids, refid[1]xreftext xsl:when xsl:otherwise xsl:value-of select=keyterm-ids, refid[1] xsl:otherwise xsl:choose a xsl:template xsl:template match=seealso b xsl:textSee also: xsl:text b xsl:for-each select=java:org.apache.xalan.lib.Extensions.tokenizerefids a href={keyterm-ids, .id} xsl:choose xsl:when test=keyterm-ids, .xreftext xsl:value-of select=keyterm-ids, .xreftext xsl:when xsl:otherwise xsl:value-of select=keyterm-ids, . 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 xsl:stylesheet