Textノードの部分文字列置換の試作

  • ECMA-262第3版のStringのreplace()に似せる
  • 置換する値としてNodeを許可する (置換する値として関数を渡した場合、その関数が返す値としてでも可)
  • 置換した結果が複数のNodeになる場合はDocumentFragmentを、正規化などをして結果が1つのTextになる場合はTextを返す
  • DOMインタフェースのprototypeの拡張、DOM2 Core、DOM2 Rangeが必要

var dummy = document.createElement("div");

var t = document.createTextNode("a\nb\nc");
var df = t.replace(/\n/g, document.createElement("br"));
dummy.appendChild(df);
alert(dummy.innerHTML); //a<br>b<br>c かそれに準ずる文字列
dummy.innerHTML = "";

t = document.createTextNode("#a10#b20#c30");
df = t.replace(/#(.)/g, function(m, n){
  var a = document.createElement("a");
  a.href = n;
  a.textContent = m;
  a.addEventListener("mouseover", function(e){ /* ... */ }, false);
  return a;
});
dummy.appendChild(df);
alert(dummy.innerHTML); 
  //<a href="a">#a</a>10<a href="b">#b</a>20<a href="c">#c</a>30
  // かそれに準ずる文字列。a要素にはイベントリスナが登録されている

コード

Text.prototype.replace = function(searchValue, replaceValue){
  var doc = this.ownerDocument;

  if (replaceValue && typeof replaceValue.nodeType == "number") {
    var node = replaceValue;
    replaceValue = function(){ return node.cloneNode(true); };
  }
  if (typeof replaceValue == "function") {
    var global = false;
    if (searchValue instanceof RegExp) {
      global = searchValue.global;
      var sv = searchValue.toString().split("/");
      var flags = sv[sv.length-1].replace("g", "");
      searchValue = new RegExp(searchValue.source, flags);
    }

    var range = doc.createRange();
    var result = doc.createDocumentFragment();
    var text = this.cloneNode(false);
    result.appendChild(text);

    do {
      var m = text.nodeValue.match(searchValue);
      if (!m) break;

      var index = text.nodeValue.search(searchValue);
      m.push(index, text);
      var r = replaceValue.apply(null, m);

      if (!r || typeof r.nodeType != "number") {
        r = doc.createTextNode(String(r));
      }

      range.setEndAfter(document.documentElement); //Fx...
      range.setStart(text, index);
      range.setEnd(text, index + m[0].length);
      range.deleteContents();
      range.insertNode(r);

      text = r.nextSibling;
    } while (global);
    result.normalize();
    if (result.childNodes.length == 1) result = result.firstChild;
  } else {
    result =
      doc.createTextNode(this.nodeValue.replace(searchValue, replaceValue));
  }
  return result;
};

問題

  • もっとコンパクトにしたい
  • 正規化するかどうか
    • 一部のTextノードにイベントリスナが登録されていた場合でも正規化される
  • Nodeの判別にx instanceof Nodeを使うかnodeTypeを調べるか