2016-06-23 12:47:54 -04:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
2016-07-21 14:41:25 -04:00
|
|
|
import {ParseError, ParseSourceSpan} from '../parse_util';
|
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
import * as html from './ast';
|
2016-06-08 19:38:52 -04:00
|
|
|
|
2016-06-09 17:53:03 -04:00
|
|
|
// http://cldr.unicode.org/index/cldr-spec/plural-rules
|
|
|
|
const PLURAL_CASES: string[] = ['zero', 'one', 'two', 'few', 'many', 'other'];
|
2016-04-13 19:01:25 -04:00
|
|
|
|
2016-04-12 14:46:49 -04:00
|
|
|
/**
|
|
|
|
* Expands special forms into elements.
|
|
|
|
*
|
|
|
|
* For example,
|
|
|
|
*
|
|
|
|
* ```
|
|
|
|
* { messages.length, plural,
|
|
|
|
* =0 {zero}
|
|
|
|
* =1 {one}
|
2016-06-09 16:48:53 -04:00
|
|
|
* other {more than one}
|
2016-04-12 14:46:49 -04:00
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* will be expanded into
|
|
|
|
*
|
|
|
|
* ```
|
2016-06-17 14:38:24 -04:00
|
|
|
* <ng-container [ngPlural]="messages.length">
|
2016-12-16 18:33:16 -05:00
|
|
|
* <template ngPluralCase="=0">zero</template>
|
|
|
|
* <template ngPluralCase="=1">one</template>
|
|
|
|
* <template ngPluralCase="other">more than one</template>
|
2016-06-17 14:38:24 -04:00
|
|
|
* </ng-container>
|
2016-04-12 14:46:49 -04:00
|
|
|
* ```
|
|
|
|
*/
|
2016-07-21 16:56:58 -04:00
|
|
|
export function expandNodes(nodes: html.Node[]): ExpansionResult {
|
2016-06-17 14:38:24 -04:00
|
|
|
const expander = new _Expander();
|
2016-07-21 16:56:58 -04:00
|
|
|
return new ExpansionResult(html.visitAll(expander, nodes), expander.isExpanded, expander.errors);
|
2016-04-13 19:01:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export class ExpansionResult {
|
2016-07-21 16:56:58 -04:00
|
|
|
constructor(public nodes: html.Node[], public expanded: boolean, public errors: ParseError[]) {}
|
2016-04-13 19:01:25 -04:00
|
|
|
}
|
|
|
|
|
2016-06-30 17:59:23 -04:00
|
|
|
export class ExpansionError extends ParseError {
|
|
|
|
constructor(span: ParseSourceSpan, errorMsg: string) { super(span, errorMsg); }
|
|
|
|
}
|
|
|
|
|
2016-06-14 20:50:23 -04:00
|
|
|
/**
|
|
|
|
* Expand expansion forms (plural, select) to directives
|
|
|
|
*
|
|
|
|
* @internal
|
|
|
|
*/
|
2016-07-21 16:56:58 -04:00
|
|
|
class _Expander implements html.Visitor {
|
2016-06-17 14:38:24 -04:00
|
|
|
isExpanded: boolean = false;
|
2016-06-09 17:53:03 -04:00
|
|
|
errors: ParseError[] = [];
|
2016-04-12 14:46:49 -04:00
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
visitElement(element: html.Element, context: any): any {
|
|
|
|
return new html.Element(
|
|
|
|
element.name, element.attrs, html.visitAll(this, element.children), element.sourceSpan,
|
|
|
|
element.startSourceSpan, element.endSourceSpan);
|
2016-04-12 14:46:49 -04:00
|
|
|
}
|
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
visitAttribute(attribute: html.Attribute, context: any): any { return attribute; }
|
2016-04-12 14:46:49 -04:00
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
visitText(text: html.Text, context: any): any { return text; }
|
2016-04-12 14:46:49 -04:00
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
visitComment(comment: html.Comment, context: any): any { return comment; }
|
2016-04-12 14:46:49 -04:00
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
visitExpansion(icu: html.Expansion, context: any): any {
|
2016-06-17 14:38:24 -04:00
|
|
|
this.isExpanded = true;
|
2016-07-21 16:56:58 -04:00
|
|
|
return icu.type == 'plural' ? _expandPluralForm(icu, this.errors) :
|
|
|
|
_expandDefaultForm(icu, this.errors);
|
2016-04-12 14:46:49 -04:00
|
|
|
}
|
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
visitExpansionCase(icuCase: html.ExpansionCase, context: any): any {
|
2016-06-30 17:59:23 -04:00
|
|
|
throw new Error('Should not be reached');
|
2016-04-12 14:46:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
function _expandPluralForm(ast: html.Expansion, errors: ParseError[]): html.Element {
|
2016-06-17 14:38:24 -04:00
|
|
|
const children = ast.cases.map(c => {
|
2016-06-09 17:53:03 -04:00
|
|
|
if (PLURAL_CASES.indexOf(c.value) == -1 && !c.value.match(/^=\d+$/)) {
|
2016-06-30 17:59:23 -04:00
|
|
|
errors.push(new ExpansionError(
|
2016-06-09 17:53:03 -04:00
|
|
|
c.valueSourceSpan,
|
|
|
|
`Plural cases should be "=<number>" or one of ${PLURAL_CASES.join(", ")}`));
|
|
|
|
}
|
2016-06-17 14:38:24 -04:00
|
|
|
|
|
|
|
const expansionResult = expandNodes(c.expression);
|
2016-06-14 20:50:23 -04:00
|
|
|
errors.push(...expansionResult.errors);
|
2016-04-13 19:01:25 -04:00
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
return new html.Element(
|
|
|
|
`template`, [new html.Attribute('ngPluralCase', `${c.value}`, c.valueSourceSpan)],
|
2016-06-17 14:38:24 -04:00
|
|
|
expansionResult.nodes, c.sourceSpan, c.sourceSpan, c.sourceSpan);
|
2016-04-13 19:01:25 -04:00
|
|
|
});
|
2016-07-21 16:56:58 -04:00
|
|
|
const switchAttr = new html.Attribute('[ngPlural]', ast.switchValue, ast.switchValueSourceSpan);
|
|
|
|
return new html.Element(
|
2016-06-17 14:38:24 -04:00
|
|
|
'ng-container', [switchAttr], children, ast.sourceSpan, ast.sourceSpan, ast.sourceSpan);
|
2016-04-12 14:46:49 -04:00
|
|
|
}
|
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
function _expandDefaultForm(ast: html.Expansion, errors: ParseError[]): html.Element {
|
2016-11-12 08:08:58 -05:00
|
|
|
const children = ast.cases.map(c => {
|
2016-06-17 14:38:24 -04:00
|
|
|
const expansionResult = expandNodes(c.expression);
|
|
|
|
errors.push(...expansionResult.errors);
|
2016-04-13 19:01:25 -04:00
|
|
|
|
2016-11-04 17:52:02 -04:00
|
|
|
if (c.value === 'other') {
|
|
|
|
// other is the default case when no values match
|
|
|
|
return new html.Element(
|
|
|
|
`template`, [new html.Attribute('ngSwitchDefault', '', c.valueSourceSpan)],
|
|
|
|
expansionResult.nodes, c.sourceSpan, c.sourceSpan, c.sourceSpan);
|
|
|
|
}
|
|
|
|
|
2016-07-21 16:56:58 -04:00
|
|
|
return new html.Element(
|
|
|
|
`template`, [new html.Attribute('ngSwitchCase', `${c.value}`, c.valueSourceSpan)],
|
2016-06-17 14:38:24 -04:00
|
|
|
expansionResult.nodes, c.sourceSpan, c.sourceSpan, c.sourceSpan);
|
2016-04-13 19:01:25 -04:00
|
|
|
});
|
2016-07-21 16:56:58 -04:00
|
|
|
const switchAttr = new html.Attribute('[ngSwitch]', ast.switchValue, ast.switchValueSourceSpan);
|
|
|
|
return new html.Element(
|
2016-06-17 14:38:24 -04:00
|
|
|
'ng-container', [switchAttr], children, ast.sourceSpan, ast.sourceSpan, ast.sourceSpan);
|
2016-04-28 20:50:03 -04:00
|
|
|
}
|