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.







