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 < 10 & 12 > 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 &, < y > a caracteres literales.
from xml.sax.saxutils import unescape
texto = "Tom & Ana <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.







