Seite 1 von 2

Encoding Probleme mit minidom

Verfasst: Freitag 21. September 2007, 13:04
von Hyperion
Hallo zusammen,

ich weiß, dass der Titel schlimmes ahnen lässt und das dieses Thema in unzähligen Threads behandelt wurde und alle Regulars hier sicherlich nerven wird, aber leider habe ich auch nach dem Studium von zig Tutorials und Dokus wohl einiges noch nicht abschließend kapiert.

Kurz etwas zum Problem: Ich bin dabei ein kleines CGI-Script zu schreiben, das auf meinem Uni-Webspace laufen soll. Auf der Maschine verrichtet ein Apache seinen Dienst und das ganze läuft auf einem RedHat System. Python liegt dort (nur) in Version 2.3 vor.

Nun habe ich dort leider massive Codierungsprobleme beim Auswerten und Speichern der Userdaten. Am Schluss soll es eine Ausgabe auf einer HTMl-Seite geben und die Daten sollen per minidom in ein XML-File geschrieben werden. So viel zum "eigentlichen" Problem.

So, ich habe nun mal versucht mich der Sache einfach per Shell zu nähern und einfach mal verschiedene Sachen auszuprobieren, vor allem Dinge aus dem umfangreichen Tutorial http://www.python-forum.de/viewtopic.php?p=30572#30572, welches hier gerne als Hilfe angegeben wird.

Im folgenden mal die Auszüge aus der Python-Shell auf dem Uni-Rechner.

Code: Alles auswählen

>>> s = "Österreich"
>>> print s
Österreich
Na das klappt ja :)

Code: Alles auswählen

>>> s.decode("ascii")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
Also bedeutet das, dass der Ursprungszeichensatz gar kein "ascii" war? Dies wäre eigentlich kein Problem, wenn ich nicht folgendes rausgefunden hätte:

Code: Alles auswählen

>>> sys.getdefaultencoding()
'ascii'
Beduetet das nicht, dass diese Python-Instanz ascii-Kodierung verwendet?
Aber wie kann ich dann überhaupt ein "Österreich" eintippen?

Ok, dann habe ich mal folgendes gemacht:

Code: Alles auswählen

>>> s.decode("utf-8")
u'\xd6sterreich'
Sieht nach Unicode aus, oder?

Code: Alles auswählen

>>> s.decode("utf-8").encode("utf-8")
'\xc3\x96sterreich'
Wieso wandelt er nun das "\xd6" also das "Ö" nicht wieder in ein "Ö"?
Und das geht auch nicht!

Code: Alles auswählen

>>> s.decode("utf-8").encode("ascii")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeEncodeError: 'ascii' codec can't encode character u'\xd6' in position 0: ordinal not in range(128)
Aber ascii soll doch angeblich der defaultencoding sein ... ich blicks einfach nicht (und ja, in ascii gibt es keine "ä","ö" usw ... daher bin ich noch verwirrter wie das alles zusammenhängt!)

Code: Alles auswählen

>>> sys.getfilesystemencoding()
'UTF-8'
Das besagt doch, dass die Parameter des OS in utf-8 übergeben werden, oder? Also sollten doch meine Texte aus dem CGI-Script und dem zugehörigen textarea als utf-8 übergeben werden, oder?

Ich habe also mal folgendes kleine Script geschrieben, um ein paar Sachen zu testen:

Code: Alles auswählen

import sys

print "default:",sys.getdefaultencoding()
print "filesystem:",sys.getfilesystemencoding()
print
print "Parameter:",sys.argv[1]

try:
	print "von utf-8",sys.argv[1].decode("utf-8")
except Exception, e:
	print e

try:
	print  "von ascii",sys.argv[1].decode("ascii")
except Exception, e:
	print e

try:
	print  "von iso-8859-15",sys.argv[1].decode("iso-8859-15")
except Exception, e:
	print e
Hier die Ausgabe: (lokal erhalte ich sogar dieselbe)

Code: Alles auswählen

as:> python parameter.py ä
default: ascii
filesystem: UTF-8

Parameter: ä
von utf-8 ä
von ascii 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
von iso-8859-15 À
Wenn ich das richtig begreife bekommt also Python den Übergabeparameter tatsächlich als utf-8. Wieso aber meckert dann mein Script, wenn es "ä", "ö" usw. erhält?

Hier mal der Auszug von der Stelle, an der der fehler auftritt:
(doc ist das xml-Dokument per minidom erstellt)

Code: Alles auswählen

	try:
		tmp = doc.toxml()
		print tmp
		#f = codecs.open(file,"w","utf-8")
		#f.write(tmp)
		#f.close()
	except Exception, e:
		drawError("Fehler beim Speichern der aktuellen Sprüche",e)
		sys.exit(1)
(Die Kommentare sind nur "Bastel"-Sachen, die ich mal auskommentiert habe. Der Fehler liegt also def. am toxml() von minidom!

Code: Alles auswählen

'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
Bekomme ich als Fehler, Eingabe war "ääööö"

Ich bin irgend wie mit meinem Latein am Ende!

Umlauten, Unicode und Encodings

Verfasst: Freitag 21. September 2007, 13:15
von Thomas W.
Ich will mich erst noch mit Unicode und UTF beschäftigen und kann deshalb Deine Fragen nicht beantworten. Eine gute Übersicht habe ich aber schon mal unter [wiki]Von Umlauten, Unicode und Encodings[/wiki] gefunden. Vielleicht hilft sie Dir weiter, Unicode, UTF und Encodings zu verstehen. Ansonsten sie mal unter http://www.python-forum.de/topic-6539.html nach.

Verfasst: Freitag 21. September 2007, 13:19
von Hyperion
Danke, der erste Link ist mir bekannt, ist quasi derselbe Beitrag aus dem Board hier ;)

Werd das zweite mal antesten ... wobei es bei mir ja keine Darstellungsprobleme gibt, sondern interne Fehler ...

Verfasst: Freitag 21. September 2007, 13:26
von BlackJack
Ich weiss nicht ob man da überhaupt noch was sagen kann/soll. Du hast Unicode nicht verstanden und es gibt in der Tat genug Beiträge dazu.

Nur mal so um das erste Missverständnis auszuräumen:

Code: Alles auswählen

s = 'Österreich'
`s` enthält hier keine Buchstaben sondern irgendwelche Bytes die als Buchstaben interpretiert werden können. Einfach zu sagen `s` fängt mit einem 'Ö' an ist falsch, weil man das nicht weiss! Es kommt immer darauf an wie die Zeichenkette kodiert ist und ob das die Kodierung ist, von der man ausgeht.

Bei Dir sind das zum Beispiel zwei Bytes die das 'Ö' ausmachen, weil Deine Zeichenkette offenbar UTF-8 kodiert ist.

XML ist Unicode, also verwende dort am besten auch Unicode.

Verfasst: Freitag 21. September 2007, 13:37
von Hyperion
BlackJack hat geschrieben:Ich weiss nicht ob man da überhaupt noch was sagen kann/soll. Du hast Unicode nicht verstanden und es gibt in der Tat genug Beiträge dazu.
Ja, aber die haben mir nicht geholfen ;)
Nur mal so um das erste Missverständnis auszuräumen:

Code: Alles auswählen

s = 'Österreich'
`s` enthält hier keine Buchstaben sondern irgendwelche Bytes die als Buchstaben interpretiert werden können. Einfach zu sagen `s` fängt mit einem 'Ö' an ist falsch, weil man das nicht weiss! Es kommt immer darauf an wie die Zeichenkette kodiert ist und ob das die Kodierung ist, von der man ausgeht.
Wo habe ich geschrieben, dass s mit "Ö" anfängt? Mir geht es ja grad darum, wie s codiert wird!
Bei Dir sind das zum Beispiel zwei Bytes die das 'Ö' ausmachen, weil Deine Zeichenkette offenbar UTF-8 kodiert ist.
Wie ich ja auch vermutet hatte ...
XML ist Unicode, also verwende dort am besten auch Unicode.
Ja genau das klappt ja leider nicht! Die Methode .toxml() wirft ja grad die Exception mit dem Codierungsfehler! Ist mir leider ein Rätsel, weil die Zeichenkette ja über das Webinterface in das Script gelangt und somit ja utf-8 sein sollte!

Verfasst: Freitag 21. September 2007, 14:00
von Hyperion
So, ich habe mal folgendes minimal Test-Script geschrieben, um zu sehen, ob ich den Fehler reproduzieren kann:

Code: Alles auswählen

#! /usr/bin/python
# -*- coding: utf-8 -*-
from xml.dom.minidom import getDOMImplementation
import sys

def main(args):
	impl = getDOMImplementation()
	doc = impl.createDocument(None, "test", impl.createDocumentType("noNamespaceSchemaLocation",None,None))
	root = doc.documentElement
	text = doc.createTextNode(args[1])
	root.appendChild(text)
	print doc.toprettyxml()

if __name__ == "__main__":
	main(sys.argv)
Wenn ich das nun auf dem Server aufrufe, bekomme ich folgendes korrektes Ergebnis:

Code: Alles auswählen

as:> ./mindom-example.py halloäöüö
<?xml version="1.0" ?>
<!DOCTYPE noNamespaceSchemaLocation>
<test>
        halloäöüö
</test>
Jetzt bin ich entgültig verwirrt! Wieso klappt das damit? Die einzige Schlussfolgerung, die ich im Moment ziehen kann ist folgende: Der Apache übergibt dem Script die Parameter eben nicht im "utf-8" ... aber wie kann das sein? Und vor allem, wie denn sonst?

Verfasst: Freitag 21. September 2007, 14:47
von BlackJack
Hyperion hat geschrieben:
XML ist Unicode, also verwende dort am besten auch Unicode.
Ja genau das klappt ja leider nicht! Die Methode .toxml() wirft ja grad die Exception mit dem Codierungsfehler! Ist mir leider ein Rätsel, weil die Zeichenkette ja über das Webinterface in das Script gelangt und somit ja utf-8 sein sollte!
Ich sagte verwende Unicode, nicht UTF-8. Das ist nicht das gleiche!

Verfasst: Freitag 21. September 2007, 14:52
von Hyperion
So, ich habe das Test-Script mal so umgestellt, dass es einen Parameter per CGI bekommt und diesen als TextNode einträgt.

Rufe ich das Script dann per Browser mit Parameter auf, so kann er auch Umlaute korrekt darstellen! (genauer .toxml() meckert nicht rum!)

Genau diesen Codeschnipsel habe ich nun mal in mein eigentlichs Script an die Stelle des Speicherns gesetzt und den alten Code auskommentiert. Ergebnis: Es kommt wieder er coding Fehler!

Wie kann das nun sein? Fakt ist doch, dass beide Scripte an den imho entscheidenden Stellen exakt identischen Code haben und der Aufruf ebenfalls identisch ist. Wieso geht es bei dem einen, beim anderen kommt dieser scheiß codierungs Fehler?

Auf Wunsch kann ich den kompletten Code gerne posten, aber da der doch relativ lang ist, hoffe ich erst einmal auf einen dummen Bock meinerseits, der einfach zu erkennen ist.

Verfasst: Freitag 21. September 2007, 14:57
von Hyperion
BlackJack hat geschrieben: Ich sagte verwende Unicode, nicht UTF-8. Das ist nicht das gleiche!
Ok, also muss ich an einer Stelle wohl einen .decode("utf-8") einfügen, um die vermeindliche utf-8 zeichenkette explizit in unicode zu wandeln, korrekt?

Verfasst: Freitag 21. September 2007, 15:47
von BlackJack
Falls es UTF-8 ist, sollte das gehen. Ich bin jetzt zu faul in den RFCs zu wühlen, aber soweit ich das in Erinnerung habe antworten Browser mit der Kodierung in der die Seite bei ihnen ankam.

Verfasst: Freitag 21. September 2007, 15:56
von Hyperion
BlackJack hat geschrieben:Falls es UTF-8 ist, sollte das gehen. Ich bin jetzt zu faul in den RFCs zu wühlen, aber soweit ich das in Erinnerung habe antworten Browser mit der Kodierung in der die Seite bei ihnen ankam.
Leider geht das ja genau nicht! Daher habe ich die Test-Scripte geschrieben, um das auszutesten.

Verfasst: Freitag 21. September 2007, 20:09
von Leonidas
Und warum kein Ö angezeigt wird, wenn dein "Österreich" in in Unicode enkodiert und dann in UTF-8 kodiert wird ist auch einfach zu erklären: ``repr()`` gibt bei Unicode-Strings immer "Sonderzeichen" als ``\x??``-Sequenzen aus, Wenn du einen Unicode-String direkt im Interpreter angibst, bekommst du die Ausgabe von ``repr()``. Du musst schon ``print`` verwenden.

Verfasst: Samstag 22. September 2007, 11:56
von Hyperion
Danke, das ist mal eine gute Erklärung :)

Und das andere Problem? Irgend eine Ahnung, wie ich den form-Parameter nun in unicode wandeln kann?

Verfasst: Sonntag 23. September 2007, 15:25
von Hyperion
*push*

Verfasst: Sonntag 23. September 2007, 17:21
von Leonidas
Oh, sorry, habe deinen Post ganz übersehen,
Hyperion hat geschrieben:Und das andere Problem? Irgend eine Ahnung, wie ich den form-Parameter nun in unicode wandeln kann?
Form-Parameter?

Du kannst Bytestrings alle mit ``bytestringobjekt.encode('encoding_des_bytestrings')`` zu machen (wobei ``encode()`` natürlich einen Unicode-String zurückgibt und nicht den Bytestring ändert, versteht sich).

Verfasst: Sonntag 23. September 2007, 19:52
von Hyperion
Danke Dir :) Werd ich morgen mal probieren!

Verfasst: Donnerstag 29. November 2007, 20:13
von Hyperion
Leonidas hat geschrieben:Oh, sorry, habe deinen Post ganz übersehen,
Hyperion hat geschrieben:Und das andere Problem? Irgend eine Ahnung, wie ich den form-Parameter nun in unicode wandeln kann?
Form-Parameter?

Du kannst Bytestrings alle mit ``bytestringobjekt.encode('encoding_des_bytestrings')`` zu machen (wobei ``encode()`` natürlich einen Unicode-String zurückgibt und nicht den Bytestring ändert, versteht sich).
Naja, man muss die Parameter des form per .decode("kodierung") in unicode wandeln ;) Egal, jetzt hab ichs kapiert.

Dennoch stellt sich mir nun eine Frage: Wie kann ich denn bitte bei einem CGI-Script sicherstellen, dass das richtige "Coding" ankommt bei meinem Script? Ok, beim form-Tag in HTML kann man die Kodierung zwar einstellen, aber erstens wird das sicher nicht durch jeden Browser unterstützt und zweitens kann man ein Script ja auch umgehen! Gibt es für dieses Problem eine Lösung?

Verfasst: Donnerstag 29. November 2007, 20:58
von Leonidas
Gegen mutwillig falsche Encodings kannst du nichts machen. Du kannst versuchen das Encoding zu raten, aber sauber wäre es einfach dem Formular zu sagen, dass du ein bestimmtes Encoding erwartest und fertig. Man kann nicht auf jede erdenkliche Blödheit von Browsern reagieren (wobei ich jetzt nicht wüsste welcher halbwegs aktuelle Browser kein UTF-8 schicken könnte). Letztendlich ist ein falsches Encoding auch kein Beinbruch, die Umlaute sehen dann eben zerhackt aus, aber es ist kein Sicherheitsproblem und man könnte es auch in Einzelfällen manuell wieder richten indem man es umkodiert.

Verfasst: Donnerstag 29. November 2007, 21:20
von Hyperion
Ok, danke! Prinzipiell hast Du damit wohl Recht. Kann man denn ein Script zum Absturz bringen, wenn man bewusst in ner falschen Zeichencodierung sendet? Also wenn ich UTF-8 erwarte und man mir sonst was schickt ... oder ist es prinzipiell sinnvoll utf-8 zu erwarten?

Verfasst: Donnerstag 29. November 2007, 21:40
von Leonidas
Zum Absturz bringen: ja, du kannst einen UnicodeDecodeError bekommen, wenn du versuchst einen Unicode-String in in ein Encoding zu kodieren welches nicht alle Zeichen des Unicode-Strings darstellen kann. Häufigstes Beispiel: Unicode-Strings mit Umlauten werden versucht in ASCII zu kodieren - kann nicht funktionieren.

Aber da musst du eben aufpassen, dass du intern Unicode verwendest und extern UTF-8 erwartest und das auch rausgibst. UTF-8 ist definitiv eine gute Wahl, für ein Datenaustausch-Encoding.