¿Por qué ElementTree está generando un ParseError?

He estado tratando de analizar un archivo con 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)) 

Esta es, por supuesto, una versión simplificada de mi código, pero esto es suficiente para romper mi progtwig. Obtengo este error con algunos archivos si elimino el bloque try-catch:

 Traceback (most recent call last): File "", line 1, in  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 

Sin embargo, los resultados son deterministas, si un archivo funciona, siempre funcionará. Si un archivo falla, siempre falla y siempre falla en el mismo punto.

Lo más extraño es que estoy usando la traza para averiguar si tengo algún XML con formato incorrecto que esté rompiendo el analizador. Entonces aíslo el nodo que causó el fallo. Pero cuando creo un archivo XML que contiene ese nodo y algunos de sus vecinos, ¡el análisis funciona!

Esto tampoco parece ser un problema de tamaño. He logrado analizar archivos mucho más grandes sin problemas.

¿Algunas ideas?

Como sugirió @John Machin, los archivos en cuestión sí tienen entidades numéricas dudosas, aunque los mensajes de error parecen estar apuntando al lugar equivocado en el texto. Quizás la naturaleza de la transmisión y el almacenamiento en búfer están dificultando el informe de las posiciones precisas.

De hecho, todas estas entidades aparecen en el texto:

 set(['', '', '', '', '', '', '
', '', '', '', '', '�', '', '', '
', '', '', '	', '', '', '', '', '']) 

La mayoría no están permitidos. Parece que este analizador es bastante estricto, tendrás que encontrar otro que no sea tan estricto o preprocesar el XML.

Aquí hay algunas ideas:

(0) Explique “un archivo” y “ocasionalmente”: ¿realmente quiere decir que a veces funciona y falla con el mismo archivo?

Haga lo siguiente para cada archivo que falla:

(1) Averigüe qué hay en el archivo en el punto del que se queja:

 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) Descargue su archivo en un servicio de validación XML basado en la web, por ejemplo, http://www.validome.org/xml/ o http://validator.aborla.net/

y edita tu pregunta para mostrar tus hallazgos.

Actualización : Aquí está el archivo xml mínimo que ilustra su problema:

 [badcharref.xml]  [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 "", line 1, in  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 >>> 

No todos los caracteres válidos de Unicode son válidos en XML. Ver la especificación XML 1.0 .

Es posible que desee examinar sus archivos utilizando expresiones regulares como r'&#([0-9]+);' y r'&#x([0-9A-Fa-f]+);' , convierta el texto coincidente en un int ordinal y cotejelo con la lista válida de la especificación, es decir, #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

… o quizás la referencia numérica de caracteres no es sintácticamente válida, por ejemplo, no terminada por una ; ‘, &#not-a-digit etc., etc.

Actualización 2 Me equivoqué, el número en el mensaje de error de ElementTree está contando puntos de código Unicode, no bytes. Vea el código a continuación y fragmentos de la salida de ejecutarlo sobre los dos archivos incorrectos.

 # 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 

Salida:

 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  

No estoy seguro de si esto responde a su pregunta, pero si desea usar una excepción con el ParseError generado por el árbol de elementos, haría esto:

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

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

Sentí que también podría ser importante tener en cuenta que podría detectar fácilmente su error y evitar tener que detener completamente su progtwig simplemente usando lo que ya está usando más adelante en la función, colocando su statement:

 it = ET.iterparse(file(xml)) 

dentro de un try y excepto el soporte:

 try: it = ET.iterparse(file(xml)) except: print('iterparse error') 

Por supuesto, esto no solucionará su archivo XML o su técnica de preprocesamiento, pero podría ayudar a identificar qué archivo (si está analizando mucho) está causando su error.