つい最近FirefoxのXMLオブジェクトがすごく便利なのを知りました。そして、そのXMLオブジェクトサポートのことをE4X(ECMAScript for XML)と呼んでいたというのを今知りました....
E4Xについては ECMAScript for XML (E4X) 仕様邦訳 を翻訳された nanto_viさんが書かれている E4X in Firefox が詳しいです。
しょせんはさっきE4Xがなんなのかを知った程度なのでちゃんとしたことは他のページを参照していただくことにして、11.2 左辺式 に書かれているアクセサと選別述語演算子でXPathと同じようなことができて、それがXPath以上に便利なところがありますよ、というはなしだけ書こうと思います。
E4XについてECMAScript for XML (E4X) 仕様邦訳序文にはこのプログラミング言語拡張は、世界中のもっとも大きな開発者コミュニティのひとつの既存の知識と技術を活用することで XML の学習曲線を平坦化させる、単純で親しみやすい汎用 XML プログラミングモデルを提供するために設計された。と書かれています。javascriptでXPathを使おうとするとdocument.evaluate()を使って帰ってきたイテレータをまわしたりtry/catchしないといけなかったりしますが、序文の通りE4Xはそのへんがめちゃめちゃ簡単にできます。
<rss version="2.0">
<channel>
<title>bits and bytes</title>
<link>http://labs.gmo.jp/blog/ku/</link>
<item>
<title>FirefoxのlivehttpheadersでHTTPリクエストの中身が見られるしくみ</title>
<link>http://labs.gmo.jp/blog/ku/2007/08/firefoxlivehttpheadershttp.html</link>
</item>
<item>
<title>Firefoxのsqliteデータベースの中身を表示する Storage Inspector</title>
<link>http://labs.gmo.jp/blog/ku/2007/08/firefoxsqlite_storage_inspector.html</link>
</item>
</channel>
</rss>
このXMLが文字列として raw_xml_string という変数に入っているなら
var rss = new XML(raw_xml_string);
var links = rss..item.link
for ( var i = 0; i < links.length(); i++ ) {
do_something_on( links[ i ] );
}
というふうにXPathっぽく書くことができます。 XMLオブジェクトの変数名の後に . と .. とでタグ名を書いていくことで目的の要素を取り出すことができます。XPathの/を .に置き換えただけくらいで書くことができます。結果を配列風に使うときには、長さを得るのにlengthプロパティではなくlength()メソッドを使う必要があり間違いやすいので常に for each を使う方が安全でしょう。(11.2.2 関数呼び出し)
for eachを使うと
for each ( var e in links ) {
do_something_on( e );
と書けます。ちなみにXMLオブジェクトはArrayとは別物なため、評価結果をforEachでイテレートすることはできません。残念。
var e = <elements>
<element id="He" state="gas"><name><ja>ヘリウム</ja></name></element>
<element id="Na" state="solid"><name><ja>ナトリウム</ja></name></element>
<element id="Cl" state="gas"><name><ja>塩素</ja></name><ref url="http://en.wikipedia.org/wiki/Chlorine">Chrorine</ref></element>
<element id="Hg" state="liquid"><name><ja>水銀</ja><en>quicksilver</en></name></element>
</elements>
このリストから常温で気体のものを取り出す式は
e.element.( @state == "gas" )
で表現することができます。これはXPathでは
//element[@state="gas"]
と表現できます。E4Xでは式を書くので == なのに気をつけてください。XPathのつもりで=にするとアトリビュートに値が代入されてしまいます。(これのことについてはまたあとで触れます)
stateがgasなものが選択されるので、結果は
<element id="He" state="gas">
<name>ヘリウム</name>
</element>
<element id="Cl" state="gas">
<name>塩素</name>
<ref url="http://en.wikipedia.org/wiki/Chlorine">Chrorine</ref>
</element>
になります。
もうひとつ簡単な例をあげておきます。常温で気体なもののうち、リファレンスがあるもののURLを取り出したいときには
e.element.(@state == "gas" ).ref.@url
と書きます。条件式の後にもふつうにドットで繋げて式を書いていくことができます。XPathでも同じように
//element[@state="gas"]/ref/@urlと書くことができます。
この式は
http://en.wikipedia.org/wiki/Chlorine
を返します。
これくらいならXPathでできることがちょっとだけ楽になる程度です。
e.element.( @id.match( /e$/ )
で可能です。eで終わるのはHeのヘリウムしかないので、結果は
<element id="He" state="gas">
<name><ja>ヘリウム</ja></name>
</element>
になります。XPathでは正規表現がサポートされていないので同じことはできません。(この例は例がよくないのでXPathでもやればできそうですが...)
さらにE4Xで式に書けるのはコンテキストノードのアトリビュートだけではありません。ツリー構造の一番内側のnameタグに入っている日本語での名前のその最後がいかにも元素の名前っぽい"ウム"で終わるelementを取り出す式を
<pre><code>e.element.( name.ja.match( /ウム$/ )</code></pre>
<element id="He" state="gas">
<name><ja>ヘリウム</ja></name>
</element>
<element id="Na" state="solid">
<name><ja>ナトリウム</ja></name>
</element>
と、簡潔に書くことができます。XPathではこの子孫ノードの特定の条件で判定するというのが簡単には表現できません(よね?)。
ちなみに、なんと表現したらいいのかよくわかんないのですが、この式評価時のスコープはコンテキストノードになっていて違いました。なにになっているかはわかりません。アタマにfunction::をつけることで13.4.4 XML プロトタイプオブジェクトのプロパティ (組み込みメソッド) を呼び出すことができます。function::parent()でコンテキストノードの親を参照することができます。先祖すべてを対象にするXPathのancestorのようなものはありませんが、工夫すればエミュレートすることはできるのではないでしょうか。thisが何を指しているかはちゃんと調べていませんが、コンテキストノードを指してはいませんでした。
= と書くと代入されますよ、というのを組み合わせると特定の条件をもつタグにだけ操作を適用する、ということができます。
e.element.( @state == "gas" && ( @id = @id.toLowerCase() ) )
とすれば常温で気体の元素だけ元素記号を大文字に変更することができます。
この式を評価すると、元素記号が小文字になった
<element id="he" state="gas">
<name>
<ja>ヘリウム</ja>
</name>
</element>
<element id="cl" state="gas">
<name>
<ja>塩素</ja>
</name>
<ref url="http://en.wikipedia.org/wiki/Chlorine">Chrorine</ref>
</element>
が帰ってきます。( @id = @id.toLowerCase() )は@state == "gas"がtrueのときのみ実行されるので常温で気体でない元素の元素記号は変化しません。
最小量: 適切な場所では、E4X は、現在 ECMAScript オブジェクトを操作するのに利用できない XML を操作するための新しい演算子を定義する。この演算子のセットは不必要な複雑さを避けるため最小限に保持されるべきである。例えば XPath のすべての機能を提供することは E4X の目標ではない。と書かれている通り、XPathに比べ基準点のサポートが弱いので単体での表現力では劣りますが、その欠点はjavascriptが使えることで十分補えているでしょう。
あとはFirefoxとFlex2くらいでしかサポートされていないのでGreasemonkeyスクリプトを書いたりFirefoxのextensionを書いたりFlexを使ったりするときにしか使い道がないのがなんとかなればと思います。
これ attr が設定されてないとエラーになっちゃうんだよね。というつっこみをいただいて、うわーほんと XPath -> E4x -> XPath だ.... と愕然としつつ、なんとかなんないのかなと調べてみたらattrがない場合に
reference to undefined XML name と言わせないようにすることができたので補足しておきます。
まずとりあえず
console.log(e.element.( attribute("state") == "gas" ));
はだめなのかと思ってやってみたものの、これは attribute is not defined と言われてだめでした。でも仕様にはattribute関数は存在するし条件式の中でなければ呼び出せるので、なんとかすれば呼べるんだろうとMozillaのソースを調べてみました。
E4Xのテストコードの中でattribute関数のテストも書かれているはずだから、たぶんそのへんでなにかわかるだろうと mozilla/js/tests/e4x をgrepしたところ
Regress/regress-323338-1.js:65: var el = htmlXML.body.div..div.(function::attribute('id') == 'summary');
という記述を発見。アタマに funciton:: をつけるなんてどこにも書いてなかったぞと思いながら
console.log(
e.element.( function::attribute("state") == 'gas' )
);
にしてみると、無事エラーが出たりすることなく
<element id="Cl" state="gas">
<name>
<ja>塩素</ja>
</name>
<ref url="http://en.wikipedia.org/wiki/Chlorine">Chrorine</ref>
</element>
という結果を得ることができました。
E4Xの仕様には function:: をつけるとXMLListの関数を呼び出せるとはどこにも書かれていないのでFirefoxでしか使えないのかもしれませんが、(nanto_viさんにコメントいただいたとおりSpiderMonkeyの独自拡張としてfunctionネームスペースがあるとE4X in Firefox 発表資料に書かれているのを見落としていました。SpiderMonkeyの独自拡張なためFirefoxでしか使えません)とりあえずFirefoxでは function::attribute( .... ) に書き換えればよさそうです。@だけで済んでいたのに比べると極めて冗長ですが....
トラックバック元エントリにこのエントリへのリンクがない場合はトラックバックを受け付けません。
http://labs.gmo.jp/mt/mt-tb.cgi/160
comments
> reference to undefined XML name 問題
一応「E4X in Firefox」に対する解説「E4X in Firefox 発表資料」
http://nanto.asablo.jp/blog/2007/04/22/1459018
にてそのあたりにも触れたつもりです(「SpiderMonkey による拡張」のあたり)。
ごめんなさい、ちゃんとSpiderMonkeyの独自拡張としてfunctionネームスペースのことについて書かれているのに見落としておりました... 発表資料のページとあわせて本文を訂正、補足しました。
ご指摘ありがとうございます。