pulldom no Python: DOM parcial para XML

Publicado em: 23/08/2026
Tempo de leitura: 6 minutos
A developer typing code on a laptop with a Python book beside in an office.

O módulo xml.dom.pulldom oferece uma forma intermediária de processar XML no Python. Ele combina um fluxo de eventos, parecido com SAX, com a possibilidade de transformar apenas trechos selecionados em árvores DOM completas. Essa abordagem é útil quando o documento é grande demais para ser carregado inteiramente em memória, mas algumas partes precisam de navegação, atributos, filhos e serialização como nós DOM.

Em vez de construir a árvore inteira logo no início, o programa percorre eventos como início de elemento, texto, comentários e fim de documento. Quando encontra um elemento relevante, chama expandNode() para materializar somente aquela subárvore. O resultado é um compromisso entre o baixo consumo de memória do parsing orientado a eventos e a comodidade do DOM.

Quando usar pulldom

pulldom faz sentido quando o XML possui muitos registros repetidos, como catálogos, exportações financeiras, logs, feeds, relatórios e integrações legadas. Um arquivo pode conter milhares de elementos, mas o sistema talvez precise apenas daqueles com um status específico, preço acima de um limite ou identificador presente em uma lista.

Se o documento inteiro cabe confortavelmente em memória e a aplicação precisa navegar livremente por todos os nós, o guia de minidom no Python pode ser mais direto. Quando o objetivo é apenas percorrer eventos e extrair valores sem criar subárvores DOM, veja também xml.sax no Python. Para a maioria das transformações estruturadas, ElementTree no Python costuma oferecer uma API mais simples.

Primeiro exemplo

from xml.dom import pulldom

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

for event, node in events:
    if event == pulldom.START_ELEMENT and node.tagName == "produto":
        events.expandNode(node)
        print(node.toxml())

Antes de expandNode(), o nó de início não possui todos os filhos materializados. Depois da expansão, ele pode ser tratado como um elemento de minidom, com métodos como getAttribute(), getElementsByTagName() e toxml().

Filtrar antes de expandir

A principal vantagem aparece quando a aplicação usa atributos disponíveis no evento inicial para decidir se vale a pena expandir a subárvore.

from decimal import Decimal
from xml.dom import pulldom

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

for event, node in events:
    if event != pulldom.START_ELEMENT:
        continue
    if node.tagName != "produto":
        continue

    preco_texto = node.getAttribute("preco")
    try:
        preco = Decimal(preco_texto)
    except Exception:
        continue

    if preco >= Decimal("100.00"):
        events.expandNode(node)
        print(node.toxml())

O código evita expandir produtos baratos. Em documentos grandes, isso reduz a quantidade de objetos DOM criados. Ainda assim, entradas externas precisam de validação de tamanho, profundidade, quantidade de elementos e conteúdo numérico.

Eventos disponíveis

O fluxo pode produzir eventos como START_DOCUMENT, END_DOCUMENT, START_ELEMENT, END_ELEMENT, CHARACTERS, COMMENT, PROCESSING_INSTRUCTION e IGNORABLE_WHITESPACE. O tipo do nó depende do evento: documentos, elementos e nós de texto são objetos DOM.

Não presuma que todo texto chegará em um único evento. Parsers orientados a eventos podem dividir conteúdo textual em vários blocos. Quando precisar acumular texto fora de uma subárvore expandida, use uma lista e una os fragmentos apenas ao fechar o elemento correspondente.

Manter contexto durante o fluxo

O fluxo é plano. Para saber em qual caminho do documento o elemento atual está, mantenha uma pilha de nomes.

from xml.dom import pulldom

events = pulldom.parse("dados.xml")
pilha = []

for event, node in events:
    if event == pulldom.START_ELEMENT:
        pilha.append(node.tagName)
        caminho = "/".join(pilha)
        if caminho == "catalogo/secao/produto":
            events.expandNode(node)
            print(node.getAttribute("id"))
            pilha.pop()
    elif event == pulldom.END_ELEMENT:
        if pilha:
            pilha.pop()

Depois que expandNode() consome os eventos internos do elemento, ajuste a pilha de acordo com a estratégia usada. Testes com documentos pequenos ajudam a validar o estado do parser.

Ler de arquivo, stream ou string

parse() aceita nome de arquivo ou objeto semelhante a arquivo. parseString() recebe XML em memória. Para dados vindos da rede, prefira baixar com limites e armazenar em arquivo temporário antes do parsing. O guia de urllib.request no Python mostra como limitar downloads externos, e o artigo de tempfile no Python explica áreas temporárias seguras.

from io import BytesIO
from xml.dom import pulldom

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

Evite receber uma string de tamanho ilimitado de um cliente e passá-la diretamente a parseString(). Um limite deve ser aplicado antes da alocação completa.

Namespaces

pulldom.parse() ativa suporte a namespaces no parser fornecido. Em XML com namespaces, use propriedades DOM como namespaceURI, localName e tagName com cuidado. O prefixo pode variar sem alterar o significado.

from xml.dom import pulldom

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

for event, node in events:
    if event == pulldom.START_ELEMENT:
        if node.namespaceURI == "urn:exemplo:catalogo" and node.localName == "produto":
            events.expandNode(node)
            print(node.toxml())

Comparar apenas o prefixo, como cat:produto, torna o código frágil. Prefira URI e nome local.

Segurança de entidades externas

A documentação oficial alerta que XML não autenticado pode ser perigoso. Desde o Python 3.7.1, o parser SAX usado por padrão não processa entidades externas gerais. Não reative esse recurso para documentos enviados por usuários. Entidades externas podem permitir leitura de arquivos locais, requisições de rede e ataques de expansão.

Se um parser customizado for necessário, configure um EntityResolver que rejeite recursos externos e mantenha as features externas desativadas. O fato de pulldom ser incremental não elimina riscos de XML hostil.

Limites de profundidade e volume

Imponha limites para bytes de entrada, quantidade de eventos, profundidade da pilha, tamanho de atributos, número de subárvores expandidas e tamanho serializado de cada resultado. Um arquivo pequeno pode produzir estruturas muito profundas ou textos enormes.

MAX_EVENTOS = 1_000_000
MAX_PROFUNDIDADE = 100

eventos_lidos = 0
pilha = []

for event, node in events:
    eventos_lidos += 1
    if eventos_lidos > MAX_EVENTOS:
        raise ValueError("XML excede o limite de eventos")

    if event == pulldom.START_ELEMENT:
        pilha.append(node.tagName)
        if len(pilha) > MAX_PROFUNDIDADE:
            raise ValueError("XML profundo demais")
    elif event == pulldom.END_ELEMENT and pilha:
        pilha.pop()

Expandir e liberar memória

Uma subárvore expandida continua sendo um conjunto de objetos DOM enquanto houver referências. Processe o nó, extraia apenas os dados necessários e remova referências. Para documentos muito grandes, evite armazenar todos os nós em uma lista.

resultados = []

for event, node in events:
    if event == pulldom.START_ELEMENT and node.tagName == "produto":
        events.expandNode(node)
        resultados.append({
            "id": node.getAttribute("id"),
            "xml": node.toxml(),
        })
        node.unlink()

unlink() pode liberar referências internas mais cedo, mas só deve ser chamado depois que o nó não for mais necessário.

Serialização não é sanitização

toxml() produz XML, mas não torna dados confiáveis. Conteúdo extraído de XML ainda precisa de validação antes de ser usado em HTML, SQL, comandos, caminhos ou logs. Ao lidar com entidades HTML, o guia de html.entities no Python explica a diferença entre decodificação e sanitização.

Tratamento de erros

Capture erros de parsing em uma fronteira clara, registre linha, coluna e origem sem incluir segredos e rejeite documentos incompletos. Não tente corrigir silenciosamente XML malformado em integrações críticas.

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

try:
    events = pulldom.parse("entrada.xml")
    for event, node in events:
        pass
except SAXParseException as exc:
    print(f"XML inválido na linha {exc.getLineNumber()}")

Testes recomendados

Teste documentos vazios, namespaces alternativos, atributos ausentes, texto fragmentado, elementos muito profundos, entidades externas, arquivos truncados, encodings diferentes e vários elementos de interesse. Compare também o resultado com ElementTree ou minidom em amostras pequenas.

Quando escolher outra API

Use ElementTree.iterparse() quando quiser uma árvore leve e eventos incrementais. Use SAX quando nenhum DOM for necessário. Use minidom quando o documento inteiro for pequeno e a compatibilidade DOM for importante. Escolha pulldom quando apenas alguns fragmentos precisam de recursos DOM completos.

Conclusão

xml.dom.pulldom permite percorrer XML como fluxo e expandir seletivamente apenas os elementos relevantes. Essa estratégia reduz memória e mantém a conveniência do DOM onde ela realmente é necessária.

Use filtros antes de expandNode(), mantenha entidades externas desativadas, imponha limites e libere referências após o processamento. Consulte a documentação oficial de pulldom e as orientações de segurança para XML do Python.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Close-up view of a computer screen displaying code in a software development environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    xml.sax no Python: processe XML em eventos

    Aprenda xml.sax no Python para processar XML por eventos com baixo uso de memória, namespaces, handlers, limites e segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    22/08/2026
    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    minidom no Python: manipule XML com DOM

    Aprenda xml.dom.minidom no Python para ler, navegar, criar e serializar XML com DOM, namespaces, memória e segurança.

    Ler mais

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

    ElementTree: leia e modifique XML no Python

    Aprenda ElementTree no Python para ler, buscar, modificar e gerar XML com namespaces, parsing incremental, limites e segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    22/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    poplib no Python: leia e-mails com POP3

    Aprenda poplib no Python para acessar POP3 com TLS, listar e baixar mensagens, usar UIDL, limitar dados e evitar exclusões

    Ler mais

    Tempo de leitura: 6 minutos
    22/08/2026
    A close-up of a laptop on a table, displaying a book on test-driven software with Python, set in a comfortable environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    imaplib no Python: leia e-mails com IMAP

    Aprenda imaplib no Python para acessar caixas IMAP com TLS, buscar por UID, ler mensagens sem marcá-las, usar flags e

    Ler mais

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

    ftplib no Python: FTP e FTPS seguros

    Aprenda ftplib no Python para listar, baixar e enviar arquivos por FTP ou FTPS com TLS, timeouts, limites, retomada e

    Ler mais

    Tempo de leitura: 5 minutos
    21/08/2026