Skip to content

Commit 892f44f

Browse files
author
laco0416
committed
feat(compiler): Introduce InterpolationConfig into component
1 parent dee1b77 commit 892f44f

27 files changed

+396
-117
lines changed

modules/@angular/compiler/src/assertions.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,24 @@ export function assertArrayOfStrings(identifier: string, value: any) {
1616
}
1717
}
1818
}
19+
20+
const INTERPOLATION_BLACKLIST_REGEXPS = [
21+
/^\s*$/g, // empty
22+
/[<>]/g, // html tag
23+
/^[\{\}]$/g, // i18n expansion
24+
];
25+
26+
export function assertInterpolationSymbols(identifier: string, value: any): void {
27+
if (isDevMode() && !isBlank(value) && (!isArray(value) || value.length != 2)) {
28+
throw new BaseException(`Expected '${identifier}' to be an array, [start, end].`);
29+
} else if (isDevMode() && !isBlank(value)) {
30+
const start = value[0] as string;
31+
const end = value[1] as string;
32+
// black list checking
33+
INTERPOLATION_BLACKLIST_REGEXPS.forEach(regexp => {
34+
if (regexp.test(start) || regexp.test(end)) {
35+
throw new BaseException(`['${start}', '${end}'] contains unusable interpolation symbol.`);
36+
}
37+
});
38+
}
39+
}

modules/@angular/compiler/src/compile_metadata.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -603,15 +603,18 @@ export class CompileTemplateMetadata {
603603
styleUrls: string[];
604604
animations: CompileAnimationEntryMetadata[];
605605
ngContentSelectors: string[];
606+
interpolation: [string, string];
606607
constructor(
607-
{encapsulation, template, templateUrl, styles, styleUrls, animations, ngContentSelectors}: {
608+
{encapsulation, template, templateUrl, styles, styleUrls, animations, ngContentSelectors,
609+
interpolation}: {
608610
encapsulation?: ViewEncapsulation,
609611
template?: string,
610612
templateUrl?: string,
611613
styles?: string[],
612614
styleUrls?: string[],
613615
ngContentSelectors?: string[],
614-
animations?: CompileAnimationEntryMetadata[]
616+
animations?: CompileAnimationEntryMetadata[],
617+
interpolation?: [string, string]
615618
} = {}) {
616619
this.encapsulation = encapsulation;
617620
this.template = template;
@@ -620,6 +623,10 @@ export class CompileTemplateMetadata {
620623
this.styleUrls = isPresent(styleUrls) ? styleUrls : [];
621624
this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : [];
622625
this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : [];
626+
if (isPresent(interpolation) && interpolation.length != 2) {
627+
throw new BaseException(`'interpolation' should have a start and an end symbol.`);
628+
}
629+
this.interpolation = interpolation;
623630
}
624631

625632
static fromJson(data: {[key: string]: any}): CompileTemplateMetadata {
@@ -634,7 +641,8 @@ export class CompileTemplateMetadata {
634641
styles: data['styles'],
635642
styleUrls: data['styleUrls'],
636643
animations: animations,
637-
ngContentSelectors: data['ngContentSelectors']
644+
ngContentSelectors: data['ngContentSelectors'],
645+
interpolation: data['interpolation']
638646
});
639647
}
640648

@@ -647,7 +655,8 @@ export class CompileTemplateMetadata {
647655
'styles': this.styles,
648656
'styleUrls': this.styleUrls,
649657
'animations': _objToJson(this.animations),
650-
'ngContentSelectors': this.ngContentSelectors
658+
'ngContentSelectors': this.ngContentSelectors,
659+
'interpolation': this.interpolation
651660
};
652661
}
653662
}

modules/@angular/compiler/src/directive_normalizer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ export class DirectiveNormalizer {
104104
styles: allResolvedStyles,
105105
styleUrls: allStyleAbsUrls,
106106
ngContentSelectors: visitor.ngContentSelectors,
107-
animations: templateMeta.animations
107+
animations: templateMeta.animations,
108+
interpolation: templateMeta.interpolation
108109
});
109110
}
110111
}

modules/@angular/compiler/src/expression_parser/parser.ts

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ import {Injectable} from '@angular/core';
22

33
import {ListWrapper} from '../facade/collection';
44
import {BaseException} from '../facade/exceptions';
5-
import {StringWrapper, isBlank, isPresent} from '../facade/lang';
5+
import {RegExpWrapper, StringWrapper, escapeRegExp, isBlank, isPresent} from '../facade/lang';
6+
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../interpolation_config';
67

78
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast';
89
import {$COLON, $COMMA, $LBRACE, $LBRACKET, $LPAREN, $PERIOD, $RBRACE, $RBRACKET, $RPAREN, $SEMICOLON, $SLASH, EOF, Lexer, Token, isIdentifier, isQuote} from './lexer';
910

1011

1112
var _implicitReceiver = new ImplicitReceiver();
12-
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
13-
var INTERPOLATION_REGEXP = /\{\{([\s\S]*?)\}\}/g;
1413

1514
class ParseException extends BaseException {
1615
constructor(message: string, input: string, errLocation: string, ctxLocation?: any) {
@@ -26,24 +25,40 @@ export class TemplateBindingParseResult {
2625
constructor(public templateBindings: TemplateBinding[], public warnings: string[]) {}
2726
}
2827

28+
function _createInterpolateRegExp(config: InterpolationConfig): RegExp {
29+
const regexp = escapeRegExp(config.start) + '([\\s\\S]*?)' + escapeRegExp(config.end);
30+
return RegExpWrapper.create(regexp, 'g');
31+
}
32+
2933
@Injectable()
3034
export class Parser {
35+
private _interpolationConfig: InterpolationConfig;
36+
3137
constructor(/** @internal */
3238
public _lexer: Lexer) {}
3339

34-
parseAction(input: string, location: any): ASTWithSource {
40+
parseAction(
41+
input: string, location: any,
42+
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
43+
this._interpolationConfig = interpolationConfig;
3544
this._checkNoInterpolation(input, location);
3645
var tokens = this._lexer.tokenize(this._stripComments(input));
3746
var ast = new _ParseAST(input, location, tokens, true).parseChain();
3847
return new ASTWithSource(ast, input, location);
3948
}
4049

41-
parseBinding(input: string, location: any): ASTWithSource {
50+
parseBinding(
51+
input: string, location: any,
52+
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
53+
this._interpolationConfig = interpolationConfig;
4254
var ast = this._parseBindingAst(input, location);
4355
return new ASTWithSource(ast, input, location);
4456
}
4557

46-
parseSimpleBinding(input: string, location: string): ASTWithSource {
58+
parseSimpleBinding(
59+
input: string, location: string,
60+
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
61+
this._interpolationConfig = interpolationConfig;
4762
var ast = this._parseBindingAst(input, location);
4863
if (!SimpleExpressionChecker.check(ast)) {
4964
throw new ParseException(
@@ -81,8 +96,11 @@ export class Parser {
8196
return new _ParseAST(input, location, tokens, false).parseTemplateBindings();
8297
}
8398

84-
parseInterpolation(input: string, location: any): ASTWithSource {
85-
let split = this.splitInterpolation(input, location);
99+
parseInterpolation(
100+
input: string, location: any,
101+
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
102+
this._interpolationConfig = interpolationConfig;
103+
let split = this.splitInterpolation(input, location, this._interpolationConfig);
86104
if (split == null) return null;
87105

88106
let expressions: AST[] = [];
@@ -96,8 +114,12 @@ export class Parser {
96114
return new ASTWithSource(new Interpolation(split.strings, expressions), input, location);
97115
}
98116

99-
splitInterpolation(input: string, location: string): SplitInterpolation {
100-
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
117+
splitInterpolation(
118+
input: string, location: string,
119+
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): SplitInterpolation {
120+
this._interpolationConfig = interpolationConfig;
121+
const regexp = _createInterpolateRegExp(this._interpolationConfig);
122+
const parts = StringWrapper.split(input, regexp);
101123
if (parts.length <= 1) {
102124
return null;
103125
}
@@ -147,18 +169,21 @@ export class Parser {
147169
}
148170

149171
private _checkNoInterpolation(input: string, location: any): void {
150-
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
172+
var regexp = _createInterpolateRegExp(this._interpolationConfig);
173+
var parts = StringWrapper.split(input, regexp);
151174
if (parts.length > 1) {
152175
throw new ParseException(
153-
'Got interpolation ({{}}) where expression was expected', input,
154-
`at column ${this._findInterpolationErrorColumn(parts, 1)} in`, location);
176+
`Got interpolation (${this._interpolationConfig.start}${this._interpolationConfig.end}) where expression was expected`,
177+
input, `at column ${this._findInterpolationErrorColumn(parts, 1)} in`, location);
155178
}
156179
}
157180

158181
private _findInterpolationErrorColumn(parts: string[], partInErrIdx: number): number {
159182
var errLocation = '';
160183
for (var j = 0; j < partInErrIdx; j++) {
161-
errLocation += j % 2 === 0 ? parts[j] : `{{${parts[j]}}}`;
184+
errLocation += j % 2 === 0 ?
185+
parts[j] :
186+
`${this._interpolationConfig.start}${parts[j]}${this._interpolationConfig.end}`;
162187
}
163188

164189
return errLocation.length;

modules/@angular/compiler/src/html_lexer.ts

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as chars from './chars';
22
import {ListWrapper} from './facade/collection';
33
import {NumberWrapper, StringWrapper, isBlank, isPresent} from './facade/lang';
44
import {HtmlTagContentType, NAMED_ENTITIES, getHtmlTagDefinition} from './html_tags';
5+
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config';
56
import {ParseError, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
67

78
export enum HtmlTokenType {
@@ -43,9 +44,11 @@ export class HtmlTokenizeResult {
4344
}
4445

4546
export function tokenizeHtml(
46-
sourceContent: string, sourceUrl: string,
47-
tokenizeExpansionForms: boolean = false): HtmlTokenizeResult {
48-
return new _HtmlTokenizer(new ParseSourceFile(sourceContent, sourceUrl), tokenizeExpansionForms)
47+
sourceContent: string, sourceUrl: string, tokenizeExpansionForms: boolean = false,
48+
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): HtmlTokenizeResult {
49+
return new _HtmlTokenizer(
50+
new ParseSourceFile(sourceContent, sourceUrl), tokenizeExpansionForms,
51+
interpolationConfig)
4952
.tokenize();
5053
}
5154

@@ -81,7 +84,9 @@ class _HtmlTokenizer {
8184
tokens: HtmlToken[] = [];
8285
errors: HtmlTokenError[] = [];
8386

84-
constructor(private file: ParseSourceFile, private tokenizeExpansionForms: boolean) {
87+
constructor(
88+
private file: ParseSourceFile, private tokenizeExpansionForms: boolean,
89+
private interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
8590
this._input = file.content;
8691
this._length = file.content.length;
8792
this._advance();
@@ -114,7 +119,8 @@ class _HtmlTokenizer {
114119
this._consumeTagOpen(start);
115120
}
116121
} else if (
117-
isExpansionFormStart(this._peek, this._nextPeek) && this.tokenizeExpansionForms) {
122+
isExpansionFormStart(this._input, this._index, this.interpolationConfig.start) &&
123+
this.tokenizeExpansionForms) {
118124
this._consumeExpansionFormStart();
119125

120126
} else if (
@@ -232,16 +238,12 @@ class _HtmlTokenizer {
232238
}
233239

234240
private _attemptStr(chars: string): boolean {
235-
var indexBeforeAttempt = this._index;
236-
var columnBeforeAttempt = this._column;
237-
var lineBeforeAttempt = this._line;
241+
const initialPosition = this._savePosition();
238242
for (var i = 0; i < chars.length; i++) {
239243
if (!this._attemptCharCode(StringWrapper.charCodeAt(chars, i))) {
240244
// If attempting to parse the string fails, we want to reset the parser
241245
// to where it was before the attempt
242-
this._index = indexBeforeAttempt;
243-
this._column = columnBeforeAttempt;
244-
this._line = lineBeforeAttempt;
246+
this._restorePosition(initialPosition);
245247
return false;
246248
}
247249
}
@@ -558,35 +560,38 @@ class _HtmlTokenizer {
558560
var parts: string[] = [];
559561
let interpolation = false;
560562

561-
if (this._peek === chars.$LBRACE && this._nextPeek === chars.$LBRACE) {
562-
parts.push(this._readChar(true));
563-
parts.push(this._readChar(true));
564-
interpolation = true;
565-
} else {
566-
parts.push(this._readChar(true));
567-
}
568-
569-
while (!this._isTextEnd(interpolation)) {
570-
if (this._peek === chars.$LBRACE && this._nextPeek === chars.$LBRACE) {
571-
parts.push(this._readChar(true));
572-
parts.push(this._readChar(true));
563+
do {
564+
const savedPos = this._savePosition();
565+
// _attemptStr advances the position when it is true.
566+
// To push interpolation symbols, we have to reset it.
567+
if (this._attemptStr(this.interpolationConfig.start)) {
568+
this._restorePosition(savedPos);
569+
for (let i = 0; i < this.interpolationConfig.start.length; i++) {
570+
parts.push(this._readChar(true));
571+
}
573572
interpolation = true;
574-
} else if (
575-
this._peek === chars.$RBRACE && this._nextPeek === chars.$RBRACE && interpolation) {
576-
parts.push(this._readChar(true));
577-
parts.push(this._readChar(true));
573+
} else if (this._attemptStr(this.interpolationConfig.end) && interpolation) {
574+
this._restorePosition(savedPos);
575+
for (let i = 0; i < this.interpolationConfig.end.length; i++) {
576+
parts.push(this._readChar(true));
577+
}
578578
interpolation = false;
579579
} else {
580+
this._restorePosition(savedPos);
580581
parts.push(this._readChar(true));
581582
}
582-
}
583+
} while (!this._isTextEnd(interpolation));
584+
583585
this._endToken([this._processCarriageReturns(parts.join(''))]);
584586
}
585587

586588
private _isTextEnd(interpolation: boolean): boolean {
587589
if (this._peek === chars.$LT || this._peek === chars.$EOF) return true;
588590
if (this.tokenizeExpansionForms) {
589-
if (isExpansionFormStart(this._peek, this._nextPeek)) return true;
591+
const savedPos = this._savePosition();
592+
if (isExpansionFormStart(this._input, this._index, this.interpolationConfig.start))
593+
return true;
594+
this._restorePosition(savedPos);
590595
if (this._peek === chars.$RBRACE && !interpolation &&
591596
(this._isInExpansionCase() || this._isInExpansionForm()))
592597
return true;
@@ -655,8 +660,11 @@ function isNamedEntityEnd(code: number): boolean {
655660
return code == chars.$SEMICOLON || code == chars.$EOF || !isAsciiLetter(code);
656661
}
657662

658-
function isExpansionFormStart(peek: number, nextPeek: number): boolean {
659-
return peek === chars.$LBRACE && nextPeek != chars.$LBRACE;
663+
function isExpansionFormStart(input: string, offset: number, interpolationStart: string): boolean {
664+
const substr = input.substring(offset);
665+
return StringWrapper.charCodeAt(substr, 0) === chars.$LBRACE &&
666+
StringWrapper.charCodeAt(substr, 1) !== chars.$LBRACE &&
667+
!substr.startsWith(interpolationStart);
660668
}
661669

662670
function isExpansionCaseStart(peek: number): boolean {

0 commit comments

Comments
 (0)