Overview Invoking the document Function

page 121 po filename=po38293.xml po filename=po38294.xml po filename=po38295.xml report Well fill in the details of our stylesheet as we go along, but heres what the shell of our stylesheet looks like: xsl:template match= xsl:for-each select=reportpo xsl:apply-templates select=documentfilename xsl:for-each xsl:template In this template, we use the filename attribute as the argument to the document function. The simplest thing we can do is open each purchase order, then write its details to the output stream. Heres a stylesheet that does this: ?xml version=1.0?-- xsl:stylesheet version=1.0 xmlns:xsl=http:www.w3.org1999XSLTransform xsl:output method=html indent=no xsl:strip-space elements= xsl:template match= html head titlexsl:value-of select=reporttitletitle head body xsl:for-each select=reportpo xsl:apply-templates select=documentfilenamepurchase-order xsl:for-each body html xsl:template xsl:template match=purchase-order h1 xsl:value-of select=customeraddress[type=business]nametitle xsl:text xsl:text xsl:value-of select=customeraddress[type=business]namefirst-name xsl:text xsl:text xsl:value-of select=customeraddress[type=business]namelast-name h1 p xsl:textOrdered on xsl:text xsl:value-of select=datemonth xsl:textxsl:text xsl:value-of select=dateday xsl:textxsl:text xsl:value-of select=dateyear p h2Items:h2 table width=100 border=1 cols=55 15 15 15 tr bgcolor=lightgreen thItemth thQuantityth thPrice Eachth thTotalth tr xsl:for-each select=itemsitem tr xsl:attribute name=bgcolor xsl:choose xsl:when test=position mod 2 xsl:textwhitexsl:text xsl:when xsl:otherwise xsl:textlightgreenxsl:text xsl:otherwise page 122 xsl:choose xsl:attribute td bxsl:value-of select=nameb xsl:text part xsl:text xsl:value-of select=part_no xsl:textxsl:text td td align=center xsl:value-of select=qty td td align=right xsl:value-of select=price td td align=right xsl:choose xsl:when test=position=1 xsl:value-of select=format-numberprice qty, ,.00 xsl:when xsl:otherwise xsl:value-of select=format-numberprice qty, ,.00 xsl:otherwise xsl:choose td tr xsl:for-each tr td colspan=3 align=right bTotal:b td td align=right xsl:variable name=orderTotal xsl:call-template name=sumItems xsl:with-param name=index select=1 xsl:with-param name=items select=items xsl:with-param name=runningTotal select=0 xsl:call-template xsl:variable xsl:value-of select=format-numberorderTotal, ,.00 td tr table xsl:template xsl:template name=sumItems xsl:param name=index select=1 xsl:param name=items xsl:param name=runningTotal select=0 xsl:variable name=currentItem xsl:value-of select=itemsitem[index]qty itemsitem[index]price xsl:variable xsl:variable name=remainingItems xsl:choose xsl:when test=index=countitemsitem xsl:text0xsl:text xsl:when xsl:otherwise xsl:call-template name=sumItems xsl:with-param name=index select=index+1 xsl:with-param name=items select=items xsl:with-param name=runningTotal select=runningTotal+currentItem xsl:call-template xsl:otherwise xsl:choose xsl:variable xsl:value-of select=currentItem+remainingItems xsl:template xsl:stylesheet page 123 When we process our master document with this stylesheet, the results look like Figure 7-1 . Figure 7-1. Document generated from multiple input files The most notable thing about our results is that weve been able to generate a document that contains the contents of several other documents. To keep our example short, weve only combined four purchase orders, but theres no limit beyond the physical limits of our machine to the number of documents we could combine. Best of all, we didnt have to modify any of the individual purchase orders to generate our report.

7.2.1 An Aside: Doing Math with Recursion

While were here, well also mention the recursive technique we used to calculate the total for each purchase order. At first glance, this seems like a perfect opportunity to use the sum function. We want to add the total of the price of each item multiplied by its quantity. We could try to invoke the sum function like this: xsl:value-of select=sumitemqtyitemprice Unfortunately, the sum function simply takes the node-set passed to it, converts each item in the node-set to a number, then returns the sum of all of those numbers. The expression itemqtyitemprice , while a perfectly valid XPath expression, isnt a valid node-set. With that in mind, we have to create a recursive xsl:template to do the work for us. There are a couple of techniques worth mentioning here; well go through them in the order we used them in our stylesheet. page 124

7.2.1.1 Recursive design

First, we pass three parameters to the template: items The node-set of all item elements in the current items element. index The position in that node-set of the item were currently processing. runningTotal The total of all the item s weve processed so far. Our basic design is as follows: • Calculate the total for the current item . This total is the value of the qty element multiplied by the value of the price element. We store this value in the variable currentItem : xsl:variable name=currentItem xsl:value-of select=itemsitem[index]qty itemsitem[index]price xsl:variable Notice how the XPath expression in the select attribute uses the items and index parameters to find the exact items were looking for. • Calculate the total for the remaining items. If this is the last item the parameter index is equal to the number of item elements, then the total for the remaining items is zero. Otherwise, the total for the remaining items is returned by calling the template again. When we call the template again, we increment the position of the current item: xsl:with-param name=index select=index+1 We also update the parameter runningTotal , which is equal to the value of the current item plus the previous value of runningTotal : xsl:with-param name=runningTotal select=runningTotal+currentItem This recursive design lets us generate the totals we need for our purchase order. Our approach is equivalent to invoking a function against each node in a node-set, only this approach doesnt require us to use extensions. As a result, we can use this stylesheet with any standards-compliant stylesheet processor, without having to worry about porting any extension functions or extension elements.

7.2.1.2 Generating output to initialize a variable

When we needed to set the value of the variable runningTotal , we simply called the template named sumItems . The sumItems template uses the xsl:value-of element to output some text; everything output by the template is concatenated to form the value of the variable runningTotal . The advantage of this technique is that it allows us to completely control the value of the variable, and it allows us to avoid converting the variable to a number until were ready. Once the sumItems template finishes its work, we can pass the variables value to the format-number function to print the invoice total exactly how we want. page 125

7.2.1.3 Using format-number to control output

The final nicety in our stylesheet is that we use the XSLT format-number function to display the total for the current purchase order. Weve already discussed how we set the value of the variable orderTotal to be the output of the template named sumItems ; once the variable is set, we use format-number to display it with a currency sign, commas, and two decimal places: xsl:value-of select=format-numberorder-total, ,.00

7.3 Invoking the document Function

In our previous stylesheet, we used the document function to select some number of nodes from the original source document our list of purchase orders, then open those files. There are a number of ways to invoke the document function; well discuss them briefly here. The most common way to use the document function is as we just did. We use an XPath expression to describe a node-set; the document function takes each node in the node-set, converts it to a string, then uses that string as a URI. So, when we passed a node-set containing the filename attributes in the list of purchase orders, each one is used as a URI. If those URIs are relative references i.e., they dont begin with a protocol like http , the base URI of the stylesheet is used as the base URI for the reference. If the document function has two arguments, the second must be a node-set. The first argument is processed as just described, with the difference that the base URI of the first node in the node-set is used as the base URI for any relative URIs. That combination isnt used often, but its there if you need it. You can also pass a string or any other XPath datatype to the document function. If we wanted to open a particular resource, we could simply pass the name of the resource: documenthttp:www.ibm.compricelist.xml This action would open this particular resource and process it. Be aware that XSLT processors are required to return an empty node-set if a resource cant be found, but they arent required to signal an error. XSLT processors also dont have to support any particular protocols http , ftp , etc.; you have to check the documentation of your XSLT processor to see what protocols are and arent supported. Every node in the XPath source tree is associated with a base URI. When using the document function, the base URI is important for resolving references to various resources typically specified with relative links in a file opened with the document function. If a given node is an element or processing instruction node, and that node occurs in an external entity, then the base URI for that node is the base URI of the external entity. If an element or processing instruction node does not occur in an external entity, then its base URI is the base URI of the document in which it appears. The base URI of a document node is the base URI of the document itself, and the base URI of an attribute, comment, namespace, or text node is the base URI of that nodes parent. page 126 A special case occurs when you pass an empty string to the document function. As weve discussed the various combinations of arguments that can be passed to the function, weve gone over the rules for resolving URIs. When we call document , the XSLT processor parses the current stylesheet and returns a single node, the root node of the stylesheet itself. This technique is very useful for processing lookup tables in a stylesheet, something well discuss later in this chapter.

7.4 More Sophisticated Techniques

Up to now, weve written a simple XML document that contains references to other XML documents, then we created a stylesheet that combines all those referenced XML documents into a single output document. Thats all well and good, but well probably want to do more advanced things. For example, it might be useful to generate a document that lists all items ordered by all the customers. It might be useful to sort all the purchase orders by the state to which they were shipped, by the last name of the customer, or to group them by the state to which they were shipped. Well go through some of these scenarios to illustrate the design challenges we face when generating documents from multiple input files.

7.4.1 The document Function and Sorting

Our first challenge will be to generate a listing of all purchase orders and sort them by state. This isnt terribly difficult; well simply use the xsl:sort element in conjunction with the document function. Heres the heart of our new stylesheet: body h3Selected Purchase Orders - iSorted by stateih3 xsl:for-each select=documentreportpofilenamepurchase-ordercustomeraddressstate xsl:sort select=. xsl:apply-templates select=ancestor::purchase-order xsl:for-each body Figure 7-2. Another document generated from multiple input files page 127 What makes this process slightly challenging is the fact that were sorting on one thing the value of the state element, then invoking xsl:apply-templates against the purchase- order ancestor of the state element. We simply used the ancestor:: axis to do this. Figure 7-2 shows our output document, sorted by the value of the state element in each purchase order.

7.4.2 Implementing Lookup Tables

We mentioned earlier that calling the document function with an empty string enabled us to access the nodes in the stylesheet itself. We can use this behavior to implement a lookup table. As an example, well create a lookup table that replaces an abbreviation such as ME with Maine . We can then use the value from the lookup table as the sort key. More attentive readers might have noticed in our previous example that although the abbreviation MA does indeed sort before the abbreviation ME , a sorted list of the state names themselves would put Maine abbreviation ME before Massachusetts abbreviation MA . First, well create our lookup table. Well use the fact that a stylesheet can have any element as a top-level element, provided that element is namespace-qualified to distinguish it from the xsl: namespace reserved for stylesheets. Heres the namespace prefix definition and part of the lookup table that uses it: ?xml version=1.0? xsl:stylesheet version=1.0 xmlns:xsl=http:www.w3.org1999XSLTransform xmlns:states=http:new.usps.comcgi-binuspsbvscriptscontent.jsp?D=10090 states:name abbrev=ALAlabamastates:name states:name abbrev=ALAlabamastates:name states:name abbrev=AKAlaskastates:name states:name abbrev=ASAmerican Samoastates:name -- Most state abbreviations removed to keep this listing brief... -- states:name abbrev=MEMainestates:name states:name abbrev=MHMarshall Islandsstates:name states:name abbrev=MDMarylandstates:name states:name abbrev=MAMassachusettsstates:name The namespace mapped to the states prefix is the URL for the official list of state abbreviations from the United States Postal Service. To look up values in our table, well use the document function to return the root node of our stylesheet, then well look for a states:name element with a abbrev attribute that matches the value of the current state element in the purchase order were currently processing. Heres the somewhat convoluted syntax that performs this magic: body h3Selected Purchase Orders - iSorted by stateih3 xsl:for-each select=documentreportpofilenamepurchase-ordercustomeraddressstate xsl:sort select=documentstates:name[abbrev=current] xsl:apply-templates select=ancestor::purchase-order xsl:for-each body Notice that we use the document function twice; once to open the document referred to by the filename element, and once to open the stylesheet itself. We also need to discuss the XPath expression in the select attribute of the xsl:sort element. There are four significant parts to this expression: document Returns the root node of the current stylesheet.