Почему 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))
Это конечно упрощенная версия мой код, но этого достаточно, чтобы сломать мою программу. Я получаю эту ошибку с некоторыми файлами, если я удаляю блок try-catch:
Traceback (most recent call last):
File "<pyshell#22>", line 1, in <module>
from yparse import analyze; analyze('file.xml')
File "C:Python27yparse.py", line 10, in analyze
for (ev, el) in it:
File "C:Python27libxmletreeElementTree.py", line 1258, in next
self._parser.feed(data)
File "C:Python27libxmletreeElementTree.py", line 1624, in feed
self._raiseerror(v)
File "C:Python27libxmletreeElementTree.py", line 1488, in _raiseerror
raise err
ParseError: reference to invalid character number: line 1, column 52459
результаты детерминированы, хотя, если файл работает, он всегда будет работать. Если файл терпит неудачу, он всегда терпит неудачу и всегда терпит неудачу в одной и той же точке.
самое странное, что я использую трассировку, чтобы узнать, если я у любой XML-документ, который ломает парсер. Затем я изолирую узел, вызвавший сбой. Но когда я создаю XML-файл, содержащий этот узел и несколько соседей, парсинг работает!
Это, похоже,не проблема размера. Мне удалось проанализировать гораздо большие файлы без проблем.
какие идеи?
4 ответов
Как предположил @John Machin, в рассматриваемых файлах есть сомнительные числовые объекты, хотя сообщения об ошибках, похоже, указывают на неправильное место в тексте. Возможно, потоковый характер и буферизация затрудняют представление точных позиций.
фактически, все эти сущности появляются в тексте:
set(['', '', '', '', '', '', '
', '', '', '', '', '�', '', '', '
', '', '', '	', '', '', '', '', ''])
большинство не допускаются. Похоже, этот парсер довольно строгий, вам нужно будет найти другой, который не так строг, или предварительная обработка XML.
вот некоторые идеи:
(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
>>>
не все допустимые символы Юникода допустимы в XML. Вижу XML 1.0 спецификация.
вы можете изучить свои файлы с помощью регулярных выражений, таких как r'&#([0-9]+);'
и r'&#x([0-9A-Fa-f]+);'
, преобразовать сопоставленный текст в порядковый номер int и проверить против действительного списка из спецификации, т. е. #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
... или, может быть, числовая символьная ссылка синтаксически недействительна, например, не завершается ;
', &#not-a-digit
etc etc
обновление 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 
Я не уверен, что это отвечает на ваш вопрос, но если вы хотите использовать исключение с ParseError, вызванным деревом элементов, вы сделаете это:
except ET.ParseError:
print("catastrophic failure")
print("last successful: {0}".format(last))
Я чувствовал, что также может быть важно отметить здесь, что вы можете довольно легко поймать свою ошибку и избежать необходимости полностью остановить свою программу, просто используя то, что вы уже используете позже в функции, разместив свое заявление:
it = ET.iterparse(file(xml))
внутри скобки try & except:
try:
it = ET.iterparse(file(xml))
except:
print('iterparse error')
конечно, это не исправит ваш XML-файл или метод предварительной обработки, но может помочь в определении того, какой файл (если вы разбираете много) вызывает вашу ошибку.