このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

XPathの構文を割と具体的にまとめておく

XPathの要点を少し抽象的にまとめておく」より:

以上のような「概念的にどんなものか」が分かれば、あとは構文を調べながらなんとか使えるでしょ。

というわけで、構文を調べたりしたのでまとめておきます。

XPathの正式な構文は、まーまー整合的なんですが、書くのがとても面倒。それで省略形を使うことになります。省略形は書くのにとても便利ですが、意味がわかりにくくなります。そんなわけで、XPathを使うときは、正式な記法と省略形のあいだの翻訳に慣れておくとよいようです。

XPathの正式な構文

XPathロケーションステップの正式な構文は、軸、ノードテスト、述語(predicate)という3つの部分からなり、次の形です。

  • 軸::ノードテスト[述語]

述語は条件式です。ノードテストも条件式ですが、頻繁に使う条件をノードテストにしたって感じですね。述語が不要なら、正式記法であってもブラケットごと省略可能です。

XPathの要点を少し抽象的にまとめておく」で述べたように、軸はコンテキストノードを受け取り、全順序付きノードセットを返す関数です。13種の軸があります。略記の方法も付記して軸の名前を列挙すると:

  1. ancestor
  2. ancestor-or-self (// で短く書ける)
  3. attribute (@ で短く書ける)
  4. child (完全に省略可能)
  5. descendant
  6. descendant-or-self
  7. following
  8. following-sibling
  9. namespace
  10. parent (.. で短く書ける)
  11. preceding
  12. preceding-sibling
  13. self (. で短く書ける)

ノードテストは名前か種類でノードをふるい分ける条件式です。名前のテストは単に名前そのもの(例えば、person)を書くだけ、ノードの種類は関数風に書きます(以下に列挙)。

  1. node() 軸に含まれる任意のノード
  2. text() 軸に含まれるテキストノード
  3. comment() 軸に含まれるコメントノード
  4. processing-instruction() 軸に含まれるPIノード
  5. processing-instruction(A) 軸に含まれるPIノードで、ターゲット名がAであるもの

XPathは式言語なので、演算子もあります。

優先順位 演算子
1 - (単項)
2 *, div, mod
3 +, -
4 <, <=, >, >=
5 =, !=
6 and
7 or
8 |

いちいち説明はしませんが、だいたい見たとおりの意味です。andとorは論理演算で、notは演算子ではなくて関数になっています。| はノードセットの合併(ユニオン)ですが、全順序も考慮した合併です。ノードセットの共通部分で全順序が一致しないときの結果順序はwell-definedじゃないでしょう(「XPathの要点を少し抽象的にまとめておく」に書きました)。

ロケーションステップの例をいくつか挙げておきます。

  • parent::node()
  • child::person[@userId='m-hiyama']
  • ancestor::members
  • descendant-or-self::node()[@mode]
  • attribute::userId
  • child::contact[position()=2]
  • descendant::processing-instruction('print')

XPathの省略形

以下の表で、X, YはXPath式、A, Qは名前、Nは番号(正整数)を表すプレースホルダーです。

省略形 正式な形
//X /descendant-or-self::node()/X
X//Y X/descendant-or-self::node()/Y
@A attribute::A
. self::node()
.. parent::node()
* (任意の名前)
Q:* (接頭辞Q付きの任意の名前)
$A 変数参照
[N] [position()=N]

attribute:: を @ と省略できることと、任意の名前(ワイルドカード)が * であることを組み合わせると、任意の名前の属性ノードは @* と書けます。

軸と条件式を区切る記号はコロン2つですが、名前空間接頭辞とローカル名を区切る記号はコロン1つなので注意が必要です。child:node, child::node, child::node() は全部違いますよ。

ロケーションパスの例を挙げます。

省略形 正式な形
person child::person
members/person child::member/child::person
person/@userId child::person/attribute::userId
. self::node()
../../@userId parent::node()/parent()::node()/attribute::userId
.//firstName self::node()/descendant-or-self::node()/child::firstName
../*/email[2] parent::node()/child::*/child::email[position() = 2]

略記は自由に組み合わせられるわけじゃなくて、.[2] とか //[2] はエラーになるようです。

この程度知っていれば、XMLツリー構造から必要な部分を抜き出す用途ならだいたい足りるんじゃないのかな。