2018-09-21 18:42:07 -04:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 15:08:49 -04:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2018-09-21 18:42:07 -04:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
|
|
|
import * as e from '../../../src/expression_parser/ast';
|
2018-10-18 13:08:51 -04:00
|
|
|
import {Lexer} from '../../../src/expression_parser/lexer';
|
|
|
|
import {Parser} from '../../../src/expression_parser/parser';
|
|
|
|
import * as html from '../../../src/ml_parser/ast';
|
2019-10-01 07:34:04 -04:00
|
|
|
import {HtmlParser, ParseTreeResult} from '../../../src/ml_parser/html_parser';
|
2018-10-18 13:08:51 -04:00
|
|
|
import {WhitespaceVisitor} from '../../../src/ml_parser/html_whitespaces';
|
2019-10-01 07:34:04 -04:00
|
|
|
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../src/ml_parser/interpolation_config';
|
2018-09-21 18:42:07 -04:00
|
|
|
import * as a from '../../../src/render3/r3_ast';
|
2020-04-08 13:14:18 -04:00
|
|
|
import {htmlAstToRender3Ast, Render3ParseResult} from '../../../src/render3/r3_template_transform';
|
2019-10-01 07:34:04 -04:00
|
|
|
import {I18nMetaVisitor} from '../../../src/render3/view/i18n/meta';
|
2021-01-21 15:48:42 -05:00
|
|
|
import {LEADING_TRIVIA_CHARS} from '../../../src/render3/view/template';
|
2018-10-18 13:08:51 -04:00
|
|
|
import {BindingParser} from '../../../src/template_parser/binding_parser';
|
|
|
|
import {MockSchemaRegistry} from '../../../testing';
|
2018-09-21 18:42:07 -04:00
|
|
|
|
|
|
|
export function findExpression(tmpl: a.Node[], expr: string): e.AST|null {
|
|
|
|
const res = tmpl.reduce((found, node) => {
|
|
|
|
if (found !== null) {
|
|
|
|
return found;
|
|
|
|
} else {
|
|
|
|
return findExpressionInNode(node, expr);
|
|
|
|
}
|
|
|
|
}, null as e.AST | null);
|
|
|
|
if (res instanceof e.ASTWithSource) {
|
|
|
|
return res.ast;
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
function findExpressionInNode(node: a.Node, expr: string): e.AST|null {
|
|
|
|
if (node instanceof a.Element || node instanceof a.Template) {
|
|
|
|
return findExpression(
|
|
|
|
[
|
|
|
|
...node.inputs,
|
|
|
|
...node.outputs,
|
|
|
|
...node.children,
|
|
|
|
],
|
|
|
|
expr);
|
|
|
|
} else if (node instanceof a.BoundAttribute || node instanceof a.BoundText) {
|
|
|
|
const ts = toStringExpression(node.value);
|
|
|
|
return toStringExpression(node.value) === expr ? node.value : null;
|
|
|
|
} else if (node instanceof a.BoundEvent) {
|
|
|
|
return toStringExpression(node.handler) === expr ? node.handler : null;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function toStringExpression(expr: e.AST): string {
|
|
|
|
while (expr instanceof e.ASTWithSource) {
|
|
|
|
expr = expr.ast;
|
|
|
|
}
|
|
|
|
if (expr instanceof e.PropertyRead) {
|
|
|
|
if (expr.receiver instanceof e.ImplicitReceiver) {
|
|
|
|
return expr.name;
|
|
|
|
} else {
|
|
|
|
return `${toStringExpression(expr.receiver)}.${expr.name}`;
|
|
|
|
}
|
|
|
|
} else if (expr instanceof e.ImplicitReceiver) {
|
|
|
|
return '';
|
|
|
|
} else if (expr instanceof e.Interpolation) {
|
|
|
|
let str = '{{';
|
|
|
|
for (let i = 0; i < expr.expressions.length; i++) {
|
|
|
|
str += expr.strings[i] + toStringExpression(expr.expressions[i]);
|
|
|
|
}
|
|
|
|
str += expr.strings[expr.strings.length - 1] + '}}';
|
|
|
|
return str;
|
|
|
|
} else {
|
|
|
|
throw new Error(`Unsupported type: ${(expr as any).constructor.name}`);
|
|
|
|
}
|
|
|
|
}
|
2018-10-18 13:08:51 -04:00
|
|
|
|
|
|
|
// Parse an html string to IVY specific info
|
|
|
|
export function parseR3(
|
2020-12-01 22:14:07 -05:00
|
|
|
input: string,
|
|
|
|
options: {preserveWhitespaces?: boolean,
|
|
|
|
leadingTriviaChars?: string[],
|
|
|
|
ignoreError?: boolean} = {}): Render3ParseResult {
|
2018-10-18 13:08:51 -04:00
|
|
|
const htmlParser = new HtmlParser();
|
|
|
|
|
2021-01-21 15:48:42 -05:00
|
|
|
const parseResult = htmlParser.parse(input, 'path:://to/template', {
|
|
|
|
tokenizeExpansionForms: true,
|
|
|
|
leadingTriviaChars: options.leadingTriviaChars ?? LEADING_TRIVIA_CHARS,
|
|
|
|
});
|
2018-10-18 13:08:51 -04:00
|
|
|
|
2020-12-01 22:14:07 -05:00
|
|
|
if (parseResult.errors.length > 0 && !options.ignoreError) {
|
2018-10-18 13:08:51 -04:00
|
|
|
const msg = parseResult.errors.map(e => e.toString()).join('\n');
|
|
|
|
throw new Error(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
let htmlNodes = processI18nMeta(parseResult).rootNodes;
|
|
|
|
|
|
|
|
if (!options.preserveWhitespaces) {
|
|
|
|
htmlNodes = html.visitAll(new WhitespaceVisitor(), htmlNodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
const expressionParser = new Parser(new Lexer());
|
|
|
|
const schemaRegistry = new MockSchemaRegistry(
|
|
|
|
{'invalidProp': false}, {'mappedAttr': 'mappedProp'}, {'unknown': false, 'un-known': false},
|
|
|
|
['onEvent'], ['onEvent']);
|
|
|
|
const bindingParser =
|
|
|
|
new BindingParser(expressionParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, null, []);
|
2020-08-25 09:46:40 -04:00
|
|
|
const r3Result = htmlAstToRender3Ast(htmlNodes, bindingParser);
|
|
|
|
|
2020-12-01 22:14:07 -05:00
|
|
|
if (r3Result.errors.length > 0 && !options.ignoreError) {
|
2020-08-25 09:46:40 -04:00
|
|
|
const msg = r3Result.errors.map(e => e.toString()).join('\n');
|
|
|
|
throw new Error(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
return r3Result;
|
2019-10-01 07:34:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export function processI18nMeta(
|
|
|
|
htmlAstWithErrors: ParseTreeResult,
|
|
|
|
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ParseTreeResult {
|
|
|
|
return new ParseTreeResult(
|
|
|
|
html.visitAll(
|
|
|
|
new I18nMetaVisitor(interpolationConfig, /* keepI18nAttrs */ false),
|
|
|
|
htmlAstWithErrors.rootNodes),
|
|
|
|
htmlAstWithErrors.errors);
|
|
|
|
}
|