ブラウザでミニマムXML (3)

付き合ってもう5年。一部のブラウザのDOM実装って厄介だから、きっちり講義しようという姿勢、その姿勢をみるだけで脱帽する。

DOMRangeを使う方法は、こういう解もあり。ノードの内容物全てを選択するなら、直接的にselectNodeContentsメソッドが使える:

var r = document.createRange();
r.selectNodeContents(parent);
r.deleteContents();

あとここら辺も気になった:

XMLでは、名前の大文字小文字は厳密に区別される(例:div, DIV, Divはみんな別物です)のでcreateElementに指定した名前を変えてしまうようなことは許されません。これが守られてないってことは、つまり、documentとして参照されるアノ文書ノードはXML DOM(DOMコア)の文書ノードと考えることはできない、ということです。

DOM HTMLでは要素名、属性名は全て大文字に展開されることになっている(1.3. XHTML and the HTML DOM)。DOM CoreのDocumentノードというか、それを継承したDOM HTMLのHTMLDocumentノードなので、要素名や属性名を引数に与えるメソッドは仕様に沿って上書きされていると考えれば別におかしくない。

実際のところ、ブラウザ備え付けのdocumentは、XMLのDOM ではなくて、HTML専用のDOM、それもW3CのHTML DOMでさえなくて、かつてブラウザが勝手に実装したDOMもどき(当時DOM仕様がなかったのだから、「もどき」は可哀想だが)です。アノ documentは、互換性の呪縛から逃れることができないので、レガシーHTMLをも対象とした“DOMのようなもの”だと割り切ったほうがいいでしょう。XML DOMの挙動を期待してはいけません。

DOM Level 0とも言われる、かつてブラウザが勝手に実装したDOMもどきは確かに残っているけれども、その上さらにDOM HTMLが実装されたわけで。そもそもDOM HTMLは「レガシーHTML」を対称にしたものなのであって、XHTML1.1以降、即ちapplication/xhtml+xmlでserveされるべき文書では使用不可。不可とまでは書いてなかったか。でも確かそんなニュアンスの一文がどこかに。

新規の文書ノードはXML DOMの文書ノードと考えても(たいていは)大丈夫です。しかし、これはいいことばかりではありません。HTMLが持っていた豊富な機能性が使えないのです。例えば、getElementByIdメソッドは存在しないか、動かないか、動くかも知れないがお手軽にはいかないかです。

こういう状況が改善されるためには、XHTMLマークアップが一般化して、XML DOM(DOMコア)の拡張としてのHTML DOMが、ブラウザのdocumentとして実装される必要があります。

getElementByIdメソッドがDocumentインターフェイスに存在しないのはDOM Level2 Coreを実装していないからだろうし、動かないのはスキーマがないからだし、動くかもしれないがお手軽に行かないのは……なんだか知らんけど、ブラウザのせいじゃないと思うな。少なくともMozillaはきっちり実装してると思うというか間違いなくそれを志向してるから、なんか見つけたら報告が筋。

HTMLが持っていた豊富な機能性が使えないという状況。これはcreateDocument等々でHTMLDocumentインスタンスを作る方法が仕様に示されていないのが原因では。doctype引数にHTMLのそれを与えれば良いのかというと、特に書かれていないし。どっかに書かれてたりするのかしら。そもそも推奨されていないのではないだろうか。createDocumentメソッドは、DOM HTMLのみの実装には必須ではないのだし。

実際のところIEにはバグが多く「こんなのDOMじゃない」と割り切ったほうが賢い。それでもブラウザを使って「W3C DOM」を正しく学ぼうというのなら、WWWブラウザを使うからといって、開くHTML文書がウェブページである必要はないということを考えるべき。それでは実際的でないというのなら、scriptletもある。

Hatena::agenda 2003-12-04

ここで書いてみた_XPathNSResolverも使って、MSXMLでNodeインターフェイスに定義されているselectNodesメソッドを実装してみた。Firefox用のHTMLアプリケーションで利用している。

(function(__proto__/* Node.prototype */){

var resolver = (
	Document.prototype.xpathNSResolver = new _XPathNSResolver({}, null)
);

__proto__.selectNodes = function(expression){
	var doc = this.nodeType == Node.DOCUMENT_NODE ? this : this.ownerDocument;
	var result = doc.evaluate(
			expression, // XPath expression
			this, // Context node
			resolver, // Name space resolver
			XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, // Result type
			null // XPathResult if reuse
		);
	if (result.snapshotLength == 0) return null;
	var nl = new _NodeList(result);
	return nl;
};

__proto__.selectSingleNode = function(expression){
	var doc = this.nodeType == Node.DOCUMENT_NODE ? this : this.ownerDocument;
	var result = doc.evaluate(
			expression,
			this,
			resolver,
			XPathResult.FIRST_ORDERED_NODE_TYPE,
			null
		);
	return result.singleNodeValue;
};

function _NodeList(result){ // 「死」んでいる点に注意する
	this.__result = result;
	this.length = result.snapshotLength;
}
_NodeList.prototype.item = function(index){
	return this.__result.snapshotItem(index);
};


})(Node.prototype);

MSXMLとの違いをメモしておく。selectNodesメソッド、selectSingleNodeメソッド共に、与えたXPath式が不正ならXPathExceptionインスタンスが投げられる。codeプロパティはXPathException.INVALID_EXPRESSION_ERR。評価結果がノード集合にならない場合も同様で、codeプロパティはXPathException.TYPE_ERR。戻り値のノード集合はNodeList型と言いたいところだが、文書の変更が反映されない偽者。名前空間接頭辞の登録は、doc.xpathNSResolver.registerPrefix(prefix, namespaceURI)。変数docはDOM Document。この辺りあまり考えてない。改善の余地あり。

DOM3 XPathって欲張りすぎている気がする。DOMでXPath使ってブール値なんかを直接得ようと思うかな? 殆どの場合得たいのはノード集合だろう。例えば、boolean(self::*)なんて式を評価したいと思うかってこと。まああるだろう。あるだろうけど、それを実現する為にXPathResultなんていう玉虫色のインターフェイスを抽象的に理解するくらいなら、self::*を評価してノード集合(DOMならNodeList型に変換)を得て、こちらで用意したXPathのboolean関数に相当するプロセスに渡した方が良いような。勧告までのスピードって結構大切なんだろうし、XPath全体を含めて抽象化するのではなくて、取りあえずLocation Pathだけ考えてくれれば良かった。その後でニーズがあれば幾らでもごちゃごちゃやりゃいいじゃねえか。