레거시 엔터프라이즈 API를 사용하거나, RSS 피드를 소비하거나, .docx 파일을 열거나, SVG를 그려본 적이 있다면 XML을 사용한 것입니다 — 알지 못했더라도요. 대부분의 개발자가 인식하는 것보다 훨씬 더 많은 소프트웨어 세계가 XML로 돌아가고 있습니다. JSON보다 오래됐고, 더 장황하며, 더 복잡하지만 특정 맥락에서는 더 강력합니다. 이 글은 XML이 실제로 무엇인지, 어떻게 작동하는지, 그리고 여전히 사용하는 것이 의미 있는 경우에 대한 실용적인 안내서입니다.

XML은 eXtensible Markup Language(확장 가능한 마크업 언어)의 약자입니다. W3C는 1998년에 XML 1.0 명세를 발표했으며, 초기 웹 시대의 주요 데이터 교환 형식이 되었습니다. "확장 가능"이라는 부분이 핵심입니다: 고정된 태그 세트를 가진 HTML과 달리, XML은 필요한 모든 데이터 구조를 설명하기 위해 자신만의 태그를 정의할 수 있게 해줍니다.

XML의 생김새

다음은 전자상거래 API에서 제품을 나타내는 완전한 XML 문서입니다. 여기에는 자주 접하게 될 모든 핵심 문법 특징이 담겨 있습니다:

xml
<?xml version="1.0" encoding="UTF-8"?>
<product id="SKU-8821" inStock="true">
  <name>Wireless Noise-Cancelling Headphones</name>
  <brand>SoundCore</brand>
  <price currency="USD">149.99</price>
  <categories>
    <category>Electronics</category>
    <category>Audio</category>
    <category>Accessories</category>
  </categories>
  <specs>
    <spec name="battery">30 hours</spec>
    <spec name="connectivity">Bluetooth 5.2</spec>
    <spec name="weight">250g</spec>
  </specs>
  <description>
    Premium wireless headphones with active noise cancellation,
    30-hour battery life, and foldable design.
  </description>
</product>

핵심 부분을 분석해 보겠습니다. 첫 번째 줄은 XML 선언으로 — 파서에게 이것이 UTF-8로 인코딩된 XML 1.0임을 알려줍니다. <product> 태그는 루트 요소입니다 (모든 XML 문서는 정확히 하나의 루트 요소를 가집니다). 같은 태그의 idinStock 부분은 속성입니다. 여는 태그와 닫는 태그 사이의 모든 것은 자식 요소, 속성, 또는 텍스트 콘텐츠입니다.

요소 vs 속성 — 진정한 설계 결정

새로운 XML 작성자들이 걸려 넘어지는 것 중 하나: 같은 데이터를 요소로도, 속성으로도 표현할 수 있습니다. 다음 두 가지 모두 같은 데이터를 나타내는 유효한 XML입니다:

xml
<!-- 속성으로 표현 -->
<price currency="USD">149.99</price>

<!-- 자식 요소로 표현 -->
<price>
  <amount>149.99</amount>
  <currency>USD</currency>
</price>

일반적인 통념: 속성은 요소에 대한 메타데이터(식별자, 플래그, 단위)에 사용하고, 자식 요소는 실제 데이터 콘텐츠에 사용하세요 — 특히 해당 콘텐츠가 복잡해지거나, 반복되거나, 미래에 자체적인 속성이 필요할 수 있는 경우에. 속성은 일반 텍스트만 담을 수 있지만, 자식 요소는 모든 XML 구조를 담을 수 있습니다.

올바른 형식 vs 유효성 — 두 가지 다른 기준

XML에는 자주 혼동되는 두 가지 수준의 정확성이 있습니다:

  • 올바른 형식의 XML은 기본 문법 규칙을 따릅니다: 하나의 루트 요소, 모든 태그가 올바르게 닫힘, 속성 인용, 불법 문자 없음. 모든 XML 파서가 이를 확인할 수 있습니다.
  • 유효한 XML은 특정 스키마를 준수합니다 — DTD(문서 유형 정의) 또는 XML 스키마(XSD). 유효성 검사는 문서와 스키마 모두가 필요합니다. 올바른 형식의 XML이 특정 스키마에 대해 유효하지 않을 수 있습니다.
실무에서: 대부분의 엔터프라이즈 XML 통합은 단순한 올바른 형식이 아닌 유효성을 중요시합니다. SOAP 통합을 구축하거나 HL7 의료 기록을 교환하는 경우, 스키마는 어떤 요소가 필수인지, 어떤 유형을 담는지, 어떤 값이 허용되는지를 정확히 정의합니다. XML 유효성 검사기를 사용하여 올바른 형식을 빠르게 확인하세요.

XML이 등장하는 실제 사례들

  • SOAP 웹 서비스. 엔터프라이즈 소프트웨어의 구형 부분은 여전히 SOAP으로 운영됩니다 — 뱅킹 API, ERP 시스템, 결제 게이트웨이. 모든 SOAP 메시지는 Envelope, Header, Body를 가진 잘 정의된 XML 문서입니다.
  • RSS와 Atom 피드. 모든 팟캐스트 피드, 뉴스 사이트 피드, YouTube 채널 구독은 XML 문서입니다. RSS 2.0과 Atom은 모두 2000년대 초부터 존재해온 XML 기반 형식입니다.
  • SVG 이미지. 확장 가능한 벡터 그래픽(SVG)은 XML입니다. 텍스트 편집기에서 .svg 파일을 열면 XML을 읽는 것입니다. 이것이 SVG를 CSS로 스타일링하고 JavaScript로 조작할 수 있는 이유입니다.
  • Office 문서. .docx, .xlsx, .pptx 파일은 XML 파일들을 담은 ZIP 아카이브입니다. Office Open XML은 Microsoft가 모든 현대 Office 문서 형식을 저장하는 방법입니다.
  • Android 레이아웃. Android UI 레이아웃은 XML 파일로 정의됩니다. Android 개발을 해본 적이 있다면 XML을 많이 작성했을 것입니다.
  • Maven과 pom.xml. Java의 Maven 빌드 시스템은 프로젝트 의존성과 빌드 설정을 정의하기 위해 pom.xml 파일을 사용합니다 — 모든 Java 개발자에게 익숙합니다.

JavaScript에서 XML 파싱 — DOMParser

브라우저에서는 내장 DOMParser API를 사용하여 XML 문자열을 파싱할 수 있습니다. HTML 파싱과 같은 방식으로 작동합니다:

js
const xmlString = `<?xml version="1.0"?>
<product id="SKU-8821">
  <name>Wireless Headphones</name>
  <price currency="USD">149.99</price>
  <categories>
    <category>Electronics</category>
    <category>Audio</category>
  </categories>
</product>`;

const parser = new DOMParser();
const doc = parser.parseFromString(xmlString, 'application/xml');

// 파싱 오류 확인
const parseError = doc.querySelector('parsererror');
if (parseError) {
  console.error('XML parse error:', parseError.textContent);
} else {
  const name = doc.querySelector('name').textContent;
  const currency = doc.querySelector('price').getAttribute('currency');
  const categories = [...doc.querySelectorAll('category')].map(el => el.textContent);

  console.log(name);       // Wireless Headphones
  console.log(currency);   // USD
  console.log(categories); // ['Electronics', 'Audio']
}

Python에서 XML 파싱 — ElementTree

Python 표준 라이브러리에는 xml.etree.ElementTree가 포함되어 있습니다 — 기본 XML 파싱에는 서드파티 패키지가 필요 없습니다:

python
import xml.etree.ElementTree as ET

xml_string = """<?xml version="1.0"?>
<product id="SKU-8821">
  <name>Wireless Headphones</name>
  <price currency="USD">149.99</price>
  <categories>
    <category>Electronics</category>
    <category>Audio</category>
  </categories>
</product>"""

root = ET.fromstring(xml_string)

name = root.find('name').text
currency = root.find('price').get('currency')
categories = [el.text for el in root.findall('categories/category')]

print(name)        # Wireless Headphones
print(currency)    # USD
print(categories)  # ['Electronics', 'Audio']

XML 작성자들이 빠지는 함정들

  • 엔티티 인코딩. XML 텍스트와 속성 값에서 다섯 개의 문자는 이스케이프해야 합니다: &&amp;, <&lt;, >&gt;, "&quot;, '&apos;. URL에서 앰퍼샌드를 이스케이프하는 것을 잊으면 전체 문서가 깨집니다.
  • 네임스페이스. 네임스페이스가 있는 XML<soap:Envelope xmlns:soap="...">처럼 보입니다. 네임스페이스 인식 없이 쿼리하면 요소를 찾지 못합니다. 이것이 가장 흔한 XML 파싱 버그 중 하나입니다.
  • 공백 민감성. XML에서 요소 사이의 공백은 기술적으로 의미가 있습니다(HTML과 달리). 대부분의 파서는 이를 합리적으로 처리하지만, 문서를 비교할 때 의외의 결과를 낳을 수 있습니다.
  • 문자 인코딩. XML 선언은 실제 파일 인코딩과 일치해야 합니다. ISO-8859-1로 선언된 UTF-8 파일은 비 ASCII 문자에 대해 잘못 파싱됩니다.
  • 기본 배열 유형 없음. XML에는 배열이 없습니다. 요소를 반복함으로써 목록을 표현하므로, 파서는 JavaScript 배열이 아닌 NodeList를 반환합니다 — 항상 Array.from()이나 스프레드로 변환하세요.

알아두면 좋은 XML 도구들

XML을 자주 다루시나요? 이 도구들이 시간을 절약해 줄 것입니다: XML 포매터로 압축된 XML 응답을 보기 좋게 출력하고, XML 유효성 검사기로 올바른 형식을 확인하고, XML to JSON으로 더 쉬운 처리를 위해 XML 응답을 JSON으로 변환하고, XML XPath로 문서에 대해 XPath 쿼리를 테스트해보세요.

마무리

XML은 JSON에 비해 장황하고 복잡하지만, 레거시의 유물이 아닙니다 — SVG, Office 문서, RSS 피드, SOAP 서비스, Android UI의 기반입니다. 요소/속성의 구분, 올바른 형식과 유효한 XML의 차이, 엔티티 인코딩과 네임스페이스에 관한 흔한 함정을 이해하면 XML이 작업에서 나타날 때마다 도움이 될 것입니다. 그리고 반드시 나타납니다.