Category xpath.

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

前々からいちいちあのクソながったらしい evaluate を書くのがだるかったのでちゃんと関数はさむようにした。

大きなバグがあります。詳細はXPath, $X function, NSResolverに書きました。以下のコードは非推奨です。

$X = function (exp, context) {
if (!context) context = document;
var result = document.evaluate(exp, context, null, 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 = document.evaluate(exp, context, null, 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("//p")); // Array of p elements
alert($X("count(//node())")); // => node number
alert($X("count(//body) = 1")); //=> must be true
// Firefox が嫌いになる GM スクリプト
if ($X("contains(string(/), 'Firefox')")) {
alert("I LOVE FIREFOX!");
}

これでコピペ地獄から開放される。

  1. トップ
  2. xpath
  3. JS の XPath
  1. トップ
  2. js
  3. JS の XPath

ネームスペース宣言の集合は、式が現れるアトリビュートを持つエレメントのスコープに含まれるものと同じである。この集合には、XML ネームスペース勧告 (XML Namespaces Recommendation) [XML Names] が必要とする、暗黙的に示されたプレフィックス xml の宣言も含まれる。デフォルトのネームスペース (xmlns を用いて宣言されたもの) は、この集合の一部ではない。 とか書いてあったりする。ソースツリーのデフォルトネームスペース URI が null (特に名前空間を全く宣言していない場合とか) 以外の時は絶対にプリフィックス無しではマッチとかしない。

<foo>
<bar>baz</bar>
</foo>

この場合に string(/foo/bar) = 'baz' は true。

<foo xmlns="http://foo/">
<bar>baz</bar>
</foo>

この場合は string(/foo/bar) = 'baz' は false。/foo/bar は何も選択しない。例え XSLT 側のデフォルト名前空間が http://foo/ であっても何も選択されないxmlns:f="http://foo/" とかやって /f:foo/f:bar ってやらなきゃいけない。

で、困るっていうかよくわからんのはソースツリーのデフォルトネームスペースと結果ツリーのデフォルトネームスペースを同じにしたいときなんですよと。必然的に xmlns:f="http://foo/"xmlns="http://foo/" とか (順番も重要) やるわけですよ。exclude-result-prefixes="f" とかやるわけですよ。そうすると仕様書的に正しいかはよくわからないけど Sablotron の場合はどっちとも (xmlns, xmlns:f) 消えるんですよ (msxsl では大丈夫)。で、どうすんねんと哀さんと話していた次第(謎

xsl:namespace-alias とか利用すんのかなぁと思っていくつかソレっぽく書いてみたけどダメだった……なんかセオリー的なやり方ってないのかな。

  1. トップ
  2. web
  3. XSLT の XPath の
  1. トップ
  2. xml
  3. XSLT の XPath の
  1. トップ
  2. xslt
  3. XSLT の XPath の
  1. トップ
  2. xpath
  3. XSLT の XPath の