Firmar, comparar o versionar documentos XML requiere más que guardar el mismo árbol dos veces. Los espacios, el orden de atributos, las declaraciones de namespace y otros detalles pueden cambiar la representación textual sin modificar el significado de los datos. xml.etree.ElementTree.canonicalize() resuelve este problema al producir una forma canónica de XML adecuada para comparaciones estables, auditoría y preparación de contenido para firmas digitales.
En esta guía aprenderás qué es la canonicalización XML, cómo usar ElementTree.canonicalize, qué opciones controlan la salida y qué precauciones de seguridad y compatibilidad deben formar parte de una implementación real.
Qué significa XML canónico
Dos documentos XML pueden representar la misma estructura y aun así tener textos distintos. Los atributos pueden aparecer en otro orden, los prefijos de namespace pueden cambiar y los espacios pueden variar. Una comparación directa de cadenas los consideraría diferentes. La canonicalización transforma el XML en una representación predecible según reglas C14N.
Esto resulta útil en pruebas, cachés, deduplicación, control de versiones y sistemas que calculan hashes. También es una etapa habitual antes de firmar XML, porque cualquier diferencia textual posterior cambia el resumen criptográfico.
Ejemplo básico
from xml.etree.ElementTree import canonicalize
xml = '''
<pedido xmlns:x="https://ejemplo.com/item" id="42" estado="pagado">
<x:producto cantidad="2" codigo="A1" />
</pedido>
'''
resultado = canonicalize(xml_data=xml)
print(resultado)La función recibe XML mediante xml_data y devuelve texto Unicode. También puedes proporcionar un archivo de salida con out. El resultado normaliza detalles que no deberían afectar la equivalencia estructural.
Lectura desde archivo
from xml.etree.ElementTree import canonicalize
with open("entrada.xml", encoding="utf-8") as origen:
with open("canonico.xml", "w", encoding="utf-8") as destino:
canonicalize(from_file=origen, out=destino)Cuando el XML ya está almacenado en disco, from_file evita cargarlo manualmente. Abre el destino en modo texto con UTF-8 para mantener una salida consistente entre entornos.
Comentarios
Los comentarios normalmente no forman parte del significado de negocio. La opción with_comments decide si permanecen en el resultado.
canonico = canonicalize(
xml_data=xml,
with_comments=False,
)Eliminar comentarios suele ser adecuado para hashes y comparaciones de configuración. En auditorías o flujos documentales, pueden ser relevantes y conviene conservarlos.
Espacios y strip_text
La opción strip_text elimina espacios al inicio y al final del contenido textual. Úsala solo cuando esos espacios no tengan significado para el dominio.
canonico = canonicalize(
xml_data="<nombre> Ana </nombre>",
strip_text=True,
)En XML de configuración puede ayudar. En documentos editoriales, mensajes o formatos donde el espacio sea significativo, la eliminación puede alterar el contenido. Prueba siempre la política con muestras reales.
Reescritura de prefijos de namespace
Prefijos diferentes pueden apuntar al mismo namespace. La opción rewrite_prefixes sustituye los prefijos originales por nombres previsibles como n0 y n1.
canonico = canonicalize(
xml_data=xml,
rewrite_prefixes=True,
)Esto es útil cuando documentos equivalentes provienen de sistemas que eligen prefijos distintos. El URI del namespace sigue siendo el identificador real; el prefijo es solo una abreviatura.
Tags y atributos con nombres cualificados
Algunos formatos guardan nombres con prefijo dentro de valores de atributos o texto. En esos casos, el canonicalizador debe saber qué lugares contienen QNames. Usa qname_aware_tags y qname_aware_attrs.
canonico = canonicalize(
xml_data=xml,
rewrite_prefixes=True,
qname_aware_attrs={"tipo"},
qname_aware_tags={"expresion"},
)Sin esta configuración, un prefijo puede reescribirse en la estructura XML pero permanecer antiguo dentro del texto, generando una inconsistencia semántica.
Excluir campos volátiles
Las opciones exclude_tags y exclude_attrs permiten ignorar valores volátiles como timestamps, identificadores de rastreo o metadatos generados.
canonico = canonicalize(
xml_data=xml,
exclude_attrs={"generado_en", "request_id"},
exclude_tags={"debug"},
)Esto facilita pruebas de snapshot y deduplicación, pero requiere control. Excluir campos relevantes puede hacer que documentos diferentes parezcan iguales. Documenta la política y revísala como parte del protocolo.
Generar hashes estables
from hashlib import sha256
from xml.etree.ElementTree import canonicalize
def hash_xml(texto_xml: str) -> str:
canonico = canonicalize(
xml_data=texto_xml,
with_comments=False,
rewrite_prefixes=True,
)
return sha256(canonico.encode("utf-8")).hexdigest()El hash será estable solo si todos los participantes usan exactamente las mismas opciones. Guarda una versión de la política de canonicalización junto al digest o descríbela en el protocolo.
Comparar XML en pruebas
def xml_equivalente(a: str, b: str) -> bool:
opciones = {
"with_comments": False,
"rewrite_prefixes": True,
"strip_text": True,
}
return canonicalize(xml_data=a, **opciones) == canonicalize(xml_data=b, **opciones)Este método es más fiable que comparar XML bruto, pero no sustituye validación de esquema ni reglas de negocio. Dos documentos canónicos iguales son textualmente equivalentes bajo la política elegida; no son automáticamente válidos para una API concreta.
Canonicalización y firma digital
La canonicalización es solo una parte de una firma XML. No calcula la firma, no valida certificados y no protege contra todas las transformaciones posibles. Los sistemas de firma deben seguir el algoritmo, las transformaciones y el perfil exigidos por el protocolo. Para escenarios críticos, usa bibliotecas especializadas en XML Signature y prueba interoperabilidad con otras implementaciones.
No inventes un protocolo criptográfico combinando únicamente canonicalize() con una clave privada. Usa estándares establecidos y revisión especializada.
Seguridad al procesar XML
XML externo puede provocar consumo excesivo de recursos o explotar configuraciones inseguras del parser. Limita el tamaño de los documentos, valida el origen, aplica timeouts y considera bibliotecas reforzadas para contenido hostil. La canonicalización no sanea XML ni convierte una entrada insegura en confiable.
Amplía el contexto con ElementTree en Python, hashes seguros con hashlib, archivos con pathlib y la categoría Python Avanzado.
Buenas prácticas
Crea una única función central de canonicalización y evita combinaciones distintas dispersas por el proyecto. Prueba namespaces, atributos reordenados, comentarios, espacios, Unicode y campos excluidos. Registra la política y no la cambies silenciosamente cuando hashes históricos dependan de la representación anterior.
Persiste la salida como texto UTF-8. Para XML grande, impone límites de memoria y mide el coste de mantener cadenas completas. Mantén la canonicalización, la validación de esquema, la validación de negocio y la firma criptográfica como pasos separados y explícitos.
Referencias externas
Consulta la documentación oficial de ElementTree.canonicalize y la recomendación XML Canonicalization del W3C.
Conclusión
ElementTree.canonicalize transforma XML en una forma textual estable. Es útil para pruebas, hashes, auditoría e integración con sistemas que exigen representación canónica. La salida depende de las opciones elegidas, por lo que la configuración debe tratarse como una política formal. Con validación, límites de seguridad y pruebas de interoperabilidad, la canonicalización reduce diferencias falsas y hace más predecibles los pipelines XML.







