Nivel de dificultad:4 sobre 5
La mayoría de los capítulos de este libro se han desarrollado alrededor de un código de ejemplo. Pero XML no trata sobre código, trata sobre datos. Un uso muy común para XML es la ``provisión de contenidos sindicados'' que lista los últimos artículos de un blog, foro u otro sitio web con actualizaciones frecuentes. El software para blogs más popular puede generar fuentes de información y actualizarlas cada vez que hay nuevos artículos, hilos de discusión o nuevas entradas en un blog. Puedes seguir un blog ``escribiéndote'' a su canal (feed), y puedes seguir diversos blogs mediante un agregador de canales como el lector de Google.
Aquí están los datos de XML que utilizaremos en este capítulo. Es un canal --específicamente, una fuente de información sindicada Atom.
<?xml version='1.0' encoding='utf-8'?> <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title>dive into mark</title> <subtitle>currently between addictions</subtitle> <id>tag:diveintomark.org,2001-07-29:/</id> <updated>2009-03-27T21:56:07Z</updated> <link rel='alternate' type='text/html' href='http://diveintomark.org/'/> <link rel='self' type='application/atom+xml' href='http://diveintomark.org/feed/'/> <entry> <author> <name>Mark</name> <uri>http://diveintomark.org/</uri> </author> <title>Dive into history, 2009 edition</title> <link rel='alternate' type='text/html' href='http://diveintomark.org/archives/2009/03/27/ (sigue abajo) dive-into-history-2009-edition'/> <id>tag:diveintomark.org,2009-03-27:/archives/20090327172042</id> <updated>2009-03-27T21:56:07Z</updated> <published>2009-03-27T17:20:42Z</published> <category scheme='http://diveintomark.org' term='diveintopython'/> <category scheme='http://diveintomark.org' term='docbook'/> <category scheme='http://diveintomark.org' term='html'/> <summary type='html'>Putting an entire chapter on one page sounds bloated, but consider this &mdash; my longest chapter so far would be 75 printed pages, and it loads in under 5 seconds&hellip; On dialup.</summary> </entry> |
<entry> <author> <name>Mark</name> <uri>http://diveintomark.org/</uri> </author> <title>Accessibility is a harsh mistress</title> <link rel='alternate' type='text/html' href='http://diveintomark.org/archives/2009/03/21/ (sigue) accessibility-is-a-harsh-mistress'/> <id>tag:diveintomark.org,2009-03-21:/archives/20090321200928</id> <updated>2009-03-22T01:05:37Z</updated> <published>2009-03-21T20:09:28Z</published> <category scheme='http://diveintomark.org' term='accessibility'/> <summary type='html'>The accessibility orthodoxy does not permit people to question the value of features that are rarely useful and rarely used.</summary> </entry> <entry> <author> <name>Mark</name> </author> <title>A gentle introduction to video encoding, part 1: container formats</title> <link rel='alternate' type='text/html' href='http://diveintomark.org/archives/2008/12/18/ (sigue) give-part-1-container-formats'/> <id>tag:diveintomark.org,2008-12-18:/archives/20081218155422</id> <updated>2009-01-11T19:39:22Z</updated> <published>2008-12-18T15:54:22Z</published> <category scheme='http://diveintomark.org' term='asf'/> <category scheme='http://diveintomark.org' term='avi'/> <category scheme='http://diveintomark.org' term='encoding'/> <category scheme='http://diveintomark.org' term='flv'/> <category scheme='http://diveintomark.org' term='GIVE'/> <category scheme='http://diveintomark.org' term='mp4'/> <category scheme='http://diveintomark.org' term='ogg'/> <category scheme='http://diveintomark.org' term='video'/> <summary type='html'>These notes will eventually become part of a tech talk on video encoding.</summary> </entry> </feed> |
Si conoces ya XML puedes saltarte esta sección.
XML es una forma generalizada de describir una estructura de datos jerárquica. Un documento XML contiene uno o más elementos, que están delimitados por etiquetas de inicio y fin. Lo siguiente es un documento XML completo (aunque bastante aburrido).
<foo> </foo> |
Los elementos se pueden anidar a cualquier profundidad. Si un elemento bar se encuentra dentro de un elemento foo, se dice que bar es un subelemento o hijo de foo.
<foo> <bar></bar> </foo> |
Al primer elemento de un documento XML se le llama el elemento raíz. Un documento XML únicamente puede tener un elemento raíz. Lo siguiente no es un documento XML porque tiene dos elementos raíz:
<foo></foo> <bar></bar> |
Los elementos pueden tener atributos, que son parejas de nombres con valores. Los atributos se deben incluir dentro de la etiqueta de inicio del elemento y deben estar separados por un espacio en blanco. Los nombres de atributo no se pueden repetir dentro de un elemento. Los valores de los atributos deben ir entre comillas. Es posible utilizar tanto comillas simples como dobles.
<foo lang='en'> <bar id='papayawhip' lang="fr"></bar> </foo> |
Si un elemento tiene más de un atributo, el orden de los mismos no es significativo. Los atributos de un elemento forman un conjunto desordenado de claves y valores, como en un diccionario de Python. No existe límite en el número de atributos que puedes definir para cada elemento.
Los elementos pueden contener texto.
<foo lang='en'> <bar lang='fr'>PapayaWhip</bar> </foo> |
Existe una forma de escribir elementos vacíos de forma compacta. Colocando un carácter / al final de la etiqueta de inicio se puede evitar tener que escribir la etiqueta de fin. El documento XML del ejemplo anterior se puede escribir de esta otra forma:
<foo/> |
Como pasa con las funciones de Python que se pueden declarar en diferentes módulos, los elementos XML se pueden declarar en diferentes espacios de nombre. Los espacios de nombre se suelen representar como URLs. Se puede utilizar una declaración xmlns para definir un espacio de nombres por defecto. Una declaración de un espacio de nombres es parecida a un atributo, pero tiene un significado y propósito diferente.
<feed xmlns='http://www.w3.org/2005/Atom'> <title>dive into mark</title> </feed> |
<atom:feed xmlns:atom='http://www.w3.org/2005/Atom'> <atom:title>dive into mark</atom:title> </atom:feed> |
En lo que concierne al analizador de XML, los dos documentos anteriores son idénticos. Espacio de nombres + nombre de elemento = identidad en XML. Los prefijos existen únicamente para referirse a los espacios de nombres, por lo que el prefijo utilizado en la práctica (atom:) es irrelevante. Los espacios de nombre coinciden, los nombres de elemento coinciden, los atributos (o falta de ellos) coinciden y cada contenido de texto coincide, por lo que estos dos documentos XML son el idénticos a efectos prácticos.
Finalmente, los documentos XML pueden contener en la primera línea información sobre la codificación de caracteres, antes del elemento raíz. Si tienes curiosidad sobre cómo un documento puede contener información que necesita conocerse antes de que el documento pueda analizarse consulta la Sección F de la especificación XML (http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info) para ver los detalles sobre cómo resolver este problema.
<?xml version='1.0' encoding='utf-8'?> |
Y con esto ya conoces suficiente XML como para ¡ser peligroso!
Piensa en un blog o en cualquier sitio web que tenga contenido frecuentemente actualizado como CNN.com. El propio sitio dispone de un título (CNN.com), un subtítulo (Breaking News, U.S., World, Weather, Entertaintment y Video News), una fecha de última actualización (actualizado a 12:43 p.m. EDT, Sat May 16, 2009) y una lista de artículos publicados en diferente momentos. Cada artículo, a su vez, tiene título, una fecha de primera publicación (y posiblemente una fecha de última actualización, si se publicó una corrección) y una URL única.
El formato de sindicación de contenidos Atom está diseñado para capturar toda esta información en un formato estándar. Mi blog y CNN.com son muy diferentes en diseño, ámbito y audiencia; pero ambos tienen la misma estructura básica. CNN.com tiene un título, mi blog tiene un título. CNN.com publica artículos, yo publico artículos.
En el nivel más alto existe el elemento raíz, que toda fuente Atom comparte: el elemento feed del espacio de nombres http://www.w3.org/2005/Atom.
<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> |
Una fuente Atom contiene diversas partes de información sobre la propia fuente. Se declaran como hijas del elemento raíz feed.
<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title>dive into mark</title> <subtitle>currently between addictions</subtitle> <id>tag:diveintomark.org,2001-07-29:/</id> <updated>2009-03-27T21:56:07Z</updated> <link rel='alternate' type='text/html' href='http://diveintomark.org/'/> |
Ahora ya conocemos que esta fuente lo es de un sitio denominado ``dive into mark'' que está disponible en http://diveintomark.org y que fue actualizada por última vez el 27 de marzo de 2009.
Aunque el orden de los elementos puede ser relevante en algunos documentos XML, no es relevante en una fuente Atom.
Después de los metadatos de la fuente se encuentra una lista con los artículos más recientes. Un artículo se representa así:
<entry> <author> <name>Mark</name> <uri>http://diveintomark.org/</uri> </author> <title>Dive into history, 2009 edition</title> <link rel='alternate' type='text/html' href='http://diveintomark.org/archives/2009/03/27/ dive-into-history-2009-edition'/> <id>tag:diveintomark.org,2009-03-27:/archives/20090327172042</id> <updated>2009-03-27T21:56:07Z</updated> <published>2009-03-27T17:20:42Z</published> <category scheme='http://diveintomark.org' term='diveintopython'/> <category scheme='http://diveintomark.org' term='docbook'/> <category scheme='http://diveintomark.org' term='html'/> <summary type='html'>Putting an entire chapter on one page sounds bloated, but consider this &mdash; my longest chapter so far would be 75 printed pages, and it loads in under 5 seconds &hellip; On dialup.</summary> </entry> |
Python puede analizar documentos XML de diversas formas. Dispone de analizadores DOM y SAX como otros lenguajes, pero me centraré en una librería diferente denominada ElementTree.
»> import xml.etree.ElementTree as etree »> tree = etree.parse('examples/feed.xml') »> root = tree.getroot() »> root <Element http://www.w3.org/2005/Atomfeed at cd1eb0> |
ElementTree representa a los elementos XML como {espacio_de_nombres}nombre_local. Verás y utilizarás este formato en muchos lugares de la API de ElementTree.
En la API de ElementTree los elementos se comportan como listas. Los elementos de la lista son los hijos del elemento.
# sigue del ejemplo anterior »> root.tag 'http://www.w3.org/2005/Atomfeed' »> len(root) 8 »> for child in root: ... print(child) ... <Element http://www.w3.org/2005/Atomtitle at e2b5d0> <Element http://www.w3.org/2005/Atomsubtitle at e2b4e0> <Element http://www.w3.org/2005/Atomid at e2b6c0> <Element http://www.w3.org/2005/Atomupdated at e2b6f0> <Element http://www.w3.org/2005/Atomlink at e2b4b0> <Element http://www.w3.org/2005/Atomentry at e2b720> <Element http://www.w3.org/2005/Atomentry at e2b510> <Element http://www.w3.org/2005/Atomentry at e2b750> |
Puede que ya te hayas dado cuenta, pero quiero dejarlo explícito: la lista de los elementos hijo, únicamente incluye los hijos directos. Cada uno de los elementos entry tiene sus propios hijos, pero no se muestran en esta lista. Estarán incluidos en la lista de hijos del elemento entry, pero no se encuentran en la lista de feed. Existen formas de encontrar elementos independientemente de los profundamente anidados que se encuentren; lo veremos más adelante en este mismo capítulo.
XML no solamente es una colección de elementos; cada elemento puede tener también su propio conjunto de atributos. Una vez tienes la referencia a un elemento específico puedes recuperar fácilmente sus atributos utilizando un diccionario de Python.
# sigue del ejemplo anterior »> root.attrib 'http://www.w3.org/XML/1998/namespacelang': 'en' »> root[4] <Element http://www.w3.org/2005/Atomlink at e181b0> »> root[4].attrib 'href': 'http://diveintomark.org/', 'type': 'text/html', 'rel': 'alternate' »> root[3] <Element http://www.w3.org/2005/Atomupdated at e2b4e0> »> root[3].attrib |
Hasta ahora hemos trabajado con este documento XML de ``arriba hacia abajo'', comenzando por el elemento raíz, recuperando sus hijos y luego los nietos, etc. Pero muchas aplicaciones de XML necesitan encontrar elementos específicos. ElementTree puede hacer esto también.
»> import xml.etree.ElementTree as etree »> tree = etree.parse('examples/feed.xml') »> root = tree.getroot() »> root.findall('http://www.w3.org/2005/Atomentry') [<Element http://www.w3.org/2005/Atomentry at e2b4e0>, <Element http://www.w3.org/2005/Atomentry at e2b510>, <Element http://www.w3.org/2005/Atomentry at e2b540>] »> root.tag 'http://www.w3.org/2005/Atomfeed' »> root.findall('http://www.w3.org/2005/Atomfeed') [] »> root.findall('http://www.w3.org/2005/Atomauthor') [] |
»> tree.findall('http://www.w3.org/2005/Atomentry') [<Element http://www.w3.org/2005/Atomentry at e2b4e0>, <Element http://www.w3.org/2005/Atomentry at e2b510>, <Element http://www.w3.org/2005/Atomentry at e2b540>] »> tree.findall('http://www.w3.org/2005/Atomauthor') [] |
También hay un método find() que retorna el primer elemento que coincide. Es útil para aquellas situaciones en las que únicamente esperas una coincidencia, o cuando haya varias pero solamente te importa la primera de ellas.
»> entries = tree.findall('http://www.w3.org/2005/Atomentry') »> len(entries) 3 »> title_element = entries[0].find('http://www.w3.org/2005/Atomtitle') »> title_element.text 'Dive into history, 2009 edition' »> foo_element = entries[0].find('http://www.w3.org/2005/Atomfoo') »> foo_element »> type(foo_element) <class 'NoneType'> |
Hay una complicación en el método find() que te pasará en algún momento. En un contexto booleano los objetos elemento de ElementTree se evalúan a False si no tienen hijos (si len(element) es cero). Esto significa que if element.find('...') no está comprobando si el método find() encontró un elemento coincidente; está comprobando si ¡el elemento coincidente tiene algún elemento hijo! Para comprobar si el método find() retornó algún elemento debes utilizar if element.find('...') is not None.
Existe una forma de buscar entre los elementos descendientes: hijos, nietos y niveles más profundos de anidamiento.
»> all_links = tree.findall('//http://www.w3.org/2005/Atomlink') »> all_links [<Element http://www.w3.org/2005/Atomlink at e181b0>, <Element http://www.w3.org/2005/Atomlink at e2b570>, <Element http://www.w3.org/2005/Atomlink at e2b480>, <Element http://www.w3.org/2005/Atomlink at e2b5a0>] »> all_links[0].attrib 'href': 'http://diveintomark.org/', 'type': 'text/html', 'rel': 'alternate' »> all_links[1].attrib 'href': 'http://diveintomark.org/archives/2009/03/27/ dive-into-history-2009-edition', 'type': 'text/html', 'rel': 'alternate' »> all_links[2].attrib 'href': 'http://diveintomark.org/archives/2009/03/21/ accessibility-is-a-harsh-mistress', 'type': 'text/html', 'rel': 'alternate' »> all_links[3].attrib 'href': 'http://diveintomark.org/archives/2008/12/18/ give-part-1-container-formats', 'type': 'text/html', 'rel': 'alternate' |
En general, el método findall() de ElementTree es una característica muy potente, pero el lenguaje de consulta puede ser un poco sorprendente. Está descrito oficialmente en http://effbot.org/zone/element-xpath.htm(Soporte limitado a expresiones XPath). XPath es un estándar del W3C para consultar documentos XML. El lenguaje de consulta de ElementTree es suficientemente parecido a XPath para poder hacer búsquedas básicas, pero también suficientemente diferente como para desconcertarte si ya conoces XPath.
Ahora vamos a ver una librería de terceros que extiende la API de ElementTree para proporcionar un soporte completo de XPath.
lxml7.3 es una librería de terceros de código abierto que se desarrolla sobre el popular analizador libxml27.4. Proporciona una API que es 100% compatible con ElementTree, y la extiende con soporte completo a Xpath 1.0 y otras cuantas bondades. Existe un instalador disponible para Windows7.5; los usuarios de Linux siempre deberían intentar usar las herramientas específicas de la distribución como yum o apt-get para instalar los binarios precompilados desde sus repositorios. En otro caso, necesitarás instalar los binarios manualmente7.6.
»> from lxml import etree »> tree = etree.parse('examples/feed.xml') »> root = tree.getroot() »> root.findall('http://www.w3.org/2005/Atomentry') [<Element http://www.w3.org/2005/Atomentry at e2b4e0>, <Element http://www.w3.org/2005/Atomentry at e2b510>, <Element http://www.w3.org/2005/Atomentry at e2b540>] |
Para documentos XML grandes, lxml es significativamente más rápido que la librería ElementTree. Si solamente estás utilizando la API ElementTree y quieres usar la implementación más rápida existente, puedes intentar importar lxml y de no estar disponible, usar como segunda opción ElementTree.
try: from lxml import etree except ImportError: import xml.etree.ElementTree as etree |
Pero lxml proporciona algo más que el ser más rápido que ElementTree. Su método findall() incluye el soporte de expresiones más complicadas.
»> import lxml.etree »> tree = lxml.etree.parse('examples/feed.xml') »> tree.findall('//http://www.w3.org/2005/Atom*[@href]') [<Element http://www.w3.org/2005/Atomlink at eeb8a0>, <Element http://www.w3.org/2005/Atomlink at eeb990>, <Element http://www.w3.org/2005/Atomlink at eeb960>, <Element http://www.w3.org/2005/Atomlink at eeb9c0>] »> tree.findall("//http://www.w3.org/2005/Atom*" "[@href='http://diveintomark.org/']") [<Element http://www.w3.org/2005/Atomlink at eeb930>] »> NS = 'http://www.w3.org/2005/Atom' »> tree.findall('//NSauthor[NSuri]'.format(NS=NS)) [<Element http://www.w3.org/2005/Atomauthor at eeba80>, <Element http://www.w3.org/2005/Atomauthor at eebba0>] |
¿No es suficiente para ti? lxml tampoco integra soporte de expresiones XPath 1.0. No voy a entrar en profundidad en la sintaxis de XPath; se podría escribir un libro entero sobre ello. Pero te mostraré cómo se integra en lxml.
»> import lxml.etree »> tree = lxml.etree.parse('examples/feed.xml') »> NSMAP = 'atom': 'http://www.w3.org/2005/Atom' »> entries = tree.xpath("//atom:category[@term='accessibility']/..", ... namespaces=NSMAP) »> entries [<Element http://www.w3.org/2005/Atomentry at e2b630>] »> entry = entries[0] »> entry.xpath('./atom:title/text()', namespaces=NSMAP) ['Accessibility is a harsh mistress'] |
El soporte a XML de Python no está limitado al análisis de documentos existentes. Puedes crear también documentos XML desde cero.
»> import xml.etree.ElementTree as etree »> new_feed = etree.Element('http://www.w3.org/2005/Atomfeed', ... attrib='http://www.w3.org/XML/1998/namespacelang': 'en') »> print(etree.tostring(new_feed)) <ns0:feed xmlns:ns0='http://www.w3.org/2005/Atom' xml:lang='en'/> |
¿Te ha sorprendido el resultado de la serialización? La forma en la que ElementTree serializa los elementos con espacios de nombre XML es técnicamente precisa pero no óptima. El documento XML de ejemplo al comienzo del capítulo definió un espacio de nombres por defecto (xmlns='http://www.w3.org/2005/Atom'). La definición de un espacio de nombres por defecto es útil para documentos --como las fuentes Atom-- en los que todos, o la mayoría de, los elementos pertenecen al mismo espacio de nombres, porque puedes declarar el espacio de nombres una única vez y declarar cada elemento únicamente con su nombre local (<feed>, <link>, <entry>). No hay necesidad de utilizar prefijos a menos que quieras declarar elementos de otro espacio de nombres.
Un analizador XML no verá ninguna diferencia entre un documento XML con un espacio de nombres por defecto y un documento XML con un espacio de nombres con prefijo. El DOM resultante de esta serialización:
<ns0:feed xmlns:ns0='http://www.w3.org/2005/Atom' xml:lang='en'/> |
Es idéntico al DOM de esta otra:
<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'/> |
La única diferencia práctica es que la segunda serialización es varios caracteres más corta. Si tuviéramos que modificar nuestro ejemplo para añadirle el prefijo ns0: en cada etiqueta de inicio y fin, serían 4 caracteres por cada etiqueta de inicio x 79 etiquetas + 4 caracteres por la propia declaración del espacio de nombres, en total son 320 caracteres más. En el caso de que asumamos una codificación de caracteres UTF-8 se trata de 320 bytes extras (después de comprimir la diferencia se reduce a 21 bytes). Puede que no te importe mucho, pero para una fuente Atom, que puede descargarse miles de veces cada vez que cambia, una diferencia de unos cuantos bytes por petición puede suponer una cierta diferencia.
La librería ElementTree no ofrece un control fino sobre la serialización de los elementos con espacios de nombres, pero lxml sí:
»> import lxml.etree »> NSMAP = None: 'http://www.w3.org/2005/Atom' »> new_feed = lxml.etree.Element('feed', nsmap=NSMAP) »> print(lxml.etree.tounicode(new_feed)) <feed xmlns='http://www.w3.org/2005/Atom'/> »> new_feed.set('http://www.w3.org/XML/1998/namespacelang', 'en') »> print(lxml.etree.tounicode(new_feed)) <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'/> |
¿Están los documentos XML limitados a un elemento por documento? Por supuesto que no. Puedes crear hijos de forma fácil.
»> title = lxml.etree.SubElement(new_feed, 'title', ... attrib='type':'html') »> print(lxml.etree.tounicode(new_feed)) <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title type='html'/></feed> »> title.text = 'dive into …' »> print(lxml.etree.tounicode(new_feed)) <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title type='html'>dive into &hellip;</title></feed> »> print(lxml.etree.tounicode(new_feed, pretty_print=True)) <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title type='html'>dive into&hellip;</title> </feed> |
Podrías querer echarle un vistazo a xmlwitch7.7, otra librería de terceros para generar XML. Hace uso extensivo de la sentencia with para hacer la generación de código XML más legible.
La especificación XML obliga a que todos los analizadores XML empleen un manejo de errores ``draconiano''. Esto es, deben parar tan pronto como detecten cualquier clase de error de ``malformado'' del documento. Errores de mala formación del documento son: que las etiquetas de inicio y fin no se encuentren bien balanceadas, entidades sin definir, caracteres unicode ilegales y otro número de reglas esotéricas. Esto es un contraste importante con otros formatos habituales como HTML --tu navegador no para de mostrar una página web si se te olvida cerrar una etiqueta HTML o aparece un escape o ampersand en el valor de un atributo (Es un concepto erróneo bastante extendido que HTML no tiene definida una forma de hacer manejo de errores. Sí que está bien definido, pero es significativamente más complejo que ``párate ante el primer error que encuentres''.
Algunas personas (yo mismo incluido) creen que fue un error para los inventores del XML obligar a este manejo de errores ``draconianos''. No me malinterpretes; puedo comprender el encanto de la simplificación de las reglas de manejo de errores. Pero en la práctica, el concepto de ``bien formado'' es más complejo de lo que suena, especialmente para aquellos documentos XML (como los documentos Atom) se publican en la web mediante un servidor HTTP. A pesar de la madurez de XML, cuyo manejo estandarizado de errores es de 1997, las encuestas muestran continuamente que una significativa fracción de fuentes Atom de la web están plagadas con errores de ``buena formación''.
Por eso, tengo razones teóricas y prácticas para analizar documentos XML a ``cualquier precio'', esto es, para no parar ante el primer error de formación. Si te encuentras tú mismo en esta situación, lxml puede ayudar.
Aquí hay un fragmento de un documento XML mal formado. El ampersand debería estar ``escapado''.
<?xml version='1.0' encoding='utf-8'?> <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title>dive into …</title> ... </feed> |
Eso es un error, porque la entidad … no está definida en XML (está definida en HTML). Si intentas analizar este documento XML con los valores por defecto, lxml parará en la entidad sin definir.
»> import lxml.etree »> tree = lxml.etree.parse('examples/feed-broken.xml') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "lxml.etree.pyx", line 2693, in lxml.etree.parse (src/lxml/lxml.etree.c:52591) File "parser.pxi", line 1478, in lxml.etree._parseDocument (src/lxml/lxml.etree.c:75665) File "parser.pxi", line 1507, in lxml.etree._parseDocumentFromURL (src/lxml/lxml.etree.c:75993) File "parser.pxi", line 1407, in lxml.etree._parseDocFromFile (src/lxml/lxml.etree.c:75002) File "parser.pxi", line 965, in lxml.etree._BaseParser._parseDocFromFile (src/lxml/lxml.etree.c:72023) File "parser.pxi", line 539, in lxml.etree._ParserContext._handleParseResultDoc (src/lxml/lxml.etree.c:67830) File "parser.pxi", line 625, in lxml.etree._handleParseResult (src/lxml/lxml.etree.c:68877) File "parser.pxi", line 565, in lxml.etree._raiseParseError (src/lxml/lxml.etree.c:68125) lxml.etree.XMLSyntaxError: Entity 'hellip' not defined, line 3, column 28 |
Para analizar este documento, a pesar de su error de buena formación, necesitas crear un analizador XML específico.
»> parser = lxml.etree.XMLParser(recover=True) »> tree = lxml.etree.parse('examples/feed-broken.xml', parser) »> parser.error_log examples/feed-broken.xml:3:28:FATAL:PARSER:ERR_UNDECLARED_ENTITY: Entity 'hellip' not defined »> tree.findall('http://www.w3.org/2005/Atomtitle') [<Element http://www.w3.org/2005/Atomtitle at ead510>] »> title = tree.findall('http://www.w3.org/2005/Atomtitle')[0] »> title.text 'dive into ' »> print(lxml.etree.tounicode(tree.getroot())) <feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title>dive into </title> . . [resto de la serialización suprimido por brevedad] . |
Es importante reiterar que no existe garantía de interoperabilidad entre analizadores XML que se recuperan de los errores. Una analizador diferente podría decidir que reconoce la entidad … de HTML y reemplazarla por &hellip; ¿Es esto mejor? Puede ser. ¿Es más correcto? No, ambas soluciones son igualmente erróneas. El comportamiento correcto (de acuerdo a la especificación XML) es pararse y elevar el error. Si has decidido que no es lo que quieres hacer, lo haces bajo tu propia responsabilidad.
José Miguel González Aguilera 2016-08-18