Recipe10.3.Creating HTML Tables


Recipe 10.3. Creating HTML Tables

Problem

You want to map XML content onto HTML tables.

Solution

Tables are often created in two stages. First, the top-level table markup is generated, and then templates are applied to create rows and fields. The solution is a modification of part of the stylesheet produced in Recipe 10.2. The changed portion is highlighted:

<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">     <xsl:output method="html"/>     <xsl:param name="URL"/>     <xsl:template match="/">   <xsl:apply-templates select="*" mode="index"/>   <xsl:apply-templates select="*" mode="content"/> </xsl:template>     <!-- =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  = --> <!--             Create index.html  (mode = "index")                --> <!-- =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  = --> <xsl:template match="salesBySalesperson" mode="index">   <!-- Non-standard saxon xsl:document! -->   <xsl:document href="index.html">          <html>      <head>       <title>Sales by Salesperson</title>      </head>           <body bgcolor="#FFFFFF" text="#000000">       <h1>Sales By Salesperson</h1>       <xsl:apply-templates mode="index"/>      </body>     </html>   </xsl:document> </xsl:template>     <xsl:template match="salesperson" mode="index">   <h2>     <a href="{concat($URL,@name,'.html')}">       <xsl:value-of select="@name"/>     </a>   </h2> </xsl:template>     <!-- =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  = --> <!--          Create @name.html  (mode = "content")                 --> <!-- =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  = -->     <xsl:template match="salesperson" mode="content">   <xsl:document href="{concat(@name,'.html')}">          <html>      <head>       <title><xsl:value select="@name"/></title>      </head>           <body bgcolor="#FFFFFF" text="#000000">       <h1><xsl:value-of select="@name"/> Sales</h1>       <table border="1" cellpadding="3">         <tbody >           <tr>             <th>SKU</th>             <th>Sales (in US $)</th>           </tr>           <xsl:apply-templates mode="content"/>         </tbody>       </table>       <h2><a href="{concat($URL,'index.html')}">Home</a></h2>      </body>     </html>   </xsl:document> </xsl:template>     <xsl:template match="product" mode="content">     <tr>       <td><xsl:value-of select="@sku"/></td>       <td><xsl:value-of select="@totalSales"/></td>     </tr>      </xsl:template>     </xsl:stylesheet>

Discussion

XSLT 1.0

When creating tables, you often need to group data based on specific criteria. The difficulty of a grouping problem is related to whether the input data is already grouped and whether the grouping criteria are close- or open-ended. For example, imagine that you need to group sales data by region:

<?xml version="1.0" encoding="UTF-8"?> <sales>   <product sku="10000" sales="90000.00" region="NE"/>   <product sku="10000" sales="10000.00" region="NW"/>   <product sku="10000" sales="55000.00" region="SE"/>   <product sku="10000" sales="32000.00" region="SW"/>   <product sku="10000" sales="95000.00" region="NC"/>   <product sku="10000" sales="88000.00" region="SC"/>   <product sku="20000" sales="77000.00" region="NE"/>   <product sku="20000" sales="11100.00" region="NW"/>   <product sku="20000" sales="33210.00" region="SE"/>   <product sku="20000" sales="78000.00" region="SW"/>   <product sku="20000" sales="105000.00" region="NC"/>   <product sku="20000" sales="12300.00" region="SC"/>   <product sku="30000" sales="1000.00" region="NE"/>   <product sku="30000" sales="5100.00" region="NW"/>   <product sku="30000" sales="3210.00" region="SE"/>   <product sku="30000" sales="8000.00" region="SW"/>   <product sku="30000" sales="5000.00" region="NC"/>   <product sku="30000" sales="11300.00" region="SC"/> </sales>

Here, you know in advance that there are six regions. You could solve the grouping problem by explicit selection:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output method="html"/>    <xsl:template match="sales">   <html>     <head>       <title>Sales by Region</title>     </head>     <body>       <h1>Sales by Region</h1>       <table border="1" cellpadding="3">         <tbody>           <tr>             <th>SKU</th>             <th>Sales</th>           </tr>           <xsl:call-template name="group-region">             <xsl:with-param name="region" select=" 'NE' "/>             <xsl:with-param name="title" select="'North East Sales'"/>           </xsl:call-template>           <xsl:call-template name="group-region">             <xsl:with-param name="region" select=" 'NW' "/>             <xsl:with-param name="title" select="'North West Sales'"/>           </xsl:call-template>           <xsl:call-template name="group-region">             <xsl:with-param name="region" select=" 'NC' "/>             <xsl:with-param name="title" select="'North Central Sales'"/>           </xsl:call-template>           <xsl:call-template name="group-region">             <xsl:with-param name="region" select=" 'SE' "/>             <xsl:with-param name="title" select="'South East Sales'"/>           </xsl:call-template>           <xsl:call-template name="group-region">             <xsl:with-param name="region" select=" 'SC' "/>             <xsl:with-param name="title" select="'South Central Sales'"/>           </xsl:call-template>           <xsl:call-template name="group-region">             <xsl:with-param name="region" select=" 'SW' "/>             <xsl:with-param name="title" select="'South West Sales'"/>           </xsl:call-template>         </tbody>       </table>     </body>   </html> </xsl:template>     <xsl:template name="group-region">     <xsl:param name="region"/>     <xsl:param name="title"/>     <xsl:variable name="products" select="product[@region = $region]" />     <tr>       <th colspan="2"><xsl:value-of select="$title" /></th>     </tr>     <xsl:apply-templates select="$products"/>     <tr style="font-weight:bold">       <td >Total</td>       <td align="right">         <xsl:value-of               select="format-number(sum($products/@sales), '#.00')"/>       </td>      </tr>   </xsl:template>       <xsl:template match="product">     <tr>       <td><xsl:value-of select="@sku"/></td>       <td align="right"><xsl:value-of select="@sales"/></td>     </tr>   </xsl:template>    </xsl:stylesheet>

If you find the hard coding of group names objectionable, use a table-driven approach:

<xsl:stylesheet version="1.0"                  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"                 xmlns:sales="sales">      <sales:region code="NE" name="North East"/>   <sales:region code="NC" name="North Central"/>   <sales:region code="NW" name="North West"/>   <sales:region code="SE" name="South East"/>   <sales:region code="SC" name="South Central"/>   <sales:region code="SW" name="South West"/>      <xsl:variable name="products" select="/sales/product"/>      <xsl:output method="html"/>        <xsl:template match="sales">     <html>       <head>         <title>Sales by Region</title>       </head>       <body>         <h1>Sales by Region</h1>         <table border="1" cellpadding="3">           <tbody>             <tr>               <th>SKU</th>               <th>Sales</th>             </tr>             <xsl:for-each select="document('')/*/sales:region">               <tr >                 <th colspan="2"><xsl:value-of select="@name"/> Sales</th>               </tr>               <xsl:call-template name="group-region">                 <xsl:with-param name="region" select="@code"/>               </xsl:call-template>             </xsl:for-each>           </tbody>         </table>       </body>     </html>   </xsl:template>       <xsl:template name="group-region">     <xsl:param name="region"/>         <xsl:apply-templates select="$products[@region=$region]"/>         <tr style="font-weight:bold">           <td >Total</td>           <td align="right"><xsl:value-of            select="format-number(sum($products[@region=$region]/@sales),'#.00')"/>           </td>         </tr>   </xsl:template>          <xsl:template match="product">     <tr>       <td><xsl:value-of select="@sku"/></td>       <td align="right"><xsl:value-of select="@sales"/></td>     </tr>   </xsl:template>    </xsl:stylesheet>

Of course, many grouping problems are not this easy. Imagine that you are designing a stylesheet to be used by many companies or divisions within a large company that each had their own conventions for naming sales regions. You can tackle this problem in several ways, but one of the most efficient is called the Muenchian grouping technique (named after Steve Muench of Oracle who invented it), which uses a combination of keys and IDs:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"      xmlns:sales="sales">      <xsl:output method="html"/>      <xsl:key name="region-key" match="product" use="@region"/>      <xsl:template match="sales">     <html>       <head>         <title>Sales by Region</title>       </head>       <body>         <h1>Sales by Region</h1>         <table border="1" cellpadding="3">           <tbody>             <tr>               <th>SKU</th>               <th>Sales</th>             </tr>             <xsl:variable name="unique-regions"                   select="/sales                          /product[generate-id(.) =                                    generate-id(key('region-key',@region))]                          /@region"/>             <xsl:for-each select="$unique-regions">               <tr >                 <th colspan="2"><xsl:value-of select="."/> Sales</th>               </tr>               <xsl:call-template name="group-region">                 <xsl:with-param name="region" select="."/>               </xsl:call-template>             </xsl:for-each>           </tbody>         </table>       </body>     </html>   </xsl:template>       <xsl:template name="group-region">     <xsl:param name="region"/>         <xsl:apply-templates select="key('region-key', @region)"/>         <tr style="font-weight:bold">           <td >Total</td>           <td align="right"><xsl:value-of            select="format-number(sum(key('region-key', @region)/@sales),'#.00')"/>           </td>         </tr>   </xsl:template>      <xsl:template match="product">     <tr>       <td><xsl:value-of select="@sku"/></td>       <td align="right"><xsl:value-of select="@sales"/></td>     </tr>   </xsl:template>    </xsl:stylesheet>

Structurally, the solution is similar to the table-driven method. The main difference is in how you determine the unique set of groups. In the table-driven case, the unique sales regions are literally encoded in sales:region elements. The Muenchian technique uses a key to define the grouping value. You know that the expression key('region-key',@region) returns a node set of all products whose region is @region. You also know that generate-id, when presented with a node-set, returns a unique ID for the first element in the node set. Thus, the expression [generate-id(.) = generate-id( key( 'region-key', @region))] will be true only for the first node within each group, in this case allowing you to obtain all unique regions that make up the group. Having this key also makes other parts of the stylesheet that refer to product by region more efficient.

XSLT 2.0

Unless you have skipped directly to this chapter, you know that the best way to tackle grouping problems in XSLT 2.0 is via xsl:for-each-group:

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">      <xsl:output method="html"/>         <xsl:template match="sales">     <html>       <head>         <title>Sales by Region</title>       </head>       <body>         <h1>Sales by Region</h1>         <table border="1" cellpadding="3">           <tbody>             <tr>               <th>SKU</th>               <th>Sales</th>             </tr>             <xsl:for-each-group select="product" group-by="@region">               <tr >                 <th colspan="2"><xsl:value-of select="."/> Sales</th>               </tr>               <xsl:apply-templates select="current-group( )"/>               <tr style="font-weight:bold">                 <td >Total</td>                 <td align="right"><xsl:value-of                  select="format-number(sum(current-group( )/@sales),'#.00')"/>                 </td>               </tr>             </xsl:for-each-group>           </tbody>         </table>       </body>     </html>   </xsl:template>         <xsl:template match="product">     <tr>       <td><xsl:value-of select="@sku"/></td>       <td align="right"><xsl:value-of select="@sales"/></td>     </tr>   </xsl:template>    </xsl:stylesheet>




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net