为什么ElementTree会引发ParseError?

11 投票
4 回答
44018 浏览
提问于 2025-04-17 03:54

我一直在尝试用 xml.etree.ElementTree 来解析一个文件:

import xml.etree.ElementTree as ET
from xml.etree.ElementTree import ParseError

def analyze(xml):
    it = ET.iterparse(file(xml))
    count = 0
    last = None

    try:        
        for (ev, el) in it:
            count += 1
            last = el

    except ParseError:
            print("catastrophic failure")
            print("last successful: {0}".format(last))

    print('count: {0}'.format(count))

这当然是我代码的简化版本,但这已经足以让我的程序出问题了。如果我去掉了错误处理的部分,就会遇到这个错误:

Traceback (most recent call last):
  File "<pyshell#22>", line 1, in <module>
    from yparse import analyze; analyze('file.xml')
  File "C:\Python27\yparse.py", line 10, in analyze
    for (ev, el) in it:
  File "C:\Python27\lib\xml\etree\ElementTree.py", line 1258, in next
    self._parser.feed(data)
  File "C:\Python27\lib\xml\etree\ElementTree.py", line 1624, in feed
    self._raiseerror(v)
  File "C:\Python27\lib\xml\etree\ElementTree.py", line 1488, in _raiseerror
    raise err
ParseError: reference to invalid character number: line 1, column 52459

不过结果是确定的,如果一个文件能成功解析,它就总是能成功。如果一个文件解析失败,它总是会失败,并且总是在同一个地方出错。

最奇怪的是,我在用追踪工具找出是否有格式不正确的 XML 导致解析失败。我找到了导致失败的节点。但是当我创建一个包含那个节点和它周围几个节点的 XML 文件时,解析却成功了!

这似乎也不是文件大小的问题。我已经成功解析过更大的文件,没有遇到任何问题。

有没有什么想法呢?

4 个回答

4

我不确定这是否能回答你的问题,但如果你想在使用元素树时处理ParseError这个异常,你可以这样做:

except ET.ParseError:
            print("catastrophic failure")
            print("last successful: {0}".format(last))

来源:http://effbot.org/zone/elementtree-13-intro.htm

9

这里有一些建议:

(0) 先解释一下“文件”和“偶尔”的意思:你是说同一个文件有时候能用,有时候不能用吗?

对于每个出错的文件,做以下几件事:

(1) 找出文件中出错的地方:

text = open("the_file.xml", "rb").read()
err_col = 52459
print repr(text[err_col-50:err_col+100]) # should include the error text
print repr(text[:50]) # show the XML declaration

(2) 把你的文件放到一个在线的XML验证服务上,比如 http://www.validome.org/xml/ 或者 http://validator.aborla.net/

然后更新你的问题,把你找到的结果展示出来。

更新:这里有一个最简单的xml文件,能说明你的问题:

[badcharref.xml]
<a>&#1;</a>

[Python 2.7.1 output]
>>> import xml.etree.ElementTree as ET
>>> it = ET.iterparse(file("badcharref.xml"))
>>> for ev, el in it:
...     print el.tag
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\python27\lib\xml\etree\ElementTree.py", line 1258, in next
    self._parser.feed(data)
  File "C:\python27\lib\xml\etree\ElementTree.py", line 1624, in feed
    self._raiseerror(v)
  File "C:\python27\lib\xml\etree\ElementTree.py", line 1488, in _raiseerror
    raise err
xml.etree.ElementTree.ParseError: reference to invalid character number: line 1, column 3
>>>

并不是所有有效的Unicode字符在XML中都是有效的。可以查看 XML 1.0规范

你可能想用一些正则表达式来检查你的文件,比如 r'&#([0-9]+);'r'&#x([0-9A-Fa-f]+);',把匹配到的文本转换成整数,并检查它是否在规范的有效列表中,也就是 #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

...或者可能是数字字符引用在语法上无效,比如没有以 ; 结束,或者像 &#not-a-digit 这样的情况等等。

更新 2 我之前说错了,ElementTree错误信息中的数字是计算Unicode代码点,而不是字节。请看下面的代码和运行它时对两个出错文件的输出片段。

# coding: ascii
# Find numeric character references that refer to Unicode code points
# that are not valid in XML.
# Get byte offsets for seeking etc in undecoded file bytestreams.
# Get unicode offsets for checking against ElementTree error message,
# **IF** your input file is small enough. 

BYTE_OFFSETS = True
import sys, re, codecs
fname = sys.argv[1]
print fname
if BYTE_OFFSETS:
    text = open(fname, "rb").read()
else:
    # Assumes file is encoded in UTF-8.
    text = codecs.open(fname, "rb", "utf8").read()
rx = re.compile("&#([0-9]+);|&#x([0-9a-fA-F]+);")
endpos = len(text)
pos = 0
while pos < endpos:
    m = rx.search(text, pos)
    if not m: break
    mstart, mend = m.span()
    target = m.group(1)
    if target:
        num = int(target)
    else:
        num = int(m.group(2), 16)
    # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
    if not(num in (0x9, 0xA, 0xD) or 0x20 <= num <= 0xD7FF
    or 0xE000 <= num <= 0xFFFD or 0x10000 <= num <= 0x10FFFF):
        print mstart, m.group()
    pos = mend

输出:

comments.xml
6615405 &#x10;
10205764 &#x00;
10213901 &#x00;
10213936 &#x00;
10214123 &#x00;
13292514 &#x03;
...
155656543 &#x1B;
155656564 &#x1B;
157344876 &#x10;
157722583 &#x10;

posts.xml
7607143 &#x1F;
12982273 &#x1B;
12982282 &#x1B;
12982292 &#x1B;
12982302 &#x1B;
12982310 &#x1B;
16085949 &#x1C;
16085955 &#x1C;
...
36303479 &#x12;
36303494 &#xFFFF; <<=== whoops
38942863 &#x10;
...
785292911 &#x08;
801282472 &#x13;
848911592 &#x0B;
8

正如@John Machin提到的,这些文件确实包含了一些可疑的数字实体,不过错误信息似乎指向了文本中错误的地方。可能是因为流式处理和缓冲的原因,导致无法准确报告位置。

实际上,这些实体在文本中都出现了:

set(['&#x08;', '&#x0E;', '&#x1E;', '&#x1C;', '&#x18;', '&#x04;', '&#x0A;', '&#x0C;', '&#x16;', '&#x14;', '&#x06;', '&#x00;', '&#x10;', '&#x02;', '&#x0D;', '&#x1D;', '&#x0F;', '&#x09;', '&#x1B;', '&#x05;', '&#x15;', '&#x01;', '&#x03;'])

大多数是不被允许的。看起来这个解析器要求很严格,你需要找一个要求没那么严格的解析器,或者先对XML进行预处理。

撰写回答