UNPKG

react-xml-viewer

Version:
1,486 lines 86.8 kB
import { jsx as p, jsxs as A, Fragment as de } from "react/jsx-runtime"; import * as se from "react"; import { useRef as F, useEffect as M, useCallback as B, createContext as fe, useState as V, useMemo as R, useContext as pe, memo as Be } from "react"; const ie = ":@", X = "#text", ee = "#comment", te = "#cdata", ge = "DECLARATION_TAG", Xe = "TAG", U = { tagColor: "#d43900", textColor: "#333", attributeKeyColor: "#2a7ab0", attributeValueColor: "#008000", separatorColor: "#333", commentColor: "#aaa", cdataColor: "#1d781d", fontFamily: "monospace", lineNumberBackground: "#eee", lineNumberColor: "#222" }; function me(n, e) { const t = F(n), r = F(); return M(() => { t.current = n; }, [n]), M(() => () => { r.current && clearTimeout(r.current); }, []), B( (...s) => { r.current && clearTimeout(r.current), r.current = setTimeout(() => { t.current(...s); }, e); }, [e] ); } const Ue = { lines: {}, push: () => { } }, be = fe(Ue), We = ({ children: n, enabled: e = !1 }) => { const [t, r] = V({}), s = F({}), i = B(() => { r(s.current); }, []), a = me(i, 100), o = B((h, u) => { var c; e && (((c = s.current[h]) == null ? void 0 : c.element) !== u.element && (s.current[h] = u), a()); }, []), l = R(() => ({ lines: t, push: o }), [t, o, e]); return /* @__PURE__ */ p(be.Provider, { value: l, children: n }); }, ye = () => pe(be), ze = { theme: U, collapsible: !1, indentSize: 2 }, Ee = fe(ze), _ = () => pe(Ee); function Ye(n, e) { return new Array(e * n + 1).join(" "); } function Ne(n) { return typeof n == "string" && n.includes(` `); } function Ke(n) { return typeof n == "string" ? !1 : n.length === 1 && X in n[0] && !Ne(n[0][X]); } function Ge(n) { switch (n) { case X: case te: case ee: return n; default: return n.startsWith("?") ? ge : Xe; } } function qe(n, e) { const t = {}; for (const r in n) r !== e && (t[r] = n[r]); return t; } function Ze(n) { const e = n[ie], t = qe(n, ie), [[r, s]] = Object.entries(t), i = Ge(r); return { attributes: e, tagKey: r, subElements: s, type: i }; } function Je(n, e) { return Object.keys(U).every((r) => n[r] === e[r]); } const we = ":A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD", Qe = we + "\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040", He = "[" + we + "][" + Qe + "]*", De = new RegExp("^" + He + "$"); function Te(n, e) { const t = []; let r = e.exec(n); for (; r; ) { const s = []; s.startIndex = e.lastIndex - r[0].length; const i = r.length; for (let a = 0; a < i; a++) s.push(r[a]); t.push(s), r = e.exec(n); } return t; } const Ce = function(n) { const e = De.exec(n); return !(e === null || typeof e > "u"); }; function et(n) { return typeof n < "u"; } const ne = [ // '__proto__', // 'constructor', // 'prototype', "hasOwnProperty", "toString", "valueOf", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__" ], _e = ["__proto__", "constructor", "prototype"], tt = { allowBooleanAttributes: !1, //A tag can have attributes without any value unpairedTags: [] }; function ve(n, e) { e = Object.assign({}, tt, e); const t = []; let r = !1, s = !1; n[0] === "\uFEFF" && (n = n.substr(1)); for (let i = 0; i < n.length; i++) if (n[i] === "<" && n[i + 1] === "?") { if (i += 2, i = ae(n, i), i.err) return i; } else if (n[i] === "<") { let a = i; if (i++, n[i] === "!") { i = le(n, i); continue; } else { let o = !1; n[i] === "/" && (o = !0, i++); let l = ""; for (; i < n.length && n[i] !== ">" && n[i] !== " " && n[i] !== " " && n[i] !== ` ` && n[i] !== "\r"; i++) l += n[i]; if (l = l.trim(), l[l.length - 1] === "/" && (l = l.substring(0, l.length - 1), i--), !ut(l)) { let c; return l.trim().length === 0 ? c = "Invalid space after '<'." : c = "Tag '" + l + "' is an invalid name.", b("InvalidTag", c, E(n, i)); } const h = st(n, i); if (h === !1) return b("InvalidAttr", "Attributes for '" + l + "' have open quote.", E(n, i)); let u = h.value; if (i = h.index, u[u.length - 1] === "/") { const c = i - u.length; u = u.substring(0, u.length - 1); const d = ue(u, e); if (d === !0) r = !0; else return b(d.err.code, d.err.msg, E(n, c + d.err.line)); } else if (o) if (h.tagClosed) { if (u.trim().length > 0) return b("InvalidTag", "Closing tag '" + l + "' can't have attributes or invalid starting.", E(n, a)); if (t.length === 0) return b("InvalidTag", "Closing tag '" + l + "' has not been opened.", E(n, a)); { const c = t.pop(); if (l !== c.tagName) { let d = E(n, c.tagStartPos); return b( "InvalidTag", "Expected closing tag '" + c.tagName + "' (opened in line " + d.line + ", col " + d.col + ") instead of closing tag '" + l + "'.", E(n, a) ); } t.length == 0 && (s = !0); } } else return b("InvalidTag", "Closing tag '" + l + "' doesn't have proper closing.", E(n, i)); else { const c = ue(u, e); if (c !== !0) return b(c.err.code, c.err.msg, E(n, i - u.length + c.err.line)); if (s === !0) return b("InvalidXml", "Multiple possible root nodes found.", E(n, i)); e.unpairedTags.indexOf(l) !== -1 || t.push({ tagName: l, tagStartPos: a }), r = !0; } for (i++; i < n.length; i++) if (n[i] === "<") if (n[i + 1] === "!") { i++, i = le(n, i); continue; } else if (n[i + 1] === "?") { if (i = ae(n, ++i), i.err) return i; } else break; else if (n[i] === "&") { const c = at(n, i); if (c == -1) return b("InvalidChar", "char '&' is not expected.", E(n, i)); i = c; } else if (s === !0 && !oe(n[i])) return b("InvalidXml", "Extra text at the end", E(n, i)); n[i] === "<" && i--; } } else { if (oe(n[i])) continue; return b("InvalidChar", "char '" + n[i] + "' is not expected.", E(n, i)); } if (r) { if (t.length == 1) return b("InvalidTag", "Unclosed tag '" + t[0].tagName + "'.", E(n, t[0].tagStartPos)); if (t.length > 0) return b("InvalidXml", "Invalid '" + JSON.stringify(t.map((i) => i.tagName), null, 4).replace(/\r?\n/g, "") + "' found.", { line: 1, col: 1 }); } else return b("InvalidXml", "Start tag expected.", 1); return !0; } function oe(n) { return n === " " || n === " " || n === ` ` || n === "\r"; } function ae(n, e) { const t = e; for (; e < n.length; e++) if (n[e] == "?" || n[e] == " ") { const r = n.substr(t, e - t); if (e > 5 && r === "xml") return b("InvalidXml", "XML declaration allowed only at the start of the document.", E(n, e)); if (n[e] == "?" && n[e + 1] == ">") { e++; break; } else continue; } return e; } function le(n, e) { if (n.length > e + 5 && n[e + 1] === "-" && n[e + 2] === "-") { for (e += 3; e < n.length; e++) if (n[e] === "-" && n[e + 1] === "-" && n[e + 2] === ">") { e += 2; break; } } else if (n.length > e + 8 && n[e + 1] === "D" && n[e + 2] === "O" && n[e + 3] === "C" && n[e + 4] === "T" && n[e + 5] === "Y" && n[e + 6] === "P" && n[e + 7] === "E") { let t = 1; for (e += 8; e < n.length; e++) if (n[e] === "<") t++; else if (n[e] === ">" && (t--, t === 0)) break; } else if (n.length > e + 9 && n[e + 1] === "[" && n[e + 2] === "C" && n[e + 3] === "D" && n[e + 4] === "A" && n[e + 5] === "T" && n[e + 6] === "A" && n[e + 7] === "[") { for (e += 8; e < n.length; e++) if (n[e] === "]" && n[e + 1] === "]" && n[e + 2] === ">") { e += 2; break; } } return e; } const nt = '"', rt = "'"; function st(n, e) { let t = "", r = "", s = !1; for (; e < n.length; e++) { if (n[e] === nt || n[e] === rt) r === "" ? r = n[e] : r !== n[e] || (r = ""); else if (n[e] === ">" && r === "") { s = !0; break; } t += n[e]; } return r !== "" ? !1 : { value: t, index: e, tagClosed: s }; } const it = new RegExp(`(\\s*)([^\\s=]+)(\\s*=)?(\\s*(['"])(([\\s\\S])*?)\\5)?`, "g"); function ue(n, e) { const t = Te(n, it), r = {}; for (let s = 0; s < t.length; s++) { if (t[s][1].length === 0) return b("InvalidAttr", "Attribute '" + t[s][2] + "' has no space in starting.", O(t[s])); if (t[s][3] !== void 0 && t[s][4] === void 0) return b("InvalidAttr", "Attribute '" + t[s][2] + "' is without value.", O(t[s])); if (t[s][3] === void 0 && !e.allowBooleanAttributes) return b("InvalidAttr", "boolean attribute '" + t[s][2] + "' is not allowed.", O(t[s])); const i = t[s][2]; if (!lt(i)) return b("InvalidAttr", "Attribute '" + i + "' is an invalid name.", O(t[s])); if (!Object.prototype.hasOwnProperty.call(r, i)) r[i] = 1; else return b("InvalidAttr", "Attribute '" + i + "' is repeated.", O(t[s])); } return !0; } function ot(n, e) { let t = /\d/; for (n[e] === "x" && (e++, t = /[\da-fA-F]/); e < n.length; e++) { if (n[e] === ";") return e; if (!n[e].match(t)) break; } return -1; } function at(n, e) { if (e++, n[e] === ";") return -1; if (n[e] === "#") return e++, ot(n, e); let t = 0; for (; e < n.length; e++, t++) if (!(n[e].match(/\w/) && t < 20)) { if (n[e] === ";") break; return -1; } return e; } function b(n, e, t) { return { err: { code: n, msg: e, line: t.line || t, col: t.col } }; } function lt(n) { return Ce(n); } function ut(n) { return Ce(n); } function E(n, e) { const t = n.substring(0, e).split(/\r?\n/); return { line: t.length, // column number is last line's length + 1, because column numbering starts at 1: col: t[t.length - 1].length + 1 }; } function O(n) { return n.startIndex + n[1].length; } const ct = { cent: "¢", pound: "£", curren: "¤", yen: "¥", euro: "€", dollar: "$", euro: "€", fnof: "ƒ", inr: "₹", af: "؋", birr: "ብር", peso: "₱", rub: "₽", won: "₩", yuan: "¥", cedil: "¸" }, Ae = { amp: "&", apos: "'", gt: ">", lt: "<", quot: '"' }, ht = { nbsp: " ", copy: "©", reg: "®", trade: "™", mdash: "—", ndash: "–", hellip: "…", laquo: "«", raquo: "»", lsquo: "‘", rsquo: "’", ldquo: "“", rdquo: "”", bull: "•", para: "¶", sect: "§", deg: "°", frac12: "½", frac14: "¼", frac34: "¾" }, dt = new Set("!?\\\\/[]$%{}^&*()<>|+"); function ce(n) { if (n[0] === "#") throw new Error(`[EntityReplacer] Invalid character '#' in entity name: "${n}"`); for (const e of n) if (dt.has(e)) throw new Error(`[EntityReplacer] Invalid character '${e}' in entity name: "${n}"`); return n; } function G(...n) { const e = /* @__PURE__ */ Object.create(null); for (const t of n) if (t) for (const r of Object.keys(t)) { const s = t[r]; if (typeof s == "string") e[r] = s; else if (s && typeof s == "object" && s.val !== void 0) { const i = s.val; typeof i == "string" && (e[r] = i); } } return e; } const P = "external", W = "base", Q = "all"; function ft(n) { return !n || n === P ? /* @__PURE__ */ new Set([P]) : n === Q ? /* @__PURE__ */ new Set([Q]) : n === W ? /* @__PURE__ */ new Set([W]) : Array.isArray(n) ? new Set(n) : /* @__PURE__ */ new Set([P]); } const T = Object.freeze({ allow: 0, leave: 1, remove: 2, throw: 3 }), pt = /* @__PURE__ */ new Set([9, 10, 13]); function gt(n) { if (!n) return { xmlVersion: 1, onLevel: T.allow, nullLevel: T.remove }; const e = n.xmlVersion === 1.1 ? 1.1 : 1, t = T[n.onNCR] ?? T.allow, r = T[n.nullNCR] ?? T.remove, s = Math.max(r, T.remove); return { xmlVersion: e, onLevel: t, nullLevel: s }; } class mt { /** * @param {object} [options] * @param {object|null} [options.namedEntities] — extra named entities merged into base map * @param {object} [options.limit] — security limits * @param {number} [options.limit.maxTotalExpansions=0] — 0 = unlimited * @param {number} [options.limit.maxExpandedLength=0] — 0 = unlimited * @param {'external'|'base'|'all'|string[]} [options.limit.applyLimitsTo='external'] * Which entity tiers count against the security limits: * - 'external' (default) — only input/runtime + persistent external entities * - 'base' — only DEFAULT_XML_ENTITIES + namedEntities * - 'all' — every entity regardless of tier * - string[] — explicit combination, e.g. ['external', 'base'] * @param {((resolved: string, original: string) => string)|null} [options.postCheck=null] * @param {string[]} [options.remove=[]] — entity names (e.g. ['nbsp', '#13']) to delete (replace with empty string) * @param {string[]} [options.leave=[]] — entity names to keep as literal (unchanged in output) * @param {object} [options.ncr] — Numeric Character Reference controls * @param {1.0|1.1} [options.ncr.xmlVersion=1.0] * XML version governing which codepoint ranges are restricted: * - 1.0 — C0 controls U+0001–U+001F (except U+0009/000A/000D) are prohibited * - 1.1 — C0 controls are allowed when written as NCRs; C1 (U+007F–U+009F) decoded as-is * @param {'allow'|'leave'|'remove'|'throw'} [options.ncr.onNCR='allow'] * Base action for numeric references. Severity order: allow < leave < remove < throw. * For codepoint ranges that carry a minimum level (surrogates → remove, XML 1.0 C0 → remove), * the effective action is max(onNCR, rangeMinimum). * @param {'remove'|'throw'} [options.ncr.nullNCR='remove'] * Action for U+0000 (null). 'allow' and 'leave' are clamped to 'remove' since null is never safe. */ constructor(e = {}) { this._limit = e.limit || {}, this._maxTotalExpansions = this._limit.maxTotalExpansions || 0, this._maxExpandedLength = this._limit.maxExpandedLength || 0, this._postCheck = typeof e.postCheck == "function" ? e.postCheck : (r) => r, this._limitTiers = ft(this._limit.applyLimitsTo ?? P), this._numericAllowed = e.numericAllowed ?? !0, this._baseMap = G(Ae, e.namedEntities || null), this._externalMap = /* @__PURE__ */ Object.create(null), this._inputMap = /* @__PURE__ */ Object.create(null), this._totalExpansions = 0, this._expandedLength = 0, this._removeSet = new Set(e.remove && Array.isArray(e.remove) ? e.remove : []), this._leaveSet = new Set(e.leave && Array.isArray(e.leave) ? e.leave : []); const t = gt(e.ncr); this._ncrXmlVersion = t.xmlVersion, this._ncrOnLevel = t.onLevel, this._ncrNullLevel = t.nullLevel; } // ------------------------------------------------------------------------- // Persistent external entity registration // ------------------------------------------------------------------------- /** * Replace the full set of persistent external entities. * All keys are validated — throws on invalid characters. * @param {Record<string, string | { regex?: RegExp, val: string }>} map */ setExternalEntities(e) { if (e) for (const t of Object.keys(e)) ce(t); this._externalMap = G(e); } /** * Add a single persistent external entity. * @param {string} key * @param {string} value */ addExternalEntity(e, t) { ce(e), typeof t == "string" && t.indexOf("&") === -1 && (this._externalMap[e] = t); } // ------------------------------------------------------------------------- // Input / runtime entity registration (per document) // ------------------------------------------------------------------------- /** * Inject DOCTYPE entities for the current document. * Also resets per-document expansion counters. * @param {Record<string, string | { regx?: RegExp, regex?: RegExp, val: string }>} map */ addInputEntities(e) { this._totalExpansions = 0, this._expandedLength = 0, this._inputMap = G(e); } // ------------------------------------------------------------------------- // Per-document reset // ------------------------------------------------------------------------- /** * Wipe input/runtime entities and reset counters. * Call this before processing each new document. * @returns {this} */ reset() { return this._inputMap = /* @__PURE__ */ Object.create(null), this._totalExpansions = 0, this._expandedLength = 0, this; } // ------------------------------------------------------------------------- // XML version (can be set after construction, e.g. once parser reads <?xml?>) // ------------------------------------------------------------------------- /** * Update the XML version used for NCR classification. * Call this as soon as the document's `<?xml version="...">` declaration is parsed. * @param {1.0|1.1|number} version */ setXmlVersion(e) { this._ncrXmlVersion = e === 1.1 ? 1.1 : 1; } // ------------------------------------------------------------------------- // Primary API // ------------------------------------------------------------------------- /** * Replace all entity references in `str` in a single pass. * * @param {string} str * @returns {string} */ decode(e) { if (typeof e != "string" || e.length === 0) return e; const t = e, r = [], s = e.length; let i = 0, a = 0; const o = this._maxTotalExpansions > 0, l = this._maxExpandedLength > 0, h = o || l; for (; a < s; ) { if (e.charCodeAt(a) !== 38) { a++; continue; } let c = a + 1; for (; c < s && e.charCodeAt(c) !== 59 && c - a <= 32; ) c++; if (c >= s || e.charCodeAt(c) !== 59) { a++; continue; } const d = e.slice(a + 1, c); if (d.length === 0) { a++; continue; } let f, g; if (this._removeSet.has(d)) f = "", g === void 0 && (g = P); else if (this._leaveSet.has(d)) { a++; continue; } else if (d.charCodeAt(0) === 35) { const m = this._resolveNCR(d); if (m === void 0) { a++; continue; } f = m, g = W; } else { const m = this._resolveName(d); f = m == null ? void 0 : m.value, g = m == null ? void 0 : m.tier; } if (f === void 0) { a++; continue; } if (a > i && r.push(e.slice(i, a)), r.push(f), i = c + 1, a = i, h && this._tierCounts(g)) { if (o && (this._totalExpansions++, this._totalExpansions > this._maxTotalExpansions)) throw new Error( `[EntityReplacer] Entity expansion count limit exceeded: ${this._totalExpansions} > ${this._maxTotalExpansions}` ); if (l) { const m = f.length - (d.length + 2); if (m > 0 && (this._expandedLength += m, this._expandedLength > this._maxExpandedLength)) throw new Error( `[EntityReplacer] Expanded content length limit exceeded: ${this._expandedLength} > ${this._maxExpandedLength}` ); } } } i < s && r.push(e.slice(i)); const u = r.length === 0 ? e : r.join(""); return this._postCheck(u, t); } // ------------------------------------------------------------------------- // Private: limit tier check // ------------------------------------------------------------------------- /** * Returns true if a resolved entity of the given tier should count * against the expansion/length limits. * @param {string} tier — LIMIT_TIER_EXTERNAL | LIMIT_TIER_BASE * @returns {boolean} */ _tierCounts(e) { return this._limitTiers.has(Q) ? !0 : this._limitTiers.has(e); } // ------------------------------------------------------------------------- // Private: entity resolution // ------------------------------------------------------------------------- /** * Resolve a named entity token (without & and ;). * Priority: inputMap > externalMap > baseMap * Returns the resolved value tagged with its limit tier. * * @param {string} name * @returns {{ value: string, tier: string }|undefined} */ _resolveName(e) { if (e in this._inputMap) return { value: this._inputMap[e], tier: P }; if (e in this._externalMap) return { value: this._externalMap[e], tier: P }; if (e in this._baseMap) return { value: this._baseMap[e], tier: W }; } /** * Classify a codepoint and return the minimum action level that must be applied. * Returns -1 when no minimum is imposed (normal allow path). * * Ranges checked (in priority order): * 1. U+0000 — null, governed by nullNCR (always ≥ remove) * 2. U+D800–U+DFFF — surrogates, always prohibited (min: remove) * 3. U+0001–U+001F \ {0x09,0x0A,0x0D} — XML 1.0 restricted C0 (min: remove) * (skipped in XML 1.1 — C0 controls are allowed when written as NCRs) * * @param {number} cp — codepoint * @returns {number} — minimum NCR_LEVEL value, or -1 for no restriction */ _classifyNCR(e) { return e === 0 ? this._ncrNullLevel : e >= 55296 && e <= 57343 || this._ncrXmlVersion === 1 && e >= 1 && e <= 31 && !pt.has(e) ? T.remove : -1; } /** * Execute a resolved NCR action. * * @param {number} action — NCR_LEVEL value * @param {string} token — raw token (e.g. '#38') for error messages * @param {number} cp — codepoint, used only for error messages * @returns {string|undefined} * - decoded character string → 'allow' * - '' → 'remove' * - undefined → 'leave' (caller must skip past '&' only) * - throws Error → 'throw' */ _applyNCRAction(e, t, r) { switch (e) { case T.allow: return String.fromCodePoint(r); case T.remove: return ""; case T.leave: return; case T.throw: throw new Error( `[EntityDecoder] Prohibited numeric character reference &${t}; (U+${r.toString(16).toUpperCase().padStart(4, "0")})` ); default: return String.fromCodePoint(r); } } /** * Full NCR resolution pipeline for a numeric token. * * Steps: * 1. Parse the codepoint (decimal or hex). * 2. Validate the raw codepoint range (NaN, <0, >0x10FFFF). * 3. If numericAllowed is false and no minimum restriction applies → leave as-is. * 4. Classify the codepoint to find the minimum required action level. * 5. Resolve effective action = max(onNCR, minimum). * 6. Apply and return. * * @param {string} token — e.g. '#38', '#x26', '#X26' * @returns {string|undefined} * - string (incl. '') — replacement ('' = remove) * - undefined — leave original &token; as-is */ _resolveNCR(e) { const t = e.charCodeAt(1); let r; if (t === 120 || t === 88 ? r = parseInt(e.slice(2), 16) : r = parseInt(e.slice(1), 10), Number.isNaN(r) || r < 0 || r > 1114111) return; const s = this._classifyNCR(r); if (!this._numericAllowed && s < T.remove) return; const i = s === -1 ? this._ncrOnLevel : Math.max(this._ncrOnLevel, s); return this._applyNCRAction(i, e, r); } } const Ie = (n) => ne.includes(n) ? "__" + n : n, bt = { preserveOrder: !1, attributeNamePrefix: "@_", attributesGroupName: !1, textNodeName: "#text", ignoreAttributes: !0, removeNSPrefix: !1, // remove NS from tag name or attribute name if true allowBooleanAttributes: !1, //a tag can have attributes without any value //ignoreRootElement : false, parseTagValue: !0, parseAttributeValue: !1, trimValues: !0, //Trim string values of tag and attributes cdataPropName: !1, numberParseOptions: { hex: !0, leadingZeros: !0, eNotation: !0 }, tagValueProcessor: function(n, e) { return e; }, attributeValueProcessor: function(n, e) { return e; }, stopNodes: [], //nested tags will not be parsed even for errors alwaysCreateTextNode: !1, isArray: () => !1, commentPropName: !1, unpairedTags: [], processEntities: !0, htmlEntities: !1, entityDecoder: null, ignoreDeclaration: !1, ignorePiTags: !1, transformTagName: !1, transformAttributeName: !1, updateTag: function(n, e, t) { return n; }, // skipEmptyListItem: false captureMetaData: !1, maxNestedTags: 100, strictReservedNames: !0, jPath: !0, // if true, pass jPath string to callbacks; if false, pass matcher instance onDangerousProperty: Ie }; function yt(n, e) { if (typeof n != "string") return; const t = n.toLowerCase(); if (ne.some((r) => t === r.toLowerCase())) throw new Error( `[SECURITY] Invalid ${e}: "${n}" is a reserved JavaScript keyword that could cause prototype pollution` ); if (_e.some((r) => t === r.toLowerCase())) throw new Error( `[SECURITY] Invalid ${e}: "${n}" is a reserved JavaScript keyword that could cause prototype pollution` ); } function Se(n, e) { return typeof n == "boolean" ? { enabled: n, // true or false maxEntitySize: 1e4, maxExpansionDepth: 1e4, maxTotalExpansions: 1 / 0, maxExpandedLength: 1e5, maxEntityCount: 1e3, allowedTags: null, tagFilter: null, appliesTo: "all" } : typeof n == "object" && n !== null ? { enabled: n.enabled !== !1, maxEntitySize: Math.max(1, n.maxEntitySize ?? 1e4), maxExpansionDepth: Math.max(1, n.maxExpansionDepth ?? 1e4), maxTotalExpansions: Math.max(1, n.maxTotalExpansions ?? 1 / 0), maxExpandedLength: Math.max(1, n.maxExpandedLength ?? 1e5), maxEntityCount: Math.max(1, n.maxEntityCount ?? 1e3), allowedTags: n.allowedTags ?? null, tagFilter: n.tagFilter ?? null, appliesTo: n.appliesTo ?? "all" } : Se(!0); } const Et = function(n) { const e = Object.assign({}, bt, n), t = [ { value: e.attributeNamePrefix, name: "attributeNamePrefix" }, { value: e.attributesGroupName, name: "attributesGroupName" }, { value: e.textNodeName, name: "textNodeName" }, { value: e.cdataPropName, name: "cdataPropName" }, { value: e.commentPropName, name: "commentPropName" } ]; for (const { value: r, name: s } of t) r && yt(r, s); return e.onDangerousProperty === null && (e.onDangerousProperty = Ie), e.processEntities = Se(e.processEntities, e.htmlEntities), e.unpairedTagsSet = new Set(e.unpairedTags), e.stopNodes && Array.isArray(e.stopNodes) && (e.stopNodes = e.stopNodes.map((r) => typeof r == "string" && r.startsWith("*.") ? ".." + r.substring(2) : r)), e; }; let z; typeof Symbol != "function" ? z = "@@xmlMetadata" : z = Symbol("XML Node Metadata"); class I { constructor(e) { this.tagname = e, this.child = [], this[":@"] = /* @__PURE__ */ Object.create(null); } add(e, t) { e === "__proto__" && (e = "#__proto__"), this.child.push({ [e]: t }); } addChild(e, t) { e.tagname === "__proto__" && (e.tagname = "#__proto__"), e[":@"] && Object.keys(e[":@"]).length > 0 ? this.child.push({ [e.tagname]: e.child, ":@": e[":@"] }) : this.child.push({ [e.tagname]: e.child }), t !== void 0 && (this.child[this.child.length - 1][z] = { startIndex: t }); } /** symbol used for metadata */ static getMetaDataSymbol() { return z; } } const xe = ":A-Za-z_À-ÖØ-öø-˿Ͱ-ͽͿ-҆҈-῿‌-‍⁰-↏Ⰰ-⿯、-퟿豈-﷏ﷰ-�", Nt = xe + "\\-\\.\\d·̀-ͯ‿-⁀", Pe = ":A-Za-z_À-˿Ͱ-ͽͿ-҆҈-῿‌-‍⁰-↏Ⰰ-⿯、-퟿豈-﷏ﷰ-�𐀀-󯿿", wt = Pe + "\\-\\.\\d·̀-ͯ҇‿-⁀", Me = (n, e, t = "") => { const r = n.replace(":", ""), s = e.replace(":", ""), i = `[${r}][${s}]*`; return { name: new RegExp(`^[${n}][${e}]*$`, t), ncName: new RegExp(`^${i}$`, t), qName: new RegExp(`^${i}(?::${i})?$`, t), nmToken: new RegExp(`^[${e}]+$`, t), nmTokens: new RegExp(`^[${e}]+(?:\\s+[${e}]+)*$`, t) }; }, Tt = Me(xe, Nt), Ct = Me(Pe, wt, "u"), _t = (n = "1.0") => n === "1.1" ? Ct : Tt, $e = (n, { xmlVersion: e = "1.0" } = {}) => _t(e).qName.test(n); class vt { constructor(e, t) { this.suppressValidationErr = !e, this.options = e, this.xmlVersion = t || 1; } setXmlVersion(e = 1) { this.xmlVersion = e; } readDocType(e, t) { const r = /* @__PURE__ */ Object.create(null); let s = 0; if (e[t + 3] === "O" && e[t + 4] === "C" && e[t + 5] === "T" && e[t + 6] === "Y" && e[t + 7] === "P" && e[t + 8] === "E") { t = t + 9; let i = 1, a = !1, o = !1, l = ""; for (; t < e.length; t++) if (e[t] === "<" && !o) { if (a && x(e, "!ENTITY", t)) { t += 7; let h, u; if ([h, u, t] = this.readEntityExp(e, t + 1, this.suppressValidationErr), u.indexOf("&") === -1) { if (this.options.enabled !== !1 && this.options.maxEntityCount != null && s >= this.options.maxEntityCount) throw new Error( `Entity count (${s + 1}) exceeds maximum allowed (${this.options.maxEntityCount})` ); r[h] = u, s++; } } else if (a && x(e, "!ELEMENT", t)) { t += 8; const { index: h } = this.readElementExp(e, t + 1); t = h; } else if (a && x(e, "!ATTLIST", t)) t += 8; else if (a && x(e, "!NOTATION", t)) { t += 9; const { index: h } = this.readNotationExp(e, t + 1, this.suppressValidationErr); t = h; } else if (x(e, "!--", t)) o = !0; else throw new Error("Invalid DOCTYPE"); i++, l = ""; } else if (e[t] === ">") { if (o ? e[t - 1] === "-" && e[t - 2] === "-" && (o = !1, i--) : i--, i === 0) break; } else e[t] === "[" ? a = !0 : l += e[t]; if (i !== 0) throw new Error("Unclosed DOCTYPE"); } else throw new Error("Invalid Tag instead of DOCTYPE"); return { entities: r, i: t }; } readEntityExp(e, t) { t = w(e, t); const r = t; for (; t < e.length && !/\s/.test(e[t]) && e[t] !== '"' && e[t] !== "'"; ) t++; let s = e.substring(r, t); if (L(s, { xmlVersion: this.xmlVersion }), t = w(e, t), !this.suppressValidationErr) { if (e.substring(t, t + 6).toUpperCase() === "SYSTEM") throw new Error("External entities are not supported"); if (e[t] === "%") throw new Error("Parameter entities are not supported"); } let i = ""; if ([t, i] = this.readIdentifierVal(e, t, "entity"), this.options.enabled !== !1 && this.options.maxEntitySize != null && i.length > this.options.maxEntitySize) throw new Error( `Entity "${s}" size (${i.length}) exceeds maximum allowed size (${this.options.maxEntitySize})` ); return t--, [s, i, t]; } readNotationExp(e, t) { t = w(e, t); const r = t; for (; t < e.length && !/\s/.test(e[t]); ) t++; let s = e.substring(r, t); !this.suppressValidationErr && L(s, { xmlVersion: this.xmlVersion }), t = w(e, t); const i = e.substring(t, t + 6).toUpperCase(); if (!this.suppressValidationErr && i !== "SYSTEM" && i !== "PUBLIC") throw new Error(`Expected SYSTEM or PUBLIC, found "${i}"`); t += i.length, t = w(e, t); let a = null, o = null; if (i === "PUBLIC") [t, a] = this.readIdentifierVal(e, t, "publicIdentifier"), t = w(e, t), (e[t] === '"' || e[t] === "'") && ([t, o] = this.readIdentifierVal(e, t, "systemIdentifier")); else if (i === "SYSTEM" && ([t, o] = this.readIdentifierVal(e, t, "systemIdentifier"), !this.suppressValidationErr && !o)) throw new Error("Missing mandatory system identifier for SYSTEM notation"); return { notationName: s, publicIdentifier: a, systemIdentifier: o, index: --t }; } readIdentifierVal(e, t, r) { let s = ""; const i = e[t]; if (i !== '"' && i !== "'") throw new Error(`Expected quoted string, found "${i}"`); t++; const a = t; for (; t < e.length && e[t] !== i; ) t++; if (s = e.substring(a, t), e[t] !== i) throw new Error(`Unterminated ${r} value`); return t++, [t, s]; } readElementExp(e, t) { t = w(e, t); const r = t; for (; t < e.length && !/\s/.test(e[t]); ) t++; let s = e.substring(r, t); if (!this.suppressValidationErr && !$e(s, { xmlVersion: this.xmlVersion })) throw new Error(`Invalid element name: "${s}"`); t = w(e, t); let i = ""; if (e[t] === "E" && x(e, "MPTY", t)) t += 4; else if (e[t] === "A" && x(e, "NY", t)) t += 2; else if (e[t] === "(") { t++; const a = t; for (; t < e.length && e[t] !== ")"; ) t++; if (i = e.substring(a, t), e[t] !== ")") throw new Error("Unterminated content model"); } else if (!this.suppressValidationErr) throw new Error(`Invalid Element Expression, found "${e[t]}"`); return { elementName: s, contentModel: i.trim(), index: t }; } readAttlistExp(e, t) { t = w(e, t); let r = t; for (; t < e.length && !/\s/.test(e[t]); ) t++; let s = e.substring(r, t); for (L(s, { xmlVersion: this.xmlVersion }), t = w(e, t), r = t; t < e.length && !/\s/.test(e[t]); ) t++; let i = e.substring(r, t); if (!L(i, { xmlVersion: this.xmlVersion })) throw new Error(`Invalid attribute name: "${i}"`); t = w(e, t); let a = ""; if (e.substring(t, t + 8).toUpperCase() === "NOTATION") { if (a = "NOTATION", t += 8, t = w(e, t), e[t] !== "(") throw new Error(`Expected '(', found "${e[t]}"`); t++; let l = []; for (; t < e.length && e[t] !== ")"; ) { const h = t; for (; t < e.length && e[t] !== "|" && e[t] !== ")"; ) t++; let u = e.substring(h, t); if (u = u.trim(), !L(u, { xmlVersion: this.xmlVersion })) throw new Error(`Invalid notation name: "${u}"`); l.push(u), e[t] === "|" && (t++, t = w(e, t)); } if (e[t] !== ")") throw new Error("Unterminated list of notations"); t++, a += " (" + l.join("|") + ")"; } else { const l = t; for (; t < e.length && !/\s/.test(e[t]); ) t++; a += e.substring(l, t); const h = ["CDATA", "ID", "IDREF", "IDREFS", "ENTITY", "ENTITIES", "NMTOKEN", "NMTOKENS"]; if (!this.suppressValidationErr && !h.includes(a.toUpperCase())) throw new Error(`Invalid attribute type: "${a}"`); } t = w(e, t); let o = ""; return e.substring(t, t + 8).toUpperCase() === "#REQUIRED" ? (o = "#REQUIRED", t += 8) : e.substring(t, t + 7).toUpperCase() === "#IMPLIED" ? (o = "#IMPLIED", t += 7) : [t, o] = this.readIdentifierVal(e, t, "ATTLIST"), { elementName: s, attributeName: i, attributeType: a, defaultValue: o, index: t }; } } const w = (n, e) => { for (; e < n.length && /\s/.test(n[e]); ) e++; return e; }; function x(n, e, t) { for (let r = 0; r < e.length; r++) if (e[r] !== n[t + r + 1]) return !1; return !0; } function L(n, e) { if ($e(n, { xmlVersion: e })) return n; throw new Error(`Invalid entity name ${n}`); } const At = /^[-+]?0x[a-fA-F0-9]+$/, It = /^0b[01]+$/, St = /^0o[0-7]+$/, xt = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/, Pt = { hex: !0, binary: !1, octal: !1, leadingZeros: !0, decimalPoint: ".", eNotation: !0, //skipLike: /regex/, infinity: "original" // "null", "infinity" (Infinity type), "string" ("Infinity" (the string literal)) }; function Mt(n, e = {}) { if (e = Object.assign({}, Pt, e), !n || typeof n != "string") return n; let t = n.trim(); if (t.length === 0) return n; if (e.skipLike !== void 0 && e.skipLike.test(t)) return n; if (t === "0") return 0; if (e.hex && At.test(t)) return q(t, 16); if (e.binary && It.test(t)) return q(t, 2); if (e.octal && St.test(t)) return q(t, 8); if (isFinite(t)) { if (t.includes("e") || t.includes("E")) return Ot(n, t, e); { const r = xt.exec(t); if (r) { const s = r[1] || "", i = r[2]; let a = Lt(r[3]); const o = s ? ( // 0., -00., 000. n[i.length + 1] === "." ) : n[i.length] === "."; if (!e.leadingZeros && (i.length > 1 || i.length === 1 && !o)) return n; { const l = Number(t), h = String(l); if (l === 0) return l; if (h.search(/[eE]/) !== -1) return e.eNotation ? l : n; if (t.indexOf(".") !== -1) return h === "0" || h === a || h === `${s}${a}` ? l : n; let u = i ? a : t; return i ? u === h || s + u === h ? l : n : u === h || u === s + h ? l : n; } } else return n; } } else return Ft(n, Number(t), e); } const $t = /^([-+])?(0*)(\d*(\.\d*)?[eE][-\+]?\d+)$/; function Ot(n, e, t) { if (!t.eNotation) return n; const r = e.match($t); if (r) { let s = r[1] || ""; const i = r[3].indexOf("e") === -1 ? "E" : "e", a = r[2], o = s ? ( // 0E. n[a.length + 1] === i ) : n[a.length] === i; return a.length > 1 && o ? n : a.length === 1 && (r[3].startsWith(`.${i}`) || r[3][0] === i) ? Number(e) : a.length > 0 ? t.leadingZeros && !o ? (e = (r[1] || "") + r[3], Number(e)) : n : Number(e); } else return n; } function Lt(n) { return n && n.indexOf(".") !== -1 && (n = n.replace(/0+$/, ""), n === "." ? n = "0" : n[0] === "." ? n = "0" + n : n[n.length - 1] === "." && (n = n.substring(0, n.length - 1))), n; } function q(n, e) { const t = n.trim(); if ((e === 2 || e === 8) && (n = t.substring(2)), parseInt) return parseInt(n, e); if (Number.parseInt) return Number.parseInt(n, e); if (window && window.parseInt) return window.parseInt(n, e); throw new Error("parseInt, Number.parseInt, window.parseInt are not supported"); } function Ft(n, e, t) { const r = e === 1 / 0; switch (t.infinity.toLowerCase()) { case "null": return null; case "infinity": return e; case "string": return r ? "Infinity" : "-Infinity"; case "original": default: return n; } } function Vt(n) { return typeof n == "function" ? n : Array.isArray(n) ? (e) => { for (const t of n) if (typeof t == "string" && e === t || t instanceof RegExp && t.test(e)) return !0; } : () => !1; } class he { /** * Create a new Expression * @param {string} pattern - Pattern string (e.g., "root.users.user", "..user[id]") * @param {Object} options - Configuration options * @param {string} options.separator - Path separator (default: '.') */ constructor(e, t = {}, r) { this.pattern = e, this.separator = t.separator || ".", this.segments = this._parse(e), this.data = r, this._hasDeepWildcard = this.segments.some((s) => s.type === "deep-wildcard"), this._hasAttributeCondition = this.segments.some((s) => s.attrName !== void 0), this._hasPositionSelector = this.segments.some((s) => s.position !== void 0); } /** * Parse pattern string into segments * @private * @param {string} pattern - Pattern to parse * @returns {Array} Array of segment objects */ _parse(e) { const t = []; let r = 0, s = ""; for (; r < e.length; ) e[r] === this.separator ? r + 1 < e.length && e[r + 1] === this.separator ? (s.trim() && (t.push(this._parseSegment(s.trim())), s = ""), t.push({ type: "deep-wildcard" }), r += 2) : (s.trim() && t.push(this._parseSegment(s.trim())), s = "", r++) : (s += e[r], r++); return s.trim() && t.push(this._parseSegment(s.trim())), t; } /** * Parse a single segment * @private * @param {string} part - Segment string (e.g., "user", "ns::user", "user[id]", "ns::user:first") * @returns {Object} Segment object */ _parseSegment(e) { const t = { type: "tag" }; let r = null, s = e; const i = e.match(/^([^\[]+)(\[[^\]]*\])(.*)$/); if (i && (s = i[1] + i[3], i[2])) { const u = i[2].slice(1, -1); u && (r = u); } let a, o = s; if (s.includes("::")) { const u = s.indexOf("::"); if (a = s.substring(0, u).trim(), o = s.substring(u + 2).trim(), !a) throw new Error(`Invalid namespace in pattern: ${e}`); } let l, h = null; if (o.includes(":")) { const u = o.lastIndexOf(":"), c = o.substring(0, u).trim(), d = o.substring(u + 1).trim(); ["first", "last", "odd", "even"].includes(d) || /^nth\(\d+\)$/.test(d) ? (l = c, h = d) : l = o; } else l = o; if (!l) throw new Error(`Invalid segment pattern: ${e}`); if (t.tag = l, a && (t.namespace = a), r) if (r.includes("=")) { const u = r.indexOf("="); t.attrName = r.substring(0, u).trim(), t.attrValue = r.substring(u + 1).trim(); } else t.attrName = r.trim(); if (h) { const u = h.match(/^nth\((\d+)\)$/); u ? (t.position = "nth", t.positionValue = parseInt(u[1], 10)) : t.position = h; } return t; } /** * Get the number of segments * @returns {number} */ get length() { return this.segments.length; } /** * Check if expression contains deep wildcard * @returns {boolean} */ hasDeepWildcard() { return this._hasDeepWildcard; } /** * Check if expression has attribute conditions * @returns {boolean} */ hasAttributeCondition() { return this._hasAttributeCondition; } /** * Check if expression has position selectors * @returns {boolean} */ hasPositionSelector() { return this._hasPositionSelector; } /** * Get string representation * @returns {string} */ toString() { return this.pattern; } } class Rt { constructor() { this._byDepthAndTag = /* @__PURE__ */ new Map(), this._wildcardByDepth = /* @__PURE__ */ new Map(), this._deepWildcards = [], this._patterns = /* @__PURE__ */ new Set(), this._sealed = !1; } /** * Add an Expression to the set. * Duplicate patterns (same pattern string) are silently ignored. * * @param {import('./Expression.js').default} expression - A pre-constructed Expression instance * @returns {this} for chaining * @throws {TypeError} if called after seal() * * @example * set.add(new Expression('root.users.user')); * set.add(new Expression('..script')); */ add(e) { if (this._sealed) throw new TypeError( "ExpressionSet is sealed. Create a new ExpressionSet to add more expressions." ); if (this._patterns.has(e.pattern)) return this; if (this._patterns.add(e.pattern), e.hasDeepWildcard()) return this._deepWildcards.push(e), this; const t = e.length, r = e.segments[e.segments.length - 1], s = r == null ? void 0 : r.tag; if (!s || s === "*") this._wildcardByDepth.has(t) || this._wildcardByDepth.set(t, []), this._wildcardByDepth.get(t).push(e); else { const i = `${t}:${s}`; this._byDepthAndTag.has(i) || this._byDepthAndTag.set(i, []), this._byDepthAndTag.get(i).push(e); } return this; } /** * Add multiple expressions at once. * * @param {import('./Expression.js').default[]} expressions - Array of Expression instances * @returns {this} for chaining * * @example * set.addAll([ * new Expression('root.users.user'), * new Expression('root.config.setting'), * ]); */ addAll(e) { for (const t of e) this.add(t); return this; } /** * Check whether a pattern string is already present in the set. * * @param {import('./Expression.js').default} expression * @returns {boolean} */ has(e) { return this._patterns.has(e.pattern); } /** * Number of expressions in the set. * @type {number} */ get size() { return this._patterns.size; } /** * Seal the set against further modifications. * Useful to prevent accidental mutations after config is built. * Calling add() or addAll() on a sealed set throws a TypeError. * * @returns {this} */ seal() { return this._sealed = !0, this; } /** * Whether the set has been sealed. * @type {boolean} */ get isSealed() { return this._sealed; } /** * Test whether the matcher's current path matches any expression in the set. * * Evaluation order (cheapest → most expensive): * 1. Exact depth + tag bucket — O(1) lookup, typically 0–2 expressions * 2. Depth-only wildcard bucket — O(1) lookup, rare * 3. Deep-wildcard list — always checked, but usually small * * @param {import('./Matcher.js').default} matcher - Matcher instance (or readOnly view) * @returns {boolean} true if any expression matches the current path * * @example * if (stopNodes.matchesAny(matcher)) { * // handle stop node * } */ matchesAny(e) { return this.findMatch(e) !== null; } /** * Find and return the first Expression that matches the matcher's current path. * * Uses the same evaluation order as matchesAny (cheapest → most expensive): * 1. Exact depth + tag bucket * 2. Depth-only wildcard bucket * 3. Deep-wildcard list * * @param {import('./Matcher.js').default} matcher - Matcher instance (or readOnly view) * @returns {import('./Expression.js').default | null} the first matching Expression, or null * * @example * const expr = stopNodes.findMatch(matcher); * if (expr) { * // access expr.config, expr.pattern, etc. * } */ findMatch(e) { const t = e.getDepth(), r = e.getCurrentTag(), s = `${t}:${r}`, i = this._byDepthAndTag.get(s); if (i) { for (let o = 0; o < i.length; o++) if (e.matches(i[o])) return i[o]; } const a = this._wildcardByDepth.get(t); if (a) { for (let o = 0; o < a.length; o++) if (e.matches(a[o])) return a[o]; } for (let o = 0; o < this._deepWildcards.length; o++) if (e.matches(this._deepWildcards[o])) return this._deepWildcards[o]; return null; } } class kt { /** * @param {Matcher} matcher - The parent Matcher instance to read from. */ constructor(e) { this._matcher = e; } /** * Get the path separator used by the parent matcher. * @returns {string} */ get separator() { return this._matcher.separator; } /** * Get current tag name. * @returns {string|undefined} */ getCurrentTag() { const e = this._matcher.path; return e.length > 0 ? e[e.length - 1].tag : void 0; } /** * Get current namespace. * @returns {string|undefined} */ getCurrentNamespace() { const e = this._matcher.path; return e.length > 0 ? e[e.length - 1].namespace : void 0; } /** * Get current node's attribute value. * @param {string} attrName * @returns {*} */ getAttrValue(e) { var r; const t = this._matcher.path; if (t.length !== 0) return (r = t[t.length - 1].values) == null ? void 0 : r[e]; } /** * Check if current node has an attribute. * @param {string} attrName * @returns {boolean} */ hasAttr(e) { const t = this._matcher.path; if (t.length === 0) return !1; const r = t[t.length - 1]; return r.values !== void 0 && e in r.values; } /** * Get current node's sibling position (child index in parent). * @returns {number} */ getPosition() { const e = this._matcher.path; return e.length === 0 ? -1 : e[e.length - 1].position ?? 0; } /** * Get current node's repeat counter (occurrence count of this tag name). * @returns {number} */ getCounter() { const e = this._matcher.path; return e.length === 0 ? -1 : e[e.length - 1].counter ?? 0; } /** * Get current node's sibling index (alias for getPosition). * @returns {number} * @deprecated Use getPosition() or getCounter() instead */ getIndex() { return this.getPosition(); } /** * Get current path depth. * @returns {number} */ getDepth() { return this._matcher.path.length; } /** * Get path as string. * @param {string} [separator] - Optional separator (uses default if not provided) * @param {boolean} [includeNamespace=true] * @returns {string} */ toString(e, t = !0) { return this._matcher.toString(e, t); } /** * Get path as array of tag names. * @returns {string[]} */ toArray() { return this._matcher.path.map((e) => e.tag); } /** * Match current path against an Expression. * @param {Expression} expression * @returns {boolean} */ matches(e) { return this._matcher.matches(e); } /** * Match any expression in the given set against the current path. * @param {ExpressionSet} exprSet * @returns {boolean} */ matchesAny(e) { return e.matchesAny(this._matcher); } } class jt { /** * Create a new Matcher. * @param {Object} [options={}] * @param {string} [options.separator='.'] - Default path separator */ constructor(e = {}) { this.separator = e.separator || ".", this.path = [], this.siblingStacks = [], this._pathStringCache = null, this._view = new kt(this); } /** * Push a new tag onto the path. * @param {string} tagName * @param {Object|null} [attrValues=null] * @param {string|null} [namespace=