2009-05-13 2 views
6

Quando ho alimentare un xml codifica UTF-8 a un'istanza ExpatParser:Impostazione della codifica per sax parser in Python

def test(filename): 
    parser = xml.sax.make_parser() 
    with codecs.open(filename, 'r', encoding='utf-8') as f: 
     for line in f: 
      parser.feed(line) 

... ricevo il seguente:

Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "test.py", line 72, in search_test 
    parser.feed(line) 
    File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xml/sax/expatreader.py", line 207, in feed 
    self._parser.Parse(data, isFinal) 
UnicodeEncodeError: 'ascii' codec can't encode character u'\xb4' in position 29: ordinal not in range(128) 

Sono probabilmente manca qualcosa di ovvio qui. Come cambio la codifica del parser da 'ascii' a 'utf-8'?

risposta

5

Il codice non funziona in Python 2.6, ma funziona in 3.0.

Questo funziona in 2.6, presumibilmente perché permette al parser stesso per capire la codifica (forse leggendo la codifica opzionalmente specificato sulla prima riga del file XML, e altrimenti inadempiente in utf-8):

def test(filename): 
    parser = xml.sax.make_parser() 
    parser.parse(open(filename)) 
+0

Questo ha funzionato in 2.5, anche. –

5

Il parser SAX in Python 2.6 dovrebbe essere in grado di analizzare utf-8 senza mangling. Sebbene tu abbia omesso il ContentHandler che stai usando con il parser, se quel gestore di contenuti tenta di stampare qualsiasi carattere non-ascii sulla tua console, ciò causerà un arresto anomalo.

Per esempio, dire che ho questo documento XML:

<?xml version="1.0" encoding="utf-8"?> 
<test> 
    <name>Champs-Élysées</name> 
</test> 

E questo apparato di analisi:

import xml.sax 

class MyHandler(xml.sax.handler.ContentHandler): 

    def startElement(self, name, attrs): 
     print "StartElement: %s" % name 

    def endElement(self, name): 
     print "EndElement: %s" % name 

    def characters(self, ch): 
     #print "Characters: '%s'" % ch 
     pass 

parser = xml.sax.make_parser() 
parser.setContentHandler(MyHandler()) 

for line in open('text.xml', 'r'): 
    parser.feed(line) 

Questo analizzerà bene, e il contenuto sarà infatti conservare i caratteri accentati della XML. L'unico problema è la riga in def characters() che ho commentato. Eseguendo la console in Python 2.6, questo produrrà l'eccezione che stai vedendo perché la funzione di stampa deve convertire i caratteri in ascii per l'output.

Hai 3 possibili soluzioni:

Uno: Assicurarsi che il terminale supporta Unicode, quindi creare una voce sitecustomize.py nel site-packages e impostare il set di caratteri UTF-8:

import sys sys.setdefaultencoding ('utf-8')

Due: non stampare l'uscita al terminale (lingua-in-guancia)

Tre: Normalizzare l'output utilizzando unicodedata.normalize per convertire caratteri non-ASCII in equivalenti ASCII, o encode i caratteri in ASCII per l'uscita del testo: ch.encode('ascii', 'replace'). Ovviamente, usando questo metodo non sarai in grado di valutare correttamente il testo.

Utilizzando l'opzione uno sopra, il codice ha funzionato bene per il mio in Python 2.5.

+1

Il problema reale nella domanda originale non ha nulla a che fare con la stampa di unicode sul terminale. È dovuto al fatto che l'OP stava decodificando l'input con codecs.open, come identificato da Stephan202. – DanC

5

Jarret Hardie ha già spiegato il problema. Ma quelli di voi che sono codifica per la linea di comando, e non sembrano avere il "sys.setdefaultencoding" visibile, il rapido ovviare a questo problema (o "caratteristica") è:

import sys 
reload(sys) 
sys.setdefaultencoding('utf-8') 

Speriamo reload(sys) non infrangerà qualcos'altro

Maggiori dettagli in questo vecchio blog:

The Illusive setdefaultencoding

0

Commentando la risposta di janpf (mi dispiace, non hanno abbastanza reputazione per metterla lì), si noti che la versione di Janpf si romperà IDLE che richiede la sua proprio stdout ecc. che è diverso dal default di sys. Quindi io suggerirei di modificare il codice di essere qualcosa di simile:

import sys 

currentStdOut = sys.stdout 
currentStdIn = sys.stdin 
currentStdErr = sys.stderr 

reload(sys) 
sys.setdefaultencoding('utf-8') 

sys.stdout = currentStdOut 
sys.stdin = currentStdIn 
sys.stderr = currentStdErr 

Ci possono essere altre variabili per preservare, ma questi sembrano come il più importante.

3

Per impostare una codificazione file arbitrario per un parser SAX, si può usare InputSource come segue:

def test(filename, encoding): 
    parser = xml.sax.make_parser() 
    with open(filename, "rb") as f: 
     input_source = xml.sax.xmlreader.InputSource() 
     input_source.setByteStream(f) 
     input_source.setEncoding(encoding) 
     parser.parse(input_source) 

Questo permette l'analisi di un file XML che ha un non-ASCII, la codifica non UTF-8. Ad esempio, si può analizzare un file ASCII esteso codificato con LATIN1 come: (. Aggiunta questa risposta per affrontare direttamente il titolo di questa interrogazione, in quanto tende a rango altamente nei motori di ricerca) test(filename, "latin1")