expat no Python: parser XML de baixo nível

Publicado em: 23/08/2026
Tempo de leitura: 5 minutos
A laptop screen showing a code editor with visible programming code in a dimly lit environment.

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.

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