Oliver Becker, Mitarbeiter am Lehrstuhl Systemarchitektur im Institut für Informatik der HUB .

XSLT-Tutorial

Bei dem folgenden Text handelt es sich um die deutsche Übersetzung des Tutorials "Grouping Using the Muenchian Method" von Jeni Tennison.

Gruppieren mit der Muench'schen Methode

Das Gruppieren von Elementen ist ein häufiges Problem in XSLT-Stylesheets: wie fasst man die Elemente einer Liste in Gruppen zusammen? Dieses Problem stellt sich z.B. dann, wenn das Ergebnis einer Datenbankabfrage im XML-Format geliefert wird. Das Datenbanksystem gibt üblicherweise die Daten in einer Form zurück, die der Struktur in der Datenbank entspricht. Bei einem Adressbuch könnte das beispielsweise so aussehen:

   <records>
      
<contact id="0001">
         
<title>Mr</title>
         
<forename>John</forename>
         
<surname>Smith</surname>
      
</contact>
      
<contact id="0002">
         
<title>Dr</title>
         
<forename>Amy</forename>
         
<surname>Jones</surname>
      
</contact>
      ...
   </records>

Die Aufgabe soll in diesem Fall darin bestehen, die Einträge anhand des Nachnamens (surname) zu gruppieren, d.h diese flache Struktur in eine Menge von durch den Nachnamen definierten Listen umzuwandeln:

   Jones,<br />
      Amy (Dr)
<br />
      Brian (Mr)
<br />
   Smith,
<br />
      Fiona (Ms)
<br />
      John (Mr)
<br />

Die Lösung besteht aus zwei Schritten:

  1. Ermitteln der auftretenden Nachnamen
  2. Bestimmen aller Einträge (contact) auf den gleichen Nachnamen

Die auftretenden Nachnamen werden dadurch ermittelt, dass jeweils ein Eintrag für jeden Nachnamen in der XML-Quelle bestimmt wird. Dies kann z.B. der jeweils erste Eintrag mit dem entsprechenden Namen sein. Diese Einträge findet man, indem man alle Einträge bestimmt, die einen Nachnamen besitzen, der nicht bereits bei einem vorhergehenden Eintrag des Adressbuches zu finden ist:

   contact[not(surname = preceding-sibling::contact/surname)]

Nachdem diese Einträge bestimmt wurden, lassen sich leicht alle Einträge auf den gleichen Nachnamen zusammenfassen:

   <xsl:apply-templates
        select="/records/contact[surname = current()/surname]" />

Der Haken an dieser Methode ist, dass hier zwei XPath-Ausdrücke beteiligt sind, deren Berechnung bei großen XML-Datenmengen (wie sie bei großen Datenbanken auftreten) sehr aufwändig ist. Das Durchsuchen aller Vorgänger über die preceding-sibling-Achse dauert umso länger, je näher man dem Ende der Liste kommt. Analog erfordert die Bestimmung aller Einträge mit einem bestimmten Nachnamen jeweils den Zugriff auf jeden einzelnen Eintrag. Dadurch wird diese Vorgehensweise sehr ineffizient.

Die Muench'sche Methode (entwickelt durch Steve Muench) löst diese Aufgabe wesentlich effizienter durch die Benutzung von Keys. Ein Key weist einem XML-Knoten einen Schlüsselwert zu und ermöglichen anschließend den Zugriff auf einen solchen Knoten über den dazugehörigen Schlüssel. Bei vielen Knoten mit dem gleichen Schlüsselwert werden alle diese Knoten durch Benutzung dieses Schlüsselwertes ermittelt. Das bedeutet letztendlich, dass sich Keys für das Gruppieren einer bestimmten Anzahl von Knoten bezüglich einer gewissen Eigenschaft nutzen lassen.

Betrachten wir erneut unser Adressbuch. Es sollen alle Einträge mit gleichen Nachnamen zusammengefasst werden. Also definieren wir einen Schlüssel, der jedem Eintrag als Schlüsselwert den dazugehörigen Nachnamen zuweist. Die zu gruppierenden XML-Knoten werden durch ein XSLT-Pattern im match-Attribut des key-Elementes definiert. Der zu verwendende Schlüsselwert wird im use-Attribut angegeben:

   <xsl:key name="contacts-by-surname" match="contact" use="surname" />

Nachdem ein solcher Key definiert wurde, können wir für einen bekannten Nachnamen unproblematisch alle die Einträge bestimmen, die diesen Nachnamen enthalten. Zum Beispiel liefert:

   key('contacts-by-surname', 'Smith')

alle Einträge mit dem Nachnamen "Smith". Damit kann also sehr einfach der oben genannte zweite Schritt ausgeführt werden - nämlich die Bestimmung aller Einträge mit dem gleichen Nachnamen:

   <xsl:apply-templates select="key('contacts-by-surname', surname)" />

Dazu ist es allerdings noch notwendig, die entsprechenden Nachnamen zu bestimmen, d.h. den jeweils ersten Eintrag zu einem gewissen Nachnamen herauszufinden. Hier können wiederum Keys eingesetzt werden. Wir wissen, dass jeder Eintrag Teil der Knotenliste ist, die wir erhalten, wenn wir als Schlüsselwert den dazugehörigen Nachnamen verwenden. Die Frage ist, ob es sich um den ersten Eintrag dieser Liste handelt (in der Reihenfolge des Auftretens im XML-Dokument) oder um einen weiter hinten stehenden. Uns interessiert nur der erste Fall.

Um herauszufinden, ob ein Eintrag der erste in der durch einen Schlüssel zurückgegebenen Liste ist, muss dieser Eintrag mit dem ersten Knoten der Liste verglichen werden. Für den Vergleich zweier XML-Knoten auf Identität existieren mehrere generische Methoden:

  1. Vergleich der mittels generate-id() erzeugten eindeutigen Bezeichner für die beiden Knoten:

       contact[generate-id() =
               generate-id(key('contacts-by-surname', surname)[1])]

  2. Testen, ob die aus den zu vergleichenden Knoten erzeugte Menge genau ein oder mehrere Elemente enthät. Knoten können nicht mehrfach in einer Menge auftreten, d.h. wenn nur ein Knoten enthalten ist, dann sind die beiden zu vergleichenden Knoten identisch:

       contact[count(. | key('contacts-by-surname', surname)[1]) = 1]

Nachdem die Gruppen bestimmt wurden, lassen sie sich in jede gewünschte Reihenfolge bringen. Analog lassen sich die Knoten innerhalb der Gruppe beliebig sortieren. Das folgende Template erzeugt die eingangs dargestellte Ausgabe aus dem durch eine Datenbank bereitgestellten XML-Dokument:

   <xsl:key name="contacts-by-surname" match="contact" use="surname" />
   
<xsl:template match="records">
      
<xsl:for-each select="contact[count(. | key('contacts-by-surname', surname)[1]) = 1]">
         
<xsl:sort select="surname" />
         
<xsl:value-of select="surname" />,<br />
         
<xsl:for-each select="key('contacts-by-surname', surname)">
            
<xsl:sort select="forename" />
            
<xsl:value-of select="forename" /> (<xsl:value-of select="title" />)<br />
         
</xsl:for-each>
      
</xsl:for-each>
   
</xsl:template>

Die Muench'sche Methode ist im allgemeinen der beste Weg, um Knoten eines XML-Dokuments zu gruppieren, weil sie nicht das Durchsuchen einer großen Anzahl von Knoten erfordert und daher sehr effizient ausgeführt werden kann. Das ist besonders nützlich, wenn eine flache Struktur einer Datenbankausgabe in eine hierarchische Struktur umgewandelt werden soll. Die Methode lässt sich in allen Situationen anwenden, in denen Knoten entsprechend einer durch einen XPath-Ausdruck festgelegten Knoteneigenschaft gruppiert werden sollen.

Die Kehrseite der Medaille ist, dass sich die Muench'sche Methode nur mit XSLT-Prozessoren anwenden lässt, die Keys unterstützen. Das schließt XT von James Clark sowie vor Mai 2000 erschienene MSXML-Versionen aus. Daneben kann die Verwendung von Keys sehr speicherintensiv werden, weil alle Knoten und ihre Schlüsselwerte im Speicher gehalten werden müssen. Schließlich ist es reichlich kompliziert, Keys zur Gruppierung von Knoten zu benutzen, die über mehrere Quelldokumente verteilt sind.


Links

XSLT Recommendation: Keys

XSLT Recommendation: Function generate-id()


Zurück zur XSLT-Tutorials-Seite

© ob Sun, 10. Jun 2001, 22:09 .