home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Book HomeXSLSearch this book

7.2. The document() Function

We'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

Figure 7-1. Document generated from multiple input files

The 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 Recursion

While 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 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> we're currently processing.

runningTotal
The total of all the <item>s we've 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="$items/item[$index]/qty * 
        $items/item[$index]/price"/>
    </xsl:variable>

    Notice how the XPath expression in the select attribute uses the $items and $index parameters to find the exact items we're 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 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.



Library Navigation Links

Copyright © 2002 O'Reilly & Associates. All rights reserved.