Procesamiento fácil de XML con Python y Amara

A pesar de que la libreria estándar Python cuenta con herramientas y modulos para el procesamiento de XML con SAX y DOM, muchos programadores han pensado que podrían existir formas más simples de trabajar con XML. Amara es un conjunto de herramientas que sirven para facilitar el procesamiento de XML usando Python. En este manual se da una breve introducción al uso de Amara para dichas tareas.

Instalación de Amara

Podemos instalar Amara de varias formas.

Instalación clásica (sin setuptools)

  1. Descarga los archivos correspondientes a tu plataforma y versión de Python de ftp://ftp.4suite.org/pub/Amara/
  2. Ejecutar el instalador. Si se trata de la distribución de código fuente, es necesario ejecutar lo siguiente:
    python setup.py install
    

También hay ejecutables de Windows.

Nota: Amara depende de 4Suite, por lo tanto, si no está instalado el paquete 4Suite hay que seleccionar una distribución allinone.

Instalación con setuptools

  1. Si no tienes instalado setuptools puedes instalarlo descargando el siguiente script ez_setup.py y ejecutando de la línea de comandos:
    python ez_setup.py -U setuptools
    
  2. Ejecutar
    easy_install Amara
    
    De forma automática buscará el módulo más reciente y descargará los módulos necesarios.

Ejemplo 1. Uso de expresiones XPath

Vamos a partir de un archivo input.xml que contiene los datos de prueba que usaremos en los ejemplos. Se trata de un documento XML que contiene datos de clientes.

<?xml version="1.0" encoding="iso-8859-1"?>
<clientes>
  <cliente id="ibm" agregado="2003-06-20">
    <nombre>International Bussines Machines</nombre>
    <direccion>
      <calle>8 Siempreviva</calle>
      <ciudad>Silicon Valley</ciudad>
      <provincia>California</provincia>
    </direccion>
    <lema>
      <emph>Vende todo</emph> lo que puedas
    </lema>
  </cliente>
  <cliente id="bk" agregado="2003-06-10">
    <nombre>Burguer King</nombre>
    <direccion>
      <calle>45 Santa Ursula</calle>
      <ciudad>Chicago</ciudad>
      <provincia>Illinois</provincia>
    </direccion>
    <lema>
      A la parrilla sabe mejor
    </lema>
  </cliente>
  <!-- Agregar 10,000 registros como este -->
  <cliente id="so" agregado="2004-11-01">
    <nombre>Sony</nombre>
    <direccion>
      <calle>10 Takataka
      </calle>
      <ciudad>Tokyo</ciudad>
      <provincia>Tokyo</provincia>
    </direccion>
  </cliente>
</clientes>

El siguiente programa utiliza la expresión XPath /clientes/cliente para obtener los datos de todos los clientes que aparecen en el documento.

Posteriormente se obtiene el nombre y ciudad de cada uno, y se imprimen a la consola. Los datos de entrada son le dos del archivo indicado por el parámetro source:

import amara
for fragDom in amara.pushdom(source='input.xml', xpatterns=u"/clientes/cliente"):
    label = fragDom.firstChild #Obtener el primer hijo
    nombre = label.xpath('string(nombre)') #obtener el elemento nombre
    ciudad = label.xpath('string(direccion/ciudad)') #obtener el elemento ciudad que forma parte de la direccion
    print nombre, 'de', ciudad

Nota: XPath es un lenguaje que permite seleccionar subconjuntos de un documento XML.

La salida del siguiente programa es:

International Bussines Machines de Silicon Valley
Burguer King de Chicago
Sony de Tokyo

Si se teme que el consumo de memoria sea muy elevado para documentos muy grandes, como ocurre con DOM, no hay nada de que preocuparse, pues domtools.pushdom es un generador que proporciona un fragmento del documento DOM a cada pasada del ciclo, de manera que el documento original no es procesado entero, sino en una serie de sub árboles de acuerdo al patrón /clientes/cliente proporcionado.

Ejemplo 2: Uso de bindings

El siguiente ejemplo simplifica mucho las cosas. Después de leer y analizar el archivo de entrada (input.xml), Amara crea un conjunto de objetos Python que reflejan la estructura del documento XML (a este proceso se le llama binding). Con ello el documento puede ser procesado usando instrucciones Python. El objeto contenedor representa el nodo raíz del documento en cuestión.{{{ #!python import amara contenido = amara.parse('input.xml') #se hace binding del archivo for cli in contenido.clientes.cliente: #para cada cliente

print cli.nombre, 'de', cli.direccion.ciudad}}} La salida es la misma que la del primer ejemplo:

International Bussines Machines de Silicon Valley
Burguer King de Chicago
Sony de Tokyo

Ejemplo 3: Binding eficiente

A pesar de su sencillez, el ejemplo anterior podría presentar problemas de eficiencia en el uso de memoria al tratar con documentos XML grandes, ya que se crear a una gran cantidad de objetos Python para poder representar el documento. Es por ello que Amara ofrece una forma mas adecuada de realizar algo similar, tomando el enfoque usado por el primer ejemplo. El siguiente código hace uso de la función pushbind, que es un generador de sub árboles que evita tener que crear de una sola vez los objetos:

import amara
for subArbol in amara.pushbind(source='input.xml', xpatterns=u'/clientes/cliente'):
    print subArbol.nombre, 'de', subArbol.direccion.ciudad

La salida de la ejecución del programa anterior seria:

International Bussines Machines de Silicon Valley
Burguer King de Chicago
Sony de Tokyo

Ejemplo 4: Modificación de Documentos XML

El siguiente ejemplo agrega un nuevo elemento <lema> al cliente con el identificador id="so", y cambia su fecha de última modificación.

import amara
import datetime

contenedor = amara.parse('input.xml')
#Se va a agregar este lema a Sony
texto_nuevo_lema = u'Life is good.'
#id de Sony
id = 'so'

#se obtiene una lista de clientes que tengan el id deseado
cliente_sony = [ label for label in contenedor.clientes.cliente
                   if label.id == 'so' ]

#sabemos que solo hay uno
cliente_sony = cliente_sony[0]
#Se agrega el nuevo elemento (lema) al final
cliente_sony.xml_append(contenedor.xml_create_element(u'lema', None))

#Se agrega de manera sencilla el texto del lema
cliente_sony.lema.xml_children.append(texto_nuevo_lema)

#Se cambia la fecha de modificacion de ese cliente
cliente_sony.agregado = unicode(datetime.date.today())

#se imprime el documento XML ya modificado
print contenedor.xml()

La salida obtenida al ejecutarlo es la siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<clientes>
  <cliente agregado="2003-06-20" id="ibm">
    <nombre>International Bussines Machines</nombre>
    <direccion>
      <calle>8 Siempreviva</calle>
      <ciudad>Silicon Valley</ciudad>
      <provincia>California</provincia>
    </direccion>
    <lema>
      <emph>Vende todo</emph> lo que puedas
    </lema>
  </cliente>
  <cliente agregado="2003-06-10" id="bk">
    <nombre>Burguer King</nombre>
    <direccion>
      <calle>45 Santa Ursula</calle>
      <ciudad>Chicago</ciudad>
      <provincia>Illinois</provincia>
    </direccion>
    <lema>
      A la parrilla sabe mejor
    </lema>
  </cliente>
  <!-- Agregar 10,000 registros como este -->
  <cliente agregado="2006-08-09" id="so">
    <nombre>Sony</nombre>
    <direccion>
      <calle>10 Takataka
      </calle>
      <ciudad>Tokyo</ciudad>
      <provincia>Tokyo</provincia>
    </direccion>
  <lema>
    Life is good.
  </lema></cliente>
</clientes>

Autor: César Cárdenas Desales

Corregido y actualizado: Luis Miguel Morillas

Basado en el manual de Uche Ogbuji "Introducing the Amara XML Toolkit"

Fecha de Última Actualización: 9 ago 2006