pulldom en Python: DOM parcial 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.dom.pulldom ofrece un punto intermedio para procesar XML en Python. Combina un flujo de eventos controlado por el consumidor, parecido a SAX, con la posibilidad de construir subárboles DOM completos solo cuando son necesarios. Esta estrategia resulta útil cuando un documento es demasiado grande para cargarlo entero en memoria, pero ciertas secciones necesitan navegación, atributos, hijos y serialización DOM.

En lugar de construir todo el árbol al inicio, el programa recorre eventos como inicio de elemento, texto, comentarios y fin del documento. Cuando encuentra un elemento relevante, llama a expandNode() para materializar únicamente ese subárbol. Así se equilibra el bajo consumo de memoria del parsing por eventos con la comodidad de los nodos DOM.

Cuándo usar pulldom

pulldom encaja bien con catálogos, exportaciones financieras, logs, feeds, informes e integraciones heredadas con miles de registros repetidos. El sistema puede necesitar solo elementos con cierto estado, precio, identificador o fecha.

Cuando el documento completo cabe cómodamente en memoria y se necesita navegar libremente, consulta minidom en Python. Si solo necesitas eventos y valores extraídos sin fragmentos DOM, revisa xml.sax en Python. Para muchas transformaciones estructurales, ElementTree en Python suele ofrecer una API más sencilla.

Primer ejemplo

from xml.dom import pulldom

eventos = pulldom.parse("catalogo.xml")

for evento, nodo in eventos:
    if evento == pulldom.START_ELEMENT and nodo.tagName == "producto":
        eventos.expandNode(nodo)
        print(nodo.toxml())

Antes de expandNode(), el nodo inicial no contiene todos los hijos materializados. Después de la expansión se comporta como un elemento de minidom y permite usar getAttribute(), getElementsByTagName() y toxml().

Filtrar antes de expandir

La principal ventaja aparece cuando los atributos disponibles en el evento inicial permiten decidir si vale la pena construir el subárbol.

from decimal import Decimal
from xml.dom import pulldom

eventos = pulldom.parse("catalogo.xml")

for evento, nodo in eventos:
    if evento != pulldom.START_ELEMENT:
        continue
    if nodo.tagName != "producto":
        continue

    try:
        precio = Decimal(nodo.getAttribute("precio"))
    except Exception:
        continue

    if precio >= Decimal("100.00"):
        eventos.expandNode(nodo)
        print(nodo.toxml())

Esto evita crear objetos DOM para productos que no interesan. Los documentos externos grandes siguen necesitando límites de bytes, profundidad, cantidad de elementos, atributos y fragmentos expandidos.

Eventos disponibles

El flujo puede producir START_DOCUMENT, END_DOCUMENT, START_ELEMENT, END_ELEMENT, CHARACTERS, COMMENT, PROCESSING_INSTRUCTION e IGNORABLE_WHITESPACE. El tipo de nodo cambia según el evento y puede ser documento, elemento o texto.

No supongas que todo el texto llegará en un único evento. Los parsers orientados a eventos pueden dividir datos contiguos en varios fragmentos. Si recoges texto sin expandir el subárbol, acumula las partes en una lista y únelas al recibir el evento de cierre.

Mantener contexto con una pila

El flujo de eventos es plano. Para conocer la ruta del elemento actual, mantén una pila de nombres.

from xml.dom import pulldom

eventos = pulldom.parse("datos.xml")
pila = []

for evento, nodo in eventos:
    if evento == pulldom.START_ELEMENT:
        pila.append(nodo.tagName)
        ruta = "/".join(pila)
        if ruta == "catalogo/seccion/producto":
            eventos.expandNode(nodo)
            print(nodo.getAttribute("id"))
            pila.pop()
    elif evento == pulldom.END_ELEMENT and pila:
        pila.pop()

Como expandNode() consume los eventos internos del elemento, ajusta la pila de forma coherente y valida el comportamiento con archivos pequeños.

Archivos, streams y strings

parse() acepta un nombre de archivo o un objeto similar a archivo. parseString() recibe XML ya cargado en memoria. Para datos obtenidos por red, descarga con límites explícitos y considera un archivo temporal antes del parsing. El artículo de urllib.request en Python explica descargas limitadas, y tempfile en Python muestra almacenamiento temporal seguro.

from io import BytesIO
from xml.dom import pulldom

xml_bytes = b"<raiz><item id='1'/></raiz>"
eventos = pulldom.parse(BytesIO(xml_bytes))

No aceptes un string ilimitado de un cliente para pasarlo directamente a parseString(). Aplica el límite antes de reservar el buffer completo.

Namespaces

pulldom.parse() activa el soporte de namespaces en el parser suministrado. En XML con espacios de nombres, usa namespaceURI, localName y tagName con cuidado. El prefijo puede cambiar sin alterar el significado.

from xml.dom import pulldom

eventos = pulldom.parse("feed.xml")

for evento, nodo in eventos:
    if evento == pulldom.START_ELEMENT:
        if nodo.namespaceURI == "urn:ejemplo:catalogo" and nodo.localName == "producto":
            eventos.expandNode(nodo)
            print(nodo.toxml())

Comparar solo un prefijo como cat:producto vuelve el código frágil. Prefiere la URI y el nombre local.

Seguridad de entidades externas

La documentación oficial advierte que el XML no autenticado puede ser peligroso. Desde Python 3.7.1, el parser SAX usado por defecto ya no procesa entidades externas generales. No reactives esa función para documentos enviados por usuarios. Las entidades externas pueden provocar lectura de archivos locales, solicitudes de red inesperadas o ataques de expansión.

Si necesitas un parser personalizado, instala un EntityResolver que rechace recursos externos y mantén desactivadas las features externas. El procesamiento incremental no vuelve seguro un XML hostil por sí solo.

Límites de profundidad y volumen

Define límites de bytes de entrada, número de eventos, profundidad, longitud de atributos, cantidad de nodos expandidos y tamaño de salida serializada. Un archivo pequeño puede contener anidamiento extremo o textos enormes.

MAX_EVENTOS = 1_000_000
MAX_PROFUNDIDAD = 100

contador = 0
pila = []

for evento, nodo in eventos:
    contador += 1
    if contador > MAX_EVENTOS:
        raise ValueError("Se superó el límite de eventos")

    if evento == pulldom.START_ELEMENT:
        pila.append(nodo.tagName)
        if len(pila) > MAX_PROFUNDIDAD:
            raise ValueError("XML demasiado profundo")
    elif evento == pulldom.END_ELEMENT and pila:
        pila.pop()

Expandir, extraer y liberar

Un subárbol expandido mantiene objetos DOM mientras existan referencias. Procesa el nodo, extrae solo los valores necesarios y descártalo. No guardes todos los nodos expandidos de un archivo grande.

resultados = []

for evento, nodo in eventos:
    if evento == pulldom.START_ELEMENT and nodo.tagName == "producto":
        eventos.expandNode(nodo)
        resultados.append({
            "id": nodo.getAttribute("id"),
            "xml": nodo.toxml(),
        })
        nodo.unlink()

unlink() puede liberar referencias internas antes, pero úsalo solo cuando el fragmento ya no sea necesario.

Serializar no significa sanitizar

toxml() genera sintaxis XML, pero no convierte los valores en confiables. El contenido extraído necesita validación antes de usarse en HTML, SQL, comandos, rutas o logs. El guía de html.entities en Python explica por qué decodificar y sanitizar son operaciones distintas.

Manejo de errores

Captura los fallos de parsing en una frontera clara, registra origen y posición sin revelar secretos y rechaza documentos incompletos. Las integraciones críticas no deberían corregir XML malformado en silencio.

from xml.dom import pulldom
from xml.sax import SAXParseException

try:
    eventos = pulldom.parse("entrada.xml")
    for evento, nodo in eventos:
        pass
except SAXParseException as exc:
    print(f"XML inválido en la línea {exc.getLineNumber()}")

Pruebas recomendadas

Prueba archivos vacíos, prefijos alternativos, atributos ausentes, texto fragmentado, profundidad excesiva, entidades externas desactivadas, documentos truncados, varios encodings y múltiples elementos objetivo. En fixtures pequeños, compara el resultado con ElementTree o minidom.

Cuándo elegir otra API

Usa ElementTree.iterparse() cuando basten un árbol ligero y eventos incrementales. Usa SAX cuando no necesites DOM. Usa minidom cuando el documento completo sea pequeño y la compatibilidad DOM importe. Elige pulldom cuando solo algunos fragmentos necesiten comportamiento DOM completo.

Conclusión

xml.dom.pulldom permite recorrer XML como eventos y expandir selectivamente los elementos relevantes. Esto reduce memoria sin renunciar a la comodidad DOM en los fragmentos importantes.

Filtra antes de expandir, mantén desactivadas las entidades externas, aplica límites y libera referencias cuanto antes. Consulta la documentación oficial de pulldom y las orientaciones de seguridad XML de Python.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    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
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ftplib en Python: FTP y FTPS seguros

    Aprende ftplib en Python para listar, descargar y subir archivos por FTP o FTPS con TLS, timeouts, límites, reanudación y

    Ler mais

    Tempo de leitura: 5 minutos
    21/08/2026