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には「現在のノード」という感じのものがあります。そのノードを基準に進んでいって、ノードを絞り込むという感じです。

具体例として、/html/body/pというのを例としてみましょう。まず、一番最初は、現在のノードはルートノード三章第五回)、すなわちdocumentということになっています。木構造は

document
 |
 └― html
    |
    ├―― head
    |
    └―― body
        |
        …
          

というようになっています。

XPathは/によって区切られていて、今回の場合順に「html」「body」「p」となっています。

これは、子ノードの要素名を指定するもので、それに当てはまるものが新しい現在のノードとなります。

つまり、ルートノード→html要素のノード→body要素のノード→p要素のノード という順に推移してきたことになります。最終的にp要素のノードになったので、このXPath文が表すのは、そのp要素のノードということになります。

ちなみに、body要素の中に複数のp要素がある場合どれが当てはまるかというと、全部です。このように、該当のノードが複数となることもあります。

例えば、/html/body/p/strongのようにすると、さっきのp要素の中からさらにstrongを探すことになりますが、そのときp要素が複数あった場合、それぞれのp要素の中からstrongが全て探されます。このようなノードの集まりをノードセットといいます。

さて、これで、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軸というものがあります。これは、今度は上方向です。つまり、自分の親、そのまた親、・・・というようにルートノード(document)まで探す軸です。また、parent軸というのもあります。これは、直接の親だけです。すなわち、この軸の範囲にあたるのは常に1個(ルートノードの場合は親がないので0個)となります。

さらに、横方向の軸もあります。ひとつはpreceding-sibling軸です。これは、兄弟ノードのうち自分より前のノードです。逆に、following-sibling軸という軸は、自分より後のノードを探します。

他には、self軸(自分自身のみ)という軸もあります。他に使いやすいのが、descendant-or-self軸(子孫と自分自身)やancestor-or-self(自分より上と自分自身)があります。ここで紹介した以外にもいくつかあります。

ノードテストの記法

さて、こんどはノードテストに着目します。今まで、ノードテストには要素名を書いてきました。実は、ここには他にも書けるものがあります。

まずは*です。これは、どんな名前でもよいというものです。例えば

/html/body/*

とすると、body要素の子の要素ノード全てが当てはまります。言い換えると、このXPath文の結果は、body要素の子要素を全て含むノードセットだということです。

今ここで要素ノードといったのは、要素ノード以外のものを取得するためのノードテストもあるからです。

ひとつが、text()です。これは、テキストノードを全て取得します。

/html/body/child::text()

とすると、body要素の子であるテキストノードを全て含んだノードセットが得られます。body要素に直接Webページの文章となるテキストは書けませんから、きっと改行などのテキストノードばかりになることでしょう。

今度は

/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要素を含めてそれ以下の要素全部のノードセットが結果ということになります。例えばp要素を全て取得したければ、/html/body//self::pとなることでしょう。pがself軸なのは、//によって列挙されたもののうち、それ自身からp要素を探すからです。もっとも、/descendant-or-self:pとしても同じなので、普通こんな書き方はしないことでしょう。

次回以降、XPathのさらに高機能な部分やJavaScript DOMでの利用法などを解説していきます。