xmlreader en Python: controla parsers SAX

Publicado el: 23/08/2026
Tempo de leitura: 5 minutos
A person reads 'Python for Unix and Linux System Administration' indoors.

El módulo xml.sax.xmlreader define las interfaces utilizadas por los parsers SAX en Python. Describe cómo un parser recibe fuentes, envía eventos a handlers, configura features y properties, informa línea y columna y admite parsing incremental.

La mayoría de las aplicaciones crea un parser con xml.sax.make_parser(), pero comprender XMLReader, InputSource, Locator y los objetos de atributos permite controlar mejor seguridad, encoding, streams y extensiones.

El papel de XMLReader

XMLReader es la interfaz base de los drivers SAX. Un driver expone una función create_parser(), y make_parser() la usa para crear el lector. El lector procesa XML e invoca los handlers registrados.

from xml.sax import make_parser
from xml.sax.handler import ContentHandler

class MiHandler(ContentHandler):
    def startElement(self, name, attrs):
        print("inicio:", name)

parser = make_parser()
parser.setContentHandler(MiHandler())
parser.parse("datos.xml")

Cuando parse() retorna, el documento fue procesado completamente. Usa la interfaz incremental cuando los bloques llegan poco a poco o no quieres un método bloqueante.

Handlers configurables

El lector puede recibir ContentHandler, DTDHandler, EntityResolver y ErrorHandler. Sin content handler, los eventos de contenido se descartan. Sin error handler, los errores suelen convertirse en excepciones y los warnings pueden imprimirse.

El artículo de xml.sax en Python explica los callbacks. saxutils en Python proporciona generadores y filtros.

Controlar el origen con InputSource

InputSource guarda identificador público, identificador de sistema, encoding, byte stream y character stream. Permite entregar al parser una fuente ya abierta en vez de permitir que abra una ruta o URL automáticamente.

from xml.sax import make_parser
from xml.sax.xmlreader import InputSource

fuente = InputSource()
fuente.setSystemId("importacion-local")
fuente.setEncoding("utf-8")
fuente.setByteStream(open("datos.xml", "rb"))

parser = make_parser()
parser.setContentHandler(MiHandler())
parser.parse(fuente)

Cuando existe un character stream, el parser ignora el byte stream y el encoding del InputSource. Un byte stream tiene prioridad sobre la apertura del system ID.

Byte stream y character stream

Un byte stream permite que el parser inspeccione la declaración de encoding XML, salvo que la aplicación lo indique. Un character stream ya está decodificado, por lo que la aplicación asume la responsabilidad.

No decodifiques bytes XML con errors="ignore". Eliminar caracteres puede cambiar el significado. El guía de codecs en Python explica encodings y políticas de error.

Evitar apertura automática de URLs

Un string pasado a parse() puede representar un archivo, objeto path-like o identificador de sistema. Con datos externos, abre la fuente, aplica límites de tamaño y protocolo y entrega el stream. Así una referencia controlada por usuarios no se convierte en acceso libre a red o archivos.

Combina esta práctica con un EntityResolver que rechace entidades externas. El XML no debería elegir archivos locales, hosts o protocolos.

Features del parser

getFeature() y setFeature() consultan o cambian opciones booleanas SAX. Entre las más conocidas están namespaces, prefijos y entidades externas. Un parser puede no reconocer o no soportar una feature.

from xml.sax import make_parser
from xml.sax.handler import feature_namespaces, feature_external_ges

parser = make_parser()
parser.setFeature(feature_namespaces, True)

try:
    parser.setFeature(feature_external_ges, False)
except Exception as exc:
    raise RuntimeError("No se puede garantizar la protección XML") from exc

No ignores silenciosamente una configuración de seguridad fallida. Si una protección obligatoria no está disponible, rechaza la operación.

Properties

Las properties transportan objetos más complejos, como lexical handlers. getProperty() y setProperty() pueden lanzar SAXNotRecognizedException o SAXNotSupportedException. Configúralas antes de iniciar.

Parsing incremental

IncrementalParser acepta bloques mediante feed(). Después del último bloque, close() verifica condiciones que solo pueden confirmarse al final. Llama a reset() después de close() antes de reutilizar el parser.

from xml.sax import make_parser

parser = make_parser()
parser.setContentHandler(MiHandler())

with open("grande.xml", "rb") as archivo:
    while bloque := archivo.read(64 * 1024):
        parser.feed(bloque)

parser.close()

No mezcles parse() y feed() en la misma operación. No llames a reset() mientras se procesa un documento.

Los límites siguen siendo necesarios

Leer por bloques no limita el tamaño total. Cuenta bytes, eventos, profundidad, texto acumulado y tiempo.

MAX_BYTES = 20 * 1024 * 1024
recibidos = 0

while bloque := origen.read(64 * 1024):
    recibidos += len(bloque)
    if recibidos > MAX_BYTES:
        raise ValueError("El XML supera el límite")
    parser.feed(bloque)

parser.close()

Locator para línea y columna

Un Locator asocia un evento con la posición actual. El parser lo entrega mediante setDocumentLocator(). Los valores son válidos solo durante los callbacks.

from xml.sax.handler import ContentHandler

class HandlerConPosicion(ContentHandler):
    def setDocumentLocator(self, locator):
        self.locator = locator

    def startElement(self, name, attrs):
        linea = self.locator.getLineNumber()
        columna = self.locator.getColumnNumber()
        print(name, linea, columna)

Copia línea y columna durante el evento. Consultar el locator más tarde dará otra posición.

AttributesImpl

AttributesImpl representa los atributos pasados a startElement(). Implementa parte del protocolo de mapping y métodos como getLength(), getNames(), getType() y getValue().

El parser puede reutilizar el objeto. Copia los atributos si deben sobrevivir al callback.

def startElement(self, name, attrs):
    copia = dict(attrs.items())
    self.registros.append((name, copia))

Atributos con namespaces

Con namespaces activados, startElementNS() recibe nombres como tuplas (namespaceURI, localname). AttributesNSImpl usa la misma forma y convierte entre nombres cualificados y pares de namespace.

No dependas solo del prefijo textual. Puede cambiar mientras la URI permanece igual.

Manejo de errores

Un error handler recibe warnings, errores recuperables y fatales. En pipelines de datos suele ser mejor detenerse ante errores estructurales y devolver un diagnóstico controlado. Evita registrar el XML completo o credenciales.

Resolver que bloquea entidades

from io import StringIO
from xml.sax.handler import EntityResolver
from xml.sax.xmlreader import InputSource

class BloquearExternas(EntityResolver):
    def resolveEntity(self, publicId, systemId):
        fuente = InputSource()
        fuente.setCharacterStream(StringIO(""))
        return fuente

parser.setEntityResolver(BloquearExternas())

Según la aplicación, lanzar una excepción puede ser más seguro que devolver contenido vacío. La regla esencial es evitar acceso externo automático.

Pruebas recomendadas

Prueba strings, objetos Path, byte streams, character streams, encoding declarado y sobrescrito, namespaces, atributos copiados, entrada incremental, documentos truncados, entidades externas, features no soportadas, límites de bytes y posiciones.

Errores comunes

Los fallos frecuentes son confiar en system IDs externos, decodificar mal character streams, olvidar close(), reutilizar sin reset(), guardar atributos sin copiar, habilitar entidades externas y asumir que todos los parsers soportan las mismas features.

Conclusión

xml.sax.xmlreader define la infraestructura de los parsers SAX: lectores, fuentes, handlers, features, properties, locators y atributos. Comprenderla permite controlar entrada, encoding, streaming y seguridad.

Abre fuentes externas con límites, mantén desactivadas las entidades externas y configura el lector antes del parsing. Consulta la documentación oficial de xmlreader y las orientaciones de seguridad XML de Python.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python mostrando avisos controlados con catch_warnings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: captura warnings en pruebas Python

    Aprende catch_warnings en Python para capturar, probar y controlar avisos con filtros específicos y alcance seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python que representa referencias persistentes de pickle
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serializa referencias externas

    Aprende pickle persistent_id en Python para referencias externas estables, validación, seguridad, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binario que representa buffers y vistas de memoria en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: cuenta valores sin copiar buffers

    Aprende memoryview.count en Python para contar bytes y valores en buffers sin copias, con formatos, límites y buenas prácticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python y anotaciones de tipos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evita imports circulares en anotaciones

    Aprende annotationlib en Python para inspeccionar anotaciones diferidas, evitar imports circulares y crear herramientas seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026