2007年 11月 17日

New version of $X

コピペメモ

今までの $X は evaluate を二回する問題があってダサいので、使い勝手をそのままにしつつ新しくして使いはじめました。

  • type 指定を導入
    • ECMAScript 側で受けとりたい型を指定する。 (Array, String...)
    • XPathResult.BOOLEAN_TYPE とか指定するのはめんどいし覚えられない。
  • type 指定なしの場合は UNORDERED_NODE_ITERATOR_TYPE をそのまま Array に変換してかえす
    • たぶんこれでも殆どの場合は問題ないと思う
    • ノード集合じゃない (number とか) ならそれぞれそのままかえす (いままでとおなじ)
  • type 指定で Array を指定した場合は ORDERED_NODE_SNAPSHOT_TYPE を Array に変換してかえす
    • UNORDERED なやつで特別問題がある場合つかう

あんまり type 指定するの好きじゃないので (めんどい) 基本的に指定しなくても問題ないように

(長いのでまだ変更するかも)

// $X(exp);
// $X(exp, context);
// $X(exp, type);
// $X(exp, context, type);
function $X (exp, context, type /* want type */) {
    if (typeof context == "function") {
        type    = context;
        context = null;
    }
    if (!context) context = document;
    var exp = (context.ownerDocument || context).createExpression(exp, function (prefix) {
        var o = document.createNSResolver(context).lookupNamespaceURI(prefix);
        if (o) return o;
        return (document.contentType == "application/xhtml+xml") ? "http://www.w3.org/1999/xhtml" : "";
    });

    switch (type) {
        case String:
            return exp.evaluate(
                context,
                XPathResult.STRING_TYPE,
                null
            ).stringValue;
        case Number:
            return exp.evaluate(
                context,
                XPathResult.NUMBER_TYPE,
                null
            ).numberValue;
        case Boolean:
            return exp.evaluate(
                context,
                XPathResult.BOOLEAN_TYPE,
                null
            ).booleanValue;
        case Array:
            var result = exp.evaluate(
                context,
                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                null
            );
            var ret = [];
            for (var i = 0, len = result.snapshotLength; i < len; i++) {
                ret.push(result.snapshotItem(i));
            }
            return ret;
        case undefined:
            var result = exp.evaluate(context, XPathResult.ANY_TYPE, null);
            switch (result.resultType) {
                case XPathResult.STRING_TYPE : return result.stringValue;
                case XPathResult.NUMBER_TYPE : return result.numberValue;
                case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
                case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: {
                    // not ensure the order.
                    var ret = [];
                    var i = null;
                    while (i = result.iterateNext()) {
                        ret.push(i);
                    }
                    return ret;
                }
            }
            return null;
        default:
            throw(TypeError("$X: specified type is not valid type."));
    }
}

あーあーあーあーあーあーー

もうう、すべきことがぜんぜんできない。仕事全然すすまない……

HTML ツリービルダー (Pure DOM)

function h (str) {
	var t, cur, stack = [cur = document.createElement("div")];
	while (str.length) {
		if (str.indexOf("<") == 0) {
			if (t = str.match(/^\s*<(\/?[^\s>\/]+)([^>]+?)?(\/)?>/)) {
				var tag = t[1], attrs = t[2], isempty = !!t[3];
				if (tag.indexOf("/") == -1) {
					child = document.createElement(tag);
					if (attrs) attrs.replace(/([a-z]+)=(?:'([^']+)'|"([^"]+)")/gi,
						function (m, name, v1, v2) {
							child.setAttribute(name, v1 || v2);
						}
					);
					cur.appendChild(child);
					if (!isempty) {
						stack.push(cur);
						cur = child;
					}
				} else cur = stack.pop();
			} else throw("Parse Error: " + str);
		} else {
			if (t = str.match(/^([^<]+)/)) cur.appendChild(document.createTextNode(t[0]));
		}
		str = str.substring(t[0].length);
	}
	return stack.pop().firstChild;
}
window.onload = function () {
	var t = [
		h("<div><img src='http://mixi.jp/favicon.ico'/>hello<span style='color:red'>!</span></div>"),
		h("<div class='test'/>"),
		h("<div class='test'><ul><li>aa</li></ul></div>"),
		h("<div class='test' style='background: #f00'>hogehoe  aaa</div>")
	];
	for (var i = 0; i < t.length; i++) {
		var e = t[i];
		document.body.appendChild(e);
	}
};

jQuery でふつうに HTML かいて要素生成しはじめると、$N("div", {attr}, [childs]) みたいなのがめんどくさくてしかたないので簡単なパーサ書いて生成するようにした。innerHTML 使えよって感じですね。なんで innerHTML つかわないで書いたんだっけ……

  • 実体参照もどしてない。

たぶんおれはパーサーがかきたかったんだ。よくわかんないけど

innerHTML より遅いんだろうなぁとおもってベンチマークとってみた。http://d.hatena.ne.jp/amachang/20060906/1157571938 amachang+=100

まず innerHTML バージョンを定義 (テーブルとかいろいろ考慮してないけど)

function hi (str) {
	var t = document.createElement("div");
	t.innerHTML = str;
	return t.firstChild;
}
benchmark({
	"pure dom"  : function () {
		for (var i = 0; i < 1000; i++) {
			h("<div><img src='http://mixi.jp/favicon.ico'/>hello<span style='color:red'>!</span></div>");
			h("<div class='test'/>");
			h("<div class='test'><ul><li>aa</li><li>bb</li></ul></div>");
			h("<div class='test' style='background: #f00'>hogehoe  aaa</div>");
		}
	},

	"innerHTML" : function () {
		for (var i = 0; i < 1000; i++) {
			hi("<div><img src='http://mixi.jp/favicon.ico'/>hello<span style='color:red'>!</span></div>");
			hi("<div class='test'/>");
			hi("<div class='test'><ul><li>aa</li><li>bb</li></ul></div>");
			hi("<div class='test' style='background: #f00'>hogehoe  aaa</div>");
		}
	}
});
# GranParadiso A8
preparing ...
let's go!
.
*** pure dom ***
result : 2213.987429[ms]
.
*** innerHTML ***
result : 829.987429[ms]
.
finish!

# Safari 3
preparing ...
let's go!
.
*** pure dom ***
result : 1005.991463[ms]
.
*** innerHTML ***
result : 200.991463[ms]
.
finish

# IE 6
preparing ...
let's go!
.
*** pure dom ***
result : 1921.985521[ms]
.
*** innerHTML ***
result : 1342.985521[ms]
.
finish!

# IE 7
preparing ...
let's go!
.
*** pure dom ***
result : 1890.984139[ms]
.
*** innerHTML ***
result : 1312.984139[ms]
.
finish!

# Opera 9.24
preparing ...
let's go!
.
*** pure dom ***
result : 2353.990287[ms]
.
*** innerHTML ***
result : 474.990287[ms]
.
finish!

かなしいですが現実はこんなもんですね。