XML: Guía Técnica Completa
XML (Extensible Markup Language) es un lenguaje de marcado estándar del W3C derivado de SGML. Definido en XML 1.0 (Quinta Edición, 2008), es un formato de propósito general para representar datos jerárquicos estructurados en texto plano. XML sustenta innumerables sistemas empresariales: servicios web SOAP, gráficos SVG, libros EPUB, feeds RSS/Atom, documentos Microsoft Office (OOXML), archivos de recursos Android, descriptores de proyectos Maven y muchos otros formatos de configuración e intercambio de datos.
Estructura del Documento
Un documento XML consiste en un prólogo y un elemento documento (la raíz):
<?xml version="1.0" encoding="UTF-8"?> <!-- Declaración XML -->
<!DOCTYPE catalogo SYSTEM "catalogo.dtd"> <!-- DOCTYPE / referencia DTD opcional -->
<catalogo xmlns="http://ejemplo.com/catalogo"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<!-- Un elemento con atributos y elementos hijo -->
<libro id="1" idioma="es" en-stock="true">
<dc:title>Programación en Python</dc:title>
<autor>Lucía García</autor>
<precio moneda="EUR">34.99</precio>
<descripcion><![CDATA[Una guía completa — incluye caracteres < & >.]]></descripcion>
</libro>
</catalogo>
Bien Formado vs Válido
- Bien formado: XML sintácticamente correcto — cada elemento correctamente cerrado, atributos entre comillas dobles, caracteres reservados escapados, exactamente un elemento raíz.
- Válido: también se ajusta a un esquema (DTD, XSD, RelaxNG o Schematron).
Escape de Caracteres
Cinco entidades predefinidas deben usarse cuando estos caracteres aparecen en el contenido de elementos o valores de atributos:
| Carácter | Escape | Contexto |
|---|---|---|
< |
< |
Cualquier contenido |
> |
> |
Cualquier contenido |
& |
& |
Cualquier contenido |
" |
" |
Valores de atributo entre comillas dobles |
' |
' |
Valores de atributo entre comillas simples |
Las secciones CDATA (<![CDATA[...]]>) omiten el escape — el contenido se trata como datos de carácter brutos. Solo la cadena ]]> está prohibida dentro de una sección CDATA.
Espacios de Nombres
Los espacios de nombres XML (1999) permiten mezclar vocabularios de diferentes autoridades en el mismo documento. Un espacio de nombres se declara con xmlns:prefijo="URI":
<raiz xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:math="http://www.w3.org/1998/Math/MathML">
<xhtml:p>Texto con fórmula matemática</xhtml:p>
</raiz>
Expresiones XPath
XPath es el lenguaje de consulta para seleccionar nodos dentro de un documento XML:
/catalogo/libro — todos los hijos libro del elemento raíz catalogo
//libro — todos los elementos libro en cualquier parte
//libro[@idioma='es'] — libros con atributo idioma="es"
//libro[position()=1] — primer elemento libro
//precio[@moneda='EUR'] — elementos precio con moneda EUR
//libro/titulo/text() — contenido textual de todos los elementos titulo
count(//libro) — número total de elementos libro
Procesamiento XML con Python
ElementTree (Biblioteca Estándar)
import xml.etree.ElementTree as ET
xml_datos = '''<?xml version="1.0" encoding="UTF-8"?>
<catalogo>
<libro id="1" idioma="es">
<titulo>Programación en Python</titulo>
<autor>Lucía García</autor>
<precio moneda="EUR">34.99</precio>
</libro>
<libro id="2" idioma="en">
<titulo>Python Programming</titulo>
<autor>Guido van Rossum</autor>
<precio moneda="USD">39.99</precio>
</libro>
</catalogo>'''
raiz = ET.fromstring(xml_datos)
# Iterar elementos hijo
for libro in raiz.findall('libro'):
titulo = libro.findtext('titulo', default='(sin título)')
precio_el = libro.find('precio')
print(f"[{libro.get('id')}] {titulo} — {precio_el.text} {precio_el.get('moneda')}")
# Filtro por atributo estilo XPath
libros_es = raiz.findall("libro[@idioma='es']")
print(libros_es[0].findtext('titulo')) # Programación en Python
# Construir XML programáticamente
nueva_raiz = ET.Element('catalogo')
libro = ET.SubElement(nueva_raiz, 'libro', id='3', idioma='fr')
ET.SubElement(libro, 'titulo').text = 'Python en français'
precio = ET.SubElement(libro, 'precio', moneda='EUR')
precio.text = '29.99'
arbol = ET.ElementTree(nueva_raiz)
ET.indent(arbol, space=' ') # Python 3.9+
arbol.write('salida.xml', encoding='utf-8', xml_declaration=True)
lxml (XPath Completo, XSLT, Validación XSD)
from lxml import etree
arbol = etree.parse('catalogo.xml')
raiz = arbol.getroot()
# XPath 1.0 completo con soporte de espacios de nombres
NS = {'cat': 'http://ejemplo.com/catalogo'}
libros = raiz.xpath('//cat:libro[@idioma="es"]', namespaces=NS)
# Validación con esquema XSD
esquema_doc = etree.parse('catalogo.xsd')
esquema = etree.XMLSchema(esquema_doc)
if not esquema.validate(arbol):
for error in esquema.error_log:
print(f"Línea {error.line}: {error.message}")
# Transformación XSLT
xslt_doc = etree.parse('a_html.xsl')
transformar = etree.XSLT(xslt_doc)
resultado_html = transformar(arbol)
with open('salida.html', 'wb') as f:
f.write(bytes(resultado_html))
SAX para Archivos XML Grandes (Streaming)
import xml.sax
import xml.sax.handler
class ManejadorLibros(xml.sax.handler.ContentHandler):
def __init__(self):
self.libros = []
self.actual = {}
self.capturar = None
def startElement(self, nombre, attrs):
if nombre == 'libro':
self.actual = {'id': attrs.get('id', ''), 'idioma': attrs.get('idioma', '')}
elif nombre in ('titulo', 'autor'):
self.capturar = nombre
self.actual[nombre] = ''
def endElement(self, nombre):
if nombre == 'libro':
self.libros.append(dict(self.actual))
elif nombre in ('titulo', 'autor'):
self.capturar = None
def characters(self, contenido):
if self.capturar:
self.actual[self.capturar] += contenido
manejador = ManejadorLibros()
xml.sax.parse('catalogo_grande.xml', manejador)
for libro in manejador.libros[:5]:
print(libro)
XML vs Otros Formatos de Datos
| Formato | Legible | Tipado | Comentarios | Esquema | Streaming | Espacios de nombres |
|---|---|---|---|---|---|---|
| XML | Verboso | No | Sí | XSD/DTD/RelaxNG | SAX | Sí |
| JSON | Bueno | Parcial | No | JSON Schema | NDJSON | No |
| YAML | Excelente | Sí | Sí | No | No | No |
| Protobuf | No (binario) | Sí | En .proto | .proto | Sí | No |
XML sigue siendo esencial para: servicios web SOAP (integración empresarial), gráficos SVG, EPUB, Office Open XML (DOCX/XLSX/PPTX), archivos de recursos Android, dependencias Maven/Gradle, feeds RSS/Atom y sitemap.xml.
Errores Comunes
- Olvidar escapar
&.AT&Ten contenido XML debe escribirseAT&T. Un&sin escapar hace que el documento no sea bien formado. - La declaración de codificación debe coincidir con la codificación real. Si la declaración XML dice
encoding="UTF-8"pero el archivo está guardado como Latin-1, los parsers lo rechazarán o leerán mal. - Parsing aware de espacios de nombres. ElementTree no maneja los prefijos de espacio de nombres en
find()igual que lxml. Usa la sintaxis{uri_espacio_nombres}nombre_local. - CDATA no puede contener
]]>. Divídelo como]]>+<![CDATA[>si es necesario. - Ataques de expansión de entidades DTD (Billion Laughs). Nunca parsees XML no fiable con expansión de entidades habilitada. Usa la biblioteca
defusedxmlde Python, que desactiva la expansión de entidades y otros vectores XXE.
Conversiones relacionadas
Conversiones de documento que siguen este tema: