v1.6.0 Templateクラス

v1.4.0の解読のように逐行解読はしませんです。読んでいて書きたくなったらメモするというスタンスで。

【抜粋】
var Template = Class.create({
  initialize: function(template, pattern) {
    this.template = template.toString();
    this.pattern = pattern || Template.Pattern;
  },

  evaluate: function(object) {
    if (Object.isFunction(object.toTemplateReplacements))
      object = object.toTemplateReplacements();

    return this.template.gsub(this.pattern, function(match) {
      if (object == null) return '';

      var before = match[1] || '';
      if (before == '\\') return match[2];

      var ctx = object, expr = match[3];
      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr);
      if (match == null) return before;

      while (match != null) {
        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
        ctx = ctx[comp];
        if (null == ctx || '' == match[3]) break;
        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
        match = pattern.exec(expr);
      }

      return before + String.interpret(ctx);
    }.bind(this));
  }
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

Templateクラス自体はv1.5.0preから追加されたもの。

文字列内に置換用文字列を埋め込んで使用すると、用意したオブジェクトの対応するプロパティの値に置換されるというもの。

基本的には以下のように使います。

【例】
var template = new Template("#{a}, #{b}, #{c}");
var obj = {a:"A", b:"B", c:"C"};
var str = template.evaluate(obj);
alert(str); //「A, B, C」と表示される。

このあたりは他に言及されてるサイトも多いので問題ないと思います。

さて、ソースコードを読んでみるとそれだけの機能としてはなんだか複雑。特に正規表現

      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr);

基本的な知識が不足していて当初理解不能に陥りました。最初の部分、

[^.[]+

を「すべての文字(.)または「[」でないものの1以上繰り返し」と読んでしまい混乱。この「.」はメタキャラクタとして扱われないのですね。

ブラケット表現中では「^」を除くすべてのメタキャラクタがその意味を失います。*1
(「正規表現辞典 (DESKTOP REFERENCE)」03-01-04 p121)

更に悩んで、やっと分かりました。つまり、置換用の値を保持するオブジェクトに、入れ子構造を許容するためのものなのです。*2

【例】
var obj = {
  a:{
    a:"AA",
    b:"AB"
  },
  b:{
    a:{
      a:"BAA",
      b:"BAB"
    }
  },
  c:{
    "c]c": "C]C"
  }
};
var template = new Template("#{a.a}, #{a[b]}, #{b.a.a}, #{b[a][b]}, #{c[c\\]c]}");
var str = template.evaluate(obj);
alert(str); //「AA, AB, BAA, BAB, C]C」と表示される。

ドット(.)繋ぎでも、[ ]指定でもOKになっています。[ ]指定でプロパティ名に「]」を使用したい場合は「\\]」としてエスケープします。

・・・あ、忘れてた。以下の部分について。

    if (Object.isFunction(object.toTemplateReplacements))
      object = object.toTemplateReplacements();

toTemplateReplacementsメソッドをもつのは現在Hashクラスのみです。つまり、置換用値保持オブジェクトがHashクラスインスタンスだった場合に、Hashクラスインスタンスが持つ本来のオブジェクトに置き換えるというものです。v1.4.0ではカスタムオブジェクト(連想配列)そのものにHashクラスを継承させていましたが、v1.6.0ではHashクラスインスタンスがそのオブジェクトをプロパティとして持つという形態になっているためです。

【参考URL】http://d.hatena.ne.jp/susie-t/20061106/1162785967

上記で言及した制限はv1.6.0で解消されていることになります。まあ、やっぱりそうなりますよね・・・。

*1:ただしエスケープとしての「\」は例外。

*2:じつはtheater.jsでも似たようなのを作っていたのですが、その割には気が付くのに時間がかかった;;