5.2. Transformation in HTML

5.2.1. Table

XSLT was designed from the start to transform trees into other trees. One type of tree that is easy to produce with XSLT is a well-formed HTML document, most often an XHTML document, which is a valid XML file that can also be displayed directly by web browsers. In this section, we will first show a simple selection of information from an XML file to produce tabulated information displayed as an XHTML page.

When the body of a template contains XML elements that are not XSLT elements (i.e. transformation instructions), they are copied verbatim to the output. So it is relatively easy to build the structure of an HTML document in which only some parts will be processed. This is similar in principle to the backquote macro processing in Lisp.

Selecting only red wines in our wine catalog (Example 2.3 (page )) and outputting an HTML table of a subset of the available information for each (Figure 5.1) can be done with the XSLT stylesheet given in Example 5.2.

Figure 5.1. Web browser rendering of Example 5.1 produced by running Example 5.2 on Example 2.3

Web browser rendering of Example 5.1 produced by running Example 5.2 on Example 2.3

Example 5.1. HTML tabular output of the red wines of the catalog produced by Example 5.2

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
  PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns:cat="http://www.iro.umontreal.ca/lapalme/wine-catalog"
      xmlns="http://www.w3.org/1999/xhtml">
   <head>
      <title>Wine Catalog</title>
   </head>
   <body>
      <h1>
            Wine Catalog (red only)
        </h1>
      <table border="1">
         <tr>
            <th>Wine Name</th>
            <th>Code</th>
            <th>Color</th>
            <th>Year</th>
            <th>Price</th>
            <th>ml</th>
            <th>l</th>
         </tr>
         <tr>
            <td>Domaine de l'Île Margaux</td>
            <td>C00043125</td>
            <td>red</td>
            <td align="right">2002</td>
            <td align="right">$22.80</td>
            <td align="right">750</td>
            <td align="right">0.75</td>
         </tr>
         <tr>
            <td>Prado Rey Roble</td>
            <td>C00929026</td>
            <td>red</td>
            <td align="right">2002</td>
            <td align="right">$35.25</td>
            <td align="right">1500</td>
            <td align="right">1.5</td>
         </tr>
      </table>
   </body>
</html>


The stylesheet in Example 5.2 first defines a template matching the root node (line 14‑4). Because the wine catalog is defined in a specific namespace, its prefix must be declared (line 3‑1) and used for selection. This template outputs the overall structure of the XHTML file and then calls xsl:apply-templates to search for an appropriate template on each child. In this case, it will correspond to cat:wine-catalog. The corresponding template (line 23‑5) outputs a global heading and then starts a table and defines its headers; the lines of the table will be filled by selecting (line 35‑7) wines whose color property is red. To set up this color filter, a value must be assigned to the code variable either with an xsl:variable or xsl:parameter element. Here we chose the latter (line 6‑2) Since color is defined as a parameter of the stylesheet, it is possible to change its value when the stylesheet is called by the XSLT processor. The way of setting this value from outside the stylesheet depends on each processor. In order for the select attribute value to be the character string 'red' and not the value associated with the node red (which is empty at this moment), single quotes must be added within the double quotes indicating the value of the attribute. Note again the use of the namespace prefix. The predicate used in square brackets limits the nodes to which the templates will be applied but does not change the context of the node, which is still the wine node.

The output for each selected wine is defined with a template that is applied to each cat:wine. It outputs, on a single row of the table, the values of its attributes, its color, its year (right aligned) and formats its price to start with a dollar sign (right aligned). It finally outputs the volume of each bottle in milliliters and in liters. Because the information in the wine catalog is not given in milliliters, we call both a user-defined XPath function (line 49‑9) and a named template (line 52‑10) to transform it appropriately. We have deliberately chosen the same program structure in both the function and the named template to highlight the differences and the similarities between these two means of organizing computation in a stylesheet. Usually we will use functions to compute strings and templates to produce elements, but the choice is left to the programmer.

Example 5.2. [WineCatalog.xsl] XSLT stylesheet designed to select the red wines in the catalog (Example 2.3) and to produce Example 5.1 displayed as Figure 5.1

  1 <?xml version="1.0" encoding="UTF-8" ?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
        xmlns:cat="http://www.iro.umontreal.ca/lapalme/wine-catalog"     (1)
        xmlns="http://www.w3.org/1999/xhtml"
  5     version="2.0">
        <xsl:param name="color" select="'red'"/>                         (2)
        <!-- to produce legal and validable XHTML ... -->
        <xsl:output method = "xml"                                       (3)
            doctype-public = "-//W3C//DTD XHTML 1.0 Strict//EN"
 10         doctype-system =
               "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
            indent = "yes" encoding = "UTF-8"/>
        
        <xsl:template match="/">                                         (4)
 15         <html>
                <head><title>Wine Catalog</title></head>
                <body>
                    <xsl:apply-templates/>
                </body>   
 20         </html>
        </xsl:template>
        
        <xsl:template match="cat:wine-catalog">                          (5)
            <h1>
 25             Wine Catalog (<xsl:value-of select="$color"/> only)
            </h1>
            <table border="1">
                <tr>
                    <xsl:for-each                                        (6)
 30                     select="'Wine Name','Code','Color','Year','Price','ml','l'">
                        <th><xsl:value-of select="."/></th>
                    </xsl:for-each>
                </tr>
                <xsl:apply-templates 
 35                 select="cat:wine[cat:properties/cat:color=$color]"/> (7)
            </table>        
        </xsl:template>
            
        <xsl:template match="cat:wine">                                  (8)
 40         <tr>
                <td><xsl:value-of select="@name"/></td>
                <td><xsl:value-of select="@code"/></td>
                <td><xsl:value-of select="cat:properties/cat:color"/></td>
                <td align="right"><xsl:value-of select="cat:year"/></td>
 45             <td align="right">
                    <xsl:value-of select="format-number(cat:price,'$0.00')"/>
                </td>
                <td align="right">
                    <xsl:value-of select="cat:toML(@format)"/>           (9)
 50             </td>
                <td  align="right">
                    <xsl:call-template name="toL">                       (10)
                        <xsl:with-param name="fmt" select="@format"/>
                    </xsl:call-template>
 55             </td>
            </tr>
        </xsl:template>
        
        <xsl:function name="cat:toML">                                   (11)
 60         <xsl:param name="fmt"/>
            <xsl:value-of select="
                if ($fmt='375ml') then '375'
                else if ($fmt='750ml') then '750'
                else if ($fmt='1l') then '1000'
 65             else if ($fmt='magnum') then '1500'
                else 'big'"/>
        </xsl:function>
        
        <xsl:template name="toL">                                        (12)
 70         <xsl:param name="fmt"/>
            <xsl:choose>
                <xsl:when test="$fmt='375ml'">0.375</xsl:when>
                <xsl:when test="$fmt='750ml'">0.75</xsl:when>
                <xsl:when test="$fmt='1l'">1.0</xsl:when>
 75             <xsl:when test="$fmt='magnum'">1.5</xsl:when>
                <xsl:otherwise>big</xsl:otherwise>           
            </xsl:choose>
        </xsl:template>
    </xsl:stylesheet>
 80 

1

Definition of the cat namespace prefix in order to access the elements of the wine catalog which are defined in this namespace. The default namespace is set to be the one needed for a valid XHTML file. With this declaration HTML tags that are used in the stylesheet are in the appropriate namespace for HTML validation.

2

Global parameter initialized with a string value; note the use of the internal single quotes to make sure that the string value is used and not a reference to a (non-existing) red element. This value can be overriden by a run-time parameter when the stylesheet is applied.

3

xsl:output declaration to tell the transformation engine to serialize with the appropriate headers to produce valid XHTML.

4

Template matching the root node that defines the skeleton of the HTML page: a head and a body with a call to apply templates on its children elements (only cat:wine-catalog in this case).

5

Template matching the catalog node to produce a header with a title and a table. The headings of the table are defined as the first row. The rest of the table will be filled with lines produced by each wine.

6

Uses a for-each in a sequence of strings to output the headers in the first row of the table.

7

Selects the wines with the chosen color. The result is a sequence wine nodes. The template on line 39‑8 is applied to each of them.

8

Outputs properties of a wine. The first four are output as they appear in the source. The price is formatted in dollars and cents and the bottle format is given in either milliliters or liters.

9

Outputs the bottle capacity, expressed in milliliters, using a user-defined XPath function (line 59‑11). To distinguish user-defined functions from system defined ones, user-defined functions must be declared in a separate namespace. Here we simply chose the cat namespace but we could have used a different one.

10

Calls a named template that outputs the bottle capacity, expressed in liters.

11

A user defined function which must be defined in a specific namespace and that can be called within an XPath expression like any other system defined one. Here we simply use a multi-line XPath expression to select the appropriate case within a cascaded if (...) then ... else ... expression. Note the definition of an xsl:param element to define the formal parameter that will be used as the value of the actual parameter when the function is used in an XPath expression.

12

Definition of a named template in order to output the number of liters corresponding to the value of the fmt formal parameter. The formal parameter is referred to by the XPath expression $fmt within the template. The template chooses the value to return depending on the value of the format attribute of the current node given as actual parameter when the named template is called (line 52‑10).


5.2.2. Computing New Information

XSLT can also be used for more complex selections and transformations. We will now show how to create a web page presenting the content of the cellar and integrating information from the wine catalog. The end result is shown in Figure 5.2 (an outline of the underlying HTML code is shown in Example 5.3). There are external links in order to get more information about the wines by googling for the name of wine. There are two similar links for each wine but it is just to show and compare ways of creating them in XSL.

Figure 5.2. HTML rendering of Example 5.3

HTML rendering of Example 5.3

Example 5.3. HTML output by the XSLT code is shown in Example 5.4

This HTML code has been slightly reformatted and trimmed to fit on the page.

<html>
   <head>
      <title>Cellar of Jude Raisin</title>
   </head>
   <body>
      <h1>Cellar of Jude Raisin</h1>
      <table border="1">
         <tr><th>...</th><th>...</th></tr>
         <tr>
            <td>...</td>
            <td>...</td>
         </tr>
      </table>
      <p/>
      <table border="1">
         <tr><th>...</th><th>...</th><th>...</th>
            <th>...</th><th>...</th>
         </tr>
         <tr>
            <td>...</td>
            <td>...</td>
            <td align="right">...</td>
            <td align="center"/>
            <td align="right">...</td>
         </tr>
         ... 
         <tr>
            <td colspan="3">...</td>
            <td align="right">...</td>
            <td align="right">...</td>
         </tr>
      </table>
      <h3>Comments</h3>C00043125 :<b>Guy Lapalme, Montréal</b>: should reorder soon<br/>
      C00312363 : Bottle too small...<br/>
      C00929026 :for <b>big</b> parties<br/>
      C10263859 :Really great<br/>
   </body>
</html>


Example 5.4 illustrates some new features. The root template (line 20‑3) creates the high-level structure of the XHTML file: the title of the page, also displayed at the top of the page, refers to the name of the owner. In Example 2.2, element name (line 12‑8) is structured in two elements: first and family. When the value of such an element is returned from an xsl:value-of, it is the text content of all elements. In fact, there are 5 parts in this case:

  • the text node comprising a \n (this is the notation for an end of line character) following the name opening tag and white space until the start of first

  • the content of the element first

  • the \n and spaces between the closing tag of first and the opening tag of family

  • the content of the element family

  • the \n and spaces between the closing tag of family and the closing tag of name

Given the fact that a sequence of \n and spaces in HTML is displayed as a single space by the browser, we get an appropriate display in this case. But this shows that handling text content can become a bit tricky. The next section will explain how to work with some of the most frequent cases.

The content of the cellar book is obtained by an implicit call to the template defined on line 36‑4 which creates a table with the address of the owner (line 43‑5) and the cellar (line 47‑6). It then calls the cellar template (line 54‑7). The lines of the addresses are obtained by looping on all elements with a for-each and outputting a <br/> between the text values of each element of the owner and location elements. Because we want to skip the first element (the owner name has already been given at the top), we only keep elements (line 43‑5) with a position number greater than 1.

The cellar (line 58‑8) template produces a table of information about wines in the cellar, sorted by their code. This is why the content of the xsl:apply-templates element (line 66‑9) is an xsl:sort element indicating the sorting key and the sort order. The last line of the table contains an estimated total value of the cellar obtained by a call (line 73‑10) to the total named template. To compute the total number of bottles (line 79‑11), we can use the predefined sum function. Finally, if there are any comment in the wine elements of the cellar (line 84‑12), we add a Comments section and output each of them, also in increasing order of wine code so that they are in the same order as in the table of wines.

We define the wine template (line 95‑13) to create a row in the table of wines. In order to gather some information about this wine from the wine catalog, we pass the wine node from the wine catalog as a parameter to the call to the nameAndUrl template (line 99‑14). The link between the current element and the corresponding element in the catalog is made using the value of the code variable given in the XPath expression. The remaining elements of the row are the purchase date (right-aligned), a number of stars corresponding to the rating and the quantity (right-aligned). The estimated value of the cellar is computed using XPath expressions.

nameAndUrl is a named template that receives a wine element as parameter. From this wine element, we create a XHTML link (line 117‑17) with an a element with an href attribute whose value is a string specifying the request to send to Google to search for this wine using its name. This is a bit involved because the link must be created dynamically using the the xsl:attribute elements added to an enclosing a element with appropriate contents. For that, we use two variables:

$GoogleStart
set in xsl:param for the whole stylesheet (line 8‑1). This string corresponds to the CGI call to Google to search for the name of the wine.
$name
is the name of the wine found in the catalog.

These values are used to create the value of the attribute href of the a element in the resulting HTML code.

Because creating such dynamic elements and attributes is often required, XSLT designers have defined a non-XML formalism called Attribute Value Template in which we put braces around XPath expressions that denote values that will be evaluated at run time and whose string value will replace the braces and their content (line 127‑18). One should consult the XSLT documentation to determine in which contexts this shortcut notation is allowed (it is indicated by surrounding the content of the quotes by braces).

The comment template (line 134‑19) outputs the value of the code of the parent node, a colon and the text value of the comment followed by a line break. In getting the content of the comment, the cat:bold elements will be processed by the appropriate template (line 142‑20) that will transform them in HTML b tags.

Example 5.4. [CellarBook.xsl] XSLT stylesheet to produce information about the cellar (Example 2.2). The resulting HTML code (Example 5.3) is rendered as Figure 5.2.

  1 <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
        xmlns:cat="http://www.iro.umontreal.ca/lapalme/wine-catalog"
        xmlns="http://www.w3.org/1999/xhtml"
  5     version="2.0">
    
        <!-- part of URL for a Google search -->
        <xsl:param name="GoogleStart">http://www.google.com/search?q=</xsl:param>(1)
            
 10     <!-- to produce legal and validable XHTML ... -->
        <xsl:output method="xml" 
            doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
            doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" 
            indent="yes"
 15         encoding="UTF-8"/>
        
        <xsl:key name="catalog" match="cat:wine" use="@code"/>           (2)
    
        <!-- matches the root node -->
 20     <xsl:template match="/">                                         (3)
            <html>
                <head>
                    <title>Cellar of <xsl:value-of 
                        select="cellar-book/owner/name"/>
 25                 </title>
                </head>
                <body>
                    <xsl:apply-templates/>
                </body>
 30         </html>
        </xsl:template>
        
        <!-- output of the content of the cellar: 
             addresses of the owner and cellar 
 35          followed by a table of wines -->
        <xsl:template match="cellar-book">                               (4)
            <h1>Cellar of <xsl:value-of select="owner/name"/></h1>
            <table border="1">
                <tr>
 40                 <th>Personal address</th>
                    <th>Cellar address</th>
                </tr>
                <tr><td><xsl:for-each select="owner/*[position()>1]">    (5)
                            <xsl:apply-templates/><br/>
 45                     </xsl:for-each>
                    </td>
                    <td><xsl:for-each select="location/*">               (6)
                            <xsl:apply-templates/><br/>
                        </xsl:for-each>                   
 50                 </td>
                </tr>
            </table>
            <p/>
            <xsl:apply-templates select="cellar"/>                       (7)
 55     </xsl:template>
        
        <!-- content of the cellar as a table  -->
        <xsl:template match="cellar">                                    (8)
            <table border="1">
 60             <tr><!-- head of the table -->
                    <xsl:for-each 
                    select="'Code','Name','Purchase Date','Rating','Nb bottles'">
                        <th><xsl:value-of select="."/></th>
                    </xsl:for-each>
 65             </tr>
                <xsl:apply-templates select="wine"><!-- each row -->     (9)
                    <xsl:sort select="@code" order="ascending"/>
                </xsl:apply-templates>
                <tr>
 70                 <td colspan="3">Estimated value</td>
                    <td align="right">
                    <!-- compute the estimated value of the cellar  -->
                        <xsl:value-of select="                           (10)
                            sum (for $w in wine return  
 75                        $w/quantity * key('catalog',$w/@code)/cat:price)"/>
                    </td>
                    <td align="right">
                        <!-- compute the total number of bottles -->
                        <xsl:value-of select="sum(wine/quantity)"/>      (11)
 80                 </td>
                </tr>
            </table>
            <!-- put comment section if at least one comment appears -->
            <xsl:if test="count(wine/comment)>0">                        (12)
 85             <h3>Comments</h3>
                <p>            
                    <xsl:apply-templates select="wine/comment">
                        <xsl:sort select="../@code" order="ascending"/>
                    </xsl:apply-templates>
 90             </p>
            </xsl:if>
        </xsl:template>
    
        <!-- information about a wine -->
 95     <xsl:template match="wine">                                      (13)
            <tr>
                <td><xsl:value-of select="substring(@code,2)"/></td>
                <td>
                    <xsl:call-template name="nameAndUrl">                (14)
100                     <xsl:with-param name="wine" select="key('catalog',@code)"/>
                    </xsl:call-template>
                </td>
                <td align="right"><xsl:value-of select="purchaseDate"/></td>
                <td align="center">
105               <xsl:value-of select="substring('*****',1,             (15)
                      if (rating/@stars) then rating/@stars else 0)"/></td>
                <td align="right"><xsl:value-of select="quantity"/></td>
            </tr>
        </xsl:template>
110 
        
        <!-- output the name of the wine with a link for "googling" it  -->
        <xsl:template name="nameAndUrl">                                 (16)
            <xsl:param name="wine"/>
115         <xsl:variable name="name" select="encode-for-uri($wine/@name)"/>
            <!-- dynamic creation of an element and its attributes -->
            <a>                                                          (17)
                <xsl:attribute name="href">
                    <xsl:value-of select="$GoogleStart"/>
120                 <xsl:value-of select="$name"/>
                </xsl:attribute>
                <xsl:value-of select="$wine/@name"/>
            </a>
            <!-- link creation using an Attribute Value Template -->
125         <br/>
            <i>
                <a href="{$GoogleStart}{$name}">                         (18)
                    <xsl:value-of select="$wine/@name"/>
                </a>
130         </i>
        </xsl:template>
        
        <!-- comment preceded by the code corresponding to it -->
        <xsl:template match="comment"><xsl:text>                         (19)
135         </xsl:text>
            <xsl:value-of select="../@code"/>
            <xsl:text> : </xsl:text>
            <xsl:apply-templates/><br/>
        </xsl:template>
140     
        <!-- global change of <bold> tags to html <b> tags -->
        <xsl:template match="cat:bold">                                  (20)
            <b><xsl:apply-templates/></b>
        </xsl:template>
145     
    </xsl:stylesheet>
    

1

Parameter giving the start of the URL allowing a search for a given wine on Google.

2

Declaration of a key to access a wine element of the catalog use its code attribute as a key.

3

Template for the root node that defines the skeleton the HTML page: a head with a title indicating the name of the owner and a body to be filled by the application of templates on the child element (cellar-book in this case).

4

Global header with the name of the owner and then a table giving more information about the owner and the location of the cellar.

5

Loop over all children nodes of the address, except the first one, the name of the owner. After each node which will be output using the default rules (only their text content will be output), a line break is forced with an HTML br element.

6

Loop over all children nodes of the location. After each node which will be output using the default rules (only their text content will be output), a line break is forced with an HTML br element.

7

Outputs the content of the cellar with the appropriate template.

8

The content of the cellar is given as a table with a header defined in this template, followed by a series of lines corresponding to each wine and finally a line holding the estimated value of the whole cellar.

9

Applies the template for each wine but in increasing order of the code attribute.

10

The total value of the cellar is computed using a relatively complex XPath expression: that loops over all wines and returns the sum of the value of each wine. The value of a wine is given by the number of bottles (quantity) multiplied by the price of the wine in the catalog having the same code attribute as this wine. The price in the catalog is found by first accessing the wine element of the catalog with the key function giving the value of code attribute of the cellar book.

11

The total number of bottles is the sum of the values of all quantity elements.

12

Comments appear at the bottom of the table if at least one wine has a comment. They are also given in ascending order of code attribute.

13

Outputs a line of an HTML table for a given wine.

14

Outputs the name of wine and its URL using the named template defined on line 113‑16. The wine element in the catalog is found using the key function.

15

Outputs a string of * corresponding to the number of stars for this wine.

16

Named template for outputting the name of the wine as the anchor text for an HTML link to Google with the appropriate wine name to get further information about it. We show here two alternative ways of creating the link.

17

Creates an a HTML element with a computed value for the href attribute: this value is the concatenation of the query string and the wine code.

18

Creates an a HTML element relying on an Attribute Value Template in which the content of the braces-enclosed expressions are evaluated as XPath expressions. In many cases, this notation is simpler than the preceding one.

19

A comment is preceded with the value of the surrounding code attribute and followed by a line break.

20

A cat:bold element is transformed into an HTML b element.


A observant reader might wonder where the text within a text element is coming from, because there is no template in our program for this case. This output is achieved by means a built-in XSLT template shown in Example 5.5 which stipulates at line 1‑1 that the content of text nodes and attributes are replaced by their string value. The built-in rule for attribute nodes thus ignores their name.

Another built-in template for the document and element nodes, shown in line 5‑2, launches the application of templates on the children nodes thus traversing the document in a depth-first manner (i.e. in document order). The combination of these two built-in rules explains why only the text content of an XML document is displayed in a browser when no specific template is defined.

Example 5.5. [built-in-template-rules.xsl] Built-in template rules for XSLT. These rules are applied when no other defined template can be applied.

  1 <xsl:template match="text()|@*">                                     (1)
        <xsl:value-of select="string(.)"/>
    </xsl:template>
    
  5 <xsl:template match="/|*">                                           (2)
        <xsl:apply-templates/>
    </xsl:template>

1

A text node or an attribute is replaced by its string content.

2

A document or element node applies the templates to its children elements.


5.2.3. Bulleted Lists

The previous examples showed transformations specific to a given XML file type. We now describe a transformation that can be applied to any XML file to show its indentation structure by means of nested HTML unnumbered lists.

Figure 5.3. HTML rendering of Example 5.6

HTML rendering of Example 5.6

Example 5.6. Excerpt of the HTML output (slightly reformatted here to fit in the page) produced by the transformation of Example 5.4 on the cellar book (Example 2.2).

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
  PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
   <head><title>HTML compaction of "CellarBook.xml"</title></head>
   <body>
      <ul><li><b>cellar-book</b> noNamespaceSchemaLocation="CellarBook.xsd"<ul>
            <li><b>wine-catalog</b>
               <ul><li><b>wine</b> name="Domaine de l'Île Margaux" appellation="Bordeaux supérieur" classification="a.c." code="C00043125" format="750ml"
                     <ul><li><b>properties</b>
                           <ul>
                              <li><b>color</b> red</li>
                              <li><b>alcoholic-strength</b> 12.5</li>
                              <li><b>nature</b> still</li>
                           </ul>
                        </li>
                        ...
                     </ul></li>
               </ul></li>
               <li><b>owner</b><ul><li><b>name</b>
                        <ul>
                           <li><b>first</b> Jude</li>
                           <li><b>family</b> Raisin</li>
                        </ul>
                     </li>
                     ...
                  </ul>
               </li>
               <li><b>location</b>...</li>
               <li><b>cellar</b>
                  <ul><li><b>wine</b> code="C00043125"<ul>
                        <li><b>purchaseDate</b> 2005-06-20</li>
                        <li><b>quantity</b> 2</li>
                        <li><b>comment</b>
                           <ul>
                              <li><b>bold</b> Guy Lapalme, Montréal</li>
                              <li>: should reorder soon</li>
                           </ul>
                        </li></ul>
                     </li>
                     ...
                  </ul>
               </li></ul></li></ul>
   </body>
</html>


To transform the cellar book (Example 2.2) into the HTML code of Example 5.6 (rendered in Figure 5.3) we can use the code given in Example 5.7 which has four templates:

  • one matching the root element (line 17‑4) that produces the overall structure of the HTML file with its head and body elements. The processing of subelements (line 25‑6) is called within an unnumbered list delimited by ul tags.

  • matching attributes (line 31‑7) is done by outputting a space (with an entity defined on line 3‑1), the name of the attribute followed by an equal sign and its value within double quotes.

  • elements (line 36‑8) are transformed by outputting the name of the element returned by the function local-name() in bold (line 38‑9) followed by its attributes. If the element is a node without any children (line 41‑11) then it is output with only its content, otherwise (line 44‑12) a new unnumbered list is started and template matching is applied on children nodes (line 46‑13).

  • text node content (line 53‑14) is output within li tags.

The * in the match attribute (line 36‑8) indicates that this rule applies to all element nodes not matched by a more specific rule such as the one on line 17‑4 that matches only the root node.

Example 5.7. [compactHTML.xsl] XSLT transformation to produce a bulleted outline (Example 5.6) from the cellar book (Example 2.2)

  1 <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE stylesheet [
    <!ENTITY space "<xsl:text> </xsl:text>">                             (1)
    ]>
  5 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/1999/xhtml"
        version="2.0">
        
        <xsl:strip-space elements="*"/>                                  (2)
 10     <!-- to produce legal and validable XHTML ... -->
        <xsl:output method="xhtml" 
            doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
            doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" 
            indent="yes"                                                 (3)
 15         encoding="UTF-8"/>
        
        <xsl:template match="/">                                         (4)
            <html>
                <head>
 20                 <title>HTML compaction of                            (5)
      "<xsl:value-of select="replace(document-uri(.),'.*/(.*)','$1')"/>"</title>
                </head>
                <body>
                    <ul>
 25                     <xsl:apply-templates/>                           (6)
                    </ul>
                </body>
            </html>
        </xsl:template>
 30     
        <xsl:template match="@*">                                        (7)
            &space;<xsl:value-of select="local-name()"/>
            <xsl:text>="</xsl:text><xsl:value-of select="."/><xsl:text>"</xsl:text>
        </xsl:template>
 35     
        <xsl:template match="*">                                         (8)
            <li>
                <b><xsl:value-of select="local-name()"/></b>             (9)
                <xsl:apply-templates select="@*"/>
 40             <xsl:choose>                                             (10)
                    <xsl:when test="count(*)=0"> <!--single text node ?--> (11)
                        &space;<xsl:value-of select="."/>
                    </xsl:when>
                    <xsl:otherwise> <!--possible mixed node-->           (12)
 45                     <ul>
                            <xsl:apply-templates/>                       (13)
                        </ul>
                    </xsl:otherwise>
                </xsl:choose>
 50         </li>
        </xsl:template>
        
        <xsl:template match="text()">                                    (14)
            <li><xsl:value-of select="."/></li>
 55     </xsl:template>
        
    </xsl:stylesheet>
    
    

1

Entity that corresponds to an explicit blank space.

2

Ignores blank spaces in all elements.

3

Makes sure that the output will be valid XHTML output.

4

Matches the root node and produce the HTML skeleton.

5

Outputs the title of the HTML page. Because the document-uri returns the full uri of the current document, the replace function uses a regular expression to keep only the part after the last slash.

6

Start processing the root node of the document by applying the appropriate template.

7

An attribute is output as a space (note the use of an entity to create an explicit text node that will not be ignored afterwards), the name of the attribute and its value between double quotes after an equal sign.

8

An element node is a list-item.

9

The name of the element in bold, followed by the attributes using the template defined on line 31‑7.

10

The content of the element is output differently when it has children nodes or not.

11

There are no children nodes, in our case this will only be a text node, its content is copied in the current list item.

12

If there are children nodes, a new embedded unnumbered list is started.

13

The templates are applied on all children nodes, this is the default when no select attribute is present.

14

In the case of a text node within a list of children (i.e. a child of a mixed content element), it is output as list item.