XML (eXtensible Markup Language) es un formato de texto para almacenar y transmitir datos estructurados en forma de árbol. Sobre él se construyen los feeds de productos de marketplaces y agregadores, los formatos bancarios y de intercambio, los archivos de configuración y también sus formatos derivados: los feeds RSS y los mapas del sitio. Este artículo forma parte de nuestro repaso general del parseo de documentos, centrado específicamente en XML.
Cómo está construido un XML
Un documento XML es un árbol de elementos. Cada elemento tiene un nombre (la etiqueta) y puede tener un conjunto de atributos, contenido de texto y elementos anidados.
<catalog>
<product id="101" available="true">
<name>Molinillo de café</name>
<price currency="EUR">34.90</price>
</product>
<product id="102" available="false">
<name>Hervidor de agua</name>
<price currency="EUR">21.90</price>
</product>
</catalog>La diferencia clave frente a JSON es que en XML los datos viven en dos sitios a la vez: en el texto de los elementos (<name>Molinillo de café</name>) y en los atributos (id="101"). Hay que tenerlo presente: al extraer, unas veces se consulta el contenido de la etiqueta y otras, sus atributos.
Un tema aparte son los espacios de nombres (namespaces). En los formatos complejos las etiquetas aparecen como <g:price> o <atom:link>, donde el prefijo está ligado a un URI. Los parsers exigen tener en cuenta el namespace al buscar nodos; de lo contrario, la consulta devuelve un resultado vacío — es la causa más frecuente del «el parser no encuentra nada, aunque los datos están ahí».
Dos enfoques: árbol en memoria y lectura en flujo
Existen dos maneras radicalmente distintas de hacer el parseo. El enfoque DOM construye todo el árbol en memoria y ofrece una navegación cómoda, incluida XPath: es sencillo y sirve para archivos de hasta decenas de megabytes. El enfoque en flujo (SAX / iterparse) lee el archivo por eventos, sin cargarlo entero, y resulta insustituible con volcados de cientos de megabytes o de gigabytes. Para las tareas típicas casi siempre basta DOM; al modo en flujo se pasa cuando el archivo deja de caber en memoria.
Python
La biblioteca estándar incluye el módulo xml.etree.ElementTree: es suficiente para los casos simples y no requiere instalación.
import xml.etree.ElementTree as ET
tree = ET.parse("catalog.xml")
root = tree.getroot()
for product in root.findall("product"):
name = product.find("name").text
price = product.find("price").text
available = product.get("available") # lectura del atributo
print(name, price, available)Para el trabajo serio se usa lxml: es más rápido, digiere mejor el XML «sucio» y soporta XPath por completo, namespaces incluidos.
from lxml import etree
tree = etree.parse("catalog.xml")
# XPath: todos los productos disponibles
for product in tree.xpath("//product[@available='true']"):
name = product.xpath("string(name)")
price = product.xpath("string(price)")
print(name, price)Si los datos llegaron de un API en XML pero resulta más cómodo trabajar con ellos como con un diccionario, ayuda la biblioteca xmltodict, que convierte el XML en estructuras anidadas normales de Python — muy parecido a cómo procesaría un JSON.
JavaScript / Node.js
En Node son populares dos soluciones. fast-xml-parser parsea el XML a un objeto sin dependencias externas y funciona muy rápido.
const { XMLParser } = require("fast-xml-parser");
const fs = require("fs");
const xml = fs.readFileSync("catalog.xml", "utf-8");
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@_" });
const data = parser.parse(xml);
for (const product of data.catalog.product) {
console.log(product.name, product.price["#text"], product["@_available"]);
}La alternativa es xml2js, veterana en el ecosistema y cómoda para el procesamiento asíncrono. En el navegador está disponible el DOMParser integrado, que permite recorrer el XML con los mismos métodos que un documento HTML.
PHP
Para los documentos simples encaja a la perfección el SimpleXML integrado: da acceso a los elementos como si fueran propiedades de un objeto.
<?php
$xml = simplexml_load_file("catalog.xml");
foreach ($xml->product as $product) {
$name = (string) $product->name;
$price = (string) $product->price;
$available = (string) $product["available"]; // atributo
echo "$name — $price — $available\n";
}Cuando hacen falta namespaces, XPath complejo o el procesamiento en flujo de archivos grandes, se pasa a DOMDocument y XMLReader, incluidos en la misma distribución estándar de PHP.
Go
La biblioteca estándar de Go incluye el paquete encoding/xml, que parsea el XML directamente a estructuras mediante las etiquetas de los campos.
package main
import (
"encoding/xml"
"fmt"
"os"
)
type Catalog struct {
Products []struct {
ID string `xml:"id,attr"`
Available string `xml:"available,attr"`
Name string `xml:"name"`
Price string `xml:"price"`
} `xml:"product"`
}
func main() {
data, _ := os.ReadFile("catalog.xml")
var c Catalog
xml.Unmarshal(data, &c)
for _, p := range c.Products {
fmt.Println(p.Name, p.Price, p.Available)
}
}Errores típicos
Lo más habitual es tropezar con la codificación: si la declaración anuncia ISO-8859-1 (o windows-1252) y el archivo se lee como UTF-8, los caracteres se convierten en basura — hay que dejar la codificación en manos del parser o recodificar de forma explícita. La segunda causa en frecuencia son los namespaces ignorados, por los que las consultas XPath devuelven en silencio un resultado vacío. La tercera, intentar cargar entero en el DOM un archivo demasiado grande: un volcado de gigabytes debe leerse en flujo. Y, por último, no parsee XML con expresiones regulares: el formato es anidado y recursivo, y las regex se rompen al primer caso fuera de lo común — para texto sin estructura sí se justifican (vea el parseo de TXT), pero no para XML.
Si en su caso hay un esquema de exportación no estándar, namespaces rotos o archivos de gigabytes, configuramos el parseo de XML a la medida de su formato. Y si los datos de origen no llegan en XML puro, sino en sus derivados, consulte los materiales vecinos: RSS y sitemap.