` implies
* that `width=0` and `height=1`)
*/
this._stylesIndex = new Map();
/**
* Represents the location of each class binding in the template
* (e.g. `
` implies
* that `big=0` and `hidden=1`)
*/
this._classesIndex = new Map();
this._initialStyleValues = [];
this._initialClassValues = [];
}
/**
* Registers a given input to the styling builder to be later used when producing AOT code.
*
* The code below will only accept the input if it is somehow tied to styling (whether it be
* style/class bindings or static style/class attributes).
*/
registerBoundInput(input) {
// [attr.style] or [attr.class] are skipped in the code below,
// they should not be treated as styling-based bindings since
// they are intended to be written directly to the attr and
// will therefore skip all style/class resolution that is present
// with style="", [style]="" and [style.prop]="", class="",
// [class.prop]="". [class]="" assignments
let binding = null;
let name = input.name;
switch (input.type) {
case 0 /* BindingType.Property */:
binding = this.registerInputBasedOnName(name, input.value, input.sourceSpan);
break;
case 3 /* BindingType.Style */:
binding = this.registerStyleInput(name, false, input.value, input.sourceSpan, input.unit);
break;
case 2 /* BindingType.Class */:
binding = this.registerClassInput(name, false, input.value, input.sourceSpan);
break;
}
return binding ? true : false;
}
registerInputBasedOnName(name, expression, sourceSpan) {
let binding = null;
const prefix = name.substring(0, 6);
const isStyle = name === 'style' || prefix === 'style.' || prefix === 'style!';
const isClass = !isStyle && (name === 'class' || prefix === 'class.' || prefix === 'class!');
if (isStyle || isClass) {
const isMapBased = name.charAt(5) !== '.'; // style.prop or class.prop makes this a no
const property = name.slice(isMapBased ? 5 : 6); // the dot explains why there's a +1
if (isStyle) {
binding = this.registerStyleInput(property, isMapBased, expression, sourceSpan);
}
else {
binding = this.registerClassInput(property, isMapBased, expression, sourceSpan);
}
}
return binding;
}
registerStyleInput(name, isMapBased, value, sourceSpan, suffix) {
if (isEmptyExpression(value)) {
return null;
}
// CSS custom properties are case-sensitive so we shouldn't normalize them.
// See: https://www.w3.org/TR/css-variables-1/#defining-variables
if (!isCssCustomProperty(name)) {
name = hyphenate$1(name);
}
const { property, hasOverrideFlag, suffix: bindingSuffix } = parseProperty(name);
suffix = typeof suffix === 'string' && suffix.length !== 0 ? suffix : bindingSuffix;
const entry = { name: property, suffix: suffix, value, sourceSpan, hasOverrideFlag };
if (isMapBased) {
this._styleMapInput = entry;
}
else {
(this._singleStyleInputs = this._singleStyleInputs || []).push(entry);
registerIntoMap(this._stylesIndex, property);
}
this._lastStylingInput = entry;
this._firstStylingInput = this._firstStylingInput || entry;
this._checkForPipes(value);
this.hasBindings = true;
return entry;
}
registerClassInput(name, isMapBased, value, sourceSpan) {
if (isEmptyExpression(value)) {
return null;
}
const { property, hasOverrideFlag } = parseProperty(name);
const entry = { name: property, value, sourceSpan, hasOverrideFlag, suffix: null };
if (isMapBased) {
this._classMapInput = entry;
}
else {
(this._singleClassInputs = this._singleClassInputs || []).push(entry);
registerIntoMap(this._classesIndex, property);
}
this._lastStylingInput = entry;
this._firstStylingInput = this._firstStylingInput || entry;
this._checkForPipes(value);
this.hasBindings = true;
return entry;
}
_checkForPipes(value) {
if ((value instanceof ASTWithSource) && (value.ast instanceof BindingPipe)) {
this.hasBindingsWithPipes = true;
}
}
/**
* Registers the element's static style string value to the builder.
*
* @param value the style string (e.g. `width:100px; height:200px;`)
*/
registerStyleAttr(value) {
this._initialStyleValues = parse(value);
this._hasInitialValues = true;
}
/**
* Registers the element's static class string value to the builder.
*
* @param value the className string (e.g. `disabled gold zoom`)
*/
registerClassAttr(value) {
this._initialClassValues = value.trim().split(/\s+/g);
this._hasInitialValues = true;
}
/**
* Appends all styling-related expressions to the provided attrs array.
*
* @param attrs an existing array where each of the styling expressions
* will be inserted into.
*/
populateInitialStylingAttrs(attrs) {
// [CLASS_MARKER, 'foo', 'bar', 'baz' ...]
if (this._initialClassValues.length) {
attrs.push(literal(1 /* AttributeMarker.Classes */));
for (let i = 0; i < this._initialClassValues.length; i++) {
attrs.push(literal(this._initialClassValues[i]));
}
}
// [STYLE_MARKER, 'width', '200px', 'height', '100px', ...]
if (this._initialStyleValues.length) {
attrs.push(literal(2 /* AttributeMarker.Styles */));
for (let i = 0; i < this._initialStyleValues.length; i += 2) {
attrs.push(literal(this._initialStyleValues[i]), literal(this._initialStyleValues[i + 1]));
}
}
}
/**
* Builds an instruction with all the expressions and parameters for `elementHostAttrs`.
*
* The instruction generation code below is used for producing the AOT statement code which is
* responsible for registering initial styles (within a directive hostBindings' creation block),
* as well as any of the provided attribute values, to the directive host element.
*/
assignHostAttrs(attrs, definitionMap) {
if (this._directiveExpr && (attrs.length || this._hasInitialValues)) {
this.populateInitialStylingAttrs(attrs);
definitionMap.set('hostAttrs', literalArr(attrs));
}
}
/**
* Builds an instruction with all the expressions and parameters for `classMap`.
*
* The instruction data will contain all expressions for `classMap` to function
* which includes the `[class]` expression params.
*/
buildClassMapInstruction(valueConverter) {
if (this._classMapInput) {
return this._buildMapBasedInstruction(valueConverter, true, this._classMapInput);
}
return null;
}
/**
* Builds an instruction with all the expressions and parameters for `styleMap`.
*
* The instruction data will contain all expressions for `styleMap` to function
* which includes the `[style]` expression params.
*/
buildStyleMapInstruction(valueConverter) {
if (this._styleMapInput) {
return this._buildMapBasedInstruction(valueConverter, false, this._styleMapInput);
}
return null;
}
_buildMapBasedInstruction(valueConverter, isClassBased, stylingInput) {
// each styling binding value is stored in the LView
// map-based bindings allocate two slots: one for the
// previous binding value and another for the previous
// className or style attribute value.
let totalBindingSlotsRequired = MIN_STYLING_BINDING_SLOTS_REQUIRED;
// these values must be outside of the update block so that they can
// be evaluated (the AST visit call) during creation time so that any
// pipes can be picked up in time before the template is built
const mapValue = stylingInput.value.visit(valueConverter);
let reference;
if (mapValue instanceof Interpolation$1) {
totalBindingSlotsRequired += mapValue.expressions.length;
reference = isClassBased ? getClassMapInterpolationExpression(mapValue) :
getStyleMapInterpolationExpression(mapValue);
}
else {
reference = isClassBased ? Identifiers.classMap : Identifiers.styleMap;
}
return {
reference,
calls: [{
supportsInterpolation: true,
sourceSpan: stylingInput.sourceSpan,
allocateBindingSlots: totalBindingSlotsRequired,
params: (convertFn) => {
const convertResult = convertFn(mapValue);
const params = Array.isArray(convertResult) ? convertResult : [convertResult];
return params;
}
}]
};
}
_buildSingleInputs(reference, inputs, valueConverter, getInterpolationExpressionFn, isClassBased) {
const instructions = [];
inputs.forEach(input => {
const previousInstruction = instructions[instructions.length - 1];
const value = input.value.visit(valueConverter);
let referenceForCall = reference;
// each styling binding value is stored in the LView
// but there are two values stored for each binding:
// 1) the value itself
// 2) an intermediate value (concatenation of style up to this point).
// We need to store the intermediate value so that we don't allocate
// the strings on each CD.
let totalBindingSlotsRequired = MIN_STYLING_BINDING_SLOTS_REQUIRED;
if (value instanceof Interpolation$1) {
totalBindingSlotsRequired += value.expressions.length;
if (getInterpolationExpressionFn) {
referenceForCall = getInterpolationExpressionFn(value);
}
}
const call = {
sourceSpan: input.sourceSpan,
allocateBindingSlots: totalBindingSlotsRequired,
supportsInterpolation: !!getInterpolationExpressionFn,
params: (convertFn) => {
// params => stylingProp(propName, value, suffix)
const params = [];
params.push(literal(input.name));
const convertResult = convertFn(value);
if (Array.isArray(convertResult)) {
params.push(...convertResult);
}
else {
params.push(convertResult);
}
// [style.prop] bindings may use suffix values (e.g. px, em, etc...), therefore,
// if that is detected then we need to pass that in as an optional param.
if (!isClassBased && input.suffix !== null) {
params.push(literal(input.suffix));
}
return params;
}
};
// If we ended up generating a call to the same instruction as the previous styling property
// we can chain the calls together safely to save some bytes, otherwise we have to generate
// a separate instruction call. This is primarily a concern with interpolation instructions
// where we may start off with one `reference`, but end up using another based on the
// number of interpolations.
if (previousInstruction && previousInstruction.reference === referenceForCall) {
previousInstruction.calls.push(call);
}
else {
instructions.push({ reference: referenceForCall, calls: [call] });
}
});
return instructions;
}
_buildClassInputs(valueConverter) {
if (this._singleClassInputs) {
return this._buildSingleInputs(Identifiers.classProp, this._singleClassInputs, valueConverter, null, true);
}
return [];
}
_buildStyleInputs(valueConverter) {
if (this._singleStyleInputs) {
return this._buildSingleInputs(Identifiers.styleProp, this._singleStyleInputs, valueConverter, getStylePropInterpolationExpression, false);
}
return [];
}
/**
* Constructs all instructions which contain the expressions that will be placed
* into the update block of a template function or a directive hostBindings function.
*/
buildUpdateLevelInstructions(valueConverter) {
const instructions = [];
if (this.hasBindings) {
const styleMapInstruction = this.buildStyleMapInstruction(valueConverter);
if (styleMapInstruction) {
instructions.push(styleMapInstruction);
}
const classMapInstruction = this.buildClassMapInstruction(valueConverter);
if (classMapInstruction) {
instructions.push(classMapInstruction);
}
instructions.push(...this._buildStyleInputs(valueConverter));
instructions.push(...this._buildClassInputs(valueConverter));
}
return instructions;
}
}
function registerIntoMap(map, key) {
if (!map.has(key)) {
map.set(key, map.size);
}
}
function parseProperty(name) {
let hasOverrideFlag = false;
const overrideIndex = name.indexOf(IMPORTANT_FLAG);
if (overrideIndex !== -1) {
name = overrideIndex > 0 ? name.substring(0, overrideIndex) : '';
hasOverrideFlag = true;
}
let suffix = null;
let property = name;
const unitIndex = name.lastIndexOf('.');
if (unitIndex > 0) {
suffix = name.slice(unitIndex + 1);
property = name.substring(0, unitIndex);
}
return { property, suffix, hasOverrideFlag };
}
/**
* Gets the instruction to generate for an interpolated class map.
* @param interpolation An Interpolation AST
*/
function getClassMapInterpolationExpression(interpolation) {
switch (getInterpolationArgsLength(interpolation)) {
case 1:
return Identifiers.classMap;
case 3:
return Identifiers.classMapInterpolate1;
case 5:
return Identifiers.classMapInterpolate2;
case 7:
return Identifiers.classMapInterpolate3;
case 9:
return Identifiers.classMapInterpolate4;
case 11:
return Identifiers.classMapInterpolate5;
case 13:
return Identifiers.classMapInterpolate6;
case 15:
return Identifiers.classMapInterpolate7;
case 17:
return Identifiers.classMapInterpolate8;
default:
return Identifiers.classMapInterpolateV;
}
}
/**
* Gets the instruction to generate for an interpolated style map.
* @param interpolation An Interpolation AST
*/
function getStyleMapInterpolationExpression(interpolation) {
switch (getInterpolationArgsLength(interpolation)) {
case 1:
return Identifiers.styleMap;
case 3:
return Identifiers.styleMapInterpolate1;
case 5:
return Identifiers.styleMapInterpolate2;
case 7:
return Identifiers.styleMapInterpolate3;
case 9:
return Identifiers.styleMapInterpolate4;
case 11:
return Identifiers.styleMapInterpolate5;
case 13:
return Identifiers.styleMapInterpolate6;
case 15:
return Identifiers.styleMapInterpolate7;
case 17:
return Identifiers.styleMapInterpolate8;
default:
return Identifiers.styleMapInterpolateV;
}
}
/**
* Gets the instruction to generate for an interpolated style prop.
* @param interpolation An Interpolation AST
*/
function getStylePropInterpolationExpression(interpolation) {
switch (getInterpolationArgsLength(interpolation)) {
case 1:
return Identifiers.styleProp;
case 3:
return Identifiers.stylePropInterpolate1;
case 5:
return Identifiers.stylePropInterpolate2;
case 7:
return Identifiers.stylePropInterpolate3;
case 9:
return Identifiers.stylePropInterpolate4;
case 11:
return Identifiers.stylePropInterpolate5;
case 13:
return Identifiers.stylePropInterpolate6;
case 15:
return Identifiers.stylePropInterpolate7;
case 17:
return Identifiers.stylePropInterpolate8;
default:
return Identifiers.stylePropInterpolateV;
}
}
/**
* Checks whether property name is a custom CSS property.
* See: https://www.w3.org/TR/css-variables-1
*/
function isCssCustomProperty(name) {
return name.startsWith('--');
}
function isEmptyExpression(ast) {
if (ast instanceof ASTWithSource) {
ast = ast.ast;
}
return ast instanceof EmptyExpr$1;
}
var TokenType;
(function (TokenType) {
TokenType[TokenType["Character"] = 0] = "Character";
TokenType[TokenType["Identifier"] = 1] = "Identifier";
TokenType[TokenType["PrivateIdentifier"] = 2] = "PrivateIdentifier";
TokenType[TokenType["Keyword"] = 3] = "Keyword";
TokenType[TokenType["String"] = 4] = "String";
TokenType[TokenType["Operator"] = 5] = "Operator";
TokenType[TokenType["Number"] = 6] = "Number";
TokenType[TokenType["Error"] = 7] = "Error";
})(TokenType || (TokenType = {}));
const KEYWORDS = ['var', 'let', 'as', 'null', 'undefined', 'true', 'false', 'if', 'else', 'this'];
class Lexer {
tokenize(text) {
const scanner = new _Scanner(text);
const tokens = [];
let token = scanner.scanToken();
while (token != null) {
tokens.push(token);
token = scanner.scanToken();
}
return tokens;
}
}
class Token {
constructor(index, end, type, numValue, strValue) {
this.index = index;
this.end = end;
this.type = type;
this.numValue = numValue;
this.strValue = strValue;
}
isCharacter(code) {
return this.type == TokenType.Character && this.numValue == code;
}
isNumber() {
return this.type == TokenType.Number;
}
isString() {
return this.type == TokenType.String;
}
isOperator(operator) {
return this.type == TokenType.Operator && this.strValue == operator;
}
isIdentifier() {
return this.type == TokenType.Identifier;
}
isPrivateIdentifier() {
return this.type == TokenType.PrivateIdentifier;
}
isKeyword() {
return this.type == TokenType.Keyword;
}
isKeywordLet() {
return this.type == TokenType.Keyword && this.strValue == 'let';
}
isKeywordAs() {
return this.type == TokenType.Keyword && this.strValue == 'as';
}
isKeywordNull() {
return this.type == TokenType.Keyword && this.strValue == 'null';
}
isKeywordUndefined() {
return this.type == TokenType.Keyword && this.strValue == 'undefined';
}
isKeywordTrue() {
return this.type == TokenType.Keyword && this.strValue == 'true';
}
isKeywordFalse() {
return this.type == TokenType.Keyword && this.strValue == 'false';
}
isKeywordThis() {
return this.type == TokenType.Keyword && this.strValue == 'this';
}
isError() {
return this.type == TokenType.Error;
}
toNumber() {
return this.type == TokenType.Number ? this.numValue : -1;
}
toString() {
switch (this.type) {
case TokenType.Character:
case TokenType.Identifier:
case TokenType.Keyword:
case TokenType.Operator:
case TokenType.PrivateIdentifier:
case TokenType.String:
case TokenType.Error:
return this.strValue;
case TokenType.Number:
return this.numValue.toString();
default:
return null;
}
}
}
function newCharacterToken(index, end, code) {
return new Token(index, end, TokenType.Character, code, String.fromCharCode(code));
}
function newIdentifierToken(index, end, text) {
return new Token(index, end, TokenType.Identifier, 0, text);
}
function newPrivateIdentifierToken(index, end, text) {
return new Token(index, end, TokenType.PrivateIdentifier, 0, text);
}
function newKeywordToken(index, end, text) {
return new Token(index, end, TokenType.Keyword, 0, text);
}
function newOperatorToken(index, end, text) {
return new Token(index, end, TokenType.Operator, 0, text);
}
function newStringToken(index, end, text) {
return new Token(index, end, TokenType.String, 0, text);
}
function newNumberToken(index, end, n) {
return new Token(index, end, TokenType.Number, n, '');
}
function newErrorToken(index, end, message) {
return new Token(index, end, TokenType.Error, 0, message);
}
const EOF = new Token(-1, -1, TokenType.Character, 0, '');
class _Scanner {
constructor(input) {
this.input = input;
this.peek = 0;
this.index = -1;
this.length = input.length;
this.advance();
}
advance() {
this.peek = ++this.index >= this.length ? $EOF : this.input.charCodeAt(this.index);
}
scanToken() {
const input = this.input, length = this.length;
let peek = this.peek, index = this.index;
// Skip whitespace.
while (peek <= $SPACE) {
if (++index >= length) {
peek = $EOF;
break;
}
else {
peek = input.charCodeAt(index);
}
}
this.peek = peek;
this.index = index;
if (index >= length) {
return null;
}
// Handle identifiers and numbers.
if (isIdentifierStart(peek))
return this.scanIdentifier();
if (isDigit(peek))
return this.scanNumber(index);
const start = index;
switch (peek) {
case $PERIOD:
this.advance();
return isDigit(this.peek) ? this.scanNumber(start) :
newCharacterToken(start, this.index, $PERIOD);
case $LPAREN:
case $RPAREN:
case $LBRACE:
case $RBRACE:
case $LBRACKET:
case $RBRACKET:
case $COMMA:
case $COLON:
case $SEMICOLON:
return this.scanCharacter(start, peek);
case $SQ:
case $DQ:
return this.scanString();
case $HASH:
return this.scanPrivateIdentifier();
case $PLUS:
case $MINUS:
case $STAR:
case $SLASH:
case $PERCENT:
case $CARET:
return this.scanOperator(start, String.fromCharCode(peek));
case $QUESTION:
return this.scanQuestion(start);
case $LT:
case $GT:
return this.scanComplexOperator(start, String.fromCharCode(peek), $EQ, '=');
case $BANG:
case $EQ:
return this.scanComplexOperator(start, String.fromCharCode(peek), $EQ, '=', $EQ, '=');
case $AMPERSAND:
return this.scanComplexOperator(start, '&', $AMPERSAND, '&');
case $BAR:
return this.scanComplexOperator(start, '|', $BAR, '|');
case $NBSP:
while (isWhitespace(this.peek))
this.advance();
return this.scanToken();
}
this.advance();
return this.error(`Unexpected character [${String.fromCharCode(peek)}]`, 0);
}
scanCharacter(start, code) {
this.advance();
return newCharacterToken(start, this.index, code);
}
scanOperator(start, str) {
this.advance();
return newOperatorToken(start, this.index, str);
}
/**
* Tokenize a 2/3 char long operator
*
* @param start start index in the expression
* @param one first symbol (always part of the operator)
* @param twoCode code point for the second symbol
* @param two second symbol (part of the operator when the second code point matches)
* @param threeCode code point for the third symbol
* @param three third symbol (part of the operator when provided and matches source expression)
*/
scanComplexOperator(start, one, twoCode, two, threeCode, three) {
this.advance();
let str = one;
if (this.peek == twoCode) {
this.advance();
str += two;
}
if (threeCode != null && this.peek == threeCode) {
this.advance();
str += three;
}
return newOperatorToken(start, this.index, str);
}
scanIdentifier() {
const start = this.index;
this.advance();
while (isIdentifierPart(this.peek))
this.advance();
const str = this.input.substring(start, this.index);
return KEYWORDS.indexOf(str) > -1 ? newKeywordToken(start, this.index, str) :
newIdentifierToken(start, this.index, str);
}
/** Scans an ECMAScript private identifier. */
scanPrivateIdentifier() {
const start = this.index;
this.advance();
if (!isIdentifierStart(this.peek)) {
return this.error('Invalid character [#]', -1);
}
while (isIdentifierPart(this.peek))
this.advance();
const identifierName = this.input.substring(start, this.index);
return newPrivateIdentifierToken(start, this.index, identifierName);
}
scanNumber(start) {
let simple = (this.index === start);
let hasSeparators = false;
this.advance(); // Skip initial digit.
while (true) {
if (isDigit(this.peek)) {
// Do nothing.
}
else if (this.peek === $_) {
// Separators are only valid when they're surrounded by digits. E.g. `1_0_1` is
// valid while `_101` and `101_` are not. The separator can't be next to the decimal
// point or another separator either. Note that it's unlikely that we'll hit a case where
// the underscore is at the start, because that's a valid identifier and it will be picked
// up earlier in the parsing. We validate for it anyway just in case.
if (!isDigit(this.input.charCodeAt(this.index - 1)) ||
!isDigit(this.input.charCodeAt(this.index + 1))) {
return this.error('Invalid numeric separator', 0);
}
hasSeparators = true;
}
else if (this.peek === $PERIOD) {
simple = false;
}
else if (isExponentStart(this.peek)) {
this.advance();
if (isExponentSign(this.peek))
this.advance();
if (!isDigit(this.peek))
return this.error('Invalid exponent', -1);
simple = false;
}
else {
break;
}
this.advance();
}
let str = this.input.substring(start, this.index);
if (hasSeparators) {
str = str.replace(/_/g, '');
}
const value = simple ? parseIntAutoRadix(str) : parseFloat(str);
return newNumberToken(start, this.index, value);
}
scanString() {
const start = this.index;
const quote = this.peek;
this.advance(); // Skip initial quote.
let buffer = '';
let marker = this.index;
const input = this.input;
while (this.peek != quote) {
if (this.peek == $BACKSLASH) {
buffer += input.substring(marker, this.index);
let unescapedCode;
this.advance(); // mutates this.peek
// @ts-expect-error see microsoft/TypeScript#9998
if (this.peek == $u) {
// 4 character hex code for unicode character.
const hex = input.substring(this.index + 1, this.index + 5);
if (/^[0-9a-f]+$/i.test(hex)) {
unescapedCode = parseInt(hex, 16);
}
else {
return this.error(`Invalid unicode escape [\\u${hex}]`, 0);
}
for (let i = 0; i < 5; i++) {
this.advance();
}
}
else {
unescapedCode = unescape(this.peek);
this.advance();
}
buffer += String.fromCharCode(unescapedCode);
marker = this.index;
}
else if (this.peek == $EOF) {
return this.error('Unterminated quote', 0);
}
else {
this.advance();
}
}
const last = input.substring(marker, this.index);
this.advance(); // Skip terminating quote.
return newStringToken(start, this.index, buffer + last);
}
scanQuestion(start) {
this.advance();
let str = '?';
// Either `a ?? b` or 'a?.b'.
if (this.peek === $QUESTION || this.peek === $PERIOD) {
str += this.peek === $PERIOD ? '.' : '?';
this.advance();
}
return newOperatorToken(start, this.index, str);
}
error(message, offset) {
const position = this.index + offset;
return newErrorToken(position, this.index, `Lexer Error: ${message} at column ${position} in expression [${this.input}]`);
}
}
function isIdentifierStart(code) {
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) ||
(code == $_) || (code == $$);
}
function isIdentifier(input) {
if (input.length == 0)
return false;
const scanner = new _Scanner(input);
if (!isIdentifierStart(scanner.peek))
return false;
scanner.advance();
while (scanner.peek !== $EOF) {
if (!isIdentifierPart(scanner.peek))
return false;
scanner.advance();
}
return true;
}
function isIdentifierPart(code) {
return isAsciiLetter(code) || isDigit(code) || (code == $_) ||
(code == $$);
}
function isExponentStart(code) {
return code == $e || code == $E;
}
function isExponentSign(code) {
return code == $MINUS || code == $PLUS;
}
function unescape(code) {
switch (code) {
case $n:
return $LF;
case $f:
return $FF;
case $r:
return $CR;
case $t:
return $TAB;
case $v:
return $VTAB;
default:
return code;
}
}
function parseIntAutoRadix(text) {
const result = parseInt(text);
if (isNaN(result)) {
throw new Error('Invalid integer literal when parsing ' + text);
}
return result;
}
class SplitInterpolation {
constructor(strings, expressions, offsets) {
this.strings = strings;
this.expressions = expressions;
this.offsets = offsets;
}
}
class TemplateBindingParseResult {
constructor(templateBindings, warnings, errors) {
this.templateBindings = templateBindings;
this.warnings = warnings;
this.errors = errors;
}
}
class Parser$1 {
constructor(_lexer) {
this._lexer = _lexer;
this.errors = [];
}
parseAction(input, isAssignmentEvent, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
this._checkNoInterpolation(input, location, interpolationConfig);
const sourceToLex = this._stripComments(input);
const tokens = this._lexer.tokenize(sourceToLex);
let flags = 1 /* ParseFlags.Action */;
if (isAssignmentEvent) {
flags |= 2 /* ParseFlags.AssignmentEvent */;
}
const ast = new _ParseAST(input, location, absoluteOffset, tokens, flags, this.errors, 0).parseChain();
return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
}
parseBinding(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
const ast = this._parseBindingAst(input, location, absoluteOffset, interpolationConfig);
return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
}
checkSimpleExpression(ast) {
const checker = new SimpleExpressionChecker();
ast.visit(checker);
return checker.errors;
}
// Host bindings parsed here
parseSimpleBinding(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
const ast = this._parseBindingAst(input, location, absoluteOffset, interpolationConfig);
const errors = this.checkSimpleExpression(ast);
if (errors.length > 0) {
this._reportError(`Host binding expression cannot contain ${errors.join(' ')}`, input, location);
}
return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
}
_reportError(message, input, errLocation, ctxLocation) {
this.errors.push(new ParserError(message, input, errLocation, ctxLocation));
}
_parseBindingAst(input, location, absoluteOffset, interpolationConfig) {
this._checkNoInterpolation(input, location, interpolationConfig);
const sourceToLex = this._stripComments(input);
const tokens = this._lexer.tokenize(sourceToLex);
return new _ParseAST(input, location, absoluteOffset, tokens, 0 /* ParseFlags.None */, this.errors, 0)
.parseChain();
}
/**
* Parse microsyntax template expression and return a list of bindings or
* parsing errors in case the given expression is invalid.
*
* For example,
* ```
*
* ^ ^ absoluteValueOffset for `templateValue`
* absoluteKeyOffset for `templateKey`
* ```
* contains three bindings:
* 1. ngFor -> null
* 2. item -> NgForOfContext.$implicit
* 3. ngForOf -> items
*
* This is apparent from the de-sugared template:
* ```
*
* ```
*
* @param templateKey name of directive, without the * prefix. For example: ngIf, ngFor
* @param templateValue RHS of the microsyntax attribute
* @param templateUrl template filename if it's external, component filename if it's inline
* @param absoluteKeyOffset start of the `templateKey`
* @param absoluteValueOffset start of the `templateValue`
*/
parseTemplateBindings(templateKey, templateValue, templateUrl, absoluteKeyOffset, absoluteValueOffset) {
const tokens = this._lexer.tokenize(templateValue);
const parser = new _ParseAST(templateValue, templateUrl, absoluteValueOffset, tokens, 0 /* ParseFlags.None */, this.errors, 0 /* relative offset */);
return parser.parseTemplateBindings({
source: templateKey,
span: new AbsoluteSourceSpan(absoluteKeyOffset, absoluteKeyOffset + templateKey.length),
});
}
parseInterpolation(input, location, absoluteOffset, interpolatedTokens, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
const { strings, expressions, offsets } = this.splitInterpolation(input, location, interpolatedTokens, interpolationConfig);
if (expressions.length === 0)
return null;
const expressionNodes = [];
for (let i = 0; i < expressions.length; ++i) {
const expressionText = expressions[i].text;
const sourceToLex = this._stripComments(expressionText);
const tokens = this._lexer.tokenize(sourceToLex);
const ast = new _ParseAST(input, location, absoluteOffset, tokens, 0 /* ParseFlags.None */, this.errors, offsets[i])
.parseChain();
expressionNodes.push(ast);
}
return this.createInterpolationAst(strings.map(s => s.text), expressionNodes, input, location, absoluteOffset);
}
/**
* Similar to `parseInterpolation`, but treats the provided string as a single expression
* element that would normally appear within the interpolation prefix and suffix (`{{` and `}}`).
* This is used for parsing the switch expression in ICUs.
*/
parseInterpolationExpression(expression, location, absoluteOffset) {
const sourceToLex = this._stripComments(expression);
const tokens = this._lexer.tokenize(sourceToLex);
const ast = new _ParseAST(expression, location, absoluteOffset, tokens, 0 /* ParseFlags.None */, this.errors, 0)
.parseChain();
const strings = ['', '']; // The prefix and suffix strings are both empty
return this.createInterpolationAst(strings, [ast], expression, location, absoluteOffset);
}
createInterpolationAst(strings, expressions, input, location, absoluteOffset) {
const span = new ParseSpan(0, input.length);
const interpolation = new Interpolation$1(span, span.toAbsolute(absoluteOffset), strings, expressions);
return new ASTWithSource(interpolation, input, location, absoluteOffset, this.errors);
}
/**
* Splits a string of text into "raw" text segments and expressions present in interpolations in
* the string.
* Returns `null` if there are no interpolations, otherwise a
* `SplitInterpolation` with splits that look like
* ...
*/
splitInterpolation(input, location, interpolatedTokens, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
const strings = [];
const expressions = [];
const offsets = [];
const inputToTemplateIndexMap = interpolatedTokens ? getIndexMapForOriginalTemplate(interpolatedTokens) : null;
let i = 0;
let atInterpolation = false;
let extendLastString = false;
let { start: interpStart, end: interpEnd } = interpolationConfig;
while (i < input.length) {
if (!atInterpolation) {
// parse until starting {{
const start = i;
i = input.indexOf(interpStart, i);
if (i === -1) {
i = input.length;
}
const text = input.substring(start, i);
strings.push({ text, start, end: i });
atInterpolation = true;
}
else {
// parse from starting {{ to ending }} while ignoring content inside quotes.
const fullStart = i;
const exprStart = fullStart + interpStart.length;
const exprEnd = this._getInterpolationEndIndex(input, interpEnd, exprStart);
if (exprEnd === -1) {
// Could not find the end of the interpolation; do not parse an expression.
// Instead we should extend the content on the last raw string.
atInterpolation = false;
extendLastString = true;
break;
}
const fullEnd = exprEnd + interpEnd.length;
const text = input.substring(exprStart, exprEnd);
if (text.trim().length === 0) {
this._reportError('Blank expressions are not allowed in interpolated strings', input, `at column ${i} in`, location);
}
expressions.push({ text, start: fullStart, end: fullEnd });
const startInOriginalTemplate = inputToTemplateIndexMap?.get(fullStart) ?? fullStart;
const offset = startInOriginalTemplate + interpStart.length;
offsets.push(offset);
i = fullEnd;
atInterpolation = false;
}
}
if (!atInterpolation) {
// If we are now at a text section, add the remaining content as a raw string.
if (extendLastString) {
const piece = strings[strings.length - 1];
piece.text += input.substring(i);
piece.end = input.length;
}
else {
strings.push({ text: input.substring(i), start: i, end: input.length });
}
}
return new SplitInterpolation(strings, expressions, offsets);
}
wrapLiteralPrimitive(input, location, absoluteOffset) {
const span = new ParseSpan(0, input == null ? 0 : input.length);
return new ASTWithSource(new LiteralPrimitive(span, span.toAbsolute(absoluteOffset), input), input, location, absoluteOffset, this.errors);
}
_stripComments(input) {
const i = this._commentStart(input);
return i != null ? input.substring(0, i) : input;
}
_commentStart(input) {
let outerQuote = null;
for (let i = 0; i < input.length - 1; i++) {
const char = input.charCodeAt(i);
const nextChar = input.charCodeAt(i + 1);
if (char === $SLASH && nextChar == $SLASH && outerQuote == null)
return i;
if (outerQuote === char) {
outerQuote = null;
}
else if (outerQuote == null && isQuote(char)) {
outerQuote = char;
}
}
return null;
}
_checkNoInterpolation(input, location, { start, end }) {
let startIndex = -1;
let endIndex = -1;
for (const charIndex of this._forEachUnquotedChar(input, 0)) {
if (startIndex === -1) {
if (input.startsWith(start)) {
startIndex = charIndex;
}
}
else {
endIndex = this._getInterpolationEndIndex(input, end, charIndex);
if (endIndex > -1) {
break;
}
}
}
if (startIndex > -1 && endIndex > -1) {
this._reportError(`Got interpolation (${start}${end}) where expression was expected`, input, `at column ${startIndex} in`, location);
}
}
/**
* Finds the index of the end of an interpolation expression
* while ignoring comments and quoted content.
*/
_getInterpolationEndIndex(input, expressionEnd, start) {
for (const charIndex of this._forEachUnquotedChar(input, start)) {
if (input.startsWith(expressionEnd, charIndex)) {
return charIndex;
}
// Nothing else in the expression matters after we've
// hit a comment so look directly for the end token.
if (input.startsWith('//', charIndex)) {
return input.indexOf(expressionEnd, charIndex);
}
}
return -1;
}
/**
* Generator used to iterate over the character indexes of a string that are outside of quotes.
* @param input String to loop through.
* @param start Index within the string at which to start.
*/
*_forEachUnquotedChar(input, start) {
let currentQuote = null;
let escapeCount = 0;
for (let i = start; i < input.length; i++) {
const char = input[i];
// Skip the characters inside quotes. Note that we only care about the outer-most
// quotes matching up and we need to account for escape characters.
if (isQuote(input.charCodeAt(i)) && (currentQuote === null || currentQuote === char) &&
escapeCount % 2 === 0) {
currentQuote = currentQuote === null ? char : null;
}
else if (currentQuote === null) {
yield i;
}
escapeCount = char === '\\' ? escapeCount + 1 : 0;
}
}
}
/** Describes a stateful context an expression parser is in. */
var ParseContextFlags;
(function (ParseContextFlags) {
ParseContextFlags[ParseContextFlags["None"] = 0] = "None";
/**
* A Writable context is one in which a value may be written to an lvalue.
* For example, after we see a property access, we may expect a write to the
* property via the "=" operator.
* prop
* ^ possible "=" after
*/
ParseContextFlags[ParseContextFlags["Writable"] = 1] = "Writable";
})(ParseContextFlags || (ParseContextFlags = {}));
class _ParseAST {
constructor(input, location, absoluteOffset, tokens, parseFlags, errors, offset) {
this.input = input;
this.location = location;
this.absoluteOffset = absoluteOffset;
this.tokens = tokens;
this.parseFlags = parseFlags;
this.errors = errors;
this.offset = offset;
this.rparensExpected = 0;
this.rbracketsExpected = 0;
this.rbracesExpected = 0;
this.context = ParseContextFlags.None;
// Cache of expression start and input indeces to the absolute source span they map to, used to
// prevent creating superfluous source spans in `sourceSpan`.
// A serial of the expression start and input index is used for mapping because both are stateful
// and may change for subsequent expressions visited by the parser.
this.sourceSpanCache = new Map();
this.index = 0;
}
peek(offset) {
const i = this.index + offset;
return i < this.tokens.length ? this.tokens[i] : EOF;
}
get next() {
return this.peek(0);
}
/** Whether all the parser input has been processed. */
get atEOF() {
return this.index >= this.tokens.length;
}
/**
* Index of the next token to be processed, or the end of the last token if all have been
* processed.
*/
get inputIndex() {
return this.atEOF ? this.currentEndIndex : this.next.index + this.offset;
}
/**
* End index of the last processed token, or the start of the first token if none have been
* processed.
*/
get currentEndIndex() {
if (this.index > 0) {
const curToken = this.peek(-1);
return curToken.end + this.offset;
}
// No tokens have been processed yet; return the next token's start or the length of the input
// if there is no token.
if (this.tokens.length === 0) {
return this.input.length + this.offset;
}
return this.next.index + this.offset;
}
/**
* Returns the absolute offset of the start of the current token.
*/
get currentAbsoluteOffset() {
return this.absoluteOffset + this.inputIndex;
}
/**
* Retrieve a `ParseSpan` from `start` to the current position (or to `artificialEndIndex` if
* provided).
*
* @param start Position from which the `ParseSpan` will start.
* @param artificialEndIndex Optional ending index to be used if provided (and if greater than the
* natural ending index)
*/
span(start, artificialEndIndex) {
let endIndex = this.currentEndIndex;
if (artificialEndIndex !== undefined && artificialEndIndex > this.currentEndIndex) {
endIndex = artificialEndIndex;
}
// In some unusual parsing scenarios (like when certain tokens are missing and an `EmptyExpr` is
// being created), the current token may already be advanced beyond the `currentEndIndex`. This
// appears to be a deep-seated parser bug.
//
// As a workaround for now, swap the start and end indices to ensure a valid `ParseSpan`.
// TODO(alxhub): fix the bug upstream in the parser state, and remove this workaround.
if (start > endIndex) {
const tmp = endIndex;
endIndex = start;
start = tmp;
}
return new ParseSpan(start, endIndex);
}
sourceSpan(start, artificialEndIndex) {
const serial = `${start}@${this.inputIndex}:${artificialEndIndex}`;
if (!this.sourceSpanCache.has(serial)) {
this.sourceSpanCache.set(serial, this.span(start, artificialEndIndex).toAbsolute(this.absoluteOffset));
}
return this.sourceSpanCache.get(serial);
}
advance() {
this.index++;
}
/**
* Executes a callback in the provided context.
*/
withContext(context, cb) {
this.context |= context;
const ret = cb();
this.context ^= context;
return ret;
}
consumeOptionalCharacter(code) {
if (this.next.isCharacter(code)) {
this.advance();
return true;
}
else {
return false;
}
}
peekKeywordLet() {
return this.next.isKeywordLet();
}
peekKeywordAs() {
return this.next.isKeywordAs();
}
/**
* Consumes an expected character, otherwise emits an error about the missing expected character
* and skips over the token stream until reaching a recoverable point.
*
* See `this.error` and `this.skip` for more details.
*/
expectCharacter(code) {
if (this.consumeOptionalCharacter(code))
return;
this.error(`Missing expected ${String.fromCharCode(code)}`);
}
consumeOptionalOperator(op) {
if (this.next.isOperator(op)) {
this.advance();
return true;
}
else {
return false;
}
}
expectOperator(operator) {
if (this.consumeOptionalOperator(operator))
return;
this.error(`Missing expected operator ${operator}`);
}
prettyPrintToken(tok) {
return tok === EOF ? 'end of input' : `token ${tok}`;
}
expectIdentifierOrKeyword() {
const n = this.next;
if (!n.isIdentifier() && !n.isKeyword()) {
if (n.isPrivateIdentifier()) {
this._reportErrorForPrivateIdentifier(n, 'expected identifier or keyword');
}
else {
this.error(`Unexpected ${this.prettyPrintToken(n)}, expected identifier or keyword`);
}
return null;
}
this.advance();
return n.toString();
}
expectIdentifierOrKeywordOrString() {
const n = this.next;
if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
if (n.isPrivateIdentifier()) {
this._reportErrorForPrivateIdentifier(n, 'expected identifier, keyword or string');
}
else {
this.error(`Unexpected ${this.prettyPrintToken(n)}, expected identifier, keyword, or string`);
}
return '';
}
this.advance();
return n.toString();
}
parseChain() {
const exprs = [];
const start = this.inputIndex;
while (this.index < this.tokens.length) {
const expr = this.parsePipe();
exprs.push(expr);
if (this.consumeOptionalCharacter($SEMICOLON)) {
if (!(this.parseFlags & 1 /* ParseFlags.Action */)) {
this.error('Binding expression cannot contain chained expression');
}
while (this.consumeOptionalCharacter($SEMICOLON)) {
} // read all semicolons
}
else if (this.index < this.tokens.length) {
const errorIndex = this.index;
this.error(`Unexpected token '${this.next}'`);
// The `error` call above will skip ahead to the next recovery point in an attempt to
// recover part of the expression, but that might be the token we started from which will
// lead to an infinite loop. If that's the case, break the loop assuming that we can't
// parse further.
if (this.index === errorIndex) {
break;
}
}
}
if (exprs.length === 0) {
// We have no expressions so create an empty expression that spans the entire input length
const artificialStart = this.offset;
const artificialEnd = this.offset + this.input.length;
return new EmptyExpr$1(this.span(artificialStart, artificialEnd), this.sourceSpan(artificialStart, artificialEnd));
}
if (exprs.length == 1)
return exprs[0];
return new Chain(this.span(start), this.sourceSpan(start), exprs);
}
parsePipe() {
const start = this.inputIndex;
let result = this.parseExpression();
if (this.consumeOptionalOperator('|')) {
if (this.parseFlags & 1 /* ParseFlags.Action */) {
this.error('Cannot have a pipe in an action expression');
}
do {
const nameStart = this.inputIndex;
let nameId = this.expectIdentifierOrKeyword();
let nameSpan;
let fullSpanEnd = undefined;
if (nameId !== null) {
nameSpan = this.sourceSpan(nameStart);
}
else {
// No valid identifier was found, so we'll assume an empty pipe name ('').
nameId = '';
// However, there may have been whitespace present between the pipe character and the next
// token in the sequence (or the end of input). We want to track this whitespace so that
// the `BindingPipe` we produce covers not just the pipe character, but any trailing
// whitespace beyond it. Another way of thinking about this is that the zero-length name
// is assumed to be at the end of any whitespace beyond the pipe character.
//
// Therefore, we push the end of the `ParseSpan` for this pipe all the way up to the
// beginning of the next token, or until the end of input if the next token is EOF.
fullSpanEnd = this.next.index !== -1 ? this.next.index : this.input.length + this.offset;
// The `nameSpan` for an empty pipe name is zero-length at the end of any whitespace
// beyond the pipe character.
nameSpan = new ParseSpan(fullSpanEnd, fullSpanEnd).toAbsolute(this.absoluteOffset);
}
const args = [];
while (this.consumeOptionalCharacter($COLON)) {
args.push(this.parseExpression());
// If there are additional expressions beyond the name, then the artificial end for the
// name is no longer relevant.
}
result = new BindingPipe(this.span(start), this.sourceSpan(start, fullSpanEnd), result, nameId, args, nameSpan);
} while (this.consumeOptionalOperator('|'));
}
return result;
}
parseExpression() {
return this.parseConditional();
}
parseConditional() {
const start = this.inputIndex;
const result = this.parseLogicalOr();
if (this.consumeOptionalOperator('?')) {
const yes = this.parsePipe();
let no;
if (!this.consumeOptionalCharacter($COLON)) {
const end = this.inputIndex;
const expression = this.input.substring(start, end);
this.error(`Conditional expression ${expression} requires all 3 expressions`);
no = new EmptyExpr$1(this.span(start), this.sourceSpan(start));
}
else {
no = this.parsePipe();
}
return new Conditional(this.span(start), this.sourceSpan(start), result, yes, no);
}
else {
return result;
}
}
parseLogicalOr() {
// '||'
const start = this.inputIndex;
let result = this.parseLogicalAnd();
while (this.consumeOptionalOperator('||')) {
const right = this.parseLogicalAnd();
result = new Binary(this.span(start), this.sourceSpan(start), '||', result, right);
}
return result;
}
parseLogicalAnd() {
// '&&'
const start = this.inputIndex;
let result = this.parseNullishCoalescing();
while (this.consumeOptionalOperator('&&')) {
const right = this.parseNullishCoalescing();
result = new Binary(this.span(start), this.sourceSpan(start), '&&', result, right);
}
return result;
}
parseNullishCoalescing() {
// '??'
const start = this.inputIndex;
let result = this.parseEquality();
while (this.consumeOptionalOperator('??')) {
const right = this.parseEquality();
result = new Binary(this.span(start), this.sourceSpan(start), '??', result, right);
}
return result;
}
parseEquality() {
// '==','!=','===','!=='
const start = this.inputIndex;
let result = this.parseRelational();
while (this.next.type == TokenType.Operator) {
const operator = this.next.strValue;
switch (operator) {
case '==':
case '===':
case '!=':
case '!==':
this.advance();
const right = this.parseRelational();
result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
continue;
}
break;
}
return result;
}
parseRelational() {
// '<', '>', '<=', '>='
const start = this.inputIndex;
let result = this.parseAdditive();
while (this.next.type == TokenType.Operator) {
const operator = this.next.strValue;
switch (operator) {
case '<':
case '>':
case '<=':
case '>=':
this.advance();
const right = this.parseAdditive();
result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
continue;
}
break;
}
return result;
}
parseAdditive() {
// '+', '-'
const start = this.inputIndex;
let result = this.parseMultiplicative();
while (this.next.type == TokenType.Operator) {
const operator = this.next.strValue;
switch (operator) {
case '+':
case '-':
this.advance();
let right = this.parseMultiplicative();
result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
continue;
}
break;
}
return result;
}
parseMultiplicative() {
// '*', '%', '/'
const start = this.inputIndex;
let result = this.parsePrefix();
while (this.next.type == TokenType.Operator) {
const operator = this.next.strValue;
switch (operator) {
case '*':
case '%':
case '/':
this.advance();
let right = this.parsePrefix();
result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
continue;
}
break;
}
return result;
}
parsePrefix() {
if (this.next.type == TokenType.Operator) {
const start = this.inputIndex;
const operator = this.next.strValue;
let result;
switch (operator) {
case '+':
this.advance();
result = this.parsePrefix();
return Unary.createPlus(this.span(start), this.sourceSpan(start), result);
case '-':
this.advance();
result = this.parsePrefix();
return Unary.createMinus(this.span(start), this.sourceSpan(start), result);
case '!':
this.advance();
result = this.parsePrefix();
return new PrefixNot(this.span(start), this.sourceSpan(start), result);
}
}
return this.parseCallChain();
}
parseCallChain() {
const start = this.inputIndex;
let result = this.parsePrimary();
while (true) {
if (this.consumeOptionalCharacter($PERIOD)) {
result = this.parseAccessMember(result, start, false);
}
else if (this.consumeOptionalOperator('?.')) {
if (this.consumeOptionalCharacter($LPAREN)) {
result = this.parseCall(result, start, true);
}
else {
result = this.consumeOptionalCharacter($LBRACKET) ?
this.parseKeyedReadOrWrite(result, start, true) :
this.parseAccessMember(result, start, true);
}
}
else if (this.consumeOptionalCharacter($LBRACKET)) {
result = this.parseKeyedReadOrWrite(result, start, false);
}
else if (this.consumeOptionalCharacter($LPAREN)) {
result = this.parseCall(result, start, false);
}
else if (this.consumeOptionalOperator('!')) {
result = new NonNullAssert(this.span(start), this.sourceSpan(start), result);
}
else {
return result;
}
}
}
parsePrimary() {
const start = this.inputIndex;
if (this.consumeOptionalCharacter($LPAREN)) {
this.rparensExpected++;
const result = this.parsePipe();
this.rparensExpected--;
this.expectCharacter($RPAREN);
return result;
}
else if (this.next.isKeywordNull()) {
this.advance();
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), null);
}
else if (this.next.isKeywordUndefined()) {
this.advance();
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), void 0);
}
else if (this.next.isKeywordTrue()) {
this.advance();
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), true);
}
else if (this.next.isKeywordFalse()) {
this.advance();
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), false);
}
else if (this.next.isKeywordThis()) {
this.advance();
return new ThisReceiver(this.span(start), this.sourceSpan(start));
}
else if (this.consumeOptionalCharacter($LBRACKET)) {
this.rbracketsExpected++;
const elements = this.parseExpressionList($RBRACKET);
this.rbracketsExpected--;
this.expectCharacter($RBRACKET);
return new LiteralArray(this.span(start), this.sourceSpan(start), elements);
}
else if (this.next.isCharacter($LBRACE)) {
return this.parseLiteralMap();
}
else if (this.next.isIdentifier()) {
return this.parseAccessMember(new ImplicitReceiver(this.span(start), this.sourceSpan(start)), start, false);
}
else if (this.next.isNumber()) {
const value = this.next.toNumber();
this.advance();
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), value);
}
else if (this.next.isString()) {
const literalValue = this.next.toString();
this.advance();
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), literalValue);
}
else if (this.next.isPrivateIdentifier()) {
this._reportErrorForPrivateIdentifier(this.next, null);
return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
}
else if (this.index >= this.tokens.length) {
this.error(`Unexpected end of expression: ${this.input}`);
return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
}
else {
this.error(`Unexpected token ${this.next}`);
return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
}
}
parseExpressionList(terminator) {
const result = [];
do {
if (!this.next.isCharacter(terminator)) {
result.push(this.parsePipe());
}
else {
break;
}
} while (this.consumeOptionalCharacter($COMMA));
return result;
}
parseLiteralMap() {
const keys = [];
const values = [];
const start = this.inputIndex;
this.expectCharacter($LBRACE);
if (!this.consumeOptionalCharacter($RBRACE)) {
this.rbracesExpected++;
do {
const keyStart = this.inputIndex;
const quoted = this.next.isString();
const key = this.expectIdentifierOrKeywordOrString();
keys.push({ key, quoted });
// Properties with quoted keys can't use the shorthand syntax.
if (quoted) {
this.expectCharacter($COLON);
values.push(this.parsePipe());
}
else if (this.consumeOptionalCharacter($COLON)) {
values.push(this.parsePipe());
}
else {
const span = this.span(keyStart);
const sourceSpan = this.sourceSpan(keyStart);
values.push(new PropertyRead(span, sourceSpan, sourceSpan, new ImplicitReceiver(span, sourceSpan), key));
}
} while (this.consumeOptionalCharacter($COMMA) &&
!this.next.isCharacter($RBRACE));
this.rbracesExpected--;
this.expectCharacter($RBRACE);
}
return new LiteralMap(this.span(start), this.sourceSpan(start), keys, values);
}
parseAccessMember(readReceiver, start, isSafe) {
const nameStart = this.inputIndex;
const id = this.withContext(ParseContextFlags.Writable, () => {
const id = this.expectIdentifierOrKeyword() ?? '';
if (id.length === 0) {
this.error(`Expected identifier for property access`, readReceiver.span.end);
}
return id;
});
const nameSpan = this.sourceSpan(nameStart);
let receiver;
if (isSafe) {
if (this.consumeOptionalAssignment()) {
this.error('The \'?.\' operator cannot be used in the assignment');
receiver = new EmptyExpr$1(this.span(start), this.sourceSpan(start));
}
else {
receiver = new SafePropertyRead(this.span(start), this.sourceSpan(start), nameSpan, readReceiver, id);
}
}
else {
if (this.consumeOptionalAssignment()) {
if (!(this.parseFlags & 1 /* ParseFlags.Action */)) {
this.error('Bindings cannot contain assignments');
return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
}
const value = this.parseConditional();
receiver = new PropertyWrite(this.span(start), this.sourceSpan(start), nameSpan, readReceiver, id, value);
}
else {
receiver =
new PropertyRead(this.span(start), this.sourceSpan(start), nameSpan, readReceiver, id);
}
}
return receiver;
}
parseCall(receiver, start, isSafe) {
const argumentStart = this.inputIndex;
this.rparensExpected++;
const args = this.parseCallArguments();
const argumentSpan = this.span(argumentStart, this.inputIndex).toAbsolute(this.absoluteOffset);
this.expectCharacter($RPAREN);
this.rparensExpected--;
const span = this.span(start);
const sourceSpan = this.sourceSpan(start);
return isSafe ? new SafeCall(span, sourceSpan, receiver, args, argumentSpan) :
new Call(span, sourceSpan, receiver, args, argumentSpan);
}
consumeOptionalAssignment() {
// When parsing assignment events (originating from two-way-binding aka banana-in-a-box syntax),
// it is valid for the primary expression to be terminated by the non-null operator. This
// primary expression is substituted as LHS of the assignment operator to achieve
// two-way-binding, such that the LHS could be the non-null operator. The grammar doesn't
// naturally allow for this syntax, so assignment events are parsed specially.
if ((this.parseFlags & 2 /* ParseFlags.AssignmentEvent */) && this.next.isOperator('!') &&
this.peek(1).isOperator('=')) {
// First skip over the ! operator.
this.advance();
// Then skip over the = operator, to fully consume the optional assignment operator.
this.advance();
return true;
}
return this.consumeOptionalOperator('=');
}
parseCallArguments() {
if (this.next.isCharacter($RPAREN))
return [];
const positionals = [];
do {
positionals.push(this.parsePipe());
} while (this.consumeOptionalCharacter($COMMA));
return positionals;
}
/**
* Parses an identifier, a keyword, a string with an optional `-` in between,
* and returns the string along with its absolute source span.
*/
expectTemplateBindingKey() {
let result = '';
let operatorFound = false;
const start = this.currentAbsoluteOffset;
do {
result += this.expectIdentifierOrKeywordOrString();
operatorFound = this.consumeOptionalOperator('-');
if (operatorFound) {
result += '-';
}
} while (operatorFound);
return {
source: result,
span: new AbsoluteSourceSpan(start, start + result.length),
};
}
/**
* Parse microsyntax template expression and return a list of bindings or
* parsing errors in case the given expression is invalid.
*
* For example,
* ```
*
* ```
* contains five bindings:
* 1. ngFor -> null
* 2. item -> NgForOfContext.$implicit
* 3. ngForOf -> items
* 4. i -> NgForOfContext.index
* 5. ngForTrackBy -> func
*
* For a full description of the microsyntax grammar, see
* https://gist.github.com/mhevery/d3530294cff2e4a1b3fe15ff75d08855
*
* @param templateKey name of the microsyntax directive, like ngIf, ngFor,
* without the *, along with its absolute span.
*/
parseTemplateBindings(templateKey) {
const bindings = [];
// The first binding is for the template key itself
// In *ngFor="let item of items", key = "ngFor", value = null
// In *ngIf="cond | pipe", key = "ngIf", value = "cond | pipe"
bindings.push(...this.parseDirectiveKeywordBindings(templateKey));
while (this.index < this.tokens.length) {
// If it starts with 'let', then this must be variable declaration
const letBinding = this.parseLetBinding();
if (letBinding) {
bindings.push(letBinding);
}
else {
// Two possible cases here, either `value "as" key` or
// "directive-keyword expression". We don't know which case, but both
// "value" and "directive-keyword" are template binding key, so consume
// the key first.
const key = this.expectTemplateBindingKey();
// Peek at the next token, if it is "as" then this must be variable
// declaration.
const binding = this.parseAsBinding(key);
if (binding) {
bindings.push(binding);
}
else {
// Otherwise the key must be a directive keyword, like "of". Transform
// the key to actual key. Eg. of -> ngForOf, trackBy -> ngForTrackBy
key.source =
templateKey.source + key.source.charAt(0).toUpperCase() + key.source.substring(1);
bindings.push(...this.parseDirectiveKeywordBindings(key));
}
}
this.consumeStatementTerminator();
}
return new TemplateBindingParseResult(bindings, [] /* warnings */, this.errors);
}
parseKeyedReadOrWrite(receiver, start, isSafe) {
return this.withContext(ParseContextFlags.Writable, () => {
this.rbracketsExpected++;
const key = this.parsePipe();
if (key instanceof EmptyExpr$1) {
this.error(`Key access cannot be empty`);
}
this.rbracketsExpected--;
this.expectCharacter($RBRACKET);
if (this.consumeOptionalOperator('=')) {
if (isSafe) {
this.error('The \'?.\' operator cannot be used in the assignment');
}
else {
const value = this.parseConditional();
return new KeyedWrite(this.span(start), this.sourceSpan(start), receiver, key, value);
}
}
else {
return isSafe ? new SafeKeyedRead(this.span(start), this.sourceSpan(start), receiver, key) :
new KeyedRead(this.span(start), this.sourceSpan(start), receiver, key);
}
return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
});
}
/**
* Parse a directive keyword, followed by a mandatory expression.
* For example, "of items", "trackBy: func".
* The bindings are: ngForOf -> items, ngForTrackBy -> func
* There could be an optional "as" binding that follows the expression.
* For example,
* ```
* *ngFor="let item of items | slice:0:1 as collection".
* ^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^
* keyword bound target optional 'as' binding
* ```
*
* @param key binding key, for example, ngFor, ngIf, ngForOf, along with its
* absolute span.
*/
parseDirectiveKeywordBindings(key) {
const bindings = [];
this.consumeOptionalCharacter($COLON); // trackBy: trackByFunction
const value = this.getDirectiveBoundTarget();
let spanEnd = this.currentAbsoluteOffset;
// The binding could optionally be followed by "as". For example,
// *ngIf="cond | pipe as x". In this case, the key in the "as" binding
// is "x" and the value is the template key itself ("ngIf"). Note that the
// 'key' in the current context now becomes the "value" in the next binding.
const asBinding = this.parseAsBinding(key);
if (!asBinding) {
this.consumeStatementTerminator();
spanEnd = this.currentAbsoluteOffset;
}
const sourceSpan = new AbsoluteSourceSpan(key.span.start, spanEnd);
bindings.push(new ExpressionBinding(sourceSpan, key, value));
if (asBinding) {
bindings.push(asBinding);
}
return bindings;
}
/**
* Return the expression AST for the bound target of a directive keyword
* binding. For example,
* ```
* *ngIf="condition | pipe"
* ^^^^^^^^^^^^^^^^ bound target for "ngIf"
* *ngFor="let item of items"
* ^^^^^ bound target for "ngForOf"
* ```
*/
getDirectiveBoundTarget() {
if (this.next === EOF || this.peekKeywordAs() || this.peekKeywordLet()) {
return null;
}
const ast = this.parsePipe(); // example: "condition | async"
const { start, end } = ast.span;
const value = this.input.substring(start, end);
return new ASTWithSource(ast, value, this.location, this.absoluteOffset + start, this.errors);
}
/**
* Return the binding for a variable declared using `as`. Note that the order
* of the key-value pair in this declaration is reversed. For example,
* ```
* *ngFor="let item of items; index as i"
* ^^^^^ ^
* value key
* ```
*
* @param value name of the value in the declaration, "ngIf" in the example
* above, along with its absolute span.
*/
parseAsBinding(value) {
if (!this.peekKeywordAs()) {
return null;
}
this.advance(); // consume the 'as' keyword
const key = this.expectTemplateBindingKey();
this.consumeStatementTerminator();
const sourceSpan = new AbsoluteSourceSpan(value.span.start, this.currentAbsoluteOffset);
return new VariableBinding(sourceSpan, key, value);
}
/**
* Return the binding for a variable declared using `let`. For example,
* ```
* *ngFor="let item of items; let i=index;"
* ^^^^^^^^ ^^^^^^^^^^^
* ```
* In the first binding, `item` is bound to `NgForOfContext.$implicit`.
* In the second binding, `i` is bound to `NgForOfContext.index`.
*/
parseLetBinding() {
if (!this.peekKeywordLet()) {
return null;
}
const spanStart = this.currentAbsoluteOffset;
this.advance(); // consume the 'let' keyword
const key = this.expectTemplateBindingKey();
let value = null;
if (this.consumeOptionalOperator('=')) {
value = this.expectTemplateBindingKey();
}
this.consumeStatementTerminator();
const sourceSpan = new AbsoluteSourceSpan(spanStart, this.currentAbsoluteOffset);
return new VariableBinding(sourceSpan, key, value);
}
/**
* Consume the optional statement terminator: semicolon or comma.
*/
consumeStatementTerminator() {
this.consumeOptionalCharacter($SEMICOLON) || this.consumeOptionalCharacter($COMMA);
}
/**
* Records an error and skips over the token stream until reaching a recoverable point. See
* `this.skip` for more details on token skipping.
*/
error(message, index = null) {
this.errors.push(new ParserError(message, this.input, this.locationText(index), this.location));
this.skip();
}
locationText(index = null) {
if (index == null)
index = this.index;
return (index < this.tokens.length) ? `at column ${this.tokens[index].index + 1} in` :
`at the end of the expression`;
}
/**
* Records an error for an unexpected private identifier being discovered.
* @param token Token representing a private identifier.
* @param extraMessage Optional additional message being appended to the error.
*/
_reportErrorForPrivateIdentifier(token, extraMessage) {
let errorMessage = `Private identifiers are not supported. Unexpected private identifier: ${token}`;
if (extraMessage !== null) {
errorMessage += `, ${extraMessage}`;
}
this.error(errorMessage);
}
/**
* Error recovery should skip tokens until it encounters a recovery point.
*
* The following are treated as unconditional recovery points:
* - end of input
* - ';' (parseChain() is always the root production, and it expects a ';')
* - '|' (since pipes may be chained and each pipe expression may be treated independently)
*
* The following are conditional recovery points:
* - ')', '}', ']' if one of calling productions is expecting one of these symbols
* - This allows skip() to recover from errors such as '(a.) + 1' allowing more of the AST to
* be retained (it doesn't skip any tokens as the ')' is retained because of the '(' begins
* an '('
')' production).
* The recovery points of grouping symbols must be conditional as they must be skipped if
* none of the calling productions are not expecting the closing token else we will never
* make progress in the case of an extraneous group closing symbol (such as a stray ')').
* That is, we skip a closing symbol if we are not in a grouping production.
* - '=' in a `Writable` context
* - In this context, we are able to recover after seeing the `=` operator, which
* signals the presence of an independent rvalue expression following the `=` operator.
*
* If a production expects one of these token it increments the corresponding nesting count,
* and then decrements it just prior to checking if the token is in the input.
*/
skip() {
let n = this.next;
while (this.index < this.tokens.length && !n.isCharacter($SEMICOLON) &&
!n.isOperator('|') && (this.rparensExpected <= 0 || !n.isCharacter($RPAREN)) &&
(this.rbracesExpected <= 0 || !n.isCharacter($RBRACE)) &&
(this.rbracketsExpected <= 0 || !n.isCharacter($RBRACKET)) &&
(!(this.context & ParseContextFlags.Writable) || !n.isOperator('='))) {
if (this.next.isError()) {
this.errors.push(new ParserError(this.next.toString(), this.input, this.locationText(), this.location));
}
this.advance();
n = this.next;
}
}
}
class SimpleExpressionChecker extends RecursiveAstVisitor {
constructor() {
super(...arguments);
this.errors = [];
}
visitPipe() {
this.errors.push('pipes');
}
}
/**
* Computes the real offset in the original template for indexes in an interpolation.
*
* Because templates can have encoded HTML entities and the input passed to the parser at this stage
* of the compiler is the _decoded_ value, we need to compute the real offset using the original
* encoded values in the interpolated tokens. Note that this is only a special case handling for
* `MlParserTokenType.ENCODED_ENTITY` token types. All other interpolated tokens are expected to
* have parts which exactly match the input string for parsing the interpolation.
*
* @param interpolatedTokens The tokens for the interpolated value.
*
* @returns A map of index locations in the decoded template to indexes in the original template
*/
function getIndexMapForOriginalTemplate(interpolatedTokens) {
let offsetMap = new Map();
let consumedInOriginalTemplate = 0;
let consumedInInput = 0;
let tokenIndex = 0;
while (tokenIndex < interpolatedTokens.length) {
const currentToken = interpolatedTokens[tokenIndex];
if (currentToken.type === 9 /* MlParserTokenType.ENCODED_ENTITY */) {
const [decoded, encoded] = currentToken.parts;
consumedInOriginalTemplate += encoded.length;
consumedInInput += decoded.length;
}
else {
const lengthOfParts = currentToken.parts.reduce((sum, current) => sum + current.length, 0);
consumedInInput += lengthOfParts;
consumedInOriginalTemplate += lengthOfParts;
}
offsetMap.set(consumedInInput, consumedInOriginalTemplate);
tokenIndex++;
}
return offsetMap;
}
class NodeWithI18n {
constructor(sourceSpan, i18n) {
this.sourceSpan = sourceSpan;
this.i18n = i18n;
}
}
class Text extends NodeWithI18n {
constructor(value, sourceSpan, tokens, i18n) {
super(sourceSpan, i18n);
this.value = value;
this.tokens = tokens;
}
visit(visitor, context) {
return visitor.visitText(this, context);
}
}
class Expansion extends NodeWithI18n {
constructor(switchValue, type, cases, sourceSpan, switchValueSourceSpan, i18n) {
super(sourceSpan, i18n);
this.switchValue = switchValue;
this.type = type;
this.cases = cases;
this.switchValueSourceSpan = switchValueSourceSpan;
}
visit(visitor, context) {
return visitor.visitExpansion(this, context);
}
}
class ExpansionCase {
constructor(value, expression, sourceSpan, valueSourceSpan, expSourceSpan) {
this.value = value;
this.expression = expression;
this.sourceSpan = sourceSpan;
this.valueSourceSpan = valueSourceSpan;
this.expSourceSpan = expSourceSpan;
}
visit(visitor, context) {
return visitor.visitExpansionCase(this, context);
}
}
class Attribute extends NodeWithI18n {
constructor(name, value, sourceSpan, keySpan, valueSpan, valueTokens, i18n) {
super(sourceSpan, i18n);
this.name = name;
this.value = value;
this.keySpan = keySpan;
this.valueSpan = valueSpan;
this.valueTokens = valueTokens;
}
visit(visitor, context) {
return visitor.visitAttribute(this, context);
}
}
class Element extends NodeWithI18n {
constructor(name, attrs, children, sourceSpan, startSourceSpan, endSourceSpan = null, i18n) {
super(sourceSpan, i18n);
this.name = name;
this.attrs = attrs;
this.children = children;
this.startSourceSpan = startSourceSpan;
this.endSourceSpan = endSourceSpan;
}
visit(visitor, context) {
return visitor.visitElement(this, context);
}
}
class Comment {
constructor(value, sourceSpan) {
this.value = value;
this.sourceSpan = sourceSpan;
}
visit(visitor, context) {
return visitor.visitComment(this, context);
}
}
class BlockGroup {
constructor(blocks, sourceSpan, startSourceSpan, endSourceSpan = null) {
this.blocks = blocks;
this.sourceSpan = sourceSpan;
this.startSourceSpan = startSourceSpan;
this.endSourceSpan = endSourceSpan;
}
visit(visitor, context) {
return visitor.visitBlockGroup(this, context);
}
}
class Block {
constructor(name, parameters, children, sourceSpan, startSourceSpan, endSourceSpan = null) {
this.name = name;
this.parameters = parameters;
this.children = children;
this.sourceSpan = sourceSpan;
this.startSourceSpan = startSourceSpan;
this.endSourceSpan = endSourceSpan;
}
visit(visitor, context) {
return visitor.visitBlock(this, context);
}
}
class BlockParameter {
constructor(expression, sourceSpan) {
this.expression = expression;
this.sourceSpan = sourceSpan;
}
visit(visitor, context) {
return visitor.visitBlockParameter(this, context);
}
}
function visitAll(visitor, nodes, context = null) {
const result = [];
const visit = visitor.visit ?
(ast) => visitor.visit(ast, context) || ast.visit(visitor, context) :
(ast) => ast.visit(visitor, context);
nodes.forEach(ast => {
const astResult = visit(ast);
if (astResult) {
result.push(astResult);
}
});
return result;
}
class RecursiveVisitor {
constructor() { }
visitElement(ast, context) {
this.visitChildren(context, visit => {
visit(ast.attrs);
visit(ast.children);
});
}
visitAttribute(ast, context) { }
visitText(ast, context) { }
visitComment(ast, context) { }
visitExpansion(ast, context) {
return this.visitChildren(context, visit => {
visit(ast.cases);
});
}
visitExpansionCase(ast, context) { }
visitBlockGroup(ast, context) {
this.visitChildren(context, visit => {
visit(ast.blocks);
});
}
visitBlock(block, context) {
this.visitChildren(context, visit => {
visit(block.parameters);
visit(block.children);
});
}
visitBlockParameter(ast, context) { }
visitChildren(context, cb) {
let results = [];
let t = this;
function visit(children) {
if (children)
results.push(visitAll(t, children, context));
}
cb(visit);
return Array.prototype.concat.apply([], results);
}
}
class ElementSchemaRegistry {
}
const BOOLEAN = 'boolean';
const NUMBER = 'number';
const STRING = 'string';
const OBJECT = 'object';
/**
* This array represents the DOM schema. It encodes inheritance, properties, and events.
*
* ## Overview
*
* Each line represents one kind of element. The `element_inheritance` and properties are joined
* using `element_inheritance|properties` syntax.
*
* ## Element Inheritance
*
* The `element_inheritance` can be further subdivided as `element1,element2,...^parentElement`.
* Here the individual elements are separated by `,` (commas). Every element in the list
* has identical properties.
*
* An `element` may inherit additional properties from `parentElement` If no `^parentElement` is
* specified then `""` (blank) element is assumed.
*
* NOTE: The blank element inherits from root `[Element]` element, the super element of all
* elements.
*
* NOTE an element prefix such as `:svg:` has no special meaning to the schema.
*
* ## Properties
*
* Each element has a set of properties separated by `,` (commas). Each property can be prefixed
* by a special character designating its type:
*
* - (no prefix): property is a string.
* - `*`: property represents an event.
* - `!`: property is a boolean.
* - `#`: property is a number.
* - `%`: property is an object.
*
* ## Query
*
* The class creates an internal squas representation which allows to easily answer the query of
* if a given property exist on a given element.
*
* NOTE: We don't yet support querying for types or events.
* NOTE: This schema is auto extracted from `schema_extractor.ts` located in the test folder,
* see dom_element_schema_registry_spec.ts
*/
// =================================================================================================
// =================================================================================================
// =========== S T O P - S T O P - S T O P - S T O P - S T O P - S T O P ===========
// =================================================================================================
// =================================================================================================
//
// DO NOT EDIT THIS DOM SCHEMA WITHOUT A SECURITY REVIEW!
//
// Newly added properties must be security reviewed and assigned an appropriate SecurityContext in
// dom_security_schema.ts. Reach out to mprobst & rjamet for details.
//
// =================================================================================================
const SCHEMA = [
'[Element]|textContent,%ariaAtomic,%ariaAutoComplete,%ariaBusy,%ariaChecked,%ariaColCount,%ariaColIndex,%ariaColSpan,%ariaCurrent,%ariaDescription,%ariaDisabled,%ariaExpanded,%ariaHasPopup,%ariaHidden,%ariaKeyShortcuts,%ariaLabel,%ariaLevel,%ariaLive,%ariaModal,%ariaMultiLine,%ariaMultiSelectable,%ariaOrientation,%ariaPlaceholder,%ariaPosInSet,%ariaPressed,%ariaReadOnly,%ariaRelevant,%ariaRequired,%ariaRoleDescription,%ariaRowCount,%ariaRowIndex,%ariaRowSpan,%ariaSelected,%ariaSetSize,%ariaSort,%ariaValueMax,%ariaValueMin,%ariaValueNow,%ariaValueText,%classList,className,elementTiming,id,innerHTML,*beforecopy,*beforecut,*beforepaste,*fullscreenchange,*fullscreenerror,*search,*webkitfullscreenchange,*webkitfullscreenerror,outerHTML,%part,#scrollLeft,#scrollTop,slot' +
/* added manually to avoid breaking changes */
',*message,*mozfullscreenchange,*mozfullscreenerror,*mozpointerlockchange,*mozpointerlockerror,*webglcontextcreationerror,*webglcontextlost,*webglcontextrestored',
'[HTMLElement]^[Element]|accessKey,autocapitalize,!autofocus,contentEditable,dir,!draggable,enterKeyHint,!hidden,innerText,inputMode,lang,nonce,*abort,*animationend,*animationiteration,*animationstart,*auxclick,*beforexrselect,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*copy,*cuechange,*cut,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*formdata,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*paste,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerrawupdate,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*securitypolicyviolation,*seeked,*seeking,*select,*selectionchange,*selectstart,*slotchange,*stalled,*submit,*suspend,*timeupdate,*toggle,*transitioncancel,*transitionend,*transitionrun,*transitionstart,*volumechange,*waiting,*webkitanimationend,*webkitanimationiteration,*webkitanimationstart,*webkittransitionend,*wheel,outerText,!spellcheck,%style,#tabIndex,title,!translate,virtualKeyboardPolicy',
'abbr,address,article,aside,b,bdi,bdo,cite,content,code,dd,dfn,dt,em,figcaption,figure,footer,header,hgroup,i,kbd,main,mark,nav,noscript,rb,rp,rt,rtc,ruby,s,samp,section,small,strong,sub,sup,u,var,wbr^[HTMLElement]|accessKey,autocapitalize,!autofocus,contentEditable,dir,!draggable,enterKeyHint,!hidden,innerText,inputMode,lang,nonce,*abort,*animationend,*animationiteration,*animationstart,*auxclick,*beforexrselect,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*copy,*cuechange,*cut,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*formdata,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*paste,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerrawupdate,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*securitypolicyviolation,*seeked,*seeking,*select,*selectionchange,*selectstart,*slotchange,*stalled,*submit,*suspend,*timeupdate,*toggle,*transitioncancel,*transitionend,*transitionrun,*transitionstart,*volumechange,*waiting,*webkitanimationend,*webkitanimationiteration,*webkitanimationstart,*webkittransitionend,*wheel,outerText,!spellcheck,%style,#tabIndex,title,!translate,virtualKeyboardPolicy',
'media^[HTMLElement]|!autoplay,!controls,%controlsList,%crossOrigin,#currentTime,!defaultMuted,#defaultPlaybackRate,!disableRemotePlayback,!loop,!muted,*encrypted,*waitingforkey,#playbackRate,preload,!preservesPitch,src,%srcObject,#volume',
':svg:^[HTMLElement]|!autofocus,nonce,*abort,*animationend,*animationiteration,*animationstart,*auxclick,*beforexrselect,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*copy,*cuechange,*cut,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*formdata,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*paste,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerrawupdate,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*securitypolicyviolation,*seeked,*seeking,*select,*selectionchange,*selectstart,*slotchange,*stalled,*submit,*suspend,*timeupdate,*toggle,*transitioncancel,*transitionend,*transitionrun,*transitionstart,*volumechange,*waiting,*webkitanimationend,*webkitanimationiteration,*webkitanimationstart,*webkittransitionend,*wheel,%style,#tabIndex',
':svg:graphics^:svg:|',
':svg:animation^:svg:|*begin,*end,*repeat',
':svg:geometry^:svg:|',
':svg:componentTransferFunction^:svg:|',
':svg:gradient^:svg:|',
':svg:textContent^:svg:graphics|',
':svg:textPositioning^:svg:textContent|',
'a^[HTMLElement]|charset,coords,download,hash,host,hostname,href,hreflang,name,password,pathname,ping,port,protocol,referrerPolicy,rel,%relList,rev,search,shape,target,text,type,username',
'area^[HTMLElement]|alt,coords,download,hash,host,hostname,href,!noHref,password,pathname,ping,port,protocol,referrerPolicy,rel,%relList,search,shape,target,username',
'audio^media|',
'br^[HTMLElement]|clear',
'base^[HTMLElement]|href,target',
'body^[HTMLElement]|aLink,background,bgColor,link,*afterprint,*beforeprint,*beforeunload,*blur,*error,*focus,*hashchange,*languagechange,*load,*message,*messageerror,*offline,*online,*pagehide,*pageshow,*popstate,*rejectionhandled,*resize,*scroll,*storage,*unhandledrejection,*unload,text,vLink',
'button^[HTMLElement]|!disabled,formAction,formEnctype,formMethod,!formNoValidate,formTarget,name,type,value',
'canvas^[HTMLElement]|#height,#width',
'content^[HTMLElement]|select',
'dl^[HTMLElement]|!compact',
'data^[HTMLElement]|value',
'datalist^[HTMLElement]|',
'details^[HTMLElement]|!open',
'dialog^[HTMLElement]|!open,returnValue',
'dir^[HTMLElement]|!compact',
'div^[HTMLElement]|align',
'embed^[HTMLElement]|align,height,name,src,type,width',
'fieldset^[HTMLElement]|!disabled,name',
'font^[HTMLElement]|color,face,size',
'form^[HTMLElement]|acceptCharset,action,autocomplete,encoding,enctype,method,name,!noValidate,target',
'frame^[HTMLElement]|frameBorder,longDesc,marginHeight,marginWidth,name,!noResize,scrolling,src',
'frameset^[HTMLElement]|cols,*afterprint,*beforeprint,*beforeunload,*blur,*error,*focus,*hashchange,*languagechange,*load,*message,*messageerror,*offline,*online,*pagehide,*pageshow,*popstate,*rejectionhandled,*resize,*scroll,*storage,*unhandledrejection,*unload,rows',
'hr^[HTMLElement]|align,color,!noShade,size,width',
'head^[HTMLElement]|',
'h1,h2,h3,h4,h5,h6^[HTMLElement]|align',
'html^[HTMLElement]|version',
'iframe^[HTMLElement]|align,allow,!allowFullscreen,!allowPaymentRequest,csp,frameBorder,height,loading,longDesc,marginHeight,marginWidth,name,referrerPolicy,%sandbox,scrolling,src,srcdoc,width',
'img^[HTMLElement]|align,alt,border,%crossOrigin,decoding,#height,#hspace,!isMap,loading,longDesc,lowsrc,name,referrerPolicy,sizes,src,srcset,useMap,#vspace,#width',
'input^[HTMLElement]|accept,align,alt,autocomplete,!checked,!defaultChecked,defaultValue,dirName,!disabled,%files,formAction,formEnctype,formMethod,!formNoValidate,formTarget,#height,!incremental,!indeterminate,max,#maxLength,min,#minLength,!multiple,name,pattern,placeholder,!readOnly,!required,selectionDirection,#selectionEnd,#selectionStart,#size,src,step,type,useMap,value,%valueAsDate,#valueAsNumber,#width',
'li^[HTMLElement]|type,#value',
'label^[HTMLElement]|htmlFor',
'legend^[HTMLElement]|align',
'link^[HTMLElement]|as,charset,%crossOrigin,!disabled,href,hreflang,imageSizes,imageSrcset,integrity,media,referrerPolicy,rel,%relList,rev,%sizes,target,type',
'map^[HTMLElement]|name',
'marquee^[HTMLElement]|behavior,bgColor,direction,height,#hspace,#loop,#scrollAmount,#scrollDelay,!trueSpeed,#vspace,width',
'menu^[HTMLElement]|!compact',
'meta^[HTMLElement]|content,httpEquiv,media,name,scheme',
'meter^[HTMLElement]|#high,#low,#max,#min,#optimum,#value',
'ins,del^[HTMLElement]|cite,dateTime',
'ol^[HTMLElement]|!compact,!reversed,#start,type',
'object^[HTMLElement]|align,archive,border,code,codeBase,codeType,data,!declare,height,#hspace,name,standby,type,useMap,#vspace,width',
'optgroup^[HTMLElement]|!disabled,label',
'option^[HTMLElement]|!defaultSelected,!disabled,label,!selected,text,value',
'output^[HTMLElement]|defaultValue,%htmlFor,name,value',
'p^[HTMLElement]|align',
'param^[HTMLElement]|name,type,value,valueType',
'picture^[HTMLElement]|',
'pre^[HTMLElement]|#width',
'progress^[HTMLElement]|#max,#value',
'q,blockquote,cite^[HTMLElement]|',
'script^[HTMLElement]|!async,charset,%crossOrigin,!defer,event,htmlFor,integrity,!noModule,%referrerPolicy,src,text,type',
'select^[HTMLElement]|autocomplete,!disabled,#length,!multiple,name,!required,#selectedIndex,#size,value',
'slot^[HTMLElement]|name',
'source^[HTMLElement]|#height,media,sizes,src,srcset,type,#width',
'span^[HTMLElement]|',
'style^[HTMLElement]|!disabled,media,type',
'caption^[HTMLElement]|align',
'th,td^[HTMLElement]|abbr,align,axis,bgColor,ch,chOff,#colSpan,headers,height,!noWrap,#rowSpan,scope,vAlign,width',
'col,colgroup^[HTMLElement]|align,ch,chOff,#span,vAlign,width',
'table^[HTMLElement]|align,bgColor,border,%caption,cellPadding,cellSpacing,frame,rules,summary,%tFoot,%tHead,width',
'tr^[HTMLElement]|align,bgColor,ch,chOff,vAlign',
'tfoot,thead,tbody^[HTMLElement]|align,ch,chOff,vAlign',
'template^[HTMLElement]|',
'textarea^[HTMLElement]|autocomplete,#cols,defaultValue,dirName,!disabled,#maxLength,#minLength,name,placeholder,!readOnly,!required,#rows,selectionDirection,#selectionEnd,#selectionStart,value,wrap',
'time^[HTMLElement]|dateTime',
'title^[HTMLElement]|text',
'track^[HTMLElement]|!default,kind,label,src,srclang',
'ul^[HTMLElement]|!compact,type',
'unknown^[HTMLElement]|',
'video^media|!disablePictureInPicture,#height,*enterpictureinpicture,*leavepictureinpicture,!playsInline,poster,#width',
':svg:a^:svg:graphics|',
':svg:animate^:svg:animation|',
':svg:animateMotion^:svg:animation|',
':svg:animateTransform^:svg:animation|',
':svg:circle^:svg:geometry|',
':svg:clipPath^:svg:graphics|',
':svg:defs^:svg:graphics|',
':svg:desc^:svg:|',
':svg:discard^:svg:|',
':svg:ellipse^:svg:geometry|',
':svg:feBlend^:svg:|',
':svg:feColorMatrix^:svg:|',
':svg:feComponentTransfer^:svg:|',
':svg:feComposite^:svg:|',
':svg:feConvolveMatrix^:svg:|',
':svg:feDiffuseLighting^:svg:|',
':svg:feDisplacementMap^:svg:|',
':svg:feDistantLight^:svg:|',
':svg:feDropShadow^:svg:|',
':svg:feFlood^:svg:|',
':svg:feFuncA^:svg:componentTransferFunction|',
':svg:feFuncB^:svg:componentTransferFunction|',
':svg:feFuncG^:svg:componentTransferFunction|',
':svg:feFuncR^:svg:componentTransferFunction|',
':svg:feGaussianBlur^:svg:|',
':svg:feImage^:svg:|',
':svg:feMerge^:svg:|',
':svg:feMergeNode^:svg:|',
':svg:feMorphology^:svg:|',
':svg:feOffset^:svg:|',
':svg:fePointLight^:svg:|',
':svg:feSpecularLighting^:svg:|',
':svg:feSpotLight^:svg:|',
':svg:feTile^:svg:|',
':svg:feTurbulence^:svg:|',
':svg:filter^:svg:|',
':svg:foreignObject^:svg:graphics|',
':svg:g^:svg:graphics|',
':svg:image^:svg:graphics|decoding',
':svg:line^:svg:geometry|',
':svg:linearGradient^:svg:gradient|',
':svg:mpath^:svg:|',
':svg:marker^:svg:|',
':svg:mask^:svg:|',
':svg:metadata^:svg:|',
':svg:path^:svg:geometry|',
':svg:pattern^:svg:|',
':svg:polygon^:svg:geometry|',
':svg:polyline^:svg:geometry|',
':svg:radialGradient^:svg:gradient|',
':svg:rect^:svg:geometry|',
':svg:svg^:svg:graphics|#currentScale,#zoomAndPan',
':svg:script^:svg:|type',
':svg:set^:svg:animation|',
':svg:stop^:svg:|',
':svg:style^:svg:|!disabled,media,title,type',
':svg:switch^:svg:graphics|',
':svg:symbol^:svg:|',
':svg:tspan^:svg:textPositioning|',
':svg:text^:svg:textPositioning|',
':svg:textPath^:svg:textContent|',
':svg:title^:svg:|',
':svg:use^:svg:graphics|',
':svg:view^:svg:|#zoomAndPan',
'data^[HTMLElement]|value',
'keygen^[HTMLElement]|!autofocus,challenge,!disabled,form,keytype,name',
'menuitem^[HTMLElement]|type,label,icon,!disabled,!checked,radiogroup,!default',
'summary^[HTMLElement]|',
'time^[HTMLElement]|dateTime',
':svg:cursor^:svg:|',
];
const _ATTR_TO_PROP = new Map(Object.entries({
'class': 'className',
'for': 'htmlFor',
'formaction': 'formAction',
'innerHtml': 'innerHTML',
'readonly': 'readOnly',
'tabindex': 'tabIndex',
}));
// Invert _ATTR_TO_PROP.
const _PROP_TO_ATTR = Array.from(_ATTR_TO_PROP).reduce((inverted, [propertyName, attributeName]) => {
inverted.set(propertyName, attributeName);
return inverted;
}, new Map());
class DomElementSchemaRegistry extends ElementSchemaRegistry {
constructor() {
super();
this._schema = new Map();
// We don't allow binding to events for security reasons. Allowing event bindings would almost
// certainly introduce bad XSS vulnerabilities. Instead, we store events in a separate schema.
this._eventSchema = new Map;
SCHEMA.forEach(encodedType => {
const type = new Map();
const events = new Set();
const [strType, strProperties] = encodedType.split('|');
const properties = strProperties.split(',');
const [typeNames, superName] = strType.split('^');
typeNames.split(',').forEach(tag => {
this._schema.set(tag.toLowerCase(), type);
this._eventSchema.set(tag.toLowerCase(), events);
});
const superType = superName && this._schema.get(superName.toLowerCase());
if (superType) {
for (const [prop, value] of superType) {
type.set(prop, value);
}
for (const superEvent of this._eventSchema.get(superName.toLowerCase())) {
events.add(superEvent);
}
}
properties.forEach((property) => {
if (property.length > 0) {
switch (property[0]) {
case '*':
events.add(property.substring(1));
break;
case '!':
type.set(property.substring(1), BOOLEAN);
break;
case '#':
type.set(property.substring(1), NUMBER);
break;
case '%':
type.set(property.substring(1), OBJECT);
break;
default:
type.set(property, STRING);
}
}
});
});
}
hasProperty(tagName, propName, schemaMetas) {
if (schemaMetas.some((schema) => schema.name === NO_ERRORS_SCHEMA.name)) {
return true;
}
if (tagName.indexOf('-') > -1) {
if (isNgContainer(tagName) || isNgContent(tagName)) {
return false;
}
if (schemaMetas.some((schema) => schema.name === CUSTOM_ELEMENTS_SCHEMA.name)) {
// Can't tell now as we don't know which properties a custom element will get
// once it is instantiated
return true;
}
}
const elementProperties = this._schema.get(tagName.toLowerCase()) || this._schema.get('unknown');
return elementProperties.has(propName);
}
hasElement(tagName, schemaMetas) {
if (schemaMetas.some((schema) => schema.name === NO_ERRORS_SCHEMA.name)) {
return true;
}
if (tagName.indexOf('-') > -1) {
if (isNgContainer(tagName) || isNgContent(tagName)) {
return true;
}
if (schemaMetas.some((schema) => schema.name === CUSTOM_ELEMENTS_SCHEMA.name)) {
// Allow any custom elements
return true;
}
}
return this._schema.has(tagName.toLowerCase());
}
/**
* securityContext returns the security context for the given property on the given DOM tag.
*
* Tag and property name are statically known and cannot change at runtime, i.e. it is not
* possible to bind a value into a changing attribute or tag name.
*
* The filtering is based on a list of allowed tags|attributes. All attributes in the schema
* above are assumed to have the 'NONE' security context, i.e. that they are safe inert
* string values. Only specific well known attack vectors are assigned their appropriate context.
*/
securityContext(tagName, propName, isAttribute) {
if (isAttribute) {
// NB: For security purposes, use the mapped property name, not the attribute name.
propName = this.getMappedPropName(propName);
}
// Make sure comparisons are case insensitive, so that case differences between attribute and
// property names do not have a security impact.
tagName = tagName.toLowerCase();
propName = propName.toLowerCase();
let ctx = SECURITY_SCHEMA()[tagName + '|' + propName];
if (ctx) {
return ctx;
}
ctx = SECURITY_SCHEMA()['*|' + propName];
return ctx ? ctx : SecurityContext.NONE;
}
getMappedPropName(propName) {
return _ATTR_TO_PROP.get(propName) ?? propName;
}
getDefaultComponentElementName() {
return 'ng-component';
}
validateProperty(name) {
if (name.toLowerCase().startsWith('on')) {
const msg = `Binding to event property '${name}' is disallowed for security reasons, ` +
`please use (${name.slice(2)})=...` +
`\nIf '${name}' is a directive input, make sure the directive is imported by the` +
` current module.`;
return { error: true, msg: msg };
}
else {
return { error: false };
}
}
validateAttribute(name) {
if (name.toLowerCase().startsWith('on')) {
const msg = `Binding to event attribute '${name}' is disallowed for security reasons, ` +
`please use (${name.slice(2)})=...`;
return { error: true, msg: msg };
}
else {
return { error: false };
}
}
allKnownElementNames() {
return Array.from(this._schema.keys());
}
allKnownAttributesOfElement(tagName) {
const elementProperties = this._schema.get(tagName.toLowerCase()) || this._schema.get('unknown');
// Convert properties to attributes.
return Array.from(elementProperties.keys()).map(prop => _PROP_TO_ATTR.get(prop) ?? prop);
}
allKnownEventsOfElement(tagName) {
return Array.from(this._eventSchema.get(tagName.toLowerCase()) ?? []);
}
normalizeAnimationStyleProperty(propName) {
return dashCaseToCamelCase(propName);
}
normalizeAnimationStyleValue(camelCaseProp, userProvidedProp, val) {
let unit = '';
const strVal = val.toString().trim();
let errorMsg = null;
if (_isPixelDimensionStyle(camelCaseProp) && val !== 0 && val !== '0') {
if (typeof val === 'number') {
unit = 'px';
}
else {
const valAndSuffixMatch = val.match(/^[+-]?[\d\.]+([a-z]*)$/);
if (valAndSuffixMatch && valAndSuffixMatch[1].length == 0) {
errorMsg = `Please provide a CSS unit value for ${userProvidedProp}:${val}`;
}
}
}
return { error: errorMsg, value: strVal + unit };
}
}
function _isPixelDimensionStyle(prop) {
switch (prop) {
case 'width':
case 'height':
case 'minWidth':
case 'minHeight':
case 'maxWidth':
case 'maxHeight':
case 'left':
case 'top':
case 'bottom':
case 'right':
case 'fontSize':
case 'outlineWidth':
case 'outlineOffset':
case 'paddingTop':
case 'paddingLeft':
case 'paddingBottom':
case 'paddingRight':
case 'marginTop':
case 'marginLeft':
case 'marginBottom':
case 'marginRight':
case 'borderRadius':
case 'borderWidth':
case 'borderTopWidth':
case 'borderLeftWidth':
case 'borderRightWidth':
case 'borderBottomWidth':
case 'textIndent':
return true;
default:
return false;
}
}
class HtmlTagDefinition {
constructor({ closedByChildren, implicitNamespacePrefix, contentType = TagContentType.PARSABLE_DATA, closedByParent = false, isVoid = false, ignoreFirstLf = false, preventNamespaceInheritance = false, canSelfClose = false, } = {}) {
this.closedByChildren = {};
this.closedByParent = false;
if (closedByChildren && closedByChildren.length > 0) {
closedByChildren.forEach(tagName => this.closedByChildren[tagName] = true);
}
this.isVoid = isVoid;
this.closedByParent = closedByParent || isVoid;
this.implicitNamespacePrefix = implicitNamespacePrefix || null;
this.contentType = contentType;
this.ignoreFirstLf = ignoreFirstLf;
this.preventNamespaceInheritance = preventNamespaceInheritance;
this.canSelfClose = canSelfClose ?? isVoid;
}
isClosedByChild(name) {
return this.isVoid || name.toLowerCase() in this.closedByChildren;
}
getContentType(prefix) {
if (typeof this.contentType === 'object') {
const overrideType = prefix === undefined ? undefined : this.contentType[prefix];
return overrideType ?? this.contentType.default;
}
return this.contentType;
}
}
let DEFAULT_TAG_DEFINITION;
// see https://www.w3.org/TR/html51/syntax.html#optional-tags
// This implementation does not fully conform to the HTML5 spec.
let TAG_DEFINITIONS;
function getHtmlTagDefinition(tagName) {
if (!TAG_DEFINITIONS) {
DEFAULT_TAG_DEFINITION = new HtmlTagDefinition({ canSelfClose: true });
TAG_DEFINITIONS = {
'base': new HtmlTagDefinition({ isVoid: true }),
'meta': new HtmlTagDefinition({ isVoid: true }),
'area': new HtmlTagDefinition({ isVoid: true }),
'embed': new HtmlTagDefinition({ isVoid: true }),
'link': new HtmlTagDefinition({ isVoid: true }),
'img': new HtmlTagDefinition({ isVoid: true }),
'input': new HtmlTagDefinition({ isVoid: true }),
'param': new HtmlTagDefinition({ isVoid: true }),
'hr': new HtmlTagDefinition({ isVoid: true }),
'br': new HtmlTagDefinition({ isVoid: true }),
'source': new HtmlTagDefinition({ isVoid: true }),
'track': new HtmlTagDefinition({ isVoid: true }),
'wbr': new HtmlTagDefinition({ isVoid: true }),
'p': new HtmlTagDefinition({
closedByChildren: [
'address', 'article', 'aside', 'blockquote', 'div', 'dl', 'fieldset',
'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5',
'h6', 'header', 'hgroup', 'hr', 'main', 'nav', 'ol',
'p', 'pre', 'section', 'table', 'ul'
],
closedByParent: true
}),
'thead': new HtmlTagDefinition({ closedByChildren: ['tbody', 'tfoot'] }),
'tbody': new HtmlTagDefinition({ closedByChildren: ['tbody', 'tfoot'], closedByParent: true }),
'tfoot': new HtmlTagDefinition({ closedByChildren: ['tbody'], closedByParent: true }),
'tr': new HtmlTagDefinition({ closedByChildren: ['tr'], closedByParent: true }),
'td': new HtmlTagDefinition({ closedByChildren: ['td', 'th'], closedByParent: true }),
'th': new HtmlTagDefinition({ closedByChildren: ['td', 'th'], closedByParent: true }),
'col': new HtmlTagDefinition({ isVoid: true }),
'svg': new HtmlTagDefinition({ implicitNamespacePrefix: 'svg' }),
'foreignObject': new HtmlTagDefinition({
// Usually the implicit namespace here would be redundant since it will be inherited from
// the parent `svg`, but we have to do it for `foreignObject`, because the way the parser
// works is that the parent node of an end tag is its own start tag which means that
// the `preventNamespaceInheritance` on `foreignObject` would have it default to the
// implicit namespace which is `html`, unless specified otherwise.
implicitNamespacePrefix: 'svg',
// We want to prevent children of foreignObject from inheriting its namespace, because
// the point of the element is to allow nodes from other namespaces to be inserted.
preventNamespaceInheritance: true,
}),
'math': new HtmlTagDefinition({ implicitNamespacePrefix: 'math' }),
'li': new HtmlTagDefinition({ closedByChildren: ['li'], closedByParent: true }),
'dt': new HtmlTagDefinition({ closedByChildren: ['dt', 'dd'] }),
'dd': new HtmlTagDefinition({ closedByChildren: ['dt', 'dd'], closedByParent: true }),
'rb': new HtmlTagDefinition({ closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true }),
'rt': new HtmlTagDefinition({ closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true }),
'rtc': new HtmlTagDefinition({ closedByChildren: ['rb', 'rtc', 'rp'], closedByParent: true }),
'rp': new HtmlTagDefinition({ closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true }),
'optgroup': new HtmlTagDefinition({ closedByChildren: ['optgroup'], closedByParent: true }),
'option': new HtmlTagDefinition({ closedByChildren: ['option', 'optgroup'], closedByParent: true }),
'pre': new HtmlTagDefinition({ ignoreFirstLf: true }),
'listing': new HtmlTagDefinition({ ignoreFirstLf: true }),
'style': new HtmlTagDefinition({ contentType: TagContentType.RAW_TEXT }),
'script': new HtmlTagDefinition({ contentType: TagContentType.RAW_TEXT }),
'title': new HtmlTagDefinition({
// The browser supports two separate `title` tags which have to use
// a different content type: `HTMLTitleElement` and `SVGTitleElement`
contentType: { default: TagContentType.ESCAPABLE_RAW_TEXT, svg: TagContentType.PARSABLE_DATA }
}),
'textarea': new HtmlTagDefinition({ contentType: TagContentType.ESCAPABLE_RAW_TEXT, ignoreFirstLf: true }),
};
new DomElementSchemaRegistry().allKnownElementNames().forEach(knownTagName => {
if (!TAG_DEFINITIONS.hasOwnProperty(knownTagName) && getNsPrefix(knownTagName) === null) {
TAG_DEFINITIONS[knownTagName] = new HtmlTagDefinition({ canSelfClose: false });
}
});
}
// We have to make both a case-sensitive and a case-insensitive lookup, because
// HTML tag names are case insensitive, whereas some SVG tags are case sensitive.
return TAG_DEFINITIONS[tagName] ?? TAG_DEFINITIONS[tagName.toLowerCase()] ??
DEFAULT_TAG_DEFINITION;
}
// Mapping between all HTML entity names and their unicode representation.
// Generated from https://html.spec.whatwg.org/multipage/entities.json by stripping
// the `&` and `;` from the keys and removing the duplicates.
// see https://www.w3.org/TR/html51/syntax.html#named-character-references
const NAMED_ENTITIES = {
'AElig': '\u00C6',
'AMP': '\u0026',
'amp': '\u0026',
'Aacute': '\u00C1',
'Abreve': '\u0102',
'Acirc': '\u00C2',
'Acy': '\u0410',
'Afr': '\uD835\uDD04',
'Agrave': '\u00C0',
'Alpha': '\u0391',
'Amacr': '\u0100',
'And': '\u2A53',
'Aogon': '\u0104',
'Aopf': '\uD835\uDD38',
'ApplyFunction': '\u2061',
'af': '\u2061',
'Aring': '\u00C5',
'angst': '\u00C5',
'Ascr': '\uD835\uDC9C',
'Assign': '\u2254',
'colone': '\u2254',
'coloneq': '\u2254',
'Atilde': '\u00C3',
'Auml': '\u00C4',
'Backslash': '\u2216',
'setminus': '\u2216',
'setmn': '\u2216',
'smallsetminus': '\u2216',
'ssetmn': '\u2216',
'Barv': '\u2AE7',
'Barwed': '\u2306',
'doublebarwedge': '\u2306',
'Bcy': '\u0411',
'Because': '\u2235',
'becaus': '\u2235',
'because': '\u2235',
'Bernoullis': '\u212C',
'Bscr': '\u212C',
'bernou': '\u212C',
'Beta': '\u0392',
'Bfr': '\uD835\uDD05',
'Bopf': '\uD835\uDD39',
'Breve': '\u02D8',
'breve': '\u02D8',
'Bumpeq': '\u224E',
'HumpDownHump': '\u224E',
'bump': '\u224E',
'CHcy': '\u0427',
'COPY': '\u00A9',
'copy': '\u00A9',
'Cacute': '\u0106',
'Cap': '\u22D2',
'CapitalDifferentialD': '\u2145',
'DD': '\u2145',
'Cayleys': '\u212D',
'Cfr': '\u212D',
'Ccaron': '\u010C',
'Ccedil': '\u00C7',
'Ccirc': '\u0108',
'Cconint': '\u2230',
'Cdot': '\u010A',
'Cedilla': '\u00B8',
'cedil': '\u00B8',
'CenterDot': '\u00B7',
'centerdot': '\u00B7',
'middot': '\u00B7',
'Chi': '\u03A7',
'CircleDot': '\u2299',
'odot': '\u2299',
'CircleMinus': '\u2296',
'ominus': '\u2296',
'CirclePlus': '\u2295',
'oplus': '\u2295',
'CircleTimes': '\u2297',
'otimes': '\u2297',
'ClockwiseContourIntegral': '\u2232',
'cwconint': '\u2232',
'CloseCurlyDoubleQuote': '\u201D',
'rdquo': '\u201D',
'rdquor': '\u201D',
'CloseCurlyQuote': '\u2019',
'rsquo': '\u2019',
'rsquor': '\u2019',
'Colon': '\u2237',
'Proportion': '\u2237',
'Colone': '\u2A74',
'Congruent': '\u2261',
'equiv': '\u2261',
'Conint': '\u222F',
'DoubleContourIntegral': '\u222F',
'ContourIntegral': '\u222E',
'conint': '\u222E',
'oint': '\u222E',
'Copf': '\u2102',
'complexes': '\u2102',
'Coproduct': '\u2210',
'coprod': '\u2210',
'CounterClockwiseContourIntegral': '\u2233',
'awconint': '\u2233',
'Cross': '\u2A2F',
'Cscr': '\uD835\uDC9E',
'Cup': '\u22D3',
'CupCap': '\u224D',
'asympeq': '\u224D',
'DDotrahd': '\u2911',
'DJcy': '\u0402',
'DScy': '\u0405',
'DZcy': '\u040F',
'Dagger': '\u2021',
'ddagger': '\u2021',
'Darr': '\u21A1',
'Dashv': '\u2AE4',
'DoubleLeftTee': '\u2AE4',
'Dcaron': '\u010E',
'Dcy': '\u0414',
'Del': '\u2207',
'nabla': '\u2207',
'Delta': '\u0394',
'Dfr': '\uD835\uDD07',
'DiacriticalAcute': '\u00B4',
'acute': '\u00B4',
'DiacriticalDot': '\u02D9',
'dot': '\u02D9',
'DiacriticalDoubleAcute': '\u02DD',
'dblac': '\u02DD',
'DiacriticalGrave': '\u0060',
'grave': '\u0060',
'DiacriticalTilde': '\u02DC',
'tilde': '\u02DC',
'Diamond': '\u22C4',
'diam': '\u22C4',
'diamond': '\u22C4',
'DifferentialD': '\u2146',
'dd': '\u2146',
'Dopf': '\uD835\uDD3B',
'Dot': '\u00A8',
'DoubleDot': '\u00A8',
'die': '\u00A8',
'uml': '\u00A8',
'DotDot': '\u20DC',
'DotEqual': '\u2250',
'doteq': '\u2250',
'esdot': '\u2250',
'DoubleDownArrow': '\u21D3',
'Downarrow': '\u21D3',
'dArr': '\u21D3',
'DoubleLeftArrow': '\u21D0',
'Leftarrow': '\u21D0',
'lArr': '\u21D0',
'DoubleLeftRightArrow': '\u21D4',
'Leftrightarrow': '\u21D4',
'hArr': '\u21D4',
'iff': '\u21D4',
'DoubleLongLeftArrow': '\u27F8',
'Longleftarrow': '\u27F8',
'xlArr': '\u27F8',
'DoubleLongLeftRightArrow': '\u27FA',
'Longleftrightarrow': '\u27FA',
'xhArr': '\u27FA',
'DoubleLongRightArrow': '\u27F9',
'Longrightarrow': '\u27F9',
'xrArr': '\u27F9',
'DoubleRightArrow': '\u21D2',
'Implies': '\u21D2',
'Rightarrow': '\u21D2',
'rArr': '\u21D2',
'DoubleRightTee': '\u22A8',
'vDash': '\u22A8',
'DoubleUpArrow': '\u21D1',
'Uparrow': '\u21D1',
'uArr': '\u21D1',
'DoubleUpDownArrow': '\u21D5',
'Updownarrow': '\u21D5',
'vArr': '\u21D5',
'DoubleVerticalBar': '\u2225',
'par': '\u2225',
'parallel': '\u2225',
'shortparallel': '\u2225',
'spar': '\u2225',
'DownArrow': '\u2193',
'ShortDownArrow': '\u2193',
'darr': '\u2193',
'downarrow': '\u2193',
'DownArrowBar': '\u2913',
'DownArrowUpArrow': '\u21F5',
'duarr': '\u21F5',
'DownBreve': '\u0311',
'DownLeftRightVector': '\u2950',
'DownLeftTeeVector': '\u295E',
'DownLeftVector': '\u21BD',
'leftharpoondown': '\u21BD',
'lhard': '\u21BD',
'DownLeftVectorBar': '\u2956',
'DownRightTeeVector': '\u295F',
'DownRightVector': '\u21C1',
'rhard': '\u21C1',
'rightharpoondown': '\u21C1',
'DownRightVectorBar': '\u2957',
'DownTee': '\u22A4',
'top': '\u22A4',
'DownTeeArrow': '\u21A7',
'mapstodown': '\u21A7',
'Dscr': '\uD835\uDC9F',
'Dstrok': '\u0110',
'ENG': '\u014A',
'ETH': '\u00D0',
'Eacute': '\u00C9',
'Ecaron': '\u011A',
'Ecirc': '\u00CA',
'Ecy': '\u042D',
'Edot': '\u0116',
'Efr': '\uD835\uDD08',
'Egrave': '\u00C8',
'Element': '\u2208',
'in': '\u2208',
'isin': '\u2208',
'isinv': '\u2208',
'Emacr': '\u0112',
'EmptySmallSquare': '\u25FB',
'EmptyVerySmallSquare': '\u25AB',
'Eogon': '\u0118',
'Eopf': '\uD835\uDD3C',
'Epsilon': '\u0395',
'Equal': '\u2A75',
'EqualTilde': '\u2242',
'eqsim': '\u2242',
'esim': '\u2242',
'Equilibrium': '\u21CC',
'rightleftharpoons': '\u21CC',
'rlhar': '\u21CC',
'Escr': '\u2130',
'expectation': '\u2130',
'Esim': '\u2A73',
'Eta': '\u0397',
'Euml': '\u00CB',
'Exists': '\u2203',
'exist': '\u2203',
'ExponentialE': '\u2147',
'ee': '\u2147',
'exponentiale': '\u2147',
'Fcy': '\u0424',
'Ffr': '\uD835\uDD09',
'FilledSmallSquare': '\u25FC',
'FilledVerySmallSquare': '\u25AA',
'blacksquare': '\u25AA',
'squarf': '\u25AA',
'squf': '\u25AA',
'Fopf': '\uD835\uDD3D',
'ForAll': '\u2200',
'forall': '\u2200',
'Fouriertrf': '\u2131',
'Fscr': '\u2131',
'GJcy': '\u0403',
'GT': '\u003E',
'gt': '\u003E',
'Gamma': '\u0393',
'Gammad': '\u03DC',
'Gbreve': '\u011E',
'Gcedil': '\u0122',
'Gcirc': '\u011C',
'Gcy': '\u0413',
'Gdot': '\u0120',
'Gfr': '\uD835\uDD0A',
'Gg': '\u22D9',
'ggg': '\u22D9',
'Gopf': '\uD835\uDD3E',
'GreaterEqual': '\u2265',
'ge': '\u2265',
'geq': '\u2265',
'GreaterEqualLess': '\u22DB',
'gel': '\u22DB',
'gtreqless': '\u22DB',
'GreaterFullEqual': '\u2267',
'gE': '\u2267',
'geqq': '\u2267',
'GreaterGreater': '\u2AA2',
'GreaterLess': '\u2277',
'gl': '\u2277',
'gtrless': '\u2277',
'GreaterSlantEqual': '\u2A7E',
'geqslant': '\u2A7E',
'ges': '\u2A7E',
'GreaterTilde': '\u2273',
'gsim': '\u2273',
'gtrsim': '\u2273',
'Gscr': '\uD835\uDCA2',
'Gt': '\u226B',
'NestedGreaterGreater': '\u226B',
'gg': '\u226B',
'HARDcy': '\u042A',
'Hacek': '\u02C7',
'caron': '\u02C7',
'Hat': '\u005E',
'Hcirc': '\u0124',
'Hfr': '\u210C',
'Poincareplane': '\u210C',
'HilbertSpace': '\u210B',
'Hscr': '\u210B',
'hamilt': '\u210B',
'Hopf': '\u210D',
'quaternions': '\u210D',
'HorizontalLine': '\u2500',
'boxh': '\u2500',
'Hstrok': '\u0126',
'HumpEqual': '\u224F',
'bumpe': '\u224F',
'bumpeq': '\u224F',
'IEcy': '\u0415',
'IJlig': '\u0132',
'IOcy': '\u0401',
'Iacute': '\u00CD',
'Icirc': '\u00CE',
'Icy': '\u0418',
'Idot': '\u0130',
'Ifr': '\u2111',
'Im': '\u2111',
'image': '\u2111',
'imagpart': '\u2111',
'Igrave': '\u00CC',
'Imacr': '\u012A',
'ImaginaryI': '\u2148',
'ii': '\u2148',
'Int': '\u222C',
'Integral': '\u222B',
'int': '\u222B',
'Intersection': '\u22C2',
'bigcap': '\u22C2',
'xcap': '\u22C2',
'InvisibleComma': '\u2063',
'ic': '\u2063',
'InvisibleTimes': '\u2062',
'it': '\u2062',
'Iogon': '\u012E',
'Iopf': '\uD835\uDD40',
'Iota': '\u0399',
'Iscr': '\u2110',
'imagline': '\u2110',
'Itilde': '\u0128',
'Iukcy': '\u0406',
'Iuml': '\u00CF',
'Jcirc': '\u0134',
'Jcy': '\u0419',
'Jfr': '\uD835\uDD0D',
'Jopf': '\uD835\uDD41',
'Jscr': '\uD835\uDCA5',
'Jsercy': '\u0408',
'Jukcy': '\u0404',
'KHcy': '\u0425',
'KJcy': '\u040C',
'Kappa': '\u039A',
'Kcedil': '\u0136',
'Kcy': '\u041A',
'Kfr': '\uD835\uDD0E',
'Kopf': '\uD835\uDD42',
'Kscr': '\uD835\uDCA6',
'LJcy': '\u0409',
'LT': '\u003C',
'lt': '\u003C',
'Lacute': '\u0139',
'Lambda': '\u039B',
'Lang': '\u27EA',
'Laplacetrf': '\u2112',
'Lscr': '\u2112',
'lagran': '\u2112',
'Larr': '\u219E',
'twoheadleftarrow': '\u219E',
'Lcaron': '\u013D',
'Lcedil': '\u013B',
'Lcy': '\u041B',
'LeftAngleBracket': '\u27E8',
'lang': '\u27E8',
'langle': '\u27E8',
'LeftArrow': '\u2190',
'ShortLeftArrow': '\u2190',
'larr': '\u2190',
'leftarrow': '\u2190',
'slarr': '\u2190',
'LeftArrowBar': '\u21E4',
'larrb': '\u21E4',
'LeftArrowRightArrow': '\u21C6',
'leftrightarrows': '\u21C6',
'lrarr': '\u21C6',
'LeftCeiling': '\u2308',
'lceil': '\u2308',
'LeftDoubleBracket': '\u27E6',
'lobrk': '\u27E6',
'LeftDownTeeVector': '\u2961',
'LeftDownVector': '\u21C3',
'dharl': '\u21C3',
'downharpoonleft': '\u21C3',
'LeftDownVectorBar': '\u2959',
'LeftFloor': '\u230A',
'lfloor': '\u230A',
'LeftRightArrow': '\u2194',
'harr': '\u2194',
'leftrightarrow': '\u2194',
'LeftRightVector': '\u294E',
'LeftTee': '\u22A3',
'dashv': '\u22A3',
'LeftTeeArrow': '\u21A4',
'mapstoleft': '\u21A4',
'LeftTeeVector': '\u295A',
'LeftTriangle': '\u22B2',
'vartriangleleft': '\u22B2',
'vltri': '\u22B2',
'LeftTriangleBar': '\u29CF',
'LeftTriangleEqual': '\u22B4',
'ltrie': '\u22B4',
'trianglelefteq': '\u22B4',
'LeftUpDownVector': '\u2951',
'LeftUpTeeVector': '\u2960',
'LeftUpVector': '\u21BF',
'uharl': '\u21BF',
'upharpoonleft': '\u21BF',
'LeftUpVectorBar': '\u2958',
'LeftVector': '\u21BC',
'leftharpoonup': '\u21BC',
'lharu': '\u21BC',
'LeftVectorBar': '\u2952',
'LessEqualGreater': '\u22DA',
'leg': '\u22DA',
'lesseqgtr': '\u22DA',
'LessFullEqual': '\u2266',
'lE': '\u2266',
'leqq': '\u2266',
'LessGreater': '\u2276',
'lessgtr': '\u2276',
'lg': '\u2276',
'LessLess': '\u2AA1',
'LessSlantEqual': '\u2A7D',
'leqslant': '\u2A7D',
'les': '\u2A7D',
'LessTilde': '\u2272',
'lesssim': '\u2272',
'lsim': '\u2272',
'Lfr': '\uD835\uDD0F',
'Ll': '\u22D8',
'Lleftarrow': '\u21DA',
'lAarr': '\u21DA',
'Lmidot': '\u013F',
'LongLeftArrow': '\u27F5',
'longleftarrow': '\u27F5',
'xlarr': '\u27F5',
'LongLeftRightArrow': '\u27F7',
'longleftrightarrow': '\u27F7',
'xharr': '\u27F7',
'LongRightArrow': '\u27F6',
'longrightarrow': '\u27F6',
'xrarr': '\u27F6',
'Lopf': '\uD835\uDD43',
'LowerLeftArrow': '\u2199',
'swarr': '\u2199',
'swarrow': '\u2199',
'LowerRightArrow': '\u2198',
'searr': '\u2198',
'searrow': '\u2198',
'Lsh': '\u21B0',
'lsh': '\u21B0',
'Lstrok': '\u0141',
'Lt': '\u226A',
'NestedLessLess': '\u226A',
'll': '\u226A',
'Map': '\u2905',
'Mcy': '\u041C',
'MediumSpace': '\u205F',
'Mellintrf': '\u2133',
'Mscr': '\u2133',
'phmmat': '\u2133',
'Mfr': '\uD835\uDD10',
'MinusPlus': '\u2213',
'mnplus': '\u2213',
'mp': '\u2213',
'Mopf': '\uD835\uDD44',
'Mu': '\u039C',
'NJcy': '\u040A',
'Nacute': '\u0143',
'Ncaron': '\u0147',
'Ncedil': '\u0145',
'Ncy': '\u041D',
'NegativeMediumSpace': '\u200B',
'NegativeThickSpace': '\u200B',
'NegativeThinSpace': '\u200B',
'NegativeVeryThinSpace': '\u200B',
'ZeroWidthSpace': '\u200B',
'NewLine': '\u000A',
'Nfr': '\uD835\uDD11',
'NoBreak': '\u2060',
'NonBreakingSpace': '\u00A0',
'nbsp': '\u00A0',
'Nopf': '\u2115',
'naturals': '\u2115',
'Not': '\u2AEC',
'NotCongruent': '\u2262',
'nequiv': '\u2262',
'NotCupCap': '\u226D',
'NotDoubleVerticalBar': '\u2226',
'npar': '\u2226',
'nparallel': '\u2226',
'nshortparallel': '\u2226',
'nspar': '\u2226',
'NotElement': '\u2209',
'notin': '\u2209',
'notinva': '\u2209',
'NotEqual': '\u2260',
'ne': '\u2260',
'NotEqualTilde': '\u2242\u0338',
'nesim': '\u2242\u0338',
'NotExists': '\u2204',
'nexist': '\u2204',
'nexists': '\u2204',
'NotGreater': '\u226F',
'ngt': '\u226F',
'ngtr': '\u226F',
'NotGreaterEqual': '\u2271',
'nge': '\u2271',
'ngeq': '\u2271',
'NotGreaterFullEqual': '\u2267\u0338',
'ngE': '\u2267\u0338',
'ngeqq': '\u2267\u0338',
'NotGreaterGreater': '\u226B\u0338',
'nGtv': '\u226B\u0338',
'NotGreaterLess': '\u2279',
'ntgl': '\u2279',
'NotGreaterSlantEqual': '\u2A7E\u0338',
'ngeqslant': '\u2A7E\u0338',
'nges': '\u2A7E\u0338',
'NotGreaterTilde': '\u2275',
'ngsim': '\u2275',
'NotHumpDownHump': '\u224E\u0338',
'nbump': '\u224E\u0338',
'NotHumpEqual': '\u224F\u0338',
'nbumpe': '\u224F\u0338',
'NotLeftTriangle': '\u22EA',
'nltri': '\u22EA',
'ntriangleleft': '\u22EA',
'NotLeftTriangleBar': '\u29CF\u0338',
'NotLeftTriangleEqual': '\u22EC',
'nltrie': '\u22EC',
'ntrianglelefteq': '\u22EC',
'NotLess': '\u226E',
'nless': '\u226E',
'nlt': '\u226E',
'NotLessEqual': '\u2270',
'nle': '\u2270',
'nleq': '\u2270',
'NotLessGreater': '\u2278',
'ntlg': '\u2278',
'NotLessLess': '\u226A\u0338',
'nLtv': '\u226A\u0338',
'NotLessSlantEqual': '\u2A7D\u0338',
'nleqslant': '\u2A7D\u0338',
'nles': '\u2A7D\u0338',
'NotLessTilde': '\u2274',
'nlsim': '\u2274',
'NotNestedGreaterGreater': '\u2AA2\u0338',
'NotNestedLessLess': '\u2AA1\u0338',
'NotPrecedes': '\u2280',
'npr': '\u2280',
'nprec': '\u2280',
'NotPrecedesEqual': '\u2AAF\u0338',
'npre': '\u2AAF\u0338',
'npreceq': '\u2AAF\u0338',
'NotPrecedesSlantEqual': '\u22E0',
'nprcue': '\u22E0',
'NotReverseElement': '\u220C',
'notni': '\u220C',
'notniva': '\u220C',
'NotRightTriangle': '\u22EB',
'nrtri': '\u22EB',
'ntriangleright': '\u22EB',
'NotRightTriangleBar': '\u29D0\u0338',
'NotRightTriangleEqual': '\u22ED',
'nrtrie': '\u22ED',
'ntrianglerighteq': '\u22ED',
'NotSquareSubset': '\u228F\u0338',
'NotSquareSubsetEqual': '\u22E2',
'nsqsube': '\u22E2',
'NotSquareSuperset': '\u2290\u0338',
'NotSquareSupersetEqual': '\u22E3',
'nsqsupe': '\u22E3',
'NotSubset': '\u2282\u20D2',
'nsubset': '\u2282\u20D2',
'vnsub': '\u2282\u20D2',
'NotSubsetEqual': '\u2288',
'nsube': '\u2288',
'nsubseteq': '\u2288',
'NotSucceeds': '\u2281',
'nsc': '\u2281',
'nsucc': '\u2281',
'NotSucceedsEqual': '\u2AB0\u0338',
'nsce': '\u2AB0\u0338',
'nsucceq': '\u2AB0\u0338',
'NotSucceedsSlantEqual': '\u22E1',
'nsccue': '\u22E1',
'NotSucceedsTilde': '\u227F\u0338',
'NotSuperset': '\u2283\u20D2',
'nsupset': '\u2283\u20D2',
'vnsup': '\u2283\u20D2',
'NotSupersetEqual': '\u2289',
'nsupe': '\u2289',
'nsupseteq': '\u2289',
'NotTilde': '\u2241',
'nsim': '\u2241',
'NotTildeEqual': '\u2244',
'nsime': '\u2244',
'nsimeq': '\u2244',
'NotTildeFullEqual': '\u2247',
'ncong': '\u2247',
'NotTildeTilde': '\u2249',
'nap': '\u2249',
'napprox': '\u2249',
'NotVerticalBar': '\u2224',
'nmid': '\u2224',
'nshortmid': '\u2224',
'nsmid': '\u2224',
'Nscr': '\uD835\uDCA9',
'Ntilde': '\u00D1',
'Nu': '\u039D',
'OElig': '\u0152',
'Oacute': '\u00D3',
'Ocirc': '\u00D4',
'Ocy': '\u041E',
'Odblac': '\u0150',
'Ofr': '\uD835\uDD12',
'Ograve': '\u00D2',
'Omacr': '\u014C',
'Omega': '\u03A9',
'ohm': '\u03A9',
'Omicron': '\u039F',
'Oopf': '\uD835\uDD46',
'OpenCurlyDoubleQuote': '\u201C',
'ldquo': '\u201C',
'OpenCurlyQuote': '\u2018',
'lsquo': '\u2018',
'Or': '\u2A54',
'Oscr': '\uD835\uDCAA',
'Oslash': '\u00D8',
'Otilde': '\u00D5',
'Otimes': '\u2A37',
'Ouml': '\u00D6',
'OverBar': '\u203E',
'oline': '\u203E',
'OverBrace': '\u23DE',
'OverBracket': '\u23B4',
'tbrk': '\u23B4',
'OverParenthesis': '\u23DC',
'PartialD': '\u2202',
'part': '\u2202',
'Pcy': '\u041F',
'Pfr': '\uD835\uDD13',
'Phi': '\u03A6',
'Pi': '\u03A0',
'PlusMinus': '\u00B1',
'plusmn': '\u00B1',
'pm': '\u00B1',
'Popf': '\u2119',
'primes': '\u2119',
'Pr': '\u2ABB',
'Precedes': '\u227A',
'pr': '\u227A',
'prec': '\u227A',
'PrecedesEqual': '\u2AAF',
'pre': '\u2AAF',
'preceq': '\u2AAF',
'PrecedesSlantEqual': '\u227C',
'prcue': '\u227C',
'preccurlyeq': '\u227C',
'PrecedesTilde': '\u227E',
'precsim': '\u227E',
'prsim': '\u227E',
'Prime': '\u2033',
'Product': '\u220F',
'prod': '\u220F',
'Proportional': '\u221D',
'prop': '\u221D',
'propto': '\u221D',
'varpropto': '\u221D',
'vprop': '\u221D',
'Pscr': '\uD835\uDCAB',
'Psi': '\u03A8',
'QUOT': '\u0022',
'quot': '\u0022',
'Qfr': '\uD835\uDD14',
'Qopf': '\u211A',
'rationals': '\u211A',
'Qscr': '\uD835\uDCAC',
'RBarr': '\u2910',
'drbkarow': '\u2910',
'REG': '\u00AE',
'circledR': '\u00AE',
'reg': '\u00AE',
'Racute': '\u0154',
'Rang': '\u27EB',
'Rarr': '\u21A0',
'twoheadrightarrow': '\u21A0',
'Rarrtl': '\u2916',
'Rcaron': '\u0158',
'Rcedil': '\u0156',
'Rcy': '\u0420',
'Re': '\u211C',
'Rfr': '\u211C',
'real': '\u211C',
'realpart': '\u211C',
'ReverseElement': '\u220B',
'SuchThat': '\u220B',
'ni': '\u220B',
'niv': '\u220B',
'ReverseEquilibrium': '\u21CB',
'leftrightharpoons': '\u21CB',
'lrhar': '\u21CB',
'ReverseUpEquilibrium': '\u296F',
'duhar': '\u296F',
'Rho': '\u03A1',
'RightAngleBracket': '\u27E9',
'rang': '\u27E9',
'rangle': '\u27E9',
'RightArrow': '\u2192',
'ShortRightArrow': '\u2192',
'rarr': '\u2192',
'rightarrow': '\u2192',
'srarr': '\u2192',
'RightArrowBar': '\u21E5',
'rarrb': '\u21E5',
'RightArrowLeftArrow': '\u21C4',
'rightleftarrows': '\u21C4',
'rlarr': '\u21C4',
'RightCeiling': '\u2309',
'rceil': '\u2309',
'RightDoubleBracket': '\u27E7',
'robrk': '\u27E7',
'RightDownTeeVector': '\u295D',
'RightDownVector': '\u21C2',
'dharr': '\u21C2',
'downharpoonright': '\u21C2',
'RightDownVectorBar': '\u2955',
'RightFloor': '\u230B',
'rfloor': '\u230B',
'RightTee': '\u22A2',
'vdash': '\u22A2',
'RightTeeArrow': '\u21A6',
'map': '\u21A6',
'mapsto': '\u21A6',
'RightTeeVector': '\u295B',
'RightTriangle': '\u22B3',
'vartriangleright': '\u22B3',
'vrtri': '\u22B3',
'RightTriangleBar': '\u29D0',
'RightTriangleEqual': '\u22B5',
'rtrie': '\u22B5',
'trianglerighteq': '\u22B5',
'RightUpDownVector': '\u294F',
'RightUpTeeVector': '\u295C',
'RightUpVector': '\u21BE',
'uharr': '\u21BE',
'upharpoonright': '\u21BE',
'RightUpVectorBar': '\u2954',
'RightVector': '\u21C0',
'rharu': '\u21C0',
'rightharpoonup': '\u21C0',
'RightVectorBar': '\u2953',
'Ropf': '\u211D',
'reals': '\u211D',
'RoundImplies': '\u2970',
'Rrightarrow': '\u21DB',
'rAarr': '\u21DB',
'Rscr': '\u211B',
'realine': '\u211B',
'Rsh': '\u21B1',
'rsh': '\u21B1',
'RuleDelayed': '\u29F4',
'SHCHcy': '\u0429',
'SHcy': '\u0428',
'SOFTcy': '\u042C',
'Sacute': '\u015A',
'Sc': '\u2ABC',
'Scaron': '\u0160',
'Scedil': '\u015E',
'Scirc': '\u015C',
'Scy': '\u0421',
'Sfr': '\uD835\uDD16',
'ShortUpArrow': '\u2191',
'UpArrow': '\u2191',
'uarr': '\u2191',
'uparrow': '\u2191',
'Sigma': '\u03A3',
'SmallCircle': '\u2218',
'compfn': '\u2218',
'Sopf': '\uD835\uDD4A',
'Sqrt': '\u221A',
'radic': '\u221A',
'Square': '\u25A1',
'squ': '\u25A1',
'square': '\u25A1',
'SquareIntersection': '\u2293',
'sqcap': '\u2293',
'SquareSubset': '\u228F',
'sqsub': '\u228F',
'sqsubset': '\u228F',
'SquareSubsetEqual': '\u2291',
'sqsube': '\u2291',
'sqsubseteq': '\u2291',
'SquareSuperset': '\u2290',
'sqsup': '\u2290',
'sqsupset': '\u2290',
'SquareSupersetEqual': '\u2292',
'sqsupe': '\u2292',
'sqsupseteq': '\u2292',
'SquareUnion': '\u2294',
'sqcup': '\u2294',
'Sscr': '\uD835\uDCAE',
'Star': '\u22C6',
'sstarf': '\u22C6',
'Sub': '\u22D0',
'Subset': '\u22D0',
'SubsetEqual': '\u2286',
'sube': '\u2286',
'subseteq': '\u2286',
'Succeeds': '\u227B',
'sc': '\u227B',
'succ': '\u227B',
'SucceedsEqual': '\u2AB0',
'sce': '\u2AB0',
'succeq': '\u2AB0',
'SucceedsSlantEqual': '\u227D',
'sccue': '\u227D',
'succcurlyeq': '\u227D',
'SucceedsTilde': '\u227F',
'scsim': '\u227F',
'succsim': '\u227F',
'Sum': '\u2211',
'sum': '\u2211',
'Sup': '\u22D1',
'Supset': '\u22D1',
'Superset': '\u2283',
'sup': '\u2283',
'supset': '\u2283',
'SupersetEqual': '\u2287',
'supe': '\u2287',
'supseteq': '\u2287',
'THORN': '\u00DE',
'TRADE': '\u2122',
'trade': '\u2122',
'TSHcy': '\u040B',
'TScy': '\u0426',
'Tab': '\u0009',
'Tau': '\u03A4',
'Tcaron': '\u0164',
'Tcedil': '\u0162',
'Tcy': '\u0422',
'Tfr': '\uD835\uDD17',
'Therefore': '\u2234',
'there4': '\u2234',
'therefore': '\u2234',
'Theta': '\u0398',
'ThickSpace': '\u205F\u200A',
'ThinSpace': '\u2009',
'thinsp': '\u2009',
'Tilde': '\u223C',
'sim': '\u223C',
'thicksim': '\u223C',
'thksim': '\u223C',
'TildeEqual': '\u2243',
'sime': '\u2243',
'simeq': '\u2243',
'TildeFullEqual': '\u2245',
'cong': '\u2245',
'TildeTilde': '\u2248',
'ap': '\u2248',
'approx': '\u2248',
'asymp': '\u2248',
'thickapprox': '\u2248',
'thkap': '\u2248',
'Topf': '\uD835\uDD4B',
'TripleDot': '\u20DB',
'tdot': '\u20DB',
'Tscr': '\uD835\uDCAF',
'Tstrok': '\u0166',
'Uacute': '\u00DA',
'Uarr': '\u219F',
'Uarrocir': '\u2949',
'Ubrcy': '\u040E',
'Ubreve': '\u016C',
'Ucirc': '\u00DB',
'Ucy': '\u0423',
'Udblac': '\u0170',
'Ufr': '\uD835\uDD18',
'Ugrave': '\u00D9',
'Umacr': '\u016A',
'UnderBar': '\u005F',
'lowbar': '\u005F',
'UnderBrace': '\u23DF',
'UnderBracket': '\u23B5',
'bbrk': '\u23B5',
'UnderParenthesis': '\u23DD',
'Union': '\u22C3',
'bigcup': '\u22C3',
'xcup': '\u22C3',
'UnionPlus': '\u228E',
'uplus': '\u228E',
'Uogon': '\u0172',
'Uopf': '\uD835\uDD4C',
'UpArrowBar': '\u2912',
'UpArrowDownArrow': '\u21C5',
'udarr': '\u21C5',
'UpDownArrow': '\u2195',
'updownarrow': '\u2195',
'varr': '\u2195',
'UpEquilibrium': '\u296E',
'udhar': '\u296E',
'UpTee': '\u22A5',
'bot': '\u22A5',
'bottom': '\u22A5',
'perp': '\u22A5',
'UpTeeArrow': '\u21A5',
'mapstoup': '\u21A5',
'UpperLeftArrow': '\u2196',
'nwarr': '\u2196',
'nwarrow': '\u2196',
'UpperRightArrow': '\u2197',
'nearr': '\u2197',
'nearrow': '\u2197',
'Upsi': '\u03D2',
'upsih': '\u03D2',
'Upsilon': '\u03A5',
'Uring': '\u016E',
'Uscr': '\uD835\uDCB0',
'Utilde': '\u0168',
'Uuml': '\u00DC',
'VDash': '\u22AB',
'Vbar': '\u2AEB',
'Vcy': '\u0412',
'Vdash': '\u22A9',
'Vdashl': '\u2AE6',
'Vee': '\u22C1',
'bigvee': '\u22C1',
'xvee': '\u22C1',
'Verbar': '\u2016',
'Vert': '\u2016',
'VerticalBar': '\u2223',
'mid': '\u2223',
'shortmid': '\u2223',
'smid': '\u2223',
'VerticalLine': '\u007C',
'verbar': '\u007C',
'vert': '\u007C',
'VerticalSeparator': '\u2758',
'VerticalTilde': '\u2240',
'wr': '\u2240',
'wreath': '\u2240',
'VeryThinSpace': '\u200A',
'hairsp': '\u200A',
'Vfr': '\uD835\uDD19',
'Vopf': '\uD835\uDD4D',
'Vscr': '\uD835\uDCB1',
'Vvdash': '\u22AA',
'Wcirc': '\u0174',
'Wedge': '\u22C0',
'bigwedge': '\u22C0',
'xwedge': '\u22C0',
'Wfr': '\uD835\uDD1A',
'Wopf': '\uD835\uDD4E',
'Wscr': '\uD835\uDCB2',
'Xfr': '\uD835\uDD1B',
'Xi': '\u039E',
'Xopf': '\uD835\uDD4F',
'Xscr': '\uD835\uDCB3',
'YAcy': '\u042F',
'YIcy': '\u0407',
'YUcy': '\u042E',
'Yacute': '\u00DD',
'Ycirc': '\u0176',
'Ycy': '\u042B',
'Yfr': '\uD835\uDD1C',
'Yopf': '\uD835\uDD50',
'Yscr': '\uD835\uDCB4',
'Yuml': '\u0178',
'ZHcy': '\u0416',
'Zacute': '\u0179',
'Zcaron': '\u017D',
'Zcy': '\u0417',
'Zdot': '\u017B',
'Zeta': '\u0396',
'Zfr': '\u2128',
'zeetrf': '\u2128',
'Zopf': '\u2124',
'integers': '\u2124',
'Zscr': '\uD835\uDCB5',
'aacute': '\u00E1',
'abreve': '\u0103',
'ac': '\u223E',
'mstpos': '\u223E',
'acE': '\u223E\u0333',
'acd': '\u223F',
'acirc': '\u00E2',
'acy': '\u0430',
'aelig': '\u00E6',
'afr': '\uD835\uDD1E',
'agrave': '\u00E0',
'alefsym': '\u2135',
'aleph': '\u2135',
'alpha': '\u03B1',
'amacr': '\u0101',
'amalg': '\u2A3F',
'and': '\u2227',
'wedge': '\u2227',
'andand': '\u2A55',
'andd': '\u2A5C',
'andslope': '\u2A58',
'andv': '\u2A5A',
'ang': '\u2220',
'angle': '\u2220',
'ange': '\u29A4',
'angmsd': '\u2221',
'measuredangle': '\u2221',
'angmsdaa': '\u29A8',
'angmsdab': '\u29A9',
'angmsdac': '\u29AA',
'angmsdad': '\u29AB',
'angmsdae': '\u29AC',
'angmsdaf': '\u29AD',
'angmsdag': '\u29AE',
'angmsdah': '\u29AF',
'angrt': '\u221F',
'angrtvb': '\u22BE',
'angrtvbd': '\u299D',
'angsph': '\u2222',
'angzarr': '\u237C',
'aogon': '\u0105',
'aopf': '\uD835\uDD52',
'apE': '\u2A70',
'apacir': '\u2A6F',
'ape': '\u224A',
'approxeq': '\u224A',
'apid': '\u224B',
'apos': '\u0027',
'aring': '\u00E5',
'ascr': '\uD835\uDCB6',
'ast': '\u002A',
'midast': '\u002A',
'atilde': '\u00E3',
'auml': '\u00E4',
'awint': '\u2A11',
'bNot': '\u2AED',
'backcong': '\u224C',
'bcong': '\u224C',
'backepsilon': '\u03F6',
'bepsi': '\u03F6',
'backprime': '\u2035',
'bprime': '\u2035',
'backsim': '\u223D',
'bsim': '\u223D',
'backsimeq': '\u22CD',
'bsime': '\u22CD',
'barvee': '\u22BD',
'barwed': '\u2305',
'barwedge': '\u2305',
'bbrktbrk': '\u23B6',
'bcy': '\u0431',
'bdquo': '\u201E',
'ldquor': '\u201E',
'bemptyv': '\u29B0',
'beta': '\u03B2',
'beth': '\u2136',
'between': '\u226C',
'twixt': '\u226C',
'bfr': '\uD835\uDD1F',
'bigcirc': '\u25EF',
'xcirc': '\u25EF',
'bigodot': '\u2A00',
'xodot': '\u2A00',
'bigoplus': '\u2A01',
'xoplus': '\u2A01',
'bigotimes': '\u2A02',
'xotime': '\u2A02',
'bigsqcup': '\u2A06',
'xsqcup': '\u2A06',
'bigstar': '\u2605',
'starf': '\u2605',
'bigtriangledown': '\u25BD',
'xdtri': '\u25BD',
'bigtriangleup': '\u25B3',
'xutri': '\u25B3',
'biguplus': '\u2A04',
'xuplus': '\u2A04',
'bkarow': '\u290D',
'rbarr': '\u290D',
'blacklozenge': '\u29EB',
'lozf': '\u29EB',
'blacktriangle': '\u25B4',
'utrif': '\u25B4',
'blacktriangledown': '\u25BE',
'dtrif': '\u25BE',
'blacktriangleleft': '\u25C2',
'ltrif': '\u25C2',
'blacktriangleright': '\u25B8',
'rtrif': '\u25B8',
'blank': '\u2423',
'blk12': '\u2592',
'blk14': '\u2591',
'blk34': '\u2593',
'block': '\u2588',
'bne': '\u003D\u20E5',
'bnequiv': '\u2261\u20E5',
'bnot': '\u2310',
'bopf': '\uD835\uDD53',
'bowtie': '\u22C8',
'boxDL': '\u2557',
'boxDR': '\u2554',
'boxDl': '\u2556',
'boxDr': '\u2553',
'boxH': '\u2550',
'boxHD': '\u2566',
'boxHU': '\u2569',
'boxHd': '\u2564',
'boxHu': '\u2567',
'boxUL': '\u255D',
'boxUR': '\u255A',
'boxUl': '\u255C',
'boxUr': '\u2559',
'boxV': '\u2551',
'boxVH': '\u256C',
'boxVL': '\u2563',
'boxVR': '\u2560',
'boxVh': '\u256B',
'boxVl': '\u2562',
'boxVr': '\u255F',
'boxbox': '\u29C9',
'boxdL': '\u2555',
'boxdR': '\u2552',
'boxdl': '\u2510',
'boxdr': '\u250C',
'boxhD': '\u2565',
'boxhU': '\u2568',
'boxhd': '\u252C',
'boxhu': '\u2534',
'boxminus': '\u229F',
'minusb': '\u229F',
'boxplus': '\u229E',
'plusb': '\u229E',
'boxtimes': '\u22A0',
'timesb': '\u22A0',
'boxuL': '\u255B',
'boxuR': '\u2558',
'boxul': '\u2518',
'boxur': '\u2514',
'boxv': '\u2502',
'boxvH': '\u256A',
'boxvL': '\u2561',
'boxvR': '\u255E',
'boxvh': '\u253C',
'boxvl': '\u2524',
'boxvr': '\u251C',
'brvbar': '\u00A6',
'bscr': '\uD835\uDCB7',
'bsemi': '\u204F',
'bsol': '\u005C',
'bsolb': '\u29C5',
'bsolhsub': '\u27C8',
'bull': '\u2022',
'bullet': '\u2022',
'bumpE': '\u2AAE',
'cacute': '\u0107',
'cap': '\u2229',
'capand': '\u2A44',
'capbrcup': '\u2A49',
'capcap': '\u2A4B',
'capcup': '\u2A47',
'capdot': '\u2A40',
'caps': '\u2229\uFE00',
'caret': '\u2041',
'ccaps': '\u2A4D',
'ccaron': '\u010D',
'ccedil': '\u00E7',
'ccirc': '\u0109',
'ccups': '\u2A4C',
'ccupssm': '\u2A50',
'cdot': '\u010B',
'cemptyv': '\u29B2',
'cent': '\u00A2',
'cfr': '\uD835\uDD20',
'chcy': '\u0447',
'check': '\u2713',
'checkmark': '\u2713',
'chi': '\u03C7',
'cir': '\u25CB',
'cirE': '\u29C3',
'circ': '\u02C6',
'circeq': '\u2257',
'cire': '\u2257',
'circlearrowleft': '\u21BA',
'olarr': '\u21BA',
'circlearrowright': '\u21BB',
'orarr': '\u21BB',
'circledS': '\u24C8',
'oS': '\u24C8',
'circledast': '\u229B',
'oast': '\u229B',
'circledcirc': '\u229A',
'ocir': '\u229A',
'circleddash': '\u229D',
'odash': '\u229D',
'cirfnint': '\u2A10',
'cirmid': '\u2AEF',
'cirscir': '\u29C2',
'clubs': '\u2663',
'clubsuit': '\u2663',
'colon': '\u003A',
'comma': '\u002C',
'commat': '\u0040',
'comp': '\u2201',
'complement': '\u2201',
'congdot': '\u2A6D',
'copf': '\uD835\uDD54',
'copysr': '\u2117',
'crarr': '\u21B5',
'cross': '\u2717',
'cscr': '\uD835\uDCB8',
'csub': '\u2ACF',
'csube': '\u2AD1',
'csup': '\u2AD0',
'csupe': '\u2AD2',
'ctdot': '\u22EF',
'cudarrl': '\u2938',
'cudarrr': '\u2935',
'cuepr': '\u22DE',
'curlyeqprec': '\u22DE',
'cuesc': '\u22DF',
'curlyeqsucc': '\u22DF',
'cularr': '\u21B6',
'curvearrowleft': '\u21B6',
'cularrp': '\u293D',
'cup': '\u222A',
'cupbrcap': '\u2A48',
'cupcap': '\u2A46',
'cupcup': '\u2A4A',
'cupdot': '\u228D',
'cupor': '\u2A45',
'cups': '\u222A\uFE00',
'curarr': '\u21B7',
'curvearrowright': '\u21B7',
'curarrm': '\u293C',
'curlyvee': '\u22CE',
'cuvee': '\u22CE',
'curlywedge': '\u22CF',
'cuwed': '\u22CF',
'curren': '\u00A4',
'cwint': '\u2231',
'cylcty': '\u232D',
'dHar': '\u2965',
'dagger': '\u2020',
'daleth': '\u2138',
'dash': '\u2010',
'hyphen': '\u2010',
'dbkarow': '\u290F',
'rBarr': '\u290F',
'dcaron': '\u010F',
'dcy': '\u0434',
'ddarr': '\u21CA',
'downdownarrows': '\u21CA',
'ddotseq': '\u2A77',
'eDDot': '\u2A77',
'deg': '\u00B0',
'delta': '\u03B4',
'demptyv': '\u29B1',
'dfisht': '\u297F',
'dfr': '\uD835\uDD21',
'diamondsuit': '\u2666',
'diams': '\u2666',
'digamma': '\u03DD',
'gammad': '\u03DD',
'disin': '\u22F2',
'div': '\u00F7',
'divide': '\u00F7',
'divideontimes': '\u22C7',
'divonx': '\u22C7',
'djcy': '\u0452',
'dlcorn': '\u231E',
'llcorner': '\u231E',
'dlcrop': '\u230D',
'dollar': '\u0024',
'dopf': '\uD835\uDD55',
'doteqdot': '\u2251',
'eDot': '\u2251',
'dotminus': '\u2238',
'minusd': '\u2238',
'dotplus': '\u2214',
'plusdo': '\u2214',
'dotsquare': '\u22A1',
'sdotb': '\u22A1',
'drcorn': '\u231F',
'lrcorner': '\u231F',
'drcrop': '\u230C',
'dscr': '\uD835\uDCB9',
'dscy': '\u0455',
'dsol': '\u29F6',
'dstrok': '\u0111',
'dtdot': '\u22F1',
'dtri': '\u25BF',
'triangledown': '\u25BF',
'dwangle': '\u29A6',
'dzcy': '\u045F',
'dzigrarr': '\u27FF',
'eacute': '\u00E9',
'easter': '\u2A6E',
'ecaron': '\u011B',
'ecir': '\u2256',
'eqcirc': '\u2256',
'ecirc': '\u00EA',
'ecolon': '\u2255',
'eqcolon': '\u2255',
'ecy': '\u044D',
'edot': '\u0117',
'efDot': '\u2252',
'fallingdotseq': '\u2252',
'efr': '\uD835\uDD22',
'eg': '\u2A9A',
'egrave': '\u00E8',
'egs': '\u2A96',
'eqslantgtr': '\u2A96',
'egsdot': '\u2A98',
'el': '\u2A99',
'elinters': '\u23E7',
'ell': '\u2113',
'els': '\u2A95',
'eqslantless': '\u2A95',
'elsdot': '\u2A97',
'emacr': '\u0113',
'empty': '\u2205',
'emptyset': '\u2205',
'emptyv': '\u2205',
'varnothing': '\u2205',
'emsp13': '\u2004',
'emsp14': '\u2005',
'emsp': '\u2003',
'eng': '\u014B',
'ensp': '\u2002',
'eogon': '\u0119',
'eopf': '\uD835\uDD56',
'epar': '\u22D5',
'eparsl': '\u29E3',
'eplus': '\u2A71',
'epsi': '\u03B5',
'epsilon': '\u03B5',
'epsiv': '\u03F5',
'straightepsilon': '\u03F5',
'varepsilon': '\u03F5',
'equals': '\u003D',
'equest': '\u225F',
'questeq': '\u225F',
'equivDD': '\u2A78',
'eqvparsl': '\u29E5',
'erDot': '\u2253',
'risingdotseq': '\u2253',
'erarr': '\u2971',
'escr': '\u212F',
'eta': '\u03B7',
'eth': '\u00F0',
'euml': '\u00EB',
'euro': '\u20AC',
'excl': '\u0021',
'fcy': '\u0444',
'female': '\u2640',
'ffilig': '\uFB03',
'fflig': '\uFB00',
'ffllig': '\uFB04',
'ffr': '\uD835\uDD23',
'filig': '\uFB01',
'fjlig': '\u0066\u006A',
'flat': '\u266D',
'fllig': '\uFB02',
'fltns': '\u25B1',
'fnof': '\u0192',
'fopf': '\uD835\uDD57',
'fork': '\u22D4',
'pitchfork': '\u22D4',
'forkv': '\u2AD9',
'fpartint': '\u2A0D',
'frac12': '\u00BD',
'half': '\u00BD',
'frac13': '\u2153',
'frac14': '\u00BC',
'frac15': '\u2155',
'frac16': '\u2159',
'frac18': '\u215B',
'frac23': '\u2154',
'frac25': '\u2156',
'frac34': '\u00BE',
'frac35': '\u2157',
'frac38': '\u215C',
'frac45': '\u2158',
'frac56': '\u215A',
'frac58': '\u215D',
'frac78': '\u215E',
'frasl': '\u2044',
'frown': '\u2322',
'sfrown': '\u2322',
'fscr': '\uD835\uDCBB',
'gEl': '\u2A8C',
'gtreqqless': '\u2A8C',
'gacute': '\u01F5',
'gamma': '\u03B3',
'gap': '\u2A86',
'gtrapprox': '\u2A86',
'gbreve': '\u011F',
'gcirc': '\u011D',
'gcy': '\u0433',
'gdot': '\u0121',
'gescc': '\u2AA9',
'gesdot': '\u2A80',
'gesdoto': '\u2A82',
'gesdotol': '\u2A84',
'gesl': '\u22DB\uFE00',
'gesles': '\u2A94',
'gfr': '\uD835\uDD24',
'gimel': '\u2137',
'gjcy': '\u0453',
'glE': '\u2A92',
'gla': '\u2AA5',
'glj': '\u2AA4',
'gnE': '\u2269',
'gneqq': '\u2269',
'gnap': '\u2A8A',
'gnapprox': '\u2A8A',
'gne': '\u2A88',
'gneq': '\u2A88',
'gnsim': '\u22E7',
'gopf': '\uD835\uDD58',
'gscr': '\u210A',
'gsime': '\u2A8E',
'gsiml': '\u2A90',
'gtcc': '\u2AA7',
'gtcir': '\u2A7A',
'gtdot': '\u22D7',
'gtrdot': '\u22D7',
'gtlPar': '\u2995',
'gtquest': '\u2A7C',
'gtrarr': '\u2978',
'gvertneqq': '\u2269\uFE00',
'gvnE': '\u2269\uFE00',
'hardcy': '\u044A',
'harrcir': '\u2948',
'harrw': '\u21AD',
'leftrightsquigarrow': '\u21AD',
'hbar': '\u210F',
'hslash': '\u210F',
'planck': '\u210F',
'plankv': '\u210F',
'hcirc': '\u0125',
'hearts': '\u2665',
'heartsuit': '\u2665',
'hellip': '\u2026',
'mldr': '\u2026',
'hercon': '\u22B9',
'hfr': '\uD835\uDD25',
'hksearow': '\u2925',
'searhk': '\u2925',
'hkswarow': '\u2926',
'swarhk': '\u2926',
'hoarr': '\u21FF',
'homtht': '\u223B',
'hookleftarrow': '\u21A9',
'larrhk': '\u21A9',
'hookrightarrow': '\u21AA',
'rarrhk': '\u21AA',
'hopf': '\uD835\uDD59',
'horbar': '\u2015',
'hscr': '\uD835\uDCBD',
'hstrok': '\u0127',
'hybull': '\u2043',
'iacute': '\u00ED',
'icirc': '\u00EE',
'icy': '\u0438',
'iecy': '\u0435',
'iexcl': '\u00A1',
'ifr': '\uD835\uDD26',
'igrave': '\u00EC',
'iiiint': '\u2A0C',
'qint': '\u2A0C',
'iiint': '\u222D',
'tint': '\u222D',
'iinfin': '\u29DC',
'iiota': '\u2129',
'ijlig': '\u0133',
'imacr': '\u012B',
'imath': '\u0131',
'inodot': '\u0131',
'imof': '\u22B7',
'imped': '\u01B5',
'incare': '\u2105',
'infin': '\u221E',
'infintie': '\u29DD',
'intcal': '\u22BA',
'intercal': '\u22BA',
'intlarhk': '\u2A17',
'intprod': '\u2A3C',
'iprod': '\u2A3C',
'iocy': '\u0451',
'iogon': '\u012F',
'iopf': '\uD835\uDD5A',
'iota': '\u03B9',
'iquest': '\u00BF',
'iscr': '\uD835\uDCBE',
'isinE': '\u22F9',
'isindot': '\u22F5',
'isins': '\u22F4',
'isinsv': '\u22F3',
'itilde': '\u0129',
'iukcy': '\u0456',
'iuml': '\u00EF',
'jcirc': '\u0135',
'jcy': '\u0439',
'jfr': '\uD835\uDD27',
'jmath': '\u0237',
'jopf': '\uD835\uDD5B',
'jscr': '\uD835\uDCBF',
'jsercy': '\u0458',
'jukcy': '\u0454',
'kappa': '\u03BA',
'kappav': '\u03F0',
'varkappa': '\u03F0',
'kcedil': '\u0137',
'kcy': '\u043A',
'kfr': '\uD835\uDD28',
'kgreen': '\u0138',
'khcy': '\u0445',
'kjcy': '\u045C',
'kopf': '\uD835\uDD5C',
'kscr': '\uD835\uDCC0',
'lAtail': '\u291B',
'lBarr': '\u290E',
'lEg': '\u2A8B',
'lesseqqgtr': '\u2A8B',
'lHar': '\u2962',
'lacute': '\u013A',
'laemptyv': '\u29B4',
'lambda': '\u03BB',
'langd': '\u2991',
'lap': '\u2A85',
'lessapprox': '\u2A85',
'laquo': '\u00AB',
'larrbfs': '\u291F',
'larrfs': '\u291D',
'larrlp': '\u21AB',
'looparrowleft': '\u21AB',
'larrpl': '\u2939',
'larrsim': '\u2973',
'larrtl': '\u21A2',
'leftarrowtail': '\u21A2',
'lat': '\u2AAB',
'latail': '\u2919',
'late': '\u2AAD',
'lates': '\u2AAD\uFE00',
'lbarr': '\u290C',
'lbbrk': '\u2772',
'lbrace': '\u007B',
'lcub': '\u007B',
'lbrack': '\u005B',
'lsqb': '\u005B',
'lbrke': '\u298B',
'lbrksld': '\u298F',
'lbrkslu': '\u298D',
'lcaron': '\u013E',
'lcedil': '\u013C',
'lcy': '\u043B',
'ldca': '\u2936',
'ldrdhar': '\u2967',
'ldrushar': '\u294B',
'ldsh': '\u21B2',
'le': '\u2264',
'leq': '\u2264',
'leftleftarrows': '\u21C7',
'llarr': '\u21C7',
'leftthreetimes': '\u22CB',
'lthree': '\u22CB',
'lescc': '\u2AA8',
'lesdot': '\u2A7F',
'lesdoto': '\u2A81',
'lesdotor': '\u2A83',
'lesg': '\u22DA\uFE00',
'lesges': '\u2A93',
'lessdot': '\u22D6',
'ltdot': '\u22D6',
'lfisht': '\u297C',
'lfr': '\uD835\uDD29',
'lgE': '\u2A91',
'lharul': '\u296A',
'lhblk': '\u2584',
'ljcy': '\u0459',
'llhard': '\u296B',
'lltri': '\u25FA',
'lmidot': '\u0140',
'lmoust': '\u23B0',
'lmoustache': '\u23B0',
'lnE': '\u2268',
'lneqq': '\u2268',
'lnap': '\u2A89',
'lnapprox': '\u2A89',
'lne': '\u2A87',
'lneq': '\u2A87',
'lnsim': '\u22E6',
'loang': '\u27EC',
'loarr': '\u21FD',
'longmapsto': '\u27FC',
'xmap': '\u27FC',
'looparrowright': '\u21AC',
'rarrlp': '\u21AC',
'lopar': '\u2985',
'lopf': '\uD835\uDD5D',
'loplus': '\u2A2D',
'lotimes': '\u2A34',
'lowast': '\u2217',
'loz': '\u25CA',
'lozenge': '\u25CA',
'lpar': '\u0028',
'lparlt': '\u2993',
'lrhard': '\u296D',
'lrm': '\u200E',
'lrtri': '\u22BF',
'lsaquo': '\u2039',
'lscr': '\uD835\uDCC1',
'lsime': '\u2A8D',
'lsimg': '\u2A8F',
'lsquor': '\u201A',
'sbquo': '\u201A',
'lstrok': '\u0142',
'ltcc': '\u2AA6',
'ltcir': '\u2A79',
'ltimes': '\u22C9',
'ltlarr': '\u2976',
'ltquest': '\u2A7B',
'ltrPar': '\u2996',
'ltri': '\u25C3',
'triangleleft': '\u25C3',
'lurdshar': '\u294A',
'luruhar': '\u2966',
'lvertneqq': '\u2268\uFE00',
'lvnE': '\u2268\uFE00',
'mDDot': '\u223A',
'macr': '\u00AF',
'strns': '\u00AF',
'male': '\u2642',
'malt': '\u2720',
'maltese': '\u2720',
'marker': '\u25AE',
'mcomma': '\u2A29',
'mcy': '\u043C',
'mdash': '\u2014',
'mfr': '\uD835\uDD2A',
'mho': '\u2127',
'micro': '\u00B5',
'midcir': '\u2AF0',
'minus': '\u2212',
'minusdu': '\u2A2A',
'mlcp': '\u2ADB',
'models': '\u22A7',
'mopf': '\uD835\uDD5E',
'mscr': '\uD835\uDCC2',
'mu': '\u03BC',
'multimap': '\u22B8',
'mumap': '\u22B8',
'nGg': '\u22D9\u0338',
'nGt': '\u226B\u20D2',
'nLeftarrow': '\u21CD',
'nlArr': '\u21CD',
'nLeftrightarrow': '\u21CE',
'nhArr': '\u21CE',
'nLl': '\u22D8\u0338',
'nLt': '\u226A\u20D2',
'nRightarrow': '\u21CF',
'nrArr': '\u21CF',
'nVDash': '\u22AF',
'nVdash': '\u22AE',
'nacute': '\u0144',
'nang': '\u2220\u20D2',
'napE': '\u2A70\u0338',
'napid': '\u224B\u0338',
'napos': '\u0149',
'natur': '\u266E',
'natural': '\u266E',
'ncap': '\u2A43',
'ncaron': '\u0148',
'ncedil': '\u0146',
'ncongdot': '\u2A6D\u0338',
'ncup': '\u2A42',
'ncy': '\u043D',
'ndash': '\u2013',
'neArr': '\u21D7',
'nearhk': '\u2924',
'nedot': '\u2250\u0338',
'nesear': '\u2928',
'toea': '\u2928',
'nfr': '\uD835\uDD2B',
'nharr': '\u21AE',
'nleftrightarrow': '\u21AE',
'nhpar': '\u2AF2',
'nis': '\u22FC',
'nisd': '\u22FA',
'njcy': '\u045A',
'nlE': '\u2266\u0338',
'nleqq': '\u2266\u0338',
'nlarr': '\u219A',
'nleftarrow': '\u219A',
'nldr': '\u2025',
'nopf': '\uD835\uDD5F',
'not': '\u00AC',
'notinE': '\u22F9\u0338',
'notindot': '\u22F5\u0338',
'notinvb': '\u22F7',
'notinvc': '\u22F6',
'notnivb': '\u22FE',
'notnivc': '\u22FD',
'nparsl': '\u2AFD\u20E5',
'npart': '\u2202\u0338',
'npolint': '\u2A14',
'nrarr': '\u219B',
'nrightarrow': '\u219B',
'nrarrc': '\u2933\u0338',
'nrarrw': '\u219D\u0338',
'nscr': '\uD835\uDCC3',
'nsub': '\u2284',
'nsubE': '\u2AC5\u0338',
'nsubseteqq': '\u2AC5\u0338',
'nsup': '\u2285',
'nsupE': '\u2AC6\u0338',
'nsupseteqq': '\u2AC6\u0338',
'ntilde': '\u00F1',
'nu': '\u03BD',
'num': '\u0023',
'numero': '\u2116',
'numsp': '\u2007',
'nvDash': '\u22AD',
'nvHarr': '\u2904',
'nvap': '\u224D\u20D2',
'nvdash': '\u22AC',
'nvge': '\u2265\u20D2',
'nvgt': '\u003E\u20D2',
'nvinfin': '\u29DE',
'nvlArr': '\u2902',
'nvle': '\u2264\u20D2',
'nvlt': '\u003C\u20D2',
'nvltrie': '\u22B4\u20D2',
'nvrArr': '\u2903',
'nvrtrie': '\u22B5\u20D2',
'nvsim': '\u223C\u20D2',
'nwArr': '\u21D6',
'nwarhk': '\u2923',
'nwnear': '\u2927',
'oacute': '\u00F3',
'ocirc': '\u00F4',
'ocy': '\u043E',
'odblac': '\u0151',
'odiv': '\u2A38',
'odsold': '\u29BC',
'oelig': '\u0153',
'ofcir': '\u29BF',
'ofr': '\uD835\uDD2C',
'ogon': '\u02DB',
'ograve': '\u00F2',
'ogt': '\u29C1',
'ohbar': '\u29B5',
'olcir': '\u29BE',
'olcross': '\u29BB',
'olt': '\u29C0',
'omacr': '\u014D',
'omega': '\u03C9',
'omicron': '\u03BF',
'omid': '\u29B6',
'oopf': '\uD835\uDD60',
'opar': '\u29B7',
'operp': '\u29B9',
'or': '\u2228',
'vee': '\u2228',
'ord': '\u2A5D',
'order': '\u2134',
'orderof': '\u2134',
'oscr': '\u2134',
'ordf': '\u00AA',
'ordm': '\u00BA',
'origof': '\u22B6',
'oror': '\u2A56',
'orslope': '\u2A57',
'orv': '\u2A5B',
'oslash': '\u00F8',
'osol': '\u2298',
'otilde': '\u00F5',
'otimesas': '\u2A36',
'ouml': '\u00F6',
'ovbar': '\u233D',
'para': '\u00B6',
'parsim': '\u2AF3',
'parsl': '\u2AFD',
'pcy': '\u043F',
'percnt': '\u0025',
'period': '\u002E',
'permil': '\u2030',
'pertenk': '\u2031',
'pfr': '\uD835\uDD2D',
'phi': '\u03C6',
'phiv': '\u03D5',
'straightphi': '\u03D5',
'varphi': '\u03D5',
'phone': '\u260E',
'pi': '\u03C0',
'piv': '\u03D6',
'varpi': '\u03D6',
'planckh': '\u210E',
'plus': '\u002B',
'plusacir': '\u2A23',
'pluscir': '\u2A22',
'plusdu': '\u2A25',
'pluse': '\u2A72',
'plussim': '\u2A26',
'plustwo': '\u2A27',
'pointint': '\u2A15',
'popf': '\uD835\uDD61',
'pound': '\u00A3',
'prE': '\u2AB3',
'prap': '\u2AB7',
'precapprox': '\u2AB7',
'precnapprox': '\u2AB9',
'prnap': '\u2AB9',
'precneqq': '\u2AB5',
'prnE': '\u2AB5',
'precnsim': '\u22E8',
'prnsim': '\u22E8',
'prime': '\u2032',
'profalar': '\u232E',
'profline': '\u2312',
'profsurf': '\u2313',
'prurel': '\u22B0',
'pscr': '\uD835\uDCC5',
'psi': '\u03C8',
'puncsp': '\u2008',
'qfr': '\uD835\uDD2E',
'qopf': '\uD835\uDD62',
'qprime': '\u2057',
'qscr': '\uD835\uDCC6',
'quatint': '\u2A16',
'quest': '\u003F',
'rAtail': '\u291C',
'rHar': '\u2964',
'race': '\u223D\u0331',
'racute': '\u0155',
'raemptyv': '\u29B3',
'rangd': '\u2992',
'range': '\u29A5',
'raquo': '\u00BB',
'rarrap': '\u2975',
'rarrbfs': '\u2920',
'rarrc': '\u2933',
'rarrfs': '\u291E',
'rarrpl': '\u2945',
'rarrsim': '\u2974',
'rarrtl': '\u21A3',
'rightarrowtail': '\u21A3',
'rarrw': '\u219D',
'rightsquigarrow': '\u219D',
'ratail': '\u291A',
'ratio': '\u2236',
'rbbrk': '\u2773',
'rbrace': '\u007D',
'rcub': '\u007D',
'rbrack': '\u005D',
'rsqb': '\u005D',
'rbrke': '\u298C',
'rbrksld': '\u298E',
'rbrkslu': '\u2990',
'rcaron': '\u0159',
'rcedil': '\u0157',
'rcy': '\u0440',
'rdca': '\u2937',
'rdldhar': '\u2969',
'rdsh': '\u21B3',
'rect': '\u25AD',
'rfisht': '\u297D',
'rfr': '\uD835\uDD2F',
'rharul': '\u296C',
'rho': '\u03C1',
'rhov': '\u03F1',
'varrho': '\u03F1',
'rightrightarrows': '\u21C9',
'rrarr': '\u21C9',
'rightthreetimes': '\u22CC',
'rthree': '\u22CC',
'ring': '\u02DA',
'rlm': '\u200F',
'rmoust': '\u23B1',
'rmoustache': '\u23B1',
'rnmid': '\u2AEE',
'roang': '\u27ED',
'roarr': '\u21FE',
'ropar': '\u2986',
'ropf': '\uD835\uDD63',
'roplus': '\u2A2E',
'rotimes': '\u2A35',
'rpar': '\u0029',
'rpargt': '\u2994',
'rppolint': '\u2A12',
'rsaquo': '\u203A',
'rscr': '\uD835\uDCC7',
'rtimes': '\u22CA',
'rtri': '\u25B9',
'triangleright': '\u25B9',
'rtriltri': '\u29CE',
'ruluhar': '\u2968',
'rx': '\u211E',
'sacute': '\u015B',
'scE': '\u2AB4',
'scap': '\u2AB8',
'succapprox': '\u2AB8',
'scaron': '\u0161',
'scedil': '\u015F',
'scirc': '\u015D',
'scnE': '\u2AB6',
'succneqq': '\u2AB6',
'scnap': '\u2ABA',
'succnapprox': '\u2ABA',
'scnsim': '\u22E9',
'succnsim': '\u22E9',
'scpolint': '\u2A13',
'scy': '\u0441',
'sdot': '\u22C5',
'sdote': '\u2A66',
'seArr': '\u21D8',
'sect': '\u00A7',
'semi': '\u003B',
'seswar': '\u2929',
'tosa': '\u2929',
'sext': '\u2736',
'sfr': '\uD835\uDD30',
'sharp': '\u266F',
'shchcy': '\u0449',
'shcy': '\u0448',
'shy': '\u00AD',
'sigma': '\u03C3',
'sigmaf': '\u03C2',
'sigmav': '\u03C2',
'varsigma': '\u03C2',
'simdot': '\u2A6A',
'simg': '\u2A9E',
'simgE': '\u2AA0',
'siml': '\u2A9D',
'simlE': '\u2A9F',
'simne': '\u2246',
'simplus': '\u2A24',
'simrarr': '\u2972',
'smashp': '\u2A33',
'smeparsl': '\u29E4',
'smile': '\u2323',
'ssmile': '\u2323',
'smt': '\u2AAA',
'smte': '\u2AAC',
'smtes': '\u2AAC\uFE00',
'softcy': '\u044C',
'sol': '\u002F',
'solb': '\u29C4',
'solbar': '\u233F',
'sopf': '\uD835\uDD64',
'spades': '\u2660',
'spadesuit': '\u2660',
'sqcaps': '\u2293\uFE00',
'sqcups': '\u2294\uFE00',
'sscr': '\uD835\uDCC8',
'star': '\u2606',
'sub': '\u2282',
'subset': '\u2282',
'subE': '\u2AC5',
'subseteqq': '\u2AC5',
'subdot': '\u2ABD',
'subedot': '\u2AC3',
'submult': '\u2AC1',
'subnE': '\u2ACB',
'subsetneqq': '\u2ACB',
'subne': '\u228A',
'subsetneq': '\u228A',
'subplus': '\u2ABF',
'subrarr': '\u2979',
'subsim': '\u2AC7',
'subsub': '\u2AD5',
'subsup': '\u2AD3',
'sung': '\u266A',
'sup1': '\u00B9',
'sup2': '\u00B2',
'sup3': '\u00B3',
'supE': '\u2AC6',
'supseteqq': '\u2AC6',
'supdot': '\u2ABE',
'supdsub': '\u2AD8',
'supedot': '\u2AC4',
'suphsol': '\u27C9',
'suphsub': '\u2AD7',
'suplarr': '\u297B',
'supmult': '\u2AC2',
'supnE': '\u2ACC',
'supsetneqq': '\u2ACC',
'supne': '\u228B',
'supsetneq': '\u228B',
'supplus': '\u2AC0',
'supsim': '\u2AC8',
'supsub': '\u2AD4',
'supsup': '\u2AD6',
'swArr': '\u21D9',
'swnwar': '\u292A',
'szlig': '\u00DF',
'target': '\u2316',
'tau': '\u03C4',
'tcaron': '\u0165',
'tcedil': '\u0163',
'tcy': '\u0442',
'telrec': '\u2315',
'tfr': '\uD835\uDD31',
'theta': '\u03B8',
'thetasym': '\u03D1',
'thetav': '\u03D1',
'vartheta': '\u03D1',
'thorn': '\u00FE',
'times': '\u00D7',
'timesbar': '\u2A31',
'timesd': '\u2A30',
'topbot': '\u2336',
'topcir': '\u2AF1',
'topf': '\uD835\uDD65',
'topfork': '\u2ADA',
'tprime': '\u2034',
'triangle': '\u25B5',
'utri': '\u25B5',
'triangleq': '\u225C',
'trie': '\u225C',
'tridot': '\u25EC',
'triminus': '\u2A3A',
'triplus': '\u2A39',
'trisb': '\u29CD',
'tritime': '\u2A3B',
'trpezium': '\u23E2',
'tscr': '\uD835\uDCC9',
'tscy': '\u0446',
'tshcy': '\u045B',
'tstrok': '\u0167',
'uHar': '\u2963',
'uacute': '\u00FA',
'ubrcy': '\u045E',
'ubreve': '\u016D',
'ucirc': '\u00FB',
'ucy': '\u0443',
'udblac': '\u0171',
'ufisht': '\u297E',
'ufr': '\uD835\uDD32',
'ugrave': '\u00F9',
'uhblk': '\u2580',
'ulcorn': '\u231C',
'ulcorner': '\u231C',
'ulcrop': '\u230F',
'ultri': '\u25F8',
'umacr': '\u016B',
'uogon': '\u0173',
'uopf': '\uD835\uDD66',
'upsi': '\u03C5',
'upsilon': '\u03C5',
'upuparrows': '\u21C8',
'uuarr': '\u21C8',
'urcorn': '\u231D',
'urcorner': '\u231D',
'urcrop': '\u230E',
'uring': '\u016F',
'urtri': '\u25F9',
'uscr': '\uD835\uDCCA',
'utdot': '\u22F0',
'utilde': '\u0169',
'uuml': '\u00FC',
'uwangle': '\u29A7',
'vBar': '\u2AE8',
'vBarv': '\u2AE9',
'vangrt': '\u299C',
'varsubsetneq': '\u228A\uFE00',
'vsubne': '\u228A\uFE00',
'varsubsetneqq': '\u2ACB\uFE00',
'vsubnE': '\u2ACB\uFE00',
'varsupsetneq': '\u228B\uFE00',
'vsupne': '\u228B\uFE00',
'varsupsetneqq': '\u2ACC\uFE00',
'vsupnE': '\u2ACC\uFE00',
'vcy': '\u0432',
'veebar': '\u22BB',
'veeeq': '\u225A',
'vellip': '\u22EE',
'vfr': '\uD835\uDD33',
'vopf': '\uD835\uDD67',
'vscr': '\uD835\uDCCB',
'vzigzag': '\u299A',
'wcirc': '\u0175',
'wedbar': '\u2A5F',
'wedgeq': '\u2259',
'weierp': '\u2118',
'wp': '\u2118',
'wfr': '\uD835\uDD34',
'wopf': '\uD835\uDD68',
'wscr': '\uD835\uDCCC',
'xfr': '\uD835\uDD35',
'xi': '\u03BE',
'xnis': '\u22FB',
'xopf': '\uD835\uDD69',
'xscr': '\uD835\uDCCD',
'yacute': '\u00FD',
'yacy': '\u044F',
'ycirc': '\u0177',
'ycy': '\u044B',
'yen': '\u00A5',
'yfr': '\uD835\uDD36',
'yicy': '\u0457',
'yopf': '\uD835\uDD6A',
'yscr': '\uD835\uDCCE',
'yucy': '\u044E',
'yuml': '\u00FF',
'zacute': '\u017A',
'zcaron': '\u017E',
'zcy': '\u0437',
'zdot': '\u017C',
'zeta': '\u03B6',
'zfr': '\uD835\uDD37',
'zhcy': '\u0436',
'zigrarr': '\u21DD',
'zopf': '\uD835\uDD6B',
'zscr': '\uD835\uDCCF',
'zwj': '\u200D',
'zwnj': '\u200C'
};
// The &ngsp; pseudo-entity is denoting a space.
// 0xE500 is a PUA (Private Use Areas) unicode character
// This is inspired by the Angular Dart implementation.
const NGSP_UNICODE = '\uE500';
NAMED_ENTITIES['ngsp'] = NGSP_UNICODE;
class TokenError extends ParseError {
constructor(errorMsg, tokenType, span) {
super(span, errorMsg);
this.tokenType = tokenType;
}
}
class TokenizeResult {
constructor(tokens, errors, nonNormalizedIcuExpressions) {
this.tokens = tokens;
this.errors = errors;
this.nonNormalizedIcuExpressions = nonNormalizedIcuExpressions;
}
}
function tokenize(source, url, getTagDefinition, options = {}) {
const tokenizer = new _Tokenizer(new ParseSourceFile(source, url), getTagDefinition, options);
tokenizer.tokenize();
return new TokenizeResult(mergeTextTokens(tokenizer.tokens), tokenizer.errors, tokenizer.nonNormalizedIcuExpressions);
}
const _CR_OR_CRLF_REGEXP = /\r\n?/g;
function _unexpectedCharacterErrorMsg(charCode) {
const char = charCode === $EOF ? 'EOF' : String.fromCharCode(charCode);
return `Unexpected character "${char}"`;
}
function _unknownEntityErrorMsg(entitySrc) {
return `Unknown entity "${entitySrc}" - use the ";" or ";" syntax`;
}
function _unparsableEntityErrorMsg(type, entityStr) {
return `Unable to parse entity "${entityStr}" - ${type} character reference entities must end with ";"`;
}
var CharacterReferenceType;
(function (CharacterReferenceType) {
CharacterReferenceType["HEX"] = "hexadecimal";
CharacterReferenceType["DEC"] = "decimal";
})(CharacterReferenceType || (CharacterReferenceType = {}));
class _ControlFlowError {
constructor(error) {
this.error = error;
}
}
// See https://www.w3.org/TR/html51/syntax.html#writing-html-documents
class _Tokenizer {
/**
* @param _file The html source file being tokenized.
* @param _getTagDefinition A function that will retrieve a tag definition for a given tag name.
* @param options Configuration of the tokenization.
*/
constructor(_file, _getTagDefinition, options) {
this._getTagDefinition = _getTagDefinition;
this._currentTokenStart = null;
this._currentTokenType = null;
this._expansionCaseStack = [];
this._inInterpolation = false;
this.tokens = [];
this.errors = [];
this.nonNormalizedIcuExpressions = [];
this._tokenizeIcu = options.tokenizeExpansionForms || false;
this._interpolationConfig = options.interpolationConfig || DEFAULT_INTERPOLATION_CONFIG;
this._leadingTriviaCodePoints =
options.leadingTriviaChars && options.leadingTriviaChars.map(c => c.codePointAt(0) || 0);
const range = options.range || { endPos: _file.content.length, startPos: 0, startLine: 0, startCol: 0 };
this._cursor = options.escapedString ? new EscapedCharacterCursor(_file, range) :
new PlainCharacterCursor(_file, range);
this._preserveLineEndings = options.preserveLineEndings || false;
this._i18nNormalizeLineEndingsInICUs = options.i18nNormalizeLineEndingsInICUs || false;
this._tokenizeBlocks = options.tokenizeBlocks || false;
try {
this._cursor.init();
}
catch (e) {
this.handleError(e);
}
}
_processCarriageReturns(content) {
if (this._preserveLineEndings) {
return content;
}
// https://www.w3.org/TR/html51/syntax.html#preprocessing-the-input-stream
// In order to keep the original position in the source, we can not
// pre-process it.
// Instead CRs are processed right before instantiating the tokens.
return content.replace(_CR_OR_CRLF_REGEXP, '\n');
}
tokenize() {
while (this._cursor.peek() !== $EOF) {
const start = this._cursor.clone();
try {
if (this._attemptCharCode($LT)) {
if (this._attemptCharCode($BANG)) {
if (this._attemptCharCode($LBRACKET)) {
this._consumeCdata(start);
}
else if (this._attemptCharCode($MINUS)) {
this._consumeComment(start);
}
else {
this._consumeDocType(start);
}
}
else if (this._attemptCharCode($SLASH)) {
this._consumeTagClose(start);
}
else {
this._consumeTagOpen(start);
}
}
else if (this._tokenizeBlocks && this._attemptStr('{#')) {
this._consumeBlockGroupOpen(start);
}
else if (this._tokenizeBlocks && this._attemptStr('{/')) {
this._consumeBlockGroupClose(start);
}
else if (this._tokenizeBlocks && this._attemptStr('{:')) {
this._consumeBlock(start);
}
else if (!(this._tokenizeIcu && this._tokenizeExpansionForm())) {
// In (possibly interpolated) text the end of the text is given by `isTextEnd()`, while
// the premature end of an interpolation is given by the start of a new HTML element.
this._consumeWithInterpolation(5 /* TokenType.TEXT */, 8 /* TokenType.INTERPOLATION */, () => this._isTextEnd(), () => this._isTagStart());
}
}
catch (e) {
this.handleError(e);
}
}
this._beginToken(24 /* TokenType.EOF */);
this._endToken([]);
}
_consumeBlockGroupOpen(start) {
this._beginToken(25 /* TokenType.BLOCK_GROUP_OPEN_START */, start);
const nameCursor = this._cursor.clone();
this._attemptCharCodeUntilFn(code => !isBlockNameChar(code));
this._endToken([this._cursor.getChars(nameCursor)]);
this._consumeBlockParameters();
this._beginToken(26 /* TokenType.BLOCK_GROUP_OPEN_END */);
this._requireCharCode($RBRACE);
this._endToken([]);
}
_consumeBlockGroupClose(start) {
this._beginToken(27 /* TokenType.BLOCK_GROUP_CLOSE */, start);
const nameCursor = this._cursor.clone();
this._attemptCharCodeUntilFn(code => !isBlockNameChar(code));
const name = this._cursor.getChars(nameCursor);
this._requireCharCode($RBRACE);
this._endToken([name]);
}
_consumeBlock(start) {
this._beginToken(29 /* TokenType.BLOCK_OPEN_START */, start);
const nameCursor = this._cursor.clone();
this._attemptCharCodeUntilFn(code => !isBlockNameChar(code));
this._endToken([this._cursor.getChars(nameCursor)]);
this._consumeBlockParameters();
this._beginToken(30 /* TokenType.BLOCK_OPEN_END */);
this._requireCharCode($RBRACE);
this._endToken([]);
}
_consumeBlockParameters() {
// Trim the whitespace until the first parameter.
this._attemptCharCodeUntilFn(isBlockParameterChar);
while (this._cursor.peek() !== $RBRACE && this._cursor.peek() !== $EOF) {
this._beginToken(28 /* TokenType.BLOCK_PARAMETER */);
const start = this._cursor.clone();
let inQuote = null;
let openBraces = 0;
// Consume the parameter until the next semicolon or brace.
// Note that we skip over semicolons/braces inside of strings.
while ((this._cursor.peek() !== $SEMICOLON && this._cursor.peek() !== $EOF) ||
inQuote !== null) {
const char = this._cursor.peek();
// Skip to the next character if it was escaped.
if (char === $BACKSLASH) {
this._cursor.advance();
}
else if (char === inQuote) {
inQuote = null;
}
else if (inQuote === null && isQuote(char)) {
inQuote = char;
}
else if (char === $LBRACE && inQuote === null) {
openBraces++;
}
else if (char === $RBRACE && inQuote === null) {
if (openBraces === 0) {
break;
}
else if (openBraces > 0) {
openBraces--;
}
}
this._cursor.advance();
}
this._endToken([this._cursor.getChars(start)]);
// Skip to the next parameter.
this._attemptCharCodeUntilFn(isBlockParameterChar);
}
}
/**
* @returns whether an ICU token has been created
* @internal
*/
_tokenizeExpansionForm() {
if (this.isExpansionFormStart()) {
this._consumeExpansionFormStart();
return true;
}
if (isExpansionCaseStart(this._cursor.peek()) && this._isInExpansionForm()) {
this._consumeExpansionCaseStart();
return true;
}
if (this._cursor.peek() === $RBRACE) {
if (this._isInExpansionCase()) {
this._consumeExpansionCaseEnd();
return true;
}
if (this._isInExpansionForm()) {
this._consumeExpansionFormEnd();
return true;
}
}
return false;
}
_beginToken(type, start = this._cursor.clone()) {
this._currentTokenStart = start;
this._currentTokenType = type;
}
_endToken(parts, end) {
if (this._currentTokenStart === null) {
throw new TokenError('Programming error - attempted to end a token when there was no start to the token', this._currentTokenType, this._cursor.getSpan(end));
}
if (this._currentTokenType === null) {
throw new TokenError('Programming error - attempted to end a token which has no token type', null, this._cursor.getSpan(this._currentTokenStart));
}
const token = {
type: this._currentTokenType,
parts,
sourceSpan: (end ?? this._cursor).getSpan(this._currentTokenStart, this._leadingTriviaCodePoints),
};
this.tokens.push(token);
this._currentTokenStart = null;
this._currentTokenType = null;
return token;
}
_createError(msg, span) {
if (this._isInExpansionForm()) {
msg += ` (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.)`;
}
const error = new TokenError(msg, this._currentTokenType, span);
this._currentTokenStart = null;
this._currentTokenType = null;
return new _ControlFlowError(error);
}
handleError(e) {
if (e instanceof CursorError) {
e = this._createError(e.msg, this._cursor.getSpan(e.cursor));
}
if (e instanceof _ControlFlowError) {
this.errors.push(e.error);
}
else {
throw e;
}
}
_attemptCharCode(charCode) {
if (this._cursor.peek() === charCode) {
this._cursor.advance();
return true;
}
return false;
}
_attemptCharCodeCaseInsensitive(charCode) {
if (compareCharCodeCaseInsensitive(this._cursor.peek(), charCode)) {
this._cursor.advance();
return true;
}
return false;
}
_requireCharCode(charCode) {
const location = this._cursor.clone();
if (!this._attemptCharCode(charCode)) {
throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(location));
}
}
_attemptStr(chars) {
const len = chars.length;
if (this._cursor.charsLeft() < len) {
return false;
}
const initialPosition = this._cursor.clone();
for (let i = 0; i < len; i++) {
if (!this._attemptCharCode(chars.charCodeAt(i))) {
// If attempting to parse the string fails, we want to reset the parser
// to where it was before the attempt
this._cursor = initialPosition;
return false;
}
}
return true;
}
_attemptStrCaseInsensitive(chars) {
for (let i = 0; i < chars.length; i++) {
if (!this._attemptCharCodeCaseInsensitive(chars.charCodeAt(i))) {
return false;
}
}
return true;
}
_requireStr(chars) {
const location = this._cursor.clone();
if (!this._attemptStr(chars)) {
throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(location));
}
}
_attemptCharCodeUntilFn(predicate) {
while (!predicate(this._cursor.peek())) {
this._cursor.advance();
}
}
_requireCharCodeUntilFn(predicate, len) {
const start = this._cursor.clone();
this._attemptCharCodeUntilFn(predicate);
if (this._cursor.diff(start) < len) {
throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
}
}
_attemptUntilChar(char) {
while (this._cursor.peek() !== char) {
this._cursor.advance();
}
}
_readChar() {
// Don't rely upon reading directly from `_input` as the actual char value
// may have been generated from an escape sequence.
const char = String.fromCodePoint(this._cursor.peek());
this._cursor.advance();
return char;
}
_consumeEntity(textTokenType) {
this._beginToken(9 /* TokenType.ENCODED_ENTITY */);
const start = this._cursor.clone();
this._cursor.advance();
if (this._attemptCharCode($HASH)) {
const isHex = this._attemptCharCode($x) || this._attemptCharCode($X);
const codeStart = this._cursor.clone();
this._attemptCharCodeUntilFn(isDigitEntityEnd);
if (this._cursor.peek() != $SEMICOLON) {
// Advance cursor to include the peeked character in the string provided to the error
// message.
this._cursor.advance();
const entityType = isHex ? CharacterReferenceType.HEX : CharacterReferenceType.DEC;
throw this._createError(_unparsableEntityErrorMsg(entityType, this._cursor.getChars(start)), this._cursor.getSpan());
}
const strNum = this._cursor.getChars(codeStart);
this._cursor.advance();
try {
const charCode = parseInt(strNum, isHex ? 16 : 10);
this._endToken([String.fromCharCode(charCode), this._cursor.getChars(start)]);
}
catch {
throw this._createError(_unknownEntityErrorMsg(this._cursor.getChars(start)), this._cursor.getSpan());
}
}
else {
const nameStart = this._cursor.clone();
this._attemptCharCodeUntilFn(isNamedEntityEnd);
if (this._cursor.peek() != $SEMICOLON) {
// No semicolon was found so abort the encoded entity token that was in progress, and treat
// this as a text token
this._beginToken(textTokenType, start);
this._cursor = nameStart;
this._endToken(['&']);
}
else {
const name = this._cursor.getChars(nameStart);
this._cursor.advance();
const char = NAMED_ENTITIES[name];
if (!char) {
throw this._createError(_unknownEntityErrorMsg(name), this._cursor.getSpan(start));
}
this._endToken([char, `&${name};`]);
}
}
}
_consumeRawText(consumeEntities, endMarkerPredicate) {
this._beginToken(consumeEntities ? 6 /* TokenType.ESCAPABLE_RAW_TEXT */ : 7 /* TokenType.RAW_TEXT */);
const parts = [];
while (true) {
const tagCloseStart = this._cursor.clone();
const foundEndMarker = endMarkerPredicate();
this._cursor = tagCloseStart;
if (foundEndMarker) {
break;
}
if (consumeEntities && this._cursor.peek() === $AMPERSAND) {
this._endToken([this._processCarriageReturns(parts.join(''))]);
parts.length = 0;
this._consumeEntity(6 /* TokenType.ESCAPABLE_RAW_TEXT */);
this._beginToken(6 /* TokenType.ESCAPABLE_RAW_TEXT */);
}
else {
parts.push(this._readChar());
}
}
this._endToken([this._processCarriageReturns(parts.join(''))]);
}
_consumeComment(start) {
this._beginToken(10 /* TokenType.COMMENT_START */, start);
this._requireCharCode($MINUS);
this._endToken([]);
this._consumeRawText(false, () => this._attemptStr('-->'));
this._beginToken(11 /* TokenType.COMMENT_END */);
this._requireStr('-->');
this._endToken([]);
}
_consumeCdata(start) {
this._beginToken(12 /* TokenType.CDATA_START */, start);
this._requireStr('CDATA[');
this._endToken([]);
this._consumeRawText(false, () => this._attemptStr(']]>'));
this._beginToken(13 /* TokenType.CDATA_END */);
this._requireStr(']]>');
this._endToken([]);
}
_consumeDocType(start) {
this._beginToken(18 /* TokenType.DOC_TYPE */, start);
const contentStart = this._cursor.clone();
this._attemptUntilChar($GT);
const content = this._cursor.getChars(contentStart);
this._cursor.advance();
this._endToken([content]);
}
_consumePrefixAndName() {
const nameOrPrefixStart = this._cursor.clone();
let prefix = '';
while (this._cursor.peek() !== $COLON && !isPrefixEnd(this._cursor.peek())) {
this._cursor.advance();
}
let nameStart;
if (this._cursor.peek() === $COLON) {
prefix = this._cursor.getChars(nameOrPrefixStart);
this._cursor.advance();
nameStart = this._cursor.clone();
}
else {
nameStart = nameOrPrefixStart;
}
this._requireCharCodeUntilFn(isNameEnd, prefix === '' ? 0 : 1);
const name = this._cursor.getChars(nameStart);
return [prefix, name];
}
_consumeTagOpen(start) {
let tagName;
let prefix;
let openTagToken;
try {
if (!isAsciiLetter(this._cursor.peek())) {
throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
}
openTagToken = this._consumeTagOpenStart(start);
prefix = openTagToken.parts[0];
tagName = openTagToken.parts[1];
this._attemptCharCodeUntilFn(isNotWhitespace);
while (this._cursor.peek() !== $SLASH && this._cursor.peek() !== $GT &&
this._cursor.peek() !== $LT && this._cursor.peek() !== $EOF) {
this._consumeAttributeName();
this._attemptCharCodeUntilFn(isNotWhitespace);
if (this._attemptCharCode($EQ)) {
this._attemptCharCodeUntilFn(isNotWhitespace);
this._consumeAttributeValue();
}
this._attemptCharCodeUntilFn(isNotWhitespace);
}
this._consumeTagOpenEnd();
}
catch (e) {
if (e instanceof _ControlFlowError) {
if (openTagToken) {
// We errored before we could close the opening tag, so it is incomplete.
openTagToken.type = 4 /* TokenType.INCOMPLETE_TAG_OPEN */;
}
else {
// When the start tag is invalid, assume we want a "<" as text.
// Back to back text tokens are merged at the end.
this._beginToken(5 /* TokenType.TEXT */, start);
this._endToken(['<']);
}
return;
}
throw e;
}
const contentTokenType = this._getTagDefinition(tagName).getContentType(prefix);
if (contentTokenType === TagContentType.RAW_TEXT) {
this._consumeRawTextWithTagClose(prefix, tagName, false);
}
else if (contentTokenType === TagContentType.ESCAPABLE_RAW_TEXT) {
this._consumeRawTextWithTagClose(prefix, tagName, true);
}
}
_consumeRawTextWithTagClose(prefix, tagName, consumeEntities) {
this._consumeRawText(consumeEntities, () => {
if (!this._attemptCharCode($LT))
return false;
if (!this._attemptCharCode($SLASH))
return false;
this._attemptCharCodeUntilFn(isNotWhitespace);
if (!this._attemptStrCaseInsensitive(tagName))
return false;
this._attemptCharCodeUntilFn(isNotWhitespace);
return this._attemptCharCode($GT);
});
this._beginToken(3 /* TokenType.TAG_CLOSE */);
this._requireCharCodeUntilFn(code => code === $GT, 3);
this._cursor.advance(); // Consume the `>`
this._endToken([prefix, tagName]);
}
_consumeTagOpenStart(start) {
this._beginToken(0 /* TokenType.TAG_OPEN_START */, start);
const parts = this._consumePrefixAndName();
return this._endToken(parts);
}
_consumeAttributeName() {
const attrNameStart = this._cursor.peek();
if (attrNameStart === $SQ || attrNameStart === $DQ) {
throw this._createError(_unexpectedCharacterErrorMsg(attrNameStart), this._cursor.getSpan());
}
this._beginToken(14 /* TokenType.ATTR_NAME */);
const prefixAndName = this._consumePrefixAndName();
this._endToken(prefixAndName);
}
_consumeAttributeValue() {
if (this._cursor.peek() === $SQ || this._cursor.peek() === $DQ) {
const quoteChar = this._cursor.peek();
this._consumeQuote(quoteChar);
// In an attribute then end of the attribute value and the premature end to an interpolation
// are both triggered by the `quoteChar`.
const endPredicate = () => this._cursor.peek() === quoteChar;
this._consumeWithInterpolation(16 /* TokenType.ATTR_VALUE_TEXT */, 17 /* TokenType.ATTR_VALUE_INTERPOLATION */, endPredicate, endPredicate);
this._consumeQuote(quoteChar);
}
else {
const endPredicate = () => isNameEnd(this._cursor.peek());
this._consumeWithInterpolation(16 /* TokenType.ATTR_VALUE_TEXT */, 17 /* TokenType.ATTR_VALUE_INTERPOLATION */, endPredicate, endPredicate);
}
}
_consumeQuote(quoteChar) {
this._beginToken(15 /* TokenType.ATTR_QUOTE */);
this._requireCharCode(quoteChar);
this._endToken([String.fromCodePoint(quoteChar)]);
}
_consumeTagOpenEnd() {
const tokenType = this._attemptCharCode($SLASH) ? 2 /* TokenType.TAG_OPEN_END_VOID */ : 1 /* TokenType.TAG_OPEN_END */;
this._beginToken(tokenType);
this._requireCharCode($GT);
this._endToken([]);
}
_consumeTagClose(start) {
this._beginToken(3 /* TokenType.TAG_CLOSE */, start);
this._attemptCharCodeUntilFn(isNotWhitespace);
const prefixAndName = this._consumePrefixAndName();
this._attemptCharCodeUntilFn(isNotWhitespace);
this._requireCharCode($GT);
this._endToken(prefixAndName);
}
_consumeExpansionFormStart() {
this._beginToken(19 /* TokenType.EXPANSION_FORM_START */);
this._requireCharCode($LBRACE);
this._endToken([]);
this._expansionCaseStack.push(19 /* TokenType.EXPANSION_FORM_START */);
this._beginToken(7 /* TokenType.RAW_TEXT */);
const condition = this._readUntil($COMMA);
const normalizedCondition = this._processCarriageReturns(condition);
if (this._i18nNormalizeLineEndingsInICUs) {
// We explicitly want to normalize line endings for this text.
this._endToken([normalizedCondition]);
}
else {
// We are not normalizing line endings.
const conditionToken = this._endToken([condition]);
if (normalizedCondition !== condition) {
this.nonNormalizedIcuExpressions.push(conditionToken);
}
}
this._requireCharCode($COMMA);
this._attemptCharCodeUntilFn(isNotWhitespace);
this._beginToken(7 /* TokenType.RAW_TEXT */);
const type = this._readUntil($COMMA);
this._endToken([type]);
this._requireCharCode($COMMA);
this._attemptCharCodeUntilFn(isNotWhitespace);
}
_consumeExpansionCaseStart() {
this._beginToken(20 /* TokenType.EXPANSION_CASE_VALUE */);
const value = this._readUntil($LBRACE).trim();
this._endToken([value]);
this._attemptCharCodeUntilFn(isNotWhitespace);
this._beginToken(21 /* TokenType.EXPANSION_CASE_EXP_START */);
this._requireCharCode($LBRACE);
this._endToken([]);
this._attemptCharCodeUntilFn(isNotWhitespace);
this._expansionCaseStack.push(21 /* TokenType.EXPANSION_CASE_EXP_START */);
}
_consumeExpansionCaseEnd() {
this._beginToken(22 /* TokenType.EXPANSION_CASE_EXP_END */);
this._requireCharCode($RBRACE);
this._endToken([]);
this._attemptCharCodeUntilFn(isNotWhitespace);
this._expansionCaseStack.pop();
}
_consumeExpansionFormEnd() {
this._beginToken(23 /* TokenType.EXPANSION_FORM_END */);
this._requireCharCode($RBRACE);
this._endToken([]);
this._expansionCaseStack.pop();
}
/**
* Consume a string that may contain interpolation expressions.
*
* The first token consumed will be of `tokenType` and then there will be alternating
* `interpolationTokenType` and `tokenType` tokens until the `endPredicate()` returns true.
*
* If an interpolation token ends prematurely it will have no end marker in its `parts` array.
*
* @param textTokenType the kind of tokens to interleave around interpolation tokens.
* @param interpolationTokenType the kind of tokens that contain interpolation.
* @param endPredicate a function that should return true when we should stop consuming.
* @param endInterpolation a function that should return true if there is a premature end to an
* interpolation expression - i.e. before we get to the normal interpolation closing marker.
*/
_consumeWithInterpolation(textTokenType, interpolationTokenType, endPredicate, endInterpolation) {
this._beginToken(textTokenType);
const parts = [];
while (!endPredicate()) {
const current = this._cursor.clone();
if (this._interpolationConfig && this._attemptStr(this._interpolationConfig.start)) {
this._endToken([this._processCarriageReturns(parts.join(''))], current);
parts.length = 0;
this._consumeInterpolation(interpolationTokenType, current, endInterpolation);
this._beginToken(textTokenType);
}
else if (this._cursor.peek() === $AMPERSAND) {
this._endToken([this._processCarriageReturns(parts.join(''))]);
parts.length = 0;
this._consumeEntity(textTokenType);
this._beginToken(textTokenType);
}
else {
parts.push(this._readChar());
}
}
// It is possible that an interpolation was started but not ended inside this text token.
// Make sure that we reset the state of the lexer correctly.
this._inInterpolation = false;
this._endToken([this._processCarriageReturns(parts.join(''))]);
}
/**
* Consume a block of text that has been interpreted as an Angular interpolation.
*
* @param interpolationTokenType the type of the interpolation token to generate.
* @param interpolationStart a cursor that points to the start of this interpolation.
* @param prematureEndPredicate a function that should return true if the next characters indicate
* an end to the interpolation before its normal closing marker.
*/
_consumeInterpolation(interpolationTokenType, interpolationStart, prematureEndPredicate) {
const parts = [];
this._beginToken(interpolationTokenType, interpolationStart);
parts.push(this._interpolationConfig.start);
// Find the end of the interpolation, ignoring content inside quotes.
const expressionStart = this._cursor.clone();
let inQuote = null;
let inComment = false;
while (this._cursor.peek() !== $EOF &&
(prematureEndPredicate === null || !prematureEndPredicate())) {
const current = this._cursor.clone();
if (this._isTagStart()) {
// We are starting what looks like an HTML element in the middle of this interpolation.
// Reset the cursor to before the `<` character and end the interpolation token.
// (This is actually wrong but here for backward compatibility).
this._cursor = current;
parts.push(this._getProcessedChars(expressionStart, current));
this._endToken(parts);
return;
}
if (inQuote === null) {
if (this._attemptStr(this._interpolationConfig.end)) {
// We are not in a string, and we hit the end interpolation marker
parts.push(this._getProcessedChars(expressionStart, current));
parts.push(this._interpolationConfig.end);
this._endToken(parts);
return;
}
else if (this._attemptStr('//')) {
// Once we are in a comment we ignore any quotes
inComment = true;
}
}
const char = this._cursor.peek();
this._cursor.advance();
if (char === $BACKSLASH) {
// Skip the next character because it was escaped.
this._cursor.advance();
}
else if (char === inQuote) {
// Exiting the current quoted string
inQuote = null;
}
else if (!inComment && inQuote === null && isQuote(char)) {
// Entering a new quoted string
inQuote = char;
}
}
// We hit EOF without finding a closing interpolation marker
parts.push(this._getProcessedChars(expressionStart, this._cursor));
this._endToken(parts);
}
_getProcessedChars(start, end) {
return this._processCarriageReturns(end.getChars(start));
}
_isTextEnd() {
if (this._isTagStart() || this._isBlockStart() || this._cursor.peek() === $EOF) {
return true;
}
if (this._tokenizeIcu && !this._inInterpolation) {
if (this.isExpansionFormStart()) {
// start of an expansion form
return true;
}
if (this._cursor.peek() === $RBRACE && this._isInExpansionCase()) {
// end of and expansion case
return true;
}
}
return false;
}
/**
* Returns true if the current cursor is pointing to the start of a tag
* (opening/closing/comments/cdata/etc).
*/
_isTagStart() {
if (this._cursor.peek() === $LT) {
// We assume that `<` followed by whitespace is not the start of an HTML element.
const tmp = this._cursor.clone();
tmp.advance();
// If the next character is alphabetic, ! nor / then it is a tag start
const code = tmp.peek();
if (($a <= code && code <= $z) || ($A <= code && code <= $Z) ||
code === $SLASH || code === $BANG) {
return true;
}
}
return false;
}
_isBlockStart() {
if (this._tokenizeBlocks && this._cursor.peek() === $LBRACE) {
const tmp = this._cursor.clone();
// Check that the cursor is on a `{#`, `{/` or `{:`.
tmp.advance();
const next = tmp.peek();
if (next !== $BANG && next !== $SLASH && next !== $COLON) {
return false;
}
// If it is, also verify that the next character is a valid block identifier.
tmp.advance();
if (isBlockNameChar(tmp.peek())) {
return true;
}
}
return false;
}
_readUntil(char) {
const start = this._cursor.clone();
this._attemptUntilChar(char);
return this._cursor.getChars(start);
}
_isInExpansionCase() {
return this._expansionCaseStack.length > 0 &&
this._expansionCaseStack[this._expansionCaseStack.length - 1] ===
21 /* TokenType.EXPANSION_CASE_EXP_START */;
}
_isInExpansionForm() {
return this._expansionCaseStack.length > 0 &&
this._expansionCaseStack[this._expansionCaseStack.length - 1] ===
19 /* TokenType.EXPANSION_FORM_START */;
}
isExpansionFormStart() {
if (this._cursor.peek() !== $LBRACE) {
return false;
}
if (this._interpolationConfig) {
const start = this._cursor.clone();
const isInterpolation = this._attemptStr(this._interpolationConfig.start);
this._cursor = start;
return !isInterpolation;
}
return true;
}
}
function isNotWhitespace(code) {
return !isWhitespace(code) || code === $EOF;
}
function isNameEnd(code) {
return isWhitespace(code) || code === $GT || code === $LT ||
code === $SLASH || code === $SQ || code === $DQ || code === $EQ ||
code === $EOF;
}
function isPrefixEnd(code) {
return (code < $a || $z < code) && (code < $A || $Z < code) &&
(code < $0 || code > $9);
}
function isDigitEntityEnd(code) {
return code === $SEMICOLON || code === $EOF || !isAsciiHexDigit(code);
}
function isNamedEntityEnd(code) {
return code === $SEMICOLON || code === $EOF || !isAsciiLetter(code);
}
function isExpansionCaseStart(peek) {
return peek !== $RBRACE;
}
function compareCharCodeCaseInsensitive(code1, code2) {
return toUpperCaseCharCode(code1) === toUpperCaseCharCode(code2);
}
function toUpperCaseCharCode(code) {
return code >= $a && code <= $z ? code - $a + $A : code;
}
function isBlockNameChar(code) {
return isAsciiLetter(code) || isDigit(code) || code === $_;
}
function isBlockParameterChar(code) {
return code !== $SEMICOLON && isNotWhitespace(code);
}
function mergeTextTokens(srcTokens) {
const dstTokens = [];
let lastDstToken = undefined;
for (let i = 0; i < srcTokens.length; i++) {
const token = srcTokens[i];
if ((lastDstToken && lastDstToken.type === 5 /* TokenType.TEXT */ && token.type === 5 /* TokenType.TEXT */) ||
(lastDstToken && lastDstToken.type === 16 /* TokenType.ATTR_VALUE_TEXT */ &&
token.type === 16 /* TokenType.ATTR_VALUE_TEXT */)) {
lastDstToken.parts[0] += token.parts[0];
lastDstToken.sourceSpan.end = token.sourceSpan.end;
}
else {
lastDstToken = token;
dstTokens.push(lastDstToken);
}
}
return dstTokens;
}
class PlainCharacterCursor {
constructor(fileOrCursor, range) {
if (fileOrCursor instanceof PlainCharacterCursor) {
this.file = fileOrCursor.file;
this.input = fileOrCursor.input;
this.end = fileOrCursor.end;
const state = fileOrCursor.state;
// Note: avoid using `{...fileOrCursor.state}` here as that has a severe performance penalty.
// In ES5 bundles the object spread operator is translated into the `__assign` helper, which
// is not optimized by VMs as efficiently as a raw object literal. Since this constructor is
// called in tight loops, this difference matters.
this.state = {
peek: state.peek,
offset: state.offset,
line: state.line,
column: state.column,
};
}
else {
if (!range) {
throw new Error('Programming error: the range argument must be provided with a file argument.');
}
this.file = fileOrCursor;
this.input = fileOrCursor.content;
this.end = range.endPos;
this.state = {
peek: -1,
offset: range.startPos,
line: range.startLine,
column: range.startCol,
};
}
}
clone() {
return new PlainCharacterCursor(this);
}
peek() {
return this.state.peek;
}
charsLeft() {
return this.end - this.state.offset;
}
diff(other) {
return this.state.offset - other.state.offset;
}
advance() {
this.advanceState(this.state);
}
init() {
this.updatePeek(this.state);
}
getSpan(start, leadingTriviaCodePoints) {
start = start || this;
let fullStart = start;
if (leadingTriviaCodePoints) {
while (this.diff(start) > 0 && leadingTriviaCodePoints.indexOf(start.peek()) !== -1) {
if (fullStart === start) {
start = start.clone();
}
start.advance();
}
}
const startLocation = this.locationFromCursor(start);
const endLocation = this.locationFromCursor(this);
const fullStartLocation = fullStart !== start ? this.locationFromCursor(fullStart) : startLocation;
return new ParseSourceSpan(startLocation, endLocation, fullStartLocation);
}
getChars(start) {
return this.input.substring(start.state.offset, this.state.offset);
}
charAt(pos) {
return this.input.charCodeAt(pos);
}
advanceState(state) {
if (state.offset >= this.end) {
this.state = state;
throw new CursorError('Unexpected character "EOF"', this);
}
const currentChar = this.charAt(state.offset);
if (currentChar === $LF) {
state.line++;
state.column = 0;
}
else if (!isNewLine(currentChar)) {
state.column++;
}
state.offset++;
this.updatePeek(state);
}
updatePeek(state) {
state.peek = state.offset >= this.end ? $EOF : this.charAt(state.offset);
}
locationFromCursor(cursor) {
return new ParseLocation(cursor.file, cursor.state.offset, cursor.state.line, cursor.state.column);
}
}
class EscapedCharacterCursor extends PlainCharacterCursor {
constructor(fileOrCursor, range) {
if (fileOrCursor instanceof EscapedCharacterCursor) {
super(fileOrCursor);
this.internalState = { ...fileOrCursor.internalState };
}
else {
super(fileOrCursor, range);
this.internalState = this.state;
}
}
advance() {
this.state = this.internalState;
super.advance();
this.processEscapeSequence();
}
init() {
super.init();
this.processEscapeSequence();
}
clone() {
return new EscapedCharacterCursor(this);
}
getChars(start) {
const cursor = start.clone();
let chars = '';
while (cursor.internalState.offset < this.internalState.offset) {
chars += String.fromCodePoint(cursor.peek());
cursor.advance();
}
return chars;
}
/**
* Process the escape sequence that starts at the current position in the text.
*
* This method is called to ensure that `peek` has the unescaped value of escape sequences.
*/
processEscapeSequence() {
const peek = () => this.internalState.peek;
if (peek() === $BACKSLASH) {
// We have hit an escape sequence so we need the internal state to become independent
// of the external state.
this.internalState = { ...this.state };
// Move past the backslash
this.advanceState(this.internalState);
// First check for standard control char sequences
if (peek() === $n) {
this.state.peek = $LF;
}
else if (peek() === $r) {
this.state.peek = $CR;
}
else if (peek() === $v) {
this.state.peek = $VTAB;
}
else if (peek() === $t) {
this.state.peek = $TAB;
}
else if (peek() === $b) {
this.state.peek = $BSPACE;
}
else if (peek() === $f) {
this.state.peek = $FF;
}
// Now consider more complex sequences
else if (peek() === $u) {
// Unicode code-point sequence
this.advanceState(this.internalState); // advance past the `u` char
if (peek() === $LBRACE) {
// Variable length Unicode, e.g. `\x{123}`
this.advanceState(this.internalState); // advance past the `{` char
// Advance past the variable number of hex digits until we hit a `}` char
const digitStart = this.clone();
let length = 0;
while (peek() !== $RBRACE) {
this.advanceState(this.internalState);
length++;
}
this.state.peek = this.decodeHexDigits(digitStart, length);
}
else {
// Fixed length Unicode, e.g. `\u1234`
const digitStart = this.clone();
this.advanceState(this.internalState);
this.advanceState(this.internalState);
this.advanceState(this.internalState);
this.state.peek = this.decodeHexDigits(digitStart, 4);
}
}
else if (peek() === $x) {
// Hex char code, e.g. `\x2F`
this.advanceState(this.internalState); // advance past the `x` char
const digitStart = this.clone();
this.advanceState(this.internalState);
this.state.peek = this.decodeHexDigits(digitStart, 2);
}
else if (isOctalDigit(peek())) {
// Octal char code, e.g. `\012`,
let octal = '';
let length = 0;
let previous = this.clone();
while (isOctalDigit(peek()) && length < 3) {
previous = this.clone();
octal += String.fromCodePoint(peek());
this.advanceState(this.internalState);
length++;
}
this.state.peek = parseInt(octal, 8);
// Backup one char
this.internalState = previous.internalState;
}
else if (isNewLine(this.internalState.peek)) {
// Line continuation `\` followed by a new line
this.advanceState(this.internalState); // advance over the newline
this.state = this.internalState;
}
else {
// If none of the `if` blocks were executed then we just have an escaped normal character.
// In that case we just, effectively, skip the backslash from the character.
this.state.peek = this.internalState.peek;
}
}
}
decodeHexDigits(start, length) {
const hex = this.input.slice(start.internalState.offset, start.internalState.offset + length);
const charCode = parseInt(hex, 16);
if (!isNaN(charCode)) {
return charCode;
}
else {
start.state = start.internalState;
throw new CursorError('Invalid hexadecimal escape sequence', start);
}
}
}
class CursorError {
constructor(msg, cursor) {
this.msg = msg;
this.cursor = cursor;
}
}
class TreeError extends ParseError {
static create(elementName, span, msg) {
return new TreeError(elementName, span, msg);
}
constructor(elementName, span, msg) {
super(span, msg);
this.elementName = elementName;
}
}
class ParseTreeResult {
constructor(rootNodes, errors) {
this.rootNodes = rootNodes;
this.errors = errors;
}
}
class Parser {
constructor(getTagDefinition) {
this.getTagDefinition = getTagDefinition;
}
parse(source, url, options) {
const tokenizeResult = tokenize(source, url, this.getTagDefinition, options);
const parser = new _TreeBuilder(tokenizeResult.tokens, this.getTagDefinition);
parser.build();
return new ParseTreeResult(parser.rootNodes, tokenizeResult.errors.concat(parser.errors));
}
}
class _TreeBuilder {
constructor(tokens, getTagDefinition) {
this.tokens = tokens;
this.getTagDefinition = getTagDefinition;
this._index = -1;
this._containerStack = [];
this.rootNodes = [];
this.errors = [];
this._advance();
}
build() {
while (this._peek.type !== 24 /* TokenType.EOF */) {
if (this._peek.type === 0 /* TokenType.TAG_OPEN_START */ ||
this._peek.type === 4 /* TokenType.INCOMPLETE_TAG_OPEN */) {
this._consumeStartTag(this._advance());
}
else if (this._peek.type === 3 /* TokenType.TAG_CLOSE */) {
this._consumeEndTag(this._advance());
}
else if (this._peek.type === 12 /* TokenType.CDATA_START */) {
this._closeVoidElement();
this._consumeCdata(this._advance());
}
else if (this._peek.type === 10 /* TokenType.COMMENT_START */) {
this._closeVoidElement();
this._consumeComment(this._advance());
}
else if (this._peek.type === 5 /* TokenType.TEXT */ || this._peek.type === 7 /* TokenType.RAW_TEXT */ ||
this._peek.type === 6 /* TokenType.ESCAPABLE_RAW_TEXT */) {
this._closeVoidElement();
this._consumeText(this._advance());
}
else if (this._peek.type === 19 /* TokenType.EXPANSION_FORM_START */) {
this._consumeExpansion(this._advance());
}
else if (this._peek.type === 25 /* TokenType.BLOCK_GROUP_OPEN_START */) {
this._closeVoidElement();
this._consumeBlockGroupOpen(this._advance());
}
else if (this._peek.type === 29 /* TokenType.BLOCK_OPEN_START */) {
this._closeVoidElement();
this._consumeBlock(this._advance(), 30 /* TokenType.BLOCK_OPEN_END */);
}
else if (this._peek.type === 27 /* TokenType.BLOCK_GROUP_CLOSE */) {
this._closeVoidElement();
this._consumeBlockGroupClose(this._advance());
}
else {
// Skip all other tokens...
this._advance();
}
}
}
_advance() {
const prev = this._peek;
if (this._index < this.tokens.length - 1) {
// Note: there is always an EOF token at the end
this._index++;
}
this._peek = this.tokens[this._index];
return prev;
}
_advanceIf(type) {
if (this._peek.type === type) {
return this._advance();
}
return null;
}
_consumeCdata(_startToken) {
this._consumeText(this._advance());
this._advanceIf(13 /* TokenType.CDATA_END */);
}
_consumeComment(token) {
const text = this._advanceIf(7 /* TokenType.RAW_TEXT */);
const endToken = this._advanceIf(11 /* TokenType.COMMENT_END */);
const value = text != null ? text.parts[0].trim() : null;
const sourceSpan = endToken == null ?
token.sourceSpan :
new ParseSourceSpan(token.sourceSpan.start, endToken.sourceSpan.end, token.sourceSpan.fullStart);
this._addToParent(new Comment(value, sourceSpan));
}
_consumeExpansion(token) {
const switchValue = this._advance();
const type = this._advance();
const cases = [];
// read =
while (this._peek.type === 20 /* TokenType.EXPANSION_CASE_VALUE */) {
const expCase = this._parseExpansionCase();
if (!expCase)
return; // error
cases.push(expCase);
}
// read the final }
if (this._peek.type !== 23 /* TokenType.EXPANSION_FORM_END */) {
this.errors.push(TreeError.create(null, this._peek.sourceSpan, `Invalid ICU message. Missing '}'.`));
return;
}
const sourceSpan = new ParseSourceSpan(token.sourceSpan.start, this._peek.sourceSpan.end, token.sourceSpan.fullStart);
this._addToParent(new Expansion(switchValue.parts[0], type.parts[0], cases, sourceSpan, switchValue.sourceSpan));
this._advance();
}
_parseExpansionCase() {
const value = this._advance();
// read {
if (this._peek.type !== 21 /* TokenType.EXPANSION_CASE_EXP_START */) {
this.errors.push(TreeError.create(null, this._peek.sourceSpan, `Invalid ICU message. Missing '{'.`));
return null;
}
// read until }
const start = this._advance();
const exp = this._collectExpansionExpTokens(start);
if (!exp)
return null;
const end = this._advance();
exp.push({ type: 24 /* TokenType.EOF */, parts: [], sourceSpan: end.sourceSpan });
// parse everything in between { and }
const expansionCaseParser = new _TreeBuilder(exp, this.getTagDefinition);
expansionCaseParser.build();
if (expansionCaseParser.errors.length > 0) {
this.errors = this.errors.concat(expansionCaseParser.errors);
return null;
}
const sourceSpan = new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end, value.sourceSpan.fullStart);
const expSourceSpan = new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end, start.sourceSpan.fullStart);
return new ExpansionCase(value.parts[0], expansionCaseParser.rootNodes, sourceSpan, value.sourceSpan, expSourceSpan);
}
_collectExpansionExpTokens(start) {
const exp = [];
const expansionFormStack = [21 /* TokenType.EXPANSION_CASE_EXP_START */];
while (true) {
if (this._peek.type === 19 /* TokenType.EXPANSION_FORM_START */ ||
this._peek.type === 21 /* TokenType.EXPANSION_CASE_EXP_START */) {
expansionFormStack.push(this._peek.type);
}
if (this._peek.type === 22 /* TokenType.EXPANSION_CASE_EXP_END */) {
if (lastOnStack(expansionFormStack, 21 /* TokenType.EXPANSION_CASE_EXP_START */)) {
expansionFormStack.pop();
if (expansionFormStack.length === 0)
return exp;
}
else {
this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
return null;
}
}
if (this._peek.type === 23 /* TokenType.EXPANSION_FORM_END */) {
if (lastOnStack(expansionFormStack, 19 /* TokenType.EXPANSION_FORM_START */)) {
expansionFormStack.pop();
}
else {
this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
return null;
}
}
if (this._peek.type === 24 /* TokenType.EOF */) {
this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
return null;
}
exp.push(this._advance());
}
}
_consumeText(token) {
const tokens = [token];
const startSpan = token.sourceSpan;
let text = token.parts[0];
if (text.length > 0 && text[0] === '\n') {
const parent = this._getContainer();
// This is unlikely to happen, but we have an assertion just in case.
if (parent instanceof BlockGroup) {
this.errors.push(TreeError.create(null, startSpan, 'Text cannot be placed directly inside of a block group.'));
return null;
}
if (parent != null && parent.children.length === 0 &&
this.getTagDefinition(parent.name).ignoreFirstLf) {
text = text.substring(1);
tokens[0] = { type: token.type, sourceSpan: token.sourceSpan, parts: [text] };
}
}
while (this._peek.type === 8 /* TokenType.INTERPOLATION */ || this._peek.type === 5 /* TokenType.TEXT */ ||
this._peek.type === 9 /* TokenType.ENCODED_ENTITY */) {
token = this._advance();
tokens.push(token);
if (token.type === 8 /* TokenType.INTERPOLATION */) {
// For backward compatibility we decode HTML entities that appear in interpolation
// expressions. This is arguably a bug, but it could be a considerable breaking change to
// fix it. It should be addressed in a larger project to refactor the entire parser/lexer
// chain after View Engine has been removed.
text += token.parts.join('').replace(/&([^;]+);/g, decodeEntity);
}
else if (token.type === 9 /* TokenType.ENCODED_ENTITY */) {
text += token.parts[0];
}
else {
text += token.parts.join('');
}
}
if (text.length > 0) {
const endSpan = token.sourceSpan;
this._addToParent(new Text(text, new ParseSourceSpan(startSpan.start, endSpan.end, startSpan.fullStart, startSpan.details), tokens));
}
}
_closeVoidElement() {
const el = this._getContainer();
if (el instanceof Element && this.getTagDefinition(el.name).isVoid) {
this._containerStack.pop();
}
}
_consumeStartTag(startTagToken) {
const [prefix, name] = startTagToken.parts;
const attrs = [];
while (this._peek.type === 14 /* TokenType.ATTR_NAME */) {
attrs.push(this._consumeAttr(this._advance()));
}
const fullName = this._getElementFullName(prefix, name, this._getClosestParentElement());
let selfClosing = false;
// Note: There could have been a tokenizer error
// so that we don't get a token for the end tag...
if (this._peek.type === 2 /* TokenType.TAG_OPEN_END_VOID */) {
this._advance();
selfClosing = true;
const tagDef = this.getTagDefinition(fullName);
if (!(tagDef.canSelfClose || getNsPrefix(fullName) !== null || tagDef.isVoid)) {
this.errors.push(TreeError.create(fullName, startTagToken.sourceSpan, `Only void, custom and foreign elements can be self closed "${startTagToken.parts[1]}"`));
}
}
else if (this._peek.type === 1 /* TokenType.TAG_OPEN_END */) {
this._advance();
selfClosing = false;
}
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
// Create a separate `startSpan` because `span` will be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
const el = new Element(fullName, attrs, [], span, startSpan, undefined);
const parentEl = this._getContainer();
this._pushContainer(el, parentEl instanceof Element &&
this.getTagDefinition(parentEl.name).isClosedByChild(el.name));
if (selfClosing) {
// Elements that are self-closed have their `endSourceSpan` set to the full span, as the
// element start tag also represents the end tag.
this._popContainer(fullName, Element, span);
}
else if (startTagToken.type === 4 /* TokenType.INCOMPLETE_TAG_OPEN */) {
// We already know the opening tag is not complete, so it is unlikely it has a corresponding
// close tag. Let's optimistically parse it as a full element and emit an error.
this._popContainer(fullName, Element, null);
this.errors.push(TreeError.create(fullName, span, `Opening tag "${fullName}" not terminated.`));
}
}
_pushContainer(node, isClosedByChild) {
if (isClosedByChild) {
this._containerStack.pop();
}
this._addToParent(node);
this._containerStack.push(node);
}
_consumeEndTag(endTagToken) {
const fullName = this._getElementFullName(endTagToken.parts[0], endTagToken.parts[1], this._getClosestParentElement());
if (this.getTagDefinition(fullName).isVoid) {
this.errors.push(TreeError.create(fullName, endTagToken.sourceSpan, `Void elements do not have end tags "${endTagToken.parts[1]}"`));
}
else if (!this._popContainer(fullName, Element, endTagToken.sourceSpan)) {
const errMsg = `Unexpected closing tag "${fullName}". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags`;
this.errors.push(TreeError.create(fullName, endTagToken.sourceSpan, errMsg));
}
}
/**
* Closes the nearest element with the tag name `fullName` in the parse tree.
* `endSourceSpan` is the span of the closing tag, or null if the element does
* not have a closing tag (for example, this happens when an incomplete
* opening tag is recovered).
*/
_popContainer(fullName, expectedType, endSourceSpan) {
let unexpectedCloseTagDetected = false;
for (let stackIndex = this._containerStack.length - 1; stackIndex >= 0; stackIndex--) {
const node = this._containerStack[stackIndex];
const name = node instanceof BlockGroup ? node.blocks[0]?.name : node.name;
if (name === fullName && node instanceof expectedType) {
// Record the parse span with the element that is being closed. Any elements that are
// removed from the element stack at this point are closed implicitly, so they won't get
// an end source span (as there is no explicit closing element).
node.endSourceSpan = endSourceSpan;
node.sourceSpan.end = endSourceSpan !== null ? endSourceSpan.end : node.sourceSpan.end;
this._containerStack.splice(stackIndex, this._containerStack.length - stackIndex);
return !unexpectedCloseTagDetected;
}
// Blocks are self-closing while block groups and (most times) elements are not.
if (node instanceof BlockGroup ||
node instanceof Element && !this.getTagDefinition(node.name).closedByParent) {
// Note that we encountered an unexpected close tag but continue processing the element
// stack so we can assign an `endSourceSpan` if there is a corresponding start tag for this
// end tag in the stack.
unexpectedCloseTagDetected = true;
}
}
return false;
}
_consumeAttr(attrName) {
const fullName = mergeNsAndName(attrName.parts[0], attrName.parts[1]);
let attrEnd = attrName.sourceSpan.end;
// Consume any quote
if (this._peek.type === 15 /* TokenType.ATTR_QUOTE */) {
this._advance();
}
// Consume the attribute value
let value = '';
const valueTokens = [];
let valueStartSpan = undefined;
let valueEnd = undefined;
// NOTE: We need to use a new variable `nextTokenType` here to hide the actual type of
// `_peek.type` from TS. Otherwise TS will narrow the type of `_peek.type` preventing it from
// being able to consider `ATTR_VALUE_INTERPOLATION` as an option. This is because TS is not
// able to see that `_advance()` will actually mutate `_peek`.
const nextTokenType = this._peek.type;
if (nextTokenType === 16 /* TokenType.ATTR_VALUE_TEXT */) {
valueStartSpan = this._peek.sourceSpan;
valueEnd = this._peek.sourceSpan.end;
while (this._peek.type === 16 /* TokenType.ATTR_VALUE_TEXT */ ||
this._peek.type === 17 /* TokenType.ATTR_VALUE_INTERPOLATION */ ||
this._peek.type === 9 /* TokenType.ENCODED_ENTITY */) {
const valueToken = this._advance();
valueTokens.push(valueToken);
if (valueToken.type === 17 /* TokenType.ATTR_VALUE_INTERPOLATION */) {
// For backward compatibility we decode HTML entities that appear in interpolation
// expressions. This is arguably a bug, but it could be a considerable breaking change to
// fix it. It should be addressed in a larger project to refactor the entire parser/lexer
// chain after View Engine has been removed.
value += valueToken.parts.join('').replace(/&([^;]+);/g, decodeEntity);
}
else if (valueToken.type === 9 /* TokenType.ENCODED_ENTITY */) {
value += valueToken.parts[0];
}
else {
value += valueToken.parts.join('');
}
valueEnd = attrEnd = valueToken.sourceSpan.end;
}
}
// Consume any quote
if (this._peek.type === 15 /* TokenType.ATTR_QUOTE */) {
const quoteToken = this._advance();
attrEnd = quoteToken.sourceSpan.end;
}
const valueSpan = valueStartSpan && valueEnd &&
new ParseSourceSpan(valueStartSpan.start, valueEnd, valueStartSpan.fullStart);
return new Attribute(fullName, value, new ParseSourceSpan(attrName.sourceSpan.start, attrEnd, attrName.sourceSpan.fullStart), attrName.sourceSpan, valueSpan, valueTokens.length > 0 ? valueTokens : undefined, undefined);
}
_consumeBlockGroupOpen(token) {
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
// Create a separate `startSpan` because `span` will be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
const blockGroup = new BlockGroup([], span, startSpan, null);
this._pushContainer(blockGroup, false);
const implicitBlock = this._consumeBlock(token, 26 /* TokenType.BLOCK_GROUP_OPEN_END */);
// Block parameters are consumed as a part of the implicit block so we need to expand the
// start source span once the block is parsed to include the full opening tag.
startSpan.end = implicitBlock.startSourceSpan.end;
}
_consumeBlock(token, closeToken) {
// The start of a block implicitly closes the previous block.
this._conditionallyClosePreviousBlock();
const parameters = [];
while (this._peek.type === 28 /* TokenType.BLOCK_PARAMETER */) {
const paramToken = this._advance();
parameters.push(new BlockParameter(paramToken.parts[0], paramToken.sourceSpan));
}
if (this._peek.type === closeToken) {
this._advance();
}
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
// Create a separate `startSpan` because `span` will be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
const block = new Block(token.parts[0], parameters, [], span, startSpan);
const parent = this._getContainer();
if (!(parent instanceof BlockGroup)) {
this.errors.push(TreeError.create(block.name, block.sourceSpan, 'Blocks can only be placed inside of block groups.'));
}
else {
parent.blocks.push(block);
this._containerStack.push(block);
}
return block;
}
_consumeBlockGroupClose(token) {
const name = token.parts[0];
const previousContainer = this._getContainer();
// Blocks are implcitly closed by the block group.
this._conditionallyClosePreviousBlock();
if (!this._popContainer(name, BlockGroup, token.sourceSpan)) {
const context = previousContainer instanceof Element ?
`There is an unclosed "${previousContainer.name}" HTML tag named that may have to be closed first.` :
`The block may have been closed earlier.`;
this.errors.push(TreeError.create(name, token.sourceSpan, `Unexpected closing block "${name}". ${context}`));
}
}
_conditionallyClosePreviousBlock() {
const container = this._getContainer();
if (container instanceof Block) {
// Blocks don't have an explicit closing tag, they're closed either by the next block or
// the end of the block group. Infer the end span from the last child node.
const lastChild = container.children.length ? container.children[container.children.length - 1] : null;
const endSpan = lastChild === null ?
null :
new ParseSourceSpan(lastChild.sourceSpan.end, lastChild.sourceSpan.end);
this._popContainer(container.name, Block, endSpan);
}
}
_getContainer() {
return this._containerStack.length > 0 ? this._containerStack[this._containerStack.length - 1] :
null;
}
_getClosestParentElement() {
for (let i = this._containerStack.length - 1; i > -1; i--) {
if (this._containerStack[i] instanceof Element) {
return this._containerStack[i];
}
}
return null;
}
_addToParent(node) {
const parent = this._getContainer();
if (parent === null) {
this.rootNodes.push(node);
}
else if (parent instanceof BlockGroup) {
// Due to how parsing is set up, we're unlikely to hit this code path, but we
// have the assertion here just in case and to satisfy the type checker.
this.errors.push(TreeError.create(null, node.sourceSpan, 'Block groups can only contain blocks.'));
}
else {
parent.children.push(node);
}
}
_getElementFullName(prefix, localName, parentElement) {
if (prefix === '') {
prefix = this.getTagDefinition(localName).implicitNamespacePrefix || '';
if (prefix === '' && parentElement != null) {
const parentTagName = splitNsName(parentElement.name)[1];
const parentTagDefinition = this.getTagDefinition(parentTagName);
if (!parentTagDefinition.preventNamespaceInheritance) {
prefix = getNsPrefix(parentElement.name);
}
}
}
return mergeNsAndName(prefix, localName);
}
}
function lastOnStack(stack, element) {
return stack.length > 0 && stack[stack.length - 1] === element;
}
/**
* Decode the `entity` string, which we believe is the contents of an HTML entity.
*
* If the string is not actually a valid/known entity then just return the original `match` string.
*/
function decodeEntity(match, entity) {
if (NAMED_ENTITIES[entity] !== undefined) {
return NAMED_ENTITIES[entity] || match;
}
if (/^#x[a-f0-9]+$/i.test(entity)) {
return String.fromCodePoint(parseInt(entity.slice(2), 16));
}
if (/^#\d+$/.test(entity)) {
return String.fromCodePoint(parseInt(entity.slice(1), 10));
}
return match;
}
class HtmlParser extends Parser {
constructor() {
super(getHtmlTagDefinition);
}
parse(source, url, options) {
return super.parse(source, url, options);
}
}
const PRESERVE_WS_ATTR_NAME = 'ngPreserveWhitespaces';
const SKIP_WS_TRIM_TAGS = new Set(['pre', 'template', 'textarea', 'script', 'style']);
// Equivalent to \s with \u00a0 (non-breaking space) excluded.
// Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
const WS_CHARS = ' \f\n\r\t\v\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff';
const NO_WS_REGEXP = new RegExp(`[^${WS_CHARS}]`);
const WS_REPLACE_REGEXP = new RegExp(`[${WS_CHARS}]{2,}`, 'g');
function hasPreserveWhitespacesAttr(attrs) {
return attrs.some((attr) => attr.name === PRESERVE_WS_ATTR_NAME);
}
/**
* &ngsp; is a placeholder for non-removable space
* &ngsp; is converted to the 0xE500 PUA (Private Use Areas) unicode character
* and later on replaced by a space.
*/
function replaceNgsp(value) {
// lexer is replacing the &ngsp; pseudo-entity with NGSP_UNICODE
return value.replace(new RegExp(NGSP_UNICODE, 'g'), ' ');
}
/**
* This visitor can walk HTML parse tree and remove / trim text nodes using the following rules:
* - consider spaces, tabs and new lines as whitespace characters;
* - drop text nodes consisting of whitespace characters only;
* - for all other text nodes replace consecutive whitespace characters with one space;
* - convert &ngsp; pseudo-entity to a single space;
*
* Removal and trimming of whitespaces have positive performance impact (less code to generate
* while compiling templates, faster view creation). At the same time it can be "destructive"
* in some cases (whitespaces can influence layout). Because of the potential of breaking layout
* this visitor is not activated by default in Angular 5 and people need to explicitly opt-in for
* whitespace removal. The default option for whitespace removal will be revisited in Angular 6
* and might be changed to "on" by default.
*/
class WhitespaceVisitor {
visitElement(element, context) {
if (SKIP_WS_TRIM_TAGS.has(element.name) || hasPreserveWhitespacesAttr(element.attrs)) {
// don't descent into elements where we need to preserve whitespaces
// but still visit all attributes to eliminate one used as a market to preserve WS
return new Element(element.name, visitAll(this, element.attrs), element.children, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
}
return new Element(element.name, element.attrs, visitAllWithSiblings(this, element.children), element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
}
visitAttribute(attribute, context) {
return attribute.name !== PRESERVE_WS_ATTR_NAME ? attribute : null;
}
visitText(text, context) {
const isNotBlank = text.value.match(NO_WS_REGEXP);
const hasExpansionSibling = context &&
(context.prev instanceof Expansion || context.next instanceof Expansion);
if (isNotBlank || hasExpansionSibling) {
// Process the whitespace in the tokens of this Text node
const tokens = text.tokens.map(token => token.type === 5 /* TokenType.TEXT */ ? createWhitespaceProcessedTextToken(token) : token);
// Process the whitespace of the value of this Text node
const value = processWhitespace(text.value);
return new Text(value, text.sourceSpan, tokens, text.i18n);
}
return null;
}
visitComment(comment, context) {
return comment;
}
visitExpansion(expansion, context) {
return expansion;
}
visitExpansionCase(expansionCase, context) {
return expansionCase;
}
visitBlockGroup(group, context) {
return new BlockGroup(visitAllWithSiblings(this, group.blocks), group.sourceSpan, group.startSourceSpan, group.endSourceSpan);
}
visitBlock(block, context) {
return new Block(block.name, block.parameters, visitAllWithSiblings(this, block.children), block.sourceSpan, block.startSourceSpan);
}
visitBlockParameter(parameter, context) {
return parameter;
}
}
function createWhitespaceProcessedTextToken({ type, parts, sourceSpan }) {
return { type, parts: [processWhitespace(parts[0])], sourceSpan };
}
function processWhitespace(text) {
return replaceNgsp(text).replace(WS_REPLACE_REGEXP, ' ');
}
function removeWhitespaces(htmlAstWithErrors) {
return new ParseTreeResult(visitAll(new WhitespaceVisitor(), htmlAstWithErrors.rootNodes), htmlAstWithErrors.errors);
}
function visitAllWithSiblings(visitor, nodes) {
const result = [];
nodes.forEach((ast, i) => {
const context = { prev: nodes[i - 1], next: nodes[i + 1] };
const astResult = ast.visit(visitor, context);
if (astResult) {
result.push(astResult);
}
});
return result;
}
function mapEntry(key, value) {
return { key, value, quoted: false };
}
function mapLiteral(obj, quoted = false) {
return literalMap(Object.keys(obj).map(key => ({
key,
quoted,
value: obj[key],
})));
}
/**
* Set of tagName|propertyName corresponding to Trusted Types sinks. Properties applying to all
* tags use '*'.
*
* Extracted from, and should be kept in sync with
* https://w3c.github.io/webappsec-trusted-types/dist/spec/#integrations
*/
const TRUSTED_TYPES_SINKS = new Set([
// NOTE: All strings in this set *must* be lowercase!
// TrustedHTML
'iframe|srcdoc',
'*|innerhtml',
'*|outerhtml',
// NB: no TrustedScript here, as the corresponding tags are stripped by the compiler.
// TrustedScriptURL
'embed|src',
'object|codebase',
'object|data',
]);
/**
* isTrustedTypesSink returns true if the given property on the given DOM tag is a Trusted Types
* sink. In that case, use `ElementSchemaRegistry.securityContext` to determine which particular
* Trusted Type is required for values passed to the sink:
* - SecurityContext.HTML corresponds to TrustedHTML
* - SecurityContext.RESOURCE_URL corresponds to TrustedScriptURL
*/
function isTrustedTypesSink(tagName, propName) {
// Make sure comparisons are case insensitive, so that case differences between attribute and
// property names do not have a security impact.
tagName = tagName.toLowerCase();
propName = propName.toLowerCase();
return TRUSTED_TYPES_SINKS.has(tagName + '|' + propName) ||
TRUSTED_TYPES_SINKS.has('*|' + propName);
}
const PROPERTY_PARTS_SEPARATOR = '.';
const ATTRIBUTE_PREFIX = 'attr';
const CLASS_PREFIX = 'class';
const STYLE_PREFIX = 'style';
const TEMPLATE_ATTR_PREFIX$1 = '*';
const ANIMATE_PROP_PREFIX = 'animate-';
/**
* Parses bindings in templates and in the directive host area.
*/
class BindingParser {
constructor(_exprParser, _interpolationConfig, _schemaRegistry, errors) {
this._exprParser = _exprParser;
this._interpolationConfig = _interpolationConfig;
this._schemaRegistry = _schemaRegistry;
this.errors = errors;
}
get interpolationConfig() {
return this._interpolationConfig;
}
createBoundHostProperties(properties, sourceSpan) {
const boundProps = [];
for (const propName of Object.keys(properties)) {
const expression = properties[propName];
if (typeof expression === 'string') {
this.parsePropertyBinding(propName, expression, true, sourceSpan, sourceSpan.start.offset, undefined, [],
// Use the `sourceSpan` for `keySpan`. This isn't really accurate, but neither is the
// sourceSpan, as it represents the sourceSpan of the host itself rather than the
// source of the host binding (which doesn't exist in the template). Regardless,
// neither of these values are used in Ivy but are only here to satisfy the function
// signature. This should likely be refactored in the future so that `sourceSpan`
// isn't being used inaccurately.
boundProps, sourceSpan);
}
else {
this._reportError(`Value of the host property binding "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`, sourceSpan);
}
}
return boundProps;
}
createDirectiveHostEventAsts(hostListeners, sourceSpan) {
const targetEvents = [];
for (const propName of Object.keys(hostListeners)) {
const expression = hostListeners[propName];
if (typeof expression === 'string') {
// Use the `sourceSpan` for `keySpan` and `handlerSpan`. This isn't really accurate, but
// neither is the `sourceSpan`, as it represents the `sourceSpan` of the host itself
// rather than the source of the host binding (which doesn't exist in the template).
// Regardless, neither of these values are used in Ivy but are only here to satisfy the
// function signature. This should likely be refactored in the future so that `sourceSpan`
// isn't being used inaccurately.
this.parseEvent(propName, expression, /* isAssignmentEvent */ false, sourceSpan, sourceSpan, [], targetEvents, sourceSpan);
}
else {
this._reportError(`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`, sourceSpan);
}
}
return targetEvents;
}
parseInterpolation(value, sourceSpan, interpolatedTokens) {
const sourceInfo = sourceSpan.start.toString();
const absoluteOffset = sourceSpan.fullStart.offset;
try {
const ast = this._exprParser.parseInterpolation(value, sourceInfo, absoluteOffset, interpolatedTokens, this._interpolationConfig);
if (ast)
this._reportExpressionParserErrors(ast.errors, sourceSpan);
return ast;
}
catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
}
}
/**
* Similar to `parseInterpolation`, but treats the provided string as a single expression
* element that would normally appear within the interpolation prefix and suffix (`{{` and `}}`).
* This is used for parsing the switch expression in ICUs.
*/
parseInterpolationExpression(expression, sourceSpan) {
const sourceInfo = sourceSpan.start.toString();
const absoluteOffset = sourceSpan.start.offset;
try {
const ast = this._exprParser.parseInterpolationExpression(expression, sourceInfo, absoluteOffset);
if (ast)
this._reportExpressionParserErrors(ast.errors, sourceSpan);
return ast;
}
catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
}
}
/**
* Parses the bindings in a microsyntax expression, and converts them to
* `ParsedProperty` or `ParsedVariable`.
*
* @param tplKey template binding name
* @param tplValue template binding value
* @param sourceSpan span of template binding relative to entire the template
* @param absoluteValueOffset start of the tplValue relative to the entire template
* @param targetMatchableAttrs potential attributes to match in the template
* @param targetProps target property bindings in the template
* @param targetVars target variables in the template
*/
parseInlineTemplateBinding(tplKey, tplValue, sourceSpan, absoluteValueOffset, targetMatchableAttrs, targetProps, targetVars, isIvyAst) {
const absoluteKeyOffset = sourceSpan.start.offset + TEMPLATE_ATTR_PREFIX$1.length;
const bindings = this._parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteKeyOffset, absoluteValueOffset);
for (const binding of bindings) {
// sourceSpan is for the entire HTML attribute. bindingSpan is for a particular
// binding within the microsyntax expression so it's more narrow than sourceSpan.
const bindingSpan = moveParseSourceSpan(sourceSpan, binding.sourceSpan);
const key = binding.key.source;
const keySpan = moveParseSourceSpan(sourceSpan, binding.key.span);
if (binding instanceof VariableBinding) {
const value = binding.value ? binding.value.source : '$implicit';
const valueSpan = binding.value ? moveParseSourceSpan(sourceSpan, binding.value.span) : undefined;
targetVars.push(new ParsedVariable(key, value, bindingSpan, keySpan, valueSpan));
}
else if (binding.value) {
const srcSpan = isIvyAst ? bindingSpan : sourceSpan;
const valueSpan = moveParseSourceSpan(sourceSpan, binding.value.ast.sourceSpan);
this._parsePropertyAst(key, binding.value, srcSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
}
else {
targetMatchableAttrs.push([key, '' /* value */]);
// Since this is a literal attribute with no RHS, source span should be
// just the key span.
this.parseLiteralAttr(key, null /* value */, keySpan, absoluteValueOffset, undefined /* valueSpan */, targetMatchableAttrs, targetProps, keySpan);
}
}
}
/**
* Parses the bindings in a microsyntax expression, e.g.
* ```
*
* ```
*
* @param tplKey template binding name
* @param tplValue template binding value
* @param sourceSpan span of template binding relative to entire the template
* @param absoluteKeyOffset start of the `tplKey`
* @param absoluteValueOffset start of the `tplValue`
*/
_parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteKeyOffset, absoluteValueOffset) {
const sourceInfo = sourceSpan.start.toString();
try {
const bindingsResult = this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo, absoluteKeyOffset, absoluteValueOffset);
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
bindingsResult.warnings.forEach((warning) => {
this._reportError(warning, sourceSpan, ParseErrorLevel.WARNING);
});
return bindingsResult.templateBindings;
}
catch (e) {
this._reportError(`${e}`, sourceSpan);
return [];
}
}
parseLiteralAttr(name, value, sourceSpan, absoluteOffset, valueSpan, targetMatchableAttrs, targetProps, keySpan) {
if (isAnimationLabel(name)) {
name = name.substring(1);
if (keySpan !== undefined) {
keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
}
if (value) {
this._reportError(`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`, sourceSpan, ParseErrorLevel.ERROR);
}
this._parseAnimation(name, value, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps);
}
else {
targetProps.push(new ParsedProperty(name, this._exprParser.wrapLiteralPrimitive(value, '', absoluteOffset), ParsedPropertyType.LITERAL_ATTR, sourceSpan, keySpan, valueSpan));
}
}
parsePropertyBinding(name, expression, isHost, sourceSpan, absoluteOffset, valueSpan, targetMatchableAttrs, targetProps, keySpan) {
if (name.length === 0) {
this._reportError(`Property name is missing in binding`, sourceSpan);
}
let isAnimationProp = false;
if (name.startsWith(ANIMATE_PROP_PREFIX)) {
isAnimationProp = true;
name = name.substring(ANIMATE_PROP_PREFIX.length);
if (keySpan !== undefined) {
keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + ANIMATE_PROP_PREFIX.length, keySpan.end.offset));
}
}
else if (isAnimationLabel(name)) {
isAnimationProp = true;
name = name.substring(1);
if (keySpan !== undefined) {
keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
}
}
if (isAnimationProp) {
this._parseAnimation(name, expression, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps);
}
else {
this._parsePropertyAst(name, this.parseBinding(expression, isHost, valueSpan || sourceSpan, absoluteOffset), sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
}
}
parsePropertyInterpolation(name, value, sourceSpan, valueSpan, targetMatchableAttrs, targetProps, keySpan, interpolatedTokens) {
const expr = this.parseInterpolation(value, valueSpan || sourceSpan, interpolatedTokens);
if (expr) {
this._parsePropertyAst(name, expr, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
return true;
}
return false;
}
_parsePropertyAst(name, ast, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps) {
targetMatchableAttrs.push([name, ast.source]);
targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.DEFAULT, sourceSpan, keySpan, valueSpan));
}
_parseAnimation(name, expression, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps) {
if (name.length === 0) {
this._reportError('Animation trigger is missing', sourceSpan);
}
// This will occur when a @trigger is not paired with an expression.
// For animations it is valid to not have an expression since */void
// states will be applied by angular when the element is attached/detached
const ast = this.parseBinding(expression || 'undefined', false, valueSpan || sourceSpan, absoluteOffset);
targetMatchableAttrs.push([name, ast.source]);
targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.ANIMATION, sourceSpan, keySpan, valueSpan));
}
parseBinding(value, isHostBinding, sourceSpan, absoluteOffset) {
const sourceInfo = (sourceSpan && sourceSpan.start || '(unknown)').toString();
try {
const ast = isHostBinding ?
this._exprParser.parseSimpleBinding(value, sourceInfo, absoluteOffset, this._interpolationConfig) :
this._exprParser.parseBinding(value, sourceInfo, absoluteOffset, this._interpolationConfig);
if (ast)
this._reportExpressionParserErrors(ast.errors, sourceSpan);
return ast;
}
catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
}
}
createBoundElementProperty(elementSelector, boundProp, skipValidation = false, mapPropertyName = true) {
if (boundProp.isAnimation) {
return new BoundElementProperty(boundProp.name, 4 /* BindingType.Animation */, SecurityContext.NONE, boundProp.expression, null, boundProp.sourceSpan, boundProp.keySpan, boundProp.valueSpan);
}
let unit = null;
let bindingType = undefined;
let boundPropertyName = null;
const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
let securityContexts = undefined;
// Check for special cases (prefix style, attr, class)
if (parts.length > 1) {
if (parts[0] == ATTRIBUTE_PREFIX) {
boundPropertyName = parts.slice(1).join(PROPERTY_PARTS_SEPARATOR);
if (!skipValidation) {
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, true);
}
securityContexts = calcPossibleSecurityContexts(this._schemaRegistry, elementSelector, boundPropertyName, true);
const nsSeparatorIdx = boundPropertyName.indexOf(':');
if (nsSeparatorIdx > -1) {
const ns = boundPropertyName.substring(0, nsSeparatorIdx);
const name = boundPropertyName.substring(nsSeparatorIdx + 1);
boundPropertyName = mergeNsAndName(ns, name);
}
bindingType = 1 /* BindingType.Attribute */;
}
else if (parts[0] == CLASS_PREFIX) {
boundPropertyName = parts[1];
bindingType = 2 /* BindingType.Class */;
securityContexts = [SecurityContext.NONE];
}
else if (parts[0] == STYLE_PREFIX) {
unit = parts.length > 2 ? parts[2] : null;
boundPropertyName = parts[1];
bindingType = 3 /* BindingType.Style */;
securityContexts = [SecurityContext.STYLE];
}
}
// If not a special case, use the full property name
if (boundPropertyName === null) {
const mappedPropName = this._schemaRegistry.getMappedPropName(boundProp.name);
boundPropertyName = mapPropertyName ? mappedPropName : boundProp.name;
securityContexts = calcPossibleSecurityContexts(this._schemaRegistry, elementSelector, mappedPropName, false);
bindingType = 0 /* BindingType.Property */;
if (!skipValidation) {
this._validatePropertyOrAttributeName(mappedPropName, boundProp.sourceSpan, false);
}
}
return new BoundElementProperty(boundPropertyName, bindingType, securityContexts[0], boundProp.expression, unit, boundProp.sourceSpan, boundProp.keySpan, boundProp.valueSpan);
}
// TODO: keySpan should be required but was made optional to avoid changing VE parser.
parseEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan) {
if (name.length === 0) {
this._reportError(`Event name is missing in binding`, sourceSpan);
}
if (isAnimationLabel(name)) {
name = name.slice(1);
if (keySpan !== undefined) {
keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
}
this._parseAnimationEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetEvents, keySpan);
}
else {
this._parseRegularEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan);
}
}
calcPossibleSecurityContexts(selector, propName, isAttribute) {
const prop = this._schemaRegistry.getMappedPropName(propName);
return calcPossibleSecurityContexts(this._schemaRegistry, selector, prop, isAttribute);
}
_parseAnimationEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetEvents, keySpan) {
const matches = splitAtPeriod(name, [name, '']);
const eventName = matches[0];
const phase = matches[1].toLowerCase();
const ast = this._parseAction(expression, isAssignmentEvent, handlerSpan);
targetEvents.push(new ParsedEvent(eventName, phase, 1 /* ParsedEventType.Animation */, ast, sourceSpan, handlerSpan, keySpan));
if (eventName.length === 0) {
this._reportError(`Animation event name is missing in binding`, sourceSpan);
}
if (phase) {
if (phase !== 'start' && phase !== 'done') {
this._reportError(`The provided animation output phase value "${phase}" for "@${eventName}" is not supported (use start or done)`, sourceSpan);
}
}
else {
this._reportError(`The animation trigger output event (@${eventName}) is missing its phase value name (start or done are currently supported)`, sourceSpan);
}
}
_parseRegularEvent(name, expression, isAssignmentEvent, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan) {
// long format: 'target: eventName'
const [target, eventName] = splitAtColon(name, [null, name]);
const ast = this._parseAction(expression, isAssignmentEvent, handlerSpan);
targetMatchableAttrs.push([name, ast.source]);
targetEvents.push(new ParsedEvent(eventName, target, 0 /* ParsedEventType.Regular */, ast, sourceSpan, handlerSpan, keySpan));
// Don't detect directives for event names for now,
// so don't add the event name to the matchableAttrs
}
_parseAction(value, isAssignmentEvent, sourceSpan) {
const sourceInfo = (sourceSpan && sourceSpan.start || '(unknown').toString();
const absoluteOffset = (sourceSpan && sourceSpan.start) ? sourceSpan.start.offset : 0;
try {
const ast = this._exprParser.parseAction(value, isAssignmentEvent, sourceInfo, absoluteOffset, this._interpolationConfig);
if (ast) {
this._reportExpressionParserErrors(ast.errors, sourceSpan);
}
if (!ast || ast.ast instanceof EmptyExpr$1) {
this._reportError(`Empty expressions are not allowed`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
}
return ast;
}
catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
}
}
_reportError(message, sourceSpan, level = ParseErrorLevel.ERROR) {
this.errors.push(new ParseError(sourceSpan, message, level));
}
_reportExpressionParserErrors(errors, sourceSpan) {
for (const error of errors) {
this._reportError(error.message, sourceSpan);
}
}
/**
* @param propName the name of the property / attribute
* @param sourceSpan
* @param isAttr true when binding to an attribute
*/
_validatePropertyOrAttributeName(propName, sourceSpan, isAttr) {
const report = isAttr ? this._schemaRegistry.validateAttribute(propName) :
this._schemaRegistry.validateProperty(propName);
if (report.error) {
this._reportError(report.msg, sourceSpan, ParseErrorLevel.ERROR);
}
}
}
class PipeCollector extends RecursiveAstVisitor {
constructor() {
super(...arguments);
this.pipes = new Map();
}
visitPipe(ast, context) {
this.pipes.set(ast.name, ast);
ast.exp.visit(this);
this.visitAll(ast.args, context);
return null;
}
}
function isAnimationLabel(name) {
return name[0] == '@';
}
function calcPossibleSecurityContexts(registry, selector, propName, isAttribute) {
const ctxs = [];
CssSelector.parse(selector).forEach((selector) => {
const elementNames = selector.element ? [selector.element] : registry.allKnownElementNames();
const notElementNames = new Set(selector.notSelectors.filter(selector => selector.isElementSelector())
.map((selector) => selector.element));
const possibleElementNames = elementNames.filter(elementName => !notElementNames.has(elementName));
ctxs.push(...possibleElementNames.map(elementName => registry.securityContext(elementName, propName, isAttribute)));
});
return ctxs.length === 0 ? [SecurityContext.NONE] : Array.from(new Set(ctxs)).sort();
}
/**
* Compute a new ParseSourceSpan based off an original `sourceSpan` by using
* absolute offsets from the specified `absoluteSpan`.
*
* @param sourceSpan original source span
* @param absoluteSpan absolute source span to move to
*/
function moveParseSourceSpan(sourceSpan, absoluteSpan) {
// The difference of two absolute offsets provide the relative offset
const startDiff = absoluteSpan.start - sourceSpan.start.offset;
const endDiff = absoluteSpan.end - sourceSpan.end.offset;
return new ParseSourceSpan(sourceSpan.start.moveBy(startDiff), sourceSpan.end.moveBy(endDiff), sourceSpan.fullStart.moveBy(startDiff), sourceSpan.details);
}
// Some of the code comes from WebComponents.JS
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
function isStyleUrlResolvable(url) {
if (url == null || url.length === 0 || url[0] == '/')
return false;
const schemeMatch = url.match(URL_WITH_SCHEMA_REGEXP);
return schemeMatch === null || schemeMatch[1] == 'package' || schemeMatch[1] == 'asset';
}
const URL_WITH_SCHEMA_REGEXP = /^([^:/?#]+):/;
const NG_CONTENT_SELECT_ATTR$1 = 'select';
const LINK_ELEMENT = 'link';
const LINK_STYLE_REL_ATTR = 'rel';
const LINK_STYLE_HREF_ATTR = 'href';
const LINK_STYLE_REL_VALUE = 'stylesheet';
const STYLE_ELEMENT = 'style';
const SCRIPT_ELEMENT = 'script';
const NG_NON_BINDABLE_ATTR = 'ngNonBindable';
const NG_PROJECT_AS = 'ngProjectAs';
function preparseElement(ast) {
let selectAttr = null;
let hrefAttr = null;
let relAttr = null;
let nonBindable = false;
let projectAs = '';
ast.attrs.forEach(attr => {
const lcAttrName = attr.name.toLowerCase();
if (lcAttrName == NG_CONTENT_SELECT_ATTR$1) {
selectAttr = attr.value;
}
else if (lcAttrName == LINK_STYLE_HREF_ATTR) {
hrefAttr = attr.value;
}
else if (lcAttrName == LINK_STYLE_REL_ATTR) {
relAttr = attr.value;
}
else if (attr.name == NG_NON_BINDABLE_ATTR) {
nonBindable = true;
}
else if (attr.name == NG_PROJECT_AS) {
if (attr.value.length > 0) {
projectAs = attr.value;
}
}
});
selectAttr = normalizeNgContentSelect(selectAttr);
const nodeName = ast.name.toLowerCase();
let type = PreparsedElementType.OTHER;
if (isNgContent(nodeName)) {
type = PreparsedElementType.NG_CONTENT;
}
else if (nodeName == STYLE_ELEMENT) {
type = PreparsedElementType.STYLE;
}
else if (nodeName == SCRIPT_ELEMENT) {
type = PreparsedElementType.SCRIPT;
}
else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) {
type = PreparsedElementType.STYLESHEET;
}
return new PreparsedElement(type, selectAttr, hrefAttr, nonBindable, projectAs);
}
var PreparsedElementType;
(function (PreparsedElementType) {
PreparsedElementType[PreparsedElementType["NG_CONTENT"] = 0] = "NG_CONTENT";
PreparsedElementType[PreparsedElementType["STYLE"] = 1] = "STYLE";
PreparsedElementType[PreparsedElementType["STYLESHEET"] = 2] = "STYLESHEET";
PreparsedElementType[PreparsedElementType["SCRIPT"] = 3] = "SCRIPT";
PreparsedElementType[PreparsedElementType["OTHER"] = 4] = "OTHER";
})(PreparsedElementType || (PreparsedElementType = {}));
class PreparsedElement {
constructor(type, selectAttr, hrefAttr, nonBindable, projectAs) {
this.type = type;
this.selectAttr = selectAttr;
this.hrefAttr = hrefAttr;
this.nonBindable = nonBindable;
this.projectAs = projectAs;
}
}
function normalizeNgContentSelect(selectAttr) {
if (selectAttr === null || selectAttr.length === 0) {
return '*';
}
return selectAttr;
}
/** Pattern for a timing value in a trigger. */
const TIME_PATTERN = /^\d+(ms|s)?$/;
/** Pattern for a separator between keywords in a trigger expression. */
const SEPARATOR_PATTERN = /^\s$/;
/** Pairs of characters that form syntax that is comma-delimited. */
const COMMA_DELIMITED_SYNTAX = new Map([
[$LBRACE, $RBRACE],
[$LBRACKET, $RBRACKET],
[$LPAREN, $RPAREN], // Function calls
]);
/** Possible types of `on` triggers. */
var OnTriggerType;
(function (OnTriggerType) {
OnTriggerType["IDLE"] = "idle";
OnTriggerType["TIMER"] = "timer";
OnTriggerType["INTERACTION"] = "interaction";
OnTriggerType["IMMEDIATE"] = "immediate";
OnTriggerType["HOVER"] = "hover";
OnTriggerType["VIEWPORT"] = "viewport";
})(OnTriggerType || (OnTriggerType = {}));
/** Parses a `when` deferred trigger. */
function parseWhenTrigger({ expression, sourceSpan }, bindingParser, errors) {
const whenIndex = expression.indexOf('when');
// This is here just to be safe, we shouldn't enter this function
// in the first place if a block doesn't have the "when" keyword.
if (whenIndex === -1) {
errors.push(new ParseError(sourceSpan, `Could not find "when" keyword in expression`));
return null;
}
const start = getTriggerParametersStart(expression, whenIndex + 1);
const parsed = bindingParser.parseBinding(expression.slice(start), false, sourceSpan, sourceSpan.start.offset + start);
return new BoundDeferredTrigger(parsed, sourceSpan);
}
/** Parses an `on` trigger */
function parseOnTrigger({ expression, sourceSpan }, errors) {
const onIndex = expression.indexOf('on');
// This is here just to be safe, we shouldn't enter this function
// in the first place if a block doesn't have the "on" keyword.
if (onIndex === -1) {
errors.push(new ParseError(sourceSpan, `Could not find "on" keyword in expression`));
return [];
}
const start = getTriggerParametersStart(expression, onIndex + 1);
return new OnTriggerParser(expression, start, sourceSpan, errors).parse();
}
class OnTriggerParser {
constructor(expression, start, span, errors) {
this.expression = expression;
this.start = start;
this.span = span;
this.errors = errors;
this.index = 0;
this.triggers = [];
this.tokens = new Lexer().tokenize(expression.slice(start));
}
parse() {
while (this.tokens.length > 0 && this.index < this.tokens.length) {
const token = this.token();
if (!token.isIdentifier()) {
this.unexpectedToken(token);
break;
}
// An identifier immediately followed by a comma or the end of
// the expression cannot have parameters so we can exit early.
if (this.isFollowedByOrLast($COMMA)) {
this.consumeTrigger(token, []);
this.advance();
}
else if (this.isFollowedByOrLast($LPAREN)) {
this.advance(); // Advance to the opening paren.
const prevErrors = this.errors.length;
const parameters = this.consumeParameters();
if (this.errors.length !== prevErrors) {
break;
}
this.consumeTrigger(token, parameters);
this.advance(); // Advance past the closing paren.
}
else if (this.index < this.tokens.length - 1) {
this.unexpectedToken(this.tokens[this.index + 1]);
}
this.advance();
}
return this.triggers;
}
advance() {
this.index++;
}
isFollowedByOrLast(char) {
if (this.index === this.tokens.length - 1) {
return true;
}
return this.tokens[this.index + 1].isCharacter(char);
}
token() {
return this.tokens[Math.min(this.index, this.tokens.length - 1)];
}
consumeTrigger(identifier, parameters) {
const startSpan = this.span.start.moveBy(this.start + identifier.index - this.tokens[0].index);
const endSpan = startSpan.moveBy(this.token().end - identifier.index);
const sourceSpan = new ParseSourceSpan(startSpan, endSpan);
try {
switch (identifier.toString()) {
case OnTriggerType.IDLE:
this.triggers.push(createIdleTrigger(parameters, sourceSpan));
break;
case OnTriggerType.TIMER:
this.triggers.push(createTimerTrigger(parameters, sourceSpan));
break;
case OnTriggerType.INTERACTION:
this.triggers.push(createInteractionTrigger(parameters, sourceSpan));
break;
case OnTriggerType.IMMEDIATE:
this.triggers.push(createImmediateTrigger(parameters, sourceSpan));
break;
case OnTriggerType.HOVER:
this.triggers.push(createHoverTrigger(parameters, sourceSpan));
break;
case OnTriggerType.VIEWPORT:
this.triggers.push(createViewportTrigger(parameters, sourceSpan));
break;
default:
throw new Error(`Unrecognized trigger type "${identifier}"`);
}
}
catch (e) {
this.error(identifier, e.message);
}
}
consumeParameters() {
const parameters = [];
if (!this.token().isCharacter($LPAREN)) {
this.unexpectedToken(this.token());
return parameters;
}
this.advance();
const commaDelimStack = [];
let current = '';
while (this.index < this.tokens.length) {
const token = this.token();
// Stop parsing if we've hit the end character and we're outside of a comma-delimited syntax.
// Note that we don't need to account for strings here since the lexer already parsed them
// into string tokens.
if (token.isCharacter($RPAREN) && commaDelimStack.length === 0) {
if (current.length) {
parameters.push(current);
}
break;
}
// In the `on` microsyntax "top-level" commas (e.g. ones outside of an parameters) separate
// the different triggers (e.g. `on idle,timer(500)`). This is problematic, because the
// function-like syntax also implies that multiple parameters can be passed into the
// individual trigger (e.g. `on foo(a, b)`). To avoid tripping up the parser with commas that
// are part of other sorts of syntax (object literals, arrays), we treat anything inside
// a comma-delimited syntax block as plain text.
if (token.type === TokenType.Character && COMMA_DELIMITED_SYNTAX.has(token.numValue)) {
commaDelimStack.push(COMMA_DELIMITED_SYNTAX.get(token.numValue));
}
if (commaDelimStack.length > 0 &&
token.isCharacter(commaDelimStack[commaDelimStack.length - 1])) {
commaDelimStack.pop();
}
// If we hit a comma outside of a comma-delimited syntax, it means
// that we're at the top level and we're starting a new parameter.
if (commaDelimStack.length === 0 && token.isCharacter($COMMA) && current.length > 0) {
parameters.push(current);
current = '';
this.advance();
continue;
}
// Otherwise treat the token as a plain text character in the current parameter.
current += this.tokenText();
this.advance();
}
if (!this.token().isCharacter($RPAREN) || commaDelimStack.length > 0) {
this.error(this.token(), 'Unexpected end of expression');
}
if (this.index < this.tokens.length - 1 &&
!this.tokens[this.index + 1].isCharacter($COMMA)) {
this.unexpectedToken(this.tokens[this.index + 1]);
}
return parameters;
}
tokenText() {
// Tokens have a toString already which we could use, but for string tokens it omits the quotes.
// Eventually we could expose this information on the token directly.
return this.expression.slice(this.start + this.token().index, this.start + this.token().end);
}
error(token, message) {
const newStart = this.span.start.moveBy(this.start + token.index);
const newEnd = newStart.moveBy(token.end - token.index);
this.errors.push(new ParseError(new ParseSourceSpan(newStart, newEnd), message));
}
unexpectedToken(token) {
this.error(token, `Unexpected token "${token}"`);
}
}
function createIdleTrigger(parameters, sourceSpan) {
if (parameters.length > 0) {
throw new Error(`"${OnTriggerType.IDLE}" trigger cannot have parameters`);
}
return new IdleDeferredTrigger(sourceSpan);
}
function createTimerTrigger(parameters, sourceSpan) {
if (parameters.length !== 1) {
throw new Error(`"${OnTriggerType.TIMER}" trigger must have exactly one parameter`);
}
const delay = parseDeferredTime(parameters[0]);
if (delay === null) {
throw new Error(`Could not parse time value of trigger "${OnTriggerType.TIMER}"`);
}
return new TimerDeferredTrigger(delay, sourceSpan);
}
function createInteractionTrigger(parameters, sourceSpan) {
if (parameters.length > 1) {
throw new Error(`"${OnTriggerType.INTERACTION}" trigger can only have zero or one parameters`);
}
return new InteractionDeferredTrigger(parameters[0] ?? null, sourceSpan);
}
function createImmediateTrigger(parameters, sourceSpan) {
if (parameters.length > 0) {
throw new Error(`"${OnTriggerType.IMMEDIATE}" trigger cannot have parameters`);
}
return new ImmediateDeferredTrigger(sourceSpan);
}
function createHoverTrigger(parameters, sourceSpan) {
if (parameters.length > 0) {
throw new Error(`"${OnTriggerType.HOVER}" trigger cannot have parameters`);
}
return new HoverDeferredTrigger(sourceSpan);
}
function createViewportTrigger(parameters, sourceSpan) {
// TODO: the RFC has some more potential parameters for `viewport`.
if (parameters.length > 1) {
throw new Error(`"${OnTriggerType.VIEWPORT}" trigger can only have zero or one parameters`);
}
return new ViewportDeferredTrigger(parameters[0] ?? null, sourceSpan);
}
/** Gets the index within an expression at which the trigger parameters start. */
function getTriggerParametersStart(value, startPosition = 0) {
let hasFoundSeparator = false;
for (let i = startPosition; i < value.length; i++) {
if (SEPARATOR_PATTERN.test(value[i])) {
hasFoundSeparator = true;
}
else if (hasFoundSeparator) {
return i;
}
}
return -1;
}
/**
* Parses a time expression from a deferred trigger to
* milliseconds. Returns null if it cannot be parsed.
*/
function parseDeferredTime(value) {
const match = value.match(TIME_PATTERN);
if (!match) {
return null;
}
const [time, units] = match;
return parseInt(time) * (units === 's' ? 1000 : 1);
}
/** Pattern to identify a `prefetch when` trigger. */
const PREFETCH_WHEN_PATTERN = /^prefetch\s+when\s/;
/** Pattern to identify a `prefetch on` trigger. */
const PREFETCH_ON_PATTERN = /^prefetch\s+on\s/;
/** Pattern to identify a `minimum` parameter in a block. */
const MINIMUM_PARAMETER_PATTERN = /^minimum\s/;
/** Pattern to identify a `after` parameter in a block. */
const AFTER_PARAMETER_PATTERN = /^after\s/;
/** Pattern to identify a `when` parameter in a block. */
const WHEN_PARAMETER_PATTERN = /^when\s/;
/** Pattern to identify a `on` parameter in a block. */
const ON_PARAMETER_PATTERN = /^on\s/;
/** Possible types of secondary deferred blocks. */
var SecondaryDeferredBlockType;
(function (SecondaryDeferredBlockType) {
SecondaryDeferredBlockType["PLACEHOLDER"] = "placeholder";
SecondaryDeferredBlockType["LOADING"] = "loading";
SecondaryDeferredBlockType["ERROR"] = "error";
})(SecondaryDeferredBlockType || (SecondaryDeferredBlockType = {}));
/** Creates a deferred block from an HTML AST node. */
function createDeferredBlock(ast, visitor, bindingParser) {
const errors = [];
const [primaryBlock, ...secondaryBlocks] = ast.blocks;
const { triggers, prefetchTriggers } = parsePrimaryTriggers(primaryBlock.parameters, bindingParser, errors);
const { placeholder, loading, error } = parseSecondaryBlocks(secondaryBlocks, errors, visitor);
return {
node: new DeferredBlock(visitAll(visitor, primaryBlock.children), triggers, prefetchTriggers, placeholder, loading, error, ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan),
errors,
};
}
function parseSecondaryBlocks(blocks, errors, visitor) {
let placeholder = null;
let loading = null;
let error = null;
for (const block of blocks) {
try {
switch (block.name) {
case SecondaryDeferredBlockType.PLACEHOLDER:
if (placeholder !== null) {
errors.push(new ParseError(block.startSourceSpan, `"defer" block can only have one "${SecondaryDeferredBlockType.PLACEHOLDER}" block`));
}
else {
placeholder = parsePlaceholderBlock(block, visitor);
}
break;
case SecondaryDeferredBlockType.LOADING:
if (loading !== null) {
errors.push(new ParseError(block.startSourceSpan, `"defer" block can only have one "${SecondaryDeferredBlockType.LOADING}" block`));
}
else {
loading = parseLoadingBlock(block, visitor);
}
break;
case SecondaryDeferredBlockType.ERROR:
if (error !== null) {
errors.push(new ParseError(block.startSourceSpan, `"defer" block can only have one "${SecondaryDeferredBlockType.ERROR}" block`));
}
else {
error = parseErrorBlock(block, visitor);
}
break;
default:
errors.push(new ParseError(block.startSourceSpan, `Unrecognized block "${block.name}"`));
break;
}
}
catch (e) {
errors.push(new ParseError(block.startSourceSpan, e.message));
}
}
return { placeholder, loading, error };
}
function parsePlaceholderBlock(ast, visitor) {
let minimumTime = null;
for (const param of ast.parameters) {
if (MINIMUM_PARAMETER_PATTERN.test(param.expression)) {
const parsedTime = parseDeferredTime(param.expression.slice(getTriggerParametersStart(param.expression)));
if (parsedTime === null) {
throw new Error(`Could not parse time value of parameter "minimum"`);
}
minimumTime = parsedTime;
}
else {
throw new Error(`Unrecognized parameter in "${SecondaryDeferredBlockType.PLACEHOLDER}" block: "${param.expression}"`);
}
}
return new DeferredBlockPlaceholder(visitAll(visitor, ast.children), minimumTime, ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan);
}
function parseLoadingBlock(ast, visitor) {
let afterTime = null;
let minimumTime = null;
for (const param of ast.parameters) {
if (AFTER_PARAMETER_PATTERN.test(param.expression)) {
const parsedTime = parseDeferredTime(param.expression.slice(getTriggerParametersStart(param.expression)));
if (parsedTime === null) {
throw new Error(`Could not parse time value of parameter "after"`);
}
afterTime = parsedTime;
}
else if (MINIMUM_PARAMETER_PATTERN.test(param.expression)) {
const parsedTime = parseDeferredTime(param.expression.slice(getTriggerParametersStart(param.expression)));
if (parsedTime === null) {
throw new Error(`Could not parse time value of parameter "minimum"`);
}
minimumTime = parsedTime;
}
else {
throw new Error(`Unrecognized parameter in "${SecondaryDeferredBlockType.LOADING}" block: "${param.expression}"`);
}
}
return new DeferredBlockLoading(visitAll(visitor, ast.children), afterTime, minimumTime, ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan);
}
function parseErrorBlock(ast, visitor) {
if (ast.parameters.length > 0) {
throw new Error(`"${SecondaryDeferredBlockType.ERROR}" block cannot have parameters`);
}
return new DeferredBlockError(visitAll(visitor, ast.children), ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan);
}
function parsePrimaryTriggers(params, bindingParser, errors) {
const triggers = [];
const prefetchTriggers = [];
for (const param of params) {
// The lexer ignores the leading spaces so we can assume
// that the expression starts with a keyword.
if (WHEN_PARAMETER_PATTERN.test(param.expression)) {
const result = parseWhenTrigger(param, bindingParser, errors);
result !== null && triggers.push(result);
}
else if (ON_PARAMETER_PATTERN.test(param.expression)) {
triggers.push(...parseOnTrigger(param, errors));
}
else if (PREFETCH_WHEN_PATTERN.test(param.expression)) {
const result = parseWhenTrigger(param, bindingParser, errors);
result !== null && prefetchTriggers.push(result);
}
else if (PREFETCH_ON_PATTERN.test(param.expression)) {
prefetchTriggers.push(...parseOnTrigger(param, errors));
}
else {
errors.push(new ParseError(param.sourceSpan, 'Unrecognized trigger'));
}
}
return { triggers, prefetchTriggers };
}
const BIND_NAME_REGEXP = /^(?:(bind-)|(let-)|(ref-|#)|(on-)|(bindon-)|(@))(.*)$/;
// Group 1 = "bind-"
const KW_BIND_IDX = 1;
// Group 2 = "let-"
const KW_LET_IDX = 2;
// Group 3 = "ref-/#"
const KW_REF_IDX = 3;
// Group 4 = "on-"
const KW_ON_IDX = 4;
// Group 5 = "bindon-"
const KW_BINDON_IDX = 5;
// Group 6 = "@"
const KW_AT_IDX = 6;
// Group 7 = the identifier after "bind-", "let-", "ref-/#", "on-", "bindon-" or "@"
const IDENT_KW_IDX = 7;
const BINDING_DELIMS = {
BANANA_BOX: { start: '[(', end: ')]' },
PROPERTY: { start: '[', end: ']' },
EVENT: { start: '(', end: ')' },
};
const TEMPLATE_ATTR_PREFIX = '*';
function htmlAstToRender3Ast(htmlNodes, bindingParser, options) {
const transformer = new HtmlAstToIvyAst(bindingParser, options);
const ivyNodes = visitAll(transformer, htmlNodes);
// Errors might originate in either the binding parser or the html to ivy transformer
const allErrors = bindingParser.errors.concat(transformer.errors);
const result = {
nodes: ivyNodes,
errors: allErrors,
styleUrls: transformer.styleUrls,
styles: transformer.styles,
ngContentSelectors: transformer.ngContentSelectors
};
if (options.collectCommentNodes) {
result.commentNodes = transformer.commentNodes;
}
return result;
}
class HtmlAstToIvyAst {
constructor(bindingParser, options) {
this.bindingParser = bindingParser;
this.options = options;
this.errors = [];
this.styles = [];
this.styleUrls = [];
this.ngContentSelectors = [];
// This array will be populated if `Render3ParseOptions['collectCommentNodes']` is true
this.commentNodes = [];
this.inI18nBlock = false;
}
// HTML visitor
visitElement(element) {
const isI18nRootElement = isI18nRootNode(element.i18n);
if (isI18nRootElement) {
if (this.inI18nBlock) {
this.reportError('Cannot mark an element as translatable inside of a translatable section. Please remove the nested i18n marker.', element.sourceSpan);
}
this.inI18nBlock = true;
}
const preparsedElement = preparseElement(element);
if (preparsedElement.type === PreparsedElementType.SCRIPT) {
return null;
}
else if (preparsedElement.type === PreparsedElementType.STYLE) {
const contents = textContents(element);
if (contents !== null) {
this.styles.push(contents);
}
return null;
}
else if (preparsedElement.type === PreparsedElementType.STYLESHEET &&
isStyleUrlResolvable(preparsedElement.hrefAttr)) {
this.styleUrls.push(preparsedElement.hrefAttr);
return null;
}
// Whether the element is a ``
const isTemplateElement = isNgTemplate(element.name);
const parsedProperties = [];
const boundEvents = [];
const variables = [];
const references = [];
const attributes = [];
const i18nAttrsMeta = {};
const templateParsedProperties = [];
const templateVariables = [];
// Whether the element has any *-attribute
let elementHasInlineTemplate = false;
for (const attribute of element.attrs) {
let hasBinding = false;
const normalizedName = normalizeAttributeName(attribute.name);
// `*attr` defines template bindings
let isTemplateBinding = false;
if (attribute.i18n) {
i18nAttrsMeta[attribute.name] = attribute.i18n;
}
if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
// *-attributes
if (elementHasInlineTemplate) {
this.reportError(`Can't have multiple template bindings on one element. Use only one attribute prefixed with *`, attribute.sourceSpan);
}
isTemplateBinding = true;
elementHasInlineTemplate = true;
const templateValue = attribute.value;
const templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
const parsedVariables = [];
const absoluteValueOffset = attribute.valueSpan ?
attribute.valueSpan.start.offset :
// If there is no value span the attribute does not have a value, like `attr` in
//``. In this case, point to one character beyond the last character of
// the attribute name.
attribute.sourceSpan.start.offset + attribute.name.length;
this.bindingParser.parseInlineTemplateBinding(templateKey, templateValue, attribute.sourceSpan, absoluteValueOffset, [], templateParsedProperties, parsedVariables, true /* isIvyAst */);
templateVariables.push(...parsedVariables.map(v => new Variable(v.name, v.value, v.sourceSpan, v.keySpan, v.valueSpan)));
}
else {
// Check for variables, events, property bindings, interpolation
hasBinding = this.parseAttribute(isTemplateElement, attribute, [], parsedProperties, boundEvents, variables, references);
}
if (!hasBinding && !isTemplateBinding) {
// don't include the bindings as attributes as well in the AST
attributes.push(this.visitAttribute(attribute));
}
}
let children;
if (preparsedElement.nonBindable) {
// The `NonBindableVisitor` may need to return an array of nodes for block groups so we need
// to flatten the array here. Avoid doing this for the `HtmlAstToIvyAst` since `flat` creates
// a new array.
children = visitAll(NON_BINDABLE_VISITOR, element.children).flat(Infinity);
}
else {
children = visitAll(this, element.children);
}
let parsedElement;
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
// ``
if (element.children &&
!element.children.every((node) => isEmptyTextNode(node) || isCommentNode(node))) {
this.reportError(` element cannot have content.`, element.sourceSpan);
}
const selector = preparsedElement.selectAttr;
const attrs = element.attrs.map(attr => this.visitAttribute(attr));
parsedElement = new Content(selector, attrs, element.sourceSpan, element.i18n);
this.ngContentSelectors.push(selector);
}
else if (isTemplateElement) {
// ``
const attrs = this.extractAttributes(element.name, parsedProperties, i18nAttrsMeta);
parsedElement = new Template(element.name, attributes, attrs.bound, boundEvents, [ /* no template attributes */], children, references, variables, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
}
else {
const attrs = this.extractAttributes(element.name, parsedProperties, i18nAttrsMeta);
parsedElement = new Element$1(element.name, attributes, attrs.bound, boundEvents, children, references, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
}
if (elementHasInlineTemplate) {
// If this node is an inline-template (e.g. has *ngFor) then we need to create a template
// node that contains this node.
// Moreover, if the node is an element, then we need to hoist its attributes to the template
// node for matching against content projection selectors.
const attrs = this.extractAttributes('ng-template', templateParsedProperties, i18nAttrsMeta);
const templateAttrs = [];
attrs.literal.forEach(attr => templateAttrs.push(attr));
attrs.bound.forEach(attr => templateAttrs.push(attr));
const hoistedAttrs = parsedElement instanceof Element$1 ?
{
attributes: parsedElement.attributes,
inputs: parsedElement.inputs,
outputs: parsedElement.outputs,
} :
{ attributes: [], inputs: [], outputs: [] };
// For s with structural directives on them, avoid passing i18n information to
// the wrapping template to prevent unnecessary i18n instructions from being generated. The
// necessary i18n meta information will be extracted from child elements.
const i18n = isTemplateElement && isI18nRootElement ? undefined : element.i18n;
const name = parsedElement instanceof Template ? null : parsedElement.name;
parsedElement = new Template(name, hoistedAttrs.attributes, hoistedAttrs.inputs, hoistedAttrs.outputs, templateAttrs, [parsedElement], [ /* no references */], templateVariables, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, i18n);
}
if (isI18nRootElement) {
this.inI18nBlock = false;
}
return parsedElement;
}
visitAttribute(attribute) {
return new TextAttribute(attribute.name, attribute.value, attribute.sourceSpan, attribute.keySpan, attribute.valueSpan, attribute.i18n);
}
visitText(text) {
return this._visitTextWithInterpolation(text.value, text.sourceSpan, text.tokens, text.i18n);
}
visitExpansion(expansion) {
if (!expansion.i18n) {
// do not generate Icu in case it was created
// outside of i18n block in a template
return null;
}
if (!isI18nRootNode(expansion.i18n)) {
throw new Error(`Invalid type "${expansion.i18n.constructor}" for "i18n" property of ${expansion.sourceSpan.toString()}. Expected a "Message"`);
}
const message = expansion.i18n;
const vars = {};
const placeholders = {};
// extract VARs from ICUs - we process them separately while
// assembling resulting message via goog.getMsg function, since
// we need to pass them to top-level goog.getMsg call
Object.keys(message.placeholders).forEach(key => {
const value = message.placeholders[key];
if (key.startsWith(I18N_ICU_VAR_PREFIX)) {
// Currently when the `plural` or `select` keywords in an ICU contain trailing spaces (e.g.
// `{count, select , ...}`), these spaces are also included into the key names in ICU vars
// (e.g. "VAR_SELECT "). These trailing spaces are not desirable, since they will later be
// converted into `_` symbols while normalizing placeholder names, which might lead to
// mismatches at runtime (i.e. placeholder will not be replaced with the correct value).
const formattedKey = key.trim();
const ast = this.bindingParser.parseInterpolationExpression(value.text, value.sourceSpan);
vars[formattedKey] = new BoundText(ast, value.sourceSpan);
}
else {
placeholders[key] = this._visitTextWithInterpolation(value.text, value.sourceSpan, null);
}
});
return new Icu$1(vars, placeholders, expansion.sourceSpan, message);
}
visitExpansionCase(expansionCase) {
return null;
}
visitComment(comment) {
if (this.options.collectCommentNodes) {
this.commentNodes.push(new Comment$1(comment.value || '', comment.sourceSpan));
}
return null;
}
visitBlockGroup(group, context) {
const primaryBlock = group.blocks[0];
// The HTML parser ensures that we don't hit this case, but we have an assertion just in case.
if (!primaryBlock) {
this.reportError('Block group must have at least one block.', group.sourceSpan);
return null;
}
if (primaryBlock.name === 'defer' && this.options.enabledBlockTypes.has(primaryBlock.name)) {
const { node, errors } = createDeferredBlock(group, this, this.bindingParser);
this.errors.push(...errors);
return node;
}
this.reportError(`Unrecognized block "${primaryBlock.name}".`, primaryBlock.sourceSpan);
return null;
}
visitBlock(block, context) { }
visitBlockParameter(parameter, context) { }
// convert view engine `ParsedProperty` to a format suitable for IVY
extractAttributes(elementName, properties, i18nPropsMeta) {
const bound = [];
const literal = [];
properties.forEach(prop => {
const i18n = i18nPropsMeta[prop.name];
if (prop.isLiteral) {
literal.push(new TextAttribute(prop.name, prop.expression.source || '', prop.sourceSpan, prop.keySpan, prop.valueSpan, i18n));
}
else {
// Note that validation is skipped and property mapping is disabled
// due to the fact that we need to make sure a given prop is not an
// input of a directive and directive matching happens at runtime.
const bep = this.bindingParser.createBoundElementProperty(elementName, prop, /* skipValidation */ true, /* mapPropertyName */ false);
bound.push(BoundAttribute.fromBoundElementProperty(bep, i18n));
}
});
return { bound, literal };
}
parseAttribute(isTemplateElement, attribute, matchableAttributes, parsedProperties, boundEvents, variables, references) {
const name = normalizeAttributeName(attribute.name);
const value = attribute.value;
const srcSpan = attribute.sourceSpan;
const absoluteOffset = attribute.valueSpan ? attribute.valueSpan.start.offset : srcSpan.start.offset;
function createKeySpan(srcSpan, prefix, identifier) {
// We need to adjust the start location for the keySpan to account for the removed 'data-'
// prefix from `normalizeAttributeName`.
const normalizationAdjustment = attribute.name.length - name.length;
const keySpanStart = srcSpan.start.moveBy(prefix.length + normalizationAdjustment);
const keySpanEnd = keySpanStart.moveBy(identifier.length);
return new ParseSourceSpan(keySpanStart, keySpanEnd, keySpanStart, identifier);
}
const bindParts = name.match(BIND_NAME_REGEXP);
if (bindParts) {
if (bindParts[KW_BIND_IDX] != null) {
const identifier = bindParts[IDENT_KW_IDX];
const keySpan = createKeySpan(srcSpan, bindParts[KW_BIND_IDX], identifier);
this.bindingParser.parsePropertyBinding(identifier, value, false, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
}
else if (bindParts[KW_LET_IDX]) {
if (isTemplateElement) {
const identifier = bindParts[IDENT_KW_IDX];
const keySpan = createKeySpan(srcSpan, bindParts[KW_LET_IDX], identifier);
this.parseVariable(identifier, value, srcSpan, keySpan, attribute.valueSpan, variables);
}
else {
this.reportError(`"let-" is only supported on ng-template elements.`, srcSpan);
}
}
else if (bindParts[KW_REF_IDX]) {
const identifier = bindParts[IDENT_KW_IDX];
const keySpan = createKeySpan(srcSpan, bindParts[KW_REF_IDX], identifier);
this.parseReference(identifier, value, srcSpan, keySpan, attribute.valueSpan, references);
}
else if (bindParts[KW_ON_IDX]) {
const events = [];
const identifier = bindParts[IDENT_KW_IDX];
const keySpan = createKeySpan(srcSpan, bindParts[KW_ON_IDX], identifier);
this.bindingParser.parseEvent(identifier, value, /* isAssignmentEvent */ false, srcSpan, attribute.valueSpan || srcSpan, matchableAttributes, events, keySpan);
addEvents(events, boundEvents);
}
else if (bindParts[KW_BINDON_IDX]) {
const identifier = bindParts[IDENT_KW_IDX];
const keySpan = createKeySpan(srcSpan, bindParts[KW_BINDON_IDX], identifier);
this.bindingParser.parsePropertyBinding(identifier, value, false, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
this.parseAssignmentEvent(identifier, value, srcSpan, attribute.valueSpan, matchableAttributes, boundEvents, keySpan);
}
else if (bindParts[KW_AT_IDX]) {
const keySpan = createKeySpan(srcSpan, '', name);
this.bindingParser.parseLiteralAttr(name, value, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
}
return true;
}
// We didn't see a kw-prefixed property binding, but we have not yet checked
// for the []/()/[()] syntax.
let delims = null;
if (name.startsWith(BINDING_DELIMS.BANANA_BOX.start)) {
delims = BINDING_DELIMS.BANANA_BOX;
}
else if (name.startsWith(BINDING_DELIMS.PROPERTY.start)) {
delims = BINDING_DELIMS.PROPERTY;
}
else if (name.startsWith(BINDING_DELIMS.EVENT.start)) {
delims = BINDING_DELIMS.EVENT;
}
if (delims !== null &&
// NOTE: older versions of the parser would match a start/end delimited
// binding iff the property name was terminated by the ending delimiter
// and the identifier in the binding was non-empty.
// TODO(ayazhafiz): update this to handle malformed bindings.
name.endsWith(delims.end) && name.length > delims.start.length + delims.end.length) {
const identifier = name.substring(delims.start.length, name.length - delims.end.length);
const keySpan = createKeySpan(srcSpan, delims.start, identifier);
if (delims.start === BINDING_DELIMS.BANANA_BOX.start) {
this.bindingParser.parsePropertyBinding(identifier, value, false, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
this.parseAssignmentEvent(identifier, value, srcSpan, attribute.valueSpan, matchableAttributes, boundEvents, keySpan);
}
else if (delims.start === BINDING_DELIMS.PROPERTY.start) {
this.bindingParser.parsePropertyBinding(identifier, value, false, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
}
else {
const events = [];
this.bindingParser.parseEvent(identifier, value, /* isAssignmentEvent */ false, srcSpan, attribute.valueSpan || srcSpan, matchableAttributes, events, keySpan);
addEvents(events, boundEvents);
}
return true;
}
// No explicit binding found.
const keySpan = createKeySpan(srcSpan, '' /* prefix */, name);
const hasBinding = this.bindingParser.parsePropertyInterpolation(name, value, srcSpan, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan, attribute.valueTokens ?? null);
return hasBinding;
}
_visitTextWithInterpolation(value, sourceSpan, interpolatedTokens, i18n) {
const valueNoNgsp = replaceNgsp(value);
const expr = this.bindingParser.parseInterpolation(valueNoNgsp, sourceSpan, interpolatedTokens);
return expr ? new BoundText(expr, sourceSpan, i18n) : new Text$3(valueNoNgsp, sourceSpan);
}
parseVariable(identifier, value, sourceSpan, keySpan, valueSpan, variables) {
if (identifier.indexOf('-') > -1) {
this.reportError(`"-" is not allowed in variable names`, sourceSpan);
}
else if (identifier.length === 0) {
this.reportError(`Variable does not have a name`, sourceSpan);
}
variables.push(new Variable(identifier, value, sourceSpan, keySpan, valueSpan));
}
parseReference(identifier, value, sourceSpan, keySpan, valueSpan, references) {
if (identifier.indexOf('-') > -1) {
this.reportError(`"-" is not allowed in reference names`, sourceSpan);
}
else if (identifier.length === 0) {
this.reportError(`Reference does not have a name`, sourceSpan);
}
else if (references.some(reference => reference.name === identifier)) {
this.reportError(`Reference "#${identifier}" is defined more than once`, sourceSpan);
}
references.push(new Reference(identifier, value, sourceSpan, keySpan, valueSpan));
}
parseAssignmentEvent(name, expression, sourceSpan, valueSpan, targetMatchableAttrs, boundEvents, keySpan) {
const events = [];
this.bindingParser.parseEvent(`${name}Change`, `${expression} =$event`, /* isAssignmentEvent */ true, sourceSpan, valueSpan || sourceSpan, targetMatchableAttrs, events, keySpan);
addEvents(events, boundEvents);
}
reportError(message, sourceSpan, level = ParseErrorLevel.ERROR) {
this.errors.push(new ParseError(sourceSpan, message, level));
}
}
class NonBindableVisitor {
visitElement(ast) {
const preparsedElement = preparseElement(ast);
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
preparsedElement.type === PreparsedElementType.STYLE ||
preparsedElement.type === PreparsedElementType.STYLESHEET) {
// Skipping