Spaces:
Paused
Paused
| ; | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.SaxesParser = exports.EVENTS = void 0; | |
| 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, @typescript-eslint/no-unsafe-assignment | |
| __proto__: null, | |
| xml: XML_NAMESPACE, | |
| xmlns: XMLNS_NAMESPACE, | |
| }; | |
| const XML_ENTITIES = { | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment | |
| __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", | |
| }; | |
| // eslint-disable-next-line @typescript-eslint/ban-types | |
| 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, @typescript-eslint/no-unsafe-assignment | |
| 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, | |
| /* eslint-enable @typescript-eslint/unbound-method */ | |
| ]; | |
| 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); | |
| (_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, @typescript-eslint/no-unsafe-member-access | |
| 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, @typescript-eslint/no-unsafe-member-access | |
| 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 | |
| */ | |
| // We do need object for the type here. Yes, it often causes problems | |
| // but not in this case. | |
| 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, @typescript-eslint/no-unsafe-argument | |
| 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: { | |
| (_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; | |
| (_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: { | |
| (_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."); | |
| } | |
| (_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."); | |
| } | |
| (_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); | |
| } | |
| (_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); | |
| (_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); | |
| (_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) { | |
| (_a = this.textHandler) === null || _a === void 0 ? void 0 : _a.call(this, text); | |
| this.text = ""; | |
| } | |
| this._closed = true; | |
| (_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. | |
| (_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. | |
| (_a = this.openTagHandler) === null || _a === void 0 ? void 0 : _a.call(this, tag); | |
| (_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; | |
| 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. | |
| 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 |