Thomas J.S.: doppeltes xsl:for-each

Beitrag lesen

Hallo,

Sorry, davon versteh ich kein Wort :-(

OK, dann mal langsam:

<kunde><name>hase</name><jahr>2007<jahr><betrag>20.00</betrag>

Nehmen wir ein korrektes XML-Beispiel:

  
<data>  
<kunde><name>hase</name><jahr>2007</jahr><betrag>20.00</betrag></kunde>  
<kunde><name>hase</name><jahr>2006</jahr><betrag>880.00</betrag></kunde>  
<kunde><name>vogel</name><jahr>2007</jahr><betrag>21.00</betrag></kunde>  
<kunde><name>vogel</name><jahr>2007</jahr><betrag>20.00</betrag></kunde>  
<kunde><name>vogel</name><jahr>2006</jahr><betrag>880.00</betrag></kunde>  
<kunde><name>hase</name><jahr>2006</jahr><betrag>37.00</betrag></kunde>  
<kunde><name>hase</name><jahr>2007</jahr><betrag>21.00</betrag></kunde>  
<kunde><name>vogel</name><jahr>2007</jahr><betrag>21.00</betrag></kunde>  
</data>  

ich will aber das es so aussieht...

hase 2007  20.00
     2006  880.00
     2006  37.00
     2007  21.00

vogel 2007 21.00
      2007 20.00
      2006 880.00
      2007 21.00

  
<?xml version="1.0" encoding="UTF-8"?>  
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">  
 <xsl:output method="html" indent="yes" />  
 <xsl:key name="kunden" match="kunde" use="name" />  
 <xsl:template match="/">  
  <xsl:apply-templates select="data" />  
 </xsl:template>  
 <xsl:template match="data">  
  <table border="1">  
   <tr>  
    <th>Kunde</th>  
    <th>Jahr</th>  
    <th>Betrag</th>  
   </tr>  
   <xsl:for-each select="kunde[generate-id(.) = generate-id(key('kunden', name)[1])]">  
    <xsl:variable name="bestellungen" select="count(key('kunden', name))" />  
  
    <xsl:for-each select="key('kunden', name)">  
     <xsl:sort select="jahr" data-type="number" />  
     <tr>  
      <xsl:if test="position() = 1">  
       <td valign="top" rowspan="{$bestellungen}">  
        <xsl:value-of select="name" />  
       </td>  
      </xsl:if>  
      <td>  
       <xsl:value-of select="jahr" />  
      </td>  
      <td>  
       <xsl:value-of select="betrag" />  
      </td>  
     </tr>  
    </xsl:for-each>  
   </xsl:for-each>  
  </table>  
 </xsl:template>  
</xsl:stylesheet>  

selbst die xsl datei ist ellenlang....
Ich brauche also einmal ein output für jeden kunden, und dann das der name jeweils nur einmal auftaucht...

Jetzt im einzelen:

  
<xsl:key name="kunden" match="kunde" use="name" />  

Dieser Schlüssel sammelt alle Kunden und benutzt den Namen als Wert (nimm mal das so an, mehr weiter unten).

  
<xsl:template match="/">  
  <xsl:apply-templates select="data" />  
 </xsl:template>  
 <xsl:template match="data">  

das habe ich nur gemacht, damit du siehst, wie du das Template aufrufen kannst.

Jetzt machen wir die Tabelle. Der Teil sollte klar sein.

  
<table border="1">  
    <tr>  
     <th>Kunde</th>  
     <th>Jahr</th>  
     <th>Betrag</th>  
    </tr>  

  
<xsl:for-each select="kunde[generate-id(.) = generate-id(key('kunden', name)[1])]">  

Wir sind also im Template für <data>, jetzt kommt ein for-each für jedes <kunde>. Aber wir wollen ja nur den ersten aus einer Gruppe, wo der Name gleich ist:

"generate-id(.)": erzeugt eine eindeutige ID für das aktuelle <kunde>-Element. Diese ID vergleich wir mit:

"generate-id(key('kunden', name)[1])": erzeugt eine eindeutige ID für das erste ([1]) <kunde> aus dem Schlüssel, dessen <name> mit dem <name> aus dem aktuell bearbeiteten <kunde> übereinstimmt.

Damit haben wir unser Vergleichbasis.

Bildlich:
Das for-each ist gerade bei _irgendeinem_ <kunde>-Element angekommen.
Jetzt wird eine ID für dieses <kunde> generiert. Dann wird eine ID für den im Schlüssel befindlichen <kunde>-Elemente erzeugt, aber eben nur für den ersten, wo der name gleich ist. Und weil generate-id() für ein und dasselbe Element immer dieselbe ID erzeugt, können wir vergleichen. Im Schlüssel befinden sich alle Kunden, in unserem Fall so wie das auch im XML steht.

OK, wir können also vergleichen.
Stimmen die zwei IDs überein, haben wir das erste <kunde> aus der Gruppe mit dem gleichen Namen gefunden. Hier wollen wir jetzt gar nicht viel machen, sondern gleich alle <kunde> aus dieser Gruppe ausgeben:

  
<xsl:for-each select="key('kunden', name)">  

Dieser for-each macht nun nichts anderes als für jeden <kunde> dessen Name mit dem ersten gefundenen übereinstimmt bearbeiten (also auch selbst den ersten).

  
<tr>  
      <xsl:if test="position() = 1">  
       <td valign="top" rowspan="{$bestellungen}">  
        <xsl:value-of select="name" />  
       </td>  
      </xsl:if>  
      <td>  
       <xsl:value-of select="jahr" />  
      </td>  
      <td>  
       <xsl:value-of select="betrag" />  
      </td>  
     </tr>  

Das if testet wieder auf das erste aus der Gruppe (wir haben jetzt ja die Gruppe wo die Namen gleich sind) und die weiter oben definierte Variable

  
<xsl:variable name="bestellungen" select="count(key('kunden', name))" />  

zählt, wie viele <kunde>in dieser Gruppe gibt, das können wir dann eben für den rowspan verwenden. (wir wollen ja eine Tabelle mit korrketem HTML)

Das war's dann auch schon.

Die ausgabe ist:

  
<table border="1">  
   <tr>  
      <th>Kunde</th>  
      <th>Jahr</th>  
      <th>Betrag</th>  
   </tr>  
   <tr>  
      <td valign="top" rowspan="4">hase</td>  
  
      <td>2006</td>  
      <td>880.00</td>  
   </tr>  
   <tr>  
      <td>2006</td>  
      <td>37.00</td>  
   </tr>  
  
   <tr>  
      <td>2007</td>  
      <td>20.00</td>  
   </tr>  
   <tr>  
      <td>2007</td>  
      <td>21.00</td>  
  
   </tr>  
   <tr>  
      <td valign="top" rowspan="4">vogel</td>  
      <td>2006</td>  
      <td>880.00</td>  
   </tr>  
   <tr>  
  
      <td>2007</td>  
      <td>21.00</td>  
   </tr>  
   <tr>  
      <td>2007</td>  
      <td>20.00</td>  
   </tr>  
  
   <tr>  
      <td>2007</td>  
      <td>21.00</td>  
   </tr>  
</table>  

Stellst du die Tabelle innterhalb des ersten for-each, erzeugst du für jede Gruppe eine eigene Tabelle:

  
  <xsl:for-each select="kunde[generate-id(.) = generate-id(key('kunden', name)[1])]">  
   <xsl:variable name="bestellungen" select="count(key('kunden', name))" />  
   <table border="1">  
    <tr>  
     <th>Kunde</th>  
     <th>Jahr</th>  
     <th>Betrag</th>  
    </tr>  
    <xsl:for-each select="key('kunden', name)">  
     <xsl:sort select="jahr" data-type="number" />  
     <tr>  
      <xsl:if test="position() = 1">  
       <td valign="top" rowspan="{$bestellungen}">  
        <xsl:value-of select="name" />  
       </td>  
      </xsl:if>  
      <td>  
       <xsl:value-of select="jahr" />  
      </td>  
      <td>  
       <xsl:value-of select="betrag" />  
      </td>  
     </tr>  
    </xsl:for-each>  
   </table>  
  </xsl:for-each>  

Ich weiß schon, dass du hier xsl:fo brauchst, aber ich denke du schaffst es schon selbst die HTML-Tabelle da abzuändern.

Grüße
Thomas