| import { Preprocessor } from './preprocessor.js'; |
| import { CODE_POINTS as $, SEQUENCES as $$, REPLACEMENT_CHARACTER, isSurrogate, isUndefinedCodePoint, isControlCodePoint, } from '../common/unicode.js'; |
| import { TokenType, getTokenAttr, } from '../common/token.js'; |
| import { htmlDecodeTree, EntityDecoder, DecodingMode } from 'entities/decode'; |
| import { ERR } from '../common/error-codes.js'; |
| import { TAG_ID, getTagID } from '../common/html.js'; |
| //States |
| var State; |
| (function (State) { |
| State[State["DATA"] = 0] = "DATA"; |
| State[State["RCDATA"] = 1] = "RCDATA"; |
| State[State["RAWTEXT"] = 2] = "RAWTEXT"; |
| State[State["SCRIPT_DATA"] = 3] = "SCRIPT_DATA"; |
| State[State["PLAINTEXT"] = 4] = "PLAINTEXT"; |
| State[State["TAG_OPEN"] = 5] = "TAG_OPEN"; |
| State[State["END_TAG_OPEN"] = 6] = "END_TAG_OPEN"; |
| State[State["TAG_NAME"] = 7] = "TAG_NAME"; |
| State[State["RCDATA_LESS_THAN_SIGN"] = 8] = "RCDATA_LESS_THAN_SIGN"; |
| State[State["RCDATA_END_TAG_OPEN"] = 9] = "RCDATA_END_TAG_OPEN"; |
| State[State["RCDATA_END_TAG_NAME"] = 10] = "RCDATA_END_TAG_NAME"; |
| State[State["RAWTEXT_LESS_THAN_SIGN"] = 11] = "RAWTEXT_LESS_THAN_SIGN"; |
| State[State["RAWTEXT_END_TAG_OPEN"] = 12] = "RAWTEXT_END_TAG_OPEN"; |
| State[State["RAWTEXT_END_TAG_NAME"] = 13] = "RAWTEXT_END_TAG_NAME"; |
| State[State["SCRIPT_DATA_LESS_THAN_SIGN"] = 14] = "SCRIPT_DATA_LESS_THAN_SIGN"; |
| State[State["SCRIPT_DATA_END_TAG_OPEN"] = 15] = "SCRIPT_DATA_END_TAG_OPEN"; |
| State[State["SCRIPT_DATA_END_TAG_NAME"] = 16] = "SCRIPT_DATA_END_TAG_NAME"; |
| State[State["SCRIPT_DATA_ESCAPE_START"] = 17] = "SCRIPT_DATA_ESCAPE_START"; |
| State[State["SCRIPT_DATA_ESCAPE_START_DASH"] = 18] = "SCRIPT_DATA_ESCAPE_START_DASH"; |
| State[State["SCRIPT_DATA_ESCAPED"] = 19] = "SCRIPT_DATA_ESCAPED"; |
| State[State["SCRIPT_DATA_ESCAPED_DASH"] = 20] = "SCRIPT_DATA_ESCAPED_DASH"; |
| State[State["SCRIPT_DATA_ESCAPED_DASH_DASH"] = 21] = "SCRIPT_DATA_ESCAPED_DASH_DASH"; |
| State[State["SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN"] = 22] = "SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN"; |
| State[State["SCRIPT_DATA_ESCAPED_END_TAG_OPEN"] = 23] = "SCRIPT_DATA_ESCAPED_END_TAG_OPEN"; |
| State[State["SCRIPT_DATA_ESCAPED_END_TAG_NAME"] = 24] = "SCRIPT_DATA_ESCAPED_END_TAG_NAME"; |
| State[State["SCRIPT_DATA_DOUBLE_ESCAPE_START"] = 25] = "SCRIPT_DATA_DOUBLE_ESCAPE_START"; |
| State[State["SCRIPT_DATA_DOUBLE_ESCAPED"] = 26] = "SCRIPT_DATA_DOUBLE_ESCAPED"; |
| State[State["SCRIPT_DATA_DOUBLE_ESCAPED_DASH"] = 27] = "SCRIPT_DATA_DOUBLE_ESCAPED_DASH"; |
| State[State["SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH"] = 28] = "SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH"; |
| State[State["SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN"] = 29] = "SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN"; |
| State[State["SCRIPT_DATA_DOUBLE_ESCAPE_END"] = 30] = "SCRIPT_DATA_DOUBLE_ESCAPE_END"; |
| State[State["BEFORE_ATTRIBUTE_NAME"] = 31] = "BEFORE_ATTRIBUTE_NAME"; |
| State[State["ATTRIBUTE_NAME"] = 32] = "ATTRIBUTE_NAME"; |
| State[State["AFTER_ATTRIBUTE_NAME"] = 33] = "AFTER_ATTRIBUTE_NAME"; |
| State[State["BEFORE_ATTRIBUTE_VALUE"] = 34] = "BEFORE_ATTRIBUTE_VALUE"; |
| State[State["ATTRIBUTE_VALUE_DOUBLE_QUOTED"] = 35] = "ATTRIBUTE_VALUE_DOUBLE_QUOTED"; |
| State[State["ATTRIBUTE_VALUE_SINGLE_QUOTED"] = 36] = "ATTRIBUTE_VALUE_SINGLE_QUOTED"; |
| State[State["ATTRIBUTE_VALUE_UNQUOTED"] = 37] = "ATTRIBUTE_VALUE_UNQUOTED"; |
| State[State["AFTER_ATTRIBUTE_VALUE_QUOTED"] = 38] = "AFTER_ATTRIBUTE_VALUE_QUOTED"; |
| State[State["SELF_CLOSING_START_TAG"] = 39] = "SELF_CLOSING_START_TAG"; |
| State[State["BOGUS_COMMENT"] = 40] = "BOGUS_COMMENT"; |
| State[State["MARKUP_DECLARATION_OPEN"] = 41] = "MARKUP_DECLARATION_OPEN"; |
| State[State["COMMENT_START"] = 42] = "COMMENT_START"; |
| State[State["COMMENT_START_DASH"] = 43] = "COMMENT_START_DASH"; |
| State[State["COMMENT"] = 44] = "COMMENT"; |
| State[State["COMMENT_LESS_THAN_SIGN"] = 45] = "COMMENT_LESS_THAN_SIGN"; |
| State[State["COMMENT_LESS_THAN_SIGN_BANG"] = 46] = "COMMENT_LESS_THAN_SIGN_BANG"; |
| State[State["COMMENT_LESS_THAN_SIGN_BANG_DASH"] = 47] = "COMMENT_LESS_THAN_SIGN_BANG_DASH"; |
| State[State["COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH"] = 48] = "COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH"; |
| State[State["COMMENT_END_DASH"] = 49] = "COMMENT_END_DASH"; |
| State[State["COMMENT_END"] = 50] = "COMMENT_END"; |
| State[State["COMMENT_END_BANG"] = 51] = "COMMENT_END_BANG"; |
| State[State["DOCTYPE"] = 52] = "DOCTYPE"; |
| State[State["BEFORE_DOCTYPE_NAME"] = 53] = "BEFORE_DOCTYPE_NAME"; |
| State[State["DOCTYPE_NAME"] = 54] = "DOCTYPE_NAME"; |
| State[State["AFTER_DOCTYPE_NAME"] = 55] = "AFTER_DOCTYPE_NAME"; |
| State[State["AFTER_DOCTYPE_PUBLIC_KEYWORD"] = 56] = "AFTER_DOCTYPE_PUBLIC_KEYWORD"; |
| State[State["BEFORE_DOCTYPE_PUBLIC_IDENTIFIER"] = 57] = "BEFORE_DOCTYPE_PUBLIC_IDENTIFIER"; |
| State[State["DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED"] = 58] = "DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED"; |
| State[State["DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED"] = 59] = "DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED"; |
| State[State["AFTER_DOCTYPE_PUBLIC_IDENTIFIER"] = 60] = "AFTER_DOCTYPE_PUBLIC_IDENTIFIER"; |
| State[State["BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS"] = 61] = "BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS"; |
| State[State["AFTER_DOCTYPE_SYSTEM_KEYWORD"] = 62] = "AFTER_DOCTYPE_SYSTEM_KEYWORD"; |
| State[State["BEFORE_DOCTYPE_SYSTEM_IDENTIFIER"] = 63] = "BEFORE_DOCTYPE_SYSTEM_IDENTIFIER"; |
| State[State["DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED"] = 64] = "DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED"; |
| State[State["DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED"] = 65] = "DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED"; |
| State[State["AFTER_DOCTYPE_SYSTEM_IDENTIFIER"] = 66] = "AFTER_DOCTYPE_SYSTEM_IDENTIFIER"; |
| State[State["BOGUS_DOCTYPE"] = 67] = "BOGUS_DOCTYPE"; |
| State[State["CDATA_SECTION"] = 68] = "CDATA_SECTION"; |
| State[State["CDATA_SECTION_BRACKET"] = 69] = "CDATA_SECTION_BRACKET"; |
| State[State["CDATA_SECTION_END"] = 70] = "CDATA_SECTION_END"; |
| State[State["CHARACTER_REFERENCE"] = 71] = "CHARACTER_REFERENCE"; |
| State[State["AMBIGUOUS_AMPERSAND"] = 72] = "AMBIGUOUS_AMPERSAND"; |
| })(State || (State = {})); |
| //Tokenizer initial states for different modes |
| export const TokenizerMode = { |
| DATA: State.DATA, |
| RCDATA: State.RCDATA, |
| RAWTEXT: State.RAWTEXT, |
| SCRIPT_DATA: State.SCRIPT_DATA, |
| PLAINTEXT: State.PLAINTEXT, |
| CDATA_SECTION: State.CDATA_SECTION, |
| }; |
| //Utils |
| //OPTIMIZATION: these utility functions should not be moved out of this module. V8 Crankshaft will not inline |
| //this functions if they will be situated in another module due to context switch. |
| //Always perform inlining check before modifying this functions ('node --trace-inlining'). |
| function isAsciiDigit(cp) { |
| return cp >= $.DIGIT_0 && cp <= $.DIGIT_9; |
| } |
| function isAsciiUpper(cp) { |
| return cp >= $.LATIN_CAPITAL_A && cp <= $.LATIN_CAPITAL_Z; |
| } |
| function isAsciiLower(cp) { |
| return cp >= $.LATIN_SMALL_A && cp <= $.LATIN_SMALL_Z; |
| } |
| function isAsciiLetter(cp) { |
| return isAsciiLower(cp) || isAsciiUpper(cp); |
| } |
| function isAsciiAlphaNumeric(cp) { |
| return isAsciiLetter(cp) || isAsciiDigit(cp); |
| } |
| function toAsciiLower(cp) { |
| return cp + 32; |
| } |
| function isWhitespace(cp) { |
| return cp === $.SPACE || cp === $.LINE_FEED || cp === $.TABULATION || cp === $.FORM_FEED; |
| } |
| function isScriptDataDoubleEscapeSequenceEnd(cp) { |
| return isWhitespace(cp) || cp === $.SOLIDUS || cp === $.GREATER_THAN_SIGN; |
| } |
| function getErrorForNumericCharacterReference(code) { |
| if (code === $.NULL) { |
| return ERR.nullCharacterReference; |
| } |
| else if (code > 1114111) { |
| return ERR.characterReferenceOutsideUnicodeRange; |
| } |
| else if (isSurrogate(code)) { |
| return ERR.surrogateCharacterReference; |
| } |
| else if (isUndefinedCodePoint(code)) { |
| return ERR.noncharacterCharacterReference; |
| } |
| else if (isControlCodePoint(code) || code === $.CARRIAGE_RETURN) { |
| return ERR.controlCharacterReference; |
| } |
| return null; |
| } |
| //Tokenizer |
| export class Tokenizer { |
| constructor(options, handler) { |
| this.options = options; |
| this.handler = handler; |
| this.paused = false; |
| /** Ensures that the parsing loop isn't run multiple times at once. */ |
| this.inLoop = false; |
| /** |
| * Indicates that the current adjusted node exists, is not an element in the HTML namespace, |
| * and that it is not an integration point for either MathML or HTML. |
| * |
| * @see {@link https://html.spec.whatwg.org/multipage/parsing.html#tree-construction} |
| */ |
| this.inForeignNode = false; |
| this.lastStartTagName = ''; |
| this.active = false; |
| this.state = State.DATA; |
| this.returnState = State.DATA; |
| this.entityStartPos = 0; |
| this.consumedAfterSnapshot = -1; |
| this.currentCharacterToken = null; |
| this.currentToken = null; |
| this.currentAttr = { name: '', value: '' }; |
| this.preprocessor = new Preprocessor(handler); |
| this.currentLocation = this.getCurrentLocation(-1); |
| this.entityDecoder = new EntityDecoder(htmlDecodeTree, (cp, consumed) => { |
| // Note: Set `pos` _before_ flushing, as flushing might drop |
| // the current chunk and invalidate `entityStartPos`. |
| this.preprocessor.pos = this.entityStartPos + consumed - 1; |
| this._flushCodePointConsumedAsCharacterReference(cp); |
| }, handler.onParseError |
| ? { |
| missingSemicolonAfterCharacterReference: () => { |
| this._err(ERR.missingSemicolonAfterCharacterReference, 1); |
| }, |
| absenceOfDigitsInNumericCharacterReference: (consumed) => { |
| this._err(ERR.absenceOfDigitsInNumericCharacterReference, this.entityStartPos - this.preprocessor.pos + consumed); |
| }, |
| validateNumericCharacterReference: (code) => { |
| const error = getErrorForNumericCharacterReference(code); |
| if (error) |
| this._err(error, 1); |
| }, |
| } |
| : undefined); |
| } |
| //Errors |
| _err(code, cpOffset = 0) { |
| var _a, _b; |
| (_b = (_a = this.handler).onParseError) === null || _b === void 0 ? void 0 : _b.call(_a, this.preprocessor.getError(code, cpOffset)); |
| } |
| // NOTE: `offset` may never run across line boundaries. |
| getCurrentLocation(offset) { |
| if (!this.options.sourceCodeLocationInfo) { |
| return null; |
| } |
| return { |
| startLine: this.preprocessor.line, |
| startCol: this.preprocessor.col - offset, |
| startOffset: this.preprocessor.offset - offset, |
| endLine: -1, |
| endCol: -1, |
| endOffset: -1, |
| }; |
| } |
| _runParsingLoop() { |
| if (this.inLoop) |
| return; |
| this.inLoop = true; |
| while (this.active && !this.paused) { |
| this.consumedAfterSnapshot = 0; |
| const cp = this._consume(); |
| if (!this._ensureHibernation()) { |
| this._callState(cp); |
| } |
| } |
| this.inLoop = false; |
| } |
| //API |
| pause() { |
| this.paused = true; |
| } |
| resume(writeCallback) { |
| if (!this.paused) { |
| throw new Error('Parser was already resumed'); |
| } |
| this.paused = false; |
| // Necessary for synchronous resume. |
| if (this.inLoop) |
| return; |
| this._runParsingLoop(); |
| if (!this.paused) { |
| writeCallback === null || writeCallback === void 0 ? void 0 : writeCallback(); |
| } |
| } |
| write(chunk, isLastChunk, writeCallback) { |
| this.active = true; |
| this.preprocessor.write(chunk, isLastChunk); |
| this._runParsingLoop(); |
| if (!this.paused) { |
| writeCallback === null || writeCallback === void 0 ? void 0 : writeCallback(); |
| } |
| } |
| insertHtmlAtCurrentPos(chunk) { |
| this.active = true; |
| this.preprocessor.insertHtmlAtCurrentPos(chunk); |
| this._runParsingLoop(); |
| } |
| //Hibernation |
| _ensureHibernation() { |
| if (this.preprocessor.endOfChunkHit) { |
| this.preprocessor.retreat(this.consumedAfterSnapshot); |
| this.consumedAfterSnapshot = 0; |
| this.active = false; |
| return true; |
| } |
| return false; |
| } |
| //Consumption |
| _consume() { |
| this.consumedAfterSnapshot++; |
| return this.preprocessor.advance(); |
| } |
| _advanceBy(count) { |
| this.consumedAfterSnapshot += count; |
| for (let i = 0; i < count; i++) { |
| this.preprocessor.advance(); |
| } |
| } |
| _consumeSequenceIfMatch(pattern, caseSensitive) { |
| if (this.preprocessor.startsWith(pattern, caseSensitive)) { |
| // We will already have consumed one character before calling this method. |
| this._advanceBy(pattern.length - 1); |
| return true; |
| } |
| return false; |
| } |
| //Token creation |
| _createStartTagToken() { |
| this.currentToken = { |
| type: TokenType.START_TAG, |
| tagName: '', |
| tagID: TAG_ID.UNKNOWN, |
| selfClosing: false, |
| ackSelfClosing: false, |
| attrs: [], |
| location: this.getCurrentLocation(1), |
| }; |
| } |
| _createEndTagToken() { |
| this.currentToken = { |
| type: TokenType.END_TAG, |
| tagName: '', |
| tagID: TAG_ID.UNKNOWN, |
| selfClosing: false, |
| ackSelfClosing: false, |
| attrs: [], |
| location: this.getCurrentLocation(2), |
| }; |
| } |
| _createCommentToken(offset) { |
| this.currentToken = { |
| type: TokenType.COMMENT, |
| data: '', |
| location: this.getCurrentLocation(offset), |
| }; |
| } |
| _createDoctypeToken(initialName) { |
| this.currentToken = { |
| type: TokenType.DOCTYPE, |
| name: initialName, |
| forceQuirks: false, |
| publicId: null, |
| systemId: null, |
| location: this.currentLocation, |
| }; |
| } |
| _createCharacterToken(type, chars) { |
| this.currentCharacterToken = { |
| type, |
| chars, |
| location: this.currentLocation, |
| }; |
| } |
| //Tag attributes |
| _createAttr(attrNameFirstCh) { |
| this.currentAttr = { |
| name: attrNameFirstCh, |
| value: '', |
| }; |
| this.currentLocation = this.getCurrentLocation(0); |
| } |
| _leaveAttrName() { |
| var _a; |
| var _b; |
| const token = this.currentToken; |
| if (getTokenAttr(token, this.currentAttr.name) === null) { |
| token.attrs.push(this.currentAttr); |
| if (token.location && this.currentLocation) { |
| const attrLocations = ((_a = (_b = token.location).attrs) !== null && _a !== void 0 ? _a : (_b.attrs = Object.create(null))); |
| attrLocations[this.currentAttr.name] = this.currentLocation; |
| // Set end location |
| this._leaveAttrValue(); |
| } |
| } |
| else { |
| this._err(ERR.duplicateAttribute); |
| } |
| } |
| _leaveAttrValue() { |
| if (this.currentLocation) { |
| this.currentLocation.endLine = this.preprocessor.line; |
| this.currentLocation.endCol = this.preprocessor.col; |
| this.currentLocation.endOffset = this.preprocessor.offset; |
| } |
| } |
| //Token emission |
| prepareToken(ct) { |
| this._emitCurrentCharacterToken(ct.location); |
| this.currentToken = null; |
| if (ct.location) { |
| ct.location.endLine = this.preprocessor.line; |
| ct.location.endCol = this.preprocessor.col + 1; |
| ct.location.endOffset = this.preprocessor.offset + 1; |
| } |
| this.currentLocation = this.getCurrentLocation(-1); |
| } |
| emitCurrentTagToken() { |
| const ct = this.currentToken; |
| this.prepareToken(ct); |
| ct.tagID = getTagID(ct.tagName); |
| if (ct.type === TokenType.START_TAG) { |
| this.lastStartTagName = ct.tagName; |
| this.handler.onStartTag(ct); |
| } |
| else { |
| if (ct.attrs.length > 0) { |
| this._err(ERR.endTagWithAttributes); |
| } |
| if (ct.selfClosing) { |
| this._err(ERR.endTagWithTrailingSolidus); |
| } |
| this.handler.onEndTag(ct); |
| } |
| this.preprocessor.dropParsedChunk(); |
| } |
| emitCurrentComment(ct) { |
| this.prepareToken(ct); |
| this.handler.onComment(ct); |
| this.preprocessor.dropParsedChunk(); |
| } |
| emitCurrentDoctype(ct) { |
| this.prepareToken(ct); |
| this.handler.onDoctype(ct); |
| this.preprocessor.dropParsedChunk(); |
| } |
| _emitCurrentCharacterToken(nextLocation) { |
| if (this.currentCharacterToken) { |
| //NOTE: if we have a pending character token, make it's end location equal to the |
| //current token's start location. |
| if (nextLocation && this.currentCharacterToken.location) { |
| this.currentCharacterToken.location.endLine = nextLocation.startLine; |
| this.currentCharacterToken.location.endCol = nextLocation.startCol; |
| this.currentCharacterToken.location.endOffset = nextLocation.startOffset; |
| } |
| switch (this.currentCharacterToken.type) { |
| case TokenType.CHARACTER: { |
| this.handler.onCharacter(this.currentCharacterToken); |
| break; |
| } |
| case TokenType.NULL_CHARACTER: { |
| this.handler.onNullCharacter(this.currentCharacterToken); |
| break; |
| } |
| case TokenType.WHITESPACE_CHARACTER: { |
| this.handler.onWhitespaceCharacter(this.currentCharacterToken); |
| break; |
| } |
| } |
| this.currentCharacterToken = null; |
| } |
| } |
| _emitEOFToken() { |
| const location = this.getCurrentLocation(0); |
| if (location) { |
| location.endLine = location.startLine; |
| location.endCol = location.startCol; |
| location.endOffset = location.startOffset; |
| } |
| this._emitCurrentCharacterToken(location); |
| this.handler.onEof({ type: TokenType.EOF, location }); |
| this.active = false; |
| } |
| //Characters emission |
| //OPTIMIZATION: The specification uses only one type of character token (one token per character). |
| //This causes a huge memory overhead and a lot of unnecessary parser loops. parse5 uses 3 groups of characters. |
| //If we have a sequence of characters that belong to the same group, the parser can process it |
| //as a single solid character token. |
| //So, there are 3 types of character tokens in parse5: |
| //1)TokenType.NULL_CHARACTER - \u0000-character sequences (e.g. '\u0000\u0000\u0000') |
| //2)TokenType.WHITESPACE_CHARACTER - any whitespace/new-line character sequences (e.g. '\n \r\t \f') |
| //3)TokenType.CHARACTER - any character sequence which don't belong to groups 1 and 2 (e.g. 'abcdef1234@@#$%^') |
| _appendCharToCurrentCharacterToken(type, ch) { |
| if (this.currentCharacterToken) { |
| if (this.currentCharacterToken.type === type) { |
| this.currentCharacterToken.chars += ch; |
| return; |
| } |
| else { |
| this.currentLocation = this.getCurrentLocation(0); |
| this._emitCurrentCharacterToken(this.currentLocation); |
| this.preprocessor.dropParsedChunk(); |
| } |
| } |
| this._createCharacterToken(type, ch); |
| } |
| _emitCodePoint(cp) { |
| const type = isWhitespace(cp) |
| ? TokenType.WHITESPACE_CHARACTER |
| : cp === $.NULL |
| ? TokenType.NULL_CHARACTER |
| : TokenType.CHARACTER; |
| // OPTIMIZATION: Use String.fromCharCode for BMP characters (< 0x10000) which is faster |
| // than String.fromCodePoint. Characters outside BMP are rare in HTML. |
| this._appendCharToCurrentCharacterToken(type, cp < 65536 ? String.fromCharCode(cp) : String.fromCodePoint(cp)); |
| } |
| //NOTE: used when we emit characters explicitly. |
| //This is always for non-whitespace and non-null characters, which allows us to avoid additional checks. |
| _emitChars(ch) { |
| this._appendCharToCurrentCharacterToken(TokenType.CHARACTER, ch); |
| } |
| // Character reference helpers |
| _startCharacterReference() { |
| this.returnState = this.state; |
| this.state = State.CHARACTER_REFERENCE; |
| this.entityStartPos = this.preprocessor.pos; |
| this.entityDecoder.startEntity(this._isCharacterReferenceInAttribute() ? DecodingMode.Attribute : DecodingMode.Legacy); |
| } |
| _isCharacterReferenceInAttribute() { |
| return (this.returnState === State.ATTRIBUTE_VALUE_DOUBLE_QUOTED || |
| this.returnState === State.ATTRIBUTE_VALUE_SINGLE_QUOTED || |
| this.returnState === State.ATTRIBUTE_VALUE_UNQUOTED); |
| } |
| _flushCodePointConsumedAsCharacterReference(cp) { |
| if (this._isCharacterReferenceInAttribute()) { |
| this.currentAttr.value += String.fromCodePoint(cp); |
| } |
| else { |
| this._emitCodePoint(cp); |
| } |
| } |
| // Calling states this way turns out to be much faster than any other approach. |
| _callState(cp) { |
| switch (this.state) { |
| case State.DATA: { |
| this._stateData(cp); |
| break; |
| } |
| case State.RCDATA: { |
| this._stateRcdata(cp); |
| break; |
| } |
| case State.RAWTEXT: { |
| this._stateRawtext(cp); |
| break; |
| } |
| case State.SCRIPT_DATA: { |
| this._stateScriptData(cp); |
| break; |
| } |
| case State.PLAINTEXT: { |
| this._statePlaintext(cp); |
| break; |
| } |
| case State.TAG_OPEN: { |
| this._stateTagOpen(cp); |
| break; |
| } |
| case State.END_TAG_OPEN: { |
| this._stateEndTagOpen(cp); |
| break; |
| } |
| case State.TAG_NAME: { |
| this._stateTagName(cp); |
| break; |
| } |
| case State.RCDATA_LESS_THAN_SIGN: { |
| this._stateRcdataLessThanSign(cp); |
| break; |
| } |
| case State.RCDATA_END_TAG_OPEN: { |
| this._stateRcdataEndTagOpen(cp); |
| break; |
| } |
| case State.RCDATA_END_TAG_NAME: { |
| this._stateRcdataEndTagName(cp); |
| break; |
| } |
| case State.RAWTEXT_LESS_THAN_SIGN: { |
| this._stateRawtextLessThanSign(cp); |
| break; |
| } |
| case State.RAWTEXT_END_TAG_OPEN: { |
| this._stateRawtextEndTagOpen(cp); |
| break; |
| } |
| case State.RAWTEXT_END_TAG_NAME: { |
| this._stateRawtextEndTagName(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_LESS_THAN_SIGN: { |
| this._stateScriptDataLessThanSign(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_END_TAG_OPEN: { |
| this._stateScriptDataEndTagOpen(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_END_TAG_NAME: { |
| this._stateScriptDataEndTagName(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_ESCAPE_START: { |
| this._stateScriptDataEscapeStart(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_ESCAPE_START_DASH: { |
| this._stateScriptDataEscapeStartDash(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_ESCAPED: { |
| this._stateScriptDataEscaped(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_ESCAPED_DASH: { |
| this._stateScriptDataEscapedDash(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_ESCAPED_DASH_DASH: { |
| this._stateScriptDataEscapedDashDash(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN: { |
| this._stateScriptDataEscapedLessThanSign(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_ESCAPED_END_TAG_OPEN: { |
| this._stateScriptDataEscapedEndTagOpen(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_ESCAPED_END_TAG_NAME: { |
| this._stateScriptDataEscapedEndTagName(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_DOUBLE_ESCAPE_START: { |
| this._stateScriptDataDoubleEscapeStart(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_DOUBLE_ESCAPED: { |
| this._stateScriptDataDoubleEscaped(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_DOUBLE_ESCAPED_DASH: { |
| this._stateScriptDataDoubleEscapedDash(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH: { |
| this._stateScriptDataDoubleEscapedDashDash(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN: { |
| this._stateScriptDataDoubleEscapedLessThanSign(cp); |
| break; |
| } |
| case State.SCRIPT_DATA_DOUBLE_ESCAPE_END: { |
| this._stateScriptDataDoubleEscapeEnd(cp); |
| break; |
| } |
| case State.BEFORE_ATTRIBUTE_NAME: { |
| this._stateBeforeAttributeName(cp); |
| break; |
| } |
| case State.ATTRIBUTE_NAME: { |
| this._stateAttributeName(cp); |
| break; |
| } |
| case State.AFTER_ATTRIBUTE_NAME: { |
| this._stateAfterAttributeName(cp); |
| break; |
| } |
| case State.BEFORE_ATTRIBUTE_VALUE: { |
| this._stateBeforeAttributeValue(cp); |
| break; |
| } |
| case State.ATTRIBUTE_VALUE_DOUBLE_QUOTED: { |
| this._stateAttributeValueDoubleQuoted(cp); |
| break; |
| } |
| case State.ATTRIBUTE_VALUE_SINGLE_QUOTED: { |
| this._stateAttributeValueSingleQuoted(cp); |
| break; |
| } |
| case State.ATTRIBUTE_VALUE_UNQUOTED: { |
| this._stateAttributeValueUnquoted(cp); |
| break; |
| } |
| case State.AFTER_ATTRIBUTE_VALUE_QUOTED: { |
| this._stateAfterAttributeValueQuoted(cp); |
| break; |
| } |
| case State.SELF_CLOSING_START_TAG: { |
| this._stateSelfClosingStartTag(cp); |
| break; |
| } |
| case State.BOGUS_COMMENT: { |
| this._stateBogusComment(cp); |
| break; |
| } |
| case State.MARKUP_DECLARATION_OPEN: { |
| this._stateMarkupDeclarationOpen(cp); |
| break; |
| } |
| case State.COMMENT_START: { |
| this._stateCommentStart(cp); |
| break; |
| } |
| case State.COMMENT_START_DASH: { |
| this._stateCommentStartDash(cp); |
| break; |
| } |
| case State.COMMENT: { |
| this._stateComment(cp); |
| break; |
| } |
| case State.COMMENT_LESS_THAN_SIGN: { |
| this._stateCommentLessThanSign(cp); |
| break; |
| } |
| case State.COMMENT_LESS_THAN_SIGN_BANG: { |
| this._stateCommentLessThanSignBang(cp); |
| break; |
| } |
| case State.COMMENT_LESS_THAN_SIGN_BANG_DASH: { |
| this._stateCommentLessThanSignBangDash(cp); |
| break; |
| } |
| case State.COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH: { |
| this._stateCommentLessThanSignBangDashDash(cp); |
| break; |
| } |
| case State.COMMENT_END_DASH: { |
| this._stateCommentEndDash(cp); |
| break; |
| } |
| case State.COMMENT_END: { |
| this._stateCommentEnd(cp); |
| break; |
| } |
| case State.COMMENT_END_BANG: { |
| this._stateCommentEndBang(cp); |
| break; |
| } |
| case State.DOCTYPE: { |
| this._stateDoctype(cp); |
| break; |
| } |
| case State.BEFORE_DOCTYPE_NAME: { |
| this._stateBeforeDoctypeName(cp); |
| break; |
| } |
| case State.DOCTYPE_NAME: { |
| this._stateDoctypeName(cp); |
| break; |
| } |
| case State.AFTER_DOCTYPE_NAME: { |
| this._stateAfterDoctypeName(cp); |
| break; |
| } |
| case State.AFTER_DOCTYPE_PUBLIC_KEYWORD: { |
| this._stateAfterDoctypePublicKeyword(cp); |
| break; |
| } |
| case State.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER: { |
| this._stateBeforeDoctypePublicIdentifier(cp); |
| break; |
| } |
| case State.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED: { |
| this._stateDoctypePublicIdentifierDoubleQuoted(cp); |
| break; |
| } |
| case State.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED: { |
| this._stateDoctypePublicIdentifierSingleQuoted(cp); |
| break; |
| } |
| case State.AFTER_DOCTYPE_PUBLIC_IDENTIFIER: { |
| this._stateAfterDoctypePublicIdentifier(cp); |
| break; |
| } |
| case State.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS: { |
| this._stateBetweenDoctypePublicAndSystemIdentifiers(cp); |
| break; |
| } |
| case State.AFTER_DOCTYPE_SYSTEM_KEYWORD: { |
| this._stateAfterDoctypeSystemKeyword(cp); |
| break; |
| } |
| case State.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER: { |
| this._stateBeforeDoctypeSystemIdentifier(cp); |
| break; |
| } |
| case State.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED: { |
| this._stateDoctypeSystemIdentifierDoubleQuoted(cp); |
| break; |
| } |
| case State.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED: { |
| this._stateDoctypeSystemIdentifierSingleQuoted(cp); |
| break; |
| } |
| case State.AFTER_DOCTYPE_SYSTEM_IDENTIFIER: { |
| this._stateAfterDoctypeSystemIdentifier(cp); |
| break; |
| } |
| case State.BOGUS_DOCTYPE: { |
| this._stateBogusDoctype(cp); |
| break; |
| } |
| case State.CDATA_SECTION: { |
| this._stateCdataSection(cp); |
| break; |
| } |
| case State.CDATA_SECTION_BRACKET: { |
| this._stateCdataSectionBracket(cp); |
| break; |
| } |
| case State.CDATA_SECTION_END: { |
| this._stateCdataSectionEnd(cp); |
| break; |
| } |
| case State.CHARACTER_REFERENCE: { |
| this._stateCharacterReference(); |
| break; |
| } |
| case State.AMBIGUOUS_AMPERSAND: { |
| this._stateAmbiguousAmpersand(cp); |
| break; |
| } |
| default: { |
| throw new Error('Unknown state'); |
| } |
| } |
| } |
| // State machine |
| // Data state |
| //------------------------------------------------------------------ |
| _stateData(cp) { |
| switch (cp) { |
| case $.LESS_THAN_SIGN: { |
| this.state = State.TAG_OPEN; |
| break; |
| } |
| case $.AMPERSAND: { |
| this._startCharacterReference(); |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this._emitCodePoint(cp); |
| break; |
| } |
| case $.EOF: { |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._emitCodePoint(cp); |
| } |
| } |
| } |
| // RCDATA state |
| //------------------------------------------------------------------ |
| _stateRcdata(cp) { |
| switch (cp) { |
| case $.AMPERSAND: { |
| this._startCharacterReference(); |
| break; |
| } |
| case $.LESS_THAN_SIGN: { |
| this.state = State.RCDATA_LESS_THAN_SIGN; |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this._emitChars(REPLACEMENT_CHARACTER); |
| break; |
| } |
| case $.EOF: { |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._emitCodePoint(cp); |
| } |
| } |
| } |
| // RAWTEXT state |
| //------------------------------------------------------------------ |
| _stateRawtext(cp) { |
| switch (cp) { |
| case $.LESS_THAN_SIGN: { |
| this.state = State.RAWTEXT_LESS_THAN_SIGN; |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this._emitChars(REPLACEMENT_CHARACTER); |
| break; |
| } |
| case $.EOF: { |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._emitCodePoint(cp); |
| } |
| } |
| } |
| // Script data state |
| //------------------------------------------------------------------ |
| _stateScriptData(cp) { |
| switch (cp) { |
| case $.LESS_THAN_SIGN: { |
| this.state = State.SCRIPT_DATA_LESS_THAN_SIGN; |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this._emitChars(REPLACEMENT_CHARACTER); |
| break; |
| } |
| case $.EOF: { |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._emitCodePoint(cp); |
| } |
| } |
| } |
| // PLAINTEXT state |
| //------------------------------------------------------------------ |
| _statePlaintext(cp) { |
| switch (cp) { |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this._emitChars(REPLACEMENT_CHARACTER); |
| break; |
| } |
| case $.EOF: { |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._emitCodePoint(cp); |
| } |
| } |
| } |
| // Tag open state |
| //------------------------------------------------------------------ |
| _stateTagOpen(cp) { |
| if (isAsciiLetter(cp)) { |
| this._createStartTagToken(); |
| this.state = State.TAG_NAME; |
| this._stateTagName(cp); |
| } |
| else |
| switch (cp) { |
| case $.EXCLAMATION_MARK: { |
| this.state = State.MARKUP_DECLARATION_OPEN; |
| break; |
| } |
| case $.SOLIDUS: { |
| this.state = State.END_TAG_OPEN; |
| break; |
| } |
| case $.QUESTION_MARK: { |
| this._err(ERR.unexpectedQuestionMarkInsteadOfTagName); |
| this._createCommentToken(1); |
| this.state = State.BOGUS_COMMENT; |
| this._stateBogusComment(cp); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofBeforeTagName); |
| this._emitChars('<'); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._err(ERR.invalidFirstCharacterOfTagName); |
| this._emitChars('<'); |
| this.state = State.DATA; |
| this._stateData(cp); |
| } |
| } |
| } |
| // End tag open state |
| //------------------------------------------------------------------ |
| _stateEndTagOpen(cp) { |
| if (isAsciiLetter(cp)) { |
| this._createEndTagToken(); |
| this.state = State.TAG_NAME; |
| this._stateTagName(cp); |
| } |
| else |
| switch (cp) { |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.missingEndTagName); |
| this.state = State.DATA; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofBeforeTagName); |
| this._emitChars('</'); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._err(ERR.invalidFirstCharacterOfTagName); |
| this._createCommentToken(2); |
| this.state = State.BOGUS_COMMENT; |
| this._stateBogusComment(cp); |
| } |
| } |
| } |
| // Tag name state |
| //------------------------------------------------------------------ |
| _stateTagName(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| this.state = State.BEFORE_ATTRIBUTE_NAME; |
| break; |
| } |
| case $.SOLIDUS: { |
| this.state = State.SELF_CLOSING_START_TAG; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this.state = State.DATA; |
| this.emitCurrentTagToken(); |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| token.tagName += REPLACEMENT_CHARACTER; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInTag); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| token.tagName += String.fromCodePoint(isAsciiUpper(cp) ? toAsciiLower(cp) : cp); |
| } |
| } |
| } |
| // RCDATA less-than sign state |
| //------------------------------------------------------------------ |
| _stateRcdataLessThanSign(cp) { |
| if (cp === $.SOLIDUS) { |
| this.state = State.RCDATA_END_TAG_OPEN; |
| } |
| else { |
| this._emitChars('<'); |
| this.state = State.RCDATA; |
| this._stateRcdata(cp); |
| } |
| } |
| // RCDATA end tag open state |
| //------------------------------------------------------------------ |
| _stateRcdataEndTagOpen(cp) { |
| if (isAsciiLetter(cp)) { |
| this.state = State.RCDATA_END_TAG_NAME; |
| this._stateRcdataEndTagName(cp); |
| } |
| else { |
| this._emitChars('</'); |
| this.state = State.RCDATA; |
| this._stateRcdata(cp); |
| } |
| } |
| handleSpecialEndTag(_cp) { |
| if (!this.preprocessor.startsWith(this.lastStartTagName, false)) { |
| return !this._ensureHibernation(); |
| } |
| this._createEndTagToken(); |
| const token = this.currentToken; |
| token.tagName = this.lastStartTagName; |
| const cp = this.preprocessor.peek(this.lastStartTagName.length); |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| this._advanceBy(this.lastStartTagName.length); |
| this.state = State.BEFORE_ATTRIBUTE_NAME; |
| return false; |
| } |
| case $.SOLIDUS: { |
| this._advanceBy(this.lastStartTagName.length); |
| this.state = State.SELF_CLOSING_START_TAG; |
| return false; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._advanceBy(this.lastStartTagName.length); |
| this.emitCurrentTagToken(); |
| this.state = State.DATA; |
| return false; |
| } |
| default: { |
| return !this._ensureHibernation(); |
| } |
| } |
| } |
| // RCDATA end tag name state |
| //------------------------------------------------------------------ |
| _stateRcdataEndTagName(cp) { |
| if (this.handleSpecialEndTag(cp)) { |
| this._emitChars('</'); |
| this.state = State.RCDATA; |
| this._stateRcdata(cp); |
| } |
| } |
| // RAWTEXT less-than sign state |
| //------------------------------------------------------------------ |
| _stateRawtextLessThanSign(cp) { |
| if (cp === $.SOLIDUS) { |
| this.state = State.RAWTEXT_END_TAG_OPEN; |
| } |
| else { |
| this._emitChars('<'); |
| this.state = State.RAWTEXT; |
| this._stateRawtext(cp); |
| } |
| } |
| // RAWTEXT end tag open state |
| //------------------------------------------------------------------ |
| _stateRawtextEndTagOpen(cp) { |
| if (isAsciiLetter(cp)) { |
| this.state = State.RAWTEXT_END_TAG_NAME; |
| this._stateRawtextEndTagName(cp); |
| } |
| else { |
| this._emitChars('</'); |
| this.state = State.RAWTEXT; |
| this._stateRawtext(cp); |
| } |
| } |
| // RAWTEXT end tag name state |
| //------------------------------------------------------------------ |
| _stateRawtextEndTagName(cp) { |
| if (this.handleSpecialEndTag(cp)) { |
| this._emitChars('</'); |
| this.state = State.RAWTEXT; |
| this._stateRawtext(cp); |
| } |
| } |
| // Script data less-than sign state |
| //------------------------------------------------------------------ |
| _stateScriptDataLessThanSign(cp) { |
| switch (cp) { |
| case $.SOLIDUS: { |
| this.state = State.SCRIPT_DATA_END_TAG_OPEN; |
| break; |
| } |
| case $.EXCLAMATION_MARK: { |
| this.state = State.SCRIPT_DATA_ESCAPE_START; |
| this._emitChars('<!'); |
| break; |
| } |
| default: { |
| this._emitChars('<'); |
| this.state = State.SCRIPT_DATA; |
| this._stateScriptData(cp); |
| } |
| } |
| } |
| // Script data end tag open state |
| //------------------------------------------------------------------ |
| _stateScriptDataEndTagOpen(cp) { |
| if (isAsciiLetter(cp)) { |
| this.state = State.SCRIPT_DATA_END_TAG_NAME; |
| this._stateScriptDataEndTagName(cp); |
| } |
| else { |
| this._emitChars('</'); |
| this.state = State.SCRIPT_DATA; |
| this._stateScriptData(cp); |
| } |
| } |
| // Script data end tag name state |
| //------------------------------------------------------------------ |
| _stateScriptDataEndTagName(cp) { |
| if (this.handleSpecialEndTag(cp)) { |
| this._emitChars('</'); |
| this.state = State.SCRIPT_DATA; |
| this._stateScriptData(cp); |
| } |
| } |
| // Script data escape start state |
| //------------------------------------------------------------------ |
| _stateScriptDataEscapeStart(cp) { |
| if (cp === $.HYPHEN_MINUS) { |
| this.state = State.SCRIPT_DATA_ESCAPE_START_DASH; |
| this._emitChars('-'); |
| } |
| else { |
| this.state = State.SCRIPT_DATA; |
| this._stateScriptData(cp); |
| } |
| } |
| // Script data escape start dash state |
| //------------------------------------------------------------------ |
| _stateScriptDataEscapeStartDash(cp) { |
| if (cp === $.HYPHEN_MINUS) { |
| this.state = State.SCRIPT_DATA_ESCAPED_DASH_DASH; |
| this._emitChars('-'); |
| } |
| else { |
| this.state = State.SCRIPT_DATA; |
| this._stateScriptData(cp); |
| } |
| } |
| // Script data escaped state |
| //------------------------------------------------------------------ |
| _stateScriptDataEscaped(cp) { |
| switch (cp) { |
| case $.HYPHEN_MINUS: { |
| this.state = State.SCRIPT_DATA_ESCAPED_DASH; |
| this._emitChars('-'); |
| break; |
| } |
| case $.LESS_THAN_SIGN: { |
| this.state = State.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN; |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this._emitChars(REPLACEMENT_CHARACTER); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInScriptHtmlCommentLikeText); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._emitCodePoint(cp); |
| } |
| } |
| } |
| // Script data escaped dash state |
| //------------------------------------------------------------------ |
| _stateScriptDataEscapedDash(cp) { |
| switch (cp) { |
| case $.HYPHEN_MINUS: { |
| this.state = State.SCRIPT_DATA_ESCAPED_DASH_DASH; |
| this._emitChars('-'); |
| break; |
| } |
| case $.LESS_THAN_SIGN: { |
| this.state = State.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN; |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this.state = State.SCRIPT_DATA_ESCAPED; |
| this._emitChars(REPLACEMENT_CHARACTER); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInScriptHtmlCommentLikeText); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this.state = State.SCRIPT_DATA_ESCAPED; |
| this._emitCodePoint(cp); |
| } |
| } |
| } |
| // Script data escaped dash dash state |
| //------------------------------------------------------------------ |
| _stateScriptDataEscapedDashDash(cp) { |
| switch (cp) { |
| case $.HYPHEN_MINUS: { |
| this._emitChars('-'); |
| break; |
| } |
| case $.LESS_THAN_SIGN: { |
| this.state = State.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this.state = State.SCRIPT_DATA; |
| this._emitChars('>'); |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this.state = State.SCRIPT_DATA_ESCAPED; |
| this._emitChars(REPLACEMENT_CHARACTER); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInScriptHtmlCommentLikeText); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this.state = State.SCRIPT_DATA_ESCAPED; |
| this._emitCodePoint(cp); |
| } |
| } |
| } |
| // Script data escaped less-than sign state |
| //------------------------------------------------------------------ |
| _stateScriptDataEscapedLessThanSign(cp) { |
| if (cp === $.SOLIDUS) { |
| this.state = State.SCRIPT_DATA_ESCAPED_END_TAG_OPEN; |
| } |
| else if (isAsciiLetter(cp)) { |
| this._emitChars('<'); |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPE_START; |
| this._stateScriptDataDoubleEscapeStart(cp); |
| } |
| else { |
| this._emitChars('<'); |
| this.state = State.SCRIPT_DATA_ESCAPED; |
| this._stateScriptDataEscaped(cp); |
| } |
| } |
| // Script data escaped end tag open state |
| //------------------------------------------------------------------ |
| _stateScriptDataEscapedEndTagOpen(cp) { |
| if (isAsciiLetter(cp)) { |
| this.state = State.SCRIPT_DATA_ESCAPED_END_TAG_NAME; |
| this._stateScriptDataEscapedEndTagName(cp); |
| } |
| else { |
| this._emitChars('</'); |
| this.state = State.SCRIPT_DATA_ESCAPED; |
| this._stateScriptDataEscaped(cp); |
| } |
| } |
| // Script data escaped end tag name state |
| //------------------------------------------------------------------ |
| _stateScriptDataEscapedEndTagName(cp) { |
| if (this.handleSpecialEndTag(cp)) { |
| this._emitChars('</'); |
| this.state = State.SCRIPT_DATA_ESCAPED; |
| this._stateScriptDataEscaped(cp); |
| } |
| } |
| // Script data double escape start state |
| //------------------------------------------------------------------ |
| _stateScriptDataDoubleEscapeStart(cp) { |
| if (this.preprocessor.startsWith($$.SCRIPT, false) && |
| isScriptDataDoubleEscapeSequenceEnd(this.preprocessor.peek($$.SCRIPT.length))) { |
| this._emitCodePoint(cp); |
| for (let i = 0; i < $$.SCRIPT.length; i++) { |
| this._emitCodePoint(this._consume()); |
| } |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPED; |
| } |
| else if (!this._ensureHibernation()) { |
| this.state = State.SCRIPT_DATA_ESCAPED; |
| this._stateScriptDataEscaped(cp); |
| } |
| } |
| // Script data double escaped state |
| //------------------------------------------------------------------ |
| _stateScriptDataDoubleEscaped(cp) { |
| switch (cp) { |
| case $.HYPHEN_MINUS: { |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPED_DASH; |
| this._emitChars('-'); |
| break; |
| } |
| case $.LESS_THAN_SIGN: { |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN; |
| this._emitChars('<'); |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this._emitChars(REPLACEMENT_CHARACTER); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInScriptHtmlCommentLikeText); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._emitCodePoint(cp); |
| } |
| } |
| } |
| // Script data double escaped dash state |
| //------------------------------------------------------------------ |
| _stateScriptDataDoubleEscapedDash(cp) { |
| switch (cp) { |
| case $.HYPHEN_MINUS: { |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH; |
| this._emitChars('-'); |
| break; |
| } |
| case $.LESS_THAN_SIGN: { |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN; |
| this._emitChars('<'); |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPED; |
| this._emitChars(REPLACEMENT_CHARACTER); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInScriptHtmlCommentLikeText); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPED; |
| this._emitCodePoint(cp); |
| } |
| } |
| } |
| // Script data double escaped dash dash state |
| //------------------------------------------------------------------ |
| _stateScriptDataDoubleEscapedDashDash(cp) { |
| switch (cp) { |
| case $.HYPHEN_MINUS: { |
| this._emitChars('-'); |
| break; |
| } |
| case $.LESS_THAN_SIGN: { |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN; |
| this._emitChars('<'); |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this.state = State.SCRIPT_DATA; |
| this._emitChars('>'); |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPED; |
| this._emitChars(REPLACEMENT_CHARACTER); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInScriptHtmlCommentLikeText); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPED; |
| this._emitCodePoint(cp); |
| } |
| } |
| } |
| // Script data double escaped less-than sign state |
| //------------------------------------------------------------------ |
| _stateScriptDataDoubleEscapedLessThanSign(cp) { |
| if (cp === $.SOLIDUS) { |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPE_END; |
| this._emitChars('/'); |
| } |
| else { |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPED; |
| this._stateScriptDataDoubleEscaped(cp); |
| } |
| } |
| // Script data double escape end state |
| //------------------------------------------------------------------ |
| _stateScriptDataDoubleEscapeEnd(cp) { |
| if (this.preprocessor.startsWith($$.SCRIPT, false) && |
| isScriptDataDoubleEscapeSequenceEnd(this.preprocessor.peek($$.SCRIPT.length))) { |
| this._emitCodePoint(cp); |
| for (let i = 0; i < $$.SCRIPT.length; i++) { |
| this._emitCodePoint(this._consume()); |
| } |
| this.state = State.SCRIPT_DATA_ESCAPED; |
| } |
| else if (!this._ensureHibernation()) { |
| this.state = State.SCRIPT_DATA_DOUBLE_ESCAPED; |
| this._stateScriptDataDoubleEscaped(cp); |
| } |
| } |
| // Before attribute name state |
| //------------------------------------------------------------------ |
| _stateBeforeAttributeName(cp) { |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| // Ignore whitespace |
| break; |
| } |
| case $.SOLIDUS: |
| case $.GREATER_THAN_SIGN: |
| case $.EOF: { |
| this.state = State.AFTER_ATTRIBUTE_NAME; |
| this._stateAfterAttributeName(cp); |
| break; |
| } |
| case $.EQUALS_SIGN: { |
| this._err(ERR.unexpectedEqualsSignBeforeAttributeName); |
| this._createAttr('='); |
| this.state = State.ATTRIBUTE_NAME; |
| break; |
| } |
| default: { |
| this._createAttr(''); |
| this.state = State.ATTRIBUTE_NAME; |
| this._stateAttributeName(cp); |
| } |
| } |
| } |
| // Attribute name state |
| //------------------------------------------------------------------ |
| _stateAttributeName(cp) { |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: |
| case $.SOLIDUS: |
| case $.GREATER_THAN_SIGN: |
| case $.EOF: { |
| this._leaveAttrName(); |
| this.state = State.AFTER_ATTRIBUTE_NAME; |
| this._stateAfterAttributeName(cp); |
| break; |
| } |
| case $.EQUALS_SIGN: { |
| this._leaveAttrName(); |
| this.state = State.BEFORE_ATTRIBUTE_VALUE; |
| break; |
| } |
| case $.QUOTATION_MARK: |
| case $.APOSTROPHE: |
| case $.LESS_THAN_SIGN: { |
| this._err(ERR.unexpectedCharacterInAttributeName); |
| this.currentAttr.name += String.fromCodePoint(cp); |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this.currentAttr.name += REPLACEMENT_CHARACTER; |
| break; |
| } |
| default: { |
| this.currentAttr.name += String.fromCodePoint(isAsciiUpper(cp) ? toAsciiLower(cp) : cp); |
| } |
| } |
| } |
| // After attribute name state |
| //------------------------------------------------------------------ |
| _stateAfterAttributeName(cp) { |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| // Ignore whitespace |
| break; |
| } |
| case $.SOLIDUS: { |
| this.state = State.SELF_CLOSING_START_TAG; |
| break; |
| } |
| case $.EQUALS_SIGN: { |
| this.state = State.BEFORE_ATTRIBUTE_VALUE; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this.state = State.DATA; |
| this.emitCurrentTagToken(); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInTag); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._createAttr(''); |
| this.state = State.ATTRIBUTE_NAME; |
| this._stateAttributeName(cp); |
| } |
| } |
| } |
| // Before attribute value state |
| //------------------------------------------------------------------ |
| _stateBeforeAttributeValue(cp) { |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| // Ignore whitespace |
| break; |
| } |
| case $.QUOTATION_MARK: { |
| this.state = State.ATTRIBUTE_VALUE_DOUBLE_QUOTED; |
| break; |
| } |
| case $.APOSTROPHE: { |
| this.state = State.ATTRIBUTE_VALUE_SINGLE_QUOTED; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.missingAttributeValue); |
| this.state = State.DATA; |
| this.emitCurrentTagToken(); |
| break; |
| } |
| default: { |
| this.state = State.ATTRIBUTE_VALUE_UNQUOTED; |
| this._stateAttributeValueUnquoted(cp); |
| } |
| } |
| } |
| // Attribute value (double-quoted) state |
| //------------------------------------------------------------------ |
| _stateAttributeValueDoubleQuoted(cp) { |
| switch (cp) { |
| case $.QUOTATION_MARK: { |
| this.state = State.AFTER_ATTRIBUTE_VALUE_QUOTED; |
| break; |
| } |
| case $.AMPERSAND: { |
| this._startCharacterReference(); |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this.currentAttr.value += REPLACEMENT_CHARACTER; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInTag); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this.currentAttr.value += String.fromCodePoint(cp); |
| } |
| } |
| } |
| // Attribute value (single-quoted) state |
| //------------------------------------------------------------------ |
| _stateAttributeValueSingleQuoted(cp) { |
| switch (cp) { |
| case $.APOSTROPHE: { |
| this.state = State.AFTER_ATTRIBUTE_VALUE_QUOTED; |
| break; |
| } |
| case $.AMPERSAND: { |
| this._startCharacterReference(); |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this.currentAttr.value += REPLACEMENT_CHARACTER; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInTag); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this.currentAttr.value += String.fromCodePoint(cp); |
| } |
| } |
| } |
| // Attribute value (unquoted) state |
| //------------------------------------------------------------------ |
| _stateAttributeValueUnquoted(cp) { |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| this._leaveAttrValue(); |
| this.state = State.BEFORE_ATTRIBUTE_NAME; |
| break; |
| } |
| case $.AMPERSAND: { |
| this._startCharacterReference(); |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._leaveAttrValue(); |
| this.state = State.DATA; |
| this.emitCurrentTagToken(); |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this.currentAttr.value += REPLACEMENT_CHARACTER; |
| break; |
| } |
| case $.QUOTATION_MARK: |
| case $.APOSTROPHE: |
| case $.LESS_THAN_SIGN: |
| case $.EQUALS_SIGN: |
| case $.GRAVE_ACCENT: { |
| this._err(ERR.unexpectedCharacterInUnquotedAttributeValue); |
| this.currentAttr.value += String.fromCodePoint(cp); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInTag); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this.currentAttr.value += String.fromCodePoint(cp); |
| } |
| } |
| } |
| // After attribute value (quoted) state |
| //------------------------------------------------------------------ |
| _stateAfterAttributeValueQuoted(cp) { |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| this._leaveAttrValue(); |
| this.state = State.BEFORE_ATTRIBUTE_NAME; |
| break; |
| } |
| case $.SOLIDUS: { |
| this._leaveAttrValue(); |
| this.state = State.SELF_CLOSING_START_TAG; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._leaveAttrValue(); |
| this.state = State.DATA; |
| this.emitCurrentTagToken(); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInTag); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._err(ERR.missingWhitespaceBetweenAttributes); |
| this.state = State.BEFORE_ATTRIBUTE_NAME; |
| this._stateBeforeAttributeName(cp); |
| } |
| } |
| } |
| // Self-closing start tag state |
| //------------------------------------------------------------------ |
| _stateSelfClosingStartTag(cp) { |
| switch (cp) { |
| case $.GREATER_THAN_SIGN: { |
| const token = this.currentToken; |
| token.selfClosing = true; |
| this.state = State.DATA; |
| this.emitCurrentTagToken(); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInTag); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._err(ERR.unexpectedSolidusInTag); |
| this.state = State.BEFORE_ATTRIBUTE_NAME; |
| this._stateBeforeAttributeName(cp); |
| } |
| } |
| } |
| // Bogus comment state |
| //------------------------------------------------------------------ |
| _stateBogusComment(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.GREATER_THAN_SIGN: { |
| this.state = State.DATA; |
| this.emitCurrentComment(token); |
| break; |
| } |
| case $.EOF: { |
| this.emitCurrentComment(token); |
| this._emitEOFToken(); |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| token.data += REPLACEMENT_CHARACTER; |
| break; |
| } |
| default: { |
| token.data += String.fromCodePoint(cp); |
| } |
| } |
| } |
| // Markup declaration open state |
| //------------------------------------------------------------------ |
| _stateMarkupDeclarationOpen(cp) { |
| if (this._consumeSequenceIfMatch($$.DASH_DASH, true)) { |
| this._createCommentToken($$.DASH_DASH.length + 1); |
| this.state = State.COMMENT_START; |
| } |
| else if (this._consumeSequenceIfMatch($$.DOCTYPE, false)) { |
| // NOTE: Doctypes tokens are created without fixed offsets. We keep track of the moment a doctype *might* start here. |
| this.currentLocation = this.getCurrentLocation($$.DOCTYPE.length + 1); |
| this.state = State.DOCTYPE; |
| } |
| else if (this._consumeSequenceIfMatch($$.CDATA_START, true)) { |
| if (this.inForeignNode) { |
| this.state = State.CDATA_SECTION; |
| } |
| else { |
| this._err(ERR.cdataInHtmlContent); |
| this._createCommentToken($$.CDATA_START.length + 1); |
| this.currentToken.data = '[CDATA['; |
| this.state = State.BOGUS_COMMENT; |
| } |
| } |
| //NOTE: Sequence lookups can be abrupted by hibernation. In that case, lookup |
| //results are no longer valid and we will need to start over. |
| else if (!this._ensureHibernation()) { |
| this._err(ERR.incorrectlyOpenedComment); |
| this._createCommentToken(2); |
| this.state = State.BOGUS_COMMENT; |
| this._stateBogusComment(cp); |
| } |
| } |
| // Comment start state |
| //------------------------------------------------------------------ |
| _stateCommentStart(cp) { |
| switch (cp) { |
| case $.HYPHEN_MINUS: { |
| this.state = State.COMMENT_START_DASH; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.abruptClosingOfEmptyComment); |
| this.state = State.DATA; |
| const token = this.currentToken; |
| this.emitCurrentComment(token); |
| break; |
| } |
| default: { |
| this.state = State.COMMENT; |
| this._stateComment(cp); |
| } |
| } |
| } |
| // Comment start dash state |
| //------------------------------------------------------------------ |
| _stateCommentStartDash(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.HYPHEN_MINUS: { |
| this.state = State.COMMENT_END; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.abruptClosingOfEmptyComment); |
| this.state = State.DATA; |
| this.emitCurrentComment(token); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInComment); |
| this.emitCurrentComment(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| token.data += '-'; |
| this.state = State.COMMENT; |
| this._stateComment(cp); |
| } |
| } |
| } |
| // Comment state |
| //------------------------------------------------------------------ |
| _stateComment(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.HYPHEN_MINUS: { |
| this.state = State.COMMENT_END_DASH; |
| break; |
| } |
| case $.LESS_THAN_SIGN: { |
| token.data += '<'; |
| this.state = State.COMMENT_LESS_THAN_SIGN; |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| token.data += REPLACEMENT_CHARACTER; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInComment); |
| this.emitCurrentComment(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| token.data += String.fromCodePoint(cp); |
| } |
| } |
| } |
| // Comment less-than sign state |
| //------------------------------------------------------------------ |
| _stateCommentLessThanSign(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.EXCLAMATION_MARK: { |
| token.data += '!'; |
| this.state = State.COMMENT_LESS_THAN_SIGN_BANG; |
| break; |
| } |
| case $.LESS_THAN_SIGN: { |
| token.data += '<'; |
| break; |
| } |
| default: { |
| this.state = State.COMMENT; |
| this._stateComment(cp); |
| } |
| } |
| } |
| // Comment less-than sign bang state |
| //------------------------------------------------------------------ |
| _stateCommentLessThanSignBang(cp) { |
| if (cp === $.HYPHEN_MINUS) { |
| this.state = State.COMMENT_LESS_THAN_SIGN_BANG_DASH; |
| } |
| else { |
| this.state = State.COMMENT; |
| this._stateComment(cp); |
| } |
| } |
| // Comment less-than sign bang dash state |
| //------------------------------------------------------------------ |
| _stateCommentLessThanSignBangDash(cp) { |
| if (cp === $.HYPHEN_MINUS) { |
| this.state = State.COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH; |
| } |
| else { |
| this.state = State.COMMENT_END_DASH; |
| this._stateCommentEndDash(cp); |
| } |
| } |
| // Comment less-than sign bang dash dash state |
| //------------------------------------------------------------------ |
| _stateCommentLessThanSignBangDashDash(cp) { |
| if (cp !== $.GREATER_THAN_SIGN && cp !== $.EOF) { |
| this._err(ERR.nestedComment); |
| } |
| this.state = State.COMMENT_END; |
| this._stateCommentEnd(cp); |
| } |
| // Comment end dash state |
| //------------------------------------------------------------------ |
| _stateCommentEndDash(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.HYPHEN_MINUS: { |
| this.state = State.COMMENT_END; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInComment); |
| this.emitCurrentComment(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| token.data += '-'; |
| this.state = State.COMMENT; |
| this._stateComment(cp); |
| } |
| } |
| } |
| // Comment end state |
| //------------------------------------------------------------------ |
| _stateCommentEnd(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.GREATER_THAN_SIGN: { |
| this.state = State.DATA; |
| this.emitCurrentComment(token); |
| break; |
| } |
| case $.EXCLAMATION_MARK: { |
| this.state = State.COMMENT_END_BANG; |
| break; |
| } |
| case $.HYPHEN_MINUS: { |
| token.data += '-'; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInComment); |
| this.emitCurrentComment(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| token.data += '--'; |
| this.state = State.COMMENT; |
| this._stateComment(cp); |
| } |
| } |
| } |
| // Comment end bang state |
| //------------------------------------------------------------------ |
| _stateCommentEndBang(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.HYPHEN_MINUS: { |
| token.data += '--!'; |
| this.state = State.COMMENT_END_DASH; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.incorrectlyClosedComment); |
| this.state = State.DATA; |
| this.emitCurrentComment(token); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInComment); |
| this.emitCurrentComment(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| token.data += '--!'; |
| this.state = State.COMMENT; |
| this._stateComment(cp); |
| } |
| } |
| } |
| // DOCTYPE state |
| //------------------------------------------------------------------ |
| _stateDoctype(cp) { |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| this.state = State.BEFORE_DOCTYPE_NAME; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this.state = State.BEFORE_DOCTYPE_NAME; |
| this._stateBeforeDoctypeName(cp); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| this._createDoctypeToken(null); |
| const token = this.currentToken; |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._err(ERR.missingWhitespaceBeforeDoctypeName); |
| this.state = State.BEFORE_DOCTYPE_NAME; |
| this._stateBeforeDoctypeName(cp); |
| } |
| } |
| } |
| // Before DOCTYPE name state |
| //------------------------------------------------------------------ |
| _stateBeforeDoctypeName(cp) { |
| if (isAsciiUpper(cp)) { |
| this._createDoctypeToken(String.fromCharCode(toAsciiLower(cp))); |
| this.state = State.DOCTYPE_NAME; |
| } |
| else |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| // Ignore whitespace |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| this._createDoctypeToken(REPLACEMENT_CHARACTER); |
| this.state = State.DOCTYPE_NAME; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.missingDoctypeName); |
| this._createDoctypeToken(null); |
| const token = this.currentToken; |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this.state = State.DATA; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| this._createDoctypeToken(null); |
| const token = this.currentToken; |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._createDoctypeToken(String.fromCodePoint(cp)); |
| this.state = State.DOCTYPE_NAME; |
| } |
| } |
| } |
| // DOCTYPE name state |
| //------------------------------------------------------------------ |
| _stateDoctypeName(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| this.state = State.AFTER_DOCTYPE_NAME; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this.state = State.DATA; |
| this.emitCurrentDoctype(token); |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| token.name += REPLACEMENT_CHARACTER; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| token.name += String.fromCodePoint(isAsciiUpper(cp) ? toAsciiLower(cp) : cp); |
| } |
| } |
| } |
| // After DOCTYPE name state |
| //------------------------------------------------------------------ |
| _stateAfterDoctypeName(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| // Ignore whitespace |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this.state = State.DATA; |
| this.emitCurrentDoctype(token); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| if (this._consumeSequenceIfMatch($$.PUBLIC, false)) { |
| this.state = State.AFTER_DOCTYPE_PUBLIC_KEYWORD; |
| } |
| else if (this._consumeSequenceIfMatch($$.SYSTEM, false)) { |
| this.state = State.AFTER_DOCTYPE_SYSTEM_KEYWORD; |
| } |
| //NOTE: sequence lookup can be abrupted by hibernation. In that case lookup |
| //results are no longer valid and we will need to start over. |
| else if (!this._ensureHibernation()) { |
| this._err(ERR.invalidCharacterSequenceAfterDoctypeName); |
| token.forceQuirks = true; |
| this.state = State.BOGUS_DOCTYPE; |
| this._stateBogusDoctype(cp); |
| } |
| } |
| } |
| } |
| // After DOCTYPE public keyword state |
| //------------------------------------------------------------------ |
| _stateAfterDoctypePublicKeyword(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| this.state = State.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER; |
| break; |
| } |
| case $.QUOTATION_MARK: { |
| this._err(ERR.missingWhitespaceAfterDoctypePublicKeyword); |
| token.publicId = ''; |
| this.state = State.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED; |
| break; |
| } |
| case $.APOSTROPHE: { |
| this._err(ERR.missingWhitespaceAfterDoctypePublicKeyword); |
| token.publicId = ''; |
| this.state = State.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.missingDoctypePublicIdentifier); |
| token.forceQuirks = true; |
| this.state = State.DATA; |
| this.emitCurrentDoctype(token); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._err(ERR.missingQuoteBeforeDoctypePublicIdentifier); |
| token.forceQuirks = true; |
| this.state = State.BOGUS_DOCTYPE; |
| this._stateBogusDoctype(cp); |
| } |
| } |
| } |
| // Before DOCTYPE public identifier state |
| //------------------------------------------------------------------ |
| _stateBeforeDoctypePublicIdentifier(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| // Ignore whitespace |
| break; |
| } |
| case $.QUOTATION_MARK: { |
| token.publicId = ''; |
| this.state = State.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED; |
| break; |
| } |
| case $.APOSTROPHE: { |
| token.publicId = ''; |
| this.state = State.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.missingDoctypePublicIdentifier); |
| token.forceQuirks = true; |
| this.state = State.DATA; |
| this.emitCurrentDoctype(token); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._err(ERR.missingQuoteBeforeDoctypePublicIdentifier); |
| token.forceQuirks = true; |
| this.state = State.BOGUS_DOCTYPE; |
| this._stateBogusDoctype(cp); |
| } |
| } |
| } |
| // DOCTYPE public identifier (double-quoted) state |
| //------------------------------------------------------------------ |
| _stateDoctypePublicIdentifierDoubleQuoted(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.QUOTATION_MARK: { |
| this.state = State.AFTER_DOCTYPE_PUBLIC_IDENTIFIER; |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| token.publicId += REPLACEMENT_CHARACTER; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.abruptDoctypePublicIdentifier); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this.state = State.DATA; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| token.publicId += String.fromCodePoint(cp); |
| } |
| } |
| } |
| // DOCTYPE public identifier (single-quoted) state |
| //------------------------------------------------------------------ |
| _stateDoctypePublicIdentifierSingleQuoted(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.APOSTROPHE: { |
| this.state = State.AFTER_DOCTYPE_PUBLIC_IDENTIFIER; |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| token.publicId += REPLACEMENT_CHARACTER; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.abruptDoctypePublicIdentifier); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this.state = State.DATA; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| token.publicId += String.fromCodePoint(cp); |
| } |
| } |
| } |
| // After DOCTYPE public identifier state |
| //------------------------------------------------------------------ |
| _stateAfterDoctypePublicIdentifier(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| this.state = State.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this.state = State.DATA; |
| this.emitCurrentDoctype(token); |
| break; |
| } |
| case $.QUOTATION_MARK: { |
| this._err(ERR.missingWhitespaceBetweenDoctypePublicAndSystemIdentifiers); |
| token.systemId = ''; |
| this.state = State.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED; |
| break; |
| } |
| case $.APOSTROPHE: { |
| this._err(ERR.missingWhitespaceBetweenDoctypePublicAndSystemIdentifiers); |
| token.systemId = ''; |
| this.state = State.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._err(ERR.missingQuoteBeforeDoctypeSystemIdentifier); |
| token.forceQuirks = true; |
| this.state = State.BOGUS_DOCTYPE; |
| this._stateBogusDoctype(cp); |
| } |
| } |
| } |
| // Between DOCTYPE public and system identifiers state |
| //------------------------------------------------------------------ |
| _stateBetweenDoctypePublicAndSystemIdentifiers(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| // Ignore whitespace |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this.emitCurrentDoctype(token); |
| this.state = State.DATA; |
| break; |
| } |
| case $.QUOTATION_MARK: { |
| token.systemId = ''; |
| this.state = State.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED; |
| break; |
| } |
| case $.APOSTROPHE: { |
| token.systemId = ''; |
| this.state = State.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._err(ERR.missingQuoteBeforeDoctypeSystemIdentifier); |
| token.forceQuirks = true; |
| this.state = State.BOGUS_DOCTYPE; |
| this._stateBogusDoctype(cp); |
| } |
| } |
| } |
| // After DOCTYPE system keyword state |
| //------------------------------------------------------------------ |
| _stateAfterDoctypeSystemKeyword(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| this.state = State.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER; |
| break; |
| } |
| case $.QUOTATION_MARK: { |
| this._err(ERR.missingWhitespaceAfterDoctypeSystemKeyword); |
| token.systemId = ''; |
| this.state = State.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED; |
| break; |
| } |
| case $.APOSTROPHE: { |
| this._err(ERR.missingWhitespaceAfterDoctypeSystemKeyword); |
| token.systemId = ''; |
| this.state = State.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.missingDoctypeSystemIdentifier); |
| token.forceQuirks = true; |
| this.state = State.DATA; |
| this.emitCurrentDoctype(token); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._err(ERR.missingQuoteBeforeDoctypeSystemIdentifier); |
| token.forceQuirks = true; |
| this.state = State.BOGUS_DOCTYPE; |
| this._stateBogusDoctype(cp); |
| } |
| } |
| } |
| // Before DOCTYPE system identifier state |
| //------------------------------------------------------------------ |
| _stateBeforeDoctypeSystemIdentifier(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| // Ignore whitespace |
| break; |
| } |
| case $.QUOTATION_MARK: { |
| token.systemId = ''; |
| this.state = State.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED; |
| break; |
| } |
| case $.APOSTROPHE: { |
| token.systemId = ''; |
| this.state = State.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.missingDoctypeSystemIdentifier); |
| token.forceQuirks = true; |
| this.state = State.DATA; |
| this.emitCurrentDoctype(token); |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._err(ERR.missingQuoteBeforeDoctypeSystemIdentifier); |
| token.forceQuirks = true; |
| this.state = State.BOGUS_DOCTYPE; |
| this._stateBogusDoctype(cp); |
| } |
| } |
| } |
| // DOCTYPE system identifier (double-quoted) state |
| //------------------------------------------------------------------ |
| _stateDoctypeSystemIdentifierDoubleQuoted(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.QUOTATION_MARK: { |
| this.state = State.AFTER_DOCTYPE_SYSTEM_IDENTIFIER; |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| token.systemId += REPLACEMENT_CHARACTER; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.abruptDoctypeSystemIdentifier); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this.state = State.DATA; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| token.systemId += String.fromCodePoint(cp); |
| } |
| } |
| } |
| // DOCTYPE system identifier (single-quoted) state |
| //------------------------------------------------------------------ |
| _stateDoctypeSystemIdentifierSingleQuoted(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.APOSTROPHE: { |
| this.state = State.AFTER_DOCTYPE_SYSTEM_IDENTIFIER; |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| token.systemId += REPLACEMENT_CHARACTER; |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this._err(ERR.abruptDoctypeSystemIdentifier); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this.state = State.DATA; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| token.systemId += String.fromCodePoint(cp); |
| } |
| } |
| } |
| // After DOCTYPE system identifier state |
| //------------------------------------------------------------------ |
| _stateAfterDoctypeSystemIdentifier(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.SPACE: |
| case $.LINE_FEED: |
| case $.TABULATION: |
| case $.FORM_FEED: { |
| // Ignore whitespace |
| break; |
| } |
| case $.GREATER_THAN_SIGN: { |
| this.emitCurrentDoctype(token); |
| this.state = State.DATA; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInDoctype); |
| token.forceQuirks = true; |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._err(ERR.unexpectedCharacterAfterDoctypeSystemIdentifier); |
| this.state = State.BOGUS_DOCTYPE; |
| this._stateBogusDoctype(cp); |
| } |
| } |
| } |
| // Bogus DOCTYPE state |
| //------------------------------------------------------------------ |
| _stateBogusDoctype(cp) { |
| const token = this.currentToken; |
| switch (cp) { |
| case $.GREATER_THAN_SIGN: { |
| this.emitCurrentDoctype(token); |
| this.state = State.DATA; |
| break; |
| } |
| case $.NULL: { |
| this._err(ERR.unexpectedNullCharacter); |
| break; |
| } |
| case $.EOF: { |
| this.emitCurrentDoctype(token); |
| this._emitEOFToken(); |
| break; |
| } |
| default: |
| // Do nothing |
| } |
| } |
| // CDATA section state |
| //------------------------------------------------------------------ |
| _stateCdataSection(cp) { |
| switch (cp) { |
| case $.RIGHT_SQUARE_BRACKET: { |
| this.state = State.CDATA_SECTION_BRACKET; |
| break; |
| } |
| case $.EOF: { |
| this._err(ERR.eofInCdata); |
| this._emitEOFToken(); |
| break; |
| } |
| default: { |
| this._emitCodePoint(cp); |
| } |
| } |
| } |
| // CDATA section bracket state |
| //------------------------------------------------------------------ |
| _stateCdataSectionBracket(cp) { |
| if (cp === $.RIGHT_SQUARE_BRACKET) { |
| this.state = State.CDATA_SECTION_END; |
| } |
| else { |
| this._emitChars(']'); |
| this.state = State.CDATA_SECTION; |
| this._stateCdataSection(cp); |
| } |
| } |
| // CDATA section end state |
| //------------------------------------------------------------------ |
| _stateCdataSectionEnd(cp) { |
| switch (cp) { |
| case $.GREATER_THAN_SIGN: { |
| this.state = State.DATA; |
| break; |
| } |
| case $.RIGHT_SQUARE_BRACKET: { |
| this._emitChars(']'); |
| break; |
| } |
| default: { |
| this._emitChars(']]'); |
| this.state = State.CDATA_SECTION; |
| this._stateCdataSection(cp); |
| } |
| } |
| } |
| // Character reference state |
| //------------------------------------------------------------------ |
| _stateCharacterReference() { |
| let length = this.entityDecoder.write(this.preprocessor.html, this.preprocessor.pos); |
| if (length < 0) { |
| if (this.preprocessor.lastChunkWritten) { |
| length = this.entityDecoder.end(); |
| } |
| else { |
| // Wait for the rest of the entity. |
| this.active = false; |
| // Mark the entire buffer as read. |
| this.preprocessor.pos = this.preprocessor.html.length - 1; |
| this.consumedAfterSnapshot = 0; |
| this.preprocessor.endOfChunkHit = true; |
| return; |
| } |
| } |
| if (length === 0) { |
| // This was not a valid entity. Go back to the beginning, and |
| // figure out what to do. |
| this.preprocessor.pos = this.entityStartPos; |
| this._flushCodePointConsumedAsCharacterReference($.AMPERSAND); |
| this.state = |
| !this._isCharacterReferenceInAttribute() && isAsciiAlphaNumeric(this.preprocessor.peek(1)) |
| ? State.AMBIGUOUS_AMPERSAND |
| : this.returnState; |
| } |
| else { |
| // We successfully parsed an entity. Switch to the return state. |
| this.state = this.returnState; |
| } |
| } |
| // Ambiguos ampersand state |
| //------------------------------------------------------------------ |
| _stateAmbiguousAmpersand(cp) { |
| if (isAsciiAlphaNumeric(cp)) { |
| this._flushCodePointConsumedAsCharacterReference(cp); |
| } |
| else { |
| if (cp === $.SEMICOLON) { |
| this._err(ERR.unknownNamedCharacterReference); |
| } |
| this.state = this.returnState; |
| this._callState(cp); |
| } |
| } |
| } |