MozillaはXPath関係の機能をDOM3 XPathに則って実装しています。これを活用すれば、getElementsByTagName()
などのメソッドと条件分岐を駆使してノードを収集しなくても、複雑な条件でノードを検索することができます。
以下に一例を示しましょう。
こういう内容のメニューがあったとします。このうち、いくつかのアイテムはhidden="true"
となっているので、実際に表示されるのは、残りの強調した項目だけです。
この時、「メニューの最初や最後にあるmenuseparator」「二つ以上連続したmenuseparator」だけを効率よく非表示にする方法はないものでしょうか――という風なケースで役に立つのが、DOM3 XPathなのです。
DOM3 XPathの仕様はW3Cでワーキングドラフトや勧告候補にもなっていますが、まだ正式な勧告にはなっていません。そのため、細かい部分が変更される可能性もあります。最新の仕様はW3C DOMのページから見ることができます。2005年5月現在でこの仕様を実装しているのは、Mozilla(FirefoxやThunderbird、XULRunnerなども含む)のみです。
DOM3 XPathは、XPathの式で指定された条件を満たすノードを取得するためのインターフェースを提供します。使い方の解説はHawk's W3 Laboratoryの「DOMとXPathの連携」が詳しいので、そちらを参照してください。一つのXPath式で様々な条件を指定し、それを元にDOM3 XPathでノードを取得すれば、何度もループを使ったりしなくても、効率的にノードを処理することができます。
Hawk's W3 Laboratoryの「DOMとXPathの連携」の解説に従って、僕は以下のような関数を定義して使っています。この関数は、XPath式の文字列とコンテキストノードを渡すと、それを元にXPath式の表すノードを検索して、配列として返すというものです。
function getNodesFromXPath(aXPath, aContextNode)
{
const XULNS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
const XHTMLNS = 'http://www.w3.org/1999/xhtml';
const XLinkNS = 'http://www.w3.org/1999/xlink';
// 引数の型チェック。
if (aXPath) {
aXPath = String(aXPath);
}
else {
throw 'ERROR: blank XPath expression';
}
if (aContextNode) {
try {
if (!(gContextNode instanceof Node))
throw '';
}
catch(e) {
throw 'ERROR: invalid context node';
}
}
const xmlDoc = aContextNode ? aContextNode.ownerDocument : document ;
const context = aContextNode || xmlDoc.documentElement;
const type = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE;
const resolver = {
lookupNamespaceURI : function(aPrefix)
{
switch (aPrefix)
{
case 'xul':
return XULNS;
case 'html':
case 'xhtml':
return XHTMLNS;
case 'xlink':
return XLinkNS;
default:
return '';
}
}
};
try {
var expression = xmlDoc.createExpression(aXPath, resolver);
var result = expression.evaluate(context, type, null);
}
catch(e) {
return {
snapshotLength : 0,
snapshotItem : function()
{
return null;
}
};
}
return result;
}
または、以下のようにも書けます。
function getNodesFromXPath(aXPath, aContextNode)
{
const XULNS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
const XHTMLNS = 'http://www.w3.org/1999/xhtml';
const XLinkNS = 'http://www.w3.org/1999/xlink';
// 引数の型チェック。
if (aXPath) {
aXPath = String(aXPath);
}
else {
throw 'ERROR: blank XPath expression';
}
if (aContextNode) {
try {
if (!(gContextNode instanceof Node))
throw '';
}
catch(e) {
throw 'ERROR: invalid context node';
}
}
const xmlDoc = aContextNode ? aContextNode.ownerDocument : document ;
const context = aContextNode || xmlDoc.documentElement;
const type = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE;
const resolver = {
lookupNamespaceURI : function(aPrefix)
{
switch (aPrefix)
{
case 'xul':
return XULNS;
case 'html':
case 'xhtml':
return XHTMLNS;
case 'xlink':
return XLinkNS;
default:
return '';
}
}
};
try {
var result = xmlDoc.evaluate(aXPath, context, resolver, type, null);
}
catch(e) {
return {
snapshotLength : 0,
snapshotItem : function()
{
return null;
}
};
}
return result;
}
XPathNSResolverを得るのにXMLDocument.createNSResolver()
を使っていませんが、これは、名前空間と接頭辞の関係を保持しているノードによって、その対応が変わってしまう恐れがあるためです。
これはどういう事かというと、例えば「child::html:p」という式を渡した場合に、<html:html xmlns:html="http://www.w3.org/1999/xhtml"/>
という風な要素ノードを元にcreateNSResolverで生成したXPathNSResolverと、<html:original xmlns:html="http://piro.srakura.ne.jp/namespace"/>
という風な要素ノードを元にcreateNSResolverで生成したXPathNSResolverとでは、「html」という接頭辞に対して返す名前空間URIが変わってしまうということです。
これに対して、前述のコードの例のように、自分でXPathNSResolverに相当するオブジェクトを用意しておけば、このXPathNSResolverは、「xul」と書けば必ずXULの名前空間URIを、「html」を書けば必ずXHTMLの名前空間URIを返してくれます。これによって、安心して「決め打ち」でXPath式を記述することができます。
これを利用した具体例については、次のページあるいは他のTipsで紹介したいと思います。