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