Announcement

Collapse
No announcement yet.

Doppelte Einträge ausfiltern

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

  • Doppelte Einträge ausfiltern

    Hi,

    ich bin relativ neu in XSL, habe aber bereits ein ganzes Buch verschlungen. Dennoch finde ich keine zufriedenstellende Lösung für mein aktuelles Problem, auch die Suche im mehreren Foren, einschließlich diesem, hat zu keinem Ergebnis geführt. Ich schildere einfach mal das Problem, doch zuerst die Struktur meiner XML-Datei:

    <productlist>
    <product>
    <num>000160562</num>
    <cat>Waschmaschinen</cat>
    <name>Miele XY1</name>
    </product>
    <product>
    <num>000160563</num>
    <cat>Waschmaschinen</cat>
    <name>Siemens CR3</name>
    </product>
    <product>
    <num>000160565</num>
    <cat>Radios</cat>
    <name>Philips CT-1</name>
    </product>
    .
    .
    </productlist>

    Nun möchte ich, dass mir der Inhalt von <cat>(Kategorie) ausgegeben wird, wenn jedoch eine Kategorie bereits vorkam, soll diese nicht nochmal ausgegeben werden. Für obigen Ausschnitt sollte das gewünschte Ergebnis wie folgt aussehen:

    Waschmaschinen
    Radios

    Mit der preceding-Achse habe ich das hinbekommen, jedoch vergeht bei der Größe der Datei (über 10.000 Datensätze) eine halbe Ewigkeitbis das Ergebnis ausgegeben wird, falls nicht vorher der Browser mitsamt Webserver abstürzt. In einem anderen Forum habe ich gelesen, dass man bei größeren Dateien besser <xsl:key> verwenden sollte, daraufhin habe ich mir was zusammengeschustert, was bei wenigen Datensätzen zu funktionieren scheint, aber bei Anwendung auf die komplette Datei ebenfalls total versagt:

    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xslutput method="html" encoding="utf-8"/>

    <xsl:key name="category" match="productlist/product" use="cat"/>

    <xsl:template match="/productlist">
    <xsl:for-each select="product[count(key('category',cat))]">
    <div><xsl:value-of select="cat"/></div>
    </xsl:for-each>
    </xsl:template>

    </xsl:stylesheet>

    Demnach müsste die for-each-Schleife immer nur das letzte Produkt einer bestimmten Kategorie zurückgeben. Wie gesagt, bei kleinen Dateien klappts, bei großen leider nicht.

    Vielen Dank schon mal für jede Hilfe.

  • #2
    Probiere es so:

    Code:
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
    <xsl:output method="xml" encoding="ISO-8859-1" indent="yes" version="1.0"
      doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
      doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"/>
    
    <xsl:key name="cat_key" match="product" use="cat"/>
    
    <xsl:template match="productlist">
    
    <html xmlns="http://www.w3.org/1999/xhtml" lang="de" xml:lang="de">
    <head><title>Test</title></head><body>
      <ul>
        <xsl:for-each select="product[generate-id() = generate-id(key('cat_key',cat)[1])]">
          <xsl:sort select="cat" data-type="text" order="ascending"/>
          <li><xsl:value-of select="cat"/></li>
        </xsl:for-each>
      </ul>
    </body></html>
    
    </xsl:template>
    
    </xsl:stylesheet>
    Ergebnis:
    Code:
    ...
    <ul>
      <li>Radios</li>
      <li>Waschmaschinen</li>
    </ul>
    ...
    Sofern XSLT 2.0 zur Verfügung steht, wäre xsl:for-each-group eine Alternative:

    Code:
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <xsl:stylesheet version="2.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:fn="http://www.w3.org/2005/xpath-functions"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      exclude-result-prefixes="fn xs">
    
    <xsl:output method="xml" encoding="ISO-8859-1" indent="yes" version="1.0"
      doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
      doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"/>
    
    <xsl:template match="productlist">
    
    <html xmlns="http://www.w3.org/1999/xhtml" lang="de" xml:lang="de">
    <head><title>Test</title></head><body>
      <ul>
        <xsl:for-each-group select="product" group-by="cat">
          <xsl:sort select="cat" data-type="text" order="ascending"/>   
          <li><xsl:value-of select="cat"/></li>
        </xsl:for-each-group>
      </ul>
    </body></html>
    
    </xsl:template>
    
    </xsl:stylesheet>

    Comment


    • #3
      Hi Thomas,

      danke für die schnelle Antwort, doch leider führt das zum gleichen Ergebnis: Bei wenigen Datensätzen funktioniert es wunderbar, mit der kompletten Datei jedoch nicht.

      Scheinbar scheint es für so große Dateien keine Lösung mit XSLT zu geben, so dass ich das Ganze doch mit mysql realisieren muss. Das betrübt mich sehr, denn ich habe viel wertvolle Zeit damit verbracht mich in XSLT einzuarbeiten. Wirklich sehr ärgerlich, weil ich mich auch richtig darauf gefreut hatte, endlich die Theorie in die Praxis umzusetzen.

      Ich habe zum einen die xml-Datei direkt aus dem Browser (Firefox) aufgerufen. Und dann habe ich es noch mit dem XSLT-Prozessor von php probiert (libxslt).

      Grüße
      Nube2021

      Comment


      • #4
        Das leuchtet mir nicht ein. Mit XSLT werden produktiv oft sehr große Dokumente verarbeitet. Die Browser-Implementierungen sind nicht sehr robust. Probiere es mal mit Saxon 9.0B und speziell dem geposteten XSLT 2.0-Code.

        java -jar pfad/zu/saxon9.jar -o name.html name.xml name.xsl

        PS: Entscheide Dich mal für ein Forum, von mir aus gern für dieses ;-).

        Comment


        • #5
          Hi Thomas,

          nun hatte ich wieder Zeit, mich ausführlich mit der Problematik auseinanderzusetzen. Ich führe meine Ergebnisse mal vor, da bestimmt in Zukunft noch andere Personen auf ähnliche Fragen stoßen werden.

          Ich habe Saxon 9.0B installiert und damit deine beiden Codevorschläge ausgeführt:
          • Für den ersten Vorschlag (also der mit der XSLT 1.0-Version) benötigt Saxon 5939 Millisekunden.
          • Der in PHP integrierte XSLT-Prozessor libxslt benötigt für die gleiche Transformation zwischen 8 und 9 Sekunden.
          • Deinen zweiten Code-Vorschlag (XSLT 2.0) konnte Saxon innerhalb von 1261 Millisekunden abarbeiten. Dies ist auch die performanteste Code/Prozessor-Kombination.
          • In libxslt ist keine XSLT 2.0-Untersützung integriert, und das wird auch in absehbarer Zeit nicht der Fall sein. Daher kann der zweite Vorschlag nicht direkt mit php ausgeführt werden.
          Ein paar Recherchen später habe ich jedoch herausgefunden, dass man über PHP auf Java-Klassen zugreifen kann, womit es also möglich sein sollte, Saxon über php auszuführen. Das habe ich dann auch geschafft. Nachfolgend die Links, die eigentlich reichen sollten, um das Ganze zum Laufen zu bringen:

          Zuerst muss die JavaBridge installiert werden:
          http://php-java-bridge.sourceforge.net/pjb/
          http://jabirahmed.blogspot.com/2007/...th-php-52.html
          http://www.php-resource.de/forum/sho...d/t-82396.html (2. Eintrag von Hopka)

          Dann muss man wissen, wie Saxon von PHP aus aufgerufen werden kann:
          http://saxon.wiki.sourceforge.net/Saxon-from-PHP

          Die Ausführung von Saxon über PHP steht erfreulicherweise der Ausführung von Saxon als Standalone in nichts nach. Für beide Code-Vorschläge wurden die identischen Zeiten gebraucht.

          Nunja, aber dass mein ISP sich überreden lässt, die JavaBridge zu installieren und dazu noch Saxon, das erscheint mir recht unrealistisch. Zudem ist die Transformation in eine Typo3-Umgebung eingebettet, so dass mir das Ganze einfach zu unübersichtlich wird. Ich habe mir daher überlegt, die große Katalogdatei in mehrere kleinere Dateien aufzuteilen. Je nachdem welche Kategorie benötig wird, holt sich dann das Stylesheet die entsprechende Datei. Somit läuft das auf jedes standardmäßiges PHP-System und dazu noch mit akzeptalen Verarbeitungszeiten. Außerdem sind 1261 Millisekunden zwar relativ schnell, aber für den Aufbau einer Website immer noch ungenügend, denn das war nur die Navigation der Website, es kommen noch weitere Teile hinzu, die ebenfalls auf die selbe Katalogdatei zugreifen, so dass die endgültige Verarbeitungszeit deutlich höher liegen sollte. Die Aufteilung der Katalogdatei kann dann praktischerweise mit Saxon Standalone geschehen, so war die Installation nicht ganz umsonst.

          Nun habe ich aber zum Schluß noch eine (codetechnische) Frage.
          Mithilfe deines Codevorschlags habe es geschafft, alle uniquen <act> herauszulesen. Darüber hinaus möchte ich zu jedem <act> die passenden <cat> angezeigt bekommen. Unter den 20.000 Produkten existieren 6 verschiedene <act> und 72 <cat>, jeder <cat> gehört zu just einem <act>. Hier nochmal die Struktur der XML-Datei und danach mein Code-Vorschlag, der leider nicht funktionieren will, denn es werden mir bei jedem <act> alle 72 <cat> angezeigt.

          HTML Code:
          <?xml version="1.0" encoding="ISO-8859-1"?>
          <productlist>
          <product>
              <num>000160562</num>
              <nam>Freddi Fredo</nam>
              <act>Name der Oberkategorie</act>
              <cat>Name der Unterkategorie</cat>
          </product>
          </productlist>
          Und hier das Stylesheet:

          HTML Code:
          <?xml version="1.0"?>
          <xsl:stylesheet version="2.0"
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            xmlns:fn="http://www.w3.org/2005/xpath-functions"
            xmlns:xs="http://www.w3.org/2001/XMLSchema"
            exclude-result-prefixes="fn xs"
            xmlns:saxon="http://icl.com/saxon"
            extension-element-prefixes="saxon">
          
          <xsl:output 
            omit-xml-declaration = "yes"
            method="xml" encoding="UTF-8" indent="yes" version="1.0"
            saxon:character-representation="native"
            doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
            doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"/>
          
          <xsl:template match="productlist">
          
          <html xmlns="http://www.w3.org/1999/xhtml" lang="de" xml:lang="de">
          <head><title>Test</title></head><body>
            <ul>
              <xsl:for-each-group select="product" group-by="act">
                <xsl:sort select="act" data-type="text" order="ascending"/>   
                <li><xsl:value-of select="act"/>
                 <ul>
                   <xsl:for-each-group select="/productlist/product[act=./act]" group-by="cat">
                      <li><xsl:value-of select="cat"/></li>
                  </xsl:for-each-group>
                </ul>
                </li>
              </xsl:for-each-group>
            </ul>
          </body></html>
          
          </xsl:template>
          
          </xsl:stylesheet>
          Vielen Dank schon mal im voraus.

          Beste Grüße
          Nube2021
          Zuletzt editiert von Nube2021; 07.02.2008, 17:30.

          Comment


          • #6
            Hi,

            ich führe dann mal meinen Monolog weiter ;-) Folgendermaßen habe ich das Problem lösen können:

            HTML Code:
             <ul>
                <xsl:for-each-group select="product" group-by="act">
                   <li><xsl:value-of select="./act"/>
                    <xsl:variable name="currAct" select="act"></xsl:variable>
                  <ul>
                    <xsl:for-each-group select="/productlist/product[act=$currAct]" group-by="cat">
                      <li><xsl:value-of select="cat"/></li>
                    </xsl:for-each-group>
                  </ul>
                  </li>
                </xsl:for-each-group>
              </ul>
            Ich habe also eine Variable namens "currAct" hinzugefügt, die bei jedem Durchlauf von for-each-group auf den aktuellen Wert von "act" gesetzt wird. In der verschachtelten for-each-group wird dann auf diese Variable zugegriffen. Die Idee ist eigentlich die gleiche wie bei meinem gescheiterten Versuch zuvor, dort hatte ich
            <xsl:for-each-group select="/productlist/product[act=./act]" group-by="cat">

            statt

            <xsl:for-each-group select="/productlist/product[act=$currAct]" group-by="cat">

            Ich verstehe nicht warum das nicht zum gleichen Ergebnis führt, denn ./act sollte eigentlich das Gleiche enthalten wie $currAct.

            Für eine Erklärung wäre ich sehr dankbar.

            Beste Grüße
            Nube2021

            Comment


            • #7
              Ohne Daten ist das Spekulation, aber ich vermute, dass einerseits Knotenvergleiche durchgeführt werden und andererseits durch select die explizite Abfrage des Textknotens erfolgt. Probiere es alternativ mal mit cat/text().

              Comment


              • #8
                Leider war das nicht die Lösung. Vielleicht bringt uns folgende Textstelle aus einem XSLT-Buch weiter:

                "In dem <xsl:for-each>-Element verweist ».« nicht auf den Kontextknoten des Template, sondern auf den aktuellen Knoten, an dem <xsl:for-each> arbeitet."

                Demnach würde der Ausdruck "/productlist/product[act=./act]" im select-Attribut einer <for-each> bzw. <for-each-group>-Schleife immer true ergeben, da act und ./act den gleichen Inhalt zurückgeben, nämlich den des act-Elements des aktuellen Knotens, an dem <xsl:for-each> arbeitet.
                Aber mit der Lösung mit <xsl:variable> bin ich eigentlich ganz zufrieden.

                Nun noch eine alternative Lösung zu meinem ursprünglichen Problem, weshalb ich diesen Beitrag hier gepostet habe:
                Um doppelte Einträge auszufiltern, kann man statt for-each-group die Funktion distinct-values verwenden:
                http://www.w3.org/TR/xpath-functions...istinct-values

                Comment


                • #9
                  Guter Punkt mit fn:distinct-values() [habe ich ja auch in meinem Vortragsmaterial unter "sequences" drin].

                  Im genannten Beispiel wäre das ein Ansatz:

                  Code:
                  <xsl:template match="productlist">
                    <ul>
                      <xsl:for-each select="fn:distinct-values(product/cat)">
                        <li>
                          <xsl:value-of select="."/>
                        </li>
                      </xsl:for-each>
                    </ul>
                  </xsl:template>

                  Comment

                  Working...
                  X