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

    Desenvolvedor configurando servidor HTTPS e certificado TLS com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crie servidor HTTPS local no Python

    Aprenda HTTPSServer no Python para servir HTTPS localmente, configurar certificados, usar threads e entender limites de segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026
    Arquivos protegidos representando extração segura de TAR com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extraia TAR com segurança

    Aprenda tarfile extraction_filter no Python para extrair arquivos TAR com validação, segurança e controle de caminhos.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python exibindo avisos controlados com catch_warnings
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: capture warnings em testes Python

    Aprenda catch_warnings no Python para capturar, testar e controlar avisos com filtros específicos e segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python representando referências persistentes do pickle
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serialize referências externas

    Aprenda pickle persistent_id no Python para serializar referências externas com IDs estáveis, validação, segurança e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binário representando buffers e memória no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: conte valores sem copiar buffers

    Aprenda memoryview.count no Python para contar bytes e valores em buffers sem cópias, com formatos, limites e boas práticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python com anotações de tipos
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evite imports circulares em anotações

    Aprenda annotationlib no Python para ler anotações adiadas, evitar imports circulares e criar ferramentas de introspecção seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026