十章第一回 XPathとは
このページの最終更新日:
十章では、またDOMの話題に戻ってXPathを解説します。
XPathとは
XPathとは、木構造中のノードを指し示す表現のことです。ノードは1つでもいいし複数でも構いません。
具体的な例として、例えば「body要素中の最初のp要素」とか、「3番目のa要素」とかそういった指定が可能です。そういわれるとCSSのセレクタを思い浮かべるかもしれません。しかし、XPathはCSSセレクタよりも数段強力な表現力をもちます。
JavaScriptのDOMにおいては、このあるノードを表す表現を条件として使うことで、対応するノードを絞り込んで処理することができます。
これは、例えば「要素名」という条件に限ればgetElementsByTagNameでできていました。また、getElementByIdも条件によりノードを絞り込んでいるといえるでしょう。
似たようなことは以前TreeWalkerでもやりました。TreeWalkerでは、関数でノードを判定することで条件としていたのです。XPathを利用したノードの絞り込みは、その別方向からのアプローチということもできます。
XPathの書き方
さて、それではXPathの書き方を解説していきます。XPathには、独自の文法があります。
まず基本的な概念として、XPathには「現在のノード」という概念があります。XPathの構文により「現在のノード」が移動していきます。
具体例として、/html/body/p
というのを例としてみましょう。このXPathは/html
, /body
, /p
の3つから成っています。このように、スラッシュ(/
)に続けて要素名を書く構文は、子ノードへの移動を意味します
実は、一番最初は現在のノードはルートノード、すなわちdocumentということになっています。知っての通り、documentはDOMにおける木構造の頂点であり、その下にhtml要素が存在しています。つまり、最初の/html
でdocumentからその子ノードたるhtml要素に移動し、さらにその子のbody要素、その子のp要素に進みます。
したがって、/html/body/p
はbodyの子要素であるp要素を表すXPathだったということです。
ちなみに、body要素の子に複数のp要素がある場合どれが当てはまるかというと、全部です。このように、現在のノードが複数となることもあります。
例えば、/html/body/p/strong
のようにすると、さっきのp要素の中からさらにstrongを探すことになりますが、そのときp要素が複数あった場合、それぞれのp要素の中からstrongが全て探されます。
なお、XPath用語でノードの集まりをノードセットといいます。
軸
さて、XPathの構文として/要素名
というものを紹介しました。この書き方はXPathの構文のほんの一部です。実はこの形は省略形であり、一般には次のような形をしています。
軸 :: ノードテスト
これをロケーションステップといいます。つまり、例えば上の書き方において/body
の部分は軸と::
が省略されて、ノードテストの部分がbody
となっている形でした。XPathはこのように、ノードテストを/
で区切って並べて書くことができます。
軸というのは、ノードを探す範囲を示すものです。今までは、「body要素の子の中のp要素」とか「p要素の子の中のem要素」のように、ノードを探す範囲はその要素の子の中からでした。実は、これは「子」という範囲を表す軸が省略されていたからなのです。
その軸とは、child軸です。だから、先の例を省略せずに書くと
/child::html/child::body/child::p
のようになります。このように、軸を省略した場合、child軸とみなされます。
注意すべき点は、このchild軸は、直接の子しか範囲にとらないということです。例えば、
- p
- div
- p
- p
という木構造で/html/body/p
というXPath文の場合、bodyの直接の子であるp要素は当てはまる一方、div要素の子となっているp要素は当てはまらないのです。
さて、それでは他の軸も紹介していきます。まず紹介するのが、descendant軸です。これは子孫ノードを意味する軸で、直接の子ではなく、自分の下全てから探します。つまり、先の例だと、div要素の子のp要素も含めて3つ全てのp要素が当てはまることになります。これは、/html/body/descendant::p
というように書くわけですね。
さらに、ancestor軸という軸があります。これは、descendant軸とは逆に上方向を探す軸です。つまり、自分の親、そのまた親、・・・というようにルートノード(document)まで探します。また、parent軸というのもあります。これは、直接の親だけです。すなわち、この軸の範囲にあたるのは常に1個(ルートノードの場合は親がないので0個)となります。
上下の関係だけでなく、横方向の軸もあります。ひとつはpreceding-sibling軸です。これは、兄弟ノードのうち自分より前のノードが範囲となります。逆に、following-sibling軸という軸は、自分より後のノードを探します。
他には、self軸(自分自身のみ)という変わった軸もあります。他にも使いやすい軸として、descendant-or-self軸(子孫と自分自身)やancestor-or-self(自分より上と自分自身)があります。ここで紹介した以外にもいくつかあります。
ノードテストの記法
さて、こんどはノードテストに着目します。今まで、ノードテストには要素名を書いてきました。実は、ここには他にも書けるものがあります。
まずは*
です。これは、どんな名前でもよいというノードテストです。getElementsByTagNameと似た感じがしますね。例えば/html/body/*
というXPath文は、body要素の子である要素ノード全てが当てはまります。言い換えると、このXPath文の結果は、body要素の子要素を全て含むノードセットだということです。
ここで、全てのノードではなく全ての要素ノードである点には注意してください。つまり、*
は全ての要素ノードを意味するノードテストであり、テキストノードなどその他のノードは含みません。
もちろん、要素ノード以外のものを取得できるノードテストもあります。
そのひとつがtext()です。これは、テキストノード全てを意味するノードテストです。使用例は次の通りです。
/html/body/child::text()
とすると、body要素の子であるテキストノードを全て含んだノードセットが得られます。
今度は/html/body/descendant::text()
と書けば、body要素内の全てのテキストノードのノードセットとなり、Webページのテキストが全て集まるでしょう。
このように()
がついているノードテストは、何か関数のようですね。実際これは関数のようなもので、後々紹介しますが引数がある関数とかも出てきます。
他のノードテストとしてはcomment()(コメントノード)や、node()(全ての種類のノード)などがあります。
ここまでノードの種類と要素名くらいしかノードテストで判定していませんが、より詳細な判定方法については後で紹介します。
ロケーションステップの省略
次に、ロケーションステップ全体に関する省略記法を紹介します。ロケーションステップとは、/と/で挟まれた間全体で、軸::ノードテスト
の記法を持つものでした。実は、よく使うロケーションステップを省略して書ける記法が用意されています。ひとつは、.
です。これは、self::node()
の省略形で、self軸のノード全てという意味ですが、self軸は自分自身で、当然ひとつのノードにつきひとつしかありません。つまり、これは結局自分自身を表しているということになり、ノードセットは変わりません。だから、/html/body/././p
のように、間に挟んだりしても関係ありません。
次は..
です。これはparent::node()
の省略形です。parent軸は直接の親なので、self軸同様、ノードセット中のノード1つに対して最大ひとつしかありません。例えば、/html/body/..
とすると、bodyの親はhtml要素なので、これはhtml要素を表すということになります。
最後にもうひとつ紹介します。それは、//
のようにロケーションステップに何も書かないという記法です。このとき、このロケーションステップはdescendant-or-self::node()
の省略形になります。descendant-or-self軸は、自分自身とその子孫を範囲としていて、その範囲のノード全てです。例えば、/html/body//
とすると、body要素を含めてそれ以下の要素全部のノードセットが結果ということになります。これの利用例は、例えばこうです。
/html/body//self::p
これは「body要素中のp要素全て」という意味です。なぜなら、/html/body//
までの部分でbody要素の中にある全てのノードが列挙されており、その中(self軸)からp要素のノードを集めるからです。もっとも、これは/html/body/descendant-or-self:p
と同じ意味なのでわざわざ//
を使う必要がないのですが。
次回以降、XPathのさらに高機能な部分やDOMでの利用法などを解説していきます。