whine: group by mit XSLT 1.0

Moin!

Folgende XML-Struktur versuche ich zu gruppieren:

<Instruction>
  <Fact>A</Fact>
  <Locations>			
     <Location>
        <Coordinates>50,14</Coordinates>
  			<ChartsAffected>
 				  <Chart>100</Chart>
 				  <Chart>101</Chart>					
          <Chart>103</Chart>
			  </ChartsAffected>											
     </Location>
     <Location>
       <Coordinates>52,16</Coordinates>
		   <ChartsAffected>
					<Chart>100</Chart>
  				<Chart>101</Chart>
  				<Chart>98</Chart>
			 </ChartsAffected>											
    </Location>
 </Locations>	
</Instruction>
<Instruction>
  <Fact>B</Fact>
  <Locations>			
     <Location>
      <Coordinates>50.6,14.4</Coordinates>
  			<ChartsAffected>
 				  <Chart>50</Chart>
 				  <Chart>77</Chart>					
          <Chart>98</Chart>
			  </ChartsAffected>											
     </Location>
     <Location>
     <Coordinates>50.7,14.5</Coordinates>
			 <ChartsAffected>
					<Chart>66</Chart>
					<Chart>77</Chart>
  				<Chart>100</Chart>
			 </ChartsAffected>											
    </Location>
 </Locations>	
</Instruction>

Rauskommen soll in HTML in etwa so etwas:

<table> 
  <tr> 
   <th>Chart</th> 
   <th>Fact</th> 
   <th>Location</th> 
  </tr> 
  <tr> 
   <td>100</td> 
   <td>A</td> 
   <td>50,14</td> 
  </tr> 
  <tr> 
   <td>100</td> 
   <td>A</td> 
   <td>52,16</td> 
  </tr>
  <tr> 
   <td>100</td> 
   <td>B</td> 
   <td>50.7,14.5</td> 
  </tr>
  <tr> 
   <td>101</td> 
   <td>A</td> 
   <td>50,14</td> 
 ...
  </tr>
</table>

Ich weiß, dass Muenchian Grouping das Mittel der Wahl ist, aber durch das Gruppieren der Charts über die Location und Instruction bekomme ich mit

<xsl:apply-templates select="Instruction/Locations/Location/ChartsAffected/[generate-id() = generate-id(key('groups', Chart)[1])]"/>

immer alles doppelt und dreifach ausgegeben. Ehrlich gesagt übersteigt das Muenchian Grouping meinen Horizont :( Und wenn ich es genau betrachte, ist es vielleicht auch gar kein Gruppieren, sondern mehr ein Umsortieren. Letztlich will ich sehen: Chart 100: Welche Facts an welchen Coordinates.

Weiß jemand Rat?

LG whine

  1. Hallo whine,

    dieser Ansatz sollte das Prinzip der Gruppierung und Sortierung zeigen. Sortierung ggf. umkehren (descending) oder weglassen, falls die Reihenfolge des jeweils ersten Auftreten eines Chart-Wertes von Bedeutung ist (also 100 …):

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
      <xsl:output method="html" version="5" encoding="UTF-8"/>
    
      <xsl:key name="chart_group" match="Chart" use="."/>
    
      <xsl:template match="/">
        <html>
          <head>
            <title>Test</title>
          </head>
          <body>
            <table>
              <tr>
                <th>Chart</th>
                <th>Fact</th>
                <th>Location</th>
              </tr>
              <xsl:for-each select="//Chart[generate-id() = generate-id(key('chart_group', .)[1])]">
                <xsl:sort select="." data-type="number" order="ascending"/>
                <xsl:variable name="akt_chart" select="."/>
                <xsl:for-each select="//Chart[. = $akt_chart]">
                  <tr>
                    <td>
                      <xsl:value-of select="$akt_chart"/>
                    </td>
                    <td>
                      <xsl:value-of select="preceding::Fact[1]"/>
                    </td>
                    <td>
                      <xsl:value-of select="../../Coordinates"/>
                    </td>
                  </tr>
                </xsl:for-each>
              </xsl:for-each>
            </table>
          </body>
        </html>
      </xsl:template>
    
    </xsl:stylesheet>
    

    Ausgabe:

    <table>
      <tr>
        <th>Chart</th>
        <th>Fact</th>
        <th>Location</th>
      </tr>
      <tr>
        <td>50</td>
        <td>B</td>
        <td>50.6,14.4</td>
      </tr>
      <!-- + 10 tr … -->
      <tr>
        <td>103</td>
        <td>A</td>
        <td>50,14</td>
      </tr>
    </table>
    

    Grüße,
    Thomas

    1. Hier noch eine Variante, welche die gruppierten Chart-Wert nur 1x darstellt:

      <?xml version="1.0" encoding="UTF-8"?>
      <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      
        <xsl:output method="html" version="5" encoding="UTF-8"/>
        <xsl:key name="chart_group" match="Chart" use="."/>
      
        <xsl:template match="/">
          <html>
            <head>
              <title>Test</title>
              <style type="text/css">
                table,td,th { border: 1px solid #000; border-collapse: collapse; padding: 0.5em; }
                td[rowspan] { color: #F00; font-weight: bold; }
              </style>
            </head>
            <body>
              <table>
                <tr>
                  <th>Chart</th>
                  <th>Fact</th>
                  <th>Location</th>
                </tr>
                <xsl:for-each select="//Chart[generate-id() = generate-id(key('chart_group', .)[1])]">
                  <xsl:sort select="." data-type="number" order="ascending"/>
                  <xsl:variable name="akt_chart" select="."/>
                  <xsl:variable name="anz_chart" select="count(//Chart[. = $akt_chart])"/>
                  <xsl:for-each select="//Chart[. = $akt_chart]">
                    <tr>
                      <xsl:if test="position() = 1">
                        <td rowspan="{$anz_chart}">
                          <xsl:value-of select="$akt_chart"/>
                        </td>
                      </xsl:if>
                      <td>
                        <xsl:value-of select="preceding::Fact[1]"/>
                      </td>
                      <td>
                        <xsl:value-of select="../../Coordinates"/>
                      </td>
                    </tr>
                  </xsl:for-each>
                </xsl:for-each>
              </table>
            </body>
          </html>
        </xsl:template>
      
      </xsl:stylesheet>
      
      

      Am Beispiel 100:

      <tr>
        <td rowspan="3">100</td>
        <td>A</td>
        <td>50,14</td>
      </tr>
      <tr>
        <td>A</td>
        <td>52,16</td>
      </tr>
      <tr>
        <td>B</td>
        <td>50.7,14.5</td>
      </tr>
      

      Grüße,
      Thomas

    2. Hallo Thomas,

      grandios! Danke! Es hat zwar eine Weile gebraucht, bis ich es auf meine Belange angepasst habe, weil mir meine XML zu komplex erschien und ich etwas vereinfacht^^ habe... ich wollte ja niemanden verschrecken ;)

      Hättest Du noch eine Idee, wie ich zusammenhängende Charts

        <tr>
          <td>50</td>
          <td>B</td>
          <td>...</td>
        </tr> 
        <tr>
          <td>50</td>
          <td>C</td>
          <td>...</td>
        </tr> 
        <tr>
          <td>60</td>
          <td>C</td>
          <td>...</td>
        </tr> 
      

      kennzeichnen kann. Also z.B. 50 = Hintergrundfarbe x, 60 = Hintergrundfarbe y? Ohne dass ich natürlich die 50 oder 60 kenne...

      LG whine

      1. Hallo whine,

        Hättest Du noch eine Idee, wie ich zusammenhängende Charts kennzeichnen kann. Also z.B. 50 = Hintergrundfarbe x, 60 = Hintergrundfarbe y? Ohne dass ich natürlich die 50 oder 60 kenne...

        Ansatz über durchnummerierte Klassennamen, die dann über die Position ($pos_chart) zugewiesen werden:

        <xsl:template match="/">
          <html>
            <head>
              <title>Test</title>
              <style type="text/css">
                .chart1 { color: #000; background-color: #FFC; }
                .chart2 { color: #000; background-color: #FC6; }
                .chart3 { color: #000; background-color: #3FF; }
                /* usw. */
              </style>
            </head>
            <body>
              <table>
                <tr>
                  <th>Chart</th>
                  <th>Fact</th>
                  <th>Location</th>
                </tr>
                <xsl:for-each select="//Chart[generate-id() = generate-id(key('chart_group', .)[1])]">
                  <xsl:sort select="." data-type="number" order="ascending"/>
                  <xsl:variable name="akt_chart" select="."/>
                  <xsl:variable name="pos_chart" select="position()"/>
                  <xsl:for-each select="//Chart[. = $akt_chart]">
                    <tr class="chart{$pos_chart}">
                      <td>
                        <xsl:value-of select="$akt_chart"/>
                      </td>
                      <td>
                        <xsl:value-of select="preceding::Fact[1]"/>
                      </td>
                      <td>
                        <xsl:value-of select="../../Coordinates"/>
                      </td>
                    </tr>
                  </xsl:for-each>
                </xsl:for-each>
              </table>
            </body>
          </html>
        </xsl:template>
        
        

        Ergebnis:

        <tr class="chart1">
          <td>50</td>
          <td>B</td>
          <td>50.6,14.4</td>
        </tr>
        <tr class="chart2">
          <td>66</td>
          <td>B</td>
          <td>50.7,14.5</td>
        </tr>
        <tr class="chart3">
          <td>77</td>
          <td>B</td>
          <td>50.6,14.4</td>
        </tr>
        <tr class="chart3">
          <td>77</td>
          <td>B</td>
          <td>50.7,14.5</td>
        </tr>
        
        

        Grüße,
        Thomas

        1. Hallo Thomas,

          sehr schön! Nun habe ich versucht, das ganze wechselseitig, also nur mit 2 Farben zu machen:

          			<xsl:choose>
          			  <xsl:when test="($pos_chart mod 2) = 1">
          				  <xsl:variable name="pos_chart_color" select="1"/>
          			  </xsl:when>
          			  <xsl:otherwise>
          				  <xsl:variable name="pos_chart_color" select="2"/>
          			  </xsl:otherwise>
          			</xsl:choose><tr class="chart{$pos_chart_color}">
          

          Allerdings bekomme ich einen Fehler: Kann Variable nicht auflösen Ich habe mir als Zeilennummerierung mal die $pos_chart_color ausgeben lassen, da kommt die 1 oder 2 sauber… Habe ja den Verdacht, dass es ein Datentyp-Problem gibt, bin aber mit number() und string() auch nicht weiter gekommen.

          LG whine

          1. Hallo whine,

            Allerdings bekomme ich einen Fehler: Kann Variable nicht auflösen Ich habe mir als Zeilennummerierung mal die $pos_chart_color ausgeben lassen, da kommt die 1 oder 2 sauber… Habe ja den Verdacht, dass es ein Datentyp-Problem gibt, bin aber mit number() und string() auch nicht weiter gekommen.

            Lege die xsl:variable um das xsl:choose-Konstrukt herum, dann erhält sie den jeweiligen Wert und ist an der gewünschten Stelle verfügbar:

            <xsl:variable name="pos_chart_color">
              <xsl:choose>
                <xsl:when test="($pos_chart mod 2) = 1">1</xsl:when>
                <xsl:otherwise>2</xsl:otherwise>
              </xsl:choose> 
            </xsl:variable>
            

            Grüße,
            Thomas

            1. Hallo Thomas,

              prima, danke!

              LG whine

          2. Hi,

            sehr schön! Nun habe ich versucht, das ganze wechselseitig, also nur mit 2 Farben zu machen:

            wenn das Resultat sein soll, die HTML-Tabellenzeilen abwechselnd einzufärben, ist keine class notwendig.

            Das kann man per Selektor nth-child oder nth-of-type erledigen.

            cu,
            Andreas a/k/a MudGuard