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 .

6.3 Summary

In this chapter, weve gone over all of the common techniques used for sorting and grouping elements. Regardless of the kinds of stylesheets youll need to write in your XML projects, youll probably use these techniques in everything you do. Now that weve covered how to sort and group elements, well talk about how to combine multiple input documents next; this subject will build on the topics weve covered here. page 120

Chapter 7. Combining XML Documents

One of XSLTs most powerful features is the document function. document lets you use part of an XML document identified with an XPath expression, of course as a URI. In other words, you can look in a document, use parts of that document as URLs or filenames, open and parse those files, then perform stylesheet functions on the combination of all those documents. In this chapter, well cover the document function in all its glory.

7.1 Overview

The document function is very useful for defining views of multiple XML documents. In this chapter, well use XML-tagged purchase orders that look like this: purchase-order id=38292 customer id=4738 level=Platinum address type=business name titleMr.title first-nameChester Hasbrouckfirst-name last-nameFrisbylast-name name street1234 Main Streetstreet citySheboygancity stateWIstate zip48392zip address address type=ship-to customer items item part_no=28392-33-TT nameTurnip Twaddlername qty3qty price9.95price item item part_no=28813-70-PG namePrawn Goadername qty1qty price18.95price item items purchase-order If we had a few dozen documents like this, we might want to view the collection of purchase orders in a number of ways. We could view them sorted or even grouped by customer, by part number, by the amount of the total order, by the state to which they were shipped, etc. One way to do this would be to write code that worked directly with the Document Object Model. We could parse each document, retrieve its DOM tree, then use DOM functions to order and group the various DOM trees, display certain parts of the DOM trees, etc. Because this is an XSLT book, though, you probably wont be surprised to learn that XSLT provides a function to handle most of the heavy lifting for us.

7.2 The document Function

Well start with a couple of simple examples that use the document function. Well assume that we have several purchase orders and that we want to combine them into a single report document. One thing we can do is create a master document that references all the purchase orders we want to include in the report. Heres what that master document might look like: report titlePurchase Orderstitle po filename=po38292.xml