更新日時
2004年09月18日
文書ステータス
公開

DOMとXPathの連携

現在JavaScriptが使用可能な、W3C DOM準拠のXML/HTML処理系には以下の種類があります。

筆者が(パーソナルコンピュータとして)使用しているのは、今のところWindowsだけなので、最後のものは外して考えさせていただきます。また簡単のため、以下の内容はそれぞれ2003年10月時点での最新バージョンでのみ検証された内容とします。

さて、これらの中でXPathを使ったHTML/XMLへのアクセスが可能なのはNetscape / Mozilla 等Geckoベースのブラウザと、MSXMLのみです。(ただしMSXMLはXMLしか扱うことができません。勿論XHTMLをXMLとして処理することは可能ですが、レンダリングされたHTMLを操作することは出来ません)。ここではそれぞれのアプローチを簡単に紹介し、さらにIEやOperaも含めた相互運用性についても議論します。

GeckoベースのWebブラウザの場合

GeckoベースのWebブラウザが実装しているのはDOM3 XPathなので、以下はDOM3 XPathそのものの紹介でもあります(DOM3 XPathは2004年02月26にGroup Notes になっています)。いずれOperaやKHTMLが対応したとしたらほとんど同じ手順で利用できることでしょう。

基本的に、以下のように使います。

//xmlDoc == DOMDocument とします

var xpath  = "descendant::TITLE";       //評価するXPath
var context= xmlDoc.documentElement; //コンテクストノード
var type   = XPathResult.FIRST_ORDERED_NODE_TYPE; //取得する結果のタイプを指定

var resolver  = xmlDoc.createNSResolver(xmlDoc.documentElement); //ネームスペースを解決するためのオブジェクト
var expression= xmlDoc.createExpression(xpath, resolver); //XPath式を表現するオブジェクト

var result = expression.evaluate(context, type, null); //XPathを評価して結果を取得

var titleElm = result.singleNodeValue; //結果をDOMオブジェクトとして取り出す

……本心ではここで「メソッドの詳細は省略…」とやってしまいたいところですが、どうも本当に資料が足りていない感じなので、一通り全て解説することにします。

まずXPathの中に出てくる名前空間を解決するためのオブジェクトXPathNSResolverを取得する必要があります。次のようにDocument.createNSResolverメソッドを使って取得します。

var resolver = document.createNSResolver(nodeResolver);

nodeResolverは解決すべき名前空間を”知って”いるノードです。あらゆるノードはそのノードか祖先ノードで宣言された名前空間を”知って”いるので、XPathで対象にするノードと同じ名前空間を知っているノードなら何でも構いません。

XPathNSResolverが取得できたら、次に評価すべきXPath式を表現するオブジェクトXPathExpressionを取得します。XPathExpressionを取得するにはDocument.createExpressionメソッドを使います。

var xpathExpression = document.createExpression(expression, resolver);

expressionは評価したいXPathを表す文字列で、resolverは先程取得したXPathNSResolverです。

無事XPathExpressionが取得できたら、evaluateメソッドを使っていよいよXPathを評価します。

var xpathResult = xpathExpression.evaluate(contextNode, type, result);

引数について説明します。まずcontextNodeはXPathのコンテクストノードとなるノードです。そしてtypeには戻り値として”希望する”タイプを指定します。この指定に使う定数はXPathResultのクラスプロパティとして用意されています。指定によって戻り値の扱い方が変わるのですが、そのあたりはXPathResultの解説をするときに併せて解説します。
そして最後のresult、ここには結果を格納するためのXPathResultオブジェクトを指定する……のですが、nullを渡しておけば普通にメソッドの戻り値として結果が帰ってくるので、ここではあまり気にせず、とりあえずnullを与えておくことにします。

さて、評価の結果返ってくるのはXPathResultオブジェクトです。このオブジェクトには以下のようなメソッド・プロパティ(インスタンスメソッド・プロパティ)が用意されています。

多くのプロパティ・メソッドはXPathを評価した結果をどういう形で取り出すか、を指定するものです。つまりここでXPathの世界とDOMの世界との変換を行うわけです。このときになってevaluateメソッドで指定したtypeの値が重要になってきます。つまり、期待した結果の種類(=typeの値)に応じて、取り出し方(=使用するメソッド/プロパティ)が変わってくるのです。この対応関係を以下に解説します。

XPathResult.ORDERED_NODE_ITERATOR_TYPE
使用可能メソッド/プロパティiterateNext()

結果はノードの集合として表現されます。要素はドキュメント中の順序を保持しています。またこの集合にはもとのドキュメントに加えられた変更が即座に反映されます。各々の要素はiterateNextメソッドで取り出すことが可能です。

XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
使用可能メソッド/プロパティsnapshotItem(index), snapshotLength

結果はノードの集合として表現されます。要素はドキュメント中の順序を保持しています。しかし”スナップショット”の名が示すとおり、もとのドキュメントに加えられた変更は反映されません。各々の要素にはsnapshotItemメソッドとsnapshotLengthプロパティを使ってアクセスします。

XPathResult.UNORDERED_NODE_ITERATOR_TYPE
使用可能メソッド/プロパティiterateNext();

結果はノードの集合として表現されます。要素はドキュメント中の順序を保持しません。またこの集合にはもとのドキュメントに加えられた変更が即座に反映されます。各々の要素はiterateNextメソッドで取り出すことが可能です。

XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE
使用可能メソッド/プロパティsnapshotItem(index), snapshotLength

結果はノードの集合として表現されます。要素はドキュメント中の順序を保持せず、もとのドキュメントに加えられた変更は反映されません。各々の要素にはsnapshotItemメソッドとsnapshotLengthプロパティを使ってアクセスします。

上述した4つのタイプは下の表のような関係にあります。

順序あり順序なし使用可能なメソッド
変更の反映ありORDERED_NODE_ITERATOR_TYPEUNORDERED_NODE_ITERATOR_TYPEiterateNext()
反映なし ORDERED_NODE_SNAPSHOT_TYPEUNORDERED_NODE_SNAPSHOT_TYPEsnapshotItem(index), snapshotLength
XPathResult.FIRST_ORDERED_NODE_TYPE
使用可能メソッド/プロパティsingleNodeValue, snapshotItem(index), snapshotLength

順序が保持され変更の反映がないノードの集合で、snapshotItem(index), snapshotLengthを使って各要素が取り出せます。一見ORDERED_NODE_SNAPSHOT_TYPEと同じですが、このタイプの場合、singleNodeValueプロパティで単一のノードとして取り出すことが出来ます。複数の要素が含まれる場合、取り出されるのは一番最初のものです。要素数が0の場合はnullが返ります。

XPathResult.ANY_UNORDERED_NODE_TYPE
使用可能メソッド/プロパティsingleNodeValue, snapshotItem(index), snapshotLength

順序が保持されず、変更の反映がないノードの集合で、snapshotItem(index), snapshotLengthを使って各要素が取り出せて、singleNodeValueプロパティで単一のノードとして取り出すことが出来ます。ただし要素の順序がないので、複数の要素が含まれる場合に取り出される要素は不定です。要素数が0の場合はnullが返ります。

XPathResult.STRING_TYPE
使用可能メソッド/プロパティstringValue

結果を文字列として表現します。XPathを評価した結果がTextNodeだったらそのnodeValueが、Elementだったらその子TextNodeのnodeValueが、Attributeだったらvalueが返されるといった具合になります。

XPathResult.NUMBER_TYPE
使用可能メソッド/プロパティnumberValue

結果を数値として表現します。STRING_TYPEと同じように値を取得した後、それを数値に変換したものが返されるようです。

XPathResult.BOOLEAN_TYPE
使用可能メソッド/プロパティbooleanValue

結果を真偽値として表現します。結果が空集合や空文字列だったら偽で、他は真となるようです。

XPathResult.ANY_TYPE
使用可能メソッド/プロパティ--

この値をtypeとして指定すると、結果は自動的に適当な形式に決定され、使用できるメソッドは場合に応じて変わります。ただし結果がノード集合の場合はUNORDERED_NODE_ITERATOR_TYPEになります。

さて、これで一通りの解説が終わりました。最後にXPathExpressionオブジェクトを使わず、一気に結果を得るDocument.evaluateメソッドを紹介しておきます。

var xpathResult = document.evaluate(expression, contextNode, resolver, type, result);

第一引数のexpressionが文字列のXPath式です。他の引数は解説不要でしょう。

ここで解説したXPathを使ったノードツリーへのアクセスは、HTMLに対してもXMLに対しても有効です。ただし複数の名前空間が混在するXML文書を扱うときには厄介な問題が発生します。これはMSXMLでも同様なので、また後で解説しようと思います。

※普通のHTMLを扱う場合、要素名は全て大文字になるようです。何か納得いきませんが……。

MSXMLの場合

MSXMLでXPathを使うためには(当然のように)独自拡張のメソッドを使用します。使用するのはNodeインターフェイスに追加で実装されたselectNodes / selectNodeメソッドです。

基本的に、以下のように使用します。

//xmlDoc == XMLDOMDocument とします
xmlDoc.setProperty("SelectionLanguage", "XPath"); //使用する言語にXPathを指定
/*
xmlDoc.setProperty("SelectionNamespaces", "xmlns:prefix='uri'"); //必要に応じてネームスペースを指定
*/

var xpath  = "descendant::TITLE";       //評価するXPath
var context= xmlDoc.documentElement; //コンテクストノード
var titleElm = context.selectSingleNode(xpath); //結果をDOMオブジェクトとして取り出す

MSXMLのこの方法についてはWeb上にも数多くの解説があるようですが、Mozillaベースの方を詳細に解説した手前、このページでも解説しておきます。

まずはじめに、DOMDocumentに対して「XPathを使う」ことを明示してやります。これにはDOMDocument.setPropertyメソッドを使用します。

xmlDoc.setProperty("SelectionLanguage", "XPath");

この設定を行わないと、(MSXMLバージョン4より前では)選択言語としてXPathではなく"XSLPattern"が使用されます。この点についてはMSXMLのバージョン互換性を参照してください。

次に必要に応じてXPath中に現れるプリフィックスとネームスペースURIを結びつけるための文字列を設定します。やはりDOMDocument.setPropertyメソッドを使用することになります。

xmlDoc.setProperty("SelectionNamespaces", "xmlns:prefix1='uri1' xmlns:prefix2='uri2'");

引数として渡す文字列のフォーマットは、属性として記述する場合と同じ形です。はっきり言って非常に”ダサい”です。注意しなければいけないのは、ここで設定するプリフィックスは実際のXML文書中で設定されているものと全く無関係だと言うことです。詳しくは後述するつもりですが……とりあえず今すぐ実験したい場合はネームスペースが一切無いXMLファイルを使った方がよいかも知れません。

まあ何にせよ、これで準備完了です。XPathの評価に入りましょう。これは非常に簡単です。評価したいXPathのコンテクストノードに当たるノードの、selectNodes / selectSingleNodeメソッドを呼び出すだけです。

var nodeList = context.selectNodes(xpath);
var node = context.selectSingleNode(xpath);

引数xpathは評価したいXPathを表す文字列です。またselectNodesメソッドとselectSingleNodeメソッドの違いは戻り値だけです。selectNodesが(要素数が1や0の場合でも)常にノードリストを返すのに対し、selectSingleNodeの方は複数のノードが選択された場合でも先頭の一つのみを返します。ちなみにこれらメソッドは順序を保持し、かつドキュメントの側に加えられた変更は即座に反映されるので、DOM3 XPathの定数ではそれぞれORDERED_NODE_ITERATOR_TYPE, FIRST_ORDERED_NODE_TYPEに相当するでしょう。

MSXMLでXPathを使う方法は、基本的にはこれで全部です。非常に簡単で非常に便利です。……が、この方法はいささか便利すぎます。かのinnerHTMLと同じ種類の、危うい便利さです。あまりにも便利すぎてどれだけ汚いコードでも書けてしまうのです。

勿論、ちょっとしたコードを手早く書くのには最適といえます。しかしある程度の規模を持ったプログラムを書く場合には、もう少し用途を限定したインターフェイスを別に用意すべきでしょう。いずれこのページでも詳しく議論する予定です。

MSXMLのバージョン互換性

MSXMLにおいて、上で解説するXPathによるノード選択を利用するには、MSXML API Historyなどから推測するに、バージョン2.6以降が必要となるようです。対応するIEのバージョンとしてはIE6以降となります(参考)。

ではそれ以前のバージョンでは、上記の内容は一切無効なのかというと、実はそうでもありません。例えばselectNodes/selectSingleNodeメソッドはMSXMLのバージョン2.0から既に実装されています。ただ「XPathによる選択」が可能なのはバージョン2.6以降なのです。SelectionLanguageの解説でも少し触れましたが、それより前のバージョンのselectNodes/selectSingleNodeメソッドで使用できる選択言語は”XSL Pattern”という、XSLのWorking Draft段階において存在した選択言語です。この言語は、ルートからの位置を/で区切って指し示すなど、現在のXPathと一部共通する文法を持っています。つまり、ごく簡単なXPathならばそのまま使用することが出来るということです。

このあたりの変遷について、多少推測混じりになりますが時系列を追ってみますと、

  1. MSXML2.0には、(恐らく当時最新だった)”XSL Pattern”を使用して要素を選択する selectNodes/selectSingleNodeというメソッドが存在した。

  2. MSXML2.6で、selectNodes/selectSingleNodeは (多分そのころ正式勧告になった)XPath1.0に対応した。 しかし後方互換性を保つためデフォルトの動作は”XSL Pattern”のまま据え置かれ、 選択言語を切り替えるためのsetProperty メソッドと SelectionLanguage プロパティが用意された。

  3. MSXML4.0からは”XPath”が唯一の選択言語となり、 SelectionLanguageを設定する必要はなくなった。

根拠としたのはMSXMLのSDKや、以下のページなどです。
MSXML 3.0 Supports XPath 1.0, XSLT 1.0, XDR, and SAX2
内容は全然関係ないですが、Microsoftのページですからある程度信頼できるでしょう。

また先述したようにMSXML3.0以前ならば SelectionLanguageを指定しないことで2.5以前の動作が再現できます。 完全なチェックはしていませんが、IE6+MSXML3.0で 概ね仕様書通りの動作をすることを確かめました。 (ただしfirst-of-type()などは最初から実装されていないのか、動作しませんでした)

サイト内検索 : ×
123