Michael: XSLT - Sortieren und doppelte Einträge unterdrücken

Hallo,

gibt es mit XSLT einen einfachen Trick wie man in eine Liste nach (oder während)dem Sortieren auf doppelte Einträge prüfen und deren Ausgabe dann unterbinden kann?
Hier ein kleines Bsp
XML mit Liste ala:
<liste>
<eintrag>b</eintrag>
<eintrag>a</eintrag>
<eintrag>b</eintrag>
</liste>
XSLT:
<xsl:template match="liste">
<ol>
<xsl:for-each select="eintrag">
<xsl:sort select="." />
<li><xsl:value-of select="." /></li>
</xsl:for-each>
</ol>
</xsl:template>
==> Ergebnis daraus:
1. a
2. b
3. b

Hier würde ich gerne verhindern, dass das zweite b ausgegeben wird, dass das Ergebnis praktisch so aussieht:
1. a
2. b

Rein instinktiv würde ich das jetzt eigentlich so lösen, dass ich in einer if-Abfrage den Inhalt von "." mit dem des "position()+1" vergleiche und falls die gleich sind nichts ausgeben lasse. Das müsste ja eigentlich funktionieren(?).
Da mein XSLT in das ich das integrieren möchte nicht ganz so trivial aufgebaut ist wie das Bsp. hier oben, wollte ich vorher nur noch kurz nachfragen, ob es da nicht doch vielleicht irgendeine XSL-Anweisung im Kontext von xsl-sort gibt, welche ich bisher noch nicht gefunden habe und die die Ausgabe von doppelt vorkommenden Knoteninhalten verhindert.

Danke schon mal und nen schönen Sonntag noch.

Michael

  1. Hi,

    Rein instinktiv würde ich das jetzt eigentlich so lösen, dass ich in einer if-Abfrage den Inhalt von "." mit dem des "position()+1" vergleiche und falls die gleich sind nichts ausgeben lasse. Das müsste ja eigentlich funktionieren(?).

    IIRC kann das nicht funktionieren, denn position() bezieht sich auf die Position im Dokument, nicht im (sortierten) Ergebnis.

    Du müßtest eher darauf testen, ob es ein vorhergehendes Geschwister gibt mit demselben Inhalt, also sowas
    <xsl:variable name="this" value="."/>
    <xsl:if test="not(preceding-sibling::eintrag[. = $this])">
      <xsl:value-of select="."/>
    </xsl:if>

    (ungetestet)

    cu,
    Andreas

    --
    Warum nennt sich Andreas hier MudGuard?
    Schreinerei Waechter
    Fachfragen per E-Mail halte ich für unverschämt und werde entsprechende E-Mails nicht beantworten. Für Fachfragen ist das Forum da.
    1. Hallo,

      Rein instinktiv würde ich das jetzt eigentlich so lösen, dass ich in einer if-Abfrage den Inhalt von "." mit dem des "position()+1" vergleiche und falls die gleich sind nichts ausgeben lasse. Das müsste ja eigentlich funktionieren(?).

      IIRC kann das nicht funktionieren, denn position() bezieht sich auf die Position im Dokument, nicht im (sortierten) Ergebnis.

      Aja, genau, das hab ich jetzt verwechselt.

      Du müßtest eher darauf testen, ob es ein vorhergehendes Geschwister gibt mit demselben Inhalt, also sowas
      <xsl:variable name="this" value="."/>
      <xsl:if test="not(preceding-sibling::eintrag[. = $this])">
        <xsl:value-of select="."/>
      </xsl:if>

      (ungetestet)

      jetzt nicht mehr ;-)
      hast recht, so funktionierts prinzipiell. Dummerweise hab ich in meinem XSLT für solche Listen noch was eingebaut, dass es mir jedes zweite Ausgabeelement farblich abhebt (mit <xsl:if test="(position()mod2) != 0". Wenn ich jetzt weniger Knoten ausgebe, stimmt die farbige Hervorhebung nicht mehr. Mal kucken, ob ich das irgendwie in den Griff kriege. Evtl. muss ichs doch noch irgendwie anders lösen.

      Danke und viele Grüße

      Michael

      1. Hi,

        Dummerweise hab ich in meinem XSLT für solche Listen noch was eingebaut, dass es mir jedes zweite Ausgabeelement farblich abhebt (mit <xsl:if test="(position()mod2) != 0". Wenn ich jetzt weniger Knoten ausgebe, stimmt die farbige Hervorhebung nicht mehr.

        IIRC kann das so sowieso nicht funktionieren, denn position() bezieht sich auf die Position im Dokument, nicht im (sortierten) Ergebnis.
        ;-)

        Wie es zu lösen ist, weiß ich im Moment aber auch nicht.

        cu,
        Andreas

        --
        Warum nennt sich Andreas hier MudGuard?
        Schreinerei Waechter
        Fachfragen per E-Mail halte ich für unverschämt und werde entsprechende E-Mails nicht beantworten. Für Fachfragen ist das Forum da.
        1. Hallo,

          IIRC kann das so sowieso nicht funktionieren, denn position() bezieht sich auf die Position im Dokument, nicht im (sortierten) Ergebnis.
          ;-)

          (was heißt eigentlich IIRC?)
          ???
          bei mir funktioniert es aber:
          Hier mal das Template:
          <xsl:template match="litlist">
           <ul>
            <xsl:for-each select=".//li">
             <xsl:sort select="." />
              <xsl:variable name="position"><xsl:value-of select="position() mod 2"></xsl:value-of> </xsl:variable>
              <li>
               <xsl:attribute name="class">
                xsl:choose
                 <xsl:when test="$position = 1">xsl:textlitli</xsl:text>
                 </xsl:when>
                 <xsl:when test="$position = 0">xsl:textlitli2</xsl:text>
                 </xsl:when>
                </xsl:choose>
               </xsl:attribute>
               <xsl:call-template name="ankersetzen"></xsl:call-template>
               <xsl:value-of select="."></xsl:value-of>
              </li>
             </xsl:for-each>
           </ul>
          </xsl:template>

          Ergebnis: Ausgabe ist sortiert und jedes zweite li hat eine andere class. (Einen Zufall kann ich 100%ig ausschließen, da die Liste zum einen schon ziemlich lang ist und ich zum anderen mal zwei Knoten im XML vertauscht habe)

          Wie kann das sein? - bezieht sich position() nach sort nicht doch auf das sortierte Knotenset???
           »» Wie es zu lösen ist, weiß ich im Moment aber auch nicht.
          Hätte sich ja dann erledigt, wenn es so sein sollte wie oben ich probier mal die Methode, die ich erst nehmen wollte und schreib mal obs funzt.

          PS: Hätte noch ne kleine Zusatzfrage, da kniffle ich auch grad dran rum, ohne auf die Lösung zu kommen.
          In einem XML das linear aufgebaut ist, möchte ich den nächsten vorhergehenden Geschwisterknoten "überschrift" mit Attribut h=1 auswählen, hier ein kurzes Beispiel:
          <überschrift h="1" />
          <bla />
          <überschrift h="1" />(der soll adressiert werden)
          <bla />
          <bla />
          <überschrift h="2" />(von hier aus)
          <bla />
          ...
          <bla />
          <überschrift h="2" />(oder von hier aus)
          Wie adressiere ich denn den Knoten?
          preceding-sibling::überschrift/@h1 Trifft bei mir nichts. Habe ich da einen Denkfehler? - falls das eigentlich funktionieren müsste check ich wahrscheinlich nicht, wo der Parser beim Aufruf gerade steht.

          Vielen Dank und viele Grüße

          Michael

          1. Hi,

            (was heißt eigentlich IIRC?)

            IIRC heißt das If I Remember Correctly ;-)

            Wie kann das sein? - bezieht sich position() nach sort nicht doch auf das sortierte Knotenset???

            Thomas kann das mit Sicherheit aufklären?

            PS: Hätte noch ne kleine Zusatzfrage, da kniffle ich auch grad dran rum, ohne auf die Lösung zu kommen.
            In einem XML das linear aufgebaut ist, möchte ich den nächsten vorhergehenden Geschwisterknoten

            also preceding-sibling

            "überschrift"

            überschrift

            mit Attribut h=1

            [@h=1]

            ==>
            preceding-sibling::überschrift[@h = 1]

            preceding-sibling::überschrift/@h1 Trifft bei mir nichts. Habe ich da einen Denkfehler?

            Ja.
            Das würde ein Attribut h1 eines vorherigen Geschwisters mit Elementnamen überschrift auswählen.

            Wenn der Knoten, den Du auswählen willst, eine Bedingung erfüllen soll, kommt diese in [].

            Und @h1 ist natürlich was anderes als @h = 1

            cu,
            Andreas

            --
            Warum nennt sich Andreas hier MudGuard?
            Schreinerei Waechter
            Fachfragen per E-Mail halte ich für unverschämt und werde entsprechende E-Mails nicht beantworten. Für Fachfragen ist das Forum da.
          2. Hallo,

            <xsl:for-each select=".//li">
               <xsl:sort select="." />
                <xsl:variable name="position"><xsl:value-of select="position() mod 2"></xsl:value-of> </xsl:variable>

            variable select="position() mod 2" name="position"

            Ergebnis: Ausgabe ist sortiert und jedes zweite li hat eine andere class. (Einen Zufall kann ich 100%ig ausschließen, da die Liste zum einen schon ziemlich lang ist und ich zum anderen mal zwei Knoten im XML vertauscht habe)

            Wie kann das sein? - bezieht sich position() nach sort nicht doch auf das sortierte Knotenset???

            Nein, das ist schon OK so. Denn mit for-each selektierst du eine Knotenmenge, alle weitere Angaben in diesem Template (ja, auch for-ech ist ein Template, aber das soll uns jetzt nicht kümmern) gelten für diese Knotenmenge. So wird es immer noch nach position() im Dokument gearbeitet, aber mit der Einschrenkung, dass dabei nur diese Knotenmenge beachtet wird. Deshalb funktioniert es auch, wenn du Knoten vertauschst.

            »» Wie es zu lösen ist, weiß ich im Moment aber auch nicht.
            Hätte sich ja dann erledigt, wenn es so sein sollte wie oben ich probier mal die Methode, die ich erst nehmen wollte und schreib mal obs funzt.

            Was heisst das auf Deutsch?

            Wie adressiere ich denn den Knoten?
            preceding-sibling::überschrift/@h1 Trifft bei mir nichts. Habe ich da einen Denkfehler?

            Ja, denn du hast kein Attribut mit dem Namen "h1". Außerdem so würdest du zwar das Attribut "h1" treffen, nicht aber das Element.

            Grüße
            Thomas

            1. Hallo,

              <xsl:variable name="position"><xsl:value-of select="position() mod 2"></xsl:value-of> </xsl:variable>

              variable select="position() mod 2" name="position"

              Das würde in dem Template, wie ich es gepostet habe doch nicht funktionieren, da ich nicht gegen ein Ergebnisbaumfragment prüfe, sondern gegen den Wert.(?)
              Hier verstehe ich aber auch  nicht genau, worauf Du hinaus willst.(?)
              (Beziehst Du Dich damit auf mein gepostetes Template? Oder auf Deinen Lösungsvorschlag aus Deiner ersten Antwort auf meine ursprüngliche Frage?) (PS: Den auszuprobieren hänge ich noch darüber. Habe da noch ein Problem mit dem aufgrufenen Template "ankersetzen".)

              Wie kann das sein? - bezieht sich position() nach sort nicht doch auf das sortierte Knotenset???

              Nein, das ist schon OK so. Denn mit for-each selektierst du eine Knotenmenge, alle weitere Angaben in diesem Template (ja, auch for-ech ist ein Template, aber das soll uns jetzt nicht kümmern) gelten für diese Knotenmenge. So wird es immer noch nach position() im Dokument gearbeitet, aber mit der Einschrenkung, dass dabei nur diese Knotenmenge beachtet wird. Deshalb funktioniert es auch, wenn du Knoten vertauschst.

              Das will mir immer noch nicht ganz eingehen.
              Nochmal ein Beispiel:

              <bsp>B</bsp> (pos 1)
              <bsp>A</bsp> (pos 2)
              <bsp>C</bsp> (pos 3)
              ...
              <xsl:sort />
              ...
              Knoten sortiert aber aber position() wie im Dokument stelle ich mir dann so vor:
              <bsp>A</bsp> (pos 2)
              <bsp>B</bsp> (pos 1)
              <bsp>C</bsp> (pos 3)
              Was ist jetzt <xsl:value-of select="bsp[position() = 2] />?
              A, oder? - Warum oder wie funktioniert das dann, dass trotzdem im Ergebnisbaum, würde man jeweils position() mod2 ausgeben, das Ergebnis so ist?:
              1
              0
              1
              und eben nicht
              0
              1
              1

              Hätte sich ja dann erledigt, wenn es so sein sollte wie oben ich probier mal die Methode, die ich erst nehmen wollte und schreib mal obs funzt.

              Was heisst das auf Deutsch?

              funzt? = umgangssprachlich für funktioniert
              und "Methode" die ich ursprünglich verwendet hätte wäre (was allerdings auf der fälschlichen Annahme zu den Positionen der Knoten basierte) nach der Sortierung einfach nur den Inhalt von "." mit position()+1 zu vergleichen (da ja im Falle ebenfalls sortierter Positionen Knoten mit gleichem Inhalt aufeinander gefolgt wären).

              Wie adressiere ich denn den Knoten?
              preceding-sibling::überschrift/@h1 Trifft bei mir nichts. Habe ich da einen Denkfehler?

              Entschuldigung, das war ein ganz schöner Fauxpas.
              preceding-sibling::überschrift[@h = 1] trifft dann natürlich auch gleich viel besser.
              Zudem hätte die Frage auch lauten müssen:
              Wie adressiere ich denn GENAU den Knoten - und nicht alle vorhergehenden überschriften mit h=1?

              Danke nochmal und viele Grüße

              Michael

              1. Hi,

                preceding-sibling::überschrift[@h = 1] trifft dann natürlich auch gleich viel besser.
                Zudem hätte die Frage auch lauten müssen:
                Wie adressiere ich denn GENAU den Knoten - und nicht alle vorhergehenden überschriften mit h=1?

                Das preceding-sibling::überschrift[@h = 1] ist ein Nodeset. Da kannst Du wieder mit position in der [] einschränken, welchen Du willst.
                Du willst nur den ersten dieser Knoten, also (ohne Gewähr) preceding-sibling::überschrift[@h = 1 and position()=1] (oder war's position() = last()?).
                Ich müßte mich mal wieder etwas intensiver damit befassen.

                cu,
                Andreas

                --
                Warum nennt sich Andreas hier MudGuard?
                Schreinerei Waechter
                Fachfragen per E-Mail halte ich für unverschämt und werde entsprechende E-Mails nicht beantworten. Für Fachfragen ist das Forum da.
                1. Hallo,

                  Das preceding-sibling::überschrift[@h = 1] ist ein Nodeset. Da kannst Du wieder mit position in der [] einschränken, welchen Du willst.

                  So weit ist das schon klar, nur wusste ich nicht, was die position im jeweiligen Fall ist, da ja mal mehr und mal weniger Knoten dazwischen liegen können, aber...

                  Du willst nur den ersten dieser Knoten, also (ohne Gewähr) preceding-sibling::überschrift[@h = 1 and position()=1] (oder war's position() = last()?).

                  preceding-sibling::überschrift[@h = 1 and (position()=1)]... und es funktioniert. Riesiges Dankeschön! Da wäre ich nie draufgekommen, dass auf der preceding-Achse die Positionen auch quasi rückwärts laufen. Damit bin ich mit meinem Verständnis für die Sache glaub ich echt ein gutes Stück weitergekommen. Danke nochmal.
                  Jetzt würde ich nur noch gerne kapieren, wie das mit den Positionen bei sort funktioniert. Nach wie vor ist das ein Paradoxon für mich.

                  Viele Grüße

                  Michael

              2. Hallo,

                <xsl:variable name="position"><xsl:value-of select="position() mod 2"></xsl:value-of> </xsl:variable>

                variable select="position() mod 2" name="position"

                Hier verstehe ich aber auch  nicht genau, worauf Du hinaus willst.(?)

                Dass du es abkürzen kannst ;-)

                Wie kann das sein? - bezieht sich position() nach sort nicht doch auf das sortierte Knotenset???

                Nein, das ist schon OK so. Denn mit for-each selektierst du eine Knotenmenge, alle weitere Angaben in diesem Template (ja, auch for-ech ist ein Template, aber das soll uns jetzt nicht kümmern) gelten für diese Knotenmenge. So wird es immer noch nach position() im Dokument gearbeitet, aber mit der Einschrenkung, dass dabei nur diese Knotenmenge beachtet wird. Deshalb funktioniert es auch, wenn du Knoten vertauschst.

                Das will mir immer noch nicht ganz eingehen.
                Nochmal ein Beispiel:

                <bsp>B</bsp> (pos 1)
                <bsp>A</bsp> (pos 2)
                <bsp>C</bsp> (pos 3)
                ...
                <xsl:sort />
                ...
                Knoten sortiert aber aber position() wie im Dokument stelle ich mir dann so vor:
                <bsp>A</bsp> (pos 2)
                <bsp>B</bsp> (pos 1)
                <bsp>C</bsp> (pos 3)
                Was ist jetzt <xsl:value-of select="bsp[position() = 2] />?
                A, oder? -

                Das kommt darauf an ;-)
                Vielleicht nochmal die Erklärung:
                xsl:sort bestimmt die Reihenfolge in welcher die selektierten Knoten abgearbeitet werden. Wenn kein Sortierkriterium angegeben ist (im select="") dann werden die Konten in Dokumentordnung abgearbeitet (nicht vergessen: die Voreinstellung für sort ist "selext='.'" als String).

                Jetzt ein Beispiel:

                <?xml version="1.0"?>
                <data>
                 <bsp>B (pos 1)</bsp>
                 <bsp>A (pos 2)</bsp>
                 <bsp>C (pos 3)</bsp>
                 <subdata>
                  <bsp>D (pos 4)</bsp>
                  <bsp>F (pos 5)</bsp>
                  <bsp>E (pos 6)</bsp>
                 </subdata>
                </data>
                --------------------------
                <?xml version="1.0"?>
                <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
                <xsl:template match="/">
                 <html>
                  <head>
                   <title>Untitled</title>
                  </head>
                  <body>
                   <xsl:apply-templates />
                  </body>
                 </html>
                </xsl:template>

                <xsl:template match="data">
                 <h3>Alle Elemente</h3>
                 <div>
                  <h4>ohne "sort"</h4>
                  <xsl:for-each select="//bsp">
                   <span>
                   <xsl:if test="position() mod 2 = 0"><xsl:attribute name="style">background:silver;</xsl:attribute></xsl:if>
                   [Position: <xsl:value-of select="position()" />] Inhalt: <xsl:value-of select="." />
                   </span><br />
                  </xsl:for-each>
                 </div>
                 <div>
                  <h4>mit "sort"</h4>
                  <xsl:for-each select="//bsp">
                   <xsl:sort />
                   <span>
                   <xsl:if test="position() mod 2 = 0"><xsl:attribute name="style">background:silver;</xsl:attribute></xsl:if>
                   [Position: <xsl:value-of select="position()" />] Inhalt: <xsl:value-of select="." />
                   </span><br />
                  </xsl:for-each>
                 </div>
                 <xsl:apply-templates select="subdata" />
                </xsl:template>
                <xsl:template match="subdata">
                 <h3>Untermge</h3>
                 <div>
                  <h4>ohne "sort"</h4>
                  <xsl:for-each select="bsp">
                   <span>
                   <xsl:if test="position() mod 2 = 0"><xsl:attribute name="style">background:silver;</xsl:attribute></xsl:if>
                   [Position: <xsl:value-of select="position()" />] Inhalt: <xsl:value-of select="." />
                   </span><br />
                  </xsl:for-each>
                 </div>
                 <div>
                  <h4>mit "sort"</h4>
                  <xsl:for-each select="bsp">
                   <xsl:sort />
                   <span>
                   <xsl:if test="position() mod 2 = 0"><xsl:attribute name="style">background:silver;</xsl:attribute></xsl:if>
                   [Position: <xsl:value-of select="position()" />] Inhalt: <xsl:value-of select="." />
                   </span><br />
                  </xsl:for-each>
                 </div>
                </xsl:template>
                </xsl:stylesheet>
                ----------------------

                Wie du sehen kannst, wird die Angabe der Position sich verändern, weil die Knoten in der Reihenfolge des Sortierkriteriums abgearbeitet werden.
                Aber dies gilt eben nur für die Ausgabe, d.h. wenn du mit "position() = 2" immer auf "A" zugreifen möchtest, funktioniert das nicht, weil die Knoten in anderen Reihenfolge abgearbeitet werden und sich dementsprechnd ihre Position  für die Ausgabe ändert.
                Du kannst auch sehen, dass bei for-each immer eine bestimmte Knotenmenge selektiert wird und sich dann auch die Sortierung nur auf diese Knotenmenge bezieht.

                Warum oder wie funktioniert das dann, dass trotzdem im Ergebnisbaum, würde man jeweils position() mod2 ausgeben, das Ergebnis so ist?:
                1
                0
                1
                und eben nicht
                0
                1
                1

                Das ist eine ganz andere Sache: das modulo-Operator lifert das Ergebnis einer Division, genauer den ganzzahlingen Rest einer abgeschnittenen/abgerundeten (truncating) Divison. D.h.:
                1 mod 2 = 1 (1 druch 2 ist 0, bleibt 1 ...)
                1:2 = 0,5
                 1|0:2 = 5

                2 mod 2 = 0 (2 durch 2 ist 1, bleibt 0)

                3 mod 2 = 1 (3 durch 2 ist 1, bleibt 1 ...)
                3:2 = 1,5
                 1|0:2 = 5

                4 mod 2 = 0 (4 durch 2 ist 2, bleibt 0)

                5 mod 2 = 1 (5 durch 2 ist 2, bleibt 1 ...)
                5:2 = 1,5
                 1|0:2 = 5

                5 mod -2 = 1
                -5 mod 2 = -1
                -5 mod -2 = -1
                17 mod 5 = 2

                usw.

                Deshalb kann man sehr gut mit  "position() mod 2" gearde ungerade Positionen von Knoten unterscheiden. ;-)

                Ich hoffe das hilft.

                Grüße
                Thomas

                1. Hallo Thomas,

                  danke für die Mühe die Du Dir mit mir gibst. Dein Beispiel hat geholfen und ich glaube ich habe es jetzt wirklich kapiert.

                  Wie du sehen kannst, wird die Angabe der Position sich verändern, weil die Knoten in der Reihenfolge des Sortierkriteriums abgearbeitet werden.
                  Aber dies gilt eben nur für die Ausgabe, d.h. wenn du mit "position() = 2" immer auf "A" zugreifen möchtest, funktioniert das nicht, weil die Knoten in anderen Reihenfolge abgearbeitet werden und sich dementsprechnd ihre Position  für die Ausgabe ändert.
                  Du kannst auch sehen, dass bei for-each immer eine bestimmte Knotenmenge selektiert wird und sich dann auch die Sortierung nur auf diese Knotenmenge bezieht.

                  Soweit ist jetzt alles klar und einleuchtend.

                  Allerdings habe ich mein Problem (doppelte Einträge aussortieren und im sortierten Ergebnis jedes zweite Element farblich abheben) trotzdem noch nicht lösen können.

                  Die Schwierigkeit ist, dass die Knotenmenge die ich mit for-each zunächst selektiere mehr Elemente enthält wie schließlich ausgegeben werden. Ich schaffe es nicht, mit for-each "auf einmal" so zu selektieren, dass nur auszugebende Knoten übrig bleiben.
                  Das was Du als Lösungsvorschlag mal geschrieben hast...
                  ("
                  <xsl:variable name="this" value="."/>
                  <xsl:for-each select="not(preceding-sibling::eintrag[. = $this])">
                   <xsl:sort />
                   <xsl:if test="(position()mod2) != 0" />
                   <xsl:value-of select="."/>
                  ")
                  ... kann doch nicht funktionieren.
                  (Bei der Variable meintest Du statt value schon select? - frage nur sicherheitshalber, nicht dass Du einen XSLT Prozessor verwendest, der sowas unterstützt und es deshalb bei mir scheitert, wenn ich es durch select ersetze.)

                  Jedenfalls kann ich die Variable doch nur innerhalb einer for-each Schleife verwenden, da sonst alle Knoten (gleichzeitig) in ihr gespeichert würden und ich in der folgenden Bedingung für die for-each Selektion einen der Knoten gegen alle vergleiche.
                  Ich habe jetzt alle Variationen, die mir zu Deinem Vorschlag einfielen, durchprobiert und finde einfach keine Lösung mit der ich die Knotenmenge so selektieren kann, dass ich nicht nachträglich doppelte Einträge entfernen müsste.

                  Auch wenn es etwas mehr Quelltext wird paste ich Dir mal einen Auszug aus meinem XML / XSLT um einmal mit einem konkreten Beispiel zu arbeiten:

                  XML:
                  <litlist>
                   <ul>
                    <li>Scriven, Michael (1967): The methodology of evaluation. In: R. Tyler, R. Gagne und M. Scriven: Perspectives of curriculum evaluation. Rand McNally.</li>
                    <li>Thimbleby, Harold (2004). User interface design with matrix algebra. In: ACM Transactions on Computer-Human-Interaction (11-2). S. 181-236.</li>
                    <li>Hegner, M. (2003). Methoden zur Evaluation von Sowftware. IZ-Arbeitsbericht Nr. 29, Bonn: InformationsZentrum Sozialwissenschaften (ASI).</li>
                    <li>Rosson, M. &amp; Carroll, J. (2002). Usability Engineering - Scenario-based development of human-computer interaction. San Francisco(u.a.): Morgan Kaufmann.</li>
                    <li>Rosenfeld, L. &amp; Morville, P. (2002). Information Architecture for the World Wide Web: Designing Large Scale Web Sites. Cambridge: O’Reilly &amp; Associates.</li>
                    <li>Rosenfeld, L. &amp; Morville, P. (2002). Information Architecture for the World Wide Web: Designing Large Scale Web Sites. Cambridge: O’Reilly &amp; Associates.</li>
                   </ul>
                  </litlist>
                  .......................
                  XSLT (in der Version, in der zwar sortiert und aussortiert wird, aber wo eben das farblich hinterlegen jedes zweiten Listenelements nicht klappt)
                  .......................
                  <xsl:template match="litlist/ul">
                   <ul>
                    <xsl:for-each select="./li">
                     <xsl:sort />
                     <xsl:variable name="aktuellerKnoten" select="." />
                     <xsl:if test="not(preceding-sibling::li[. = $aktuellerKnoten])">
                      <li>
                       <xsl:attribute name="class">
                        <xsl:if test="position() mod 2 = 0">xsl:textlitli</xsl:text></xsl:if>
                        <xsl:if test="position() mod 2 = 1">xsl:textlitli2</xsl:text></xsl:if>
                       </xsl:attribute>
                       <xsl:call-template name="ankersetzen"></xsl:call-template>
                       <xsl:apply-templates />
                      </li>
                     </xsl:if>
                    </xsl:for-each>
                   </ul>
                  </xsl:template>

                  Deshalb kann man sehr gut mit  "position() mod 2" gearde ungerade Positionen von Knoten unterscheiden. ;-)

                  Sorry, aber bei meinem Beispiel zu den Ergebnissen der Modulo-Division habe ich eigentlich nur die Reihenfolge der Ergebnisse gemeint und nicht die Modulo-Division an sich. Was eine Modulo-Division ist war mir schon klar. Aber trotzdem danke für die Mühe.

                  Ich hoffe das hilft.

                  Auch wenn ich mein Problem nicht lösen konnte, geholfen hat es trotzdem ungemein.

                  Tausend Dank und viele Grüße

                  Michael

                  1. Hallo,

                    danke für die Mühe die Du Dir mit mir gibst. Dein Beispiel hat geholfen und ich glaube ich habe es jetzt wirklich kapiert.

                    Gerne ;-)

                    Allerdings habe ich mein Problem (doppelte Einträge aussortieren und im sortierten Ergebnis jedes zweite Element farblich abheben) trotzdem noch nicht lösen können.

                    <xsl:variable name="this" value="."/>
                    (Bei der Variable meintest Du statt value schon select?

                    Ja, war nur ein Fehler den ich hier gemacht habe.

                    .......................
                    XSLT (in der Version, in der zwar sortiert und aussortiert wird, aber wo eben das farblich hinterlegen jedes zweiten Listenelements nicht klappt)
                    .......................

                    Wenn du die Position ausgibst bekommst du folgendes:
                    <li class="litli2">1) Hegner</li>
                    <li class="litli">2) Rosenfeld</li>
                    <li class="litli">4) Rosson</li>
                    <li class="litli2">5) Scriven</li>
                    <li class="litli">6) Thimbleby</li>

                    Dann wird auch klar warum das nicht klapp: erst findet die Sortierung statt, dann kommt die Einschränkung, welche dann keine Auswirkungen auf die Sortierung hat.

                    Ich habe das jetzt ganz anders gelöst:
                    ----------------
                    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
                    <xsl:key name="likey" match="li" use="." />
                    ...
                    <xsl:template match="litlist/ul">
                     <ul>
                      <xsl:for-each select="li[generate-id(.) = generate-id(key('likey', .)[1])]">
                       <xsl:sort />
                        <li>
                         <xsl:attribute name="class">
                          <xsl:if test="position() mod 2 = 0">xsl:textlitli</xsl:text></xsl:if>
                          <xsl:if test="position() mod 2 = 1">xsl:textlitli2</xsl:text></xsl:if>
                         </xsl:attribute>
                         <!-- <xsl:call-template name="ankersetzen"></xsl:call-template> -->
                         <!-- <xsl:apply-templates /> -->
                      <xsl:value-of select="substring-before(., ',')" />
                        </li>
                     </xsl:for-each>
                     </ul>
                    </xsl:template>
                    ...
                    -------------------

                    Ergebnis:
                    <li class="litli2">Hegner</li>
                    <li class="litli">Rosenfeld</li>
                    <li class="litli2">Rosson</li>
                    <li class="litli">Scriven</li>
                    <li class="litli2">Thimbleby</li>
                    </ul>

                    Im Prinzip gruppiere ich jetzt die Elemente und vergliche dann immer das erste aus der Gruppe mit dem aktuellen, so werden Duplikate nicht mehr in die Sortierung hineingenommen.
                    (Falls dich interessiert: wenn du nach "gernerate-id(key" und nach meinem Namen im Archiv suchst, findest du die lange Erklärung)

                    Grüße
                    Thomas

                    1. Hallo,

                      und erst nochmal vielen Dank.

                      Im Prinzip gruppiere ich jetzt die Elemente und vergliche dann immer das erste aus der Gruppe mit dem aktuellen, so werden Duplikate nicht mehr in die Sortierung hineingenommen.
                      (Falls dich interessiert: wenn du nach "gernerate-id(key" und nach meinem Namen im Archiv suchst, findest du die lange Erklärung)

                      Und ob mich das interessiert hat. Danke für den Tipp, hab gleich mal Deinen Artikel und die Posts zu dem Thema im Forenarchiv durchgelesen sowie ein weitere Tutorial zum Gruppieren mit der Muench'schen Methode.

                      Was Du geschrieben hast funktioniert einwandfrei (!) aber (und jetzt bitte nicht lachen oder weinen ;-) ) nicht bei mir.
                      Wenn ich das was ich gelesen habe richtig verstanden habe, liegt das wohl daran, dass ich bisher keine id's für li-Elemente habe (obgleich diese ja durch generate-id(.) (zumindest für den Ausgabeknoten) erzeugt werden)?

                      Dazu noch eine Frage nebenbei: Könnte es in dem Kontext Konflikte geben, wenn ich in meinem Schema an mehreren Stellen Attribute namens "id" definiert habe, die allerdings nicht vom Typ ID sind?

                      Was mich noch etwas verwirrt ist folgendes:
                      Zum testen habe ich das Template in einem neuen und ansonsten leeren XSL probiert und da funktionierte es natürlich wie es soll.

                      Im XSL, in dem ich es verwenden möchte hingegen scheitert es, bei ansonsten identischen Bedingungen (gleiches Schema und XML) an der Auswertung von "li[generate-id(.) = generate-id(key('likey', .)[1])]".

                      Soviel mal zu meinen Misserfolgen und Missverständnissen ;-)

                      Erfolgreich hingegen ist, dass ich nun doch einen Ausdruck gefunden habe mit dem ich mit for-each die Knotenmenge so selektieren kann, dass ich nachträglich nicht mehr filtern muss:
                      "ul/li[not(. = preceding-sibling::li)]"
                      Damit funktioniert es jetzt ganz gut.
                      Auch dafür möchte ich Dir danken, weil ich darauf nämlich wahrscheinlich nicht gekommen wäre, wenn ich nicht in einem Tutorial zum Gruppieren mit der Muench'schen Methode einen ähnlichen Ausdruck gelesen hätte ;-)

                      Viele Grüße

                      Michael

                      1. Hallo Nachtmensch ;-)

                        Was Du geschrieben hast funktioniert einwandfrei (!) aber (und jetzt bitte nicht lachen oder weinen ;-) ) nicht bei mir.
                        Wenn ich das was ich gelesen habe richtig verstanden habe, liegt das wohl daran, dass ich bisher keine id's für li-Elemente habe (obgleich diese ja durch generate-id(.) (zumindest für den Ausgabeknoten) erzeugt werden)?

                        Daran wird es nicht liegen.
                        gernerate-id() erzeugt einen ID für eine Element, erstmal intern zu "runtime". Natürlich kann man dann diese ID auch für die Ausgabe verwenden.

                        Dazu noch eine Frage nebenbei: Könnte es in dem Kontext Konflikte geben, wenn ich in meinem Schema an mehreren Stellen Attribute namens "id" definiert habe, die allerdings nicht vom Typ ID sind?

                        Es wird immer wieder empfohlen id wirklich als Typ ID zu benutzen.
                        Aber ein Konflikt sollte es deshalb nicht geben.

                        Was mich noch etwas verwirrt ist folgendes:
                        Zum testen habe ich das Template in einem neuen und ansonsten leeren XSL probiert und da funktionierte es natürlich wie es soll.

                        Im XSL, in dem ich es verwenden möchte hingegen scheitert es, bei ansonsten identischen Bedingungen (gleiches Schema und XML) an der Auswertung von "li[generate-id(.) = generate-id(key('likey', .)[1])]".

                        Was genau passiert da?
                        Gibt es eine Ausgabe - wenn auch eine falsche - oder wird nichts dargestellt?
                        Gegebenfalls muss du mehr von deinem "einsatzt" XSL zeigen.

                        Erfolgreich hingegen ist, dass ich nun doch einen Ausdruck gefunden habe mit dem ich mit for-each die Knotenmenge so selektieren kann, dass ich nachträglich nicht mehr filtern muss:
                        "ul/li[not(. = preceding-sibling::li)]"

                        Das deutet aber darauf hin, dass du nicht im Template mit match="litlist/ul" bist, oder dass dein XML etwas anders aussieht. ;-)

                        Damit funktioniert es jetzt ganz gut.

                        Nun, dann passt es eigentlich ;-)

                        Grüße
                        Thomas

                        1. Hallo auch,

                          Nachtmensch ;-)

                          heute bist Du aber auch mal zu recht später Stunde unterwegs ;-)

                          Daran wird es nicht liegen.
                          gernerate-id() erzeugt einen ID für eine Element, erstmal intern zu "runtime". Natürlich kann man dann diese ID auch für die Ausgabe verwenden.

                          Hmmm, so was hab ich mir schon gedacht.

                          Dazu noch eine Frage nebenbei: Könnte es in dem Kontext Konflikte geben, wenn ich in meinem Schema an mehreren Stellen Attribute namens "id" definiert habe, die allerdings nicht vom Typ ID sind?

                          Es wird immer wieder empfohlen id wirklich als Typ ID zu benutzen.
                          Aber ein Konflikt sollte es deshalb nicht geben.

                          Dann ist es ja gut. So sehr zufrieden bin ich damit eigentlich nicht, aber ich lasse an manchen Stellen "interaktive Elemente" generieren. Im zugehörigen JS werden diese mit getElementById angesprochen. Deshalb hab ich der Übersichtlichkeit halber die Attribute im XML auch id genannt.

                          Was mich noch etwas verwirrt ist folgendes:
                          Zum testen habe ich das Template in einem neuen und ansonsten leeren XSL probiert und da funktionierte es natürlich wie es soll.

                          Im XSL, in dem ich es verwenden möchte hingegen scheitert es, bei ansonsten identischen Bedingungen (gleiches Schema und XML) an der Auswertung von "li[generate-id(.) = generate-id(key('likey', .)[1])]".

                          Was genau passiert da?

                          Nix ;-)
                          Wenn ich es im "großen XSL" transformieren möchte, bricht er an der genannten Stelle mit der Fehlermeldung "This file is not valid: Error in XPath 2.0 Expression" ab - ohne etwas auszugeben. (Ich arbeite mit XMLSpy Home und transformiere mit der enthaltenen MS-XSL-Engine)

                          Gibt es eine Ausgabe - wenn auch eine falsche - oder wird nichts dargestellt?

                          Mit dem "einsatz" XSL gibt es keine. Mit den "Test" XSL sieht die Ausgabe so aus, wie Du es in Deinem Bsp. geschrieben hast.
                          Richtig seltsam ist das was der IE daraus macht, wenn ich das XML direkt damit aufrufe. Er gibt keine Texte mehr aus und zeigt nur leere Listenelemente (auch von Sachen, die mit ganz anderen Templates abgearbeitet werden).(Lediglich Hyperlinks werden noch angezeigt.)

                          Gegebenfalls muss du mehr von deinem "einsatzt" XSL zeigen.

                          Kann ich gerne machen, nur was davon?
                          (Das ganze kann / will ich hier nicht posten, denn a) sind knapp 1000 Zeilen und b) ist es noch in Abeit und teilweise noch etwas unschön.)

                          Erfolgreich hingegen ist, dass ich nun doch einen Ausdruck gefunden habe mit dem ich mit for-each die Knotenmenge so selektieren kann, dass ich nachträglich nicht mehr filtern muss:
                          "ul/li[not(. = preceding-sibling::li)]"

                          Das deutet aber darauf hin, dass du nicht im Template mit match="litlist/ul" bist, oder dass dein XML etwas anders aussieht. ;-)

                          Gut aufgepasst ;-) - match="litlist"

                          Damit funktioniert es jetzt ganz gut.

                          Nun, dann passt es eigentlich ;-)

                          Alles Weitere diene dann "nur" mehr der intellektuellen Bereicherung, denn einem pragmatischen Zweck ;-)

                          Ich finde den Dialog mit Dir sehr bereichernd und wäre natürlich noch brennend daran interessiert, warum das mit generate-id nicht klappt.

                          Viele nachtschlafende Grüße

                          Michael

                          1. Hallo,

                            Nachtmensch ;-)
                            heute bist Du aber auch mal zu recht später Stunde unterwegs ;-)

                            'Spellforce' sollte man auf die Liste der suchterzeugenden Mittel setzen ;-)

                            Was genau passiert da?
                            Nix ;-)
                            Wenn ich es im "großen XSL" transformieren möchte, bricht er an der genannten Stelle mit der Fehlermeldung "This file is not valid: Error in XPath 2.0 Expression" ab

                            Hm....

                            Gegebenfalls muss du mehr von deinem "einsatzt" XSL zeigen.

                            Kann ich gerne machen, nur was davon?
                            (Das ganze kann / will ich hier nicht posten,

                            Für so einem Fall steht meine Mailadresse in den Postings ;-)

                            Alles Weitere diene dann "nur" mehr der intellektuellen Bereicherung, denn einem pragmatischen Zweck ;-)
                            Ich finde den Dialog mit Dir sehr bereichernd

                            Danke :-)

                            »»und wäre natürlich noch brennend daran interessiert, warum das mit generate-id nicht klappt.

                            mailto:thomas.js@selfhtml.org

                            Grüße
                            Thomas

                            1. Hallo,

                              'Spellforce' sollte man auf die Liste der suchterzeugenden Mittel setzen ;-)

                              Mit sowas bin ich auch immer echt gefährdet. Deshalb sträube ich mich immer degegen, mir Spiele zu installieren. Aber wenn mich dann ein Freund doch mal dazu überredet, dann habe ich im wahrsten Sinne des Wortes verspielt.

                              Für so einem Fall steht meine Mailadresse in den Postings ;-)

                              ...you've got mail...
                              ...da hast Du Dir was angetan ;-)

                              Viele Grüße

                              Michael

                              1. Hallo,

                                Für so einem Fall steht meine Mailadresse in den Postings ;-)

                                ...you've got mail...
                                ...da hast Du Dir was angetan ;-)

                                Dein Problem, jetzt unabhängig davon das du XSLT 2 verwendest (warum eigentlich?) war das erwähnte:
                                ---------------
                                "ul/li[not(. = preceding-sibling::li)]"

                                Das deutet aber darauf hin, dass du nicht im Template mit match="litlist/ul" bist, oder dass dein XML etwas anders aussieht. ;-)
                                ----------------

                                Da reicht es eine kleine Anpassung im Template:
                                (key bleibt dasselbe)

                                <xsl:template match="litlist">
                                 <ul>
                                  <xsl:for-each select="ul/li[generate-id(.) = generate-id(key('likey', .)[1])]">
                                   <xsl:sort />
                                    <li>
                                     <xsl:attribute name="class">
                                      <xsl:if test="position() mod 2 = 0">xsl:textlitli</xsl:text></xsl:if>
                                      <xsl:if test="position() mod 2 = 1">xsl:textlitli2</xsl:text></xsl:if>
                                     </xsl:attribute>
                                     <xsl:call-template name="ankersetzen"></xsl:call-template>
                                     <xsl:value-of select="." />
                                    </li>
                                   </xsl:for-each>
                                  </ul>
                                </xsl:template>

                                Der Rest dann als Antwort auf deine Mail. ;-)

                                Grüße
                                Thomas

                                1. Hallo,

                                  Dein Problem, jetzt unabhängig davon das du XSLT 2 verwendest (warum eigentlich?) war das erwähnte:

                                  "ul/li[not(. = preceding-sibling::li)]"

                                  Das deutet aber darauf hin, dass du nicht im Template mit match="litlist/ul" bist, oder dass dein XML etwas anders aussieht. ;-)

                                  ??? - habe ich Dir doch schon geschrieben, dass ich im oberen Fall den match nur auf "litlist" habe.

                                  Warum nicht XSLT 2 ? - weils noch nicht Recommendation ist?
                                  Ich habe mir da ehrlichgesagt noch keinen großen Kopf gemacht deswegen.

                                  Da reicht es eine kleine Anpassung im Template:
                                  (key bleibt dasselbe)

                                  <xsl:template match="litlist">
                                  <ul>
                                    <xsl:for-each select="ul/li[generate-id(.) = generate-id(key('likey', .)[1])]">
                                     <xsl:sort />
                                      <li>
                                       <xsl:attribute name="class">
                                        <xsl:if test="position() mod 2 = 0">xsl:textlitli</xsl:text></xsl:if>
                                        <xsl:if test="position() mod 2 = 1">xsl:textlitli2</xsl:text></xsl:if>
                                       </xsl:attribute>
                                       <xsl:call-template name="ankersetzen"></xsl:call-template>
                                       <xsl:value-of select="." />
                                      </li>
                                     </xsl:for-each>
                                    </ul>
                                  </xsl:template>

                                  Das ist schon klar ;-) Das dürfte aber keinen Unterschied machen ob man hier match auf "litlist" macht und dafür für for-each "ul/li" wählt oder "litlist/ul" und for-each "li".

                                  Der Rest dann als Antwort auf deine Mail. ;-)

                                  Bin schon gespannt ;-)

                                  Viele Grüße

                                  Michael

                                  1. Hallo,

                                    Warum nicht XSLT 2 ? - weils noch nicht Recommendation ist?
                                    Ich habe mir da ehrlichgesagt noch keinen großen Kopf gemacht deswegen.

                                    Weil 1)außer Saxon es keinen Prozessor gibt, den man a) frei verwenden kann und b) XSLT2 wirklich richtig kann. Saxon meckert z.B. auch nicht bei der fraglichen for-each, wo XMLSpy die Verarbeitung abbricht (habe ich auch ausprobiert).
                                    2) weil du nichs vom XSLT2 verwendest ;-)
                                    3) weil du vermutlich nicht daran gedacht hast, dass sich in XSLT2 zwei viele Sachen gegenüber XSLT1 geändert haben.

                                    bis dann ;-)
                                    Thomas

      2. Hallo,

        Du müßtest eher darauf testen, ob es ein vorhergehendes Geschwister gibt mit demselben Inhalt, also sowas
        <xsl:variable name="this" value="."/>
        <xsl:if test="not(preceding-sibling::eintrag[. = $this])">
          <xsl:value-of select="."/>
        </xsl:if>

        (ungetestet)
        jetzt nicht mehr ;-)
        hast recht, so funktionierts prinzipiell. Dummerweise hab ich in meinem XSLT für solche Listen noch was eingebaut, dass es mir jedes zweite Ausgabeelement farblich abhebt (mit <xsl:if test="(position()mod2) != 0". Wenn ich jetzt weniger Knoten ausgebe, stimmt die farbige Hervorhebung nicht mehr. Mal kucken, ob ich das irgendwie in den Griff kriege. Evtl. muss ichs doch noch irgendwie anders lösen.

        Schematisch:
        <xsl:variable name="this" value="."/>
        <xsl:for-each select="not(preceding-sibling::eintrag[. = $this])">
         <xsl:sort />
         <xsl:if test="(position()mod2) != 0" />
         <xsl:value-of select="."/>

        Grüße
        Thomas