Hatena::agenda

CSS、HTML、XHTML、XML、DOM、XSLT、XPath、ECMAScript、Python、ウェブユーザビリティ、その他に関連する文書等のリソースを挙げていったりします。より本質的な議論を志向。
注意:Hatena::agendaの更新は終了しています!過去の記事はagenda のホームページよりどうぞ。今後の更新情報の取得は、agendaのフィード(XML/Atom)から。

 | 

2007-09-04 Javascript1.7のカスタムイテレータとXPath

数年前Hatena::agendaで公開したselectNodesメソッドの実装に加えて、カスタムイテレータを定義してfor each文で使えるようにしてみた。

_XPathNSResolver

まず_XPathNSResolverを定義。非XMLなHTMLで使う限りこれは読み飛ばし可。経緯はHatena::agenda - 2003-12-04

function _XPathNSResolver(nsmap, nodeResolver){
  this._nsmap = nsmap;
  this._resolver = nodeResolver?
    nodeResolver.ownerDocument.createNSResolver(nodeResolver) : null;
}
_XPathNSResolver.prototype.lookupNamespaceURI = function(prefix){
  var v;
  if (v = this._nsmap[prefix])
    return v;
  if (v = this._resolver)
    return v.lookupNamespaceURI(prefix);
  return v;
};

_XPathNSResolverの使い方。第一引数に接頭辞とURIのペアのオブジェクトリテラルを渡してインスタンスを作って、evaluateメソッドの第三引数に渡す:

/* Tipical usage of _XPathNSResolver
var de = document.documentElement;
var type = XPathResult.FIRST_ORDERED_NODE_TYPE;
var nsresolver = new _XPathNSResolver(
	{xht: "http://www.w3.org/1999/xhtml"}, de);
var result = document.evaluate(
	'descendant::xht:*', de, nsresolver, type, null);
 */

Nodeインターフェイスを拡張

次。Nodeインターフェイスを拡張。まず個人的に良く使う手でスコープを作る:

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

次にselectNodesメソッドを書く:

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;
	return new _NodeList(result);
};

たぶん毎回_XPathNSResolverを作るのがアレだからdocumentオブジェクトのメンバにして使いまわそうとしたんだろう>自分。ここ改善の余地。

_NodeListコンストラクタ

で、ここからがついさっき書き加えた部分。_NodeListコンストラクタ。selectNodesメソッドの戻り値はNodeListなので、それに似たものを自作。

function _NodeList(result){ // 「死」んでいる点に注意する
	this.__index = 0;
	this.__result = result;
	this.length = result.snapshotLength;
}

_NodeList自身をカスタムイテレータに

いよいよ本題というか、_NodeListのメソッドと共にカスタムイテレータを定義。Javascript1.7は……本当にいいものだ。

(function(__proto__){
	__proto__.__iterator__ = function(){
		return this;
	};
	__proto__.item = function(index){
		return this.__result.snapshotItem(index);
	};
	__proto__.next = function(){
		var item = this.item(this.__index++);
		if (!item) {
			this.__index = 0;
			throw StopIteration;
		} else {
			return item;
		}
	};
})(_NodeList.prototype);

_NodeList自身をイテレータにした。こうすることでfor each文を自然なPython風に使用できるという寸法。おっとスコープを閉じなきゃ。:

})(Node.prototype);

使用例

で使い方なんだけど例えば次のようなタスクを考える:

  • 文書中のUL要素とOL要素全てについて、子のLI要素の数が4未満のものに"short"というクラス名を付けてやる

DOMでは割と小面倒な部類なんじゃないかな。北村さん(誰)に突っ込まれそうなクラス名だけど気にしない。

for each(let e in document.selectNodes("descendant::ul | descendant::ol"))
		e.selectNodes("child::li").length < 4 && (e.className = "short");

終了。XPathをもっとうまく使う別解もあり。

 |