접해본 모든 SOAP 서비스, 소비한 모든 RSS 피드, 조작한 모든 SVG — 이것들은 모두 XML입니다. JavaScript에는 브라우저에서 XML을 파싱하는 두 가지 내장 방법이 있으며, Node.js를 위한 강력한 npm 라이브러리도 있습니다. 까다로운 부분은 파싱 자체가 아니라, 결과 DOM을 탐색하고, 네임스페이스를 처리하며, 처음 접할 때 모든 사람을 놀라게 하는 특이한 점들에 걸려 넘어지지 않는 것입니다. 실제 패턴들을 살펴보겠습니다.
DOMParser — 브라우저에서 XML 파싱
브라우저에 내장된
DOMParser
API는 XML 문자열을 DOM 문서로 변환합니다. 파서가 엄격한 XML 규칙을 적용하도록 MIME 유형을
'application/xml'로 사용하세요 ('text/html'이 아닙니다):
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); // libraryJSON.parse()와 달리,
DOMParser는 파싱이 실패하면 예외를 던지지 않고 <parsererror> 요소를 포함하는
문서를 반환합니다. 오류 확인을 건너뛰면 잘못된 문서에서 조용히 작업하게 되어 이후에 혼란스러운 결과를 얻게 됩니다.DOM 탐색 — getElementsByTagName vs querySelector
파싱된 문서가 있으면 요소를 찾기 위한 두 가지 주요 API가 있습니다. 둘 다 작동하지만 서로 다른 장점이 있습니다:
// 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 객체 배열로 매핑하는 방법입니다:
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을 반환합니다. 올바르게 처리하는 방법은 다음과 같습니다:
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-5521evaluate()를 사용한 XPath 쿼리
XPath는 XML 문서를 위한 쿼리 언어입니다. 브라우저는 document.evaluate()를 통해 이를 제공합니다.
CSS 선택자보다 XML에 더 강력하여 속성 값, 위치, 텍스트 콘텐츠, 계층 관계로 쿼리할 수 있습니다.
전체 표현식 구문은 MDN XPath 문서를 참조하세요:
// 앞서 사용한 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 객체를 반환합니다:
npm install fast-xml-parserimport { 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
});fast-xml-parser는 하나의 항목에는 객체를, 여러 항목에는 배열을 반환합니다.
isArray 옵션은 명명된 태그에 대해 일관된 배열 동작을 강제합니다 — 반복될 수 있는 요소에는 항상 사용하세요.Node.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%를 커버합니다.