Konvertieren von XML nach HTML
(Auszug aus "Python & XML" von Christopher A. Jones & Fred L. Drake, Jr.)
Zum PyXML-Paket gehören XML-Parser wie etwa PyExpat ebenso wie Unterstützung von SAX und DOM und noch vieles mehr. Solange Sie sich noch in das PyXML-Paket einarbeiten, wäre es nett, eine vollständige Liste aller Klassen und Methoden zu haben. Da dies ein Buch über Programmierung ist, erscheint es nur passend, ein Python-Programm zu schreiben, um die Informationen zu extrahieren, die wir brauchen – und das natürlich in XML!
Erzeugen wir also eine XML-Datei, die jede der Dateien im PyXML-Paket mit ihren Klassen und deren Methoden detailliert angibt. Dieser Vorgang ermöglicht es uns, schnell brauchbares XML zu erzeugen. Statt eines Ersatzes für all diese todschicken Generatoren, die Code in Dokumentation umwandeln, zeigt das folgende Beispiel einen einfachen, schnellen Weg, XML zu erzeugen, mit dem wir experimentieren und das wir für alle weiteren Beispiele in diesem Abschnitt verwenden können. Schließlich ist es beim Umgang mit XML hilfreich, einige hunderttausend Bytes davon zum Spielen herumliegen zu haben. (Dieses Programm demonstriert auch die Leichtigkeit, mit der man all diese Dateien in einem Verzeichnisbaum mit der os.path.walk-Funktion untersuchen kann.)
Beispiel: genxml.py
"""
genxml.py
Wandert durch den PyXML-Baum, indexiert Quelldateien und
erzeugt XML-Tags zur Navigation in den Quellen.
"""
import os
import sys
from xml.sax.saxutils import escape
def quoteattr(data):
# Basiert auf quoteattr() aus xml.sax.saxutils in Python 2.2.
'''Schützt einen Attributwert und setzt ihn in Anführungszeichen.
Schützt &, < und > in einem Datenstring und setzt ihn dann in
Anführungszeichen zum Gebrauch als Attributwert. Das Zeichen "
wird wenn nötig ebenfalls geschützt.
'''
data = escape(data)
if '"' in data:
if "'" in data:
data = '"%s"' % data.replace('"', """)
else:
data = "'%s'" % data
else:
data = '"%s"' % data
return data
def process(filename, fp):
print "* Bearbeiten von:", filename,
# Parse die Datei
pyFile = open(filename)
fp.write("<file name=\"" + filename + "\">\n")
inClass = 0
line = pyFile.readline( )
while line:
line = line.strip( )
if line.startswith("class") and line[-1] == ":":
if inClass:
fp.write(" </class>\n")
inClass = 1
fp.write(" <class name='" + line[:-1] + "'>\n")
elif line.startswith("def") and line[-1] == ":" and inClass:
fp.write(" <method name='" + escape(line[:-1]) + "'/>\n")
line = pyFile.readline( )
pyFile.close( )
if inClass:
fp.write(" </class>\n")
inClass = 0
fp.write("</file>\n")
def finder(fp, dirname, names):
"""Füge Dateien im Verzeichnis dirname zu einer Liste hinzu."""
for name in names:
if name.endswith(".py"):
path = os.path.join(dirname, name)
if os.path.isfile(path):
process(path, fp)
def main( ):
print "[genxml.py gestartet]"
xmlFd = open("pyxml.xml", "w")
xmlFd.write("<?xml version=\"1.0\"?>\n")
xmlFd.write("<pyxml>\n")
os.path.walk(sys.argv[1], finder, xmlFd)
xmlFd.write("</pyxml>")
xmlFd.close( )
print "[genxml.py beendet]"
if __name__ == "__main__":
main( )
Die Funktion main in diesem Beispiel benutzt die Funktion os.path.walk, um in Ihrem PyXML-Verzeichnis nach Python-Dateien zu suchen. Für jede Python-Quelldatei, die unterhalb des Startverzeichnisses existiert (das Argument für das Skript), wird die process-Funktion aufgerufen, um Klassen-Informationen zu extrahieren. Diese Funktion schreibt die gewonnenen Informationen in die geöffnete XML-Datei.
An dieser Stelle fährt das Skript damit fort, jede Python-Quelldatei zu parsen und dabei jede der darin enthaltenen Klassen und Methoden hervorzuheben, indem jede Zeile nach relevanten Schlüsselwörtern wie class und def geparst wird:
def process(filename, fp):
print "* Bearbeiten von:", filename,
# Parse die Datei
pyFile = open(filename)
fp.write("<file name='" + filename + "'>\n")
inClass = 0
line = pyFile.readline()
while line:
line = line.strip()
Wenn das Programm eine Klassendeklaration findet, erzeugt es ein passendes class-Tag sowie passende Attribute im XML-Dokument:
if line.startswith("class") and line[-1] == ":":
if inClass:
fp.write(" </class>\n")
inClass = 1
fp.write(" <class name='" + line[:-1] + "'>\n")
Wenn das Programm auf eine Methodendefinition stößt, ersetzt es spezielle Zeichen durch Entities, damit diese im XML-Code keine Probleme bereiten. Der String der Methodendefinition wird gestutzt und dann mit den entsprechenden Auszeichnungen versehen:
elif line.startswith("def") and line[-1] == ":" and inClass:
fp.write(" <method name='" + escape(line[:-1]) + "'/>\n")
line = pyFile.readline()
Nachdem eine Datei beendet ist, schließt das Programm die letzte Klasse, in der es sich befand, und schließt ebenfalls das file-Tag:
pyFile.close()
if inClass:
fp.write(" </class>\n")
inClass = 0
fp.write("</file>\n")
Python vereinfacht das Parsen von Text. Jede Zeile wird etwas manipuliert, Anführungszeichen werden durch Entities ersetzt (mit der escape-Funktion des Moduls xml.sax.saxutils), und XML-Tags werden um Klassendefinitionen und um Methodennamen herum gesetzt.
Um dieses Programm aus der Shell zu starten:
$> python genxml.py /home/chris/PyXML/xml
Der Parameter an das Skript ist der Pfad Ihres PyXML-Quellverzeichnisses (inklusive des xml-Unterverzeichnisses).
Das erzeugte Dokument
Das erzeugte XML wird in einer Datei namens pyxml.xml abgelegt. Jedes file-Element sieht ungefähr so aus:
<file name="../xml/dom/ext/reader/Sax2Lib.py">
<class name="class LexicalHandler">
<method name="def xmlDecl(self, version, encoding, standalone)"/>
<method name="def startDTD(self, doctype, publicID, systemID)"/>
<method name="def endDTD(self)"/>
<method name="def startEntity(self, name)"/>
<method name="def endEntity(self, name)"/>
<method name="def comment(self, text)"/>
<method name="def startCDATA(self)"/>
<method name="def endCDATA(self)"/>
</class>
<class name="class EntityRefList">
<method name="def getLength(self)"/>
<method name="def getEntityName(self, index)"/>
<method name="def getEntityRefStart(self, index)"/>
<method name="def getEntityRefEnd(self, index)"/>
<method name="def __len__(self)"/>
</class>
<class name="class NamespaceHandler">
<method name="def startNamespaceDeclScope(prefix, uri)"/>
<method name="def endNamespaceDeclScope(prefix)"/>
</class>
<class name="class SAXNotSupportedException(Exception)"></class>
</file>
Beachten Sie, daß das name-Attribut des file-Tags abhängig davon variiert, was Ihr Parameter für das Skript (Ihr PyXML-Quellcodepfad) ist. Funktionen, die nicht als Methoden in einer Klasse definiert sind, werden von der einfachen Parse-Schleife nicht aufgenommen (ähm – das ist schließlich kein Compiler!), aber Sie sollten sich dessen bewußt sein, daß die XML-Unterstützung sowohl der Standardbibliothek als auch des PyXML-Pakets viele brauchbare Funktionen umfaßt – lesen Sie die Referenzdokumentation für weitere Informationen. Die escape-Funktion, die wir in diesem Skript benutzen, ist dafür eine perfektes Beispiel. Wenn Python neu für Sie ist, werden Sie feststellen, daß kleine Hilfsfunktionen charakteristisch für Python-Bibliotheken sind; die meisten der kleinen Helferlein, die man braucht, um die größeren Brocken leichter benutzbar zu machen, sind bereits enthalten, was es Ihnen gestattet, sich auf Ihre Anwendung zu konzentrieren.
Wenn Sie ein wenig Zeit damit verbringen, diese XML-Datei zu betrachten, werden Sie allmählich mit dem Anwendungsbereich des PyXML-Toolkits vertraut werden. Etwas später in diesem Kapitel wird ein Skript vorgestellt, das dieses XML in HTML konvertiert und dabei die SAX-API und Pythons Möglichkeiten der Stringbearbeitung anwendet. Die folgende Abbildung zeigt das XML in einem Browser.
Abbildung: Ausgabe von genxml.py in einem Browser
Der Konvertierungs-Handler
Sie können dieses Programm fertigstellen, indem Sie die Klasse PyXMLConversionHandler implementieren. Diese Klasse generiert HTML aus der XML-Datei, die wir zuvor erzeugt haben. Der Prozeß ermöglicht es Ihnen, die HTML-Datei in Ihren Browser zu laden und alle Dateien, Klassen und Methoden aus PyXML in formatiertem Text zu sehen. Erzeugen Sie diese Klasse, wie im nächsten Beispiel in der Datei handlers.py gezeigt.
Beispiel: handlers.py
from xml.sax import ContentHandler
class PyXMLConversionHandler(ContentHandler):
"""Ein einfacher Handler, der drei Methoden der SAX-Schnittstelle implementiert."""
def __init__(self, fp):
"""Speichere Dateiobjekt, in dem wir HTML erzeugen."""
self.fp = fp
def startDocument(self):
"""Schreibe den Anfang des HTML-Dokuments."""
self.fp.write("<html><body><b>\n")
def startElement(self, name, attrs):
if name == "file":
# Erzeuge Anfang von HTML
s = attrs.get('name', "")
self.fp.write("<p>File: %s<br>\n" % s)
elif name == "class":
self.fp.write(" " * 3 + "Class: " + attrs.get('name', "") + "<br>\n")
elif name == "method":
self.fp.write(" " * 6 + "Method: " + attrs.get('name', "") + "<br>\n")
def endDocument(self):
"""Beende das zu erzeugende HTML-Dokument."""
self.fp.write("</b></body></html>")
Die Konvertierung ist sehr einfach, aber man kann dabei die interessante Beobachtung machen, daß diese Klasse ihre Ausgabe in ein Dateiobjekt schreibt, das an den Konstruktor übergeben wird, statt einen String aus XML-Text im Speicher zusammenzusetzen. Damit vermeidet man es, einen potentiell sehr großen Puffer im Speicher zu halten und ihn inkrementell mit vielen Kopieroperationen im Speicher zusammenzubauen. Wenn verlangt wird, daß der String im Speicher sein muß, sobald der Vorgang beendet ist, kann der Erzeuger eine StringIO-Instanz als Zieldatei angeben; die Implementierung von StringIO ist effizienter beim Bau eines großen Strings als viele String-Zusammensetzungen. Dies ist ein Python-Idiom, das seinen Nutzen über viele Projekte hinweg bewiesen hat.
Betrieb des Konvertierungs-Handlers
Das Hauptskript unterscheidet sich nicht wirklich von jenen, die wir bisher gesehen haben. Wir erzeugen den Parser, instanziieren unsere Handler-Klasse, registrieren den Handler und setzen den Parser in Bewegung. Dieser Vorgang wird im folgenden Bespiel gezeigt.
Beispiel: genhtml.py
#!/usr/bin/env python
#
# Erzeugt HTML aus pyxml.xml
import sys
from xml.sax import make_parser
from handlers import PyXMLConversionHandler
dh = PyXMLConversionHandler(sys.stdout)
parser = make_parser( )
parser.setContentHandler(dh)
parser.parse(sys.stdin)
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