Recipe10.5.Creating Data-Driven Stylesheets


Recipe 10.5. Creating Data-Driven Stylesheets

Problem

You want to generate HTML that is styled based on data content.

Solution

XSLT 1.0

XSLT attribute sets provide a nice vehicle for encapsulating the complexity of data-driven stylization. Consider how the following XML describes an investment portfolio:

<portfolio>   <investment>     <symbol>IBM</symbol>     <current>72.70</current>     <paid>65.00</paid>     <qty>1000</qty>   </investment>   <investment>     <symbol>JMAR</symbol>     <current>1.90</current>     <paid>5.10</paid>     <qty>5000</qty>   </investment>   <investment>     <symbol>DELL</symbol>     <current>24.50</current>     <paid>18.00</paid>     <qty>100000</qty>   </investment>   <investment>     <symbol>P</symbol>     <current>57.33</current>     <paid>63</paid>     <qty>100</qty>   </investment> </portfolio>

You can display this portfolio in a table with a column showing the gain in black or the loss in red:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">       <xsl:output method="html"/>       <xsl:attribute-set name="gain-loss-font">     <xsl:attribute name="color">       <xsl:choose>         <xsl:when test="(current - paid) * qty >= 0">black</xsl:when>         <xsl:otherwise>red</xsl:otherwise>       </xsl:choose>     </xsl:attribute>   </xsl:attribute-set>           <xsl:template match="portfolio">     <html>      <head>       <title>My Portfolio</title>      </head>           <body bgcolor="#FFFFFF" text="#000000">       <h1>Portfolio</h1>       <table border="1" cellpadding="2">         <tbody>           <tr>             <th>Symbol</th>             <th>Current</th>             <th>Paid</th>             <th>Qty</th>             <th>Gain/Loss</th>           </tr>           <xsl:apply-templates/>         </tbody>       </table>      </body>     </html> </xsl:template>     <xsl:template match="investment">   <tr>     <td><xsl:value-of select="symbol"/></td>     <td><xsl:value-of select="current"/></td>     <td><xsl:value-of select="paid"/></td>     <td><xsl:value-of select="qty"/></td>     <td>      <font xsl:use-attribute-sets="gain-loss-font">       <xsl:value-of          select="format-number((current - paid) * qty, '#,##0.00')"/>      </font>     </td>   </tr> </xsl:template>          </xsl:stylesheet>

If you are not concerned with backward compatibility to older browsers, this example can be made cleaner by using the HTML 4.0 style attribute instead of the font element:

  <xsl:attribute-set name="gain-loss-color">     <xsl:attribute name="style">color:<xsl:text/>       <xsl:choose>         <xsl:when test="(current - paid) * qty >= 0">black</xsl:when>         <xsl:otherwise>red</xsl:otherwise>       </xsl:choose>     </xsl:attribute>   </xsl:attribute-set>           ...     <xsl:template match="investment">   <tr>     <td><xsl:value-of select="symbol"/></td>     <td><xsl:value-of select="current"/></td>     <td><xsl:value-of select="paid"/></td>     <td><xsl:value-of select="qty"/></td>     <td xsl:use-attribute-sets="gain-loss-color">       <xsl:value-of                select="format-number((current - paid) * qty, '#,##0.00')"/>     </td>   </tr> </xsl:template>

XSLT 2.0

The main improvement when using 2.0 is the brevity achieved by using XPath expressions instead of xsl:choose. For example, consider the following rewrite of a code snippet from above:

  <xsl:attribute-set name="gain-loss-font">     <xsl:attribute name="color"                     select="if ((current - paid) * qty ge 0)                             then 'black' else 'red'"/>   </xsl:attribute-set>

Discussion

As is usually the case with XSLT, you can approach this problem in many ways. You might consider embedding the style logic directly into the table-generation logic:

<xsl:template match="investment">   <tr>     <td><xsl:value-of select="symbol"/></td>     <td><xsl:value-of select="current"/></td>     <td><xsl:value-of select="paid"/></td>     <td><xsl:value-of select="qty"/></td>     <td>       <font>         <xsl:attribute name="color">           <xsl:choose>             <xsl:when test="(current - paid) * qty >= 0">black</xsl:when>             <xsl:otherwise>red</xsl:otherwise>           </xsl:choose>         </xsl:attribute>         <xsl:value-of            select="format-number((current - paid) * qty, '#,##0.00')"/>       </font>     </td>   </tr> </xsl:template>

Although placing the color determination logic inline might help you figure out what is happening, it complicates the table-creation logic. A more complex example might compute style attributes for many elements. Mixing the structural building aspect of the stylesheet with the stylization aspect will make each aspect harder to understand and modify.

Nevertheless, you can argue that the hardcoding element references in attributes sets detract from their reusability. However, you can usually remedy this problem by simply moving the logic of the attribute determination outside of the attribute set by using templates and modes. Consider a portfolio with varied investments whose profitability is calculated in different ways:

<portfolio>       <stock>     <symbol>IBM</symbol>     <current>72.70</current>     <paid>65.00</paid>     <qty>1000</qty>   </stock>       <stock>     <symbol>JMAR</symbol>     <current>1.90</current>     <paid>5.10</paid>     <qty>5000</qty>   </stock>       <stock>     <symbol>DELL</symbol>     <current>24.50</current>     <paid>18.00</paid>     <qty>100000</qty>   </stock>       <stock>     <symbol>P</symbol>     <current>57.33</current>     <paid>63.00</paid>     <qty>100</qty>   </stock>      <property>     <address>123 Main St. Anytown NY</address>     <paid>100000</paid>     <appraisal>250000</appraisal>   </property>       <property>     <address>13 Skunks Misery Dr. Stinksville NJ</address>     <paid>200000</paid>     <appraisal>50000</appraisal>   </property>    </portfolio>

You can avoid having to define two attribute sets that perform the same function by pushing the logic into templates:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">       <xsl:output method="html"/>       <xsl:attribute-set name="gain-loss-font">     <xsl:attribute name="color">       <xsl:apply-templates select="." mode="gain-loss-font-color"/>     </xsl:attribute>   </xsl:attribute-set>           <xsl:template match="stock" mode="gain-loss-font-color">     <xsl:choose>       <xsl:when test="(current - paid) * qty >= 0">black</xsl:when>       <xsl:otherwise>red</xsl:otherwise>     </xsl:choose> </xsl:template>     <xsl:template match="property" mode="gain-loss-font-color">     <xsl:choose>       <xsl:when test="appraisal - paid  >= 0">black</xsl:when>       <xsl:otherwise>red</xsl:otherwise>     </xsl:choose> </xsl:template>     ...      </xsl:stylesheet>

You might be uncomfortable incorporating any purely stylistic attributes such as colors, fonts, and the like into your XSLT transformation. Perhaps it is not your jobbut the job of a company style czarto decide how to render gains and losses. In this case, you can simply classify the elements and defer stylizing decisions to a separately defined stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">       <xsl:output method="html"/>       <xsl:attribute-set name="gain-loss">     <xsl:attribute name="class">       <xsl:apply-templates select="." mode="gain-loss"/>     </xsl:attribute>   </xsl:attribute-set>           <xsl:template match="stock" mode="gain-loss">     <xsl:choose>       <xsl:when test="(current - paid) * qty >= 0">gain</xsl:when>       <xsl:otherwise>loss</xsl:otherwise>     </xsl:choose> </xsl:template>     <xsl:template match="property" mode="gain-loss">     <xsl:choose>       <xsl:when test="appraisal - paid  >= 0">gain</xsl:when>       <xsl:otherwise>loss</xsl:otherwise>     </xsl:choose> </xsl:template>     <xsl:template match="portfolio">     <html>      <head>       <title>My Portfolio</title>       <link  rel="stylesheet"  type="text/css"  href="portfolio.css"/>      </head>          ...      </xsl:template>     <xsl:template match="stock">   <tr>     <td><xsl:value-of select="symbol"/></td>     <td align="right"><xsl:value-of select="current"/></td>     <td align="right"><xsl:value-of select="paid"/></td>     <td align="right"><xsl:value-of select="qty"/></td>     <td align="right" xsl:use-attribute-sets="gain-loss">       <xsl:value-of              select="format-number((current - paid) * qty, '#,##0.00')"/>     </td>   </tr> </xsl:template>          <xsl:template match="property">   <tr>     <td><xsl:value-of select="address"/></td>     <td align="right"><xsl:value-of select="paid"/></td>     <td align="right"><xsl:value-of select="appraisal"/></td>     <td align="right" xsl:use-attribute-sets="gain-loss">       <xsl:value-of               select="format-number(appraisal - paid, '#,##0.00')"/>     </td>   </tr> </xsl:template>          </xsl:stylesheet>

The style czar can then decide how to render <td > and <td > by using portfolio.css, as shown in Example 10-1.

Example 10-1. portfolio.css
td.gain {      color:black; }     td.loss {      color:red;      font-weight:700; }




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