触ったことのあるすべてのSOAPサービス、消費したすべてのRSSフィード、操作したすべてのSVG — それらはすべてXMLです。 JavaScriptにはブラウザでXMLを解析する2つの組み込み方法と、Node.js向けの堅固なnpmライブラリがあります。 難しい部分は解析自体ではなく、結果のDOMをナビゲートすること、名前空間を処理すること、 そして最初は誰もが引っかかる落とし穴を避けることです。実際のパターンを見ていきましょう。

DOMParser — ブラウザでXMLを解析する

ブラウザ組み込みの DOMParser APIはXML文字列をDOMドキュメントに変換します。パーサーが厳格なXMLルールを適用するように、 MIMEタイプは'text/html'ではなく'application/xml'を使用してください:

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解析失敗: ' + parseError.textContent);
}

console.log(doc.documentElement.tagName); // library
parsererrorは必ず確認してください。例外を投げるJSON.parse()とは異なり、 DOMParserは解析失敗時に<parsererror>要素を含むドキュメントを返します — 例外は投げません。 エラーチェックをスキップすると、不正なドキュメントで静かに操作してしまい、後で混乱した結果を得ることになります。

DOMをナビゲートする — getElementsByTagName vs querySelector

解析されたドキュメントを取得したら、要素を見つけるための2つの主要な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ドキュメントをきれいなJavaScriptオブジェクトの配列にマップする方法を紹介します — 実際のXML APIレスポンスを消費する際に使うパターンです:

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

  if (doc.querySelector('parsererror')) {
    throw new Error('無効な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ドキュメントでnullを返します。 要素は実際にはsoap:Bodyだからです。正しい処理方法を見てみましょう:

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()を通して公開しています。 XMLに対してCSSセレクターより強力で、属性値、位置、テキストコンテンツ、祖先によってクエリできます。 完全な式構文についてはMDN XPathドキュメント を参照してください:

js
// 先ほどのライブラリ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がありません。2つの選択肢があります:組み込みの 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のリスト要素が時に1つのアイテムを、時に複数を含む場合、 fast-xml-parserは1つのアイテムにはオブジェクトを、複数には配列を返します。 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(`無効なXML: ${validation.err.msg}(行${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解析失敗:', 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オブジェクトを提供します。 ここで紹介したパターンは実世界のXML解析シナリオの95%をカバーします — SOAPレスポンス、RSSフィード、 設定ファイルなど。