ElementTree的替代XML解析器,解决UTF-8问题?
我正在使用elementtree.parse()函数解析一些XML文件。这个函数可以正常工作,但对于一些utf-8字符(单字节字符,值大于128)就不行了。我发现默认的解析器是XMLTreeBuilder,它是基于expat的。
有没有其他的解析器可以使用,它可能不那么严格,允许utf-8字符呢?
这是我在使用默认解析器时遇到的错误:
ExpatError: not well-formed (invalid token): line 311, column 190
导致这个错误的字符是一个单字节的x92(十六进制表示)。我不确定这是否是一个有效的utf-8字符。不过如果能处理这个字符就好了,因为大多数文本编辑器会把它显示为:í
编辑:这个字符的上下文是:canít,我猜它应该是一个花式撇号,但在十六进制编辑器中,这个序列是:63 61 6E 92 74
4 个回答
字节0x92在UTF-8字符中永远不能作为第一个字节。不过,它可以作为后续字节是有效的。想了解有效的字节序列,可以查看这份UTF-8指南。
你能告诉我们0x92周围的字节是什么吗?XML声明中是否包含字符编码?
看起来你有CP1252格式的文本。如果是这样的话,应该在文件的顶部注明,比如:
<?xml version="1.0" encoding="CP1252" ?>
这在使用ElementTree时是有效的。
如果你自己在创建这些文件,尽量不要用这种编码。保存为UTF-8格式,这样可以帮助淘汰过时的文本编码。
如果你收到的CP1252数据没有编码说明,而且你确定它总是CP1252格式,你可以在把数据发送给解析器之前,先把它转换成UTF-8格式:
s.decode("CP1252").encode("UTF-8")
我先从问题开始:“有没有其他的解析器可以用,它可能不那么严格,允许使用utf-8字符?”
所有的XML解析器都会接受用UTF-8编码的数据。实际上,UTF-8就是默认的编码方式。
一个XML文档可能会以这样的声明开始:
`<?xml version="1.0" encoding="UTF-8"?>`
或者像这样:
<?xml version="1.0"?>
或者根本没有声明……在每种情况下,解析器都会使用UTF-8来解码文档。
但是你的数据并不是用UTF-8编码的……它可能是Windows-1252,也就是cp1252。
如果编码不是UTF-8,那么创建者应该包含一个声明(或者接收者可以在前面加一个),或者接收者可以把数据转换成UTF-8。下面的内容展示了什么是有效的,什么是无效的:
>>> import xml.etree.ElementTree as ET
>>> from StringIO import StringIO as sio
>>> raw_text = '<root>can\x92t</root>' # text encoded in cp1252, no XML declaration
>>> t = ET.parse(sio(raw_text))
[tracebacks omitted]
xml.parsers.expat.ExpatError: not well-formed (invalid token): line 1, column 9
# parser is expecting UTF-8
>>> t = ET.parse(sio('<?xml version="1.0" encoding="UTF-8"?>' + raw_text))
xml.parsers.expat.ExpatError: not well-formed (invalid token): line 1, column 47
# parser is expecting UTF-8 again
>>> t = ET.parse(sio('<?xml version="1.0" encoding="cp1252"?>' + raw_text))
>>> t.getroot().text
u'can\u2019t'
# parser was told to expect cp1252; it works
>>> import unicodedata
>>> unicodedata.name(u'\u2019')
'RIGHT SINGLE QUOTATION MARK'
# not quite an apostrophe, but better than an exception
>>> fixed_text = raw_text.decode('cp1252').encode('utf8')
# alternative: we transcode the data to UTF-8
>>> t = ET.parse(sio(fixed_text))
>>> t.getroot().text
u'can\u2019t'
# UTF-8 is the default; no declaration needed