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.







