Leyendo XML en Python: MiniDOM


Por Sebastián Bassi:

Conocimiento previos: Suponemos que el lector sabe que es un archivo XML y cuales son sus componentes básicos. También suponemos conocimientos elementales de Python, aunque no de tratamiento de XML con Python.

IMPORTANTE: Documento no terminado, acepto críticas de todo tipo.


Teoría de DOM (Document Object Model): PONER TEORIA DOM.

Python tiene amplio soporte XML, incluyendo por supuesto DOM. Uno de los módulos usados para DOM es minidom, que está en xml.dom.minidom. Minidom provee un método llamado parse que es quien hace el trabajo sucio de leer todo el archivo, analizar la estructura contenida en el XML y reproducirla en una representación accesible desde nuestro programa Python.
Veamos a parse en acción:

>>>from xml.dom.minidom import parse
>>>midom=parse("/home/sbassi/bioinfo/smallUniprot.xml")

Esto crea un objeto llamado "midom" que contiene una representación en forma de árbol de los datos contenidos en smallUniprot.xml. A partir de ahora nos referiremos a ese objeto para extraer la información contenida en el archivo XML. En este caso, encuentro interesante la información de secuencia y sus datos asociados, aunque los métodos descriptos aquí sirven para extraer cualquier otro elemento de cualquier archivo XML.

Veamos que herramientas nos proveé Python para navegar este árbol:

childNodes: Un método que devuelve una lista de todos los nodos que hay dentro del nodo al que se le aplica este atributo. Si lo aplicamos a nuestro objeto derivado del parser:

>>> midom.childNodes
[<DOM Element: uniprot at 0xb65efccc>]

El resultado es una lista con solo un item: La referencia a un elemento llamado "uniprot". Esto era esperable, ya que cada archivo XML tiene un nodo del que cuelgan todos los demas, y ese es el elemento encontrado. Una manera de acceder directamente a ese elemento particular, es usando documentElement:

>>> midom.documentElement
<DOM Element: uniprot at 0xb65efccc>

Si queremos ver mas alla del elemento "uniprot", volvemos a aplicar childNodes, solo que está vez lo hacemos sobre el primer item de la lista que devuelve el primer childNodes:

>>> a=midom.childNodes[0].childNodes
>>> print a
[<DOM Text node "
">, <DOM Element: entry at 0xb65ef50c>, <DOM Text node "
">]

El resultado en este caso es una lista con tres items, dos de tipo texto y uno tipo "elemento". Los objetos de texto corresponden a lo que se encuentra entre las etiquetas. Si miramos el documento original, veremos que lo único que hay entre la etiqueta "uniprot" y "entry" es un retorno de carro (\n). Para obtener este retorno de carro desde Python, usamos el método data, asi:

>>>a[0].data
"\n"

En el caso de los elementos, el método a utiizar es nodeName. Se usa de la misma manera que data:

>>>a[1].nodeName
"entry"

Probablemente se esté preguntando como darse cuenta de que tipo de nodo se trata, para eso tiene el método noteType, que devuelve un entero (del 1 al 12) que representa a los distintos tipos de nodos posibles. Para evitar tener que recordar que representa cada entero, la clase nodo cuenta con constantes simbolicas para todos ellos. A continuación la lista de tipos de nodos:

ELEMENT_NODE, ATTRIBUTE_NODE, TEXT_NODE, CDATA_SECTION_NODE, ENTITY_NODE, PROCESSING_INSTRUCTION_NODE, COMMENT_NODE, DOCUMENT_NODE, DOCUMENT_TYPE_NODE, NOTATION_NODE

Si queremos saber que clase de elemento es "entry":

>>> a[1].nodeType
1

O podemos chequear directamente su tipo usando estas constantes:

>>> a[1].nodeType==x[1].TEXT_NODE
False
>>> a[1].nodeType==a[1].ELEMENT_NODE
True

Para seguir recorriendo el árbol, podemos seguir usando childnodes sobre el nodo anterior (pruebe que pasa con "a[1].childNodes") o buscar directamente un elemento en particular.

El método que se usa para buscar un elemento cuya etiqueta es conocida es getElementsByTagName(TAGNAME), que devuelve una lista:

>>> a[1].getElementsByTagName("sequence")
[<DOM Element: sequence at 0xb65fff6c>]

Combinando todo lo visto hasta ahora, podemos obtener la secuencia de una vez por todas:

>>> a[1].getElementsByTagName("sequence")[0].childNodes[0].data
'\nMPKKKPTPIQLNPAPDGSAVNGTSSAETNLEALQKKLEELELDEQQRKRL\nEAFLTQKQKVGELKDD...'

Si bien obtenemos un string con la secuencia, también tenemos algunos '\n' no queridos, que podemos filtrar con el método replace:

>>> a[1].getElementsByTagName("sequence")[0].childNodes[0].data.replace("\n","")
'MPKKKPTPIQLNPAPDGSAVNGTSSAETNLEALQKKLEELELDEQQRKRLEAFLTQKQKVGELKDDD...'

Esto no termina aqui, el elemento etiquetado como "sequence" tiene algunos atributos que quisiera recuperar. Como primera medida puedo verificar que efectivamente "sequence" tenga atributos

>>> a[1].getElementsByTagName("sequence")[0].hasAttributes()
True

La lista de atributos es provista por attributes.keys() y el contenido de los mismos se obtiene con attributes.get(ATTRIB).value:

>>> a[1].getElementsByTagName("sequence")[0].attributes.keys()
[u'checksum', u'length']
>>> a[1].getElementsByTagName("sequence")[0].attributes.get("checksum").value
u'E0C0CC2E1F189B8A'
>>> a[1].getElementsByTagName("sequence")[0].attributes.get("length").value
u'393'

FALTA: Programa resumiendo todo sacando info de un XML mas largo.

BIBLIOGAFIA