ElementTree的替代XML解析器,解决UTF-8问题?

9 投票
4 回答
13413 浏览
提问于 2025-04-15 12:57

我正在使用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 个回答

1

字节0x92在UTF-8字符中永远不能作为第一个字节。不过,它可以作为后续字节是有效的。想了解有效的字节序列,可以查看这份UTF-8指南

你能告诉我们0x92周围的字节是什么吗?XML声明中是否包含字符编码?

4

看起来你有CP1252格式的文本。如果是这样的话,应该在文件的顶部注明,比如:

<?xml version="1.0" encoding="CP1252" ?>

这在使用ElementTree时是有效的。

如果你自己在创建这些文件,尽量不要用这种编码。保存为UTF-8格式,这样可以帮助淘汰过时的文本编码。

如果你收到的CP1252数据没有编码说明,而且你确定它总是CP1252格式,你可以在把数据发送给解析器之前,先把它转换成UTF-8格式:

s.decode("CP1252").encode("UTF-8")
15

我先从问题开始:“有没有其他的解析器可以用,它可能不那么严格,允许使用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

撰写回答