Datengesteuerte Stylesheets erzeugen

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie möchten HTML generieren, dessen Stil abhängig vom Dateninhalt ist.

Lösung

XSLT 1.0

XSLT-Attributmengen bieten ein nettes Mittel für die Kapselung der Komplexität einer datengesteuerten Gestaltung. Betrachten Sie, wie folgendes Stück XML ein Investment-Portfolio beschreibt:

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

Dieses Portfolio können Sie in einer Tabelle darstellen, in der eine Spalte den Gewinn schwarz bzw. den Verlust rot anzeigt:

<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 &gt;= 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>

Sofern Sie keine Rücksicht auf die Rückwärtskompatibilität mit älteren Browsern nehmen müssen, können Sie dieses Beispiel sauberer formulieren, indem Sie an Stelle des font-Elements das style-Attribut aus HTML 4.0 verwenden:

<xsl:attribute-set name="gain-loss-color">
  <xsl:attribute name="style">color:<xsl:text/>
    <xsl:choose>
      <xsl:when test="(current - paid) * qty &gt;= 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

Der Hauptvorteil beim Einsatz von 2.0 liegt in der mit XPath-Ausdrücken erreichten Kürze verglichen mit xsl:choose. Betrachten Sie zum Beispiel folgende Neuformulierung eines weiter oben verwendeten Codeschnipsels:

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

Diskussion

Wie es bei XSLT häufig der Fall ist, können Sie dieses Problem auf viele Arten anpacken. Eventuell möchten Sie die Logik für den Stil direkt in der Logik für die Tabellenerzeugung unterbringen:

<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 &gt;= 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>

Die Inline-Platzierung der Logik für die Farbauswahl mag Ihnen zwar dabei helfen zu verstehen, was vor sich geht, aber sie verkompliziert die Logik für die Tabellenerzeugung. In einem komplexeren Beispiel könnten style-Attribute für viele Elemente berechnet werden. Durch die Vermischung von strukturbildenden Aspekten des Stylesheets mit stilorientierten Aspekten wird es nur schwieriger, beide zu verstehen und zu modifizieren.

Trotzdem können Sie argumentieren, dass die feste Kodierung von Element-Referenzen in Attributmengen ihre Wiederverwendbarkeit beeinträchtigt. Diesem Problem können Sie allerdings normalerweise dadurch begegnen, dass Sie die Logik für die Attributbestimmung einfach außerhalb der Attributmenge ansiedeln, indem Sie Templates und Modi verwenden. Betrachten Sie ein Portfolio mit verschiedenen Investments, deren Profit auf verschiedene Arten berechnet wird:

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

Wenn Sie es vermeiden möchten, zwei Attributmengen definieren zu müssen, die die gleiche Funktion haben, dann können Sie diese Logik in Templates verlagern:

<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 &gt;= 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  &gt;= 0">black</xsl:when>
      <xsl:otherwise>red</xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  ...
</xsl:stylesheet>

Vielleicht ist Ihnen unwohl dabei, wenn Sie rein stilistische Attribute wie Farben, Fonts und Ähnliches in Ihre XSLT-Transformation aufnehmen. Vielleicht ist es nicht Ihre Aufgabe, sondern die eines Corporate Identity-Gurus, zu entscheiden, wie Gewinne und Verluste dargestellt werden sollen. In diesem Fall können Sie die Elemente einfach klassifizieren und Stilentscheidungen an ein separat definiertes Stylesheet abgeben:

<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 &gt;= 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  &gt;= 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>

Der Stil-Guru kann dann entscheiden, wie <td class="gain"> und <td class="loss"> dargestellt werden sollen, indem er portfolio.css benutzt, wie im folgenden Beispiel gezeigt wird.

Beispiel: portfolio.css

td.gain
{
     color:black;
}

td.loss
{
     color:red;
     font-weight:700;
}

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT Kochbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O'Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de