diff --git a/src/lang/template.js b/src/lang/template.js index 435a3e7a8..d097e3c35 100644 --- a/src/lang/template.js +++ b/src/lang/template.js @@ -86,7 +86,7 @@ * constructs: * * // matches symbols like '<&= field %>' - * var syntax = /(^|.|\r|\n)(\<%=\s*(\w+)\s*%\>)/; + * var syntax = /\\?\<%=\s*(\w+)\s*%\>/g; * * var t = new Template( * '
Name: <&= name %>, Age: <&=age%>
', @@ -94,16 +94,20 @@ * t.evaluate( {name: 'John Smith', age: 26} ); * // ->
Name: John Smith, Age: 26
* - * 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. + * + *
Backward compatilibility
+ * + * 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]) * @@ -112,10 +116,10 @@ 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 @@ -123,31 +127,33 @@ var Template = Class.create({ * 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; diff --git a/test/unit/string_test.js b/test/unit/string_test.js index a7ef84197..f23848c77 100644 --- a/test/unit/string_test.js +++ b/test/unit/string_test.js @@ -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() { @@ -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() {