Como comparar dois arquivos XML e ver o que mudou
A forma mais rápida de comparar dois arquivos XML é colar ambos em uma ferramenta de diff lado a lado, dar o mesmo formato e ler as linhas que ela destaca. A comparação é a parte fácil. O ruído é o que confunde as pessoas: atributos reordenados, espaços entre tags e prefixos de namespace podem fazer com que dois arquivos que significam a mesma coisa pareçam não ter nada em comum.
Este guia mostra como obter um diff XML limpo e confiável. Vamos ver por que dois documentos equivalentes se distanciam no papel, quais métodos vale a pena conhecer, e um exemplo prático que você pode seguir. Se você só quer a ferramenta, nossa página de comparação XML faz tudo isso no navegador.
Por que os arquivos XML são enganosamente difíceis de comparar
O XML tem uma gramática estrita (veja a especificação XML do W3C), mas dá aos autores muita liberdade sobre como dispor o texto. Dois documentos podem descrever exatamente os mesmos dados e ainda diferir byte a byte. Um diff de texto simples não entende nada disso, então marca tudo.
Aqui está o fato-chave a guardar: em XML, a ordem dos atributos de um elemento
não é significativa. O
XML Information Set
trata os atributos como um conjunto não ordenado. Então
<user id="7" role="admin"/> e
<user role="admin" id="7"/> carregam a mesma informação,
mesmo que um diff de linhas os pinte de vermelho e verde. A ordem dos elementos,
por outro lado, normalmente importa.
| O que você vê no diff | É uma mudança real? | O que fazer |
|---|---|---|
| Atributos em ordem diferente | Não, a ordem de atributos não é significativa | Canonicalize os dois lados |
| Indentação de 2 vs 4 espaços | Não | Formate os dois lados igual |
| Espaços entre elementos | Geralmente não | Formate, ou remova o espaço insignificante |
<br/> vs <br></br> | Não, mesmo elemento vazio | Canonicalize os dois lados |
| Um prefixo de namespace diferente para o mesmo URI | Não, prefixos são rótulos arbitrários | Compare por URI do namespace, não por prefixo |
| Elementos filhos em ordem diferente | Geralmente sim, a ordem dos elementos importa | Investigue, provavelmente é real |
Essa última linha é a que merece atenção. A ordem dos atributos é livre, mas a ordem dos elementos filhos faz parte do documento na maioria dos esquemas. Se você quer o detalhe de como um parser vê tudo isso, o MDN tem uma sólida referência sobre análise de XML com DOMParser.
Quatro formas de comparar XML e quando usar cada uma
Não existe um único método melhor. Depende de onde os arquivos estão e do que você quer descobrir. Veja como as opções comuns se comparam.
| Método | Melhor para | Esforço | Entende XML? |
|---|---|---|---|
| Inspeção visual | Arquivos pequenos, um ou dois elementos | Baixo | Não, você é o parser |
| Ferramenta de diff online | Verificações rápidas, colar de qualquer lugar | Baixo | Com formatação, sim |
Linha de comando (xmllint) | Arquivos em disco, scripting, forma canônica | Médio | Sim, com --c14n |
IDE ou git diff | Arquivos já em um repositório | Baixo se já comitado | Baseado em linhas por padrão |
Para a maioria das pessoas, uma ferramenta de navegador ganha em velocidade:
nada para instalar, e você pode colar um fragmento direto de um arquivo de
configuração ou de uma resposta SOAP. O problema é o ruído de formatação, que
tratamos a seguir. Se você vive no terminal, o
xmllint do libxml2
é a ferramenta a conhecer.
A comparação limpa mais rápida, passo a passo
Essa é a rotina que uso quando alguém me passa dois arquivos de configuração e pergunta "o que está diferente?". Leva cerca de quinze segundos.
- Abra a ferramenta de comparação XML.
- Cole o original à esquerda, a nova versão à direita.
- Clique em Formatar nos dois lados para que compartilhem a mesma indentação.
- Procure diferenças reais. Verde é adicionado, vermelho é removido, e um valor alterado aparece como um de cada.
- Ignore as linhas que são apenas reordenação de atributos ou espaços.
O passo três é quase todo o truque. Quando os dois documentos usam a mesma indentação, a única coisa que resta destacar é o que realmente mudou. Nosso motor de diff é construído sobre o diff-match-patch do Google, que compara linha por linha primeiro para se manter rápido mesmo em arquivos longos.
Um exemplo prático
Suponha que você esteja revisando uma mudança em uma configuração de serviço. Aqui está o antes:
<user id="7" role="editor">
<name>Ada Lovelace</name>
<active>true</active>
<seats>3</seats>
</user>
E aqui está o depois, como um colega entregou a você:
<user role="admin" id="7">
<name>Ada Lovelace</name>
<active>true</active>
<seats>5</seats>
<team>platform</team>
</user>
Jogue esses em um diff de linhas cru e a primeira linha parece alterada, porque
id e role trocaram de lugar. Formate ambos, compare por
significado, e a história real é curta:
| Nó | Antes | Depois | Mudança |
|---|---|---|---|
@role | editor | admin | Modificado |
seats | 3 | 5 | Modificado |
team | — | platform | Adicionado |
@id | 7 | 7 | Sem mudança (apenas movido) |
name | Ada Lovelace | Ada Lovelace | Sem mudança |
Três edições reais: uma promoção de papel, uma contagem de assentos e um novo
elemento de equipe. A troca de atributos era ruído. Essa promoção de
editor para admin é exatamente o tipo de coisa que você
quer pegar na revisão, e é fácil de perder quando está enterrada sob uma linha que
o diff marcou por engano.
XML canônico: a forma correta de ignorar o ruído
Formatar os dois lados resolve a indentação, mas existe um padrão feito exatamente para esse problema. O XML canônico, definido pelo W3C em Canonical XML 1.1, reescreve um documento em uma única forma normalizada: atributos ordenados, elementos vazios expandidos, espaços nas tags normalizados, e atributos padrão tornados explícitos. Dois documentos equivalentes produzem uma saída canônica idêntica. É o equivalente XML de ordenar as chaves do JSON.
xmllint --c14n old.xml > old.c14n.xml
xmllint --c14n new.xml > new.c14n.xml
diff old.c14n.xml new.c14n.xml
Agora o diff só reporta conteúdo que realmente mudou, porque os dois
arquivos foram normalizados da mesma forma. Se você só quer uma indentação legível
em vez da forma canônica estrita, xmllint --format file.xml faz a
formatação, que é o equivalente no terminal de clicar em Formatar no navegador.
Namespaces: a parte que confunde todo mundo
Os namespaces do XML permitem que dois documentos usem o mesmo vocabulário com
rótulos de prefixo diferentes. <ns1:user> vinculado a um URI e
<u:user> vinculado ao mesmo URI são o mesmo elemento;
o prefixo é apenas um apelido local. Um diff de texto vê ns1 contra
u e marca uma mudança que não é. A solução é comparar pelo URI do
namespace em vez do prefixo, que é precisamente o que a canonicalização faz. A
especificação
Namespaces in XML
é a referência se você precisar resolver uma discussão sobre isso.
Armadilhas comuns para observar
| Armadilha | Por que pega | Solução |
|---|---|---|
| Codificação de caracteres | Um arquivo UTF-8 e um UTF-16 podem ter o mesmo texto mas diferir byte a byte | Normalize a codificação; a declaração XML a indica |
| Referências a entidades | & e um & literal podem aparecer ambos para o mesmo caractere | Canonicalize, o que resolve as entidades de forma consistente |
| CDATA vs texto escapado | <![CDATA[a<b]]> e a<b são o mesmo conteúdo de texto | Compare o valor analisado, não os bytes crus |
| Espaços significativos | Dentro de xml:space="preserve", os espaços importam e não devem ser removidos | Não corte às cegas; respeite xml:space |
| Tags auto-fechadas | <x/> e <x></x> são idênticas | Canonicalize para que ambas sejam renderizadas igual |
Diff de texto vs diff estrutural
Tudo acima é um diff de texto: rápido, visual e perfeito para uma pessoa ler uma mudança. Um diff estrutural vai além e descreve a mudança em termos da árvore XML: este atributo mudou, aquele elemento filho foi inserido neste caminho. Você quer um diff estrutural quando um programa precisa aplicar a mudança ou quando a ordem dos elementos genuinamente não importa e você quer ignorá-la. Para a revisão do dia a dia, um diff de texto de dois documentos formatados basta.
Ferramentas relacionadas
O XML raramente é o único formato com que você lida. Se você está comparando payloads de API, comparar JSON aplica a mesma ideia ao JSON. Páginas marcadas são mais fáceis de ler na página de comparar HTML, e configurações de ambiente se alinham bem na ferramenta de comparar config.
Perguntas frequentes
- Comparar arquivos XML online os envia para algum servidor?
- No comparetext.org o diff roda no seu navegador. Os dois arquivos XML são comparados por JavaScript na sua própria máquina, então nada é enviado a um servidor a menos que você clique explicitamente em Salvar ou Compartilhar. Isso o torna seguro para arquivos de configuração, mensagens SOAP e outros dados que você não gostaria de colar em um site que envia tudo a cada tecla pressionada.
- Por que meus dois arquivos XML mostram cada linha como diferente?
- Quase sempre é formatação, não mudanças reais. Um arquivo está minificado ou indentado com tabulações, o outro com dois espaços, ou os atributos estão em uma ordem diferente. Clique em Formatar nos dois lados para que usem a mesma indentação. Depois disso o diff geralmente encolhe para o punhado de valores que realmente mudaram. Para uma normalização mais estrita, canonicalize primeiro os dois arquivos com xmllint --c14n.
- A ordem dos atributos importa ao comparar XML?
- Não. Em XML os atributos de um elemento são um conjunto não ordenado, então
<a x="1" y="2"/>e<a y="2" x="1"/>são equivalentes. Um diff de texto simples não sabe disso e marcará a reordenação como mudança. O XML canônico ordena os atributos em uma ordem fixa, então canonicalizar os dois lados antes de comparar faz o falso positivo desaparecer. A ordem dos elementos, por outro lado, geralmente é significativa. - Como comparo XML ignorando os prefixos de namespace?
- Os prefixos de namespace são rótulos locais para um URI de namespace, então
ns1:usereu:uservinculados ao mesmo URI são o mesmo elemento. Para comparar corretamente, normalize por URI em vez de por prefixo. A forma mais simples é canonicalizar os dois documentos com xmllint --c14n, que reescreve as vinculações de namespace de forma consistente, e então fazer diff dos resultados. Um diff de texto cru não consegue fazer isso sozinho. - Posso comparar arquivos XML grandes sem a página travar?
- Sim, até certo ponto. Um diff em modo de linha se mantém rápido em arquivos com milhares de linhas porque compara linhas inteiras primeiro em vez de cada caractere. Arquivos muito grandes (vários megabytes) são melhor tratados com uma ferramenta de linha de comando como xmllint ou git diff, que faz streaming dos dados. Para qualquer coisa que você consiga rolar confortavelmente em um navegador, um diff online é a opção mais rápida.
- Qual é a diferença entre um diff de texto e um diff estrutural de XML?
- Um diff de texto compara os arquivos linha por linha, da mesma forma que compararia duas redações. Um diff estrutural entende a árvore XML, então sabe que um atributo reordenado não é uma mudança e pode reportar um elemento inserido pelo seu caminho. Diffs de texto são mais rápidos e suficientes para a maioria das revisões depois que os dois lados estão formatados. Diffs estruturais importam quando um programa precisa aplicar a mudança ou quando você quer ignorar a ordem dos elementos.
Pronto para experimentar? Cole seus arquivos na ferramenta de comparação XML e veja o que mudou.