XML Umlaute in Python
Angenommen, du möchtest in deiner Eigenschaft als Berufscholeriker eine XML-Datei mit dem - im übrigen völlig berechtigten - Eintrag "XML ist blöd" erstellen, in Python, und das ganze später natürlich auch wieder einlesen.
Leichter gesagt als getan! Diese Aufgabe berührt einige Themenbereiche, die nicht völlig trivial sind, so zum Beispiel:
- Wie stelle ich Umlaute überhaupt in XML dar?
- Wie wird Unicode in Python behandelt?
Wie werden Umlaute in XML dargestellt?
Grundsätzlich gibt es zwei Möglichkeiten:
- Die XML-Datei kann in UNICODE verfasst sein
- Die XML-Datei kann Entities für die Umlaute definieren
Eine UNICODE XML-Datei erzeugen
In diesem Fall muß man einfach als encoding utf-8
angeben, und (natürlich)
eine UTF-8
Datei erzeugen. Hört sich einfach an! Versuchen wir folgendes:
import codecs
output = codecs.open("test.xml","wb","utf-8")
print >>output, '''<?xml version="1.0" encoding="utf-8"?>
<root>
<item>XML ist blöd</item>
</root>
'''
output.close()
Das klappt leider nicht:
Traceback (most recent call last):
File >>"test.py", line 4, in ?
print output, '''<?xml version="1.0" encoding="utf-8"?>
File "C:\Python22\lib\codecs.py", line 338, in write
return self.writer.write(data)
File "C:\Python22\lib\codecs.py", line 137, in write
data, consumed = self.encode(object, self.errors)
UnicodeError: ASCII decoding error: ordinal not in range(128)
Die Begründung für dieses Verhalten steht in der Python-Unicode-Faq. Allerdings ist die Behebung nicht offensichtlich.
Man könnte zum Beispiel versuchen, den Quelltext des Programms in UTF-8 abzuspeichern. Dann sollte der String bereits
Unicode sein, oder? Versuchen wir das. Der beste Texteditor der Welt (TM) - SciTE -
kann über den Menüpunkt File/Encoding/UTF-8
die Datei in UTF-8 abspeichern. Versucht man jetzt, das Programm
auszuführen, erscheint folgendes:
python -u utf8.py
File "utf8.py", line 1
import codecs
^
SyntaxError: invalid syntax
Offenbar kommt die aktuelle Pythonversion (2.2.2) nicht mit dem BOM zurecht. Für Python 2.3 ist eine Lösung vorgesehen: der Einsatz eines Encodings. Es gibt dazu ein PEP 263, in dem diese Lösung näher erläutert ist.
Für Python 2.2 müssen wir also eine andere Lösung suchen. Wenn man einen Quelltext mit
beispielsweise Notepad (oder Scite im 8-Bit Mode) erfasst, so wird die
Datei (in Deutschland) in der Microsoft Windows Codepage 1252 abgefasst.
Der String ist also cp1252
codiert (er liegt ja als Bytebatzen vor), und muß deshalb decodiert werden.
Unser Code schaut folglich so aus:
import codecs
output = codecs.open("test.xml","wb","utf-8")
print >>output, '''<?xml version="1.0" encoding="utf-8"?>
<root>
<item>XML ist blöd</item>
</root>
'''.decode("cp1252")
output.close()
Und, oh wunder, das klappt!
Eine XML-Datei mit Entities erzeugen
Wir wollen unseren String später sowieso auf einer Webseite anzeigen; es macht also
Sinn, das ö
als ö
zu codieren. Hört sich auch erstmal einfach an:
import codecs
output = open("test.xml","wb")
print >>output, '''<?xml version="1.0"?>
<root>
<item>XML ist blöd</item>
</root>
'''
output.close()
Aber, wenn man versucht die Datei einzulesen, erscheint beispielsweise im IE folgende Fehlermeldung:
Reference to undefined entity 'ouml'.
Error processing resource 'file:///test.xml'. Line 3, Position 22
<item>XML ist blöd</item>
---------------------^
Die Ursache ist, daß die in HTML übliche Kodierung in XML nicht einfach so zur Verfügung steht, sondern explizit definiert werden muß, als Entity. Was aber ist eine XML Entity? Dazu gibt es eine Erklärung bei Selfhtml. Jetzt muß man noch wissen, wie ö definiert wird! Dazu gibt es eine einfache und eine umständliche Möglichkeit. Einfach ist folgendes:
>>> ord("ö")
246
Daraus liest man ab, daß ö
als ö
codiert werden kann.
Die Von-Hinten-Durch-Die-Brust-Ins-Auge-Schuß-Methode geht so: Guckst du
http://www.unicode.org/charts/, klickst du alle
PDFs durch, bis du dein ö findest. Als verantwortungsvoller Autor habe ich das natürlich
getan, hier: http://www.unicode.org/charts/PDF/U0080.pdf
findest du für ö 0xF6, was dezimal 246 ist.
Erschöpft aber froh schreibt man nun:
import codecs
output = open("test.xml","wb")
print >>output, '''<?xml version="1.0"?>
<!DOCTYPE book [
<!ENTITY ouml "ö">
]>
<root>
<item>XML ist blöd</item>
</root>
'''
output.close()
Jetzt ist die XML-Datei korrekt.
Die XML-Datei einlesen
Das einlesen der XML-Datei ist in Python denkbar einfach:
import xml.sax
class handler(xml.sax.ContentHandler):
def startElement(self, name, attrs):
print "startElement:", name, attrs
def endElement(self, name):
print "endElement:", name
def characters(self, content):
print "characters:", content
xml.sax.parse("test.xml", handler())
Aber, leider leider, es gibt ein Problem:
startElement: root <xml.sax.xmlreader.AttributesImpl instance at 0x00816F80>
characters:
characters:
startElement: item <xml.sax.xmlreader.AttributesImpl instance at 0x00817050>
characters: XML ist bl
characters:
Traceback (most recent call last):
File "test.py", line 27, in ?
xml.sax.parse("test.xml", handler())
File "C:\Python22\lib\xml\sax\__init__.py", line 33, in parse
parser.parse(source)
File "C:\Python22\lib\xml\sax\expatreader.py", line 91, in parse
xmlreader.IncrementalParser.parse(self, source)
File "C:\Python22\lib\xml\sax\xmlreader.py", line 123, in parse
self.feed(buffer)
File "C:\Python22\lib\xml\sax\expatreader.py", line 144, in feed
self._parser.Parse(data, isFinal)
File "test.py", line 25, in characters
print "characters:", content
UnicodeError: ASCII encoding error: ordinal not in range(128)
Was hier passiert ist folgendes: das ü wird eingelesen und soll per print
ausgegeben werden.
Das Zeichen ist aber nicht bytecodiert, sondern liegt in Unicodeform vor. Wir müssen den String also in cp1252
codieren:
import xml.sax
class handler(xml.sax.ContentHandler):
def startElement(self, name, attrs):
print "startElement:", name, attrs
def endElement(self, name):
print "endElement:", name
def characters(self, content):
print "characters:", content.encode("cp1252")
xml.sax.parse("test.xml", handler())
Und so klappt das dann auch.