XSLT 2.0 - Invoice example  04/22/09 19:47
We create an invoice XML and schema, and an XSL stylesheet to render the XML in HTML. In the process we use some very handy XSLT and XPATH 2.0 additions.

We also deal with derived data - subtotals and totals.

XSLT 2.0 has many very useful additions over version 1.0. This, coupled with similar changes to XPATH, makes converting to XSLT 2.0 quite attractive. In this example we'll investigate the creation of an invoice; its underlying XML file, a schema for that file, and an XSL stylesheet for rendering the invoice file in HTML - using XSLT 2.0.

Another goal is to deal with derived data; things like subtotals (quantity * unit-price) and totals (the sum of all the subtotals). These really should be calculated values - derived values - not stored in the data file. There are many ways to handle this sort of thing - but XSLT 2.0 has a few tricks up its sleeve - let's take a look.

Here is our example invoice xml file:

<?xml version="1.0" encoding="UTF-8"?>
<invoice xmlns="invoice"
    xsi:schemaLocation="invoice invoice.xsd">
    	<name>Coyote Group, LLC</name>
    	<street>123 Ink Place</street>
    	<city>Toon Town</city>
    	<phone>888 555-1234</phone>
    	<attn>Wiley Coyote</attn>

I won't include the whole schema here, but you can download it here

The piece which is of particular note is this:

<xs:element name="item">
            <xs:element ref="date"/>
            <xs:element ref="description"/>
            <xs:element ref="quantity"/>
            <xs:element ref="unitprice"/>

Notice that there is no subtotal element - the quantity times unitprice - nor is there any total for the sum of the subtotals. The reason for this should be clear - they are able to be calculated from the data present - they are referred to as calculated or inferred values. Putting them in the data file just wastes space, and invites errors (forgetting to re-calculate them when updating the invoice, changing prices, etc.).

Here's the beginning of our XSL file, meant to render the invoice.xml file in html:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:inv="invoice" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema" xpath-default-namespace="invoice"
  exclude-result-prefixes="xs xsl inv" version="2.0">
  <xsl:output method="xhtml"/>
  <xsl:template match="/">
    <xsl:variable name="ext">
      <xsl:element name="inv:subtotals">
        <xsl:for-each select="/inv:invoice/inv:items/inv:item">
          <xsl:element name="inv:subtotal">
            <xsl:value-of select="inv:unitprice * inv:quantity"/>
    <html xmlns="http://www.w3.org/1999/xhtml">
        <title>Invoice example</title>
      <body style="width: 50%; margin-left: 5em; font-family: 'verdana, arial, sans">
        <p style="text-align: right;">
          <img align="left" src="acme.gif" alt="Acme Corporation"/>
          <xsl:text>Invoice Date: </xsl:text>
          <span style="font-weight: bold;">
            <xsl:value-of select="format-date(/invoice/invdate,'[M01]/[D01]/[Y01]')"/>
          <xsl:text> Invoice Number: </xsl:text>
          <span style="font-weight: bold;">
            <xsl:value-of select="/invoice/invnumber"/>
        <br clear="all"/>
          <span style="font-size: larger; font-style: bold; ">Acme, Corporation</span>
          <br/> 543 Warner Blvd.<br/> Burbank, CA 54321 </p>
        <p>Bill to:<br/>
          <xsl:value-of select="/invoice/customer/name"/><br/>
          <xsl:value-of select="/invoice/customer/street"/><br/>
          <xsl:value-of select="/invoice/customer/city"/><xsl:text>, </xsl:text>
          <xsl:value-of select="/invoice/customer/state"/><xsl:text> </xsl:text>
          <xsl:value-of select="/invoice/customer/zip"/><br/>
          <xsl:value-of select="/invoice/customer/phone"/><br/>
          <xsl:if test="/invoice/customer/fax != ''"> FAX: <xsl:value-of
          </xsl:if> ATTN: <xsl:value-of select="/invoice/customer/attn"/>

A few things to note here are the namespaces and the xpath-default-namespace attributes. The default namespace is really handy – namespaces can be somewhat confusing when using XSLT – having a default really helps.

Another point of interest is the format-date() function. This is new to XSLT 2.0/XPATH 2.0, and along with format-number() is really handy. These use a picture string to format a date, date-time or number. In this example the picture string is '[M01]/[D01]/[Y01]'; meaning the month as a leading-zero, 2 digit number, a slash, the day as a leading-zero, 2 digit number, another slash, and the year as a leading-zero 2 digit number. The picture strings are described here for format-number and here for format-date.

As we mentioned, there are no subtotals (extension prices) in the XML file. By creating a variable name ext we can form them "on the fly". Here is our code, repeated from above:

<xsl:template match="/">
  <xsl:variable name="ext">
    <xsl:element name="inv:subtotals">
      <xsl:for-each select="/inv:invoice/inv:items/inv:item">
        <xsl:element name="inv:subtotal">
          <xsl:value-of select="inv:unitprice * inv:quantity"/>

Notice that when creating the elements in this variable, I use the namespace indicator inv: so that the contents will remain in the same namespace as the rest of the data.

Here is where the subtotals (extension prices) are calculated; simply unitprice * quantity.

The rest of the story

The following code will, finally, complete the invoice.

<table width="100%" align="center" border="1">
  <xsl:for-each select="/invoice/items/item">
        <xsl:value-of select="format-date(./date,'[M01]/[D01]/[Y01]')"/>
        <xsl:value-of select="./description"/>
        <xsl:value-of select="quantity"/>
      <td align="right" 
                style="font-family: courier, fixed, monospace; font-weight: bold;">
        <xsl:value-of select="format-number(unitprice,'$#.00')"/>
      <td align="right" 
                style="font-family: courier, fixed, monospace; font-weight: bold;">
        <xsl:value-of select="format-number(sum(unitprice * quantity),'$#.00')"/>
  <tr style="font-weight: bold;">
    <td colspan="4" align="right">Total</td>
    <td align="right" style="font-family: courier, fixed, monospace; ">
      <xsl:value-of select="format-number(sum($ext/subtotals/subtotal),'$#.00')"/>

Totals, the sum() function

Notice that I have used the sum() function to sum the subtotals (extension prices). This only works on a "vector" of numbers, such as in the variable ext. There is no way to calculate the subtotals on each line of the "for-each" and then sum them, hence the use of ext.

And here it is:


Full size, here.


This transform was done with "Saxon" from Saxonica, see http://www.saxonica.com/


The XSLT2.0/XPATH2.0 combination is a considerable improvement over the 1.x versions. I'm sure you can use the format-number() and format-date() functions over and over. The namespace default is practically worth the switch to 2.0 alone!
Enjoy, and keep in touch.

Picture strings

<-Home    <-Printer friendly page.

John's Examples, OO, SOA, XML and other examples and tutorials.

John Pantone EMail me
[RSS]Subscribe to a RSS syndicated feed of this blog.

These are a few of my favorite links.