접해본 모든 SOAP 서비스, 소비한 모든 RSS 피드, 조작한 모든 SVG — 이것들은 모두 XML입니다. JavaScript에는 브라우저에서 XML을 파싱하는 두 가지 내장 방법이 있으며, Node.js를 위한 강력한 npm 라이브러리도 있습니다. 까다로운 부분은 파싱 자체가 아니라, 결과 DOM을 탐색하고, 네임스페이스를 처리하며, 처음 접할 때 모든 사람을 놀라게 하는 특이한 점들에 걸려 넘어지지 않는 것입니다. 실제 패턴들을 살펴보겠습니다.

DOMParser — 브라우저에서 XML 파싱

브라우저에 내장된 DOMParser API는 XML 문자열을 DOM 문서로 변환합니다. 파서가 엄격한 XML 규칙을 적용하도록 MIME 유형을 'application/xml'로 사용하세요 ('text/html'이 아닙니다):

js
const xmlString = `<?xml version="1.0" encoding="UTF-8"?>
<library>
  <book isbn="978-0-13-110362-7">
    <title>The C Programming Language</title>
    <authors>
      <author>Brian W. Kernighan</author>
      <author>Dennis M. Ritchie</author>
    </authors>
    <year>1988</year>
    <price currency="USD">45.99</price>
  </book>
  <book isbn="978-0-201-63361-0">
    <title>The Pragmatic Programmer</title>
    <authors>
      <author>Andrew Hunt</author>
      <author>David Thomas</author>
    </authors>
    <year>1999</year>
    <price currency="USD">52.00</price>
  </book>
</library>`;

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

// 먼저 파싱 오류 확인
const parseError = doc.querySelector('parsererror');
if (parseError) {
  throw new Error('XML parse failed: ' + parseError.textContent);
}

console.log(doc.documentElement.tagName); // library
항상 parsererror를 확인하세요. 예외를 던지는 JSON.parse()와 달리, DOMParser는 파싱이 실패하면 예외를 던지지 않고 <parsererror> 요소를 포함하는 문서를 반환합니다. 오류 확인을 건너뛰면 잘못된 문서에서 조용히 작업하게 되어 이후에 혼란스러운 결과를 얻게 됩니다.

DOM 탐색 — getElementsByTagName vs querySelector

파싱된 문서가 있으면 요소를 찾기 위한 두 가지 주요 API가 있습니다. 둘 다 작동하지만 서로 다른 장점이 있습니다:

js
// getElementsByTagName — 라이브 HTMLCollection 반환
const books = doc.getElementsByTagName('book');
console.log(books.length); // 2

// querySelector / querySelectorAll — CSS 선택자 구문, NodeList 반환
const firstTitle = doc.querySelector('title').textContent;
console.log(firstTitle); // The C Programming Language

// 모든 제목 가져오기
const titles = [...doc.querySelectorAll('title')].map(el => el.textContent);
console.log(titles);
// ['The C Programming Language', 'The Pragmatic Programmer']

// 속성 읽기
const firstBook = doc.querySelector('book');
const isbn = firstBook.getAttribute('isbn');
console.log(isbn); // 978-0-13-110362-7

// price 요소에서 currency 속성 읽기
const priceEl = firstBook.querySelector('price');
console.log(priceEl.textContent);           // 45.99
console.log(priceEl.getAttribute('currency')); // USD

타겟 조회에는 querySelector를 선호합니다 — CSS 선택자 구문이 익숙하고 간결합니다. 주어진 태그의 모든 요소가 필요하고 라이브 컬렉션을 원할 때는 getElementsByTagName을 사용하세요 (실제로는 스프레드된 NodeList가 보통 더 깔끔합니다).

구조화된 데이터 추출 — 실용적인 패턴

실제 XML API 응답을 소비할 때 사용하는 패턴 — XML 문서를 깔끔한 JavaScript 객체 배열로 매핑하는 방법입니다:

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

  if (doc.querySelector('parsererror')) {
    throw new Error('Invalid XML');
  }

  return [...doc.querySelectorAll('book')].map(book => ({
    isbn: book.getAttribute('isbn'),
    title: book.querySelector('title').textContent.trim(),
    authors: [...book.querySelectorAll('author')].map(a => a.textContent.trim()),
    year: parseInt(book.querySelector('year').textContent, 10),
    price: {
      amount: parseFloat(book.querySelector('price').textContent),
      currency: book.querySelector('price').getAttribute('currency')
    }
  }));
}

const books = parseLibraryXml(xmlString);
console.log(books[0].title);          // The C Programming Language
console.log(books[0].authors);        // ['Brian W. Kernighan', 'Dennis M. Ritchie']
console.log(books[0].price.amount);   // 45.99

네임스페이스가 있는 XML 처리

네임스페이스는 대부분의 개발자가 벽에 부딪히는 곳입니다. SOAP 응답, Atom 피드, SVG는 모두 XML 네임스페이스를 사용합니다 — 그리고 단순한 querySelector('body')는 요소가 실제로 soap:Body이기 때문에 SOAP 문서에서 null을 반환합니다. 올바르게 처리하는 방법은 다음과 같습니다:

js
const soapResponse = `<?xml version="1.0"?>
<soap:Envelope
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:m="http://www.example.com/orders">
  <soap:Header/>
  <soap:Body>
    <m:GetOrderResponse>
      <m:OrderId>ORD-5521</m:OrderId>
      <m:Status>Shipped</m:Status>
      <m:Total currency="EUR">289.50</m:Total>
    </m:GetOrderResponse>
  </soap:Body>
</soap:Envelope>`;

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

// 방법 1: getElementsByTagNameNS — 명시적 네임스페이스 URI
const SOAP_NS = 'http://schemas.xmlsoap.org/soap/envelope/';
const ORDER_NS = 'http://www.example.com/orders';

const body = doc.getElementsByTagNameNS(SOAP_NS, 'Body')[0];
const orderId = doc.getElementsByTagNameNS(ORDER_NS, 'OrderId')[0].textContent;
const status = doc.getElementsByTagNameNS(ORDER_NS, 'Status')[0].textContent;

console.log(orderId); // ORD-5521
console.log(status);  // Shipped

// 방법 2: 네임스페이스 리졸버를 사용한 XPath (더 유연)
function nsResolver(prefix) {
  const namespaces = {
    soap: 'http://schemas.xmlsoap.org/soap/envelope/',
    m: 'http://www.example.com/orders'
  };
  return namespaces[prefix] || null;
}

const xpathResult = doc.evaluate(
  '//m:OrderId',
  doc,
  nsResolver,
  XPathResult.STRING_TYPE,
  null
);
console.log(xpathResult.stringValue); // ORD-5521

evaluate()를 사용한 XPath 쿼리

XPath는 XML 문서를 위한 쿼리 언어입니다. 브라우저는 document.evaluate()를 통해 이를 제공합니다. CSS 선택자보다 XML에 더 강력하여 속성 값, 위치, 텍스트 콘텐츠, 계층 관계로 쿼리할 수 있습니다. 전체 표현식 구문은 MDN XPath 문서를 참조하세요:

js
// 앞서 사용한 library XML 문서 사용
function xpath(doc, expression, contextNode = doc) {
  const result = doc.evaluate(
    expression,
    contextNode,
    null,  // 네임스페이스 리졸버 — 네임스페이스 없는 XML은 null
    XPathResult.ANY_TYPE,
    null
  );
  return result;
}

// 모든 책 제목 가져오기
const titlesResult = xpath(doc, '//book/title');
const titles = [];
let node;
while ((node = titlesResult.iterateNext())) {
  titles.push(node.textContent);
}
console.log(titles);
// ['The C Programming Language', 'The Pragmatic Programmer']

// 특정 ISBN으로 책 가져오기
const bookResult = doc.evaluate(
  '//book[@isbn="978-0-13-110362-7"]/title',
  doc, null,
  XPathResult.STRING_TYPE,
  null
);
console.log(bookResult.stringValue); // The C Programming Language

// $50 이상 가격의 책 가져오기
const expensiveResult = xpath(doc, '//book[price > 50]/title');
let expensiveNode;
while ((expensiveNode = expensiveResult.iterateNext())) {
  console.log(expensiveNode.textContent); // The Pragmatic Programmer
}

Node.js — fast-xml-parser (최선의 선택)

Node.js에는 DOMParser가 없습니다. 두 가지 선택지가 있습니다: 내장 node:stream 기반 SAX 방식(번거로움)을 사용하거나, fast-xml-parser (대부분의 사용 사례에 적합한 선택)을 사용하는 것입니다. 빠르고, 의존성이 없으며, 일반 JavaScript 객체를 반환합니다:

bash
npm install fast-xml-parser
js
import { XMLParser } from 'fast-xml-parser';

const xmlString = `<?xml version="1.0"?>
<library>
  <book isbn="978-0-13-110362-7">
    <title>The C Programming Language</title>
    <year>1988</year>
    <price currency="USD">45.99</price>
  </book>
  <book isbn="978-0-201-63361-0">
    <title>The Pragmatic Programmer</title>
    <year>1999</year>
    <price currency="USD">52.00</price>
  </book>
</library>`;

const parser = new XMLParser({
  ignoreAttributes: false,     // XML 속성 포함
  attributeNamePrefix: '@_',   // 요소와 구분하기 위해 속성에 접두사 추가
  isArray: (tagName) => tagName === 'book'  // <book>을 항상 배열로 처리
});

const result = parser.parse(xmlString);
const books = result.library.book;

books.forEach(book => {
  console.log(book.title);       // The C Programming Language
  console.log(book['@_isbn']);   // 978-0-13-110362-7
  console.log(book.price['#text']);       // 45.99
  console.log(book.price['@_currency']); // USD
});
isArray 옵션이 매우 중요합니다. XML에 때로는 하나의 항목을, 때로는 여러 항목을 포함하는 목록 요소가 있는 경우, fast-xml-parser는 하나의 항목에는 객체를, 여러 항목에는 배열을 반환합니다. isArray 옵션은 명명된 태그에 대해 일관된 배열 동작을 강제합니다 — 반복될 수 있는 요소에는 항상 사용하세요.

Node.js에서 오류 처리

js
import { XMLParser, XMLValidator } from 'fast-xml-parser';

function parseXmlSafely(xmlString) {
  // 먼저 유효성 검사 — true 또는 오류 객체 반환
  const validation = XMLValidator.validate(xmlString);
  if (validation !== true) {
    throw new Error(`Invalid XML: ${validation.err.msg} at line ${validation.err.line}`);
  }

  const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' });
  return parser.parse(xmlString);
}

try {
  const data = parseXmlSafely(xmlString);
  console.log(data);
} catch (err) {
  console.error('XML parsing failed:', err.message);
}

관련 도구들

JavaScript 프로젝트에서 XML을 다룰 때: XML 포매터로 압축된 응답을 보기 좋게 출력하고, XML 유효성 검사기로 파싱 전 올바른 형식을 확인하고, XML XPath 테스터로 XPath 쿼리를 실험하고, XML to JSON으로 더 간단한 구조로 변환해보세요.

마무리

브라우저에서는 'application/xml'과 함께 DOMParser를 사용하는 것이 기본입니다 — parsererror를 확인하는 것을 잊지 마세요. 네임스페이스가 있는 XML에는 getElementsByTagNameNS나 네임스페이스 리졸버를 사용한 XPath를 사용하세요. Node.js에서는 fast-xml-parser가 DOM 오버헤드 없이 깔끔한 JavaScript 객체를 제공합니다. 여기의 패턴들은 SOAP 응답, RSS 피드, 설정 파일 등 실제 XML 파싱 시나리오의 95%를 커버합니다.