두 XML 파일을 비교하고 변경 사항을 확인하는 방법

두 XML 파일을 비교하는 가장 빠른 방법은 둘 다 나란히 보여주는 diff 도구에 붙여넣고, 같은 방식으로 포맷한 뒤 강조 표시된 줄을 읽는 것입니다. 비교 자체는 쉬운 부분입니다. 사람들을 헷갈리게 하는 것은 노이즈입니다. 재배열된 속성, 태그 사이의 공백, namespace 접두사 때문에 같은 의미를 가진 두 파일이 전혀 공통점이 없어 보일 수 있습니다.

이 가이드는 깨끗하고 신뢰할 수 있는 XML diff를 얻는 방법을 설명합니다. 동등한 두 문서가 문서상으로 왜 벌어지는지, 알아둘 만한 방법들, 그리고 따라 할 수 있는 실제 예제를 살펴봅니다. 도구만 원한다면 XML 비교 페이지가 이 모든 것을 브라우저에서 처리합니다.

XML 파일이 비교하기 의외로 어려운 이유

XML에는 엄격한 문법이 있지만(W3C XML 명세 참조), 작성자에게 텍스트를 어떻게 배치할지에 대한 많은 자유를 줍니다. 두 문서가 정확히 같은 데이터를 기술하면서도 바이트 단위로 다를 수 있습니다. 일반 텍스트 diff는 그 점을 이해하지 못하므로 모든 것을 표시합니다.

기억해야 할 핵심 사실이 있습니다. XML에서 요소의 속성 순서는 의미가 없습니다. XML Information Set은 속성을 순서 없는 집합으로 취급합니다. 그래서 <user id="7" role="admin"/><user role="admin" id="7"/>는 같은 정보를 담고 있지만, 줄 단위 diff는 이들을 빨강과 초록으로 칠합니다. 반면 요소의 순서는 보통 의미가 있습니다.

변경처럼 보이지만 대개 그렇지 않은 것
diff에서 보이는 것실제 변경인가?해야 할 일
속성 순서가 다름아니요, 속성 순서는 의미가 없음양쪽을 정규화
2칸 vs 4칸 들여쓰기아니요양쪽을 같은 방식으로 포맷
요소 사이의 공백보통 아니요포맷하거나 의미 없는 공백 제거
<br/> vs <br></br>아니요, 같은 빈 요소양쪽을 정규화
같은 URI에 대한 다른 namespace 접두사아니요, 접두사는 임의의 레이블접두사가 아닌 namespace URI로 비교
자식 요소가 다른 순서보통 예, 요소 순서는 중요함조사 필요, 실제일 가능성이 높음

마지막 줄이 주의해야 할 항목입니다. 속성 순서는 자유롭지만 자식 요소의 순서는 대부분의 스키마에서 문서의 일부입니다. 파서가 이 모든 것을 어떻게 보는지에 대한 자세한 내용은, MDN에 DOMParser로 XML 파싱하기에 대한 훌륭한 참고 자료가 있습니다.

XML을 비교하는 네 가지 방법과 각각의 사용 시점

단 하나의 최선의 방법은 없습니다. 파일이 어디에 있는지, 무엇을 알아내려는지에 따라 다릅니다. 흔한 옵션들이 어떻게 비교되는지 살펴봅니다.

방법적합한 경우노력XML을 이해하는가?
눈으로 확인아주 작은 파일, 한두 요소낮음아니요, 당신이 파서
온라인 diff 도구빠른 확인, 어디서든 붙여넣기낮음포맷하면 예
명령줄(xmllint)디스크의 파일, 스크립팅, 정규형중간예, --c14n으로
IDE 또는 git diff이미 저장소에 있는 파일커밋되어 있으면 낮음기본은 줄 기반

대부분의 사람에게 브라우저 도구가 속도 면에서 유리합니다. 설치할 것이 없고 설정 파일이나 SOAP 응답에서 바로 조각을 붙여넣을 수 있습니다. 문제는 포맷 노이즈이며, 다음에서 다룹니다. 터미널에서 작업한다면 libxml2xmllint가 알아둘 도구입니다.

가장 빠른 깨끗한 비교, 단계별

누군가 설정 파일 두 개를 건네며 "뭐가 다른데?"라고 물을 때 제가 사용하는 루틴입니다. 약 15초가 걸립니다.

  1. XML 비교 도구를 엽니다.
  2. 원본을 왼쪽에, 새 버전을 오른쪽에 붙여넣습니다.
  3. 양쪽에서 포맷을 클릭해 같은 들여쓰기를 공유하게 합니다.
  4. 실제 차이를 찾습니다. 초록은 추가, 빨강은 제거이며, 변경된 값은 각 색으로 하나씩 표시됩니다.
  5. 속성 재배열이나 공백뿐인 줄은 무시합니다.

3단계가 요령의 대부분입니다. 두 문서가 같은 들여쓰기를 사용하면 강조 표시되는 것은 실제로 바뀐 것뿐입니다. 저희 diff 엔진은 Google의 diff-match-patch를 기반으로 하며, 먼저 줄 단위로 비교하므로 긴 파일에서도 빠르게 유지됩니다.

실제 예제

서비스 설정의 변경을 검토하고 있다고 가정합시다. 변경 전:

<user id="7" role="editor">
  <name>Ada Lovelace</name>
  <active>true</active>
  <seats>3</seats>
</user>

그리고 팀원이 건네준 변경 후:

<user role="admin" id="7">
  <name>Ada Lovelace</name>
  <active>true</active>
  <seats>5</seats>
  <team>platform</team>
</user>

이것을 원시 줄 단위 diff에 넣으면 idrole이 자리를 바꿨기 때문에 맨 첫 줄이 변경된 것처럼 보입니다. 둘 다 포맷하고 의미로 비교하면 실제 이야기는 짧습니다:

실제로 변경된 것
노드변경 전변경 후변경
@roleeditoradmin수정됨
seats35수정됨
teamplatform추가됨
@id77변경 없음 (이동만 함)
nameAda LovelaceAda Lovelace변경 없음

실제 편집은 세 가지입니다: 역할 승격, 좌석 수, 새 team 요소. 속성 교환은 노이즈였습니다. editor에서 admin으로의 승격은 검토에서 잡아내고 싶은 종류의 변경이며, diff가 잘못 표시한 줄 아래 묻혀 있으면 놓치기 쉽습니다.

정규화 XML: 노이즈를 무시하는 올바른 방법

양쪽을 포맷하면 들여쓰기는 처리되지만, 바로 이 문제를 위해 만들어진 표준이 있습니다. W3C가 Canonical XML 1.1에서 정의한 정규화 XML은 문서를 단일 정규화 형식으로 다시 씁니다. 속성은 정렬되고, 빈 요소는 확장되며, 태그의 공백은 정규화되고, 기본 속성은 명시됩니다. 동등한 두 문서는 동일한 정규화 출력을 생성합니다. 이는 JSON 키를 정렬하는 것의 XML 버전입니다.

xmllint --c14n old.xml > old.c14n.xml
xmllint --c14n new.xml > new.c14n.xml
diff old.c14n.xml new.c14n.xml

이제 diff는 진정으로 바뀐 내용만 보고합니다. 두 파일이 같은 방식으로 정규화되었기 때문입니다. 엄격한 정규형 대신 읽기 좋은 들여쓰기만 원한다면 xmllint --format file.xml이 보기 좋게 포맷하며, 이는 브라우저에서 포맷을 클릭하는 것의 터미널 버전입니다.

Namespace: 모두를 헷갈리게 하는 부분

XML namespace는 두 문서가 다른 접두사 레이블로 같은 어휘를 사용하게 해줍니다. 한 URI에 바인딩된 <ns1:user>같은 URI에 바인딩된 <u:user>는 같은 요소입니다. 접두사는 그저 로컬 별명일 뿐입니다. 텍스트 diff는 ns1u를 보고 변경이 아닌 것을 변경으로 표시합니다. 해결책은 접두사가 아닌 namespace URI로 비교하는 것이며, 이는 정확히 정규화가 하는 일입니다. Namespaces in XML 명세는 이에 대한 논쟁을 정리해야 할 때의 참고 자료입니다.

주의해야 할 흔한 함정

함정왜 문제가 되는가해결
문자 인코딩UTF-8 파일과 UTF-16 파일은 같은 텍스트를 담을 수 있지만 바이트 단위로 다름인코딩을 정규화. XML 선언이 이를 명시함
엔티티 참조&amp;와 리터럴 &가 같은 문자에 대해 둘 다 나타날 수 있음정규화하면 엔티티가 일관되게 해석됨
CDATA vs 이스케이프된 텍스트<![CDATA[a<b]]>a&lt;b는 같은 텍스트 내용원시 바이트가 아닌 파싱된 값을 비교
의미 있는 공백xml:space="preserve" 안에서는 공백이 중요하며 제거하면 안 됨무턱대고 잘라내지 말고 xml:space를 존중
자체 닫힘 태그<x/><x></x>는 동일함정규화해 둘 다 같은 방식으로 렌더링되게 함

텍스트 diff vs 구조적 diff

위의 모든 것은 텍스트 diff입니다. 빠르고 시각적이며 변경을 읽는 사람에게 완벽합니다. 구조적 diff는 한 걸음 더 나아가 변경을 XML 트리 관점에서 기술합니다. 이 속성이 바뀌었고, 저 자식 요소가 이 경로에 삽입되었다는 식입니다. 프로그램이 변경을 적용해야 하거나 요소 순서가 정말로 중요하지 않아 무시하고 싶을 때 구조적 diff가 필요합니다. 일상적인 검토에는 포맷된 두 문서의 텍스트 diff면 충분합니다.

관련 도구

XML이 다루는 유일한 형식인 경우는 드뭅니다. API 페이로드를 비교한다면 JSON 비교가 같은 아이디어를 JSON에 적용합니다. 마크업된 페이지는 HTML 비교 페이지에서 읽기 쉽고, 환경 설정은 config 비교 도구에서 잘 정렬됩니다.

자주 묻는 질문

XML 파일을 온라인으로 비교하면 어딘가에 업로드되나요?
comparetext.org에서는 diff가 브라우저에서 실행됩니다. 두 XML 파일은 사용자 자신의 컴퓨터에서 JavaScript로 비교되므로, 저장 또는 공유를 명시적으로 클릭하지 않는 한 아무것도 서버로 전송되지 않습니다. 따라서 설정 파일, SOAP 메시지, 그리고 키 입력마다 업로드하는 사이트에 붙여넣고 싶지 않은 데이터에도 안전합니다.
두 XML 파일이 모든 줄을 다르게 표시하는 이유는 무엇인가요?
거의 항상 실제 변경이 아니라 포맷 문제입니다. 한 파일은 minified되었거나 탭으로 들여쓰기되어 있고, 다른 파일은 두 칸 공백을 쓰거나 속성이 다른 순서로 되어 있습니다. 양쪽에서 포맷을 클릭해 같은 들여쓰기를 사용하게 하세요. 그러면 diff는 보통 진정으로 바뀐 소수의 값으로 줄어듭니다. 더 엄격한 정규화를 원하면 먼저 두 파일을 xmllint --c14n으로 정규화하세요.
XML을 비교할 때 속성 순서가 중요한가요?
아니요. XML에서 요소의 속성은 순서 없는 집합이므로 <a x="1" y="2"/><a y="2" x="1"/>는 동등합니다. 일반 텍스트 diff는 이를 모르고 재배열을 변경으로 표시합니다. 정규화 XML은 속성을 고정 순서로 정렬하므로, 비교 전에 양쪽을 정규화하면 거짓 양성이 사라집니다. 반면 요소의 순서는 보통 의미가 있습니다.
namespace 접두사를 무시하고 XML을 비교하려면 어떻게 하나요?
namespace 접두사는 namespace URI의 로컬 레이블이므로, 같은 URI에 바인딩된 ns1:useru:user는 같은 요소입니다. 올바르게 비교하려면 접두사가 아닌 URI로 정규화하세요. 가장 간단한 방법은 두 문서를 xmllint --c14n으로 정규화하는 것입니다. 이는 namespace 바인딩을 일관되게 다시 쓰며, 그런 다음 결과를 diff합니다. 원시 텍스트 diff는 혼자서 이를 할 수 없습니다.
페이지가 멈추지 않고 큰 XML 파일을 비교할 수 있나요?
예, 어느 정도까지는요. 줄 모드 diff는 각 문자가 아니라 먼저 전체 줄을 비교하므로 수천 줄짜리 파일에서도 빠르게 유지됩니다. 매우 큰 파일(수 메가바이트)은 데이터를 스트리밍하는 xmllint나 git diff 같은 명령줄 도구로 처리하는 것이 더 좋습니다. 브라우저에서 편하게 스크롤할 수 있는 것이라면 온라인 diff가 더 빠른 선택입니다.
XML의 텍스트 diff와 구조적 diff의 차이는 무엇인가요?
텍스트 diff는 두 에세이를 비교하는 것과 같은 방식으로 파일을 줄 단위로 비교합니다. 구조적 diff는 XML 트리를 이해하므로 재배열된 속성이 변경이 아님을 알고, 삽입된 요소를 그 경로로 보고할 수 있습니다. 텍스트 diff는 더 빠르고 양쪽이 포맷되면 대부분의 검토에 충분합니다. 구조적 diff는 프로그램이 변경을 적용해야 하거나 요소 순서를 무시하고 싶을 때 중요합니다.

사용해 볼 준비가 되셨나요? XML 비교 도구에 파일을 붙여넣고 무엇이 바뀌었는지 확인하세요.