for Firefox #2

例えば続きを読んだ際に、最新のレス数を反映させたりする。それを行うには、簡単に言えば
レス
みたいなマークアップに対して、javascript で

document.getElementById('replies-total').textContent = '100';

などとすればいいわけだ。

しかし実際にはこの手のコードは赤福プラスの中にはない。実際のマークアップは
レス
などと binding 属性が付加してある。それで、汎用的な再バインド関数を呼ぶことにより、binding 属性に応じて自動的に更新させるようになっている。再バインド関数内では、バインドの接頭辞が [cci]xpath:[/cci] であればふたばのサーバが返した html をスクレイピングした結果の中間 xml からの xpath の結果、あるいは [cci]template:[/cci] であれば中間 xml をパラメータにして与えた XSL の変換の結果を用いて要素の内容を置換する。

この記事の話題はその、文書の一部分を更新するために呼ばれる XSL 変換だ。文書の一部といっても、変換に際して使用する一時的な文書は html 要素から始まるまっとうなそれである。つまり





とこんな感じ。この変換結果を、XSLTProcessor#transformToFragment() を用いて DocumentFragment として得る。

この時、返ってくる要素のツリー構造が Firefox とその他で微妙に違う。Presto Opera と Chrome は、DocumentFragment の子要素として body 要素の子要素を割り当てる。一方 Firefox は、単純に DocumentFragment の子要素として html 要素を割り当てる。

どちらが正しいのだろうか。正しさというのは違うか。そもそも ブラウザの javascript から利用できる XSLTProcessor のインターフェースは誰が考えて管理してるのかよくわからない。とりあえず Opera と Chrome の動作のほうが、そのまま単純に DocumentFragment を appendChild() の引数にできるので楽だし、何より多数派だ。一方で、動作としては Firefox のほうが素直ではある。body 要素を特別扱いします的な例外中の例外動作は何なんだろうか。DocumentFragment はあくまで断片なので html/head/body といった大物は対象にしない、みたいなルールがあるのかもしれない。しかし誰がその仕様を決めているのかよくわからない。

そんなわけで、得た DocumentFragment が body 要素を持っていたら、さらに body の子要素を DocumentFragment としてくくり出すようにした。くくり出す時に Range を使うのだが、これがちゃんと DocumentFragment にも作用するのか確証は持てない(DocumentFragment は Document のいわばサブクラスなので Range による操作も理屈の上では互換性があるはずなのだが、DocumentFragment に対する操作というのはけっこうどのブラウザも怪しい、あるいは怪しい時期があったのだ)。でもまあ、とりあえず動いているようだ。

function fixFragment (/*DocumentFragment*/f) {
var bodies = f.querySelectorAll('body');
if (bodies.length == 0) return f;
var r = document.createRange();
r.selectNodeContents(bodies[0]);
return r.cloneContents();
}

Leave a Reply

Your email address will not be published. Required fields are marked *