From 4569a1ecfc9650c10fa73c5118506913d63609b6 Mon Sep 17 00:00:00 2001 From: Lucas Galfaso Date: Sat, 23 Aug 2014 14:17:58 +0100 Subject: [PATCH] fix($interpolate): use the first end symbol that forms a valid expression When an interpolation string has multiple end symbol choices, pick the occurrence that forms a valid expression Closes #8642 --- src/ng/interpolate.js | 39 ++++++++++++++++++++++++++++++++++++-- test/ng/interpolateSpec.js | 10 +++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/ng/interpolate.js b/src/ng/interpolate.js index 4d3db469ce83..6c926ba5274d 100644 --- a/src/ng/interpolate.js +++ b/src/ng/interpolate.js @@ -41,6 +41,8 @@ var $interpolateMinErr = minErr('$interpolate'); function $InterpolateProvider() { var startSymbol = '{{'; var endSymbol = '}}'; + var quoteChars = '\'"'; + var brackets = {"(": ")", "[": "]", "{": "}"}; /** * @ngdoc method @@ -193,17 +195,42 @@ function $InterpolateProvider() { textLength = text.length, exp, concat = [], - expressionPositions = []; + expressionPositions = [], + quoteChar, + bracketCounts = {}, + ch; + forEach(brackets, function(start, end) { + bracketCounts[start] = 0; + bracketCounts[end] = 0; + }); while(index < textLength) { if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { if (index !== startIndex) { concat.push(unescapeText(text.substring(index, startIndex))); } + index = startIndex + startSymbolLength; + while (index < endIndex || quoteChar !== undefined || !bracketsMatch(bracketCounts)) { + if (index >= endIndex) { + endIndex = text.indexOf(endSymbol, endIndex + 1); + if (endIndex === -1) { + break; + } + } + ch = text[index]; + if (quoteChar) { + if (quoteChar === ch) quoteChar = undefined; + else if (ch === '\\') index++; + } else { + if (ch in bracketCounts) bracketCounts[ch]++; + else if (quoteChars.indexOf(ch) != -1) quoteChar = ch; + } + index++; + } exp = text.substring(startIndex + startSymbolLength, endIndex); - expressions.push(exp); parseFns.push($parse(exp, parseStringifyInterceptor)); + expressions.push(exp); index = endIndex + endSymbolLength; expressionPositions.push(concat.length); concat.push(''); @@ -312,6 +339,14 @@ function $InterpolateProvider() { $exceptionHandler(newErr); } } + + function bracketsMatch(bracketCounts) { + var match = true; + forEach(brackets, function(start, end) { + match = match && bracketCounts[start] === bracketCounts[end]; + }); + return match; + } } diff --git a/test/ng/interpolateSpec.js b/test/ng/interpolateSpec.js index a30278424751..7f23190f8dc9 100644 --- a/test/ng/interpolateSpec.js +++ b/test/ng/interpolateSpec.js @@ -105,7 +105,7 @@ describe('$interpolate', function() { expect(function() { $interpolate('{{\\{\\{foo\\}\\}}}')(obj); }).toThrowMinErr('$parse', 'lexerr', - 'Lexer Error: Unexpected next character at columns 0-0 [\\] in expression [\\{\\{foo\\}\\]'); + 'Lexer Error: Unexpected next character at columns 0-0 [\\] in expression [\\{\\{foo\\}\\}]'); })); @@ -116,6 +116,14 @@ describe('$interpolate', function() { it('should evaluate expressions between escaped start/end symbols', inject(function($interpolate) { expect($interpolate('\\{\\{Hello, {{bar}}!\\}\\}')(obj)).toBe('{{Hello, World!}}'); })); + + it('should pick the first end symbol location that forms a valid expression', inject(function($interpolate) { + expect($interpolate('{{{}}}')(obj)).toBe('{}'); + expect($interpolate('{{"{{ }}"}}')(obj)).toBe('{{ }}'); + expect($interpolate('{{{foo: "bar"}}}')(obj)).toBe('{"foo":"bar"}'); + expect($interpolate('{{{foo: {"bar": "baz"}}}}')(obj)).toBe('{"foo":{"bar":"baz"}}'); + expect($interpolate('{{[{foo: {"bar": "baz"}}]}}')(obj)).toBe('[{"foo":{"bar":"baz"}}]'); + })); });