Erzeugung einer Cross-Referenz zu einem XML Dokument mit Python und XSLT

Mit Schlange und Stil

Manfred Knobloch

Immer wieder gibt es Situationen, in denen es hilfreich wäre, schnell mal zu sehen, welche Attribute in einem bestimmten Tag innerhalb eines XML-Dokuments vorkommen können. Sind keine DTD oder Schema zur Hand oder das Lesen einer komplexen DTD zu zeitraubend, hilft eine Cross-Referenz, die zeigt, welche Attribute ein Element haben kann, wie oft es im Dokument vorkommt, welche Kindelemente auftreten und welches die Elternelemente sind. Mit Hilfe eines kleinen Python-Skripts und eines XSLT-Stylesheets lässt sich das im Handumdrehen generieren.

Der Versuch, die Aufgabe allein mit XSLT-Mitteln zu lösen, ist sehr aufwändig. Denn zumindest XSLT 1.0 unterstützt Gruppieren nicht ohne Tricks, die auch geübten Stylesheetautoren Kopfschmerzen bereiten. Und Gruppieren von Elementen nach Namen ist eine zentrale Anforderung an ein Dokumentationswerkzeug wie das gerade beschriebene. Denn potenziell kann ein Element auf jeder Hierarchiestufe eines XML-Dokuments vorkommen und es kann jeweils verschiedene Mengen von optionalen Attributen enthalten. Es genügt also nicht, das erstbeste Auftreten eines Elements als Grundlage für eine Dokumentation zu verwenden. Es müssen alle Vorkommen im Dokument überprüft werden. Weiterhin ist es möglich, dass ein Element ein gleichartiges Element als Kindelement enthält. Ein -Element kann rekursiv weitere -Elemente enthalten. Auch das lässt sich mit XSLT-Mitteln allein schlecht analysieren und dokumentieren.

Mit Hilfe einiger Hash-Variablen (dictionary im Python-Jargon) und einem SAX-Parser ist genau dies jedoch relativ einfach zu bewerkstelligen. Als Datentypen finden sich Hash-Variablen in jeder modernen Programmiersprache, ebenso sind SAX-Parser verfügbar. Das Analyseprogramm sammelt beim Ablauf alle Elemente und deren Aufbau in solchen Variablen und gibt sie am Ende in einen XML-Datenstrom aus. Dieser kann dann durch ein XSLT-Stylesheet leicht in eine Crossreferenz umgewandelt werden. Werfen wir zunächst einen kurzen Blick auf das verwendete Python-Skript.

Was man braucht und wie es läuft

Zum Betrieb des Skripts braucht man eine Java-Laufzeitumgebung, eine Python-Laufzeitumgebung (2.1 oder höher), einen Apache Xalan XSLT-Prozessor und eine dazu passende CLASSPATH-Umgebungsvariable und die pyana-Schnittstelle zu Xalan.

Aufrufen kann man das Skript von der Kommandozeile mit:

python xmlDocumenter.py   [xsl-stylesheet] [Ausgabedatei]

Beim Durchlesen des Dokuments generiert ein SAX-Parser bei jedem Start- und Endtag ein Ereignis, das mit einer eigenen Methode behandelt werden kann. Existiert beim startElement-Ereignis noch kein Eintrag für das Element, werden für Attribute, Kind- und Elternelemente entsprechende Hash-Variablen angelegt. Als Key wird immer der Elementname eingesetzt, als Wert jeweils eine Liste der gefundenen Eltern-, Kind- bzw. Attributnamen. Für den Zähler genügt natürlich ein skalarer Wert. Da wir es mit einem flüchtigen SAX-Datenstrom zu tun haben, brauchen wir noch einen Elementstack. Beim nächsten SAX-Event dient er als Gedächtnis, das weiß, wie das vorige Element hieß und die Abpictureung von Elementhierarchien ermöglicht.

Im vorgestellten Skript wird eine dafür angepasste Klasse als Content Handler (Callback-Methode) beim Parser registriert [1]. Das folgende Codefragment zeigt die Initialisierung der Hilfsvariablen in der Content Handler-Klasse:

def __init__(self):
self.estack = ['document root']
self.tags = {} #count occurance of tagname here
self.attributes = {}
self.parents = {}
self.children = {}

Die Hauptarbeit liegt natürlich bei der startElement-Methode: Sie muss jeweils nachsehen, ob für den Elementnamen ein Eintrag existiert, dito für die im SAX-Datenstrom vorbeirauschenden Attribute. Außerdem muss Sie das aktuelle Elternelement in die Liste der Elternelemente aufnehmen und schließlich den aktuellen Elementnamen auf den Stack legen, denn im SAX-Datenstrom weiß bereits das nächste Element nichts mehr vom Aktuellen (siehe Listing 1).

Listing 1

def startElement(self, name, attr):
if not self.tags.has_key(name):
# new tag occurs, create attribute- and parents list for it
self.tags[name] = 0
self.attributes[name] = []
self.parents[name] = []
self.children[name] = []

if not self.estack[-1] in self.parents[name]:
# find parent on stack top
self.parents[name].append(self.estack[-1])

for att in attr.keys():
# check wheather att is new and keep it
if not att in self.attributes[name]:
self.attributes[name].append(att)

self.tags[name] += 1
self.estack.append(name)

Die endElement-Methode muss den Stack abräumen und darauf achten, ob der aktuell vom Stack entfernte Name als Kind im Elternelement gelistet ist.

Die main-Routine des Skripts muss lediglich einen SAX-Parser initialisieren, die angepasste ContentHandler-Klasse registrieren und einen Dateinamen übergeben. Wenn der Parsing-Vorgang korrekt abgelaufen ist, müssen die Ergebnisse noch in XML-Form gebracht werden. Das geschieht mit einigen geschachtelten Schleifen, die einen XML-String erzeugen:

output = Pyana.transform2String(source=str(xmlstring), style=str(xsl))
open(outfile, 'wb').write(output)

Hier werden der Pyana.transform2String-Methode lediglich der XML-String und der Inhalt des Stylesheets übergeben. Das Transformationsergebnis ist seinerseits eine Zeichenkette, die lediglich noch mit einem Dateinamen versehen und auf die Platte geschrieben werden muss.

Dass hier überhaupt noch eine XSLT-Transformation verwendet wurde und nicht direkt aus dem Python-Skript die HTML-Dokumentation erzeugt wird, hat seinen Sinn darin, dass es mit diesem Aufbau möglich ist, ein weiteres Stylesheet aufzurufen, das etwas anderes als HTML erzeugt. So kann leicht das Gerüst eines XSLT-Stylesheets für das Quelldokument erzeugt werden, das für jedes Element einen -Eintrag enthält und in einem Kommentar auf Attribute und Kindelemente hinweist. Auf diese Weise können den Stylesheetautoren Schreibarbeit abgenommen und Fehlerquellen vermindert werden.

  • [1] Das Beispielskript greift zwei Abschnitte aus dem Python Cookbook von Martelli & Ascher auf (O’Reilly, 2002, S. 285 ff.)
  • [2] Pyana: pyana.sourceforge.net/
Geschrieben von
Manfred Knobloch
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.