uhyohyo.net

JavaScript初級者から中級者になろう

十章第一回 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/pbodyの子要素である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軸は、直接の子しか範囲にとらないということです。例えば、

body
  • 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での利用法などを解説していきます。