agenda 2004-03(上旬)

とことん標準に拘ったCSS切り替えスクリプト(その2)

最終更新
2004-03-29T02:37:20+09:00

とことん標準に拘ったCSS切り替えスクリプト(その1)の続き。典拠の見直し等。

DOM Level 2 HTML の意義

前回、DOM Level 2 HTMLを考慮に入れないと書きましたが、するとdocument.cookieが使用できなくなりますのでこれを撤回します。

こうなると、document.writeメソッドを忌避する理由が無くなり、DOM Level 2 StyleSheets等に拘る理由も消えます。

本来、CSSの切り替えはブラウザが行ってくれれば良いわけですが、そのようなブラウザでも幾つか不満が残ります:

  • 切り替えたCSSの情報を保持してくれない
  • 代替スタイルシートが予め用意されていなければならない

前者はdocument.cookieの書き換え及び参照で解決できますが、問題は後者です。実はDOM Level 2 (StyleSheets|CSS)的には、この問題を解決する方法はありません。CSSスタイルシートは、CSSStyleSheetインターフェイスを持ったオブジェクトとして扱われます(例えば実装ブラウザではdocument.styleSheets.item(0) でこのインターフェイスを持ったオブジェクトにアクセスできます)。しかしながら、このCSSStyleSheetインターフェイスを持ったオブジェクトを作成する手段、DOMImplementationCSS.createCSSStyleSheetメソッドを実装しているブラウザがありません(今のところ)。あったとしてもStyleSheetsコレクションに追加する手段がありませんから無意味です(参照:作成と追加をいっぺんにやってくれる、MSIEの document.createStyleSheet

link要素を生成して挿入するといった方法は非論理的な物理的なものであって、それを行ったとしてもCSSが切り替わる保障はありません。 HTMLBodyElement.setAttribute('style', 'color: red');を実行するのと似ています。style属性を追加したからといって、ダイナミックにスタイルが変更される保障はありません。

従って、HTML文書からリンクされていない新たなCSSを追加する確実な方法は、「document.writeメソッドでlink要素を書き出す」しかありません。

2004年3月28日まで - 徒書より誤りのご指摘を受け、削除。

設計思想的にあり得ないと思い込んでおりました。

とことん標準に拘ったCSS切り替えスクリプト(その1)

最終更新
2004-03-12T19:35:15+09:00

CSS切替スクリプトを書いてみる(徒書)で、なるべくDOM標準に沿った感じのCSS切替スクリプトが模索されているようです。一度徹底的なものを書いてみたかったので、私も考えてみようと思います。「標準」ふぇちではなくて単に「CSS切り替えスクリプト」に興味があるという方は、ある意味読まないほうが無難です。

拠り所としてはこれらを参考にすればよかろうと思われます:

さてここで、document.writeメソッドはDOM Level 2 HTMLに規定されていますから、これはHTML、XHTML1.0文書においてはきちんと「標準」に準拠していることになります。しかしこの仕様の対象外である、XHTML1.1文書等のXMLブラウザで表示させるような文書については駄目です。私のサイトの文書はXHTML1.0準拠ですからdocument.writeに頼りまくっていますが、ここではこのメソッドを、言い換えるとDOM Level (1|2) HTML を考えないことにします。

北村さんのスクリプトを叩き台にして

まず、「叩き台」の意味を誤解しないでください。ほぼ無意味な注意書きですが一応。

今回は、addEvent関数を見てみます。

function addEvent(obj, eventType, func) {
    if (obj.addEventListener) {
        obj.addEventListener(eventType, func, false);
    } else if (obj.attachEvent) {
        /* WinIE5-6用 */
        obj.attachEvent('on' + eventType, func);
    } else {
        /* MacIE5用 */
        if (eventType == 'load') {
            obj.onload = func;
        } else if (eventType == 'change') {
            obj.onchange = func;
        }
    }
}

CSS切替スクリプト - 徒委記 より

これは主にwindow.onloadの代替に使用されるものだと思われました。疑問に思うのは、window.addEventListenerは「可能」か、言い換えるとwindowオブジェクトがEventTargetインターフェイスを持っていることを何が保障してくれているのかということです。

The EventTarget interface is implemented by all Nodes in an implementation which supports the DOM Event Model. Therefore, this interface can be obtained by using binding-specific casting methods on an instance of the Node interface. The interface allows registration and removal of EventListeners on an EventTarget and dispatch of events to that EventTarget.

Document Object Model Events より

windowオブジェクトはall Nodes に含まれるのでしょうか。DOMツリーとしてはHTMLの上に最上位ノードの#documentがあって終わりです。

同じ疑問を持っている人が誰もいないようなので不安なのですが、Mozillaが実装しているwindow.addEventListenerというのは、ひょっとして、DOM Level 2 Eventとは何の関係も無いのではないでしょうか?

その他 1 実装判別

if (obj.addEventListener)という書き方は推奨されますが、同名異質のメソッドである可能性があります。各メソッドごとにその危険性を生じるよりは、if (document.implementation) でただ一回だけその危険性をあえて甘受し、if (document.implementation.hasFeature('HTMLEvents', '2.0'))で判別した方が良かろうと私は考えます。

その他 2 コードの簡略化

これは標準とは何の関係もありませんが、この分岐は必要ありません:

        if (eventType == 'load') {
            obj.onload = func;
        } else if (eventType == 'change') {
            obj.onchange = func;
        }

次のように書けます。

obj["on"+eventType] = func;

innerHTMLネタについて

最終更新
2004-03-07T22:55:41+09:00

JavaScript:document.body.innerHTML= document.body.innerHTML.split('ん').join('ン');focus();JavaScript:document.body.innerHTML= document.body.innerHTML.split('、').join(',');focus();JavaScript:document.body.innerHTML= document.body.innerHTML.replace(/<br\s?\/?>/g, '');focus();

こういうのを見るとウズウズする。データは分離しておこう。

javascript:(function(){
  for(var i=0,b=document.body,k,h,h=h?h:b.innerHTML,len=arguments.length;i<len;i++)
    h=((k=arguments[i])[0].length)?
      h.split(k[0]).join(k[1]) : h.replace(k[0],k[1]);
  b.innerHTML = h;
})(['ん','ン'],['、',','],[/<br\s?\/?>/g, ''])

結果ツリーフラグメントをノード集合として扱う(XSLT 1.0)

最終更新
2004-03-03T21:15:01+09:00

スタイルシート作成者による任意のノード集合を変数にバインドしたい場合があります。しかし一つのxsl:variable要素だけでは不可能です。二つのxsl:variable要素を使ってこれを無理矢理やってしまおうという話なので一見地味ですが、便利かも知れない応用が幾つかあります。

要点の説明

次のxsl:variable要素がトップレベル(ルート要素xsl:stylesheetの子供)にあるとします:

<xsl:variable name="test.fragment">
	<li n="1" />
	<li n="2" />
	<li n="3" />
</xsl:variable>

$test.fragmentとして参照できるオブジェクトの型はノード集合ではありません。従って、<xsl:for-each select="$test.fragment" />等とするとエラーになります。これは結果ツリーフラグメント(Result Tree Fragment)と呼ばれるXSLTの特殊な型です。string型として評価可能かどうかのチェックが行われ、しかし評価する際にはノード集合として扱われる、と考えて差し支えありません。つまり<xsl:for-each select="$test.fragment" />は、<xsl:for-each select="'文字列'" />と同じ理由で弾かれることになるのです。

仮に私がXSLTの試験問題を作るとしたら絶対このネタを使うだろう、という程に重要かつ誤解しやすいポイントなので要チェックです(11.1 Result Tree Fragments(XSL Transformations (XSLT)))。

この「結果ツリーフラグメント」を「ノード集合」と等価に扱うにはどうするか、というのがこの記事の要点です。

document関数を使って解決

document関数を利用し、スタイルシート自身を経由したロケーションパスで表現することで解決します。


<xsl:variable
  name="test.nodeset"
  select="document('')/*/xsl:variable[@name = 'test.fragment']" />

ここで、document('')は(殆どの場合)スタイルシート自身のルートノード一つを含んだノード集合です。相対URI('')の解決は、第二引数がない場合スタイルシート自身のURIとなるからです。正確にはdocument()関数を含むスタイルシートのノードのBase URIが基底になるので、そのノードが外部実体の場合はどうなのでしょうか。面倒くさいし話題が逸れまくるので考えないことにします。

ともかくこれで変換元文書(ソース文書)に依存しない、スタイルシート編集者による任意のノード集合を変数を通じて扱うことが出来ます。

応用1. 構造をユーザーが定義する

T/Oとしたいところですが、例えば汎用的なスタイルシートを公開する際、XMLの構造そのものをユーザー(クライアント)が定義、という形式が可能です。具体例は面倒なので省略。

応用2. 回数指定の繰り返し処理

何回繰り返し処理する、という場合にはxsl:call-templateの再帰で行うのが通例ですが、例えば先に示した変数を使用すると、三回繰り返す場合には次のように書けます:

<xsl:for-each select="$test.nodeset/*">
  (処理内容)
</xsl:for-each>

見た目がシンプルになる場合がある、というだけであまり推奨したくはありません。

応用3. 構造化されたパラメタを渡す

結果ツリーフラグメントを入れるxsl:variable要素の方をカラッポにしておいて、プロセッサクラスにDOMノードツリーをそこに挿入するメソッドでも追加すれば、構造化されたパラメタを渡すことが出来ます。以前書いたXSLTをテンプレートとして使用するの方法より汎用的です。