O módulo xml.parsers.expat oferece acesso direto ao parser XML Expat. Ele é rápido, orientado a eventos e não valida documentos contra schemas. Em vez de construir uma árvore automaticamente, a aplicação registra funções para eventos como início de elemento, texto, comentários, namespaces e declarações.
Esse nível de controle é útil em protocolos, ferramentas de análise, conversores e integrações que precisam de desempenho ou callbacks específicos. Para a maioria das tarefas de aplicação, ElementTree no Python ou xml.sax no Python oferecem interfaces mais simples.
Criar um parser
Use ParserCreate(). O parâmetro opcional encoding sobrescreve o encoding declarado no XML. Expat oferece suporte nativo a um conjunto restrito, incluindo UTF-8, UTF-16, ISO-8859-1 e ASCII.
from xml.parsers import expat
parser = expat.ParserCreate()
Uma instância deve processar apenas um documento. Para o próximo arquivo, crie outro parser.
Registrar handlers
Handlers são atribuídos diretamente aos atributos do parser.
from xml.parsers import expat
def inicio(nome, atributos):
print("início", nome, atributos)
def fim(nome):
print("fim", nome)
def texto(dados):
if dados.strip():
print("texto", repr(dados))
parser = expat.ParserCreate()
parser.StartElementHandler = inicio
parser.EndElementHandler = fim
parser.CharacterDataHandler = texto
parser.Parse("<raiz><item id='1'>Olá</item></raiz>", True)
O segundo argumento de Parse() deve ser verdadeiro na chamada final. Ele informa ao parser que não haverá mais dados e permite verificar tokens incompletos.
Parsing incremental
O documento pode ser enviado em blocos. Isso ajuda com arquivos grandes e streams, mas não limita o volume total automaticamente.
MAX_BYTES = 20 * 1024 * 1024
recebidos = 0
with open("dados.xml", "rb") as arquivo:
while bloco := arquivo.read(64 * 1024):
recebidos += len(bloco)
if recebidos > MAX_BYTES:
raise ValueError("XML grande demais")
parser.Parse(bloco, False)
parser.Parse(b"", True)
Defina também limites de eventos, profundidade, texto acumulado e tempo. Ler em blocos evita uma alocação única, mas não impede ataques de expansão.
CharacterDataHandler pode fragmentar texto
Expat pode chamar CharacterDataHandler várias vezes para um único trecho lógico, especialmente em quebras de linha ou limites de blocos. Acumule os fragmentos até o fim do elemento.
partes = []
parser.buffer_text = True
def texto(dados):
partes.append(dados)
buffer_text=True reduz callbacks, mas ainda não é uma garantia de um callback por elemento. O atributo buffer_size controla o tamanho do buffer.
Namespaces
Passe um separador de um caractere para ParserCreate() a fim de ativar namespaces.
parser = expat.ParserCreate(namespace_separator=" ")
def inicio(nome, atributos):
namespace, _, local = nome.partition(" ")
print(namespace, local)
Com namespaces ativados, nomes são expandidos como URI, separador e nome local. Evite depender do prefixo textual original.
Posição atual e erros
Durante callbacks, CurrentLineNumber, CurrentColumnNumber e CurrentByteIndex indicam a posição do evento. Após um erro, use ErrorLineNumber, ErrorColumnNumber, ErrorByteIndex e ErrorCode.
from xml.parsers import expat
try:
parser.Parse("<raiz><item></raiz>", True)
except expat.ExpatError as erro:
mensagem = expat.ErrorString(erro.code)
print(mensagem, erro.lineno, erro.offset)
Não registre o documento inteiro em produção. Inclua apenas origem, posição e uma janela pequena quando ela não contiver segredos.
Atributos ordenados
Por padrão, os atributos chegam como dicionário. Com ordered_attributes=True, chegam como lista alternando nome e valor na ordem do documento.
parser.ordered_attributes = True
Use isso apenas quando a ordem da fonte for necessária para diagnóstico ou reprodução. A ordem de atributos não deve carregar significado semântico em XML.
Atributos especificados
specified_attributes=True faz o parser relatar somente atributos presentes no documento, omitindo valores derivados de declarações. Esse comportamento exige conhecimento das regras de DTD e raramente é necessário em aplicações comuns.
Entidades externas
ExternalEntityRefHandler permite carregar entidades externas, mas pode abrir acesso a arquivos locais e rede. Para XML controlado por usuários, não implemente um handler que abra o systemId.
O artigo de xmlreader no Python mostra como bloquear fontes externas em parsers SAX. A regra é a mesma: o documento não deve escolher recursos.
Entidades de parâmetro e DTD
SetParamEntityParsing() controla entidades de parâmetro. UseForeignDTD() permite solicitar uma DTD alternativa. Essas funções ampliam a superfície de ataque e devem permanecer desativadas em documentos não confiáveis.
Reparse deferral
Expat 2.6 introduziu “reparse deferral” para evitar custo quadrático ao receber tokens muito grandes em fragmentos. No Python, SetReparseDeferralEnabled(False) desativa a proteção e pode reintroduzir negação de serviço.
if hasattr(parser, "GetReparseDeferralEnabled"):
assert parser.GetReparseDeferralEnabled()
Mantenha a proteção ativada. A consequência é que um handler talvez não seja chamado imediatamente após cada Parse(); isso é esperado.
Proteção contra billion laughs
No Python 3.14.6, parsers Expat podem oferecer métodos para configurar o limiar de ativação e o fator máximo de amplificação de entidades:
if hasattr(parser, "SetBillionLaughsAttackProtectionActivationThreshold"):
parser.SetBillionLaughsAttackProtectionActivationThreshold(8 * 1024 * 1024)
if hasattr(parser, "SetBillionLaughsAttackProtectionMaximumAmplification"):
parser.SetBillionLaughsAttackProtectionMaximumAmplification(100.0)
Os valores padrão dependem da biblioteca Expat. Limites muito baixos podem bloquear documentos legítimos. Faça testes com os formatos reais e nunca aumente valores sem compreender a ameaça.
Proteção de memória
Também existem métodos de rastreamento de alocação em versões recentes:
if hasattr(parser, "SetAllocTrackerActivationThreshold"):
parser.SetAllocTrackerActivationThreshold(64 * 1024 * 1024)
if hasattr(parser, "SetAllocTrackerMaximumAmplification"):
parser.SetAllocTrackerMaximumAmplification(100.0)
Esses controles complementam, mas não substituem, limites externos de bytes, tempo, profundidade e quantidade de eventos.
Comentários, CDATA e instruções
O parser possui handlers para comentários, início e fim de CDATA, instruções de processamento e declarações XML. CharacterDataHandler recebe tanto texto normal quanto conteúdo CDATA; os handlers de CDATA servem para diferenciar a forma sintática.
GetInputContext()
Durante um callback, GetInputContext() pode retornar o trecho de entrada relacionado ao evento. Use apenas para diagnóstico controlado. O conteúdo pode incluir dados sensíveis ou ser grande.
ParseFile()
ParseFile() recebe um objeto com método read(nbytes). É simples, mas oferece menos controle direto sobre contagem de bytes e cancelamento do que um loop explícito com Parse().
Erro de encoding
Se o XML declara um encoding não suportado ou contém bytes inválidos, Expat gera ExpatError. Não force um encoding incorreto para “fazer funcionar”. Corrija a origem ou converta os bytes em uma etapa bem definida.
Testes recomendados
Teste parsing completo e fragmentado, texto dividido, namespaces, encodings, documento truncado, tag incompatível, atributo duplicado, tokens muito grandes, entidades internas, DTD, entidades externas bloqueadas, profundidade excessiva e limites de amplificação.
Erros comuns
Os erros mais frequentes são reutilizar um parser para vários documentos, esquecer isfinal=True, assumir texto em um único callback, desativar reparse deferral, implementar entidades externas sem política, confiar somente nas proteções internas e ignorar limites de aplicação.
Quando escolher outra API
Use Expat diretamente quando precisar de callbacks de baixo nível, desempenho e controle fino. Para árvores, prefira ElementTree. Para uma interface SAX padronizada, use xml.sax. Para DOM parcial, veja pulldom no Python.
Conclusão
xml.parsers.expat oferece um parser XML rápido e detalhado, mas exige que a aplicação gerencie estado, texto fragmentado, limites e segurança. Mantenha reparse deferral e proteções de amplificação ativas, não carregue entidades externas e crie uma instância por documento.
Consulte a documentação oficial de Expat no Python e o projeto oficial Expat.







