UNPKG

angular2

Version:

Angular 2 - a web framework for modern web apps

463 lines (462 loc) 17 kB
import { StringWrapper, NumberWrapper, isBlank } from 'angular2/src/facade/lang'; import { ParseLocation, ParseError, ParseSourceFile, ParseSourceSpan } from './parse_util'; import { getHtmlTagDefinition, HtmlTagContentType, NAMED_ENTITIES } from './html_tags'; export var HtmlTokenType; (function (HtmlTokenType) { HtmlTokenType[HtmlTokenType["TAG_OPEN_START"] = 0] = "TAG_OPEN_START"; HtmlTokenType[HtmlTokenType["TAG_OPEN_END"] = 1] = "TAG_OPEN_END"; HtmlTokenType[HtmlTokenType["TAG_OPEN_END_VOID"] = 2] = "TAG_OPEN_END_VOID"; HtmlTokenType[HtmlTokenType["TAG_CLOSE"] = 3] = "TAG_CLOSE"; HtmlTokenType[HtmlTokenType["TEXT"] = 4] = "TEXT"; HtmlTokenType[HtmlTokenType["ESCAPABLE_RAW_TEXT"] = 5] = "ESCAPABLE_RAW_TEXT"; HtmlTokenType[HtmlTokenType["RAW_TEXT"] = 6] = "RAW_TEXT"; HtmlTokenType[HtmlTokenType["COMMENT_START"] = 7] = "COMMENT_START"; HtmlTokenType[HtmlTokenType["COMMENT_END"] = 8] = "COMMENT_END"; HtmlTokenType[HtmlTokenType["CDATA_START"] = 9] = "CDATA_START"; HtmlTokenType[HtmlTokenType["CDATA_END"] = 10] = "CDATA_END"; HtmlTokenType[HtmlTokenType["ATTR_NAME"] = 11] = "ATTR_NAME"; HtmlTokenType[HtmlTokenType["ATTR_VALUE"] = 12] = "ATTR_VALUE"; HtmlTokenType[HtmlTokenType["DOC_TYPE"] = 13] = "DOC_TYPE"; HtmlTokenType[HtmlTokenType["EOF"] = 14] = "EOF"; })(HtmlTokenType || (HtmlTokenType = {})); export class HtmlToken { constructor(type, parts, sourceSpan) { this.type = type; this.parts = parts; this.sourceSpan = sourceSpan; } } export class HtmlTokenError extends ParseError { constructor(errorMsg, tokenType, location) { super(location, errorMsg); this.tokenType = tokenType; } } export class HtmlTokenizeResult { constructor(tokens, errors) { this.tokens = tokens; this.errors = errors; } } export function tokenizeHtml(sourceContent, sourceUrl) { return new _HtmlTokenizer(new ParseSourceFile(sourceContent, sourceUrl)).tokenize(); } const $EOF = 0; const $TAB = 9; const $LF = 10; const $FF = 12; const $CR = 13; const $SPACE = 32; const $BANG = 33; const $DQ = 34; const $HASH = 35; const $$ = 36; const $AMPERSAND = 38; const $SQ = 39; const $MINUS = 45; const $SLASH = 47; const $0 = 48; const $SEMICOLON = 59; const $9 = 57; const $COLON = 58; const $LT = 60; const $EQ = 61; const $GT = 62; const $QUESTION = 63; const $A = 65; const $Z = 90; const $LBRACKET = 91; const $RBRACKET = 93; const $a = 97; const $f = 102; const $z = 122; const $x = 120; const $NBSP = 160; function unexpectedCharacterErrorMsg(charCode) { var char = charCode === $EOF ? 'EOF' : StringWrapper.fromCharCode(charCode); return `Unexpected character "${char}"`; } function unknownEntityErrorMsg(entitySrc) { return `Unknown entity "${entitySrc}" - use the "&#<decimal>;" or "&#x<hex>;" syntax`; } class ControlFlowError { constructor(error) { this.error = error; } } // See http://www.w3.org/TR/html51/syntax.html#writing class _HtmlTokenizer { constructor(file) { this.file = file; // Note: this is always lowercase! this.peek = -1; this.index = -1; this.line = 0; this.column = -1; this.tokens = []; this.errors = []; this.input = file.content; this.inputLowercase = file.content.toLowerCase(); this.length = file.content.length; this._advance(); } tokenize() { while (this.peek !== $EOF) { var start = this._getLocation(); try { if (this._attemptChar($LT)) { if (this._attemptChar($BANG)) { if (this._attemptChar($LBRACKET)) { this._consumeCdata(start); } else if (this._attemptChar($MINUS)) { this._consumeComment(start); } else { this._consumeDocType(start); } } else if (this._attemptChar($SLASH)) { this._consumeTagClose(start); } else { this._consumeTagOpen(start); } } else { this._consumeText(); } } catch (e) { if (e instanceof ControlFlowError) { this.errors.push(e.error); } else { throw e; } } } this._beginToken(HtmlTokenType.EOF); this._endToken([]); return new HtmlTokenizeResult(this.tokens, this.errors); } _getLocation() { return new ParseLocation(this.file, this.index, this.line, this.column); } _beginToken(type, start = null) { if (isBlank(start)) { start = this._getLocation(); } this.currentTokenStart = start; this.currentTokenType = type; } _endToken(parts, end = null) { if (isBlank(end)) { end = this._getLocation(); } var token = new HtmlToken(this.currentTokenType, parts, new ParseSourceSpan(this.currentTokenStart, end)); this.tokens.push(token); this.currentTokenStart = null; this.currentTokenType = null; return token; } _createError(msg, position) { var error = new HtmlTokenError(msg, this.currentTokenType, position); this.currentTokenStart = null; this.currentTokenType = null; return new ControlFlowError(error); } _advance() { if (this.index >= this.length) { throw this._createError(unexpectedCharacterErrorMsg($EOF), this._getLocation()); } if (this.peek === $LF) { this.line++; this.column = 0; } else if (this.peek !== $LF && this.peek !== $CR) { this.column++; } this.index++; this.peek = this.index >= this.length ? $EOF : StringWrapper.charCodeAt(this.inputLowercase, this.index); } _attemptChar(charCode) { if (this.peek === charCode) { this._advance(); return true; } return false; } _requireChar(charCode) { var location = this._getLocation(); if (!this._attemptChar(charCode)) { throw this._createError(unexpectedCharacterErrorMsg(this.peek), location); } } _attemptChars(chars) { for (var i = 0; i < chars.length; i++) { if (!this._attemptChar(StringWrapper.charCodeAt(chars, i))) { return false; } } return true; } _requireChars(chars) { var location = this._getLocation(); if (!this._attemptChars(chars)) { throw this._createError(unexpectedCharacterErrorMsg(this.peek), location); } } _attemptUntilFn(predicate) { while (!predicate(this.peek)) { this._advance(); } } _requireUntilFn(predicate, len) { var start = this._getLocation(); this._attemptUntilFn(predicate); if (this.index - start.offset < len) { throw this._createError(unexpectedCharacterErrorMsg(this.peek), start); } } _attemptUntilChar(char) { while (this.peek !== char) { this._advance(); } } _readChar(decodeEntities) { if (decodeEntities && this.peek === $AMPERSAND) { return this._decodeEntity(); } else { var index = this.index; this._advance(); return this.input[index]; } } _decodeEntity() { var start = this._getLocation(); this._advance(); if (this._attemptChar($HASH)) { let isHex = this._attemptChar($x); let numberStart = this._getLocation().offset; this._attemptUntilFn(isDigitEntityEnd); if (this.peek != $SEMICOLON) { throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getLocation()); } this._advance(); let strNum = this.input.substring(numberStart, this.index - 1); try { let charCode = NumberWrapper.parseInt(strNum, isHex ? 16 : 10); return StringWrapper.fromCharCode(charCode); } catch (e) { let entity = this.input.substring(start.offset + 1, this.index - 1); throw this._createError(unknownEntityErrorMsg(entity), start); } } else { let startPosition = this._savePosition(); this._attemptUntilFn(isNamedEntityEnd); if (this.peek != $SEMICOLON) { this._restorePosition(startPosition); return '&'; } this._advance(); let name = this.input.substring(start.offset + 1, this.index - 1); let char = NAMED_ENTITIES[name]; if (isBlank(char)) { throw this._createError(unknownEntityErrorMsg(name), start); } return char; } } _consumeRawText(decodeEntities, firstCharOfEnd, attemptEndRest) { var tagCloseStart; var textStart = this._getLocation(); this._beginToken(decodeEntities ? HtmlTokenType.ESCAPABLE_RAW_TEXT : HtmlTokenType.RAW_TEXT, textStart); var parts = []; while (true) { tagCloseStart = this._getLocation(); if (this._attemptChar(firstCharOfEnd) && attemptEndRest()) { break; } if (this.index > tagCloseStart.offset) { parts.push(this.input.substring(tagCloseStart.offset, this.index)); } while (this.peek !== firstCharOfEnd) { parts.push(this._readChar(decodeEntities)); } } return this._endToken([parts.join('')], tagCloseStart); } _consumeComment(start) { this._beginToken(HtmlTokenType.COMMENT_START, start); this._requireChar($MINUS); this._endToken([]); var textToken = this._consumeRawText(false, $MINUS, () => this._attemptChars('->')); this._beginToken(HtmlTokenType.COMMENT_END, textToken.sourceSpan.end); this._endToken([]); } _consumeCdata(start) { this._beginToken(HtmlTokenType.CDATA_START, start); this._requireChars('cdata['); this._endToken([]); var textToken = this._consumeRawText(false, $RBRACKET, () => this._attemptChars(']>')); this._beginToken(HtmlTokenType.CDATA_END, textToken.sourceSpan.end); this._endToken([]); } _consumeDocType(start) { this._beginToken(HtmlTokenType.DOC_TYPE, start); this._attemptUntilChar($GT); this._advance(); this._endToken([this.input.substring(start.offset + 2, this.index - 1)]); } _consumePrefixAndName() { var nameOrPrefixStart = this.index; var prefix = null; while (this.peek !== $COLON && !isPrefixEnd(this.peek)) { this._advance(); } var nameStart; if (this.peek === $COLON) { this._advance(); prefix = this.input.substring(nameOrPrefixStart, this.index - 1); nameStart = this.index; } else { nameStart = nameOrPrefixStart; } this._requireUntilFn(isNameEnd, this.index === nameStart ? 1 : 0); var name = this.input.substring(nameStart, this.index); return [prefix, name]; } _consumeTagOpen(start) { this._attemptUntilFn(isNotWhitespace); var nameStart = this.index; this._consumeTagOpenStart(start); var lowercaseTagName = this.inputLowercase.substring(nameStart, this.index); this._attemptUntilFn(isNotWhitespace); while (this.peek !== $SLASH && this.peek !== $GT) { this._consumeAttributeName(); this._attemptUntilFn(isNotWhitespace); if (this._attemptChar($EQ)) { this._attemptUntilFn(isNotWhitespace); this._consumeAttributeValue(); } this._attemptUntilFn(isNotWhitespace); } this._consumeTagOpenEnd(); var contentTokenType = getHtmlTagDefinition(lowercaseTagName).contentType; if (contentTokenType === HtmlTagContentType.RAW_TEXT) { this._consumeRawTextWithTagClose(lowercaseTagName, false); } else if (contentTokenType === HtmlTagContentType.ESCAPABLE_RAW_TEXT) { this._consumeRawTextWithTagClose(lowercaseTagName, true); } } _consumeRawTextWithTagClose(lowercaseTagName, decodeEntities) { var textToken = this._consumeRawText(decodeEntities, $LT, () => { if (!this._attemptChar($SLASH)) return false; this._attemptUntilFn(isNotWhitespace); if (!this._attemptChars(lowercaseTagName)) return false; this._attemptUntilFn(isNotWhitespace); if (!this._attemptChar($GT)) return false; return true; }); this._beginToken(HtmlTokenType.TAG_CLOSE, textToken.sourceSpan.end); this._endToken([null, lowercaseTagName]); } _consumeTagOpenStart(start) { this._beginToken(HtmlTokenType.TAG_OPEN_START, start); var parts = this._consumePrefixAndName(); this._endToken(parts); } _consumeAttributeName() { this._beginToken(HtmlTokenType.ATTR_NAME); var prefixAndName = this._consumePrefixAndName(); this._endToken(prefixAndName); } _consumeAttributeValue() { this._beginToken(HtmlTokenType.ATTR_VALUE); var value; if (this.peek === $SQ || this.peek === $DQ) { var quoteChar = this.peek; this._advance(); var parts = []; while (this.peek !== quoteChar) { parts.push(this._readChar(true)); } value = parts.join(''); this._advance(); } else { var valueStart = this.index; this._requireUntilFn(isNameEnd, 1); value = this.input.substring(valueStart, this.index); } this._endToken([value]); } _consumeTagOpenEnd() { var tokenType = this._attemptChar($SLASH) ? HtmlTokenType.TAG_OPEN_END_VOID : HtmlTokenType.TAG_OPEN_END; this._beginToken(tokenType); this._requireChar($GT); this._endToken([]); } _consumeTagClose(start) { this._beginToken(HtmlTokenType.TAG_CLOSE, start); this._attemptUntilFn(isNotWhitespace); var prefixAndName; prefixAndName = this._consumePrefixAndName(); this._attemptUntilFn(isNotWhitespace); this._requireChar($GT); this._endToken(prefixAndName); } _consumeText() { var start = this._getLocation(); this._beginToken(HtmlTokenType.TEXT, start); var parts = [this._readChar(true)]; while (!isTextEnd(this.peek)) { parts.push(this._readChar(true)); } this._endToken([parts.join('')]); } _savePosition() { return [this.peek, this.index, this.column, this.line]; } _restorePosition(position) { this.peek = position[0]; this.index = position[1]; this.column = position[2]; this.line = position[3]; } } function isNotWhitespace(code) { return !isWhitespace(code) || code === $EOF; } function isWhitespace(code) { return (code >= $TAB && code <= $SPACE) || (code === $NBSP); } function isNameEnd(code) { return isWhitespace(code) || code === $GT || code === $SLASH || code === $SQ || code === $DQ || code === $EQ; } 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 isTextEnd(code) { return code === $LT || code === $EOF; } function isAsciiLetter(code) { return code >= $a && code <= $z; } function isAsciiHexDigit(code) { return code >= $a && code <= $f || code >= $0 && code <= $9; }