fix(HtmlParser): correctly propagate the interpolation config across layers

This commit is contained in:
Victor Berchet 2016-06-22 17:25:42 -07:00
parent da8eb9f8b8
commit 25e070dd65
21 changed files with 258 additions and 172 deletions

View File

@ -126,7 +126,8 @@ export class CodeGenerator {
const reflectorHost = new ReflectorHost(program, compilerHost, options, reflectorHostContext); const reflectorHost = new ReflectorHost(program, compilerHost, options, reflectorHostContext);
const staticReflector = new StaticReflector(reflectorHost); const staticReflector = new StaticReflector(reflectorHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector); StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser = new HtmlParser(); const expressionParser = new Parser(new Lexer());
const htmlParser = new HtmlParser(expressionParser);
const config = new compiler.CompilerConfig({ const config = new compiler.CompilerConfig({
genDebugInfo: options.debug === true, genDebugInfo: options.debug === true,
defaultEncapsulation: ViewEncapsulation.Emulated, defaultEncapsulation: ViewEncapsulation.Emulated,
@ -134,9 +135,8 @@ export class CodeGenerator {
useJit: false useJit: false
}); });
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config); const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
const parser = new Parser(new Lexer());
const tmplParser = new TemplateParser( const tmplParser = new TemplateParser(
parser, new DomElementSchemaRegistry(), htmlParser, expressionParser, new DomElementSchemaRegistry(), htmlParser,
/*console*/ null, []); /*console*/ null, []);
const resolver = new CompileMetadataResolver( const resolver = new CompileMetadataResolver(
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector), new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),

View File

@ -137,7 +137,8 @@ class Extractor {
const reflectorHost = new ReflectorHost(program, compilerHost, options); const reflectorHost = new ReflectorHost(program, compilerHost, options);
const staticReflector = new StaticReflector(reflectorHost); const staticReflector = new StaticReflector(reflectorHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector); StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser = new HtmlParser(); const expressionParser = new Parser(new Lexer());
const htmlParser = new HtmlParser(expressionParser);
const config = new compiler.CompilerConfig({ const config = new compiler.CompilerConfig({
genDebugInfo: true, genDebugInfo: true,
defaultEncapsulation: ViewEncapsulation.Emulated, defaultEncapsulation: ViewEncapsulation.Emulated,
@ -145,13 +146,12 @@ class Extractor {
useJit: false useJit: false
}); });
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config); const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
const parser = new Parser(new Lexer());
const resolver = new CompileMetadataResolver( const resolver = new CompileMetadataResolver(
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector), new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
new compiler.ViewResolver(staticReflector), config, staticReflector); new compiler.ViewResolver(staticReflector), config, staticReflector);
// TODO(vicb): handle implicit // TODO(vicb): handle implicit
const extractor = new MessageExtractor(htmlParser, parser, [], {}); const extractor = new MessageExtractor(htmlParser, expressionParser, [], {});
return new Extractor( return new Extractor(
options, program, compilerHost, staticReflector, resolver, normalizer, reflectorHost, options, program, compilerHost, staticReflector, resolver, normalizer, reflectorHost,

View File

@ -8,19 +8,18 @@
import {isDevMode} from '@angular/core'; import {isDevMode} from '@angular/core';
import {BaseException} from '../src/facade/exceptions'; import {isArray, isBlank, isPresent, isString} from '../src/facade/lang';
import {isArray, isBlank, isString} from '../src/facade/lang';
export function assertArrayOfStrings(identifier: string, value: any) { export function assertArrayOfStrings(identifier: string, value: any) {
if (!isDevMode() || isBlank(value)) { if (!isDevMode() || isBlank(value)) {
return; return;
} }
if (!isArray(value)) { if (!isArray(value)) {
throw new BaseException(`Expected '${identifier}' to be an array of strings.`); throw new Error(`Expected '${identifier}' to be an array of strings.`);
} }
for (var i = 0; i < value.length; i += 1) { for (var i = 0; i < value.length; i += 1) {
if (!isString(value[i])) { if (!isString(value[i])) {
throw new BaseException(`Expected '${identifier}' to be an array of strings.`); throw new Error(`Expected '${identifier}' to be an array of strings.`);
} }
} }
} }
@ -33,15 +32,15 @@ const INTERPOLATION_BLACKLIST_REGEXPS = [
]; ];
export function assertInterpolationSymbols(identifier: string, value: any): void { export function assertInterpolationSymbols(identifier: string, value: any): void {
if (isDevMode() && !isBlank(value) && (!isArray(value) || value.length != 2)) { if (isPresent(value) && !(isArray(value) && value.length == 2)) {
throw new BaseException(`Expected '${identifier}' to be an array, [start, end].`); throw new Error(`Expected '${identifier}' to be an array, [start, end].`);
} else if (isDevMode() && !isBlank(value)) { } else if (isDevMode() && !isBlank(value)) {
const start = value[0] as string; const start = value[0] as string;
const end = value[1] as string; const end = value[1] as string;
// black list checking // black list checking
INTERPOLATION_BLACKLIST_REGEXPS.forEach(regexp => { INTERPOLATION_BLACKLIST_REGEXPS.forEach(regexp => {
if (regexp.test(start) || regexp.test(end)) { if (regexp.test(start) || regexp.test(end)) {
throw new BaseException(`['${start}', '${end}'] contains unusable interpolation symbol.`); throw new Error(`['${start}', '${end}'] contains unusable interpolation symbol.`);
} }
}); });
} }

View File

@ -768,23 +768,17 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
} }
return new CompileDirectiveMetadata({ return new CompileDirectiveMetadata({
type: type, type,
isComponent: normalizeBool(isComponent), isComponent: normalizeBool(isComponent), selector, exportAs, changeDetection,
selector: selector,
exportAs: exportAs,
changeDetection: changeDetection,
inputs: inputsMap, inputs: inputsMap,
outputs: outputsMap, outputs: outputsMap, hostListeners, hostProperties, hostAttributes,
hostListeners: hostListeners,
hostProperties: hostProperties,
hostAttributes: hostAttributes,
lifecycleHooks: isPresent(lifecycleHooks) ? lifecycleHooks : [], lifecycleHooks: isPresent(lifecycleHooks) ? lifecycleHooks : [],
providers: providers, providers,
viewProviders: viewProviders, viewProviders,
queries: queries, queries,
viewQueries: viewQueries, viewQueries,
precompile: precompile, precompile,
template: template template,
}); });
} }
type: CompileTypeMetadata; type: CompileTypeMetadata;
@ -803,8 +797,8 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
queries: CompileQueryMetadata[]; queries: CompileQueryMetadata[];
viewQueries: CompileQueryMetadata[]; viewQueries: CompileQueryMetadata[];
precompile: CompileTypeMetadata[]; precompile: CompileTypeMetadata[];
template: CompileTemplateMetadata; template: CompileTemplateMetadata;
constructor( constructor(
{type, isComponent, selector, exportAs, changeDetection, inputs, outputs, hostListeners, {type, isComponent, selector, exportAs, changeDetection, inputs, outputs, hostListeners,
hostProperties, hostAttributes, lifecycleHooks, providers, viewProviders, queries, hostProperties, hostAttributes, lifecycleHooks, providers, viewProviders, queries,
@ -827,7 +821,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
queries?: CompileQueryMetadata[], queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[], viewQueries?: CompileQueryMetadata[],
precompile?: CompileTypeMetadata[], precompile?: CompileTypeMetadata[],
template?: CompileTemplateMetadata template?: CompileTemplateMetadata,
} = {}) { } = {}) {
this.type = type; this.type = type;
this.isComponent = isComponent; this.isComponent = isComponent;

View File

@ -7,12 +7,9 @@
*/ */
import {Injectable, ViewEncapsulation} from '@angular/core'; import {Injectable, ViewEncapsulation} from '@angular/core';
import {PromiseWrapper} from '../src/facade/async';
import {MapWrapper} from '../src/facade/collection'; import {MapWrapper} from '../src/facade/collection';
import {BaseException} from '../src/facade/exceptions'; import {BaseException} from '../src/facade/exceptions';
import {isBlank, isPresent} from '../src/facade/lang'; import {isBlank, isPresent} from '../src/facade/lang';
import {CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata'; import {CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import {HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from './html_ast'; import {HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from './html_ast';
@ -22,6 +19,7 @@ import {PreparsedElementType, preparseElement} from './template_preparser';
import {UrlResolver} from './url_resolver'; import {UrlResolver} from './url_resolver';
import {SyncAsyncResult} from './util'; import {SyncAsyncResult} from './util';
import {XHR} from './xhr'; import {XHR} from './xhr';
import {InterpolationConfig} from './interpolation_config';
@Injectable() @Injectable()
export class DirectiveNormalizer { export class DirectiveNormalizer {
@ -99,7 +97,9 @@ export class DirectiveNormalizer {
normalizeLoadedTemplate( normalizeLoadedTemplate(
directiveType: CompileTypeMetadata, templateMeta: CompileTemplateMetadata, template: string, directiveType: CompileTypeMetadata, templateMeta: CompileTemplateMetadata, template: string,
templateAbsUrl: string): CompileTemplateMetadata { templateAbsUrl: string): CompileTemplateMetadata {
const rootNodesAndErrors = this._htmlParser.parse(template, directiveType.name); const interpolationConfig = InterpolationConfig.fromArray(templateMeta.interpolation);
const rootNodesAndErrors =
this._htmlParser.parse(template, directiveType.name, false, interpolationConfig);
if (rootNodesAndErrors.errors.length > 0) { if (rootNodesAndErrors.errors.length > 0) {
const errorString = rootNodesAndErrors.errors.join('\n'); const errorString = rootNodesAndErrors.errors.join('\n');
throw new BaseException(`Template parse errors:\n${errorString}`); throw new BaseException(`Template parse errors:\n${errorString}`);
@ -127,7 +127,7 @@ export class DirectiveNormalizer {
encapsulation = ViewEncapsulation.None; encapsulation = ViewEncapsulation.None;
} }
return new CompileTemplateMetadata({ return new CompileTemplateMetadata({
encapsulation: encapsulation, encapsulation,
template: template, template: template,
templateUrl: templateAbsUrl, templateUrl: templateAbsUrl,
styles: allStyles, styles: allStyles,

View File

@ -8,7 +8,6 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import * as chars from '../chars'; import * as chars from '../chars';
import {SetWrapper} from '../facade/collection';
import {BaseException} from '../facade/exceptions'; import {BaseException} from '../facade/exceptions';
import {NumberWrapper, StringJoiner, StringWrapper, isPresent} from '../facade/lang'; import {NumberWrapper, StringJoiner, StringWrapper, isPresent} from '../facade/lang';

View File

@ -37,8 +37,7 @@ function _createInterpolateRegExp(config: InterpolationConfig): RegExp {
export class Parser { export class Parser {
private errors: ParserError[] = []; private errors: ParserError[] = [];
constructor(/** @internal */ constructor(private _lexer: Lexer) {}
public _lexer: Lexer) {}
parseAction( parseAction(
input: string, location: any, input: string, location: any,

View File

@ -7,6 +7,7 @@
*/ */
import * as chars from './chars'; import * as chars from './chars';
import {Parser as ExpressionParser} from './expression_parser/parser';
import {NumberWrapper, StringWrapper, isBlank, isPresent} from './facade/lang'; import {NumberWrapper, StringWrapper, isBlank, isPresent} from './facade/lang';
import {HtmlTagContentType, NAMED_ENTITIES, getHtmlTagDefinition} from './html_tags'; import {HtmlTagContentType, NAMED_ENTITIES, getHtmlTagDefinition} from './html_tags';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config';
@ -32,6 +33,7 @@ export enum HtmlTokenType {
EXPANSION_CASE_EXP_START, EXPANSION_CASE_EXP_START,
EXPANSION_CASE_EXP_END, EXPANSION_CASE_EXP_END,
EXPANSION_FORM_END, EXPANSION_FORM_END,
INTERPOLATION,
EOF EOF
} }
@ -51,26 +53,27 @@ export class HtmlTokenizeResult {
} }
export function tokenizeHtml( export function tokenizeHtml(
sourceContent: string, sourceUrl: string, tokenizeExpansionForms: boolean = false, sourceContent: string, sourceUrl: string, parser: ExpressionParser,
tokenizeExpansionForms: boolean = false,
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): HtmlTokenizeResult { interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): HtmlTokenizeResult {
return new _HtmlTokenizer( return new _HtmlTokenizer(
new ParseSourceFile(sourceContent, sourceUrl), tokenizeExpansionForms, new ParseSourceFile(sourceContent, sourceUrl), tokenizeExpansionForms, parser,
interpolationConfig) interpolationConfig)
.tokenize(); .tokenize();
} }
var CR_OR_CRLF_REGEXP = /\r\n?/g; const _CR_OR_CRLF_REGEXP = /\r\n?/g;
function unexpectedCharacterErrorMsg(charCode: number): string { function _unexpectedCharacterErrorMsg(charCode: number): string {
var char = charCode === chars.$EOF ? 'EOF' : StringWrapper.fromCharCode(charCode); var char = charCode === chars.$EOF ? 'EOF' : StringWrapper.fromCharCode(charCode);
return `Unexpected character "${char}"`; return `Unexpected character "${char}"`;
} }
function unknownEntityErrorMsg(entitySrc: string): string { function _unknownEntityErrorMsg(entitySrc: string): string {
return `Unknown entity "${entitySrc}" - use the "&#<decimal>;" or "&#x<hex>;" syntax`; return `Unknown entity "${entitySrc}" - use the "&#<decimal>;" or "&#x<hex>;" syntax`;
} }
class ControlFlowError { class _ControlFlowError {
constructor(public error: HtmlTokenError) {} constructor(public error: HtmlTokenError) {}
} }
@ -92,11 +95,18 @@ class _HtmlTokenizer {
tokens: HtmlToken[] = []; tokens: HtmlToken[] = [];
errors: HtmlTokenError[] = []; errors: HtmlTokenError[] = [];
/**
* @param _file The html source
* @param _tokenizeIcu Whether to tokenize ICU messages (considered as text nodes when false)
* @param _expressionParser Used to check syntax of interpolations
* @param _interpolationConfig
*/
constructor( constructor(
private file: ParseSourceFile, private tokenizeExpansionForms: boolean, private _file: ParseSourceFile, private _tokenizeIcu: boolean,
private interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG) { private _expressionParser: ExpressionParser,
this._input = file.content; private _interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
this._length = file.content.length; this._input = _file.content;
this._length = _file.content.length;
this._advance(); this._advance();
} }
@ -105,7 +115,7 @@ class _HtmlTokenizer {
// In order to keep the original position in the source, we can not // In order to keep the original position in the source, we can not
// pre-process it. // pre-process it.
// Instead CRs are processed right before instantiating the tokens. // Instead CRs are processed right before instantiating the tokens.
return StringWrapper.replaceAll(content, CR_OR_CRLF_REGEXP, '\n'); return StringWrapper.replaceAll(content, _CR_OR_CRLF_REGEXP, '\n');
} }
tokenize(): HtmlTokenizeResult { tokenize(): HtmlTokenizeResult {
@ -126,31 +136,11 @@ class _HtmlTokenizer {
} else { } else {
this._consumeTagOpen(start); this._consumeTagOpen(start);
} }
} else if ( } else if (!this._tokenizeIcu || !this._tokenizeExpansionForm()) {
isExpansionFormStart(this._input, this._index, this.interpolationConfig.start) &&
this.tokenizeExpansionForms) {
this._consumeExpansionFormStart();
} else if (
isExpansionCaseStart(this._peek) && this._isInExpansionForm() &&
this.tokenizeExpansionForms) {
this._consumeExpansionCaseStart();
} else if (
this._peek === chars.$RBRACE && this._isInExpansionCase() &&
this.tokenizeExpansionForms) {
this._consumeExpansionCaseEnd();
} else if (
this._peek === chars.$RBRACE && this._isInExpansionForm() &&
this.tokenizeExpansionForms) {
this._consumeExpansionFormEnd();
} else {
this._consumeText(); this._consumeText();
} }
} catch (e) { } catch (e) {
if (e instanceof ControlFlowError) { if (e instanceof _ControlFlowError) {
this.errors.push(e.error); this.errors.push(e.error);
} else { } else {
throw e; throw e;
@ -162,8 +152,38 @@ class _HtmlTokenizer {
return new HtmlTokenizeResult(mergeTextTokens(this.tokens), this.errors); return new HtmlTokenizeResult(mergeTextTokens(this.tokens), this.errors);
} }
/**
* @returns {boolean} whether an ICU token has been created
* @internal
*/
private _tokenizeExpansionForm(): boolean {
if (isExpansionFormStart(this._input, this._index, this._interpolationConfig.start)) {
this._consumeExpansionFormStart();
return true;
}
if (isExpansionCaseStart(this._peek) && this._isInExpansionForm()) {
this._consumeExpansionCaseStart();
return true;
}
if (this._peek === chars.$RBRACE) {
if (this._isInExpansionCase()) {
this._consumeExpansionCaseEnd();
return true;
}
if (this._isInExpansionForm()) {
this._consumeExpansionFormEnd();
return true;
}
}
return false;
}
private _getLocation(): ParseLocation { private _getLocation(): ParseLocation {
return new ParseLocation(this.file, this._index, this._line, this._column); return new ParseLocation(this._file, this._index, this._line, this._column);
} }
private _getSpan(start?: ParseLocation, end?: ParseLocation): ParseSourceSpan { private _getSpan(start?: ParseLocation, end?: ParseLocation): ParseSourceSpan {
@ -196,16 +216,16 @@ class _HtmlTokenizer {
return token; return token;
} }
private _createError(msg: string, span: ParseSourceSpan): ControlFlowError { private _createError(msg: string, span: ParseSourceSpan): _ControlFlowError {
var error = new HtmlTokenError(msg, this._currentTokenType, span); var error = new HtmlTokenError(msg, this._currentTokenType, span);
this._currentTokenStart = null; this._currentTokenStart = null;
this._currentTokenType = null; this._currentTokenType = null;
return new ControlFlowError(error); return new _ControlFlowError(error);
} }
private _advance() { private _advance() {
if (this._index >= this._length) { if (this._index >= this._length) {
throw this._createError(unexpectedCharacterErrorMsg(chars.$EOF), this._getSpan()); throw this._createError(_unexpectedCharacterErrorMsg(chars.$EOF), this._getSpan());
} }
if (this._peek === chars.$LF) { if (this._peek === chars.$LF) {
this._line++; this._line++;
@ -241,7 +261,7 @@ class _HtmlTokenizer {
var location = this._getLocation(); var location = this._getLocation();
if (!this._attemptCharCode(charCode)) { if (!this._attemptCharCode(charCode)) {
throw this._createError( throw this._createError(
unexpectedCharacterErrorMsg(this._peek), this._getSpan(location, location)); _unexpectedCharacterErrorMsg(this._peek), this._getSpan(location, location));
} }
} }
@ -274,7 +294,7 @@ class _HtmlTokenizer {
private _requireStr(chars: string) { private _requireStr(chars: string) {
var location = this._getLocation(); var location = this._getLocation();
if (!this._attemptStr(chars)) { if (!this._attemptStr(chars)) {
throw this._createError(unexpectedCharacterErrorMsg(this._peek), this._getSpan(location)); throw this._createError(_unexpectedCharacterErrorMsg(this._peek), this._getSpan(location));
} }
} }
@ -288,7 +308,8 @@ class _HtmlTokenizer {
var start = this._getLocation(); var start = this._getLocation();
this._attemptCharCodeUntilFn(predicate); this._attemptCharCodeUntilFn(predicate);
if (this._index - start.offset < len) { if (this._index - start.offset < len) {
throw this._createError(unexpectedCharacterErrorMsg(this._peek), this._getSpan(start, start)); throw this._createError(
_unexpectedCharacterErrorMsg(this._peek), this._getSpan(start, start));
} }
} }
@ -316,7 +337,7 @@ class _HtmlTokenizer {
let numberStart = this._getLocation().offset; let numberStart = this._getLocation().offset;
this._attemptCharCodeUntilFn(isDigitEntityEnd); this._attemptCharCodeUntilFn(isDigitEntityEnd);
if (this._peek != chars.$SEMICOLON) { if (this._peek != chars.$SEMICOLON) {
throw this._createError(unexpectedCharacterErrorMsg(this._peek), this._getSpan()); throw this._createError(_unexpectedCharacterErrorMsg(this._peek), this._getSpan());
} }
this._advance(); this._advance();
let strNum = this._input.substring(numberStart, this._index - 1); let strNum = this._input.substring(numberStart, this._index - 1);
@ -325,7 +346,7 @@ class _HtmlTokenizer {
return StringWrapper.fromCharCode(charCode); return StringWrapper.fromCharCode(charCode);
} catch (e) { } catch (e) {
let entity = this._input.substring(start.offset + 1, this._index - 1); let entity = this._input.substring(start.offset + 1, this._index - 1);
throw this._createError(unknownEntityErrorMsg(entity), this._getSpan(start)); throw this._createError(_unknownEntityErrorMsg(entity), this._getSpan(start));
} }
} else { } else {
let startPosition = this._savePosition(); let startPosition = this._savePosition();
@ -338,7 +359,7 @@ class _HtmlTokenizer {
let name = this._input.substring(start.offset + 1, this._index - 1); let name = this._input.substring(start.offset + 1, this._index - 1);
let char = (NAMED_ENTITIES as any)[name]; let char = (NAMED_ENTITIES as any)[name];
if (isBlank(char)) { if (isBlank(char)) {
throw this._createError(unknownEntityErrorMsg(name), this._getSpan(start)); throw this._createError(_unknownEntityErrorMsg(name), this._getSpan(start));
} }
return char; return char;
} }
@ -416,7 +437,7 @@ class _HtmlTokenizer {
let lowercaseTagName: string; let lowercaseTagName: string;
try { try {
if (!chars.isAsciiLetter(this._peek)) { if (!chars.isAsciiLetter(this._peek)) {
throw this._createError(unexpectedCharacterErrorMsg(this._peek), this._getSpan()); throw this._createError(_unexpectedCharacterErrorMsg(this._peek), this._getSpan());
} }
var nameStart = this._index; var nameStart = this._index;
this._consumeTagOpenStart(start); this._consumeTagOpenStart(start);
@ -433,7 +454,7 @@ class _HtmlTokenizer {
} }
this._consumeTagOpenEnd(); this._consumeTagOpenEnd();
} catch (e) { } catch (e) {
if (e instanceof ControlFlowError) { if (e instanceof _ControlFlowError) {
// When the start tag is invalid, assume we want a "<" // When the start tag is invalid, assume we want a "<"
this._restorePosition(savedPos); this._restorePosition(savedPos);
// Back to back text tokens are merged at the end // Back to back text tokens are merged at the end
@ -573,11 +594,11 @@ class _HtmlTokenizer {
var parts: string[] = []; var parts: string[] = [];
do { do {
if (this._attemptStr(this.interpolationConfig.start)) { if (this._attemptStr(this._interpolationConfig.start)) {
parts.push(this.interpolationConfig.start); parts.push(this._interpolationConfig.start);
this._inInterpolation = true; this._inInterpolation = true;
} else if (this._attemptStr(this.interpolationConfig.end) && this._inInterpolation) { } else if (this._attemptStr(this._interpolationConfig.end) && this._inInterpolation) {
parts.push(this.interpolationConfig.end); parts.push(this._interpolationConfig.end);
this._inInterpolation = false; this._inInterpolation = false;
} else { } else {
parts.push(this._readChar(true)); parts.push(this._readChar(true));
@ -592,8 +613,8 @@ class _HtmlTokenizer {
return true; return true;
} }
if (this.tokenizeExpansionForms) { if (this._tokenizeIcu) {
if (isExpansionFormStart(this._input, this._index, this.interpolationConfig.start)) { if (isExpansionFormStart(this._input, this._index, this._interpolationConfig.start)) {
// start of an expansion form // start of an expansion form
return true; return true;
} }

View File

@ -13,6 +13,8 @@ import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlCommentAst, HtmlElementAst, HtmlE
import {HtmlToken, HtmlTokenType, tokenizeHtml} from './html_lexer'; import {HtmlToken, HtmlTokenType, tokenizeHtml} from './html_lexer';
import {ParseError, ParseSourceSpan} from './parse_util'; import {ParseError, ParseSourceSpan} from './parse_util';
import {getHtmlTagDefinition, getNsPrefix, mergeNsAndName} from './html_tags'; import {getHtmlTagDefinition, getNsPrefix, mergeNsAndName} from './html_tags';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config';
import {Parser as ExpressionParser} from './expression_parser/parser';
export class HtmlTreeError extends ParseError { export class HtmlTreeError extends ParseError {
static create(elementName: string, span: ParseSourceSpan, msg: string): HtmlTreeError { static create(elementName: string, span: ParseSourceSpan, msg: string): HtmlTreeError {
@ -28,9 +30,14 @@ export class HtmlParseTreeResult {
@Injectable() @Injectable()
export class HtmlParser { export class HtmlParser {
parse(sourceContent: string, sourceUrl: string, parseExpansionForms: boolean = false): constructor(public _expressionParser: ExpressionParser) {}
parse(
sourceContent: string, sourceUrl: string, parseExpansionForms: boolean = false,
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG):
HtmlParseTreeResult { HtmlParseTreeResult {
var tokensAndErrors = tokenizeHtml(sourceContent, sourceUrl, parseExpansionForms); var tokensAndErrors = tokenizeHtml(
sourceContent, sourceUrl, this._expressionParser, parseExpansionForms, interpolationConfig);
var treeAndErrors = new TreeBuilder(tokensAndErrors.tokens).build(); var treeAndErrors = new TreeBuilder(tokensAndErrors.tokens).build();
return new HtmlParseTreeResult( return new HtmlParseTreeResult(
treeAndErrors.rootNodes, treeAndErrors.rootNodes,

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Parser} from '../expression_parser/parser'; import {Parser as ExpressionParser} from '../expression_parser/parser';
import {ListWrapper, StringMapWrapper} from '../facade/collection'; import {ListWrapper, StringMapWrapper} from '../facade/collection';
import {BaseException} from '../facade/exceptions'; import {BaseException} from '../facade/exceptions';
import {NumberWrapper, RegExpWrapper, isPresent} from '../facade/lang'; import {NumberWrapper, RegExpWrapper, isPresent} from '../facade/lang';
@ -55,9 +55,9 @@ export class I18nHtmlParser implements HtmlParser {
private _interpolationConfig: InterpolationConfig; private _interpolationConfig: InterpolationConfig;
constructor( constructor(
private _htmlParser: HtmlParser, private _parser: Parser, private _messagesContent: string, private _htmlParser: HtmlParser, public _expressionParser: ExpressionParser,
private _messages: {[key: string]: HtmlAst[]}, private _implicitTags: string[], private _messagesContent: string, private _messages: {[key: string]: HtmlAst[]},
private _implicitAttrs: {[k: string]: string[]}) {} private _implicitTags: string[], private _implicitAttrs: {[k: string]: string[]}) {}
parse( parse(
sourceContent: string, sourceUrl: string, parseExpansionForms: boolean = false, sourceContent: string, sourceUrl: string, parseExpansionForms: boolean = false,
@ -66,19 +66,19 @@ export class I18nHtmlParser implements HtmlParser {
this.errors = []; this.errors = [];
this._interpolationConfig = interpolationConfig; this._interpolationConfig = interpolationConfig;
let res = this._htmlParser.parse(sourceContent, sourceUrl, true); let res = this._htmlParser.parse(sourceContent, sourceUrl, true, interpolationConfig);
if (res.errors.length > 0) { if (res.errors.length > 0) {
return res; return res;
} else { }
let expanded = expandNodes(res.rootNodes);
let nodes = this._recurse(expanded.nodes); const expanded = expandNodes(res.rootNodes);
const nodes = this._recurse(expanded.nodes);
this.errors.push(...expanded.errors); this.errors.push(...expanded.errors);
return this.errors.length > 0 ? new HtmlParseTreeResult([], this.errors) : return this.errors.length > 0 ? new HtmlParseTreeResult([], this.errors) :
new HtmlParseTreeResult(nodes, []); new HtmlParseTreeResult(nodes, []);
} }
}
private _processI18nPart(part: Part): HtmlAst[] { private _processI18nPart(part: Part): HtmlAst[] {
try { try {
@ -94,7 +94,7 @@ export class I18nHtmlParser implements HtmlParser {
} }
private _mergeI18Part(part: Part): HtmlAst[] { private _mergeI18Part(part: Part): HtmlAst[] {
let message = part.createMessage(this._parser, this._interpolationConfig); let message = part.createMessage(this._expressionParser, this._interpolationConfig);
let messageId = id(message); let messageId = id(message);
if (!StringMapWrapper.contains(this._messages, messageId)) { if (!StringMapWrapper.contains(this._messages, messageId)) {
throw new I18nError( throw new I18nError(
@ -200,7 +200,7 @@ export class I18nHtmlParser implements HtmlParser {
} }
private _mergeTextInterpolation(t: HtmlElementAst, originalNode: HtmlTextAst): HtmlTextAst { private _mergeTextInterpolation(t: HtmlElementAst, originalNode: HtmlTextAst): HtmlTextAst {
let split = this._parser.splitInterpolation( let split = this._expressionParser.splitInterpolation(
originalNode.value, originalNode.sourceSpan.toString(), this._interpolationConfig); originalNode.value, originalNode.sourceSpan.toString(), this._interpolationConfig);
let exps = isPresent(split) ? split.expressions : []; let exps = isPresent(split) ? split.expressions : [];
@ -237,9 +237,10 @@ export class I18nHtmlParser implements HtmlParser {
res.push(attr); res.push(attr);
return; return;
} }
message = messageFromAttribute(this._parser, this._interpolationConfig, attr); message = messageFromAttribute(this._expressionParser, this._interpolationConfig, attr);
} else { } else {
message = messageFromI18nAttribute(this._parser, this._interpolationConfig, el, i18ns[0]); message = messageFromI18nAttribute(
this._expressionParser, this._interpolationConfig, el, i18ns[0]);
} }
let messageId = id(message); let messageId = id(message);
@ -258,7 +259,7 @@ export class I18nHtmlParser implements HtmlParser {
} }
private _replaceInterpolationInAttr(attr: HtmlAttrAst, msg: HtmlAst[]): string { private _replaceInterpolationInAttr(attr: HtmlAttrAst, msg: HtmlAst[]): string {
let split = this._parser.splitInterpolation( let split = this._expressionParser.splitInterpolation(
attr.value, attr.sourceSpan.toString(), this._interpolationConfig); attr.value, attr.sourceSpan.toString(), this._interpolationConfig);
let exps = isPresent(split) ? split.expressions : []; let exps = isPresent(split) ? split.expressions : [];

View File

@ -6,17 +6,19 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Parser} from '../expression_parser/parser'; import {Parser as ExpressionParser} from '../expression_parser/parser';
import {StringMapWrapper} from '../facade/collection'; import {StringMapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {HtmlAst, HtmlElementAst} from '../html_ast'; import {HtmlAst, HtmlElementAst} from '../html_ast';
import {HtmlParser} from '../html_parser'; import {HtmlParser} from '../html_parser';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../interpolation_config'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../interpolation_config';
import {ParseError} from '../parse_util'; import {ParseError} from '../parse_util';
import {Message, id} from './message'; import {Message, id} from './message';
import {I18N_ATTR_PREFIX, I18nError, Part, messageFromAttribute, messageFromI18nAttribute, partition} from './shared'; import {I18N_ATTR_PREFIX, I18nError, Part, messageFromAttribute, messageFromI18nAttribute, partition} from './shared';
/** /**
* All messages extracted from a template. * All messages extracted from a template.
*/ */
@ -101,8 +103,8 @@ export class MessageExtractor {
private _errors: ParseError[]; private _errors: ParseError[];
constructor( constructor(
private _htmlParser: HtmlParser, private _parser: Parser, private _implicitTags: string[], private _htmlParser: HtmlParser, private _expressionParser: ExpressionParser,
private _implicitAttrs: {[k: string]: string[]}) {} private _implicitTags: string[], private _implicitAttrs: {[k: string]: string[]}) {}
extract( extract(
template: string, sourceUrl: string, template: string, sourceUrl: string,
@ -110,7 +112,7 @@ export class MessageExtractor {
this._messages = []; this._messages = [];
this._errors = []; this._errors = [];
const res = this._htmlParser.parse(template, sourceUrl, true); const res = this._htmlParser.parse(template, sourceUrl, true, interpolationConfig);
if (res.errors.length == 0) { if (res.errors.length == 0) {
this._recurse(res.rootNodes, interpolationConfig); this._recurse(res.rootNodes, interpolationConfig);
@ -121,7 +123,7 @@ export class MessageExtractor {
private _extractMessagesFromPart(part: Part, interpolationConfig: InterpolationConfig): void { private _extractMessagesFromPart(part: Part, interpolationConfig: InterpolationConfig): void {
if (part.hasI18n) { if (part.hasI18n) {
this._messages.push(part.createMessage(this._parser, interpolationConfig)); this._messages.push(part.createMessage(this._expressionParser, interpolationConfig));
this._recurseToExtractMessagesFromAttributes(part.children, interpolationConfig); this._recurseToExtractMessagesFromAttributes(part.children, interpolationConfig);
} else { } else {
this._recurse(part.children, interpolationConfig); this._recurse(part.children, interpolationConfig);
@ -159,7 +161,8 @@ export class MessageExtractor {
p.attrs.filter(attr => attr.name.startsWith(I18N_ATTR_PREFIX)).forEach(attr => { p.attrs.filter(attr => attr.name.startsWith(I18N_ATTR_PREFIX)).forEach(attr => {
try { try {
explicitAttrs.push(attr.name.substring(I18N_ATTR_PREFIX.length)); explicitAttrs.push(attr.name.substring(I18N_ATTR_PREFIX.length));
this._messages.push(messageFromI18nAttribute(this._parser, interpolationConfig, p, attr)); this._messages.push(
messageFromI18nAttribute(this._expressionParser, interpolationConfig, p, attr));
} catch (e) { } catch (e) {
if (e instanceof I18nError) { if (e instanceof I18nError) {
this._errors.push(e); this._errors.push(e);
@ -174,7 +177,7 @@ export class MessageExtractor {
.filter(attr => explicitAttrs.indexOf(attr.name) == -1) .filter(attr => explicitAttrs.indexOf(attr.name) == -1)
.filter(attr => transAttrs.indexOf(attr.name) > -1) .filter(attr => transAttrs.indexOf(attr.name) > -1)
.forEach( .forEach(
attr => attr => this._messages.push(
this._messages.push(messageFromAttribute(this._parser, interpolationConfig, attr))); messageFromAttribute(this._expressionParser, interpolationConfig, attr)));
} }
} }

View File

@ -6,6 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Lexer as ExpressionLexer} from '@angular/compiler/src/expression_parser/lexer';
import {Parser as ExpressionParser} from '@angular/compiler/src/expression_parser/parser';
import {RegExpWrapper, isBlank, isPresent} from '../facade/lang'; import {RegExpWrapper, isBlank, isPresent} from '../facade/lang';
import {HtmlAst, HtmlElementAst} from '../html_ast'; import {HtmlAst, HtmlElementAst} from '../html_ast';
import {HtmlParser} from '../html_parser'; import {HtmlParser} from '../html_parser';
@ -34,9 +37,11 @@ export class XmbDeserializationError extends ParseError {
} }
export function deserializeXmb(content: string, url: string): XmbDeserializationResult { export function deserializeXmb(content: string, url: string): XmbDeserializationResult {
let parser = new HtmlParser(); const expLexer = new ExpressionLexer();
let normalizedContent = _expandPlaceholder(content.trim()); const expParser = new ExpressionParser(expLexer);
let parsed = parser.parse(normalizedContent, url); const parser = new HtmlParser(expParser);
const normalizedContent = _expandPlaceholder(content.trim());
const parsed = parser.parse(normalizedContent, url);
if (parsed.errors.length > 0) { if (parsed.errors.length > 0) {
return new XmbDeserializationResult(null, {}, parsed.errors); return new XmbDeserializationResult(null, {}, parsed.errors);

View File

@ -6,12 +6,22 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
export interface InterpolationConfig { import {assertInterpolationSymbols} from './assertions';
start: string; import {isBlank} from './facade/lang';
end: string;
export class InterpolationConfig {
static fromArray(markers: [string, string]): InterpolationConfig {
if (isBlank(markers)) {
// TODO:bad ??
return DEFAULT_INTERPOLATION_CONFIG;
}
assertInterpolationSymbols('interpolation', markers);
return new InterpolationConfig(markers[0], markers[1]);
}
constructor(public start: string, public end: string){};
} }
export const DEFAULT_INTERPOLATION_CONFIG: InterpolationConfig = { export const DEFAULT_INTERPOLATION_CONFIG: InterpolationConfig =
start: '{{', new InterpolationConfig('{{', '}}');
end: '}}'
};

View File

@ -111,8 +111,8 @@ export class CompileMetadataResolver {
if (isBlank(meta)) { if (isBlank(meta)) {
var dirMeta = this._directiveResolver.resolve(directiveType); var dirMeta = this._directiveResolver.resolve(directiveType);
var templateMeta: cpl.CompileTemplateMetadata = null; var templateMeta: cpl.CompileTemplateMetadata = null;
var changeDetectionStrategy: any /** TODO #9100 */ = null; var changeDetectionStrategy: ChangeDetectionStrategy = null;
var viewProviders: any[] /** TODO #9100 */ = []; var viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
var moduleUrl = staticTypeModuleUrl(directiveType); var moduleUrl = staticTypeModuleUrl(directiveType);
var precompileTypes: cpl.CompileTypeMetadata[] = []; var precompileTypes: cpl.CompileTypeMetadata[] = [];
if (dirMeta instanceof ComponentMetadata) { if (dirMeta instanceof ComponentMetadata) {
@ -147,7 +147,7 @@ export class CompileMetadataResolver {
} }
} }
var providers: any[] /** TODO #9100 */ = []; var providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
if (isPresent(dirMeta.providers)) { if (isPresent(dirMeta.providers)) {
providers = this.getProvidersMetadata( providers = this.getProvidersMetadata(
verifyNonBlankProviders(directiveType, dirMeta.providers, 'providers'), verifyNonBlankProviders(directiveType, dirMeta.providers, 'providers'),
@ -492,14 +492,13 @@ export class CompileMetadataResolver {
getQueriesMetadata( getQueriesMetadata(
queries: {[key: string]: QueryMetadata}, isViewQuery: boolean, queries: {[key: string]: QueryMetadata}, isViewQuery: boolean,
directiveType: Type): cpl.CompileQueryMetadata[] { directiveType: Type): cpl.CompileQueryMetadata[] {
var compileQueries: any[] /** TODO #9100 */ = []; var res: cpl.CompileQueryMetadata[] = [];
StringMapWrapper.forEach( StringMapWrapper.forEach(queries, (query: QueryMetadata, propertyName: string) => {
queries, (query: any /** TODO #9100 */, propertyName: any /** TODO #9100 */) => {
if (query.isViewQuery === isViewQuery) { if (query.isViewQuery === isViewQuery) {
compileQueries.push(this.getQueryMetadata(query, propertyName, directiveType)); res.push(this.getQueryMetadata(query, propertyName, directiveType));
} }
}); });
return compileQueries; return res;
} }
getQueryMetadata(q: QueryMetadata, propertyName: string, typeOrFunc: Type|Function): getQueryMetadata(q: QueryMetadata, propertyName: string, typeOrFunc: Type|Function):

View File

@ -101,15 +101,23 @@ export class TemplateParser {
const errorString = errors.join('\n'); const errorString = errors.join('\n');
throw new BaseException(`Template parse errors:\n${errorString}`); throw new BaseException(`Template parse errors:\n${errorString}`);
} }
return result.templateAst; return result.templateAst;
} }
tryParse( tryParse(
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveMetadata[], component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveMetadata[],
pipes: CompilePipeMetadata[], templateUrl: string): TemplateParseResult { pipes: CompilePipeMetadata[], templateUrl: string): TemplateParseResult {
const htmlAstWithErrors = this._htmlParser.parse(template, templateUrl); // TODO: bad ???
let interpolationConfig: any;
if (component.template) {
interpolationConfig = InterpolationConfig.fromArray(component.template.interpolation);
}
const htmlAstWithErrors =
this._htmlParser.parse(template, templateUrl, false, interpolationConfig);
const errors: ParseError[] = htmlAstWithErrors.errors; const errors: ParseError[] = htmlAstWithErrors.errors;
let result: any[]; let result: TemplateAst[];
if (htmlAstWithErrors.rootNodes.length > 0) { if (htmlAstWithErrors.rootNodes.length > 0) {
const uniqDirectives = <CompileDirectiveMetadata[]>removeDuplicates(directives); const uniqDirectives = <CompileDirectiveMetadata[]>removeDuplicates(directives);
const uniqPipes = <CompilePipeMetadata[]>removeDuplicates(pipes); const uniqPipes = <CompilePipeMetadata[]>removeDuplicates(pipes);
@ -137,10 +145,12 @@ export class TemplateParser {
} }
/** @internal */ /** @internal */
_assertNoReferenceDuplicationOnTemplate(result: any[], errors: TemplateParseError[]): void { _assertNoReferenceDuplicationOnTemplate(result: TemplateAst[], errors: TemplateParseError[]):
const existingReferences: any[] /** TODO #???? */ = []; void {
result.filter(element => !!element.references) const existingReferences: string[] = [];
.forEach(element => element.references.forEach((reference: any /** TODO #???? */) => {
result.filter(element => !!(<any>element).references)
.forEach(element => (<any>element).references.forEach((reference: ReferenceAst) => {
const name = reference.name; const name = reference.name;
if (existingReferences.indexOf(name) < 0) { if (existingReferences.indexOf(name) < 0) {
existingReferences.push(name); existingReferences.push(name);
@ -167,19 +177,24 @@ class TemplateParseVisitor implements HtmlAstVisitor {
pipes: CompilePipeMetadata[], private _exprParser: Parser, pipes: CompilePipeMetadata[], private _exprParser: Parser,
private _schemaRegistry: ElementSchemaRegistry) { private _schemaRegistry: ElementSchemaRegistry) {
this.selectorMatcher = new SelectorMatcher(); this.selectorMatcher = new SelectorMatcher();
const tempMeta = providerViewContext.component.template; const tempMeta = providerViewContext.component.template;
// TODO
if (isPresent(tempMeta) && isPresent(tempMeta.interpolation)) { if (isPresent(tempMeta) && isPresent(tempMeta.interpolation)) {
this._interpolationConfig = { this._interpolationConfig = {
start: tempMeta.interpolation[0], start: tempMeta.interpolation[0],
end: tempMeta.interpolation[1] end: tempMeta.interpolation[1]
}; };
} }
ListWrapper.forEachWithIndex( ListWrapper.forEachWithIndex(
directives, (directive: CompileDirectiveMetadata, index: number) => { directives, (directive: CompileDirectiveMetadata, index: number) => {
const selector = CssSelector.parse(directive.selector); const selector = CssSelector.parse(directive.selector);
this.selectorMatcher.addSelectables(selector, directive); this.selectorMatcher.addSelectables(selector, directive);
this.directivesIndex.set(directive, index); this.directivesIndex.set(directive, index);
}); });
this.pipesByName = new Map<string, CompilePipeMetadata>(); this.pipesByName = new Map<string, CompilePipeMetadata>();
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe)); pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
} }

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Lexer as ExpressionLexer} from '@angular/compiler/src/expression_parser/lexer';
import {Parser as ExpressionParser} from '@angular/compiler/src/expression_parser/parser';
import {HtmlToken, HtmlTokenError, HtmlTokenType, tokenizeHtml} from '@angular/compiler/src/html_lexer'; import {HtmlToken, HtmlTokenError, HtmlTokenType, tokenizeHtml} from '@angular/compiler/src/html_lexer';
import {InterpolationConfig} from '@angular/compiler/src/interpolation_config'; import {InterpolationConfig} from '@angular/compiler/src/interpolation_config';
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler/src/parse_util'; import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler/src/parse_util';
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal'; import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
import {BaseException} from '../src/facade/exceptions';
export function main() { export function main() {
describe('HtmlLexer', () => { describe('HtmlLexer', () => {
describe('line/column numbers', () => { describe('line/column numbers', () => {
@ -358,7 +358,15 @@ export function main() {
expect(tokenizeAndHumanizeParts('{{ a }}')).toEqual([ expect(tokenizeAndHumanizeParts('{{ a }}')).toEqual([
[HtmlTokenType.TEXT, '{{ a }}'], [HtmlTokenType.EOF] [HtmlTokenType.TEXT, '{{ a }}'], [HtmlTokenType.EOF]
]); ]);
});
it('should detect interpolation end', () => {
expect(tokenizeAndHumanizeParts('{{value|filter:{params: {strict: true}}}}')).toEqual([
[HtmlTokenType.TEXT, '{{ a }}'], [HtmlTokenType.EOF]
]);
});
it('should parse interpolation with custom markers', () => {
expect(tokenizeAndHumanizeParts('{% a %}', null, {start: '{%', end: '%}'})).toEqual([ expect(tokenizeAndHumanizeParts('{% a %}', null, {start: '{%', end: '%}'})).toEqual([
[HtmlTokenType.TEXT, '{% a %}'], [HtmlTokenType.EOF] [HtmlTokenType.TEXT, '{% a %}'], [HtmlTokenType.EOF]
]); ]);
@ -598,11 +606,14 @@ export function main() {
function tokenizeWithoutErrors( function tokenizeWithoutErrors(
input: string, tokenizeExpansionForms: boolean = false, input: string, tokenizeExpansionForms: boolean = false,
interpolationConfig?: InterpolationConfig): HtmlToken[] { interpolationConfig?: InterpolationConfig): HtmlToken[] {
var tokenizeResult = tokenizeHtml(input, 'someUrl', tokenizeExpansionForms, interpolationConfig); var tokenizeResult = tokenizeHtml(
input, 'someUrl', _getExpressionParser(), tokenizeExpansionForms, interpolationConfig);
if (tokenizeResult.errors.length > 0) { if (tokenizeResult.errors.length > 0) {
var errorString = tokenizeResult.errors.join('\n'); const errorString = tokenizeResult.errors.join('\n');
throw new BaseException(`Unexpected parse errors:\n${errorString}`); throw new Error(`Unexpected parse errors:\n${errorString}`);
} }
return tokenizeResult.tokens; return tokenizeResult.tokens;
} }
@ -627,9 +638,10 @@ function tokenizeAndHumanizeLineColumn(input: string): any[] {
} }
function tokenizeAndHumanizeErrors(input: string): any[] { function tokenizeAndHumanizeErrors(input: string): any[] {
return tokenizeHtml(input, 'someUrl') return tokenizeHtml(input, 'someUrl', _getExpressionParser())
.errors.map( .errors.map(e => [<any>e.tokenType, e.msg, humanizeLineColumn(e.span.start)]);
tokenError => }
[<any>tokenError.tokenType, tokenError.msg,
humanizeLineColumn(tokenError.span.start)]); function _getExpressionParser(): ExpressionParser {
return new ExpressionParser(new ExpressionLexer());
} }

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Lexer as ExpressionLexer} from '@angular/compiler/src/expression_parser/lexer';
import {Parser as ExpressionParser} from '@angular/compiler/src/expression_parser/parser';
import {HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst} from '@angular/compiler/src/html_ast'; import {HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst} from '@angular/compiler/src/html_ast';
import {HtmlTokenType} from '@angular/compiler/src/html_lexer'; import {HtmlTokenType} from '@angular/compiler/src/html_lexer';
import {HtmlParseTreeResult, HtmlParser, HtmlTreeError} from '@angular/compiler/src/html_parser'; import {HtmlParseTreeResult, HtmlParser, HtmlTreeError} from '@angular/compiler/src/html_parser';
@ -17,7 +19,14 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './html_as
export function main() { export function main() {
describe('HtmlParser', () => { describe('HtmlParser', () => {
var parser: HtmlParser; var parser: HtmlParser;
beforeEach(() => { parser = new HtmlParser(); }); var expLexer: ExpressionLexer;
var expParser: ExpressionParser;
beforeEach(() => {
expLexer = new ExpressionLexer();
expParser = new ExpressionParser(expLexer);
parser = new HtmlParser(expParser);
});
describe('parse', () => { describe('parse', () => {
describe('text nodes', () => { describe('text nodes', () => {
@ -412,12 +421,12 @@ export function main() {
} }
export function humanizeErrors(errors: ParseError[]): any[] { export function humanizeErrors(errors: ParseError[]): any[] {
return errors.map(error => { return errors.map(e => {
if (error instanceof HtmlTreeError) { if (e instanceof HtmlTreeError) {
// Parser errors // Parser errors
return [<any>error.elementName, error.msg, humanizeLineColumn(error.span.start)]; return [<any>e.elementName, e.msg, humanizeLineColumn(e.span.start)];
} }
// Tokenizer errors // Tokenizer errors
return [(<any>error).tokenType, error.msg, humanizeLineColumn(error.span.start)]; return [(<any>e).tokenType, e.msg, humanizeLineColumn(e.span.start)];
}); });
} }

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Lexer as ExpressionLexer} from '@angular/compiler/src/expression_parser/lexer';
import {Parser as ExpressionParser} from '@angular/compiler/src/expression_parser/parser';
import {HtmlAttrAst, HtmlElementAst, HtmlTextAst} from '@angular/compiler/src/html_ast'; import {HtmlAttrAst, HtmlElementAst, HtmlTextAst} from '@angular/compiler/src/html_ast';
import {HtmlParser} from '@angular/compiler/src/html_parser'; import {HtmlParser} from '@angular/compiler/src/html_parser';
import {ExpansionResult, expandNodes} from '@angular/compiler/src/i18n/expander'; import {ExpansionResult, expandNodes} from '@angular/compiler/src/i18n/expander';
@ -16,7 +18,9 @@ import {ddescribe, describe, expect, iit, it} from '@angular/core/testing/testin
export function main() { export function main() {
describe('Expander', () => { describe('Expander', () => {
function expand(template: string): ExpansionResult { function expand(template: string): ExpansionResult {
const htmlParser = new HtmlParser(); const expLexer = new ExpressionLexer();
const expParser = new ExpressionParser(expLexer);
const htmlParser = new HtmlParser(expParser);
const res = htmlParser.parse(template, 'url', true); const res = htmlParser.parse(template, 'url', true);
return expandNodes(res.rootNodes); return expandNodes(res.rootNodes);
} }

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Lexer} from '@angular/compiler/src/expression_parser/lexer'; import {Lexer as ExpressionLexer} from '@angular/compiler/src/expression_parser/lexer';
import {Parser} from '@angular/compiler/src/expression_parser/parser'; import {Parser as ExpressionParser} from '@angular/compiler/src/expression_parser/parser';
import {HtmlAttrAst, HtmlElementAst, HtmlTextAst} from '@angular/compiler/src/html_ast'; import {HtmlAttrAst, HtmlElementAst, HtmlTextAst} from '@angular/compiler/src/html_ast';
import {HtmlParseTreeResult, HtmlParser} from '@angular/compiler/src/html_parser'; import {HtmlParseTreeResult, HtmlParser} from '@angular/compiler/src/html_parser';
import {I18nHtmlParser} from '@angular/compiler/src/i18n/i18n_html_parser'; import {I18nHtmlParser} from '@angular/compiler/src/i18n/i18n_html_parser';
@ -26,8 +26,8 @@ export function main() {
template: string, messages: {[key: string]: string}, implicitTags: string[] = [], template: string, messages: {[key: string]: string}, implicitTags: string[] = [],
implicitAttrs: {[k: string]: string[]} = {}, implicitAttrs: {[k: string]: string[]} = {},
interpolation?: InterpolationConfig): HtmlParseTreeResult { interpolation?: InterpolationConfig): HtmlParseTreeResult {
var parser = new Parser(new Lexer()); var expParser = new ExpressionParser(new ExpressionLexer());
let htmlParser = new HtmlParser(); let htmlParser = new HtmlParser(expParser);
let msgs = ''; let msgs = '';
StringMapWrapper.forEach( StringMapWrapper.forEach(
@ -35,7 +35,7 @@ export function main() {
let res = deserializeXmb(`<message-bundle>${msgs}</message-bundle>`, 'someUrl'); let res = deserializeXmb(`<message-bundle>${msgs}</message-bundle>`, 'someUrl');
return new I18nHtmlParser( return new I18nHtmlParser(
htmlParser, parser, res.content, res.messages, implicitTags, implicitAttrs) htmlParser, expParser, res.content, res.messages, implicitTags, implicitAttrs)
.parse(template, 'someurl', true, interpolation); .parse(template, 'someurl', true, interpolation);
} }
@ -80,8 +80,11 @@ export function main() {
expect(humanizeDom(parse( expect(humanizeDom(parse(
'<div value=\'{%a%} and {%b%}\' i18n-value></div>', translations, [], {}, '<div value=\'{%a%} and {%b%}\' i18n-value></div>', translations, [], {},
{start: '{%', end: '%}'}))) InterpolationConfig.fromArray(['{%', '%}']))))
.toEqual([[HtmlElementAst, 'div', 0], [HtmlAttrAst, 'value', '{%b%} or {%a%}']]); .toEqual([
[HtmlElementAst, 'div', 0],
[HtmlAttrAst, 'value', '{%b%} or {%a%}'],
]);
}); });
it('should handle interpolation with custom placeholder names', () => { it('should handle interpolation with custom placeholder names', () => {

View File

@ -6,21 +6,23 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Lexer} from '@angular/compiler/src/expression_parser/lexer'; import {Lexer as ExpressionLexer} from '@angular/compiler/src/expression_parser/lexer';
import {Parser} from '@angular/compiler/src/expression_parser/parser'; import {Parser as ExpressionParser} from '@angular/compiler/src/expression_parser/parser';
import {HtmlParser} from '@angular/compiler/src/html_parser'; import {HtmlParser} from '@angular/compiler/src/html_parser';
import {Message} from '@angular/compiler/src/i18n/message'; import {Message} from '@angular/compiler/src/i18n/message';
import {MessageExtractor, removeDuplicates} from '@angular/compiler/src/i18n/message_extractor'; import {MessageExtractor, removeDuplicates} from '@angular/compiler/src/i18n/message_extractor';
import {beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal'; import {beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
export function main() { export function main() {
describe('MessageExtractor', () => { describe('MessageExtractor', () => {
let extractor: MessageExtractor; let extractor: MessageExtractor;
beforeEach(() => { beforeEach(() => {
const htmlParser = new HtmlParser(); const expParser = new ExpressionParser(new ExpressionLexer());
const parser = new Parser(new Lexer()); const htmlParser = new HtmlParser(expParser);
extractor = new MessageExtractor(htmlParser, parser, ['i18n-tag'], {'i18n-el': ['trans']}); // TODO: pass expression parser
extractor = new MessageExtractor(htmlParser, expParser, ['i18n-tag'], {'i18n-el': ['trans']});
}); });
it('should extract from elements with the i18n attr', () => { it('should extract from elements with the i18n attr', () => {

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Lexer as ExpressionLexer} from '@angular/compiler/src/expression_parser/lexer';
import {Parser as ExpressionParser} from '@angular/compiler/src/expression_parser/parser';
import {HtmlElementAst} from '@angular/compiler/src/html_ast'; import {HtmlElementAst} from '@angular/compiler/src/html_ast';
import {HtmlParser} from '@angular/compiler/src/html_parser'; import {HtmlParser} from '@angular/compiler/src/html_parser';
import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry'; import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry';
@ -68,7 +70,9 @@ export function main() {
}); });
it('should detect properties on namespaced elements', () => { it('should detect properties on namespaced elements', () => {
let htmlAst = new HtmlParser().parse('<svg:style>', 'TestComp'); const expLexer = new ExpressionLexer();
const expParser = new ExpressionParser(expLexer);
let htmlAst = new HtmlParser(expParser).parse('<svg:style>', 'TestComp');
let nodeName = (<HtmlElementAst>htmlAst.rootNodes[0]).name; let nodeName = (<HtmlElementAst>htmlAst.rootNodes[0]).name;
expect(registry.hasProperty(nodeName, 'type')).toBeTruthy(); expect(registry.hasProperty(nodeName, 'type')).toBeTruthy();
}); });