XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement
   Print.Print
Email.Email article link
The XSLT Cookbook (cover)

XSLT Recipe of the Day

The following recipe is from XSLT Cookbook, by Sal Mangano. All links in this recipe point to the online version of the book on the Safari Bookshelf.

Buy it now, or read it online on the Safari Bookshelf.


8.5. Creating Data-Driven Stylesheets

8.5.2. 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>

8.5.3. 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 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="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 class="gain"> and <td class="loss"> by using portfolio.css, as shown in Example 8-1.


View the past week's recipes: Today | Yesterday | 3 days ago | 4 days ago | 5 days ago | 6 days ago | A week ago





close