Dungeon of Bits
Aprendiendo informática.
Dungeon of Bits

Manipular ficheros XML con Python

En este tutorial vamos a utilizar Python para leer, escribir y manipular datos de ficheros XML.

Python

Requisitos:

Para realizar esta práctica tan solo necesitamos un sistema con Python instalado. En nuestro caso vamos a utilizar un equipo con Ubuntu 22.04 LTS y Pycharm como framework de trabajo.

Para instalar Pycharm desde Ubuntu tan solo necesitamos teclear la instrucción:

sudo snap install pycharm-community --classic

Manipular XML con Python:

En este tutorial usaremos el módulo ElementTree de Python, del cual podemos consultar la documentación aquí.

Cargar los datos XML:

Con ElementTree podemos cargar los datos XML tanto desde fichero como desde una cadena de texto, imaginemos que tenemos el siguiente fichero XML llamada "clientes.xml":

<data>
    <cliente id="1">
        <nombre>Juan Ortega</nombre>
        <direccion>
            <calle>Carrer Nou</calle>
            <numero>95</numero>
            <piso>4-1</piso>
            <cp>46003</cp>
            <poblacion>València</poblacion>
        </direccion>
    </cliente>
    <cliente id="2">
        <nombre>Eva Rada</nombre>
        <direccion>
            <calle>Avda. Libertad</calle>
            <numero>254</numero>
            <piso>ENTLO-2</piso>
            <cp>27071</cp>
            <poblacion>Lugo</poblacion>
        </direccion>
    </cliente>
</data>

Como podemos ver, existen dos clientes en el fichero, aunque el elemento raíz o root es el tag data.

Cada cliente tiene un atributo que es su id de cliente.

Además cada cliente cuenta con un nombre y una dirección que son elementos hijos de cliente.

Por último cada dirección cuenta con 5 elementos hijos: calle, numero, piso, cp y poblacion.

Cargar datos XML desde fichero:

ElementTree nos permite cargar los datos desde fichero con la función parse(), en el siguiente ejemplo simplemente cargamos los datos desde el fichero y mostraremos el número de elementos que tiene:

import xml.etree.ElementTree as ET

#cargamos los datos desde fichero
tree = ET.parse("clientes.xml")
#cargamos el elemento raiz
root = tree.getroot()

#imprimimos por pantalla la cantidad de elementos del documento
print(len(root))

RESULTADO:

2

Cargar datos XML desde cadena de texto:

ElementTree nos permite también cargar los datos desde una cadena de texto, vamos a ver un ejemplo donde cargará el contenido XML directamente así:

import xml.etree.ElementTree as ET

root2 = ET.fromstring("""<data>
    <cliente id="1">
        <nombre>Juan Ortega</nombre>
        <direccion>
            <calle>Carrer Nou</calle>
            <numero>95</numero>
            <piso>4-1</piso>
            <cp>46003</cp>
            <poblacion>València</poblacion>
        </direccion>
    </cliente>
    <cliente id="2">
        <nombre>Eva Rada</nombre>
        <direccion>
            <calle>Avda. Libertad</calle>
            <numero>254</numero>
            <piso>ENTLO-2</piso>
            <cp>27071</cp>
            <poblacion>Lugo</poblacion>
        </direccion>
    </cliente>
</data>""")

print(len(root2))

RESULTADO:

2

Mostrar datos cargados:

Cada elemento del árbol XML que hemos cargado anteriormente tiene un nombre: tag y una serie de atributos.

Por ejemplo para mostrar el nombre y los atributos del elemento raíz escribiríamos:

import xml.etree.ElementTree as ET

tree = ET.parse("clientes.xml")
root = tree.getroot()

print(root.tag)
print(root.attrib)

RESULTADO:

data
{}

Como vemos el listado de atributos está vacío porque en el XML el tag data no tiene ninguno, pero cliente sí que los tiene, vamos a hacer un programa que imprima por pantalla los nombres y los atributos de los elementos hijos de raíz:

import xml.etree.ElementTree as ET

tree = ET.parse("clientes.xml")
root = tree.getroot()

for child in root:
    print(child.tag, child.attrib)

RESULTADO:

cliente {'id': '1'}
cliente {'id': '2'}

Los datos están guardados en forma de árbol y podemos acceder a ellos como si se tratase de una matriz multidimensional, por ejemplo, para imprimir el nombre y la calle de un cliente podemos escribir:

import xml.etree.ElementTree as ET

tree = ET.parse("clientes.xml")
root = tree.getroot()

print(root[0][0].text)
print(root[0][1][0].text)

RESULTADO:

Juan Ortega
Carrer Nou

Como se puede ver se puede acceder directamente a los elementos y text muestra el texto entre los dos tags.

También podemos iterar entre los elementos hijos y mostrarlos por pantalla, por ejemplo para mostrar el texto del tag nombre de cada cliente podemos usar este programa:

import xml.etree.ElementTree as ET

tree = ET.parse("clientes.xml")
root = tree.getroot()

for child in root:
    for subChild in child:
        print(subChild.text)

RESULTADO:

Juan Ortega


Eva Rada

Buscar datos en XML:

Iteradores:

Como los ficheros XML pueden tener muchas anidaciones es interesante poder buscar directamente los elementos que necesitemos, con independencia de dónde se encuentren, para ello usamos un iterador.

En el ejemplo siguiente usaremos un iterador para imprimir los atributos id de los elementos cliente, los textos de los elementos nombre y los textos de los elementos poblacion:

import xml.etree.ElementTree as ET

tree = ET.parse("clientes.xml")
root = tree.getroot()

#Mostrar los atributos de los nodos cliente
for child in root.iter("cliente"):
    print(child.attrib)

#Mostrar los valores de los nodos nombre
for child in root.iter("nombre"):
    print(child.text)

# Mostrar los valores de los nodos poblacion
for child in root.iter("poblacion"):
    print(child.text)

RESULTADO:

{'id': '1'}
{'id': '2'}
Juan Ortega
Eva Rada
València
Lugo

Funciones de búsqueda:

Además de los iteradores tenemos un par de funciones de búsqueda como son findall() y find().

findall() busca entre los elementos que son directamente hijos del elemento actual que tienen el tag que se le pasa por parámetro.

find() encuentra el primer hijo del elemento que se le pasa como argumento.

Element.text muestra el texto de un elemento.

la función get() accede a los atributos del elemento.

En el ejemplo siguiente usamos estas funciones para buscar los elementos cliente y de ellos mostrar su atributo id y el texto del elemento nombre:

import xml.etree.ElementTree as ET

tree = ET.parse("clientes.xml")
root = tree.getroot()

for cliente in root.findall('cliente'):
    nombre = cliente.find('nombre').text
    id = cliente.get('id')
    print(id, nombre)

RESULTADO:

1 Juan Ortega
2 Eva Rada

Modificar XML con Python:

Añadir o eliminar atributos de un elemento:

Para añadir atributos a un elemento disponemos de la función set() y para eliminarlos podemos usar la función pop(), hemos de conocer el nombre del atributo antes.

En el siguiente ejemplo vamos a:

  • Añadir un atributo en el elemento direccion, el atributo se llama "actualizada" y tendrá por valor no.
  • Guardar el árbol XML modificado en el fichero "modificado.xml".
  • Volver a quitar el atributo del elemento direccion.
  • Guardar el árbol XML modificado en el fichero "clientes.xml".
import xml.etree.ElementTree as ET

tree = ET.parse("clientes.xml")
root = tree.getroot()

#Añadir un nuevo atributo en el elemento direccion
for dir in root.iter('direccion'):
    dir.set("actualizada","no")

#Escribir el XML modificado en un fichero
tree.write("moficado.xml")

#Eliminar el atributo que hemos creado antes
for dir in root.iter('direccion'):
    dir.attrib.pop("actualizada", None)

#Escribir el XML modificado en un fichero
tree.write("output2.xml")

#Escribir el XML modificado en un fichero
tree.write("clientes.xml")

Fichero "modificado.xml":

<data>
    <cliente id="1">
        <nombre>Juan Ortega</nombre>
        <direccion actualizada="no">
            <calle>Carrer Nou</calle>
            <numero>95</numero>
            <piso>4-1</piso>
            <cp>46003</cp>
            <poblacion>Val&#232;ncia</poblacion>
        </direccion>
    </cliente>
    <cliente id="2">
        <nombre>Eva Rada</nombre>
        <direccion actualizada="no">
            <calle>Avda. Libertad</calle>
            <numero>254</numero>
            <piso>ENTLO-2</piso>
            <cp>27071</cp>
            <poblacion>Lugo</poblacion>
        </direccion>
    </cliente>
</data>

Añadir elementos:

Para añadir elementos nuevos en el árbol XML usaremos las funciones append() o insert().

  • append() añade el elemento al final.
  • insert() añade el elemento en la posición que se pase como primer argumento.

En el siguiente ejemplo vemos un programa que añade el elemento saldo con valor 0 dentro del elemento cliente de nuestro árbol XML:

import xml.etree.ElementTree as ET

tree = ET.parse("clientes.xml")
root = tree.getroot()

#Creamos el elemento saldo con valor 0
child = ET.Element("saldo")
child.text = "0"

#Añadimos el elemento dentro de cada elemento cliente
for cli in root.iter('cliente'):    
    cli.insert(1,child)
    #equivalente:
    # cli.append(child)

#Escribir el XML modificado en un fichero
tree.write("modificado.xml")

Fichero "modificado.xml":

<data>
    <cliente id="1">
        <nombre>Juan Ortega</nombre>
        <saldo>0</saldo><direccion>
            <calle>Carrer Nou</calle>
            <numero>95</numero>
            <piso>4-1</piso>
            <cp>46003</cp>
            <poblacion>Val&#232;ncia</poblacion>
        </direccion>
    </cliente>
    <cliente id="2">
        <nombre>Eva Rada</nombre>
        <saldo>0</saldo><direccion>
            <calle>Avda. Libertad</calle>
            <numero>254</numero>
            <piso>ENTLO-2</piso>
            <cp>27071</cp>
            <poblacion>Lugo</poblacion>
        </direccion>
    </cliente>
</data>

En el siguiente ejemplo vamos a añadir un nodo cliente entero y guardaremos el resultado en el fichero "modificado.xml":

import xml.etree.ElementTree as ET

tree = ET.parse("clientes.xml")
root = tree.getroot()

cliente = ET.Element("cliente")
nombre = ET.Element("nombre")
nombre.text = "Isaac Tesla"
direccion = ET.Element("direccion")
calle = ET.Element("calle")
calle.text = "calle Luna"
numero = ET.Element("numero")
numero.text = "12"
piso = ET.Element("piso")
piso.text = "5"
cp = ET.Element("cp")
cp.text = "08029"
poblacion = ET.Element("poblacion")
poblacion.text = "Barcelona"
direccion.append(calle)
direccion.append(numero)
direccion.append(piso)
direccion.append(cp)
direccion.append(poblacion)
cliente.set("id","3")
cliente.append(nombre)
cliente.append(direccion)
root.append(cliente)

#Escribir el XML modificado en un fichero
tree.write("modificado.xml")

Fichero "modificado.xml":

<data>
    <cliente id="1">
        <nombre>Juan Ortega</nombre>
        <direccion>
            <calle>Carrer Nou</calle>
            <numero>95</numero>
            <piso>4-1</piso>
            <cp>46003</cp>
            <poblacion>Val&#232;ncia</poblacion>
        </direccion>
    </cliente>
    <cliente id="2">
        <nombre>Eva Rada</nombre>
        <direccion>
            <calle>Avda. Libertad</calle>
            <numero>254</numero>
            <piso>ENTLO-2</piso>
            <cp>27071</cp>
            <poblacion>Lugo</poblacion>
        </direccion>
    </cliente>
<cliente id="3"><nombre>Isaac Tesla</nombre><direccion><calle>calle Luna</calle><numero>12</numero><piso>5</piso><cp>08029</cp><poblacion>Barcelona</poblacion></direccion></cliente></data>

También podemos añadir elementos XML con la función SubElement() que crea nodos hijo del nodo que se le paso como argumento, por ejemplo **SubElement(padre, "hijo"), crea un nodo hijo llamado hijo dentro del nodo padre.

Veamos un ejemplo que crea un cliente nuevo:

import xml.etree.ElementTree as ET

tree = ET.parse("clientes.xml")
root = tree.getroot()

cliente = ET.Element("cliente")
nombre = ET.SubElement(cliente, "nombre")
nombre.text = "Isaac Tesla"
direccion = ET.SubElement(cliente, "direccion")
calle = ET.SubElement(direccion, "calle")
calle.text = "calle Luna"
numero = ET.SubElement(direccion, "numero")
numero.text = "12"
piso = ET.SubElement(direccion, "piso")
piso.text = "5"
cp = ET.SubElement(direccion, "cp")
cp.text = "08029"
poblacion = ET.SubElement(direccion, "poblacion")
poblacion.text = "Barcelona"
cliente.set("id","3")
root.append(cliente)

#Escribir el XML modificado en un fichero
tree.write("modificado.xml")

Fichero "modificado.xml":

<data>
    <cliente id="1">
        <nombre>Juan Ortega</nombre>
        <direccion>
            <calle>Carrer Nou</calle>
            <numero>95</numero>
            <piso>4-1</piso>
            <cp>46003</cp>
            <poblacion>Val&#232;ncia</poblacion>
        </direccion>
    </cliente>
    <cliente id="2">
        <nombre>Eva Rada</nombre>
        <direccion>
            <calle>Avda. Libertad</calle>
            <numero>254</numero>
            <piso>ENTLO-2</piso>
            <cp>27071</cp>
            <poblacion>Lugo</poblacion>
        </direccion>
    </cliente>
<cliente id="3"><nombre>Isaac Tesla</nombre><direccion><calle>calle Luna</calle><numero>12</numero><piso>5</piso><cp>08029</cp><poblacion>Barcelona</poblacion></direccion></cliente></data>

Eliminar elementos XML con Python:

Para eliminar elementos usaremos la función remove() a la cual le pasaremos el elemento a eliminar como parámetro.

En el siguiente ejemplo eliminaremos del árbol XML todos los elementos con atributo id de cliente superior a 1:

import xml.etree.ElementTree as ET

tree = ET.parse("clientes.xml")
root = tree.getroot()

#Buscamos todos los nodos cliente
for cliente in root.findall('cliente'):
    #leemos el atributo id de cliente
    id = cliente.get('id')
    #Si el atributo es mayor que 1 eliminamos el elemento y todos sus nodos hijos
    if int(id) > 1:
        root.remove(cliente)

#Escribir el XML modificado en un fichero
tree.write("modificado.xml")

Fichero "modificado.xml":

<data>
    <cliente id="1">
        <nombre>Juan Ortega</nombre>
        <direccion>
            <calle>Carrer Nou</calle>
            <numero>95</numero>
            <piso>4-1</piso>
            <cp>46003</cp>
            <poblacion>Val&#232;ncia</poblacion>
        </direccion>
    </cliente>
    </data>

A veces querremos eliminar nodos hijos, por ejemplo si quisiéramos eliminar el elemento cp de cada cliente usaríamos el siguiente programa:

import xml.etree.ElementTree as ET

tree = ET.parse("clientes.xml")
root = tree.getroot()


for cliente in root.findall('cliente'):
    direccion = cliente.find('direccion')
    cp = direccion.find('cp')
    direccion.remove(cp)

#Escribir el XML modificado en un fichero
tree.write("modificado.xml")

Fichero "modificado.xml":

<data>
    <cliente id="1">
        <nombre>Juan Ortega</nombre>
        <direccion>
            <calle>Carrer Nou</calle>
            <numero>95</numero>
            <piso>4-1</piso>
            <poblacion>Val&#232;ncia</poblacion>
        </direccion>
    </cliente>
    <cliente id="2">
        <nombre>Eva Rada</nombre>
        <direccion>
            <calle>Avda. Libertad</calle>
            <numero>254</numero>
            <piso>ENTLO-2</piso>
            <poblacion>Lugo</poblacion>
        </direccion>
    </cliente>
</data>