You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					2064 lines
				
				72 KiB
			
		
		
			
		
	
	
					2064 lines
				
				72 KiB
			| 
											2 years ago
										 | "use strict"; | ||
|  | Object.defineProperty(exports, "__esModule", { value: true }); | ||
|  | const ed5 = require("xmlchars/xml/1.0/ed5"); | ||
|  | const ed2 = require("xmlchars/xml/1.1/ed2"); | ||
|  | const NSed3 = require("xmlchars/xmlns/1.0/ed3"); | ||
|  | var isS = ed5.isS; | ||
|  | var isChar10 = ed5.isChar; | ||
|  | var isNameStartChar = ed5.isNameStartChar; | ||
|  | var isNameChar = ed5.isNameChar; | ||
|  | var S_LIST = ed5.S_LIST; | ||
|  | var NAME_RE = ed5.NAME_RE; | ||
|  | var isChar11 = ed2.isChar; | ||
|  | var isNCNameStartChar = NSed3.isNCNameStartChar; | ||
|  | var isNCNameChar = NSed3.isNCNameChar; | ||
|  | var NC_NAME_RE = NSed3.NC_NAME_RE; | ||
|  | const XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"; | ||
|  | const XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/"; | ||
|  | const rootNS = { | ||
|  |     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||
|  |     __proto__: null, | ||
|  |     xml: XML_NAMESPACE, | ||
|  |     xmlns: XMLNS_NAMESPACE, | ||
|  | }; | ||
|  | const XML_ENTITIES = { | ||
|  |     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||
|  |     __proto__: null, | ||
|  |     amp: "&", | ||
|  |     gt: ">", | ||
|  |     lt: "<", | ||
|  |     quot: "\"", | ||
|  |     apos: "'", | ||
|  | }; | ||
|  | // EOC: end-of-chunk
 | ||
|  | const EOC = -1; | ||
|  | const NL_LIKE = -2; | ||
|  | const S_BEGIN = 0; // Initial state.
 | ||
|  | const S_BEGIN_WHITESPACE = 1; // leading whitespace
 | ||
|  | const S_DOCTYPE = 2; // <!DOCTYPE
 | ||
|  | const S_DOCTYPE_QUOTE = 3; // <!DOCTYPE "//blah
 | ||
|  | const S_DTD = 4; // <!DOCTYPE "//blah" [ ...
 | ||
|  | const S_DTD_QUOTED = 5; // <!DOCTYPE "//blah" [ "foo
 | ||
|  | const S_DTD_OPEN_WAKA = 6; | ||
|  | const S_DTD_OPEN_WAKA_BANG = 7; | ||
|  | const S_DTD_COMMENT = 8; // <!--
 | ||
|  | const S_DTD_COMMENT_ENDING = 9; // <!-- blah -
 | ||
|  | const S_DTD_COMMENT_ENDED = 10; // <!-- blah --
 | ||
|  | const S_DTD_PI = 11; // <?
 | ||
|  | const S_DTD_PI_ENDING = 12; // <?hi "there" ?
 | ||
|  | const S_TEXT = 13; // general stuff
 | ||
|  | const S_ENTITY = 14; // & and such
 | ||
|  | const S_OPEN_WAKA = 15; // <
 | ||
|  | const S_OPEN_WAKA_BANG = 16; // <!...
 | ||
|  | const S_COMMENT = 17; // <!--
 | ||
|  | const S_COMMENT_ENDING = 18; // <!-- blah -
 | ||
|  | const S_COMMENT_ENDED = 19; // <!-- blah --
 | ||
|  | const S_CDATA = 20; // <![CDATA[ something
 | ||
|  | const S_CDATA_ENDING = 21; // ]
 | ||
|  | const S_CDATA_ENDING_2 = 22; // ]]
 | ||
|  | const S_PI_FIRST_CHAR = 23; // <?hi, first char
 | ||
|  | const S_PI_REST = 24; // <?hi, rest of the name
 | ||
|  | const S_PI_BODY = 25; // <?hi there
 | ||
|  | const S_PI_ENDING = 26; // <?hi "there" ?
 | ||
|  | const S_XML_DECL_NAME_START = 27; // <?xml
 | ||
|  | const S_XML_DECL_NAME = 28; // <?xml foo
 | ||
|  | const S_XML_DECL_EQ = 29; // <?xml foo=
 | ||
|  | const S_XML_DECL_VALUE_START = 30; // <?xml foo=
 | ||
|  | const S_XML_DECL_VALUE = 31; // <?xml foo="bar"
 | ||
|  | const S_XML_DECL_SEPARATOR = 32; // <?xml foo="bar"
 | ||
|  | const S_XML_DECL_ENDING = 33; // <?xml ... ?
 | ||
|  | const S_OPEN_TAG = 34; // <strong
 | ||
|  | const S_OPEN_TAG_SLASH = 35; // <strong /
 | ||
|  | const S_ATTRIB = 36; // <a
 | ||
|  | const S_ATTRIB_NAME = 37; // <a foo
 | ||
|  | const S_ATTRIB_NAME_SAW_WHITE = 38; // <a foo _
 | ||
|  | const S_ATTRIB_VALUE = 39; // <a foo=
 | ||
|  | const S_ATTRIB_VALUE_QUOTED = 40; // <a foo="bar
 | ||
|  | const S_ATTRIB_VALUE_CLOSED = 41; // <a foo="bar"
 | ||
|  | const S_ATTRIB_VALUE_UNQUOTED = 42; // <a foo=bar
 | ||
|  | const S_CLOSE_TAG = 43; // </a
 | ||
|  | const S_CLOSE_TAG_SAW_WHITE = 44; // </a   >
 | ||
|  | const TAB = 9; | ||
|  | const NL = 0xA; | ||
|  | const CR = 0xD; | ||
|  | const SPACE = 0x20; | ||
|  | const BANG = 0x21; | ||
|  | const DQUOTE = 0x22; | ||
|  | const AMP = 0x26; | ||
|  | const SQUOTE = 0x27; | ||
|  | const MINUS = 0x2D; | ||
|  | const FORWARD_SLASH = 0x2F; | ||
|  | const SEMICOLON = 0x3B; | ||
|  | const LESS = 0x3C; | ||
|  | const EQUAL = 0x3D; | ||
|  | const GREATER = 0x3E; | ||
|  | const QUESTION = 0x3F; | ||
|  | const OPEN_BRACKET = 0x5B; | ||
|  | const CLOSE_BRACKET = 0x5D; | ||
|  | const NEL = 0x85; | ||
|  | const LS = 0x2028; // Line Separator
 | ||
|  | const isQuote = (c) => c === DQUOTE || c === SQUOTE; | ||
|  | const QUOTES = [DQUOTE, SQUOTE]; | ||
|  | const DOCTYPE_TERMINATOR = [...QUOTES, OPEN_BRACKET, GREATER]; | ||
|  | const DTD_TERMINATOR = [...QUOTES, LESS, CLOSE_BRACKET]; | ||
|  | const XML_DECL_NAME_TERMINATOR = [EQUAL, QUESTION, ...S_LIST]; | ||
|  | const ATTRIB_VALUE_UNQUOTED_TERMINATOR = [...S_LIST, GREATER, AMP, LESS]; | ||
|  | function nsPairCheck(parser, prefix, uri) { | ||
|  |     switch (prefix) { | ||
|  |         case "xml": | ||
|  |             if (uri !== XML_NAMESPACE) { | ||
|  |                 parser.fail(`xml prefix must be bound to ${XML_NAMESPACE}.`); | ||
|  |             } | ||
|  |             break; | ||
|  |         case "xmlns": | ||
|  |             if (uri !== XMLNS_NAMESPACE) { | ||
|  |                 parser.fail(`xmlns prefix must be bound to ${XMLNS_NAMESPACE}.`); | ||
|  |             } | ||
|  |             break; | ||
|  |         default: | ||
|  |     } | ||
|  |     switch (uri) { | ||
|  |         case XMLNS_NAMESPACE: | ||
|  |             parser.fail(prefix === "" ? | ||
|  |                 `the default namespace may not be set to ${uri}.` : | ||
|  |                 `may not assign a prefix (even "xmlns") to the URI \
 | ||
|  | ${XMLNS_NAMESPACE}.`);
 | ||
|  |             break; | ||
|  |         case XML_NAMESPACE: | ||
|  |             switch (prefix) { | ||
|  |                 case "xml": | ||
|  |                     // Assinging the XML namespace to "xml" is fine.
 | ||
|  |                     break; | ||
|  |                 case "": | ||
|  |                     parser.fail(`the default namespace may not be set to ${uri}.`); | ||
|  |                     break; | ||
|  |                 default: | ||
|  |                     parser.fail("may not assign the xml namespace to another prefix."); | ||
|  |             } | ||
|  |             break; | ||
|  |         default: | ||
|  |     } | ||
|  | } | ||
|  | function nsMappingCheck(parser, mapping) { | ||
|  |     for (const local of Object.keys(mapping)) { | ||
|  |         nsPairCheck(parser, local, mapping[local]); | ||
|  |     } | ||
|  | } | ||
|  | const isNCName = (name) => NC_NAME_RE.test(name); | ||
|  | const isName = (name) => NAME_RE.test(name); | ||
|  | const FORBIDDEN_START = 0; | ||
|  | const FORBIDDEN_BRACKET = 1; | ||
|  | const FORBIDDEN_BRACKET_BRACKET = 2; | ||
|  | /** | ||
|  |  * The list of supported events. | ||
|  |  */ | ||
|  | exports.EVENTS = [ | ||
|  |     "xmldecl", | ||
|  |     "text", | ||
|  |     "processinginstruction", | ||
|  |     "doctype", | ||
|  |     "comment", | ||
|  |     "opentagstart", | ||
|  |     "attribute", | ||
|  |     "opentag", | ||
|  |     "closetag", | ||
|  |     "cdata", | ||
|  |     "error", | ||
|  |     "end", | ||
|  |     "ready", | ||
|  | ]; | ||
|  | const EVENT_NAME_TO_HANDLER_NAME = { | ||
|  |     xmldecl: "xmldeclHandler", | ||
|  |     text: "textHandler", | ||
|  |     processinginstruction: "piHandler", | ||
|  |     doctype: "doctypeHandler", | ||
|  |     comment: "commentHandler", | ||
|  |     opentagstart: "openTagStartHandler", | ||
|  |     attribute: "attributeHandler", | ||
|  |     opentag: "openTagHandler", | ||
|  |     closetag: "closeTagHandler", | ||
|  |     cdata: "cdataHandler", | ||
|  |     error: "errorHandler", | ||
|  |     end: "endHandler", | ||
|  |     ready: "readyHandler", | ||
|  | }; | ||
|  | class SaxesParser { | ||
|  |     /** | ||
|  |      * @param opt The parser options. | ||
|  |      */ | ||
|  |     constructor(opt) { | ||
|  |         this.opt = opt !== null && opt !== void 0 ? opt : {}; | ||
|  |         this.fragmentOpt = !!this.opt.fragment; | ||
|  |         const xmlnsOpt = this.xmlnsOpt = !!this.opt.xmlns; | ||
|  |         this.trackPosition = this.opt.position !== false; | ||
|  |         this.fileName = this.opt.fileName; | ||
|  |         if (xmlnsOpt) { | ||
|  |             // This is the function we use to perform name checks on PIs and entities.
 | ||
|  |             // When namespaces are used, colons are not allowed in PI target names or
 | ||
|  |             // entity names. So the check depends on whether namespaces are used. See:
 | ||
|  |             //
 | ||
|  |             // https://www.w3.org/XML/xml-names-19990114-errata.html
 | ||
|  |             // NE08
 | ||
|  |             //
 | ||
|  |             this.nameStartCheck = isNCNameStartChar; | ||
|  |             this.nameCheck = isNCNameChar; | ||
|  |             this.isName = isNCName; | ||
|  |             // eslint-disable-next-line @typescript-eslint/unbound-method
 | ||
|  |             this.processAttribs = this.processAttribsNS; | ||
|  |             // eslint-disable-next-line @typescript-eslint/unbound-method
 | ||
|  |             this.pushAttrib = this.pushAttribNS; | ||
|  |             // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||
|  |             this.ns = Object.assign({ __proto__: null }, rootNS); | ||
|  |             const additional = this.opt.additionalNamespaces; | ||
|  |             if (additional != null) { | ||
|  |                 nsMappingCheck(this, additional); | ||
|  |                 Object.assign(this.ns, additional); | ||
|  |             } | ||
|  |         } | ||
|  |         else { | ||
|  |             this.nameStartCheck = isNameStartChar; | ||
|  |             this.nameCheck = isNameChar; | ||
|  |             this.isName = isName; | ||
|  |             // eslint-disable-next-line @typescript-eslint/unbound-method
 | ||
|  |             this.processAttribs = this.processAttribsPlain; | ||
|  |             // eslint-disable-next-line @typescript-eslint/unbound-method
 | ||
|  |             this.pushAttrib = this.pushAttribPlain; | ||
|  |         } | ||
|  |         //
 | ||
|  |         // The order of the members in this table needs to correspond to the state
 | ||
|  |         // numbers given to the states that correspond to the methods being recorded
 | ||
|  |         // here.
 | ||
|  |         //
 | ||
|  |         this.stateTable = [ | ||
|  |             /* eslint-disable @typescript-eslint/unbound-method */ | ||
|  |             this.sBegin, | ||
|  |             this.sBeginWhitespace, | ||
|  |             this.sDoctype, | ||
|  |             this.sDoctypeQuote, | ||
|  |             this.sDTD, | ||
|  |             this.sDTDQuoted, | ||
|  |             this.sDTDOpenWaka, | ||
|  |             this.sDTDOpenWakaBang, | ||
|  |             this.sDTDComment, | ||
|  |             this.sDTDCommentEnding, | ||
|  |             this.sDTDCommentEnded, | ||
|  |             this.sDTDPI, | ||
|  |             this.sDTDPIEnding, | ||
|  |             this.sText, | ||
|  |             this.sEntity, | ||
|  |             this.sOpenWaka, | ||
|  |             this.sOpenWakaBang, | ||
|  |             this.sComment, | ||
|  |             this.sCommentEnding, | ||
|  |             this.sCommentEnded, | ||
|  |             this.sCData, | ||
|  |             this.sCDataEnding, | ||
|  |             this.sCDataEnding2, | ||
|  |             this.sPIFirstChar, | ||
|  |             this.sPIRest, | ||
|  |             this.sPIBody, | ||
|  |             this.sPIEnding, | ||
|  |             this.sXMLDeclNameStart, | ||
|  |             this.sXMLDeclName, | ||
|  |             this.sXMLDeclEq, | ||
|  |             this.sXMLDeclValueStart, | ||
|  |             this.sXMLDeclValue, | ||
|  |             this.sXMLDeclSeparator, | ||
|  |             this.sXMLDeclEnding, | ||
|  |             this.sOpenTag, | ||
|  |             this.sOpenTagSlash, | ||
|  |             this.sAttrib, | ||
|  |             this.sAttribName, | ||
|  |             this.sAttribNameSawWhite, | ||
|  |             this.sAttribValue, | ||
|  |             this.sAttribValueQuoted, | ||
|  |             this.sAttribValueClosed, | ||
|  |             this.sAttribValueUnquoted, | ||
|  |             this.sCloseTag, | ||
|  |             this.sCloseTagSawWhite, | ||
|  |         ]; | ||
|  |         this._init(); | ||
|  |     } | ||
|  |     /** | ||
|  |      * Indicates whether or not the parser is closed. If ``true``, wait for | ||
|  |      * the ``ready`` event to write again. | ||
|  |      */ | ||
|  |     get closed() { | ||
|  |         return this._closed; | ||
|  |     } | ||
|  |     _init() { | ||
|  |         var _a; | ||
|  |         this.openWakaBang = ""; | ||
|  |         this.text = ""; | ||
|  |         this.name = ""; | ||
|  |         this.piTarget = ""; | ||
|  |         this.entity = ""; | ||
|  |         this.q = null; | ||
|  |         this.tags = []; | ||
|  |         this.tag = null; | ||
|  |         this.topNS = null; | ||
|  |         this.chunk = ""; | ||
|  |         this.chunkPosition = 0; | ||
|  |         this.i = 0; | ||
|  |         this.prevI = 0; | ||
|  |         this.carriedFromPrevious = undefined; | ||
|  |         this.forbiddenState = FORBIDDEN_START; | ||
|  |         this.attribList = []; | ||
|  |         // The logic is organized so as to minimize the need to check
 | ||
|  |         // this.opt.fragment while parsing.
 | ||
|  |         const { fragmentOpt } = this; | ||
|  |         this.state = fragmentOpt ? S_TEXT : S_BEGIN; | ||
|  |         // We want these to be all true if we are dealing with a fragment.
 | ||
|  |         this.reportedTextBeforeRoot = this.reportedTextAfterRoot = this.closedRoot = | ||
|  |             this.sawRoot = fragmentOpt; | ||
|  |         // An XML declaration is intially possible only when parsing whole
 | ||
|  |         // documents.
 | ||
|  |         this.xmlDeclPossible = !fragmentOpt; | ||
|  |         this.xmlDeclExpects = ["version"]; | ||
|  |         this.entityReturnState = undefined; | ||
|  |         let { defaultXMLVersion } = this.opt; | ||
|  |         if (defaultXMLVersion === undefined) { | ||
|  |             if (this.opt.forceXMLVersion === true) { | ||
|  |                 throw new Error("forceXMLVersion set but defaultXMLVersion is not set"); | ||
|  |             } | ||
|  |             defaultXMLVersion = "1.0"; | ||
|  |         } | ||
|  |         this.setXMLVersion(defaultXMLVersion); | ||
|  |         this.positionAtNewLine = 0; | ||
|  |         this.doctype = false; | ||
|  |         this._closed = false; | ||
|  |         this.xmlDecl = { | ||
|  |             version: undefined, | ||
|  |             encoding: undefined, | ||
|  |             standalone: undefined, | ||
|  |         }; | ||
|  |         this.line = 1; | ||
|  |         this.column = 0; | ||
|  |         this.ENTITIES = Object.create(XML_ENTITIES); | ||
|  |         // eslint-disable-next-line no-unused-expressions
 | ||
|  |         (_a = this.readyHandler) === null || _a === void 0 ? void 0 : _a.call(this); | ||
|  |     } | ||
|  |     /** | ||
|  |      * The stream position the parser is currently looking at. This field is | ||
|  |      * zero-based. | ||
|  |      * | ||
|  |      * This field is not based on counting Unicode characters but is to be | ||
|  |      * interpreted as a plain index into a JavaScript string. | ||
|  |      */ | ||
|  |     get position() { | ||
|  |         return this.chunkPosition + this.i; | ||
|  |     } | ||
|  |     /** | ||
|  |      * The column number of the next character to be read by the parser.  * | ||
|  |      * This field is zero-based. (The first column in a line is 0.) | ||
|  |      * | ||
|  |      * This field reports the index at which the next character would be in the | ||
|  |      * line if the line were represented as a JavaScript string.  Note that this | ||
|  |      * *can* be different to a count based on the number of *Unicode characters* | ||
|  |      * due to how JavaScript handles astral plane characters. | ||
|  |      * | ||
|  |      * See [[column]] for a number that corresponds to a count of Unicode | ||
|  |      * characters. | ||
|  |      */ | ||
|  |     get columnIndex() { | ||
|  |         return this.position - this.positionAtNewLine; | ||
|  |     } | ||
|  |     /** | ||
|  |      * Set an event listener on an event. The parser supports one handler per | ||
|  |      * event type. If you try to set an event handler over an existing handler, | ||
|  |      * the old handler is silently overwritten. | ||
|  |      * | ||
|  |      * @param name The event to listen to. | ||
|  |      * | ||
|  |      * @param handler The handler to set. | ||
|  |      */ | ||
|  |     on(name, handler) { | ||
|  |         // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||
|  |         this[EVENT_NAME_TO_HANDLER_NAME[name]] = handler; | ||
|  |     } | ||
|  |     /** | ||
|  |      * Unset an event handler. | ||
|  |      * | ||
|  |      * @parma name The event to stop listening to. | ||
|  |      */ | ||
|  |     off(name) { | ||
|  |         // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||
|  |         this[EVENT_NAME_TO_HANDLER_NAME[name]] = undefined; | ||
|  |     } | ||
|  |     /** | ||
|  |      * Make an error object. The error object will have a message that contains | ||
|  |      * the ``fileName`` option passed at the creation of the parser. If position | ||
|  |      * tracking was turned on, it will also have line and column number | ||
|  |      * information. | ||
|  |      * | ||
|  |      * @param message The message describing the error to report. | ||
|  |      * | ||
|  |      * @returns An error object with a properly formatted message. | ||
|  |      */ | ||
|  |     makeError(message) { | ||
|  |         var _a; | ||
|  |         let msg = (_a = this.fileName) !== null && _a !== void 0 ? _a : ""; | ||
|  |         if (this.trackPosition) { | ||
|  |             if (msg.length > 0) { | ||
|  |                 msg += ":"; | ||
|  |             } | ||
|  |             msg += `${this.line}:${this.column}`; | ||
|  |         } | ||
|  |         if (msg.length > 0) { | ||
|  |             msg += ": "; | ||
|  |         } | ||
|  |         return new Error(msg + message); | ||
|  |     } | ||
|  |     /** | ||
|  |      * Report a parsing error. This method is made public so that client code may | ||
|  |      * check for issues that are outside the scope of this project and can report | ||
|  |      * errors. | ||
|  |      * | ||
|  |      * @param message The error to report. | ||
|  |      * | ||
|  |      * @returns this | ||
|  |      */ | ||
|  |     fail(message) { | ||
|  |         const err = this.makeError(message); | ||
|  |         const handler = this.errorHandler; | ||
|  |         if (handler === undefined) { | ||
|  |             throw err; | ||
|  |         } | ||
|  |         else { | ||
|  |             handler(err); | ||
|  |         } | ||
|  |         return this; | ||
|  |     } | ||
|  |     /** | ||
|  |      * Write a XML data to the parser. | ||
|  |      * | ||
|  |      * @param chunk The XML data to write. | ||
|  |      * | ||
|  |      * @returns this | ||
|  |      */ | ||
|  |     write(chunk) { | ||
|  |         if (this.closed) { | ||
|  |             return this.fail("cannot write after close; assign an onready handler."); | ||
|  |         } | ||
|  |         let end = false; | ||
|  |         if (chunk === null) { | ||
|  |             // We cannot return immediately because carriedFromPrevious may need
 | ||
|  |             // processing.
 | ||
|  |             end = true; | ||
|  |             chunk = ""; | ||
|  |         } | ||
|  |         else if (typeof chunk === "object") { | ||
|  |             chunk = chunk.toString(); | ||
|  |         } | ||
|  |         // We checked if performing a pre-decomposition of the string into an array
 | ||
|  |         // of single complete characters (``Array.from(chunk)``) would be faster
 | ||
|  |         // than the current repeated calls to ``charCodeAt``. As of August 2018, it
 | ||
|  |         // isn't. (There may be Node-specific code that would perform faster than
 | ||
|  |         // ``Array.from`` but don't want to be dependent on Node.)
 | ||
|  |         if (this.carriedFromPrevious !== undefined) { | ||
|  |             // The previous chunk had char we must carry over.
 | ||
|  |             chunk = `${this.carriedFromPrevious}${chunk}`; | ||
|  |             this.carriedFromPrevious = undefined; | ||
|  |         } | ||
|  |         let limit = chunk.length; | ||
|  |         const lastCode = chunk.charCodeAt(limit - 1); | ||
|  |         if (!end && | ||
|  |             // A trailing CR or surrogate must be carried over to the next
 | ||
|  |             // chunk.
 | ||
|  |             (lastCode === CR || (lastCode >= 0xD800 && lastCode <= 0xDBFF))) { | ||
|  |             // The chunk ends with a character that must be carried over. We cannot
 | ||
|  |             // know how to handle it until we get the next chunk or the end of the
 | ||
|  |             // stream. So save it for later.
 | ||
|  |             this.carriedFromPrevious = chunk[limit - 1]; | ||
|  |             limit--; | ||
|  |             chunk = chunk.slice(0, limit); | ||
|  |         } | ||
|  |         const { stateTable } = this; | ||
|  |         this.chunk = chunk; | ||
|  |         this.i = 0; | ||
|  |         while (this.i < limit) { | ||
|  |             // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||
|  |             stateTable[this.state].call(this); | ||
|  |         } | ||
|  |         this.chunkPosition += limit; | ||
|  |         return end ? this.end() : this; | ||
|  |     } | ||
|  |     /** | ||
|  |      * Close the current stream. Perform final well-formedness checks and reset | ||
|  |      * the parser tstate. | ||
|  |      * | ||
|  |      * @returns this | ||
|  |      */ | ||
|  |     close() { | ||
|  |         return this.write(null); | ||
|  |     } | ||
|  |     /** | ||
|  |      * Get a single code point out of the current chunk. This updates the current | ||
|  |      * position if we do position tracking. | ||
|  |      * | ||
|  |      * This is the algorithm to use for XML 1.0. | ||
|  |      * | ||
|  |      * @returns The character read. | ||
|  |      */ | ||
|  |     getCode10() { | ||
|  |         const { chunk, i } = this; | ||
|  |         this.prevI = i; | ||
|  |         // Yes, we do this instead of doing this.i++. Doing it this way, we do not
 | ||
|  |         // read this.i again, which is a bit faster.
 | ||
|  |         this.i = i + 1; | ||
|  |         if (i >= chunk.length) { | ||
|  |             return EOC; | ||
|  |         } | ||
|  |         // Using charCodeAt and handling the surrogates ourselves is faster
 | ||
|  |         // than using codePointAt.
 | ||
|  |         const code = chunk.charCodeAt(i); | ||
|  |         this.column++; | ||
|  |         if (code < 0xD800) { | ||
|  |             if (code >= SPACE || code === TAB) { | ||
|  |                 return code; | ||
|  |             } | ||
|  |             switch (code) { | ||
|  |                 case NL: | ||
|  |                     this.line++; | ||
|  |                     this.column = 0; | ||
|  |                     this.positionAtNewLine = this.position; | ||
|  |                     return NL; | ||
|  |                 case CR: | ||
|  |                     // We may get NaN if we read past the end of the chunk, which is fine.
 | ||
|  |                     if (chunk.charCodeAt(i + 1) === NL) { | ||
|  |                         // A \r\n sequence is converted to \n so we have to skip over the
 | ||
|  |                         // next character. We already know it has a size of 1 so ++ is fine
 | ||
|  |                         // here.
 | ||
|  |                         this.i = i + 2; | ||
|  |                     } | ||
|  |                     // Otherwise, a \r is just converted to \n, so we don't have to skip
 | ||
|  |                     // ahead.
 | ||
|  |                     // In either case, \r becomes \n.
 | ||
|  |                     this.line++; | ||
|  |                     this.column = 0; | ||
|  |                     this.positionAtNewLine = this.position; | ||
|  |                     return NL_LIKE; | ||
|  |                 default: | ||
|  |                     // If we get here, then code < SPACE and it is not NL CR or TAB.
 | ||
|  |                     this.fail("disallowed character."); | ||
|  |                     return code; | ||
|  |             } | ||
|  |         } | ||
|  |         if (code > 0xDBFF) { | ||
|  |             // This is a specialized version of isChar10 that takes into account
 | ||
|  |             // that in this context code > 0xDBFF and code <= 0xFFFF. So it does not
 | ||
|  |             // test cases that don't need testing.
 | ||
|  |             if (!(code >= 0xE000 && code <= 0xFFFD)) { | ||
|  |                 this.fail("disallowed character."); | ||
|  |             } | ||
|  |             return code; | ||
|  |         } | ||
|  |         const final = 0x10000 + ((code - 0xD800) * 0x400) + | ||
|  |             (chunk.charCodeAt(i + 1) - 0xDC00); | ||
|  |         this.i = i + 2; | ||
|  |         // This is a specialized version of isChar10 that takes into account that in
 | ||
|  |         // this context necessarily final >= 0x10000.
 | ||
|  |         if (final > 0x10FFFF) { | ||
|  |             this.fail("disallowed character."); | ||
|  |         } | ||
|  |         return final; | ||
|  |     } | ||
|  |     /** | ||
|  |      * Get a single code point out of the current chunk. This updates the current | ||
|  |      * position if we do position tracking. | ||
|  |      * | ||
|  |      * This is the algorithm to use for XML 1.1. | ||
|  |      * | ||
|  |      * @returns {number} The character read. | ||
|  |      */ | ||
|  |     getCode11() { | ||
|  |         const { chunk, i } = this; | ||
|  |         this.prevI = i; | ||
|  |         // Yes, we do this instead of doing this.i++. Doing it this way, we do not
 | ||
|  |         // read this.i again, which is a bit faster.
 | ||
|  |         this.i = i + 1; | ||
|  |         if (i >= chunk.length) { | ||
|  |             return EOC; | ||
|  |         } | ||
|  |         // Using charCodeAt and handling the surrogates ourselves is faster
 | ||
|  |         // than using codePointAt.
 | ||
|  |         const code = chunk.charCodeAt(i); | ||
|  |         this.column++; | ||
|  |         if (code < 0xD800) { | ||
|  |             if ((code > 0x1F && code < 0x7F) || (code > 0x9F && code !== LS) || | ||
|  |                 code === TAB) { | ||
|  |                 return code; | ||
|  |             } | ||
|  |             switch (code) { | ||
|  |                 case NL: // 0xA
 | ||
|  |                     this.line++; | ||
|  |                     this.column = 0; | ||
|  |                     this.positionAtNewLine = this.position; | ||
|  |                     return NL; | ||
|  |                 case CR: { // 0xD
 | ||
|  |                     // We may get NaN if we read past the end of the chunk, which is
 | ||
|  |                     // fine.
 | ||
|  |                     const next = chunk.charCodeAt(i + 1); | ||
|  |                     if (next === NL || next === NEL) { | ||
|  |                         // A CR NL or CR NEL sequence is converted to NL so we have to skip
 | ||
|  |                         // over the next character. We already know it has a size of 1.
 | ||
|  |                         this.i = i + 2; | ||
|  |                     } | ||
|  |                     // Otherwise, a CR is just converted to NL, no skip.
 | ||
|  |                 } | ||
|  |                 /* yes, fall through */ | ||
|  |                 case NEL: // 0x85
 | ||
|  |                 case LS: // Ox2028
 | ||
|  |                     this.line++; | ||
|  |                     this.column = 0; | ||
|  |                     this.positionAtNewLine = this.position; | ||
|  |                     return NL_LIKE; | ||
|  |                 default: | ||
|  |                     this.fail("disallowed character."); | ||
|  |                     return code; | ||
|  |             } | ||
|  |         } | ||
|  |         if (code > 0xDBFF) { | ||
|  |             // This is a specialized version of isCharAndNotRestricted that takes into
 | ||
|  |             // account that in this context code > 0xDBFF and code <= 0xFFFF. So it
 | ||
|  |             // does not test cases that don't need testing.
 | ||
|  |             if (!(code >= 0xE000 && code <= 0xFFFD)) { | ||
|  |                 this.fail("disallowed character."); | ||
|  |             } | ||
|  |             return code; | ||
|  |         } | ||
|  |         const final = 0x10000 + ((code - 0xD800) * 0x400) + | ||
|  |             (chunk.charCodeAt(i + 1) - 0xDC00); | ||
|  |         this.i = i + 2; | ||
|  |         // This is a specialized version of isCharAndNotRestricted that takes into
 | ||
|  |         // account that in this context necessarily final >= 0x10000.
 | ||
|  |         if (final > 0x10FFFF) { | ||
|  |             this.fail("disallowed character."); | ||
|  |         } | ||
|  |         return final; | ||
|  |     } | ||
|  |     /** | ||
|  |      * Like ``getCode`` but with the return value normalized so that ``NL`` is | ||
|  |      * returned for ``NL_LIKE``. | ||
|  |      */ | ||
|  |     getCodeNorm() { | ||
|  |         const c = this.getCode(); | ||
|  |         return c === NL_LIKE ? NL : c; | ||
|  |     } | ||
|  |     unget() { | ||
|  |         this.i = this.prevI; | ||
|  |         this.column--; | ||
|  |     } | ||
|  |     /** | ||
|  |      * Capture characters into a buffer until encountering one of a set of | ||
|  |      * characters. | ||
|  |      * | ||
|  |      * @param chars An array of codepoints. Encountering a character in the array | ||
|  |      * ends the capture. (``chars`` may safely contain ``NL``.) | ||
|  |      * | ||
|  |      * @return The character code that made the capture end, or ``EOC`` if we hit | ||
|  |      * the end of the chunk. The return value cannot be NL_LIKE: NL is returned | ||
|  |      * instead. | ||
|  |      */ | ||
|  |     captureTo(chars) { | ||
|  |         let { i: start } = this; | ||
|  |         const { chunk } = this; | ||
|  |         // eslint-disable-next-line no-constant-condition
 | ||
|  |         while (true) { | ||
|  |             const c = this.getCode(); | ||
|  |             const isNLLike = c === NL_LIKE; | ||
|  |             const final = isNLLike ? NL : c; | ||
|  |             if (final === EOC || chars.includes(final)) { | ||
|  |                 this.text += chunk.slice(start, this.prevI); | ||
|  |                 return final; | ||
|  |             } | ||
|  |             if (isNLLike) { | ||
|  |                 this.text += `${chunk.slice(start, this.prevI)}\n`; | ||
|  |                 start = this.i; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     /** | ||
|  |      * Capture characters into a buffer until encountering a character. | ||
|  |      * | ||
|  |      * @param char The codepoint that ends the capture. **NOTE ``char`` MAY NOT | ||
|  |      * CONTAIN ``NL``.** Passing ``NL`` will result in buggy behavior. | ||
|  |      * | ||
|  |      * @return ``true`` if we ran into the character. Otherwise, we ran into the | ||
|  |      * end of the current chunk. | ||
|  |      */ | ||
|  |     captureToChar(char) { | ||
|  |         let { i: start } = this; | ||
|  |         const { chunk } = this; | ||
|  |         // eslint-disable-next-line no-constant-condition
 | ||
|  |         while (true) { | ||
|  |             let c = this.getCode(); | ||
|  |             switch (c) { | ||
|  |                 case NL_LIKE: | ||
|  |                     this.text += `${chunk.slice(start, this.prevI)}\n`; | ||
|  |                     start = this.i; | ||
|  |                     c = NL; | ||
|  |                     break; | ||
|  |                 case EOC: | ||
|  |                     this.text += chunk.slice(start); | ||
|  |                     return false; | ||
|  |                 default: | ||
|  |             } | ||
|  |             if (c === char) { | ||
|  |                 this.text += chunk.slice(start, this.prevI); | ||
|  |                 return true; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     /** | ||
|  |      * Capture characters that satisfy ``isNameChar`` into the ``name`` field of | ||
|  |      * this parser. | ||
|  |      * | ||
|  |      * @return The character code that made the test fail, or ``EOC`` if we hit | ||
|  |      * the end of the chunk. The return value cannot be NL_LIKE: NL is returned | ||
|  |      * instead. | ||
|  |      */ | ||
|  |     captureNameChars() { | ||
|  |         const { chunk, i: start } = this; | ||
|  |         // eslint-disable-next-line no-constant-condition
 | ||
|  |         while (true) { | ||
|  |             const c = this.getCode(); | ||
|  |             if (c === EOC) { | ||
|  |                 this.name += chunk.slice(start); | ||
|  |                 return EOC; | ||
|  |             } | ||
|  |             // NL is not a name char so we don't have to test specifically for it.
 | ||
|  |             if (!isNameChar(c)) { | ||
|  |                 this.name += chunk.slice(start, this.prevI); | ||
|  |                 return c === NL_LIKE ? NL : c; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     /** | ||
|  |      * Skip white spaces. | ||
|  |      * | ||
|  |      * @return The character that ended the skip, or ``EOC`` if we hit | ||
|  |      * the end of the chunk. The return value cannot be NL_LIKE: NL is returned | ||
|  |      * instead. | ||
|  |      */ | ||
|  |     skipSpaces() { | ||
|  |         // eslint-disable-next-line no-constant-condition
 | ||
|  |         while (true) { | ||
|  |             const c = this.getCodeNorm(); | ||
|  |             if (c === EOC || !isS(c)) { | ||
|  |                 return c; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     setXMLVersion(version) { | ||
|  |         this.currentXMLVersion = version; | ||
|  |         /*  eslint-disable @typescript-eslint/unbound-method */ | ||
|  |         if (version === "1.0") { | ||
|  |             this.isChar = isChar10; | ||
|  |             this.getCode = this.getCode10; | ||
|  |         } | ||
|  |         else { | ||
|  |             this.isChar = isChar11; | ||
|  |             this.getCode = this.getCode11; | ||
|  |         } | ||
|  |         /* eslint-enable @typescript-eslint/unbound-method */ | ||
|  |     } | ||
|  |     // STATE ENGINE METHODS
 | ||
|  |     // This needs to be a state separate from S_BEGIN_WHITESPACE because we want
 | ||
|  |     // to be sure never to come back to this state later.
 | ||
|  |     sBegin() { | ||
|  |         // We are essentially peeking at the first character of the chunk. Since
 | ||
|  |         // S_BEGIN can be in effect only when we start working on the first chunk,
 | ||
|  |         // the index at which we must look is necessarily 0. Note also that the
 | ||
|  |         // following test does not depend on decoding surrogates.
 | ||
|  |         // If the initial character is 0xFEFF, ignore it.
 | ||
|  |         if (this.chunk.charCodeAt(0) === 0xFEFF) { | ||
|  |             this.i++; | ||
|  |             this.column++; | ||
|  |         } | ||
|  |         this.state = S_BEGIN_WHITESPACE; | ||
|  |     } | ||
|  |     sBeginWhitespace() { | ||
|  |         // We need to know whether we've encountered spaces or not because as soon
 | ||
|  |         // as we run into a space, an XML declaration is no longer possible. Rather
 | ||
|  |         // than slow down skipSpaces even in places where we don't care whether it
 | ||
|  |         // skipped anything or not, we check whether prevI is equal to the value of
 | ||
|  |         // i from before we skip spaces.
 | ||
|  |         const iBefore = this.i; | ||
|  |         const c = this.skipSpaces(); | ||
|  |         if (this.prevI !== iBefore) { | ||
|  |             this.xmlDeclPossible = false; | ||
|  |         } | ||
|  |         switch (c) { | ||
|  |             case LESS: | ||
|  |                 this.state = S_OPEN_WAKA; | ||
|  |                 // We could naively call closeText but in this state, it is not normal
 | ||
|  |                 // to have text be filled with any data.
 | ||
|  |                 if (this.text.length !== 0) { | ||
|  |                     throw new Error("no-empty text at start"); | ||
|  |                 } | ||
|  |                 break; | ||
|  |             case EOC: | ||
|  |                 break; | ||
|  |             default: | ||
|  |                 this.unget(); | ||
|  |                 this.state = S_TEXT; | ||
|  |                 this.xmlDeclPossible = false; | ||
|  |         } | ||
|  |     } | ||
|  |     sDoctype() { | ||
|  |         var _a; | ||
|  |         const c = this.captureTo(DOCTYPE_TERMINATOR); | ||
|  |         switch (c) { | ||
|  |             case GREATER: { | ||
|  |                 // eslint-disable-next-line no-unused-expressions
 | ||
|  |                 (_a = this.doctypeHandler) === null || _a === void 0 ? void 0 : _a.call(this, this.text); | ||
|  |                 this.text = ""; | ||
|  |                 this.state = S_TEXT; | ||
|  |                 this.doctype = true; // just remember that we saw it.
 | ||
|  |                 break; | ||
|  |             } | ||
|  |             case EOC: | ||
|  |                 break; | ||
|  |             default: | ||
|  |                 this.text += String.fromCodePoint(c); | ||
|  |                 if (c === OPEN_BRACKET) { | ||
|  |                     this.state = S_DTD; | ||
|  |                 } | ||
|  |                 else if (isQuote(c)) { | ||
|  |                     this.state = S_DOCTYPE_QUOTE; | ||
|  |                     this.q = c; | ||
|  |                 } | ||
|  |         } | ||
|  |     } | ||
|  |     sDoctypeQuote() { | ||
|  |         const q = this.q; | ||
|  |         if (this.captureToChar(q)) { | ||
|  |             this.text += String.fromCodePoint(q); | ||
|  |             this.q = null; | ||
|  |             this.state = S_DOCTYPE; | ||
|  |         } | ||
|  |     } | ||
|  |     sDTD() { | ||
|  |         const c = this.captureTo(DTD_TERMINATOR); | ||
|  |         if (c === EOC) { | ||
|  |             return; | ||
|  |         } | ||
|  |         this.text += String.fromCodePoint(c); | ||
|  |         if (c === CLOSE_BRACKET) { | ||
|  |             this.state = S_DOCTYPE; | ||
|  |         } | ||
|  |         else if (c === LESS) { | ||
|  |             this.state = S_DTD_OPEN_WAKA; | ||
|  |         } | ||
|  |         else if (isQuote(c)) { | ||
|  |             this.state = S_DTD_QUOTED; | ||
|  |             this.q = c; | ||
|  |         } | ||
|  |     } | ||
|  |     sDTDQuoted() { | ||
|  |         const q = this.q; | ||
|  |         if (this.captureToChar(q)) { | ||
|  |             this.text += String.fromCodePoint(q); | ||
|  |             this.state = S_DTD; | ||
|  |             this.q = null; | ||
|  |         } | ||
|  |     } | ||
|  |     sDTDOpenWaka() { | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         this.text += String.fromCodePoint(c); | ||
|  |         switch (c) { | ||
|  |             case BANG: | ||
|  |                 this.state = S_DTD_OPEN_WAKA_BANG; | ||
|  |                 this.openWakaBang = ""; | ||
|  |                 break; | ||
|  |             case QUESTION: | ||
|  |                 this.state = S_DTD_PI; | ||
|  |                 break; | ||
|  |             default: | ||
|  |                 this.state = S_DTD; | ||
|  |         } | ||
|  |     } | ||
|  |     sDTDOpenWakaBang() { | ||
|  |         const char = String.fromCodePoint(this.getCodeNorm()); | ||
|  |         const owb = this.openWakaBang += char; | ||
|  |         this.text += char; | ||
|  |         if (owb !== "-") { | ||
|  |             this.state = owb === "--" ? S_DTD_COMMENT : S_DTD; | ||
|  |             this.openWakaBang = ""; | ||
|  |         } | ||
|  |     } | ||
|  |     sDTDComment() { | ||
|  |         if (this.captureToChar(MINUS)) { | ||
|  |             this.text += "-"; | ||
|  |             this.state = S_DTD_COMMENT_ENDING; | ||
|  |         } | ||
|  |     } | ||
|  |     sDTDCommentEnding() { | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         this.text += String.fromCodePoint(c); | ||
|  |         this.state = c === MINUS ? S_DTD_COMMENT_ENDED : S_DTD_COMMENT; | ||
|  |     } | ||
|  |     sDTDCommentEnded() { | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         this.text += String.fromCodePoint(c); | ||
|  |         if (c === GREATER) { | ||
|  |             this.state = S_DTD; | ||
|  |         } | ||
|  |         else { | ||
|  |             this.fail("malformed comment."); | ||
|  |             // <!-- blah -- bloo --> will be recorded as
 | ||
|  |             // a comment of " blah -- bloo "
 | ||
|  |             this.state = S_DTD_COMMENT; | ||
|  |         } | ||
|  |     } | ||
|  |     sDTDPI() { | ||
|  |         if (this.captureToChar(QUESTION)) { | ||
|  |             this.text += "?"; | ||
|  |             this.state = S_DTD_PI_ENDING; | ||
|  |         } | ||
|  |     } | ||
|  |     sDTDPIEnding() { | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         this.text += String.fromCodePoint(c); | ||
|  |         if (c === GREATER) { | ||
|  |             this.state = S_DTD; | ||
|  |         } | ||
|  |     } | ||
|  |     sText() { | ||
|  |         //
 | ||
|  |         // We did try a version of saxes where the S_TEXT state was split in two
 | ||
|  |         // states: one for text inside the root element, and one for text
 | ||
|  |         // outside. This was avoiding having to test this.tags.length to decide
 | ||
|  |         // what implementation to actually use.
 | ||
|  |         //
 | ||
|  |         // Peformance testing on gigabyte-size files did not show any advantage to
 | ||
|  |         // using the two states solution instead of the current one. Conversely, it
 | ||
|  |         // made the code a bit more complicated elsewhere. For instance, a comment
 | ||
|  |         // can appear before the root element so when a comment ended it was
 | ||
|  |         // necessary to determine whether to return to the S_TEXT state or to the
 | ||
|  |         // new text-outside-root state.
 | ||
|  |         //
 | ||
|  |         if (this.tags.length !== 0) { | ||
|  |             this.handleTextInRoot(); | ||
|  |         } | ||
|  |         else { | ||
|  |             this.handleTextOutsideRoot(); | ||
|  |         } | ||
|  |     } | ||
|  |     sEntity() { | ||
|  |         // This is essentially a specialized version of captureToChar(SEMICOLON...)
 | ||
|  |         let { i: start } = this; | ||
|  |         const { chunk } = this; | ||
|  |         // eslint-disable-next-line no-labels, no-restricted-syntax
 | ||
|  |         loop:  | ||
|  |         // eslint-disable-next-line no-constant-condition
 | ||
|  |         while (true) { | ||
|  |             switch (this.getCode()) { | ||
|  |                 case NL_LIKE: | ||
|  |                     this.entity += `${chunk.slice(start, this.prevI)}\n`; | ||
|  |                     start = this.i; | ||
|  |                     break; | ||
|  |                 case SEMICOLON: { | ||
|  |                     const { entityReturnState } = this; | ||
|  |                     const entity = this.entity + chunk.slice(start, this.prevI); | ||
|  |                     this.state = entityReturnState; | ||
|  |                     let parsed; | ||
|  |                     if (entity === "") { | ||
|  |                         this.fail("empty entity name."); | ||
|  |                         parsed = "&;"; | ||
|  |                     } | ||
|  |                     else { | ||
|  |                         parsed = this.parseEntity(entity); | ||
|  |                         this.entity = ""; | ||
|  |                     } | ||
|  |                     if (entityReturnState !== S_TEXT || this.textHandler !== undefined) { | ||
|  |                         this.text += parsed; | ||
|  |                     } | ||
|  |                     // eslint-disable-next-line no-labels
 | ||
|  |                     break loop; | ||
|  |                 } | ||
|  |                 case EOC: | ||
|  |                     this.entity += chunk.slice(start); | ||
|  |                     // eslint-disable-next-line no-labels
 | ||
|  |                     break loop; | ||
|  |                 default: | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     sOpenWaka() { | ||
|  |         // Reminder: a state handler is called with at least one character
 | ||
|  |         // available in the current chunk. So the first call to get code inside of
 | ||
|  |         // a state handler cannot return ``EOC``. That's why we don't test
 | ||
|  |         // for it.
 | ||
|  |         const c = this.getCode(); | ||
|  |         // either a /, ?, !, or text is coming next.
 | ||
|  |         if (isNameStartChar(c)) { | ||
|  |             this.state = S_OPEN_TAG; | ||
|  |             this.unget(); | ||
|  |             this.xmlDeclPossible = false; | ||
|  |         } | ||
|  |         else { | ||
|  |             switch (c) { | ||
|  |                 case FORWARD_SLASH: | ||
|  |                     this.state = S_CLOSE_TAG; | ||
|  |                     this.xmlDeclPossible = false; | ||
|  |                     break; | ||
|  |                 case BANG: | ||
|  |                     this.state = S_OPEN_WAKA_BANG; | ||
|  |                     this.openWakaBang = ""; | ||
|  |                     this.xmlDeclPossible = false; | ||
|  |                     break; | ||
|  |                 case QUESTION: | ||
|  |                     this.state = S_PI_FIRST_CHAR; | ||
|  |                     break; | ||
|  |                 default: | ||
|  |                     this.fail("disallowed character in tag name"); | ||
|  |                     this.state = S_TEXT; | ||
|  |                     this.xmlDeclPossible = false; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     sOpenWakaBang() { | ||
|  |         this.openWakaBang += String.fromCodePoint(this.getCodeNorm()); | ||
|  |         switch (this.openWakaBang) { | ||
|  |             case "[CDATA[": | ||
|  |                 if (!this.sawRoot && !this.reportedTextBeforeRoot) { | ||
|  |                     this.fail("text data outside of root node."); | ||
|  |                     this.reportedTextBeforeRoot = true; | ||
|  |                 } | ||
|  |                 if (this.closedRoot && !this.reportedTextAfterRoot) { | ||
|  |                     this.fail("text data outside of root node."); | ||
|  |                     this.reportedTextAfterRoot = true; | ||
|  |                 } | ||
|  |                 this.state = S_CDATA; | ||
|  |                 this.openWakaBang = ""; | ||
|  |                 break; | ||
|  |             case "--": | ||
|  |                 this.state = S_COMMENT; | ||
|  |                 this.openWakaBang = ""; | ||
|  |                 break; | ||
|  |             case "DOCTYPE": | ||
|  |                 this.state = S_DOCTYPE; | ||
|  |                 if (this.doctype || this.sawRoot) { | ||
|  |                     this.fail("inappropriately located doctype declaration."); | ||
|  |                 } | ||
|  |                 this.openWakaBang = ""; | ||
|  |                 break; | ||
|  |             default: | ||
|  |                 // 7 happens to be the maximum length of the string that can possibly
 | ||
|  |                 // match one of the cases above.
 | ||
|  |                 if (this.openWakaBang.length >= 7) { | ||
|  |                     this.fail("incorrect syntax."); | ||
|  |                 } | ||
|  |         } | ||
|  |     } | ||
|  |     sComment() { | ||
|  |         if (this.captureToChar(MINUS)) { | ||
|  |             this.state = S_COMMENT_ENDING; | ||
|  |         } | ||
|  |     } | ||
|  |     sCommentEnding() { | ||
|  |         var _a; | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         if (c === MINUS) { | ||
|  |             this.state = S_COMMENT_ENDED; | ||
|  |             // eslint-disable-next-line no-unused-expressions
 | ||
|  |             (_a = this.commentHandler) === null || _a === void 0 ? void 0 : _a.call(this, this.text); | ||
|  |             this.text = ""; | ||
|  |         } | ||
|  |         else { | ||
|  |             this.text += `-${String.fromCodePoint(c)}`; | ||
|  |             this.state = S_COMMENT; | ||
|  |         } | ||
|  |     } | ||
|  |     sCommentEnded() { | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         if (c !== GREATER) { | ||
|  |             this.fail("malformed comment."); | ||
|  |             // <!-- blah -- bloo --> will be recorded as
 | ||
|  |             // a comment of " blah -- bloo "
 | ||
|  |             this.text += `--${String.fromCodePoint(c)}`; | ||
|  |             this.state = S_COMMENT; | ||
|  |         } | ||
|  |         else { | ||
|  |             this.state = S_TEXT; | ||
|  |         } | ||
|  |     } | ||
|  |     sCData() { | ||
|  |         if (this.captureToChar(CLOSE_BRACKET)) { | ||
|  |             this.state = S_CDATA_ENDING; | ||
|  |         } | ||
|  |     } | ||
|  |     sCDataEnding() { | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         if (c === CLOSE_BRACKET) { | ||
|  |             this.state = S_CDATA_ENDING_2; | ||
|  |         } | ||
|  |         else { | ||
|  |             this.text += `]${String.fromCodePoint(c)}`; | ||
|  |             this.state = S_CDATA; | ||
|  |         } | ||
|  |     } | ||
|  |     sCDataEnding2() { | ||
|  |         var _a; | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         switch (c) { | ||
|  |             case GREATER: { | ||
|  |                 // eslint-disable-next-line no-unused-expressions
 | ||
|  |                 (_a = this.cdataHandler) === null || _a === void 0 ? void 0 : _a.call(this, this.text); | ||
|  |                 this.text = ""; | ||
|  |                 this.state = S_TEXT; | ||
|  |                 break; | ||
|  |             } | ||
|  |             case CLOSE_BRACKET: | ||
|  |                 this.text += "]"; | ||
|  |                 break; | ||
|  |             default: | ||
|  |                 this.text += `]]${String.fromCodePoint(c)}`; | ||
|  |                 this.state = S_CDATA; | ||
|  |         } | ||
|  |     } | ||
|  |     // We need this separate state to check the first character fo the pi target
 | ||
|  |     // with this.nameStartCheck which allows less characters than this.nameCheck.
 | ||
|  |     sPIFirstChar() { | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         // This is first because in the case where the file is well-formed this is
 | ||
|  |         // the branch taken. We optimize for well-formedness.
 | ||
|  |         if (this.nameStartCheck(c)) { | ||
|  |             this.piTarget += String.fromCodePoint(c); | ||
|  |             this.state = S_PI_REST; | ||
|  |         } | ||
|  |         else if (c === QUESTION || isS(c)) { | ||
|  |             this.fail("processing instruction without a target."); | ||
|  |             this.state = c === QUESTION ? S_PI_ENDING : S_PI_BODY; | ||
|  |         } | ||
|  |         else { | ||
|  |             this.fail("disallowed character in processing instruction name."); | ||
|  |             this.piTarget += String.fromCodePoint(c); | ||
|  |             this.state = S_PI_REST; | ||
|  |         } | ||
|  |     } | ||
|  |     sPIRest() { | ||
|  |         // Capture characters into a piTarget while ``this.nameCheck`` run on the
 | ||
|  |         // character read returns true.
 | ||
|  |         const { chunk, i: start } = this; | ||
|  |         // eslint-disable-next-line no-constant-condition
 | ||
|  |         while (true) { | ||
|  |             const c = this.getCodeNorm(); | ||
|  |             if (c === EOC) { | ||
|  |                 this.piTarget += chunk.slice(start); | ||
|  |                 return; | ||
|  |             } | ||
|  |             // NL cannot satisfy this.nameCheck so we don't have to test specifically
 | ||
|  |             // for it.
 | ||
|  |             if (!this.nameCheck(c)) { | ||
|  |                 this.piTarget += chunk.slice(start, this.prevI); | ||
|  |                 const isQuestion = c === QUESTION; | ||
|  |                 if (isQuestion || isS(c)) { | ||
|  |                     if (this.piTarget === "xml") { | ||
|  |                         if (!this.xmlDeclPossible) { | ||
|  |                             this.fail("an XML declaration must be at the start of the document."); | ||
|  |                         } | ||
|  |                         this.state = isQuestion ? S_XML_DECL_ENDING : S_XML_DECL_NAME_START; | ||
|  |                     } | ||
|  |                     else { | ||
|  |                         this.state = isQuestion ? S_PI_ENDING : S_PI_BODY; | ||
|  |                     } | ||
|  |                 } | ||
|  |                 else { | ||
|  |                     this.fail("disallowed character in processing instruction name."); | ||
|  |                     this.piTarget += String.fromCodePoint(c); | ||
|  |                 } | ||
|  |                 break; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     sPIBody() { | ||
|  |         if (this.text.length === 0) { | ||
|  |             const c = this.getCodeNorm(); | ||
|  |             if (c === QUESTION) { | ||
|  |                 this.state = S_PI_ENDING; | ||
|  |             } | ||
|  |             else if (!isS(c)) { | ||
|  |                 this.text = String.fromCodePoint(c); | ||
|  |             } | ||
|  |         } | ||
|  |         // The question mark character is not valid inside any of the XML
 | ||
|  |         // declaration name/value pairs.
 | ||
|  |         else if (this.captureToChar(QUESTION)) { | ||
|  |             this.state = S_PI_ENDING; | ||
|  |         } | ||
|  |     } | ||
|  |     sPIEnding() { | ||
|  |         var _a; | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         if (c === GREATER) { | ||
|  |             const { piTarget } = this; | ||
|  |             if (piTarget.toLowerCase() === "xml") { | ||
|  |                 this.fail("the XML declaration must appear at the start of the document."); | ||
|  |             } | ||
|  |             // eslint-disable-next-line no-unused-expressions
 | ||
|  |             (_a = this.piHandler) === null || _a === void 0 ? void 0 : _a.call(this, { | ||
|  |                 target: piTarget, | ||
|  |                 body: this.text, | ||
|  |             }); | ||
|  |             this.piTarget = this.text = ""; | ||
|  |             this.state = S_TEXT; | ||
|  |         } | ||
|  |         else if (c === QUESTION) { | ||
|  |             // We ran into ?? as part of a processing instruction. We initially took
 | ||
|  |             // the first ? as a sign that the PI was ending, but it is not. So we have
 | ||
|  |             // to add it to the body but we take the new ? as a sign that the PI is
 | ||
|  |             // ending.
 | ||
|  |             this.text += "?"; | ||
|  |         } | ||
|  |         else { | ||
|  |             this.text += `?${String.fromCodePoint(c)}`; | ||
|  |             this.state = S_PI_BODY; | ||
|  |         } | ||
|  |         this.xmlDeclPossible = false; | ||
|  |     } | ||
|  |     sXMLDeclNameStart() { | ||
|  |         const c = this.skipSpaces(); | ||
|  |         // The question mark character is not valid inside any of the XML
 | ||
|  |         // declaration name/value pairs.
 | ||
|  |         if (c === QUESTION) { | ||
|  |             // It is valid to go to S_XML_DECL_ENDING from this state.
 | ||
|  |             this.state = S_XML_DECL_ENDING; | ||
|  |             return; | ||
|  |         } | ||
|  |         if (c !== EOC) { | ||
|  |             this.state = S_XML_DECL_NAME; | ||
|  |             this.name = String.fromCodePoint(c); | ||
|  |         } | ||
|  |     } | ||
|  |     sXMLDeclName() { | ||
|  |         const c = this.captureTo(XML_DECL_NAME_TERMINATOR); | ||
|  |         // The question mark character is not valid inside any of the XML
 | ||
|  |         // declaration name/value pairs.
 | ||
|  |         if (c === QUESTION) { | ||
|  |             this.state = S_XML_DECL_ENDING; | ||
|  |             this.name += this.text; | ||
|  |             this.text = ""; | ||
|  |             this.fail("XML declaration is incomplete."); | ||
|  |             return; | ||
|  |         } | ||
|  |         if (!(isS(c) || c === EQUAL)) { | ||
|  |             return; | ||
|  |         } | ||
|  |         this.name += this.text; | ||
|  |         this.text = ""; | ||
|  |         if (!this.xmlDeclExpects.includes(this.name)) { | ||
|  |             switch (this.name.length) { | ||
|  |                 case 0: | ||
|  |                     this.fail("did not expect any more name/value pairs."); | ||
|  |                     break; | ||
|  |                 case 1: | ||
|  |                     this.fail(`expected the name ${this.xmlDeclExpects[0]}.`); | ||
|  |                     break; | ||
|  |                 default: | ||
|  |                     this.fail(`expected one of ${this.xmlDeclExpects.join(", ")}`); | ||
|  |             } | ||
|  |         } | ||
|  |         this.state = c === EQUAL ? S_XML_DECL_VALUE_START : S_XML_DECL_EQ; | ||
|  |     } | ||
|  |     sXMLDeclEq() { | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         // The question mark character is not valid inside any of the XML
 | ||
|  |         // declaration name/value pairs.
 | ||
|  |         if (c === QUESTION) { | ||
|  |             this.state = S_XML_DECL_ENDING; | ||
|  |             this.fail("XML declaration is incomplete."); | ||
|  |             return; | ||
|  |         } | ||
|  |         if (isS(c)) { | ||
|  |             return; | ||
|  |         } | ||
|  |         if (c !== EQUAL) { | ||
|  |             this.fail("value required."); | ||
|  |         } | ||
|  |         this.state = S_XML_DECL_VALUE_START; | ||
|  |     } | ||
|  |     sXMLDeclValueStart() { | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         // The question mark character is not valid inside any of the XML
 | ||
|  |         // declaration name/value pairs.
 | ||
|  |         if (c === QUESTION) { | ||
|  |             this.state = S_XML_DECL_ENDING; | ||
|  |             this.fail("XML declaration is incomplete."); | ||
|  |             return; | ||
|  |         } | ||
|  |         if (isS(c)) { | ||
|  |             return; | ||
|  |         } | ||
|  |         if (!isQuote(c)) { | ||
|  |             this.fail("value must be quoted."); | ||
|  |             this.q = SPACE; | ||
|  |         } | ||
|  |         else { | ||
|  |             this.q = c; | ||
|  |         } | ||
|  |         this.state = S_XML_DECL_VALUE; | ||
|  |     } | ||
|  |     sXMLDeclValue() { | ||
|  |         const c = this.captureTo([this.q, QUESTION]); | ||
|  |         // The question mark character is not valid inside any of the XML
 | ||
|  |         // declaration name/value pairs.
 | ||
|  |         if (c === QUESTION) { | ||
|  |             this.state = S_XML_DECL_ENDING; | ||
|  |             this.text = ""; | ||
|  |             this.fail("XML declaration is incomplete."); | ||
|  |             return; | ||
|  |         } | ||
|  |         if (c === EOC) { | ||
|  |             return; | ||
|  |         } | ||
|  |         const value = this.text; | ||
|  |         this.text = ""; | ||
|  |         switch (this.name) { | ||
|  |             case "version": { | ||
|  |                 this.xmlDeclExpects = ["encoding", "standalone"]; | ||
|  |                 const version = value; | ||
|  |                 this.xmlDecl.version = version; | ||
|  |                 // This is the test specified by XML 1.0 but it is fine for XML 1.1.
 | ||
|  |                 if (!/^1\.[0-9]+$/.test(version)) { | ||
|  |                     this.fail("version number must match /^1\\.[0-9]+$/."); | ||
|  |                 } | ||
|  |                 // When forceXMLVersion is set, the XML declaration is ignored.
 | ||
|  |                 else if (!this.opt.forceXMLVersion) { | ||
|  |                     this.setXMLVersion(version); | ||
|  |                 } | ||
|  |                 break; | ||
|  |             } | ||
|  |             case "encoding": | ||
|  |                 if (!/^[A-Za-z][A-Za-z0-9._-]*$/.test(value)) { | ||
|  |                     this.fail("encoding value must match \ | ||
|  | /^[A-Za-z0-9][A-Za-z0-9._-]*$/."); | ||
|  |                 } | ||
|  |                 this.xmlDeclExpects = ["standalone"]; | ||
|  |                 this.xmlDecl.encoding = value; | ||
|  |                 break; | ||
|  |             case "standalone": | ||
|  |                 if (value !== "yes" && value !== "no") { | ||
|  |                     this.fail("standalone value must match \"yes\" or \"no\"."); | ||
|  |                 } | ||
|  |                 this.xmlDeclExpects = []; | ||
|  |                 this.xmlDecl.standalone = value; | ||
|  |                 break; | ||
|  |             default: | ||
|  |             // We don't need to raise an error here since we've already raised one
 | ||
|  |             // when checking what name was expected.
 | ||
|  |         } | ||
|  |         this.name = ""; | ||
|  |         this.state = S_XML_DECL_SEPARATOR; | ||
|  |     } | ||
|  |     sXMLDeclSeparator() { | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         // The question mark character is not valid inside any of the XML
 | ||
|  |         // declaration name/value pairs.
 | ||
|  |         if (c === QUESTION) { | ||
|  |             // It is valid to go to S_XML_DECL_ENDING from this state.
 | ||
|  |             this.state = S_XML_DECL_ENDING; | ||
|  |             return; | ||
|  |         } | ||
|  |         if (!isS(c)) { | ||
|  |             this.fail("whitespace required."); | ||
|  |             this.unget(); | ||
|  |         } | ||
|  |         this.state = S_XML_DECL_NAME_START; | ||
|  |     } | ||
|  |     sXMLDeclEnding() { | ||
|  |         var _a; | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         if (c === GREATER) { | ||
|  |             if (this.piTarget !== "xml") { | ||
|  |                 this.fail("processing instructions are not allowed before root."); | ||
|  |             } | ||
|  |             else if (this.name !== "version" && | ||
|  |                 this.xmlDeclExpects.includes("version")) { | ||
|  |                 this.fail("XML declaration must contain a version."); | ||
|  |             } | ||
|  |             // eslint-disable-next-line no-unused-expressions
 | ||
|  |             (_a = this.xmldeclHandler) === null || _a === void 0 ? void 0 : _a.call(this, this.xmlDecl); | ||
|  |             this.name = ""; | ||
|  |             this.piTarget = this.text = ""; | ||
|  |             this.state = S_TEXT; | ||
|  |         } | ||
|  |         else { | ||
|  |             // We got here because the previous character was a ?, but the question
 | ||
|  |             // mark character is not valid inside any of the XML declaration
 | ||
|  |             // name/value pairs.
 | ||
|  |             this.fail("The character ? is disallowed anywhere in XML declarations."); | ||
|  |         } | ||
|  |         this.xmlDeclPossible = false; | ||
|  |     } | ||
|  |     sOpenTag() { | ||
|  |         var _a; | ||
|  |         const c = this.captureNameChars(); | ||
|  |         if (c === EOC) { | ||
|  |             return; | ||
|  |         } | ||
|  |         const tag = this.tag = { | ||
|  |             name: this.name, | ||
|  |             attributes: Object.create(null), | ||
|  |         }; | ||
|  |         this.name = ""; | ||
|  |         if (this.xmlnsOpt) { | ||
|  |             this.topNS = tag.ns = Object.create(null); | ||
|  |         } | ||
|  |         // eslint-disable-next-line no-unused-expressions
 | ||
|  |         (_a = this.openTagStartHandler) === null || _a === void 0 ? void 0 : _a.call(this, tag); | ||
|  |         this.sawRoot = true; | ||
|  |         if (!this.fragmentOpt && this.closedRoot) { | ||
|  |             this.fail("documents may contain only one root."); | ||
|  |         } | ||
|  |         switch (c) { | ||
|  |             case GREATER: | ||
|  |                 this.openTag(); | ||
|  |                 break; | ||
|  |             case FORWARD_SLASH: | ||
|  |                 this.state = S_OPEN_TAG_SLASH; | ||
|  |                 break; | ||
|  |             default: | ||
|  |                 if (!isS(c)) { | ||
|  |                     this.fail("disallowed character in tag name."); | ||
|  |                 } | ||
|  |                 this.state = S_ATTRIB; | ||
|  |         } | ||
|  |     } | ||
|  |     sOpenTagSlash() { | ||
|  |         if (this.getCode() === GREATER) { | ||
|  |             this.openSelfClosingTag(); | ||
|  |         } | ||
|  |         else { | ||
|  |             this.fail("forward-slash in opening tag not followed by >."); | ||
|  |             this.state = S_ATTRIB; | ||
|  |         } | ||
|  |     } | ||
|  |     sAttrib() { | ||
|  |         const c = this.skipSpaces(); | ||
|  |         if (c === EOC) { | ||
|  |             return; | ||
|  |         } | ||
|  |         if (isNameStartChar(c)) { | ||
|  |             this.unget(); | ||
|  |             this.state = S_ATTRIB_NAME; | ||
|  |         } | ||
|  |         else if (c === GREATER) { | ||
|  |             this.openTag(); | ||
|  |         } | ||
|  |         else if (c === FORWARD_SLASH) { | ||
|  |             this.state = S_OPEN_TAG_SLASH; | ||
|  |         } | ||
|  |         else { | ||
|  |             this.fail("disallowed character in attribute name."); | ||
|  |         } | ||
|  |     } | ||
|  |     sAttribName() { | ||
|  |         const c = this.captureNameChars(); | ||
|  |         if (c === EQUAL) { | ||
|  |             this.state = S_ATTRIB_VALUE; | ||
|  |         } | ||
|  |         else if (isS(c)) { | ||
|  |             this.state = S_ATTRIB_NAME_SAW_WHITE; | ||
|  |         } | ||
|  |         else if (c === GREATER) { | ||
|  |             this.fail("attribute without value."); | ||
|  |             this.pushAttrib(this.name, this.name); | ||
|  |             this.name = this.text = ""; | ||
|  |             this.openTag(); | ||
|  |         } | ||
|  |         else if (c !== EOC) { | ||
|  |             this.fail("disallowed character in attribute name."); | ||
|  |         } | ||
|  |     } | ||
|  |     sAttribNameSawWhite() { | ||
|  |         const c = this.skipSpaces(); | ||
|  |         switch (c) { | ||
|  |             case EOC: | ||
|  |                 return; | ||
|  |             case EQUAL: | ||
|  |                 this.state = S_ATTRIB_VALUE; | ||
|  |                 break; | ||
|  |             default: | ||
|  |                 this.fail("attribute without value."); | ||
|  |                 // Should we do this???
 | ||
|  |                 // this.tag.attributes[this.name] = "";
 | ||
|  |                 this.text = ""; | ||
|  |                 this.name = ""; | ||
|  |                 if (c === GREATER) { | ||
|  |                     this.openTag(); | ||
|  |                 } | ||
|  |                 else if (isNameStartChar(c)) { | ||
|  |                     this.unget(); | ||
|  |                     this.state = S_ATTRIB_NAME; | ||
|  |                 } | ||
|  |                 else { | ||
|  |                     this.fail("disallowed character in attribute name."); | ||
|  |                     this.state = S_ATTRIB; | ||
|  |                 } | ||
|  |         } | ||
|  |     } | ||
|  |     sAttribValue() { | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         if (isQuote(c)) { | ||
|  |             this.q = c; | ||
|  |             this.state = S_ATTRIB_VALUE_QUOTED; | ||
|  |         } | ||
|  |         else if (!isS(c)) { | ||
|  |             this.fail("unquoted attribute value."); | ||
|  |             this.state = S_ATTRIB_VALUE_UNQUOTED; | ||
|  |             this.unget(); | ||
|  |         } | ||
|  |     } | ||
|  |     sAttribValueQuoted() { | ||
|  |         // We deliberately do not use captureTo here. The specialized code we use
 | ||
|  |         // here is faster than using captureTo.
 | ||
|  |         const { q, chunk } = this; | ||
|  |         let { i: start } = this; | ||
|  |         // eslint-disable-next-line no-constant-condition
 | ||
|  |         while (true) { | ||
|  |             switch (this.getCode()) { | ||
|  |                 case q: | ||
|  |                     this.pushAttrib(this.name, this.text + chunk.slice(start, this.prevI)); | ||
|  |                     this.name = this.text = ""; | ||
|  |                     this.q = null; | ||
|  |                     this.state = S_ATTRIB_VALUE_CLOSED; | ||
|  |                     return; | ||
|  |                 case AMP: | ||
|  |                     this.text += chunk.slice(start, this.prevI); | ||
|  |                     this.state = S_ENTITY; | ||
|  |                     this.entityReturnState = S_ATTRIB_VALUE_QUOTED; | ||
|  |                     return; | ||
|  |                 case NL: | ||
|  |                 case NL_LIKE: | ||
|  |                 case TAB: | ||
|  |                     this.text += `${chunk.slice(start, this.prevI)} `; | ||
|  |                     start = this.i; | ||
|  |                     break; | ||
|  |                 case LESS: | ||
|  |                     this.text += chunk.slice(start, this.prevI); | ||
|  |                     this.fail("disallowed character."); | ||
|  |                     return; | ||
|  |                 case EOC: | ||
|  |                     this.text += chunk.slice(start); | ||
|  |                     return; | ||
|  |                 default: | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     sAttribValueClosed() { | ||
|  |         const c = this.getCodeNorm(); | ||
|  |         if (isS(c)) { | ||
|  |             this.state = S_ATTRIB; | ||
|  |         } | ||
|  |         else if (c === GREATER) { | ||
|  |             this.openTag(); | ||
|  |         } | ||
|  |         else if (c === FORWARD_SLASH) { | ||
|  |             this.state = S_OPEN_TAG_SLASH; | ||
|  |         } | ||
|  |         else if (isNameStartChar(c)) { | ||
|  |             this.fail("no whitespace between attributes."); | ||
|  |             this.unget(); | ||
|  |             this.state = S_ATTRIB_NAME; | ||
|  |         } | ||
|  |         else { | ||
|  |             this.fail("disallowed character in attribute name."); | ||
|  |         } | ||
|  |     } | ||
|  |     sAttribValueUnquoted() { | ||
|  |         // We don't do anything regarding EOL or space handling for unquoted
 | ||
|  |         // attributes. We already have failed by the time we get here, and the
 | ||
|  |         // contract that saxes upholds states that upon failure, it is not safe to
 | ||
|  |         // rely on the data passed to event handlers (other than
 | ||
|  |         // ``onerror``). Passing "bad" data is not a problem.
 | ||
|  |         const c = this.captureTo(ATTRIB_VALUE_UNQUOTED_TERMINATOR); | ||
|  |         switch (c) { | ||
|  |             case AMP: | ||
|  |                 this.state = S_ENTITY; | ||
|  |                 this.entityReturnState = S_ATTRIB_VALUE_UNQUOTED; | ||
|  |                 break; | ||
|  |             case LESS: | ||
|  |                 this.fail("disallowed character."); | ||
|  |                 break; | ||
|  |             case EOC: | ||
|  |                 break; | ||
|  |             default: | ||
|  |                 if (this.text.includes("]]>")) { | ||
|  |                     this.fail("the string \"]]>\" is disallowed in char data."); | ||
|  |                 } | ||
|  |                 this.pushAttrib(this.name, this.text); | ||
|  |                 this.name = this.text = ""; | ||
|  |                 if (c === GREATER) { | ||
|  |                     this.openTag(); | ||
|  |                 } | ||
|  |                 else { | ||
|  |                     this.state = S_ATTRIB; | ||
|  |                 } | ||
|  |         } | ||
|  |     } | ||
|  |     sCloseTag() { | ||
|  |         const c = this.captureNameChars(); | ||
|  |         if (c === GREATER) { | ||
|  |             this.closeTag(); | ||
|  |         } | ||
|  |         else if (isS(c)) { | ||
|  |             this.state = S_CLOSE_TAG_SAW_WHITE; | ||
|  |         } | ||
|  |         else if (c !== EOC) { | ||
|  |             this.fail("disallowed character in closing tag."); | ||
|  |         } | ||
|  |     } | ||
|  |     sCloseTagSawWhite() { | ||
|  |         switch (this.skipSpaces()) { | ||
|  |             case GREATER: | ||
|  |                 this.closeTag(); | ||
|  |                 break; | ||
|  |             case EOC: | ||
|  |                 break; | ||
|  |             default: | ||
|  |                 this.fail("disallowed character in closing tag."); | ||
|  |         } | ||
|  |     } | ||
|  |     // END OF STATE ENGINE METHODS
 | ||
|  |     handleTextInRoot() { | ||
|  |         // This is essentially a specialized version of captureTo which is optimized
 | ||
|  |         // for performing the ]]> check. A previous version of this code, checked
 | ||
|  |         // ``this.text`` for the presence of ]]>. It simplified the code but was
 | ||
|  |         // very costly when character data contained a lot of entities to be parsed.
 | ||
|  |         //
 | ||
|  |         // Since we are using a specialized loop, we also keep track of the presence
 | ||
|  |         // of ]]> in text data. The sequence ]]> is forbidden to appear as-is.
 | ||
|  |         //
 | ||
|  |         let { i: start, forbiddenState } = this; | ||
|  |         const { chunk, textHandler: handler } = this; | ||
|  |         // eslint-disable-next-line no-labels, no-restricted-syntax
 | ||
|  |         scanLoop:  | ||
|  |         // eslint-disable-next-line no-constant-condition
 | ||
|  |         while (true) { | ||
|  |             switch (this.getCode()) { | ||
|  |                 case LESS: { | ||
|  |                     this.state = S_OPEN_WAKA; | ||
|  |                     if (handler !== undefined) { | ||
|  |                         const { text } = this; | ||
|  |                         const slice = chunk.slice(start, this.prevI); | ||
|  |                         if (text.length !== 0) { | ||
|  |                             handler(text + slice); | ||
|  |                             this.text = ""; | ||
|  |                         } | ||
|  |                         else if (slice.length !== 0) { | ||
|  |                             handler(slice); | ||
|  |                         } | ||
|  |                     } | ||
|  |                     forbiddenState = FORBIDDEN_START; | ||
|  |                     // eslint-disable-next-line no-labels
 | ||
|  |                     break scanLoop; | ||
|  |                 } | ||
|  |                 case AMP: | ||
|  |                     this.state = S_ENTITY; | ||
|  |                     this.entityReturnState = S_TEXT; | ||
|  |                     if (handler !== undefined) { | ||
|  |                         this.text += chunk.slice(start, this.prevI); | ||
|  |                     } | ||
|  |                     forbiddenState = FORBIDDEN_START; | ||
|  |                     // eslint-disable-next-line no-labels
 | ||
|  |                     break scanLoop; | ||
|  |                 case CLOSE_BRACKET: | ||
|  |                     switch (forbiddenState) { | ||
|  |                         case FORBIDDEN_START: | ||
|  |                             forbiddenState = FORBIDDEN_BRACKET; | ||
|  |                             break; | ||
|  |                         case FORBIDDEN_BRACKET: | ||
|  |                             forbiddenState = FORBIDDEN_BRACKET_BRACKET; | ||
|  |                             break; | ||
|  |                         case FORBIDDEN_BRACKET_BRACKET: | ||
|  |                             break; | ||
|  |                         default: | ||
|  |                             throw new Error("impossible state"); | ||
|  |                     } | ||
|  |                     break; | ||
|  |                 case GREATER: | ||
|  |                     if (forbiddenState === FORBIDDEN_BRACKET_BRACKET) { | ||
|  |                         this.fail("the string \"]]>\" is disallowed in char data."); | ||
|  |                     } | ||
|  |                     forbiddenState = FORBIDDEN_START; | ||
|  |                     break; | ||
|  |                 case NL_LIKE: | ||
|  |                     if (handler !== undefined) { | ||
|  |                         this.text += `${chunk.slice(start, this.prevI)}\n`; | ||
|  |                     } | ||
|  |                     start = this.i; | ||
|  |                     forbiddenState = FORBIDDEN_START; | ||
|  |                     break; | ||
|  |                 case EOC: | ||
|  |                     if (handler !== undefined) { | ||
|  |                         this.text += chunk.slice(start); | ||
|  |                     } | ||
|  |                     // eslint-disable-next-line no-labels
 | ||
|  |                     break scanLoop; | ||
|  |                 default: | ||
|  |                     forbiddenState = FORBIDDEN_START; | ||
|  |             } | ||
|  |         } | ||
|  |         this.forbiddenState = forbiddenState; | ||
|  |     } | ||
|  |     handleTextOutsideRoot() { | ||
|  |         // This is essentially a specialized version of captureTo which is optimized
 | ||
|  |         // for a specialized task. We keep track of the presence of non-space
 | ||
|  |         // characters in the text since these are errors when appearing outside the
 | ||
|  |         // document root element.
 | ||
|  |         let { i: start } = this; | ||
|  |         const { chunk, textHandler: handler } = this; | ||
|  |         let nonSpace = false; | ||
|  |         // eslint-disable-next-line no-labels, no-restricted-syntax
 | ||
|  |         outRootLoop:  | ||
|  |         // eslint-disable-next-line no-constant-condition
 | ||
|  |         while (true) { | ||
|  |             const code = this.getCode(); | ||
|  |             switch (code) { | ||
|  |                 case LESS: { | ||
|  |                     this.state = S_OPEN_WAKA; | ||
|  |                     if (handler !== undefined) { | ||
|  |                         const { text } = this; | ||
|  |                         const slice = chunk.slice(start, this.prevI); | ||
|  |                         if (text.length !== 0) { | ||
|  |                             handler(text + slice); | ||
|  |                             this.text = ""; | ||
|  |                         } | ||
|  |                         else if (slice.length !== 0) { | ||
|  |                             handler(slice); | ||
|  |                         } | ||
|  |                     } | ||
|  |                     // eslint-disable-next-line no-labels
 | ||
|  |                     break outRootLoop; | ||
|  |                 } | ||
|  |                 case AMP: | ||
|  |                     this.state = S_ENTITY; | ||
|  |                     this.entityReturnState = S_TEXT; | ||
|  |                     if (handler !== undefined) { | ||
|  |                         this.text += chunk.slice(start, this.prevI); | ||
|  |                     } | ||
|  |                     nonSpace = true; | ||
|  |                     // eslint-disable-next-line no-labels
 | ||
|  |                     break outRootLoop; | ||
|  |                 case NL_LIKE: | ||
|  |                     if (handler !== undefined) { | ||
|  |                         this.text += `${chunk.slice(start, this.prevI)}\n`; | ||
|  |                     } | ||
|  |                     start = this.i; | ||
|  |                     break; | ||
|  |                 case EOC: | ||
|  |                     if (handler !== undefined) { | ||
|  |                         this.text += chunk.slice(start); | ||
|  |                     } | ||
|  |                     // eslint-disable-next-line no-labels
 | ||
|  |                     break outRootLoop; | ||
|  |                 default: | ||
|  |                     if (!isS(code)) { | ||
|  |                         nonSpace = true; | ||
|  |                     } | ||
|  |             } | ||
|  |         } | ||
|  |         if (!nonSpace) { | ||
|  |             return; | ||
|  |         } | ||
|  |         // We use the reportedTextBeforeRoot and reportedTextAfterRoot flags
 | ||
|  |         // to avoid reporting errors for every single character that is out of
 | ||
|  |         // place.
 | ||
|  |         if (!this.sawRoot && !this.reportedTextBeforeRoot) { | ||
|  |             this.fail("text data outside of root node."); | ||
|  |             this.reportedTextBeforeRoot = true; | ||
|  |         } | ||
|  |         if (this.closedRoot && !this.reportedTextAfterRoot) { | ||
|  |             this.fail("text data outside of root node."); | ||
|  |             this.reportedTextAfterRoot = true; | ||
|  |         } | ||
|  |     } | ||
|  |     pushAttribNS(name, value) { | ||
|  |         var _a; | ||
|  |         const { prefix, local } = this.qname(name); | ||
|  |         const attr = { name, prefix, local, value }; | ||
|  |         this.attribList.push(attr); | ||
|  |         // eslint-disable-next-line no-unused-expressions
 | ||
|  |         (_a = this.attributeHandler) === null || _a === void 0 ? void 0 : _a.call(this, attr); | ||
|  |         if (prefix === "xmlns") { | ||
|  |             const trimmed = value.trim(); | ||
|  |             if (this.currentXMLVersion === "1.0" && trimmed === "") { | ||
|  |                 this.fail("invalid attempt to undefine prefix in XML 1.0"); | ||
|  |             } | ||
|  |             this.topNS[local] = trimmed; | ||
|  |             nsPairCheck(this, local, trimmed); | ||
|  |         } | ||
|  |         else if (name === "xmlns") { | ||
|  |             const trimmed = value.trim(); | ||
|  |             this.topNS[""] = trimmed; | ||
|  |             nsPairCheck(this, "", trimmed); | ||
|  |         } | ||
|  |     } | ||
|  |     pushAttribPlain(name, value) { | ||
|  |         var _a; | ||
|  |         const attr = { name, value }; | ||
|  |         this.attribList.push(attr); | ||
|  |         // eslint-disable-next-line no-unused-expressions
 | ||
|  |         (_a = this.attributeHandler) === null || _a === void 0 ? void 0 : _a.call(this, attr); | ||
|  |     } | ||
|  |     /** | ||
|  |      * End parsing. This performs final well-formedness checks and resets the | ||
|  |      * parser to a clean state. | ||
|  |      * | ||
|  |      * @returns this | ||
|  |      */ | ||
|  |     end() { | ||
|  |         var _a, _b; | ||
|  |         if (!this.sawRoot) { | ||
|  |             this.fail("document must contain a root element."); | ||
|  |         } | ||
|  |         const { tags } = this; | ||
|  |         while (tags.length > 0) { | ||
|  |             const tag = tags.pop(); | ||
|  |             this.fail(`unclosed tag: ${tag.name}`); | ||
|  |         } | ||
|  |         if ((this.state !== S_BEGIN) && (this.state !== S_TEXT)) { | ||
|  |             this.fail("unexpected end."); | ||
|  |         } | ||
|  |         const { text } = this; | ||
|  |         if (text.length !== 0) { | ||
|  |             // eslint-disable-next-line no-unused-expressions
 | ||
|  |             (_a = this.textHandler) === null || _a === void 0 ? void 0 : _a.call(this, text); | ||
|  |             this.text = ""; | ||
|  |         } | ||
|  |         this._closed = true; | ||
|  |         // eslint-disable-next-line no-unused-expressions
 | ||
|  |         (_b = this.endHandler) === null || _b === void 0 ? void 0 : _b.call(this); | ||
|  |         this._init(); | ||
|  |         return this; | ||
|  |     } | ||
|  |     /** | ||
|  |      * Resolve a namespace prefix. | ||
|  |      * | ||
|  |      * @param prefix The prefix to resolve. | ||
|  |      * | ||
|  |      * @returns The namespace URI or ``undefined`` if the prefix is not defined. | ||
|  |      */ | ||
|  |     resolve(prefix) { | ||
|  |         var _a, _b; | ||
|  |         let uri = this.topNS[prefix]; | ||
|  |         if (uri !== undefined) { | ||
|  |             return uri; | ||
|  |         } | ||
|  |         const { tags } = this; | ||
|  |         for (let index = tags.length - 1; index >= 0; index--) { | ||
|  |             uri = tags[index].ns[prefix]; | ||
|  |             if (uri !== undefined) { | ||
|  |                 return uri; | ||
|  |             } | ||
|  |         } | ||
|  |         uri = this.ns[prefix]; | ||
|  |         if (uri !== undefined) { | ||
|  |             return uri; | ||
|  |         } | ||
|  |         return (_b = (_a = this.opt).resolvePrefix) === null || _b === void 0 ? void 0 : _b.call(_a, prefix); | ||
|  |     } | ||
|  |     /** | ||
|  |      * Parse a qname into its prefix and local name parts. | ||
|  |      * | ||
|  |      * @param name The name to parse | ||
|  |      * | ||
|  |      * @returns | ||
|  |      */ | ||
|  |     qname(name) { | ||
|  |         // This is faster than using name.split(":").
 | ||
|  |         const colon = name.indexOf(":"); | ||
|  |         if (colon === -1) { | ||
|  |             return { prefix: "", local: name }; | ||
|  |         } | ||
|  |         const local = name.slice(colon + 1); | ||
|  |         const prefix = name.slice(0, colon); | ||
|  |         if (prefix === "" || local === "" || local.includes(":")) { | ||
|  |             this.fail(`malformed name: ${name}.`); | ||
|  |         } | ||
|  |         return { prefix, local }; | ||
|  |     } | ||
|  |     processAttribsNS() { | ||
|  |         var _a; | ||
|  |         const { attribList } = this; | ||
|  |         const tag = this.tag; | ||
|  |         { | ||
|  |             // add namespace info to tag
 | ||
|  |             const { prefix, local } = this.qname(tag.name); | ||
|  |             tag.prefix = prefix; | ||
|  |             tag.local = local; | ||
|  |             const uri = tag.uri = (_a = this.resolve(prefix)) !== null && _a !== void 0 ? _a : ""; | ||
|  |             if (prefix !== "") { | ||
|  |                 if (prefix === "xmlns") { | ||
|  |                     this.fail("tags may not have \"xmlns\" as prefix."); | ||
|  |                 } | ||
|  |                 if (uri === "") { | ||
|  |                     this.fail(`unbound namespace prefix: ${JSON.stringify(prefix)}.`); | ||
|  |                     tag.uri = prefix; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |         if (attribList.length === 0) { | ||
|  |             return; | ||
|  |         } | ||
|  |         const { attributes } = tag; | ||
|  |         const seen = new Set(); | ||
|  |         // Note: do not apply default ns to attributes:
 | ||
|  |         //   http://www.w3.org/TR/REC-xml-names/#defaulting
 | ||
|  |         for (const attr of attribList) { | ||
|  |             const { name, prefix, local } = attr; | ||
|  |             let uri; | ||
|  |             let eqname; | ||
|  |             if (prefix === "") { | ||
|  |                 uri = name === "xmlns" ? XMLNS_NAMESPACE : ""; | ||
|  |                 eqname = name; | ||
|  |             } | ||
|  |             else { | ||
|  |                 uri = this.resolve(prefix); | ||
|  |                 // if there's any attributes with an undefined namespace,
 | ||
|  |                 // then fail on them now.
 | ||
|  |                 if (uri === undefined) { | ||
|  |                     this.fail(`unbound namespace prefix: ${JSON.stringify(prefix)}.`); | ||
|  |                     uri = prefix; | ||
|  |                 } | ||
|  |                 eqname = `{${uri}}${local}`; | ||
|  |             } | ||
|  |             if (seen.has(eqname)) { | ||
|  |                 this.fail(`duplicate attribute: ${eqname}.`); | ||
|  |             } | ||
|  |             seen.add(eqname); | ||
|  |             attr.uri = uri; | ||
|  |             attributes[name] = attr; | ||
|  |         } | ||
|  |         this.attribList = []; | ||
|  |     } | ||
|  |     processAttribsPlain() { | ||
|  |         const { attribList } = this; | ||
|  |         // eslint-disable-next-line prefer-destructuring
 | ||
|  |         const attributes = this.tag.attributes; | ||
|  |         for (const { name, value } of attribList) { | ||
|  |             if (attributes[name] !== undefined) { | ||
|  |                 this.fail(`duplicate attribute: ${name}.`); | ||
|  |             } | ||
|  |             attributes[name] = value; | ||
|  |         } | ||
|  |         this.attribList = []; | ||
|  |     } | ||
|  |     /** | ||
|  |      * Handle a complete open tag. This parser code calls this once it has seen | ||
|  |      * the whole tag. This method checks for well-formeness and then emits | ||
|  |      * ``onopentag``. | ||
|  |      */ | ||
|  |     openTag() { | ||
|  |         var _a; | ||
|  |         this.processAttribs(); | ||
|  |         const { tags } = this; | ||
|  |         const tag = this.tag; | ||
|  |         tag.isSelfClosing = false; | ||
|  |         // There cannot be any pending text here due to the onopentagstart that was
 | ||
|  |         // necessarily emitted before we get here. So we do not check text.
 | ||
|  |         // eslint-disable-next-line no-unused-expressions
 | ||
|  |         (_a = this.openTagHandler) === null || _a === void 0 ? void 0 : _a.call(this, tag); | ||
|  |         tags.push(tag); | ||
|  |         this.state = S_TEXT; | ||
|  |         this.name = ""; | ||
|  |     } | ||
|  |     /** | ||
|  |      * Handle a complete self-closing tag. This parser code calls this once it has | ||
|  |      * seen the whole tag. This method checks for well-formeness and then emits | ||
|  |      * ``onopentag`` and ``onclosetag``. | ||
|  |      */ | ||
|  |     openSelfClosingTag() { | ||
|  |         var _a, _b, _c; | ||
|  |         this.processAttribs(); | ||
|  |         const { tags } = this; | ||
|  |         const tag = this.tag; | ||
|  |         tag.isSelfClosing = true; | ||
|  |         // There cannot be any pending text here due to the onopentagstart that was
 | ||
|  |         // necessarily emitted before we get here. So we do not check text.
 | ||
|  |         // eslint-disable-next-line no-unused-expressions
 | ||
|  |         (_a = this.openTagHandler) === null || _a === void 0 ? void 0 : _a.call(this, tag); | ||
|  |         // eslint-disable-next-line no-unused-expressions
 | ||
|  |         (_b = this.closeTagHandler) === null || _b === void 0 ? void 0 : _b.call(this, tag); | ||
|  |         const top = this.tag = (_c = tags[tags.length - 1]) !== null && _c !== void 0 ? _c : null; | ||
|  |         if (top === null) { | ||
|  |             this.closedRoot = true; | ||
|  |         } | ||
|  |         this.state = S_TEXT; | ||
|  |         this.name = ""; | ||
|  |     } | ||
|  |     /** | ||
|  |      * Handle a complete close tag. This parser code calls this once it has seen | ||
|  |      * the whole tag. This method checks for well-formeness and then emits | ||
|  |      * ``onclosetag``. | ||
|  |      */ | ||
|  |     closeTag() { | ||
|  |         const { tags, name } = this; | ||
|  |         // Our state after this will be S_TEXT, no matter what, and we can clear
 | ||
|  |         // tagName now.
 | ||
|  |         this.state = S_TEXT; | ||
|  |         this.name = ""; | ||
|  |         if (name === "") { | ||
|  |             this.fail("weird empty close tag."); | ||
|  |             this.text += "</>"; | ||
|  |             return; | ||
|  |         } | ||
|  |         const handler = this.closeTagHandler; | ||
|  |         let l = tags.length; | ||
|  |         while (l-- > 0) { | ||
|  |             const tag = this.tag = tags.pop(); | ||
|  |             this.topNS = tag.ns; | ||
|  |             // eslint-disable-next-line no-unused-expressions
 | ||
|  |             handler === null || handler === void 0 ? void 0 : handler(tag); | ||
|  |             if (tag.name === name) { | ||
|  |                 break; | ||
|  |             } | ||
|  |             this.fail("unexpected close tag."); | ||
|  |         } | ||
|  |         if (l === 0) { | ||
|  |             this.closedRoot = true; | ||
|  |         } | ||
|  |         else if (l < 0) { | ||
|  |             this.fail(`unmatched closing tag: ${name}.`); | ||
|  |             this.text += `</${name}>`; | ||
|  |         } | ||
|  |     } | ||
|  |     /** | ||
|  |      * Resolves an entity. Makes any necessary well-formedness checks. | ||
|  |      * | ||
|  |      * @param entity The entity to resolve. | ||
|  |      * | ||
|  |      * @returns The parsed entity. | ||
|  |      */ | ||
|  |     parseEntity(entity) { | ||
|  |         // startsWith would be significantly slower for this test.
 | ||
|  |         // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
 | ||
|  |         if (entity[0] !== "#") { | ||
|  |             const defined = this.ENTITIES[entity]; | ||
|  |             if (defined !== undefined) { | ||
|  |                 return defined; | ||
|  |             } | ||
|  |             this.fail(this.isName(entity) ? "undefined entity." : | ||
|  |                 "disallowed character in entity name."); | ||
|  |             return `&${entity};`; | ||
|  |         } | ||
|  |         let num = NaN; | ||
|  |         if (entity[1] === "x" && /^#x[0-9a-f]+$/i.test(entity)) { | ||
|  |             num = parseInt(entity.slice(2), 16); | ||
|  |         } | ||
|  |         else if (/^#[0-9]+$/.test(entity)) { | ||
|  |             num = parseInt(entity.slice(1), 10); | ||
|  |         } | ||
|  |         // The character reference is required to match the CHAR production.
 | ||
|  |         if (!this.isChar(num)) { | ||
|  |             this.fail("malformed character entity."); | ||
|  |             return `&${entity};`; | ||
|  |         } | ||
|  |         return String.fromCodePoint(num); | ||
|  |     } | ||
|  | } | ||
|  | exports.SaxesParser = SaxesParser; | ||
|  | //# sourceMappingURL=saxes.js.map
 |