Assinar, comparar ou versionar documentos XML exige mais do que simplesmente salvar a mesma árvore duas vezes. Espaços, ordem de atributos, declarações de namespace e outros detalhes podem mudar a representação textual sem alterar o significado dos dados. A função xml.etree.ElementTree.canonicalize() resolve esse problema ao produzir uma forma canônica de XML, adequada para comparações estáveis, auditoria e preparação de conteúdo para assinaturas digitais.
Neste guia, você aprenderá o que é canonicalização XML, como usar ElementTree.canonicalize, quais opções controlam a saída e quais cuidados de segurança e compatibilidade devem fazer parte de uma implementação real.
O que é XML canônico
Dois documentos XML podem representar a mesma estrutura e ainda possuir textos diferentes. Atributos podem aparecer em ordens distintas, namespaces podem usar prefixos diferentes e espaços podem variar. Uma comparação simples de strings consideraria esses documentos diferentes. A canonicalização transforma o XML em uma representação previsível, seguindo regras definidas pelo padrão C14N.
Isso é útil em testes, caches, deduplicação, controle de versão e sistemas que calculam hashes. Também é uma etapa comum antes de assinar XML, porque qualquer diferença textual posterior altera o resumo criptográfico.
Exemplo básico
from xml.etree.ElementTree import canonicalize
xml = '''
<pedido xmlns:x="https://exemplo.com/item" id="42" status="pago">
<x:produto quantidade="2" codigo="A1" />
</pedido>
'''
resultado = canonicalize(xml_data=xml)
print(resultado)A função recebe o XML por xml_data e retorna texto Unicode. Também é possível informar um arquivo de saída por meio de out. O resultado reorganiza detalhes que não devem influenciar a equivalência estrutural.
Lendo de arquivo
from xml.etree.ElementTree import canonicalize
with open("entrada.xml", encoding="utf-8") as origem:
with open("canonico.xml", "w", encoding="utf-8") as destino:
canonicalize(from_file=origem, out=destino)Quando o XML já está em disco, from_file evita carregar manualmente o conteúdo. O arquivo de destino deve ser aberto em modo texto com UTF-8. Essa escolha reduz ambiguidades e mantém a saída consistente entre ambientes.
Removendo comentários
Comentários normalmente não participam do significado dos dados. A opção with_comments define se eles permanecem no resultado.
canonico = canonicalize(
xml_data=xml,
with_comments=False,
)Para hashes e comparações de configuração, remover comentários costuma ser adequado. Em auditorias ou fluxos documentais, eles podem ser relevantes e devem ser preservados.
Controlando espaços com strip_text
A opção strip_text remove espaços antes e depois do conteúdo textual. Use-a apenas quando esses espaços não tiverem significado para o domínio.
canonico = canonicalize(
xml_data="<nome> Ana </nome>",
strip_text=True,
)Em XML de configuração, isso pode ajudar. Em documentos editoriais, mensagens ou formatos nos quais espaços são significativos, a remoção pode alterar o conteúdo. Teste sempre com amostras reais.
Reescrevendo prefixos de namespace
Prefixos diferentes podem apontar para o mesmo namespace. A opção rewrite_prefixes substitui os prefixos originais por nomes previsíveis, como n0 e n1.
canonico = canonicalize(
xml_data=xml,
rewrite_prefixes=True,
)Essa opção é útil quando documentos equivalentes chegam de sistemas que escolhem prefixos diferentes. O URI do namespace continua sendo o identificador real; o prefixo é apenas uma forma abreviada.
Atributos e tags que contêm nomes qualificados
Alguns formatos guardam nomes com prefixos dentro de valores textuais. Nesses casos, o canonicalizador precisa saber quais atributos ou tags contêm QNames. Use qname_aware_tags e qname_aware_attrs.
canonico = canonicalize(
xml_data=xml,
rewrite_prefixes=True,
qname_aware_attrs={"tipo"},
qname_aware_tags={"expressao"},
)Sem essa configuração, um prefixo reescrito na estrutura pode permanecer antigo dentro do texto, criando inconsistência semântica.
Excluindo elementos e atributos
As opções exclude_tags e exclude_attrs permitem ignorar partes voláteis, como timestamps, identificadores de rastreamento ou campos calculados.
canonico = canonicalize(
xml_data=xml,
exclude_attrs={"gerado_em", "request_id"},
exclude_tags={"debug"},
)Isso facilita testes de snapshot e deduplicação, mas exige cuidado. Excluir campos relevantes pode fazer documentos diferentes parecerem iguais. Defina a política por domínio e documente-a.
Gerando hashes estáveis
from hashlib import sha256
from xml.etree.ElementTree import canonicalize
def hash_xml(xml_texto: str) -> str:
canonico = canonicalize(
xml_data=xml_texto,
with_comments=False,
rewrite_prefixes=True,
)
return sha256(canonico.encode("utf-8")).hexdigest()O hash será estável somente se todos os participantes aplicarem exatamente as mesmas opções. Guarde a versão da política de canonicalização junto ao hash ou ao protocolo.
Comparando documentos em testes
def xml_equivalente(a: str, b: str) -> bool:
opcoes = {
"with_comments": False,
"rewrite_prefixes": True,
"strip_text": True,
}
return canonicalize(xml_data=a, **opcoes) == canonicalize(xml_data=b, **opcoes)Esse método é melhor do que comparar XML bruto, mas não substitui validação de esquema ou regras do negócio. Dois documentos canônicos iguais são textualmente equivalentes segundo a política escolhida; isso não garante que sejam válidos para uma API.
Canonicalização e assinatura digital
A canonicalização é somente uma parte de uma assinatura XML. Ela não calcula a assinatura, não valida certificados e não protege contra todas as transformações possíveis. Sistemas de assinatura devem seguir o algoritmo, as transformações e o perfil exigidos pelo protocolo. Para cenários críticos, use bibliotecas especializadas em XML Signature e valide a interoperabilidade com os outros participantes.
Nunca implemente um protocolo criptográfico apenas concatenando canonicalize() com uma chave privada sem compreender o padrão completo.
Segurança no parsing
XML externo pode causar consumo excessivo de recursos ou explorar comportamentos perigosos em parsers inadequados. Limite o tamanho dos documentos, valide a origem, imponha timeouts e considere bibliotecas reforçadas quando o conteúdo não for confiável. A canonicalização não sanitiza automaticamente o XML nem valida sua segurança.
Leia também os artigos sobre ElementTree no Python, hashes com hashlib, arquivos com pathlib e a categoria de Python Avançado.
Boas práticas
Defina uma única função central para canonicalizar XML, evitando opções diferentes espalhadas pelo projeto. Escreva testes com namespaces, atributos fora de ordem, comentários, espaços, caracteres Unicode e campos excluídos. Registre a política usada e não altere opções silenciosamente em sistemas que dependem de hashes antigos.
Quando a saída for persistida, use UTF-8 e modo texto. Ao comparar documentos muito grandes, estabeleça limites de memória e considere o impacto de manter strings completas em memória.
Referências externas
Consulte a documentação oficial de ElementTree.canonicalize e a recomendação XML Canonicalization do W3C.
Conclusão
ElementTree.canonicalize oferece uma forma prática de transformar XML em texto estável. Ela é útil para testes, hashes, auditoria e integração com sistemas que exigem representação canônica. O resultado depende das opções escolhidas, portanto trate a configuração como uma política formal. Com validação, limites de segurança e testes de interoperabilidade, a função reduz falsos positivos em comparações e torna pipelines XML mais previsíveis.







