[python] Python nem ismeri a utf-8 szövegeket?

Sziasztok!

Van egy olyan problémám, hogy a python sok helyről visszadobja a unicode szövegeket, és exceptionnal elszáll, még encode/decode használata esetén is.

Példa:



#!/usr/bin/python

import sys, MySQLdb, posixpath
from xml.dom import minidom


def getMetaData(doc):
        dom = minidom.parseString(doc.encode('utf-8'))
        metadata = {}
        temp = dom.getElementsByTagName("title")
        if len(temp) != 0:
                metadata['title'] = MySQLdb.escape_string(temp[0].childNodes[0].data)
        temp = dom.getElementsByTagName("date")
        if len(temp) != 0:
                metadata['date'] = MySQLdb.escape_string(temp[0].childNodes[0].data)
        return metadata


xmlfile = posixpath.basename(sys.argv[1])
xmldoc = open(xmlfile, 'r').read()
metadata = getMetaData(xmldoc)

A hiba:


Traceback (most recent call last):
  File "./tomysql.py", line 26, in ?
    metadata = getMetaData(xmldoc)
  File "./tomysql.py", line 14, in getMetaData
    dom = minidom.parseString(doc.encode('utf-8'))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 220: ordinal not in range(128)

Valaki segítsen, és szájbarágósan elmagyarázni, hogy mi a baj és mi a teendő??? mert már nem értem.
Az alábbi hatástalan:


# -*- coding: utf8 -*- 

Hozzászólások

Nem tudom mi hiba maradt még a kódban, de tegyük fel, hogy tényleg azt az üzit dobja.

A kód egy nagyobb kódból lett kiollózva.

ascii???? UTF8 a fájl enkódolása amit olvas! szájbarágóssabban lécci.

Amúgy:


Traceback (most recent call last):
  File "./tomysql.py", line 26, in ?
    metadata = getMetaData(xmldoc)
  File "./tomysql.py", line 14, in getMetaData
    dom = minidom.parseString(doc.encode('utf-8', 'xmlcharrefreplace'))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 220: ordinal not in range(128)

Ugyan tök nem tudok pythonban programozni, de a perles tapasztalatom és a józan ész azt súgja, hogy doc.encode('utf-8') helyett doc.decode('utf-8') kellene. Ugyanis doc a nyers bytesorozat, ebből akarsz Unicode entitásokból álló sztringet csinálni, ez pedig a dekódolás irány, ha a belső Unicode reprezentációból csinálsz valamilyen kódolás (pl. utf-8) szerinti bytesorozatot, az volna az enkódolás.

Kipróbáltam:


Traceback (most recent call last):
  File "./tomysql.py", line 26, in ?
    metadata = getMetaData(xmldoc)
  File "./tomysql.py", line 14, in getMetaData
    dom = minidom.parseString(doc.decode('utf-8'))
  File "/usr/lib/python2.4/site-packages/_xmlplus/dom/minidom.py", line 1925, in parseString
    return expatbuilder.parseString(string)
  File "/usr/lib/python2.4/site-packages/_xmlplus/dom/expatbuilder.py", line 942, in parseString
    return builder.parseString(string)
  File "/usr/lib/python2.4/site-packages/_xmlplus/dom/expatbuilder.py", line 223, in parseString
    parser.Parse(string, True)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf3' in position 220: ordinal not in range(128)

Ebben az a jó, hogy immáron a külső lib-re tudom áttolni a felelősséget, a rossz meg az, hogy ettől nem lettem okosabb.

Próbáld ki simán ezt:
dom = minidom.parseString(doc)
a neten ezt találtam: http://evanjones.ca/python-utf8.html és ez alapján szerintem ez kell. Ugyanis a read függvénnyel byte-okat olvasol be, mivel nem adod meg a kódolást, és a parseString is byte-okra számít, hiszen magából az xml fájlból veszi ki annak kódolását (és fallbackel utf8-ra, ha nem lenne megemlítve).

Python tudás nélkül:

Az xmldoc gondolom egy XML dokumentum.
Az XML dokumentum az sosem nyers byteok sorozata,
hanem KARAKTEREK sorozata. A karakterek valamilyen
kódolással bytesorozattá vannak konvertálva (sorosítva).
Hogy milyen kódolással vannak a karakterek
bytesorozatra konvertálva, az benne szokott lenni
az XML dokumentumban (az elején), és ha nincs megadva,
akkor a default az UTF-8.

Az esetedben a hibaüzenetből az látszik,
hogy a parser ASCII kódolásúnak gondolja a dokumentumot,
és emiatt a 128 feletti byteokat eleve nem tudja
karakternek értelmezni. Azt gondolnád talán,
hogy a 128 alatti byteok mind karakterek,
ez azonban nincs így. Úgy emlékszem, a szabvány szerint
32 alatt csak a CR, LF, TAB számítanak karakternek.
(Igen, szabvány foglalkozik azzal, hogy mi a karakter.)

Ugyanezért nem igaz, hogy Latin-1 (ISO-8859-1, nyolc bites)
kódolással minden byte egy karakter volna.

Az XML elemzők hibát dobnak, ha a bytesorozat
nem értelmezhető karakterként.

Tehát szerintem az a megoldás, hogy ellenőrizni kell,
hogy az xmldoc tényleg egy szabványos XML dokumentum
legyen, jó legyen benne a kódolás, és jól benne legyen,
hogy milyen kódolású. Azaz a változó tartalma okozza a hibát.

--
CCC3

Az XML teljesen szabvanyos xml, elejen az encoding utf-8 es a doksi telleg utf8 kodolasu. xmllint nem dob ra hibat.
Tisztaban vagyok a xml dokumentumok elmleti felepitesevel, de nekem jelenleg a xml dokumentum mint byte-ok sorozata all rendelkezesemre (tekintve hogy a python keptelen maskent tekinteni a szovegre, ezzel nem tudok mit csinalni).

Mivel ezt a doksit en allitottam elo, ezert teljesen biztos vagyok benne hogyszabvanyos. Bizonyos technikai korlatok megakadalyoznak az xml bepasztazasaban, ezert elnezest kell kerjek, es el kellhidd latatlanba.

Ha egy teljesen szabvanyos angol nyelvu doksit adok neki, semmi gond. Mindig csak a utf karakterek jelentenek problemat.

nekem jelenleg a xml dokumentum mint byte-ok sorozata all rendelkezesemre

Persze, végül is minden byteok sorozata. Hogy az XML karakterekből áll, azt úgy kell érteni, hogy karakterek vannak benne valamilyen kódolással bytesorozatra konvertálva (nem pedig összevissza byteok).

Lehet, hogy jó az XML dokumentum, de a parser mégis azt hiszi, hogy ASCII kódolású. Ebből gondolom:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 220: ordinal not in range(128)

Ha az XML 'ascii' kódolású, akkor a 0xc3 az egy 'összevissza' byte.

--
CCC3

Még egy dolog. Amikor Latin-1 kódolású XML-t elemeztem Python minidom-mal, akkor a site.py-ban a def setencoding()-ban a default kódolást átállítottam 'latin-1'-re, másként az ékezetes betűkön kiakadt. UTF-8-cal nem próbáltam a Pythont, de ennek mintájára próbáld átírni a kódolást a site.py-ban 'UTF-8'-ra.

Ez persze zavaros, hiszen az elemzőnek ahhoz a kódoláshoz kellene alkalmazkodni, ami magában az XML dokumentumban van megadva, függetlenül a programod kódolásától, vagy a hoston beállított kódolástól. Ha ez nem így van, az Python hibája. Azt gondoltam, ezen már túl vannak.

--
CCC3

Már tegnap este válaszoltam, biztos nem vetted észre. Szóval amikor beolvasod a fájlt (a forrásod végén, az utolsó előtti sor), akkor tedd így:

xmldoc = codecs.open(xmlfile, encoding='utf-8').read()

Kipróbáltam, így szépen lefutott a kódod.

================================================
[szerk] Hahó Trey, 10:24 van. Valami időzóna tréfa lehet.

godri, én most elhiszem hogy hülye vagyok.


~ $ ./tomysql mynews.xml

Traceback (most recent call last):
  File "./hirek/tomysql.py", line 27, in ?
    metadata = getMetaData(xmldoc)
  File "./hirek/tomysql.py", line 15, in getMetaData
    dom = minidom.parseString(doc)
  File "/usr/lib/python2.4/xml/dom/minidom.py", line 1925, in parseString
    return expatbuilder.parseString(string)
  File "/usr/lib/python2.4/xml/dom/expatbuilder.py", line 940, in parseString
    return builder.parseString(string)
  File "/usr/lib/python2.4/xml/dom/expatbuilder.py", line 223, in parseString
    parser.Parse(string, True)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe1' in position 139: ordinal not in range(128)

tomysql.py


#!/usr/bin/python

import sys, MySQLdb, posixpath
import codecs
from xml.dom import minidom

def connect():
        try:
                conn = MySQLdb.connect("localhost", "root", "ut9uUb7", "gentoosite")
                return conn.cursor()
        except MySQLdb.OperationalError, msg:
                print msg

def getMetaData(doc):
        dom = minidom.parseString(doc)
        metadata = {}
        temp = dom.getElementsByTagName("title")
        if len(temp) != 0:
                metadata['title'] = MySQLdb.escape_string(temp[0].childNodes[0].data)
        temp = dom.getElementsByTagName("date")
        if len(temp) != 0:
                metadata['date'] = MySQLdb.escape_string(temp[0].childNodes[0].data)
        return metadata

xmlfile = posixpath.basename(sys.argv[1])
xmldoc = codecs.open(xmlfile, 'r', encoding='utf-8').read()
metadata = getMetaData(xmldoc)

mynews.xml


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE news SYSTEM "/dtd/guide.dtd">
<news category="gentoo" link="mynews.xml">
 <poster>Garami Gábor</poster>
 <date>2007-04-06</date>
 <title>Ez egy tesz hír</title>
 <summary>Elloptak egy hidat az M3 bevezető szakaszánál. A tettesek még szabadlábon</summary>
 <body>
 <p>

  <!-- Ez egy nagyon érdekes hír... :) -->

  Tegnapról ma hajnalra virradólag elloptak egy felüljárót az <c>M3</c> bevezető szakaszánál. Az ellopás
  tényét egy névtelen telefonáló jelentette be, miután nem tudott továbbhajtani az úton.
  A híd értéke körülbelül 4 000 000 Ft, ám az újraépítése ennél sokkal többe fog kerülni, mivel fel
  kell bontani a híd visszavezető szakaszát. A tettesek alaposan gyanusíthatók a győri
  <uri link="/news/szoborlopas.xml">Sántha Ferenc</uri> szobor ellopásával is.
 </p>
 </body>
</news>

PS: Természetesen a hír koholmány, a valósághoz semmi köze.

Viszont egy erős hax eredményeképp megy:



import sys, MySQLdb, posixpath
import codecs
from xml.dom import minidom

reload(sys)

sys.setdefaultencoding('utf-8')

def connect():
        try:
                conn = MySQLdb.connect("localhost", "root", "ut9uUb7", "gentoosite")
                return conn.cursor()
        except MySQLdb.OperationalError, msg:
                print msg

def getMetaData(doc):
        dom = minidom.parseString(doc)
        metadata = {}
        temp = dom.getElementsByTagName("title")
        if len(temp) != 0:
                metadata['title'] = MySQLdb.escape_string(temp[0].childNodes[0].data)
        temp = dom.getElementsByTagName("date")
        if len(temp) != 0:
                metadata['date'] = MySQLdb.escape_string(temp[0].childNodes[0].data)
        return metadata

xmlfile = posixpath.basename(sys.argv[1])
# xmldoc = codecs.open(xmlfile, 'rb', encoding='utf-8').read()
xmldoc = open(xmlfile, 'rb').read()
metadata = getMetaData(xmldoc)

Végül is mrev hatására találtam meg még telepítés alatt azt az oldalt, ahol elmagyarázzák, hogy a fenti hack (gyk: reload(sys) sys.setdefaultencoding('utf-8') ) miért nem ajánlott. Ha valamit nem ajánlanak, az általában működni szokott, csak vagy csúgya vagy secu hack. Namost ezek közül mindkettő hidegen hagy jelen esetben.

PS: ami tuti nem jó, arra általában azt mondják hogy tilos.

Helyesen:

import sys, MySQLdb, posixpath
from xml.dom import minidom
def getMetaData(doc):
        dom = minidom.parseString(doc)
        metadata = {}
        temp = dom.getElementsByTagName("title")
        if len(temp) != 0:
                metadata['title'] = MySQLdb.escape_string(temp[0].childNodes[0].data.encode('utf-8'))
        temp = dom.getElementsByTagName("date")
        if len(temp) != 0:
                metadata['date'] = MySQLdb.escape_string(temp[0].childNodes[0].data.encode('utf-8'))
        return metadata
xmlfile = posixpath.basename(sys.argv[1])
xmldoc = open(xmlfile, 'r').read()
metadata = getMetaData(xmldoc)
print metadata['title']

Ne haragudj, de ezt most nem értem. Persze, ez lenne a helyes - ha a python képes lenne menet közben kódolást váltani.

Mivel azonban (nálam) nem képes, az import sor után mindenképp kell a reload(sys): setdefaultencoding('utf-8').

Értsétek meg, én már kipróbáltam azt hogy a stringeken nyomok .encode('utf-8') függvényt, de folyamatosan a fenti üzenetet kapom (UnicodeError: The 'ascii' encoder can't ...).

Tényleg nem nézek senkit se hülyének, ha valaki nagyon nem hiszi, kérjen tőlem ssh jogot, adok, belép, megmutatom. Higgyétek el, hogy bármily szép megoldás lenne ez, nálam NEM MŰKÖDIK.
Vagy a MySQLdb.escapestring vagy az xml-feldolgozó rész hal meg.



Traceback (most recent call last):
  File "./tomysql_hup.py", line 50, in ?
    conn.execute(sql % (xmldoc, xmlfile, metadata['title'], metadata['date']))
  File "/usr/lib/python2.4/site-packages/MySQLdb/cursors.py", line 146, in execute
    query = query.encode(charset)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 220: ordinal not in range(128)

Nézd, én nem várok el semmit. Van egy megoldás ami nekem müködik (reload (sys); sys.setdefaultencoding('utf-8');). Te elkezdted, hogy ez emígy is működhet. Én azért szálltam bele ebbe a szálba mert kiváncsi voltam, hogy akkor ezt így most hogy.

Ne hidd, hogy nem gugliztam (spec amit adtál oldalt azt még nem láttam, átolvasom, köszönöm), csak már rém untam, hogy minden, a gugli által kidobott módszer a fenti hibaüzit eredményezte.

Sajnálom hogy rabooltam az idődet, és köszönöm a segítségedet.