9.3. Análisis de XML

Como iba diciendo, analizar un documento XML es muy sencillo: una línea de código. A dónde ir partiendo de eso es cosa suya.

Ejemplo 9.8. Carga de un documento XML (ahora de verdad)

>>> from xml.dom import minidom                                          1
>>> xmldoc = minidom.parse('~/diveintopython/common/py/kgp/binary.xml')  2
>>> xmldoc                                                               3
<xml.dom.minidom.Document instance at 010BE87C>
>>> print xmldoc.toxml()                                                 4
<?xml version="1.0" ?>
<grammar>
<ref id="bit">
  <p>0</p>
  <p>1</p>
</ref>
<ref id="byte">
  <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
</ref>
</grammar>
1 Como vio en la sección anterior, esto importa el módulo minidom del paquete xml.dom.
2 Ésta es la línea de código que hace todo el trabajo: minidom.parse toma un argumento y devuelve una representación analizada del documento XML. El argumento puede ser muchas cosas; en este caso, es sólo el nombre del fichero de un documento XML en mi disco (para poder seguir, tendrá que cambiar la ruta para que coincida con el directorio de ejemplos que habrá descargado). Pero también puede pasarle un objeto de fichero, o incluso un objeto que simule un fichero. Aprovecharemos esta flexibilidad más adelante.
3 El objeto devuelto por minidom.parse es un objeto de tipo Document, una clase descendiente de Node. Este objeto Document es la raíz de una estructura compleja de tipo árbol de objetos de Python coordinados de manera que representan completamente el documento XML que le indicó a minidom.parse.
4 toxml es un método de la clase Node (y por tanto está disponible en el objeto Document que obtuvo de minidom.parse). toxml imprime el XML que representa este Node. En el caso del nodo Document, imprime el documento XML entero.

Ahora que tiene el documento XML en memoria, puede empezar a recorrerlo.

Ejemplo 9.9. Obtener nodos hijos

>>> xmldoc.childNodes    1
[<DOM Element: grammar at 17538908>]
>>> xmldoc.childNodes[0] 2
<DOM Element: grammar at 17538908>
>>> xmldoc.firstChild    3
<DOM Element: grammar at 17538908>
1 Cada Node tiene un atributo childNodes, que es una lista de objetos Node. Un Document siempre tiene un único nodo hijo, el elemento raíz del documento XML (en este caso, el elemento grammar).
2 Para obtener el primer nodo hijo (en este caso, el único), use la sintaxis normal de las listas. Recuerde, no hay nada especial aquí; es sólo una lista normal de Python que contiene objetos normales.
3 Como obtener el primer nodo hijo de un nodo es una actividad útil y común, la clase Node cuenta con el atributo firstChild, que es sinónimo de childNodes[0] (también tiene un atributo lastChild, que es sinónimo de childNodes[-1]).

Ejemplo 9.10. toxml funciona en cualquier nodo

>>> grammarNode = xmldoc.firstChild
>>> print grammarNode.toxml() 1
<grammar>
<ref id="bit">
  <p>0</p>
  <p>1</p>
</ref>
<ref id="byte">
  <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
</ref>
</grammar>
1 Como el método toxml está definido en la clase Node, está disponible en cualquier nodo de XML, no sólo en el elemento Document.

Ejemplo 9.11. Los nodos hijos pueden ser un texto

>>> grammarNode.childNodes                  1
[<DOM Text node "\n">, <DOM Element: ref at 17533332>, \
<DOM Text node "\n">, <DOM Element: ref at 17549660>, <DOM Text node "\n">]
>>> print grammarNode.firstChild.toxml()    2



>>> print grammarNode.childNodes[1].toxml() 3
<ref id="bit">
  <p>0</p>
  <p>1</p>
</ref>
>>> print grammarNode.childNodes[3].toxml() 4
<ref id="byte">
  <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
</ref>
>>> print grammarNode.lastChild.toxml()     5


1 Viendo el XML de binary.xml, podría pensar que el nodo grammar sólo tiene dos hijos, los dos elementos ref. Pero está olvidándose de algo: ¡los retornos de carro! Tras '<grammar>' y antes del primer '<ref>' hay un retorno de carro, y este texto cuenta como nodo hijo del elemento grammar. De forma similar, hay un retorno de carro tras cada '</ref>'; que también cuentan como nodos hijos. Así que grammar.childNodes en realidad es una lista de 5 objetos: 3 objetos Text y 2 Element.
2 El primer hijo es un objeto Text que representa el retorno de carro tras la etiqueta '<grammar>' y antes de la primera etiqueta '<ref>'.
3 El segundo hijo es un objeto Element que representa el primer elemento ref.
4 El cuarto hijo es un objeto Element que representa el segundo elemento ref.
5 El último hijo es un objeto Text que representa el retorno de carro que hay antes de la etiqueta de fin '</ref>' antes de la etiqueta '</grammar>'.

Ejemplo 9.12. Explorando en busca del texto

>>> grammarNode
<DOM Element: grammar at 19167148>
>>> refNode = grammarNode.childNodes[1] 1
>>> refNode
<DOM Element: ref at 17987740>
>>> refNode.childNodes                  2
[<DOM Text node "\n">, <DOM Text node "  ">, <DOM Element: p at 19315844>, \
<DOM Text node "\n">, <DOM Text node "  ">, \
<DOM Element: p at 19462036>, <DOM Text node "\n">]
>>> pNode = refNode.childNodes[2]
>>> pNode
<DOM Element: p at 19315844>
>>> print pNode.toxml()                 3
<p>0</p>
>>> pNode.firstChild                    4
<DOM Text node "0">
>>> pNode.firstChild.data               5
u'0'
1 Como vio en los ejemplos anteriores, el primer elemento ref es grammarNode.childNodes[1], ya que childNodes[0] es el nodo Text del retorno de carro.
2 El elemento ref tiene su propio juego de nodos hijos, uno por el retorno de carro, otro para los espacios, otro más para el elemento p, etc..
3 Puede usar el método toxml incluso aquí, en las profundidades del documento.
4 El elemento p tiene un solo nodo hijo (no podría adivinarlo por este ejemplo, pero mire pNode.childNodes si no me cree), que es un nodo Text para el carácter '0'.
5 El atributo .data de un nodo Text le da la cadena que representa el texto del nodo. Pero, ¿qué es esa 'u' delante de la cadena? La respuesta para eso merece su propia sección.