7.2. The document() FunctionWe'll start with a couple of simple examples that use the document() function. We'll 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. Here's what that master document might look like: <report> <title>Purchase Orders</title> <po filename="po38292.xml"/> <po filename="po38293.xml"/> <po filename="po38294.xml"/> <po filename="po38295.xml"/> </report> We'll fill in the details of our stylesheet as we go along, but here's what the shell of our stylesheet looks like: <xsl:template match="/"> <xsl:for-each select="/report/po"> <xsl:apply-templates select="document(@filename)"/> </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. Here's a stylesheet that does this: <?xml version="1.0"?>--> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" indent="no"/> <xsl:strip-space elements="*"/> <xsl:template match="/"> <html> <head> <title><xsl:value-of select="/report/title"/></title> </head> <body> <xsl:for-each select="/report/po"> <xsl:apply-templates select="document(@filename)/purchase-order"/> </xsl:for-each> </body> </html> </xsl:template> <xsl:template match="purchase-order"> <h1> <xsl:value-of select="customer/address[@type='business']/name/title"/> <xsl:text> </xsl:text> <xsl:value-of select="customer/address[@type='business']/name/first-name"/> <xsl:text> </xsl:text> <xsl:value-of select="customer/address[@type='business']/name/last-name"/> </h1> <p> <xsl:text>Ordered on </xsl:text> <xsl:value-of select="date/@month"/> <xsl:text>/</xsl:text> <xsl:value-of select="date/@day"/> <xsl:text>/</xsl:text> <xsl:value-of select="date/@year"/> </p> <h2>Items:</h2> <table width="100%" border="1" cols="55% 15% 15% 15%"> <tr bgcolor="lightgreen"> <th>Item</th> <th>Quantity</th> <th>Price Each</th> <th>Total</th> </tr> <xsl:for-each select="items/item"> <tr> <xsl:attribute name="bgcolor"> <xsl:choose> <xsl:when test="position() mod 2"> <xsl:text>white</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>lightgreen</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:attribute> <td> <b><xsl:value-of select="name"/></b> <xsl:text> (part #</xsl:text> <xsl:value-of select="@part_no"/> <xsl:text>)</xsl: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-number(price * qty, '$#,###.00')"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="format-number(price * qty, '#,###.00')"/> </xsl:otherwise> </xsl:choose> </td> </tr> </xsl:for-each> <tr> <td colspan="3" align="right"> <b>Total:</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-number($orderTotal, '$#,###.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="$items/item[$index]/qty * $items/item[$index]/price"/> </xsl:variable> <xsl:variable name="remainingItems"> <xsl:choose> <xsl:when test="$index=count($items/item)"> <xsl:text>0</xsl: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> When we process our master document with this stylesheet, the results look like Figure 7-1. Figure 7-1. Document generated from multiple input filesThe most notable thing about our results is that we've been able to generate a document that contains the contents of several other documents. To keep our example short, we've only combined four purchase orders, but there's no limit (beyond the physical limits of our machine) to the number of documents we could combine. Best of all, we didn't have to modify any of the individual purchase orders to generate our report. 7.2.1. An Aside: Doing Math with RecursionWhile we're here, we'll 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="sum(item/qty*item/price)"/> 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 item/qty*item/price, while a perfectly valid XPath expression, isn't 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; we'll go through them in the order we used them in our stylesheet. 7.2.1.1. Recursive designFirst, we pass three parameters to the template:
Our basic design is as follows:
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 doesn't 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 variableWhen 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 we're ready. Once the sumItems template finishes its work, we can pass the variable's value to the format-number() function to print the invoice total exactly how we want. 7.2.1.3. Using format-number() to control outputThe final nicety in our stylesheet is that we use the XSLT format-number() function to display the total for the current purchase order. We've 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-number($order-total, '$#,###.00')"/> Copyright © 2002 O'Reilly & Associates. All rights reserved. |
|