From 516f8e7fe647669507df08916df9d14defe14f63 Mon Sep 17 00:00:00 2001 From: mathuraditya724 Date: Fri, 27 Mar 2026 12:12:08 +0530 Subject: [PATCH 1/3] feat: deploy website to GitHub Pages on release Add gh-pages target to craft configuration so the website dashboard is automatically deployed to GitHub Pages when a release is published. Changes: - Add build_website job to CI workflow that builds the Vite app and uploads a gh-pages.zip artifact - Register the gh-pages artifact and target in .craft.yml - Set Vite base path to /codecov-action/ for correct asset URLs - Add basename to BrowserRouter for subpath routing - Fix broken Github icon import (removed in lucide-react 1.x) --- .craft.yml | 2 ++ .github/workflows/build.yml | 31 +++++++++++++++++++++++++++++++ website/src/App.tsx | 2 +- website/src/pages/HomePage.tsx | 4 ++-- website/vite.config.ts | 1 + 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/.craft.yml b/.craft.yml index 83c5b84..34eda13 100644 --- a/.craft.yml +++ b/.craft.yml @@ -7,5 +7,7 @@ artifactProvider: artifacts: Build & Test: - action-package + - gh-pages targets: - name: github + - name: gh-pages diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 573c8ff..a043afa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,3 +50,34 @@ jobs: with: name: action-package path: "*.tgz" + + build_website: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 + + - name: Install pnpm + run: npm install -g pnpm + + - name: Install dependencies + run: pnpm install + + - name: Build website + working-directory: website + run: pnpm run build + + - name: Package for gh-pages + run: cd website/dist && zip -r ../../gh-pages.zip . + + - name: Upload gh-pages artifact + uses: actions/upload-artifact@v7 + with: + name: gh-pages + path: gh-pages.zip diff --git a/website/src/App.tsx b/website/src/App.tsx index e60ca2e..40c1fef 100644 --- a/website/src/App.tsx +++ b/website/src/App.tsx @@ -22,7 +22,7 @@ export default function App() { return ( - + }> } /> diff --git a/website/src/pages/HomePage.tsx b/website/src/pages/HomePage.tsx index 26c5bf9..0ea2c71 100644 --- a/website/src/pages/HomePage.tsx +++ b/website/src/pages/HomePage.tsx @@ -1,4 +1,4 @@ -import { CheckCircle, FileCode, Github, TrendingUp } from "lucide-react"; +import { CheckCircle, FileCode, GitBranch, TrendingUp } from "lucide-react"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { Button } from "@/components/ui/button"; @@ -69,7 +69,7 @@ export default function HomePage() { className="flex-1" /> diff --git a/website/vite.config.ts b/website/vite.config.ts index 03ab6c7..6f2b455 100644 --- a/website/vite.config.ts +++ b/website/vite.config.ts @@ -5,6 +5,7 @@ import { defineConfig } from "vite"; // https://vite.dev/config/ export default defineConfig({ + base: "/codecov-action/", plugins: [react(), tailwindcss()], resolve: { alias: { From 42cbe4ac5db98cad1016984d243a116da10ffa3e Mon Sep 17 00:00:00 2001 From: mathuraditya724 Date: Fri, 27 Mar 2026 13:55:30 +0530 Subject: [PATCH 2/3] Update index.js --- dist/index.js | 1299 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 1142 insertions(+), 157 deletions(-) diff --git a/dist/index.js b/dist/index.js index 6fcde4f..ebf21d2 100644 --- a/dist/index.js +++ b/dist/index.js @@ -33532,6 +33532,24 @@ function isExist(v) { return typeof v !== 'undefined'; } +/** + * Dangerous property names that could lead to prototype pollution or security issues + */ +const DANGEROUS_PROPERTY_NAMES = [ + // '__proto__', + // 'constructor', + // 'prototype', + 'hasOwnProperty', + 'toString', + 'valueOf', + '__defineGetter__', + '__defineSetter__', + '__lookupGetter__', + '__lookupSetter__' +]; + +const criticalProperties = ["__proto__", "constructor", "prototype"]; + const defaultOptions$1 = { allowBooleanAttributes: false, //A tag can have attributes without any value unpairedTags: [] @@ -33949,6 +33967,14 @@ function getPositionFromMatch(match) { return match.startIndex + match[1].length; } +const defaultOnDangerousProperty = (name) => { + if (DANGEROUS_PROPERTY_NAMES.includes(name)) { + return "__" + name; + } + return name; +}; + + const defaultOptions = { preserveOrder: false, attributeNamePrefix: '@_', @@ -33991,8 +34017,36 @@ const defaultOptions = { captureMetaData: false, maxNestedTags: 100, strictReservedNames: true, + jPath: true, // if true, pass jPath string to callbacks; if false, pass matcher instance + onDangerousProperty: defaultOnDangerousProperty }; + +/** + * Validates that a property name is safe to use + * @param {string} propertyName - The property name to validate + * @param {string} optionName - The option field name (for error message) + * @throws {Error} If property name is dangerous + */ +function validatePropertyName(propertyName, optionName) { + if (typeof propertyName !== 'string') { + return; // Only validate string property names + } + + const normalized = propertyName.toLowerCase(); + if (DANGEROUS_PROPERTY_NAMES.some(dangerous => normalized === dangerous.toLowerCase())) { + throw new Error( + `[SECURITY] Invalid ${optionName}: "${propertyName}" is a reserved JavaScript keyword that could cause prototype pollution` + ); + } + + if (criticalProperties.some(dangerous => normalized === dangerous.toLowerCase())) { + throw new Error( + `[SECURITY] Invalid ${optionName}: "${propertyName}" is a reserved JavaScript keyword that could cause prototype pollution` + ); + } +} + /** * Normalizes processEntities option for backward compatibility * @param {boolean|object} value @@ -34007,6 +34061,7 @@ function normalizeProcessEntities(value) { maxExpansionDepth: 10, maxTotalExpansions: 1000, maxExpandedLength: 100000, + maxEntityCount: 100, allowedTags: null, tagFilter: null }; @@ -34015,11 +34070,12 @@ function normalizeProcessEntities(value) { // Object config - merge with defaults if (typeof value === 'object' && value !== null) { return { - enabled: value.enabled !== false, // default true if not specified - maxEntitySize: value.maxEntitySize ?? 10000, - maxExpansionDepth: value.maxExpansionDepth ?? 10, - maxTotalExpansions: value.maxTotalExpansions ?? 1000, - maxExpandedLength: value.maxExpandedLength ?? 100000, + enabled: value.enabled !== false, + maxEntitySize: Math.max(1, value.maxEntitySize ?? 10000), + maxExpansionDepth: Math.max(1, value.maxExpansionDepth ?? 10), + maxTotalExpansions: Math.max(1, value.maxTotalExpansions ?? 1000), + maxExpandedLength: Math.max(1, value.maxExpandedLength ?? 100000), + maxEntityCount: Math.max(1, value.maxEntityCount ?? 100), allowedTags: value.allowedTags ?? null, tagFilter: value.tagFilter ?? null }; @@ -34032,8 +34088,39 @@ function normalizeProcessEntities(value) { const buildOptions = function (options) { const built = Object.assign({}, defaultOptions, options); + // Validate property names to prevent prototype pollution + const propertyNameOptions = [ + { value: built.attributeNamePrefix, name: 'attributeNamePrefix' }, + { value: built.attributesGroupName, name: 'attributesGroupName' }, + { value: built.textNodeName, name: 'textNodeName' }, + { value: built.cdataPropName, name: 'cdataPropName' }, + { value: built.commentPropName, name: 'commentPropName' } + ]; + + for (const { value, name } of propertyNameOptions) { + if (value) { + validatePropertyName(value, name); + } + } + + if (built.onDangerousProperty === null) { + built.onDangerousProperty = defaultOnDangerousProperty; + } + // Always normalize processEntities for backward compatibility and validation built.processEntities = normalizeProcessEntities(built.processEntities); + + // Convert old-style stopNodes for backward compatibility + if (built.stopNodes && Array.isArray(built.stopNodes)) { + built.stopNodes = built.stopNodes.map(node => { + if (typeof node === 'string' && node.startsWith('*.')) { + // Old syntax: *.tagname meant "tagname anywhere" + // Convert to new syntax: ..tagname + return '..' + node.substring(2); + } + return node; + }); + } //console.debug(built.processEntities) return built; }; @@ -34084,8 +34171,9 @@ class DocTypeReader { } readDocType(xmlData, i) { - const entities = Object.create(null); + let entityCount = 0; + if (xmlData[i + 3] === 'O' && xmlData[i + 4] === 'C' && xmlData[i + 5] === 'T' && @@ -34103,11 +34191,20 @@ class DocTypeReader { let entityName, val; [entityName, val, i] = this.readEntityExp(xmlData, i + 1, this.suppressValidationErr); if (val.indexOf("&") === -1) { //Parameter entities are not supported - const escaped = entityName.replace(/[.\-+*:]/g, '\\.'); + if (this.options.enabled !== false && + this.options.maxEntityCount != null && + entityCount >= this.options.maxEntityCount) { + throw new Error( + `Entity count (${entityCount + 1}) exceeds maximum allowed (${this.options.maxEntityCount})` + ); + } + //const escaped = entityName.replace(/[.\-+*:]/g, '\\.'); + const escaped = entityName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); entities[entityName] = { regx: RegExp(`&${escaped};`, "g"), val: val }; + entityCount++; } } else if (hasBody && hasSeq(xmlData, "!ELEMENT", i)) { @@ -34167,11 +34264,12 @@ class DocTypeReader { i = skipWhitespace(xmlData, i); // Read entity name - let entityName = ""; + const startIndex = i; while (i < xmlData.length && !/\s/.test(xmlData[i]) && xmlData[i] !== '"' && xmlData[i] !== "'") { - entityName += xmlData[i]; i++; } + let entityName = xmlData.substring(startIndex, i); + validateEntityName(entityName); // Skip whitespace after entity name @@ -34192,7 +34290,7 @@ class DocTypeReader { // Validate entity size if (this.options.enabled !== false && - this.options.maxEntitySize && + this.options.maxEntitySize != null && entityValue.length > this.options.maxEntitySize) { throw new Error( `Entity "${entityName}" size (${entityValue.length}) exceeds maximum allowed size (${this.options.maxEntitySize})` @@ -34208,11 +34306,13 @@ class DocTypeReader { i = skipWhitespace(xmlData, i); // Read notation name - let notationName = ""; + + const startIndex = i; while (i < xmlData.length && !/\s/.test(xmlData[i])) { - notationName += xmlData[i]; i++; } + let notationName = xmlData.substring(startIndex, i); + !this.suppressValidationErr && validateEntityName(notationName); // Skip whitespace after notation name @@ -34262,10 +34362,11 @@ class DocTypeReader { } i++; + const startIndex = i; while (i < xmlData.length && xmlData[i] !== startChar) { - identifierVal += xmlData[i]; i++; } + identifierVal = xmlData.substring(startIndex, i); if (xmlData[i] !== startChar) { throw new Error(`Unterminated ${type} value`); @@ -34285,11 +34386,11 @@ class DocTypeReader { i = skipWhitespace(xmlData, i); // Read element name - let elementName = ""; + const startIndex = i; while (i < xmlData.length && !/\s/.test(xmlData[i])) { - elementName += xmlData[i]; i++; } + let elementName = xmlData.substring(startIndex, i); // Validate element name if (!this.suppressValidationErr && !isName(elementName)) { @@ -34306,10 +34407,12 @@ class DocTypeReader { i++; // Move past '(' // Read content model + const startIndex = i; while (i < xmlData.length && xmlData[i] !== ")") { - contentModel += xmlData[i]; i++; } + contentModel = xmlData.substring(startIndex, i); + if (xmlData[i] !== ")") { throw new Error("Unterminated content model"); } @@ -34330,11 +34433,11 @@ class DocTypeReader { i = skipWhitespace(xmlData, i); // Read element name - let elementName = ""; + let startIndex = i; while (i < xmlData.length && !/\s/.test(xmlData[i])) { - elementName += xmlData[i]; i++; } + let elementName = xmlData.substring(startIndex, i); // Validate element name validateEntityName(elementName); @@ -34343,11 +34446,11 @@ class DocTypeReader { i = skipWhitespace(xmlData, i); // Read attribute name - let attributeName = ""; + startIndex = i; while (i < xmlData.length && !/\s/.test(xmlData[i])) { - attributeName += xmlData[i]; i++; } + let attributeName = xmlData.substring(startIndex, i); // Validate attribute name if (!validateEntityName(attributeName)) { @@ -34375,11 +34478,13 @@ class DocTypeReader { // Read the list of allowed notations let allowedNotations = []; while (i < xmlData.length && xmlData[i] !== ")") { - let notation = ""; + + + const startIndex = i; while (i < xmlData.length && xmlData[i] !== "|" && xmlData[i] !== ")") { - notation += xmlData[i]; i++; } + let notation = xmlData.substring(startIndex, i); // Validate notation name notation = notation.trim(); @@ -34405,10 +34510,11 @@ class DocTypeReader { attributeType += " (" + allowedNotations.join("|") + ")"; } else { // Handle simple types (e.g., CDATA, ID, IDREF, etc.) + const startIndex = i; while (i < xmlData.length && !/\s/.test(xmlData[i])) { - attributeType += xmlData[i]; i++; } + attributeType += xmlData.substring(startIndex, i); // Validate simple attribute type const validTypes = ["CDATA", "ID", "IDREF", "IDREFS", "ENTITY", "ENTITIES", "NMTOKEN", "NMTOKENS"]; @@ -34566,11 +34672,16 @@ function resolveEnotation(str, trimmedStr, options) { else if (leadingZeros.length === 1 && (notation[3].startsWith(`.${eChar}`) || notation[3][0] === eChar)) { return Number(trimmedStr); - } else if (options.leadingZeros && !eAdjacentToLeadingZeros) { //accept with leading zeros - //remove leading 0s - trimmedStr = (notation[1] || "") + notation[3]; + } else if (leadingZeros.length > 0) { + // Has leading zeros — only accept if leadingZeros option allows it + if (options.leadingZeros && !eAdjacentToLeadingZeros) { + trimmedStr = (notation[1] || "") + notation[3]; + return Number(trimmedStr); + } else return str; + } else { + // No leading zeros — always valid e-notation, parse it return Number(trimmedStr); - } else return str; + } } else { return str; } @@ -34642,6 +34753,654 @@ function getIgnoreAttributesFn(ignoreAttributes) { return () => false } +/** + * Expression - Parses and stores a tag pattern expression + * + * Patterns are parsed once and stored in an optimized structure for fast matching. + * + * @example + * const expr = new Expression("root.users.user"); + * const expr2 = new Expression("..user[id]:first"); + * const expr3 = new Expression("root/users/user", { separator: '/' }); + */ +class Expression { + /** + * 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(pattern, options = {}) { + this.pattern = pattern; + this.separator = options.separator || '.'; + this.segments = this._parse(pattern); + + // Cache expensive checks for performance (O(1) instead of O(n)) + this._hasDeepWildcard = this.segments.some(seg => seg.type === 'deep-wildcard'); + this._hasAttributeCondition = this.segments.some(seg => seg.attrName !== undefined); + this._hasPositionSelector = this.segments.some(seg => seg.position !== undefined); + } + + /** + * Parse pattern string into segments + * @private + * @param {string} pattern - Pattern to parse + * @returns {Array} Array of segment objects + */ + _parse(pattern) { + const segments = []; + + // Split by separator but handle ".." specially + let i = 0; + let currentPart = ''; + + while (i < pattern.length) { + if (pattern[i] === this.separator) { + // Check if next char is also separator (deep wildcard) + if (i + 1 < pattern.length && pattern[i + 1] === this.separator) { + // Flush current part if any + if (currentPart.trim()) { + segments.push(this._parseSegment(currentPart.trim())); + currentPart = ''; + } + // Add deep wildcard + segments.push({ type: 'deep-wildcard' }); + i += 2; // Skip both separators + } else { + // Regular separator + if (currentPart.trim()) { + segments.push(this._parseSegment(currentPart.trim())); + } + currentPart = ''; + i++; + } + } else { + currentPart += pattern[i]; + i++; + } + } + + // Flush remaining part + if (currentPart.trim()) { + segments.push(this._parseSegment(currentPart.trim())); + } + + return segments; + } + + /** + * 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(part) { + const segment = { type: 'tag' }; + + // NEW NAMESPACE SYNTAX (v2.0): + // ============================ + // Namespace uses DOUBLE colon (::) + // Position uses SINGLE colon (:) + // + // Examples: + // "user" → tag + // "user:first" → tag + position + // "user[id]" → tag + attribute + // "user[id]:first" → tag + attribute + position + // "ns::user" → namespace + tag + // "ns::user:first" → namespace + tag + position + // "ns::user[id]" → namespace + tag + attribute + // "ns::user[id]:first" → namespace + tag + attribute + position + // "ns::first" → namespace + tag named "first" (NO ambiguity!) + // + // This eliminates all ambiguity: + // :: = namespace separator + // : = position selector + // [] = attributes + + // Step 1: Extract brackets [attr] or [attr=value] + let bracketContent = null; + let withoutBrackets = part; + + const bracketMatch = part.match(/^([^\[]+)(\[[^\]]*\])(.*)$/); + if (bracketMatch) { + withoutBrackets = bracketMatch[1] + bracketMatch[3]; + if (bracketMatch[2]) { + const content = bracketMatch[2].slice(1, -1); + if (content) { + bracketContent = content; + } + } + } + + // Step 2: Check for namespace (double colon ::) + let namespace = undefined; + let tagAndPosition = withoutBrackets; + + if (withoutBrackets.includes('::')) { + const nsIndex = withoutBrackets.indexOf('::'); + namespace = withoutBrackets.substring(0, nsIndex).trim(); + tagAndPosition = withoutBrackets.substring(nsIndex + 2).trim(); // Skip :: + + if (!namespace) { + throw new Error(`Invalid namespace in pattern: ${part}`); + } + } + + // Step 3: Parse tag and position (single colon :) + let tag = undefined; + let positionMatch = null; + + if (tagAndPosition.includes(':')) { + const colonIndex = tagAndPosition.lastIndexOf(':'); // Use last colon for position + const tagPart = tagAndPosition.substring(0, colonIndex).trim(); + const posPart = tagAndPosition.substring(colonIndex + 1).trim(); + + // Verify position is a valid keyword + const isPositionKeyword = ['first', 'last', 'odd', 'even'].includes(posPart) || + /^nth\(\d+\)$/.test(posPart); + + if (isPositionKeyword) { + tag = tagPart; + positionMatch = posPart; + } else { + // Not a valid position keyword, treat whole thing as tag + tag = tagAndPosition; + } + } else { + tag = tagAndPosition; + } + + if (!tag) { + throw new Error(`Invalid segment pattern: ${part}`); + } + + segment.tag = tag; + if (namespace) { + segment.namespace = namespace; + } + + // Step 4: Parse attributes + if (bracketContent) { + if (bracketContent.includes('=')) { + const eqIndex = bracketContent.indexOf('='); + segment.attrName = bracketContent.substring(0, eqIndex).trim(); + segment.attrValue = bracketContent.substring(eqIndex + 1).trim(); + } else { + segment.attrName = bracketContent.trim(); + } + } + + // Step 5: Parse position selector + if (positionMatch) { + const nthMatch = positionMatch.match(/^nth\((\d+)\)$/); + if (nthMatch) { + segment.position = 'nth'; + segment.positionValue = parseInt(nthMatch[1], 10); + } else { + segment.position = positionMatch; + } + } + + return segment; + } + + /** + * 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; + } +} + +/** + * Matcher - Tracks current path in XML/JSON tree and matches against Expressions + * + * The matcher maintains a stack of nodes representing the current path from root to + * current tag. It only stores attribute values for the current (top) node to minimize + * memory usage. Sibling tracking is used to auto-calculate position and counter. + * + * @example + * const matcher = new Matcher(); + * matcher.push("root", {}); + * matcher.push("users", {}); + * matcher.push("user", { id: "123", type: "admin" }); + * + * const expr = new Expression("root.users.user"); + * matcher.matches(expr); // true + */ +class Matcher { + /** + * Create a new Matcher + * @param {Object} options - Configuration options + * @param {string} options.separator - Default path separator (default: '.') + */ + constructor(options = {}) { + this.separator = options.separator || '.'; + this.path = []; + this.siblingStacks = []; + // Each path node: { tag: string, values: object, position: number, counter: number } + // values only present for current (last) node + // Each siblingStacks entry: Map tracking occurrences at each level + } + + /** + * Push a new tag onto the path + * @param {string} tagName - Name of the tag + * @param {Object} attrValues - Attribute key-value pairs for current node (optional) + * @param {string} namespace - Namespace for the tag (optional) + */ + push(tagName, attrValues = null, namespace = null) { + // Remove values from previous current node (now becoming ancestor) + if (this.path.length > 0) { + const prev = this.path[this.path.length - 1]; + prev.values = undefined; + } + + // Get or create sibling tracking for current level + const currentLevel = this.path.length; + if (!this.siblingStacks[currentLevel]) { + this.siblingStacks[currentLevel] = new Map(); + } + + const siblings = this.siblingStacks[currentLevel]; + + // Create a unique key for sibling tracking that includes namespace + const siblingKey = namespace ? `${namespace}:${tagName}` : tagName; + + // Calculate counter (how many times this tag appeared at this level) + const counter = siblings.get(siblingKey) || 0; + + // Calculate position (total children at this level so far) + let position = 0; + for (const count of siblings.values()) { + position += count; + } + + // Update sibling count for this tag + siblings.set(siblingKey, counter + 1); + + // Create new node + const node = { + tag: tagName, + position: position, + counter: counter + }; + + // Store namespace if provided + if (namespace !== null && namespace !== undefined) { + node.namespace = namespace; + } + + // Store values only for current node + if (attrValues !== null && attrValues !== undefined) { + node.values = attrValues; + } + + this.path.push(node); + } + + /** + * Pop the last tag from the path + * @returns {Object|undefined} The popped node + */ + pop() { + if (this.path.length === 0) { + return undefined; + } + + const node = this.path.pop(); + + // Clean up sibling tracking for levels deeper than current + // After pop, path.length is the new depth + // We need to clean up siblingStacks[path.length + 1] and beyond + if (this.siblingStacks.length > this.path.length + 1) { + this.siblingStacks.length = this.path.length + 1; + } + + return node; + } + + /** + * Update current node's attribute values + * Useful when attributes are parsed after push + * @param {Object} attrValues - Attribute values + */ + updateCurrent(attrValues) { + if (this.path.length > 0) { + const current = this.path[this.path.length - 1]; + if (attrValues !== null && attrValues !== undefined) { + current.values = attrValues; + } + } + } + + /** + * Get current tag name + * @returns {string|undefined} + */ + getCurrentTag() { + return this.path.length > 0 ? this.path[this.path.length - 1].tag : undefined; + } + + /** + * Get current namespace + * @returns {string|undefined} + */ + getCurrentNamespace() { + return this.path.length > 0 ? this.path[this.path.length - 1].namespace : undefined; + } + + /** + * Get current node's attribute value + * @param {string} attrName - Attribute name + * @returns {*} Attribute value or undefined + */ + getAttrValue(attrName) { + if (this.path.length === 0) return undefined; + const current = this.path[this.path.length - 1]; + return current.values?.[attrName]; + } + + /** + * Check if current node has an attribute + * @param {string} attrName - Attribute name + * @returns {boolean} + */ + hasAttr(attrName) { + if (this.path.length === 0) return false; + const current = this.path[this.path.length - 1]; + return current.values !== undefined && attrName in current.values; + } + + /** + * Get current node's sibling position (child index in parent) + * @returns {number} + */ + getPosition() { + if (this.path.length === 0) return -1; + return this.path[this.path.length - 1].position ?? 0; + } + + /** + * Get current node's repeat counter (occurrence count of this tag name) + * @returns {number} + */ + getCounter() { + if (this.path.length === 0) return -1; + return this.path[this.path.length - 1].counter ?? 0; + } + + /** + * Get current node's sibling index (alias for getPosition for backward compatibility) + * @returns {number} + * @deprecated Use getPosition() or getCounter() instead + */ + getIndex() { + return this.getPosition(); + } + + /** + * Get current path depth + * @returns {number} + */ + getDepth() { + return this.path.length; + } + + /** + * Get path as string + * @param {string} separator - Optional separator (uses default if not provided) + * @param {boolean} includeNamespace - Whether to include namespace in output (default: true) + * @returns {string} + */ + toString(separator, includeNamespace = true) { + const sep = separator || this.separator; + return this.path.map(n => { + if (includeNamespace && n.namespace) { + return `${n.namespace}:${n.tag}`; + } + return n.tag; + }).join(sep); + } + + /** + * Get path as array of tag names + * @returns {string[]} + */ + toArray() { + return this.path.map(n => n.tag); + } + + /** + * Reset the path to empty + */ + reset() { + this.path = []; + this.siblingStacks = []; + } + + /** + * Match current path against an Expression + * @param {Expression} expression - The expression to match against + * @returns {boolean} True if current path matches the expression + */ + matches(expression) { + const segments = expression.segments; + + if (segments.length === 0) { + return false; + } + + // Handle deep wildcard patterns + if (expression.hasDeepWildcard()) { + return this._matchWithDeepWildcard(segments); + } + + // Simple path matching (no deep wildcards) + return this._matchSimple(segments); + } + + /** + * Match simple path (no deep wildcards) + * @private + */ + _matchSimple(segments) { + // Path must be same length as segments + if (this.path.length !== segments.length) { + return false; + } + + // Match each segment bottom-to-top + for (let i = 0; i < segments.length; i++) { + const segment = segments[i]; + const node = this.path[i]; + const isCurrentNode = (i === this.path.length - 1); + + if (!this._matchSegment(segment, node, isCurrentNode)) { + return false; + } + } + + return true; + } + + /** + * Match path with deep wildcards + * @private + */ + _matchWithDeepWildcard(segments) { + let pathIdx = this.path.length - 1; // Start from current node (bottom) + let segIdx = segments.length - 1; // Start from last segment + + while (segIdx >= 0 && pathIdx >= 0) { + const segment = segments[segIdx]; + + if (segment.type === 'deep-wildcard') { + // ".." matches zero or more levels + segIdx--; + + if (segIdx < 0) { + // Pattern ends with "..", always matches + return true; + } + + // Find where next segment matches in the path + const nextSeg = segments[segIdx]; + let found = false; + + for (let i = pathIdx; i >= 0; i--) { + const isCurrentNode = (i === this.path.length - 1); + if (this._matchSegment(nextSeg, this.path[i], isCurrentNode)) { + pathIdx = i - 1; + segIdx--; + found = true; + break; + } + } + + if (!found) { + return false; + } + } else { + // Regular segment + const isCurrentNode = (pathIdx === this.path.length - 1); + if (!this._matchSegment(segment, this.path[pathIdx], isCurrentNode)) { + return false; + } + pathIdx--; + segIdx--; + } + } + + // All segments must be consumed + return segIdx < 0; + } + + /** + * Match a single segment against a node + * @private + * @param {Object} segment - Segment from Expression + * @param {Object} node - Node from path + * @param {boolean} isCurrentNode - Whether this is the current (last) node + * @returns {boolean} + */ + _matchSegment(segment, node, isCurrentNode) { + // Match tag name (* is wildcard) + if (segment.tag !== '*' && segment.tag !== node.tag) { + return false; + } + + // Match namespace if specified in segment + if (segment.namespace !== undefined) { + // Segment has namespace - node must match it + if (segment.namespace !== '*' && segment.namespace !== node.namespace) { + return false; + } + } + // If segment has no namespace, it matches nodes with or without namespace + + // Match attribute name (check if node has this attribute) + // Can only check for current node since ancestors don't have values + if (segment.attrName !== undefined) { + if (!isCurrentNode) { + // Can't check attributes for ancestor nodes (values not stored) + return false; + } + + if (!node.values || !(segment.attrName in node.values)) { + return false; + } + + // Match attribute value (only possible for current node) + if (segment.attrValue !== undefined) { + const actualValue = node.values[segment.attrName]; + // Both should be strings + if (String(actualValue) !== String(segment.attrValue)) { + return false; + } + } + } + + // Match position (only for current node) + if (segment.position !== undefined) { + if (!isCurrentNode) { + // Can't check position for ancestor nodes + return false; + } + + const counter = node.counter ?? 0; + + if (segment.position === 'first' && counter !== 0) { + return false; + } else if (segment.position === 'odd' && counter % 2 !== 1) { + return false; + } else if (segment.position === 'even' && counter % 2 !== 0) { + return false; + } else if (segment.position === 'nth') { + if (counter !== segment.positionValue) { + return false; + } + } + } + + return true; + } + + /** + * Create a snapshot of current state + * @returns {Object} State snapshot + */ + snapshot() { + return { + path: this.path.map(node => ({ ...node })), + siblingStacks: this.siblingStacks.map(map => new Map(map)) + }; + } + + /** + * Restore state from snapshot + * @param {Object} snapshot - State snapshot + */ + restore(snapshot) { + this.path = snapshot.path.map(node => ({ ...node })); + this.siblingStacks = snapshot.siblingStacks.map(map => new Map(map)); + } +} + // const regx = // '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)' // .replace(/NAME/g, util.nameRegexp); @@ -34649,6 +35408,57 @@ function getIgnoreAttributesFn(ignoreAttributes) { //const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g"); //const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g"); +// Helper functions for attribute and namespace handling + +/** + * Extract raw attributes (without prefix) from prefixed attribute map + * @param {object} prefixedAttrs - Attributes with prefix from buildAttributesMap + * @param {object} options - Parser options containing attributeNamePrefix + * @returns {object} Raw attributes for matcher + */ +function extractRawAttributes(prefixedAttrs, options) { + if (!prefixedAttrs) return {}; + + // Handle attributesGroupName option + const attrs = options.attributesGroupName + ? prefixedAttrs[options.attributesGroupName] + : prefixedAttrs; + + if (!attrs) return {}; + + const rawAttrs = {}; + for (const key in attrs) { + // Remove the attribute prefix to get raw name + if (key.startsWith(options.attributeNamePrefix)) { + const rawName = key.substring(options.attributeNamePrefix.length); + rawAttrs[rawName] = attrs[key]; + } else { + // Attribute without prefix (shouldn't normally happen, but be safe) + rawAttrs[key] = attrs[key]; + } + } + return rawAttrs; +} + +/** + * Extract namespace from raw tag name + * @param {string} rawTagName - Tag name possibly with namespace (e.g., "soap:Envelope") + * @returns {string|undefined} Namespace or undefined + */ +function extractNamespace(rawTagName) { + if (!rawTagName || typeof rawTagName !== 'string') return undefined; + + const colonIndex = rawTagName.indexOf(':'); + if (colonIndex !== -1 && colonIndex > 0) { + const ns = rawTagName.substring(0, colonIndex); + // Don't treat xmlns as a namespace + if (ns !== 'xmlns') { + return ns; + } + } + return undefined; +} + class OrderedObjParser { constructor(options) { this.options = options; @@ -34693,16 +35503,23 @@ class OrderedObjParser { this.entityExpansionCount = 0; this.currentExpandedLength = 0; + // Initialize path matcher for path-expression-matcher + this.matcher = new Matcher(); + + // Flag to track if current node is a stop node (optimization) + this.isCurrentNodeStopNode = false; + + // Pre-compile stopNodes expressions if (this.options.stopNodes && this.options.stopNodes.length > 0) { - this.stopNodesExact = new Set(); - this.stopNodesWildcard = new Set(); + this.stopNodeExpressions = []; for (let i = 0; i < this.options.stopNodes.length; i++) { const stopNodeExp = this.options.stopNodes[i]; - if (typeof stopNodeExp !== 'string') continue; - if (stopNodeExp.startsWith("*.")) { - this.stopNodesWildcard.add(stopNodeExp.substring(2)); - } else { - this.stopNodesExact.add(stopNodeExp); + if (typeof stopNodeExp === 'string') { + // Convert string to Expression object + this.stopNodeExpressions.push(new Expression(stopNodeExp)); + } else if (stopNodeExp instanceof Expression) { + // Already an Expression object + this.stopNodeExpressions.push(stopNodeExp); } } } @@ -34725,7 +35542,7 @@ function addExternalEntities(externalEntities) { /** * @param {string} val * @param {string} tagName - * @param {string} jPath + * @param {string|Matcher} jPath - jPath string or Matcher instance based on options.jPath * @param {boolean} dontTrim * @param {boolean} hasAttributes * @param {boolean} isLeafNode @@ -34739,7 +35556,9 @@ function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode, if (val.length > 0) { if (!escapeEntities) val = this.replaceEntitiesValue(val, tagName, jPath); - const newval = this.options.tagValueProcessor(tagName, val, jPath, hasAttributes, isLeafNode); + // Pass jPath string or matcher based on options.jPath setting + const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath; + const newval = this.options.tagValueProcessor(tagName, val, jPathOrMatcher, hasAttributes, isLeafNode); if (newval === null || newval === undefined) { //don't parse return val; @@ -34786,25 +35605,58 @@ function buildAttributesMap(attrStr, jPath, tagName) { const matches = getAllMatches(attrStr, attrsRegx); const len = matches.length; //don't make it inline const attrs = {}; + + // First pass: parse all attributes and update matcher with raw values + // This ensures the matcher has all attribute values when processors run + const rawAttrsForMatcher = {}; + for (let i = 0; i < len; i++) { + const attrName = this.resolveNameSpace(matches[i][1]); + const oldVal = matches[i][4]; + + if (attrName.length && oldVal !== undefined) { + let parsedVal = oldVal; + if (this.options.trimValues) { + parsedVal = parsedVal.trim(); + } + parsedVal = this.replaceEntitiesValue(parsedVal, tagName, jPath); + rawAttrsForMatcher[attrName] = parsedVal; + } + } + + // Update matcher with raw attribute values BEFORE running processors + if (Object.keys(rawAttrsForMatcher).length > 0 && typeof jPath === 'object' && jPath.updateCurrent) { + jPath.updateCurrent(rawAttrsForMatcher); + } + + // Second pass: now process attributes with matcher having full attribute context for (let i = 0; i < len; i++) { const attrName = this.resolveNameSpace(matches[i][1]); - if (this.ignoreAttributesFn(attrName, jPath)) { + + // Convert jPath to string if needed for ignoreAttributesFn + const jPathStr = this.options.jPath ? jPath.toString() : jPath; + if (this.ignoreAttributesFn(attrName, jPathStr)) { continue } + let oldVal = matches[i][4]; let aName = this.options.attributeNamePrefix + attrName; + if (attrName.length) { if (this.options.transformAttributeName) { aName = this.options.transformAttributeName(aName); } - if (aName === "__proto__") aName = "#__proto__"; + //if (aName === "__proto__") aName = "#__proto__"; + aName = sanitizeName(aName, this.options); if (oldVal !== undefined) { if (this.options.trimValues) { oldVal = oldVal.trim(); } oldVal = this.replaceEntitiesValue(oldVal, tagName, jPath); - const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPath); + + // Pass jPath string or matcher based on options.jPath setting + const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath; + const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPathOrMatcher); if (newVal === null || newVal === undefined) { //don't parse attrs[aName] = oldVal; @@ -34824,6 +35676,7 @@ function buildAttributesMap(attrStr, jPath, tagName) { } } } + if (!Object.keys(attrs).length) { return; } @@ -34841,7 +35694,9 @@ const parseXml = function (xmlData) { const xmlObj = new XmlNode('!xml'); let currentNode = xmlObj; let textData = ""; - let jPath = ""; + + // Reset matcher for new document + this.matcher.reset(); // Reset entity expansion counters for this document this.entityExpansionCount = 0; @@ -34864,27 +35719,25 @@ const parseXml = function (xmlData) { } } - if (this.options.transformTagName) { - tagName = this.options.transformTagName(tagName); - } + tagName = transformTagName(this.options.transformTagName, tagName, "", this.options).tagName; if (currentNode) { - textData = this.saveTextToParentTag(textData, currentNode, jPath); + textData = this.saveTextToParentTag(textData, currentNode, this.matcher); } //check if last tag of nested tag was unpaired tag - const lastTagName = jPath.substring(jPath.lastIndexOf(".") + 1); + const lastTagName = this.matcher.getCurrentTag(); if (tagName && this.options.unpairedTags.indexOf(tagName) !== -1) { throw new Error(`Unpaired tag can not be used as closing tag: `); } - let propIndex = 0; if (lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1) { - propIndex = jPath.lastIndexOf('.', jPath.lastIndexOf('.') - 1); + // Pop the unpaired tag + this.matcher.pop(); this.tagsNodeStack.pop(); - } else { - propIndex = jPath.lastIndexOf("."); } - jPath = jPath.substring(0, propIndex); + // Pop the closing tag + this.matcher.pop(); + this.isCurrentNodeStopNode = false; // Reset flag when closing tag currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope textData = ""; @@ -34894,16 +35747,16 @@ const parseXml = function (xmlData) { let tagData = readTagExp(xmlData, i, false, "?>"); if (!tagData) throw new Error("Pi Tag is not closed."); - textData = this.saveTextToParentTag(textData, currentNode, jPath); + textData = this.saveTextToParentTag(textData, currentNode, this.matcher); if ((this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags) ; else { const childNode = new XmlNode(tagData.tagName); childNode.add(this.options.textNodeName, ""); if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent) { - childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath, tagData.tagName); + childNode[":@"] = this.buildAttributesMap(tagData.tagExp, this.matcher, tagData.tagName); } - this.addChild(currentNode, childNode, jPath, i); + this.addChild(currentNode, childNode, this.matcher, i); } @@ -34913,7 +35766,7 @@ const parseXml = function (xmlData) { if (this.options.commentPropName) { const comment = xmlData.substring(i + 4, endIndex - 2); - textData = this.saveTextToParentTag(textData, currentNode, jPath); + textData = this.saveTextToParentTag(textData, currentNode, this.matcher); currentNode.add(this.options.commentPropName, [{ [this.options.textNodeName]: comment }]); } @@ -34926,9 +35779,9 @@ const parseXml = function (xmlData) { const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2; const tagExp = xmlData.substring(i + 9, closeIndex); - textData = this.saveTextToParentTag(textData, currentNode, jPath); + textData = this.saveTextToParentTag(textData, currentNode, this.matcher); - let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true, true); + let val = this.parseTextData(tagExp, currentNode.tagname, this.matcher, true, false, true, true); if (val == undefined) val = ""; //cdata should be set even if it is 0 length string @@ -34941,24 +35794,27 @@ const parseXml = function (xmlData) { i = closeIndex + 2; } else {//Opening tag let result = readTagExp(xmlData, i, this.options.removeNSPrefix); + + // Safety check: readTagExp can return undefined + if (!result) { + // Log context for debugging + const context = xmlData.substring(Math.max(0, i - 50), Math.min(xmlData.length, i + 50)); + throw new Error(`readTagExp returned undefined at position ${i}. Context: "${context}"`); + } + let tagName = result.tagName; const rawTagName = result.rawTagName; let tagExp = result.tagExp; let attrExpPresent = result.attrExpPresent; let closeIndex = result.closeIndex; - if (this.options.transformTagName) { - //console.log(tagExp, tagName) - const newTagName = this.options.transformTagName(tagName); - if (tagExp === tagName) { - tagExp = newTagName; - } - tagName = newTagName; - } + ({ tagName, tagExp } = transformTagName(this.options.transformTagName, tagName, tagExp, this.options)); if (this.options.strictReservedNames && (tagName === this.options.commentPropName || tagName === this.options.cdataPropName + || tagName === this.options.textNodeName + || tagName === this.options.attributesGroupName )) { throw new Error(`Invalid tag name: ${tagName}`); } @@ -34967,7 +35823,7 @@ const parseXml = function (xmlData) { if (currentNode && textData) { if (currentNode.tagname !== '!xml') { //when nested tag is found - textData = this.saveTextToParentTag(textData, currentNode, jPath, false); + textData = this.saveTextToParentTag(textData, currentNode, this.matcher, false); } } @@ -34975,28 +35831,64 @@ const parseXml = function (xmlData) { const lastTag = currentNode; if (lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1) { currentNode = this.tagsNodeStack.pop(); - jPath = jPath.substring(0, jPath.lastIndexOf(".")); + this.matcher.pop(); } + + // Clean up self-closing syntax BEFORE processing attributes + // This is where tagExp gets the trailing / removed + let isSelfClosing = false; + if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) { + isSelfClosing = true; + if (tagName[tagName.length - 1] === "/") { + tagName = tagName.substr(0, tagName.length - 1); + tagExp = tagName; + } else { + tagExp = tagExp.substr(0, tagExp.length - 1); + } + + // Re-check attrExpPresent after cleaning + attrExpPresent = (tagName !== tagExp); + } + + // Now process attributes with CLEAN tagExp (no trailing /) + let prefixedAttrs = null; + let namespace = undefined; + + // Extract namespace from rawTagName + namespace = extractNamespace(rawTagName); + + // Push tag to matcher FIRST (with empty attrs for now) so callbacks see correct path + if (tagName !== xmlObj.tagname) { + this.matcher.push(tagName, {}, namespace); + } + + // Now build attributes - callbacks will see correct matcher state + if (tagName !== tagExp && attrExpPresent) { + // Build attributes (returns prefixed attributes for the tree) + // Note: buildAttributesMap now internally updates the matcher with raw attributes + prefixedAttrs = this.buildAttributesMap(tagExp, this.matcher, tagName); + + if (prefixedAttrs) { + // Extract raw attributes (without prefix) for our use + extractRawAttributes(prefixedAttrs, this.options); + } + } + + // Now check if this is a stop node (after attributes are set) if (tagName !== xmlObj.tagname) { - jPath += jPath ? "." + tagName : tagName; + this.isCurrentNodeStopNode = this.isItStopNode(this.stopNodeExpressions, this.matcher); } + const startIndex = i; - if (this.isItStopNode(this.stopNodesExact, this.stopNodesWildcard, jPath, tagName)) { + if (this.isCurrentNodeStopNode) { let tagContent = ""; - //self-closing tag - if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) { - if (tagName[tagName.length - 1] === "/") { //remove trailing '/' - tagName = tagName.substr(0, tagName.length - 1); - jPath = jPath.substr(0, jPath.length - 1); - tagExp = tagName; - } else { - tagExp = tagExp.substr(0, tagExp.length - 1); - } + + // For self-closing tags, content is empty + if (isSelfClosing) { i = result.closeIndex; } //unpaired tag else if (this.options.unpairedTags.indexOf(tagName) !== -1) { - i = result.closeIndex; } //normal tag @@ -35010,50 +35902,38 @@ const parseXml = function (xmlData) { const childNode = new XmlNode(tagName); - if (tagName !== tagExp && attrExpPresent) { - childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName); - } - if (tagContent) { - tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true); + if (prefixedAttrs) { + childNode[":@"] = prefixedAttrs; } - jPath = jPath.substr(0, jPath.lastIndexOf(".")); + // For stop nodes, store raw content as-is without any processing childNode.add(this.options.textNodeName, tagContent); - this.addChild(currentNode, childNode, jPath, startIndex); + this.matcher.pop(); // Pop the stop node tag + this.isCurrentNodeStopNode = false; // Reset flag + + this.addChild(currentNode, childNode, this.matcher, startIndex); } else { //selfClosing tag - if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) { - if (tagName[tagName.length - 1] === "/") { //remove trailing '/' - tagName = tagName.substr(0, tagName.length - 1); - jPath = jPath.substr(0, jPath.length - 1); - tagExp = tagName; - } else { - tagExp = tagExp.substr(0, tagExp.length - 1); - } - - if (this.options.transformTagName) { - const newTagName = this.options.transformTagName(tagName); - if (tagExp === tagName) { - tagExp = newTagName; - } - tagName = newTagName; - } + if (isSelfClosing) { + ({ tagName, tagExp } = transformTagName(this.options.transformTagName, tagName, tagExp, this.options)); const childNode = new XmlNode(tagName); - if (tagName !== tagExp && attrExpPresent) { - childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName); + if (prefixedAttrs) { + childNode[":@"] = prefixedAttrs; } - this.addChild(currentNode, childNode, jPath, startIndex); - jPath = jPath.substr(0, jPath.lastIndexOf(".")); + this.addChild(currentNode, childNode, this.matcher, startIndex); + this.matcher.pop(); // Pop self-closing tag + this.isCurrentNodeStopNode = false; // Reset flag } - else if(this.options.unpairedTags.indexOf(tagName) !== -1){//unpaired tag + else if (this.options.unpairedTags.indexOf(tagName) !== -1) {//unpaired tag const childNode = new XmlNode(tagName); - if(tagName !== tagExp && attrExpPresent){ - childNode[":@"] = this.buildAttributesMap(tagExp, jPath); + if (prefixedAttrs) { + childNode[":@"] = prefixedAttrs; } - this.addChild(currentNode, childNode, jPath, startIndex); - jPath = jPath.substr(0, jPath.lastIndexOf(".")); + this.addChild(currentNode, childNode, this.matcher, startIndex); + this.matcher.pop(); // Pop unpaired tag + this.isCurrentNodeStopNode = false; // Reset flag i = result.closeIndex; // Continue to next iteration without changing currentNode continue; @@ -35066,10 +35946,10 @@ const parseXml = function (xmlData) { } this.tagsNodeStack.push(currentNode); - if (tagName !== tagExp && attrExpPresent) { - childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName); + if (prefixedAttrs) { + childNode[":@"] = prefixedAttrs; } - this.addChild(currentNode, childNode, jPath, startIndex); + this.addChild(currentNode, childNode, this.matcher, startIndex); currentNode = childNode; } textData = ""; @@ -35083,10 +35963,13 @@ const parseXml = function (xmlData) { return xmlObj.child; }; -function addChild(currentNode, childNode, jPath, startIndex) { +function addChild(currentNode, childNode, matcher, startIndex) { // unset startIndex if not requested if (!this.options.captureMetaData) startIndex = undefined; - const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"]); + + // Pass jPath string or matcher based on options.jPath setting + const jPathOrMatcher = this.options.jPath ? matcher.toString() : matcher; + const result = this.options.updateTag(childNode.tagname, jPathOrMatcher, childNode[":@"]); if (result === false) ; else if (typeof result === "string") { childNode.tagname = result; currentNode.addChild(childNode, startIndex); @@ -35095,33 +35978,40 @@ function addChild(currentNode, childNode, jPath, startIndex) { } } -const replaceEntitiesValue = function (val, tagName, jPath) { - // Performance optimization: Early return if no entities to replace - if (val.indexOf('&') === -1) { - return val; - } - +/** + * @param {object} val - Entity object with regex and val properties + * @param {string} tagName - Tag name + * @param {string|Matcher} jPath - jPath string or Matcher instance based on options.jPath + */ +function replaceEntitiesValue(val, tagName, jPath) { const entityConfig = this.options.processEntities; - if (!entityConfig.enabled) { + if (!entityConfig || !entityConfig.enabled) { return val; } - // Check tag-specific filtering + // Check if tag is allowed to contain entities if (entityConfig.allowedTags) { - if (!entityConfig.allowedTags.includes(tagName)) { - return val; // Skip entity replacement for current tag as not set + const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath; + const allowed = Array.isArray(entityConfig.allowedTags) + ? entityConfig.allowedTags.includes(tagName) + : entityConfig.allowedTags(tagName, jPathOrMatcher); + + if (!allowed) { + return val; } } + // Apply custom tag filter if provided if (entityConfig.tagFilter) { - if (!entityConfig.tagFilter(tagName, jPath)) { + const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath; + if (!entityConfig.tagFilter(tagName, jPathOrMatcher)) { return val; // Skip based on custom filter } } // Replace DOCTYPE entities - for (let entityName in this.docTypeEntities) { + for (const entityName of Object.keys(this.docTypeEntities)) { const entity = this.docTypeEntities[entityName]; const matches = val.match(entity.regx); @@ -35153,19 +36043,38 @@ const replaceEntitiesValue = function (val, tagName, jPath) { } } } - if (val.indexOf('&') === -1) return val; // Early exit - // Replace standard entities - for (let entityName in this.lastEntities) { + for (const entityName of Object.keys(this.lastEntities)) { const entity = this.lastEntities[entityName]; + const matches = val.match(entity.regex); + if (matches) { + this.entityExpansionCount += matches.length; + if (entityConfig.maxTotalExpansions && + this.entityExpansionCount > entityConfig.maxTotalExpansions) { + throw new Error( + `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}` + ); + } + } val = val.replace(entity.regex, entity.val); } - if (val.indexOf('&') === -1) return val; // Early exit + if (val.indexOf('&') === -1) return val; // Replace HTML entities if enabled if (this.options.htmlEntities) { - for (let entityName in this.htmlEntities) { + for (const entityName of Object.keys(this.htmlEntities)) { const entity = this.htmlEntities[entityName]; + const matches = val.match(entity.regex); + if (matches) { + //console.log(matches); + this.entityExpansionCount += matches.length; + if (entityConfig.maxTotalExpansions && + this.entityExpansionCount > entityConfig.maxTotalExpansions) { + throw new Error( + `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}` + ); + } + } val = val.replace(entity.regex, entity.val); } } @@ -35174,16 +36083,16 @@ const replaceEntitiesValue = function (val, tagName, jPath) { val = val.replace(this.ampEntity.regex, this.ampEntity.val); return val; -}; +} -function saveTextToParentTag(textData, parentNode, jPath, isLeafNode) { +function saveTextToParentTag(textData, parentNode, matcher, isLeafNode) { if (textData) { //store previously collected data as textNode if (isLeafNode === undefined) isLeafNode = parentNode.child.length === 0; textData = this.parseTextData(textData, parentNode.tagname, - jPath, + matcher, false, parentNode[":@"] ? Object.keys(parentNode[":@"]).length !== 0 : false, isLeafNode); @@ -35197,14 +36106,17 @@ function saveTextToParentTag(textData, parentNode, jPath, isLeafNode) { //TODO: use jPath to simplify the logic /** - * @param {Set} stopNodesExact - * @param {Set} stopNodesWildcard - * @param {string} jPath - * @param {string} currentTagName + * @param {Array} stopNodeExpressions - Array of compiled Expression objects + * @param {Matcher} matcher - Current path matcher */ -function isItStopNode(stopNodesExact, stopNodesWildcard, jPath, currentTagName) { - if (stopNodesWildcard && stopNodesWildcard.has(currentTagName)) return true; - if (stopNodesExact && stopNodesExact.has(jPath)) return true; +function isItStopNode(stopNodeExpressions, matcher) { + if (!stopNodeExpressions || stopNodeExpressions.length === 0) return false; + + for (let i = 0; i < stopNodeExpressions.length; i++) { + if (matcher.matches(stopNodeExpressions[i])) { + return true; + } + } return false; } @@ -35359,34 +36271,87 @@ function fromCodePoint(str, base, prefix) { } } +function transformTagName(fn, tagName, tagExp, options) { + if (fn) { + const newTagName = fn(tagName); + if (tagExp === tagName) { + tagExp = newTagName; + } + tagName = newTagName; + } + tagName = sanitizeName(tagName, options); + return { tagName, tagExp }; +} + + + +function sanitizeName(name, options) { + if (criticalProperties.includes(name)) { + throw new Error(`[SECURITY] Invalid name: "${name}" is a reserved JavaScript keyword that could cause prototype pollution`); + } else if (DANGEROUS_PROPERTY_NAMES.includes(name)) { + return options.onDangerousProperty(name); + } + return name; +} + const METADATA_SYMBOL = XmlNode.getMetaDataSymbol(); +/** + * Helper function to strip attribute prefix from attribute map + * @param {object} attrs - Attributes with prefix (e.g., {"@_class": "code"}) + * @param {string} prefix - Attribute prefix to remove (e.g., "@_") + * @returns {object} Attributes without prefix (e.g., {"class": "code"}) + */ +function stripAttributePrefix(attrs, prefix) { + if (!attrs || typeof attrs !== 'object') return {}; + if (!prefix) return attrs; + + const rawAttrs = {}; + for (const key in attrs) { + if (key.startsWith(prefix)) { + const rawName = key.substring(prefix.length); + rawAttrs[rawName] = attrs[key]; + } else { + // Attribute without prefix (shouldn't normally happen, but be safe) + rawAttrs[key] = attrs[key]; + } + } + return rawAttrs; +} + /** * * @param {array} node * @param {any} options + * @param {Matcher} matcher - Path matcher instance * @returns */ -function prettify(node, options) { - return compress(node, options); +function prettify(node, options, matcher) { + return compress(node, options, matcher); } /** * * @param {array} arr * @param {object} options - * @param {string} jPath + * @param {Matcher} matcher - Path matcher instance * @returns object */ -function compress(arr, options, jPath) { +function compress(arr, options, matcher) { let text; const compressedObj = {}; //This is intended to be a plain object for (let i = 0; i < arr.length; i++) { const tagObj = arr[i]; const property = propName(tagObj); - let newJpath = ""; - if (jPath === undefined) newJpath = property; - else newJpath = jPath + "." + property; + + // Push current property to matcher WITH RAW ATTRIBUTES (no prefix) + if (property !== undefined && property !== options.textNodeName) { + const rawAttrs = stripAttributePrefix( + tagObj[":@"] || {}, + options.attributeNamePrefix + ); + matcher.push(property, rawAttrs); + } if (property === options.textNodeName) { if (text === undefined) text = tagObj[property]; @@ -35395,11 +36360,11 @@ function compress(arr, options, jPath) { continue; } else if (tagObj[property]) { - let val = compress(tagObj[property], options, newJpath); + let val = compress(tagObj[property], options, matcher); const isLeaf = isLeafTag(val, options); if (tagObj[":@"]) { - assignAttributes(val, tagObj[":@"], newJpath, options); + assignAttributes(val, tagObj[":@"], matcher, options); } else if (Object.keys(val).length === 1 && val[options.textNodeName] !== undefined && !options.alwaysCreateTextNode) { val = val[options.textNodeName]; } else if (Object.keys(val).length === 0) { @@ -35420,12 +36385,20 @@ function compress(arr, options, jPath) { } else { //TODO: if a node is not an array, then check if it should be an array //also determine if it is a leaf node - if (options.isArray(property, newJpath, isLeaf)) { + + // Pass jPath string or matcher based on options.jPath setting + const jPathOrMatcher = options.jPath ? matcher.toString() : matcher; + if (options.isArray(property, jPathOrMatcher, isLeaf)) { compressedObj[property] = [val]; } else { compressedObj[property] = val; } } + + // Pop property from matcher after processing + if (property !== undefined && property !== options.textNodeName) { + matcher.pop(); + } } } @@ -35446,13 +36419,25 @@ function propName(obj) { } } -function assignAttributes(obj, attrMap, jpath, options) { +function assignAttributes(obj, attrMap, matcher, options) { if (attrMap) { const keys = Object.keys(attrMap); const len = keys.length; //don't make it inline for (let i = 0; i < len; i++) { - const atrrName = keys[i]; - if (options.isArray(atrrName, jpath + "." + atrrName, true, true)) { + const atrrName = keys[i]; // This is the PREFIXED name (e.g., "@_class") + + // Strip prefix for matcher path (for isArray callback) + const rawAttrName = atrrName.startsWith(options.attributeNamePrefix) + ? atrrName.substring(options.attributeNamePrefix.length) + : atrrName; + + // For attributes, we need to create a temporary path + // Pass jPath string or matcher based on options.jPath setting + const jPathOrMatcher = options.jPath + ? matcher.toString() + "." + rawAttrName + : matcher; + + if (options.isArray(atrrName, jPathOrMatcher, true, true)) { obj[atrrName] = [attrMap[atrrName]]; } else { obj[atrrName] = attrMap[atrrName]; @@ -35510,7 +36495,7 @@ class XMLParser { orderedObjParser.addExternalEntities(this.externalEntities); const orderedResult = orderedObjParser.parseXml(xmlData); if (this.options.preserveOrder || orderedResult === undefined) return orderedResult; - else return prettify(orderedResult, this.options); + else return prettify(orderedResult, this.options, orderedObjParser.matcher); } /** @@ -68316,7 +69301,7 @@ var hasRequiredFxp; function requireFxp () { if (hasRequiredFxp) return fxp.exports; hasRequiredFxp = 1; - (()=>{var t={d:(e,n)=>{for(var i in n)t.o(n,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:true,get:n[i]});},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:true});}},e={};t.r(e),t.d(e,{XMLBuilder:()=>gt,XMLParser:()=>it,XMLValidator:()=>xt});const n=":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",i=new RegExp("^["+n+"]["+n+"\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$");function s(t,e){const n=[];let i=e.exec(t);for(;i;){const s=[];s.startIndex=e.lastIndex-i[0].length;const r=i.length;for(let t=0;t"!==t[r]&&" "!==t[r]&&"\t"!==t[r]&&"\n"!==t[r]&&"\r"!==t[r];r++)h+=t[r];if(h=h.trim(),"/"===h[h.length-1]&&(h=h.substring(0,h.length-1),r--),!b(h)){let e;return e=0===h.trim().length?"Invalid space after '<'.":"Tag '"+h+"' is an invalid name.",m("InvalidTag",e,N(t,r))}const p=c(t,r);if(false===p)return m("InvalidAttr","Attributes for '"+h+"' have open quote.",N(t,r));let f=p.value;if(r=p.index,"/"===f[f.length-1]){const n=r-f.length;f=f.substring(0,f.length-1);const s=g(f,e);if(true!==s)return m(s.err.code,s.err.msg,N(t,n+s.err.line));i=true;}else if(a){if(!p.tagClosed)return m("InvalidTag","Closing tag '"+h+"' doesn't have proper closing.",N(t,r));if(f.trim().length>0)return m("InvalidTag","Closing tag '"+h+"' can't have attributes or invalid starting.",N(t,o));if(0===n.length)return m("InvalidTag","Closing tag '"+h+"' has not been opened.",N(t,o));{const e=n.pop();if(h!==e.tagName){let n=N(t,e.tagStartPos);return m("InvalidTag","Expected closing tag '"+e.tagName+"' (opened in line "+n.line+", col "+n.col+") instead of closing tag '"+h+"'.",N(t,o))}0==n.length&&(s=true);}}else {const a=g(f,e);if(true!==a)return m(a.err.code,a.err.msg,N(t,r-f.length+a.err.line));if(true===s)return m("InvalidXml","Multiple possible root nodes found.",N(t,r));-1!==e.unpairedTags.indexOf(h)||n.push({tagName:h,tagStartPos:o}),i=true;}for(r++;r0)||m("InvalidXml","Invalid '"+JSON.stringify(n.map(t=>t.tagName),null,4).replace(/\r?\n/g,"")+"' found.",{line:1,col:1}):m("InvalidXml","Start tag expected.",1)}function l(t){return " "===t||"\t"===t||"\n"===t||"\r"===t}function u(t,e){const n=e;for(;e5&&"xml"===i)return m("InvalidXml","XML declaration allowed only at the start of the document.",N(t,e));if("?"==t[e]&&">"==t[e+1]){e++;break}continue}return e}function d(t,e){if(t.length>e+5&&"-"===t[e+1]&&"-"===t[e+2]){for(e+=3;e"===t[e+2]){e+=2;break}}else if(t.length>e+8&&"D"===t[e+1]&&"O"===t[e+2]&&"C"===t[e+3]&&"T"===t[e+4]&&"Y"===t[e+5]&&"P"===t[e+6]&&"E"===t[e+7]){let n=1;for(e+=8;e"===t[e]&&(n--,0===n))break}else if(t.length>e+9&&"["===t[e+1]&&"C"===t[e+2]&&"D"===t[e+3]&&"A"===t[e+4]&&"T"===t[e+5]&&"A"===t[e+6]&&"["===t[e+7])for(e+=8;e"===t[e+2]){e+=2;break}return e}const h='"',p="'";function c(t,e){let n="",i="",s=false;for(;e"===t[e]&&""===i){s=true;break}n+=t[e];}return ""===i&&{value:n,index:e,tagClosed:s}}const f=new RegExp("(\\s*)([^\\s=]+)(\\s*=)?(\\s*(['\"])(([\\s\\S])*?)\\5)?","g");function g(t,e){const n=s(t,f),i={};for(let t=0;tfalse,commentPropName:false,unpairedTags:[],processEntities:true,htmlEntities:false,ignoreDeclaration:false,ignorePiTags:false,transformTagName:false,transformAttributeName:false,updateTag:function(t,e,n){return t},captureMetaData:false,maxNestedTags:100,strictReservedNames:true};function w(t){return "boolean"==typeof t?{enabled:t,maxEntitySize:1e4,maxExpansionDepth:10,maxTotalExpansions:1e3,maxExpandedLength:1e5,allowedTags:null,tagFilter:null}:"object"==typeof t&&null!==t?{enabled:false!==t.enabled,maxEntitySize:t.maxEntitySize??1e4,maxExpansionDepth:t.maxExpansionDepth??10,maxTotalExpansions:t.maxTotalExpansions??1e3,maxExpandedLength:t.maxExpandedLength??1e5,allowedTags:t.allowedTags??null,tagFilter:t.tagFilter??null}:w(true)}const v=function(t){const e=Object.assign({},T,t);return e.processEntities=w(e.processEntities),e};let O;O="function"!=typeof Symbol?"@@xmlMetadata":Symbol("XML Node Metadata");class I{constructor(t){this.tagname=t,this.child=[],this[":@"]=Object.create(null);}add(t,e){"__proto__"===t&&(t="#__proto__"),this.child.push({[t]:e});}addChild(t,e){"__proto__"===t.tagname&&(t.tagname="#__proto__"),t[":@"]&&Object.keys(t[":@"]).length>0?this.child.push({[t.tagname]:t.child,":@":t[":@"]}):this.child.push({[t.tagname]:t.child}),void 0!==e&&(this.child[this.child.length-1][O]={startIndex:e});}static getMetaDataSymbol(){return O}}class P{constructor(t){this.suppressValidationErr=!t,this.options=t;}readDocType(t,e){const n=Object.create(null);if("O"!==t[e+3]||"C"!==t[e+4]||"T"!==t[e+5]||"Y"!==t[e+6]||"P"!==t[e+7]||"E"!==t[e+8])throw new Error("Invalid Tag instead of DOCTYPE");{e+=9;let i=1,s=false,r=false,o="";for(;e"===t[e]){if(r?"-"===t[e-1]&&"-"===t[e-2]&&(r=false,i--):i--,0===i)break}else "["===t[e]?s=true:o+=t[e];else {if(s&&S(t,"!ENTITY",e)){let i,s;if(e+=7,[i,s,e]=this.readEntityExp(t,e+1,this.suppressValidationErr),-1===s.indexOf("&")){const t=i.replace(/[.\-+*:]/g,"\\.");n[i]={regx:RegExp(`&${t};`,"g"),val:s};}}else if(s&&S(t,"!ELEMENT",e)){e+=8;const{index:n}=this.readElementExp(t,e+1);e=n;}else if(s&&S(t,"!ATTLIST",e))e+=8;else if(s&&S(t,"!NOTATION",e)){e+=9;const{index:n}=this.readNotationExp(t,e+1,this.suppressValidationErr);e=n;}else {if(!S(t,"!--",e))throw new Error("Invalid DOCTYPE");r=true;}i++,o="";}if(0!==i)throw new Error("Unclosed DOCTYPE")}return {entities:n,i:e}}readEntityExp(t,e){e=A(t,e);let n="";for(;ethis.options.maxEntitySize)throw new Error(`Entity "${n}" size (${i.length}) exceeds maximum allowed size (${this.options.maxEntitySize})`);return [n,i,--e]}readNotationExp(t,e){e=A(t,e);let n="";for(;e{for(;e"},lt:{regex:/&(lt|#60|#x3C);/g,val:"<"},quot:{regex:/&(quot|#34|#x22);/g,val:'"'}},this.ampEntity={regex:/&(amp|#38|#x26);/g,val:"&"},this.htmlEntities={space:{regex:/&(nbsp|#160);/g,val:" "},cent:{regex:/&(cent|#162);/g,val:"¢"},pound:{regex:/&(pound|#163);/g,val:"£"},yen:{regex:/&(yen|#165);/g,val:"¥"},euro:{regex:/&(euro|#8364);/g,val:"€"},copyright:{regex:/&(copy|#169);/g,val:"©"},reg:{regex:/&(reg|#174);/g,val:"®"},inr:{regex:/&(inr|#8377);/g,val:"₹"},num_dec:{regex:/&#([0-9]{1,7});/g,val:(t,e)=>K(e,10,"&#")},num_hex:{regex:/&#x([0-9a-fA-F]{1,6});/g,val:(t,e)=>K(e,16,"&#x")}},this.addExternalEntities=F,this.parseXml=R,this.parseTextData=M,this.resolveNameSpace=k,this.buildAttributesMap=U,this.isItStopNode=X,this.replaceEntitiesValue=Y,this.readStopNodeData=q,this.saveTextToParentTag=G,this.addChild=B,this.ignoreAttributesFn="function"==typeof(e=this.options.ignoreAttributes)?e:Array.isArray(e)?t=>{for(const n of e){if("string"==typeof n&&t===n)return true;if(n instanceof RegExp&&n.test(t))return true}}:()=>false,this.entityExpansionCount=0,this.currentExpandedLength=0,this.options.stopNodes&&this.options.stopNodes.length>0){this.stopNodesExact=new Set,this.stopNodesWildcard=new Set;for(let t=0;t0)){o||(t=this.replaceEntitiesValue(t,e,n));const i=this.options.tagValueProcessor(e,t,n,s,r);return null==i?t:typeof i!=typeof t||i!==t?i:this.options.trimValues||t.trim()===t?Z(t,this.options.parseTagValue,this.options.numberParseOptions):t}}function k(t){if(this.options.removeNSPrefix){const e=t.split(":"),n="/"===t.charAt(0)?"/":"";if("xmlns"===e[0])return "";2===e.length&&(t=n+e[1]);}return t}const _=new RegExp("([^\\s=]+)\\s*(=\\s*(['\"])([\\s\\S]*?)\\3)?","gm");function U(t,e,n){if(true!==this.options.ignoreAttributes&&"string"==typeof t){const i=s(t,_),r=i.length,o={};for(let t=0;t",o,"Closing Tag is not closed.");let r=t.substring(o+2,e).trim();if(this.options.removeNSPrefix){const t=r.indexOf(":");-1!==t&&(r=r.substr(t+1));}this.options.transformTagName&&(r=this.options.transformTagName(r)),n&&(i=this.saveTextToParentTag(i,n,s));const a=s.substring(s.lastIndexOf(".")+1);if(r&&-1!==this.options.unpairedTags.indexOf(r))throw new Error(`Unpaired tag can not be used as closing tag: `);let l=0;a&&-1!==this.options.unpairedTags.indexOf(a)?(l=s.lastIndexOf(".",s.lastIndexOf(".")-1),this.tagsNodeStack.pop()):l=s.lastIndexOf("."),s=s.substring(0,l),n=this.tagsNodeStack.pop(),i="",o=e;}else if("?"===t[o+1]){let e=W(t,o,false,"?>");if(!e)throw new Error("Pi Tag is not closed.");if(i=this.saveTextToParentTag(i,n,s),this.options.ignoreDeclaration&&"?xml"===e.tagName||this.options.ignorePiTags);else {const t=new I(e.tagName);t.add(this.options.textNodeName,""),e.tagName!==e.tagExp&&e.attrExpPresent&&(t[":@"]=this.buildAttributesMap(e.tagExp,s,e.tagName)),this.addChild(n,t,s,o);}o=e.closeIndex+1;}else if("!--"===t.substr(o+1,3)){const e=z(t,"--\x3e",o+4,"Comment is not closed.");if(this.options.commentPropName){const r=t.substring(o+4,e-2);i=this.saveTextToParentTag(i,n,s),n.add(this.options.commentPropName,[{[this.options.textNodeName]:r}]);}o=e;}else if("!D"===t.substr(o+1,2)){const e=r.readDocType(t,o);this.docTypeEntities=e.entities,o=e.i;}else if("!["===t.substr(o+1,2)){const e=z(t,"]]>",o,"CDATA is not closed.")-2,r=t.substring(o+9,e);i=this.saveTextToParentTag(i,n,s);let a=this.parseTextData(r,n.tagname,s,true,false,true,true);null==a&&(a=""),this.options.cdataPropName?n.add(this.options.cdataPropName,[{[this.options.textNodeName]:r}]):n.add(this.options.textNodeName,a),o=e+2;}else {let r=W(t,o,this.options.removeNSPrefix),a=r.tagName;const l=r.rawTagName;let u=r.tagExp,d=r.attrExpPresent,h=r.closeIndex;if(this.options.transformTagName){const t=this.options.transformTagName(a);u===a&&(u=t),a=t;}if(this.options.strictReservedNames&&(a===this.options.commentPropName||a===this.options.cdataPropName))throw new Error(`Invalid tag name: ${a}`);n&&i&&"!xml"!==n.tagname&&(i=this.saveTextToParentTag(i,n,s,false));const p=n;p&&-1!==this.options.unpairedTags.indexOf(p.tagname)&&(n=this.tagsNodeStack.pop(),s=s.substring(0,s.lastIndexOf("."))),a!==e.tagname&&(s+=s?"."+a:a);const c=o;if(this.isItStopNode(this.stopNodesExact,this.stopNodesWildcard,s,a)){let e="";if(u.length>0&&u.lastIndexOf("/")===u.length-1)"/"===a[a.length-1]?(a=a.substr(0,a.length-1),s=s.substr(0,s.length-1),u=a):u=u.substr(0,u.length-1),o=r.closeIndex;else if(-1!==this.options.unpairedTags.indexOf(a))o=r.closeIndex;else {const n=this.readStopNodeData(t,l,h+1);if(!n)throw new Error(`Unexpected end of ${l}`);o=n.i,e=n.tagContent;}const i=new I(a);a!==u&&d&&(i[":@"]=this.buildAttributesMap(u,s,a)),e&&(e=this.parseTextData(e,a,s,true,d,true,true)),s=s.substr(0,s.lastIndexOf(".")),i.add(this.options.textNodeName,e),this.addChild(n,i,s,c);}else {if(u.length>0&&u.lastIndexOf("/")===u.length-1){if("/"===a[a.length-1]?(a=a.substr(0,a.length-1),s=s.substr(0,s.length-1),u=a):u=u.substr(0,u.length-1),this.options.transformTagName){const t=this.options.transformTagName(a);u===a&&(u=t),a=t;}const t=new I(a);a!==u&&d&&(t[":@"]=this.buildAttributesMap(u,s,a)),this.addChild(n,t,s,c),s=s.substr(0,s.lastIndexOf("."));}else {if(-1!==this.options.unpairedTags.indexOf(a)){const t=new I(a);a!==u&&d&&(t[":@"]=this.buildAttributesMap(u,s)),this.addChild(n,t,s,c),s=s.substr(0,s.lastIndexOf(".")),o=r.closeIndex;continue}{const t=new I(a);if(this.tagsNodeStack.length>this.options.maxNestedTags)throw new Error("Maximum nested tags exceeded");this.tagsNodeStack.push(n),a!==u&&d&&(t[":@"]=this.buildAttributesMap(u,s,a)),this.addChild(n,t,s,c),n=t;}}i="",o=h;}}else i+=t[o];return e.child};function B(t,e,n,i){this.options.captureMetaData||(i=void 0);const s=this.options.updateTag(e.tagname,n,e[":@"]);false===s||("string"==typeof s?(e.tagname=s,t.addChild(e,i)):t.addChild(e,i));}const Y=function(t,e,n){if(-1===t.indexOf("&"))return t;const i=this.options.processEntities;if(!i.enabled)return t;if(i.allowedTags&&!i.allowedTags.includes(e))return t;if(i.tagFilter&&!i.tagFilter(e,n))return t;for(let e in this.docTypeEntities){const n=this.docTypeEntities[e],s=t.match(n.regx);if(s){if(this.entityExpansionCount+=s.length,i.maxTotalExpansions&&this.entityExpansionCount>i.maxTotalExpansions)throw new Error(`Entity expansion limit exceeded: ${this.entityExpansionCount} > ${i.maxTotalExpansions}`);const e=t.length;if(t=t.replace(n.regx,n.val),i.maxExpandedLength&&(this.currentExpandedLength+=t.length-e,this.currentExpandedLength>i.maxExpandedLength))throw new Error(`Total expanded content size exceeded: ${this.currentExpandedLength} > ${i.maxExpandedLength}`)}}if(-1===t.indexOf("&"))return t;for(let e in this.lastEntities){const n=this.lastEntities[e];t=t.replace(n.regex,n.val);}if(-1===t.indexOf("&"))return t;if(this.options.htmlEntities)for(let e in this.htmlEntities){const n=this.htmlEntities[e];t=t.replace(n.regex,n.val);}return t.replace(this.ampEntity.regex,this.ampEntity.val)};function G(t,e,n,i){return t&&(void 0===i&&(i=0===e.child.length),void 0!==(t=this.parseTextData(t,e.tagname,n,false,!!e[":@"]&&0!==Object.keys(e[":@"]).length,i))&&""!==t&&e.add(this.options.textNodeName,t),t=""),t}function X(t,e,n,i){return !(!e||!e.has(i))||!(!t||!t.has(n))}function z(t,e,n,i){const s=t.indexOf(e,n);if(-1===s)throw new Error(i);return s+e.length-1}function W(t,e,n,i=">"){const s=function(t,e,n=">"){let i,s="";for(let r=e;r",n,`${e} is not closed`);if(t.substring(n+2,r).trim()===e&&(s--,0===s))return {tagContent:t.substring(i,n),i:r};n=r;}else if("?"===t[n+1])n=z(t,"?>",n+1,"StopNode is not closed.");else if("!--"===t.substr(n+1,3))n=z(t,"--\x3e",n+3,"StopNode is not closed.");else if("!["===t.substr(n+1,2))n=z(t,"]]>",n,"StopNode is not closed.")-2;else {const i=W(t,n,">");i&&((i&&i.tagName)===e&&"/"!==i.tagExp[i.tagExp.length-1]&&s++,n=i.closeIndex);}}function Z(t,e,n){if(e&&"string"==typeof t){const e=t.trim();return "true"===e||"false"!==e&&function(t,e={}){if(e=Object.assign({},D,e),!t||"string"!=typeof t)return t;let n=t.trim();if(void 0!==e.skipLike&&e.skipLike.test(n))return t;if("0"===t)return 0;if(e.hex&&$.test(n))return function(t){if(parseInt)return parseInt(t,16);if(Number.parseInt)return Number.parseInt(t,16);if(window&&window.parseInt)return window.parseInt(t,16);throw new Error("parseInt, Number.parseInt, window.parseInt are not supported")}(n);if(n.includes("e")||n.includes("E"))return function(t,e,n){if(!n.eNotation)return t;const i=e.match(j);if(i){let s=i[1]||"";const r=-1===i[3].indexOf("e")?"E":"e",o=i[2],a=s?t[o.length+1]===r:t[o.length]===r;return o.length>1&&a?t:1!==o.length||!i[3].startsWith(`.${r}`)&&i[3][0]!==r?n.leadingZeros&&!a?(e=(i[1]||"")+i[3],Number(e)):t:Number(e)}return t}(t,n,e);{const s=V.exec(n);if(s){const r=s[1]||"",o=s[2];let a=(i=s[3])&&-1!==i.indexOf(".")?("."===(i=i.replace(/0+$/,""))?i="0":"."===i[0]?i="0"+i:"."===i[i.length-1]&&(i=i.substring(0,i.length-1)),i):i;const l=r?"."===t[o.length+1]:"."===t[o.length];if(!e.leadingZeros&&(o.length>1||1===o.length&&!l))return t;{const i=Number(n),s=String(i);if(0===i)return i;if(-1!==s.search(/[eE]/))return e.eNotation?i:t;if(-1!==n.indexOf("."))return "0"===s||s===a||s===`${r}${a}`?i:t;let l=o?a:n;return o?l===s||r+l===s?i:t:l===s||l===r+s?i:t}}return t}var i;}(t,n)}return void 0!==t?t:""}function K(t,e,n){const i=Number.parseInt(t,e);return i>=0&&i<=1114111?String.fromCodePoint(i):n+t+";"}const Q=I.getMetaDataSymbol();function J(t,e){return H(t,e)}function H(t,e,n){let i;const s={};for(let r=0;r0&&(s[e.textNodeName]=i):void 0!==i&&(s[e.textNodeName]=i),s}function tt(t){const e=Object.keys(t);for(let t=0;t0&&(n="\n"),rt(t,e,"",n)}function rt(t,e,n,i){let s="",r=false;if(!Array.isArray(t)){if(null!=t){let n=t.toString();return n=ut(n,e),n}return ""}for(let o=0;o`,r=false;continue}if(l===e.commentPropName){s+=i+`\x3c!--${a[l][0][e.textNodeName]}--\x3e`,r=true;continue}if("?"===l[0]){const t=at(a[":@"],e),n="?xml"===l?"":i;let o=a[l][0][e.textNodeName];o=0!==o.length?" "+o:"",s+=n+`<${l}${o}${t}?>`,r=true;continue}let d=i;""!==d&&(d+=e.indentBy);const h=i+`<${l}${at(a[":@"],e)}`,p=rt(a[l],e,u,d);-1!==e.unpairedTags.indexOf(l)?e.suppressUnpairedNode?s+=h+">":s+=h+"/>":p&&0!==p.length||!e.suppressEmptyNode?p&&p.endsWith(">")?s+=h+`>${p}${i}`:(s+=h+">",p&&""!==i&&(p.includes("/>")||p.includes("`):s+=h+"/>",r=true;}return s}function ot(t){const e=Object.keys(t);for(let n=0;n0&&e.processEntities)for(let n=0;n","g"),val:">"},{regex:new RegExp("<","g"),val:"<"},{regex:new RegExp("'","g"),val:"'"},{regex:new RegExp('"',"g"),val:"""}],processEntities:true,stopNodes:[],oneListGroup:false};function ht(t){var e;this.options=Object.assign({},dt,t),true===this.options.ignoreAttributes||this.options.attributesGroupName?this.isAttribute=function(){return false}:(this.ignoreAttributesFn="function"==typeof(e=this.options.ignoreAttributes)?e:Array.isArray(e)?t=>{for(const n of e){if("string"==typeof n&&t===n)return true;if(n instanceof RegExp&&n.test(t))return true}}:()=>false,this.attrPrefixLen=this.options.attributeNamePrefix.length,this.isAttribute=ft),this.processTextOrObjNode=pt,this.options.format?(this.indentate=ct,this.tagEndChar=">\n",this.newLine="\n"):(this.indentate=function(){return ""},this.tagEndChar=">",this.newLine="");}function pt(t,e,n,i){const s=this.j2x(t,n+1,i.concat(e));return void 0!==t[this.options.textNodeName]&&1===Object.keys(t).length?this.buildTextValNode(t[this.options.textNodeName],e,s.attrStr,n):this.buildObjectNode(s.val,e,s.attrStr,n)}function ct(t){return this.options.indentBy.repeat(t)}function ft(t){return !(!t.startsWith(this.options.attributeNamePrefix)||t===this.options.textNodeName)&&t.substr(this.attrPrefixLen)}ht.prototype.build=function(t){return this.options.preserveOrder?st(t,this.options):(Array.isArray(t)&&this.options.arrayNodeName&&this.options.arrayNodeName.length>1&&(t={[this.options.arrayNodeName]:t}),this.j2x(t,0,[]).val)},ht.prototype.j2x=function(t,e,n){let i="",s="";const r=n.join(".");for(let o in t)if(Object.prototype.hasOwnProperty.call(t,o))if(void 0===t[o])this.isAttribute(o)&&(s+="");else if(null===t[o])this.isAttribute(o)||o===this.options.cdataPropName?s+="":"?"===o[0]?s+=this.indentate(e)+"<"+o+"?"+this.tagEndChar:s+=this.indentate(e)+"<"+o+"/"+this.tagEndChar;else if(t[o]instanceof Date)s+=this.buildTextValNode(t[o],o,"",e);else if("object"!=typeof t[o]){const n=this.isAttribute(o);if(n&&!this.ignoreAttributesFn(n,r))i+=this.buildAttrPairStr(n,""+t[o]);else if(!n)if(o===this.options.textNodeName){let e=this.options.tagValueProcessor(o,""+t[o]);s+=this.replaceEntitiesValue(e);}else s+=this.buildTextValNode(t[o],o,"",e);}else if(Array.isArray(t[o])){const i=t[o].length;let r="",a="";for(let l=0;l"+t+s}},ht.prototype.closeTag=function(t){let e="";return -1!==this.options.unpairedTags.indexOf(t)?this.options.suppressUnpairedNode||(e="/"):e=this.options.suppressEmptyNode?"/":`>`+this.newLine;if(false!==this.options.commentPropName&&e===this.options.commentPropName)return this.indentate(i)+`\x3c!--${t}--\x3e`+this.newLine;if("?"===e[0])return this.indentate(i)+"<"+e+n+"?"+this.tagEndChar;{let s=this.options.tagValueProcessor(e,t);return s=this.replaceEntitiesValue(s),""===s?this.indentate(i)+"<"+e+n+this.closeTag(e)+this.tagEndChar:this.indentate(i)+"<"+e+n+">"+s+"0&&this.options.processEntities)for(let e=0;e{var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:true,get:i[n]});},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:true});}},e={};t.r(e),t.d(e,{XMLBuilder:()=>Ot,XMLParser:()=>ft,XMLValidator:()=>$t});const i=":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",n=new RegExp("^["+i+"]["+i+"\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$");function s(t,e){const i=[];let n=e.exec(t);for(;n;){const s=[];s.startIndex=e.lastIndex-n[0].length;const r=n.length;for(let t=0;t"!==t[r]&&" "!==t[r]&&"\t"!==t[r]&&"\n"!==t[r]&&"\r"!==t[r];r++)h+=t[r];if(h=h.trim(),"/"===h[h.length-1]&&(h=h.substring(0,h.length-1),r--),!y(h)){let e;return e=0===h.trim().length?"Invalid space after '<'.":"Tag '"+h+"' is an invalid name.",b("InvalidTag",e,w(t,r))}const l=g(t,r);if(false===l)return b("InvalidAttr","Attributes for '"+h+"' have open quote.",w(t,r));let d=l.value;if(r=l.index,"/"===d[d.length-1]){const i=r-d.length;d=d.substring(0,d.length-1);const s=x(d,e);if(true!==s)return b(s.err.code,s.err.msg,w(t,i+s.err.line));n=true;}else if(a){if(!l.tagClosed)return b("InvalidTag","Closing tag '"+h+"' doesn't have proper closing.",w(t,r));if(d.trim().length>0)return b("InvalidTag","Closing tag '"+h+"' can't have attributes or invalid starting.",w(t,o));if(0===i.length)return b("InvalidTag","Closing tag '"+h+"' has not been opened.",w(t,o));{const e=i.pop();if(h!==e.tagName){let i=w(t,e.tagStartPos);return b("InvalidTag","Expected closing tag '"+e.tagName+"' (opened in line "+i.line+", col "+i.col+") instead of closing tag '"+h+"'.",w(t,o))}0==i.length&&(s=true);}}else {const a=x(d,e);if(true!==a)return b(a.err.code,a.err.msg,w(t,r-d.length+a.err.line));if(true===s)return b("InvalidXml","Multiple possible root nodes found.",w(t,r));-1!==e.unpairedTags.indexOf(h)||i.push({tagName:h,tagStartPos:o}),n=true;}for(r++;r0)||b("InvalidXml","Invalid '"+JSON.stringify(i.map(t=>t.tagName),null,4).replace(/\r?\n/g,"")+"' found.",{line:1,col:1}):b("InvalidXml","Start tag expected.",1)}function p(t){return " "===t||"\t"===t||"\n"===t||"\r"===t}function u(t,e){const i=e;for(;e5&&"xml"===n)return b("InvalidXml","XML declaration allowed only at the start of the document.",w(t,e));if("?"==t[e]&&">"==t[e+1]){e++;break}continue}return e}function c(t,e){if(t.length>e+5&&"-"===t[e+1]&&"-"===t[e+2]){for(e+=3;e"===t[e+2]){e+=2;break}}else if(t.length>e+8&&"D"===t[e+1]&&"O"===t[e+2]&&"C"===t[e+3]&&"T"===t[e+4]&&"Y"===t[e+5]&&"P"===t[e+6]&&"E"===t[e+7]){let i=1;for(e+=8;e"===t[e]&&(i--,0===i))break}else if(t.length>e+9&&"["===t[e+1]&&"C"===t[e+2]&&"D"===t[e+3]&&"A"===t[e+4]&&"T"===t[e+5]&&"A"===t[e+6]&&"["===t[e+7])for(e+=8;e"===t[e+2]){e+=2;break}return e}const d='"',f="'";function g(t,e){let i="",n="",s=false;for(;e"===t[e]&&""===n){s=true;break}i+=t[e];}return ""===n&&{value:i,index:e,tagClosed:s}}const m=new RegExp("(\\s*)([^\\s=]+)(\\s*=)?(\\s*(['\"])(([\\s\\S])*?)\\5)?","g");function x(t,e){const i=s(t,m),n={};for(let t=0;to.includes(t)?"__"+t:t,P={preserveOrder:false,attributeNamePrefix:"@_",attributesGroupName:false,textNodeName:"#text",ignoreAttributes:true,removeNSPrefix:false,allowBooleanAttributes:false,parseTagValue:true,parseAttributeValue:false,trimValues:true,cdataPropName:false,numberParseOptions:{hex:true,leadingZeros:true,eNotation:true},tagValueProcessor:function(t,e){return e},attributeValueProcessor:function(t,e){return e},stopNodes:[],alwaysCreateTextNode:false,isArray:()=>false,commentPropName:false,unpairedTags:[],processEntities:true,htmlEntities:false,ignoreDeclaration:false,ignorePiTags:false,transformTagName:false,transformAttributeName:false,updateTag:function(t,e,i){return t},captureMetaData:false,maxNestedTags:100,strictReservedNames:true,jPath:true,onDangerousProperty:T};function S(t,e){if("string"!=typeof t)return;const i=t.toLowerCase();if(o.some(t=>i===t.toLowerCase()))throw new Error(`[SECURITY] Invalid ${e}: "${t}" is a reserved JavaScript keyword that could cause prototype pollution`);if(a.some(t=>i===t.toLowerCase()))throw new Error(`[SECURITY] Invalid ${e}: "${t}" is a reserved JavaScript keyword that could cause prototype pollution`)}function A(t){return "boolean"==typeof t?{enabled:t,maxEntitySize:1e4,maxExpansionDepth:10,maxTotalExpansions:1e3,maxExpandedLength:1e5,maxEntityCount:100,allowedTags:null,tagFilter:null}:"object"==typeof t&&null!==t?{enabled:false!==t.enabled,maxEntitySize:Math.max(1,t.maxEntitySize??1e4),maxExpansionDepth:Math.max(1,t.maxExpansionDepth??10),maxTotalExpansions:Math.max(1,t.maxTotalExpansions??1e3),maxExpandedLength:Math.max(1,t.maxExpandedLength??1e5),maxEntityCount:Math.max(1,t.maxEntityCount??100),allowedTags:t.allowedTags??null,tagFilter:t.tagFilter??null}:A(true)}const C=function(t){const e=Object.assign({},P,t),i=[{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:t,name:e}of i)t&&S(t,e);return null===e.onDangerousProperty&&(e.onDangerousProperty=T),e.processEntities=A(e.processEntities),e.stopNodes&&Array.isArray(e.stopNodes)&&(e.stopNodes=e.stopNodes.map(t=>"string"==typeof t&&t.startsWith("*.")?".."+t.substring(2):t)),e};let O;O="function"!=typeof Symbol?"@@xmlMetadata":Symbol("XML Node Metadata");class ${constructor(t){this.tagname=t,this.child=[],this[":@"]=Object.create(null);}add(t,e){"__proto__"===t&&(t="#__proto__"),this.child.push({[t]:e});}addChild(t,e){"__proto__"===t.tagname&&(t.tagname="#__proto__"),t[":@"]&&Object.keys(t[":@"]).length>0?this.child.push({[t.tagname]:t.child,":@":t[":@"]}):this.child.push({[t.tagname]:t.child}),void 0!==e&&(this.child[this.child.length-1][O]={startIndex:e});}static getMetaDataSymbol(){return O}}class I{constructor(t){this.suppressValidationErr=!t,this.options=t;}readDocType(t,e){const i=Object.create(null);let n=0;if("O"!==t[e+3]||"C"!==t[e+4]||"T"!==t[e+5]||"Y"!==t[e+6]||"P"!==t[e+7]||"E"!==t[e+8])throw new Error("Invalid Tag instead of DOCTYPE");{e+=9;let s=1,r=false,o=false,a="";for(;e"===t[e]){if(o?"-"===t[e-1]&&"-"===t[e-2]&&(o=false,s--):s--,0===s)break}else "["===t[e]?r=true:a+=t[e];else {if(r&&_(t,"!ENTITY",e)){let s,r;if(e+=7,[s,r,e]=this.readEntityExp(t,e+1,this.suppressValidationErr),-1===r.indexOf("&")){if(false!==this.options.enabled&&null!=this.options.maxEntityCount&&n>=this.options.maxEntityCount)throw new Error(`Entity count (${n+1}) exceeds maximum allowed (${this.options.maxEntityCount})`);const t=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");i[s]={regx:RegExp(`&${t};`,"g"),val:r},n++;}}else if(r&&_(t,"!ELEMENT",e)){e+=8;const{index:i}=this.readElementExp(t,e+1);e=i;}else if(r&&_(t,"!ATTLIST",e))e+=8;else if(r&&_(t,"!NOTATION",e)){e+=9;const{index:i}=this.readNotationExp(t,e+1,this.suppressValidationErr);e=i;}else {if(!_(t,"!--",e))throw new Error("Invalid DOCTYPE");o=true;}s++,a="";}if(0!==s)throw new Error("Unclosed DOCTYPE")}return {entities:i,i:e}}readEntityExp(t,e){const i=e=j(t,e);for(;ethis.options.maxEntitySize)throw new Error(`Entity "${n}" size (${s.length}) exceeds maximum allowed size (${this.options.maxEntitySize})`);return [n,s,--e]}readNotationExp(t,e){const i=e=j(t,e);for(;e{for(;e0&&(this.path[this.path.length-1].values=void 0);const n=this.path.length;this.siblingStacks[n]||(this.siblingStacks[n]=new Map);const s=this.siblingStacks[n],r=i?`${i}:${t}`:t,o=s.get(r)||0;let a=0;for(const t of s.values())a+=t;s.set(r,o+1);const h={tag:t,position:a,counter:o};null!=i&&(h.namespace=i),null!=e&&(h.values=e),this.path.push(h);}pop(){if(0===this.path.length)return;const t=this.path.pop();return this.siblingStacks.length>this.path.length+1&&(this.siblingStacks.length=this.path.length+1),t}updateCurrent(t){if(this.path.length>0){const e=this.path[this.path.length-1];null!=t&&(e.values=t);}}getCurrentTag(){return this.path.length>0?this.path[this.path.length-1].tag:void 0}getCurrentNamespace(){return this.path.length>0?this.path[this.path.length-1].namespace:void 0}getAttrValue(t){if(0===this.path.length)return;const e=this.path[this.path.length-1];return e.values?.[t]}hasAttr(t){if(0===this.path.length)return false;const e=this.path[this.path.length-1];return void 0!==e.values&&t in e.values}getPosition(){return 0===this.path.length?-1:this.path[this.path.length-1].position??0}getCounter(){return 0===this.path.length?-1:this.path[this.path.length-1].counter??0}getIndex(){return this.getPosition()}getDepth(){return this.path.length}toString(t,e=true){const i=t||this.separator;return this.path.map(t=>e&&t.namespace?`${t.namespace}:${t.tag}`:t.tag).join(i)}toArray(){return this.path.map(t=>t.tag)}reset(){this.path=[],this.siblingStacks=[];}matches(t){const e=t.segments;return 0!==e.length&&(t.hasDeepWildcard()?this._matchWithDeepWildcard(e):this._matchSimple(e))}_matchSimple(t){if(this.path.length!==t.length)return false;for(let e=0;e=0&&e>=0;){const n=t[i];if("deep-wildcard"===n.type){if(i--,i<0)return true;const n=t[i];let s=false;for(let t=e;t>=0;t--){const r=t===this.path.length-1;if(this._matchSegment(n,this.path[t],r)){e=t-1,i--,s=true;break}}if(!s)return false}else {const t=e===this.path.length-1;if(!this._matchSegment(n,this.path[e],t))return false;e--,i--;}}return i<0}_matchSegment(t,e,i){if("*"!==t.tag&&t.tag!==e.tag)return false;if(void 0!==t.namespace&&"*"!==t.namespace&&t.namespace!==e.namespace)return false;if(void 0!==t.attrName){if(!i)return false;if(!e.values||!(t.attrName in e.values))return false;if(void 0!==t.attrValue){const i=e.values[t.attrName];if(String(i)!==String(t.attrValue))return false}}if(void 0!==t.position){if(!i)return false;const n=e.counter??0;if("first"===t.position&&0!==n)return false;if("odd"===t.position&&n%2!=1)return false;if("even"===t.position&&n%2!=0)return false;if("nth"===t.position&&n!==t.positionValue)return false}return true}snapshot(){return {path:this.path.map(t=>({...t})),siblingStacks:this.siblingStacks.map(t=>new Map(t))}}restore(t){this.path=t.path.map(t=>({...t})),this.siblingStacks=t.siblingStacks.map(t=>new Map(t));}}class G{constructor(t,e={}){this.pattern=t,this.separator=e.separator||".",this.segments=this._parse(t),this._hasDeepWildcard=this.segments.some(t=>"deep-wildcard"===t.type),this._hasAttributeCondition=this.segments.some(t=>void 0!==t.attrName),this._hasPositionSelector=this.segments.some(t=>void 0!==t.position);}_parse(t){const e=[];let i=0,n="";for(;i0){const i=t.substring(0,e);if("xmlns"!==i)return i}}class B{constructor(t){var e;if(this.options=t,this.currentNode=null,this.tagsNodeStack=[],this.docTypeEntities={},this.lastEntities={apos:{regex:/&(apos|#39|#x27);/g,val:"'"},gt:{regex:/&(gt|#62|#x3E);/g,val:">"},lt:{regex:/&(lt|#60|#x3C);/g,val:"<"},quot:{regex:/&(quot|#34|#x22);/g,val:'"'}},this.ampEntity={regex:/&(amp|#38|#x26);/g,val:"&"},this.htmlEntities={space:{regex:/&(nbsp|#160);/g,val:" "},cent:{regex:/&(cent|#162);/g,val:"¢"},pound:{regex:/&(pound|#163);/g,val:"£"},yen:{regex:/&(yen|#165);/g,val:"¥"},euro:{regex:/&(euro|#8364);/g,val:"€"},copyright:{regex:/&(copy|#169);/g,val:"©"},reg:{regex:/&(reg|#174);/g,val:"®"},inr:{regex:/&(inr|#8377);/g,val:"₹"},num_dec:{regex:/&#([0-9]{1,7});/g,val:(t,e)=>st(e,10,"&#")},num_hex:{regex:/&#x([0-9a-fA-F]{1,6});/g,val:(t,e)=>st(e,16,"&#x")}},this.addExternalEntities=W,this.parseXml=Z,this.parseTextData=Y,this.resolveNameSpace=X,this.buildAttributesMap=q,this.isItStopNode=H,this.replaceEntitiesValue=K,this.readStopNodeData=it,this.saveTextToParentTag=Q,this.addChild=J,this.ignoreAttributesFn="function"==typeof(e=this.options.ignoreAttributes)?e:Array.isArray(e)?t=>{for(const i of e){if("string"==typeof i&&t===i)return true;if(i instanceof RegExp&&i.test(t))return true}}:()=>false,this.entityExpansionCount=0,this.currentExpandedLength=0,this.matcher=new L,this.isCurrentNodeStopNode=false,this.options.stopNodes&&this.options.stopNodes.length>0){this.stopNodeExpressions=[];for(let t=0;t0)){o||(t=this.replaceEntitiesValue(t,e,i));const n=this.options.jPath?i.toString():i,a=this.options.tagValueProcessor(e,t,n,s,r);return null==a?t:typeof a!=typeof t||a!==t?a:this.options.trimValues||t.trim()===t?nt(t,this.options.parseTagValue,this.options.numberParseOptions):t}}function X(t){if(this.options.removeNSPrefix){const e=t.split(":"),i="/"===t.charAt(0)?"/":"";if("xmlns"===e[0])return "";2===e.length&&(t=i+e[1]);}return t}const z=new RegExp("([^\\s=]+)\\s*(=\\s*(['\"])([\\s\\S]*?)\\3)?","gm");function q(t,e,i){if(true!==this.options.ignoreAttributes&&"string"==typeof t){const n=s(t,z),r=n.length,o={},a={};for(let t=0;t0&&"object"==typeof e&&e.updateCurrent&&e.updateCurrent(a);for(let t=0;t",r,"Closing Tag is not closed.");let s=t.substring(r+2,e).trim();if(this.options.removeNSPrefix){const t=s.indexOf(":");-1!==t&&(s=s.substr(t+1));}s=rt(this.options.transformTagName,s,"",this.options).tagName,i&&(n=this.saveTextToParentTag(n,i,this.matcher));const o=this.matcher.getCurrentTag();if(s&&-1!==this.options.unpairedTags.indexOf(s))throw new Error(`Unpaired tag can not be used as closing tag: `);o&&-1!==this.options.unpairedTags.indexOf(o)&&(this.matcher.pop(),this.tagsNodeStack.pop()),this.matcher.pop(),this.isCurrentNodeStopNode=false,i=this.tagsNodeStack.pop(),n="",r=e;}else if("?"===t[r+1]){let e=et(t,r,false,"?>");if(!e)throw new Error("Pi Tag is not closed.");if(n=this.saveTextToParentTag(n,i,this.matcher),this.options.ignoreDeclaration&&"?xml"===e.tagName||this.options.ignorePiTags);else {const t=new $(e.tagName);t.add(this.options.textNodeName,""),e.tagName!==e.tagExp&&e.attrExpPresent&&(t[":@"]=this.buildAttributesMap(e.tagExp,this.matcher,e.tagName)),this.addChild(i,t,this.matcher,r);}r=e.closeIndex+1;}else if("!--"===t.substr(r+1,3)){const e=tt(t,"--\x3e",r+4,"Comment is not closed.");if(this.options.commentPropName){const s=t.substring(r+4,e-2);n=this.saveTextToParentTag(n,i,this.matcher),i.add(this.options.commentPropName,[{[this.options.textNodeName]:s}]);}r=e;}else if("!D"===t.substr(r+1,2)){const e=s.readDocType(t,r);this.docTypeEntities=e.entities,r=e.i;}else if("!["===t.substr(r+1,2)){const e=tt(t,"]]>",r,"CDATA is not closed.")-2,s=t.substring(r+9,e);n=this.saveTextToParentTag(n,i,this.matcher);let o=this.parseTextData(s,i.tagname,this.matcher,true,false,true,true);null==o&&(o=""),this.options.cdataPropName?i.add(this.options.cdataPropName,[{[this.options.textNodeName]:s}]):i.add(this.options.textNodeName,o),r=e+2;}else {let s=et(t,r,this.options.removeNSPrefix);if(!s){const e=t.substring(Math.max(0,r-50),Math.min(t.length,r+50));throw new Error(`readTagExp returned undefined at position ${r}. Context: "${e}"`)}let o=s.tagName;const a=s.rawTagName;let h=s.tagExp,l=s.attrExpPresent,p=s.closeIndex;if(({tagName:o,tagExp:h}=rt(this.options.transformTagName,o,h,this.options)),this.options.strictReservedNames&&(o===this.options.commentPropName||o===this.options.cdataPropName||o===this.options.textNodeName||o===this.options.attributesGroupName))throw new Error(`Invalid tag name: ${o}`);i&&n&&"!xml"!==i.tagname&&(n=this.saveTextToParentTag(n,i,this.matcher,false));const u=i;u&&-1!==this.options.unpairedTags.indexOf(u.tagname)&&(i=this.tagsNodeStack.pop(),this.matcher.pop());let c=false;h.length>0&&h.lastIndexOf("/")===h.length-1&&(c=true,"/"===o[o.length-1]?(o=o.substr(0,o.length-1),h=o):h=h.substr(0,h.length-1),l=o!==h);let d,f=null;d=U(a),o!==e.tagname&&this.matcher.push(o,{},d),o!==h&&l&&(f=this.buildAttributesMap(h,this.matcher,o),f&&(R(f,this.options))),o!==e.tagname&&(this.isCurrentNodeStopNode=this.isItStopNode(this.stopNodeExpressions,this.matcher));const m=r;if(this.isCurrentNodeStopNode){let e="";if(c)r=s.closeIndex;else if(-1!==this.options.unpairedTags.indexOf(o))r=s.closeIndex;else {const i=this.readStopNodeData(t,a,p+1);if(!i)throw new Error(`Unexpected end of ${a}`);r=i.i,e=i.tagContent;}const n=new $(o);f&&(n[":@"]=f),n.add(this.options.textNodeName,e),this.matcher.pop(),this.isCurrentNodeStopNode=false,this.addChild(i,n,this.matcher,m);}else {if(c){({tagName:o,tagExp:h}=rt(this.options.transformTagName,o,h,this.options));const t=new $(o);f&&(t[":@"]=f),this.addChild(i,t,this.matcher,m),this.matcher.pop(),this.isCurrentNodeStopNode=false;}else {if(-1!==this.options.unpairedTags.indexOf(o)){const t=new $(o);f&&(t[":@"]=f),this.addChild(i,t,this.matcher,m),this.matcher.pop(),this.isCurrentNodeStopNode=false,r=s.closeIndex;continue}{const t=new $(o);if(this.tagsNodeStack.length>this.options.maxNestedTags)throw new Error("Maximum nested tags exceeded");this.tagsNodeStack.push(i),f&&(t[":@"]=f),this.addChild(i,t,this.matcher,m),i=t;}}n="",r=p;}}else n+=t[r];return e.child};function J(t,e,i,n){this.options.captureMetaData||(n=void 0);const s=this.options.jPath?i.toString():i,r=this.options.updateTag(e.tagname,s,e[":@"]);false===r||("string"==typeof r?(e.tagname=r,t.addChild(e,n)):t.addChild(e,n));}function K(t,e,i){const n=this.options.processEntities;if(!n||!n.enabled)return t;if(n.allowedTags){const s=this.options.jPath?i.toString():i;if(!(Array.isArray(n.allowedTags)?n.allowedTags.includes(e):n.allowedTags(e,s)))return t}if(n.tagFilter){const s=this.options.jPath?i.toString():i;if(!n.tagFilter(e,s))return t}for(const e of Object.keys(this.docTypeEntities)){const i=this.docTypeEntities[e],s=t.match(i.regx);if(s){if(this.entityExpansionCount+=s.length,n.maxTotalExpansions&&this.entityExpansionCount>n.maxTotalExpansions)throw new Error(`Entity expansion limit exceeded: ${this.entityExpansionCount} > ${n.maxTotalExpansions}`);const e=t.length;if(t=t.replace(i.regx,i.val),n.maxExpandedLength&&(this.currentExpandedLength+=t.length-e,this.currentExpandedLength>n.maxExpandedLength))throw new Error(`Total expanded content size exceeded: ${this.currentExpandedLength} > ${n.maxExpandedLength}`)}}for(const e of Object.keys(this.lastEntities)){const i=this.lastEntities[e],s=t.match(i.regex);if(s&&(this.entityExpansionCount+=s.length,n.maxTotalExpansions&&this.entityExpansionCount>n.maxTotalExpansions))throw new Error(`Entity expansion limit exceeded: ${this.entityExpansionCount} > ${n.maxTotalExpansions}`);t=t.replace(i.regex,i.val);}if(-1===t.indexOf("&"))return t;if(this.options.htmlEntities)for(const e of Object.keys(this.htmlEntities)){const i=this.htmlEntities[e],s=t.match(i.regex);if(s&&(this.entityExpansionCount+=s.length,n.maxTotalExpansions&&this.entityExpansionCount>n.maxTotalExpansions))throw new Error(`Entity expansion limit exceeded: ${this.entityExpansionCount} > ${n.maxTotalExpansions}`);t=t.replace(i.regex,i.val);}return t.replace(this.ampEntity.regex,this.ampEntity.val)}function Q(t,e,i,n){return t&&(void 0===n&&(n=0===e.child.length),void 0!==(t=this.parseTextData(t,e.tagname,i,false,!!e[":@"]&&0!==Object.keys(e[":@"]).length,n))&&""!==t&&e.add(this.options.textNodeName,t),t=""),t}function H(t,e){if(!t||0===t.length)return false;for(let i=0;i"){let n,s="";for(let r=e;r",i,`${e} is not closed`);if(t.substring(i+2,r).trim()===e&&(s--,0===s))return {tagContent:t.substring(n,i),i:r};i=r;}else if("?"===t[i+1])i=tt(t,"?>",i+1,"StopNode is not closed.");else if("!--"===t.substr(i+1,3))i=tt(t,"--\x3e",i+3,"StopNode is not closed.");else if("!["===t.substr(i+1,2))i=tt(t,"]]>",i,"StopNode is not closed.")-2;else {const n=et(t,i,">");n&&((n&&n.tagName)===e&&"/"!==n.tagExp[n.tagExp.length-1]&&s++,i=n.closeIndex);}}function nt(t,e,i){if(e&&"string"==typeof t){const e=t.trim();return "true"===e||"false"!==e&&function(t,e={}){if(e=Object.assign({},M,e),!t||"string"!=typeof t)return t;let i=t.trim();if(void 0!==e.skipLike&&e.skipLike.test(i))return t;if("0"===t)return 0;if(e.hex&&V.test(i))return function(t){if(parseInt)return parseInt(t,16);if(Number.parseInt)return Number.parseInt(t,16);if(window&&window.parseInt)return window.parseInt(t,16);throw new Error("parseInt, Number.parseInt, window.parseInt are not supported")}(i);if(isFinite(i)){if(i.includes("e")||i.includes("E"))return function(t,e,i){if(!i.eNotation)return t;const n=e.match(F);if(n){let s=n[1]||"";const r=-1===n[3].indexOf("e")?"E":"e",o=n[2],a=s?t[o.length+1]===r:t[o.length]===r;return o.length>1&&a?t:(1!==o.length||!n[3].startsWith(`.${r}`)&&n[3][0]!==r)&&o.length>0?i.leadingZeros&&!a?(e=(n[1]||"")+n[3],Number(e)):t:Number(e)}return t}(t,i,e);{const s=k.exec(i);if(s){const r=s[1]||"",o=s[2];let a=(n=s[3])&&-1!==n.indexOf(".")?("."===(n=n.replace(/0+$/,""))?n="0":"."===n[0]?n="0"+n:"."===n[n.length-1]&&(n=n.substring(0,n.length-1)),n):n;const h=r?"."===t[o.length+1]:"."===t[o.length];if(!e.leadingZeros&&(o.length>1||1===o.length&&!h))return t;{const n=Number(i),s=String(n);if(0===n)return n;if(-1!==s.search(/[eE]/))return e.eNotation?n:t;if(-1!==i.indexOf("."))return "0"===s||s===a||s===`${r}${a}`?n:t;let h=o?a:i;return o?h===s||r+h===s?n:t:h===s||h===r+s?n:t}}return t}}var n;return function(t,e,i){const n=e===1/0;switch(i.infinity.toLowerCase()){case "null":return null;case "infinity":return e;case "string":return n?"Infinity":"-Infinity";default:return t}}(t,Number(i),e)}(t,i)}return void 0!==t?t:""}function st(t,e,i){const n=Number.parseInt(t,e);return n>=0&&n<=1114111?String.fromCodePoint(n):i+t+";"}function rt(t,e,i,n){if(t){const n=t(e);i===e&&(i=n),e=n;}return {tagName:e=ot(e,n),tagExp:i}}function ot(t,e){if(a.includes(t))throw new Error(`[SECURITY] Invalid name: "${t}" is a reserved JavaScript keyword that could cause prototype pollution`);return o.includes(t)?e.onDangerousProperty(t):t}const at=$.getMetaDataSymbol();function ht(t,e){if(!t||"object"!=typeof t)return {};if(!e)return t;const i={};for(const n in t)n.startsWith(e)?i[n.substring(e.length)]=t[n]:i[n]=t[n];return i}function lt(t,e,i){return pt(t,e,i)}function pt(t,e,i){let n;const s={};for(let r=0;r0&&(s[e.textNodeName]=n):void 0!==n&&(s[e.textNodeName]=n),s}function ut(t){const e=Object.keys(t);for(let t=0;t0&&(i="\n");const n=[];if(e.stopNodes&&Array.isArray(e.stopNodes))for(let t=0;te.maxNestedTags)throw new Error("Maximum nested tags exceeded");if(!Array.isArray(t)){if(null!=t){let i=t.toString();return i=vt(i,e),i}return ""}for(let a=0;a`,o=false,n.pop();continue}if(l===e.commentPropName){r+=i+`\x3c!--${h[l][0][e.textNodeName]}--\x3e`,o=true,n.pop();continue}if("?"===l[0]){const t=yt(h[":@"],e,u),s="?xml"===l?"":i;let a=h[l][0][e.textNodeName];a=0!==a.length?" "+a:"",r+=s+`<${l}${a}${t}?>`,o=true,n.pop();continue}let c=i;""!==c&&(c+=e.indentBy);const d=i+`<${l}${yt(h[":@"],e,u)}`;let f;f=u?Nt(h[l],e):mt(h[l],e,c,n,s),-1!==e.unpairedTags.indexOf(l)?e.suppressUnpairedNode?r+=d+">":r+=d+"/>":f&&0!==f.length||!e.suppressEmptyNode?f&&f.endsWith(">")?r+=d+`>${f}${i}`:(r+=d+">",f&&""!==i&&(f.includes("/>")||f.includes("`):r+=d+"/>",o=true,n.pop();}return r}function xt(t,e){if(!t||e.ignoreAttributes)return null;const i={};let n=false;for(let s in t)Object.prototype.hasOwnProperty.call(t,s)&&(i[s.startsWith(e.attributeNamePrefix)?s.substr(e.attributeNamePrefix.length):s]=t[s],n=true);return n?i:null}function Nt(t,e){if(!Array.isArray(t))return null!=t?t.toString():"";let i="";for(let n=0;n${n}`:i+=`<${r}${t}/>`;}}}return i}function bt(t,e){let i="";if(t&&!e.ignoreAttributes)for(let n in t){if(!Object.prototype.hasOwnProperty.call(t,n))continue;let s=t[n];true===s&&e.suppressBooleanAttributes?i+=` ${n.substr(e.attributeNamePrefix.length)}`:i+=` ${n.substr(e.attributeNamePrefix.length)}="${s}"`;}return i}function Et(t){const e=Object.keys(t);for(let i=0;i0&&e.processEntities)for(let i=0;i","g"),val:">"},{regex:new RegExp("<","g"),val:"<"},{regex:new RegExp("'","g"),val:"'"},{regex:new RegExp('"',"g"),val:"""}],processEntities:true,stopNodes:[],oneListGroup:false,maxNestedTags:100,jPath:true};function Pt(t){if(this.options=Object.assign({},Tt,t),this.options.stopNodes&&Array.isArray(this.options.stopNodes)&&(this.options.stopNodes=this.options.stopNodes.map(t=>"string"==typeof t&&t.startsWith("*.")?".."+t.substring(2):t)),this.stopNodeExpressions=[],this.options.stopNodes&&Array.isArray(this.options.stopNodes))for(let t=0;t{for(const i of e){if("string"==typeof i&&t===i)return true;if(i instanceof RegExp&&i.test(t))return true}}:()=>false,this.attrPrefixLen=this.options.attributeNamePrefix.length,this.isAttribute=Ct),this.processTextOrObjNode=St,this.options.format?(this.indentate=At,this.tagEndChar=">\n",this.newLine="\n"):(this.indentate=function(){return ""},this.tagEndChar=">",this.newLine="");}function St(t,e,i,n){const s=this.extractAttributes(t);if(n.push(e,s),this.checkStopNode(n)){const s=this.buildRawContent(t),r=this.buildAttributesForStopNode(t);return n.pop(),this.buildObjectNode(s,e,r,i)}const r=this.j2x(t,i+1,n);return n.pop(),void 0!==t[this.options.textNodeName]&&1===Object.keys(t).length?this.buildTextValNode(t[this.options.textNodeName],e,r.attrStr,i,n):this.buildObjectNode(r.val,e,r.attrStr,i)}function At(t){return this.options.indentBy.repeat(t)}function Ct(t){return !(!t.startsWith(this.options.attributeNamePrefix)||t===this.options.textNodeName)&&t.substr(this.attrPrefixLen)}Pt.prototype.build=function(t){if(this.options.preserveOrder)return gt(t,this.options);{Array.isArray(t)&&this.options.arrayNodeName&&this.options.arrayNodeName.length>1&&(t={[this.options.arrayNodeName]:t});const e=new L;return this.j2x(t,0,e).val}},Pt.prototype.j2x=function(t,e,i){let n="",s="";if(this.options.maxNestedTags&&i.getDepth()>=this.options.maxNestedTags)throw new Error("Maximum nested tags exceeded");const r=this.options.jPath?i.toString():i,o=this.checkStopNode(i);for(let a in t)if(Object.prototype.hasOwnProperty.call(t,a))if(void 0===t[a])this.isAttribute(a)&&(s+="");else if(null===t[a])this.isAttribute(a)||a===this.options.cdataPropName?s+="":"?"===a[0]?s+=this.indentate(e)+"<"+a+"?"+this.tagEndChar:s+=this.indentate(e)+"<"+a+"/"+this.tagEndChar;else if(t[a]instanceof Date)s+=this.buildTextValNode(t[a],a,"",e,i);else if("object"!=typeof t[a]){const h=this.isAttribute(a);if(h&&!this.ignoreAttributesFn(h,r))n+=this.buildAttrPairStr(h,""+t[a],o);else if(!h)if(a===this.options.textNodeName){let e=this.options.tagValueProcessor(a,""+t[a]);s+=this.replaceEntitiesValue(e);}else {i.push(a);const n=this.checkStopNode(i);if(i.pop(),n){const i=""+t[a];s+=""===i?this.indentate(e)+"<"+a+this.closeTag(a)+this.tagEndChar:this.indentate(e)+"<"+a+">"+i+""+t+"${t}`;else if("object"==typeof t&&null!==t){const n=this.buildRawContent(t),s=this.buildAttributesForStopNode(t);e+=""===n?`<${i}${s}/>`:`<${i}${s}>${n}`;}}else if("object"==typeof n&&null!==n){const t=this.buildRawContent(n),s=this.buildAttributesForStopNode(n);e+=""===t?`<${i}${s}/>`:`<${i}${s}>${t}`;}else e+=`<${i}>${n}`;}return e},Pt.prototype.buildAttributesForStopNode=function(t){if(!t||"object"!=typeof t)return "";let e="";if(this.options.attributesGroupName&&t[this.options.attributesGroupName]){const i=t[this.options.attributesGroupName];for(let t in i){if(!Object.prototype.hasOwnProperty.call(i,t))continue;const n=t.startsWith(this.options.attributeNamePrefix)?t.substring(this.options.attributeNamePrefix.length):t,s=i[t];true===s&&this.options.suppressBooleanAttributes?e+=" "+n:e+=" "+n+'="'+s+'"';}}else for(let i in t){if(!Object.prototype.hasOwnProperty.call(t,i))continue;const n=this.isAttribute(i);if(n){const s=t[i];true===s&&this.options.suppressBooleanAttributes?e+=" "+n:e+=" "+n+'="'+s+'"';}}return e},Pt.prototype.buildObjectNode=function(t,e,i,n){if(""===t)return "?"===e[0]?this.indentate(n)+"<"+e+i+"?"+this.tagEndChar:this.indentate(n)+"<"+e+i+this.closeTag(e)+this.tagEndChar;{let s=""+t+s}},Pt.prototype.closeTag=function(t){let e="";return -1!==this.options.unpairedTags.indexOf(t)?this.options.suppressUnpairedNode||(e="/"):e=this.options.suppressEmptyNode?"/":`>`+this.newLine;if(false!==this.options.commentPropName&&e===this.options.commentPropName)return this.indentate(n)+`\x3c!--${t}--\x3e`+this.newLine;if("?"===e[0])return this.indentate(n)+"<"+e+i+"?"+this.tagEndChar;{let s=this.options.tagValueProcessor(e,t);return s=this.replaceEntitiesValue(s),""===s?this.indentate(n)+"<"+e+i+this.closeTag(e)+this.tagEndChar:this.indentate(n)+"<"+e+i+">"+s+"0&&this.options.processEntities)for(let e=0;e Date: Fri, 27 Mar 2026 14:06:59 +0530 Subject: [PATCH 3/3] fix: add permissions block to build_website job Restrict GITHUB_TOKEN to contents:read only, as flagged by github-advanced-security. --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a043afa..67d381f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,6 +53,8 @@ jobs: build_website: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout