IT戦記 このページをアンテナに追加 RSSフィード Twitter

2007-11-12

JavaScript-XPath をリリースしました!さあ、あなたも XPath を使おう!(解説付き)

JavaScript-XPath とは

JavaScript-XPath は、 DOM 3 XPath を実装していないブラウザに対して、実用的な速度で動作する DOM 3 XPath のエンジンを追加します。

一言で乱暴に言ってしまえば、どのブラウザでも document.evaluate って関数XPath 使えるようになるよ!ってことです。

以下が公式サイトになります。

http://coderepos.org/share/wiki/JavaScript-XPath

DOM 3 XPath ってなんなの!?

めっちゃ簡単(で、ちょっとだけ適当)なDOM 3 XPath の説明をします><。

JavaScript でよく使う document.getElementByIddocument.getElementsByTagName って関数ありますよね?

DOM 3 XPath はそれのパワーアップ版の関数 document.evaluate のことです!

わーわー!

どのくらいパワーアップしてるかというと。。。

  • document.getElementById では id 属性でしか取得できないけど document.evaluate はどんな属性の値からも要素を取得できます!
  • document.getElementsByTagName では要素の名前(タグの名前)でしか取得できなかったけど、 tag が 't' から始まるやつ!とか div か p !とかできます!
  • 他にも、 type 属性が text の input 要素を含んでいる form 要素を取得!とかもできます!

すごいでしょ?

でも、この便利な関数って一部のブラウザでしか使えないのです。。そこで、 JavaScript-XPath を使うとすべてのブラウザDOM 3 XPath が使えるようになるんです。

じゃあ、 document.evaluate には何を渡せばいいの?

ここで、 XPath の登場です!

( ゚∀゚)o彡゜XPathXPath

document.evaluate は以下のように使います!

var result = document.evaluate(
               '//div',            // これが XPath !
               document,           // どこから取得するか たとえば、 document.body とかってすると head 以下の要素は取得されない
               null,               // 基本使わないと思っていい
               7,                  // 結果の種類を指定する。基本は 6 か 7 でいい。 6 だったら結果がソートされない可能性がある。
               null                // 基本使わない
             );

result.snapshotLength;  // 取得した要素(正確にはノード)の数。
result.snapshotItem(0); // 1 個目の要素
result.snapshotItem(1); // 2 個目の要素

ね?簡単でしょ?

XPath 覚えなきゃいけねーじゃねーか!

うん><でも、 XPath って超簡単だよ!

超基本的なチートシートだよ。

全要素//*/descendant::*
全 div 要素//div/descendant::div
class 属性が "hoge" な div 要素//div[@class="hoge"]/descendant::div[@class="hoge"]
id 属性が "hoge" な要素id("hoge")//*[@id="hoge"]/descendant::*[@id="hoge"]
title 属性が "hoge" で class 属性が "fuga" でない要素//*[@title="hoge" and @class!="fuga"]/descendant::*[@title="hoge" and @class!="fuga"]
form 要素の 3 番目の input 要素//form/descendant::input[3]/descendant::form/descendant::input[3]*1
チェックされたチェックボックスの親要素//input[@checked="checked"]/..//input[@checked="checked"]/parent::node()

JavaScript-XPath って使うの結構めんどいんでしょ?

違うよ!ぜんぜん違うよ!

超簡単だよ><

という訳で、 JavaScript-XPath を使う為の 3 ステップを教えちゃいます。

1. 最新の JavaScript-XPathダウンロードしてくる

ここからダウンロードしてね。

http://svn.coderepos.org/share/lang/javascript/javascript-xpath/trunk/release/javascript-xpath-latest.js

2. 以下のように HTML ファイルにインポートする
<!-- こんな感じで javascript-xpath-***.js を読み込む -->
<script src="javascript-xpath-latest.js"></script>

<!-- ダウンロードがめんどくて、とりあえず使ってみるだけだったら直接参照してみる><(本番環境ではしないでね><)
<script src="http://svn.coderepos.org/share/lang/javascript/javascript-xpath/trunk/release/javascript-xpath-latest.js"></script>
-->
3. そして、 DOM 3 XPath を使う!
window.onload = function() {
  // div 要素全部取ってくる
  var r = document.evaluate('//div', document, null, 7, null); // この '//div' が XPath。これ、まめ知識ね。

  // div 要素の数
  alert(r.snapshotLength); 

  // 最初の div 要素
  alert(r.snapshotItem(0));

  // 2 番目の div 要素
  alert(r.snapshotItem(1));
};
ほら、できちゃったでしょ?

わいわいがやがや

まとめ

ぜひぜひ、 JavaScript-XPath を使ってください!

この冬も XPath が熱い!


[余談] でも、 JavaScript-XPath にもバグとかあるんじゃないの?

JavaScript-XPath ではある程度のテストはしており、基本的にはバグは少ないと思います。

でも、人が作るものなので、少しのバグはあると思います><

ですので、あなたの力を貸してください!!

JavaScript-XPath では以下のようなテストを行っています

http://svn.coderepos.org/share/lang/javascript/javascript-xpath/trunk/test/functional/index.html

JavaScript-XPath にあなたのテストファイルを提供してください!

ここ にあなたのサイトの HTMLXPath と 結果を書いたテストデータを提供してください><

そうすれば、あなたの XPath がテストに組み込まれ、半永久的にサポートされることでしょう><

テストデータは、 CodeRepos のコミット権を取得すれば誰でも提供する事ができます。

テストデータの形式

テストデータの形式は以下のような形式です。

テストのタイトル
--------
<html>HTMLの内容</html>
--------
コンテキストノード(キホン的には / だけでいい)
--------
XPath => 結果(CSS セレクタのように div.class#id[attributeName="attributeValue"] というような文字列を空白区切りで )
XPath => 結果
XPath => 結果
:
:

具体的にはこんな感じのファイルになります。

よろしくお願いします!

*1:'//form//input[3] はダメ><

LanceLance 2007/11/12 18:39 JavaScript-XPathでのcurrent nodeの概念がイマイチよくわからないのですが、それってdocument.evaluateの第2引数で、documentオブジェクトを起点にしてパスを作って渡す、という理解でいいのでしょうか?

amachangamachang 2007/11/12 18:57 コメントありがとうございます。
はい。そうです。
DOM 3 XPath の仕様と同様に、 document を渡せば document ノードを起点として XPath 式を評価します。

tete 2007/11/13 04:32 jQueryじゃダメなんですか?

ww 2007/11/13 09:08 時代はjQuery

kdaibakdaiba 2007/11/13 11:50 こんなブックマークレットを作ってみました.firebugから使ってます.ieのcompanion.jsでも使ってみようとしたんですが,使い方がわからなくて断念 orz

javascript:(function(){var s = document.createElement(’script’);s.src = ’http://svn.coderepos.org/share/lang/javascript/javascript-xpath/trunk/release/javascript-xpath-latest.js’;s.type = ’text/javascript’;document.getElementsByTagName(’head’)[0].appendChild(s)})()

kohiro0kohiro0 2007/11/13 11:54 [last()-1]とかは非対応?

amachangamachang 2007/11/13 11:56 te さん, w さん
jQuery はいいですよね^^正に jQuery の時代です。

id:kdaiba さん
おおお。ブックマークレットありがとうございます!
Companion.JS に組み込めたら楽しそうですねえ><

amachangamachang 2007/11/13 11:58 kohiro0 さん
対応してます!以下のようなので試してみてください!
alert(document.evaluate(’/descendant::*[last() - 1]’, document, null, 7, null).snapshotItem(0));

kohiro0kohiro0 2007/11/13 13:05 上の続きです
どうやらFIRST_ORDERED_NODE_TYPEだとダメっぽい

amachangamachang 2007/11/13 13:18 ><
了解しました。ありがとうございます。さっそく調査いたします。
よかったら HTML や XPath なども教えていただければ嬉しいです。

amachangamachang 2007/11/13 13:23 どうやら、僕の環境では旨くいくようです。引き続き調べてみます。

僕が試したのは、以下。
alert(document.evaluate(’/descendant::*[last()-1]’, document, null, 9, null, 7, null).singleNodeValue);
alert(document.evaluate(’/descendant::*[last()-1]’, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null, 7, null).singleNodeValue);

amachangamachang 2007/11/13 13:26 FIRST_ORDERED_NODE_TYPE だと snapshotItem ではなく、 singleNodeValue で取得するところが注意です><

kohiro0kohiro0 2007/11/13 13:34 ソースそのまま乗っけちゃいます。適当に作った&見づらくてごめん。
コメントアウトしてあるソースだとダメなのです。
そうそう。環境はIE7。
<html>
<head>
<script src=”javascript-xpath.js”></script>
<script>
function init() {
//document.evaluate(’id(”res”)/*[last() - 1]’, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)
//.singleNodeValue.style.border=”5px solid red”;
document.evaluate(’id(”res”)/*[last() - 1]’, document, null, 7, null)
.snapshotItem(0).style.border=”5px solid red”;
}
</script>
<style>.a { height:20px; width:20px; }</style>
</head>
<body onload=”init()”>
<div id=”res”><div class=”a”></div><div class=”a”></div><div class=”a”></div><div class=”a”></div><div class=”a”></div><div class=”a”></div><div class=”a”></div>
</div>
</body>
</html>

amachangamachang 2007/11/13 14:04 一応、確認いたしましたが正常に動いているようです><

http://amachang.art-code.org/test.html

いかがでしょうか?

amachangamachang 2007/11/13 14:09 おっと last() - 1 の評価が違うということか><失礼しました

kohiro0kohiro0 2007/11/13 14:13 あ、そういうことです。言葉足らずでした

kohiro0kohiro0 2007/11/13 14:28 あ、もう1つ
AutoPagerizeのSITEINFOのなかの
Google Blog SearchのnextLinkがマッチしない
あんまり検証してないけど、フルパスだとおkっぽい(http://〜/nav_next.gif)
対応難しそうだけど、一応報告

amachangamachang 2007/11/13 15:07 とりあえず、 singleNodeValue の件は対応しました><

http://amachang.art-code.org/test.html

href がフルパスの件はちょっと対応が難しそうなのでゆっくり考えてみます>< IE が IE が。。。

amachangamachang 2007/11/13 15:08 バグ報告ありがとうございました!><

kohiro0kohiro0 2007/11/13 15:22 早ッ!?対応thx
hrefの件、気長に待ってます
ちょっと考えたら気が狂いそうになりました
すごく・・・相対パスです・・・

amachangamachang 2007/11/13 15:26 いえいえ><
こちらこそ、特定の条件でしか起きないバグだったので、非常に助かりました!

amachangamachang 2007/11/13 15:26 相対パスは・・・ orz

karachangkarachang 2007/11/13 19:28 よくわかってないのかもしれませんが、
GoogleのAjaxsltと似たような感じじゃあないんですかね?

YuichirouYuichirou 2007/11/14 00:17 >karachangさん
部外者だけど返信コメ。その通りだよ。http://coderepos.org/share/wiki/JavaScript-XPath にも ”This code runs 10 times faster than Google Ajaxslt’s xpath.js!!”(このコードはGoogle Ajaxsltのxpath.jsより10倍速いよ!!)って書いてあるよ。

yoheiyohei 2007/11/14 12:15 サンプルに出てくる class 名の指定ですが、= や != で比較すると class=”hoge fuga” のように複数のクラスが設定されている要素がマッチしなくなるので注意が必要です。なので

//div[@class=”hoge”]



//div[contains(concat(” ”, @class, ” ”), ” hoge ”)]

の方がよいと思います。

tarchantarchan 2007/11/15 00:39 XPathのチートシートとっても参考になりました!
ところで<dt>または<dd>を選択したいときはどう書けばいいのかな?
’//dt or //dd’ではダメっぽい...

Puyo2Puyo2 2007/11/21 17:43 こんにちはamachangさん。
まっとうな使い方では問題起きないのかもしれませんが
>var convigValue = configStringSplited[1];
ってなってます。
あとダメ元でいってみますがスクリプト要素がないとエラーになるみたいです。

makomako 2007/12/21 15:23 初心者なので変な質問でしたら申し訳ありません。
これって、if 文の条件にするにはどうしたらよいでしょうか?

if(document.evaluate(’/descendant::body[@class=”top”]’, document, null, 7, null)){

こういった書き方では無視されてしまいました。

makomako 2007/12/21 18:30 すみません、解決しました。
お騒がせいたしました。

satosisatosi 2008/03/13 16:53 ここのページを参考にして勝手にXPathの日本語チートシートを作ってしまいましたw
一人で使うのはもったいないので皆さんに使ってもらいたいです。
http://pearl-white.hp.infoseek.co.jp/xpath/

XPathさいこーw

>tarchan
[dt or dd]を試してみてください

Atsushi777Atsushi777 2008/03/14 14:33 ここでは、”//div” と ”/descendant::div” を同じものとして扱っていますが、本当は ”//div” は ”/descendant-or-self::node()/div” ですよね?
で、これらは意味も evaluate() した結果も異なる(ノードセットのノード順が異なる)ので、明確に区別する必要があるのではないでしょうか。

Atsushi777Atsushi777 2008/03/14 14:37 もうちょっとちゃんと書いておくと、
”/descendant::*” はドキュメント順にノードが列挙されるのに対し、
”//*” はノードの深さの浅い順(同じ深さならドキュメント順)に列挙されます。

したがって、”/descendant::*[2]” と ”//*[2]” は異なるノードを指す可能性があります。

amachangamachang 2008/03/14 15:29 同じ物として扱っているわけではありません。
コンテキストポジションを参照しない式においては、同じになるのでそう書きました。

指摘ありがとうございます。

Atsushi777Atsushi777 2008/03/15 13:02 返答ありがとうございます。
しつこいようですが、順番が違うのですからコンテキストポジションを使わなくてもいろんな場面で同じになりません。
amachang さんにこのようなことをいうのは釈迦に説法であることは重々承知しておりますが、どうもこれをみて勘違いしている人がいることが気になっているのです。

amachangamachang 2008/03/17 12:58 こちらこそ、理解していないのかもしれませんが。

僕の知識では、
document.evaluate で評価された XPath 式の結果のノード順は、
必ず「ドキュメント順」か「不定な順番」になります。

以下をご確認ください
http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#Interfaces

ただし、僕のエントリに誤解を招くような記述が含まれる場合は
どこをどのように直すことがベストだと思われるかを教えてください。

申し訳ございませんが、よろしくお願いいたします。

Atsushi777Atsushi777 2008/03/18 14:36 いろいろとすみません。

> 順番が違うのですからコンテキストポジションを使わなくてもいろんな場面で同じになりません。
は関数適用した場合なんかを想定していたのですが、調べてみるとそういうケースはなさそうです。すみません。

> document.evaluate で評価された XPath 式の結果のノード順は、必ず「ドキュメント順」か「不定な順番」になります。
のとおりですので、”//*”, ”/descendant::*” が結果として同じになるというのに異論があるわけではないです。

どう直すのがベストかはわかりませんので、そのままでいいと思います。

お騒がせしてすみません。_o_

gwkygwky 2008/12/12 14:58 フレーム内の要素へのXPATHはどのように書けばよいのでしょうか?
また、それは可能なのでしょうか?
実際には、firefoxのgreasemonkey で、フレームを使ったページ内のテーブル内のデータを取得したいと考えています。

下記のようにやればできるかと思ったのですが、できないようです・・・
var xpath ='/html/frameset/frame[2]/html/body/table/tbody';
var tbody = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.contentDocument;
alert(tbody.rows[0].cells[0].firstChild.data);


対象としているサイトの構造は、下記のようになっています。
2つのフレームを含むソース
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=x-sjis">
<TITLE>ほげほげ</TITLE>
<FRAMESET ROWS="88,*">
<FRAME SRC="frame1.html" NAME="FRM1">
<FRAME SRC="frame2.html" NAME="FRM2">
</FRAMESET>
</HTML>

frame2.html
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=x-sjis">
<TITLE>フレーム2</TITLE>
</HEAD>
<BODY>
<TABLE border="0">
<TR><TD>データ1</TD>・・・</TR>
・・・・・
</TABLE>
</BODY>
</HTML>


以上です。
はたしてできるのかどうかもわからない状態です。
どうぞ宜しくお願い致します!!!

furyu-teifuryu-tei 2009/03/25 05:49 Pythonに移植してみました。
公開してしまったのですが、
http://d.hatena.ne.jp/furyu-tei/20090324/BSXPath
問題あるようでしたらお知らせ願います。

ついでに、JavaScript-XPath 0.1.11 で typo を見つけてしまいました(^^;)。

BaseExprHasPredicates.prototyps = new BaseExpr();
prototyps→prototype

h-montah-monta 2009/03/29 20:29 FirefoxのGreaseMonbkey用のスクリプトをIEのTrixieで使いたいのですが、当然XPathは使えない・・・ということでJavaScript-XPathを使って何とかならないかと思うのですが、どうやって組み込めばいいんでしょうか?

bonkbonk 2010/04/06 00:55
某ブラウザ●国志の要素を取得してみようと試してみたのですが、
何か間違ったみたいです。。
要素の取得にはfirefoxのxpath checkerを使いました。

javascript:(function(){var s = document.createElement('script');s.src = 'http://svn.coderepos.org/share/lang/javascript/javascript-xpath/trunk/release/javascript-xpath-latest.js';s.type = 'text/javascript';document.getElementsByTagName('head')[0].appendChild(s)})()

javascript:var r=document.evaluate("id('gnavi')/x:ul/x:li[1]/x:a",document,null,7,null);alert(r.snapshotItem(0));

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。

トラックバック - http://d.hatena.ne.jp/amachang/20071112/1194856493