Creating Data-Driven Stylesheets

Problem

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

Solution

XSLT attribute sets provide a nice vehicle for encapsulating the complexity of data-driven stylization. Consider how 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 should 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>

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>
    <appriasal>250000</appriasal>
  </property>
   
  <property>
    <address>13 Skunks Misery Dr. Stinksville NJ</address>
    <paid>200000</paid>
    <appriasal>50000</appriasal>
  </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="appriasal - 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 job—but the job of a company style czar—to 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="appriasal - 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="appriasal"/></td>
    <td align="right" xsl:use-attribute-sets="gain-loss">
      <xsl:value-of 
             select="format-number(appriasal - paid, '#,##0.00')"/>
    </td>
  </tr>
</xsl:template>     
   
</xsl:stylesheet>

The style czar can then decide how to render <td class="gain"> and <td class="loss"> by using portfolio.css, as shown in Example 8-1.

Example 8-1. portfolio.css

td.gain
{
     color:black;
}
   
td.loss
{
     color:red;
     font-weight:700;
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset