Definir a codificação para analisador sax em Python
Pergunta
Quando alimentar uma utf-8 xml codificado para uma instância ExpatParser:
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)
... eu recebo o seguinte:
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)
Eu sou provavelmente faltando algo óbvio aqui. Como faço para alterar a codificação do analisador de 'ascii' para 'utf-8'?
Solução
O código de falha em Python 2.6, mas funciona em 3.0.
Isto funciona em 2,6, presumivelmente porque permite que o próprio analisador para descobrir a codificação (talvez lendo a codificação opcionalmente especificado na primeira linha do arquivo XML, e caso contrário assumem a utf-8):
def test(filename):
parser = xml.sax.make_parser()
parser.parse(open(filename))
Outras dicas
Jarret Hardie já explicou a questão. Mas aqueles de vocês que estão codificando para a linha de comando, e não parecem ter o "sys.setdefaultencoding", o trabalho rápido visível em torno este bug (ou "recurso") é:
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
Esperamos reload(sys)
não vai quebrar qualquer outra coisa.
Mais detalhes neste blog idade:
O analisador SAX em Python 2.6 deve ser capaz de analisar utf-8, sem deturpação ele. Embora você tenha deixado de fora o ContentHandler você está usando com o analisador, se que tenta manipulador de conteúdo para imprimir quaisquer caracteres não-ASCII para o seu console, que irá causar um acidente.
Por exemplo, digamos que eu tenho esse documento XML:
<?xml version="1.0" encoding="utf-8"?>
<test>
<name>Champs-Élysées</name>
</test>
E este aparelho de análise:
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)
Esta irá analisar muito bem, e o conteúdo vai realmente preservar os caracteres acentuados no XML. O único problema é que a linha em def characters()
que tenho comentado. Em execução no console em Python 2.6, este irá produzir a exceção que você está vendo porque a função de impressão deve converter os caracteres para ascii para a saída.
Você tem 3 soluções possíveis:
Um : Certifique-se de seus suportes terminais Unicode, em seguida, criar uma entrada sitecustomize.py
em sua site-packages
e definir o conjunto de caracteres padrão para utf-8:
sys importação sys.setdefaultencoding ( 'utf-8')
Dois : Não imprima a saída para o terminal (tongue-in-cheek)
Três : Normalize a saída usando unicodedata.normalize
para converter caracteres não-ascii para equivalentes ASCII, ou encode
os caracteres para ascii para saída de texto: ch.encode('ascii', 'replace')
. Claro, usando esse método, você não será capaz de avaliar corretamente o texto.
Usando opção citada acima, o código funcionou muito bem para o meu em Python 2.5.
Para configurar uma codificação de arquivo arbitrário para um analisador SAX, pode-se usar InputSource da seguinte forma:
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)
Isso permite analisar um arquivo XML que tem um não-ASCII, não-UTF8 codificação. Por exemplo, pode-se analisar um arquivo ASCII estendido codificado com LATIN1 como: test(filename, "latin1")
(Adicionado esta resposta para abordar diretamente o título desta questão, pois ele tende a classificar altamente nos motores de busca.)
Ao comentar sobre a resposta de janpf (desculpe, eu não tenho reputação suficiente para colocá-lo lá), nota que a versão de Janpf vai quebrar IDLE que requer sua própria stdout etc. que é diferente do padrão das sys. Então, eu sugiro modificar o código para ser algo como:
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
Pode haver outras variáveis ??para preservar, mas estes parecem ser os mais importantes.