Micha0987: doppeltes xsl:for-each

Hallo,
ich benötige eine Verschachtelung mit for-each. Allerdings wird immer nur eines der for-each beachtet.

Ich möchte zum Beispiel eine folgende Liste

A   12  88
A   22  23
A   9   101
B   23  890
B   45  42
B   66  456
C   90  11
C   87  86
C   62  45

so aussehen lassen:
A
   12  88
   22  23
   9   101
B
   23  890
   45  42
   66  456
C
   90  11
   87  86
   62  45

Kann mir da jemand weiterhelfen?
Danke!

  1. Hi Micha!

    So hilf mir doch:

    1. Wer xsl braucht der hat auch ein xml (und nicht eine Liste...);
    Magst du nicht ein kleines Stück davon herzeigen (oder als Beispiel in der Struktur kurz alla <el col1="A" col2="12" col3="88"/> ... aufschreiben)?

    2. Und wie soll das jetzt sortiert werden? Nach einer beliebigen Spalte (alphabetisch oder numerisch?) unter Beibehaltung der sonstigen Elementabfolge?
    Oder wie sonst?

    Grüsse,
    Richard

    1. Hi Richard,

      ich würde gerne, aber da das "sensible" Daten von Arbeit sind, kann ich nichts schicken. Ich habe auch eine xml Datei...
      Sortiert sind die Daten bereits nur halt alle gleich untereinander und daher extrem unübersichtlich.

      Das Programm wurde von jemand anderem geschrieben, wo die xml erzeugt werden. Und ich wurde verdonnert Anpassungen zu machen. Das ist toll, denn ich habe so gut we gar keine Ahnung von der Materie.

      Sorry.... trotzdem Danke....

      Hi Micha!

      So hilf mir doch:

      1. Wer xsl braucht der hat auch ein xml (und nicht eine Liste...);
        Magst du nicht ein kleines Stück davon herzeigen (oder als Beispiel in der Struktur kurz alla <el col1="A" col2="12" col3="88"/> ... aufschreiben)?

      2. Und wie soll das jetzt sortiert werden? Nach einer beliebigen Spalte (alphabetisch oder numerisch?) unter Beibehaltung der sonstigen Elementabfolge?
        Oder wie sonst?

      Grüsse,
      Richard

      1. Oder gibts die Möglichkeit Daten nicht doppelt auszugeben?
        Also z.B.
        <name>  --> Hans nur einmal und wenn er darunter nochmal auftauchen würde ihn zu unterdrücken?

        1. Hi nochmal!

          Dann versuch ich's halt als Orakel:

          Ich glaube jetzt verstanden zu haben, dass deine xml Datei aus, sagen wir mal, Datengruppen aufgebaut wie vorgeschlagen aufgebaut ist:
          <el col1="A" col2="12" col3="88"/>
          <el col1="A" col2="02" col3="188"/>
          <el col1="B" col2="09" col3="818"/>
          <el col1="B" col2="99" col3="81"/>

          Und jetzt möchtest du ein html output, indem col1 nur jeweils einmal pro Gruppe aufgeführt ist ?

          Nun gut:

          (sei col1 ein kein rein nummersicher eintrag => type=text)

          <xsl:for each select="./el">
          <xsl:sort select = "./@col1" order="ascending" data-type="text"/>
            <xsl:variable name="compare">
              xsl:choose
                <xsl:when test="position()=1"></xsl:when>
                xsl:otherwise<xsl:value-of select="./@col1"/></xsl:otherwise>
              </xsl:choose>
            </xsl:variable>
            <xsl:if test="./@col1!=$compare">
              <p>Nur einmal: <xsl:value-of select="./@col1"/></p>
            </xsl:if>
            <p>Jedesmal: <xsl:value-of select="./@col2"/> <xsl:value-of select="./@col3"/></p>
          </xsl:for-each>

          Mehr gibt die Kristallkugel leider nicht her.

          Grüsse,
          Richard

          1. nein eher so in der xml
            <name>hase</name><jahr>2007<jahr><betrag>20.00</betrag>
            <name>hase</name><jahr>2006<jahr><betrag>880.00</betrag>
            <name>hase</name><jahr>2006<jahr><betrag>37.00</betrag>
            <name>hase</name><jahr>2007<jahr><betrag>21.00</betrag>

            davon mit verschiedenen namen halt einige seiten.

            das ganzw wird in ein pdf gezaubert...

            aber beim pdf steht halt dann immer
            hase     2007  20.00
            hase     2006  880.00
            hase     2006  37.00
            hase     2007  21.00

            ich will aber das es so aussieht...

            hase 2007  20.00
                 2006  880.00
                 2006  37.00
                 2007  21.00

            selbst die xsl datei ist ellenlang....

            <xsl:for-each select="****">
              fo:table-row
               <fo:table-cell padding="0.5pt">
                fo:block
                 <xsl:value-of select="****"/>
                </fo:block>
               </fo:table-cell>
              </fo:table-row>
             <xsl:apply-templates select="****"/>
            </xsl:for-each>

            Das ist jetzt eine sehr gekürzte Variante... hab noch ein paar Seiten davon *g*
            Das for-each oben ist dafür, die Zeilen zu wiederholen mit den Zahlen (jahr/betrag)
            Oder kann man "doppelte Einträge" wegblenden?

            vielleicht hilft das ja ein wenig

            Hi nochmal!

            Dann versuch ich's halt als Orakel:

            Ich glaube jetzt verstanden zu haben, dass deine xml Datei aus, sagen wir mal, Datengruppen aufgebaut wie vorgeschlagen aufgebaut ist:
            <el col1="A" col2="12" col3="88"/>
            <el col1="A" col2="02" col3="188"/>
            <el col1="B" col2="09" col3="818"/>
            <el col1="B" col2="99" col3="81"/>

            Und jetzt möchtest du ein html output, indem col1 nur jeweils einmal pro Gruppe aufgeführt ist ?

            Nun gut:

            (sei col1 ein kein rein nummersicher eintrag => type=text)

            <xsl:for each select="./el">
            <xsl:sort select = "./@col1" order="ascending" data-type="text"/>
              <xsl:variable name="compare">
                xsl:choose
                  <xsl:when test="position()=1"></xsl:when>
                  xsl:otherwise<xsl:value-of select="./@col1"/></xsl:otherwise>
                </xsl:choose>
              </xsl:variable>
              <xsl:if test="./@col1!=$compare">
                <p>Nur einmal: <xsl:value-of select="./@col1"/></p>
              </xsl:if>
              <p>Jedesmal: <xsl:value-of select="./@col2"/> <xsl:value-of select="./@col3"/></p>
            </xsl:for-each>

            Mehr gibt die Kristallkugel leider nicht her.

            Grüsse,
            Richard

            1. Hi!
              Nachdem du

              <xsl:for-each select="****">
                fo:table-row

              schreibst, scheint es ein umfassendes Element zu geben, z.B.
              <kunde><name>hase</name><jahr>2007<jahr><betrag>20.00</betrag></kunde>
              <kunde><name>hase</name><jahr>2006<jahr><betrag>880.00</betrag></kunde>

              somit dann:
              <xsl:for each select="./kunde">
               <xsl:sort select = "./name" order="ascending" data-type="text"/>
                 fo:table-row
                 <xsl:variable name="compare">
                   xsl:choose
                     <xsl:when test="position()=1"></xsl:when>
                     xsl:otherwise<xsl:value-of select="./name"/></xsl:otherwise>
                   </xsl:choose>
                 </xsl:variable>
                 xsl:choose
                   <xsl:when test="./name!=$compare">
                      <fo:table-cell padding="0.5pt"><xsl:value-of select="./name"/></fo:table-cell>
                   </xsl:when>
                   xsl:otherwise
                      <fo:table-cell padding="0.5pt"> </fo:table-cell>
                   </xsl:otherwise>
                 </xsl:choose>
                 <fo:table-cell padding="0.5pt"><xsl:value-of select="./jahr"/></fo:table-cell>
                 <fo:table-cell padding="0.5pt"><xsl:value-of select="./betrag"/></fo:table-cell>
                 </fo:table-row>
              </xsl:for-each>

              Oder kann man "doppelte Einträge" wegblenden?

              Nicht, dass ich wüßte...

              Grüsse,
              Richard

              1. Hi,

                erst einmal danke! Ich scheine mich zu blöd anzustellen... bei mir stehen jetzt in der Tabelle nur noch die Namen und das auch noch doppelt, dreifach... sooft wie sie halt vorkommen... ich raff das irgendwie nicht.

                Micha0987

                Hi!
                Nachdem du

                <xsl:for-each select="****">
                  fo:table-row
                schreibst, scheint es ein umfassendes Element zu geben, z.B.
                <kunde><name>hase</name><jahr>2007<jahr><betrag>20.00</betrag></kunde>
                <kunde><name>hase</name><jahr>2006<jahr><betrag>880.00</betrag></kunde>

                somit dann:
                <xsl:for each select="./kunde">
                <xsl:sort select = "./name" order="ascending" data-type="text"/>
                   fo:table-row
                   <xsl:variable name="compare">
                     xsl:choose
                       <xsl:when test="position()=1"></xsl:when>
                       xsl:otherwise<xsl:value-of select="./name"/></xsl:otherwise>
                     </xsl:choose>
                   </xsl:variable>
                   xsl:choose
                     <xsl:when test="./name!=$compare">
                        <fo:table-cell padding="0.5pt"><xsl:value-of select="./name"/></fo:table-cell>
                     </xsl:when>
                     xsl:otherwise
                        <fo:table-cell padding="0.5pt"> </fo:table-cell>
                     </xsl:otherwise>
                   </xsl:choose>
                   <fo:table-cell padding="0.5pt"><xsl:value-of select="./jahr"/></fo:table-cell>
                   <fo:table-cell padding="0.5pt"><xsl:value-of select="./betrag"/></fo:table-cell>
                   </fo:table-row>
                </xsl:for-each>

                Oder kann man "doppelte Einträge" wegblenden?
                Nicht, dass ich wüßte...

                Grüsse,
                Richard

                1. einige Versuche später,... jetzt steht nichts mehr in dem pdf (in der tabelle). ich würde das am liebsten in die tonne werfen.

                  Hi,

                  erst einmal danke! Ich scheine mich zu blöd anzustellen... bei mir stehen jetzt in der Tabelle nur noch die Namen und das auch noch doppelt, dreifach... sooft wie sie halt vorkommen... ich raff das irgendwie nicht.

                  Micha0987

                  Hi!
                  Nachdem du

                  <xsl:for-each select="****">
                    fo:table-row
                  schreibst, scheint es ein umfassendes Element zu geben, z.B.
                  <kunde><name>hase</name><jahr>2007<jahr><betrag>20.00</betrag></kunde>
                  <kunde><name>hase</name><jahr>2006<jahr><betrag>880.00</betrag></kunde>

                  somit dann:
                  <xsl:for each select="./kunde">
                  <xsl:sort select = "./name" order="ascending" data-type="text"/>
                     fo:table-row
                     <xsl:variable name="compare">
                       xsl:choose
                         <xsl:when test="position()=1"></xsl:when>
                         xsl:otherwise<xsl:value-of select="./name"/></xsl:otherwise>
                       </xsl:choose>
                     </xsl:variable>
                     xsl:choose
                       <xsl:when test="./name!=$compare">
                          <fo:table-cell padding="0.5pt"><xsl:value-of select="./name"/></fo:table-cell>
                       </xsl:when>
                       xsl:otherwise
                          <fo:table-cell padding="0.5pt"> </fo:table-cell>
                       </xsl:otherwise>
                     </xsl:choose>
                     <fo:table-cell padding="0.5pt"><xsl:value-of select="./jahr"/></fo:table-cell>
                     <fo:table-cell padding="0.5pt"><xsl:value-of select="./betrag"/></fo:table-cell>
                     </fo:table-row>
                  </xsl:for-each>

                  Oder kann man "doppelte Einträge" wegblenden?
                  Nicht, dass ich wüßte...

                  Grüsse,
                  Richard

  2. Hallo,

    ich benötige eine Verschachtelung mit for-each. Allerdings wird immer nur eines der for-each beachtet.

    Ich möchte zum Beispiel eine folgende Liste
    so aussehen lassen:
    A
       12  88
       22  23
       9   101
    B

    Ohne das XML (du kannst ja eines mit dummy Daten basteln, es braucht nur wenige Daten um die Struktur zu sehen) kann man dir kaum was sagen:
    Du willst offensichtlich gruppieren:
    Schaue dir: https://forum.selfhtml.org/?t=156356&m=1017213 an, dort geht es um eine sehr ähnliche Frage.
    Wenn du damit nicht klarkommst, kannst du hier nachfragen.

    Grüße
    Thomas

    1. Sorry, davon versteh ich kein Wort :-(

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

      davon mit verschiedenen namen halt einige seiten.

      das ganzw wird in ein pdf gezaubert...

      aber beim pdf steht halt dann immer
      hase     2007  20.00
      hase     2006  880.00
      hase     2006  37.00
      hase     2007  21.00

      vogel 2007 21.00
      vogel 2007 20.00
      vogel 2006 880.00
      vogel 2007 21.00

      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

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

      <xsl:for-each select="kunde">
        fo:table-row
         <fo:table-cell padding="0.5pt">
          fo:block
           <xsl:value-of select="****"/>
          </fo:block>
         </fo:table-cell>
        </fo:table-row>
       <xsl:apply-templates select="****"/>
      </xsl:for-each>

      Das ist jetzt eine sehr gekürzte Variante... hab noch ein paar Seiten davon *g*
      Das for-each oben ist dafür, die Zeilen zu wiederholen mit den Zahlen (jahr/betrag)
      Oder kann man "doppelte Einträge" wegblenden?

      vielleicht hilft das ja ein wenig

      Hallo,

      ich benötige eine Verschachtelung mit for-each. Allerdings wird immer nur eines der for-each beachtet.

      Ich möchte zum Beispiel eine folgende Liste
      so aussehen lassen:
      A
         12  88
         22  23
         9   101
      B

      Ohne das XML (du kannst ja eines mit dummy Daten basteln, es braucht nur wenige Daten um die Struktur zu sehen) kann man dir kaum was sagen:
      Du willst offensichtlich gruppieren:
      Schaue dir: https://forum.selfhtml.org/?t=156356&m=1017213 an, dort geht es um eine sehr ähnliche Frage.
      Wenn du damit nicht klarkommst, kannst du hier nachfragen.

      Grüße
      Thomas

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