prefix無しの名前空間をもったノードをQNameで表現できないなんて馬鹿な話があったとしたら、DOM level 3 XPathは屑だと思った。まだ勧告されていないけれど。
全く信じられなかったので、読みたくないDocument Object Model (DOM) Level 3 XPath Specification(31 March 2003)を真面目に読んでみたのだけれど、もう驚きの一言。目が覚めたよ。
隅から隅まで読んだけれど、「QName」の一言も出てこない。prefixから名前空間を解決する際、Node.lookupNamspaceURIメソッドと似た方法で行うそうで(それくらいしか書かれていない)、この引数にprefixを与えると、そのノードのスコープ内でprefixで関連付けられた名前空間URIを得られる。このメソッドがprefix無しの名前空間URIを得るには、引数にnull値を与えるのだそうだ。しかしこのnullをXPathでどう表現するのか。そんなデータ型はノードテストのQNameで表現できない。それどころかXPathにもない。
そもそも「XPathNSResolver」が名前空間の解決をするというのに、このインターフェイスを持ったオブジェクトを作成する唯一のメソッド、 DOMDocument.createNSResolverの引数がNodeだというのだから、これはもう、名前空間接頭辞を事前に知らなければDOMでXPathを使えないということでは。悪あがき的に、node = document.createElementNS('http://www.w3.org/1999/xhtml’, 'xht:div'); を作成して、node.lookupNamespaceURI('xht')を試してみたのだけれど、駄目だった。少なくともMozillaの実装では。もし可能だったとしても、prefixと名前空間URIを関連付けるためにノードを作成するなんて馬鹿げた話もない。
で、私の結論としてはこれを解決するにはXPathNSResolverインターフェイスと同じメソッドを持ったオブジェクトを自作すること。JavaScriptで書くと例えばこんな風になる:
var nsresolver = {
lookupNamespaceURI:
function(prefix){
switch(prefix){
case 'xht':
return 'http://www.w3.org/1999/xhtml’;
default:
break;
}
}
};
これをXPathNSResolverとして渡せば、少なくともMozillaではQName xht:htmlを XHTMLのhtml要素として扱ってくれた。要するに引数のlookupNamesaceURIメソッドを呼んでいるだけであって、型のチェック等はしていないらしい。多分、「本物の」lookupNamespaceURIメソッドを書き換えても大丈夫だろうというかそっちの方が良い。以下実験したコード:
var de = document.documentElement;
var type = XPathResult.FIRST_ORDERED_NODE_TYPE;
var result = document.evaluate('/xht:html', de, nsresolver, type, null);
参照している「XMLメモ」では、XPathExpression インターフェイスを持ったオブジェクトを生成しているけれども、これはXPath式を使いまわす際に有効な方法で、一度しか使わないならXPathEvaluatorのevaluateメソッドを使うこともできるようだ。上の「document.evaluate()」がそれ。私の場合はそのような使い方のほうが多いと思う。
しかしこんな引数が四つも五つもあるような汚いインターフェイスは常用したくないから、当然別の何かを被せて使用することになる筈。
あーもう驚いた。
ちなみにDOM Level 2/3 標準技術をMSIEで使う(XML読み込みとXPath) @ レナ姫のWeb研究室経由。でもノードテストに主ノード型しか使えないXPathなんて駄目でしょう。絶対に何とかすべきです。
つづきというか修正
function _XPathNSResolver(nsmap, nodeResolver){
this._nsmap = nsmap;
this._resolver = nodeResolver?
nodeResolver.ownerDocument.createNSResolver(nodeResolver) : null;
}
_XPathNSResolver.prototype.lookupNamespaceURI = function(prefix){
var v;
if (v = this._nsmap[prefix])
return v;
if (v = this._resolver)
return v.lookupNamespaceURI(prefix);
return v;
};
var de = document.documentElement;
var type = XPathResult.FIRST_ORDERED_NODE_TYPE;
var nsresolver = new _XPathNSResolver({xht: "http://www.w3.org/1999/xhtml"}, de);
var result = document.evaluate('descendant::xht:*', de, nsresolver, type, null);
Node.lookupNamespaceURIメソッドはこのように名前空間URIを解決するらしい。実装がこの手順をきちんと守るのであれば、先ほどのようなものに書き換えても問題は起こらないはず。むしろ軽量化を図れるのではないか。逆に、evaluateメソッドでノードテストのQNameを評価する際に、毎度このプロセスが行われるのだとしたら、相当に遅くなるのではないか。
なんだかいい加減な用語連発でダラダラ書いてしまったけれども、きちんと纏めておきたいところ。