feat(HtmlLexer): add support for alphabetic cases
This commit is contained in:
parent
537e99b4ea
commit
43148d8233
|
@ -108,25 +108,24 @@ class ControlFlowError {
|
||||||
|
|
||||||
// See http://www.w3.org/TR/html51/syntax.html#writing
|
// See http://www.w3.org/TR/html51/syntax.html#writing
|
||||||
class _HtmlTokenizer {
|
class _HtmlTokenizer {
|
||||||
private input: string;
|
private _input: string;
|
||||||
private length: number;
|
private _length: number;
|
||||||
// Note: this is always lowercase!
|
// Note: this is always lowercase!
|
||||||
private peek: number = -1;
|
private _peek: number = -1;
|
||||||
private nextPeek: number = -1;
|
private _nextPeek: number = -1;
|
||||||
private index: number = -1;
|
private _index: number = -1;
|
||||||
private line: number = 0;
|
private _line: number = 0;
|
||||||
private column: number = -1;
|
private _column: number = -1;
|
||||||
private currentTokenStart: ParseLocation;
|
private _currentTokenStart: ParseLocation;
|
||||||
private currentTokenType: HtmlTokenType;
|
private _currentTokenType: HtmlTokenType;
|
||||||
|
private _expansionCaseStack: HtmlTokenType[] = [];
|
||||||
private expansionCaseStack: any[] /** TODO #9100 */ = [];
|
|
||||||
|
|
||||||
tokens: HtmlToken[] = [];
|
tokens: HtmlToken[] = [];
|
||||||
errors: HtmlTokenError[] = [];
|
errors: HtmlTokenError[] = [];
|
||||||
|
|
||||||
constructor(private file: ParseSourceFile, private tokenizeExpansionForms: boolean) {
|
constructor(private file: ParseSourceFile, private tokenizeExpansionForms: boolean) {
|
||||||
this.input = file.content;
|
this._input = file.content;
|
||||||
this.length = file.content.length;
|
this._length = file.content.length;
|
||||||
this._advance();
|
this._advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +138,7 @@ class _HtmlTokenizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenize(): HtmlTokenizeResult {
|
tokenize(): HtmlTokenizeResult {
|
||||||
while (this.peek !== $EOF) {
|
while (this._peek !== $EOF) {
|
||||||
var start = this._getLocation();
|
var start = this._getLocation();
|
||||||
try {
|
try {
|
||||||
if (this._attemptCharCode($LT)) {
|
if (this._attemptCharCode($LT)) {
|
||||||
|
@ -156,18 +155,21 @@ class _HtmlTokenizer {
|
||||||
} else {
|
} else {
|
||||||
this._consumeTagOpen(start);
|
this._consumeTagOpen(start);
|
||||||
}
|
}
|
||||||
} else if (isSpecialFormStart(this.peek, this.nextPeek) && this.tokenizeExpansionForms) {
|
} else if (
|
||||||
|
isExpansionFormStart(this._peek, this._nextPeek) && this.tokenizeExpansionForms) {
|
||||||
this._consumeExpansionFormStart();
|
this._consumeExpansionFormStart();
|
||||||
|
|
||||||
} else if (this.peek === $EQ && this.tokenizeExpansionForms) {
|
} else if (
|
||||||
|
isExpansionCaseStart(this._peek) && this._isInExpansionForm() &&
|
||||||
|
this.tokenizeExpansionForms) {
|
||||||
this._consumeExpansionCaseStart();
|
this._consumeExpansionCaseStart();
|
||||||
|
|
||||||
} else if (
|
} else if (this._peek === $RBRACE && this._isInExpansionCase() &&
|
||||||
this.peek === $RBRACE && this.isInExpansionCase() && this.tokenizeExpansionForms) {
|
this.tokenizeExpansionForms) {
|
||||||
this._consumeExpansionCaseEnd();
|
this._consumeExpansionCaseEnd();
|
||||||
|
|
||||||
} else if (
|
} else if (this._peek === $RBRACE && this._isInExpansionForm() &&
|
||||||
this.peek === $RBRACE && this.isInExpansionForm() && this.tokenizeExpansionForms) {
|
this.tokenizeExpansionForms) {
|
||||||
this._consumeExpansionFormEnd();
|
this._consumeExpansionFormEnd();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -187,7 +189,7 @@ class _HtmlTokenizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getLocation(): ParseLocation {
|
private _getLocation(): ParseLocation {
|
||||||
return new ParseLocation(this.file, this.index, this.line, this.column);
|
return new ParseLocation(this.file, this._index, this._line, this._column);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getSpan(start?: ParseLocation, end?: ParseLocation): ParseSourceSpan {
|
private _getSpan(start?: ParseLocation, end?: ParseLocation): ParseSourceSpan {
|
||||||
|
@ -204,47 +206,47 @@ class _HtmlTokenizer {
|
||||||
if (isBlank(start)) {
|
if (isBlank(start)) {
|
||||||
start = this._getLocation();
|
start = this._getLocation();
|
||||||
}
|
}
|
||||||
this.currentTokenStart = start;
|
this._currentTokenStart = start;
|
||||||
this.currentTokenType = type;
|
this._currentTokenType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _endToken(parts: string[], end: ParseLocation = null): HtmlToken {
|
private _endToken(parts: string[], end: ParseLocation = null): HtmlToken {
|
||||||
if (isBlank(end)) {
|
if (isBlank(end)) {
|
||||||
end = this._getLocation();
|
end = this._getLocation();
|
||||||
}
|
}
|
||||||
var token = new HtmlToken(
|
var token = new HtmlToken(this._currentTokenType, parts,
|
||||||
this.currentTokenType, parts, new ParseSourceSpan(this.currentTokenStart, end));
|
new ParseSourceSpan(this._currentTokenStart, end));
|
||||||
this.tokens.push(token);
|
this.tokens.push(token);
|
||||||
this.currentTokenStart = null;
|
this._currentTokenStart = null;
|
||||||
this.currentTokenType = null;
|
this._currentTokenType = null;
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createError(msg: string, span: ParseSourceSpan): ControlFlowError {
|
private _createError(msg: string, span: ParseSourceSpan): ControlFlowError {
|
||||||
var error = new HtmlTokenError(msg, this.currentTokenType, span);
|
var error = new HtmlTokenError(msg, this._currentTokenType, span);
|
||||||
this.currentTokenStart = null;
|
this._currentTokenStart = null;
|
||||||
this.currentTokenType = null;
|
this._currentTokenType = null;
|
||||||
return new ControlFlowError(error);
|
return new ControlFlowError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _advance() {
|
private _advance() {
|
||||||
if (this.index >= this.length) {
|
if (this._index >= this._length) {
|
||||||
throw this._createError(unexpectedCharacterErrorMsg($EOF), this._getSpan());
|
throw this._createError(unexpectedCharacterErrorMsg($EOF), this._getSpan());
|
||||||
}
|
}
|
||||||
if (this.peek === $LF) {
|
if (this._peek === $LF) {
|
||||||
this.line++;
|
this._line++;
|
||||||
this.column = 0;
|
this._column = 0;
|
||||||
} else if (this.peek !== $LF && this.peek !== $CR) {
|
} else if (this._peek !== $LF && this._peek !== $CR) {
|
||||||
this.column++;
|
this._column++;
|
||||||
}
|
}
|
||||||
this.index++;
|
this._index++;
|
||||||
this.peek = this.index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, this.index);
|
this._peek = this._index >= this._length ? $EOF : StringWrapper.charCodeAt(this._input, this._index);
|
||||||
this.nextPeek =
|
this._nextPeek =
|
||||||
this.index + 1 >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, this.index + 1);
|
this._index + 1 >= this._length ? $EOF : StringWrapper.charCodeAt(this._input, this._index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _attemptCharCode(charCode: number): boolean {
|
private _attemptCharCode(charCode: number): boolean {
|
||||||
if (this.peek === charCode) {
|
if (this._peek === charCode) {
|
||||||
this._advance();
|
this._advance();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -252,7 +254,7 @@ class _HtmlTokenizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _attemptCharCodeCaseInsensitive(charCode: number): boolean {
|
private _attemptCharCodeCaseInsensitive(charCode: number): boolean {
|
||||||
if (compareCharCodeCaseInsensitive(this.peek, charCode)) {
|
if (compareCharCodeCaseInsensitive(this._peek, charCode)) {
|
||||||
this._advance();
|
this._advance();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -262,22 +264,22 @@ class _HtmlTokenizer {
|
||||||
private _requireCharCode(charCode: number) {
|
private _requireCharCode(charCode: number) {
|
||||||
var location = this._getLocation();
|
var location = this._getLocation();
|
||||||
if (!this._attemptCharCode(charCode)) {
|
if (!this._attemptCharCode(charCode)) {
|
||||||
throw this._createError(
|
throw this._createError(unexpectedCharacterErrorMsg(this._peek),
|
||||||
unexpectedCharacterErrorMsg(this.peek), this._getSpan(location, location));
|
this._getSpan(location, location));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _attemptStr(chars: string): boolean {
|
private _attemptStr(chars: string): boolean {
|
||||||
var indexBeforeAttempt = this.index;
|
var indexBeforeAttempt = this._index;
|
||||||
var columnBeforeAttempt = this.column;
|
var columnBeforeAttempt = this._column;
|
||||||
var lineBeforeAttempt = this.line;
|
var lineBeforeAttempt = this._line;
|
||||||
for (var i = 0; i < chars.length; i++) {
|
for (var i = 0; i < chars.length; i++) {
|
||||||
if (!this._attemptCharCode(StringWrapper.charCodeAt(chars, i))) {
|
if (!this._attemptCharCode(StringWrapper.charCodeAt(chars, i))) {
|
||||||
// If attempting to parse the string fails, we want to reset the parser
|
// If attempting to parse the string fails, we want to reset the parser
|
||||||
// to where it was before the attempt
|
// to where it was before the attempt
|
||||||
this.index = indexBeforeAttempt;
|
this._index = indexBeforeAttempt;
|
||||||
this.column = columnBeforeAttempt;
|
this._column = columnBeforeAttempt;
|
||||||
this.line = lineBeforeAttempt;
|
this._line = lineBeforeAttempt;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,12 +298,12 @@ class _HtmlTokenizer {
|
||||||
private _requireStr(chars: string) {
|
private _requireStr(chars: string) {
|
||||||
var location = this._getLocation();
|
var location = this._getLocation();
|
||||||
if (!this._attemptStr(chars)) {
|
if (!this._attemptStr(chars)) {
|
||||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan(location));
|
throw this._createError(unexpectedCharacterErrorMsg(this._peek), this._getSpan(location));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _attemptCharCodeUntilFn(predicate: Function) {
|
private _attemptCharCodeUntilFn(predicate: Function) {
|
||||||
while (!predicate(this.peek)) {
|
while (!predicate(this._peek)) {
|
||||||
this._advance();
|
this._advance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -309,24 +311,24 @@ class _HtmlTokenizer {
|
||||||
private _requireCharCodeUntilFn(predicate: Function, len: number) {
|
private _requireCharCodeUntilFn(predicate: Function, len: number) {
|
||||||
var start = this._getLocation();
|
var start = this._getLocation();
|
||||||
this._attemptCharCodeUntilFn(predicate);
|
this._attemptCharCodeUntilFn(predicate);
|
||||||
if (this.index - start.offset < len) {
|
if (this._index - start.offset < len) {
|
||||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan(start, start));
|
throw this._createError(unexpectedCharacterErrorMsg(this._peek), this._getSpan(start, start));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _attemptUntilChar(char: number) {
|
private _attemptUntilChar(char: number) {
|
||||||
while (this.peek !== char) {
|
while (this._peek !== char) {
|
||||||
this._advance();
|
this._advance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _readChar(decodeEntities: boolean): string {
|
private _readChar(decodeEntities: boolean): string {
|
||||||
if (decodeEntities && this.peek === $AMPERSAND) {
|
if (decodeEntities && this._peek === $AMPERSAND) {
|
||||||
return this._decodeEntity();
|
return this._decodeEntity();
|
||||||
} else {
|
} else {
|
||||||
var index = this.index;
|
var index = this._index;
|
||||||
this._advance();
|
this._advance();
|
||||||
return this.input[index];
|
return this._input[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,27 +339,27 @@ class _HtmlTokenizer {
|
||||||
let isHex = this._attemptCharCode($x) || this._attemptCharCode($X);
|
let isHex = this._attemptCharCode($x) || this._attemptCharCode($X);
|
||||||
let numberStart = this._getLocation().offset;
|
let numberStart = this._getLocation().offset;
|
||||||
this._attemptCharCodeUntilFn(isDigitEntityEnd);
|
this._attemptCharCodeUntilFn(isDigitEntityEnd);
|
||||||
if (this.peek != $SEMICOLON) {
|
if (this._peek != $SEMICOLON) {
|
||||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan());
|
throw this._createError(unexpectedCharacterErrorMsg(this._peek), this._getSpan());
|
||||||
}
|
}
|
||||||
this._advance();
|
this._advance();
|
||||||
let strNum = this.input.substring(numberStart, this.index - 1);
|
let strNum = this._input.substring(numberStart, this._index - 1);
|
||||||
try {
|
try {
|
||||||
let charCode = NumberWrapper.parseInt(strNum, isHex ? 16 : 10);
|
let charCode = NumberWrapper.parseInt(strNum, isHex ? 16 : 10);
|
||||||
return StringWrapper.fromCharCode(charCode);
|
return StringWrapper.fromCharCode(charCode);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let entity = this.input.substring(start.offset + 1, this.index - 1);
|
let entity = this._input.substring(start.offset + 1, this._index - 1);
|
||||||
throw this._createError(unknownEntityErrorMsg(entity), this._getSpan(start));
|
throw this._createError(unknownEntityErrorMsg(entity), this._getSpan(start));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let startPosition = this._savePosition();
|
let startPosition = this._savePosition();
|
||||||
this._attemptCharCodeUntilFn(isNamedEntityEnd);
|
this._attemptCharCodeUntilFn(isNamedEntityEnd);
|
||||||
if (this.peek != $SEMICOLON) {
|
if (this._peek != $SEMICOLON) {
|
||||||
this._restorePosition(startPosition);
|
this._restorePosition(startPosition);
|
||||||
return '&';
|
return '&';
|
||||||
}
|
}
|
||||||
this._advance();
|
this._advance();
|
||||||
let name = this.input.substring(start.offset + 1, this.index - 1);
|
let name = this._input.substring(start.offset + 1, this._index - 1);
|
||||||
let char = (NAMED_ENTITIES as any /** TODO #9100 */)[name];
|
let char = (NAMED_ENTITIES as any /** TODO #9100 */)[name];
|
||||||
if (isBlank(char)) {
|
if (isBlank(char)) {
|
||||||
throw this._createError(unknownEntityErrorMsg(name), this._getSpan(start));
|
throw this._createError(unknownEntityErrorMsg(name), this._getSpan(start));
|
||||||
|
@ -378,10 +380,10 @@ class _HtmlTokenizer {
|
||||||
if (this._attemptCharCode(firstCharOfEnd) && attemptEndRest()) {
|
if (this._attemptCharCode(firstCharOfEnd) && attemptEndRest()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (this.index > tagCloseStart.offset) {
|
if (this._index > tagCloseStart.offset) {
|
||||||
parts.push(this.input.substring(tagCloseStart.offset, this.index));
|
parts.push(this._input.substring(tagCloseStart.offset, this._index));
|
||||||
}
|
}
|
||||||
while (this.peek !== firstCharOfEnd) {
|
while (this._peek !== firstCharOfEnd) {
|
||||||
parts.push(this._readChar(decodeEntities));
|
parts.push(this._readChar(decodeEntities));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -410,25 +412,25 @@ class _HtmlTokenizer {
|
||||||
this._beginToken(HtmlTokenType.DOC_TYPE, start);
|
this._beginToken(HtmlTokenType.DOC_TYPE, start);
|
||||||
this._attemptUntilChar($GT);
|
this._attemptUntilChar($GT);
|
||||||
this._advance();
|
this._advance();
|
||||||
this._endToken([this.input.substring(start.offset + 2, this.index - 1)]);
|
this._endToken([this._input.substring(start.offset + 2, this._index - 1)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _consumePrefixAndName(): string[] {
|
private _consumePrefixAndName(): string[] {
|
||||||
var nameOrPrefixStart = this.index;
|
var nameOrPrefixStart = this._index;
|
||||||
var prefix: any /** TODO #9100 */ = null;
|
var prefix: any /** TODO #9100 */ = null;
|
||||||
while (this.peek !== $COLON && !isPrefixEnd(this.peek)) {
|
while (this._peek !== $COLON && !isPrefixEnd(this._peek)) {
|
||||||
this._advance();
|
this._advance();
|
||||||
}
|
}
|
||||||
var nameStart: any /** TODO #9100 */;
|
var nameStart: any /** TODO #9100 */;
|
||||||
if (this.peek === $COLON) {
|
if (this._peek === $COLON) {
|
||||||
this._advance();
|
this._advance();
|
||||||
prefix = this.input.substring(nameOrPrefixStart, this.index - 1);
|
prefix = this._input.substring(nameOrPrefixStart, this._index - 1);
|
||||||
nameStart = this.index;
|
nameStart = this._index;
|
||||||
} else {
|
} else {
|
||||||
nameStart = nameOrPrefixStart;
|
nameStart = nameOrPrefixStart;
|
||||||
}
|
}
|
||||||
this._requireCharCodeUntilFn(isNameEnd, this.index === nameStart ? 1 : 0);
|
this._requireCharCodeUntilFn(isNameEnd, this._index === nameStart ? 1 : 0);
|
||||||
var name = this.input.substring(nameStart, this.index);
|
var name = this._input.substring(nameStart, this._index);
|
||||||
return [prefix, name];
|
return [prefix, name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,14 +438,14 @@ class _HtmlTokenizer {
|
||||||
let savedPos = this._savePosition();
|
let savedPos = this._savePosition();
|
||||||
let lowercaseTagName: any /** TODO #9100 */;
|
let lowercaseTagName: any /** TODO #9100 */;
|
||||||
try {
|
try {
|
||||||
if (!isAsciiLetter(this.peek)) {
|
if (!isAsciiLetter(this._peek)) {
|
||||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan());
|
throw this._createError(unexpectedCharacterErrorMsg(this._peek), this._getSpan());
|
||||||
}
|
}
|
||||||
var nameStart = this.index;
|
var nameStart = this._index;
|
||||||
this._consumeTagOpenStart(start);
|
this._consumeTagOpenStart(start);
|
||||||
lowercaseTagName = this.input.substring(nameStart, this.index).toLowerCase();
|
lowercaseTagName = this._input.substring(nameStart, this._index).toLowerCase();
|
||||||
this._attemptCharCodeUntilFn(isNotWhitespace);
|
this._attemptCharCodeUntilFn(isNotWhitespace);
|
||||||
while (this.peek !== $SLASH && this.peek !== $GT) {
|
while (this._peek !== $SLASH && this._peek !== $GT) {
|
||||||
this._consumeAttributeName();
|
this._consumeAttributeName();
|
||||||
this._attemptCharCodeUntilFn(isNotWhitespace);
|
this._attemptCharCodeUntilFn(isNotWhitespace);
|
||||||
if (this._attemptCharCode($EQ)) {
|
if (this._attemptCharCode($EQ)) {
|
||||||
|
@ -502,19 +504,19 @@ class _HtmlTokenizer {
|
||||||
private _consumeAttributeValue() {
|
private _consumeAttributeValue() {
|
||||||
this._beginToken(HtmlTokenType.ATTR_VALUE);
|
this._beginToken(HtmlTokenType.ATTR_VALUE);
|
||||||
var value: any /** TODO #9100 */;
|
var value: any /** TODO #9100 */;
|
||||||
if (this.peek === $SQ || this.peek === $DQ) {
|
if (this._peek === $SQ || this._peek === $DQ) {
|
||||||
var quoteChar = this.peek;
|
var quoteChar = this._peek;
|
||||||
this._advance();
|
this._advance();
|
||||||
var parts: any[] /** TODO #9100 */ = [];
|
var parts: any[] /** TODO #9100 */ = [];
|
||||||
while (this.peek !== quoteChar) {
|
while (this._peek !== quoteChar) {
|
||||||
parts.push(this._readChar(true));
|
parts.push(this._readChar(true));
|
||||||
}
|
}
|
||||||
value = parts.join('');
|
value = parts.join('');
|
||||||
this._advance();
|
this._advance();
|
||||||
} else {
|
} else {
|
||||||
var valueStart = this.index;
|
var valueStart = this._index;
|
||||||
this._requireCharCodeUntilFn(isNameEnd, 1);
|
this._requireCharCodeUntilFn(isNameEnd, 1);
|
||||||
value = this.input.substring(valueStart, this.index);
|
value = this._input.substring(valueStart, this._index);
|
||||||
}
|
}
|
||||||
this._endToken([this._processCarriageReturns(value)]);
|
this._endToken([this._processCarriageReturns(value)]);
|
||||||
}
|
}
|
||||||
|
@ -554,12 +556,10 @@ class _HtmlTokenizer {
|
||||||
this._requireCharCode($COMMA);
|
this._requireCharCode($COMMA);
|
||||||
this._attemptCharCodeUntilFn(isNotWhitespace);
|
this._attemptCharCodeUntilFn(isNotWhitespace);
|
||||||
|
|
||||||
this.expansionCaseStack.push(HtmlTokenType.EXPANSION_FORM_START);
|
this._expansionCaseStack.push(HtmlTokenType.EXPANSION_FORM_START);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _consumeExpansionCaseStart() {
|
private _consumeExpansionCaseStart() {
|
||||||
this._requireCharCode($EQ);
|
|
||||||
|
|
||||||
this._beginToken(HtmlTokenType.EXPANSION_CASE_VALUE, this._getLocation());
|
this._beginToken(HtmlTokenType.EXPANSION_CASE_VALUE, this._getLocation());
|
||||||
let value = this._readUntil($LBRACE).trim();
|
let value = this._readUntil($LBRACE).trim();
|
||||||
this._endToken([value], this._getLocation());
|
this._endToken([value], this._getLocation());
|
||||||
|
@ -570,7 +570,7 @@ class _HtmlTokenizer {
|
||||||
this._endToken([], this._getLocation());
|
this._endToken([], this._getLocation());
|
||||||
this._attemptCharCodeUntilFn(isNotWhitespace);
|
this._attemptCharCodeUntilFn(isNotWhitespace);
|
||||||
|
|
||||||
this.expansionCaseStack.push(HtmlTokenType.EXPANSION_CASE_EXP_START);
|
this._expansionCaseStack.push(HtmlTokenType.EXPANSION_CASE_EXP_START);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _consumeExpansionCaseEnd() {
|
private _consumeExpansionCaseEnd() {
|
||||||
|
@ -579,7 +579,7 @@ class _HtmlTokenizer {
|
||||||
this._endToken([], this._getLocation());
|
this._endToken([], this._getLocation());
|
||||||
this._attemptCharCodeUntilFn(isNotWhitespace);
|
this._attemptCharCodeUntilFn(isNotWhitespace);
|
||||||
|
|
||||||
this.expansionCaseStack.pop();
|
this._expansionCaseStack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _consumeExpansionFormEnd() {
|
private _consumeExpansionFormEnd() {
|
||||||
|
@ -587,7 +587,7 @@ class _HtmlTokenizer {
|
||||||
this._requireCharCode($RBRACE);
|
this._requireCharCode($RBRACE);
|
||||||
this._endToken([]);
|
this._endToken([]);
|
||||||
|
|
||||||
this.expansionCaseStack.pop();
|
this._expansionCaseStack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _consumeText() {
|
private _consumeText() {
|
||||||
|
@ -597,7 +597,7 @@ class _HtmlTokenizer {
|
||||||
var parts: any[] /** TODO #9100 */ = [];
|
var parts: any[] /** TODO #9100 */ = [];
|
||||||
let interpolation = false;
|
let interpolation = false;
|
||||||
|
|
||||||
if (this.peek === $LBRACE && this.nextPeek === $LBRACE) {
|
if (this._peek === $LBRACE && this._nextPeek === $LBRACE) {
|
||||||
parts.push(this._readChar(true));
|
parts.push(this._readChar(true));
|
||||||
parts.push(this._readChar(true));
|
parts.push(this._readChar(true));
|
||||||
interpolation = true;
|
interpolation = true;
|
||||||
|
@ -605,12 +605,12 @@ class _HtmlTokenizer {
|
||||||
parts.push(this._readChar(true));
|
parts.push(this._readChar(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!this.isTextEnd(interpolation)) {
|
while (!this._isTextEnd(interpolation)) {
|
||||||
if (this.peek === $LBRACE && this.nextPeek === $LBRACE) {
|
if (this._peek === $LBRACE && this._nextPeek === $LBRACE) {
|
||||||
parts.push(this._readChar(true));
|
parts.push(this._readChar(true));
|
||||||
parts.push(this._readChar(true));
|
parts.push(this._readChar(true));
|
||||||
interpolation = true;
|
interpolation = true;
|
||||||
} else if (this.peek === $RBRACE && this.nextPeek === $RBRACE && interpolation) {
|
} else if (this._peek === $RBRACE && this._nextPeek === $RBRACE && interpolation) {
|
||||||
parts.push(this._readChar(true));
|
parts.push(this._readChar(true));
|
||||||
parts.push(this._readChar(true));
|
parts.push(this._readChar(true));
|
||||||
interpolation = false;
|
interpolation = false;
|
||||||
|
@ -621,32 +621,32 @@ class _HtmlTokenizer {
|
||||||
this._endToken([this._processCarriageReturns(parts.join(''))]);
|
this._endToken([this._processCarriageReturns(parts.join(''))]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isTextEnd(interpolation: boolean): boolean {
|
private _isTextEnd(interpolation: boolean): boolean {
|
||||||
if (this.peek === $LT || this.peek === $EOF) return true;
|
if (this._peek === $LT || this._peek === $EOF) return true;
|
||||||
if (this.tokenizeExpansionForms) {
|
if (this.tokenizeExpansionForms) {
|
||||||
if (isSpecialFormStart(this.peek, this.nextPeek)) return true;
|
if (isExpansionFormStart(this._peek, this._nextPeek)) return true;
|
||||||
if (this.peek === $RBRACE && !interpolation &&
|
if (this._peek === $RBRACE && !interpolation &&
|
||||||
(this.isInExpansionCase() || this.isInExpansionForm()))
|
(this._isInExpansionCase() || this._isInExpansionForm()))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _savePosition(): number[] {
|
private _savePosition(): number[] {
|
||||||
return [this.peek, this.index, this.column, this.line, this.tokens.length];
|
return [this._peek, this._index, this._column, this._line, this.tokens.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _readUntil(char: number): string {
|
private _readUntil(char: number): string {
|
||||||
let start = this.index;
|
let start = this._index;
|
||||||
this._attemptUntilChar(char);
|
this._attemptUntilChar(char);
|
||||||
return this.input.substring(start, this.index);
|
return this._input.substring(start, this._index);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _restorePosition(position: number[]): void {
|
private _restorePosition(position: number[]): void {
|
||||||
this.peek = position[0];
|
this._peek = position[0];
|
||||||
this.index = position[1];
|
this._index = position[1];
|
||||||
this.column = position[2];
|
this._column = position[2];
|
||||||
this.line = position[3];
|
this._line = position[3];
|
||||||
let nbTokens = position[4];
|
let nbTokens = position[4];
|
||||||
if (nbTokens < this.tokens.length) {
|
if (nbTokens < this.tokens.length) {
|
||||||
// remove any extra tokens
|
// remove any extra tokens
|
||||||
|
@ -654,15 +654,15 @@ class _HtmlTokenizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private isInExpansionCase(): boolean {
|
private _isInExpansionCase(): boolean {
|
||||||
return this.expansionCaseStack.length > 0 &&
|
return this._expansionCaseStack.length > 0 &&
|
||||||
this.expansionCaseStack[this.expansionCaseStack.length - 1] ===
|
this._expansionCaseStack[this._expansionCaseStack.length - 1] ===
|
||||||
HtmlTokenType.EXPANSION_CASE_EXP_START;
|
HtmlTokenType.EXPANSION_CASE_EXP_START;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isInExpansionForm(): boolean {
|
private _isInExpansionForm(): boolean {
|
||||||
return this.expansionCaseStack.length > 0 &&
|
return this._expansionCaseStack.length > 0 &&
|
||||||
this.expansionCaseStack[this.expansionCaseStack.length - 1] ===
|
this._expansionCaseStack[this._expansionCaseStack.length - 1] ===
|
||||||
HtmlTokenType.EXPANSION_FORM_START;
|
HtmlTokenType.EXPANSION_FORM_START;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -692,10 +692,14 @@ function isNamedEntityEnd(code: number): boolean {
|
||||||
return code == $SEMICOLON || code == $EOF || !isAsciiLetter(code);
|
return code == $SEMICOLON || code == $EOF || !isAsciiLetter(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSpecialFormStart(peek: number, nextPeek: number): boolean {
|
function isExpansionFormStart(peek: number, nextPeek: number): boolean {
|
||||||
return peek === $LBRACE && nextPeek != $LBRACE;
|
return peek === $LBRACE && nextPeek != $LBRACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isExpansionCaseStart(peek: number): boolean {
|
||||||
|
return peek === $EQ || isAsciiLetter(peek);
|
||||||
|
}
|
||||||
|
|
||||||
function isAsciiLetter(code: number): boolean {
|
function isAsciiLetter(code: number): boolean {
|
||||||
return code >= $a && code <= $z || code >= $A && code <= $Z;
|
return code >= $a && code <= $z || code >= $A && code <= $Z;
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ class TreeBuilder {
|
||||||
let switchValue = this._advance();
|
let switchValue = this._advance();
|
||||||
|
|
||||||
let type = this._advance();
|
let type = this._advance();
|
||||||
let cases: any[] /** TODO #9100 */ = [];
|
let cases: HtmlExpansionCaseAst[] = [];
|
||||||
|
|
||||||
// read =
|
// read =
|
||||||
while (this.peek.type === HtmlTokenType.EXPANSION_CASE_VALUE) {
|
while (this.peek.type === HtmlTokenType.EXPANSION_CASE_VALUE) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {HtmlAst, HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, Ht
|
||||||
* { messages.length, plural,
|
* { messages.length, plural,
|
||||||
* =0 {zero}
|
* =0 {zero}
|
||||||
* =1 {one}
|
* =1 {one}
|
||||||
* =other {more than one}
|
* other {more than one}
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
|
|
@ -485,62 +485,98 @@ export function main() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('expansion forms', () => {
|
describe("expansion forms", () => {
|
||||||
it('should parse an expansion form', () => {
|
it("should parse an expansion form", () => {
|
||||||
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four} =5 {five} }', true)).toEqual([
|
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four} =5 {five} foo {bar} }', true))
|
||||||
[HtmlTokenType.EXPANSION_FORM_START], [HtmlTokenType.RAW_TEXT, 'one.two'],
|
.toEqual([
|
||||||
[HtmlTokenType.RAW_TEXT, 'three'], [HtmlTokenType.EXPANSION_CASE_VALUE, '4'],
|
[HtmlTokenType.EXPANSION_FORM_START],
|
||||||
[HtmlTokenType.EXPANSION_CASE_EXP_START], [HtmlTokenType.TEXT, 'four'],
|
[HtmlTokenType.RAW_TEXT, 'one.two'],
|
||||||
[HtmlTokenType.EXPANSION_CASE_EXP_END], [HtmlTokenType.EXPANSION_CASE_VALUE, '5'],
|
[HtmlTokenType.RAW_TEXT, 'three'],
|
||||||
[HtmlTokenType.EXPANSION_CASE_EXP_START], [HtmlTokenType.TEXT, 'five'],
|
[HtmlTokenType.EXPANSION_CASE_VALUE, '=4'],
|
||||||
[HtmlTokenType.EXPANSION_CASE_EXP_END], [HtmlTokenType.EXPANSION_FORM_END],
|
[HtmlTokenType.EXPANSION_CASE_EXP_START],
|
||||||
|
[HtmlTokenType.TEXT, 'four'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_END],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_VALUE, '=5'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_START],
|
||||||
|
[HtmlTokenType.TEXT, 'five'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_END],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_VALUE, 'foo'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_START],
|
||||||
|
[HtmlTokenType.TEXT, 'bar'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_END],
|
||||||
|
[HtmlTokenType.EXPANSION_FORM_END],
|
||||||
[HtmlTokenType.EOF]
|
[HtmlTokenType.EOF]
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse an expansion form with text elements surrounding it', () => {
|
it("should parse an expansion form with text elements surrounding it", () => {
|
||||||
expect(tokenizeAndHumanizeParts('before{one.two, three, =4 {four}}after', true)).toEqual([
|
expect(tokenizeAndHumanizeParts('before{one.two, three, =4 {four}}after', true))
|
||||||
[HtmlTokenType.TEXT, 'before'], [HtmlTokenType.EXPANSION_FORM_START],
|
.toEqual([
|
||||||
[HtmlTokenType.RAW_TEXT, 'one.two'], [HtmlTokenType.RAW_TEXT, 'three'],
|
[HtmlTokenType.TEXT, "before"],
|
||||||
[HtmlTokenType.EXPANSION_CASE_VALUE, '4'], [HtmlTokenType.EXPANSION_CASE_EXP_START],
|
[HtmlTokenType.EXPANSION_FORM_START],
|
||||||
[HtmlTokenType.TEXT, 'four'], [HtmlTokenType.EXPANSION_CASE_EXP_END],
|
[HtmlTokenType.RAW_TEXT, 'one.two'],
|
||||||
[HtmlTokenType.EXPANSION_FORM_END], [HtmlTokenType.TEXT, 'after'], [HtmlTokenType.EOF]
|
[HtmlTokenType.RAW_TEXT, 'three'],
|
||||||
]);
|
[HtmlTokenType.EXPANSION_CASE_VALUE, '=4'],
|
||||||
});
|
[HtmlTokenType.EXPANSION_CASE_EXP_START],
|
||||||
|
[HtmlTokenType.TEXT, 'four'],
|
||||||
it('should parse an expansion forms with elements in it', () => {
|
[HtmlTokenType.EXPANSION_CASE_EXP_END],
|
||||||
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four <b>a</b>}}', true)).toEqual([
|
[HtmlTokenType.EXPANSION_FORM_END],
|
||||||
[HtmlTokenType.EXPANSION_FORM_START], [HtmlTokenType.RAW_TEXT, 'one.two'],
|
[HtmlTokenType.TEXT, "after"],
|
||||||
[HtmlTokenType.RAW_TEXT, 'three'], [HtmlTokenType.EXPANSION_CASE_VALUE, '4'],
|
|
||||||
[HtmlTokenType.EXPANSION_CASE_EXP_START], [HtmlTokenType.TEXT, 'four '],
|
|
||||||
[HtmlTokenType.TAG_OPEN_START, null, 'b'], [HtmlTokenType.TAG_OPEN_END],
|
|
||||||
[HtmlTokenType.TEXT, 'a'], [HtmlTokenType.TAG_CLOSE, null, 'b'],
|
|
||||||
[HtmlTokenType.EXPANSION_CASE_EXP_END], [HtmlTokenType.EXPANSION_FORM_END],
|
|
||||||
[HtmlTokenType.EOF]
|
[HtmlTokenType.EOF]
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse an expansion forms with interpolation in it', () => {
|
it("should parse an expansion forms with elements in it", () => {
|
||||||
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four {{a}}}}', true)).toEqual([
|
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four <b>a</b>}}', true))
|
||||||
[HtmlTokenType.EXPANSION_FORM_START], [HtmlTokenType.RAW_TEXT, 'one.two'],
|
.toEqual([
|
||||||
[HtmlTokenType.RAW_TEXT, 'three'], [HtmlTokenType.EXPANSION_CASE_VALUE, '4'],
|
[HtmlTokenType.EXPANSION_FORM_START],
|
||||||
[HtmlTokenType.EXPANSION_CASE_EXP_START], [HtmlTokenType.TEXT, 'four {{a}}'],
|
[HtmlTokenType.RAW_TEXT, 'one.two'],
|
||||||
[HtmlTokenType.EXPANSION_CASE_EXP_END], [HtmlTokenType.EXPANSION_FORM_END],
|
[HtmlTokenType.RAW_TEXT, 'three'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_VALUE, '=4'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_START],
|
||||||
|
[HtmlTokenType.TEXT, 'four '],
|
||||||
|
[HtmlTokenType.TAG_OPEN_START, null, 'b'],
|
||||||
|
[HtmlTokenType.TAG_OPEN_END],
|
||||||
|
[HtmlTokenType.TEXT, 'a'],
|
||||||
|
[HtmlTokenType.TAG_CLOSE, null, 'b'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_END],
|
||||||
|
[HtmlTokenType.EXPANSION_FORM_END],
|
||||||
[HtmlTokenType.EOF]
|
[HtmlTokenType.EOF]
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse nested expansion forms', () => {
|
it("should parse an expansion forms with interpolation in it", () => {
|
||||||
|
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four {{a}}}}', true))
|
||||||
|
.toEqual([
|
||||||
|
[HtmlTokenType.EXPANSION_FORM_START],
|
||||||
|
[HtmlTokenType.RAW_TEXT, 'one.two'],
|
||||||
|
[HtmlTokenType.RAW_TEXT, 'three'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_VALUE, '=4'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_START],
|
||||||
|
[HtmlTokenType.TEXT, 'four {{a}}'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_END],
|
||||||
|
[HtmlTokenType.EXPANSION_FORM_END],
|
||||||
|
[HtmlTokenType.EOF]
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse nested expansion forms", () => {
|
||||||
expect(tokenizeAndHumanizeParts(`{one.two, three, =4 { {xx, yy, =x {one}} }}`, true))
|
expect(tokenizeAndHumanizeParts(`{one.two, three, =4 { {xx, yy, =x {one}} }}`, true))
|
||||||
.toEqual([
|
.toEqual([
|
||||||
[HtmlTokenType.EXPANSION_FORM_START], [HtmlTokenType.RAW_TEXT, 'one.two'],
|
[HtmlTokenType.EXPANSION_FORM_START],
|
||||||
[HtmlTokenType.RAW_TEXT, 'three'], [HtmlTokenType.EXPANSION_CASE_VALUE, '4'],
|
[HtmlTokenType.RAW_TEXT, 'one.two'],
|
||||||
|
[HtmlTokenType.RAW_TEXT, 'three'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_VALUE, '=4'],
|
||||||
[HtmlTokenType.EXPANSION_CASE_EXP_START],
|
[HtmlTokenType.EXPANSION_CASE_EXP_START],
|
||||||
|
|
||||||
[HtmlTokenType.EXPANSION_FORM_START], [HtmlTokenType.RAW_TEXT, 'xx'],
|
[HtmlTokenType.EXPANSION_FORM_START],
|
||||||
[HtmlTokenType.RAW_TEXT, 'yy'], [HtmlTokenType.EXPANSION_CASE_VALUE, 'x'],
|
[HtmlTokenType.RAW_TEXT, 'xx'],
|
||||||
[HtmlTokenType.EXPANSION_CASE_EXP_START], [HtmlTokenType.TEXT, 'one'],
|
[HtmlTokenType.RAW_TEXT, 'yy'],
|
||||||
[HtmlTokenType.EXPANSION_CASE_EXP_END], [HtmlTokenType.EXPANSION_FORM_END],
|
[HtmlTokenType.EXPANSION_CASE_VALUE, '=x'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_START],
|
||||||
|
[HtmlTokenType.TEXT, 'one'],
|
||||||
|
[HtmlTokenType.EXPANSION_CASE_EXP_END],
|
||||||
|
[HtmlTokenType.EXPANSION_FORM_END],
|
||||||
[HtmlTokenType.TEXT, ' '],
|
[HtmlTokenType.TEXT, ' '],
|
||||||
|
|
||||||
[HtmlTokenType.EXPANSION_CASE_EXP_END], [HtmlTokenType.EXPANSION_FORM_END],
|
[HtmlTokenType.EXPANSION_CASE_EXP_END], [HtmlTokenType.EXPANSION_FORM_END],
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import {HtmlAst, HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from '@angular/compiler/src/html_ast';
|
import {HtmlAst, HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from '@angular/compiler/src/html_ast';
|
||||||
import {HtmlTokenType} from '@angular/compiler/src/html_lexer';
|
import {HtmlTokenType} from '@angular/compiler/src/html_lexer';
|
||||||
import {HtmlParseTreeResult, HtmlParser, HtmlTreeError} from '@angular/compiler/src/html_parser';
|
import {HtmlParser, HtmlParseTreeResult, HtmlTreeError} from '@angular/compiler/src/html_parser';
|
||||||
import {ParseError, ParseLocation} from '@angular/compiler/src/parse_util';
|
import {
|
||||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
|
HtmlElementAst,
|
||||||
|
HtmlAttrAst,
|
||||||
|
HtmlTextAst,
|
||||||
|
HtmlCommentAst,
|
||||||
|
HtmlExpansionAst,
|
||||||
|
HtmlExpansionCaseAst
|
||||||
|
} from '@angular/compiler/src/html_ast';
|
||||||
|
import {ParseError} from '@angular/compiler/src/parse_util';
|
||||||
import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './html_ast_spec_utils';
|
import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './html_ast_spec_utils';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
|
@ -232,12 +238,15 @@ export function main() {
|
||||||
`<div>before{messages.length, plural, =0 {You have <b>no</b> messages} =1 {One {{message}}}}after</div>`,
|
`<div>before{messages.length, plural, =0 {You have <b>no</b> messages} =1 {One {{message}}}}after</div>`,
|
||||||
'TestComp', true);
|
'TestComp', true);
|
||||||
|
|
||||||
expect(humanizeDom(parsed)).toEqual([
|
expect(humanizeDom(parsed))
|
||||||
[HtmlElementAst, 'div', 0], [HtmlTextAst, 'before', 1],
|
.toEqual([
|
||||||
[HtmlExpansionAst, 'messages.length', 'plural'], [HtmlExpansionCaseAst, '0'],
|
[HtmlElementAst, 'div', 0],
|
||||||
[HtmlExpansionCaseAst, '1'], [HtmlTextAst, 'after', 1]
|
[HtmlTextAst, 'before', 1],
|
||||||
|
[HtmlExpansionAst, 'messages.length', 'plural'],
|
||||||
|
[HtmlExpansionCaseAst, '=0'],
|
||||||
|
[HtmlExpansionCaseAst, '=1'],
|
||||||
|
[HtmlTextAst, 'after', 1],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let cases = (<any>parsed.rootNodes[0]).children[1].cases;
|
let cases = (<any>parsed.rootNodes[0]).children[1].cases;
|
||||||
|
|
||||||
expect(humanizeDom(new HtmlParseTreeResult(cases[0].expression, []))).toEqual([
|
expect(humanizeDom(new HtmlParseTreeResult(cases[0].expression, []))).toEqual([
|
||||||
|
@ -254,18 +263,18 @@ export function main() {
|
||||||
it('should parse out nested expansion forms', () => {
|
it('should parse out nested expansion forms', () => {
|
||||||
let parsed = parser.parse(
|
let parsed = parser.parse(
|
||||||
`{messages.length, plural, =0 { {p.gender, gender, =m {m}} }}`, 'TestComp', true);
|
`{messages.length, plural, =0 { {p.gender, gender, =m {m}} }}`, 'TestComp', true);
|
||||||
|
expect(humanizeDom(parsed))
|
||||||
|
.toEqual([
|
||||||
expect(humanizeDom(parsed)).toEqual([
|
|
||||||
[HtmlExpansionAst, 'messages.length', 'plural'],
|
[HtmlExpansionAst, 'messages.length', 'plural'],
|
||||||
[HtmlExpansionCaseAst, '0'],
|
[HtmlExpansionCaseAst, '=0'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let firstCase = (<any>parsed.rootNodes[0]).cases[0];
|
let firstCase = (<any>parsed.rootNodes[0]).cases[0];
|
||||||
|
|
||||||
expect(humanizeDom(new HtmlParseTreeResult(firstCase.expression, []))).toEqual([
|
expect(humanizeDom(new HtmlParseTreeResult(firstCase.expression, [])))
|
||||||
|
.toEqual([
|
||||||
[HtmlExpansionAst, 'p.gender', 'gender'],
|
[HtmlExpansionAst, 'p.gender', 'gender'],
|
||||||
[HtmlExpansionCaseAst, 'm'],
|
[HtmlExpansionCaseAst, '=m'],
|
||||||
[HtmlTextAst, ' ', 0],
|
[HtmlTextAst, ' ', 0],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -175,33 +175,44 @@ export function main() {
|
||||||
|
|
||||||
it('should handle the plural expansion form', () => {
|
it('should handle the plural expansion form', () => {
|
||||||
let translations: {[key: string]: string} = {};
|
let translations: {[key: string]: string} = {};
|
||||||
translations[id(new Message('zero<ph name="e1">bold</ph>', 'plural_0', null))] =
|
translations[id(new Message('zero<ph name="e1">bold</ph>', "plural_=0", null))] =
|
||||||
'ZERO<ph name="e1">BOLD</ph>';
|
'ZERO<ph name="e1">BOLD</ph>';
|
||||||
|
|
||||||
let res = parse(`{messages.length, plural,=0 {zero<b>bold</b>}}`, translations);
|
let res = parse(`{messages.length, plural,=0 {zero<b>bold</b>}}`, translations);
|
||||||
|
|
||||||
expect(humanizeDom(res)).toEqual([
|
expect(humanizeDom(res))
|
||||||
[HtmlElementAst, 'ul', 0], [HtmlAttrAst, '[ngPlural]', 'messages.length'],
|
.toEqual([
|
||||||
[HtmlElementAst, 'template', 1], [HtmlAttrAst, 'ngPluralCase', '0'],
|
[HtmlElementAst, 'ul', 0],
|
||||||
[HtmlElementAst, 'li', 2], [HtmlTextAst, 'ZERO', 3], [HtmlElementAst, 'b', 3],
|
[HtmlAttrAst, '[ngPlural]', 'messages.length'],
|
||||||
[HtmlTextAst, 'BOLD', 4]
|
[HtmlElementAst, 'template', 1],
|
||||||
|
[HtmlAttrAst, 'ngPluralCase', '=0'],
|
||||||
|
[HtmlElementAst, 'li', 2],
|
||||||
|
[HtmlTextAst, 'ZERO', 3],
|
||||||
|
[HtmlElementAst, 'b', 3],
|
||||||
|
[HtmlTextAst, 'BOLD', 4],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle nested expansion forms', () => {
|
it('should handle nested expansion forms', () => {
|
||||||
let translations: {[key: string]: string} = {};
|
let translations: {[key: string]: string} = {};
|
||||||
translations[id(new Message('m', 'gender_m', null))] = 'M';
|
translations[id(new Message('m', "gender_=m", null))] = 'M';
|
||||||
|
|
||||||
let res = parse(`{messages.length, plural, =0 { {p.gender, gender, =m {m}} }}`, translations);
|
let res = parse(`{messages.length, plural, =0 { {p.gender, gender, =m {m}} }}`, translations);
|
||||||
|
|
||||||
expect(humanizeDom(res)).toEqual([
|
expect(humanizeDom(res))
|
||||||
[HtmlElementAst, 'ul', 0], [HtmlAttrAst, '[ngPlural]', 'messages.length'],
|
.toEqual([
|
||||||
[HtmlElementAst, 'template', 1], [HtmlAttrAst, 'ngPluralCase', '0'],
|
[HtmlElementAst, 'ul', 0],
|
||||||
|
[HtmlAttrAst, '[ngPlural]', 'messages.length'],
|
||||||
|
[HtmlElementAst, 'template', 1],
|
||||||
|
[HtmlAttrAst, 'ngPluralCase', '=0'],
|
||||||
[HtmlElementAst, 'li', 2],
|
[HtmlElementAst, 'li', 2],
|
||||||
|
|
||||||
[HtmlElementAst, 'ul', 3], [HtmlAttrAst, '[ngSwitch]', 'p.gender'],
|
[HtmlElementAst, 'ul', 3],
|
||||||
[HtmlElementAst, 'template', 4], [HtmlAttrAst, 'ngSwitchWhen', 'm'],
|
[HtmlAttrAst, '[ngSwitch]', 'p.gender'],
|
||||||
[HtmlElementAst, 'li', 5], [HtmlTextAst, 'M', 6],
|
[HtmlElementAst, 'template', 4],
|
||||||
|
[HtmlAttrAst, 'ngSwitchWhen', '=m'],
|
||||||
|
[HtmlElementAst, 'li', 5],
|
||||||
|
[HtmlTextAst, 'M', 6],
|
||||||
|
|
||||||
[HtmlTextAst, ' ', 3]
|
[HtmlTextAst, ' ', 3]
|
||||||
]);
|
]);
|
||||||
|
@ -209,7 +220,7 @@ export function main() {
|
||||||
|
|
||||||
it('should correctly set source code positions', () => {
|
it('should correctly set source code positions', () => {
|
||||||
let translations: {[key: string]: string} = {};
|
let translations: {[key: string]: string} = {};
|
||||||
translations[id(new Message('<ph name="e0">bold</ph>', 'plural_0', null))] =
|
translations[id(new Message('<ph name="e0">bold</ph>', "plural_=0", null))] =
|
||||||
'<ph name="e0">BOLD</ph>';
|
'<ph name="e0">BOLD</ph>';
|
||||||
|
|
||||||
let nodes = parse(`{messages.length, plural,=0 {<b>bold</b>}}`, translations).rootNodes;
|
let nodes = parse(`{messages.length, plural,=0 {<b>bold</b>}}`, translations).rootNodes;
|
||||||
|
@ -230,15 +241,15 @@ export function main() {
|
||||||
expect(switchExp.sourceSpan.end.col).toEqual(16);
|
expect(switchExp.sourceSpan.end.col).toEqual(16);
|
||||||
|
|
||||||
let template: HtmlElementAst = <HtmlElementAst>ul.children[0];
|
let template: HtmlElementAst = <HtmlElementAst>ul.children[0];
|
||||||
expect(template.sourceSpan.start.col).toEqual(26);
|
expect(template.sourceSpan.start.col).toEqual(25);
|
||||||
expect(template.sourceSpan.end.col).toEqual(41);
|
expect(template.sourceSpan.end.col).toEqual(41);
|
||||||
|
|
||||||
let switchCheck = template.attrs[0];
|
let switchCheck = template.attrs[0];
|
||||||
expect(switchCheck.sourceSpan.start.col).toEqual(26);
|
expect(switchCheck.sourceSpan.start.col).toEqual(25);
|
||||||
expect(switchCheck.sourceSpan.end.col).toEqual(28);
|
expect(switchCheck.sourceSpan.end.col).toEqual(28);
|
||||||
|
|
||||||
let li: HtmlElementAst = <HtmlElementAst>template.children[0];
|
let li: HtmlElementAst = <HtmlElementAst>template.children[0];
|
||||||
expect(li.sourceSpan.start.col).toEqual(26);
|
expect(li.sourceSpan.start.col).toEqual(25);
|
||||||
expect(li.sourceSpan.end.col).toEqual(41);
|
expect(li.sourceSpan.end.col).toEqual(41);
|
||||||
|
|
||||||
let b: HtmlElementAst = <HtmlElementAst>li.children[0];
|
let b: HtmlElementAst = <HtmlElementAst>li.children[0];
|
||||||
|
@ -248,15 +259,16 @@ export function main() {
|
||||||
|
|
||||||
it('should handle other special forms', () => {
|
it('should handle other special forms', () => {
|
||||||
let translations: {[key: string]: string} = {};
|
let translations: {[key: string]: string} = {};
|
||||||
translations[id(new Message('m', 'gender_male', null))] = 'M';
|
translations[id(new Message('m', "gender_=male", null))] = 'M';
|
||||||
|
|
||||||
let res = parse(`{person.gender, gender,=male {m}}`, translations);
|
let res = parse(`{person.gender, gender,=male {m}}`, translations);
|
||||||
|
|
||||||
expect(humanizeDom(res)).toEqual([
|
expect(humanizeDom(res))
|
||||||
|
.toEqual([
|
||||||
[HtmlElementAst, 'ul', 0],
|
[HtmlElementAst, 'ul', 0],
|
||||||
[HtmlAttrAst, '[ngSwitch]', 'person.gender'],
|
[HtmlAttrAst, '[ngSwitch]', 'person.gender'],
|
||||||
[HtmlElementAst, 'template', 1],
|
[HtmlElementAst, 'template', 1],
|
||||||
[HtmlAttrAst, 'ngSwitchWhen', 'male'],
|
[HtmlAttrAst, 'ngSwitchWhen', '=male'],
|
||||||
[HtmlElementAst, 'li', 2],
|
[HtmlElementAst, 'li', 2],
|
||||||
[HtmlTextAst, 'M', 3],
|
[HtmlTextAst, 'M', 3],
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -151,21 +151,22 @@ export function main() {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should extract messages from special forms', () => {
|
it("should extract messages from expansion forms", () => {
|
||||||
let res = extractor.extract(
|
let res = extractor.extract(`
|
||||||
`
|
|
||||||
<div>
|
<div>
|
||||||
{messages.length, plural,
|
{messages.length, plural,
|
||||||
=0 {You have <b>no</b> messages}
|
=0 {You have <b>no</b> messages}
|
||||||
=1 {You have one message}
|
=1 {You have one message}
|
||||||
|
other {You have messages}
|
||||||
}
|
}
|
||||||
</div>
|
</div>`,
|
||||||
`,
|
"someurl");
|
||||||
'someurl');
|
|
||||||
|
|
||||||
expect(res.messages).toEqual([
|
expect(res.messages)
|
||||||
new Message('You have <ph name="e1">no</ph> messages', 'plural_0', null),
|
.toEqual([
|
||||||
new Message('You have one message', 'plural_1', null)
|
new Message('You have <ph name="e1">no</ph> messages', "plural_=0", null),
|
||||||
|
new Message('You have one message', "plural_=1", null),
|
||||||
|
new Message('You have messages', "plural_other", null),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue