Our First Attempt Grouping Nodes

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