XML/XSL/XSLT/Elemente gruppieren durch Referenzierung von ID-Werten

Aus SELFHTML-Wiki
< XML‎ | XSL‎ | XSLT
Wechseln zu: Navigation, Suche

Dieser Artikel von Thomas J. Sebestyen erschien am 09.05.2004 im Selfhtml-aktuell-Bereich. Im vorliegenden Artikel wird der oft gestellten Frage nachgegangen, wie man auf die in einer XML-Datei als ID notierten Attribute zugreifen und diese für die Referenzierung und Gruppierung von Elementen bei der Ausgabe verwenden kann. Für das Verständnis dieses Artikels sind Grundkenntnisse in XML und XSLT Voraussetzung.

Beispiel mit Erläuterungen

Das Beispiel zeigt eine XML-Datei, in der verschiedene Veranstaltungen und die Teilnehmer, die diese besuchen, aufgelistet sind. Die Beziehung zwischen den Veranstaltungen und den Teilnehmern wird durch die Attribute id und events (IDREF) hergestellt.

Die gewünschte Ausgabe sollte einerseits die jeweilige Veranstaltung und ihre Teilnehmer auflisten, anderseits sollte es auch eine Liste der Teilnehmer mit den von ihnen besuchten Veranstaltungen geben.

Beispiel-DTD veranstaltungen.dtd
<!ELEMENT activity (events,participants)>
<!ELEMENT events (event)+>
<!ELEMENT event EMPTY>
<!ATTLIST event
   id   ID    #REQUIRED
   name CDATA #REQUIRED>
<!ELEMENT participants (participant)+>
<!ELEMENT participant EMPTY>
<!ATTLIST participant
   events IDREFS  #REQUIRED
   name   CDATA   #REQUIRED>


Beispiel-XML veranstaltungen.xml
<?xml version="1.0" encoding="iso-8859-1"?>
<?xml-stylesheet type="text/xsl" href="veranstaltungen.xsl"?>
<!DOCTYPE activity SYSTEM "veranstaltungen.dtd">
<activity>
  <events>
     <event id="e01" name="XML on Stage"/>
     <event id="e02" name="SELFHTML Treffen"/>
     <event id="e03" name="Expertenchat"/>
  </events>
  <participants>
    <participant events="e02" name="Otto Lieb"/>
    <participant events="e02" name="Axel Bravo"/>
    <participant events="e01 e03" name="Petra Klug"/>
    <participant events="e02 e03" name="Birgit Lob"/>
    <participant events="e01 e03" name="Andrea Groß"/>
    <participant events="e01 e02 e03" name="Heinrich Aller"/>
    <participant events="e01 e02 e03" name="Jessy Keen"/>
  </participants>
</activity>


Beispiel-XSL veranstaltungen.xsl
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
   <xsl:output 
      method="html"
      encoding="iso-8859-1"
      doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
      doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
      indent="yes" />
   
   <xsl:key name="participant2event" match="participant" use="id(@events)/@id" />

   <xsl:template match="/activity">
   <html>
      <head>
         <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
         <title>Veranstaltungen und Teilnehmer</title>
      </head>
      <body>
         <h1>Veranstaltungen und Teilnehmer</h1>
         <h2>Pro Veranstaltung</h2>
         <xsl:apply-templates select="events"  />
         <hr />
         <h2>Pro Teilnehmer</h2>
         <xsl:apply-templates select="participants" />
      </body>
   </html>
   </xsl:template>

   <xsl:template match="event">
       <h3><xsl:value-of select="@name"/></h3>
       <ul>
           <xsl:apply-templates select="key('participant2event', @id)" mode="byevent"/>
       </ul>
   </xsl:template>
   <xsl:template match="participant">
       <p>
          <strong><xsl:value-of select="@name"/>: </strong>
         <xsl:apply-templates select="id(@events)" mode="byparticipant"/>
      </p>
   </xsl:template>

   <xsl:template match="event" mode="byparticipant">
      <xsl:value-of select="@name"/>
      <xsl:if test="position() != last()">, </xsl:if>
   </xsl:template>
   <xsl:template match="participant" mode="byevent">
      <li><xsl:value-of select="@name"/></li>
   </xsl:template>
   
</xsl:stylesheet>

Erläuterung zu XML und DTD

In unserem Beispiel verwenden wir eine komplette DTD, in der Praxis ist es jedoch oft der Fall, dass für die XML-Datei zwar keine DTD existiert, dennoch Elemente mit Attributen versehen werden, die eine ID-Funktion erfüllen bzw. erfüllen sollen. Aus der Sicht des XSL-Prozessors ist es nicht nötig, dass eine komplette DTD vorhanden ist. Es genügt lediglich, dem Prozessor mitzuteilen, welches Attribut vom Typ ID ist. So können Sie in unserem Beispiel-XML, statt auf eine externe DTD zu verweisen, eine interne DTD-Untermenge verwenden.

Beispiel
<?xml version="1.0" encoding="iso-8859-1"?>
<?xml-stylesheet type="text/xsl" href="veranstaltungen.xsl"?>
<!DOCTYPE activity [
   <!ELEMENT event EMPTY>
   <!ATTLIST event id   ID    #REQUIRED>
]>
<activity>
...
</activity>
Beachten Sie: Falls Sie einen nicht-validierenden XML-Parser verwenden (wie dies z.B. bei Mozilla der Fall ist) und weil ein XSL-Prozessor sich nicht um die um die Validität der XML-Datei kümmert, ist es sogar möglich, ungültige ID-Werte (z.B. Werte, die mit einer Zahl beginnen) zu notieren. Es ist jedoch davon abzuraten, diese Möglichkeit in der Praxis zu nutzen, denn beim Umstieg auf einen validierenden XML-Parser wird dieser solche Fehler melden und die Transformation abbrechen.

Da der Mozilla Browser einen nicht-validierenden XML-Parser verwendet und somit keine extenen Entities (wie etwa eine DTD) auflösen kann, müssen Sie – falls Sie direkt Ihre XML-Dateien im Internet anbieten – für diesen Browser die Attribut-Deklaration auf alle Fälle (also auch dann, wenn Sie eine externe DTD benutzen!) in der internen DTD-Untermenge notieren.

Erläuterung zu XSLT

In unserem XSL definieren wir zuerst einen key, der auf die participant-Elemente im XML zugreift und als Wert des Schlüssels den XPath-Ausdruck id(@events)/@id verwendet.

Dieser Ausdruck verwendet die id()-Funktion für das Attribut events im participant-Element. Dadurch werden die in diesem Attribut angegebenen Werte als ID-Referenzen auf andere Elemente im XML-Dokument erkannt. Durch die Angabe /@id wird dann die Verknüpfung zu den referenzierten Elementen, genauer gesagt mit deren id-Attribut, hergestellt. Somit enthält dieser key für jedes participant-Element das referenzierte event-Element bzw. die referenzierten event-Elemente.

Die nächsten wichtigen Schritte sind, die Templates für die beiden Elemente event und participant zu definieren.

Beispiel
<xsl:template match="event">
    <h3><xsl:value-of select="@name"/></h3>
    <ul>
        <xsl:apply-templates select="key('participant2event', @id)" mode="byevent"/>
    </ul>
</xsl:template>

In diesem Template wird nach der Ausgabe des Namens des events eine Aufzählungsliste erstellt. In dieser Liste wird dann mit Hilfe des apply-templates-Elements ein anderes Template mit dem Modus "byevent" instanziiert.

Das select-Attribut in diesem apply-templates-Element benutzt die XPath-Funktion key(). Diese greift auf unseren, mit dem Namen participant2event definierten, key zu und als Vergleichs- oder Referenzwert benutzt sie dabei das id-Attribut im gegenwärtigen event-Element.

Das bedeutet, dass hier ein Template mit dem Modus "byevent" für diejenige participant-Elemente gesucht und ausgeführt wird, in deren event-Attribut der Wert des id-Attributs des gegenwärtigen event-Elements vorhanden ist. Beim event "XML on Stage" sind dies die Elemente:

Beispiel
<participant events="e01 e03" name="Petra Klug" />
<participant events="e01 e03" name="Andrea Groß" />
<participant events="e01 e02 e03" name="Heinrich Aller" />
<participant events="e01 e02 e03" name="Jessy Keen" />

Das Template für diese Elemente erstellt nur einen Listenpunkt, der den Namen des Teilnehmers enthält:

{{Beispiel|titel=|
{{BeispielCode|<source lang="xml">
<xsl:template match="participant" mode="byevent">
   <li><xsl:value-of select="@name"/></li>
</xsl:template>
Beachten Sie: Ein Template wird für jedes Element, auf das es zutrifft, neu instanziiert, deshalb ändert sich das Ziel-Element bei jedem "Durchlauf" des Templates und aus diesem Grund spricht man in diesem Zusammenhang vom gegenwärtigen Element, also von dem Element, das gerade abgearbeitet wird. Instanziieren bedeutet, dass alle Elemente und Attribute, die innerhalb des entsprechenden (z.B.) xsl:template-Elements stehen und nicht zum XSLT-Namensraum gehören, in den Ergebnisbaum geschrieben werden.

Das Template für das participant-Element sieht etwas anders aus:

Beispiel
<xsl:template match="participant">
    <p>
       <strong><xsl:value-of select="@name"/>: </strong>
      <xsl:apply-templates select="id(@events)" mode="byparticipant"/>
   </p>
</xsl:template>

Hier wird für jedes participant-Element ein Absatz erzeugt und der Name des Teilnehmers in fetter Schrift ausgegeben. Danach wird wiederum ein apply-templates-Element, diesmal mit dem Modus "byparticipant", aufgerufen. Gesucht und ausgeführt wird ein Template für event-Elemente durch die id()-Funktion, die im event-Attribut des gegenwärtigen participant-Elements referenziert ist. Beim Teilnehmer "Petra Klug" sind dies die Elemente:

Beispiel
<event id="e01" name="XML on Stage" />
<event id="e03" name="Expertenchat" />

Das Template für diese Elemente gibt einfach den Namen der Veranstaltung aus und wenn diese nicht die letzte in der Reihe ist, fügt es nach dem Namen ein Komma und ein Leerzeichen ein.

Beispiel
<xsl:template match="event" mode="byparticipant">
   <xsl:value-of select="@name"/>
   <xsl:if test="position() != last()">, </xsl:if>
</xsl:template>

Das Template für das activity-Element (<xsl:template match="/activity">) enthält das Grundgerüst für die HTML-Seite und ruft an den entsprechenden Stellen die normalen Templates für Veranstaltungen und Teilnehmer auf.

Weblinks

Deutsche Übersetzung 18. März 2002

Version 1.0 Deutsche, kommentierte Übersetzung 26. Februar 2002