Skip to content

Commit

Permalink
Template rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
samleb committed Aug 13, 2009
1 parent d3ee5e1 commit 8ba0a0b
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 42 deletions.
74 changes: 40 additions & 34 deletions src/lang/template.js
Expand Up @@ -86,24 +86,28 @@
* constructs:
*
* // matches symbols like '<&#38;= field %>'
* var syntax = /(^|.|\r|\n)(\<%=\s*(\w+)\s*%\>)/;
* var syntax = /\\?\<%=\s*(\w+)\s*%\>/g;
*
* var t = new Template(
* '<div>Name: <b><&#38;= name %></b>, Age: <b><&#38;=age%></b></div>',
* syntax);
* t.evaluate( {name: 'John Smith', age: 26} );
* // -> <div>Name: <b>John Smith</b>, Age: <b>26</b></div>
*
* There are important constraints to any custom syntax. Any syntax must
* provide at least three groupings in the regular expression. The first
* grouping is to capture what comes before the symbol, to detect the backslash
* escape character (no, you cannot use a different character). The second
* grouping captures the entire symbol and will be completely replaced upon
* evaluation. Lastly, the third required grouping captures the name of the
* field inside the symbol.
* There are important constraints to any custom syntax. Any syntax must be a
* global regular expression starting with an eventual backslash escape character
* (no, you cannot use a different character) and containing a group capturing the
* name of the field inside the symbol.
*
* <h5>Backward compatilibility</h5>
*
* Old style syntaxes are still supported but deprecated and will be removed in
* future releases:
*
* var oldSyntax = /(^|.|\r|\n)(\<%=\s*(\w+)\s*%\>)/;
*
**/
var Template = Class.create({
var Template = Class.create((function() {
/**
* new Template(template[, pattern = Template.Pattern])
*
Expand All @@ -112,42 +116,44 @@ var Template = Class.create({
* The optional `pattern` argument expects a `RegExp` that defines a custom
* syntax for the replaceable symbols in `template`.
**/
initialize: function(template, pattern) {
function initialize(template, pattern) {
this.template = template.toString();
this.pattern = pattern || Template.Pattern;
},
this.pattern = formatPattern(pattern || Template.Pattern);
}

/**
* Template#evaluate(object) -> String
*
* Applies the template to `object`'s data, producing a formatted string
* with symbols replaced by `object`'s corresponding properties.
**/
evaluate: function(object) {
function evaluate(object) {
if (object && Object.isFunction(object.toTemplateReplacements))
object = object.toTemplateReplacements();

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

var before = match[1] || '';
if (before == '\\') return match[2];
return this.template.replace(this.pattern, function(interpolation, expr) {
if (interpolation.charAt(0) === '\\') return interpolation.slice(1);
if (object == null) return '';

var result = expr.split(/\.|\[|\]/).inject(object, function(ctx, property) {
return property.length ? ctx[property] : ctx;
});

return result === object ? '' : String.interpret(result);
});
}

var ctx = object, expr = match[3];
var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
match = pattern.exec(expr);
if (match == null) return before;
// Backward-compatibility with 1.6 syntax
function formatPattern(pattern) {
if (pattern.global) return pattern;
var source = pattern.source.replace('(^|.|\\r|\\n)', '\\\\?').replace(/\((.*)\)/, '$1');
return new RegExp(source, 'g');
}

while (match != null) {
var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : 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 {
initialize: initialize,
evaluate: evaluate
};
})());

return before + String.interpret(ctx);
});
}
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
Template.Pattern = /\\?#\{(.*?)\}/g;
19 changes: 11 additions & 8 deletions test/unit/string_test.js
Expand Up @@ -324,21 +324,16 @@ new Test.Unit.Runner({
},

testTemplateEvaluationWithIndexing: function() {
var source = '#{0} = #{[0]} - #{1} = #{[1]} - #{[2][0]} - #{[2].name} - #{first[0]} - #{[first][0]} - #{[\]]} - #{first[\]]}';
var source = '#{0} = #{[0]} - #{1} = #{[1]} - #{[2][0]} - #{[2].name} - #{first[0]} - #{[first][0]}';
var subject = [ 'zero', 'one', [ 'two-zero' ] ];
subject[2].name = 'two-zero-name';
subject.first = subject[2];
subject[']'] = '\\';
subject.first[']'] = 'first\\';
this.assertEqual('zero', new Template('#{[0]}').evaluate(subject), "#{[0]}");
this.assertEqual('one', new Template('#{[1]}').evaluate(subject), "#{[1]}");
this.assertEqual('two-zero', new Template('#{[2][0]}').evaluate(subject), '#{[2][0]}');
this.assertEqual('two-zero-name', new Template('#{[2].name}').evaluate(subject), '#{[2].name}');
this.assertEqual('two-zero', new Template('#{first[0]}').evaluate(subject), '#{first[0]}');
this.assertEqual('\\', new Template('#{[\]]}').evaluate(subject), '#{[\]]}');
this.assertEqual('first\\', new Template('#{first[\]]}').evaluate(subject), '#{first[\]]}');
this.assertEqual('empty - empty2', new Template('#{[]} - #{m[]}').evaluate({ '': 'empty', m: {'': 'empty2'}}), '#{[]} - #{m[]}');
this.assertEqual('zero = zero - one = one - two-zero - two-zero-name - two-zero - two-zero - \\ - first\\', new Template(source).evaluate(subject));
this.assertEqual('zero = zero - one = one - two-zero - two-zero-name - two-zero - two-zero', new Template(source).evaluate(subject));
},

testTemplateToTemplateReplacements: function() {
Expand Down Expand Up @@ -372,9 +367,17 @@ new Test.Unit.Runner({

testInterpolate: function() {
var subject = { name: 'Stephan' };
var pattern = /(^|.|\r|\n)(#\((.*?)\))/;
var pattern = /\\?#\((.*?)\)/g;

this.assertEqual('#{name}: Stephan', '\\#{name}: #{name}'.interpolate(subject));
this.assertEqual('#(name): Stephan', '\\#(name): #(name)'.interpolate(subject, pattern));

this.assertEqual('foobar', '#{a}#{b}'.interpolate({a: 'foo', b: 'bar'}), "adjacent regexps");
this.assertEqual('#{b}', '#{a}\\#{b}'.interpolate(), "null object");

// 1.6 non-global syntax backward compatibility test
pattern = /(^|.|\r|\n)(#\((.*?)\))/;
this.assertEqual('#(name): Stephan', '\\#(name): #(name)'.interpolate(subject, pattern));
},

testToQueryParams: function() {
Expand Down

0 comments on commit 8ba0a0b

Please sign in to comment.