JS の XPath なんて書きましたけど、重大なバグがありまして、っていうかなんで気がつかなかったんだろう、えーそれは application/xhtml+xml なページ、すなわち XML として、名前空間をちゃんと扱うページではまともにセレクトできないんですよーははははー、例えばこのサイトとかね。

$X = function (exp, context) {
if (!context) context = document;
var resolver = function (prefix) {
var o = document.createNSResolver(context)(prefix);
return o ? o : (document.contentType == "text/html") ? "" : "http://www.w3.org/1999/xhtml";
}
var exp = document.createExpression(exp, resolver);
var result = exp.evaluate(context, XPathResult.ANY_TYPE, null);
switch (result.resultType) {
case XPathResult.STRING_TYPE : return result.stringValue;
case XPathResult.NUMBER_TYPE : return result.numberValue;
case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: {
result = exp.evaluate(context, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var ret = [];
for (var i = 0, len = result.snapshotLength; i < len ; i++) {
ret.push(result.snapshotItem(i));
}
return ret;
}
}
return null;
}
alert($X("//x:p")); // Array of p elements
alert($X("count(//node())")); // => node number
alert($X("count(//x:body) = 1")); //=> must be true

かなり強引に修正してみた。

XPath で要素を指定するとき x という prefix (上のコードの場合は別になんでもいいんだけど、普通は x とか xhtml とかいうのをつける) を必ずつけるようにしとく。使い勝手が悪くなったけど、仕方ない。prefix がないときは resolver をよんでくれないみたいだ。

そう、で、resolver なんだけど、実はただの関数だった。evaluate は prefix を見つけると、resolver に prefix を渡し、URI を返すように要求する。resolver は prefix に対応する URI を返す。null の場合はエラー, "" の場合は、名前空間が null のものとして扱われるみたい (要追試)。

つまり、上のコードの resolver がやってることは、とりあえず普通の場合のように NSResolver を作って投げてみて、ダメだったら contentType にあわせて名前空間を返してやるっていう、かなり強引な (二回目) 方法なわけです。誰かもっと美しくして!

使い勝手が悪くなったけど と書いたけど、XSLT で使うような XPath と同じになった。まぁ名前空間を考慮するとこういうことになるっていう名前空間マジックなんだけど、やっぱり面倒くさいよなぁ。

$X("count(//x:body) = 1")$X("count(//*[local-name() = 'body' and namespace-uri() = "http://www.w3.org/1999/xhtml"]) = 1") みたいに書きたくはないし、HTML なページと XHTML ページとで、同じ XPath を使おうとするとこんなもんになってしまうような気もする。

冷静に考えると x より h のほうがいいや。

  1. トップ
  2. xpath
  3. XPath, $X function, NSResolver
  1. トップ
  2. js
  3. XPath, $X function, NSResolver