宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。
以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
拡張機能勉強会の時に焚き付けられた、Text Shadowのコード(textshadow.js)を教材にして拡張機能開発のノウハウを解説していくシリーズ。
W3CのDOMでは、要素ノード(およびそのリスト)を得る方法として以下の方法がある。
getElementById(aName)
getElementsByTagName(aTagName)
childNodes
本当はネームスペースを指定して検索する物もあるんだけど、ここでは割愛。
これら以外に、W3C DOMではないがこういうのもある。
getElementsByClassName(aClassName)
getElementsByAttribute(aName, aValue)
ただ、探したい要素ノードの条件が複雑な時は、これらを使って取得したノードリストをループ回して条件判断しないといけないし、そもそもこれらでは要素ノード以外は取得できない。そこで最近のJS界隈でよく使われているのが、XPathだ。
XPathとは、/html/descendant::li[@class="navigation"]
という風な「式」でXMLノードを特定する技術だ。XPathの書き方を新たに憶える必要はあるが、これを使えば、複雑な条件に合致するノードのリストを一発で取得することができる。コードが簡潔になるのはいいことだし、FirefoxでもSafari 3でもOperaでも、普通にDOMとJavaScriptでごりごりやるのに比べて20倍以上高速に動作するという話もある。
XUL Tipsのページに書いてるけど、FirefoxではDOM3 XPathで提案されているXPath関係の機能が利用できる。詳しい解説はHawk's W3 Laboratoryの「DOMとXPathの連携」(サイトが消えてるので、インターネットアーカイブからどうぞ)を見て欲しい。リンク先では「Gecko用」と書かれてるけど、現在ではOperaとSafari 3でも利用できるようになっている。
面倒な説明を省くと、使い方はこんな感じ。
var nodes = document.evaluate('/descendant::*[@class="navigation"]',
document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0, maxi = nodes.snapshotLength; i < maxi; i++)
{
this.processNavItems(nodes.snapshotItem(i));
}
通常のノードリストと違って、全体の長さはlength
プロパティではなくsnapshotLength
プロパティで取得し、各項目は添字やitem()
メソッドではなくsnapshotItem()
メソッドで取得する。
evaluate()
メソッドの引数には、XPath式、その式を評価する時のコンテキストノード、ネームスペースリゾルバー、結果の型、最後にnull
を渡す。
第3引数のネームスペースリゾルバーというのは何かというと、要するにlookupNamespaceURI()
というメソッドを持っていて、そこに何か文字列を投げるとそれに対応する名前空間URIの文字列を返す、という機能を持ったオブジェクトのこと。JavaScriptで実装するならこうなる。
var resolver = {
lookupNamespaceURI : function(aPrefix)
{
switch (aPrefix)
{
case 'xul':
return 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
case 'html':
case 'xhtml':
return 'http://www.w3.org/1999/xhtml';
case 'xlink':
return 'http://www.w3.org/1999/xlink';
default:
return '';
}
}
}
なんでこういう物が必要かというと、/descendant::html:div[@class="foobar"]
という風な式を評価する時に、名前空間接頭辞(この例ならhtml
)に対応する名前空間URIを調べる(解決する)必要があるからだ。とはいえ、こういう名前空間を使った式を使うのでもなければ(属性値とか要素の順番とかくらいしか条件に使わないなら)、ネームスペースリゾルバーを使う必要はない。ということで、さっきの例でも第3引数にはnull
を渡している。
第4引数の「型」。これは、やたらたくさんあってどう使い分けたらいいか大変困るのだけれども、僕の場合はXPathResult.ORDERED_NODE_SNAPSHOT_TYPE
を使うことがとても多い。前述の例を見てもわかるとおり、結果の形がノードリストに似ている。なので、表題の通り「getElementsByなんちゃら の代わりにXPathを使う」には、これが一番使い方が似ていることになる。
第5引数は、本当はXPathResult型……つまりevaluate()
の返り値と同じ型のオブジェクトを渡すとそれを再利用するらしいんだけど、試してみても特に何も起こらなかった(ぉぃ)ので、普通はnullでいい。
最後におまけとして、DOM3 XPathを使って具体的にどういう事ができるかという例をいくつか挙げてみる。
document.evaluate('preceding-sibling::*', node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotLength
でその要素より前にある要素の個数を取得できるので、つまり、この数値が「親要素から見た時のその要素の順番」である。document.evaluate('ancestor::*', node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotLength
で「祖先要素の個数」が分かる。これを比較して「よりネストの深い方」という風な条件判断ができる。基本的には、今までgetElementsByTagName()
とかの方法でノードを取得して条件分岐でさらにフィルタリングしてたものが、もっと高速に簡単になる、ノードの取得とフィルタリングを一度に行えると考えるといい。XPath式で表現可能なフィルタリング条件に限って言えば、ぶっちゃけ、これさえあればDOM2 TraversalのTreeWalkerとかは存在価値がだいぶ減ってしまうと言えよう。
注意点も書いておく。
descendant::*[local-name()="DIV" or local-name()="div"]
ancestor::*
と指定しても、「親のli要素」「その親のul要素」「その親のdiv要素」……という順番でなく、「ルートのhtml要素」「その子のbody要素」「その子のdiv要素」……という順番で結果が帰ってくる。Geckoでは、これはevaluate()
メソッドに渡す型の指定が何であっても変わらない。というわけで、上記の点に気をつけてXPathをばしばし活用しちゃってください。
> XMLとしてパースされた場合と両方に対応させたいなら、こんな風に書かないといけない。descendant::*[local-name()="DIV" or local-name()="div"]
厳密に XHTML 名前空間を持つ div 要素にマッチさせたいなら namespace-uri() = "http://w3.org/..." って条件も要るけどもね。XPath 2.0 ではもっとスマートにできたような気もするが。
function getXHtmlElemCond(localName)
{
return "local-name()='" + localName.toUpperCase() ' or local-name()='" + localName + "' and namespace-uri() = '" + XHTML_NAMESPACE_URI + "'"
}
document.evaluate('decendant::*[' + getXHtmlElemCond('div') + ']', ...
とでもやるとか? よくわかりません><
Geckoではtext/htmlだと名前空間URIが無いということになってたような希ガス
Greasemonkeyで、AutoPagerize対応のスクリプトを自作する時の注意点を2つメモ。 自分はひよっこですが、これからGreasemonkeyスクリプト書いてみようかなという人の助けに少しでもなれば嬉しいです。 継ぎ足されたページに適用する方法 AutoPagerizeで継ぎ足された部分に
の末尾に2020年11月30日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2007-09-09_xpath.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。
writeback message: Ready to post a comment.