2006-03-16 ========== XPath, $X function, NSResolver ------------------------------ Reference URI: http://lowreal.net/logs/2006/03/16/1 Written Time: 2006-03-16T23:06:04+09:00 Tags: xpath js [JS の XPath](my:2006/03/13/3) なんて書きましたけど、重大なバグがありまして、っていうかなんで気がつかなかったん だろう、えーそれは application/xhtml+xml なページ、すなわち XML (Extensible Markup Language) として、名前空間をちゃんと扱うページではまともにセレクトできないんですよーははは はー、例えばこのサイトとかね。 $X = function (exp, context) { if (!context) context = document; var resolv er = 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.stringV alue; case XPathResult.NUMBER_TYPE : return result.numberValue; cas e XPathResult.BOOLEAN_TYPE: return result.booleanValue; case XPathResult.U NORDERED_NODE_ITERATOR_TYPE: { result = exp.evaluate(context, XPathRes ult.ORDERED_NODE_SNAPSHOT_TYPE, null); var ret = []; for (v ar i = 0, len = result.snapshotLength; i < len ; i++) { ret.push(r esult.snapshotItem(i)); } return ret; } } retu rn null;}alert($X("//x:p")); // Array of p elementsalert($X("count(//node())")); // => node numberalert($X("count(//x:body) = 1")); //=> must be trueかなり強引に修正してみた。 XPath (XML Path language) で要素を指定するとき x という prefix (上のコードの場合は別になんでもいいんだけど、普通は x とか xhtml と かいうのをつける) を必ずつけるようにしとく。使い勝手が悪くなったけど、仕方ない。p refix がないときは resolver をよんでくれないみたいだ。 そう、で、resolver なんだけど、実はただの関数だった。evaluate は prefix を見つけると、resolver に prefix を渡し、URI を返すように要求する。res olver は prefix に対応する URI (Uniform Resource Identifer | Universal Resource Identifer) を返す。null の場合はエラー, "" の場合は、名前空間が null のものとして扱われるみたい (要追試)。 つまり、上のコードの resolver がやってることは、とりあえず普通の場合のように NSRe solver を作って投げてみて、ダメだったら contentType にあわせて名前空間を返してやる っていう、かなり強引な (二回目) 方法なわけです。誰かもっと美しくして! 使い勝手が悪くなったけど と書いたけど、XSLT (XSL Transformations) で使うような XPath と同じになった。まぁ名前空間を考慮するとこういうことになるって いう名前空間マジックなんだけど、やっぱり面倒くさいよなぁ。 $X("count(//x:body) = 1") を $X("count(//*[local-name() = 'body' and namespace-uri() = "http://www.w3.org/1999 /xhtml"]) = 1") みたいに書きたくはないし、HTML (Hyper Text Markup Language) なページと XHTML (Extensible Hyper Text Markup Language) ページとで、同じ XPath を使おうとするとこんなもんになってしまうような気もする。 冷静に考えると x より h のほうがいいや。