xml.sax no Python: processe XML em eventos

Publicado em: 22/08/2026
Tempo de leitura: 5 minutos
Close-up view of a computer screen displaying code in a software development environment.

O pacote xml.sax implementa a Simple API for XML, uma abordagem orientada a eventos. Em vez de construir uma árvore completa, o parser lê o documento e chama métodos quando encontra início e fim de elementos, texto, namespaces, instruções e erros.

SAX é útil para arquivos grandes, pipelines de transformação, importação de dados e validações que não precisam manter todo o XML em memória. A contrapartida é que a aplicação precisa controlar estado, acumular texto fragmentado e decidir o que fazer em cada evento.

Primeiro handler SAX

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

class CatalogoHandler(ContentHandler):
    def startElement(self, name, attrs):
        print("início", name, dict(attrs))

    def characters(self, content):
        if content.strip():
            print("texto", repr(content))

    def endElement(self, name):
        print("fim", name)

xml.sax.parse("catalogo.xml", CatalogoHandler())

parse() cria um parser, conecta o handler e processa arquivo ou stream. Todo trabalho acontece dentro dos callbacks; não existe uma árvore retornada.

characters pode ser chamado várias vezes

O parser não garante que o texto contíguo chegue em uma única chamada. Um valor pode ser dividido por buffers internos.

class ProdutosHandler(ContentHandler):
    def __init__(self):
        super().__init__()
        self.tag_atual = None
        self.buffer = []

    def startElement(self, name, attrs):
        self.tag_atual = name
        self.buffer.clear()

    def characters(self, content):
        if self.tag_atual in {"nome", "preco"}:
            self.buffer.append(content)

    def endElement(self, name):
        if name in {"nome", "preco"}:
            valor = "".join(self.buffer).strip()
            print(name, valor)
        self.tag_atual = None
        self.buffer.clear()

Acumule o conteúdo até o evento de fechamento. Não processe cada fragmento como se fosse um campo completo.

Estado e pilha de elementos

Em estruturas aninhadas, mantenha uma pilha para saber o caminho atual.

class CaminhoHandler(ContentHandler):
    def __init__(self):
        self.pilha = []

    def startElement(self, name, attrs):
        self.pilha.append(name)
        print("/".join(self.pilha))

    def endElement(self, name):
        if not self.pilha or self.pilha[-1] != name:
            raise ValueError("estado inconsistente")
        self.pilha.pop()

Defina limite de profundidade para evitar XML extremamente aninhado.

Parser configurável

import xml.sax
from xml.sax.handler import feature_namespaces

parser = xml.sax.make_parser()
parser.setFeature(feature_namespaces, True)
parser.setContentHandler(MeuHandler())
parser.parse("dados.xml")

make_parser() retorna um XMLReader. Features devem ser configuradas antes do parsing; tentar alterá-las durante a leitura pode lançar SAXNotSupportedException.

Namespaces

Com namespaces habilitados, implemente startElementNS() e endElementNS(). O nome chega como tupla (URI, nome_local).

class AtomHandler(ContentHandler):
    ATOM = "http://www.w3.org/2005/Atom"

    def startElementNS(self, name, qname, attrs):
        uri, local = name
        if uri == self.ATOM and local == "entry":
            print("nova entrada")

O qname pode ser None se o parser não estiver configurado para reportar prefixos. Use URI e nome local para lógica de domínio.

Mapeamento de prefixos

startPrefixMapping() e endPrefixMapping() informam escopos de prefixos. A ordem desses eventos não é garantida como uma pilha simples, então mantenha um mapa por escopo se precisar interpretar QNames em conteúdo ou atributos.

Atributos

O objeto attrs pode ser reutilizado pelo parser. Se precisar guardar seus valores depois do callback, faça uma cópia.

def startElement(self, name, attrs):
    atributos = dict(attrs.items())
    self.fila.append((name, atributos))

Em modo namespace, use a interface AttributesNS e chaves por URI e nome local.

Locator para linha e coluna

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

    def startElement(self, name, attrs):
        if name == "produto" and "id" not in attrs:
            linha = self.locator.getLineNumber()
            coluna = self.locator.getColumnNumber()
            raise ValueError(
                f"produto sem id em {linha}:{coluna}"
            )

O locator é confiável durante callbacks. Não o consulte muito tempo depois do evento sem copiar linha e coluna.

Tratamento de erros

Um ErrorHandler recebe warnings, erros recuperáveis e erros fatais.

from xml.sax.handler import ErrorHandler

class Erros(ErrorHandler):
    def warning(self, exception):
        print("aviso", exception)

    def error(self, exception):
        raise exception

    def fatalError(self, exception):
        raise exception

Se não fornecer handler, erros normalmente geram SAXParseException. Não continue usando resultados após erro recuperável sem uma política muito clara.

Bloqueando entidades externas

Entidades externas gerais estão desativadas por padrão em Python atual. Não reative feature_external_ges para XML fornecido por usuários.

from xml.sax.handler import (
    EntityResolver,
    feature_external_ges,
)
from xml.sax.xmlreader import InputSource
import io

class ResolverBloqueado(EntityResolver):
    def resolveEntity(self, publicId, systemId):
        fonte = InputSource()
        fonte.setCharacterStream(io.StringIO(""))
        return fonte

parser.setFeature(feature_external_ges, False)
parser.setEntityResolver(ResolverBloqueado())

Reativar entidades externas pode permitir leitura de arquivos locais ou conexões de rede. Analise o threat model antes de qualquer mudança.

InputSource

InputSource permite informar byte stream, character stream, encoding e system ID.

from xml.sax.xmlreader import InputSource

fonte = InputSource()
fonte.setByteStream(arquivo_binario)
fonte.setSystemId("entrada-controlada.xml")
parser.parse(fonte)

Não use um system ID remoto controlado por usuário. Ele pode influenciar resolução de recursos e mensagens de erro.

Limites de bytes e elementos

SAX reduz memória, mas não elimina ataques de CPU, profundidade ou texto gigante. Conte elementos, bytes, tamanho de campos e quantidade de atributos.

class LimitadoHandler(ContentHandler):
    def __init__(self, max_elementos=100_000):
        self.max_elementos = max_elementos
        self.elementos = 0
        self.profundidade = 0

    def startElement(self, name, attrs):
        self.elementos += 1
        self.profundidade += 1
        if self.elementos > self.max_elementos:
            raise ValueError("elementos demais")
        if self.profundidade > 100:
            raise ValueError("XML profundo demais")

    def endElement(self, name):
        self.profundidade -= 1

Processando registros em streaming

Um padrão comum é manter somente o registro atual.

class RegistrosHandler(ContentHandler):
    def __init__(self, destino):
        self.destino = destino
        self.registro = None
        self.campo = None
        self.buffer = []

    def startElement(self, name, attrs):
        if name == "registro":
            self.registro = {"id": attrs.get("id")}
        elif self.registro is not None:
            self.campo = name
            self.buffer = []

    def characters(self, content):
        if self.campo is not None:
            self.buffer.append(content)

    def endElement(self, name):
        if self.registro is None:
            return
        if name == "registro":
            self.destino(self.registro)
            self.registro = None
        elif name == self.campo:
            self.registro[name] = "".join(self.buffer).strip()
            self.campo = None
            self.buffer = []

Valide e persista cada registro. Use transações e idempotência se o processamento puder ser repetido.

Interrompendo cedo

Você pode lançar uma exceção própria quando encontrar o dado desejado ou atingir um limite. Certifique-se de fechar o stream externo no bloco finally.

LexicalHandler

Um LexicalHandler opcional recebe comentários, início e fim de DTD e limites de CDATA. Configure-o com property_lexical_handler. Nem todo parser suporta todas as propriedades; trate SAXNotRecognizedException e SAXNotSupportedException.

DTDHandler

O DTDHandler recebe declarações de notation e entidades não parseadas. A maioria das aplicações não precisa implementá-lo. Não habilite validação ou DTD externo apenas para obter eventos adicionais em conteúdo não confiável.

Serialização e saxutils

xml.sax.saxutils fornece escape(), quoteattr() e XMLGenerator para gerar XML a partir de eventos.

from xml.sax.saxutils import XMLGenerator

with open("saida.xml", "w", encoding="utf-8") as arquivo:
    gerador = XMLGenerator(arquivo, encoding="utf-8")
    gerador.startDocument()
    gerador.startElement("status", {})
    gerador.characters("ok & validado")
    gerador.endElement("status")
    gerador.endDocument()

O gerador faz escaping correto de texto. Ainda valide nomes de tags e atributos dinâmicos.

SAX, ElementTree ou minidom

SAX é ideal para streaming e baixo uso de memória. ElementTree é mais simples quando você precisa consultar e modificar uma árvore. Minidom oferece API DOM completa, mas mantém muitos objetos em memória.

Veja ElementTree no Python e minidom no Python.

Encoding

Prefira fornecer bytes ao parser para que a declaração XML determine o encoding. Se você decodificar antes, garanta que a escolha corresponde ao documento. Para problemas de codecs, consulte codecs no Python.

Logs

Registre nome lógico do documento, contagem, duração e localização do erro. Não registre o XML completo, credenciais, tokens ou dados pessoais. Limite mensagens originadas do parser.

Testes recomendados

Teste fragmentação de characters(), espaços, namespaces, atributos copiados, profundidade, campos gigantes, XML malformado, entidades externas bloqueadas, parser sem suporte a feature, interrupção antecipada, encoding e rollback da persistência.

Erros comuns

Os erros frequentes são assumir um único callback de texto, não manter pilha, guardar attrs sem copiar, habilitar entidades externas, esquecer limites, usar prefixo em vez de URI, continuar após erro, misturar estado entre registros e escolher SAX quando a aplicação precisa navegar para trás na árvore.

Conclusão

xml.sax processa XML como uma sequência de eventos, permitindo arquivos grandes com memória controlada. Implemente handlers pequenos, acumule texto até o fechamento, modele namespaces por URI, bloqueie entidades externas, imponha limites e valide cada registro antes de persistir.

Consulte a documentação oficial de xml.sax e a referência do projeto SAX. Para XML externo, mantenha as proteções padrão e combine streaming com limites de recursos.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    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
    Rack de servidores representando um endpoint criado com xmlrpc.server no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    xmlrpc.server: crie servidores XML-RPC

    Aprenda xmlrpc.server no Python para criar servidores XML-RPC, registrar funções, limitar métodos e caminhos e evitar exposição insegura.

    Ler mais

    Tempo de leitura: 6 minutos
    21/08/2026