XPath 完全ガイド:概要・歴史・基本概念から実務活用まで

XPath とは — 概要

XPath(XML Path Language)は、XML(およびXMLライクな文書、たとえばHTMLのDOM)内のノードを指定・検索するための宣言的な式言語です。要素、属性、テキストノード、コメント、処理命令など、ツリー構造を持つ文書に対してパス(経路)や条件を使って位置を特定し、値を取り出したりフィルタリングしたりできます。XPath は XSLT や XQuery と密接に関係し、単体でもデータ抽出や自動化で広く用いられています。

歴史とバージョンの変遷

  • XPath 1.0(1999年) — 初期版。基本的なパス式、軸(axis)、ノードテスト、述語(predicate)、よく使われる関数(string(), contains(), starts-with(), substring-before() など)を定義。
  • XPath 2.0(2007年) — シーケンス型や型システム、正規表現を使う関数(matches(), replace() など)を導入。より強力なデータ処理が可能に。
  • XPath 3.0(2014年) — 関数アイテムや高階関数など、関数型プログラミング的な機能を強化。
  • XPath 3.1(2017年) — マップ(連想配列)や配列を導入し、JSONデータとの連携や複雑なデータ操作が容易に。

ただし、ブラウザや多くのライブラリでは(特に組み込みのdocument.evaluateやSelenium等が)XPath 1.0 相当しかサポートしていないことが多い点に注意が必要です。

基本概念(ノードと軸)

XML/DOM 文書はツリー構造で、XPath はそのノード集合を扱います。主なノード型は以下の通りです:

  • 要素ノード(element)
  • 属性ノード(attribute)
  • テキストノード(text)
  • コメントノード(comment)
  • 処理命令(processing-instruction)
  • 名前空間ノード(namespace) — XPath 1.0 では扱いが限定的

軸(axis)とは、現在のノードからの関係を示すもので、よく使われる軸は次の通りです:

  • child::(子ノード)
  • descendant::(全ての子孫)
  • parent::(親)
  • ancestor::(祖先)
  • following-sibling::(以降の同階層の兄弟)
  • preceding-sibling::(以前の同階層の兄弟)
  • attribute::(属性)
  • self::(自身)
  • descendant-or-self::(自身と全子孫)

基本的な構文と例

XPath 式はいくつかの形式があります。代表的なものを例とともに示します。

  • 絶対パス: /html/body/div — ドキュメントルートからのパス。

  • 相対パス: ./div/span — 現在ノードからの相対指定。

  • 任意の階層(略記): //a — ドキュメント中のすべての a 要素(descendant-or-self の短縮)。

  • 属性選択: //input[@id='email'] — id 属性が email の input 要素を選択。

  • テキスト比較: //p[text()='サンプル'] — テキストが完全一致する p 要素(text() は直接の子テキストノード)。

  • 部分一致: //a[contains(@href, 'example.com')] — href に example.com を含む a 要素。

  • 位置指定: (//li)[1] または //ul/li[1] — 最初の li、あるいは各 ul の最初の li(文脈が異なる)。

  • 属性ノードを取得: //@class — ドキュメント中の全ての class 属性ノード。

例(HTMLからリンクのテキストを取得):

//div[@id='main']//a[@class='download']/text()

述語(Predicate)とフィルタリングの詳細

述語は中括弧ではなく角括弧で書き、ノード集合に条件を与えます。述語に与える式はノードごとに評価され、真偽や数値で選択を制御します。

  • 数値は位置を意味します([1] は最初のノード)。
  • boolean 値を返す式は真のノードのみを残します([contains(@class,'active')] など)。
  • position() と last() を使って相対位置を指定できます([position() <= 3], [last()])。
  • 述語内で軸やさらに深い述語を組み合わせると強力なフィルタが可能です://article[.//h2 and .//time[@datetime]]

関数群(よく使うもの)

XPath には文字列操作、数値変換、ノード集合に関する関数など多くの組込関数があります。代表的なもの:

  • string(), number(), boolean() — 型変換。
  • contains(string, substr), starts-with(string, prefix), substring-before(), substring-after() — 文字列操作(XPath 1.0)。
  • normalize-space() — 前後と連続空白を整理して比較時に便利。
  • count(nodeset) — ノード数を数える。
  • name(), local-name(), namespace-uri() — 名前に関するメタ情報。
  • matches(), replace(), tokenize() — 正規表現関連(XPath 2.0 以降)。
  • concat(), translate() — 文字列結合や文字置換。

名前空間(Namespaces)に関する注意点

XML 名前空間を含む文書では、XPath の名前(例えば要素名)は名前空間にバインドされたプレフィックスで評価されます。重要な点:

  • XPath 式での未接頭辞(プレフィックスなし)要素名は「空の名前空間」として扱われ、デフォルト名前空間の要素とは一致しない場合があります。
  • 多くのXPath実装(API)では、XPath を評価する際に名前空間プレフィックスをコード側でマッピング(登録)する必要があります。ブラウザの document.evaluate でも同様です。
  • 名前空間を回避するテクニックとして local-name() や name() を使った比較(*[local-name()='tag'])がありますが、可読性やパフォーマンスに注意が必要です。

XPath の用途(実務での利用例)

  • XSLT や XQuery 内でのデータ選択/変換。
  • スクレイピングや画面自動操作(Selenium 等で要素を特定)。ブラウザの開発者ツールから XPath をコピーしてそのまま使うことがよくあります。
  • XML 設定ファイルや SOAP メッセージの解析。
  • テスト自動化での検証ポイント(特定の要素が存在するか、値が期待通りかなど)。
  • データマイグレーションやバッチ処理における部分抽出。

ブラウザとライブラリのサポート状況

実際の環境ではバージョン差に注意が必要です:

  • 多くのウェブブラウザ(Chrome、Firefox、Safari 等)の JavaScript API(document.evaluate)は XPath 1.0 相当をサポートします。XPath 2.0/3.x は標準ブラウザ環境では一般的にサポートされていません。
  • Selenium や Puppeteer などの自動化ツールでも XPath 1.0 スタイルが基本です。
  • XML 処理ライブラリ(Java の javax.xml.xpath、.NET の System.Xml.XPath、Python の lxml など)はそれぞれのエンジン(libxml2 や Saxon 等)によりサポートバージョンが異なります。高度な XPath 2.0/3.x 機能を使う場合は Saxon のような実装を選ぶ必要があります。

よくある間違い・落とし穴

  • // の乱用 — 文書全体を横断検索するためパフォーマンスが悪化することがある。可能ならより限定的なルートや属性で絞る。
  • テキスト比較の誤り — text() はノードの直下のテキストノードを指す。要素内の全テキストを比較したい場合は normalize-space() や string(.) を使う。
  • 名前空間の不一致 — 前述の通り、デフォルト名前空間を持つ XML に単純な unprefixed 名を使うとヒットしない。
  • 位置(index)の混乱 — XPath の位置は1始まり([1] が最初)であり、配列的な考え方とズレがある。
  • XPath インジェクション — 外部入力を直接文字列連結して XPath を作ると、インジェクション攻撃のリスクがある。ライブラリのバインディング機能や入力サニタイズを活用する。

パフォーマンスと最適化のポイント

  • 可能ならルートや親要素を特定してから相対パスで検索する(// を全体に使わない)。
  • 複数条件は述語内でまとめて評価する(例://div[@class='a' and contains(. ,'hoge') ])。
  • 頻繁に実行する処理では、先に count() などで存在チェックをしてから値取得を行う等の戦略が有効。
  • XPath エンジンにより最適化挙動が異なる。大規模XMLでは専用ライブラリやストリーミング(SAX/ StAX)を検討する。

実践例(よく使われるパターン)

いくつか実務で便利な式を示します。

  • あるクラスを含む要素://div[contains(concat(' ', normalize-space(@class), ' '), ' target-class ')] — スペース区切りクラス名に対する堅牢なチェック。
  • ラベルと入力を組み合わせて探す://label[text()='Email']/following::input[1]
  • 表のヘッダに基づき列を取得(header が動的)://table//th[.='価格']/ancestor::table//tr/td[count(ancestor::table//th[.='価格']/preceding-sibling::th)+1]
  • テキストの部分一致(前後空白を無視)://p[contains(normalize-space(.), 'キーワード')]

セキュリティ上の注意(XPath インジェクション)

SQL と同様に、ユーザ入力を直接 XPath に埋め込むと XPath インジェクションの危険があります。例えば検索語をエスケープせずに //user[login/text() = 'ユーザ入力'] のようにすると、悪意ある入力で式を変更される可能性があります。対策として:

  • ライブラリのパラメータバインディング機能を利用する。
  • ユーザ入力はきちんとエスケープ、または比較に文字列関数(translate等の適用)を使う。
  • 必要なら入力文字集合を制限し、正規表現で検査する。

実装・ツールとデバッグ方法

  • ブラウザ開発者ツール — Chrome/Firefox の Elements パネルでノードを右クリックし「Copy XPath」等が使える。document.evaluate をコンソールで試せる。
  • オンライン XPath テスター — 簡易的な式検証に便利。
  • ライブラリ — Python の lxml(libxml2 ベース)、Java の Saxon(XPath 2.x/3.x をフルサポートする実装)、.NET の XPath 実装など。
  • Selenium 等のテストフレームワーク — UI 要素の特定に頻繁に使用。

まとめ(いつ使うべきか)

XPath はツリー構造の文書から高度に指定してノードを抽出する強力な手段です。単純な CSS セレクタで済む場合は CSS の方が読みやすく高速なこともありますが、属性だけでなくノードの位置関係、テキスト内容、親子関係に基づく高度な条件指定が必要な場合は XPath が有利です。使用する際は、対象の環境がサポートする XPath バージョン(1.0/2.0/3.x)を確認し、名前空間・パフォーマンス・セキュリティに注意してください。

参考文献