parent
22ae2d0976
commit
c6244d1470
@ -125,8 +125,7 @@ class _HtmlTokenizer {
|
|||||||
private currentTokenStart: ParseLocation;
|
private currentTokenStart: ParseLocation;
|
||||||
private currentTokenType: HtmlTokenType;
|
private currentTokenType: HtmlTokenType;
|
||||||
|
|
||||||
private inExpansionCase: boolean = false;
|
private expansionCaseStack = [];
|
||||||
private inExpansionForm: boolean = false;
|
|
||||||
|
|
||||||
tokens: HtmlToken[] = [];
|
tokens: HtmlToken[] = [];
|
||||||
errors: HtmlTokenError[] = [];
|
errors: HtmlTokenError[] = [];
|
||||||
@ -169,10 +168,12 @@ class _HtmlTokenizer {
|
|||||||
} else if (this.peek === $EQ && this.tokenizeExpansionForms) {
|
} else if (this.peek === $EQ && this.tokenizeExpansionForms) {
|
||||||
this._consumeExpansionCaseStart();
|
this._consumeExpansionCaseStart();
|
||||||
|
|
||||||
} else if (this.peek === $RBRACE && this.inExpansionCase && this.tokenizeExpansionForms) {
|
} else if (this.peek === $RBRACE && this.isInExpansionCase() &&
|
||||||
|
this.tokenizeExpansionForms) {
|
||||||
this._consumeExpansionCaseEnd();
|
this._consumeExpansionCaseEnd();
|
||||||
|
|
||||||
} else if (this.peek === $RBRACE && !this.inExpansionCase && this.tokenizeExpansionForms) {
|
} else if (this.peek === $RBRACE && this.isInExpansionForm() &&
|
||||||
|
this.tokenizeExpansionForms) {
|
||||||
this._consumeExpansionFormEnd();
|
this._consumeExpansionFormEnd();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -551,7 +552,7 @@ class _HtmlTokenizer {
|
|||||||
this._requireCharCode($COMMA);
|
this._requireCharCode($COMMA);
|
||||||
this._attemptCharCodeUntilFn(isNotWhitespace);
|
this._attemptCharCodeUntilFn(isNotWhitespace);
|
||||||
|
|
||||||
this.inExpansionForm = true;
|
this.expansionCaseStack.push(HtmlTokenType.EXPANSION_FORM_START);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _consumeExpansionCaseStart() {
|
private _consumeExpansionCaseStart() {
|
||||||
@ -567,7 +568,7 @@ class _HtmlTokenizer {
|
|||||||
this._endToken([], this._getLocation());
|
this._endToken([], this._getLocation());
|
||||||
this._attemptCharCodeUntilFn(isNotWhitespace);
|
this._attemptCharCodeUntilFn(isNotWhitespace);
|
||||||
|
|
||||||
this.inExpansionCase = true;
|
this.expansionCaseStack.push(HtmlTokenType.EXPANSION_CASE_EXP_START);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _consumeExpansionCaseEnd() {
|
private _consumeExpansionCaseEnd() {
|
||||||
@ -576,7 +577,7 @@ class _HtmlTokenizer {
|
|||||||
this._endToken([], this._getLocation());
|
this._endToken([], this._getLocation());
|
||||||
this._attemptCharCodeUntilFn(isNotWhitespace);
|
this._attemptCharCodeUntilFn(isNotWhitespace);
|
||||||
|
|
||||||
this.inExpansionCase = false;
|
this.expansionCaseStack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _consumeExpansionFormEnd() {
|
private _consumeExpansionFormEnd() {
|
||||||
@ -584,7 +585,7 @@ class _HtmlTokenizer {
|
|||||||
this._requireCharCode($RBRACE);
|
this._requireCharCode($RBRACE);
|
||||||
this._endToken([]);
|
this._endToken([]);
|
||||||
|
|
||||||
this.inExpansionForm = false;
|
this.expansionCaseStack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _consumeText() {
|
private _consumeText() {
|
||||||
@ -622,7 +623,9 @@ class _HtmlTokenizer {
|
|||||||
if (this.peek === $LT || this.peek === $EOF) return true;
|
if (this.peek === $LT || this.peek === $EOF) return true;
|
||||||
if (this.tokenizeExpansionForms) {
|
if (this.tokenizeExpansionForms) {
|
||||||
if (isSpecialFormStart(this.peek, this.nextPeek)) return true;
|
if (isSpecialFormStart(this.peek, this.nextPeek)) return true;
|
||||||
if (this.peek === $RBRACE && !interpolation && this.inExpansionForm) return true;
|
if (this.peek === $RBRACE && !interpolation &&
|
||||||
|
(this.isInExpansionCase() || this.isInExpansionForm()))
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -648,6 +651,18 @@ class _HtmlTokenizer {
|
|||||||
this.tokens = ListWrapper.slice(this.tokens, 0, nbTokens);
|
this.tokens = ListWrapper.slice(this.tokens, 0, nbTokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isInExpansionCase(): boolean {
|
||||||
|
return this.expansionCaseStack.length > 0 &&
|
||||||
|
this.expansionCaseStack[this.expansionCaseStack.length - 1] ===
|
||||||
|
HtmlTokenType.EXPANSION_CASE_EXP_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isInExpansionForm(): boolean {
|
||||||
|
return this.expansionCaseStack.length > 0 &&
|
||||||
|
this.expansionCaseStack[this.expansionCaseStack.length - 1] ===
|
||||||
|
HtmlTokenType.EXPANSION_FORM_START;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNotWhitespace(code: number): boolean {
|
function isNotWhitespace(code: number): boolean {
|
||||||
|
@ -124,40 +124,9 @@ class TreeBuilder {
|
|||||||
|
|
||||||
// read =
|
// read =
|
||||||
while (this.peek.type === HtmlTokenType.EXPANSION_CASE_VALUE) {
|
while (this.peek.type === HtmlTokenType.EXPANSION_CASE_VALUE) {
|
||||||
let value = this._advance();
|
let expCase = this._parseExpansionCase();
|
||||||
|
if (isBlank(expCase)) return; // error
|
||||||
// read {
|
cases.push(expCase);
|
||||||
let exp = [];
|
|
||||||
if (this.peek.type !== HtmlTokenType.EXPANSION_CASE_EXP_START) {
|
|
||||||
this.errors.push(HtmlTreeError.create(null, this.peek.sourceSpan,
|
|
||||||
`Invalid expansion form. Missing '{'.,`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// read until }
|
|
||||||
let start = this._advance();
|
|
||||||
while (this.peek.type !== HtmlTokenType.EXPANSION_CASE_EXP_END) {
|
|
||||||
exp.push(this._advance());
|
|
||||||
if (this.peek.type === HtmlTokenType.EOF) {
|
|
||||||
this.errors.push(
|
|
||||||
HtmlTreeError.create(null, start.sourceSpan, `Invalid expansion form. Missing '}'.`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let end = this._advance();
|
|
||||||
exp.push(new HtmlToken(HtmlTokenType.EOF, [], end.sourceSpan));
|
|
||||||
|
|
||||||
// parse everything in between { and }
|
|
||||||
let parsedExp = new TreeBuilder(exp).build();
|
|
||||||
if (parsedExp.errors.length > 0) {
|
|
||||||
this.errors = this.errors.concat(<HtmlTreeError[]>parsedExp.errors);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sourceSpan = new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end);
|
|
||||||
let expSourceSpan = new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end);
|
|
||||||
cases.push(new HtmlExpansionCaseAst(value.parts[0], parsedExp.rootNodes, sourceSpan,
|
|
||||||
value.sourceSpan, expSourceSpan));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// read the final }
|
// read the final }
|
||||||
@ -173,6 +142,80 @@ class TreeBuilder {
|
|||||||
mainSourceSpan, switchValue.sourceSpan));
|
mainSourceSpan, switchValue.sourceSpan));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _parseExpansionCase(): HtmlExpansionCaseAst {
|
||||||
|
let value = this._advance();
|
||||||
|
|
||||||
|
// read {
|
||||||
|
if (this.peek.type !== HtmlTokenType.EXPANSION_CASE_EXP_START) {
|
||||||
|
this.errors.push(HtmlTreeError.create(null, this.peek.sourceSpan,
|
||||||
|
`Invalid expansion form. Missing '{'.,`));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read until }
|
||||||
|
let start = this._advance();
|
||||||
|
|
||||||
|
let exp = this._collectExpansionExpTokens(start);
|
||||||
|
if (isBlank(exp)) return null;
|
||||||
|
|
||||||
|
let end = this._advance();
|
||||||
|
exp.push(new HtmlToken(HtmlTokenType.EOF, [], end.sourceSpan));
|
||||||
|
|
||||||
|
// parse everything in between { and }
|
||||||
|
let parsedExp = new TreeBuilder(exp).build();
|
||||||
|
if (parsedExp.errors.length > 0) {
|
||||||
|
this.errors = this.errors.concat(<HtmlTreeError[]>parsedExp.errors);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sourceSpan = new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end);
|
||||||
|
let expSourceSpan = new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end);
|
||||||
|
return new HtmlExpansionCaseAst(value.parts[0], parsedExp.rootNodes, sourceSpan,
|
||||||
|
value.sourceSpan, expSourceSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _collectExpansionExpTokens(start: HtmlToken): HtmlToken[] {
|
||||||
|
let exp = [];
|
||||||
|
let expansionFormStack = [HtmlTokenType.EXPANSION_CASE_EXP_START];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (this.peek.type === HtmlTokenType.EXPANSION_FORM_START ||
|
||||||
|
this.peek.type === HtmlTokenType.EXPANSION_CASE_EXP_START) {
|
||||||
|
expansionFormStack.push(this.peek.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.peek.type === HtmlTokenType.EXPANSION_CASE_EXP_END) {
|
||||||
|
if (lastOnStack(expansionFormStack, HtmlTokenType.EXPANSION_CASE_EXP_START)) {
|
||||||
|
expansionFormStack.pop();
|
||||||
|
if (expansionFormStack.length == 0) return exp;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.errors.push(
|
||||||
|
HtmlTreeError.create(null, start.sourceSpan, `Invalid expansion form. Missing '}'.`));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.peek.type === HtmlTokenType.EXPANSION_FORM_END) {
|
||||||
|
if (lastOnStack(expansionFormStack, HtmlTokenType.EXPANSION_FORM_START)) {
|
||||||
|
expansionFormStack.pop();
|
||||||
|
} else {
|
||||||
|
this.errors.push(
|
||||||
|
HtmlTreeError.create(null, start.sourceSpan, `Invalid expansion form. Missing '}'.`));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.peek.type === HtmlTokenType.EOF) {
|
||||||
|
this.errors.push(
|
||||||
|
HtmlTreeError.create(null, start.sourceSpan, `Invalid expansion form. Missing '}'.`));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
exp.push(this._advance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _consumeText(token: HtmlToken) {
|
private _consumeText(token: HtmlToken) {
|
||||||
let text = token.parts[0];
|
let text = token.parts[0];
|
||||||
if (text.length > 0 && text[0] == '\n') {
|
if (text.length > 0 && text[0] == '\n') {
|
||||||
@ -321,3 +364,7 @@ function getElementFullName(prefix: string, localName: string,
|
|||||||
|
|
||||||
return mergeNsAndName(prefix, localName);
|
return mergeNsAndName(prefix, localName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lastOnStack(stack: any[], element: any): boolean {
|
||||||
|
return stack.length > 0 && stack[stack.length - 1] === element;
|
||||||
|
}
|
@ -254,6 +254,10 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visitExpansion(ast: HtmlExpansionAst, context: any): any { return null; }
|
||||||
|
|
||||||
|
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return null; }
|
||||||
|
|
||||||
visitText(ast: HtmlTextAst, parent: ElementContext): any {
|
visitText(ast: HtmlTextAst, parent: ElementContext): any {
|
||||||
var ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
|
var ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
|
||||||
var expr = this._parseInterpolation(ast.value, ast.sourceSpan);
|
var expr = this._parseInterpolation(ast.value, ast.sourceSpan);
|
||||||
@ -270,7 +274,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
|||||||
|
|
||||||
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
|
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
|
||||||
|
|
||||||
visitElement(element: HtmlElementAst, component: ElementContext): any {
|
visitElement(element: HtmlElementAst, parent: ElementContext): any {
|
||||||
var nodeName = element.name;
|
var nodeName = element.name;
|
||||||
var preparsedElement = preparseElement(element);
|
var preparsedElement = preparseElement(element);
|
||||||
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
||||||
@ -773,7 +777,6 @@ class NonBindableVisitor implements HtmlAstVisitor {
|
|||||||
return new TextAst(ast.value, ngContentIndex, ast.sourceSpan);
|
return new TextAst(ast.value, ngContentIndex, ast.sourceSpan);
|
||||||
}
|
}
|
||||||
visitExpansion(ast: HtmlExpansionAst, context: any): any { return ast; }
|
visitExpansion(ast: HtmlExpansionAst, context: any): any { return ast; }
|
||||||
|
|
||||||
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return ast; }
|
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return ast; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
|
|
||||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expands special forms into elements.
|
* Expands special forms into elements.
|
||||||
*
|
*
|
||||||
@ -35,7 +36,18 @@ import {BaseException} from 'angular2/src/facade/exceptions';
|
|||||||
* </ul>
|
* </ul>
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class Expander implements HtmlAstVisitor {
|
export function expandNodes(nodes: HtmlAst[]): ExpansionResult {
|
||||||
|
let e = new _Expander();
|
||||||
|
let n = htmlVisitAll(e, nodes);
|
||||||
|
return new ExpansionResult(n, e.expanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExpansionResult {
|
||||||
|
constructor(public nodes: HtmlAst[], public expanded: boolean) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Expander implements HtmlAstVisitor {
|
||||||
|
expanded: boolean = false;
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
visitElement(ast: HtmlElementAst, context: any): any {
|
visitElement(ast: HtmlElementAst, context: any): any {
|
||||||
@ -50,6 +62,7 @@ export class Expander implements HtmlAstVisitor {
|
|||||||
visitComment(ast: HtmlCommentAst, context: any): any { return ast; }
|
visitComment(ast: HtmlCommentAst, context: any): any { return ast; }
|
||||||
|
|
||||||
visitExpansion(ast: HtmlExpansionAst, context: any): any {
|
visitExpansion(ast: HtmlExpansionAst, context: any): any {
|
||||||
|
this.expanded = true;
|
||||||
return ast.type == "plural" ? _expandPluralForm(ast) : _expandDefaultForm(ast);
|
return ast.type == "plural" ? _expandPluralForm(ast) : _expandDefaultForm(ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,36 +72,44 @@ export class Expander implements HtmlAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _expandPluralForm(ast: HtmlExpansionAst): HtmlElementAst {
|
function _expandPluralForm(ast: HtmlExpansionAst): HtmlElementAst {
|
||||||
let children = ast.cases.map(
|
let children = ast.cases.map(c => {
|
||||||
c => new HtmlElementAst(
|
let expansionResult = expandNodes(c.expression);
|
||||||
`template`,
|
let i18nAttrs = expansionResult.expanded ?
|
||||||
|
[] :
|
||||||
|
[new HtmlAttrAst("i18n", `${ast.type}_${c.value}`, c.valueSourceSpan)];
|
||||||
|
|
||||||
|
return new HtmlElementAst(`template`,
|
||||||
[
|
[
|
||||||
new HtmlAttrAst("[ngPluralCase]", c.value, c.valueSourceSpan),
|
new HtmlAttrAst("ngPluralCase", c.value, c.valueSourceSpan),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
new HtmlElementAst(
|
new HtmlElementAst(`li`, i18nAttrs, expansionResult.nodes,
|
||||||
`li`, [new HtmlAttrAst("i18n", `${ast.type}_${c.value}`, c.valueSourceSpan)],
|
c.sourceSpan, c.sourceSpan, c.sourceSpan)
|
||||||
c.expression, c.sourceSpan, c.sourceSpan, c.sourceSpan)
|
|
||||||
],
|
],
|
||||||
c.sourceSpan, c.sourceSpan, c.sourceSpan));
|
c.sourceSpan, c.sourceSpan, c.sourceSpan);
|
||||||
|
});
|
||||||
let switchAttr = new HtmlAttrAst("[ngPlural]", ast.switchValue, ast.switchValueSourceSpan);
|
let switchAttr = new HtmlAttrAst("[ngPlural]", ast.switchValue, ast.switchValueSourceSpan);
|
||||||
return new HtmlElementAst("ul", [switchAttr], children, ast.sourceSpan, ast.sourceSpan,
|
return new HtmlElementAst("ul", [switchAttr], children, ast.sourceSpan, ast.sourceSpan,
|
||||||
ast.sourceSpan);
|
ast.sourceSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _expandDefaultForm(ast: HtmlExpansionAst): HtmlElementAst {
|
function _expandDefaultForm(ast: HtmlExpansionAst): HtmlElementAst {
|
||||||
let children = ast.cases.map(
|
let children = ast.cases.map(c => {
|
||||||
c => new HtmlElementAst(
|
let expansionResult = expandNodes(c.expression);
|
||||||
`template`,
|
let i18nAttrs = expansionResult.expanded ?
|
||||||
|
[] :
|
||||||
|
[new HtmlAttrAst("i18n", `${ast.type}_${c.value}`, c.valueSourceSpan)];
|
||||||
|
|
||||||
|
return new HtmlElementAst(`template`,
|
||||||
[
|
[
|
||||||
new HtmlAttrAst("[ngSwitchWhen]", c.value, c.valueSourceSpan),
|
new HtmlAttrAst("ngSwitchWhen", c.value, c.valueSourceSpan),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
new HtmlElementAst(
|
new HtmlElementAst(`li`, i18nAttrs, expansionResult.nodes,
|
||||||
`li`, [new HtmlAttrAst("i18n", `${ast.type}_${c.value}`, c.valueSourceSpan)],
|
c.sourceSpan, c.sourceSpan, c.sourceSpan)
|
||||||
c.expression, c.sourceSpan, c.sourceSpan, c.sourceSpan)
|
|
||||||
],
|
],
|
||||||
c.sourceSpan, c.sourceSpan, c.sourceSpan));
|
c.sourceSpan, c.sourceSpan, c.sourceSpan);
|
||||||
|
});
|
||||||
let switchAttr = new HtmlAttrAst("[ngSwitch]", ast.switchValue, ast.switchValueSourceSpan);
|
let switchAttr = new HtmlAttrAst("[ngSwitch]", ast.switchValue, ast.switchValueSourceSpan);
|
||||||
return new HtmlElementAst("ul", [switchAttr], children, ast.sourceSpan, ast.sourceSpan,
|
return new HtmlElementAst("ul", [switchAttr], children, ast.sourceSpan, ast.sourceSpan,
|
||||||
ast.sourceSpan);
|
ast.sourceSpan);
|
||||||
|
@ -16,7 +16,7 @@ import {RegExpWrapper, NumberWrapper, isPresent} from 'angular2/src/facade/lang'
|
|||||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||||
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
|
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
|
||||||
import {Message, id} from './message';
|
import {Message, id} from './message';
|
||||||
import {Expander} from './expander';
|
import {expandNodes} from './expander';
|
||||||
import {
|
import {
|
||||||
messageFromAttribute,
|
messageFromAttribute,
|
||||||
I18nError,
|
I18nError,
|
||||||
@ -126,21 +126,16 @@ export class I18nHtmlParser implements HtmlParser {
|
|||||||
parseExpansionForms: boolean = false): HtmlParseTreeResult {
|
parseExpansionForms: boolean = false): HtmlParseTreeResult {
|
||||||
this.errors = [];
|
this.errors = [];
|
||||||
|
|
||||||
let res = this._htmlParser.parse(sourceContent, sourceUrl, parseExpansionForms);
|
let res = this._htmlParser.parse(sourceContent, sourceUrl, true);
|
||||||
if (res.errors.length > 0) {
|
if (res.errors.length > 0) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
let nodes = this._recurse(this._expandNodes(res.rootNodes));
|
let nodes = this._recurse(expandNodes(res.rootNodes).nodes);
|
||||||
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 _expandNodes(nodes: HtmlAst[]): HtmlAst[] {
|
|
||||||
let e = new Expander();
|
|
||||||
return htmlVisitAll(e, nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _processI18nPart(p: Part): HtmlAst[] {
|
private _processI18nPart(p: Part): HtmlAst[] {
|
||||||
try {
|
try {
|
||||||
return p.hasI18n ? this._mergeI18Part(p) : this._recurseIntoI18nPart(p);
|
return p.hasI18n ? this._mergeI18Part(p) : this._recurseIntoI18nPart(p);
|
||||||
@ -155,9 +150,11 @@ export class I18nHtmlParser implements HtmlParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _mergeI18Part(p: Part): HtmlAst[] {
|
private _mergeI18Part(p: Part): HtmlAst[] {
|
||||||
let messageId = id(p.createMessage(this._parser));
|
let message = p.createMessage(this._parser);
|
||||||
|
let messageId = id(message);
|
||||||
if (!StringMapWrapper.contains(this._messages, messageId)) {
|
if (!StringMapWrapper.contains(this._messages, messageId)) {
|
||||||
throw new I18nError(p.sourceSpan, `Cannot find message for id '${messageId}'`);
|
throw new I18nError(
|
||||||
|
p.sourceSpan, `Cannot find message for id '${messageId}', content '${message.content}'.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsedMessage = this._messages[messageId];
|
let parsedMessage = this._messages[messageId];
|
||||||
@ -294,14 +291,17 @@ export class I18nHtmlParser implements HtmlParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let i18n = i18ns[0];
|
let i18n = i18ns[0];
|
||||||
let messageId = id(messageFromAttribute(this._parser, el, i18n));
|
let message = messageFromAttribute(this._parser, el, i18n);
|
||||||
|
let messageId = id(message);
|
||||||
|
|
||||||
if (StringMapWrapper.contains(this._messages, messageId)) {
|
if (StringMapWrapper.contains(this._messages, messageId)) {
|
||||||
let updatedMessage = this._replaceInterpolationInAttr(attr, this._messages[messageId]);
|
let updatedMessage = this._replaceInterpolationInAttr(attr, this._messages[messageId]);
|
||||||
res.push(new HtmlAttrAst(attr.name, updatedMessage, attr.sourceSpan));
|
res.push(new HtmlAttrAst(attr.name, updatedMessage, attr.sourceSpan));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new I18nError(attr.sourceSpan, `Cannot find message for id '${messageId}'`);
|
throw new I18nError(
|
||||||
|
attr.sourceSpan,
|
||||||
|
`Cannot find message for id '${messageId}', content '${message.content}'.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
|
@ -13,7 +13,7 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
|||||||
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
|
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
|
||||||
import {Message, id} from './message';
|
import {Message, id} from './message';
|
||||||
import {Expander} from './expander';
|
import {expandNodes} from './expander';
|
||||||
import {
|
import {
|
||||||
I18nError,
|
I18nError,
|
||||||
Part,
|
Part,
|
||||||
@ -126,16 +126,11 @@ export class MessageExtractor {
|
|||||||
if (res.errors.length > 0) {
|
if (res.errors.length > 0) {
|
||||||
return new ExtractionResult([], res.errors);
|
return new ExtractionResult([], res.errors);
|
||||||
} else {
|
} else {
|
||||||
this._recurse(this._expandNodes(res.rootNodes));
|
this._recurse(expandNodes(res.rootNodes).nodes);
|
||||||
return new ExtractionResult(this.messages, this.errors);
|
return new ExtractionResult(this.messages, this.errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _expandNodes(nodes: HtmlAst[]): HtmlAst[] {
|
|
||||||
let e = new Expander();
|
|
||||||
return htmlVisitAll(e, nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _extractMessagesFromPart(p: Part): void {
|
private _extractMessagesFromPart(p: Part): void {
|
||||||
if (p.hasI18n) {
|
if (p.hasI18n) {
|
||||||
this.messages.push(p.createMessage(this._parser));
|
this.messages.push(p.createMessage(this._parser));
|
||||||
|
@ -646,6 +646,31 @@ export function main() {
|
|||||||
[HtmlTokenType.EOF]
|
[HtmlTokenType.EOF]
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should parse nested expansion forms", () => {
|
||||||
|
expect(tokenizeAndHumanizeParts(`{one.two, three, =4 { {xx, yy, =x {one}} }}`, true))
|
||||||
|
.toEqual([
|
||||||
|
[HtmlTokenType.EXPANSION_FORM_START],
|
||||||
|
[HtmlTokenType.RAW_TEXT, 'one.two'],
|
||||||
|
[HtmlTokenType.RAW_TEXT, 'three'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_VALUE, '4'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_START],
|
||||||
|
|
||||||
|
[HtmlTokenType.EXPANSION_FORM_START],
|
||||||
|
[HtmlTokenType.RAW_TEXT, 'xx'],
|
||||||
|
[HtmlTokenType.RAW_TEXT, 'yy'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_VALUE, 'x'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_START],
|
||||||
|
[HtmlTokenType.TEXT, 'one'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_END],
|
||||||
|
[HtmlTokenType.EXPANSION_FORM_END],
|
||||||
|
[HtmlTokenType.TEXT, ' '],
|
||||||
|
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_END],
|
||||||
|
[HtmlTokenType.EXPANSION_FORM_END],
|
||||||
|
[HtmlTokenType.EOF]
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
|
@ -271,6 +271,27 @@ export function main() {
|
|||||||
.toEqual([[HtmlTextAst, 'One {{message}}', 0]]);
|
.toEqual([[HtmlTextAst, 'One {{message}}', 0]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should parse out nested expansion forms", () => {
|
||||||
|
let parsed = parser.parse(`{messages.length, plural, =0 { {p.gender, gender, =m {m}} }}`,
|
||||||
|
'TestComp', true);
|
||||||
|
|
||||||
|
|
||||||
|
expect(humanizeDom(parsed))
|
||||||
|
.toEqual([
|
||||||
|
[HtmlExpansionAst, 'messages.length', 'plural'],
|
||||||
|
[HtmlExpansionCaseAst, '0'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
let firstCase = (<any>parsed.rootNodes[0]).cases[0];
|
||||||
|
|
||||||
|
expect(humanizeDom(new HtmlParseTreeResult(firstCase.expression, [])))
|
||||||
|
.toEqual([
|
||||||
|
[HtmlExpansionAst, 'p.gender', 'gender'],
|
||||||
|
[HtmlExpansionCaseAst, 'm'],
|
||||||
|
[HtmlTextAst, ' ', 0],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("should error when expansion form is not closed", () => {
|
it("should error when expansion form is not closed", () => {
|
||||||
let p = parser.parse(`{messages.length, plural, =0 {one}`, 'TestComp', true);
|
let p = parser.parse(`{messages.length, plural, =0 {one}`, 'TestComp', true);
|
||||||
expect(humanizeErrors(p.errors))
|
expect(humanizeErrors(p.errors))
|
||||||
|
@ -188,7 +188,7 @@ export function main() {
|
|||||||
expect(res[1].sourceSpan.start.offset).toEqual(10);
|
expect(res[1].sourceSpan.start.offset).toEqual(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle the plural special form", () => {
|
it("should handle the plural expansion form", () => {
|
||||||
let translations: {[key: string]: string} = {};
|
let translations: {[key: string]: string} = {};
|
||||||
translations[id(new Message('zero<ph name="e1">bold</ph>', "plural_0", null))] =
|
translations[id(new Message('zero<ph name="e1">bold</ph>', "plural_0", null))] =
|
||||||
'ZERO<ph name="e1">BOLD</ph>';
|
'ZERO<ph name="e1">BOLD</ph>';
|
||||||
@ -200,7 +200,7 @@ export function main() {
|
|||||||
[HtmlElementAst, 'ul', 0],
|
[HtmlElementAst, 'ul', 0],
|
||||||
[HtmlAttrAst, '[ngPlural]', 'messages.length'],
|
[HtmlAttrAst, '[ngPlural]', 'messages.length'],
|
||||||
[HtmlElementAst, 'template', 1],
|
[HtmlElementAst, 'template', 1],
|
||||||
[HtmlAttrAst, '[ngPluralCase]', '0'],
|
[HtmlAttrAst, 'ngPluralCase', '0'],
|
||||||
[HtmlElementAst, 'li', 2],
|
[HtmlElementAst, 'li', 2],
|
||||||
[HtmlTextAst, 'ZERO', 3],
|
[HtmlTextAst, 'ZERO', 3],
|
||||||
[HtmlElementAst, 'b', 3],
|
[HtmlElementAst, 'b', 3],
|
||||||
@ -208,6 +208,31 @@ export function main() {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should handle nested expansion forms", () => {
|
||||||
|
let translations: {[key: string]: string} = {};
|
||||||
|
translations[id(new Message('m', "gender_m", null))] = 'M';
|
||||||
|
|
||||||
|
let res = parse(`{messages.length, plural, =0 { {p.gender, gender, =m {m}} }}`, translations);
|
||||||
|
|
||||||
|
expect(humanizeDom(res))
|
||||||
|
.toEqual([
|
||||||
|
[HtmlElementAst, 'ul', 0],
|
||||||
|
[HtmlAttrAst, '[ngPlural]', 'messages.length'],
|
||||||
|
[HtmlElementAst, 'template', 1],
|
||||||
|
[HtmlAttrAst, 'ngPluralCase', '0'],
|
||||||
|
[HtmlElementAst, 'li', 2],
|
||||||
|
|
||||||
|
[HtmlElementAst, 'ul', 3],
|
||||||
|
[HtmlAttrAst, '[ngSwitch]', 'p.gender'],
|
||||||
|
[HtmlElementAst, 'template', 4],
|
||||||
|
[HtmlAttrAst, 'ngSwitchWhen', 'm'],
|
||||||
|
[HtmlElementAst, 'li', 5],
|
||||||
|
[HtmlTextAst, 'M', 6],
|
||||||
|
|
||||||
|
[HtmlTextAst, ' ', 3]
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("should correctly set source code positions", () => {
|
it("should correctly set source code positions", () => {
|
||||||
let translations: {[key: string]: string} = {};
|
let translations: {[key: string]: string} = {};
|
||||||
translations[id(new Message('<ph name="e0">bold</ph>', "plural_0", null))] =
|
translations[id(new Message('<ph name="e0">bold</ph>', "plural_0", null))] =
|
||||||
@ -258,7 +283,7 @@ export function main() {
|
|||||||
[HtmlElementAst, 'ul', 0],
|
[HtmlElementAst, 'ul', 0],
|
||||||
[HtmlAttrAst, '[ngSwitch]', 'person.gender'],
|
[HtmlAttrAst, '[ngSwitch]', 'person.gender'],
|
||||||
[HtmlElementAst, 'template', 1],
|
[HtmlElementAst, 'template', 1],
|
||||||
[HtmlAttrAst, '[ngSwitchWhen]', 'male'],
|
[HtmlAttrAst, 'ngSwitchWhen', 'male'],
|
||||||
[HtmlElementAst, 'li', 2],
|
[HtmlElementAst, 'li', 2],
|
||||||
[HtmlTextAst, 'M', 3],
|
[HtmlTextAst, 'M', 3],
|
||||||
]);
|
]);
|
||||||
@ -273,13 +298,13 @@ export function main() {
|
|||||||
it("should error when no matching message (attr)", () => {
|
it("should error when no matching message (attr)", () => {
|
||||||
let mid = id(new Message("some message", null, null));
|
let mid = id(new Message("some message", null, null));
|
||||||
expect(humanizeErrors(parse("<div value='some message' i18n-value></div>", {}).errors))
|
expect(humanizeErrors(parse("<div value='some message' i18n-value></div>", {}).errors))
|
||||||
.toEqual([`Cannot find message for id '${mid}'`]);
|
.toEqual([`Cannot find message for id '${mid}', content 'some message'.`]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should error when no matching message (text)", () => {
|
it("should error when no matching message (text)", () => {
|
||||||
let mid = id(new Message("some message", null, null));
|
let mid = id(new Message("some message", null, null));
|
||||||
expect(humanizeErrors(parse("<div i18n>some message</div>", {}).errors))
|
expect(humanizeErrors(parse("<div i18n>some message</div>", {}).errors))
|
||||||
.toEqual([`Cannot find message for id '${mid}'`]);
|
.toEqual([`Cannot find message for id '${mid}', content 'some message'.`]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should error when a non-placeholder element appears in translation", () => {
|
it("should error when a non-placeholder element appears in translation", () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user