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