Menu

Getting started with XSLT and XPath (II)

August 23, 2000

G. Ken Holman

2.1.2  Some more complex examples

The following more complex examples are meant merely as illustrations of some of the powerful facilities and techniques available in XSLT. These samples expose concepts such as variables, functions, and process control constructs a stylesheet writer uses to effect the desired result, but does not attempt any tutelage in their use.

Note:

This subsection can be skipped entirely, or, for quick exposure to some of the facilities available in XSLT and XPath, only briefly reviewed. In the associated narratives, I've avoided the precise terminology that hasn't yet been introduced and I overview the stylesheet contents and processor behaviors in only broad terms. Subsequent subsections of this chapter review some of the basic terminology and design approaches.

I hope not to frighten the reader with the complexity of these examples, but it is important to realize that there are more complex operations than can be illustrated using our earlier three-line source file example. The complexity of your transformations will dictate the complexity of the stylesheet facilities being engaged. Simple transformations can be performed quite simply using XSLT, but not all of us have to meet only simple requirements.

The following XML source information in prod.xml is used to produce two very dissimilar renderings:


01  <?xml version="1.0"?><!--prod.xml-->

02  <!DOCTYPE sales [

03  <!ELEMENT sales ( products, record )> <!--sales information-->

04  <!ELEMENT products ( product+ )>         <!--product record-->

05  <!ELEMENT product ( #PCDATA )>      <!--product information-->

06  <!ATTLIST product id ID #REQUIRED>

07  <!ELEMENT record ( cust+ )>                <!--sales record-->

08  <!ELEMENT cust ( prodsale+ )>     <!--customer sales record-->

09  <!ATTLIST cust num CDATA #REQUIRED>     <!--customer number-->

10  <!ELEMENT prodsale ( #PCDATA )>     <!--product sale record-->

11  <!ATTLIST prodsale idref IDREF #REQUIRED>

12  ]>

13  <sales>

14    <products><product id="p1">Packing Boxes</product>

15              <product id="p2">Packing Tape</product></products>

16    <record><cust num="C1001">

17              <prodsale idref="p1">100</prodsale>

18              <prodsale idref="p2">200</prodsale></cust>

19            <cust num="C1002">

20              <prodsale idref="p2">50</prodsale></cust>

21            <cust num="C1003">

22              <prodsale idref="p1">75</prodsale>

23              <prodsale idref="p2">15</prodsale></cust></record>

24  </sales>
Example 2-6: Sample product sales source information

Lines 2 through 11 describe the document model for the sales information. Lines 14 and 15 summarize product description information and have unique identifiers according to the ID/IDREF rules. Lines 16 through 23 summarize customer purchases (product sales), each entry referring to the product having been sold by use of the idref= attribute. Not all customers have been sold all products.

Consider the following two renderings of the same data using two orientations, each produced with different stylesheets:

Figure 2-3: Different HTML results from the same XML source
Figure 2-3: Different HTML results from the same XML source.

Note how the same information is projected into a table orientation on the left canvas and a list orientation on the right canvas. The one authored order is delivered in two different presentation orders. Both results include titles from boilerplate text not found in the source. The table information on the left includes calculations of the sums of quantities in the columns, generated by the stylesheet and not present explicitly in the source.

The implicit stylesheet prod-imp.xsl is an XHTML file utilizing the XSLT vocabulary for instructions to fill in the one result template by pulling data from the source:


01  <?xml version="1.0"?><!--prod-imp.xsl-->

02  <!--XSLT 1.0 - http://www.CraneSoftwrights.com/training -->

03  <html xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

04        xsl:version="1.0">

05    <head><title>Product Sales Summary</title></head>

06    <body><h2>Product Sales Summary</h2>

07      <table summary="Product Sales Summary" border="1">

08                                               <!--list products-->

09        <th align="center">

10            <xsl:for-each select="//product">

11              <td><b><xsl:value-of select="."/></b></td>

12            </xsl:for-each></th>

13                                              <!--list customers-->

14        <xsl:for-each select="/sales/record/cust">

15          <xsl:variable name="customer" select="."/>

16          <tr align="right"><td><xsl:value-of select="@num"/></td>

17            <xsl:for-each select="//product">   <!--each product-->

18              <td><xsl:value-of select="$customer/prodsale

19                                          [@idref=current()/@id]"/>

20              </td></xsl:for-each>

21          </tr></xsl:for-each>

22                                                   <!--summarize-->

23        <tr align="right"><td><b>Totals:</b></td>

24            <xsl:for-each select="//product">

25              <xsl:variable name="pid" select="@id"/>

26              <td><i><xsl:value-of 

27                        select="sum(//prodsale[@idref=$pid])"/></i>

28              </td></xsl:for-each></tr>

29      </table>

30    </body></html>
Example 2-7: Tabular presentation of the sample product sales source information

Recall that a stylesheet is oriented according to the desired result, producing the result in result parse order. The entire document is an HTML file whose document element begins on line 3 and ends on line 30. The XSLT namespace and version declarations are included in the document element. The naming of the document element as "html" triggers the default use of HTML result tree serialization conventions. Lines 5 and 6 are fixed boilerplate information for the mandatory <title> element.

Lines 7 through 29 build the result table from the content. A single header row <th> is generated in lines 9 through 12, with the columns of that row generated by traversing all of the <product> elements of the source. The focus moves on line 11 to each <product> source element in turn and the markup associated with the traversal builds each <td> result element. The content of each column is specified as ".", which for an element evaluates to the string value of that element.

Having completed the table header, the table body rows are then built, one at a time traversing each <cust> child of a <record> child of the <sales> child of the root of the document, according to the XPath expression "/sales/record/cust". The current focus moves to the <cust> element for the processing on lines 15 through 21. A local scope variable is bound on line 15 with the tree location of the current focus (note how this instruction uses the same XPath expression as on line 11 but with a different result). A table row is started on line 16 with the leftmost column calculated from the num= attribute of the <cust> element being processed.

The stylesheet then builds in lines 17 through 20 a column for each of the same columns created for the table header on line 10. The focus moves to each product in turn for the processing of lines 18 through 20. Each column's value is then calculated with the expression "$customer/prodsale[@idref=current()/@id]", which could be expressed as follows "from the customer location bound to the variable $customer, from all of the <prodsale> children of that customer, find that child whose idref= attribute is the value of the id= attribute of the focus element." When there is no such child, the column value is empty and processing continues. As many columns are produced for a body row as for the header row and our output becomes perfectly aligned.

Finally, lines 23 through 28 build the bottom row of the table with the totals calculated for each product. After the boilerplate leftmost column, line 24 uses the same "//product" expression as on lines 10 and 17 to generate the same number of table columns. The focus changes to each product for lines 25 through 28. A local scope variable is bound with the focus position in the tree. Each column is then calculated using a built-in function as the sum of all <prodsale> elements that reference the column being totaled. The XPath designers, having provided the sum() function in the language, keep the stylesheet writer from having to implement complex counting and summing code; rather, the writer merely declares the need for the summed value to be added to the result on demand by using the appropriate XPath expression.

The file prod-exp.xsl is an explicit XSLT stylesheet with a number of result templates for handling source information:


01  <?xml version="1.0"?><!--prod-exp.xsl-->

02  <!--XSLT 1.0 - http://www.CraneSoftwrights.com/training -->

03  <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

04                  version="1.0">

05  

06  <xsl:template match="/">                         <!--root rule-->

07    <html><head><title>Record of Sales</title></head>

08      <body><h2>Record of Sales</h2>

09        <xsl:apply-templates select="/sales/record"/>

10      </body></html></xsl:template>

11  

12  <xsl:template match="record">   <!--processing for each record-->

13    <ul><xsl:apply-templates/></ul></xsl:template>

14  

15  <xsl:template match="prodsale">   <!--processing for each sale-->

16    <li><xsl:value-of select="../@num"/>   <!--use parent's attr-->

17        <xsl:text> - </xsl:text>

18        <xsl:value-of select="id(@idref)"/>      <!--go indirect-->

19        <xsl:text> - </xsl:text>

20        <xsl:value-of select="."/></li></xsl:template>

21  

22  </xsl:stylesheet>
Example 2-8: List-oriented presentation of the sample product sales source information

The document element on line 3 includes the requisite declarations of the language namespace and the version being used in the stylesheet. The children of the document element are the template rules describing the source tree event handlers for the transformation. Each event handler associates a template with an event trigger described by an XPath expression.

Lines 6 through 10 describe the template rule for processing the root of the document, as indicated by the "/" trigger in the match= attribute on line 6. The result document element and boilerplate is added to the result tree on lines 7 and 8. Line 9 instructs the XSLT processor in <xsl:apply-templates> to visit all <record> element children of the <sales> document element, as specified in the select= attribute. For each location visited, the processor pushes that location through the stylesheet, thus triggering the template of result markup it can match for each location.

Lines 12 and 13 describe the result markup when matching a <record> element. The focus moves to the <record> element being visited. The template rule on line 13 adds the markup for the HTML unordered list <ul> element to the result tree. The content of the list is created by instructing the processor to visit all children of the focus location (implicitly by not specifying any select= attribute) and apply the templates of result markup it triggers for each child. The only children of <record> are <cust> elements.

The stylesheet does not provide any template rule for the <cust> element, so built-in template rules automatically process the children of each location being visited in turn. Implicitly, then, our source information is being traversed in the depth-first order, visiting the locations in parse order and pushing each location through any template rules that are then found in the stylesheet. The children of the <cust> elements are <prodsale> elements.

The stylesheet does provide a template rule in lines 15 through 20 to handle a <prodsale> element when it is pushed, so the XSLT processor adds the markup triggered by that rule to the result. The focus changes when the template rule handles it, thus, lines 16, 18, and 20 each pull information relative to the <prodsale> element, respectively: the parent's num= attribute (the <cust> element's attribute); the string value of the target element being pointed to by the <prodsale> element's idref= attribute (indirectly obtaining the <product> element's value); and the value of the <prodsale> element itself.

This is a prose version of an excerpt from the book "Practical Transformation Using XSLT and XPath" (Eighth Edition ISBN 1-894049-05-5 at the time of this writing) published by Crane Softwrights Ltd., written by G. Ken Holman; this excerpt was edited by Stan Swaren, and reviewed by Dave Pawson.