xsl:for-each-group

(Auszug aus "XSLT 2.0 & XPath 2.0" von Frank Bongers, Kapitel 6.)

A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z

 

Die Instruktion xsl:for-each-group dient der einfachen Gruppierung von Items (Nodes oder atomare Werte) einer Sequenz nach bestimmten vorgegebenen Kriterien, wie übereinstimmende Stringwerte, Bezeichner, Position (im Dokument oder in der Sequenz) oder anderen, mittels XPath-Ausdrücken bestimmbaren Eigenschaften.

Klassifizierung Instruktion
Funktionsgruppe Gruppen aus Eingabesequenz
Einführung XSLT 2.0

Position im Stylesheet und erlaubte Inhalte:

xsl:for-each-group tritt als Instruktion an beliebigen Positionen innerhalb von Template-Regeln xsl:template auf.

Der Inhalt eines xsl:for-each-group-Elements darf mit einer beliebigen Anzahl von xsl:sort-Elementen zur Sortierung der gebildeten Gruppen beginnen und anschließend in beliebiger Anzahl und Reihenfolge XSLT-Instruktionen, Literal Result Elements und Text enthalten, die für ihre Ausgabe einen Templateblock in Form eines Sequenzkonstruktors bilden.

Attribute:

Es gelten die Standardattribute. Zusätzlich bietet die Instruktion xsl:for-each-group eine Reihe von Attributen, die zur Zusammenstellung der gruppiert auszugebenden Sequenz dienen.

Bis auf select, das die zu gruppierende Sequenz zusammenstellt, sind die elementspezifischen Attribute alle optional. Ihr Auftreten ist dabei dennoch reglementiert. So darf von den Attributen group-by, group-adjacent, group-starting-with und group-ending-with jeweils nur genau eines verwendet werden. Das Attribut collation darf seinerseits nur dann eingesetzt werden, wenn entweder group-by oder group-adjacent vorhanden ist.

collation

Wert

{uri} (auch als AVT)

Verwendung

Optional

Einführung

XSLT 2.0

Das collation-Attribut lädt bei Bedarf eine Datei, die Regeln für Stringvergleiche festlegt. Das collation-Attribut darf nur dann eingesetzt werden, wenn group-by oder group-adjacent für die Gruppierungsregel verwendet werden, da die Sequenz andernfalls nur aus Nodes bestehen darf. Das Attribut darf ein Attributwert-Template enthalten.

group-adjacent

Wert

xpath-expression

Verwendung

Optional

Einführung

XSLT 2.0

Der ausgewertete XPath-Ausdruck des group-adjacent-Attributs wird auf die Items der Sequenz in ihrer vorliegenden Reihenfolge (population order) angewendet. Gruppiert werden jeweils in der population order aufeinander folgende Items, die, basierend auf dem Gruppierungskriterium, den gleichen Wert aufweisen. Jedes Item, das einen anderen Wert aufweist als sein Vorgänger, beginnt eine neue Gruppe.

Hinweis: Dies ist im Gegensatz zu group-by auch dann der Fall, wenn ein Wert bereits einer bestehenden Gruppe zugrunde liegt. Die Zahl der gebildeten Gruppen kann also die Anzahl unterscheidbarer Werte übersteigen, wenn die Abfolge in der Sequenz dies erzwingt.

group-by

Wert

xpath-expression

Verwendung

Optional

Einführung

XSLT 2.0

Das group-by-Attribut bestimmt mittels eines XPath-Ausdrucks das Gruppierkriterium, das die Zugehörigkeit eines Items der Sequenz zu einer Gruppe bestimmt. Alle Items mit Übereinstimmungen in Bezug auf das Gruppierungskriterium werden einer Gruppe zugeteilt. Die Anzahl der Gruppen entspricht somit der Anzahl unterscheidbarer Werte des Gruppierungskriteriums in der Sequenz.

Ein Item kann im Prinzip mehreren Gruppen gleichzeitig zugeordnet werden. Wird das group-by-Attribut verwendet, darf auch das collation-Attribut eingesetzt werden.

group-ending-with

Wert

xpath-pattern

Verwendung

Optional

Einführung

XSLT 2.0

Das group-ending-with-Attribut arbeitet mit einem XPath-Pattern und darf deshalb nur angewendet werden, wenn sich die Eingabesequenz ausschließlich aus Nodes zusammensetzt – andernfalls wird ein Fehler gemeldet. Das Pattern bestimmt das letzte Mitglied einer Gruppe. Die Nodes werden in der Reihenfolge geprüft, die sie in der Sequenz einnehmen.

Die erste Gruppe beginnt in jedem Fall mit dem ersten Knoten der Sequenz und endet mit dem ersten Knoten, auf den das Pattern passt. Beginnt die Sequenz mit einem passenden Knoten, so bildet dieser allein die erste Gruppe. Jede neue Gruppe beginnt mit dem nächstfolgenden Knoten, nach demjenigen, auf den das Pattern passt. Die neue Gruppe umfasst alle auf diesen folgenden Knoten der Sequenz, einschließlich desjenigen, für den ein erneuter Pattern-Match erfolgt. Es werden für n Pattern-Matches ebenso viele Gruppen gebildet – eine Gruppe mehr, falls das Pattern nicht auf den letzten Knoten der Sequenz passt.

group-starting-with

Wert

xpath-pattern

Verwendung

Optional

Einführung

XSLT 2.0

Das group-starting-with-Attribut arbeitet mit einem XPath-Pattern und darf deshalb nur angewendet werden, wenn sich die Eingabesequenz ausschließlich aus Nodes zusammensetzt – andernfalls wird ein Fehler gemeldet. Das Pattern bestimmt das erste Mitglied einer Gruppe. Die Nodes werden in der Reihenfolge geprüft, die sie in der Sequenz einnehmen.

Die erste Gruppe beginnt in jedem Fall mit dem ersten Knoten der Sequenz, auch dann, falls das Pattern auf diesen nicht passen sollte. Jede neue Gruppe beginnt mit dem nächstfolgenden Knoten, auf den das Pattern passt, und umfasst alle auf diesen folgenden Knoten der Sequenz, bis ein erneuter Pattern-Match eine neue Gruppe beginnt. Es werden daher für n Pattern-Matches ebensoviele Gruppe gebildet. Eine zusätzliche Gruppe entsteht, falls das Pattern nicht auf den ersten Knoten der Sequenz passt.

select

Wert

xpath-expression

Verwendung

Obligatorisch

Einführung

XSLT 2.0

Das obligatorisch einzusetzende select-Attribut stellt die Sequenz zusammen, deren Items nach den vorgegebenen Kriterien gruppiert werden. Zu ihrer Festlegung dient genau eines der vier – sich gegenseitig ausschließen­den – Attribute group-by, group-adjacent, group-starting-with oder group-ending-with.

Hinweis: Werden group-starting-with oder group-ending-with als Gruppierungsre­gel verwendet, so muss die Sequenz vollständig aus Nodes bestehen. In den anderen Fällen darf die Sequenz Items beliebigen Typs enthalten.

Verwendungszweck:

Die Anweisung xsl:for-each-group ist ein Neu­zugang in XSLT 2.0, der eine in XSLT 1.0 lange schmerzlich bemerkte Lücke füllt. Bislang bedeutete die Gruppierung von Nodes eine umständliche Angele­genheit, die mittels der neuen Instruktion stark erleichtert und gleichzeitig durch die Ausdehnung des Konzepts auf »allgemeine« Items einer Sequenz viel­fältiger wird.

Die Eingabesequenz (Population)
Die Eingabesequenz der Instruktion ergibt sich durch die Auswertung des XPath-Ausdrucks des select-Attributs. Diese Sequenz wird als »Population« bezeichnet, die Reihenfolge ihrer Items als »Population Order«. Jedes Mitglied der Population wird genau einer Gruppe zugeordnet (Dies ist der gegenwärtig allgemeine Fall – siehe Hinweis zu group-by-Attribut!). Die Anzahl der Items in der Sequenz wird als Kontextgröße bezeichnet.

Zusammenstellung der Gruppen
Die Instruktion xsl:for-each-group dient dazu, ihre Eingabesequenz (in diesem Zusammenhang als »Population« bezeichnet) in Gruppen von Items einzuteilen, also Untersequenzen innerhalb der Sequenz anzulegen. Ist die Eingabesequenz leer, so ist die Anzahl der erzeugten Gruppen folgerichtig gleich null.

Die Zugehörigkeit zu einer Gruppe wird mittels group-by oder group-adja­cent anhand eines gemeinsamen Wertes (Gruppierungsschlüssels) oder mittels group-starting-with oder group-ending-with anhand eines Patterns (Ver­gleichsmusters) bestimmt, das auf das erste Mitglied einer Gruppe zutrifft. Per Definition können auf diese Weise keine leeren Gruppen erzeugt werden – es gibt stets mindestens ein Item pro Gruppe. Für jede der so etablierten Gruppen wird in Folge der Template-Inhalt der Instruktion instanziiert.

Gruppierungsschlüssel
Die Werte entweder des group-by- oder des group-adjacent-Attributs ergeben einen Schlüssel, der auf jedes Item der Sequenz angewendet wird. Das überprüfte Item wird in diesem Zusammen­hang als Kontextitem betrachtet, seine Position innerhalb der Sequenz als Kon­textposition.

Grundsätzlich wird ein Stringwertvergleich mit der entsprechenden Größe eines Items angestrengt, es sei denn, es wurde durch ein zusätzliches as-Attri­but ein anderer Datentyp festgelegt. Für die Auswertung des Stringvergleichs können Angaben einer Collation verwendet werden.

Ist die Umwandlung des betrachteten Werts in den benötigten Typ für ein Item nicht möglich, so wird ein Fehler gemeldet. Ergibt die Auswertung des Grup­pierungsschlüssels für ein Item den Wert NaN, so wird dieses mit anderen Items, für die dies ebenso der Fall ist, einer Gruppe zugeordnet (ansonsten gilt auch in XSLT überall, dass der Wert NaN ungleich zu sich selbst ist!).

Die Verarbeitungsreihenfolge der Gruppen
Ist die Sequenz zusammenge­stellt und entsprechend den verwendeten Attributwerten in Gruppen unter­teilt, so kann eine Sortierung der auf diese Art erstellten Gruppen anhand der xsl:sort-Instruktionen (eine oder mehrere in Folge) erfolgen, die als unmit­telbare Child-Elemente von xsl:for-each-group auftreten können. In dieser neuen Reihenfolge werden die Gruppen anschließend mit jeweils allen ihren Mitgliedern durch den Templateblock verarbeitet. Die Reihenfolge der Ele­mente innerhalb der Gruppen wird durch die Sortierung nicht verändert.

Liegt keine Sortierung durch xsl:sort vor, so erfolgt die Verarbeitung in der so genannten »Reihenfolge des ersten Erscheinens« (order of first appearance). Die Positionen der ersten Mitglieder der Gruppen zueinander bestimmen die Verarbeitungsreihenfolge: Die Gruppe, deren erstes Mitglied zuerst steht, wird auch als erste verarbeitet und so fort.

Die Verarbeitung der Gruppen
Für jede Gruppe wird in Folge der in xsl:for-each-group enthaltene Templateblock instanziiert, wobei die aktuell verarbeitete Gruppe jeweils als Current Group bezeichnet wird. Während der Verarbeitung der Gruppen wird jeweils ihr erstes Mitglied (initial item) als Kon­textitem betrachtet, die Gesamtzahl der zu verarbeitenden Gruppen ergibt die Kontextgröße, die Position der Gruppe in der Verarbeitungsreihenfolge ihre Kontextposition. Die Funktionen fn:position() und fn:last() geben, zu diesem Zeitpunkt verwendet, entsprechende Werte zurück.

Im Templateblock kann weiterhin die Funktion current-group() angewendet werden, um auf die Mitglieder dieser Gruppe als Sequenz zurückzugreifen – beispielsweise um die Stringwerte aller Gruppenmitglieder mit xsl:value-of auszugeben. Die Funktion gibt, da Gruppen mindestens ein Mitglied besitzen, eine Sequenz aus mindestens einem Item zurück.

Zum selben Zeitpunkt kann mit der Funktion current-grouping-key() auf den aktuellen Wert des Gruppierkriteriums zugegriffen werden, der für die aktuelle Gruppe gilt.

Hinweis – Keine Aktivierung weiterer Template-Regeln:
Da xsl:for-each-group seine eigene Sequenz zusammenstellt und die aktuelle Template-Regel auf »null« setzt, ist aus dem Inneren der Instruktion keine Aktivierung von Template-Regeln mittels xsl:apply-imports oder xsl:next-match möglich.

Beispiele:

Beispiel 1 – Gruppierung mit group-by:

Quelldokument:
Als Quelldokument liegt eine XML-Datei vor, die eine Liste von Songs enthält. Interpret und Songtitel liegen jeweils als Attributwerte vor.

<lieder>
  ...
  <lied name="She loves you" kuenstler="Beatles"/>
  <lied name="Yesterday" kuenstler="Beatles "/>
  <lied name="Satisfaction" kuenstler="Rolling Stones"/>
  <lied name="My Generation" kuenstler="Who"/>
  <lied name="Under My Thumb" kuenstler="Rolling Stones"/>
  ...
</lieder>

Die Lieder sollen für die Ausgabe nach Künstlern gruppiert und in diesem Kon­text als kommagetrennte Liste ausgegeben werden. Das Ergebnis soll als Tabelle ausgegeben werden, die pro Zeile einen Künstler und die ihm zuzuord­nenden Stücke enthält.

Ergebnisdokument:

<table>
  <tr>
    <th>Künstler</th>
    <th>Stückliste</th>
  </tr>
  <tr>
    <td>Beatles</td>
    <td>She Loves You, Yesterday</td>
  </tr>
  <tr>
    <td>Rolling Stones</td>
    <td>Satifaction, Under My Thumb</td>
  </tr>  
  <tr>
    <td>Who</td>
    <td>My Generation</td>
  </tr>
  ...
</table>

XSLT-Stylesheet:

<xsl:template match="/">
  <table>
    <tr>
      <th>Künstler</th>
      <th>Stückliste</th>
    </tr>
    <xsl:for-each-group select="lieder/lied" group-by="@kuenstler">
      <tr>
        <td><xsl:value-of select="current-grouping-key()"/></td>
        <td><xsl:value-of select="current-group()/@name" separator=","/></td>
      </tr>
    </xsl:for-each-group>
  </table>
</xsl:template>

Das gewünschte Ergebnis wird realisiert durch Verwendung von xsl:for-each-group mit group-by-Attribut. Als Gruppierungsschlüssel wird der String­wert des Attributs kuenstler verwendet:

<xsl:for-each-group select="lieder/lied" group-by="@kuenstler">

Der mit xsl:value-of ausgegebene Name des Interpreten wird über die Funk­tion current-grouping-key() ausgegeben:

<xsl:value-of select="current-grouping-key()"/>

Alternativ könnte er auch dem Attribut kuenstler des ersten Gruppenmit­glieds entnommen werden, da dieses im Augenblick der Verarbeitung inner­halb des Templateblocks das Kontextitem darstellt (der Wert wäre für alle Mit­glieder der Gruppe identisch):

<!-- Alternative -->
<xsl:value-of select="@kuenstler"/>

Für die Ausgabe der Songtitel mit xsl:value-of wird die Funktion current-group() verwendet. Dabei wird auf die Werte der name-Attribute aller hier als Sequenz zurückgegebenen Gruppenmitglieder zurückgegriffen:

<xsl:value-of select="current-group()/@name" separator=","/>

Das Komma als Trennelement zwischen den ausgegebenen Einzelwerten wird mit dem separator-Attribut vereinbart (dieses Attribut ist der Instruktion in XSLT 2.0 hinzugefügt worden).

Beispiel 2 – Gruppierung mit group-starting-with:

Quelldokument:

<body>
  <h2>Sektionen in XHTML 2.0</h2>
  <p>Dem body wird eine Sektion untergeordnet.</p>
  <p>Diese Sektion kann wieder mehrere Sektionen enthalten.</p>
  <h2>Überschriften in Sektionen</h2>
  <p>Jeder Sektion können Überschriften zugeordnet werden.</p>
  <p>Die Hierarchie der Sektion bestimmt den Überschriftsgrad.</p>
  <p>Deshalb kann ein allgemeines Element 'h' verwendet werden.</p>
</body>

Dieses XHTML 1.0-Dokument soll in ein XHTML 2.0-Dokument umgeformt werden, dessen Dokumentinhalt in Sektionen unterteilt ist. Diesen soll jeweils eine Überschrift zugeordnet sein. Hierbei werden jeweils eine Überschrift <h2> und die folgenden <p>-Container einer Sektion zugewiesen – gruppiert werden also benachbarte <p>-Container und ihre vorausgehende <h2>-Überschrift.

Ergebnisdokument mit Sektionen:

<body>
  <section>
    <section>
      <h>Sektionen in XHTML 2.0</h>
      <p>Dem body wird eine Sektion untergeordnet.</p>
      <p>Diese Sektion kann wieder mehrere Sektionen enthalten.</p>
    </section> 
    <section>
      <h>Überschriften in Sektionen</h>
      <p>Jeder Sektion können Überschriften zugeordnet werden.</p>
      <p>Die Hierarchie der Sektion bestimmt den Überschriftsgrad.</p>
      <p>Deshalb kann ein allgemeines Element 'h' verwendet werden.</p>
    </section>
  </section>
<body>

Das XSLT-Stylesheet:

<xsl:template match="body">
  <section>
    <xsl:for-each-group select="*" group-starting-with="h2">
      <section>
        <h><xsl:value-of select="current-group()[self::h2]"/></h>
        <xsl:for-each select="current-group()[self::p]">
          <xsl:copy-of select="."/>
        </xsl:for-each> 
      </section>
    </xsl:for-each-group>
  </section>
</xsl:template>

Das select-Attribut des xsl:for-each-group-Elements wählt zunächst alle Elemente ungeachtet ihres Namens aus – als XPath-Ausdruck dient der Wild­card-Nametest "*". Die so gewählten Knoten werden mittels group-star­ting-with="h2" in Gruppen unterteilt, die jeweils mit einem <h2>-Element beginnen. Dieses wird zunächst ausgegeben, anschließend werden mit einer xsl:for-each-Schleife alle <p>-Container der aktuellen Gruppe in das Ergeb­nisdokument kopiert.

Die Ausgabe verwendet immer die gesamte Gruppe als Sequenz, die durch current-group() zur Verfügung gestellt wird. Es werden jeweils durch ein Predicate die auszugebenden Knotentypen ausgefiltert – dies geschieht mit self::h2 bzw. mit self::p, um Eindeutigkeit zu gewährleisten. Sollte die erste Gruppe nicht mit einem <h2>-Element beginnen, so würde in ihrem Fall allerdings der <h>-Container leer bleiben.

Beispiel 3 – Gruppierung mit group-adjacent:

Quelldokument:

<p>Überprüfen Sie <em>unbedingt</em>:
  <ul>
    <li>Warnblinkanlage,</li>
    <li>Bremslicht,</li>
    <li>Reifendruck</li>
  </ul> vor jeder längeren Fahrt.</p>

Das oben stehende Dokument soll in eine, unten gezeigte, verbesserte Form gebracht werden – der <p>-Container soll hierbei nicht die Liste enthalten.

Ergebnisdokument:

<p>Überprüfen Sie <em>unbedingt</em>:</p>
<ul>
  <li>Warnblinkanlage,</li>
  <li>Bremslicht,</li>
  <li>Reifendruck</li>
</ul>
<p>vor jeder längeren Fahrt.</p>

Technisch ausgedrückt ist es hierfür erforderlich, einen <p>-Container um jene aufeinander folgenden Knoten zu legen, die keine <ul>-Elemente sind – hier­für wird group-adjacent in xsl:for-each-group verwendet.

XSLT-Stylesheet:

<xsl:template match="p">
  <xsl:for-each-group select="node()" group-adjacent="self::ul">
    <xsl:choose>
      <xsl:when test="self::ul">
        <xsl:copy-of select="current-group()"/>
      </xsl:when>
      <xsl:otherwise>
        <p><xsl:copy-of select="current-group()"/></p>
      </xsl:otherwise>  
    </xsl:choose>
  </xsl:for-each-group>
</xsl:template>

Das Template wird für das <p>-Element instanziiert – die xsl:for-each-group-Instruktion wählt mit select="node()" pauschal alle in diesem enthal­tenen Knoten für seine Population aus. Die Gruppierung erfolgt mit group-adjacent. Dieses Attribut ordnet alle in der Population aufeinander folgenden Items mit gleichem Gruppierungskriterium einer Gruppe zu:

<xsl:for-each-group select="node()" group-adjacent="self::ul">

Hier ist das mit group-adjacent="self::ul" formulierte Kriterium ein Boo­lescher Wert – das Kontextitem, mit anderen Worten, jedes der nacheinander geprüften Items der Population, ist entweder ein <ul>-Element oder nicht.

In diesem konkreten Fall ergeben sich daher drei Gruppen: die erste besteht aus einem Textknoten und einem <em>-Element, die zweite aus einem <ul>-Element, die dritte wieder aus einem Textknoten.

Im Templatebody werden diese drei Gruppen nacheinander verarbeitet. Hier wird geprüft, ob das erste Mitglied einer Gruppe ein <ul>-Element ist. In die­sem Fall werden alle Mitglieder der Gruppe in das Ergebnisdokument kopiert:

<xsl:when test="self::ul">
  <xsl:copy-of select="current-group()"/>
</xsl:when>

Zu beachten ist, dass innerhalb des Templateblocks das jeweils erste Gruppen­mitglied Kontextitem ist, hier also nicht alle Gruppenmitglieder geprüft wür­den. In unserem Fall besteht die current-group()-Sequenz zwar nur aus einem Item – das Ergebnis wäre aber für mehrere unmittelbar aufeinander fol­gende Listen identisch.

Die anderen Gruppen werden, von einem <p>-Container umgeben, ausgege­ben:

<xsl:otherwise>
  <p><xsl:copy-of select="current-group()"/></p>
</xsl:otherwise>

Die Funktion current-group() kopiert alle Gruppenmitglieder als Sequenz innerhalb des <p>-Containers in das Ergebnisdokument.

Elementdefinition:

XSLT 1.0:

Element in XSLT 1.0 nicht verfügbar.

XSLT 2.0:

<!-- Category: instruction -->
<xsl:for-each-group
     select = expression
     group-by? = expression
     group-adjacent? = expression
     group-starting-with? = pattern
     group-ending-with? = pattern
     collation? = { uri-string }>

     <!-- Content: (xsl:sort*, sequence-constructor) -->
</xsl:for-each-group>
Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © Galileo Press, Bonn 2008
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT 2.0 & XPath 2.0 ― Das umfassende Handbuch" denselben Bestimmungen wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Galileo Press, Rheinwerkallee 4, 53227 Bonn