The xsl:key Approach
page 117
Now that weve defined our
key
, were ready for the complicated part. We use the
key
and
generate-id
functions together. Heres the syntax, which well discuss extensively in a minute:
xsl:for-each select=address[generate-id.= generate-idkeyzipcodes, zip[1]]
Okay, lets take a deep, cleansing breath and start digging through this syntax. What were selecting here is all
address
elements in which the automatically generated
id
matches the automatically generated
id
of the first node returned by the
key
function when we ask for all
address
elements that match the current
zip
element. Well, thats clear as crystal, isnt it? Let me try to explain that again from a slightly different
perspective. For each
address
, we use the
key
function to retrieve all
address
es that have the same
zip
. We then take the first node from that node-set. Finally, we use the
generate-id
function to generate an
id
for both nodes. If the two generated
id
s are identical, then the two nodes are the same.
Whew. Let me catch my breath. If this
address
matches the first node returned by the
key
function, then we know weve found the first
address
that matches this grouping value. Selecting all of the first values remember, our previous predicate ends with
[1]
gives us a node-set of some number of
address
elements, each of which contains one of the unique grouping values we need. Well, thats how this technique works. At this point, weve got a way to generate a node-set
that contains all of the unique grouping values; now we need to process those nodes. From this point, well do several things, all of which are comparatively simple:
•
Sort all nodes based on the grouping property. In this example, the property is the
zip
element. We start by selecting the first occurrence of every unique
zip
element in the document, then we sort those
zip
elements. Heres how it looks in the stylesheet:
xsl:for-each select=address[generate-id.=generate-idkeyzipcodes, zip[1]]
xsl:sort select=zip
•
The outer
xsl:for-each
element selects all the unique values of the
zip
element. Next, we use the
key
function to retrieve all
address
elements that match the current
zip
element:
xsl:for-each select=keyzipcodes, zip
•
The
key
function gives us a node-set of all matching
address
elements. We sort that node-set based on the
last-name
and
first-name
elements, then process them in turn:
xsl:sort select=namelast-name xsl:sort select=namefirst-name
tr xsl:if test=position = 1
td valign=center bgcolor=999999 xsl:attribute name=rowspan
xsl:value-of select=countkeyzipcodes, zip xsl:attribute
b xsl:textZip code xsl:textxsl:value-of select=zip
b
page 118 td
xsl:if td align=right
xsl:value-of select=namefirst-name xsl:text xsl:text
bxsl:value-of select=namelast-nameb td
td xsl:value-of select=street
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=zip
td tr
xsl:for-each xsl:for-each
We generate a table cell that contains the Zip Code common to all addresses, creating a
rowspan
attribute based on the number of matches for the current Zip Code. From there, we write the other data items into table cells.
Heres our complete stylesheet:
?xml version=1.0? xsl:stylesheet version=1.0 xmlns:xsl=http:www.w3.org1999XSLTransform
xsl:output method=html indent=no xsl:key name=zipcodes match=address use=zip
xsl:template match= table border=1
xsl:for-each select=address[generate-id.= generate-idkeyzipcodes, zip[1]]
xsl:sort select=zip xsl:for-each select=keyzipcodes, zip
xsl:sort select=namelast-name xsl:sort select=namefirst-name
tr xsl:if test=position = 1
td valign=center bgcolor=999999 xsl:attribute name=rowspan
xsl:value-of select=countkeyzipcodes, zip xsl:attribute
bxsl:textZip code xsl:textxsl:value-of select=zipb td
xsl:if td align=right
xsl:value-of select=namefirst-name xsl:text xsl:text
bxsl:value-of select=namelast-nameb td
td xsl:value-of select=street
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=zip
td tr
xsl:for-each xsl:for-each
table xsl:template
xsl:stylesheet
page 119
When we view the generated HTML document in a browser, it looks like Figure 6-1
.
Figure 6-1. HTML document with grouped items
Notice how the two
xsl:for-each
and the various
xsl:sort
elements work together. The outer
xsl:for-each
element selects the unique values of the
zip
element and sorts them; the inner
xsl:for-each
element selects all
address
elements that match the current
zip
element, and then sorts them by
last-name
and
first-name
.