为什么ElementTree会引发ParseError?
我一直在尝试用 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 个回答
我不确定这是否能回答你的问题,但如果你想在使用元素树时处理ParseError这个异常,你可以这样做:
except ET.ParseError:
print("catastrophic failure")
print("last successful: {0}".format(last))
这里有一些建议:
(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></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 
10205764 �
10213901 �
10213936 �
10214123 �
13292514 
...
155656543 
155656564 
157344876 
157722583 
posts.xml
7607143 
12982273 
12982282 
12982292 
12982302 
12982310 
16085949 
16085955 
...
36303479 
36303494  <<=== whoops
38942863 
...
785292911 
801282472 
848911592 
正如@John Machin提到的,这些文件确实包含了一些可疑的数字实体,不过错误信息似乎指向了文本中错误的地方。可能是因为流式处理和缓冲的原因,导致无法准确报告位置。
实际上,这些实体在文本中都出现了:
set(['', '', '', '', '', '', '
', '', '', '', '', '�', '', '', '
', '', '', '	', '', '', '', '', ''])
大多数是不被允许的。看起来这个解析器要求很严格,你需要找一个要求没那么严格的解析器,或者先对XML进行预处理。