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
						
					
					
				| "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
 |