XPath 1.0 完全ガイド:仕組み・文法・実践的なコツと注意点

はじめに — XPath 1.0 の位置づけ

XPath 1.0 は W3C によって 1999 年に勧告された XML を横断的に参照・選択するための言語仕様です。XSLT 1.0 や XPointer、DOM を介した XPath API(例:ブラウザの document.evaluate)など多くの技術で基盤として採用されてきました。本稿では、XPath 1.0 の基本から実務で役立つ注意点までを深掘りして解説します。

基本概念と式の構造

XPath の基本は「ロケーションパス」と「式(expression)」です。ロケーションパスは XML ドキュメント内のノード集合を表現するための文法で、パス区切り(/)や省略記法(//、.、..、@)を使います。式は算術演算、比較、論理演算、関数呼び出しなどを組み合わせて評価結果(ノードセット、文字列、数値、真偽値)を返します。

  • 相対パスと絶対パス:/root/child はドキュメントルートからの絶対パス。child::name や ./name は相対パス。
  • 省略記法:// は descendant-or-self::node()/child::、@ は attribute:: の短縮。
  • 軸(axis):child、parent、descendant、ancestor、following-sibling、preceding-sibling、attribute、namespace、self、descendant-or-self、ancestor-or-self、following、preceding など。

データ型と型変換のルール

XPath 1.0 の基本的なデータ型は 4 種類です:node-set、string、number、boolean。重要なのは暗黙の型変換(キャスト)ルールで、これを知らないと意図しない評価結果になります。

  • node-set → string:node-set の最初のノード(ドキュメント順)の文字列値(string-value)を返します。
  • node-set → number:上の string に number() を適用したもの。つまり最初のノードの文字列値を数値に変換。
  • node-set → boolean:空でなければ true、空なら false。
  • 比較演算時の特殊ルール:node-set とスカラー(string/number/boolean)を比較する場合、node-set の各ノードの文字列値とスカラーを適切に変換した上で要素間比較を行い、いずれかが一致すれば true。

例:/people/person/name = 'Alice' は person/name が node-set であっても、いずれかの name の文字列値が 'Alice' なら true になります。これが Node-set 比較の肝です。

ロケーションパス、ノードテスト、述語(predicate)

ロケーションパスはステップの連結から成り、各ステップは軸、ノードテスト、述語(オプション)で構成されます。述語はフィルタで、position()、last()、count()、特定の条件式を使ってノードセットを絞り込みます。

  • 述語のインデックス:/a/b[1] は各 a に対して最初の b を返します。一方で (//b)[1] はドキュメント内の最初の b 1 つだけを返すという違いがあります。
  • position() と last():順序に依存するフィルタ。たとえば //item[position() > 3 and position() <= last()]。
  • ノードテスト:name、*(任意の要素)、node()(任意ノード)、text()、comment() など。

主要関数と演算子

XPath 1.0 には文字列操作、数値演算、集合操作、論理関数が備わっています。代表的なものを挙げます。

  • 文字列:string(), concat(), substring(), substring-before(), substring-after(), string-length(), normalize-space(), contains(), starts-with(), translate()
  • 数値:number(), sum(), floor(), ceiling(), round()
  • 論理:boolean(), not(), true(), false(), lang()
  • 集合・ノード:count(), name(), local-name(), namespace-uri(), id()

例:translate() を使った大文字小文字無視の比較(英字のみ)は便利です。normalize-space() は空白の正規化に使えます。

比較と評価のセマンティクス(留意点)

比較演算子(=, !=, <, <=, >, >=)は、オペランドの型により挙動が変わります。特に node-set が絡む比較は要注意です。

  • node-set = node-set:いずれかのノードの文字列値同士の組合せに一致があれば true。
  • node-set = string:node-set のいずれかのノードの文字列値が string と等しければ true。
  • node-set = number:node-set のいずれかのノードの文字列値を数値化したものと比較。
  • boolean に暗黙変換されるタイミング:if (node-set) のような文脈は存在しないが、boolean(node-set) を明示的に使うか、論理演算子で評価される。

名前空間とプレフィックスの扱い(よくある落とし穴)

XPath 1.0 の大きな混乱ポイントの一つが名前空間です。XPath 式内の未接頭辞(プレフィックスなし)の要素名は「名前空間なし(null namespace)」を意味します。つまり XML ドキュメントにデフォルト名前空間がある場合、単純な unprefixed な XPath ではその要素をマッチできません。

  • 対策:XPath を評価する側の API(例:document.evaluate、libxml2、Saxon 等)に対して、適切なプレフィックスと名前空間 URI のマッピング(namespace resolver)を登録し、XPath 内ではプレフィックスを使う。
  • ブラウザ注意点:document.evaluate を使う際、名前空間を扱うためにカスタムの NSResolver を渡す必要がある。

実装差異と互換性

XPath 1.0 自体は仕様が安定していますが、実装ごとに差異が見られます。代表的な差分には以下があります。

  • namespace 軸のサポートが不完全な実装がある(特に古いライブラリ、ブラウザ)。
  • ノードの順序や重複排除の実装は仕様に準拠しますが、API レベルで NodeList や XPathResult の返し方が異なる。
  • XPath 1.0 は型システムや正規表現を持たないため、複雑な文字列マッチングが必要な場合は XSLT 側の拡張関数やホスト言語(JavaScript、Python)側の処理に委ねる必要があります。

実務で役立つテクニックとベストプラクティス

実際の開発で役立つコツを列挙します。

  • 名前空間付きドキュメントには必ずプレフィックスを使う。デフォルト名前空間には注意。
  • 大きなノード集合を扱う場合はなるべく早期に述語で絞る(例://item[@type='x'] など)。
  • テキスト比較は normalize-space() と translate() を組み合わせて空白や大文字小文字の差を吸収する。
  • 位置ベースの選択([1])とフィルタ([position()=1])の違いを理解する。前者は各親ごとの最初の子を取る振る舞い。
  • 数値の比較や sum() を使う場合、空文字を数値変換すると NaN になる点に注意。isNaN 判定は XPath 1.0 では直接できないため、ホスト言語でのフォローが必要。

XPath 1.0 の限界と XPath 2.0 以降との違い

XPath 1.0 は当時のユースケースを満たす一方で、以下の限界があります。

  • 静的型やシーケンス型がない(全てが 4 種類の型に集約される)。
  • 正規表現がない(XPath 2.0 では matches(), replace() 等が導入)。
  • 日付や時間といった複雑型の組み込みがない。
  • カスタム関数や名前空間型の扱いが乏しい。

これらは XPath 2.0 / XQuery で大幅に改善されており、できるだけ新しい仕様やライブラリが利用可能ならそちらを検討すべきです。ただし多くの環境(古いライブラリやブラウザ互換性)では依然として XPath 1.0 が現役です。

サンプル集 — よく使う式とその意味

  • //book[author='Tanaka'] — document 中のすべての book 要素で author 子要素の文字列値が 'Tanaka' のもの。

  • /catalog/book[1] — catalog の直下にある book のうち、最初の book(catalog ごとに最初の book を返す)。

  • (//book)[1] — ドキュメント全体で最初に現れる book 1 つだけ。

  • //item[contains(normalize-space(.), '重要')] — テキストを正規化して「重要」を含む item。

  • //node()[name() = 'h1' or local-name() = 'h1'] — 名前空間を意識せずに h1 要素を探す(ただし完全ではない)。

まとめ

XPath 1.0 はシンプルながら強力で、XML をプログラムで扱う際の基盤として非常に有用です。型変換のセマンティクス、名前空間の取り扱い、述語と位置指定の違いなど、細かな仕様を理解しておくことでバグや誤解を大幅に減らせます。新しいプロジェクトや複雑なクエリが必要なら XPath 2.0 以降や XQuery の採用を検討してくださいが、互換性の観点から XPath 1.0 の知識は今も役立ちます。

参考文献

W3C Recommendation: XPath 1.0 (1999-11-16)

MDN Web Docs: XPath

W3C: Namespaces in XML 1.0