saxutils en Python: utilidades para XML

Publicado el: 23/08/2026
Tempo de leitura: 5 minutos
High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.

El módulo xml.sax.saxutils reúne funciones y clases auxiliares para aplicaciones XML basadas en SAX. Permite escapar texto, preparar valores de atributos, deshacer entidades conocidas, generar XML a partir de eventos, crear filtros entre el parser y la aplicación y normalizar fuentes de entrada.

Aunque su nombre está ligado a SAX, varias funciones son útiles fuera de un parser orientado a eventos. Aun así, deben aplicarse en el contexto correcto. Escapar XML no equivale a sanitizar HTML, parametrizar SQL, proteger comandos de shell ni validar rutas.

Herramientas principales

Las funciones más conocidas son escape(), unescape() y quoteattr(). Las clases principales son XMLGenerator y XMLFilterBase. El módulo también ofrece prepare_input_source() para normalizar entradas aceptadas por parsers.

Escapar texto con escape()

escape() sustituye &, < y > por sus entidades XML. Esto permite insertar texto dentro de un elemento sin alterar la estructura.

from xml.sax.saxutils import escape

texto = "5 < 10 & 12 > 8"
seguro = escape(texto)
print(seguro)
# 5 &lt; 10 &amp; 12 &gt; 8

Estos tres caracteres siempre se escapan. El mapping opcional entities añade sustituciones, pero no debería usarse como traductor genérico de strings.

resultado = escape(
    "línea 1\nlínea 2",
    {"\n": "
"},
)

Añade entidades personalizadas solo cuando el formato de destino lo requiera. Reemplazos arbitrarios pueden producir XML poco legible o incompatible.

El escaping depende del contexto

El texto de un elemento y el valor de un atributo son contextos diferentes. escape() no rodea el atributo con comillas ni decide cómo tratar comillas simples y dobles. Para atributos usa quoteattr().

No uses escape() como política para HTML completo enviado por usuarios. Protege una inserción en un nodo textual, pero no analiza tags, URLs, scripts, CSS ni atributos peligrosos. El artículo de html.entities en Python explica la diferencia entre entidades, decoding y sanitización.

Preparar atributos con quoteattr()

quoteattr() escapa los caracteres necesarios, elige un delimitador y devuelve el valor ya rodeado por comillas.

from xml.sax.saxutils import quoteattr

valor = 'Informe "final" & aprobado'
atributo = quoteattr(valor)
print(f"<archivo nombre={atributo}/>")

Si el valor contiene solo un tipo de comillas, la función intenta usar el otro como delimitador. Cuando contiene ambos, codifica las comillas necesarias.

Evita montar documentos XML extensos mediante concatenación. Para salida estructurada usa ElementTree, minidom o XMLGenerator. Consulta ElementTree en Python para una API de árboles.

Deshacer entidades con unescape()

unescape() convierte &amp;, &lt; y &gt; a caracteres literales.

from xml.sax.saxutils import unescape

texto = "Tom &amp; Ana &lt;3 XML"
print(unescape(texto))

Un mapping opcional puede definir entidades adicionales. Sin embargo, unescape() no es un parser XML completo. No valida estructura, namespaces, encodings, DTD ni documentos malformados.

Evita decodificar dos veces el mismo valor. Un doble decoding puede convertir texto que debía permanecer literal en markup activo. Define una única capa responsable de esa transformación.

Generar XML con XMLGenerator

XMLGenerator implementa la interfaz SAX ContentHandler y escribe eventos de vuelta como XML. Puede reproducir eventos parseados o generar un documento nuevo.

from io import StringIO
from xml.sax.saxutils import XMLGenerator
from xml.sax.xmlreader import AttributesImpl

salida = StringIO()
generador = XMLGenerator(
    salida,
    encoding="utf-8",
    short_empty_elements=True,
)

generador.startDocument()
generador.startElement("catalogo", AttributesImpl({"version": "1"}))
generador.startElement("producto", AttributesImpl({"id": "42"}))
generador.characters("Café & código")
generador.endElement("producto")
generador.endElement("catalogo")
generador.endDocument()

print(salida.getvalue())

El generador escapa texto y atributos según los eventos recibidos. Mantén la secuencia correctamente anidada: inicia el documento, abre elementos, escribe caracteres, cierra en orden inverso y finaliza.

Encoding de salida

El encoding configurado en XMLGenerator debe coincidir con la estrategia del stream. Con StringIO el resultado es texto Unicode. Para archivos o streams binarios, verifica cómo se producen los bytes y prueba caracteres no ASCII.

El artículo de codecs en Python explica encodings, BOM, decoding incremental y políticas de error.

Transformar eventos con XMLFilterBase

XMLFilterBase se coloca entre un XMLReader y el handler final. Por defecto transmite solicitudes y eventos sin cambios. Una subclase puede renombrar elementos, quitar atributos, normalizar texto o bloquear datos.

from xml.sax.saxutils import XMLFilterBase
from xml.sax.xmlreader import AttributesImpl

class QuitarSecreto(XMLFilterBase):
    def startElement(self, name, attrs):
        if "secreto" in attrs:
            nuevos = dict(attrs.items())
            nuevos.pop("secreto", None)
            attrs = AttributesImpl(nuevos)
        super().startElement(name, attrs)

Un filtro debe preservar una secuencia válida. Si elimina un inicio, también debe gestionar el cierre correspondiente. Prueba namespaces, texto fragmentado, comentarios, elementos vacíos e instrucciones de procesamiento.

El guía de xml.sax en Python explica handlers, namespaces, locators y callbacks.

Normalizar fuentes con prepare_input_source()

prepare_input_source() transforma un string, objeto similar a archivo o InputSource existente en una fuente lista para el parser. Una URL base puede ayudar a resolver identificadores relativos.

from xml.sax.saxutils import prepare_input_source

fuente = prepare_input_source("datos.xml")
print(fuente.getSystemId())

La resolución automática de rutas o URLs puede ser peligrosa cuando la fuente está controlada por usuarios. Restringe protocolos, hosts, directorios, redirects y tamaño. No conviertas un identificador arbitrario en una solicitud de red sin límites.

Seguridad para XML externo

El módulo no elimina vulnerabilidades del parser. Mantén desactivadas las entidades externas, limita bytes y profundidad y evita que documentos externos seleccionen archivos locales o recursos remotos. El artículo de pulldom en Python muestra límites prácticos para streams grandes.

Al generar XML, valida nombres de elementos y atributos. escape() protege contenido textual, pero no convierte un string arbitrario en un nombre XML válido. Prefiere nombres definidos por código confiable o por un esquema.

Evitar XML construido por concatenación

Un error común es unir strings:

# Evita esto en documentos reales
xml = "<usuario nombre=\"" + nombre + "\">" + texto + "</usuario>"

Incluso con escaping es fácil olvidar un contexto, producir una declaración de encoding incorrecta o desequilibrar tags. Para fragmentos mínimos, quoteattr() y escape() pueden bastar. Para documentos completos, usa una API estructural.

Pruebas recomendadas

Prueba ampersands, signos menor y mayor, ambos tipos de comillas, Unicode, valores vacíos, saltos de línea, texto ya escapado y caracteres prohibidos por XML. En generadores, prueba elementos vacíos, namespaces, atributos y streams de texto o bytes.

En filtros, parsea la salida generada durante los tests en lugar de comparar solo strings. Un documento visualmente razonable puede seguir siendo malformado.

Errores comunes

Los fallos más frecuentes son usar escape() para atributos, llamar unescape() dos veces, tratar escaping como sanitización, concatenar tags con datos externos, dejar el encoding implícito, habilitar entidades externas y modificar eventos sin preservar aperturas y cierres.

Conclusión

xml.sax.saxutils proporciona bloques prácticos para escapar texto XML, citar atributos, generar documentos por eventos, filtrar streams SAX y normalizar fuentes.

Aplica cada herramienta en su contexto, usa APIs estructurales para documentos completos y limita las entradas externas. Consulta la documentación oficial de saxutils y la especificación XML del W3C.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pulldom en Python: DOM parcial para XML

    Aprende pulldom en Python para procesar XML por eventos, expandir subárboles selectivos y reducir memoria con límites seguros.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    Close-up of a laptop screen with code and a coffee mug, perfect for tech abstract themes.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    xml.sax en Python: procesa XML por eventos

    Aprende xml.sax en Python para procesar XML por eventos con poca memoria, namespaces, handlers, límites y entidades seguras.

    Ler mais

    Tempo de leitura: 5 minutos
    22/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    minidom en Python: manipula XML con DOM

    Aprende xml.dom.minidom en Python para leer, navegar, crear y serializar XML con nodos DOM, namespaces, memoria y seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    22/08/2026
    Vibrant green snake coiled on a tree branch amidst lush jungle foliage.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ElementTree: lee y modifica XML en Python

    Aprende ElementTree en Python para leer, buscar, modificar y generar XML con namespaces, parsing incremental, límites y seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    22/08/2026
    A retro blue mailbox attached to a vibrant yellow wall, perfect for vintage themes.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    poplib en Python: lee correos con POP3

    Aprende poplib en Python para acceder a POP3 con TLS, listar y descargar mensajes, usar UIDL, aplicar límites y evitar

    Ler mais

    Tempo de leitura: 6 minutos
    22/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    imaplib en Python: lee correos con IMAP

    Aprende imaplib en Python para acceder a buzones IMAP con TLS, buscar por UID, leer sin marcar mensajes, usar flags

    Ler mais

    Tempo de leitura: 6 minutos
    22/08/2026