Home
Über mich
Veröffentlichungen

XML XML-Schema XPath XSL-T XSL-FO XQuery XProc SVG

XSL-T / Die XSLT - Struktur / xsl:sort, xsl:perform-sort, fn:sort / Sortieren und Filtern

Sortieren und Filtern

Sortieren und Filtern

➪ Häufig ist die Sortierung einer Sequenz auch mit einer Filterung verbunden. In diesem Abschnitt lesen Sie, wie Sie eine Elementliste sortieren und eine ausgewählte Menge selektieren können.

Auf dieser Seite:

Nehmen Sie das folgende XML-Dokument:


<root>
  <Ort name="Neustadt">
    <Einwohner name="Brotlos"     AnzahlBriefmarken="333"/>
    <Einwohner name="Problemlos"  AnzahlBriefmarken="2345"/>
    <Einwohner name="Haltlos"     AnzahlBriefmarken="123"/>
    <Einwohner name="Achtlos"     AnzahlBriefmarken="332"/>
    <Einwohner name="Wolkenlos"   AnzahlBriefmarken="555"/>
    <Einwohner name="Traumschlos" AnzahlBriefmarken="732"/>
  </Ort>
  <Ort name="Kapstadt">
    <Einwohner name="Holzflos"    AnzahlBriefmarken="345"/>
    <Einwohner name="Sprachlos"   AnzahlBriefmarken="543"/>
    <Einwohner name="Nixlos"      AnzahlBriefmarken="12"/>
    <Einwohner name="Lustlos"     AnzahlBriefmarken="33"/>
    <Einwohner name="Schlaflos"   AnzahlBriefmarken="12"/>
    <Einwohner name="Sorglos"     AnzahlBriefmarken="987"/>
  </Ort>
</root>

Die Aufgabe besteht darin, für jeden Ort jene drei Einwohner zu ermitteln, die die jeweils größte Anzahl an Briefmarken haben, und diese in sortierter Reihenfolge absteigend darzustellen. Gesucht ist also dieses Resultat:


<Ergebnis>
  <Briefmarkenstadt name="Neustadt">
   <Einwohner name="Problemlos"  AnzahlBriefmarken="2345"/>
   <Einwohner name="Traumschlos" AnzahlBriefmarken="732"/>
   <Einwohner name="Wolkenlos"   AnzahlBriefmarken="555"/>
  </Briefmarkenstadt>
  <Briefmarkenstadt name="Kapstadt">
   <Einwohner name="Sorglos"     AnzahlBriefmarken="987"/>
   <Einwohner name="Sprachlos"   AnzahlBriefmarken="543"/>
   <Einwohner name="Holzflos"    AnzahlBriefmarken="345"/>
  </Briefmarkenstadt>
</Ergebnis>

Lösung in XSLT 1.0 mit xsl:for-each

Die Lösung in XSLT 1.0 arbeitet für jeden Ort mit einer simplen Sortierung der Einwohner nach der Anzahl ihrer Briefmarken. Innerhalb dieser Sortierung werden die ersten drei Elemente ausgewählt.


<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="/">
    <Ergebnis>
      <xsl:for-each select="/root/Ort">
        <Briefmarkenstadt name="{@name}">    
          <xsl:for-each select="Einwohner">
            <xsl:sort 
                 select="@AnzahlBriefmarken" 
                 order="descending" 
                 data-type="number"/>
            <xsl:if test="position() &lt; 4">
              <xsl:copy-of select="."/>
            </xsl:if>                         
          </xsl:for-each>          
        </Briefmarkenstadt>
      </xsl:for-each>
    </Ergebnis>
  </xsl:template>
</xsl:stylesheet>

Lösung in XSLT 1.0 mit xsl:apply-templates

Der Lösungsansatz mit xsl:apply-templates arbeitet ähnlich wie die XSLT 1.0-Lösung mit xsl:for-each: Innerhalb jedes Ort-Elements werden die Einwohner nach @AnzahlBriefmarken sortiert. Innerhalb dieser Sortierung werden in <xsl:template match="Einwohner"> die ersten drei Elemente ausgewählt.


<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/">
        <Ergebnis>
            <xsl:apply-templates select="/root/Ort"/>
        </Ergebnis>
    </xsl:template>
    <xsl:template match="Ort">
        <Briefmarkenstadt name="{@name}">
            <xsl:apply-templates select="Einwohner">
                <xsl:sort 
                    select="@AnzahlBriefmarken" 
                    order="descending" 
                    data-type="number"/>
            </xsl:apply-templates>            
        </Briefmarkenstadt>
    </xsl:template>
    <xsl:template match="Einwohner">
        <xsl:if test="position() &lt; 4">
            <xsl:copy-of select="."/>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

Lösung in XSLT 1.1

Die Lösung in XSLT 1.1 arbeitet für jeden Ort mit einer temporären Variable v, in der die Einwohner bereits nach der Anzahl der Briefmarken sortiert sind. Anschließend werden die ersten drei Elemente dieser Variablen ausgewählt.


<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs"
  version="1.1">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="/">
    <Ergebnis>
      <xsl:for-each select="/root/Ort">
        <Briefmarkenstadt name="{@name}">          
          <xsl:variable name="v">
            <xsl:for-each select="Einwohner">
              <xsl:sort 
                   select="@AnzahlBriefmarken" 
                   order="descending" 
                   data-type="number"/>
              <xsl:copy-of select="."/>                          
            </xsl:for-each>  
          </xsl:variable>
          <xsl:for-each 
               select="$v/Einwohner[position() &lt; 4]">
            <xsl:copy-of select="."/>
          </xsl:for-each>
        </Briefmarkenstadt>
      </xsl:for-each>
    </Ergebnis>
  </xsl:template>
</xsl:stylesheet>

Lösung in XSLT 2.0

In XSLT 2.0 können Sie die Variable sortiert_nach_Briefmarken mithilfe von xsl:perform-sort erstellen und anschliessend über die subsequence-Funktion auswerten.


<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="/">
    <Ergebnis>
      <xsl:for-each select="/root/Ort">
        <Briefmarkenstadt name="{@name}"> 
          <xsl:variable 
               name="sortiert_nach_Briefmarken" 
               as="element(Einwohner)*">
            <xsl:perform-sort select="Einwohner">
              <xsl:sort 
                   select="@AnzahlBriefmarken" 
                   data-type="number" 
                   order="descending"/>
            </xsl:perform-sort>
          </xsl:variable>          
          <xsl:for-each 
               select="subsequence($sortiert_nach_Briefmarken, 1,3)">
            <xsl:copy-of select="."/>
          </xsl:for-each>     
        </Briefmarkenstadt>
      </xsl:for-each>
    </Ergebnis>
  </xsl:template>  
</xsl:stylesheet>

Lösung in XSLT 3.0

Die alternative Lösung in XSLT 3.0 arbeitet mit der XPath-Funktion for-each, die ihrerseits die reverse-Funktion aufruft: Das ist notwendig, weil die darin aufgerufene sort-Funktion per default aufsteigend sortiert. Von dem so erzeugten Ergebnis werden auch hier die ersten drei Elemente ausgewählt.


<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs"
  version="3.0">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="/">
    <Ergebnis>
      <xsl:for-each select="/root/Ort">
        <Briefmarkenstadt name="{@name}"> 
          <xsl:for-each select="
            for-each
            (
              reverse
              (
                sort
                (
                  Einwohner, (), 
                  function ($p) 
                  {$p/xs:integer(@AnzahlBriefmarken)}
                )
              ), function ($v){$v}
            )[position() &lt; 4]">
            <xsl:copy-of select="."/>
          </xsl:for-each>          
        </Briefmarkenstadt>
      </xsl:for-each>
    </Ergebnis>
  </xsl:template>
</xsl:stylesheet>

Natürlich können Sie auch hier anstelle [position() &lt; 4] die subsequence-Funktion verwenden:


<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  exclude-result-prefixes="xs" version="3.0">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="/">
    <Ergebnis>
      <xsl:for-each select="/root/Ort">
        <Briefmarkenstadt name="{@name}">
          <xsl:for-each
            select="subsequence(
              for-each
              (
                reverse
                (
                  sort
                  (
                    Einwohner, (),
                    function ($p)
                    {$p/xs:integer(@AnzahlBriefmarken)}
                  )
                ), function ($v) {$v}
              ), 1,3)">
            <xsl:copy-of select="."/>
          </xsl:for-each>
        </Briefmarkenstadt>
      </xsl:for-each>
    </Ergebnis>
  </xsl:template>
</xsl:stylesheet>

Lösung in XQuery

In XQuery liest sich das so:


<Ergebnis>
{
  for $o in /root/Ort return 
  <Briefmarkenstadt name="{$o/@name}">
  {
    for $e at $pos in for-each
    (
      reverse
      (
        sort
        (
          $o/Einwohner, (), 
          function ($p) 
          {$p/xs:integer(@AnzahlBriefmarken)}
        )
      ), function ($v){$v}
     )
     return if ($pos < 4) then ($e) else nothing
  }
  </Briefmarkenstadt>
}
</Ergebnis>

wg / 22. April 2021



Fragen? Anmerkungen? Tipps?

Bitte nehmen Sie Kontakt zu mir auf.






Vielen Dank für Ihr Interesse an meiner Arbeit.



V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn

☎ 0151. 750 360 61 * eMail: info10@wilfried-grupe.de

www.wilfried-grupe.de/XSL_sort_mit_selektion.html