Lokalisierungspfade

(Auszug aus "Python & XML" von Christopher A. Jones & Fred L. Drake, Jr.)

Die meistverwendete Art von XPath-Ausdrücken sind Lokalisierungspfade (location paths). Einen Lokalisierungspfad kann man sich ähnlich zu einem Dateipfad auf der Festplatte vorstellen, aber viel abgefahrener. Während ein Pfad in Dateisystemen nur Namen von Verzeichnissen und Dateien enthalten kann, kann ein XPath-Lokalisierungspfad viel mehr angeben. Bei jedem Schritt im Pfad kann, basierend auf komplexen Tests, eine Auswahl von Knoten im Dokument vorgenommen werden, wobei das Resultat aus mehreren Knoten bestehen kann. Die Tests, oder Prädikate, können bei jedem Schritt im Pfad zutreffen oder nicht – je nach Elementname, Existenz oder Wert eines Attributs oder je nach Textinhalt.

Die vollständige Syntax von Lokalisierungspfaden ist komplex, aber die Spezifikation berücksichtigt dies insofern, als abgekürzte Formen für die am häufigsten benutzten Tests definiert werden. Diese werden abgekürzte Lokalisierungspfade genannt. Alle Lokalisierungspfade, die wir in diesem Abschnitt beschreiben, verwenden die abgekürzte Syntax; weitere Informationen zur vollen Syntax und zu den Auswahlmöglichkeiten von XPath entnehmen Sie bitte der Spezifikation.

Lokalisierungspfade werden in XSLT-Elementen verwendet, können aber auch programmatisch mit einer XPath-API benutzt werden, um Knotenmengen eines XML-Dokuments zur Laufzeit zurückzugeben. Letzteres wird in diesem Abschnitt noch detailliert behandelt werden, während ersteres in Transformation von XML mit XSLT vorgestellt wird.

Ein Beispieldokument

Beginnen wir mit einem Beispieldokument, das Datensätze darstellt. Die Datensätze sind alle ziemlich ähnlich, aber natürlich sind die Feldwerte bei jedem anders. Dies ist typisch für die Art von Dokumenten, in denen Sie mit XPath suchen werden. Im folgenden Beispiel wenden wir XPath-Ausdrücke auf XML-Dokumente an, die Raumschiffe aus beliebten Science-Fiction-Fernsehserien darstellen.

Beispiel: ships.xml

<?xml version="1.0" encoding="UTF-8"?>
<shiptypes name="United Federation of Planets">
  <ship name="USS Enterprise">
    <class>Sovereign</class>
    <captain>Jean-Luc Picard</captain>
    <registry-code>NCC-1701-E</registry-code>
  </ship>
  <ship name="USS Voyager">
    <class>Intrepid</class>
    <captain>Kathryn Janeway</captain>
    <registry-code>NCC-74656</registry-code>
  </ship>
  <ship name="USS Enterprise">
    <class>Galaxy</class>
    <captain>Jean-Luc Picard</captain>
    <registry-code>NCC-1701-D</registry-code>
  </ship>
  <ship name="USS Enterprise">
    <class>Constitution</class>
    <captain>James T. Kirk</captain>
    <registry-code>NCC-1701</registry-code>
  </ship>
  <ship name="USS Sao Paulo">
    <class>Defiant</class>
    <captain>Benjamin L. Sisko</captain>
    <registry-code>NCC-75633</registry-code>
  </ship>
</shiptypes>

Ein Pfad-Hosting-Skript

Die Datei ships.xml ist ein gutes Beispiel für XML-Daten, bei denen man solche Pfade ausprobieren kann. Nun können Sie ein kleines Programm schreiben, um Pfadausdrücke auf das Dokument anzuwenden und die Knoten zu untersuchen, die zurückgegeben werden. Im nächsten Beispiel erstellen wir ein kleines Skript, xp.py, das die Funktion xml.xpath.Evaluate aufruft, die in 4Suite und neueren Versionen von PyXML enthalten ist.

Beispiel: xp.py

"""
xp.py (benötigt ein XML-Dokument auf STDIN)
"""
import sys

from xml.dom.ext.reader import PyExpat
from xml.xpath import Evaluate

path0 = "ship/captain" # alle captain-Elemente

reader = PyExpat.Reader( )
dom = reader.fromStream(sys.stdin)

captain_elements = Evaluate(path0, dom.documentElement)
for element in captain_elements:
  print "Element: ", element

Um dieses Programm zu starten, müssen Sie als Eingabe die zuvor in Beispiel ships.xml erzeugte Datei ships.xml angeben:

 $ python xp.py < ships.xml 

Im Beispiel xp.py wird der Pfad ship/captain dazu benutzt, alle captain-Elemente aus dem Dokument ships.xml zu extrahieren. Das Ergebnis ist eine Knotenliste, die folgendes enthält:

<captain>Jean-Luc Picard</captain>
<captain>Kathryn Janeway</captain>
<captain>Jean-Luc Picard</captain>
<captain>James T. Kirk</captain>
<captain>Benjamin L. Sisko</captain>

Natürlich ist dies kein komplettes oder eigenständiges Dokument, sondern nur eine Knotenliste. Diese Knoten werden vom restlichen Code im Programm bearbeitet:

captain_elements = Evaluate(path0, dom.documentElement)
for element in captain_elements:
  print "Element: ", element

Der Pfad ship/captain ist ein relativer Lokalisierungspfad, da er keinen genauen Ort von der Wurzel bis zum Element angibt, wie es in /shiptypes/ship/captain der Fall ist. Der Ausdruck ship/captain gibt captain-Elemente zurück, die Kinder eines ship-Elements sind – relativ zum Dokumentknoten, der an Evaluate übergeben wurde.

Zugriff auf Zeichendaten

Oft werden Sie nach Text unterhalb eines Elements suchen. Sie könnten z. B. nur nach dem Namen des Captains statt nach dem Elementknoten suchen wollen. Dann könnten Sie Ihrem Ausdruck die text-Funktion von XPath hinzufügen:

path1 = "ship/captain/text( )"

Diese Ergänzung zum Pfadausdruck wählt alle Textknoten unterhalb des captain-Elements aus. Wenn Sie die ursprünglichen Produktionszeilen durch den folgenden Code ersetzen:

captainnodes = Evaluate(path1, dom.documentElement)
for captainnode in captainnodes:
  print "Sternenflotten-Captain: ", captainnode.nodeValue

sehen Sie folgendes Resultat:

$ python xp.py < ships.xml
Sternenflotten-Captain: Jean-Luc Picard
Sternenflotten-Captain: Kathryn Janeway
Sternenflotten-Captain: Jean-Luc Picard
Sternenflotten-Captain: James T. Kirk
Sternenflotten-Captain: Benjamin L. Sisko

Angeben eines Index

Wenn Sie viel mit Daten arbeiten, interessieren Sie sich oftmals für die Position von Elementen in Spalten, Zeilen oder Arrays. XML macht da keine Ausnahme. XPath bietet indexierte Elemente mit einer Syntax ähnlich zu der von Arrayindizes, aber es ist wichtig zu wissen, daß XPath-Indizes bei eins anfangen, während in Python Sequenzindizes bei null beginnen. Um über einen Index auf ein Element zuzugreifen, verwenden Sie eckige Klammern neben dem Elementnamen:

 path2 = "ship[2]/captain/text( )" 

In diesem Fall gibt ship[2] an, daß das zweite ship-Element eines jeden Elternelements von ship-Elementen seinen Textknoten unterhalb seines captain-Elements auswählen soll. Ändern Sie den Code, um die Ausgabe zu sehen:

capnode = Evaluate(path2, dom.documentElement)
print "Captain von ship[2] ist: ", capnode[0].nodeValue

Mit path2 lautet die Ausgabe:

$ python xp.py < ships.xml
Captain von ship[2] ist: Kathryn Janeway

Es ist wichtig, sich nicht von der visuellen Ähnlichkeit zwischen ship[2] und der Sequenzindexierung in Python täuschen zu lassen; beide sind sehr verschieden! Tatsächlich ist die Notation eine Abkürzung für ship[position( )=2], was anzeigt, daß das zweite ship-Kindelement irgendeines anderen Elements passen wird. Betrachten Sie das folgende XML-Fragment:

<fleet name="Atlantic">
  <ship id="id1"/>
  <lifeboat id="id2"/>
</fleet>
<fleet name="Pacific">
  <lifeboat id="id3"/>
  <ship id="id4"/>
  <ship id="id5"/>
</fleet>

Der XPath-Ausdruck ship[2] paßt nur auf das ship-Element mit einem id-Attribut von id5. Dies ist kein Trick, sondern ein sehr guter Grund, immer eine Kopie der XPath-Spezifikation in der Nähe zu haben.

Testen von Unterknoten

Eventuell möchten Sie auch den Textinhalt unter einem Elementnamen abfragen. Nehmen wir an, Sie haben eine Struktur von Buchkapiteln, jedes mit Überschriften und Absätzen. Sie möchten vielleicht nach Text suchen, der unterhalb einer bestimmten Überschrift vorkommt. XPath bietet Ihnen eine bequeme Möglichkeit, die Zeichendaten eines Textknotens zu prüfen, der das Kind eines bestimmten Elements ist. Wenn Sie nach einem Element <ship> mit einem Element <class> darunter suchen, das das Wort Intrepid enthält, könnten Sie folgenden Pfad benutzen:

 path3 = 'ship[class="Intrepid"]' 

Dieser Ausdruck sucht nach ship-Elementen, die ein class-Kindelement haben, dessen Zeichendaten Intrepid enthalten. Sie können die erhaltene Knotenliste mit ein wenig zusätzlichem Code weiter untersuchen:

shipnodes = Evaluate(path3, dom.documentElement)
for shipnode in shipnodes:   
   shipname = shipnode.getAttribute("name")
   captain = Evaluate("captain/text( )", shipnode)
   print "------------ Schiff der Intrepid-Klasse ------------"
   print "Name: ", shipname
   print "Captain: ", captain[0].nodeValue

In diesem Code suchen wir alle Schiffsknoten heraus, die ein class-Kindelement haben und angeben, daß sie Schiffe der Intrepid-Klasse sind. Dann können wir diesen Knoten erneut bearbeiten, um auch Schiffsnamen und Captains herauszusuchen, um schließlich folgende Ausgabe zu generieren:

$ python xp.py < ships.xml
------------ Schiff der Intrepid-Klasse ------------
Name: USS Voyager
Captain: Kathryn Janeway

Statt wie in path3 nur zu prüfen, ob ein Unterelement notwendige Informationen enthält, können Sie den Pfadausdruck weiter ausbauen, um nach etwas Bestimmtem unter dem passenden Element zu suchen:

 path4 = 'ship[class="Constitution"]/@name' 

Mit diesem Pfad stoßen Sie noch weiter vor. Zuerst wird ein ship-Element nur dann ausgesucht, wenn sein class-Kindelement die Zeichendaten Constitution enthält. Dieser Pfad wird erweitert, wenn wir das name-Attribut des ship-Elements auswählen, das die speziellen Kindzeichendaten enthält (das @-Symbol zeigt an, daß wir an einem Attribut und nicht an einem Kindelement interessiert sind). Wieder ändern wir den Code ein wenig, um die neue Knotenliste zu verwenden:

ship = Evaluate(path4, dom.documentElement)
print "Name des Schiffes der Constitution-Klasse: ", ship[0].nodeValue

Es folgt die Ausgabe:

$ python xp.py < ships.xml
Name des Schiffes der Constitution-Klasse: USS Enterprise

Testen von Attributen

Natürlich erfordert das Auswerten von XML-Attributen und ihren Inhalten ein etwas anderes Vorgehen als die Auswertung von Elementnamen und Zeichendaten in Textknoten. In XPath wird das @-Zeichen dazu benutzt, ein Attribut anzugeben. Eckige Klammern verwendet man, um damit den Knoten zu umgeben, wenn er auf Zeichendaten getestet wird. Um die Zeicheninhalte eines Attributs zu testen, benutzt man einen Pfad wie den folgenden:

 path5 = 'ship[@name="USS Enterprise"]' 

Dieser Ausdruck sucht alle ship-Elemente heraus, in deren name-Attribut das Wort Enterprise vorkommt. In Ihrer ships.xml-Datei gibt es drei Raumschiffe namens Enterprise, jedes mit leicht unterschiedlichen Registrierungscodes. Sie können die Knotenliste nach mehr Informationen absuchen:

ships = Evaluate(path5, dom.documentElement)   
for shipnode in ships:
   registry = Evaluate("registry-code/text( )", shipnode)
   captain = Evaluate("captain/text( )", shipnode)
   print "Enterprise gefunden mit Registrierung:", registry[0].nodeValue
   print "Captain: ", captain[0].nodeValue

Die folgenden Ausdrücke sind relative Pfade, die bei jedem Durchgang durch die Knotenliste die Texte captain und registry-code des aktuellen Elements heraussuchen. Diesmal sieht unsere Ausgabe mit dem vorherigen Code so aus:

$ python xp.py < ships.xml
Enterprise gefunden mit Registrierung: NCC-1701-E
Captain: Jean-Luc Picard
Enterprise gefunden mit Registrierung: NCC-1701-D
Captain: Jean-Luc Picard
Enterprise gefunden mit Registrierung: NCC-1701
Captain: James T. Kirk

Auswählen von Elementen

Wie bei jeder geordneten Menge von Daten sind Sie für gewöhnlich daran interessiert, eine bestimmte Art von Information aus dem gesamten Dokument zu extrahieren. Vielleicht sind Sie nur an den Namen der Angestellten in der Personal-Datenbank interessiert. Oder Sie haben es mit stark verschachtelten Daten zu tun, bei denen Sie sichergehen wollen, daß Sie sie bei jedem Vorkommen eines bestimmten Datentyps herausziehen, unabhängig von deren Position im Dokument. In XPath können Sie den Pfadausdruck // benutzen, um anzugeben, daß alle passenden Elemente unterhalb der Wurzel ausgewählt werden sollen:

 path6 = "/shiptypes//captain" 

Dieser Ausdruck wählt alle captain-Elemente unterhalb der Wurzel aus, egal wo sie auftauchen. Da Sie mit Elementen arbeiten, muß man für den Zugriff auf Zeichendaten auf die zuvor geleistete Arbeit zurückgreifen oder die Knotenstruktur traversieren:

captains = Evaluate(path6, dom.documentElement)
for captain in captains:
  print "Captain: ", captain.firstChild.nodeValue

Wenn Sie path6 ausführen, bekommen Sie folgende Ausgabe:

$ python xp.py < ships.xml
Captain: Jean-Luc Picard
Captain: Kathryn Janeway
Captain: Jean-Luc Picard
Captain: James T. Kirk
Captain: Benjamin L. Sisko

Zusätzliche Operatoren

Wenn Sie mit Dateisystempfaden unter Windows oder Unix vertraut sind, sind Ihnen vermutlich die Operatoren . und .. bereits bekannt. Der Operator . gibt das aktuelle Verzeichnis an (oder das aktuelle Element in XPath), während .. das Elternverzeichnis (oder Elternelement in XPath) bezeichnet. Mit ships.xml, das im Beispiel ships.xml gezeigt wurde, können wir nach dem Namen eines bestimmten Schiffes suchen und dann das Elternelement angeben, um zu sehen, zu welcher Organisation das Schiff gehört.

 path7 = "ship[@name='USS Voyager']/../@name" 

Dieser Ausdruck sucht nach einem ship-Element, dessen name-Attribut gleich »USS Voyager« ist. Der Pfad wählt dann das name-Attribut des Elternelements dieses Schiffes aus. In ships.xml ist dies das name-Attribut des Elements shiptypes. Ändern Sie den Code in xp.py, um eine Ausgabe zu erhalten:

org = Evaluate(path7, dom.documentElement)
print "USS Voyager gehört", org[0].nodeValue

Nun erzeugt xp.py eine Ausgabe, in der die Voyager der »Federation of Planets« zugeordnet wird:

$ python xp.py < ships.xml
USS Voyager gehört United Federation of Planets

  

<< zurück vor >>

 

 

 

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

Copyright © 2002 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 "Python & XML" 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