@aws-sdk/xml-builder
Advanced tools
@@ -34,3 +34,3 @@ let parser; | ||
| } | ||
| if (obj[childName]) { | ||
| if (childName in obj) { | ||
| if (Array.isArray(obj[childName])) { | ||
@@ -37,0 +37,0 @@ obj[childName].push(childResult); |
+233
-43
@@ -1,44 +0,234 @@ | ||
| const { XMLParser } = require("fast-xml-parser"); | ||
| const { COMMON_HTML, CURRENCY, EntityDecoderImpl, XML } = require("./xml-external/nodable_entities"); | ||
| const entityDecoder = new EntityDecoderImpl({ | ||
| namedEntities: { ...XML, ...COMMON_HTML, ...CURRENCY }, | ||
| numericAllowed: true, | ||
| limit: { | ||
| maxTotalExpansions: Infinity, | ||
| }, | ||
| ncr: { | ||
| xmlVersion: 1.1, | ||
| }, | ||
| }); | ||
| const parser = new XMLParser({ | ||
| attributeNamePrefix: "", | ||
| processEntities: { | ||
| enabled: true, | ||
| maxTotalExpansions: Infinity, | ||
| }, | ||
| htmlEntities: true, | ||
| entityDecoder: { | ||
| setExternalEntities: (entities) => { | ||
| entityDecoder.setExternalEntities(entities); | ||
| }, | ||
| addInputEntities: (entities) => { | ||
| entityDecoder.addInputEntities(entities); | ||
| }, | ||
| reset: () => { | ||
| entityDecoder.reset(); | ||
| }, | ||
| decode: (text) => { | ||
| return entityDecoder.decode(text); | ||
| }, | ||
| setXmlVersion: (version) => void {}, | ||
| }, | ||
| ignoreAttributes: false, | ||
| ignoreDeclaration: true, | ||
| parseTagValue: false, | ||
| trimValues: false, | ||
| tagValueProcessor: (_, val) => (val.trim() === "" && val.includes("\n") ? "" : undefined), | ||
| maxNestedTags: Infinity, | ||
| }); | ||
| exports.parseXML = function parseXML(xmlString) { | ||
| return parser.parse(xmlString, true); | ||
| exports.parseXML = function parseXML(xml) { | ||
| const state = new AwsXmlParser(xml); | ||
| return state.parse(); | ||
| }; | ||
| class AwsXmlParser { | ||
| x; | ||
| i = 0; | ||
| z; | ||
| constructor(x) { | ||
| this.x = x; | ||
| this.x = x.replace(/\r\n?/g, "\n"); | ||
| this.z = this.x.length; | ||
| } | ||
| parse() { | ||
| const p = this; | ||
| const { z } = p; | ||
| while (p.i < z) { | ||
| p.trim(); | ||
| if (p.i >= z) { | ||
| break; | ||
| } | ||
| if (p.isNext("<?")) { | ||
| p.readTo("?>"); | ||
| p.trim(); | ||
| } | ||
| else if (p.isNext("<!--")) { | ||
| p.readTo("-->"); | ||
| p.trim(); | ||
| } | ||
| else if (p.isNext("<!DOCTYPE", false)) { | ||
| p.skipDoctype(); | ||
| p.trim(); | ||
| } | ||
| else if (p.x[p.i] === "<") { | ||
| const root = p.parseTag(); | ||
| return { [root.tag]: root.value }; | ||
| } | ||
| else { | ||
| throw new Error("@aws-sdk XML parse error: unexpected content."); | ||
| } | ||
| } | ||
| throw new Error("@aws-sdk XML parse error: no root element."); | ||
| } | ||
| isNext(s, caseSensitive = true) { | ||
| const p = this; | ||
| if (caseSensitive) { | ||
| return p.x.startsWith(s, p.i); | ||
| } | ||
| return p.x.toLowerCase().startsWith(s.toLowerCase(), p.i); | ||
| } | ||
| readTo(stop) { | ||
| const p = this; | ||
| const _i = p.x.indexOf(stop, p.i); | ||
| if (_i === -1) { | ||
| throw new Error(`@aws-sdk XML parse error: expected "${stop}" not found.`); | ||
| } | ||
| const result = p.x.slice(p.i, _i); | ||
| p.i = _i + stop.length; | ||
| return result; | ||
| } | ||
| trim() { | ||
| const p = this; | ||
| while (p.i < p.z && " \t\r\n".includes(p.x[p.i])) { | ||
| ++p.i; | ||
| } | ||
| } | ||
| readAttrValue() { | ||
| const p = this; | ||
| const quote = p.x[p.i]; | ||
| ++p.i; | ||
| let value = ""; | ||
| while (p.i < p.z && p.x[p.i] !== quote) { | ||
| value += p.x[p.i++]; | ||
| } | ||
| ++p.i; | ||
| return p.decodeEntities(value); | ||
| } | ||
| parseTag() { | ||
| const p = this; | ||
| ++p.i; | ||
| let tag = ""; | ||
| while (p.i < p.z && !" \t\r\n>/".includes(p.x[p.i])) { | ||
| tag += p.x[p.i++]; | ||
| } | ||
| let hasAttrs = false; | ||
| const attrs = Object.create(null); | ||
| while (p.i < p.z) { | ||
| p.trim(); | ||
| if (">/".includes(p.x[p.i])) { | ||
| break; | ||
| } | ||
| let name = ""; | ||
| while (p.i < p.z && !"= \t\r\n>/?".includes(p.x[p.i])) { | ||
| name += p.x[p.i++]; | ||
| } | ||
| p.trim(); | ||
| if (p.x[p.i] !== "=") { | ||
| break; | ||
| } | ||
| ++p.i; | ||
| p.trim(); | ||
| attrs[name] = p.readAttrValue(); | ||
| hasAttrs = true; | ||
| } | ||
| if (p.i >= p.z) { | ||
| throw new Error("@aws-sdk XML parse error: unexpected end of input."); | ||
| } | ||
| if (p.x[p.i] === "/") { | ||
| ++p.i; | ||
| if (p.i >= p.z || p.x[p.i] !== ">") { | ||
| throw new Error("@aws-sdk XML parse error: expected > at the end of self-closing tag."); | ||
| } | ||
| ++p.i; | ||
| Object.setPrototypeOf(attrs, Object.prototype); | ||
| return { tag, value: hasAttrs ? attrs : "" }; | ||
| } | ||
| if (p.x[p.i] !== ">") { | ||
| throw new Error("@aws-sdk XML parse error: expected > at the end of opening tag."); | ||
| } | ||
| ++p.i; | ||
| const textParts = []; | ||
| const childTags = []; | ||
| let hasElementChild = false; | ||
| while (p.i < p.z) { | ||
| if (p.isNext("</")) { | ||
| break; | ||
| } | ||
| if (p.x[p.i] === "<") { | ||
| if (p.isNext("<!--")) { | ||
| p.readTo("-->"); | ||
| } | ||
| else if (p.isNext("<![CDATA[")) { | ||
| p.i += 9; | ||
| textParts.push(p.readTo("]]>")); | ||
| } | ||
| else if (p.isNext("<?")) { | ||
| p.readTo("?>"); | ||
| } | ||
| else { | ||
| hasElementChild = true; | ||
| childTags.push(p.parseTag()); | ||
| } | ||
| } | ||
| else { | ||
| let text = ""; | ||
| while (p.i < p.z && p.x[p.i] !== "<") { | ||
| text += p.x[p.i++]; | ||
| } | ||
| textParts.push(p.decodeEntities(text)); | ||
| } | ||
| } | ||
| if (!p.isNext("</")) { | ||
| throw new Error(`@aws-sdk XML parse error: missing closing tag </${tag}>.`); | ||
| } | ||
| p.i += 2; | ||
| const closeTag = p.readTo(">").trim(); | ||
| if (closeTag !== tag) { | ||
| throw new Error(`@aws-sdk XML parse error: mismatched tags <${tag}> and </${closeTag}>.`); | ||
| } | ||
| if (!hasAttrs && textParts.length === 0 && !hasElementChild) { | ||
| return { tag, value: "" }; | ||
| } | ||
| if (!hasAttrs && !hasElementChild) { | ||
| const text = textParts.length === 1 ? textParts[0] : textParts.join(""); | ||
| if (text.trim() === "" && text.includes("\n")) { | ||
| return { tag, value: "" }; | ||
| } | ||
| return { tag, value: text }; | ||
| } | ||
| const obj = Object.create(null); | ||
| for (const text of textParts) { | ||
| if (text.trim() === "" && text.includes("\n")) { | ||
| continue; | ||
| } | ||
| obj["#text"] = "#text" in obj ? obj["#text"] + text : text; | ||
| } | ||
| for (const child of childTags) { | ||
| if (child.tag in obj) { | ||
| if (Array.isArray(obj[child.tag])) { | ||
| obj[child.tag].push(child.value); | ||
| } | ||
| else { | ||
| obj[child.tag] = [obj[child.tag], child.value]; | ||
| } | ||
| } | ||
| else { | ||
| obj[child.tag] = child.value; | ||
| } | ||
| } | ||
| for (const [k, v] of Object.entries(attrs)) { | ||
| obj[k] = v; | ||
| } | ||
| Object.setPrototypeOf(obj, Object.prototype); | ||
| return { tag, value: obj }; | ||
| } | ||
| static ENTITIES = { | ||
| amp: "&", | ||
| lt: "<", | ||
| gt: ">", | ||
| quot: '"', | ||
| apos: "'", | ||
| }; | ||
| skipDoctype() { | ||
| const p = this; | ||
| p.i += 9; | ||
| let depth = 0; | ||
| while (p.i < p.z) { | ||
| const c = p.x[p.i]; | ||
| if (c === "[") { | ||
| ++depth; | ||
| } | ||
| else if (c === "]") { | ||
| --depth; | ||
| } | ||
| else if (c === ">" && depth === 0) { | ||
| ++p.i; | ||
| return; | ||
| } | ||
| ++p.i; | ||
| } | ||
| throw new Error("@aws-sdk XML parse error: unclosed DOCTYPE."); | ||
| } | ||
| decodeEntities(s) { | ||
| return s.replace(/&(?:#x([0-9a-fA-F]{1,6})|#(\d{1,7})|([a-zA-Z][a-zA-Z0-9]{0,30}));/g, (_, hex, dec, named) => { | ||
| if (hex) { | ||
| return String.fromCharCode(parseInt(hex, 16)); | ||
| } | ||
| if (dec) { | ||
| return String.fromCharCode(parseInt(dec, 10)); | ||
| } | ||
| return AwsXmlParser.ENTITIES[named] ?? ""; | ||
| }); | ||
| } | ||
| } |
@@ -34,3 +34,3 @@ let parser; | ||
| } | ||
| if (obj[childName]) { | ||
| if (childName in obj) { | ||
| if (Array.isArray(obj[childName])) { | ||
@@ -37,0 +37,0 @@ obj[childName].push(childResult); |
+233
-43
@@ -1,44 +0,234 @@ | ||
| import { XMLParser } from "fast-xml-parser"; | ||
| import { COMMON_HTML, CURRENCY, EntityDecoderImpl, XML } from "./xml-external/nodable_entities"; | ||
| const entityDecoder = new EntityDecoderImpl({ | ||
| namedEntities: { ...XML, ...COMMON_HTML, ...CURRENCY }, | ||
| numericAllowed: true, | ||
| limit: { | ||
| maxTotalExpansions: Infinity, | ||
| }, | ||
| ncr: { | ||
| xmlVersion: 1.1, | ||
| }, | ||
| }); | ||
| const parser = new XMLParser({ | ||
| attributeNamePrefix: "", | ||
| processEntities: { | ||
| enabled: true, | ||
| maxTotalExpansions: Infinity, | ||
| }, | ||
| htmlEntities: true, | ||
| entityDecoder: { | ||
| setExternalEntities: (entities) => { | ||
| entityDecoder.setExternalEntities(entities); | ||
| }, | ||
| addInputEntities: (entities) => { | ||
| entityDecoder.addInputEntities(entities); | ||
| }, | ||
| reset: () => { | ||
| entityDecoder.reset(); | ||
| }, | ||
| decode: (text) => { | ||
| return entityDecoder.decode(text); | ||
| }, | ||
| setXmlVersion: (version) => void {}, | ||
| }, | ||
| ignoreAttributes: false, | ||
| ignoreDeclaration: true, | ||
| parseTagValue: false, | ||
| trimValues: false, | ||
| tagValueProcessor: (_, val) => (val.trim() === "" && val.includes("\n") ? "" : undefined), | ||
| maxNestedTags: Infinity, | ||
| }); | ||
| export function parseXML(xmlString) { | ||
| return parser.parse(xmlString, true); | ||
| export function parseXML(xml) { | ||
| const state = new AwsXmlParser(xml); | ||
| return state.parse(); | ||
| } | ||
| class AwsXmlParser { | ||
| x; | ||
| i = 0; | ||
| z; | ||
| constructor(x) { | ||
| this.x = x; | ||
| this.x = x.replace(/\r\n?/g, "\n"); | ||
| this.z = this.x.length; | ||
| } | ||
| parse() { | ||
| const p = this; | ||
| const { z } = p; | ||
| while (p.i < z) { | ||
| p.trim(); | ||
| if (p.i >= z) { | ||
| break; | ||
| } | ||
| if (p.isNext("<?")) { | ||
| p.readTo("?>"); | ||
| p.trim(); | ||
| } | ||
| else if (p.isNext("<!--")) { | ||
| p.readTo("-->"); | ||
| p.trim(); | ||
| } | ||
| else if (p.isNext("<!DOCTYPE", false)) { | ||
| p.skipDoctype(); | ||
| p.trim(); | ||
| } | ||
| else if (p.x[p.i] === "<") { | ||
| const root = p.parseTag(); | ||
| return { [root.tag]: root.value }; | ||
| } | ||
| else { | ||
| throw new Error("@aws-sdk XML parse error: unexpected content."); | ||
| } | ||
| } | ||
| throw new Error("@aws-sdk XML parse error: no root element."); | ||
| } | ||
| isNext(s, caseSensitive = true) { | ||
| const p = this; | ||
| if (caseSensitive) { | ||
| return p.x.startsWith(s, p.i); | ||
| } | ||
| return p.x.toLowerCase().startsWith(s.toLowerCase(), p.i); | ||
| } | ||
| readTo(stop) { | ||
| const p = this; | ||
| const _i = p.x.indexOf(stop, p.i); | ||
| if (_i === -1) { | ||
| throw new Error(`@aws-sdk XML parse error: expected "${stop}" not found.`); | ||
| } | ||
| const result = p.x.slice(p.i, _i); | ||
| p.i = _i + stop.length; | ||
| return result; | ||
| } | ||
| trim() { | ||
| const p = this; | ||
| while (p.i < p.z && " \t\r\n".includes(p.x[p.i])) { | ||
| ++p.i; | ||
| } | ||
| } | ||
| readAttrValue() { | ||
| const p = this; | ||
| const quote = p.x[p.i]; | ||
| ++p.i; | ||
| let value = ""; | ||
| while (p.i < p.z && p.x[p.i] !== quote) { | ||
| value += p.x[p.i++]; | ||
| } | ||
| ++p.i; | ||
| return p.decodeEntities(value); | ||
| } | ||
| parseTag() { | ||
| const p = this; | ||
| ++p.i; | ||
| let tag = ""; | ||
| while (p.i < p.z && !" \t\r\n>/".includes(p.x[p.i])) { | ||
| tag += p.x[p.i++]; | ||
| } | ||
| let hasAttrs = false; | ||
| const attrs = Object.create(null); | ||
| while (p.i < p.z) { | ||
| p.trim(); | ||
| if (">/".includes(p.x[p.i])) { | ||
| break; | ||
| } | ||
| let name = ""; | ||
| while (p.i < p.z && !"= \t\r\n>/?".includes(p.x[p.i])) { | ||
| name += p.x[p.i++]; | ||
| } | ||
| p.trim(); | ||
| if (p.x[p.i] !== "=") { | ||
| break; | ||
| } | ||
| ++p.i; | ||
| p.trim(); | ||
| attrs[name] = p.readAttrValue(); | ||
| hasAttrs = true; | ||
| } | ||
| if (p.i >= p.z) { | ||
| throw new Error("@aws-sdk XML parse error: unexpected end of input."); | ||
| } | ||
| if (p.x[p.i] === "/") { | ||
| ++p.i; | ||
| if (p.i >= p.z || p.x[p.i] !== ">") { | ||
| throw new Error("@aws-sdk XML parse error: expected > at the end of self-closing tag."); | ||
| } | ||
| ++p.i; | ||
| Object.setPrototypeOf(attrs, Object.prototype); | ||
| return { tag, value: hasAttrs ? attrs : "" }; | ||
| } | ||
| if (p.x[p.i] !== ">") { | ||
| throw new Error("@aws-sdk XML parse error: expected > at the end of opening tag."); | ||
| } | ||
| ++p.i; | ||
| const textParts = []; | ||
| const childTags = []; | ||
| let hasElementChild = false; | ||
| while (p.i < p.z) { | ||
| if (p.isNext("</")) { | ||
| break; | ||
| } | ||
| if (p.x[p.i] === "<") { | ||
| if (p.isNext("<!--")) { | ||
| p.readTo("-->"); | ||
| } | ||
| else if (p.isNext("<![CDATA[")) { | ||
| p.i += 9; | ||
| textParts.push(p.readTo("]]>")); | ||
| } | ||
| else if (p.isNext("<?")) { | ||
| p.readTo("?>"); | ||
| } | ||
| else { | ||
| hasElementChild = true; | ||
| childTags.push(p.parseTag()); | ||
| } | ||
| } | ||
| else { | ||
| let text = ""; | ||
| while (p.i < p.z && p.x[p.i] !== "<") { | ||
| text += p.x[p.i++]; | ||
| } | ||
| textParts.push(p.decodeEntities(text)); | ||
| } | ||
| } | ||
| if (!p.isNext("</")) { | ||
| throw new Error(`@aws-sdk XML parse error: missing closing tag </${tag}>.`); | ||
| } | ||
| p.i += 2; | ||
| const closeTag = p.readTo(">").trim(); | ||
| if (closeTag !== tag) { | ||
| throw new Error(`@aws-sdk XML parse error: mismatched tags <${tag}> and </${closeTag}>.`); | ||
| } | ||
| if (!hasAttrs && textParts.length === 0 && !hasElementChild) { | ||
| return { tag, value: "" }; | ||
| } | ||
| if (!hasAttrs && !hasElementChild) { | ||
| const text = textParts.length === 1 ? textParts[0] : textParts.join(""); | ||
| if (text.trim() === "" && text.includes("\n")) { | ||
| return { tag, value: "" }; | ||
| } | ||
| return { tag, value: text }; | ||
| } | ||
| const obj = Object.create(null); | ||
| for (const text of textParts) { | ||
| if (text.trim() === "" && text.includes("\n")) { | ||
| continue; | ||
| } | ||
| obj["#text"] = "#text" in obj ? obj["#text"] + text : text; | ||
| } | ||
| for (const child of childTags) { | ||
| if (child.tag in obj) { | ||
| if (Array.isArray(obj[child.tag])) { | ||
| obj[child.tag].push(child.value); | ||
| } | ||
| else { | ||
| obj[child.tag] = [obj[child.tag], child.value]; | ||
| } | ||
| } | ||
| else { | ||
| obj[child.tag] = child.value; | ||
| } | ||
| } | ||
| for (const [k, v] of Object.entries(attrs)) { | ||
| obj[k] = v; | ||
| } | ||
| Object.setPrototypeOf(obj, Object.prototype); | ||
| return { tag, value: obj }; | ||
| } | ||
| static ENTITIES = { | ||
| amp: "&", | ||
| lt: "<", | ||
| gt: ">", | ||
| quot: '"', | ||
| apos: "'", | ||
| }; | ||
| skipDoctype() { | ||
| const p = this; | ||
| p.i += 9; | ||
| let depth = 0; | ||
| while (p.i < p.z) { | ||
| const c = p.x[p.i]; | ||
| if (c === "[") { | ||
| ++depth; | ||
| } | ||
| else if (c === "]") { | ||
| --depth; | ||
| } | ||
| else if (c === ">" && depth === 0) { | ||
| ++p.i; | ||
| return; | ||
| } | ||
| ++p.i; | ||
| } | ||
| throw new Error("@aws-sdk XML parse error: unclosed DOCTYPE."); | ||
| } | ||
| decodeEntities(s) { | ||
| return s.replace(/&(?:#x([0-9a-fA-F]{1,6})|#(\d{1,7})|([a-zA-Z][a-zA-Z0-9]{0,30}));/g, (_, hex, dec, named) => { | ||
| if (hex) { | ||
| return String.fromCharCode(parseInt(hex, 16)); | ||
| } | ||
| if (dec) { | ||
| return String.fromCharCode(parseInt(dec, 10)); | ||
| } | ||
| return AwsXmlParser.ENTITIES[named] ?? ""; | ||
| }); | ||
| } | ||
| } |
@@ -1,1 +0,1 @@ | ||
| export declare function parseXML(xmlString: string): any; | ||
| export declare function parseXML(xml: string): any; |
| /** | ||
| * AWS SDK XML to object converter. | ||
| * @internal | ||
| */ | ||
| export declare function parseXML(xmlString: string): any; | ||
| export declare function parseXML(xml: string): any; |
+4
-4
| { | ||
| "name": "@aws-sdk/xml-builder", | ||
| "version": "3.972.30", | ||
| "version": "3.972.31", | ||
| "description": "XML utilities for the AWS SDK", | ||
| "dependencies": { | ||
| "@smithy/types": "^4.14.3", | ||
| "fast-xml-parser": "5.7.3", | ||
| "tslib": "^2.6.2" | ||
@@ -18,4 +17,6 @@ }, | ||
| "clean": "premove dist-cjs dist-es dist-types", | ||
| "profile": "node --inspect-brk ./scripts/benchmark", | ||
| "test": "yarn g:vitest run", | ||
| "test:watch": "yarn g:vitest watch" | ||
| "test:watch": "yarn g:vitest watch", | ||
| "test:fuzz": "jazzer tests/xml-parser.fuzz.js corpus --sync -i xml-parser -- -max_total_time=300" | ||
| }, | ||
@@ -58,3 +59,2 @@ "sideEffects": false, | ||
| "devDependencies": { | ||
| "@nodable/entities": "2.1.0", | ||
| "@tsconfig/recommended": "1.0.1", | ||
@@ -61,0 +61,0 @@ "concurrently": "7.0.0", |
| const XML = { | ||
| amp: "&", | ||
| apos: "'", | ||
| gt: ">", | ||
| lt: "<", | ||
| quot: '"', | ||
| }; | ||
| exports.XML = XML; | ||
| exports.COMMON_HTML = { | ||
| nbsp: "\u00a0", | ||
| copy: "\u00a9", | ||
| reg: "\u00ae", | ||
| trade: "\u2122", | ||
| mdash: "\u2014", | ||
| ndash: "\u2013", | ||
| hellip: "\u2026", | ||
| laquo: "\u00ab", | ||
| raquo: "\u00bb", | ||
| lsquo: "\u2018", | ||
| rsquo: "\u2019", | ||
| ldquo: "\u201c", | ||
| rdquo: "\u201d", | ||
| bull: "\u2022", | ||
| para: "\u00b6", | ||
| sect: "\u00a7", | ||
| deg: "\u00b0", | ||
| frac12: "\u00bd", | ||
| frac14: "\u00bc", | ||
| frac34: "\u00be", | ||
| }; | ||
| exports.CURRENCY = { | ||
| cent: "\u00a2", | ||
| pound: "\u00a3", | ||
| curren: "\u00a4", | ||
| yen: "\u00a5", | ||
| euro: "\u20ac", | ||
| dollar: "$", | ||
| fnof: "\u0192", | ||
| inr: "\u20b9", | ||
| af: "\u060b", | ||
| birr: "\u1265\u122d", | ||
| peso: "\u20b1", | ||
| rub: "\u20bd", | ||
| won: "\u20a9", | ||
| yuan: "\u00a5", | ||
| cedil: "\u00b8", | ||
| }; | ||
| const SPECIAL_CHARS = new Set("!?\\/[]$%{}^&*()<>|+"); | ||
| function validateEntityName(name) { | ||
| if (name[0] === "#") { | ||
| throw new Error(`[EntityReplacer] Invalid character '#' in entity name: "${name}"`); | ||
| } | ||
| for (const ch of name) { | ||
| if (SPECIAL_CHARS.has(ch)) { | ||
| throw new Error(`[EntityReplacer] Invalid character '${ch}' in entity name: "${name}"`); | ||
| } | ||
| } | ||
| return name; | ||
| } | ||
| function mergeEntityMaps(...maps) { | ||
| const out = Object.create(null); | ||
| for (const map of maps) { | ||
| if (!map) { | ||
| continue; | ||
| } | ||
| for (const key of Object.keys(map)) { | ||
| const raw = map[key]; | ||
| if (typeof raw === "string") { | ||
| out[key] = raw; | ||
| } | ||
| else if (raw && typeof raw === "object" && raw.val !== undefined) { | ||
| const val = raw.val; | ||
| if (typeof val === "string") { | ||
| out[key] = val; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| const LIMIT_TIER_EXTERNAL = "external"; | ||
| const LIMIT_TIER_BASE = "base"; | ||
| const LIMIT_TIER_ALL = "all"; | ||
| function parseLimitTiers(raw) { | ||
| if (!raw || raw === LIMIT_TIER_EXTERNAL) { | ||
| return new Set([LIMIT_TIER_EXTERNAL]); | ||
| } | ||
| if (raw === LIMIT_TIER_ALL) { | ||
| return new Set([LIMIT_TIER_ALL]); | ||
| } | ||
| if (raw === LIMIT_TIER_BASE) { | ||
| return new Set([LIMIT_TIER_BASE]); | ||
| } | ||
| if (Array.isArray(raw)) { | ||
| return new Set(raw); | ||
| } | ||
| return new Set([LIMIT_TIER_EXTERNAL]); | ||
| } | ||
| const NCR_LEVEL = Object.freeze({ allow: 0, leave: 1, remove: 2, throw: 3 }); | ||
| const XML10_ALLOWED_C0 = new Set([0x09, 0x0a, 0x0d]); | ||
| function parseNCRConfig(ncr) { | ||
| if (!ncr) { | ||
| return { xmlVersion: 1.0, onLevel: NCR_LEVEL.allow, nullLevel: NCR_LEVEL.remove }; | ||
| } | ||
| const xmlVersion = ncr.xmlVersion === 1.1 ? 1.1 : 1.0; | ||
| const onLevel = NCR_LEVEL[ncr.onNCR ?? "allow"] ?? NCR_LEVEL.allow; | ||
| const nullLevel = NCR_LEVEL[ncr.nullNCR ?? "remove"] ?? NCR_LEVEL.remove; | ||
| const clampedNull = Math.max(nullLevel, NCR_LEVEL.remove); | ||
| return { xmlVersion, onLevel, nullLevel: clampedNull }; | ||
| } | ||
| exports.EntityDecoderImpl = class EntityDecoderImpl { | ||
| _limit; | ||
| _maxTotalExpansions; | ||
| _maxExpandedLength; | ||
| _postCheck; | ||
| _limitTiers; | ||
| _numericAllowed; | ||
| _baseMap; | ||
| _externalMap; | ||
| _inputMap; | ||
| _totalExpansions; | ||
| _expandedLength; | ||
| _removeSet; | ||
| _leaveSet; | ||
| _ncrXmlVersion; | ||
| _ncrOnLevel; | ||
| _ncrNullLevel; | ||
| constructor(options = {}) { | ||
| this._limit = options.limit || {}; | ||
| this._maxTotalExpansions = this._limit.maxTotalExpansions || 0; | ||
| this._maxExpandedLength = this._limit.maxExpandedLength || 0; | ||
| this._postCheck = typeof options.postCheck === "function" ? options.postCheck : (r) => r; | ||
| this._limitTiers = parseLimitTiers(this._limit.applyLimitsTo ?? LIMIT_TIER_EXTERNAL); | ||
| this._numericAllowed = options.numericAllowed ?? true; | ||
| this._baseMap = mergeEntityMaps(XML, options.namedEntities || null); | ||
| this._externalMap = Object.create(null); | ||
| this._inputMap = Object.create(null); | ||
| this._totalExpansions = 0; | ||
| this._expandedLength = 0; | ||
| this._removeSet = new Set(options.remove && Array.isArray(options.remove) ? options.remove : []); | ||
| this._leaveSet = new Set(options.leave && Array.isArray(options.leave) ? options.leave : []); | ||
| const ncrCfg = parseNCRConfig(options.ncr); | ||
| this._ncrXmlVersion = ncrCfg.xmlVersion; | ||
| this._ncrOnLevel = ncrCfg.onLevel; | ||
| this._ncrNullLevel = ncrCfg.nullLevel; | ||
| } | ||
| setExternalEntities(map) { | ||
| if (map) { | ||
| for (const key of Object.keys(map)) { | ||
| validateEntityName(key); | ||
| } | ||
| } | ||
| this._externalMap = mergeEntityMaps(map); | ||
| } | ||
| addExternalEntity(key, value) { | ||
| validateEntityName(key); | ||
| if (typeof value === "string" && value.indexOf("&") === -1) { | ||
| this._externalMap[key] = value; | ||
| } | ||
| } | ||
| addInputEntities(map) { | ||
| this._totalExpansions = 0; | ||
| this._expandedLength = 0; | ||
| this._inputMap = mergeEntityMaps(map); | ||
| } | ||
| reset() { | ||
| this._inputMap = Object.create(null); | ||
| this._totalExpansions = 0; | ||
| this._expandedLength = 0; | ||
| return this; | ||
| } | ||
| setXmlVersion(version) { | ||
| this._ncrXmlVersion = version === "1.1" || version === 1.1 ? 1.1 : 1.0; | ||
| } | ||
| decode(str) { | ||
| if (typeof str !== "string" || str.length === 0) { | ||
| return str; | ||
| } | ||
| const original = str; | ||
| const chunks = []; | ||
| const len = str.length; | ||
| let last = 0; | ||
| let i = 0; | ||
| const limitExpansions = this._maxTotalExpansions > 0; | ||
| const limitLength = this._maxExpandedLength > 0; | ||
| const checkLimits = limitExpansions || limitLength; | ||
| while (i < len) { | ||
| if (str.charCodeAt(i) !== 38) { | ||
| i++; | ||
| continue; | ||
| } | ||
| let j = i + 1; | ||
| while (j < len && str.charCodeAt(j) !== 59 && j - i <= 32) { | ||
| j++; | ||
| } | ||
| if (j >= len || str.charCodeAt(j) !== 59) { | ||
| i++; | ||
| continue; | ||
| } | ||
| const token = str.slice(i + 1, j); | ||
| if (token.length === 0) { | ||
| i++; | ||
| continue; | ||
| } | ||
| let replacement; | ||
| let tier; | ||
| if (this._removeSet.has(token)) { | ||
| replacement = ""; | ||
| if (tier === undefined) { | ||
| tier = LIMIT_TIER_EXTERNAL; | ||
| } | ||
| } | ||
| else if (this._leaveSet.has(token)) { | ||
| i++; | ||
| continue; | ||
| } | ||
| else if (token.charCodeAt(0) === 35) { | ||
| const ncrResult = this._resolveNCR(token); | ||
| if (ncrResult === undefined) { | ||
| i++; | ||
| continue; | ||
| } | ||
| replacement = ncrResult; | ||
| tier = LIMIT_TIER_BASE; | ||
| } | ||
| else { | ||
| const resolved = this._resolveName(token); | ||
| replacement = resolved?.value; | ||
| tier = resolved?.tier; | ||
| } | ||
| if (replacement === undefined) { | ||
| i++; | ||
| continue; | ||
| } | ||
| if (i > last) { | ||
| chunks.push(str.slice(last, i)); | ||
| } | ||
| chunks.push(replacement); | ||
| last = j + 1; | ||
| i = last; | ||
| if (checkLimits && this._tierCounts(tier)) { | ||
| if (limitExpansions) { | ||
| this._totalExpansions++; | ||
| if (this._totalExpansions > this._maxTotalExpansions) { | ||
| throw new Error(`[EntityReplacer] Entity expansion count limit exceeded: ` + | ||
| `${this._totalExpansions} > ${this._maxTotalExpansions}`); | ||
| } | ||
| } | ||
| if (limitLength) { | ||
| const delta = replacement.length - (token.length + 2); | ||
| if (delta > 0) { | ||
| this._expandedLength += delta; | ||
| if (this._expandedLength > this._maxExpandedLength) { | ||
| throw new Error(`[EntityReplacer] Expanded content length limit exceeded: ` + | ||
| `${this._expandedLength} > ${this._maxExpandedLength}`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| if (last < len) { | ||
| chunks.push(str.slice(last)); | ||
| } | ||
| const result = chunks.length === 0 ? str : chunks.join(""); | ||
| return this._postCheck(result, original); | ||
| } | ||
| _tierCounts(tier) { | ||
| if (this._limitTiers.has(LIMIT_TIER_ALL)) { | ||
| return true; | ||
| } | ||
| return this._limitTiers.has(tier); | ||
| } | ||
| _resolveName(name) { | ||
| if (name in this._inputMap) { | ||
| return { value: this._inputMap[name], tier: LIMIT_TIER_EXTERNAL }; | ||
| } | ||
| if (name in this._externalMap) { | ||
| return { value: this._externalMap[name], tier: LIMIT_TIER_EXTERNAL }; | ||
| } | ||
| if (name in this._baseMap) { | ||
| return { value: this._baseMap[name], tier: LIMIT_TIER_BASE }; | ||
| } | ||
| return undefined; | ||
| } | ||
| _classifyNCR(cp) { | ||
| if (cp === 0) { | ||
| return this._ncrNullLevel; | ||
| } | ||
| if (cp >= 0xd800 && cp <= 0xdfff) { | ||
| return NCR_LEVEL.remove; | ||
| } | ||
| if (this._ncrXmlVersion === 1.0) { | ||
| if (cp >= 0x01 && cp <= 0x1f && !XML10_ALLOWED_C0.has(cp)) { | ||
| return NCR_LEVEL.remove; | ||
| } | ||
| } | ||
| return -1; | ||
| } | ||
| _applyNCRAction(action, token, cp) { | ||
| switch (action) { | ||
| case NCR_LEVEL.allow: | ||
| return String.fromCodePoint(cp); | ||
| case NCR_LEVEL.remove: | ||
| return ""; | ||
| case NCR_LEVEL.leave: | ||
| return undefined; | ||
| case NCR_LEVEL.throw: | ||
| throw new Error(`[EntityDecoder] Prohibited numeric character reference ` + | ||
| `&${token}; (U+${cp.toString(16).toUpperCase().padStart(4, "0")})`); | ||
| default: | ||
| return String.fromCodePoint(cp); | ||
| } | ||
| } | ||
| _resolveNCR(token) { | ||
| const second = token.charCodeAt(1); | ||
| let cp; | ||
| if (second === 120 || second === 88) { | ||
| cp = parseInt(token.slice(2), 16); | ||
| } | ||
| else { | ||
| cp = parseInt(token.slice(1), 10); | ||
| } | ||
| if (Number.isNaN(cp) || cp < 0 || cp > 0x10ffff) { | ||
| return undefined; | ||
| } | ||
| const minimum = this._classifyNCR(cp); | ||
| if (!this._numericAllowed && minimum < NCR_LEVEL.remove) { | ||
| return undefined; | ||
| } | ||
| const effective = minimum === -1 ? this._ncrOnLevel : Math.max(this._ncrOnLevel, minimum); | ||
| return this._applyNCRAction(effective, token, cp); | ||
| } | ||
| }; |
| export const XML = { | ||
| amp: "&", | ||
| apos: "'", | ||
| gt: ">", | ||
| lt: "<", | ||
| quot: '"', | ||
| }; | ||
| export const COMMON_HTML = { | ||
| nbsp: "\u00a0", | ||
| copy: "\u00a9", | ||
| reg: "\u00ae", | ||
| trade: "\u2122", | ||
| mdash: "\u2014", | ||
| ndash: "\u2013", | ||
| hellip: "\u2026", | ||
| laquo: "\u00ab", | ||
| raquo: "\u00bb", | ||
| lsquo: "\u2018", | ||
| rsquo: "\u2019", | ||
| ldquo: "\u201c", | ||
| rdquo: "\u201d", | ||
| bull: "\u2022", | ||
| para: "\u00b6", | ||
| sect: "\u00a7", | ||
| deg: "\u00b0", | ||
| frac12: "\u00bd", | ||
| frac14: "\u00bc", | ||
| frac34: "\u00be", | ||
| }; | ||
| export const CURRENCY = { | ||
| cent: "\u00a2", | ||
| pound: "\u00a3", | ||
| curren: "\u00a4", | ||
| yen: "\u00a5", | ||
| euro: "\u20ac", | ||
| dollar: "$", | ||
| fnof: "\u0192", | ||
| inr: "\u20b9", | ||
| af: "\u060b", | ||
| birr: "\u1265\u122d", | ||
| peso: "\u20b1", | ||
| rub: "\u20bd", | ||
| won: "\u20a9", | ||
| yuan: "\u00a5", | ||
| cedil: "\u00b8", | ||
| }; | ||
| const SPECIAL_CHARS = new Set("!?\\/[]$%{}^&*()<>|+"); | ||
| function validateEntityName(name) { | ||
| if (name[0] === "#") { | ||
| throw new Error(`[EntityReplacer] Invalid character '#' in entity name: "${name}"`); | ||
| } | ||
| for (const ch of name) { | ||
| if (SPECIAL_CHARS.has(ch)) { | ||
| throw new Error(`[EntityReplacer] Invalid character '${ch}' in entity name: "${name}"`); | ||
| } | ||
| } | ||
| return name; | ||
| } | ||
| function mergeEntityMaps(...maps) { | ||
| const out = Object.create(null); | ||
| for (const map of maps) { | ||
| if (!map) { | ||
| continue; | ||
| } | ||
| for (const key of Object.keys(map)) { | ||
| const raw = map[key]; | ||
| if (typeof raw === "string") { | ||
| out[key] = raw; | ||
| } | ||
| else if (raw && typeof raw === "object" && raw.val !== undefined) { | ||
| const val = raw.val; | ||
| if (typeof val === "string") { | ||
| out[key] = val; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| const LIMIT_TIER_EXTERNAL = "external"; | ||
| const LIMIT_TIER_BASE = "base"; | ||
| const LIMIT_TIER_ALL = "all"; | ||
| function parseLimitTiers(raw) { | ||
| if (!raw || raw === LIMIT_TIER_EXTERNAL) { | ||
| return new Set([LIMIT_TIER_EXTERNAL]); | ||
| } | ||
| if (raw === LIMIT_TIER_ALL) { | ||
| return new Set([LIMIT_TIER_ALL]); | ||
| } | ||
| if (raw === LIMIT_TIER_BASE) { | ||
| return new Set([LIMIT_TIER_BASE]); | ||
| } | ||
| if (Array.isArray(raw)) { | ||
| return new Set(raw); | ||
| } | ||
| return new Set([LIMIT_TIER_EXTERNAL]); | ||
| } | ||
| const NCR_LEVEL = Object.freeze({ allow: 0, leave: 1, remove: 2, throw: 3 }); | ||
| const XML10_ALLOWED_C0 = new Set([0x09, 0x0a, 0x0d]); | ||
| function parseNCRConfig(ncr) { | ||
| if (!ncr) { | ||
| return { xmlVersion: 1.0, onLevel: NCR_LEVEL.allow, nullLevel: NCR_LEVEL.remove }; | ||
| } | ||
| const xmlVersion = ncr.xmlVersion === 1.1 ? 1.1 : 1.0; | ||
| const onLevel = NCR_LEVEL[ncr.onNCR ?? "allow"] ?? NCR_LEVEL.allow; | ||
| const nullLevel = NCR_LEVEL[ncr.nullNCR ?? "remove"] ?? NCR_LEVEL.remove; | ||
| const clampedNull = Math.max(nullLevel, NCR_LEVEL.remove); | ||
| return { xmlVersion, onLevel, nullLevel: clampedNull }; | ||
| } | ||
| export const EntityDecoderImpl = class EntityDecoderImpl { | ||
| _limit; | ||
| _maxTotalExpansions; | ||
| _maxExpandedLength; | ||
| _postCheck; | ||
| _limitTiers; | ||
| _numericAllowed; | ||
| _baseMap; | ||
| _externalMap; | ||
| _inputMap; | ||
| _totalExpansions; | ||
| _expandedLength; | ||
| _removeSet; | ||
| _leaveSet; | ||
| _ncrXmlVersion; | ||
| _ncrOnLevel; | ||
| _ncrNullLevel; | ||
| constructor(options = {}) { | ||
| this._limit = options.limit || {}; | ||
| this._maxTotalExpansions = this._limit.maxTotalExpansions || 0; | ||
| this._maxExpandedLength = this._limit.maxExpandedLength || 0; | ||
| this._postCheck = typeof options.postCheck === "function" ? options.postCheck : (r) => r; | ||
| this._limitTiers = parseLimitTiers(this._limit.applyLimitsTo ?? LIMIT_TIER_EXTERNAL); | ||
| this._numericAllowed = options.numericAllowed ?? true; | ||
| this._baseMap = mergeEntityMaps(XML, options.namedEntities || null); | ||
| this._externalMap = Object.create(null); | ||
| this._inputMap = Object.create(null); | ||
| this._totalExpansions = 0; | ||
| this._expandedLength = 0; | ||
| this._removeSet = new Set(options.remove && Array.isArray(options.remove) ? options.remove : []); | ||
| this._leaveSet = new Set(options.leave && Array.isArray(options.leave) ? options.leave : []); | ||
| const ncrCfg = parseNCRConfig(options.ncr); | ||
| this._ncrXmlVersion = ncrCfg.xmlVersion; | ||
| this._ncrOnLevel = ncrCfg.onLevel; | ||
| this._ncrNullLevel = ncrCfg.nullLevel; | ||
| } | ||
| setExternalEntities(map) { | ||
| if (map) { | ||
| for (const key of Object.keys(map)) { | ||
| validateEntityName(key); | ||
| } | ||
| } | ||
| this._externalMap = mergeEntityMaps(map); | ||
| } | ||
| addExternalEntity(key, value) { | ||
| validateEntityName(key); | ||
| if (typeof value === "string" && value.indexOf("&") === -1) { | ||
| this._externalMap[key] = value; | ||
| } | ||
| } | ||
| addInputEntities(map) { | ||
| this._totalExpansions = 0; | ||
| this._expandedLength = 0; | ||
| this._inputMap = mergeEntityMaps(map); | ||
| } | ||
| reset() { | ||
| this._inputMap = Object.create(null); | ||
| this._totalExpansions = 0; | ||
| this._expandedLength = 0; | ||
| return this; | ||
| } | ||
| setXmlVersion(version) { | ||
| this._ncrXmlVersion = version === "1.1" || version === 1.1 ? 1.1 : 1.0; | ||
| } | ||
| decode(str) { | ||
| if (typeof str !== "string" || str.length === 0) { | ||
| return str; | ||
| } | ||
| const original = str; | ||
| const chunks = []; | ||
| const len = str.length; | ||
| let last = 0; | ||
| let i = 0; | ||
| const limitExpansions = this._maxTotalExpansions > 0; | ||
| const limitLength = this._maxExpandedLength > 0; | ||
| const checkLimits = limitExpansions || limitLength; | ||
| while (i < len) { | ||
| if (str.charCodeAt(i) !== 38) { | ||
| i++; | ||
| continue; | ||
| } | ||
| let j = i + 1; | ||
| while (j < len && str.charCodeAt(j) !== 59 && j - i <= 32) { | ||
| j++; | ||
| } | ||
| if (j >= len || str.charCodeAt(j) !== 59) { | ||
| i++; | ||
| continue; | ||
| } | ||
| const token = str.slice(i + 1, j); | ||
| if (token.length === 0) { | ||
| i++; | ||
| continue; | ||
| } | ||
| let replacement; | ||
| let tier; | ||
| if (this._removeSet.has(token)) { | ||
| replacement = ""; | ||
| if (tier === undefined) { | ||
| tier = LIMIT_TIER_EXTERNAL; | ||
| } | ||
| } | ||
| else if (this._leaveSet.has(token)) { | ||
| i++; | ||
| continue; | ||
| } | ||
| else if (token.charCodeAt(0) === 35) { | ||
| const ncrResult = this._resolveNCR(token); | ||
| if (ncrResult === undefined) { | ||
| i++; | ||
| continue; | ||
| } | ||
| replacement = ncrResult; | ||
| tier = LIMIT_TIER_BASE; | ||
| } | ||
| else { | ||
| const resolved = this._resolveName(token); | ||
| replacement = resolved?.value; | ||
| tier = resolved?.tier; | ||
| } | ||
| if (replacement === undefined) { | ||
| i++; | ||
| continue; | ||
| } | ||
| if (i > last) { | ||
| chunks.push(str.slice(last, i)); | ||
| } | ||
| chunks.push(replacement); | ||
| last = j + 1; | ||
| i = last; | ||
| if (checkLimits && this._tierCounts(tier)) { | ||
| if (limitExpansions) { | ||
| this._totalExpansions++; | ||
| if (this._totalExpansions > this._maxTotalExpansions) { | ||
| throw new Error(`[EntityReplacer] Entity expansion count limit exceeded: ` + | ||
| `${this._totalExpansions} > ${this._maxTotalExpansions}`); | ||
| } | ||
| } | ||
| if (limitLength) { | ||
| const delta = replacement.length - (token.length + 2); | ||
| if (delta > 0) { | ||
| this._expandedLength += delta; | ||
| if (this._expandedLength > this._maxExpandedLength) { | ||
| throw new Error(`[EntityReplacer] Expanded content length limit exceeded: ` + | ||
| `${this._expandedLength} > ${this._maxExpandedLength}`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| if (last < len) { | ||
| chunks.push(str.slice(last)); | ||
| } | ||
| const result = chunks.length === 0 ? str : chunks.join(""); | ||
| return this._postCheck(result, original); | ||
| } | ||
| _tierCounts(tier) { | ||
| if (this._limitTiers.has(LIMIT_TIER_ALL)) { | ||
| return true; | ||
| } | ||
| return this._limitTiers.has(tier); | ||
| } | ||
| _resolveName(name) { | ||
| if (name in this._inputMap) { | ||
| return { value: this._inputMap[name], tier: LIMIT_TIER_EXTERNAL }; | ||
| } | ||
| if (name in this._externalMap) { | ||
| return { value: this._externalMap[name], tier: LIMIT_TIER_EXTERNAL }; | ||
| } | ||
| if (name in this._baseMap) { | ||
| return { value: this._baseMap[name], tier: LIMIT_TIER_BASE }; | ||
| } | ||
| return undefined; | ||
| } | ||
| _classifyNCR(cp) { | ||
| if (cp === 0) { | ||
| return this._ncrNullLevel; | ||
| } | ||
| if (cp >= 0xd800 && cp <= 0xdfff) { | ||
| return NCR_LEVEL.remove; | ||
| } | ||
| if (this._ncrXmlVersion === 1.0) { | ||
| if (cp >= 0x01 && cp <= 0x1f && !XML10_ALLOWED_C0.has(cp)) { | ||
| return NCR_LEVEL.remove; | ||
| } | ||
| } | ||
| return -1; | ||
| } | ||
| _applyNCRAction(action, token, cp) { | ||
| switch (action) { | ||
| case NCR_LEVEL.allow: | ||
| return String.fromCodePoint(cp); | ||
| case NCR_LEVEL.remove: | ||
| return ""; | ||
| case NCR_LEVEL.leave: | ||
| return undefined; | ||
| case NCR_LEVEL.throw: | ||
| throw new Error(`[EntityDecoder] Prohibited numeric character reference ` + | ||
| `&${token}; (U+${cp.toString(16).toUpperCase().padStart(4, "0")})`); | ||
| default: | ||
| return String.fromCodePoint(cp); | ||
| } | ||
| } | ||
| _resolveNCR(token) { | ||
| const second = token.charCodeAt(1); | ||
| let cp; | ||
| if (second === 120 || second === 88) { | ||
| cp = parseInt(token.slice(2), 16); | ||
| } | ||
| else { | ||
| cp = parseInt(token.slice(1), 10); | ||
| } | ||
| if (Number.isNaN(cp) || cp < 0 || cp > 0x10ffff) { | ||
| return undefined; | ||
| } | ||
| const minimum = this._classifyNCR(cp); | ||
| if (!this._numericAllowed && minimum < NCR_LEVEL.remove) { | ||
| return undefined; | ||
| } | ||
| const effective = minimum === -1 ? this._ncrOnLevel : Math.max(this._ncrOnLevel, minimum); | ||
| return this._applyNCRAction(effective, token, cp); | ||
| } | ||
| }; |
| export declare const XML: Record<string, string>; | ||
| export declare const COMMON_HTML: Record<string, string>; | ||
| export declare const CURRENCY: Record<string, string>; | ||
| type EntityValFn = ( | ||
| match: string, | ||
| captured: string, | ||
| ...rest: unknown[] | ||
| ) => string; | ||
| type ApplyLimitsTo = "external" | "base" | "all" | Array<"external" | "base">; | ||
| interface EntityDecoderLimitOptions { | ||
| maxTotalExpansions?: number; | ||
| maxExpandedLength?: number; | ||
| applyLimitsTo?: ApplyLimitsTo; | ||
| } | ||
| interface EntityDecoderNCROptions { | ||
| xmlVersion?: 1.0 | 1.1; | ||
| onNCR?: "allow" | "leave" | "remove" | "throw"; | ||
| nullNCR?: "remove" | "throw"; | ||
| } | ||
| interface EntityDecoderOptions { | ||
| namedEntities?: Record< | ||
| string, | ||
| | string | ||
| | { | ||
| regex: RegExp; | ||
| val: string | EntityValFn; | ||
| } | ||
| > | null; | ||
| postCheck?: ((resolved: string, original: string) => string) | null; | ||
| numericAllowed?: boolean; | ||
| leave?: string[]; | ||
| remove?: string[]; | ||
| limit?: EntityDecoderLimitOptions; | ||
| ncr?: EntityDecoderNCROptions; | ||
| } | ||
| export interface EntityDecoder { | ||
| setExternalEntities( | ||
| map: Record< | ||
| string, | ||
| | string | ||
| | { | ||
| regex: RegExp; | ||
| val: string | EntityValFn; | ||
| } | ||
| > | ||
| ): void; | ||
| addExternalEntity(key: string, value: string): void; | ||
| addInputEntities( | ||
| map: Record< | ||
| string, | ||
| | string | ||
| | { | ||
| regx?: RegExp; | ||
| regex?: RegExp; | ||
| val: string | EntityValFn; | ||
| } | ||
| > | ||
| ): void; | ||
| reset(): this; | ||
| decode(str: string): string; | ||
| setXmlVersion(version: string): void; | ||
| } | ||
| export declare const EntityDecoderImpl: new ( | ||
| options?: EntityDecoderOptions | ||
| ) => EntityDecoder; | ||
| export {}; |
| /** | ||
| * Contains code from \@nodable/entities v2.1.0 | ||
| * Copyright (c) Amit Gupta (https://solothought.com) | ||
| * https://github.com/nodable/val-parsers | ||
| * | ||
| * This file bundles the EntityDecoder class and the named-entity maps | ||
| * (XML, COMMON_HTML, CURRENCY). | ||
| * | ||
| * This is a temporary solution while using this particular custom | ||
| * EntityDecoder class. The module-only nature of the original version | ||
| * is incompatible with some of our users' applications. | ||
| * | ||
| * Given that our CJS dist must call `require` to bring in the module, and | ||
| * because the EntityDecoder class and object are inaccessible via the runtime object surface | ||
| * of the XMLParser, we must inline the package. | ||
| * | ||
| * Q: Why is this necessary given that fast-xml-parser itself uses \@nodable/entities? | ||
| * A: FXP only uses \@nodable/entities when imported in ESM mode. When importing FXP | ||
| * via require, a bundled version is used, unaffected by the module-only nature | ||
| * of the entities package. | ||
| */ | ||
| export declare const XML: Record<string, string>; | ||
| export declare const COMMON_HTML: Record<string, string>; | ||
| export declare const CURRENCY: Record<string, string>; | ||
| type EntityValFn = (match: string, captured: string, ...rest: unknown[]) => string; | ||
| type ApplyLimitsTo = "external" | "base" | "all" | Array<"external" | "base">; | ||
| interface EntityDecoderLimitOptions { | ||
| maxTotalExpansions?: number; | ||
| maxExpandedLength?: number; | ||
| applyLimitsTo?: ApplyLimitsTo; | ||
| } | ||
| interface EntityDecoderNCROptions { | ||
| xmlVersion?: 1.0 | 1.1; | ||
| onNCR?: "allow" | "leave" | "remove" | "throw"; | ||
| nullNCR?: "remove" | "throw"; | ||
| } | ||
| interface EntityDecoderOptions { | ||
| namedEntities?: Record<string, string | { | ||
| regex: RegExp; | ||
| val: string | EntityValFn; | ||
| }> | null; | ||
| postCheck?: ((resolved: string, original: string) => string) | null; | ||
| numericAllowed?: boolean; | ||
| leave?: string[]; | ||
| remove?: string[]; | ||
| limit?: EntityDecoderLimitOptions; | ||
| ncr?: EntityDecoderNCROptions; | ||
| } | ||
| export interface EntityDecoder { | ||
| setExternalEntities(map: Record<string, string | { | ||
| regex: RegExp; | ||
| val: string | EntityValFn; | ||
| }>): void; | ||
| addExternalEntity(key: string, value: string): void; | ||
| addInputEntities(map: Record<string, string | { | ||
| regx?: RegExp; | ||
| regex?: RegExp; | ||
| val: string | EntityValFn; | ||
| }>): void; | ||
| reset(): this; | ||
| decode(str: string): string; | ||
| setXmlVersion(version: string): void; | ||
| } | ||
| export declare const EntityDecoderImpl: new (options?: EntityDecoderOptions) => EntityDecoder; | ||
| export {}; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
2
-33.33%5
-16.67%43556
-24.65%30
-11.76%982
-29.71%1
Infinity%- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed