E4X in Firefox 発表資料2007年04月22日 20時11分

Mozilla Party JP 8.0 に講師として参加させていただきました。以下はプレゼンテーション用のスライドです。

OnpenOffice.org 2.0 の Impress を使って作ったのですが、私のノート PC のディスプレイ接続がうまくいかず、お借りしたマシンに入っていた OpenOffice.org 2.1/2.2 ではなぜか実行途中にフリーズするので、PDF にエクスポートしたものを用いました。

また、発表は 25 分の枠だったのですが、最初に練習したときは 1 時間かかってしまい、話す内容をいろいろと削ったものの、結局枠ぎりぎりまで使い切ってしまって、質疑応答の時間をとることができませんでした。もし何か聞きたいことがあったという方がいらっしゃれば、このコメント欄にお願いします。

それから、発表中に話したこともそうでないことも含め、上記のスライドに関する補足をいくつか挙げておきます。

XML 型と XMLList 型の相互変換に関して、1 項目の XMLList が XML として扱えるだけでなく、XML が 1 項目のみを含む XMLList として扱われる場合もあります。
XML 名前空間
:: の前に指定した変数は、その文字列化した値が使われます。ですから、名前空間 URI を扱うだけなら、ns = "http://example.org/" のように、Namespace オブジェクトを使わなくともかまいません。ただし、名前空間接頭辞の情報も保持したいといったときは、Namespace オブジェクトを使う必要があります。
SpiderMonkey による拡張
XML/XMLList 型は [[Get]]/[[Put]] といった内部メソッドが上書きされています。これにより、存在しないプロパティ名が指定されたとき、プロトタイプチェーンをたどるのではなく、空 XMLList を返すといった動作が実現されます。しかし、function 名前空間を使うことで、通常のオブジェクトと同じく、プロパティの検索にプロトタイプチェーンを使うといった動作にすることができます。
スコープチェーン上に XML オブジェクト x がある状態で式 method() を評価したとき、ECMAScript ではスコープチェーンを順にたどり、method という名前のプロパティを持つオブジェクトを探します。しかし、x が method という名前の子要素を持っていなければ、x は method という名前のプロパティを持たないことになってしまいます。そのため、list.(method()) では期待通りに動かない場合があります。
AttributeName コンストラクタに指定可能な引数は、QName コンストラクタに指定可能なものと同じです。
SpiderMonkey のバグ (1)

関数内でデフォルト名前空間を指定した場合、非修飾名を解決するのにデフォルト名前空間が使われない場合があります。たとえば、以下のコードでは "Default Namespace" と表示されるはずですが、"No Namespace" と表示されてしまいます。

var ns = new Namespace("http://example.org/");
(function () {
  default xml namespace = ns;
  var x = <a>
    <b>Default Namespace</b>
    <b xmlns="">No Namespace</b>
  </a>;
  print(x.b); // No Namespace
})();

この問題は、スコープチェーンを深くすることにより解決できます。

(function () {
  ...
  with ({}) print(x.b); // Default Namespace
})();
(function () {
  ...
  (function () { print(x.b); })(); // Default Namespace
})();

また、スコープチェーンを深くする操作があれば、それが非修飾名によるプロパティアクセスのあとであっても、解決することがあります。

(function () {
  ...
  print(x.b); // Default Namespace
  with ({});
})();
SpiderMonkey のバグ (2)
for-in 文、for-each-in 文を XML オブジェクトに対して使用すると、その XML オブジェクトは XMLList に変換されます。ですから、変換された XMLList が唯一持つプロパティ、すなわち元の XML オブジェクトそのもののみが列挙されるはずです。しかし、SpiderMonkey は誤って、XML オブジェクトが持つプロパティ、すなわちその XML の子ノードを列挙してしまいます。
DOM との変換
XMLSerializer はコンストラクタ名こそ XMLSerializer ですが、実装するインターフェース名は nsIDOMSerializer です。XPCOM コンポーネントとして呼び出すときなどには注意してください。
DOM ノードから E4X XML オブジェクトに変換するコードに関して、実際には node が文書ノードなら、そのルート要素に対して XMLSerializer を適用するなどの工夫が必要です。また、XMLSerializer は DocumentFragment にも対応しているので、XMLList コンストラクタを使うことで、DocumentFragment から XMLList への変換もできるようになります。
使用例 (2)
実際に Google カレンダーのフィードをパースするソースコードHTML エクスポートを行うソースコードをご覧ください。
ヒアドキュメントとしての使用に関して、XMLList リテラルを使えば式の埋め込みが簡単にできます。また、CDATA セクションリテラルを使うと、小なり記号 (<) やアンパサンド (&) 、開き波括弧 ({) などをエスケープする必要がなくなります。
資料
cho45 さんの「えへへうふふ E4X」が参考になります。また、ActionScript に関する Web ページで E4X について触れているものもあります。

「使用例 (1)」で述べた、Greasemonkey 用スクリプトと userChrome.js 用スクリプトの例を以下に置きます。

Greasemonkey 用スクリプト (スクリーンショットソース)
閲覧中のページに対するはてなブックマークでのコメントを表示します。さらに、ブックマーク数におけるコメントつきブックマーク数の割合を、SVG を用いてグラフ描画します。
E4X による要素の作成、XML オブジェクトから DOM ノードに変換する xml2dom 関数の実装など。
worris さんの「はてなブックマークコメントビューワ」を参考にしました。
userChrome.js 用スクリプト (スクリーンショットソース)
Web 検索バーに入力した文字列を用いて、ページ内検索もできるようにします。
E4X を用いた Overlay ファイルの動的生成、CDATA セクションのリテラル表記を用いたヒアドキュメントの実現など。
Georges-Etienne Legendre さんの SearchWP を参考にしました。

さらに、あとで Piro さんから質問があったので、その概要と回答も載せておきます。(回答はそのとき答えたことに少し付け加えています。)

E4X を使ったときのパフォーマンスはどうなのか?
今回の発表に当たってはパフォーマンスに関して調べていませんが、要素のネストが深くなると文字列化が非常に遅くなるというバグがあります。また、今回示した DOM ノードとの相互変換の手法は文字列を介したもので、DOM ノードと文字列との変換、E4X と文字列との変換がそれぞれ必要になるので、パフォーマンスはあまり良くないと思われます。なお、ヒアドキュメントとしての使用に関しては「えへへうふふ E4X」でも触れられていますが、通常の文字列連結に比べパフォーマンスの点で大きく劣るという結果が出ています。
DOM ノードとの相互変換は Firefox 1.5 でも可能か?
今回示した手法なら Firefox 1.5 から使用可能です。
ヒアドキュメントとして CDATA セクションのリテラル表記を使う際の注意点は?
後方互換性の関係から、XML オプションが有効になっていないと、CDATA セクションリテラルを直に書くことはできません。HTML 中で XML オプションを有効にするためには、<script type="text/javascript; e4x=1"> のように e4x パラメータを 1 にセットするか、<script type="text/javascript; version=1.6"> のように 1.6 以上のバージョンを指定しないといけません。XUL 中または XPCOM コンポーネント中では、最初から XML オプションが有効になっているため、このような指定を明示的に行う必要はありません。また、XML/XMLList リテラル内ならば、XML オプションの状態に関わらず、CDATA セクションを記述することができます。なお、CDATA セクションリテラルの評価結果は、テキストノードを表現する XML オブジェクトとなります。