page 113 xsl:for-each select=addressbookaddress
xsl:sort select=zip xsl:if test=zip=preceding-sibling::address[1]zip
xsl:value-of select=newline xsl:textZip code xsl:text
xsl:value-of select=zip xsl:text xsl:text
xsl:value-of select=city xsl:text, xsl:text
xsl:value-of select=state xsl:text: xsl:text
xsl:value-of select=newline xsl:if
xsl:if test=nametitle xsl:value-of select=nametitle
xsl:text xsl:text xsl:if
xsl:value-of select=namefirst-name xsl:text xsl:text
xsl:value-of select=namelast-name xsl:value-of select=newline
xsl:value-of select=street xsl:value-of select=newline
xsl:value-of select=newline xsl:for-each
xsl:template xsl:stylesheet
Our approach in this stylesheet consists of two steps: 1. Sort the addresses by Zip Code.
xsl:sort select=zip
2. For each Zip Code, if it doesnt match the previous Zip Code, print out a heading, then print out the addresses that match it.
3. xsl:if test=zip=preceding-sibling::address[1]zip 4. xsl:value-of select=newline
5. xsl:textZip code xsl:text ...
Remember that
preceding-sibling
returns a
NodeSet
, so
preceding- sibling::address[1]
represents the first preceding sibling. That sounds reasonable, doesnt it? Lets take a look at the results:
Addresses sorted by Zip Code Zip code 00218 Winter Harbor, ME:
Ms. Natalie Attired 707 Breitling Way
Zip code 02718 Skunk Haven, MA: Mary Backstayge
283 First Avenue Harry Backstayge
283 First Avenue Zip code 02930 Lynn, MA:
Ms. Amanda Reckonwith 930-A Chestnut Street
page 114 Zip code 27318 Boylston, VA:
Mary McGoon 103 Bryant Street
Mr. Chester Hasbrouck Frisby 1234 Main Street
Yes, that certainly seemed like a good approach, but theres one minor problem: it doesnt work.
Looking at our results, there seems to be only one problem: one of the addresses Mr. Chester Hasbrouck Frisby is grouped under the heading for Boylston, Virginia, but he actually lives
in Sheboygan, Wisconsin, Zip Code 48392. The problem here is that the axes work with the document order, not the sorted order weve created inside the
xsl:for-each
element. As straightforward as our logic seemed, well have to find another way.
6.2.2 A Brute-Force Approach
One thing we could do is make the transformation in two passes; we could write an intermediate stylesheet to sort the names and generate a new XML document, then use the
stylesheet weve already written, because document order and sorted order will be the same. Heres how that intermediate stylesheet would look:
?xml version=1.0? xsl:stylesheet version=1.0 xmlns:xsl=http:www.w3.org1999XSLTransform
xsl:output method=xml indent=no xsl:strip-space elements=
xsl:template match= addressbook
xsl:for-each select=addressbookaddress xsl:sort select=namelast-name
xsl:sort select=namefirst-name xsl:copy-of select=.
xsl:for-each addressbook
xsl:template xsl:stylesheet
This stylesheet generates a new
addressbook
document that has all of the
address
elements sorted correctly. We can then run our original stylesheet against the sorted document and get the results we want. This works, but its not very elegant. Even worse, its
really slow because we have to stop in the middle and write a file out to disk, then read that data back in. Well find a way to group elements in a single stylesheet, but well have to do it
with a different technique.
6.2.3 Grouping with xsl:variable
We mentioned earlier that sometimes
xsl:variable
is useful for grouping, so lets try that approach. Well save the value of the
zip
element each time through the
xsl:for-each
element and use
preceding-sibling
in a slightly different way. Heres how attempt number three looks:
?xml version=1.0? xsl:stylesheet version=1.0 xmlns:xsl=http:www.w3.org1999XSLTransform
xsl:output method=text indent=no xsl:variable name=newline
xsl:text xsl:text
page 115 xsl:variable
xsl:template match= xsl:textAddresses sorted by Zip Codexsl:text
xsl:value-of select=newline xsl:for-each select=addressbookaddress
xsl:sort select=zip xsl:sort select=namelast-name
xsl:sort select=namefirst-name xsl:variable name=lastZip select=zip
xsl:if test=notpreceding-sibling::address[zip=lastZip] xsl:textZip code xsl:text
xsl:value-of select=zip xsl:text: xsl:text
xsl:value-of select=newline xsl:for-each select=addressbookaddress[zip=lastZip]
xsl:sort select=namelast-name xsl:sort select=namefirst-name
xsl:if test=nametitle xsl:value-of select=nametitle
xsl:text xsl:text xsl:if
xsl:value-of select=namefirst-name xsl:text xsl:text
xsl:value-of select=namelast-name xsl:value-of select=newline
xsl:value-of select=street xsl:value-of select=newline
xsl:value-of select=newline xsl:for-each
xsl:if xsl:for-each
xsl:template xsl:stylesheet
This stylesheet generates what we want:
Addresses sorted by Zip Code Zip code 00218:
Ms. Natalie Attired 707 Breitling Way
Zip code 02718: Harry Backstayge
283 First Avenue Mary Backstayge
283 First Avenue Zip code 02930:
Ms. Amanda Reckonwith 930-A Chestnut Street
Zip code 27318: Mary McGoon
103 Bryant Street Zip code 48392:
Mr. Chester Hasbrouck Frisby 1234 Main Street