Announcement

Collapse
No announcement yet.

Distinct, Sort, Count

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Distinct, Sort, Count

    Moin,

    ich möchte aus dem XML
    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <table>
        <column name="Color">
            <item value="red"/>
            <item value="green"/>
            <item value="blue"/>
            <item value="red"/>
            <item value="red"/>
            <item value="blue"/>
            <item value="blue"/>
            <item value="red"/>
            <item value="green"/>
            <item value="red"/>
        </column>
    </table>
    den Output
    Code:
    var data = {
      labels: ['blue', 'green', 'red'],
      series: [3, 2, 5]
    };
    erzeugen, wobei die Farbnamen lexikographisch sortiert werden sollen.

    Meine bisherige Recherche ergab, daß ich die Farbnamen mit mit <xsl:key> und generate-id() distinkt selektieren muß,
    diese in einem Node Set speichere, aufsteigend sortiere (<xsl:sort>) und dieses Node Set dann mit <xsl:template> oder
    <xsl:for-each> iterieren kann. Während der Iteration kann ich dann zählen, wie oft die jeweilige Farbe vorkommt.

    Ist das der richtige Ansatz?

    Cheers
    Vince
    --
    Cheers Vince

  • #2
    Seit 2007 gibt es XSLT 2.0 (z.b. mit Saxon 9 HE von http://saxon.sourceforge.net/) mit for-each-group, damit kann man gruppieren, ohne auf keys zurückgreifen zu müssen:
    Code:
    <?xml version="1.0" encoding="UTF-8" ?>
    <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    
        <xsl:output method="text"/>
        
        <xsl:template match="column[@name = 'Color']">
          <xsl:variable name="groups" as="element(group)*">
            <xsl:for-each-group select="item" group-by="@value">
                <xsl:sort select="current-grouping-key()"/>
                <group color="'{current-grouping-key()}'" count="{count(current-group())}"/>
            </xsl:for-each-group>
          </xsl:variable>
    
          <xsl:text>var data = {
              labels: [</xsl:text>
              <xsl:value-of select="$groups/@color" separator=", "/>
          <xsl:text>],
              series: [</xsl:text>
              <xsl:value-of select="$groups/@count" separator=", "/>
          <xsl:text>]
          };</xsl:text>
    
        </xsl:template>
    </xsl:transform>
    Aber sicherlich kann man das auch mit XSLT 1.0 und Muenchian grouping per xsl: key machen, Beispiel und Erklärungen z.b. unter http://www.jenitennison.com/xslt/grouping/muenchian.xml.

    Comment


    • #3
      Moin,

      das ist sehr cool - danke für das Beispiel! Ich werde die XSLT 1.0-Variante nochmal als "Fingerübung" machen - nochmals danke!
      --
      Cheers Vince

      Comment


      • #4
        Moin,

        so, ich hab jetzt mal die Fingerübung gemacht. Der XSL-Code ist bewußt "auseinandergezogen", damit die einzelnen Schritte (für mich) nachvollziehbar waren. Das ganze sieht wie folgt aus:
        Code:
        <?xml version="1.0" encoding="UTF-8"?>
        <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
            
            <xsl:output method="text" encoding="UTF-8"/>
            
            <xsl:variable name="NewLine" select="'
        '"/>
            
            <!-- Create a node set for each value of the attribute 'value' -->
            <xsl:key name="keyItemValue" match="item" use="@value"/>
            
            <xsl:template match="/table/column">
                
                <!-- Iterate over distinct values -->
                <xsl:for-each select="item[generate-id(.) = generate-id(key('keyItemValue', @value)[1])]">
                    
                    <!-- Sort by value -->
                    <xsl:sort select="@value"/>
                    
                    <!-- Line break -->
                    <xsl:value-of select="$NewLine"/>
                    
                    <!-- Output value -->
                    <xsl:value-of select="@value"/>
                    
                    <!-- Output count -->
                    <xsl:value-of select="count(key('keyItemValue', @value))"/>
                </xsl:for-each>
        
            </xsl:template>
            
        </xsl:stylesheet>
        Das ganze funktioniert sogar Habe ich etwas zu kompliziert gemacht oder vergessen?
        --
        Cheers Vince

        Comment


        • #5
          Muenchian grouping hast du korrekt angewendet, aber dein Ausgabeformat mit JSON oder Javascript, bei dem du den Farbwert und den Farbzähler ja getrennt ausgeben willst, hast du damit noch nicht erreicht. Dafür musst du entweder zweimal gruppieren oder aber das Resultat in einer XML-Struktur speichern (ähnlich wie im geposteten XSLT 2.0 Beispiel), die du dann weiter verarbeitest (wobei das in XSLT 1.0) nur über exsl: node-set geht.

          Comment


          • #6
            Moin,

            elegant ist vermutlich anders, aber ich habe nun folgende funktionierende Version (versuche es mit "Plain XSLT 1.0"):
            Code:
            <?xml version="1.0" encoding="UTF-8"?>
            <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
                
                <xsl:output method="text" encoding="UTF-8"/>
                <xsl:variable name="SingleQuote">'</xsl:variable>
                <xsl:variable name="NewLine" select="'
            '"/>
                <xsl:key name="keyItemValue" match="item" use="@value"/>
                
                <xsl:template match="*">
                    <xsl:value-of select="'var data = {'"/>
                    <xsl:apply-templates select="/table/column" mode="value"/>
                    <xsl:apply-templates select="/table/column" mode="count"/>
                    <xsl:value-of select="concat($NewLine, '};')"/>
                </xsl:template>
                
                <xsl:template match="column" mode="value">
                    <xsl:value-of select="concat($NewLine, '  labels: [')"/>
                    <xsl:for-each select="item[generate-id(.) = generate-id(key('keyItemValue', @value)[1])]">
                        <xsl:sort select="@value"/>
                        <xsl:value-of select="concat($SingleQuote, @value, $SingleQuote)"/>
                        <xsl:if test="not(position() = last())">, </xsl:if>
                    </xsl:for-each>
                    <xsl:value-of select="'], '"/>
                </xsl:template>
                
                <xsl:template match="column" mode="count">
                    <xsl:value-of select="concat($NewLine, '  series: [')"/>
                    <xsl:for-each select="item[generate-id(.) = generate-id(key('keyItemValue', @value)[1])]">
                        <xsl:sort select="@value"/>
                        <xsl:value-of select="count(key('keyItemValue', @value))"/>
                        <xsl:if test="not(position() = last())">, </xsl:if>
                    </xsl:for-each>
                    <xsl:value-of select="'] '"/>
                </xsl:template>
                
            </xsl:stylesheet>
            Bin aber natürlich offen für weitere Vorschläge ...
            Zuletzt editiert von Vince42; 25.02.2016, 23:03. Reason: XSL korrigiert ^^
            --
            Cheers Vince

            Comment


            • #7
              Das sieht sehr gut aus. Als Alternative hier der Ansatz mit der Speicherung als XML und dann (unter Verwendung von exsl: node-set) der weiteren Verarbeitung per Template:

              Code:
              <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
                  xmlns:exsl="http://exslt.org/common">
              
                  <xsl:output method="text" encoding="UTF-8"/>
                  <xsl:variable name="SingleQuote">'</xsl:variable>
                  <xsl:variable name="NewLine" select="'
              '"/>
                  <xsl:key name="keyItemValue" match="item" use="@value"/>
                  
                  <xsl:template match="/">
                      <xsl:variable name="groups-rtf">
                          <xsl:for-each select="table/column/item[generate-id() = generate-id(key('keyItemValue', @value)[1])]">
                              <xsl:sort select="@value"/>
                              <group key="{@value}" count="{count(key('keyItemValue', @value))}"/>
                          </xsl:for-each>
                      </xsl:variable>
                      <xsl:variable name="groups" select="exsl:node-set($groups-rtf)/group"/>
                      
                      <xsl:value-of select="'var data = {'"/>
                      
                      <xsl:value-of select="concat($NewLine, '  labels: [')"/>
                      <xsl:apply-templates select="$groups/@key">
                          <xsl:with-param name="delim" select="$SingleQuote"/>
                      </xsl:apply-templates>
                      <xsl:value-of select="'], '"/>
                      
                      <xsl:value-of select="concat($NewLine, '  series: [')"/>
                      <xsl:apply-templates select="$groups/@count"/>
                      <xsl:value-of select="'] '"/>
                      
                      <xsl:value-of select="concat($NewLine, '};')"/>
                  </xsl:template>
                  
                  <xsl:template match="group/@*">
                      <xsl:param name="delim" select="''"/>
                      <xsl:value-of select="concat($delim, ., $delim)"/>
                      <xsl:if test="not(position() = last())">, </xsl:if>
                  </xsl:template>
                  
                  
              </xsl:stylesheet>

              Comment


              • #8
                Moin,

                auch sehr interessant! Vielen Dank! Werd's gleich mal testen ...
                --
                Cheers Vince

                Comment

                Working...
                X