uhyohyo.net

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

十章第二回 DOMでのXPathの利用

Javascript DOMでXPathを使う

いよいよ今回、JavaScript DOMでXPathを使う方法を紹介します。

ここで出てくるのが、documentのevaluateというメソッドです。

これは、5個の引数を渡してXPathを処理してもらい、結果をXPathResultというオブジェクトで返すものです。

具体的なサンプルを見てみましょう。

var result = document.evaluate('/html', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
          

これで、/htmlという単純なXPath文ですが、これの結果を取得できていることになります。

それでは引数の解説ですが、第一引数はXPath文です。これはいいですね。

つぎに、第二引数にはdocumentが渡されています。これは、ルートノードです。すなわち、(このノードも含めて)このノードより下で探索するということです。documentということは、木構造全部ということですね。基本はdocumentで事足りるでしょう。

次は第三引数で、これは今nullとなっています。これは名前空間解決器です。

これはどういうことかというと、実はXPathはXML(やXHTML)でも使用できます。その際に、名前空間(六章第二回)を考慮する必要が出てきます。

タグは<xhtml:html> のように名前空間接頭辞がついていることがありました。ところが、この接頭辞が名前空間の実体というわけではなく、実体はURI(http://www.w3.org/1999/xhtmlなど)なのです。

そこで、接頭辞を名前空間の実体に対応付ける仕組みが必要です。これが名前空間解決器なのです。名前空間があるXML文書の場合はこれが必要です。

それでは、この名前空間解決器が必要なときはどうやって作ればよいのでしょうか。実はそんなに難しくなく、そのためのメソッドがあります。それは、document.createNSResolverです。引数を1つとり、頂点ノードを渡します。すると、それ以下に出現する名前空間に対応した名前空間解決器が返り値として出てきます。すなわち、

document.createNSResolver(document.documentElement)

という感じです。頂点ノードとしてdocument.documentElementを指定すると、文書中の全ての名前空間に対応できます。

ともかく、今回はHTMLで名前空間はないので、nullで構いません。

さて、第四引数は結果のタイプです。今回は

XPathResult.FIRST_ORDERED_NODE_TYPE

です。これは明らかに定数で、当然他にもあるのですが、各定数の解説は後述とします。この定数は、見つかったノードが普通に取得できるというものです。

次に第五引数はnullとなっていますが、これは再利用器です。これは何かというと、XPathResultオブジェクトを再利用できるのです。どういうことかというと、通常document.evaluateで結果を得るときは新しいXPathResultが作られて結果として返されます。つまり、1回実行するごとに新しいXPathResultオブジェクトがどんどんできるのです。これは無駄だということで、この第五引数に使い終わったXPathResultオブジェクトを渡すと、そのオブジェクトを書き換えて新しいXPathResultオブジェクトを作らずにそれを返してくれます。特に何度もXPathを利用する場合はこれを活用するのがよいでしょう。

さて、結果のXPathResultからノードを得る方法ですが、これは結果タイプによって変わってきます。詳しくは後述しますが、今回の場合は result.singleNodeValue で該当するノードが取得できます。試してみましょう。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p>test</p>
    <script type="text/javascript">
      var result = document.evaluate('/html', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);

      console.log(result.singleNodeValue.tagName);
    </script>
  </body>
</html>
          

実行すると「HTML」というログが出ます。つまり、singleNodeValueがhtml要素になっていて、結果としてhtml要素が取得できたということです。

結果タイプ

それでは、結果タイプの解説をします。

これは定数で、XPathResultのプロパティとして利用できます。

まず基本的に、いくつか種類がありますが、それぞれについてORDEREDUNORDEREDがあります。ORDEREDは、ノードが文書順(最初から順番に並んでいる)になっています。対してUNORDEREDの場合、並び順は適当です。

ORDEREDの場合かならず文書順に探さないといけませんが、UNORDEREDの場合ブラウザが探しやすい順番で探せるので、UNORDEREDのほうが少し速い可能性があります。

ANY_UNORDERED_NODE_TYPE, FIRST_ORDERED_NODE_TYPE

これは、ノードを1つだけ取得します。というのも、前回解説したように当てはまるものが複数あった場合それら全てのノードの集まりがノードセットとしてヒットしたことになります(ちなみに、1つだけの場合でもノードが1つのノードセットということになります)。

そのうち最初の1つだけを取得します。UNORDEREDの場合探す順番が不定なので、複数あった場合どれが出てくるか分かりません。ORDEREDの場合、文書順で探して最初なので、必ず最初に見つかったノードとなります。

そして、結果の取得方法は、結果のsingleNodeValueプロパティにノードが入っています。

UNORDERED_NODE_SNAPSHOT_TYPE, ORDERED_NODE_SNAPSHOT_TYPE

複数の結果を全て扱える方法です。最初に全部集めてくる方法です。

結果のsnapshotLengthに、結果のノードの個数が入っています。そして、結果のノードには0から番号がついていて、snapshotItemというメソッドに数値を渡すことでその番号のノードが返ってきます。

すなわち、複数のノードを処理するのに次のようにできます。

//resultは結果のオブジェクト
  for(var i=0;i<result.snapshotLength;i++){
    var node = result.snapshotItem(i);
    //得られたnodeに対して処理をする
  }
            
UNORDERED_NODE_ITERATOR_TYPE, ORDERED_NODE_ITERATOR_TYPE

これも複数のノードを全て扱える方法です。前述のSNAPSHOTと違うのは、document.evaluateの時点でノードを集めてこないということです。

この方法では、結果のiterateNextメソッド(引数なし)を呼び出すことでその都度次のノードを探索して返してくれます。もう無い場合はnullになります。すなわち、次のようにして処理できます。

//resultは結果のオブジェクト
var node;
while(node = result.iterateNext()){
  //得られたnodeに対して処理をする
}

他にもまだありますが、それはここでは説明しません。あとで説明します。

さて、SNAPSHOT系とITERATOR系の違いですが、前者は最初に全部集めておいて処理し、後者は最初の時点では何もせず、必要となるたびに探して持ってくるというものでした。これには多少の違いがあります。

SNAPSHOT系はもう全部集めてきて結果が確定しているので、その後文書に変更があった場合も影響を受けず、新しく条件を満たすノードが追加されたとしてもそれを処理できません。

ではITERATOR系はできるかというと、実はそうでもありません。もっと厳しく、文書が変更されたら結果が変になるかもしれないということで、文書が変更されたらもうその結果は無効となってしまい、もともとのノードを処理することすらできなくなります。

サンプル

具体的なサンプルを用意してみました。単純なものですが、いろいろ自分で工夫するなどしてみましょう。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p id="test">test</p>
    <p id="test2">test2</p>
    <p id="test3">test3</p>
    <script type="text/javascript">
      var result = document.evaluate('/descendant::p', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);

      var node;
      while(node=result.iterateNext()){
        console.log(node.id);
      }
    </script>
  </body>
</html>

XPath文は、全てのp要素を取得するというものです。

そして、タイプはORERED_NODE_ITERATORなので、このようにwhile文で処理しています。今回は、得られたノードに対してとりあえずログでidを表示しています。

だから、「test」「test2」「test3」と表示されるはずです。