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.
Example 1. portfolio.css
td.gain
{
color:black;
}
td.loss
{
color:red;
font-weight:700;
}