HTML-Tabellen erzeugen

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie möchten XML-Inhalte auf HTML-Tabellen abbilden.

Lösung

Tabellen werden oftmals in zwei Schritten erzeugt. Zuerst wird der Markup-Code für die obersten Tabellenelemente erzeugt, und dann werden Templates angewendet, um die Zeilen und deren Felder zu erzeugen. Die Lösung ist eine Modifikation eines Teils des Stylesheets, das im Rezept Verlinkte Dokumente erstellen gezeigt wurde. Der veränderte Teil ist hier hervorgehoben:

<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html"/>
  <xsl:param name="URL"/>
  <xsl:template match="/">
    <xsl:apply-templates select="*" mode="index"/>
    <xsl:apply-templates select="*" mode="content"/>
  </xsl:template>
  <!-- =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  = -->
  <!--             Erzeuge index.html  (mode = "index")                          -->
  <!-- =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  = -->
  <xsl:template match="salesBySalesperson" mode="index">
    <!-- Nicht standardkonformes Saxon-xsl:document! -->
    <xsl:document href="index.html">
      <html>
        <head>
          <title>Sales by Salesperson</title>
        </head>
        <body bgcolor="#FFFFFF" text="#000000">
          <h1>Sales By Salesperson</h1>
          <xsl:apply-templates mode="index"/>
        </body>
      </html>
    </xsl:document>
  </xsl:template>
  <xsl:template match="salesperson" mode="index">
    <h2>
      <a href="{concat($URL,@name,'.html')}">
        <xsl:value-of select="@name"/>
      </a>
    </h2>
  </xsl:template>
  <!-- =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  = -->
  <!--          Erzeuge @name.html  (mode = "content")                           -->
  <!-- =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  = -->
  <template:xsl match="salesperson" mode="content">
    <xsl:document href="{concat(@name,'.html')}">
      <html>
        <head>
          <title><xsl:value select="@name"/></title>
        </head>
        <body bgcolor="#FFFFFF" text="#000000">
          <h1><xsl:value-of select="@name"/> Sales</h1>
          <table border="1" cellpadding="3">
            <tbody>
              <tr>
                <th>SKU</th>
                <th>Sales (in US $)</th>
              </tr>
              <xsl:apply-templates mode="content"/>
            </tbody>
          </table>
          <h2><a href="{concat($URL,'index.html')}">Home</a></h2>
        </body>
      </html>
    </xsl:document>
  </xsl:template>
  <xsl:template match="product" mode="content">
    <tr>
      <td><xsl:value-of select="@sku"/></td>
      <td><xsl:value-of select="@totalSales"/></td>
    </tr>
  </xsl:template>
</xsl:stylesheet>

Diskussion

XSLT 1.0

Beim Erstellen von Tabellen müssen Sie Daten oftmals nach bestimmten Kriterien gruppieren. Schwierigkeiten entstehen dann, wenn die Eingabedaten bereits gruppiert sind bzw. je nachdem, ob es sich bei der Gruppierung um geschlossene oder offene Kriterien handelt. Nehmen Sie z.B. an, Sie müssten Verkaufszahlen nach Region gruppieren:

<?xml version="1.0" encoding="UTF-8"?>
<sales>
  <product sku="10000" sales="90000.00" region="NE"/>
  <product sku="10000" sales="10000.00" region="NW"/>
  <product sku="10000" sales="55000.00" region="SE"/>
  <product sku="10000" sales="32000.00" region="SW"/>
  <product sku="10000" sales="95000.00" region="NC"/>
  <product sku="10000" sales="88000.00" region="SC"/>
  <product sku="20000" sales="77000.00" region="NE"/>
  <product sku="20000" sales="11100.00" region="NW"/>
  <product sku="20000" sales="33210.00" region="SE"/>
  <product sku="20000" sales="78000.00" region="SW"/>
  <product sku="20000" sales="105000.00" region="NC"/>
  <product sku="20000" sales="12300.00" region="SC"/>
  <product sku="30000" sales="1000.00" region="NE"/>
  <product sku="30000" sales="5100.00" region="NW"/>
  <product sku="30000" sales="3210.00" region="SE"/>
  <product sku="30000" sales="8000.00" region="SW"/>
  <product sku="30000" sales="5000.00" region="NC"/>
  <product sku="30000" sales="11300.00" region="SC"/>
</sales>

Hierbei wissen Sie vorab, dass es sechs Regionen gibt. Die Gruppierung könnten Sie also durch eine explizite Auswahl vornehmen:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html"/>
  <xsl:template match="sales">
    <html>
      <head>
        <title>Sales by Region</title>
      </head>
      <body>
        <h1>Sales by Region</h1>
        <table border="1" cellpadding="3">
          <tbody>
            <tr>
              <th>SKU</th>
              <th>Sales</th>
            </tr>
            <xsl:call-template name="group-region">
              <xsl:with-param name="region" select=" 'NE' "/>
              <xsl:with-param name="title" select="'North East Sales'"/>
            </xsl:call-template>
            <xsl:call-template name="group-region">
              <xsl:with-param name="region" select=" 'NW' "/>
              <xsl:with-param name="title" select="'North West Sales'"/>
            </xsl:call-template>
            <xsl:call-template name="group-region">
              <xsl:with-param name="region" select=" 'NC' "/>
              <xsl:with-param name="title" select="'North Central Sales'"/>
            </xsl:call-template>
            <xsl:call-template name="group-region">
              <xsl:with-param name="region" select=" 'SE' "/>
              <xsl:with-param name="title" select="'South East Sales'"/>
            </xsl:call-template>
            <xsl:call-template name="group-region">
              <xsl:with-param name="region" select=" 'SC' "/>
              <xsl:with-param name="title" select="'South Central Sales'"/>
            </xsl:call-template>
            <xsl:call-template name="group-region">
              <xsl:with-param name="region" select=" 'SW' "/>
              <xsl:with-param name="title" select="'South West Sales'"/>
            </xsl:call-template>
          </tbody>
        </table>
      </body>
    </html>
  </xsl:template>
  <xsl:template name="group-region">
    <xsl:param name="region"/>
    <xsl:param name="title"/>
    <xsl:variable name="products" select="product[@region = $region]" />
    <tr>
      <th colspan="2"><xsl:value-of select="$title" /></th>
    </tr>
    <xsl:apply-templates select="$products"/>
    <tr style="font-weight:bold">
      <td>Total</td>
      <td align="right">
        <xsl:value-of select="format-number(sum($products/@sales), '#.00')"/>
      </td>
    </tr>
  </xsl:template>
  <xsl:template match="product">
    <tr>
      <td><xsl:value-of select="@sku"/></td>
      <td align="right"><xsl:value-of select="@sales"/></td>
    </tr>
  </xsl:template>
</xsl:stylesheet>

Wenn Ihnen dabei die feste Kodierung der Gruppennamen nicht gefällt, können Sie einen tabellenbasierten Ansatz verwenden:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sales="sales">
  <sales:region code="NE" name="North East"/>
  <sales:region code="NC" name="North Central"/>
  <sales:region code="NW" name="North West"/>
  <sales:region code="SE" name="South East"/>
  <sales:region code="SC" name="South Central"/>
  <sales:region code="SW" name="South West"/>
  <xsl:variable name="products" select="/sales/product"/>
  <xsl:output method="html"/>
  <xsl:template match="sales">
    <html>
      <head>
        <title>Sales by Region</title>
      </head>
      <body>
        <h1>Sales by Region</h1>
        <table border="1" cellpadding="3">
          <tbody>
            <tr>
              <th>SKU</th>
              <th>Sales</th>
            </tr>
            <xsl:for-each select="document('')/*/sales:region">
              <tr>
                <th colspan="2"><xsl:value-of select="@name"/> Sales</th>
              </tr>
              <xsl:call-template name="group-region">
                <xsl:with-param name="region" select="@code"/>
              </xsl:call-template>
            </xsl:for-each>
          </tbody>
        </table>
      </body>
    </html>
  </xsl:template>
  <xsl:template name="group-region">
    <xsl:param name="region"/>
    <xsl:apply-templates select="$products[@region=$region]"/>
    <tr style="font-weight:bold">
      <td>Total</td>
      <td align="right">
        <xsl:value-of select="format-number(sum($products[@region=$region]/@sales),'#.00')"/>
      </td>
    </tr>
  </xsl:template>
  <xsl:template match="product">
    <tr>
      <td><xsl:value-of select="@sku"/></td>
      <td align="right"><xsl:value-of select="@sales"/></td>
    </tr>
  </xsl:template>
</xsl:stylesheet>

Natürlich sind viele Gruppierungsprobleme nicht ganz so leicht zu lösen. Stellen Sie sich vor, Sie entwickeln ein Stylesheet, das von vielen Firmen bzw. Abteilungen in einer großen Firma verwendet werden soll, in denen es überall verschiedene Namenskonventionen für Verkaufsbereiche gibt. Dieses Problem können Sie auf verschiedene Arten angehen, aber eine der effizientesten ist die Muenchsche Gruppierungsmethode (benannt nach Steve Muench bei Oracle, der sie erfunden hat), die eine Kombination von Schlüsseln und IDs benutzt:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sales="sales">
  <xsl:output method="html"/>
  <xsl:key name="region-key" match="product" use="@region"/>
  <xsl:template match="sales">
    <html>
      <head>
        <title>Sales by Region</title>
      </head>
      <body>
        <h1>Sales by Region</h1>
        <table border="1" cellpadding="3">
          <tbody>
            <tr>
              <th>SKU</th>
              <th>Sales</th>
            </tr>
            <xsl:variable name="unique-regions" select="/sales/product[generate-id(.) = generate-id(key('region-key',@region))]/@region"/>
            <xsl:for-each select="$unique-regions">
              <tr >
                <th colspan="2"><xsl:value-of select="."/> Sales</th>
              </tr>
              <xsl:call-template name="group-region">
                <xsl:with-param name="region" select="."/>
              </xsl:call-template>
            </xsl:for-each>
          </tbody>
        </table>
      </body>
    </html>
  </xsl:template>
  <xsl:template name="group-region">
    <xsl:param name="region"/>
    <xsl:apply-templates select="key('region-key', @region)"/>
    <tr style="font-weight:bold">
      <td>Total</td>
      <td align="right">
        <xsl:value-of select="format-number(sum(key('region-key', @region)/@sales),'#.00')"/>
      </td>
    </tr>
  </xsl:template>
  <xsl:template match="product">
    <tr>
      <td><xsl:value-of select="@sku"/></td>
      <td align="right"><xsl:value-of select="@sales"/></td>
    </tr>
  </xsl:template>
</xsl:stylesheet>

Von ihrer Struktur her ist diese Lösung vergleichbar mit der tabellenbasierten Methode. Der wesentliche Unterschied besteht darin, wie die Menge der eindeutigen Gruppen bestimmt wird. Beim tabellenbasierten Ansatz werden die eindeutigen Regionen wörtlich in sales:region-Elementen kodiert. Die Muenchsche Methode benutzt einen Schlüssel zur Definition des Gruppenwerts. Sie wissen, dass der Ausdruck key('region-key',@region) die Knotenmenge aller Produkte zurückgibt, deren Region gleich @region ist. Und Sie wissen, dass generate-id bei einer Knotenmenge die eindeutige ID des ersten Elements in der Knotenmenge zurückgibt. Deswegen wird der Ausdruck [generate-id(.) = generate-id(key('region-key', @region))] nur beim ersten Knoten in jeder Gruppe wahr sein, wodurch Sie in diesem Fall alle eindeutigen Regionen erhalten, aus denen die Gruppe besteht. Durch diesen Schlüssel werden auch andere Teile des Stylesheets effizienter, die über den Schlüssel auf Produkte verweisen.

XSLT 2.0

Sofern Sie nicht direkt zu diesem Kapitel gesprungen sind, wissen Sie, dass die beste Methode, mit der Sie Gruppierungsprobleme in XSLT 2.0 anpacken können, aus xsl:for-each-group besteht:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html"/>
  <xsl:template match="sales">
    <html>
      <head>
        <title>Sales by Region</title>
      </head>
      <body>
        <h1>Sales by Region</h1>
        <table border="1" cellpadding="3">
          <tbody>
            <tr>
              <th>SKU</th>
              <th>Sales</th>
            </tr>
            <xsl:for-each-group select="product" group-by="@region">
              <tr >
                <th colspan="2"><xsl:value-of select="."/> Sales</th>
              </tr>
              <xsl:apply-templates select="current-group( )"/>
              <tr style="font-weight:bold">
                <td>Total</td>
                <td align="right">
                  <xsl:value-of select="format-number(sum(current-group( )/@sales),'#.00')"/>
                </td>
              </tr>
            </xsl:for-each-group>
          </tbody>
        </table>
      </body>
    </html>
  </xsl:template>
  <xsl:template match="product">
    <tr>
      <td><xsl:value-of select="@sku"/></td>
      <td align="right"><xsl:value-of select="@sales"/></td>
    </tr>
  </xsl:template>
</xsl:stylesheet>

  

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