Die W3C-XML-Query-Use-Cases in XSLT implementieren

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie müssen eine Abfrageoperation ausführen, die einem der Anwendungsfälle in XML Query Use Cases ähnelt, allerdings wollen Sie XSLT anstelle von XQuery einsetzen.

Lösung

Die folgenden Beispiele sind XSLT-Lösungen für die meisten der Anwendungsfälle für XML-Abfragen, die im W3C-Dokument präsentiert werden. Die Beschreibungen der einzelnen Anwendungsfälle sind fast wörtlich dem W3C-Dokument entnommen worden.

  1. Anwendungsfall »XMP«: Erfahrungen und Beispiele.
    Dieser Anwendungsfall enthält mehrere Beispielabfragen, die die Anforderungen illustrieren, die vom W3C aus Datenbank- und Dokument-Communities gesammelt wurden. Die Daten, die von diesen Abfragen verwendet werden, folgen in den nächsten vier Beispielen.

Beispiel: bib.xml

<bib>
  <book year="1994">
    <title>TCP/IP Illustrated</title>
    <author><last>Stevens</last><first>W.</first></author>
    <publisher>Addison-Wesley</publisher>
    <price> 65.95</price>
  </book>
  <book year="1992">
    <title>Advanced Programming in the Unix environment</title>
    <author><last>Stevens</last><first>W.</first></author>
    <publisher>Addison-Wesley</publisher>
    <price>65.95</price>
  </book>
  <book year="2000">
    <title>Data on the Web</title>
    <author><last>Abiteboul</last><first>Serge</first></author>
    <author><last>Buneman</last><first>Peter</first></author>
    <author><last>Suciu</last><first>Dan</first></author>
    <publisher>Morgan Kaufmann Publishers</publisher>
    <price> 39.95</price>
  </book>
  <book year="1999">
    <title>The Economics of Technology and Content for Digital TV</title>
    <editor>
      <last>Gerbarg</last><first>Darcy</first>
      <affiliation>CITI</affiliation>
    </editor>
    <publisher>Kluwer Academic Publishers</publisher>
    <price>129.95</price>
  </book>
</bib>

Beispiel: reviews.xml

<reviews>
  <entry>
    <title>Data on the Web</title>
    <price>34.95</price>
    <review>A very good discussion of semi-structured database systems and XML.</review>
  </entry>
  <entry>
    <title>Advanced Programming in the Unix environment</title>
    <price>65.95</price>
    <review>A clear and detailed discussion of UNIX programming.</review>
  </entry>
  <entry>
    <title>TCP/IP Illustrated</title>
    <price>65.95</price>
    <review>One of the best books on TCP/IP.</review>
  </entry>
</reviews>

Beispiel: books.xml

<chapter>
  <title>Data Model</title>
  <section>
    <title>Syntax For Data Model</title>
  </section>
  <section>
    <title>XML</title>
    <section>
      <title>Basic Syntax</title>
    </section>
    <section>
      <title>XML and Semistructured Data</title>
    </section>
  </section>
</chapter>

Beispiel: prices.xml

<prices>
  <book>
    <title>Advanced Programming in the Unix environment</title>
    <source>www.amazon.com</source>
    <price>65.95</price>
  </book>
  <book>
    <title>Advanced Programming in the Unix environment</title>
    <source>www.bn.com</source>
    <price>65.95</price>
  </book>
  <book>
    <title>TCP/IP Illustrated</title>
    <source>www.amazon.com</source>
    <price>65.95</price>
  </book>
  <book>
    <title>TCP/IP Illustrated</title>
    <source>www.bn.com</source>
    <price>65.95</price>
  </book>
  <book>
    <title>Data on the Web</title>
    <source>www.amazon.com</source>
    <price>34.95</price>
  </book>
  <book>
    <title>Data on the Web</title>
    <source>www.bn.com</source>
    <price>39.95</price>
  </book>
</prices>
<!-- Frage 1. Liste Bücher in bib.xml auf, die von Addison-Wesley nach 1991 veröffentlicht wurden, einschließlich ihres Jahres und Titels: -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:import href="copy.xslt"/>
  <xsl:template match="book[publisher = 'Addison-Wesley' and @year &gt; 1991]">
    <xsl:copy-of select="."/>
  </xsl:template>
  <xsl:template match="book"/>
</xsl:stylesheet>

<!-- Frage 2. Erzeuge eine einfache Liste aller Titel-Autor-Paare aus bib.xml, wobei jedes Paar in ein "result"-Element eingeschlossen ist: -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <results>
      <xsl:apply-templates select="bib/book/author"/>
    </results>
  </xsl:template>
  <xsl:template match="author">
    <result>
      <xsl:copy-of select="preceding-sibling::title"/>
      <xsl:copy-of select="."/>
    </result>
  </xsl:template>
</xsl:stylesheet>

<!-- Frage 3. Liste für jedes Buch in bib.xml Titel und Autoren auf, und zwar in einem "result"-Element gruppiert: -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="bib">
    <results>
      <xsl:for-each select="book">
        <result>
          <xsl:copy-of select="title"/>
          <xsl:copy-of select="author"/>
        </result>
      </xsl:for-each>
    </results>
  </xsl:template>
</xsl:stylesheet>

<!-- Frage 4. Liste für jeden Autor in bib.xml den Namen des Autors und die Titel aller Bücher dieses Autors auf, und zwar in einem "result"-Element gruppiert: -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <results>
      <xsl:for-each select="//author[not(.=preceding::author)]">
        <result>
          <xsl:copy-of select="."/>
          <xsl:for-each select="/bib/book[author=current()]">
            <xsl:copy-of select="title"/>
          </xsl:for-each>
        </result>
      </xsl:for-each>
    </results>
    <!-- Frage 5. Liste für alle Bücher, die sowohl auf http://www.bn.com (bib.xml) als auch auf http://www.amazon.com (reviews.xml) gefunden wurden, den Titel des Buches und den Preis bei der jeweiligen Quelle auf: -->
    <xsl:variable name="bn" select="document('bib.xml')"/>
    <xsl:variable name="amazon" select="document('reviews.xml')"/>
  </xsl:template>
</xsl:stylesheet>

<!--Lösung 1 -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <books-with-prices>
      <xsl:for-each select="$bn//book[title = $amazon//entry/title]">
        <book-with-prices>
          <xsl:copy-of select="title"/>
          <price-amazon>
            <xsl:value-of select="$amazon//entry[title=current()/title]/price"/>
          </price-amazon>
          <price-bn>
            <xsl:value-of select="price"/>
          </price-bn>
        </book-with-prices>
      </xsl:for-each>
    </books-with-prices>
  </xsl:template>
</xsl:stylesheet>

<!--Lösung 2-->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <books-with-prices>
      <xsl:for-each select="$bn//book">
        <xsl:variable name="bn-book" select="."/>
        <xsl:for-each select="$amazon//entry[title=$bn-book/title]">
          <book-with-prices>
            <xsl:copy-of select="title"/>
            <price-amazon>
              <xsl:value-of select="price"/>
            </price-amazon>
            <price-bn>
              <xsl:value-of select="$bn-book/price"/>
            </price-bn>
          </book-with-prices>
        </xsl:for-each>
      </xsl:for-each>
    </books-with-prices>
  </xsl:template>
</xsl:stylesheet>

<!-- Frage 6. Liste für jedes Buch, das mindestens einen Autor hat, den Titel und die ersten beiden Autoren sowie ein leeres "et-al"-Element auf, wenn das Buch weitere Autoren hat: -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="bib">
    <xsl:copy>
      <xsl:for-each select="book[author]">
        <xsl:copy>
          <xsl:copy-of select="title"/>
          <xsl:copy-of select="author[position() &lt;= 2]"/>
          <xsl:if test="author[3]">
            <et-al/>
          </xsl:if>
        </xsl:copy>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

<!-- Frage 7. Liste in alphabetischer Reihenfolge die Titel und Jahre aller Bücher auf, die nach 1991 von Addison-Wesley veröffentlicht wurden: -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="bib">
    <xsl:copy>
      <xsl:for-each select="book[publisher = 'Addison-Wesley' and @year &gt; 1991]">
        <xsl:sort select="title"/>
        <xsl:copy>
          <xsl:copy-of select="@year"/>
          <xsl:copy-of select="title"/>
        </xsl:copy>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

<!-- Frage 8. Suche im Dokument books.xml alle Abschnitts-(section-) oder Kapitel-(chapter-)titel, die das Wort "XML" enthalten, ungeachtet der Verschachtelungsebene: -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <results>
      <xsl:copy-of select="(//chapter/title | //section/title)[contains(.,'XML')]"/>
    </results>
  </xsl:template>
</xsl:stylesheet>

<!-- Frage 9. Suche im Dokument prices.xml den Minimalpreis für jedes Buch in Form eines "minprice"-Elements mit dem Buchtitel als Titelattribut: -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:include href="../math/math.min.xslt"/>
  <xsl:template match="/">
    <results>
      <xsl:for-each select="//book/title[not(. = ./preceding::title)]">
        <xsl:variable name="min-price">
          <xsl:call-template name="math:min">
            <xsl:with-param name="nodes" select="//book[title = current()]/price"/>
          </xsl:call-template>
        </xsl:variable>
        <minprice title="{.}">
          <price>
            <xsl:value-of select="$min-price"/>
          </price>
        </minprice>
      </xsl:for-each>
    </results>
  </xsl:template>
</xsl:stylesheet>

<!-- Frage 10. Liefere für jedes Buch mit einem Autor das Buch mit seinem Titel und seinen Autoren zurück. Liefere für jedes Buch mit einem Herausgeber eine Referenz mit dem Buchtitel und der Zugehörigkeit des Herausgebers zurück: -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="bib">
    <xsl:copy>
      <xsl:for-each select="book[author]">
        <xsl:copy>
          <xsl:copy-of select="title"/>
          <xsl:copy-of select="author"/>
        </xsl:copy>
      </xsl:for-each>
      <xsl:for-each select="book[editor]">
        <reference>
          <xsl:copy-of select="title"/>
          <org><xsl:value-of select="editor/affiliation"/></org>
        </reference>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

<!-- Frage 11. Suche Paare von Büchern, die unterschiedliche Titel, aber die gleichen Autoren (möglicherweise in anderer Reihenfolge) haben: -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:include href="query.equal-values.xslt"/>
  <xsl:template match="bib">
    <xsl:copy>
      <xsl:for-each select="book[author]">
        <xsl:variable name="book1" select="."/>
        <xsl:for-each select="./following-sibling::book[author]">
          <xsl:variable name="same-authors">
            <xsl:call-template name="query:equal-values">
              <xsl:with-param name="nodes1" select="$book1/author"/>
              <xsl:with-param name="nodes2" select="author"/>
            </xsl:call-template>
          </xsl:variable>
          <xsl:if test="string($same-authors)">
            <book-pair>
              <xsl:copy-of select="$book1/title"/>
              <xsl:copy-of select="title"/>
            </book-pair>
          </xsl:if>
        </xsl:for-each>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>
  1. Anwendungsfall »TREE«: Abfragen, die die Hierarchie bewahren.
    Manche XML-Dokumenttypen haben eine sehr flexible Struktur, in der Text mit Elementen gemischt ist und viele Elemente optional sind. Bei diesen Dokumenttypen gibt es viele Variationen in der Struktur. Normalerweise ist in diesen Arten von Dokumenten die Art und Weise sehr wichtig, wie die Elemente angeordnet und verschachtelt sind. Eine XML-Abfragesprache sollte die Fähigkeit besitzen, Elemente aus Dokumenten zu extrahieren, während deren ursprüngliche Struktur beibehalten wird. Dieser Anwendungsfall illustriert diese Anforderung anhand eines flexiblen Dokumenttyps namens Book.

    Die DTD sowie die XML-Daten, die von diesen Abfragen verwendet werden, folgen in den beiden nächsten Beispielen.

Beispiel: book.dtd

<!ELEMENT book (title, author+, section+)>
  <!ELEMENT title (#PCDATA)>
  <!ELEMENT author (#PCDATA)>
  <!ELEMENT section (title, (p | figure | section)* )>
  <!ATTLIST section
      id         ID    #IMPLIED
      difficulty CDATA #IMPLIED>
  <!ELEMENT p (#PCDATA)>
  <!ELEMENT figure (title, image)>
  <!ATTLIST figure
      width   CDATA    #REQUIRED
      height  CDATA   #REQUIRED >
  <!ELEMENT image EMPTY>
  <!ATTLIST image
      source  CDATA   #REQUIRED >

Beispiel: book.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book SYSTEM "book.dtd">
<book>
  <title>Data on the Web</title>
  <author>Serge Abiteboul</author>
  <author>Peter Buneman</author>
  <author>Dan Suciu</author>
  <section id="intro" difficulty="easy">
    <title>Introduction</title>
    <p>Text ... </p>
    <section>
      <title>Audience</title>
      <p>Text ... </p>
    </section>
    <section>
      <title>Web Data and the Two Cultures</title>
      <p>Text ... </p>
      <figure height="400" width="400">
        <title>Traditional client/server architecture</title>
        <image source="csarch.gif"/>
      </figure>
      <p>Text ... </p>
    </section>
  </section>
  <section id="syntax" difficulty="medium" >
    <title>A Syntax For Data</title>
    <p>Text ... </p>
    <figure height="200" width="500">
      <title>Graph representations of structures</title>
      <image source="graphs.gif"/>
    </figure>
    <p>Text ... </p>
    <section>
      <title>Base Types</title>
      <p>Text ... </p>
    </section>
    <section>
      <title>Representing Relational Databases</title>
      <p>Text ... </p>
      <figure height="250" width="400">
        <title>Examples of Relations</title>
         <image source="relations.gif"/>
      </figure>
    </section>
    <section>
      <title>Representing Object Databases</title>
      <p>Text ... </p>
    </section>
  </section>
</book>
<!-- Frage 1. Bereite ein (geschachteltes) Inhaltsverzeichnis für Book1 vor, in dem alle Abschnitte und deren Titel aufgelistet werden. Die Originalattribute der einzelnen <section>-Elemente sollen beibehalten werden, falls welche existieren: -->
<xsl:template match="book">
  <toc>
    <xsl:apply-templates/>
  </toc>
</xsl:template>
<!-- Kopieren des Elements von toc -->
<xsl:template match="section | section/title | section/title/text()">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
      <xsl:apply-templates/>
  </xsl:copy>
</xsl:template>
<!-- Unterdrücken anderer Elemente -->
<xsl:template match="* | text()"/>

<!-- Frage 2. Bereite eine (einfache) Abbildungsliste für Book1 vor, in der alle Abbildungen und deren Titel aufgelistet werden. Die Originalattribute der einzelnen <figure>-Elemente sollen beibehalten werden, falls welche existieren: -->
<xsl:template match="book">
  <figlist>
    <xsl:for-each select=".//figure">
      <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:copy-of select="title"/>
      </xsl:copy>
    </xsl:for-each>
  </figlist>
</xsl:template>

<!-- Frage 3. Wie viele Abschnitte (section) und wie viele Abbildungen (figure) sind in Book1? -->
<xsl:template match="/">
  <section-count><xsl:value-of select="count(//section)"/></section-count>
  <figure-count><xsl:value-of select="count(//figure)"/></figure-count>
</xsl:template>

<!-- Frage 4. Wie viele Hauptabschnitte gibt es in Book1? -->
<xsl:template match="book">
  <top_section_count>
     <xsl:value-of select="count(section)"/>
  </top_section_count>
</xsl:template>

<!-- Frage 5. Erzeuge eine einfache Liste der section-Elemente in Book1. Anstelle seiner Originalattribute soll jedes section-Element zwei Attribute haben, die den Titel des Abschnitts und die Anzahl der Abbildungen enthalten, die sich unmittelbar im Abschnitt befinden: -->
<xsl:template match="book">
  <section_list>
    <xsl:for-each select=".//section">
      <section title="{title}" figcount="{count(figure)}"/>
    </xsl:for-each>
  </section_list>
</xsl:template>

<!-- Frage 6. Erzeuge eine geschachtelte Liste der section-Elemente in Book1, wobei ihre Originalattribute und die ursprüngliche Hierarchie erhalten bleiben. Füge in jedem section-Element den Titel des Abschnitts sowie ein Element hinzu, das die Anzahl der Abbildungen enthält, die sich unmittelbar im Abschnitt befinden (siehe Beispiel "Die Lösung, so wie ich die englischen Anforderungen interpretieren würde" und Beispiel "Was der W3C-Anwendungsfall, basierend auf einem Beispielergebnis und XQuery, haben möchte"). -->

Beispiel: Die Lösung, so wie ich die englischen Anforderungen interpretieren würde.

<xsl:template match="book">
  <toc>
    <xsl:apply-templates select="section"/>
  </toc>
</xsl:template>

<xsl:template match="section">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:copy-of select="title"/>
    <figcount><xsl:value-of select="count(figure)"/></figcount>
    <xsl:apply-templates select="section"/>
  </xsl:copy>
</xsl:template>

Beispiel: Was der W3C-Anwendungsfall, basierend auf einem Beispielergebnis und XQuery, haben möchte.

<xsl:template match="book">
  <toc>
    <xsl:for-each select="//section">
      <xsl:sort select="count(ancestor::section)"/>
      <xsl:apply-templates select="."/>
    </xsl:for-each>
  </toc>
</xsl:template>

<xsl:template match="section">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:copy-of select="title"/>
    <figcount><xsl:value-of select="count(figure)"/></figcount>
    <xsl:apply-templates select="section"/>
  </xsl:copy>
</xsl:template>
  1. Anwendungsfall »SEQ«: Abfragen, die auf Sequenz basieren.
    Dieser Anwendungsfall illustriert Abfragen, basierend auf der Sequenz, in der Elemente in einem Dokument auftauchen. Obwohl die Sequenz in den traditionellsten Datenbanksystemen oder Objektsystemen nicht entscheidend ist, kann sie in strukturierten Dokumenten wichtig sein. Dieser Anwendungsfall präsentiert eine Reihe von Abfragen auf der Grundlage eines medizinischen Berichts:
<!DOCTYPE report [
  <!ELEMENT report (section*)>
  <!ELEMENT section (section.title, section.content)>
  <!ELEMENT section.title  (#PCDATA )>
  <!ELEMENT section.content  (#PCDATA | anesthesia | prep | incision | action | observation )*>
  <!ELEMENT anesthesia (#PCDATA)>
  <!ELEMENT prep ( (#PCDATA | action)* )>
  <!ELEMENT incision ( (#PCDATA | geography | instrument)* )>
  <!ELEMENT action ( (#PCDATA | instrument )* )>
  <!ELEMENT observation (#PCDATA)>
  <!ELEMENT geography (#PCDATA)>
  <!ELEMENT instrument (#PCDATA)>
]>
<report>
  <section>
    <section.title>Procedure</section.title>
    <section.content>The patient was taken to the operating room where she was placed in supine position and <anesthesia>induced under general anesthesia.</anesthesia> <prep><action>A Foley catheter was placed to decompress the bladder</action> and the abdomen was then prepped and draped in sterile fashion.</prep> <incision>A curvilinear incision was made <geography>in the midline immediately infraumbilical</geography> and the subcutaneous issue was divided <instrument>using electrocautery.</instrument></incision> The fascia was identified and <action>#2 0 Maxon stay sutures were placed on each side of the midline.</action> <incision>The fascia was divided using <instrument>electrocautery</instrument> and the peritoneum was entered.</incision> <observation>The small bowel was identified.</observation> and <action> the <instrument>Hasson trocar</instrument> was placed under direct visualization.</action> <action>The <instrument>trocar</instrument> was secured to the fascia using the stay sutures.</action></section.content>
  </section>
</report>
<!-- Frage 1. Welche Instrumente wurden im Abschnitt Procedure von Report1 beim zweiten Einschnitt (incision) verwendet? -->
<xsl:template match="section[section.title = 'Procedure']">
  <xsl:copy-of select="(.//incision)[2]/instrument"/>
</xsl:template>
<!-- Frage 2. Welches sind die ersten beiden Instrumente, die im Abschnitt Procedure von Report1 zum Einsatz kamen? -->
<xsl:template match="section[section.title = 'Procedure']">
  <xsl:copy-of select="(.//instrument)[position( ) <= 2]"/>
</xsl:template>
<!-- Frage 3. Welche Instrumente wurden in Report1 bei den ersten beiden Aktionen nach dem zweiten Einschnitt verwendet? -->
<xsl:template match="report">
  <!-- i2 = Zweiter Einschnitt im gesamten Bericht -->
  <xsl:variable name="i2" select="(.//incision)[2]"/>
  <!-- Beziehe die Instrumente, die in den ersten beiden Aktionen verwendet werden, die auf i2 folgen -->
  <xsl:copy-of select="($i2/following::action)[position( ) &lt;= 2]/instrument"/>
</xsl:template>
<!-- Frage 4. Suche in Report1 "Procedure"-Abschnitte, in denen vor dem ersten Einschnitt (incision) kein anesthesia-Element auftaucht: -->
<xsl:template match="section[section.title = 'Procedure']">
  <xsl:variable name="i1" select="(.//incision)[1]"/>
  <xsl:if test=".//anesthesia[preceding::incision = $i1]">
    <xsl:copy-of select="current( )"/>
  </xsl:if>
</xsl:template>
<!-- Wenn das Ergebnis nicht leer ist, kommt es in Kürze zur Anklage! Frage 5. Was passierte in Report1 zwischen dem ersten und dem zweiten Schnitt? -->
<xsl:template match="report">
  <critical_sequence>
    <!-- i1 = Erster Einschnitt im gesamten Bericht -->
    <xsl:variable name="i1" select="(.//incision)[1]"/>
    <!-- i2 = Zweiter Einschnitt im gesamten Bericht -->
    <xsl:variable name="i2" select="(.//incision)[2]"/>
    <!-- Kopiere alle auf i1 folgenden Geschwisterknoten, die kein Vorgängerelement i2 haben und nicht selbst i2 sind -->
    <xsl:for-each select="$i1/following-sibling::node( ) [not(./preceding::incision = $i2) and not(. = $i2)]">
      <xsl:copy-of select="."/>
    </xsl:for-each>
  </critical_sequence>
</xsl:template>

Achtung!
In den Fragen 4 und 5 gehe ich davon aus, dass die Stringwerte der incision-Elemente eindeutig sind. Dies stimmt für die Beispieldaten auch, muss aber im allgemeinsten Fall nicht wahr sein. Um genau zu sein, sollten Sie das Rezept Den letzten Tag des Monats ermitteln anwenden. In Frage 4 sollte der Test beispielsweise lauten:

test=".//anesthesia[count(./preceding::incision | $i1) = count(./preceding::incision)]"
  1. Anwendungsfall »R«: Zugriff auf relationale Daten.
    Eine wichtige Anwendung einer XML-Abfragesprache ist der Zugriff auf Daten, die in relationalen Datenbanken gespeichert sind. Dieser Anwendungsfall beschreibt eine mögliche Methode, mit der dieser Zugriff erreicht werden kann. Ein relationales Datenbanksystem könnte eine Ansicht präsentieren, in der jede Tabelle (Relation) die Form eines XML-Dokuments annimmt. Eine Möglichkeit, um eine Datenbanktabelle als XML-Dokument zu repräsentieren, besteht darin, es dem Document-Element zu erlauben, die Tabelle selbst zu repräsentieren, und jede Zeile (Tupel) in der Tabelle durch ein geschachteltes Element zu repräsentieren. In den Tupel-Elementen wiederum wird jede Spalte durch ein geschachteltes Element repräsentiert. Spalten, die Leerwerte zulassen, werden durch optionale Elemente repräsentiert, ein fehlendes Element bedeutet einen Leerwert.

    Betrachten Sie beispielsweise eine relationale Datenbank, die von einer Online-Auktion verwendet wird. Es gibt eine USERS-Tabelle, die Informationen über registrierte Benutzer enthält, die jeweils durch eine eindeutige Benutzer-ID identifiziert werden. Jeder Benutzer kann entweder Objekte zum Verkauf anbieten oder für Objekte bieten. Eine ITEMS-Tabelle listet Objekte auf, die momentan zum Kauf angeboten werden oder vor kurzem zum Kauf angeboten wurden. Dabei ist die Benutzer-ID desjenigen Benutzers enthalten, der das jeweilige Objekt angeboten hat. Eine BIDS-Tabelle enthält alle offiziell abgegebenen Gebote, aufgeschlüsselt nach der Benutzer-ID des Bieters und der Nummer des Objekts, für das das Gebot abgegeben wurde.

    Aufgrund der großen Anzahl von Abfragen in diesem Anwendungsfall werden Sie nur eine Teilmenge implementieren. Das Implementieren der anderen Teilmenge ist eine nette Übung, falls Sie Ihre XSLT-Fertigkeiten festigen wollen (siehe die drei folgenden Beispiele).

Beispiel: users.xml

<users>
  <user_tuple>
    <userid>U01</userid>
    <name>Tom Jones</name>
    <rating>B</rating>
  </user_tuple>
  <user_tuple>
    <userid>U02</userid>
    <name>Mary Doe</name>
    <rating>A</rating>
  </user_tuple>
  <user_tuple>
    <userid>U03</userid>
    <name>Dee Linquent</name>
    <rating>D</rating>
  </user_tuple>
  <user_tuple>
    <userid>U04</userid>
    <name>Roger Smith</name>
    <rating>C</rating>
  </user_tuple>
  <user_tuple>
    <userid>U05</userid>
    <name>Jack Sprat</name>
    <rating>B</rating>
  </user_tuple>
  <user_tuple>
    <userid>U06</userid>
    <name>Rip Van Winkle</name>
    <rating>B</rating>
  </user_tuple>
</users>

Beispiel: items.xml

<items>
  <item_tuple>
    <itemno>1001</itemno>
    <description>Red Bicycle</description>
    <offered_by>U01</offered_by>
    <start_date>99-01-05</start_date>
    <end_date>99-01-20</end_date>
    <reserve_price>40</reserve_price>
  </item_tuple>
  <item_tuple>
    <itemno>1002</itemno>
    <description>Motorcycle</description>
    <offered_by>U02</offered_by>
    <start_date>99-02-11</start_date>
    <end_date>99-03-15</end_date>
    <reserve_price>500</reserve_price>
  </item_tuple>
  <item_tuple>
    <itemno>1003</itemno>
    <description>Old Bicycle</description>
    <offered_by>U02</offered_by>
    <start_date>99-01-10</start_date>
    <end_date>99-02-20</end_date>
    <reserve_price>25</reserve_price>
  </item_tuple>
  <item_tuple>
    <itemno>1004</itemno>
    <description>Tricycle</description>
    <offered_by>U01</offered_by>
    <start_date>99-02-25</start_date>
    <end_date>99-03-08</end_date>
    <reserve_price>15</reserve_price>
  </item_tuple>
  <item_tuple>
    <itemno>1005</itemno>
    <description>Tennis Racket</description>
    <offered_by>U03</offered_by>
    <start_date>99-03-19</start_date>
    <end_date>99-04-30</end_date>
    <reserve_price>20</reserve_price>
  </item_tuple>
  <item_tuple>
    <itemno>1006</itemno>
    <description>Helicopter</description>
    <offered_by>U03</offered_by>
    <start_date>99-05-05</start_date>
    <end_date>99-05-25</end_date>
    <reserve_price>50000</reserve_price>
  </item_tuple>
  <item_tuple>
    <itemno>1007</itemno>
    <description>Racing Bicycle</description>
    <offered_by>U04</offered_by>
    <start_date>99-01-20</start_date>
    <end_date>99-02-20</end_date>
    <reserve_price>200</reserve_price>
  </item_tuple>
  <item_tuple>
    <itemno>1008</itemno>
    <description>Broken Bicycle</description>
    <offered_by>U01</offered_by>
    <start_date>99-02-05</start_date>
    <end_date>99-03-06</end_date>
    <reserve_price>25</reserve_price>
  </item_tuple>
</items>

Beispiel: bids.xml

<bids>
  <bid_tuple>
    <userid>U02</userid>
    <itemno>1001</itemno>
    <bid>35</bid>
    <bid_date>99-01-07 </bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U04</userid>
    <itemno>1001</itemno>
    <bid>40</bid>
    <bid_date>99-01-08</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U02</userid>
    <itemno>1001 </itemno>
    <bid>45</bid>
    <bid_date>99-01-11</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U04</userid>
    <itemno>1001</itemno>
    <bid>50</bid>
    <bid_date>99-01-13</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U02</userid>
    <itemno>1001</itemno>
    <bid>55</bid>
    <bid_date>99-01-15</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U01</userid>
    <itemno>1002</itemno>
    <bid>400</bid>
    <bid_date>99-02-14</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U02</userid>
    <itemno>1002</itemno>
    <bid>600</bid>
    <bid_date>99-02-16</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U03</userid>
    <itemno>1002</itemno>
    <bid>800</bid>
    <bid_date>99-02-17</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U04</userid>
    <itemno>1002</itemno>
    <bid>1000</bid>
    <bid_date>99-02-25</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U02</userid>
    <itemno>1002</itemno>
    <bid>1200</bid>
    <bid_date>99-03-02</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U04</userid>
    <itemno>1003</itemno>
    <bid>15</bid>
    <bid_date>99-01-22</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U05</userid>
    <itemno>1003</itemno>
    <bid>20</bid>
    <bid_date>99-02-03</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U01</userid>
    <itemno>1004</itemno>
    <bid>40</bid>
    <bid_date>99-03-05</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U03</userid>
    <itemno>1007</itemno>
    <bid>175</bid>
    <bid_date>99-01-25</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U05</userid>
    <itemno>1007</itemno>
    <bid>200</bid>
    <bid_date>99-02-08</bid_date>
  </bid_tuple>
  <bid_tuple>
    <userid>U04</userid>
    <itemno>1007</itemno>
    <bid>225</bid>
    <bid_date>99-02-12</bid_date>
  </bid_tuple>
</bids>
<!-- Frage 1. Liste die Objektnummer und die Beschreibung aller Fahrräder auf, die momentan in einer Auktion angeboten werden, sortiert nach Objektnummer: -->
<xsl:include href="../date/date.date-time.xslt"/>
<!-- Damit das Ergebnis wie das W3C-Beispiel aussieht -->
<xsl:param name="today" select="'1999-01-21'"/>
<xsl:template match="items">
  <xsl:variable name="today-abs">
    <xsl:call-template name="date:date-to-absolute-day">
      <xsl:with-param name="date" select="$today"/>
    </xsl:call-template>
  </xsl:variable>
  <result>
    <xsl:for-each select="item_tuple">
      <xsl:sort select="itemno" data-type="number"/>
      <xsl:variable name="start-abs">
        <xsl:call-template name="date:date-to-absolute-day">
          <xsl:with-param name="date" select="start_date"/>
        </xsl:call-template>
      </xsl:variable>
      <xsl:variable name="end-abs">
        <xsl:call-template name="date:date-to-absolute-day">
          <xsl:with-param name="date" select="end_date"/>
        </xsl:call-template>
      </xsl:variable>
      <xsl:if test="$start-abs &lt;= $today-abs and $end-abs &gt;= $today-abs and contains(description, 'Bicycle')">
        <xsl:copy>
          <xsl:copy-of select="itemno"/>
          <xsl:copy-of select="description"/>
        </xsl:copy>
      </xsl:if>
    </xsl:for-each>
  </result>
</xsl:template>

<!-- Frage 2. Liste für alle Fahrräder die Objektnummer, die Beschreibung und das höchste Gebot (falls vorhanden) auf, sortiert nach Objektnummer: -->
<xsl:include href="../math/math.max.xslt"/>
<xsl:template match="items">
  <result>
    <xsl:for-each select="item_tuple[contains(description,'Bicycle')]">
      <xsl:sort select="itemno" data-type="number"/>
      <xsl:variable name="bids" select="document('bids.xml')//bid_tuple[itemno=current()/itemno]/bid"/>
      <xsl:variable name="high-bid">
        <xsl:call-template name="math:max">
          <xsl:with-param name="nodes" select="$bids"/>
        </xsl:call-template>
      </xsl:variable>
      <xsl:copy>
        <xsl:copy-of select="itemno"/>
        <xsl:copy-of select="description"/>
        <high_bid><xsl:if test="$bids"><xsl:value-of select="$high-bid"/></xsl:if></high_bid>
      </xsl:copy>
    </xsl:for-each>
  </result>
</xsl:template>

<!-- Frage 3. Suche Fälle, in denen ein Benutzer mit einer Bewertung schlechter (alphabetisch: größer) als "C" ein Objekt mit einem Mindestpreis von mehr als 1.000 anbietet: -->
<!-- Nicht unbedingt notwendig, aber die Spezifikation definiert kein Bewertungssystem, weshalb wir es dynamisch ableiten! -->
<xsl:variable name="ratings">
  <xsl:for-each select="document('users.xml')//user_tuple/rating">
    <xsl:sort select="." data-type="text"/>
    <xsl:if test="not(. = ./preceding::rating)">
      <xsl:value-of select="."/>
    </xsl:if>
  </xsl:for-each>
</xsl:variable>
<xsl:template match="items">
  <result>
    <xsl:for-each select="item_tuple[reserve_price &gt; 1000]">
      <xsl:variable name="user" select="document('users.xml')//user_tuple[userid = current()/offered_by]"/>
      <xsl:if test="string-length(substring-before($ratings,$user/rating)) &gt; string-length(substring-before($ratings,'C'))">
        <warning>
          <xsl:copy-of select="$user/name"/>
          <xsl:copy-of select="$user/rating"/>
          <xsl:copy-of select="description"/>
          <xsl:copy-of select="reserve_price"/>
        </warning>
      </xsl:if>
    </xsl:for-each>
  </result>
</xsl:template>

<!-- Frage 4. Liste Objektnummern und Beschreibungen von Objekten auf, für die es keine Gebote gibt: -->
<xsl:template match="items">
  <result>
    <xsl:for-each select="item_tuple">
      <xsl:if test="not(document('bids.xml')//bid_tuple[itemno = current()/itemno])">
        <no_bid_item>
          <xsl:copy-of select="itemno"/>
          <xsl:copy-of select="description"/>
        </no_bid_item>
      </xsl:if>
    </xsl:for-each>
  </result>
</xsl:template>
  1. Anwendungsfall »SGML«: Standard Generalized Markup Language.
    Das Beispieldokument und die Abfragen in diesem Anwendungsfall wurden im Jahre 1992 für eine Konferenz über die Standard Generalized Markup Language (SGML) geschaffen. Für Ihre Benutzung wurden die Document Type Definition (DTD) und das Beispieldokument von SGML nach XML übersetzt.

    Dieses Kapitel implementiert diese Abfragen nicht, da sie sich nicht wesentlich von den Abfragen in anderen Anwendungsfällen unterscheiden.

  2. Anwendungsfall »TEXT«: Volltextsuche.
    Dieser Anwendungsfall basiert auf Unternehmensprofilen und einer Menge neuer Dokumente, die Daten für die Öffentlichkeitsarbeit, Fusionen und Zukäufe enthalten. Bei einem Unternehmen illustriert der Anwendungsfall mehrere unterschiedliche Abfragen für das Durchsuchen von Text in News-Dokumenten und unterschiedliche Methoden, um Abfrageergebnisse zu liefern, indem die Informationen aus dem Unternehmensprofil und den News-Inhalten gefiltert werden.

    In diesem Anwendungsfall werden die Suchen nach Unternehmensnamen als wortbasiert interpretiert. Die Wörter in einem Unternehmensnamen können beliebig groß- oder kleingeschrieben und durch beliebige Leerräume getrennt werden.

    Alle Abfragen können in XSLT 1.0 ausgedrückt werden. Dadurch ist es aber möglicherweise erforderlich, eine Menge Textsuchen durchzuführen. Beispielsweise erfordern die schwierigsten Abfragen einen Mechanismus zum Testen, ob ein Mitglied einer Menge von Textwerten in einem anderen String existiert. Darüber hinaus erfordern viele Abfragen das Testen von Text-Untereinheiten, wie etwa Satzgrenzen.

    Aufbauend auf den Techniken, die unter XPath behandelt wurden, sollte es klar sein, dass diese Probleme Lösungen in XSLT haben. Falls Sie jedoch viele Textabfragen in XSLT durchführen wollen, benötigen Sie eine generische Bibliothek mit Dienstprogrammen zur Textsuche. Das Entwickeln generischer Bibliotheken ist der Schwerpunkt von Kapitel XSLT erweitern und einbetten, in dem einige der komplexesten Textabfragen noch einmal untersucht werden. Für den Augenblick werden Sie zwei der einfachsten Textsuchprobleme im W3C-Dokument lösen. In diesem Kapitel werden die anderen aufgelistet, um Ihnen ein Gefühl dafür zu vermitteln, weshalb diese Abfragen für XSLT 1.0 eine Herausforderung sein können. Die schwierigen Teile sind hervorgehoben.

<!-- Frage 1. Suche alle News-Objekte, in denen der Name "Foobar Corporation" im Titel auftaucht: -->
<xsl:template match="news">
  <result>
    <xsl:copy-of select="news_item/title[contains(., 'Foobar Corporation')]"/>
  </result>
</xsl:template>

<!-- Frage 2. Erzeuge für jedes News-Objekt, das für die Gorilla Corporation relevant ist, ein "item summary"-Element (Objektzusammenfassung). Der Inhalt der Objektzusammenfassung besteht aus dem Titel, dem Datum und dem ersten Absatz des News-Objekts, getrennt durch Punkte. Ein News-Objekt ist relevant, wenn der Name des Unternehmens irgendwo im Inhalt des News-Objekts erwähnt wird: -->
<xsl:template match="news">
  <result>
    <xsl:for-each select="news_item[contains(content,'Gorilla Corporation')]">
      <item_summary>
        <xsl:value-of select="normalize-space(title)"/>. <xsl:text/>
        <xsl:value-of select="normalize-space(date)"/>. <xsl:text/>
        <xsl:value-of select="normalize-space(content/par[1])"/>
      </item_summary>
    </xsl:for-each>
  </result>
</xsl:template>
  1. Anwendungsfall »PARTS«: rekursive Auflösung von Teilen.
    Dieser Anwendungsfall verdeutlicht, wie eine rekursive Abfrage aus den flachen Strukturen, die in einer Datenbank gespeichert sind, ein hierarchisches Dokument beliebiger Tiefe konstruieren kann.

    Dieser Anwendungsfall basiert auf einer Datenbank, die Informationen darüber enthält, wie Teile in anderen Teilen eingesetzt werden.

    Die Eingabe für diesen Anwendungsfall ist ein »flaches« (d.h. einfaches) Dokument, in dem jedes unterschiedliche Teil durch ein <part>-Element mit partid- und name-Attributen repräsentiert wird. Jedes Teil kann Bestandteil eines größeren Teils sein, muss es aber nicht sein. Ist es jedoch der Fall, dann ist die partid des größeren Teils in einem partof-Attribut enthalten. Dieses Eingabedokument könnte aus einer relationalen Datenbank abgeleitet worden sein, in der jedes Teil durch eine Tabellenzeile mit partid als primärem Schlüssel und partof als Fremdschlüssel, der partid referenziert, repräsentiert wird. Die Herausforderung dieses Anwendungsfalls besteht darin, eine Abfrage zu schreiben, die die »flache« Repräsentation der Teileauflösung basierend auf den Fremdschlüsseln in eine hierarchische Repräsentation umwandelt, in der die Teilezugehörigkeit durch die Dokumentstruktur verdeutlicht wird. Die Eingabedatenmenge verwendet die folgende DTD:

<!partlist DOCTYPE [
    <!ELEMENT partlist (part*)>
    <!ELEMENT part EMPTY>
    <!ATTLIST part
        partid  CDATA  #REQUIRED
        partof  CDATA  #IMPLIED
        name    CDATA  #REQUIRED>
]>

Obwohl die Attribute partid und partof vom Typ ID bzw. IDREF sein könnten, werden sie in diesem Schema als Zeichendaten behandelt, möglicherweise weil sie auf einfache Weise einer relationalen Datenbank entnommen werden können. Jedes partof-Attribut filtert genau eine partid. Teile, die kein partof-Attribut besitzen, sind nicht in einem anderen Teil enthalten.

Die Ausgabedaten genügen der folgenden DTD:

<!DOCTYPE parttree [
    <!ELEMENT parttree (part*)>
    <!ELEMENT part (part*)>
    <!ATTLIST part
        partid  CDATA  #REQUIRED
        name    CDATA  #REQUIRED>
]>

Beispieldaten, die dieser DTD genügen, könnten so aussehen:

<?xml version="1.0" encoding="ISO-8859-1"?>
<partlist>
  <part partid="0" name="car"/>
  <part partid="1" partof="0" name="engine"/>
  <part partid="2" partof="0" name="door"/>
  <part partid="3" partof="1" name="piston"/>
  <part partid="4" partof="2" name="window"/>
  <part partid="5" partof="2" name="lock"/>
  <part partid="10" name="skateboard"/>
  <part partid="11" partof="10" name="board"/>
  <part partid="12" partof="10" name="wheel"/>
  <part partid="20" name="canoe"/>
</partlist>

<!-- Frage 1. Konvertiere das Beispieldokument aus dem "partlist"- in das "parttree"-Format (siehe den DTD-Abschnitt für Definitionen). Im resultierenden Dokument wird die Teilezugehörigkeit dadurch repräsentiert, dass ein <part>-Element in einem anderen enthalten ist. Jedes Teil, das nicht Bestandteil eines anderen Teils ist, sollte als separates Top-Level-Dokument im Ausgabedokument auftauchen: -->
<xsl:template match="partlist">
  <parttree>
    <!-- Beginne mit dem Teil, das nicht Bestandteil eines anderen ist -->
    <xsl:apply-templates select="part[not(@partof)]"/>
  </parttree>
</xsl:template>
<xsl:template match="part">
  <part partid="{@partid}" name="{@name}">
    <xsl:apply-templates select="../part[@partof = current( )/@partid]"/>
  </part>
</xsl:template>
<!-- Es stellt sich heraus, dass diese Art der Transformation in XSLT einfacher zu kodieren und zu verstehen ist als in XQuery. Zum Vergleich kommt hier die XQuery-Lösung, die vom W3C-Artikel angeboten wird: -->
define function one_level (element $p) returns element
{
    <part partid="{ $p/@partid }" name="{ $p/@name }" >
        {
            for $s in document("partlist.xml")//part
            where $s/@partof = $p/@partid
            return one_level($s)
        }
    </part>
}

<parttree>
  {
    for $p in document("partlist.xml")//part[empty(@partof)]
    return one_level($p)
  }
</parttree>

Selbst ohne genaues Verständnis von XQuery sollten Sie in der Lage sein festzustellen, dass die XQuery-Lösung benötigt wird, um die Rekursion explizit zu implementieren, während die apply-templates und Mustererkennung von XSLT eine deklarativere Lösung erlauben. Sicher, der Unterschied ist nicht riesig, aber ich finde XSLT eleganter für diese Art von Problem.

  1. Anwendungsfall »REF«: Abfragen auf der Grundlage von Referenzen. (Diese Anwendungsfälle sind in der neuesten Version des W3C-Dokuments nicht mehr vorhanden.)
    Referenzen sind ein wichtiger Aspekt von XML. Dieser Anwendungsfall beschreibt eine Datenbank, in der Referenzen eine wesentliche Rolle spielen, und enthält mehrere repräsentative Abfragen, die diese Referenzen ausnutzen.

    Nehmen Sie an, dass die Datei census.xml ein Element für jede Person enthält, die in einer kürzlich erhobenen Volkszählung registriert wurde. Für jedes person-Element wer den der Name der Person, ihr Beruf und (falls vorhanden) ihr Ehepartner als Attribute aufgezeichnet. Das spouse-Attribut ist vom Typ IDREF und entspricht dem Attribut name vom Typ ID des spouse-Elements.

    Die Eltern-Kind-Beziehung zwischen Personen wird mit Hilfe der Elementhierarchie aufgezeichnet. Das Element, das ein Kind repräsentiert, ist in dem Element enthalten, das den Vater oder die Mutter des Kindes repräsentiert. Aufgrund von Todesfällen, Scheidungen und erneuten Heiraten könnte ein Kind entweder unter seinem Vater oder unter seiner Mutter vermerkt sein (niemals unter beiden). In dieser Übung enthält der Term »Kinder von X« auch »Kinder des Ehepartners von X«. Falls beispielsweise Joe und Martha Ehepartner sind, enthält Joes Element ein Element Sam und Marthas Element ein Element Dave. Sam und Dave werden dann als die Kinder von Joe und Martha angesehen. Jede Person in der Volkszählung hat null, ein oder zwei Elternteile.

    Dieser Anwendungsfall beruht auf einem Eingabedokument namens census.xml mit der folgenden DTD:

<!DOCTYPE census [
  <!ELEMENT census (person*)>
  <!ELEMENT person (person*)>
  <!ATTLIST person
       name    ID      #REQUIRED
       spouse  IDREF   #IMPLIED
       job     CDATA   #IMPLIED >
]>

Beispiel: Die folgenden Volkszählungsdaten beschreiben zwei befreundete Familien, zwischen denen es zu mehreren Heiraten kam:

<census>
  <person name="Bill" job="Teacher">
    <person name="Joe" job="Painter" spouse="Martha">
      <person name="Sam" job="Nurse">
        <person name="Fred" job="Senator" spouse="Jane"></person>
      </person>
      <person name="Karen" job="Doctor" spouse="Steve"></person>
    </person>
    <person name="Mary" job="Pilot">
      <person name="Susan" job="Pilot" spouse="Dave"></person>
    </person>
  </person>
  <person name="Frank" job="Writer">
    <person name="Martha" job="Programmer" spouse="Joe">
      <person name="Dave" job="Athlete" spouse="Susan"></person>
      <person name="John" job="Artist">
        <person name="Helen" job="Athlete"></person>
        <person name="Steve" job="Accountant" spouse="Karen">
          <person name="Jane" job="Doctor" spouse="Fred"></person>
        </person>
      </person>
    </person>
  </person>
</census>

<!-- Frage 1. Suche Marthas Ehepartner: -->
<xsl:strip-space elements="*"/>
<xsl:template match="person[@spouse='Martha']">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
  </xsl:copy>
</xsl:template>

<!-- Frage 2. Suche Eltern von Sportlern: -->
<xsl:template match="census">
  <xsl:variable name="everyone"  select="//person"/>
  <result>
  <!-- Für jede Person mit Kindern -->
    <xsl:for-each select="$everyone[person]">
      <xsl:variable name="spouse" select="$everyone[@spouse=current( )/@name]"/>
      <xsl:if test="./person/@job = 'Athlete' or $spouse/person/@job = 'Athlete'">
        <xsl:copy>
          <xsl:copy-of select="@*"/>
        </xsl:copy>
      </xsl:if>
    </xsl:for-each>
  </result>
</xsl:template>

<!-- Frage 3. Suche Leute, die den gleichen Beruf haben wie einer ihrer Elternteile: -->

Versuchen Sie es selbst.

<!-- Frage 4. Liste Namen von Eltern und Kindern auf, die den gleichen Beruf haben, und liste deren Berufe auf: -->
<xsl:template match="census">
  <xsl:variable name="everyone"  select="//person"/>
  <result>
    <!-- Für jede Person mit Kindern -->
    <xsl:for-each select="$everyone[person]">
      <xsl:variable name="spouse" select="$everyone[@spouse=current( )/@name]"/>
      <xsl:apply-templates select="person[@job = current( )/@job]">
        <xsl:with-param name="parent" select="@name"/>
      </xsl:apply-templates>
      <xsl:apply-templates select="person[@job = $spouse/@job]">
        <xsl:with-param name="parent" select="$spouse/@name"/>
      </xsl:apply-templates>
    </xsl:for-each>
  </result>
</xsl:template>
<xsl:template match="person">
  <xsl:param name="parent"/>
  <match parent="{$parent}"  child="{@name}" job="{@job}"/>
</xsl:template>

<!-- Frage 5. Liste Namenspaare von Großeltern und Enkeln auf: -->
<xsl:template match="census">
  <xsl:variable name="everyone"  select="//person"/>
  <result>
    <!-- Für jedes Enkelkind -->
    <xsl:for-each select="$everyone[../../../person]">
      <!-- Hole Großelternteil1 (das garantiert existiert für jedes Enkelkind) -->
      <grandparent name="{../../@name}" grandchild="{@name}"/>
      <!-- Hole Großelternteil2, das der Ehepartner von Großelternteil1 ist, falls aufgeführt -->
      <xsl:if test="../../@spouse">
        <grandparent name="{../../@spouse}" grandchild="{@name}"/>
      </xsl:if>
      <!-- Hole die Namen des Ehepartners des Elternteils dieser Person (d.h. je nachdem die Mutter oder den Vater) -->
      <xsl:variable name="spouse-of-parent" select="../@spouse"/>
      <!-- Hole die Eltern des Ehepartners des Elternteils (spouse-of-parent), falls vorhanden -->
      <xsl:variable name="gp3" select="$everyone[person/@name=$spouse-of-parent]"/>
      <xsl:if test="$gp3">
        <grandparent name="{$gp3/@name}" grandchild="{@name}"/>
        <xsl:if test="$gp3/@spouse">
          <grandparent name="{$gp3/@spouse}" grandchild="{@name}"/>
        </xsl:if>
      </xsl:if>
    </xsl:for-each>
  </result>
</xsl:template>

<!-- Frage 6. Suche Leute ohne Kinder: -->
<xsl:strip-space elements="*"/>
<xsl:template match="census">
  <xsl:variable name="everyone"  select="//person"/>
  <result>
    <xsl:for-each select="$everyone[not(./person)]">
      <xsl:variable name="spouse" select="$everyone[@name = current( )/@spouse]"/>
      <xsl:if test="not ($spouse) or not($spouse/person)">
        <xsl:copy-of select="."/>
      </xsl:if>
    </xsl:for-each>
  </result>
</xsl:template>

<!-- Frage 7. Liste die Namen aller Nachfahren von Joe auf. Zeige jeden Nachfahren als Element mit dem Namen des Nachfahrens als Inhalt und seinem oder ihrem Familienstatus sowie der Anzahl der Kinder als Attribute. Sortiere die Nachfahren erstens in absteigender Reihenfolge anhand der Anzahl der Kinder sowie zweitens alphabetisch nach dem Namen: -->
<xsl:variable name="everyone" select="//person"/>
<xsl:template match="census">
  <result>
    <xsl:apply-templates select="//person[@name='Joe']"/>
  </result>
</xsl:template>
<xsl:template match="person">
  <xsl:variable name="all-desc">
    <xsl:call-template name="descendants">
      <xsl:with-param name="nodes" select="."/>
    </xsl:call-template>
  </xsl:variable>
  <xsl:for-each select="exsl:node-set($all-desc)/*">
    <xsl:sort select="count(./* | $everyone[@name = current( )/@spouse]/*)" order="descending" data-type="number"/>
    <xsl:sort select="@name"/>
    <xsl:variable name="mstatus" select="normalize-space(substring('No Yes',boolean(@spouse)* 3+1,3))"/>
    <person married="{$mstatus}" nkids="{count(./* | $everyone[@name = current( )/@spouse]/*)}">
      <xsl:value-of select="@name"/>
    </person>
  </xsl:for-each>
</xsl:template>
<xsl:template name="descendants">
  <xsl:param name="nodes"/>
  <xsl:param name="descendants" select="/.."/>
  <xsl:choose>
    <xsl:when test="not($nodes)">
      <xsl:copy-of select="$descendants"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="descendants">
        <xsl:with-param name="nodes" select="$nodes[position( ) &gt; 1] | $nodes[1]/person | id($nodes[1]/@spouse)/person"/>
        <xsl:with-param name="descendants" select="$descendants | $nodes[1]/person | id($nodes[1]/@spouse)/person"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Dieses Beispiel führt die Abfrage durch, aber es ist nicht schön! Komplikationen ergeben sich aus der Notwendigkeit, alle Nachfahren in einer Knotenmenge zu sammeln, damit sie sortiert werden können. Dies erzwingt die Verwendung der node-set-Erweiterungsfunktion. Es bedeutet außerdem, dass die Funktion id( ) nicht dabei hilft, den Ehepartner zu finden, da sie nur relativ zum Dokument des Knotens funktioniert. Die Knoten sind jedoch Kopien der Originalknoten und haben daher nicht das gleiche Dokument. Diese Situation zwingt Sie dazu, die spouse-Elemente auf viel umständlichere Weise zu ermitteln, indem Sie nach einer Variablen suchen, die alle person-Elemente enthält. Vergleichen Sie diese Lösung mit der folgenden XQuery-Lösung:

define function descrip (element $e) returns element
{
    let $kids := $e/* union $e/@spouse = &gt; person/*
    let $mstatus :=  if ($e[@spouse]) then "Yes" else "No"
    return
        <person married={ $mstatus } nkids={ count($kids) } &gt; { $e/@name/text( ) }</person>


define function descendants (element $e)
{
    if (empty($e/* union $e/@spouse = &gt; person/*)) 
    then $e
    else $e union descendants($e/* union $e/@spouse = &gt; person/*)
}

descrip(descendants(//person[@name = "Joe"])) sortby(@nkids descending, .)

Diskussion

XSLT 1.0

Im Gegensatz zu den meisten anderen Beispielen in diesem Buch ist dieses eine bunte Mischung vorbereiteter Mahlzeiten. Das Abfragen von XML kann so vieles bedeuten, dass es schwierig ist, generische Lösungen zu präsentieren. Das W3C hat ganz gute Arbeit geleistet, als es die Arten der Abfragen klassifizierte, die in den verschiedenen Bereichen auftauchen können. Die Demonstration dieser Abfragelösungen in XSLT sollte eine solide Grundlage für das Bearbeiten vieler Arten von Abfragelösungen bilden.

Aus Platzgründen enthielt dieses Kapitel keine XQuery-Lösungen für die gezeigten Probleme. Nichtsdestotrotz ist es lehrreich, die beiden Ansätze einander gegenüberzustellen, sodass ich den Leser ermuntere, das W3C-Query-Use-Case-Dokument zu untersuchen.

Es wäre sehr unpraktisch gewesen, für jede der implementierten Abfragen einen eigenen Kommentar anzugeben. Die meisten Leser mit grundlegenden XSLT-Kenntnissen sollten keine Probleme haben, die Lösungen zu entschlüsseln. Viele der gezeigten Lösungen besitzen alternative Lösungen in XSLT. Einige der Alternativen könnten sogar besser sein als die in diesem Kapitel vorgestellten Lösungen. Meine Lösungen wurden stark von der XQuery-Lösung beeinflusst, die im Originaldokument des W3C präsentiert wurde. Ich habe jedoch auch versucht, die verwendeten XSLT-Konstrukte zu variieren, wobei ich manchmal einen iterativen Stil bevorzugt habe (xsl:for-each) und ein anderes Mal den deklarativen Stil verwendet habe, der von Mustern und xsl:apply-templates geboten wird.

XSLT 2.0

Dieses Kapitel ist ziemlich lang, sodass ich nicht alle Lösungen mit 2.0 wiederholt habe. Es gibt jedoch einige ausgewählte Probleme, die die verschiedenen Verbesserungen verdeutlichen, die man mit den Möglichkeiten von XPath 2.0 und XSLT 2.0 erzielen kann:

  • Nutzen Sie for-each-group oder distinct-values( ), wenn Sie Duplikate eliminieren müssen.
<!-- Frage 4 (aus Beispiel "prices.xml"). Liste für jeden Autor in bib.xml den Namen des Autors und die Titel aller Bücher dieses Autors auf, und zwar in einem "result"-Element gruppiert: -->
<xsl:for-each-group select="//author" group-by=".">
  <result>
    <xsl:copy-of select="."/>
    <xsl:for-each select="/bib/book[author eq current-grouping-key( )]">
      <xsl:copy-of select="title"/>
    </xsl:for-each>
  </result>
</xsl:for-each-group>
<!-- Verwendung von distinct-values( ) -->
<xsl:for-each select="distinct-values(//author)">
  <result>
    <xsl:copy-of select="."/>
    <xsl:for-each select="/bib/book[author eq .]">
       <xsl:copy-of select="title"/>
    </xsl:for-each>
  </result>
</xsl:for-each>
  • Vermeiden Sie es, Knoten zu kopieren, indem Sie xsl:sequence benutzen.
<!-- Frage 8. Suchen Sie im Dokument books.xml alle Abschnitts-(section-) oder Kapiteltitel (chapter), die das Wort "XML" enthalten, und zwar ungeachtet ihrer Schachtelungsebene: -->
<results>
  <xsl:sequence select="(//chapter | //section)/title)[contains(.,'XML')]"/>
</results>
  • Nutzen Sie Funktionen und Sequenzen, um Abfragen zu vereinfachen.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ckbk="http://www.ora.com/XSLTCkbk">
  <xsl:key name="person-key" match="person" use="@name"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:variable name="everyone" select="//person" as="item( )*"/>
  <xsl:template match="census">
    <result>
      <xsl:apply-templates select="//person[@name='Joe']"/>
    </result>
  </xsl:template>
  <xsl:template match="person">
    <!-- Kein Bedarf für die Konvertierung von Knotenmengen. Verwenden Sie die descendants-Funktion direkt -->
    <xsl:for-each select="ckbk:descendants(.,/)[current( ) != .]">
      <xsl:sort select="count(./* | $everyone[@name = current( )/@spouse]/*)" order="descending" data-type="number"/>
      <xsl:sort select="@name"/>
      <xsl:variable name="mstatus" select="if (@spouse) then 'Yes'   else 'No'"/>
      <person married="{$mstatus}" nkids="{count(./* | key('person-key', @spouse)/*)}">
        <xsl:value-of select="@name"/>
      </person>
    </xsl:for-each>
  </xsl:template>
  <!-- Beachten Sie, dass diese Funktion einfacher ist als das Template in der XSLT 1.0-Lösung. Wir übergeben sie im Dokument, da sie in Funktionen unbekannt ist.-->
  <xsl:function  name="ckbk:descendants">
    <xsl:param name="nodes" as="item( )*"/>
    <xsl:param name="doc"/>
    <xsl:sequence select="$nodes, for $person in $nodes return ckbk:descendants( ($person/person, key('person-key', $person/@spouse,$doc)/person), $doc)"/>
  </xsl:function>
</xsl:stylesheet>

Siehe auch

Evan Lenz hat das Thema der Benutzung von XSLT anstelle von XQuery ebenfalls behandelt, siehe XQuery: Reinventing the Wheel?.

Eine ausführliche Einführung in XQuery finden Sie unter XQuery - Einführung in die Sprache zur Abfrage von XML-Datenbeständen.

  

zum Seitenanfang

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT Kochbuch" 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.

O'Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de