2026-07-30 15:09:35 -04:00
export const id = 824 ;
export const ids = [ 824 ];
export const modules = {
/***/ 5824 :
/***/ (( __unused_webpack___webpack_module__ , __webpack_exports__ , __webpack_require__ ) => {
/* harmony export */ __webpack_require__ . d ( __webpack_exports__ , {
/* harmony export */ XMLParser : () => ( /* reexport safe */ _xmlparser_XMLParser_js__WEBPACK_IMPORTED_MODULE_1__ . A ),
/* harmony export */ i : () => ( /* binding */ XMLValidator )
/* harmony export */ });
/* harmony import */ var _validator_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__ ( 1176 );
/* harmony import */ var _xmlparser_XMLParser_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__ ( 6009 );
const XMLValidator = {
validate : _validator_js__WEBPACK_IMPORTED_MODULE_0__ /* .validate */ . t
}
/***/ }),
/***/ 984 :
/***/ (( __unused_webpack___webpack_module__ , __webpack_exports__ , __webpack_require__ ) => {
/* harmony export */ __webpack_require__ . d ( __webpack_exports__ , {
/* harmony export */ Eo : () => ( /* binding */ isName ),
/* harmony export */ Xe : () => ( /* binding */ getAllMatches ),
/* harmony export */ q9 : () => ( /* binding */ DANGEROUS_PROPERTY_NAMES ),
/* harmony export */ vl : () => ( /* binding */ criticalProperties ),
/* harmony export */ yQ : () => ( /* binding */ isExist )
/* harmony export */ });
/* unused harmony exports nameRegexp, isEmptyObject, getValue */
const nameStartChar = ':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' ;
const nameChar = nameStartChar + '\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040' ;
const nameRegexp = '[' + nameStartChar + '][' + nameChar + ']*' ;
const regexName = new RegExp ( '^' + nameRegexp + '$' );
function getAllMatches ( string , regex ) {
const matches = [];
let match = regex . exec ( string );
while ( match ) {
const allmatches = [];
allmatches . startIndex = regex . lastIndex - match [ 0 ]. length ;
const len = match . length ;
for ( let index = 0 ; index < len ; index ++ ) {
allmatches . push ( match [ index ]);
}
matches . push ( allmatches );
match = regex . exec ( string );
}
return matches ;
}
const isName = function ( string ) {
const match = regexName . exec ( string );
return ! ( match === null || typeof match === 'undefined' );
}
function isExist ( v ) {
return typeof v !== 'undefined' ;
}
function isEmptyObject ( obj ) {
return Object . keys ( obj ). length === 0 ;
}
function getValue ( v ) {
if ( exports . isExist ( v )) {
return v ;
} else {
return '' ;
}
}
/**
* 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" ];
/***/ }),
/***/ 1176 :
/***/ (( __unused_webpack___webpack_module__ , __webpack_exports__ , __webpack_require__ ) => {
/* harmony export */ __webpack_require__ . d ( __webpack_exports__ , {
/* harmony export */ t : () => ( /* binding */ validate )
/* harmony export */ });
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__ ( 984 );
const defaultOptions = {
allowBooleanAttributes : false , //A tag can have attributes without any value
unpairedTags : []
};
//const tagsPattern = new RegExp("<\\/?([\\w:\\-_\.]+)\\s*\/?>","g");
function validate ( xmlData , options ) {
options = Object . assign ({}, defaultOptions , options );
//xmlData = xmlData.replace(/(\r\n|\n|\r)/gm,"");//make it single line
//xmlData = xmlData.replace(/(^\s*<\?xml.*?\?>)/g,"");//Remove XML starting tag
//xmlData = xmlData.replace(/(<!DOCTYPE[\s\w\"\.\/\-\:]+(\[.*\])*\s*>)/g,"");//Remove DOCTYPE
const tags = [];
let tagFound = false ;
//indicates that the root tag has been closed (aka. depth 0 has been reached)
let reachedRoot = false ;
if ( xmlData [ 0 ] === '\ufeff' ) {
// check for byte order mark (BOM)
xmlData = xmlData . substr ( 1 );
}
for ( let i = 0 ; i < xmlData . length ; i ++ ) {
if ( xmlData [ i ] === '<' && xmlData [ i + 1 ] === '?' ) {
i += 2 ;
i = readPI ( xmlData , i );
if ( i . err ) return i ;
} else if ( xmlData [ i ] === '<' ) {
//starting of tag
//read until you reach to '>' avoiding any '>' in attribute value
let tagStartPos = i ;
i ++ ;
if ( xmlData [ i ] === '!' ) {
i = readCommentAndCDATA ( xmlData , i );
continue ;
} else {
let closingTag = false ;
if ( xmlData [ i ] === '/' ) {
//closing tag
closingTag = true ;
i ++ ;
}
//read tagname
let tagName = '' ;
for (; i < xmlData . length &&
xmlData [ i ] !== '>' &&
xmlData [ i ] !== ' ' &&
xmlData [ i ] !== '\t' &&
xmlData [ i ] !== '\n' &&
xmlData [ i ] !== '\r' ; i ++
) {
tagName += xmlData [ i ];
}
tagName = tagName . trim ();
//console.log(tagName);
if ( tagName [ tagName . length - 1 ] === '/' ) {
//self closing tag without attributes
tagName = tagName . substring ( 0 , tagName . length - 1 );
//continue;
i -- ;
}
if ( ! validateTagName ( tagName )) {
let msg ;
if ( tagName . trim (). length === 0 ) {
msg = "Invalid space after '<'." ;
} else {
msg = "Tag '" + tagName + "' is an invalid name." ;
}
return getErrorObject ( 'InvalidTag' , msg , getLineNumberForPosition ( xmlData , i ));
}
const result = readAttributeStr ( xmlData , i );
if ( result === false ) {
return getErrorObject ( 'InvalidAttr' , "Attributes for '" + tagName + "' have open quote." , getLineNumberForPosition ( xmlData , i ));
}
let attrStr = result . value ;
i = result . index ;
if ( attrStr [ attrStr . length - 1 ] === '/' ) {
//self closing tag
const attrStrStart = i - attrStr . length ;
attrStr = attrStr . substring ( 0 , attrStr . length - 1 );
const isValid = validateAttributeString ( attrStr , options );
if ( isValid === true ) {
tagFound = true ;
//continue; //text may presents after self closing tag
} else {
//the result from the nested function returns the position of the error within the attribute
//in order to get the 'true' error line, we need to calculate the position where the attribute begins (i - attrStr.length) and then add the position within the attribute
//this gives us the absolute index in the entire xml, which we can use to find the line at last
return getErrorObject ( isValid . err . code , isValid . err . msg , getLineNumberForPosition ( xmlData , attrStrStart + isValid . err . line ));
}
} else if ( closingTag ) {
if ( ! result . tagClosed ) {
return getErrorObject ( 'InvalidTag' , "Closing tag '" + tagName + "' doesn't have proper closing." , getLineNumberForPosition ( xmlData , i ));
} else if ( attrStr . trim (). length > 0 ) {
return getErrorObject ( 'InvalidTag' , "Closing tag '" + tagName + "' can't have attributes or invalid starting." , getLineNumberForPosition ( xmlData , tagStartPos ));
} else if ( tags . length === 0 ) {
return getErrorObject ( 'InvalidTag' , "Closing tag '" + tagName + "' has not been opened." , getLineNumberForPosition ( xmlData , tagStartPos ));
} else {
const otg = tags . pop ();
if ( tagName !== otg . tagName ) {
let openPos = getLineNumberForPosition ( xmlData , otg . tagStartPos );
return getErrorObject ( 'InvalidTag' ,
"Expected closing tag '" + otg . tagName + "' (opened in line " + openPos . line + ", col " + openPos . col + ") instead of closing tag '" + tagName + "'." ,
getLineNumberForPosition ( xmlData , tagStartPos ));
}
//when there are no more tags, we reached the root level.
if ( tags . length == 0 ) {
reachedRoot = true ;
}
}
} else {
const isValid = validateAttributeString ( attrStr , options );
if ( isValid !== true ) {
//the result from the nested function returns the position of the error within the attribute
//in order to get the 'true' error line, we need to calculate the position where the attribute begins (i - attrStr.length) and then add the position within the attribute
//this gives us the absolute index in the entire xml, which we can use to find the line at last
return getErrorObject ( isValid . err . code , isValid . err . msg , getLineNumberForPosition ( xmlData , i - attrStr . length + isValid . err . line ));
}
//if the root level has been reached before ...
if ( reachedRoot === true ) {
return getErrorObject ( 'InvalidXml' , 'Multiple possible root nodes found.' , getLineNumberForPosition ( xmlData , i ));
} else if ( options . unpairedTags . indexOf ( tagName ) !== - 1 ) {
//don't push into stack
} else {
tags . push ({ tagName , tagStartPos });
}
tagFound = true ;
}
//skip tag text value
//It may include comments and CDATA value
for ( i ++ ; i < xmlData . length ; i ++ ) {
if ( xmlData [ i ] === '<' ) {
if ( xmlData [ i + 1 ] === '!' ) {
//comment or CADATA
i ++ ;
i = readCommentAndCDATA ( xmlData , i );
continue ;
} else if ( xmlData [ i + 1 ] === '?' ) {
i = readPI ( xmlData , ++ i );
if ( i . err ) return i ;
} else {
break ;
}
} else if ( xmlData [ i ] === '&' ) {
const afterAmp = validateAmpersand ( xmlData , i );
if ( afterAmp == - 1 )
return getErrorObject ( 'InvalidChar' , "char '&' is not expected." , getLineNumberForPosition ( xmlData , i ));
i = afterAmp ;
} else {
if ( reachedRoot === true && ! isWhiteSpace ( xmlData [ i ])) {
return getErrorObject ( 'InvalidXml' , "Extra text at the end" , getLineNumberForPosition ( xmlData , i ));
}
}
} //end of reading tag text value
if ( xmlData [ i ] === '<' ) {
i -- ;
}
}
} else {
if ( isWhiteSpace ( xmlData [ i ])) {
continue ;
}
return getErrorObject ( 'InvalidChar' , "char '" + xmlData [ i ] + "' is not expected." , getLineNumberForPosition ( xmlData , i ));
}
}
if ( ! tagFound ) {
return getErrorObject ( 'InvalidXml' , 'Start tag expected.' , 1 );
} else if ( tags . length == 1 ) {
return getErrorObject ( 'InvalidTag' , "Unclosed tag '" + tags [ 0 ]. tagName + "'." , getLineNumberForPosition ( xmlData , tags [ 0 ]. tagStartPos ));
} else if ( tags . length > 0 ) {
return getErrorObject ( 'InvalidXml' , "Invalid '" +
JSON . stringify ( tags . map ( t => t . tagName ), null , 4 ). replace ( /\r?\n/g , '' ) +
"' found." , { line : 1 , col : 1 });
}
return true ;
};
function isWhiteSpace ( char ) {
return char === ' ' || char === '\t' || char === '\n' || char === '\r' ;
}
/**
* Read Processing insstructions and skip
* @param {*} xmlData
* @param {*} i
*/
function readPI ( xmlData , i ) {
const start = i ;
for (; i < xmlData . length ; i ++ ) {
if ( xmlData [ i ] == '?' || xmlData [ i ] == ' ' ) {
//tagname
const tagname = xmlData . substr ( start , i - start );
if ( i > 5 && tagname === 'xml' ) {
return getErrorObject ( 'InvalidXml' , 'XML declaration allowed only at the start of the document.' , getLineNumberForPosition ( xmlData , i ));
} else if ( xmlData [ i ] == '?' && xmlData [ i + 1 ] == '>' ) {
//check if valid attribut string
i ++ ;
break ;
} else {
continue ;
}
}
}
return i ;
}
function readCommentAndCDATA ( xmlData , i ) {
if ( xmlData . length > i + 5 && xmlData [ i + 1 ] === '-' && xmlData [ i + 2 ] === '-' ) {
//comment
for ( i += 3 ; i < xmlData . length ; i ++ ) {
if ( xmlData [ i ] === '-' && xmlData [ i + 1 ] === '-' && xmlData [ i + 2 ] === '>' ) {
i += 2 ;
break ;
}
}
} else if (
xmlData . length > i + 8 &&
xmlData [ i + 1 ] === 'D' &&
xmlData [ i + 2 ] === 'O' &&
xmlData [ i + 3 ] === 'C' &&
xmlData [ i + 4 ] === 'T' &&
xmlData [ i + 5 ] === 'Y' &&
xmlData [ i + 6 ] === 'P' &&
xmlData [ i + 7 ] === 'E'
) {
let angleBracketsCount = 1 ;
for ( i += 8 ; i < xmlData . length ; i ++ ) {
if ( xmlData [ i ] === '<' ) {
angleBracketsCount ++ ;
} else if ( xmlData [ i ] === '>' ) {
angleBracketsCount -- ;
if ( angleBracketsCount === 0 ) {
break ;
}
}
}
} else if (
xmlData . length > i + 9 &&
xmlData [ i + 1 ] === '[' &&
xmlData [ i + 2 ] === 'C' &&
xmlData [ i + 3 ] === 'D' &&
xmlData [ i + 4 ] === 'A' &&
xmlData [ i + 5 ] === 'T' &&
xmlData [ i + 6 ] === 'A' &&
xmlData [ i + 7 ] === '['
) {
for ( i += 8 ; i < xmlData . length ; i ++ ) {
if ( xmlData [ i ] === ']' && xmlData [ i + 1 ] === ']' && xmlData [ i + 2 ] === '>' ) {
i += 2 ;
break ;
}
}
}
return i ;
}
const doubleQuote = '"' ;
const singleQuote = "'" ;
/**
* Keep reading xmlData until '<' is found outside the attribute value.
* @param {string} xmlData
* @param {number} i
*/
function readAttributeStr ( xmlData , i ) {
let attrStr = '' ;
let startChar = '' ;
let tagClosed = false ;
for (; i < xmlData . length ; i ++ ) {
if ( xmlData [ i ] === doubleQuote || xmlData [ i ] === singleQuote ) {
if ( startChar === '' ) {
startChar = xmlData [ i ];
} else if ( startChar !== xmlData [ i ]) {
//if vaue is enclosed with double quote then single quotes are allowed inside the value and vice versa
} else {
startChar = '' ;
}
} else if ( xmlData [ i ] === '>' ) {
if ( startChar === '' ) {
tagClosed = true ;
break ;
}
}
attrStr += xmlData [ i ];
}
if ( startChar !== '' ) {
return false ;
}
return {
value : attrStr ,
index : i ,
tagClosed : tagClosed
};
}
/**
* Select all the attributes whether valid or invalid.
*/
const validAttrStrRegxp = new RegExp ( '(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s\\S])*?)\\5)?' , 'g' );
//attr, ="sd", a="amit's", a="sd"b="saf", ab cd=""
function validateAttributeString ( attrStr , options ) {
//console.log("start:"+attrStr+":end");
//if(attrStr.trim().length === 0) return true; //empty string
const matches = ( 0 , _util_js__WEBPACK_IMPORTED_MODULE_0__ /* .getAllMatches */ . Xe )( attrStr , validAttrStrRegxp );
const attrNames = {};
for ( let i = 0 ; i < matches . length ; i ++ ) {
if ( matches [ i ][ 1 ]. length === 0 ) {
//nospace before attribute name: a="sd"b="saf"
return getErrorObject ( 'InvalidAttr' , "Attribute '" + matches [ i ][ 2 ] + "' has no space in starting." , getPositionFromMatch ( matches [ i ]))
} else if ( matches [ i ][ 3 ] !== undefined && matches [ i ][ 4 ] === undefined ) {
return getErrorObject ( 'InvalidAttr' , "Attribute '" + matches [ i ][ 2 ] + "' is without value." , getPositionFromMatch ( matches [ i ]));
} else if ( matches [ i ][ 3 ] === undefined && ! options . allowBooleanAttributes ) {
//independent attribute: ab
return getErrorObject ( 'InvalidAttr' , "boolean attribute '" + matches [ i ][ 2 ] + "' is not allowed." , getPositionFromMatch ( matches [ i ]));
}
/* else if(matches[i][6] === undefined){//attribute without value: ab=
return { err: { code:"InvalidAttr",msg:"attribute " + matches[i][2] + " has no value assigned."}};
} */
const attrName = matches [ i ][ 2 ];
if ( ! validateAttrName ( attrName )) {
return getErrorObject ( 'InvalidAttr' , "Attribute '" + attrName + "' is an invalid name." , getPositionFromMatch ( matches [ i ]));
}
if ( ! Object . prototype . hasOwnProperty . call ( attrNames , attrName )) {
//check for duplicate attribute.
attrNames [ attrName ] = 1 ;
} else {
return getErrorObject ( 'InvalidAttr' , "Attribute '" + attrName + "' is repeated." , getPositionFromMatch ( matches [ i ]));
}
}
return true ;
}
function validateNumberAmpersand ( xmlData , i ) {
let re = /\d/ ;
if ( xmlData [ i ] === 'x' ) {
i ++ ;
re = /[\da-fA-F]/ ;
}
for (; i < xmlData . length ; i ++ ) {
if ( xmlData [ i ] === ';' )
return i ;
if ( ! xmlData [ i ]. match ( re ))
break ;
}
return - 1 ;
}
function validateAmpersand ( xmlData , i ) {
// https://www.w3.org/TR/xml/#dt-charref
i ++ ;
if ( xmlData [ i ] === ';' )
return - 1 ;
if ( xmlData [ i ] === '#' ) {
i ++ ;
return validateNumberAmpersand ( xmlData , i );
}
let count = 0 ;
for (; i < xmlData . length ; i ++ , count ++ ) {
if ( xmlData [ i ]. match ( /\w/ ) && count < 20 )
continue ;
if ( xmlData [ i ] === ';' )
break ;
return - 1 ;
}
return i ;
}
function getErrorObject ( code , message , lineNumber ) {
return {
err : {
code : code ,
msg : message ,
line : lineNumber . line || lineNumber ,
col : lineNumber . col ,
},
};
}
function validateAttrName ( attrName ) {
return ( 0 , _util_js__WEBPACK_IMPORTED_MODULE_0__ /* .isName */ . Eo )( attrName );
}
// const startsWithXML = /^xml/i;
function validateTagName ( tagname ) {
return ( 0 , _util_js__WEBPACK_IMPORTED_MODULE_0__ /* .isName */ . Eo )( tagname ) /* && !tagname.match(startsWithXML) */ ;
}
//this function returns the line number for the character at the given index
function getLineNumberForPosition ( xmlData , index ) {
const lines = xmlData . substring ( 0 , index ). split ( /\r?\n/ );
return {
line : lines . length ,
// column number is last line's length + 1, because column numbering starts at 1:
col : lines [ lines . length - 1 ]. length + 1
};
}
//this function returns the position of the first character of match within attrStr
function getPositionFromMatch ( match ) {
return match . startIndex + match [ 1 ]. length ;
}
/***/ }),
/***/ 6009 :
/***/ (( __unused_webpack___webpack_module__ , __webpack_exports__ , __webpack_require__ ) => {
// EXPORTS
__webpack_require__ . d ( __webpack_exports__ , {
A : () => ( /* binding */ XMLParser )
});
// EXTERNAL MODULE: ./node_modules/fast-xml-parser/src/util.js
var util = __webpack_require__ ( 984 );
; // CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js
const defaultOnDangerousProperty = ( name ) => {
if ( util /* DANGEROUS_PROPERTY_NAMES */ . q9 . includes ( name )) {
return "__" + name ;
}
return name ;
};
const defaultOptions = {
preserveOrder : false ,
attributeNamePrefix : '@_' ,
attributesGroupName : false ,
textNodeName : '#text' ,
ignoreAttributes : true ,
removeNSPrefix : false , // remove NS from tag name or attribute name if true
allowBooleanAttributes : false , //a tag can have attributes without any value
//ignoreRootElement : false,
parseTagValue : true ,
parseAttributeValue : false ,
trimValues : true , //Trim string values of tag and attributes
cdataPropName : false ,
numberParseOptions : {
hex : true ,
leadingZeros : true ,
eNotation : true ,
unicode : false
},
tagValueProcessor : function ( tagName , val ) {
return val ;
},
attributeValueProcessor : function ( attrName , val ) {
return val ;
},
stopNodes : [], //nested tags will not be parsed even for errors
alwaysCreateTextNode : false ,
isArray : () => false ,
commentPropName : false ,
unpairedTags : [],
processEntities : true ,
htmlEntities : false ,
entityDecoder : null ,
ignoreDeclaration : false ,
ignorePiTags : false ,
transformTagName : false ,
transformAttributeName : false ,
updateTag : function ( tagName , jPath , attrs ) {
return tagName
},
// skipEmptyListItem: false
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 ( util /* DANGEROUS_PROPERTY_NAMES */ . q9 . some ( dangerous => normalized === dangerous . toLowerCase ())) {
throw new Error (
`[SECURITY] Invalid ${ optionName } : " ${ propertyName } " is a reserved JavaScript keyword that could cause prototype pollution`
);
}
if ( util /* criticalProperties */ . vl . 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
* @returns {object} Always returns normalized object
*/
function normalizeProcessEntities ( value , htmlEntities ) {
// Boolean backward compatibility
if ( typeof value === 'boolean' ) {
return {
enabled : value , // true or false
maxEntitySize : 10000 ,
maxExpansionDepth : 10000 ,
maxTotalExpansions : Infinity ,
maxExpandedLength : 100000 ,
maxEntityCount : 1000 ,
allowedTags : null ,
tagFilter : null ,
appliesTo : "all" ,
};
}
// Object config - merge with defaults
if ( typeof value === 'object' && value !== null ) {
return {
enabled : value . enabled !== false ,
maxEntitySize : Math . max ( 1 , value . maxEntitySize ?? 10000 ),
maxExpansionDepth : Math . max ( 1 , value . maxExpansionDepth ?? 10000 ),
maxTotalExpansions : Math . max ( 1 , value . maxTotalExpansions ?? Infinity ),
maxExpandedLength : Math . max ( 1 , value . maxExpandedLength ?? 100000 ),
maxEntityCount : Math . max ( 1 , value . maxEntityCount ?? 1000 ),
allowedTags : value . allowedTags ?? null ,
tagFilter : value . tagFilter ?? null ,
appliesTo : value . appliesTo ?? "all" ,
};
}
// Default to enabled with limits
return normalizeProcessEntities ( true );
}
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 , built . htmlEntities );
built . unpairedTagsSet = new Set ( built . unpairedTags );
// 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 ;
};
; // CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/xmlparser/xmlNode.js
let METADATA_SYMBOL ;
if ( typeof Symbol !== "function" ) {
METADATA_SYMBOL = "@@xmlMetadata" ;
} else {
METADATA_SYMBOL = Symbol ( "XML Node Metadata" );
}
class XmlNode {
constructor ( tagname ) {
this . tagname = tagname ;
this . child = []; //nested tags, text, cdata, comments in order
this [ ":@" ] = Object . create ( null ); //attributes map
}
add ( key , val ) {
// this.child.push( {name : key, val: val, isCdata: isCdata });
if ( key === "__proto__" ) key = "#__proto__" ;
this . child . push ({ [ key ] : val });
}
addChild ( node , startIndex ) {
if ( node . tagname === "__proto__" ) node . tagname = "#__proto__" ;
if ( node [ ":@" ] && Object . keys ( node [ ":@" ]). length > 0 ) {
this . child . push ({ [ node . tagname ] : node . child , [ ":@" ] : node [ ":@" ] });
} else {
this . child . push ({ [ node . tagname ] : node . child });
}
// if requested, add the startIndex
2026-08-24 10:36:54 -04:00
this . addStartIndex ( startIndex );
}
addStartIndex ( startIndex ) {
2026-07-30 15:09:35 -04:00
if ( startIndex !== undefined ) {
// Note: for now we just overwrite the metadata. If we had more complex metadata,
// we might need to do an object append here: metadata = { ...metadata, startIndex }
this . child [ this . child . length - 1 ][ METADATA_SYMBOL ] = { startIndex };
}
}
2026-08-24 10:36:54 -04:00
addEndIndex ( endIndex ) {
const lastChild = this . child [ this . child . length - 1 ];
// endIndex is write-once: when updateTag drops a node, the last child is a
// previously completed sibling whose endIndex must not be overwritten
if ( lastChild !== undefined && lastChild [ METADATA_SYMBOL ] !== undefined
&& lastChild [ METADATA_SYMBOL ]. endIndex === undefined ) {
lastChild [ METADATA_SYMBOL ]. endIndex = endIndex ;
}
}
2026-07-30 15:09:35 -04:00
/** symbol used for metadata */
static getMetaDataSymbol () {
return METADATA_SYMBOL ;
}
}
// EXTERNAL MODULE: ./node_modules/xml-naming/src/index.js
var src = __webpack_require__ ( 4658 );
; // CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js
class DocTypeReader {
constructor ( options , xmlVersion ) {
this . suppressValidationErr = ! options ;
this . options = options ;
this . xmlVersion = xmlVersion || 1.0 ;
}
setXmlVersion ( xmlVersion = 1.0 ) {
this . xmlVersion = xmlVersion ;
}
readDocType ( xmlData , i ) {
const entities = Object . create ( null );
let entityCount = 0 ;
if ( xmlData [ i + 3 ] === 'O' &&
xmlData [ i + 4 ] === 'C' &&
xmlData [ i + 5 ] === 'T' &&
xmlData [ i + 6 ] === 'Y' &&
xmlData [ i + 7 ] === 'P' &&
xmlData [ i + 8 ] === 'E' ) {
i = i + 9 ;
let angleBracketsCount = 1 ;
let hasBody = false , comment = false ;
2026-08-24 10:36:54 -04:00
let quoteChar = null ; // tracks an open SYSTEM/PUBLIC literal before the '[' body
2026-07-30 15:09:35 -04:00
let exp = "" ;
for (; i < xmlData . length ; i ++ ) {
2026-08-24 10:36:54 -04:00
// Inside a quoted external-identifier literal — XML allows '<'
// and '>' as plain data here, so they must not be interpreted
// as DOCTYPE structure until the matching quote closes.
if ( quoteChar !== null ) {
if ( xmlData [ i ] === quoteChar ) quoteChar = null ;
exp += xmlData [ i ];
continue ;
}
if ( ! hasBody && ! comment && ( xmlData [ i ] === '"' || xmlData [ i ] === "'" )) {
quoteChar = xmlData [ i ];
exp += xmlData [ i ];
continue ;
}
2026-07-30 15:09:35 -04:00
if ( xmlData [ i ] === '<' && ! comment ) { //Determine the tag type
if ( hasBody && hasSeq ( xmlData , "!ENTITY" , i )) {
i += 7 ;
let entityName , val ;
[ entityName , val , i ] = this . readEntityExp ( xmlData , i + 1 , this . suppressValidationErr );
if ( val . indexOf ( "&" ) === - 1 ) { //Parameter entities are not supported
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 ] = val ;
entityCount ++ ;
}
}
else if ( hasBody && hasSeq ( xmlData , "!ELEMENT" , i )) {
i += 8 ; //Not supported
const { index } = this . readElementExp ( xmlData , i + 1 );
i = index ;
} else if ( hasBody && hasSeq ( xmlData , "!ATTLIST" , i )) {
i += 8 ; //Not supported
// const {index} = this.readAttlistExp(xmlData,i+1);
// i = index;
} else if ( hasBody && hasSeq ( xmlData , "!NOTATION" , i )) {
i += 9 ; //Not supported
const { index } = this . readNotationExp ( xmlData , i + 1 , this . suppressValidationErr );
i = index ;
} else if ( hasSeq ( xmlData , "!--" , i )) comment = true ;
else throw new Error ( `Invalid DOCTYPE` );
angleBracketsCount ++ ;
exp = "" ;
} else if ( xmlData [ i ] === '>' ) { //Read tag content
if ( comment ) {
if ( xmlData [ i - 1 ] === "-" && xmlData [ i - 2 ] === "-" ) {
comment = false ;
angleBracketsCount -- ;
}
} else {
angleBracketsCount -- ;
}
if ( angleBracketsCount === 0 ) {
break ;
}
} else if ( xmlData [ i ] === '[' ) {
hasBody = true ;
} else {
exp += xmlData [ i ];
}
}
2026-08-24 10:36:54 -04:00
if ( quoteChar !== null || angleBracketsCount !== 0 ) {
2026-07-30 15:09:35 -04:00
throw new Error ( `Unclosed DOCTYPE` );
}
} else {
throw new Error ( `Invalid Tag instead of DOCTYPE` );
}
return { entities , i };
}
readEntityExp ( xmlData , i ) {
//External entities are not supported
// <!ENTITY ext SYSTEM "http://normal-website.com" >
//Parameter entities are not supported
// <!ENTITY entityname "&anotherElement;">
//Internal entities are supported
// <!ENTITY entityname "replacement text">
// Skip leading whitespace after <!ENTITY
i = skipWhitespace ( xmlData , i );
// Read entity name
const startIndex = i ;
while ( i < xmlData . length && ! /\s/ . test ( xmlData [ i ]) && xmlData [ i ] !== '"' && xmlData [ i ] !== "'" ) {
i ++ ;
}
let entityName = xmlData . substring ( startIndex , i );
validateEntityName ( entityName , { xmlVersion : this . xmlVersion });
// Skip whitespace after entity name
i = skipWhitespace ( xmlData , i );
// Check for unsupported constructs (external entities or parameter entities)
if ( ! this . suppressValidationErr ) {
if ( xmlData . substring ( i , i + 6 ). toUpperCase () === "SYSTEM" ) {
throw new Error ( "External entities are not supported" );
} else if ( xmlData [ i ] === "%" ) {
throw new Error ( "Parameter entities are not supported" );
}
}
// Read entity value (internal entity)
let entityValue = "" ;
[ i , entityValue ] = this . readIdentifierVal ( xmlData , i , "entity" );
// Validate entity size
if ( this . options . enabled !== false &&
this . options . maxEntitySize != null &&
entityValue . length > this . options . maxEntitySize ) {
throw new Error (
`Entity " ${ entityName } " size ( ${ entityValue . length } ) exceeds maximum allowed size ( ${ this . options . maxEntitySize } )`
);
}
i -- ;
return [ entityName , entityValue , i ];
}
readNotationExp ( xmlData , i ) {
// Skip leading whitespace after <!NOTATION
i = skipWhitespace ( xmlData , i );
// Read notation name
const startIndex = i ;
while ( i < xmlData . length && ! /\s/ . test ( xmlData [ i ])) {
i ++ ;
}
let notationName = xmlData . substring ( startIndex , i );
! this . suppressValidationErr && validateEntityName ( notationName , { xmlVersion : this . xmlVersion });
// Skip whitespace after notation name
i = skipWhitespace ( xmlData , i );
// Check identifier type (SYSTEM or PUBLIC)
const identifierType = xmlData . substring ( i , i + 6 ). toUpperCase ();
if ( ! this . suppressValidationErr && identifierType !== "SYSTEM" && identifierType !== "PUBLIC" ) {
throw new Error ( `Expected SYSTEM or PUBLIC, found " ${ identifierType } "` );
}
i += identifierType . length ;
// Skip whitespace after identifier type
i = skipWhitespace ( xmlData , i );
// Read public identifier (if PUBLIC)
let publicIdentifier = null ;
let systemIdentifier = null ;
if ( identifierType === "PUBLIC" ) {
[ i , publicIdentifier ] = this . readIdentifierVal ( xmlData , i , "publicIdentifier" );
// Skip whitespace after public identifier
i = skipWhitespace ( xmlData , i );
// Optionally read system identifier
if ( xmlData [ i ] === '"' || xmlData [ i ] === "'" ) {
[ i , systemIdentifier ] = this . readIdentifierVal ( xmlData , i , "systemIdentifier" );
}
} else if ( identifierType === "SYSTEM" ) {
// Read system identifier (mandatory for SYSTEM)
[ i , systemIdentifier ] = this . readIdentifierVal ( xmlData , i , "systemIdentifier" );
if ( ! this . suppressValidationErr && ! systemIdentifier ) {
throw new Error ( "Missing mandatory system identifier for SYSTEM notation" );
}
}
return { notationName , publicIdentifier , systemIdentifier , index : -- i };
}
readIdentifierVal ( xmlData , i , type ) {
let identifierVal = "" ;
const startChar = xmlData [ i ];
if ( startChar !== '"' && startChar !== "'" ) {
throw new Error ( `Expected quoted string, found " ${ startChar } "` );
}
i ++ ;
const startIndex = i ;
while ( i < xmlData . length && xmlData [ i ] !== startChar ) {
i ++ ;
}
identifierVal = xmlData . substring ( startIndex , i );
if ( xmlData [ i ] !== startChar ) {
throw new Error ( `Unterminated ${ type } value` );
}
i ++ ;
return [ i , identifierVal ];
}
readElementExp ( xmlData , i ) {
// <!ELEMENT br EMPTY>
// <!ELEMENT div ANY>
// <!ELEMENT title (#PCDATA)>
// <!ELEMENT book (title, author+)>
// <!ELEMENT name (content-model)>
// Skip leading whitespace after <!ELEMENT
i = skipWhitespace ( xmlData , i );
// Read element name
const startIndex = i ;
while ( i < xmlData . length && ! /\s/ . test ( xmlData [ i ])) {
i ++ ;
}
let elementName = xmlData . substring ( startIndex , i );
// Validate element name
if ( ! this . suppressValidationErr && ! ( 0 , src /* qName */ . fG )( elementName , { xmlVersion : this . xmlVersion })) {
throw new Error ( `Invalid element name: " ${ elementName } "` );
}
// Skip whitespace after element name
i = skipWhitespace ( xmlData , i );
let contentModel = "" ;
// Expect '(' to start content model
if ( xmlData [ i ] === "E" && hasSeq ( xmlData , "MPTY" , i )) i += 4 ;
else if ( xmlData [ i ] === "A" && hasSeq ( xmlData , "NY" , i )) i += 2 ;
else if ( xmlData [ i ] === "(" ) {
i ++ ; // Move past '('
// Read content model
const startIndex = i ;
while ( i < xmlData . length && xmlData [ i ] !== ")" ) {
i ++ ;
}
contentModel = xmlData . substring ( startIndex , i );
if ( xmlData [ i ] !== ")" ) {
throw new Error ( "Unterminated content model" );
}
} else if ( ! this . suppressValidationErr ) {
throw new Error ( `Invalid Element Expression, found " ${ xmlData [ i ] } "` );
}
return {
elementName ,
contentModel : contentModel . trim (),
index : i
};
}
readAttlistExp ( xmlData , i ) {
// Skip leading whitespace after <!ATTLIST
i = skipWhitespace ( xmlData , i );
// Read element name
let startIndex = i ;
while ( i < xmlData . length && ! /\s/ . test ( xmlData [ i ])) {
i ++ ;
}
let elementName = xmlData . substring ( startIndex , i );
// Validate element name
validateEntityName ( elementName , { xmlVersion : this . xmlVersion })
// Skip whitespace after element name
i = skipWhitespace ( xmlData , i );
// Read attribute name
startIndex = i ;
while ( i < xmlData . length && ! /\s/ . test ( xmlData [ i ])) {
i ++ ;
}
let attributeName = xmlData . substring ( startIndex , i );
// Validate attribute name
if ( ! validateEntityName ( attributeName , { xmlVersion : this . xmlVersion })) {
throw new Error ( `Invalid attribute name: " ${ attributeName } "` );
}
// Skip whitespace after attribute name
i = skipWhitespace ( xmlData , i );
// Read attribute type
let attributeType = "" ;
if ( xmlData . substring ( i , i + 8 ). toUpperCase () === "NOTATION" ) {
attributeType = "NOTATION" ;
i += 8 ; // Move past "NOTATION"
// Skip whitespace after "NOTATION"
i = skipWhitespace ( xmlData , i );
// Expect '(' to start the list of notations
if ( xmlData [ i ] !== "(" ) {
throw new Error ( `Expected '(', found " ${ xmlData [ i ] } "` );
}
i ++ ; // Move past '('
// Read the list of allowed notations
let allowedNotations = [];
while ( i < xmlData . length && xmlData [ i ] !== ")" ) {
const startIndex = i ;
while ( i < xmlData . length && xmlData [ i ] !== "|" && xmlData [ i ] !== ")" ) {
i ++ ;
}
let notation = xmlData . substring ( startIndex , i );
// Validate notation name
notation = notation . trim ();
if ( ! validateEntityName ( notation , { xmlVersion : this . xmlVersion })) {
throw new Error ( `Invalid notation name: " ${ notation } "` );
}
allowedNotations . push ( notation );
// Skip '|' separator or exit loop
if ( xmlData [ i ] === "|" ) {
i ++ ; // Move past '|'
i = skipWhitespace ( xmlData , i ); // Skip optional whitespace after '|'
}
}
if ( xmlData [ i ] !== ")" ) {
throw new Error ( "Unterminated list of notations" );
}
i ++ ; // Move past ')'
// Store the allowed notations as part of the attribute type
attributeType += " (" + allowedNotations . join ( "|" ) + ")" ;
} else {
// Handle simple types (e.g., CDATA, ID, IDREF, etc.)
const startIndex = i ;
while ( i < xmlData . length && ! /\s/ . test ( xmlData [ i ])) {
i ++ ;
}
attributeType += xmlData . substring ( startIndex , i );
// Validate simple attribute type
const validTypes = [ "CDATA" , "ID" , "IDREF" , "IDREFS" , "ENTITY" , "ENTITIES" , "NMTOKEN" , "NMTOKENS" ];
if ( ! this . suppressValidationErr && ! validTypes . includes ( attributeType . toUpperCase ())) {
throw new Error ( `Invalid attribute type: " ${ attributeType } "` );
}
}
// Skip whitespace after attribute type
i = skipWhitespace ( xmlData , i );
// Read default value
let defaultValue = "" ;
if ( xmlData . substring ( i , i + 8 ). toUpperCase () === "#REQUIRED" ) {
defaultValue = "#REQUIRED" ;
i += 8 ;
} else if ( xmlData . substring ( i , i + 7 ). toUpperCase () === "#IMPLIED" ) {
defaultValue = "#IMPLIED" ;
i += 7 ;
} else {
[ i , defaultValue ] = this . readIdentifierVal ( xmlData , i , "ATTLIST" );
}
return {
elementName ,
attributeName ,
attributeType ,
defaultValue ,
index : i
}
}
}
const skipWhitespace = ( data , index ) => {
while ( index < data . length && /\s/ . test ( data [ index ])) {
index ++ ;
}
return index ;
};
function hasSeq ( data , seq , i ) {
for ( let j = 0 ; j < seq . length ; j ++ ) {
if ( seq [ j ] !== data [ i + j + 1 ]) return false ;
}
return true ;
}
function validateEntityName ( name , xmlVersion ) {
if (( 0 , src /* qName */ . fG )( name , { xmlVersion : xmlVersion }))
return name ;
else
throw new Error ( `Invalid entity name ${ name } ` );
}
; // CONCATENATED MODULE: ./node_modules/anynum/digitTable.js
/**
* Flat lookup table: maps Unicode code point → ASCII digit (0-9).
* Only decimal digit characters (Unicode category Nd) are included.
*
* Strategy: Int32Array of size (maxCodePoint - minCodePoint + 1).
* Value 0xFF means "not a digit". Value 0-9 is the ASCII digit value.
* This gives O(1) lookup with no branching, no bisect, no loop.
*
* Memory: range is 0x0660 to 0x1FBF0 → ~129,936 entries × 1 byte = ~127 KB.
* Acceptable for a one-time init; lookup is a single array index.
*/
// All known Unicode Nd (decimal digit) script zero code points.
// Each script has exactly 10 consecutive digits: zero+0 .. zero+9.
const SCRIPT_ZEROS = [
// Basic Latin (ASCII) — included for completeness / pass-through
0x0030 , // 0-9
// Arabic scripts
0x0660 , // Arabic-Indic ٠١٢٣٤٥٦٧٨٩
0x06F0 , // Extended Arabic-Indic (Urdu/Persian/Sindhi) ۰۱۲۳
// Indic scripts
0x0966 , // Devanagari ०१२३४५६७८९
0x09E6 , // Bengali ০১২৩৪৫৬৭৮৯
0x0A66 , // Gurmukhi ੦੧੨੩੪੫੬੭੮੯
0x0AE6 , // Gujarati ૦૧૨૩૪૫૬૭૮૯
0x0B66 , // Odia ୦୧୨୩୪୫୬୭୮୯
0x0BE6 , // Tamil ௦௧௨௩௪௫௬௭௮௯
0x0C66 , // Telugu ౦౧౨౩౪౫౬౭౮౯
0x0CE6 , // Kannada ೦೧೨೩೪೫೬೭೮೯
0x0D66 , // Malayalam ൦൧൨൩൪൫൬൭൮൯
0x0DE6 , // Sinhala Archaic ෦෧෨෩෪෫෬෭෮෯
// Southeast Asian scripts
0x0E50 , // Thai ๐๑๒๓๔๕๖๗๘๙
0x0ED0 , // Lao ໐໑໒໓໔໕໖໗໘໙
0x0F20 , // Tibetan ༠༡༢༣༤༥༦༧༨༩
0x1040 , // Myanmar ၀၁၂၃၄၅၆၇၈၉
0x1090 , // Myanmar Shan ႐႑႒႓႔႕႖႗႘႙
0x17E0 , // Khmer ០១២៣៤៥៦៧៨៩
0x1810 , // Mongolian ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙
0x1946 , // Limbu ᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏
0x19D0 , // New Tai Lue ᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙
0x1A80 , // Tai Tham Hora ᪀᪁᪂᪃᪄᪅᪆᪇᪈᪉
0x1A90 , // Tai Tham Tham ᪐᪑᪒᪓᪔᪕᪖᪗᪘᪙
0x1B50 , // Balinese ᭐᭑᭒᭓᭔᭕᭖᭗᭘᭙
0x1BB0 , // Sundanese ᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹
0x1C40 , // Lepcha ᱀᱁᱂᱃᱄᱅᱆᱇᱈᱉
0x1C50 , // Ol Chiki ᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙
// Fullwidth (CJK context)
0xFF10 , // Fullwidth 0123456789
// Mathematical digit variants (Unicode math block)
0x1D7CE , // Mathematical Bold
0x1D7D8 , // Mathematical Double-Struck
0x1D7E2 , // Mathematical Sans-Serif
0x1D7EC , // Mathematical Sans-Serif Bold
0x1D7F6 , // Mathematical Monospace
// Other scripts
0x104A0 , // Osmanya 𐒠𐒡𐒢𐒣𐒤𐒥𐒦𐒧𐒨𐒩
0x10D30 , // Hanifi Rohingya 𐴰𐴱𐴲𐴳𐴴𐴵𐴶𐴷𐴸𐴹
0x11066 , // Brahmi 𑁦𑁧𑁨𑁩𑁪𑁫𑁬𑁭𑁮𑁯
0x110F0 , // Sora Sompeng 𑃰𑃱𑃲𑃳𑃴𑃵𑃶𑃷𑃸𑃹
0x11136 , // Chakma 𑄶𑄷𑄸𑄹𑄺𑄻𑄼𑄽𑄾𑄿
0x111D0 , // Sharada 𑇐𑇑𑇒𑇓𑇔𑇕𑇖𑇗𑇘𑇙
0x112F0 , // Khudawadi 𑋰𑋱𑋲𑋳𑋴𑋵𑋶𑋷𑋸𑋹
0x11450 , // Newa 𑑐𑑑𑑒𑑓𑑔𑑕𑑖𑑗𑑘𑑙
0x114D0 , // Tirhuta 𑓐𑓑𑓒𑓓𑓔𑓕𑓖𑓗𑓘𑓙
0x11650 , // Modi 𑙐𑙑𑙒𑙓𑙔𑙕𑙖𑙗𑙘𑙙
0x116C0 , // Takri 𑛀𑛁𑛂𑛃𑛄𑛅𑛆𑛇𑛈𑛉
0x11730 , // Ahom 𑜰𑜱𑜲𑜳𑜴𑜵𑜶𑜷𑜸𑜹
0x118E0 , // Warang Citi 𑣠𑣡𑣢𑣣𑣤𑣥𑣦𑣧𑣨𑣩
0x11950 , // Dives Akuru 𑥐𑥑𑥒𑥓𑥔𑥕𑥖𑥗𑥘𑥙
0x11BF0 , // Khitan Small Script
0x11C50 , // Bhaiksuki 𑱐𑱑𑱒𑱓𑱔𑱕𑱖𑱗𑱘𑱙
0x11D50 , // Masaram Gondi 𑵐𑵑𑵒𑵓𑵔𑵕𑵖𑵗𑵘𑵙
0x11DA0 , // Gunjala Gondi 𑶠𑶡𑶢𑶣𑶤𑶥𑶦𑶧𑶨𑶩
0x11F50 , // Kawi 𑽐𑽑𑽒𑽓𑽔𑽕𑽖𑽗𑽘𑽙
0x16A60 , // Mro 𖩠𖩡𖩢𖩣𖩤𖩥𖩦𖩧𖩨𖩩
0x16AC0 , // Tangsa 𖫀𖫁𖫂𖫃𖫄𖫅𖫆𖫇𖫈𖫉
0x16B50 , // Pahawh Hmong 𖭐𖭑𖭒𖭓𖭔𖭕𖭖𖭗𖭘𖭙
0x1E140 , // Nyiakeng Puachue Hmong 𞅀𞅁𞅂𞅃𞅄𞅅𞅆𞅇𞅈𞅉
0x1E2F0 , // Wancho 𞋰𞋱𞋲𞋳𞋴𞋵𞋶𞋷𞋸𞋹
0x1E4F0 , // Nag Mundari 𞓰𞓱𞓲𞓳𞓴𞓵𞓶𞓷𞓸𞓹
0x1E950 , // Adlam 𞥐𞥑𞥒𞥓𞥔𞥕𞥖𞥗𞥘𞥙
0x1FBF0 , // Segmented digit symbols 🯰🯱🯲🯳🯴🯵🯶🯷🯸🯹
];
// Build a sparse Map for scripts above 0xFFFF (surrogate-pair range).
// These can't go into a flat Uint8Array indexed by code point efficiently.
const NOT_DIGIT = 0xFF ;
const HIGH_MAP = new Map (); // codePoint → digit value (0-9)
const LOW_MAX = 0xFFFF ;
const LOW_MIN = 0x0660 ; // first non-ASCII digit script
// Flat Uint8Array covering 0x0660 .. 0xFFFF
const TABLE_OFFSET = LOW_MIN ;
const TABLE_SIZE = LOW_MAX - LOW_MIN + 1 ;
const TABLE = new Uint8Array ( TABLE_SIZE ). fill ( NOT_DIGIT );
for ( const zero of SCRIPT_ZEROS ) {
for ( let d = 0 ; d < 10 ; d ++ ) {
const cp = zero + d ;
if ( cp <= LOW_MAX ) {
TABLE [ cp - TABLE_OFFSET ] = d ;
} else {
HIGH_MAP . set ( cp , d );
}
}
}
; // CONCATENATED MODULE: ./node_modules/anynum/anynum.js
const CHAR_0 = 48 ; // '0'.charCodeAt(0)
const CHAR_9 = 57 ; // '9'.charCodeAt(0)
const CHAR_MINUS = 45 ; // '-'.charCodeAt(0)
// Unicode minus/hyphen variants worth normalizing to ASCII '-' in numeric context:
// U+2212 MINUS SIGN − (mathematically correct minus)
// U+FF0D FULLWIDTH HYPHEN-MINUS - (Japanese fullwidth context)
// U+FE63 SMALL HYPHEN-MINUS ﹣ (small form variant)
//
// NOT normalized (deliberate):
// U+2013 EN DASH – (punctuation, not a numeric sign)
// U+2014 EM DASH — (punctuation)
// U+2010 HYPHEN ‐ (typographic hyphen)
//
// Rationale: only characters a human or locale formatter would plausibly use
// as a numeric minus sign are normalized. Dashes used for punctuation are left
// alone to avoid mangling non-numeric strings.
const MINUS_SET = new Set ([ 0x2212 , 0xFF0D , 0xFE63 ]);
/**
* Normalize all Unicode decimal digit characters in a string to ASCII (0-9),
* and normalize Unicode minus variants to ASCII '-' (U+002D).
*
* Non-digit, non-minus characters are passed through unchanged.
*
* Performance design:
* - Fast path: if the string has no convertible characters, return it unchanged
* (zero allocation).
* - BMP digits (0x0660..0xFFFF excl. surrogates): flat Uint8Array lookup (O(1)).
* - Supplementary plane digits (> 0xFFFF, encoded as surrogate pairs): Map lookup.
* - Minus variants: checked inline with a small fixed Set.
*
* @param {string} str
* @returns {string}
*/
function anynum ( str ) {
if ( typeof str !== 'string' ) return str ;
const len = str . length ;
if ( len === 0 ) return str ;
// Scan for first character needing conversion.
// If none found, return original string (zero allocation).
let firstHit = - 1 ;
for ( let i = 0 ; i < len ; i ++ ) {
const cc = str . charCodeAt ( i );
// ASCII digit or ASCII minus — already normalized, skip fast
if (( cc >= CHAR_0 && cc <= CHAR_9 ) || cc === CHAR_MINUS ) continue ;
// Below first unicode digit script — check minus variants only
if ( cc < TABLE_OFFSET ) {
if ( MINUS_SET . has ( cc )) { firstHit = i ; break ; }
continue ;
}
// Surrogate pairs live in BMP range 0xD800-0xDFFF — check before TABLE
if ( cc >= 0xD800 && cc <= 0xDBFF ) {
if ( i + 1 < len ) {
const low = str . charCodeAt ( i + 1 );
if ( low >= 0xDC00 && low <= 0xDFFF ) {
const cp = 0x10000 + (( cc - 0xD800 ) << 10 ) + ( low - 0xDC00 );
if ( HIGH_MAP . has ( cp )) { firstHit = i ; break ; }
}
}
continue ;
}
// BMP non-surrogate: flat table lookup; also check minus variants in this range
if ( TABLE [ cc - TABLE_OFFSET ] !== NOT_DIGIT || MINUS_SET . has ( cc )) {
firstHit = i ;
break ;
}
}
// Nothing to replace — return original, zero allocation
if ( firstHit === - 1 ) return str ;
// Build result: copy unchanged prefix, then convert from firstHit onward
const chars = [];
if ( firstHit > 0 ) chars . push ( str . slice ( 0 , firstHit ));
for ( let i = firstHit ; i < len ; i ++ ) {
const cc = str . charCodeAt ( i );
// ASCII digit or ASCII minus — pass through
if (( cc >= CHAR_0 && cc <= CHAR_9 ) || cc === CHAR_MINUS ) {
chars . push ( str [ i ]);
continue ;
}
// Below TABLE_OFFSET — check minus variants, else pass through
if ( cc < TABLE_OFFSET ) {
chars . push ( MINUS_SET . has ( cc ) ? '-' : str [ i ]);
continue ;
}
// Surrogate pairs
if ( cc >= 0xD800 && cc <= 0xDBFF ) {
if ( i + 1 < len ) {
const low = str . charCodeAt ( i + 1 );
if ( low >= 0xDC00 && low <= 0xDFFF ) {
const cp = 0x10000 + (( cc - 0xD800 ) << 10 ) + ( low - 0xDC00 );
const d = HIGH_MAP . get ( cp );
if ( d !== undefined ) {
chars . push ( String . fromCharCode ( d + 48 ));
i ++ ; // consume low surrogate
continue ;
}
}
}
chars . push ( str [ i ]);
continue ;
}
// BMP non-surrogate: flat table lookup + minus variants
if ( MINUS_SET . has ( cc )) {
chars . push ( '-' );
continue ;
}
const d = TABLE [ cc - TABLE_OFFSET ];
chars . push ( d !== NOT_DIGIT ? String . fromCharCode ( d + 48 ) : str [ i ]);
}
return chars . join ( '' );
}
/* harmony default export */ const anynum_anynum = ( anynum );
; // CONCATENATED MODULE: ./node_modules/strnum/strnum.js
const hexRegex = /^[-+]?0x[a-fA-F0-9]+$/ ;
const binRegex = /^0b[01]+$/ ;
const octRegex = /^0o[0-7]+$/ ;
const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/ ;
const consider = {
hex : true ,
binary : false ,
octal : false ,
leadingZeros : true ,
decimalPoint : "\." ,
eNotation : true ,
//skipLike: /regex/,
infinity : "original" , // "null", "infinity" (Infinity type), "string" ("Infinity" (the string literal))
unicode : false ,
};
function toNumber ( str , options = {}) {
options = Object . assign ({}, consider , options );
if ( ! str || typeof str !== "string" ) return str ;
let trimmedStr = str . trim ();
if ( trimmedStr . length === 0 ) return str ;
else if ( options . skipLike !== undefined && options . skipLike . test ( trimmedStr )) return str ;
else if ( trimmedStr === "0" ) return 0 ;
if ( options . unicode ) {
trimmedStr = anynum_anynum ( trimmedStr );
if ( trimmedStr === "0" ) return 0 ; // re-check after normalization
}
if ( options . hex && hexRegex . test ( trimmedStr )) {
return parse_int ( trimmedStr , 16 );
} else if ( options . binary && binRegex . test ( trimmedStr )) {
return parse_int ( trimmedStr , 2 );
} else if ( options . octal && octRegex . test ( trimmedStr )) {
return parse_int ( trimmedStr , 8 );
} else if ( ! isFinite ( trimmedStr )) { //Infinity
return handleInfinity ( str , Number ( trimmedStr ), options );
} else if ( trimmedStr . includes ( 'e' ) || trimmedStr . includes ( 'E' )) { //eNotation
return resolveEnotation ( str , trimmedStr , options );
} else {
//separate negative sign, leading zeros, and rest number
const match = numRegex . exec ( trimmedStr );
// +00.123 => [ , '+', '00', '.123', ..
if ( match ) {
const sign = match [ 1 ] || "" ;
const leadingZeros = match [ 2 ];
let numTrimmedByZeros = trimZeros ( match [ 3 ]); //complete num without leading zeros
const decimalAdjacentToLeadingZeros = sign ? // 0., -00., 000.
str [ leadingZeros . length + 1 ] === "."
: str [ leadingZeros . length ] === "." ;
//trim ending zeros for floating number
if ( ! options . leadingZeros //leading zeros are not allowed
&& ( leadingZeros . length > 1
|| ( leadingZeros . length === 1 && ! decimalAdjacentToLeadingZeros ))) {
// 00, 00.3, +03.24, 03, 03.24
return str ;
}
else { //no leading zeros or leading zeros are allowed
const num = Number ( trimmedStr );
const parsedStr = String ( num );
if ( num === 0 ) return num ;
if ( parsedStr . search ( /[eE]/ ) !== - 1 ) { //given number is long and parsed to eNotation
if ( options . eNotation ) return num ;
else return str ;
} else if ( trimmedStr . indexOf ( "." ) !== - 1 ) { //floating number
if ( parsedStr === "0" ) return num ; //0.0
else if ( parsedStr === numTrimmedByZeros ) return num ; //0.456. 0.79000
else if ( parsedStr === ` ${ sign }${ numTrimmedByZeros } ` ) return num ;
else return str ;
}
let n = leadingZeros ? numTrimmedByZeros : trimmedStr ;
if ( leadingZeros ) {
// -009 => -9
return ( n === parsedStr ) || ( sign + n === parsedStr ) ? num : str
} else {
// +9
return ( n === parsedStr ) || ( n === sign + parsedStr ) ? num : str
}
}
} else { //non-numeric string
return str ;
}
}
}
const eNotationRegx = /^([-+])?(0*)(\d*(\.\d*)?[eE][-\+]?\d+)$/ ;
function resolveEnotation ( str , trimmedStr , options ) {
if ( ! options . eNotation ) return str ;
const notation = trimmedStr . match ( eNotationRegx );
if ( notation ) {
let sign = notation [ 1 ] || "" ;
const eChar = notation [ 3 ]. indexOf ( "e" ) === - 1 ? "E" : "e" ;
const leadingZeros = notation [ 2 ];
const eAdjacentToLeadingZeros = sign ? // 0E.
str [ leadingZeros . length + 1 ] === eChar
: str [ leadingZeros . length ] === eChar ;
if ( leadingZeros . length > 1 && eAdjacentToLeadingZeros ) return str ;
else if ( leadingZeros . length === 1
&& ( notation [ 3 ]. startsWith ( `. ${ eChar } ` ) || notation [ 3 ][ 0 ] === eChar )) {
return Number ( trimmedStr );
} 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 ;
}
}
/**
*
* @param {string} numStr without leading zeros
* @returns
*/
function trimZeros ( numStr ) {
if ( numStr && numStr . indexOf ( "." ) !== - 1 ) { //float
2026-08-24 10:36:54 -04:00
//remove ending zeros without the O(n^2) backtracking that /0+$/ hits
//when the string doesn't end in 0 but has a long internal zero-run
let end = numStr . length ;
while ( end > 0 && numStr . charCodeAt ( end - 1 ) === 48 /* '0' */ ) end -- ;
numStr = numStr . slice ( 0 , end );
2026-07-30 15:09:35 -04:00
if ( numStr === "." ) numStr = "0" ;
else if ( numStr [ 0 ] === "." ) numStr = "0" + numStr ;
else if ( numStr [ numStr . length - 1 ] === "." ) numStr = numStr . substring ( 0 , numStr . length - 1 );
return numStr ;
}
return numStr ;
}
function parse_int ( numStr , base ) {
const str = numStr . trim ();
if ( base === 2 || base === 8 ) numStr = str . substring ( 2 );
if ( parseInt ) return parseInt ( numStr , base );
else if ( Number . parseInt ) return Number . parseInt ( numStr , base );
else if ( window && window . parseInt ) return window . parseInt ( numStr , base );
else throw new Error ( "parseInt, Number.parseInt, window.parseInt are not supported" );
}
/**
* Handle infinite values based on user option
* @param {string} str - original input string
* @param {number} num - parsed number (Infinity or -Infinity)
* @param {object} options - user options
* @returns {string|number|null} based on infinity option
*/
function handleInfinity ( str , num , options ) {
const isPositive = num === Infinity ;
switch ( options . infinity . toLowerCase ()) {
case "null" :
return null ;
case "infinity" :
return num ; // Return Infinity or -Infinity
case "string" :
return isPositive ? "Infinity" : "-Infinity" ;
case "original" :
default :
return str ; // Return original string like "1e1000"
}
}
; // CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/ignoreAttributes.js
function getIgnoreAttributesFn ( ignoreAttributes ) {
if ( typeof ignoreAttributes === 'function' ) {
return ignoreAttributes
}
if ( Array . isArray ( ignoreAttributes )) {
return ( attrName ) => {
for ( const pattern of ignoreAttributes ) {
if ( typeof pattern === 'string' && attrName === pattern ) {
return true
}
if ( pattern instanceof RegExp && pattern . test ( attrName )) {
return true
}
}
}
}
return () => false
}
// EXTERNAL MODULE: ./node_modules/path-expression-matcher/src/Matcher.js
var Matcher = __webpack_require__ ( 8257 );
// EXTERNAL MODULE: ./node_modules/path-expression-matcher/src/Expression.js
var Expression = __webpack_require__ ( 3945 );
; // CONCATENATED MODULE: ./node_modules/path-expression-matcher/src/ExpressionSet.js
/**
* ExpressionSet - An indexed collection of Expressions for efficient bulk matching
*
* Instead of iterating all expressions on every tag, ExpressionSet pre-indexes
* them at insertion time by depth and terminal tag name. At match time, only
* the relevant bucket is evaluated — typically reducing checks from O(E) to O(1)
* lookup plus O(small bucket) matches.
*
* Three buckets are maintained:
* - `_byDepthAndTag` — exact depth + exact tag name (tightest, used first)
* - `_wildcardByDepth` — exact depth + wildcard tag `*` (depth-matched only)
* - `_deepWildcards` — expressions containing `..` (cannot be depth-indexed)
*
* @example
* import { Expression, ExpressionSet } from 'fast-xml-tagger';
*
* // Build once at config time
* const stopNodes = new ExpressionSet();
* stopNodes.add(new Expression('root.users.user'));
* stopNodes.add(new Expression('root.config.setting'));
* stopNodes.add(new Expression('..script'));
*
* // Query on every tag — hot path
* if (stopNodes.matchesAny(matcher)) { ... }
*/
class ExpressionSet {
constructor () {
/** @type {Map<string, import('./Expression.js').default[]>} depth:tag → expressions */
this . _byDepthAndTag = new Map ();
/** @type {Map<number, import('./Expression.js').default[]>} depth → wildcard-tag expressions */
this . _wildcardByDepth = new Map ();
/** @type {import('./Expression.js').default[]} expressions containing deep wildcard (..) */
this . _deepWildcards = [];
/** @type {Map<string, import('./Expression.js').default[]>} terminalTag → deep wildcard expressions */
this . _deepByTerminalTag = new Map ();
/** @type {Set<string>} pattern strings already added — used for deduplication */
this . _patterns = new Set ();
/** @type {boolean} whether the set is sealed against further additions */
this . _sealed = false ;
}
/**
* Add an Expression to the set.
* Duplicate patterns (same pattern string) are silently ignored.
*
* @param {import('./Expression.js').default} expression - A pre-constructed Expression instance
* @returns {this} for chaining
* @throws {TypeError} if called after seal()
*
* @example
* set.add(new Expression('root.users.user'));
* set.add(new Expression('..script'));
*/
add ( expression ) {
if ( this . _sealed ) {
throw new TypeError (
'ExpressionSet is sealed. Create a new ExpressionSet to add more expressions.'
);
}
// Deduplicate by pattern string
if ( this . _patterns . has ( expression . pattern )) return this ;
this . _patterns . add ( expression . pattern );
if ( expression . hasDeepWildcard ()) {
const lastSeg = expression . segments [ expression . segments . length - 1 ];
if ( lastSeg && lastSeg . type !== 'deep-wildcard' && lastSeg . tag !== '*' ) {
const tag = lastSeg . tag ;
if ( ! this . _deepByTerminalTag . has ( tag )) this . _deepByTerminalTag . set ( tag , []);
this . _deepByTerminalTag . get ( tag ). push ( expression );
} else {
this . _deepWildcards . push ( expression );
}
return this ;
}
const depth = expression . length ;
const lastSeg = expression . segments [ expression . segments . length - 1 ];
const tag = lastSeg ? . tag ;
if ( ! tag || tag === '*' ) {
// Can index by depth but not by tag
if ( ! this . _wildcardByDepth . has ( depth )) this . _wildcardByDepth . set ( depth , []);
this . _wildcardByDepth . get ( depth ). push ( expression );
} else {
// Tightest bucket: depth + tag
const key = ` ${ depth } : ${ tag } ` ;
if ( ! this . _byDepthAndTag . has ( key )) this . _byDepthAndTag . set ( key , []);
this . _byDepthAndTag . get ( key ). push ( expression );
}
return this ;
}
/**
* Add multiple expressions at once.
*
* @param {import('./Expression.js').default[]} expressions - Array of Expression instances
* @returns {this} for chaining
*
* @example
* set.addAll([
* new Expression('root.users.user'),
* new Expression('root.config.setting'),
* ]);
*/
addAll ( expressions ) {
for ( const expr of expressions ) this . add ( expr );
return this ;
}
/**
* Check whether a pattern string is already present in the set.
*
* @param {import('./Expression.js').default} expression
* @returns {boolean}
*/
has ( expression ) {
return this . _patterns . has ( expression . pattern );
}
/**
* Number of expressions in the set.
* @type {number}
*/
get size () {
return this . _patterns . size ;
}
/**
* Seal the set against further modifications.
* Useful to prevent accidental mutations after config is built.
* Calling add() or addAll() on a sealed set throws a TypeError.
*
* @returns {this}
*/
seal () {
this . _sealed = true ;
return this ;
}
/**
* Whether the set has been sealed.
* @type {boolean}
*/
get isSealed () {
return this . _sealed ;
}
/**
* Test whether the matcher's current path matches any expression in the set.
*
* Evaluation order (cheapest → most expensive):
* 1. Exact depth + tag bucket — O(1) lookup, typically 0–2 expressions
* 2. Depth-only wildcard bucket — O(1) lookup, rare
* 3. Deep-wildcard list — always checked, but usually small
*
* @param {import('./Matcher.js').default} matcher - Matcher instance (or readOnly view)
* @returns {boolean} true if any expression matches the current path
*
* @example
* if (stopNodes.matchesAny(matcher)) {
* // handle stop node
* }
*/
matchesAny ( matcher ) {
return this . findMatch ( matcher ) !== null ;
}
/**
* Find and return the first Expression that matches the matcher's current path.
*
* Uses the same evaluation order as matchesAny (cheapest → most expensive):
* 1. Exact depth + tag bucket
* 2. Depth-only wildcard bucket
* 3. Deep-wildcard list
*
* @param {import('./Matcher.js').default} matcher - Matcher instance (or readOnly view)
* @returns {import('./Expression.js').default | null} the first matching Expression, or null
*
* @example
* const expr = stopNodes.findMatch(matcher);
* if (expr) {
* // access expr.config, expr.pattern, etc.
* }
*/
findMatch ( matcher ) {
const depth = matcher . getDepth ();
const tag = matcher . getCurrentTag ();
// 1. Tightest bucket — most expressions live here
const exactKey = ` ${ depth } : ${ tag } ` ;
const exactBucket = this . _byDepthAndTag . get ( exactKey );
if ( exactBucket ) {
for ( let i = 0 ; i < exactBucket . length ; i ++ ) {
if ( matcher . matches ( exactBucket [ i ])) return exactBucket [ i ];
}
}
// 2. Depth-matched wildcard-tag expressions
const wildcardBucket = this . _wildcardByDepth . get ( depth );
if ( wildcardBucket ) {
for ( let i = 0 ; i < wildcardBucket . length ; i ++ ) {
if ( matcher . matches ( wildcardBucket [ i ])) return wildcardBucket [ i ];
}
}
// 3. Deep wildcards — indexed by terminal tag, then unindexed fallback
const deepBucket = this . _deepByTerminalTag . get ( tag );
if ( deepBucket ) {
for ( let i = 0 ; i < deepBucket . length ; i ++ ) {
if ( matcher . matches ( deepBucket [ i ])) return deepBucket [ i ];
}
}
for ( let i = 0 ; i < this . _deepWildcards . length ; i ++ ) {
if ( matcher . matches ( this . _deepWildcards [ i ])) return this . _deepWildcards [ i ];
}
return null ;
}
}
; // CONCATENATED MODULE: ./node_modules/@nodable/entities/src/entities.js
// ---------------------------------------------------------------------------
// Complete HTML5 named entity reference
// Organized by logical categories for easy maintenance and selective importing
// ---------------------------------------------------------------------------
/**
* Basic Latin & Special Characters
* @type {Record<string, string>}
*/
const BASIC_LATIN = {
amp : '&' ,
AMP : '&' ,
lt : '<' ,
LT : '<' ,
gt : '>' ,
GT : '>' ,
quot : '"' ,
QUOT : '"' ,
apos : "'" ,
lsquo : '‘' ,
rsquo : '’' ,
ldquo : '“' ,
rdquo : '”' ,
lsquor : '‚' ,
rsquor : '’' ,
ldquor : '„' ,
bdquo : '„' ,
comma : ',' ,
period : '.' ,
colon : ':' ,
semi : ';' ,
excl : '!' ,
quest : '?' ,
num : '#' ,
dollar : '$' ,
percent : '%' ,
ast : '*' ,
commat : '@' ,
lowbar : '_' ,
verbar : '|' ,
vert : '|' ,
sol : '/' ,
bsol : '\\' ,
lbrace : '{' ,
rbrace : '}' ,
lbrack : '[' ,
rbrack : ']' ,
lpar : '(' ,
rpar : ')' ,
nbsp : '\u00a0' ,
iexcl : '¡' ,
cent : '¢' ,
pound : '£' ,
curren : '¤' ,
yen : '¥' ,
brvbar : '¦' ,
sect : '§' ,
uml : '¨' ,
copy : '©' ,
COPY : '©' ,
ordf : 'ª' ,
laquo : '«' ,
not : '¬' ,
shy : '\u00ad' ,
reg : '®' ,
REG : '®' ,
macr : '¯' ,
deg : '°' ,
plusmn : '±' ,
sup2 : '²' ,
sup3 : '³' ,
acute : '´' ,
micro : 'µ' ,
para : '¶' ,
middot : '·' ,
cedil : '¸' ,
sup1 : '¹' ,
ordm : 'º' ,
raquo : '»' ,
frac14 : '¼' ,
frac12 : '½' ,
half : '½' ,
frac34 : '¾' ,
iquest : '¿' ,
times : '×' ,
div : '÷' ,
divide : '÷' ,
};
/**
* Latin Extended & Accented Letters (A-Z)
* @type {Record<string, string>}
*/
const LATIN_ACCENTS = {
Agrave : 'À' ,
agrave : 'à' ,
Aacute : 'Á' ,
aacute : 'á' ,
Acirc : 'Â' ,
acirc : 'â' ,
Atilde : 'Ã' ,
atilde : 'ã' ,
Auml : 'Ä' ,
auml : 'ä' ,
Aring : 'Å' ,
aring : 'å' ,
AElig : 'Æ' ,
aelig : 'æ' ,
Ccedil : 'Ç' ,
ccedil : 'ç' ,
Egrave : 'È' ,
egrave : 'è' ,
Eacute : 'É' ,
eacute : 'é' ,
Ecirc : 'Ê' ,
ecirc : 'ê' ,
Euml : 'Ë' ,
euml : 'ë' ,
Igrave : 'Ì' ,
igrave : 'ì' ,
Iacute : 'Í' ,
iacute : 'í' ,
Icirc : 'Î' ,
icirc : 'î' ,
Iuml : 'Ï' ,
iuml : 'ï' ,
ETH : 'Ð' ,
eth : 'ð' ,
Ntilde : 'Ñ' ,
ntilde : 'ñ' ,
Ograve : 'Ò' ,
ograve : 'ò' ,
Oacute : 'Ó' ,
oacute : 'ó' ,
Ocirc : 'Ô' ,
ocirc : 'ô' ,
Otilde : 'Õ' ,
otilde : 'õ' ,
Ouml : 'Ö' ,
ouml : 'ö' ,
Oslash : 'Ø' ,
oslash : 'ø' ,
Ugrave : 'Ù' ,
ugrave : 'ù' ,
Uacute : 'Ú' ,
uacute : 'ú' ,
Ucirc : 'Û' ,
ucirc : 'û' ,
Uuml : 'Ü' ,
uuml : 'ü' ,
Yacute : 'Ý' ,
yacute : 'ý' ,
THORN : 'Þ' ,
thorn : 'þ' ,
szlig : 'ß' ,
yuml : 'ÿ' ,
Yuml : 'Ÿ' ,
};
/**
* Latin Extended (Letters with diacritics)
* @type {Record<string, string>}
*/
const LATIN_EXTENDED = {
Amacr : 'Ā' ,
amacr : 'ā' ,
Abreve : 'Ă' ,
abreve : 'ă' ,
Aogon : 'Ą' ,
aogon : 'ą' ,
Cacute : 'Ć' ,
cacute : 'ć' ,
Ccirc : 'Ĉ' ,
ccirc : 'ĉ' ,
Cdot : 'Ċ' ,
cdot : 'ċ' ,
Ccaron : 'Č' ,
ccaron : 'č' ,
Dcaron : 'Ď' ,
dcaron : 'ď' ,
Dstrok : 'Đ' ,
dstrok : 'đ' ,
Emacr : 'Ē' ,
emacr : 'ē' ,
Ecaron : 'Ě' ,
ecaron : 'ě' ,
Edot : 'Ė' ,
edot : 'ė' ,
Eogon : 'Ę' ,
eogon : 'ę' ,
Gcirc : 'Ĝ' ,
gcirc : 'ĝ' ,
Gbreve : 'Ğ' ,
gbreve : 'ğ' ,
Gdot : 'Ġ' ,
gdot : 'ġ' ,
Gcedil : 'Ģ' ,
Hcirc : 'Ĥ' ,
hcirc : 'ĥ' ,
Hstrok : 'Ħ' ,
hstrok : 'ħ' ,
Itilde : 'Ĩ' ,
itilde : 'ĩ' ,
Imacr : 'Ī' ,
imacr : 'ī' ,
Iogon : 'Į' ,
iogon : 'į' ,
Idot : 'İ' ,
IJlig : 'IJ' ,
ijlig : 'ij' ,
Jcirc : 'Ĵ' ,
jcirc : 'ĵ' ,
Kcedil : 'Ķ' ,
kcedil : 'ķ' ,
kgreen : 'ĸ' ,
Lacute : 'Ĺ' ,
lacute : 'ĺ' ,
Lcedil : 'Ļ' ,
lcedil : 'ļ' ,
Lcaron : 'Ľ' ,
lcaron : 'ľ' ,
Lmidot : 'Ŀ' ,
lmidot : 'ŀ' ,
Lstrok : 'Ł' ,
lstrok : 'ł' ,
Nacute : 'Ń' ,
nacute : 'ń' ,
Ncaron : 'Ň' ,
ncaron : 'ň' ,
Ncedil : 'Ņ' ,
ncedil : 'ņ' ,
ENG : 'Ŋ' ,
eng : 'ŋ' ,
Omacr : 'Ō' ,
omacr : 'ō' ,
Odblac : 'Ő' ,
odblac : 'ő' ,
OElig : 'Œ' ,
oelig : 'œ' ,
Racute : 'Ŕ' ,
racute : 'ŕ' ,
Rcaron : 'Ř' ,
rcaron : 'ř' ,
Rcedil : 'Ŗ' ,
rcedil : 'ŗ' ,
Sacute : 'Ś' ,
sacute : 'ś' ,
Scirc : 'Ŝ' ,
scirc : 'ŝ' ,
Scedil : 'Ş' ,
scedil : 'ş' ,
Scaron : 'Š' ,
scaron : 'š' ,
Tcedil : 'Ţ' ,
tcedil : 'ţ' ,
Tcaron : 'Ť' ,
tcaron : 'ť' ,
Tstrok : 'Ŧ' ,
tstrok : 'ŧ' ,
Utilde : 'Ũ' ,
utilde : 'ũ' ,
Umacr : 'Ū' ,
umacr : 'ū' ,
Ubreve : 'Ŭ' ,
ubreve : 'ŭ' ,
Uring : 'Ů' ,
uring : 'ů' ,
Udblac : 'Ű' ,
udblac : 'ű' ,
Uogon : 'Ų' ,
uogon : 'ų' ,
Wcirc : 'Ŵ' ,
wcirc : 'ŵ' ,
Ycirc : 'Ŷ' ,
ycirc : 'ŷ' ,
Zacute : 'Ź' ,
zacute : 'ź' ,
Zdot : 'Ż' ,
zdot : 'ż' ,
Zcaron : 'Ž' ,
zcaron : 'ž' ,
};
/**
* Greek Letters
* @type {Record<string, string>}
*/
const GREEK = {
Alpha : 'Α' ,
alpha : 'α' ,
Beta : 'Β' ,
beta : 'β' ,
Gamma : 'Γ' ,
gamma : 'γ' ,
Delta : 'Δ' ,
delta : 'δ' ,
Epsilon : 'Ε' ,
epsilon : 'ε' ,
epsiv : 'ϵ' ,
varepsilon : 'ϵ' ,
Zeta : 'Ζ' ,
zeta : 'ζ' ,
Eta : 'Η' ,
eta : 'η' ,
Theta : 'Θ' ,
theta : 'θ' ,
thetasym : 'ϑ' ,
vartheta : 'ϑ' ,
Iota : 'Ι' ,
iota : 'ι' ,
Kappa : 'Κ' ,
kappa : 'κ' ,
kappav : 'ϰ' ,
varkappa : 'ϰ' ,
Lambda : 'Λ' ,
lambda : 'λ' ,
Mu : 'Μ' ,
mu : 'μ' ,
Nu : 'Ν' ,
nu : 'ν' ,
Xi : 'Ξ' ,
xi : 'ξ' ,
Omicron : 'Ο' ,
omicron : 'ο' ,
Pi : 'Π' ,
pi : 'π' ,
piv : 'ϖ' ,
varpi : 'ϖ' ,
Rho : 'Ρ' ,
rho : 'ρ' ,
rhov : 'ϱ' ,
varrho : 'ϱ' ,
Sigma : 'Σ' ,
sigma : 'σ' ,
sigmaf : 'ς' ,
sigmav : 'ς' ,
varsigma : 'ς' ,
Tau : 'Τ' ,
tau : 'τ' ,
Upsilon : 'Υ' ,
upsilon : 'υ' ,
upsi : 'υ' ,
Upsi : 'ϒ' ,
upsih : 'ϒ' ,
Phi : 'Φ' ,
phi : 'φ' ,
phiv : 'ϕ' ,
varphi : 'ϕ' ,
Chi : 'Χ' ,
chi : 'χ' ,
Psi : 'Ψ' ,
psi : 'ψ' ,
Omega : 'Ω' ,
omega : 'ω' ,
ohm : 'Ω' ,
Gammad : 'Ϝ' ,
gammad : 'ϝ' ,
digamma : 'ϝ' ,
};
/**
* Cyrillic Letters
* @type {Record<string, string>}
*/
const CYRILLIC = {
Afr : '𝔄' ,
afr : '𝔞' ,
Acy : 'А' ,
acy : 'а' ,
Bcy : 'Б' ,
bcy : 'б' ,
Vcy : 'В' ,
vcy : 'в' ,
Gcy : 'Г' ,
gcy : 'г' ,
Dcy : 'Д' ,
dcy : 'д' ,
IEcy : 'Е' ,
iecy : 'е' ,
IOcy : 'Ё' ,
iocy : 'ё' ,
ZHcy : 'Ж' ,
zhcy : 'ж' ,
Zcy : 'З' ,
zcy : 'з' ,
Icy : 'И' ,
icy : 'и' ,
Jcy : 'Й' ,
jcy : 'й' ,
Kcy : 'К' ,
kcy : 'к' ,
Lcy : 'Л' ,
lcy : 'л' ,
Mcy : 'М' ,
mcy : 'м' ,
Ncy : 'Н' ,
ncy : 'н' ,
Ocy : 'О' ,
ocy : 'о' ,
Pcy : 'П' ,
pcy : 'п' ,
Rcy : 'Р' ,
rcy : 'р' ,
Scy : 'С' ,
scy : 'с' ,
Tcy : 'Т' ,
tcy : 'т' ,
Ucy : 'У' ,
ucy : 'у' ,
Fcy : 'Ф' ,
fcy : 'ф' ,
KHcy : 'Х' ,
khcy : 'х' ,
TScy : 'Ц' ,
tscy : 'ц' ,
CHcy : 'Ч' ,
chcy : 'ч' ,
SHcy : 'Ш' ,
shcy : 'ш' ,
SHCHcy : 'Щ' ,
shchcy : 'щ' ,
HARDcy : 'Ъ' ,
hardcy : 'ъ' ,
Ycy : 'Ы' ,
ycy : 'ы' ,
SOFTcy : 'Ь' ,
softcy : 'ь' ,
Ecy : 'Э' ,
ecy : 'э' ,
YUcy : 'Ю' ,
yucy : 'ю' ,
YAcy : 'Я' ,
yacy : 'я' ,
DJcy : 'Ђ' ,
djcy : 'ђ' ,
GJcy : 'Ѓ' ,
gjcy : 'ѓ' ,
Jukcy : 'Є' ,
jukcy : 'є' ,
DScy : 'Ѕ' ,
dscy : 'ѕ' ,
Iukcy : 'І' ,
iukcy : 'і' ,
YIcy : 'Ї' ,
yicy : 'ї' ,
Jsercy : 'Ј' ,
jsercy : 'ј' ,
LJcy : 'Љ' ,
ljcy : 'љ' ,
NJcy : 'Њ' ,
njcy : 'њ' ,
TSHcy : 'Ћ' ,
tshcy : 'ћ' ,
KJcy : 'Ќ' ,
kjcy : 'ќ' ,
Ubrcy : 'Ў' ,
ubrcy : 'ў' ,
DZcy : 'Џ' ,
dzcy : 'џ' ,
};
/**
* Mathematical Operators & Relations
* @type {Record<string, string>}
*/
const MATH = {
plus : '+' ,
pm : '±' ,
times : '×' ,
div : '÷' ,
divide : '÷' ,
sdot : '⋅' ,
star : '☆' ,
starf : '★' ,
bigstar : '★' ,
lowast : '∗' ,
ast : '*' ,
midast : '*' ,
compfn : '∘' ,
smallcircle : '∘' ,
bullet : '•' ,
bull : '•' ,
nbsp : '\u00a0' ,
hellip : '…' ,
mldr : '…' ,
prime : '′' ,
Prime : '″' ,
tprime : '‴' ,
bprime : '‵' ,
backprime : '‵' ,
minus : '−' ,
minusd : '∸' ,
dotminus : '∸' ,
plusdo : '∔' ,
dotplus : '∔' ,
plusmn : '±' ,
minusplus : '∓' ,
mnplus : '∓' ,
mp : '∓' ,
setminus : '∖' ,
smallsetminus : '∖' ,
Backslash : '∖' ,
setmn : '∖' ,
ssetmn : '∖' ,
lowbar : '_' ,
verbar : '|' ,
vert : '|' ,
VerticalLine : '|' ,
colon : ':' ,
Colon : '∷' ,
Proportion : '∷' ,
ratio : '∶' ,
equals : '=' ,
ne : '≠' ,
nequiv : '≢' ,
equiv : '≡' ,
Congruent : '≡' ,
sim : '∼' ,
thicksim : '∼' ,
thksim : '∼' ,
sime : '≃' ,
simeq : '≃' ,
TildeEqual : '≃' ,
asymp : '≈' ,
approx : '≈' ,
thickapprox : '≈' ,
thkap : '≈' ,
TildeTilde : '≈' ,
ncong : '≇' ,
cong : '≅' ,
TildeFullEqual : '≅' ,
asympeq : '≍' ,
CupCap : '≍' ,
bump : '≎' ,
Bumpeq : '≎' ,
HumpDownHump : '≎' ,
bumpe : '≏' ,
bumpeq : '≏' ,
HumpEqual : '≏' ,
le : '≤' ,
LessEqual : '≤' ,
ge : '≥' ,
GreaterEqual : '≥' ,
lesseqgtr : '⋚' ,
lesseqqgtr : '⪋' ,
greater : '>' ,
less : '<' ,
};
/**
* Mathematical Operators (Advanced)
* @type {Record<string, string>}
*/
const MATH_ADVANCED = {
alefsym : 'ℵ' ,
aleph : 'ℵ' ,
beth : 'ℶ' ,
gimel : 'ℷ' ,
daleth : 'ℸ' ,
forall : '∀' ,
ForAll : '∀' ,
part : '∂' ,
PartialD : '∂' ,
exist : '∃' ,
Exists : '∃' ,
nexist : '∄' ,
nexists : '∄' ,
empty : '∅' ,
emptyset : '∅' ,
emptyv : '∅' ,
varnothing : '∅' ,
nabla : '∇' ,
Del : '∇' ,
isin : '∈' ,
isinv : '∈' ,
in : '∈' ,
Element : '∈' ,
notin : '∉' ,
notinva : '∉' ,
ni : '∋' ,
niv : '∋' ,
SuchThat : '∋' ,
ReverseElement : '∋' ,
notni : '∌' ,
notniva : '∌' ,
prod : '∏' ,
Product : '∏' ,
coprod : '∐' ,
Coproduct : '∐' ,
sum : '∑' ,
Sum : '∑' ,
minus : '−' ,
mp : '∓' ,
plusdo : '∔' ,
dotplus : '∔' ,
setminus : '∖' ,
lowast : '∗' ,
radic : '√' ,
Sqrt : '√' ,
prop : '∝' ,
propto : '∝' ,
Proportional : '∝' ,
varpropto : '∝' ,
infin : '∞' ,
infintie : '⧝' ,
ang : '∠' ,
angle : '∠' ,
angmsd : '∡' ,
measuredangle : '∡' ,
angsph : '∢' ,
mid : '∣' ,
VerticalBar : '∣' ,
nmid : '∤' ,
nsmid : '∤' ,
npar : '∦' ,
parallel : '∥' ,
spar : '∥' ,
nparallel : '∦' ,
nspar : '∦' ,
and : '∧' ,
wedge : '∧' ,
or : '∨' ,
vee : '∨' ,
cap : '∩' ,
cup : '∪' ,
int : '∫' ,
Integral : '∫' ,
conint : '∮' ,
ContourIntegral : '∮' ,
Conint : '∯' ,
DoubleContourIntegral : '∯' ,
Cconint : '∰' ,
there4 : '∴' ,
therefore : '∴' ,
Therefore : '∴' ,
becaus : '∵' ,
because : '∵' ,
Because : '∵' ,
ratio : '∶' ,
Proportion : '∷' ,
minusd : '∸' ,
dotminus : '∸' ,
mDDot : '∺' ,
homtht : '∻' ,
sim : '∼' ,
bsimg : '∽' ,
backsim : '∽' ,
ac : '∾' ,
mstpos : '∾' ,
acd : '∿' ,
VerticalTilde : '≀' ,
wr : '≀' ,
wreath : '≀' ,
nsime : '≄' ,
nsimeq : '≄' ,
ncong : '≇' ,
simne : '≆' ,
ncongdot : '⩭̸' ,
ngsim : '≵' ,
nsim : '≁' ,
napprox : '≉' ,
nap : '≉' ,
ngeq : '≱' ,
nge : '≱' ,
nleq : '≰' ,
nle : '≰' ,
ngtr : '≯' ,
ngt : '≯' ,
nless : '≮' ,
nlt : '≮' ,
nprec : '⊀' ,
npr : '⊀' ,
nsucc : '⊁' ,
nsc : '⊁' ,
};
/**
* Arrows
* @type {Record<string, string>}
*/
const ARROWS = {
larr : '←' ,
leftarrow : '←' ,
LeftArrow : '←' ,
uarr : '↑' ,
uparrow : '↑' ,
UpArrow : '↑' ,
rarr : '→' ,
rightarrow : '→' ,
RightArrow : '→' ,
darr : '↓' ,
downarrow : '↓' ,
DownArrow : '↓' ,
harr : '↔' ,
leftrightarrow : '↔' ,
LeftRightArrow : '↔' ,
varr : '↕' ,
updownarrow : '↕' ,
UpDownArrow : '↕' ,
nwarr : '↖' ,
nwarrow : '↖' ,
UpperLeftArrow : '↖' ,
nearr : '↗' ,
nearrow : '↗' ,
UpperRightArrow : '↗' ,
searr : '↘' ,
searrow : '↘' ,
LowerRightArrow : '↘' ,
swarr : '↙' ,
swarrow : '↙' ,
LowerLeftArrow : '↙' ,
lArr : '⇐' ,
Leftarrow : '⇐' ,
uArr : '⇑' ,
Uparrow : '⇑' ,
rArr : '⇒' ,
Rightarrow : '⇒' ,
dArr : '⇓' ,
Downarrow : '⇓' ,
hArr : '⇔' ,
Leftrightarrow : '⇔' ,
iff : '⇔' ,
vArr : '⇕' ,
Updownarrow : '⇕' ,
lAarr : '⇚' ,
Lleftarrow : '⇚' ,
rAarr : '⇛' ,
Rrightarrow : '⇛' ,
lrarr : '⇆' ,
leftrightarrows : '⇆' ,
rlarr : '⇄' ,
rightleftarrows : '⇄' ,
lrhar : '⇋' ,
leftrightharpoons : '⇋' ,
ReverseEquilibrium : '⇋' ,
rlhar : '⇌' ,
rightleftharpoons : '⇌' ,
Equilibrium : '⇌' ,
udarr : '⇅' ,
UpArrowDownArrow : '⇅' ,
duarr : '⇵' ,
DownArrowUpArrow : '⇵' ,
llarr : '⇇' ,
leftleftarrows : '⇇' ,
rrarr : '⇉' ,
rightrightarrows : '⇉' ,
ddarr : '⇊' ,
downdownarrows : '⇊' ,
har : '↽' ,
lhard : '↽' ,
leftharpoondown : '↽' ,
lharu : '↼' ,
leftharpoonup : '↼' ,
rhard : '⇁' ,
rightharpoondown : '⇁' ,
rharu : '⇀' ,
rightharpoonup : '⇀' ,
lsh : '↰' ,
Lsh : '↰' ,
rsh : '↱' ,
Rsh : '↱' ,
ldsh : '↲' ,
rdsh : '↳' ,
hookleftarrow : '↩' ,
hookrightarrow : '↪' ,
mapstoleft : '↤' ,
mapstoup : '↥' ,
map : '↦' ,
mapsto : '↦' ,
mapstodown : '↧' ,
crarr : '↵' ,
nleftarrow : '↚' ,
nleftrightarrow : '↮' ,
nrightarrow : '↛' ,
nrarr : '↛' ,
larrtl : '↢' ,
rarrtl : '↣' ,
leftarrowtail : '↢' ,
rightarrowtail : '↣' ,
twoheadleftarrow : '↞' ,
twoheadrightarrow : '↠' ,
Larr : '↞' ,
Rarr : '↠' ,
larrhk : '↩' ,
rarrhk : '↪' ,
larrlp : '↫' ,
looparrowleft : '↫' ,
rarrlp : '↬' ,
looparrowright : '↬' ,
harrw : '↭' ,
leftrightsquigarrow : '↭' ,
nrarrw : '↝̸' ,
rarrw : '↝' ,
rightsquigarrow : '↝' ,
larrbfs : '⤟' ,
rarrbfs : '⤠' ,
nvHarr : '⤄' ,
nvlArr : '⤂' ,
nvrArr : '⤃' ,
larrfs : '⤝' ,
rarrfs : '⤞' ,
Map : '⤅' ,
larrsim : '⥳' ,
rarrsim : '⥴' ,
harrcir : '⥈' ,
Uarrocir : '⥉' ,
lurdshar : '⥊' ,
ldrdhar : '⥧' ,
ldrushar : '⥋' ,
rdldhar : '⥩' ,
lrhard : '⥭' ,
uharr : '↾' ,
uharl : '↿' ,
dharr : '⇂' ,
dharl : '⇃' ,
Uarr : '↟' ,
Darr : '↡' ,
zigrarr : '⇝' ,
nwArr : '⇖' ,
neArr : '⇗' ,
seArr : '⇘' ,
swArr : '⇙' ,
nharr : '↮' ,
nhArr : '⇎' ,
nlarr : '↚' ,
nlArr : '⇍' ,
nrArr : '⇏' ,
larrb : '⇤' ,
LeftArrowBar : '⇤' ,
rarrb : '⇥' ,
RightArrowBar : '⇥' ,
};
/**
* Geometric Shapes
* @type {Record<string, string>}
*/
const SHAPES = {
square : '□' ,
Square : '□' ,
squ : '□' ,
squf : '▪' ,
squarf : '▪' ,
blacksquar : '▪' ,
blacksquare : '▪' ,
FilledVerySmallSquare : '▪' ,
blk34 : '▓' ,
blk12 : '▒' ,
blk14 : '░' ,
block : '█' ,
srect : '▭' ,
rect : '▭' ,
sdot : '⋅' ,
sdotb : '⊡' ,
dotsquare : '⊡' ,
triangle : '▵' ,
tri : '▵' ,
trine : '▵' ,
utri : '▵' ,
triangledown : '▿' ,
dtri : '▿' ,
tridown : '▿' ,
triangleleft : '◃' ,
ltri : '◃' ,
triangleright : '▹' ,
rtri : '▹' ,
blacktriangle : '▴' ,
utrif : '▴' ,
blacktriangledown : '▾' ,
dtrif : '▾' ,
blacktriangleleft : '◂' ,
ltrif : '◂' ,
blacktriangleright : '▸' ,
rtrif : '▸' ,
loz : '◊' ,
lozenge : '◊' ,
blacklozenge : '⧫' ,
lozf : '⧫' ,
bigcirc : '◯' ,
xcirc : '◯' ,
circ : 'ˆ' ,
Circle : '○' ,
cir : '○' ,
o : '○' ,
bullet : '•' ,
bull : '•' ,
hellip : '…' ,
mldr : '…' ,
nldr : '‥' ,
boxh : '─' ,
HorizontalLine : '─' ,
boxv : '│' ,
boxdr : '┌' ,
boxdl : '┐' ,
boxur : '└' ,
boxul : '┘' ,
boxvr : '├' ,
boxvl : '┤' ,
boxhd : '┬' ,
boxhu : '┴' ,
boxvh : '┼' ,
boxH : '═' ,
boxV : '║' ,
boxdR : '╒' ,
boxDr : '╓' ,
boxDR : '╔' ,
boxDl : '╕' ,
boxdL : '╖' ,
boxDL : '╗' ,
boxuR : '╘' ,
boxUr : '╙' ,
boxUR : '╚' ,
boxUl : '╜' ,
boxuL : '╛' ,
boxUL : '╝' ,
boxvR : '╞' ,
boxVr : '╟' ,
boxVR : '╠' ,
boxVl : '╢' ,
boxvL : '╡' ,
boxVL : '╣' ,
boxHd : '╤' ,
boxhD : '╥' ,
boxHD : '╦' ,
boxHu : '╧' ,
boxhU : '╨' ,
boxHU : '╩' ,
boxvH : '╪' ,
boxVh : '╫' ,
boxVH : '╬' ,
};
/**
* Punctuation & Diacritics
* @type {Record<string, string>}
*/
const PUNCTUATION = {
excl : '!' ,
iexcl : '¡' ,
brvbar : '¦' ,
sect : '§' ,
uml : '¨' ,
copy : '©' ,
ordf : 'ª' ,
laquo : '«' ,
not : '¬' ,
shy : '\u00ad' ,
reg : '®' ,
macr : '¯' ,
deg : '°' ,
plusmn : '±' ,
sup2 : '²' ,
sup3 : '³' ,
acute : '´' ,
micro : 'µ' ,
para : '¶' ,
middot : '·' ,
cedil : '¸' ,
sup1 : '¹' ,
ordm : 'º' ,
raquo : '»' ,
frac14 : '¼' ,
frac12 : '½' ,
frac34 : '¾' ,
iquest : '¿' ,
nbsp : '\u00a0' ,
comma : ',' ,
period : '.' ,
colon : ':' ,
semi : ';' ,
vert : '|' ,
Verbar : '‖' ,
verbar : '|' ,
dblac : '˝' ,
circ : 'ˆ' ,
caron : 'ˇ' ,
breve : '˘' ,
dot : '˙' ,
ring : '˚' ,
ogon : '˛' ,
tilde : '˜' ,
DiacriticalGrave : '`' ,
DiacriticalAcute : '´' ,
DiacriticalTilde : '˜' ,
DiacriticalDot : '˙' ,
DiacriticalDoubleAcute : '˝' ,
grave : '`' ,
};
/**
* Currency Symbols
* @type {Record<string, string>}
*/
const CURRENCY = {
cent : '¢' ,
pound : '£' ,
curren : '¤' ,
yen : '¥' ,
euro : '€' ,
dollar : '$' ,
fnof : 'ƒ' ,
inr : '₹' ,
af : '؋' ,
birr : 'ብር' ,
peso : '₱' ,
rub : '₽' ,
won : '₩' ,
yuan : '¥' ,
cedil : '¸' ,
};
/**
* Fractions
* @type {Record<string, string>}
*/
const FRACTIONS = {
frac12 : '½' ,
half : '½' ,
frac13 : '⅓' ,
frac14 : '¼' ,
frac15 : '⅕' ,
frac16 : '⅙' ,
frac18 : '⅛' ,
frac23 : '⅔' ,
frac25 : '⅖' ,
frac34 : '¾' ,
frac35 : '⅗' ,
frac38 : '⅜' ,
frac45 : '⅘' ,
frac56 : '⅚' ,
frac58 : '⅝' ,
frac78 : '⅞' ,
frasl : '⁄' ,
};
/**
* Miscellaneous Symbols
* @type {Record<string, string>}
*/
const MISC_SYMBOLS = {
trade : '™' ,
TRADE : '™' ,
telrec : '⌕' ,
target : '⌖' ,
ulcorn : '⌜' ,
ulcorner : '⌜' ,
urcorn : '⌝' ,
urcorner : '⌝' ,
dlcorn : '⌞' ,
llcorner : '⌞' ,
drcorn : '⌟' ,
lrcorner : '⌟' ,
intercal : '⊺' ,
intcal : '⊺' ,
oplus : '⊕' ,
CirclePlus : '⊕' ,
ominus : '⊖' ,
CircleMinus : '⊖' ,
otimes : '⊗' ,
CircleTimes : '⊗' ,
osol : '⊘' ,
odot : '⊙' ,
CircleDot : '⊙' ,
oast : '⊛' ,
circledast : '⊛' ,
odash : '⊝' ,
circleddash : '⊝' ,
ocirc : '⊚' ,
circledcirc : '⊚' ,
boxplus : '⊞' ,
plusb : '⊞' ,
boxminus : '⊟' ,
minusb : '⊟' ,
boxtimes : '⊠' ,
timesb : '⊠' ,
boxdot : '⊡' ,
sdotb : '⊡' ,
veebar : '⊻' ,
vee : '∨' ,
barvee : '⊽' ,
and : '∧' ,
wedge : '∧' ,
Cap : '⋒' ,
Cup : '⋓' ,
Fork : '⋔' ,
pitchfork : '⋔' ,
epar : '⋕' ,
ltlarr : '⥶' ,
nvap : '≍⃒' ,
nvsim : '∼⃒' ,
nvge : '≥⃒' ,
nvle : '≤⃒' ,
nvlt : '<⃒' ,
nvgt : '>⃒' ,
nvltrie : '⊴⃒' ,
nvrtrie : '⊵⃒' ,
Vdash : '⊩' ,
dashv : '⊣' ,
vDash : '⊨' ,
Vvdash : '⊪' ,
nvdash : '⊬' ,
nvDash : '⊭' ,
nVdash : '⊮' ,
nVDash : '⊯' ,
};
const XML = {
amp : "&" ,
apos : "'" ,
gt : ">" ,
lt : "<" ,
quot : "\""
}
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' ,
}
// ---------------------------------------------------------------------------
// Note: NUMERIC_ENTITIES (&#NNN; / &#xHH;) are handled by the scanner directly
// via String.fromCodePoint() without any map lookup.
// ---------------------------------------------------------------------------
; // CONCATENATED MODULE: ./node_modules/@nodable/entities/src/EntityDecoder.js
// ---------------------------------------------------------------------------
// Built-in named entity map (name → replacement string)
// No regex, no {regex,val} objects — just flat key/value pairs.
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Entity hook action constants
// ---------------------------------------------------------------------------
/**
* Action constants for `onExternalEntity` and `onInputEntity` hooks.
*
* Use these instead of raw strings to avoid typos:
*
* @example
* import EntityDecoder, { ENTITY_ACTION } from './EntityDecoder.js';
* const dec = new EntityDecoder({
* onInputEntity: (name, value) => ENTITY_ACTION.BLOCK,
* });
*/
const ENTITY_ACTION = Object . freeze ({
/** Resolve and expand the entity normally. */
ALLOW : 'allow' ,
/** Silently skip this entity — it will not be registered. */
BLOCK : 'block' ,
/** Throw an error, aborting entity registration entirely. */
THROW : 'throw' ,
});
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const SPECIAL_CHARS = new Set ( '!?\\\\/[]$%{}^&*()<>|+' );
/**
* Validate that an entity name contains no dangerous characters.
* @param {string} name
* @returns {string} the name, unchanged
* @throws {Error} on invalid characters
*/
function EntityDecoder_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 ;
}
/**
* Merge one or more entity maps into a flat name→string map.
* Accepts either:
* - plain string values: { amp: '&' }
* - legacy {regex,val} / {regx,val}: { lt: { regex: /.../, val: '<' } }
*
* Values containing '&' are skipped (recursive expansion risk).
*
* @param {...object} maps
* @returns {Record<string, string>}
*/
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 ) {
// Legacy {regex,val} or {regx,val} — extract the string val only
const val = raw . val ;
if ( typeof val === 'string' ) {
out [ key ] = val ;
}
// function vals are not supported in the scanner — skip
}
}
}
return out ;
}
// ---------------------------------------------------------------------------
// applyLimitsTo helpers
// ---------------------------------------------------------------------------
const LIMIT_TIER_EXTERNAL = 'external' ; // input/runtime + persistent external maps
const LIMIT_TIER_BASE = 'base' ; // DEFAULT_XML_ENTITIES + namedEntities (system) maps
const LIMIT_TIER_ALL = 'all' ; // every entity regardless of tier
/**
* Resolve `applyLimitsTo` option into a normalised Set of tier strings.
* Accepted values: 'external' | 'base' | 'all' | string[]
* Default: 'external' (only untrusted injected entities are counted).
* @param {string|string[]|undefined} raw
* @returns {Set<string>}
*/
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 ]); // safe default for unrecognised values
}
// ---------------------------------------------------------------------------
// NCR (Numeric Character Reference) classification
// ---------------------------------------------------------------------------
// Severity order — higher number = stricter action.
// Used to enforce minimum action levels for specific codepoint ranges.
const NCR_LEVEL = Object . freeze ({ allow : 0 , leave : 1 , remove : 2 , throw : 3 });
// XML 1.0 §2.2: allowed chars are #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
// Restricted C0: U+0001–U+001F excluding U+0009, U+000A, U+000D
const XML10_ALLOWED_C0 = new Set ([ 0x09 , 0x0A , 0x0D ]);
/**
* Parse the `ncr` constructor option into flat, hot-path-friendly fields.
* @param {object|undefined} ncr
* @returns {{ xmlVersion: number, onLevel: number, nullLevel: number }}
*/
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 ] ?? NCR_LEVEL . allow ;
const nullLevel = NCR_LEVEL [ ncr . nullNCR ] ?? NCR_LEVEL . remove ;
// 'allow' is not meaningful for null — clamp to at least 'remove'
const clampedNull = Math . max ( nullLevel , NCR_LEVEL . remove );
return { xmlVersion , onLevel , nullLevel : clampedNull };
}
// ---------------------------------------------------------------------------
// EntityReplacer
// ---------------------------------------------------------------------------
/**
* Single-pass, zero-regex entity replacer for XML/HTML content.
*
* Algorithm: scan the string once for '&', read to ';', resolve via map
* or direct codepoint conversion, build output chunks, join once at the end.
*
* Entity lookup priority (highest → lowest):
* 1. input / runtime (DOCTYPE entities for current document)
* 2. persistent external (survive across documents)
* 3. base named map (DEFAULT_XML_ENTITIES + user-supplied namedEntities)
*
* Both input and external resolve as the 'external' tier for limit purposes.
* Base map entities resolve as the 'base' tier.
*
* Numeric / hex references (&#NNN; / &#xHH;) are resolved directly via
* String.fromCodePoint() — no map needed. They count as 'base' tier.
*
* @example
* const replacer = new EntityReplacer({ namedEntities: COMMON_HTML });
* replacer.setExternalEntities({ brand: 'Acme' });
*
* const instance = replacer.reset();
* instance.addInputEntities({ version: '1.0' });
* instance.encode('&brand; v&version; <'); // 'Acme v1.0 <'
*/
class EntityDecoder {
/**
* @param {object} [options]
* @param {object|null} [options.namedEntities] — extra named entities merged into base map
* @param {object} [options.limit] — security limits
* @param {number} [options.limit.maxTotalExpansions=0] — 0 = unlimited
* @param {number} [options.limit.maxExpandedLength=0] — 0 = unlimited
* @param {'external'|'base'|'all'|string[]} [options.limit.applyLimitsTo='external']
* Which entity tiers count against the security limits:
* - 'external' (default) — only input/runtime + persistent external entities
* - 'base' — only DEFAULT_XML_ENTITIES + namedEntities
* - 'all' — every entity regardless of tier
* - string[] — explicit combination, e.g. ['external', 'base']
* @param {((resolved: string, original: string) => string)|null} [options.postCheck=null]
* @param {string[]} [options.remove=[]] — entity names (e.g. ['nbsp', '#13']) to delete (replace with empty string)
* @param {string[]} [options.leave=[]] — entity names to keep as literal (unchanged in output)
* @param {object} [options.ncr] — Numeric Character Reference controls
* @param {1.0|1.1} [options.ncr.xmlVersion=1.0]
* XML version governing which codepoint ranges are restricted:
* - 1.0 — C0 controls U+0001–U+001F (except U+0009/000A/000D) are prohibited
* - 1.1 — C0 controls are allowed when written as NCRs; C1 (U+007F–U+009F) decoded as-is
* @param {'allow'|'leave'|'remove'|'throw'} [options.ncr.onNCR='allow']
* Base action for numeric references. Severity order: allow < leave < remove < throw.
* For codepoint ranges that carry a minimum level (surrogates → remove, XML 1.0 C0 → remove),
* the effective action is max(onNCR, rangeMinimum).
* @param {'remove'|'throw'} [options.ncr.nullNCR='remove']
* Action for U+0000 (null). 'allow' and 'leave' are clamped to 'remove' since null is never safe.
* @param {((name: string, value: string) => 'allow'|'block'|'throw')|null} [options.onExternalEntity=null]
* Hook called when an external entity is registered via `setExternalEntities()` or
* `addExternalEntity()`. Return `ENTITY_ACTION.ALLOW` to accept the entity,
* `ENTITY_ACTION.BLOCK` to silently skip it, or `ENTITY_ACTION.THROW` to abort with an error.
* @param {((name: string, value: string) => 'allow'|'block'|'throw')|null} [options.onInputEntity=null]
* Hook called when an input entity is registered via `addInputEntities()`. Return
* `ENTITY_ACTION.ALLOW` to accept, `ENTITY_ACTION.BLOCK` to silently skip, or
* `ENTITY_ACTION.THROW` to abort with an error.
*/
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 ;
// Base map: DEFAULT_XML_ENTITIES + user-supplied extras. Immutable after construction.
this . _baseMap = mergeEntityMaps ( XML , options . namedEntities || null );
// Persistent external entities — survive across documents.
// Stored as a separate map so reset() never touches them.
/** @type {Record<string, string>} */
this . _externalMap = Object . create ( null );
// Input / runtime entities — current document only, wiped on reset().
/** @type {Record<string, string>} */
this . _inputMap = Object . create ( null );
// Per-document counters
this . _totalExpansions = 0 ;
this . _expandedLength = 0 ;
// --- New: remove / leave sets ---
/** @type {Set<string>} */
this . _removeSet = new Set ( options . remove && Array . isArray ( options . remove ) ? options . remove : []);
/** @type {Set<string>} */
this . _leaveSet = new Set ( options . leave && Array . isArray ( options . leave ) ? options . leave : []);
// --- NCR config (parsed into flat fields for hot-path speed) ---
const ncrCfg = parseNCRConfig ( options . ncr );
this . _ncrXmlVersion = ncrCfg . xmlVersion ;
this . _ncrOnLevel = ncrCfg . onLevel ;
this . _ncrNullLevel = ncrCfg . nullLevel ;
// --- Registration hooks ---
/** @type {((name: string, value: string) => 'allow'|'block'|'throw')|null} */
this . _onExternalEntity = typeof options . onExternalEntity === 'function'
? options . onExternalEntity
: null ;
/** @type {((name: string, value: string) => 'allow'|'block'|'throw')|null} */
this . _onInputEntity = typeof options . onInputEntity === 'function'
? options . onInputEntity
: null ;
}
// -------------------------------------------------------------------------
// Private: registration hook dispatch
// -------------------------------------------------------------------------
/**
* Invoke a registration hook for a single entity name/value pair.
* Returns true when the entity should be accepted, false when it should be
* silently skipped (BLOCK), and throws when the hook returns THROW.
*
* @param {((name: string, value: string) => 'allow'|'block'|'throw')|null} hook
* @param {string} name
* @param {string} value
* @param {string} context — used in error messages ('external' | 'input')
* @returns {boolean} true = accept, false = skip
*/
_applyRegistrationHook ( hook , name , value , context ) {
if ( ! hook ) return true ; // no hook → always accept
const action = hook ( name , value );
if ( action === ENTITY_ACTION . BLOCK ) return false ;
if ( action === ENTITY_ACTION . THROW ) {
throw new Error (
`[EntityDecoder] Registration of ${ context } entity "& ${ name } ;" was rejected by hook`
);
}
return true ; // ALLOW or any unknown return value → accept
}
// -------------------------------------------------------------------------
// Persistent external entity registration
// -------------------------------------------------------------------------
/**
* Replace the full set of persistent external entities.
* All keys are validated — throws on invalid characters.
* If `onExternalEntity` is set, it is called once per entry; entries that
* return `ENTITY_ACTION.BLOCK` are silently omitted, `ENTITY_ACTION.THROW`
* aborts the whole call.
* @param {Record<string, string | { regex?: RegExp, val: string }>} map
*/
setExternalEntities ( map ) {
if ( map ) {
for ( const key of Object . keys ( map )) {
EntityDecoder_validateEntityName ( key );
}
}
if ( ! this . _onExternalEntity ) {
this . _externalMap = mergeEntityMaps ( map );
return ;
}
// Hook present — resolve values first, then filter
const flat = mergeEntityMaps ( map );
const filtered = Object . create ( null );
for ( const [ name , value ] of Object . entries ( flat )) {
if ( this . _applyRegistrationHook ( this . _onExternalEntity , name , value , 'external' )) {
filtered [ name ] = value ;
}
}
this . _externalMap = filtered ;
}
/**
* Add a single persistent external entity.
* If `onExternalEntity` is set it is called before the entity is stored;
* `ENTITY_ACTION.BLOCK` silently skips storage, `ENTITY_ACTION.THROW` raises.
* @param {string} key
* @param {string} value
*/
addExternalEntity ( key , value ) {
EntityDecoder_validateEntityName ( key );
if ( typeof value === 'string' && value . indexOf ( '&' ) === - 1 ) {
if ( this . _applyRegistrationHook ( this . _onExternalEntity , key , value , 'external' )) {
this . _externalMap [ key ] = value ;
}
}
}
// -------------------------------------------------------------------------
// Input / runtime entity registration (per document)
// -------------------------------------------------------------------------
/**
* Inject DOCTYPE entities for the current document.
* Also resets per-document expansion counters.
* If `onInputEntity` is set it is called once per entry; entries returning
* `ENTITY_ACTION.BLOCK` are silently omitted, `ENTITY_ACTION.THROW` aborts.
* @param {Record<string, string | { regx?: RegExp, regex?: RegExp, val: string }>} map
*/
addInputEntities ( map ) {
this . _totalExpansions = 0 ;
this . _expandedLength = 0 ;
if ( ! this . _onInputEntity ) {
this . _inputMap = mergeEntityMaps ( map );
return ;
}
const flat = mergeEntityMaps ( map );
const filtered = Object . create ( null );
for ( const [ name , value ] of Object . entries ( flat )) {
if ( this . _applyRegistrationHook ( this . _onInputEntity , name , value , 'input' )) {
filtered [ name ] = value ;
}
}
this . _inputMap = filtered ;
}
// -------------------------------------------------------------------------
// Per-document reset
// -------------------------------------------------------------------------
/**
* Wipe input/runtime entities and reset counters.
* Call this before processing each new document.
* @returns {this}
*/
reset () {
this . _inputMap = Object . create ( null );
this . _totalExpansions = 0 ;
this . _expandedLength = 0 ;
return this ;
}
// -------------------------------------------------------------------------
// XML version (can be set after construction, e.g. once parser reads <?xml?>)
// -------------------------------------------------------------------------
/**
* Update the XML version used for NCR classification.
* Call this as soon as the document's `<?xml version="...">` declaration is parsed.
* @param {1.0|1.1|number} version
*/
setXmlVersion ( version ) {
this . _ncrXmlVersion = version === 1.1 ? 1.1 : 1.0 ;
}
// -------------------------------------------------------------------------
// Primary API
// -------------------------------------------------------------------------
/**
* Replace all entity references in `str` in a single pass.
*
* @param {string} str
* @returns {string}
*/
decode ( str ) {
if ( typeof str !== 'string' || str . length === 0 ) return str ;
//TODO: check if needed
if ( str . indexOf ( '&' ) === - 1 ) return str ; // fast path — no entities at all
const original = str ;
const chunks = [];
const len = str . length ;
let last = 0 ; // start of next unprocessed literal chunk
let i = 0 ;
const limitExpansions = this . _maxTotalExpansions > 0 ;
const limitLength = this . _maxExpandedLength > 0 ;
const checkLimits = limitExpansions || limitLength ;
while ( i < len ) {
// Scan forward to next '&'
if ( str . charCodeAt ( i ) !== 38 /* '&' */ ) { i ++ ; continue ; }
// --- Found '&' at position i ---
// Scan forward to ';'
let j = i + 1 ;
while ( j < len && str . charCodeAt ( j ) !== 59 /* ';' */ && ( j - i ) <= 32 ) j ++ ;
if ( j >= len || str . charCodeAt ( j ) !== 59 ) {
// No closing ';' within window — treat '&' as literal
i ++ ;
continue ;
}
// Raw token between '&' and ';' (exclusive)
const token = str . slice ( i + 1 , j );
if ( token . length === 0 ) { i ++ ; continue ; }
let replacement ;
let tier ; // which limit tier this entity belongs to
if ( this . _removeSet . has ( token )) {
// Remove entity: replace with empty string
replacement = '' ;
// If entity was unknown (replacement undefined), we still need a tier for limits.
// Treat as external tier because it's user-directed removal of an unknown reference.
if ( tier === undefined ) {
tier = LIMIT_TIER_EXTERNAL ;
}
} else if ( this . _leaveSet . has ( token )) {
// Do not replace — keep original &token; as literal
i ++ ;
continue ;
} else if ( token . charCodeAt ( 0 ) === 35 /* '#' */ ) {
// ---- Numeric / NCR reference ----
// NCR classification always runs first — prohibited codepoints must be
// caught regardless of numericAllowed.
const ncrResult = this . _resolveNCR ( token );
if ( ncrResult === undefined ) {
// 'leave' action — keep original &token; as-is
i ++ ;
continue ;
}
replacement = ncrResult ; // '' for remove, char string for allow
tier = LIMIT_TIER_BASE ;
} else {
// ---- Named reference ----
const resolved = this . _resolveName ( token );
replacement = resolved ? . value ;
tier = resolved ? . tier ;
}
if ( replacement === undefined ) {
// Unknown entity — leave as-is, advance past '&' only
i ++ ;
continue ;
}
// Flush literal chunk before this entity
if ( i > last ) chunks . push ( str . slice ( last , i ));
chunks . push ( replacement );
last = j + 1 ; // skip past ';'
i = last ;
// Apply expansion limits only if this tier is being tracked
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 ) {
// delta: replacement.length minus the raw &token; length (token.length + 2 for '&' and ';')
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 } `
);
}
}
}
}
}
// Flush trailing literal
if ( last < len ) chunks . push ( str . slice ( last ));
// If nothing was replaced, chunks is empty — return original
const result = chunks . length === 0 ? str : chunks . join ( '' );
return this . _postCheck ( result , original );
}
// -------------------------------------------------------------------------
// Private: limit tier check
// -------------------------------------------------------------------------
/**
* Returns true if a resolved entity of the given tier should count
* against the expansion/length limits.
* @param {string} tier — LIMIT_TIER_EXTERNAL | LIMIT_TIER_BASE
* @returns {boolean}
*/
_tierCounts ( tier ) {
if ( this . _limitTiers . has ( LIMIT_TIER_ALL )) return true ;
return this . _limitTiers . has ( tier );
}
// -------------------------------------------------------------------------
// Private: entity resolution
// -------------------------------------------------------------------------
/**
* Resolve a named entity token (without & and ;).
* Priority: inputMap > externalMap > baseMap
* Returns the resolved value tagged with its limit tier.
*
* @param {string} name
* @returns {{ value: string, tier: string }|undefined}
*/
_resolveName ( name ) {
// input and external both count as 'external' tier for limit purposes —
// they are injected at runtime and are the untrusted surface.
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 ;
}
/**
* Classify a codepoint and return the minimum action level that must be applied.
* Returns -1 when no minimum is imposed (normal allow path).
*
* Ranges checked (in priority order):
* 1. U+0000 — null, governed by nullNCR (always ≥ remove)
* 2. U+D800–U+DFFF — surrogates, always prohibited (min: remove)
* 3. U+0001–U+001F \ {0x09,0x0A,0x0D} — XML 1.0 restricted C0 (min: remove)
* (skipped in XML 1.1 — C0 controls are allowed when written as NCRs)
*
* @param {number} cp — codepoint
* @returns {number} — minimum NCR_LEVEL value, or -1 for no restriction
*/
_classifyNCR ( cp ) {
// 1. Null
if ( cp === 0 ) return this . _ncrNullLevel ;
// 2. Surrogates — always prohibited, minimum 'remove'
if ( cp >= 0xD800 && cp <= 0xDFFF ) return NCR_LEVEL . remove ;
// 3. XML 1.0 restricted C0 controls
if ( this . _ncrXmlVersion === 1.0 ) {
if ( cp >= 0x01 && cp <= 0x1F && ! XML10_ALLOWED_C0 . has ( cp )) return NCR_LEVEL . remove ;
}
return - 1 ; // no restriction
}
/**
* Execute a resolved NCR action.
*
* @param {number} action — NCR_LEVEL value
* @param {string} token — raw token (e.g. '#38') for error messages
* @param {number} cp — codepoint, used only for error messages
* @returns {string|undefined}
* - decoded character string → 'allow'
* - '' → 'remove'
* - undefined → 'leave' (caller must skip past '&' only)
* - throws Error → 'throw'
*/
_applyNCRAction ( action , token , cp ) {
switch ( action ) {
case NCR_LEVEL . allow : return String . fromCodePoint ( cp );
case NCR_LEVEL . remove : return '' ;
case NCR_LEVEL . leave : return undefined ; // signal: keep literal
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 );
}
}
/**
* Full NCR resolution pipeline for a numeric token.
*
* Steps:
* 1. Parse the codepoint (decimal or hex).
* 2. Validate the raw codepoint range (NaN, <0, >0x10FFFF).
* 3. If numericAllowed is false and no minimum restriction applies → leave as-is.
* 4. Classify the codepoint to find the minimum required action level.
* 5. Resolve effective action = max(onNCR, minimum).
* 6. Apply and return.
*
* @param {string} token — e.g. '#38', '#x26', '#X26'
* @returns {string|undefined}
* - string (incl. '') — replacement ('' = remove)
* - undefined — leave original &token; as-is
*/
_resolveNCR ( token ) {
// Step 1: parse codepoint
const second = token . charCodeAt ( 1 );
let cp ;
if ( second === 120 /* x */ || second === 88 /* X */ ) {
cp = parseInt ( token . slice ( 2 ), 16 );
} else {
cp = parseInt ( token . slice ( 1 ), 10 );
}
// Step 2: out-of-range → leave as-is unconditionally
if ( Number . isNaN ( cp ) || cp < 0 || cp > 0x10FFFF ) return undefined ;
// Step 3: classify to get minimum action level
const minimum = this . _classifyNCR ( cp );
// Step 4: if numericAllowed is false and no hard minimum → leave
if ( ! this . _numericAllowed && minimum < NCR_LEVEL . remove ) return undefined ;
// Step 5: effective action = max(configured onNCR, range minimum)
const effective = minimum === - 1
? this . _ncrOnLevel
: Math . max ( this . _ncrOnLevel , minimum );
// Step 6: apply
return this . _applyNCRAction ( effective , token , cp );
}
}
; // CONCATENATED MODULE: ./node_modules/is-unsafe/src/contexts/sql.js
/**
* SQL context patterns — high-precision rules only.
*
* These rules have very low false-positive risk and are safe to apply to
* general user text (names, descriptions, search queries, etc.).
* All patterns are ReDoS-safe — unlike the `sql-injection` npm package
* which has an active CVE on its own detection regexes.
*
* For exhaustive coverage including noisier heuristics (comment sequences,
* hex literals, stacked queries with semicolons), use 'SQL-STRICT' instead.
* Apply 'SQL-STRICT' only to strings that are specifically SQL fragments,
* not to general free-text fields.
*/
const SQL_PATTERNS = [
{
id : 'sql-block-comment-open' ,
description : 'SQL block comment open: /* ... */ — unusual in legitimate user text' ,
pattern : /\/\*/ ,
},
{
id : 'sql-union-select' ,
description : 'UNION SELECT — most common SQL injection aggregation attack' ,
pattern : /\bUNION\s{1,20}(?:ALL\s{1,20})?SELECT\b/i ,
},
{
id : 'sql-drop-table' ,
description : 'DROP TABLE — destructive DDL injection' ,
pattern : /\bDROP\s{1,20}TABLE\b/i ,
},
{
id : 'sql-drop-database' ,
description : 'DROP DATABASE — destructive DDL injection' ,
pattern : /\bDROP\s{1,20}DATABASE\b/i ,
},
{
id : 'sql-insert-into' ,
description : 'INSERT INTO — data injection' ,
pattern : /\bINSERT\s{1,20}INTO\b/i ,
},
{
id : 'sql-delete-from' ,
description : 'DELETE FROM — data deletion injection' ,
pattern : /\bDELETE\s{1,20}FROM\b/i ,
},
{
id : 'sql-update-set' ,
description : 'UPDATE ... SET — data modification injection' ,
// Allows arbitrary content between UPDATE and SET (table name, alias, etc.)
pattern : /\bUPDATE\b[\s\S]{1,60}\bSET\b/i ,
},
{
id : 'sql-exec-xp' ,
description : 'EXEC xp_ — MSSQL extended stored procedure execution' ,
pattern : /\bEXEC(?:UTE)?\s{1,20}xp_/i ,
},
{
id : 'sql-tautology-string' ,
description : "Classic string tautology: ' OR '1'='1 or \" OR \"1\"=\"1\"" ,
// Last quote is optional — injection may truncate it: ' OR '1'='1--
pattern : /'\s{0,10}OR\s{0,10}'[^']{0,20}'\s*=\s*'[^']{0,20}/i ,
},
{
id : 'sql-tautology-numeric' ,
description : 'Numeric tautology: OR 1=1' ,
pattern : /\bOR\s{1,10}1\s*=\s*1\b/i ,
},
{
id : 'sql-always-true-zero' ,
description : 'Numeric tautology: OR 0=0' ,
pattern : /\bOR\s{1,10}0\s*=\s*0\b/i ,
},
{
id : 'sql-sleep-benchmark' ,
description : 'Time-based blind injection: SLEEP() or BENCHMARK()' ,
pattern : /\b(?:SLEEP|BENCHMARK)\s*\(/i ,
},
{
id : 'sql-waitfor-delay' ,
description : 'MSSQL time-based blind injection: WAITFOR DELAY' ,
pattern : /\bWAITFOR\s{1,20}DELAY\b/i ,
},
{
id : 'sql-char-function' ,
description : 'CHAR() function — used to obfuscate injected strings' ,
pattern : /\bCHAR\s*\(\s*\d{1,3}/i ,
},
{
id : 'sql-information-schema' ,
description : 'INFORMATION_SCHEMA — reconnaissance query for table/column enumeration' ,
pattern : /\bINFORMATION_SCHEMA\b/i ,
},
];
/* harmony default export */ const sql = ( SQL_PATTERNS );
; // CONCATENATED MODULE: ./node_modules/is-unsafe/src/contexts/sql-strict.js
/**
* SQL-STRICT context patterns.
*
* Extends the base 'SQL' context with three additional rules that are
* effective at detecting real injections but carry a higher false-positive
* risk on general free-text input.
*
* Use 'SQL-STRICT' when:
* - The string is specifically a SQL fragment or database identifier
* - You control the input domain (e.g. a dedicated SQL search field)
* - You can tolerate occasional false positives in exchange for broader coverage
*
* Use 'SQL' (not STRICT) when:
* - The field is general user text (names, descriptions, comments)
* - False positives would block legitimate content (e.g. "see note -- above")
*
* Rules moved here from 'SQL' due to false-positive risk:
*
* sql-line-comment — "--" fires on "see note -- above", "value--", CSS var(--primary)
* sql-stacked-query — "; SELECT" fires on legitimate prose with semicolons + SQL words
* sql-hex-encoding — "0xDEAD" fires on hex values in technical docs and log output
*/
const SQL_STRICT_EXTRA = [
{
id : 'sql-line-comment' ,
description : 'SQL line comment: -- followed by whitespace or end of string' ,
pattern : /--(?:\s|$)/ ,
},
{
id : 'sql-stacked-query' ,
description : 'Stacked queries: semicolon immediately followed by a SQL keyword' ,
pattern : /;\s{0,10}(?:SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC)\b/i ,
},
{
id : 'sql-hex-encoding' ,
description : 'Hex-encoded string injection: 0x41414141 style (MySQL)' ,
pattern : /\b0x[0-9a-f]{4,}/i ,
},
];
// SQL-STRICT = all base SQL rules + the three noisy extras
const SQL_STRICT_PATTERNS = [... sql , ... SQL_STRICT_EXTRA ];
/* harmony default export */ const sql_strict = ( SQL_STRICT_PATTERNS );
; // CONCATENATED MODULE: ./node_modules/is-unsafe/src/contexts/html.js
/**
* HTML context patterns.
*
* Detects XSS vectors that are dangerous when a string ends up rendered as HTML.
* All patterns use bounded quantifiers to ensure linear-time matching (ReDoS-safe).
*
* Each entry is { pattern: RegExp, id: string, description: string }
* so callers can inspect which rule fired if they need to.
*/
const HTML_PATTERNS = [
{
id : 'html-script-open' ,
description : '<script opening tag' ,
pattern : /<script[\s>/]/i ,
},
{
id : 'html-script-close' ,
description : '</script closing tag' ,
pattern : /<\/script[\s>]/i ,
},
{
id : 'html-javascript-protocol' ,
description : 'javascript: URI scheme (with optional whitespace/encoding)' ,
// Handles javascript:, j\u0061vascript:, and whitespace variants
pattern : /j[\t\n\r ]*a[\t\n\r ]*v[\t\n\r ]*a[\t\n\r ]*s[\t\n\r ]*c[\t\n\r ]*r[\t\n\r ]*i[\t\n\r ]*p[\t\n\r ]*t[\t\n\r ]*:/i ,
},
{
id : 'html-vbscript-protocol' ,
description : 'vbscript: URI scheme' ,
pattern : /vbscript[\t\n\r ]*:/i ,
},
{
id : 'html-data-html' ,
description : 'data:text/html URI — can execute scripts in browsers' ,
pattern : /data[\t\n\r ]*:[\t\n\r ]*text\/html/i ,
},
{
id : 'html-data-xhtml' ,
description : 'data:application/xhtml+xml URI' ,
pattern : /data[\t\n\r ]*:[\t\n\r ]*application\/xhtml/i ,
},
{
id : 'html-data-svg' ,
description : 'data:image/svg+xml URI — can execute scripts' ,
pattern : /data[\t\n\r ]*:[\t\n\r ]*image\/svg\+xml/i ,
},
{
id : 'html-inline-event-handler' ,
description : 'Inline event handler attributes: onclick=, onerror=, onload=, etc.' ,
// \bon ensures we match a word boundary so "phonetic=" is not caught
pattern : /\bon\w{1,30}\s*=/i ,
},
{
id : 'html-entity-obfuscated-script' ,
description : 'HTML-entity-encoded <script (e.g. <script or <script)' ,
// Entities include optional trailing semicolon: < or < (both valid in HTML5)
pattern : /(?:�*3[Cc];?|�*60;?|<)\s*script/i ,
},
{
id : 'html-entity-obfuscated-javascript' ,
description : 'HTML-entity-encoded javascript: (partial — catches common j or j for "j")' ,
pattern : /(?:�*6[Aa];?|�*106;?)\s*(?:�*61;?|a)[\s\S]{0,80}script\s*:/i ,
},
{
id : 'html-style-expression' ,
description : 'CSS expression() — IE-era code execution in style attributes' ,
pattern : /style[\s\S]{0,20}expression\s*\(/i ,
},
{
id : 'html-object-embed' ,
description : '<object or <embed tags that can load active content' ,
pattern : /<(?:object|embed)[\s>/]/i ,
},
{
id : 'html-base-tag' ,
description : '<base href= — can hijack all relative URLs on a page' ,
pattern : /<base[\s>]/i ,
},
{
id : 'html-meta-refresh' ,
description : '<meta http-equiv="refresh" — can redirect users' ,
pattern : /<meta[\s\S]{0,40}http-equiv[\s\S]{0,20}refresh/i ,
},
{
id : 'html-srcdoc' ,
description : 'srcdoc= attribute on iframes — embeds HTML that can run scripts' ,
pattern : /srcdoc\s*=/i ,
},
{
id : 'html-iframe' ,
description : '<iframe tag' ,
pattern : /<iframe[\s>/]/i ,
},
{
id : 'html-form' ,
description : '<form tag — can be used for phishing / credential harvesting injection' ,
pattern : /<form[\s>/]/i ,
},
];
/* harmony default export */ const html = ( HTML_PATTERNS );
; // CONCATENATED MODULE: ./node_modules/is-unsafe/src/contexts/xml.js
/**
* XML context patterns.
*
* Detects injection vectors that are specifically dangerous when a string
* is inserted into an XML document (not HTML rendering context).
*
* Key distinction from HTML: these patterns target parser-level attacks —
* things that can confuse or subvert an XML parser, trigger external entity
* resolution, or inject DTD content. HTML rendering concerns (XSS) belong
* in the HTML context.
*/
const XML_PATTERNS = [
{
id : 'xml-cdata-injection' ,
description : 'CDATA section injection: <![CDATA[ breaks out of text node context' ,
pattern : /<!\[CDATA\[/i ,
},
{
id : 'xml-cdata-close' ,
description : 'CDATA close sequence: ]]> can terminate an enclosing CDATA section' ,
pattern : /\]\]>/ ,
},
{
id : 'xml-processing-instruction' ,
description : 'XML processing instruction: <?xml-stylesheet or <?php etc.' ,
pattern : /<\?(?:xml[\- ]|php|asp)/i ,
},
{
id : 'xml-doctype-injection' ,
description : 'DOCTYPE declaration embedded in content — can define entities' ,
// Match <!DOCTYPE followed by end-of-string, whitespace, or [ (internal subset)
pattern : /<!DOCTYPE(?:[\s[]|$)/i ,
},
{
id : 'xml-entity-system' ,
description : 'SYSTEM keyword — used in external entity declarations (XXE)' ,
pattern : /\bSYSTEM\s+["']/i ,
},
{
id : 'xml-entity-public' ,
description : 'PUBLIC keyword — used in external entity declarations (XXE)' ,
pattern : /\bPUBLIC\s+["']/i ,
},
{
id : 'xml-entity-declaration' ,
description : '<!ENTITY declaration — defines entities, potential XXE or entity expansion' ,
pattern : /<!ENTITY[\s%]/i ,
},
{
id : 'xml-billion-laughs' ,
description : 'Entity reference chaining / billion laughs: repeated &eX; style references' ,
// Heuristic: 3+ consecutive entity refs suggests expansion attack
pattern : /(?:&\w{1,20};){3,}/ ,
},
{
id : 'xml-namespace-confusion' ,
description : 'xmlns: attribute injection — can redefine namespaces to confuse parsers' ,
pattern : /\bxmlns\s*(?::\w{1,40})?\s*=/i ,
},
{
id : 'xml-comment-injection' ,
description : '<!-- comment injection — can hide content from some parsers' ,
pattern : /<!--/ ,
},
{
id : 'xml-comment-close' ,
description : '--> closes an enclosing XML comment' ,
pattern : /--!?>/ ,
},
{
id : 'xml-pi-close' ,
description : '?> closes an enclosing processing instruction' ,
pattern : /\?>/ ,
},
];
/* harmony default export */ const xml = ( XML_PATTERNS );
; // CONCATENATED MODULE: ./node_modules/is-unsafe/src/contexts/svg.js
/**
* SVG context patterns.
*
* SVG is XML-based but renders in browsers, giving it a unique attack surface
* that combines XML parser behaviour with browser rendering and JavaScript execution.
*
* Many of these vectors bypass HTML sanitizers that don't understand SVG semantics
* (DOMPurify has documented bypass vulnerabilities specifically in SVG/XML context).
*/
const SVG_PATTERNS = [
{
id : 'svg-script-element' ,
description : '<script element inside SVG executes JavaScript' ,
pattern : /<script[\s>/]/i ,
},
{
id : 'svg-xlink-href-javascript' ,
description : 'xlink:href with javascript: — classic SVG XSS via <a> or <use>' ,
pattern : /xlink\s*:\s*href\s*=\s*["']?\s*javascript\s*:/i ,
},
{
id : 'svg-href-javascript' ,
description : 'href= with javascript: in SVG context (<a>, <animate>, etc.)' ,
pattern : /href\s*=\s*["']?\s*javascript\s*:/i ,
},
{
id : 'svg-foreignobject' ,
description : '<foreignObject embeds HTML inside SVG — can execute scripts' ,
pattern : /<foreignObject[\s>/]/i ,
},
{
id : 'svg-use-external' ,
description : '<use xlink:href or href pointing to external resource (non-fragment URL)' ,
// Match <use with href= where the value starts with a non-# character (external URL)
// [\"'][^#] catches quoted values not starting with #; [^\"'#\s>] catches unquoted
pattern : /<use[\s\S]{0,60}(?:xlink\s*:\s*)?href\s*=\s*(?:["'][^#]|[^"'#\s>])/i ,
},
{
id : 'svg-animate-href' ,
description : '<animate attributeName="href" — can dynamically change href to javascript:' ,
pattern : /<animate[\s\S]{0,80}attributeName\s*=\s*["'][\s]*href["']/i ,
},
{
id : 'svg-animate-xlinkhref' ,
description : '<animate attributeName="xlink:href"' ,
pattern : /<animate[\s\S]{0,80}attributeName\s*=\s*["'][\s]*xlink\s*:\s*href["']/i ,
},
{
id : 'svg-set-javascript' ,
description : '<set to="javascript:..." — sets an attribute to a javascript: URI' ,
pattern : /<set[\s\S]{0,80}to\s*=\s*["']?\s*javascript\s*:/i ,
},
{
id : 'svg-event-handler' ,
description : 'SVG-specific event handler attributes: onload=, onerror=, onactivate=, etc.' ,
pattern : /\bon(?:load|error|activate|begin|end|repeat|focus|blur|click|mouse\w{1,20}|key\w{1,20})\s*=/i ,
},
{
id : 'svg-handler-generic' ,
description : 'Generic on* handler catch-all for SVG attributes' ,
pattern : /\bon\w{1,30}\s*=/i ,
},
{
id : 'svg-filter-feimage' ,
description : '<feImage href= — filter primitive that can load external resources' ,
pattern : /<feImage[\s\S]{0,80}(?:xlink\s*:\s*)?href\s*=/i ,
},
{
id : 'svg-image-external' ,
description : '<image xlink:href with http/https or javascript protocol' ,
pattern : /<image[\s\S]{0,80}(?:xlink\s*:\s*)?href\s*=\s*["']?\s*(?:https?|javascript)\s*:/i ,
},
{
id : 'svg-style-javascript' ,
description : 'style= attribute containing javascript: (e.g. background:url(javascript:...))' ,
pattern : /style\s*=[\s\S]{0,60}javascript\s*:/i ,
},
];
/* harmony default export */ const svg = ( SVG_PATTERNS );
; // CONCATENATED MODULE: ./node_modules/is-unsafe/src/contexts/shell.js
/**
* SHELL context patterns.
*
* Detects shell injection vectors and path traversal patterns.
* Designed for use when a string will be passed to a shell command,
* used as a file path, or interpolated into OS-level operations.
*/
const SHELL_PATTERNS = [
{
id : 'shell-path-traversal-unix' ,
description : 'Unix path traversal: ../ — climbing the directory tree' ,
pattern : /\.\.\// ,
},
{
id : 'shell-path-traversal-windows' ,
description : 'Windows path traversal: ..\\ — climbing the directory tree' ,
pattern : /\.\.\\/ ,
},
{
id : 'shell-path-traversal-encoded' ,
description : 'URL-encoded path traversal: %2e%2e or %2f variants' ,
pattern : /%2e%2e|%2f\.\.|\.\.%2f/i ,
},
{
id : 'shell-null-byte' ,
description : 'Null byte injection: \\x00 or %00 — truncates strings in C-backed functions' ,
pattern : /\x00|%00/ ,
},
{
id : 'shell-semicolon' ,
description : 'Semicolon command separator: cmd1; cmd2' ,
pattern : /;/ ,
},
{
id : 'shell-pipe' ,
description : 'Pipe operator: cmd1 | cmd2' ,
pattern : /\|/ ,
},
{
id : 'shell-and-operator' ,
description : 'AND operator: cmd1 && cmd2' ,
pattern : /&&/ ,
},
{
id : 'shell-or-operator' ,
description : 'OR operator: cmd1 || cmd2' ,
pattern : /\|\|/ ,
},
{
id : 'shell-backtick' ,
description : 'Backtick command substitution: `cmd`' ,
pattern : /`/ ,
},
{
id : 'shell-dollar-paren' ,
description : 'Dollar-paren command substitution: $(cmd)' ,
pattern : /\$\(/ ,
},
{
id : 'shell-dollar-brace' ,
description : 'Dollar-brace variable expansion: ${var} — can be abused for injection' ,
pattern : /\$\{/ ,
},
{
id : 'shell-redirect-out' ,
description : 'Output redirection: cmd > file or cmd >> file' ,
pattern : />{1,2}/ ,
},
{
id : 'shell-redirect-in' ,
description : 'Input redirection: cmd < file' ,
pattern : /</ ,
},
{
id : 'shell-newline-injection' ,
description : 'Newline injection: \\n or \\r — can inject new shell commands' ,
pattern : /[\n\r]/ ,
},
{
id : 'shell-glob-star' ,
description : 'Glob expansion: * or ? — can expand to unintended files' ,
// Only flag when combined with path separators to reduce false positives
pattern : /[/\\][*?]/ ,
},
{
id : 'shell-absolute-root' ,
description : 'Absolute root path injection: string starting with / or \\ (Windows UNC)' ,
pattern : /^(?:\/|\\\\)/ ,
},
{
id : 'shell-windows-drive' ,
description : 'Windows drive letter path injection: C:\\ or D:/' ,
pattern : /^[a-zA-Z]:[/\\]/ ,
},
{
id : 'shell-curl-wget' ,
description : 'curl/wget with URL or flags — can exfiltrate data or download payloads' ,
// Require a URL scheme (http/https/ftp) or a flag (-) to reduce false positives
// "curl is a tool" won't match; "curl http://..." or "curl -s ..." will
pattern : /\b(?:curl|wget)\s+(?:https?:\/\/|ftp:\/\/|-)/i ,
},
];
/* harmony default export */ const shell = ( SHELL_PATTERNS );
; // CONCATENATED MODULE: ./node_modules/is-unsafe/src/contexts/redos.js
/**
* REDOS context patterns.
*
* Detects strings that, if used as regular expressions, could cause
* catastrophic backtracking (ReDoS — Regular Expression Denial of Service).
*
* These patterns detect the structural forms that lead to exponential or
* polynomial backtracking in NFA-based regex engines (V8, PCRE, Java, etc.).
*
* Use this context when user-supplied strings will be compiled into RegExp objects.
*/
const REDOS_PATTERNS = [
{
id : 'redos-nested-quantifier-plus' ,
description : 'Nested + quantifier inside a group with outer quantifier: (a+)+, (.+b)*, etc.' ,
// Matches any group containing a + quantifier, with an outer * or + — catches (a+)+, (.+b)*, etc.
pattern : /\([^)]*\+[^)]*\)[+*]/ ,
},
{
id : 'redos-nested-quantifier-star' ,
description : 'Nested * quantifier: (a*)* or (a*)+ — catastrophic backtracking' ,
pattern : /\([^)]*\*[^)]*\)[*+]/ ,
},
{
id : 'redos-nested-groups' ,
description : 'Doubly nested quantified groups: ((a+)+) — guaranteed catastrophic' ,
pattern : /\(\([^)]{0,40}\)[+*]\)[+*]/ ,
},
{
id : 'redos-alternation-overlap' ,
description : 'Overlapping alternation under quantifier: (a|a)+ — ambiguous NFA paths' ,
// Detect repeated identical alternatives under a quantifier
pattern : /\(([^|()]{1,20})\|(?:\1)(?:\|[^|()]{1,20}){0,5}\)[+*?]{1,2}/ ,
},
{
id : 'redos-star-plus-concat' ,
description : '(x*x)+ pattern — triggers super-linear backtracking' ,
pattern : /\([^)]{0,10}\*[^)]{0,10}\)[+*]/ ,
},
{
id : 'redos-dot-star-greedy' ,
description : '(.*){n,} or (.+){n,} — repeated greedy dot quantifiers' ,
pattern : /\(\.[*+]\)\{?\d/ ,
},
{
id : 'redos-large-repetition' ,
description : 'Very large fixed or range repetition count {1000,} or {1000,n} — denial of service via backtracking' ,
// Matches { followed by 4+ digits (≥1000), then optional ,digits }
pattern : /\{\d{4,}(?:,\d*)?\}/ ,
},
{
id : 'redos-catastrophic-alternation' ,
description : 'Long alternation with many similar branches — polynomial backtracking risk' ,
// Heuristic: 10+ pipe-separated alternatives in a single group
pattern : /\([^)]{0,200}(?:\|[^|)]{0,50}){9,}\)/ ,
},
];
/* harmony default export */ const redos = ( REDOS_PATTERNS );
; // CONCATENATED MODULE: ./node_modules/is-unsafe/src/contexts/nosql.js
/**
* NOSQL context patterns.
*
* Detects injection vectors specific to NoSQL databases (primarily MongoDB)
* and JavaScript-evaluated queries.
*
* Attack categories:
* 1. MongoDB query operator injection: $where, $ne, $gt, $regex, $or, $and, etc.
* These operators, when injected into a JSON query object, can bypass
* authentication or exfiltrate data without knowing passwords.
*
* 2. JavaScript execution: $where clauses execute arbitrary JS server-side.
*
* 3. Prototype pollution: __proto__, constructor.prototype — can corrupt
* the prototype chain of all objects in the Node.js process.
*
* Pattern note: MongoDB operators appear as JSON keys. In JSON, keys are
* quoted: {"$where": ...} so the pattern must allow an optional closing
* quote between the operator name and the colon: /\$where["'\s]*:/
*/
// Shared suffix: optional closing quote/whitespace before the colon
// Handles: $op: (bare), "$op": (JSON), '$op': (single-quoted)
const SEP = /["'\s]*:/ ;
const sep = '["\'\\s]*:' ;
const NOSQL_PATTERNS = [
// ─── MongoDB $ operator injection ────────────────────────────────────────
{
id : 'nosql-where-operator' ,
description : '$where — executes arbitrary JavaScript server-side in MongoDB' ,
pattern : new RegExp ( `\\$where ${ sep } ` , 'i' ),
},
{
id : 'nosql-ne-operator' ,
description : '$ne — "not equal" operator used to bypass equality checks' ,
pattern : new RegExp ( `\\$ne ${ sep } ` , 'i' ),
},
{
id : 'nosql-gt-operator' ,
description : '$gt — "greater than" used to bypass password/value checks' ,
pattern : new RegExp ( `\\$gte? ${ sep } ` , 'i' ),
},
{
id : 'nosql-lt-operator' ,
description : '$lt / $lte — "less than" bypass variants' ,
pattern : new RegExp ( `\\$lte? ${ sep } ` , 'i' ),
},
{
id : 'nosql-regex-operator' ,
description : '$regex — can be used to extract data character by character (blind injection)' ,
pattern : new RegExp ( `\\$regex ${ sep } ` , 'i' ),
},
{
id : 'nosql-or-operator' ,
description : '$or — logical OR; used to create always-true conditions' ,
pattern : new RegExp ( `\\$or ${ sep } \\s*\\[` , 'i' ),
},
{
id : 'nosql-and-operator' ,
description : '$and — logical AND operator injection' ,
pattern : new RegExp ( `\\$and ${ sep } \\s*\\[` , 'i' ),
},
{
id : 'nosql-nor-operator' ,
description : '$nor — logical NOR operator injection' ,
pattern : new RegExp ( `\\$nor ${ sep } \\s*\\[` , 'i' ),
},
{
id : 'nosql-exists-operator' ,
description : '$exists — can enumerate fields to determine schema' ,
pattern : new RegExp ( `\\$exists ${ sep } ` , 'i' ),
},
{
id : 'nosql-in-operator' ,
description : '$in — matches any value in a list; can enumerate values' ,
pattern : new RegExp ( `\\$in ${ sep } \\s*\\[` , 'i' ),
},
{
id : 'nosql-expr-operator' ,
description : '$expr — allows aggregation expressions in queries (MongoDB 3.6+)' ,
pattern : new RegExp ( `\\$expr ${ sep } ` , 'i' ),
},
{
id : 'nosql-function-operator' ,
description : '$function — executes arbitrary JavaScript in MongoDB 4.4+' ,
pattern : new RegExp ( `\\$function ${ sep } ` , 'i' ),
},
{
id : 'nosql-accumulator-operator' ,
description : '$accumulator — custom aggregation with arbitrary JS execution' ,
pattern : new RegExp ( `\\$accumulator ${ sep } ` , 'i' ),
},
// ─── Prototype pollution ─────────────────────────────────────────────────
{
id : 'nosql-proto-pollution' ,
description : '__proto__ — prototype pollution via object key injection' ,
pattern : /__proto__/ ,
},
{
id : 'nosql-constructor-prototype' ,
description : 'constructor.prototype — alternative prototype pollution vector (dot notation or JSON key)' ,
// Matches dot-notation (obj.constructor.prototype) and JSON key adjacency
// ("constructor": {"prototype": ...})
pattern : /constructor[\s"':.,{\[]*prototype/i ,
},
{
id : 'nosql-proto-bracket' ,
description : '["__proto__"] — bracket-notation prototype pollution' ,
pattern : /\[["']__proto__["']\]/ ,
},
];
/* harmony default export */ const nosql = ( NOSQL_PATTERNS );
; // CONCATENATED MODULE: ./node_modules/is-unsafe/src/contexts/log.js
/**
* LOG context patterns.
*
* Detects injection vectors that are dangerous when a string is written
* to a log file, passed to a logging framework, or interpolated into
* a log message that will be parsed or displayed.
*
* Attack categories:
* 1. CRLF injection — injects fake log lines by embedding newlines
* 2. Log4Shell (CVE-2021-44228) — ${jndi:...} triggers JNDI lookup in Log4j
* 3. SSTI in log templates — {{...}}, #{...} trigger template evaluation
* if the log message is passed through a template engine
* 4. Null byte injection — truncates log entries in some implementations
* 5. ANSI escape injection — manipulates terminal output when logs are
* tailed in a terminal (colour codes, cursor movement, etc.)
*
* Note: Newline characters (\n, \r) will produce false positives for
* multi-line legitimate values. Use this context only for single-line
* log field values (usernames, IDs, request parameters, etc.).
*/
const LOG_PATTERNS = [
// ─── CRLF / newline injection ─────────────────────────────────────────────
{
id : 'log-crlf-injection' ,
description : 'CRLF injection: literal \\r or \\n embeds fake log lines' ,
pattern : /[\r\n]/ ,
},
{
id : 'log-url-encoded-crlf' ,
description : 'URL-encoded CRLF: %0d, %0a, %0D, %0A — decoded by some log parsers' ,
pattern : /%0[dDaA]/ ,
},
{
id : 'log-unicode-newline' ,
description : 'Unicode newline variants: U+2028 (line separator), U+2029 (paragraph separator)' ,
pattern : /[\u2028\u2029]/ ,
},
// ─── Log4Shell / JNDI injection (CVE-2021-44228) ─────────────────────────
{
id : 'log-log4shell-jndi' ,
description : 'Log4Shell: ${jndi:...} triggers remote code execution in Apache Log4j' ,
pattern : /\$\{jndi\s*:/i ,
},
{
id : 'log-log4shell-obfuscated' ,
description : 'Obfuscated Log4Shell: ${::-j}... lookup-bypass prefix used to evade WAF detection' ,
// ${::- is the Log4j lookup-bypass escape sequence; presence alone is suspicious
pattern : /\$\{::-/ ,
},
{
id : 'log-log4j-lookup' ,
description : 'Log4j lookup syntax: ${env:...}, ${sys:...}, ${ctx:...} — data exfiltration' ,
pattern : /\$\{(?:env|sys|ctx|main|map|sd|web|docker|k8s|spring)\s*:/i ,
},
// ─── Server-Side Template Injection (SSTI) in log messages ───────────────
{
id : 'log-ssti-double-brace' ,
description : 'SSTI double-brace: {{expression}} — Jinja2, Twig, Handlebars, etc.' ,
pattern : /\{\{[\s\S]{0,80}\}\}/ ,
},
{
id : 'log-ssti-hash-brace' ,
description : 'SSTI hash-brace: #{expression} — Thymeleaf, Velocity, Ruby ERB' ,
pattern : /#\{[\s\S]{0,80}\}/ ,
},
{
id : 'log-ssti-dollar-brace' ,
description : 'SSTI/EL injection: ${expression with operators or method calls} — JSP EL, Freemarker, SpEL' ,
// Require that the ${...} content looks like an expression, not a plain variable name.
// Flags if the content contains: . ( * + operators, or known SSTI keywords.
// This avoids flagging ${PATH}, ${HOME} etc. (plain shell variables).
pattern : /\$\{[^}]*(?:\.|\(|\*|\+|\bclass\b|\bruntime\b|\bprocess\b|\bexec\b)[^}]{0,80}\}/i ,
},
{
id : 'log-ssti-percent-tag' ,
description : 'SSTI ERB/ASP tag: <%= expression %> — Ruby ERB, ASP' ,
pattern : /<%=[\s\S]{0,80}%>/ ,
},
// ─── Null byte ────────────────────────────────────────────────────────────
{
id : 'log-null-byte' ,
description : 'Null byte: \\x00 or %00 — can truncate log entries in C-backed loggers' ,
pattern : /\x00|%00/ ,
},
// ─── ANSI escape injection ────────────────────────────────────────────────
{
id : 'log-ansi-escape' ,
description : 'ANSI escape sequence: ESC[ — can manipulate terminal output when logs are tailed' ,
pattern : /\x1b\[/ ,
},
];
/* harmony default export */ const log = ( LOG_PATTERNS );
; // CONCATENATED MODULE: ./node_modules/is-unsafe/src/index.js
/**
* is-unsafe v2
*
* Zero-dependency, DOM-free, pure predicate for detecting unsafe strings
* across HTML, XML, SVG, SQL, SQL-STRICT, SHELL, REDOS, NOSQL, and LOG contexts.
*
* v2 change: contexts are imported as named pattern arrays rather than resolved
* via a string-keyed registry. This makes each context independently
* tree-shakeable — bundlers can drop any context you never import.
*
* @module is-unsafe
*/
// ─── Context pattern arrays (named exports) ────────────────────────────────
// Import only the ones you need. Each is independently tree-shakeable.
// SQL-STRICT needs a quoted identifier because of the hyphen
// ─── VALID_CONTEXTS convenience re-export ─────────────────────────────────
// Importing this pulls in ALL contexts. Use it only when you need all of them
// (e.g. for validation UI, tooling, or exhaustive audits).
// If you only need a subset, import the named contexts directly instead.
// ─── Attach labels to named contexts ──────────────────────────────────────
// Each built-in PatternList carries its canonical name so matchList can read
// list.label directly — no registry lookup needed at match time.
// Custom PatternLists default to 'CUSTOM' unless the caller sets list.label.
html . label = 'HTML' ;
xml . label = 'XML' ;
svg . label = 'SVG' ;
sql . label = 'SQL' ;
sql_strict . label = 'SQL-STRICT' ;
shell . label = 'SHELL' ;
redos . label = 'REDOS' ;
nosql . label = 'NOSQL' ;
log . label = 'LOG' ;
const VALID_CONTEXTS = Object . freeze ({
HTML : html ,
XML : xml ,
SVG : svg ,
SQL : sql ,
'SQL-STRICT' : sql_strict ,
SHELL : shell ,
REDOS : redos ,
NOSQL : nosql ,
LOG : log ,
});
// ─── Types ────────────────────────────────────────────────────────────────
/**
* @typedef {{ id: string, description: string, pattern: RegExp }} Rule
*/
/**
* @typedef {Rule[]} PatternList
*/
/**
* @typedef {Object} MatchResult
* @property {string} context - Label identifying which context matched ('HTML', 'CUSTOM', etc.)
* @property {string} id - Rule identifier
* @property {string} description - Human-readable description of what was matched
* @property {RegExp} pattern - The pattern that matched
*/
// ─── Internal helpers ──────────────────────────────────────────────────────
/**
* @param {unknown} value
*/
function assertString ( value ) {
if ( typeof value !== 'string' ) {
throw new TypeError (
`is-unsafe: first argument must be a string, got ${ typeof value } `
);
}
}
/**
* @param {unknown} context
*/
function assertContext ( context ) {
if ( context instanceof RegExp ) return ;
if ( Array . isArray ( context )) {
if ( context . length === 0 ) {
throw new TypeError ( 'is-unsafe: context must not be an empty array' );
}
// Detect array-of-arrays vs flat pattern list
if ( Array . isArray ( context [ 0 ])) {
// Array of PatternLists
for ( const list of context ) {
if ( ! Array . isArray ( list ) || list . length === 0 ) {
throw new TypeError (
'is-unsafe: each context in the array must be a non-empty pattern array (PatternList)'
);
}
}
}
// else: flat PatternList — trust it, no deep validation needed
return ;
}
throw new TypeError (
`is-unsafe: second argument must be a PatternList (e.g. HTML), ` +
`an array of PatternLists (e.g. [HTML, XML]), or a RegExp. Got: ${ typeof context } `
);
}
/**
* Normalise any valid context arg into an array of PatternLists.
*
* @param {Rule[]|Rule[][]|RegExp} context
* @returns {{ lists: Rule[][]|null, regex: RegExp|null }}
*/
function normalise ( context ) {
if ( context instanceof RegExp ) return { lists : null , regex : context };
// Distinguish PatternList (array of rule objects) from array of PatternLists
if ( Array . isArray ( context [ 0 ])) return { lists : context , regex : null };
return { lists : [ context ], regex : null };
}
/**
* Test value against a single PatternList. Returns the first MatchResult or null.
*
* @param {string} value
* @param {Rule[]} list
* @returns {MatchResult|null}
*/
function matchList ( value , list ) {
const label = list . label ?? 'CUSTOM' ;
for ( const rule of list ) {
if ( rule . pattern . test ( value )) {
return { context : label , id : rule . id , description : rule . description , pattern : rule . pattern };
}
}
return null ;
}
// ─── Public API ───────────────────────────────────────────────────────────
/**
* Returns `true` if `value` is unsafe in the given context(s), `false` otherwise.
*
* @param {string} value - The string to test
* @param {PatternList | PatternList[] | RegExp} context
* - A PatternList imported from is-unsafe (e.g. `HTML`, `XML`)
* - An array of PatternLists — returns true if unsafe in **any** of them
* - A custom RegExp — returns true if the pattern matches
* @returns {boolean}
*
* @example
* import { isUnsafe, HTML, SQL } from 'is-unsafe';
*
* isUnsafe('<script>alert(1)</script>', HTML) // true
* isUnsafe('hello world', HTML) // false
* isUnsafe('value', [HTML, SQL]) // false
* isUnsafe('value', /my-pattern/i) // false
*/
function isUnsafe ( value , context ) {
assertString ( value );
assertContext ( context );
const { lists , regex } = normalise ( context );
if ( regex ) return regex . test ( value );
for ( const list of lists ) {
if ( matchList ( value , list ) !== null ) return true ;
}
return false ;
}
/**
* Like `isUnsafe`, but returns the first `MatchResult` describing **why**
* the value was flagged, or `null` if it is safe.
*
* @param {string} value
* @param {PatternList | PatternList[] | RegExp} context
* @returns {MatchResult|null}
*
* @example
* import { whyUnsafe, HTML } from 'is-unsafe';
*
* whyUnsafe('<script>alert(1)</script>', HTML)
* // { context: 'HTML', id: 'html-script-open', description: '...', pattern: /.../ }
*/
function whyUnsafe ( value , context ) {
assertString ( value );
assertContext ( context );
const { lists , regex } = normalise ( context );
if ( regex ) {
return regex . test ( value )
? { context : 'CUSTOM' , id : 'custom-regex' , description : 'Matched caller-supplied pattern' , pattern : regex }
: null ;
}
for ( const list of lists ) {
const result = matchList ( value , list );
if ( result !== null ) return result ;
}
return null ;
}
/**
* Returns **all** matching rules across the given context(s), or an empty
* array if the value is safe. Useful for comprehensive auditing.
*
* @param {string} value
* @param {PatternList | PatternList[] | RegExp} context
* @returns {MatchResult[]}
*/
function allUnsafe ( value , context ) {
assertString ( value );
assertContext ( context );
const { lists , regex } = normalise ( context );
const results = [];
if ( regex ) {
if ( regex . test ( value )) {
results . push ({ context : 'CUSTOM' , id : 'custom-regex' , description : 'Matched caller-supplied pattern' , pattern : regex });
}
return results ;
}
for ( const list of lists ) {
const label = list . label ?? 'CUSTOM' ;
for ( const rule of list ) {
if ( rule . pattern . test ( value )) {
results . push ({ context : label , id : rule . id , description : rule . description , pattern : rule . pattern });
}
}
}
return results ;
}
/* harmony default export */ const is_unsafe_src = (( /* unused pure expression or super */ null && ( isUnsafe )));
; // CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js
///@ts-check
// const regx =
// '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
// .replace(/NAME/g, util.nameRegexp);
//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 , externalEntities ) {
this . options = options ;
this . currentNode = null ;
this . tagsNodeStack = [];
this . parseXml = parseXml ;
this . parseTextData = parseTextData ;
this . resolveNameSpace = resolveNameSpace ;
this . buildAttributesMap = buildAttributesMap ;
this . isItStopNode = isItStopNode ;
this . replaceEntitiesValue = replaceEntitiesValue ;
this . readStopNodeData = readStopNodeData ;
this . saveTextToParentTag = saveTextToParentTag ;
this . addChild = addChild ;
this . ignoreAttributesFn = getIgnoreAttributesFn ( this . options . ignoreAttributes )
this . entityExpansionCount = 0 ;
this . currentExpandedLength = 0 ;
this . doctypefound = false ;
let namedEntities = { ... XML };
if ( this . options . entityDecoder ) {
this . entityDecoder = this . options . entityDecoder
} else {
if ( typeof this . options . htmlEntities === "object" ) namedEntities = this . options . htmlEntities ;
else if ( this . options . htmlEntities === true ) namedEntities = { ... COMMON_HTML , ... CURRENCY };
this . entityDecoder = new EntityDecoder ({
namedEntities : { ... namedEntities , ... externalEntities },
numericAllowed : this . options . htmlEntities ,
limit : {
maxTotalExpansions : this . options . processEntities . maxTotalExpansions ,
maxExpandedLength : this . options . processEntities . maxExpandedLength ,
applyLimitsTo : this . options . processEntities . appliesTo ,
},
// onExternalEntity: (name, value) => isUnsafe(value) ? 'block' : 'allow',
onInputEntity : ( name , value ) =>
//TODO: VALID_CONTEXTS.HTML should be set only if this.options.htmlEntities
isUnsafe ( value , [ html , xml ]) ? ENTITY_ACTION . BLOCK : ENTITY_ACTION . ALLOW ,
//postCheck: resolved => resolved
});
}
// Initialize path matcher for path-expression-matcher
this . matcher = new Matcher /* default */ . A ();
this . readonlyMatcher = this . matcher . readOnly ();
// Flag to track if current node is a stop node (optimization)
this . isCurrentNodeStopNode = false ;
// Pre-compile stopNodes expressions
this . stopNodeExpressionsSet = new ExpressionSet ();
const stopNodesOpts = this . options . stopNodes ;
if ( stopNodesOpts && stopNodesOpts . length > 0 ) {
for ( let i = 0 ; i < stopNodesOpts . length ; i ++ ) {
const stopNodeExp = stopNodesOpts [ i ];
if ( typeof stopNodeExp === 'string' ) {
// Convert string to Expression object
this . stopNodeExpressionsSet . add ( new Expression /* default */ . A ( stopNodeExp ));
} else if ( stopNodeExp instanceof Expression /* default */ . A ) {
// Already an Expression object
this . stopNodeExpressionsSet . add ( stopNodeExp );
}
}
this . stopNodeExpressionsSet . seal ();
}
}
}
/**
* @param {string} val
* @param {string} tagName
* @param {string|Matcher} jPath - jPath string or Matcher instance based on options.jPath
* @param {boolean} dontTrim
* @param {boolean} hasAttributes
* @param {boolean} isLeafNode
* @param {boolean} escapeEntities
*/
function parseTextData ( val , tagName , jPath , dontTrim , hasAttributes , isLeafNode , escapeEntities ) {
const options = this . options ;
if ( val !== undefined ) {
if ( options . trimValues && ! dontTrim ) {
val = val . trim ();
}
if ( val . length > 0 ) {
if ( ! escapeEntities ) val = this . replaceEntitiesValue ( val , tagName , jPath );
// Pass jPath string or matcher based on options.jPath setting
const jPathOrMatcher = options . jPath ? jPath . toString () : jPath ;
const newval = options . tagValueProcessor ( tagName , val , jPathOrMatcher , hasAttributes , isLeafNode );
if ( newval === null || newval === undefined ) {
//don't parse
return val ;
} else if ( typeof newval !== typeof val || newval !== val ) {
//overwrite
return newval ;
} else if ( options . trimValues ) {
return parseValue ( val , options . parseTagValue , options . numberParseOptions );
} else {
const trimmedVal = val . trim ();
if ( trimmedVal === val ) {
return parseValue ( val , options . parseTagValue , options . numberParseOptions );
} else {
return val ;
}
}
}
}
}
function resolveNameSpace ( tagname ) {
if ( this . options . removeNSPrefix ) {
const tags = tagname . split ( ':' );
const prefix = tagname . charAt ( 0 ) === '/' ? '/' : '' ;
if ( tags [ 0 ] === 'xmlns' ) {
return '' ;
}
if ( tags . length === 2 ) {
tagname = prefix + tags [ 1 ];
}
}
return tagname ;
}
//TODO: change regex to capture NS
//const attrsRegx = new RegExp("([\\w\\-\\.\\:]+)\\s*=\\s*(['\"])((.|\n)*?)\\2","gm");
const attrsRegx = new RegExp ( '([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?' , 'gm' );
function buildAttributesMap ( attrStr , jPath , tagName , force = false ) {
const options = this . options ;
if ( force === true || ( options . ignoreAttributes !== true && typeof attrStr === 'string' )) {
// attrStr = attrStr.replace(/\r?\n/g, ' ');
//attrStr = attrStr || attrStr.trim();
const matches = ( 0 , util /* getAllMatches */ . Xe )( attrStr , attrsRegx );
const len = matches . length ; //don't make it inline
const attrs = {};
// Pre-process values once: trim + entity replacement
// Reused in both matcher update and second pass
const processedVals = new Array ( len );
let hasRawAttrs = false ;
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 val = oldVal ;
if ( options . trimValues ) val = val . trim ();
val = this . replaceEntitiesValue ( val , tagName , this . readonlyMatcher );
processedVals [ i ] = val ;
rawAttrsForMatcher [ attrName ] = val ;
hasRawAttrs = true ;
}
}
// Update matcher ONCE before second pass, if applicable
if ( hasRawAttrs && typeof jPath === 'object' && jPath . updateCurrent ) {
jPath . updateCurrent ( rawAttrsForMatcher );
}
// Hoist toString() once — path doesn't change during attribute processing
const jPathStr = options . jPath ? jPath . toString () : this . readonlyMatcher ;
// Second pass: apply processors, build final attrs
let hasAttrs = false ;
for ( let i = 0 ; i < len ; i ++ ) {
const attrName = this . resolveNameSpace ( matches [ i ][ 1 ]);
if ( this . ignoreAttributesFn ( attrName , jPathStr )) continue ;
let aName = options . attributeNamePrefix + attrName ;
if ( attrName . length ) {
if ( options . transformAttributeName ) {
aName = options . transformAttributeName ( aName );
}
aName = sanitizeName ( aName , options );
if ( matches [ i ][ 4 ] !== undefined ) {
// Reuse already-processed value — no double entity replacement
const oldVal = processedVals [ i ];
const newVal = options . attributeValueProcessor ( attrName , oldVal , jPathStr );
if ( newVal === null || newVal === undefined ) {
attrs [ aName ] = oldVal ;
} else if ( typeof newVal !== typeof oldVal || newVal !== oldVal ) {
attrs [ aName ] = newVal ;
} else {
attrs [ aName ] = parseValue ( oldVal , options . parseAttributeValue , options . numberParseOptions );
}
hasAttrs = true ;
} else if ( options . allowBooleanAttributes ) {
attrs [ aName ] = true ;
hasAttrs = true ;
}
}
}
if ( ! hasAttrs ) return ;
if ( options . attributesGroupName && ! options . preserveOrder ) {
const attrCollection = {};
attrCollection [ options . attributesGroupName ] = attrs ;
return attrCollection ;
}
return attrs ;
}
}
const parseXml = function ( xmlData ) {
xmlData = xmlData . replace ( /\r\n?/g , "\n" ); //TODO: remove this line
const xmlObj = new XmlNode ( '!xml' );
let currentNode = xmlObj ;
let textData = "" ;
// Reset matcher for new document
this . matcher . reset ();
this . entityDecoder . reset ();
// Reset entity expansion counters for this document
this . entityExpansionCount = 0 ;
this . currentExpandedLength = 0 ;
this . doctypefound = false ;
const options = this . options ;
const docTypeReader = new DocTypeReader ( options . processEntities );
const xmlLen = xmlData . length ;
for ( let i = 0 ; i < xmlLen ; i ++ ) { //for each char in XML data
const ch = xmlData [ i ];
if ( ch === '<' ) {
// const nextIndex = i+1;
// const _2ndChar = xmlData[nextIndex];
const c1 = xmlData . charCodeAt ( i + 1 );
if ( c1 === 47 ) { //Closing Tag '/'
const closeIndex = findClosingIndex ( xmlData , ">" , i , "Closing Tag is not closed." )
let tagName = xmlData . substring ( i + 2 , closeIndex ). trim ();
if ( options . removeNSPrefix ) {
const colonIndex = tagName . indexOf ( ":" );
if ( colonIndex !== - 1 ) {
tagName = tagName . substr ( colonIndex + 1 );
}
}
tagName = transformTagName ( options . transformTagName , tagName , "" , options ). tagName ;
if ( currentNode ) {
textData = this . saveTextToParentTag ( textData , currentNode , this . readonlyMatcher );
}
//check if last tag of nested tag was unpaired tag
const lastTagName = this . matcher . getCurrentTag ();
if ( tagName && options . unpairedTagsSet . has ( tagName )) {
throw new Error ( `Unpaired tag can not be used as closing tag: </ ${ tagName } >` );
}
if ( lastTagName && options . unpairedTagsSet . has ( lastTagName )) {
// Pop the unpaired tag
this . matcher . pop ();
this . tagsNodeStack . pop ();
}
// Pop the closing tag
this . matcher . pop ();
this . isCurrentNodeStopNode = false ; // Reset flag when closing tag
2026-08-24 10:36:54 -04:00
//a closing tag with no matching opening tag leaves the stack empty
currentNode = this . tagsNodeStack . pop () || xmlObj ; //avoid recursion, set the parent tag scope
if ( options . captureMetaData && currentNode ) {
currentNode . addEndIndex ( closeIndex + 1 );
}
2026-07-30 15:09:35 -04:00
textData = "" ;
i = closeIndex ;
} else if ( c1 === 63 ) { //'?'
let tagData = readTagExp ( xmlData , i , false , "?>" );
if ( ! tagData ) throw new Error ( "Pi Tag is not closed." );
textData = this . saveTextToParentTag ( textData , currentNode , this . readonlyMatcher );
const attsMap = this . buildAttributesMap ( tagData . tagExp , this . matcher , tagData . tagName , true );
if ( attsMap ) {
const ver = attsMap [ this . options . attributeNamePrefix + "version" ];
this . entityDecoder . setXmlVersion ( Number ( ver ) || 1.0 );
docTypeReader . setXmlVersion ( Number ( ver ) || 1.0 );
}
if (( options . ignoreDeclaration && tagData . tagName === "?xml" ) || options . ignorePiTags ) {
//do nothing
} else {
const childNode = new XmlNode ( tagData . tagName );
childNode . add ( options . textNodeName , "" );
if ( tagData . tagName !== tagData . tagExp && tagData . attrExpPresent && options . ignoreAttributes !== true ) {
childNode [ ":@" ] = attsMap
}
this . addChild ( currentNode , childNode , this . readonlyMatcher , i );
2026-08-24 10:36:54 -04:00
if ( options . captureMetaData ) {
// closeIndex points at '?' of the closing '?>'
currentNode . addEndIndex ( tagData . closeIndex + 2 );
}
2026-07-30 15:09:35 -04:00
}
i = tagData . closeIndex + 1 ;
} else if ( c1 === 33
&& xmlData . charCodeAt ( i + 2 ) === 45
&& xmlData . charCodeAt ( i + 3 ) === 45 ) { //'!--'
const endIndex = findClosingIndex ( xmlData , "-->" , i + 4 , "Comment is not closed." )
if ( options . commentPropName ) {
const comment = xmlData . substring ( i + 4 , endIndex - 2 );
textData = this . saveTextToParentTag ( textData , currentNode , this . readonlyMatcher );
currentNode . add ( options . commentPropName , [{ [ options . textNodeName ] : comment }]);
}
i = endIndex ;
} else if ( c1 === 33
&& xmlData . charCodeAt ( i + 2 ) === 68 ) { //'!D'
if ( this . doctypefound ) throw new Error ( "Multiple DOCTYPE declarations found." );
this . doctypefound = true ;
const result = docTypeReader . readDocType ( xmlData , i );
this . entityDecoder . addInputEntities ( result . entities );
i = result . i ;
} else if ( c1 === 33
&& xmlData . charCodeAt ( i + 2 ) === 91 ) { // '!['
const closeIndex = findClosingIndex ( xmlData , "]]>" , i , "CDATA is not closed." ) - 2 ;
const tagExp = xmlData . substring ( i + 9 , closeIndex );
textData = this . saveTextToParentTag ( textData , currentNode , this . readonlyMatcher );
let val = this . parseTextData ( tagExp , currentNode . tagname , this . readonlyMatcher , true , false , true , true );
if ( val == undefined ) val = "" ;
//cdata should be set even if it is 0 length string
if ( options . cdataPropName ) {
currentNode . add ( options . cdataPropName , [{ [ options . textNodeName ] : tagExp }]);
} else {
currentNode . add ( options . textNodeName , val );
}
i = closeIndex + 2 ;
} else { //Opening tag
let result = readTagExp ( xmlData , i , 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 ( xmlLen , 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 ;
({ tagName , tagExp } = transformTagName ( options . transformTagName , tagName , tagExp , options ));
if ( options . strictReservedNames &&
( tagName === options . commentPropName
|| tagName === options . cdataPropName
|| tagName === options . textNodeName
|| tagName === options . attributesGroupName
)) {
throw new Error ( `Invalid tag name: ${ tagName } ` );
}
//save text as child node
if ( currentNode && textData ) {
if ( currentNode . tagname !== '!xml' ) {
//when nested tag is found
textData = this . saveTextToParentTag ( textData , currentNode , this . readonlyMatcher , false );
}
}
//check if last tag was unpaired tag
const lastTag = currentNode ;
if ( lastTag && options . unpairedTagsSet . has ( lastTag . tagname )) {
currentNode = this . tagsNodeStack . pop ();
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 rawAttrs = {};
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
//TODO: seems a performance overhead
rawAttrs = extractRawAttributes ( prefixedAttrs , options );
}
}
// Now check if this is a stop node (after attributes are set)
if ( tagName !== xmlObj . tagname ) {
this . isCurrentNodeStopNode = this . isItStopNode ();
}
const startIndex = i ;
if ( this . isCurrentNodeStopNode ) {
let tagContent = "" ;
// For self-closing tags, content is empty
if ( isSelfClosing ) {
i = result . closeIndex ;
}
//unpaired tag
else if ( options . unpairedTagsSet . has ( tagName )) {
i = result . closeIndex ;
}
//normal tag
else {
//read until closing tag is found
const result = this . readStopNodeData ( xmlData , rawTagName , closeIndex + 1 );
if ( ! result ) throw new Error ( `Unexpected end of ${ rawTagName } ` );
i = result . i ;
tagContent = result . tagContent ;
}
const childNode = new XmlNode ( tagName );
if ( prefixedAttrs ) {
childNode [ ":@" ] = prefixedAttrs ;
}
// For stop nodes, store raw content as-is without any processing
childNode . add ( options . textNodeName , tagContent );
this . matcher . pop (); // Pop the stop node tag
this . isCurrentNodeStopNode = false ; // Reset flag
this . addChild ( currentNode , childNode , this . readonlyMatcher , startIndex );
2026-08-24 10:36:54 -04:00
if ( options . captureMetaData ) {
currentNode . addEndIndex ( i + 1 );
}
2026-07-30 15:09:35 -04:00
} else {
//selfClosing tag
if ( isSelfClosing ) {
({ tagName , tagExp } = transformTagName ( options . transformTagName , tagName , tagExp , options ));
const childNode = new XmlNode ( tagName );
if ( prefixedAttrs ) {
childNode [ ":@" ] = prefixedAttrs ;
}
this . addChild ( currentNode , childNode , this . readonlyMatcher , startIndex );
2026-08-24 10:36:54 -04:00
if ( options . captureMetaData ) {
currentNode . addEndIndex ( closeIndex + 1 );
}
2026-07-30 15:09:35 -04:00
this . matcher . pop (); // Pop self-closing tag
this . isCurrentNodeStopNode = false ; // Reset flag
}
else if ( options . unpairedTagsSet . has ( tagName )) { //unpaired tag
const childNode = new XmlNode ( tagName );
if ( prefixedAttrs ) {
childNode [ ":@" ] = prefixedAttrs ;
}
this . addChild ( currentNode , childNode , this . readonlyMatcher , startIndex );
2026-08-24 10:36:54 -04:00
if ( options . captureMetaData ) {
currentNode . addEndIndex ( result . closeIndex + 1 );
}
2026-07-30 15:09:35 -04:00
this . matcher . pop (); // Pop unpaired tag
this . isCurrentNodeStopNode = false ; // Reset flag
i = result . closeIndex ;
// Continue to next iteration without changing currentNode
continue ;
}
//opening tag
else {
const childNode = new XmlNode ( tagName );
if ( this . tagsNodeStack . length > options . maxNestedTags ) {
throw new Error ( "Maximum nested tags exceeded" );
}
this . tagsNodeStack . push ( currentNode );
if ( prefixedAttrs ) {
childNode [ ":@" ] = prefixedAttrs ;
}
this . addChild ( currentNode , childNode , this . readonlyMatcher , startIndex );
currentNode = childNode ;
}
textData = "" ;
i = closeIndex ;
}
}
} else {
textData += xmlData [ i ];
}
}
return xmlObj . child ;
}
function addChild ( currentNode , childNode , matcher , startIndex ) {
// unset startIndex if not requested
if ( ! this . options . captureMetaData ) startIndex = undefined ;
// 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 ) {
//do nothing
} else if ( typeof result === "string" ) {
childNode . tagname = result
currentNode . addChild ( childNode , startIndex );
} else {
currentNode . addChild ( childNode , startIndex );
}
}
/**
* @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 || ! entityConfig . enabled ) {
return val ;
}
// Check if tag is allowed to contain entities
if ( entityConfig . allowedTags ) {
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 ) {
const jPathOrMatcher = this . options . jPath ? jPath . toString () : jPath ;
if ( ! entityConfig . tagFilter ( tagName , jPathOrMatcher )) {
return val ; // Skip based on custom filter
}
}
return this . entityDecoder . decode ( val );
}
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 ,
matcher ,
false ,
parentNode [ ":@" ] ? Object . keys ( parentNode [ ":@" ]). length !== 0 : false ,
isLeafNode );
if ( textData !== undefined && textData !== "" )
parentNode . add ( this . options . textNodeName , textData );
textData = "" ;
}
return textData ;
}
/**
* @param {Array<Expression>} stopNodeExpressions - Array of compiled Expression objects
* @param {Matcher} matcher - Current path matcher
*/
function isItStopNode () {
if ( this . stopNodeExpressionsSet . size === 0 ) return false ;
return this . matcher . matchesAny ( this . stopNodeExpressionsSet );
}
/**
* Returns the tag Expression and where it is ending handling single-double quotes situation
* @param {string} xmlData
* @param {number} i starting index
* @returns
*/
function tagExpWithClosingIndex ( xmlData , i , closingChar = ">" ) {
//TODO: ignore boolean attributes in tag expression
//TODO: if ignore attributes, dont read full attribute expression but the end. But read for xml declaration
let attrBoundary = 0 ;
const len = xmlData . length ;
const closeCode0 = closingChar . charCodeAt ( 0 );
const closeCode1 = closingChar . length > 1 ? closingChar . charCodeAt ( 1 ) : - 1 ;
let result = '' ;
let segmentStart = i ;
for ( let index = i ; index < len ; index ++ ) {
const code = xmlData . charCodeAt ( index );
if ( attrBoundary ) {
if ( code === attrBoundary ) attrBoundary = 0 ;
} else if ( code === 34 || code === 39 ) { // " or '
attrBoundary = code ;
} else if ( code === closeCode0 ) {
if ( closeCode1 !== - 1 ) {
if ( xmlData . charCodeAt ( index + 1 ) === closeCode1 ) {
result += xmlData . substring ( segmentStart , index );
return { data : result , index };
}
} else {
result += xmlData . substring ( segmentStart , index );
return { data : result , index };
}
} else if ( code === 9 && ! attrBoundary ) { // \t - only replace with space outside attribute values
// Flush accumulated segment, add space, start new segment
result += xmlData . substring ( segmentStart , index ) + ' ' ;
segmentStart = index + 1 ;
}
}
}
function findClosingIndex ( xmlData , str , i , errMsg ) {
const closingIndex = xmlData . indexOf ( str , i );
if ( closingIndex === - 1 ) {
throw new Error ( errMsg )
} else {
return closingIndex + str . length - 1 ;
}
}
function findClosingChar ( xmlData , char , i , errMsg ) {
const closingIndex = xmlData . indexOf ( char , i );
if ( closingIndex === - 1 ) throw new Error ( errMsg );
return closingIndex ; // no offset needed
}
function readTagExp ( xmlData , i , removeNSPrefix , closingChar = ">" ) {
const result = tagExpWithClosingIndex ( xmlData , i + 1 , closingChar );
if ( ! result ) return ;
let tagExp = result . data ;
const closeIndex = result . index ;
const separatorIndex = tagExp . search ( /\s/ );
let tagName = tagExp ;
let attrExpPresent = true ;
if ( separatorIndex !== - 1 ) { //separate tag name and attributes expression
tagName = tagExp . substring ( 0 , separatorIndex );
tagExp = tagExp . substring ( separatorIndex + 1 ). trimStart ();
}
const rawTagName = tagName ;
if ( removeNSPrefix ) {
const colonIndex = tagName . indexOf ( ":" );
if ( colonIndex !== - 1 ) {
tagName = tagName . substr ( colonIndex + 1 );
attrExpPresent = tagName !== result . data . substr ( colonIndex + 1 );
}
}
return {
tagName : tagName ,
tagExp : tagExp ,
closeIndex : closeIndex ,
attrExpPresent : attrExpPresent ,
rawTagName : rawTagName ,
}
}
/**
* find paired tag for a stop node
* @param {string} xmlData
* @param {string} tagName
* @param {number} i
*/
function readStopNodeData ( xmlData , tagName , i ) {
const startIndex = i ;
// Starting at 1 since we already have an open tag
let openTagCount = 1 ;
const xmllen = xmlData . length ;
for (; i < xmllen ; i ++ ) {
if ( xmlData [ i ] === "<" ) {
const c1 = xmlData . charCodeAt ( i + 1 );
if ( c1 === 47 ) { //close tag '/'
const closeIndex = findClosingChar ( xmlData , ">" , i , ` ${ tagName } is not closed` );
let closeTagName = xmlData . substring ( i + 2 , closeIndex ). trim ();
if ( closeTagName === tagName ) {
openTagCount -- ;
if ( openTagCount === 0 ) {
return {
tagContent : xmlData . substring ( startIndex , i ),
i : closeIndex
}
}
}
i = closeIndex ;
} else if ( c1 === 63 ) { //?
const closeIndex = findClosingIndex ( xmlData , "?>" , i + 1 , "StopNode is not closed." )
i = closeIndex ;
} else if ( c1 === 33
&& xmlData . charCodeAt ( i + 2 ) === 45
&& xmlData . charCodeAt ( i + 3 ) === 45 ) { // '!--'
const closeIndex = findClosingIndex ( xmlData , "-->" , i + 3 , "StopNode is not closed." )
i = closeIndex ;
} else if ( c1 === 33
&& xmlData . charCodeAt ( i + 2 ) === 91 ) { // '!['
const closeIndex = findClosingIndex ( xmlData , "]]>" , i , "StopNode is not closed." ) - 2 ;
i = closeIndex ;
} else {
const tagData = readTagExp ( xmlData , i , false )
if ( tagData ) {
const openTagName = tagData && tagData . tagName ;
if ( openTagName === tagName && tagData . tagExp [ tagData . tagExp . length - 1 ] !== "/" ) {
openTagCount ++ ;
}
i = tagData . closeIndex ;
}
}
}
} //end for loop
}
function parseValue ( val , shouldParse , options ) {
if ( shouldParse && typeof val === 'string' ) {
//console.log(options)
const newval = val . trim ();
if ( newval === 'true' ) return true ;
else if ( newval === 'false' ) return false ;
else return toNumber ( val , options );
} else {
if (( 0 , util /* isExist */ . yQ )( val )) {
return val ;
} else {
return '' ;
}
}
}
function fromCodePoint ( str , base , prefix ) {
const codePoint = Number . parseInt ( str , base );
if ( codePoint >= 0 && codePoint <= 0x10FFFF ) {
return String . fromCodePoint ( codePoint );
} else {
return prefix + str + ";" ;
}
}
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 ( util /* criticalProperties */ . vl . includes ( name )) {
throw new Error ( `[SECURITY] Invalid name: " ${ name } " is a reserved JavaScript keyword that could cause prototype pollution` );
} else if ( util /* DANGEROUS_PROPERTY_NAMES */ . q9 . includes ( name )) {
return options . onDangerousProperty ( name );
}
return name ;
}
; // CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/xmlparser/node2json.js
const node2json_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 , matcher , readonlyMatcher ) {
return compress ( node , options , matcher , readonlyMatcher );
}
/**
* @param {array} arr
* @param {object} options
* @param {Matcher} matcher - Path matcher instance
* @returns object
*/
function compress ( arr , options , matcher , readonlyMatcher ) {
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 );
// 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 ];
else text += "" + tagObj [ property ];
} else if ( property === undefined ) {
continue ;
} else if ( tagObj [ property ]) {
let val = compress ( tagObj [ property ], options , matcher , readonlyMatcher );
const isLeaf = isLeafTag ( val , options );
if ( Object . keys ( val ). length === 0 && options . alwaysCreateTextNode ) {
val [ options . textNodeName ] = "" ;
}
if ( tagObj [ ":@" ]) {
assignAttributes ( val , tagObj [ ":@" ], readonlyMatcher , 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 ) {
if ( options . alwaysCreateTextNode ) val [ options . textNodeName ] = "" ;
else val = "" ;
}
if ( tagObj [ node2json_METADATA_SYMBOL ] !== undefined && typeof val === "object" && val !== null ) {
val [ node2json_METADATA_SYMBOL ] = tagObj [ node2json_METADATA_SYMBOL ]; // copy over metadata
}
if ( compressedObj [ property ] !== undefined && Object . prototype . hasOwnProperty . call ( compressedObj , property )) {
if ( ! Array . isArray ( compressedObj [ property ])) {
compressedObj [ property ] = [ compressedObj [ property ]];
}
compressedObj [ property ]. push ( val );
} 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
// Pass jPath string or readonlyMatcher based on options.jPath setting
const jPathOrMatcher = options . jPath ? readonlyMatcher . toString () : readonlyMatcher ;
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 ();
}
}
}
// if(text && text.length > 0) compressedObj[options.textNodeName] = text;
if ( typeof text === "string" ) {
if ( text . length > 0 ) compressedObj [ options . textNodeName ] = text ;
} else if ( text !== undefined ) compressedObj [ options . textNodeName ] = text ;
return compressedObj ;
}
function propName ( obj ) {
const keys = Object . keys ( obj );
for ( let i = 0 ; i < keys . length ; i ++ ) {
const key = keys [ i ];
if ( key !== ":@" ) return key ;
}
}
function assignAttributes ( obj , attrMap , readonlyMatcher , 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 ]; // 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
? readonlyMatcher . toString () + "." + rawAttrName
: readonlyMatcher ;
if ( options . isArray ( atrrName , jPathOrMatcher , true , true )) {
obj [ atrrName ] = [ attrMap [ atrrName ]];
} else {
obj [ atrrName ] = attrMap [ atrrName ];
}
}
}
}
function isLeafTag ( obj , options ) {
const { textNodeName } = options ;
const propCount = Object . keys ( obj ). length ;
if ( propCount === 0 ) {
return true ;
}
if (
propCount === 1 &&
( obj [ textNodeName ] || typeof obj [ textNodeName ] === "boolean" || obj [ textNodeName ] === 0 )
) {
return true ;
}
return false ;
}
// EXTERNAL MODULE: ./node_modules/fast-xml-parser/src/validator.js
var validator = __webpack_require__ ( 1176 );
; // CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/xmlparser/XMLParser.js
class XMLParser {
constructor ( options ) {
this . externalEntities = {};
this . options = buildOptions ( options );
}
/**
* Parse XML dats to JS object
* @param {string|Uint8Array} xmlData
* @param {boolean|Object} validationOption
*/
parse ( xmlData , validationOption ) {
if ( typeof xmlData !== "string" && xmlData . toString ) {
xmlData = xmlData . toString ();
} else if ( typeof xmlData !== "string" ) {
throw new Error ( "XML data is accepted in String or Bytes[] form." )
}
if ( validationOption ) {
if ( validationOption === true ) validationOption = {}; //validate with default options
const result = ( 0 , validator /* validate */ . t )( xmlData , validationOption );
if ( result !== true ) {
throw Error ( ` ${ result . err . msg } : ${ result . err . line } : ${ result . err . col } ` )
}
}
const orderedObjParser = new OrderedObjParser ( this . options , this . externalEntities );
// orderedObjParser.entityDecoder.setExternalEntities(this.externalEntities);
const orderedResult = orderedObjParser . parseXml ( xmlData );
if ( this . options . preserveOrder || orderedResult === undefined ) return orderedResult ;
else return prettify ( orderedResult , this . options , orderedObjParser . matcher , orderedObjParser . readonlyMatcher );
}
/**
* Add Entity which is not by default supported by this library
* @param {string} key
* @param {string} value
*/
addEntity ( key , value ) {
if ( value . indexOf ( "&" ) !== - 1 ) {
throw new Error ( "Entity value can't have '&'" )
} else if ( key . indexOf ( "&" ) !== - 1 || key . indexOf ( ";" ) !== - 1 ) {
throw new Error ( "An entity must be set without '&' and ';'. Eg. use '#xD' for '
'" )
} else if ( value === "&" ) {
throw new Error ( "An entity with value '&' is not permitted" );
} else {
this . externalEntities [ key ] = value ;
}
}
/**
* Returns a Symbol that can be used to access the metadata
* property on a node.
*
* If Symbol is not available in the environment, an ordinary property is used
* and the name of the property is here returned.
*
* The XMLMetaData property is only present when `captureMetaData`
* is true in the options.
*/
static getMetaDataSymbol () {
return XmlNode . getMetaDataSymbol ();
}
}
/***/ }),
/***/ 3945 :
/***/ (( __unused_webpack___webpack_module__ , __webpack_exports__ , __webpack_require__ ) => {
/* harmony export */ __webpack_require__ . d ( __webpack_exports__ , {
/* harmony export */ A : () => ( /* binding */ Expression )
/* harmony export */ });
/**
* 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 = {}, data ) {
this . pattern = pattern ;
this . separator = options . separator || '.' ;
this . segments = this . _parse ( pattern );
this . data = data ;
// 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 ;
}
}
/***/ }),
/***/ 8257 :
/***/ (( __unused_webpack___webpack_module__ , __webpack_exports__ , __webpack_require__ ) => {
/* harmony export */ __webpack_require__ . d ( __webpack_exports__ , {
/* harmony export */ A : () => ( /* binding */ Matcher )
/* harmony export */ });
/* unused harmony export MatcherView */
/**
* MatcherView - A lightweight read-only view over a Matcher's internal state.
*
* Created once by Matcher and reused across all callbacks. Holds a direct
* reference to the parent Matcher so it always reflects current parser state
* with zero copying or freezing overhead.
*
* Users receive this via {@link Matcher#readOnly} or directly from parser
* callbacks. It exposes all query and matching methods but has no mutation
* methods — misuse is caught at the TypeScript level rather than at runtime.
*
* @example
* const matcher = new Matcher();
* const view = matcher.readOnly();
*
* matcher.push("root", {});
* view.getCurrentTag(); // "root"
* view.getDepth(); // 1
*/
class MatcherView {
/**
* @param {Matcher} matcher - The parent Matcher instance to read from.
*/
constructor ( matcher ) {
this . _matcher = matcher ;
}
/**
* Get the path separator used by the parent matcher.
* @returns {string}
*/
get separator () {
return this . _matcher . separator ;
}
/**
* Get current tag name.
* @returns {string|undefined}
*/
getCurrentTag () {
const path = this . _matcher . path ;
return path . length > 0 ? path [ path . length - 1 ]. tag : undefined ;
}
/**
* Get current namespace.
* @returns {string|undefined}
*/
getCurrentNamespace () {
const path = this . _matcher . path ;
return path . length > 0 ? path [ path . length - 1 ]. namespace : undefined ;
}
/**
* Get current node's attribute value.
* @param {string} attrName
* @returns {*}
*/
getAttrValue ( attrName ) {
const path = this . _matcher . path ;
if ( path . length === 0 ) return undefined ;
return path [ path . length - 1 ]. values ? .[ attrName ];
}
/**
* Check if current node has an attribute.
* @param {string} attrName
* @returns {boolean}
*/
hasAttr ( attrName ) {
const path = this . _matcher . path ;
if ( path . length === 0 ) return false ;
const current = path [ path . length - 1 ];
return current . values !== undefined && attrName in current . values ;
}
/**
* Get the value of a "kept" attribute from the nearest ancestor (or
* current node) that declared it via `push(tag, attrs, ns, { keep: [...] })`.
* @param {string} attrName
* @returns {*}
*/
getAnyParentAttr ( attrName ) {
return this . _matcher . getAnyParentAttr ( attrName );
}
/**
* Check whether any ancestor (or the current node) kept the given
* attribute via `push(tag, attrs, ns, { keep: [...] })`.
* @param {string} attrName
* @returns {boolean}
*/
hasAnyParentAttr ( attrName ) {
return this . _matcher . hasAnyParentAttr ( attrName );
}
/**
* Get current node's sibling position (child index in parent).
* @returns {number}
*/
getPosition () {
const path = this . _matcher . path ;
if ( path . length === 0 ) return - 1 ;
return path [ path . length - 1 ]. position ?? 0 ;
}
/**
* Get current node's repeat counter (occurrence count of this tag name).
* @returns {number}
*/
getCounter () {
const path = this . _matcher . path ;
if ( path . length === 0 ) return - 1 ;
return path [ path . length - 1 ]. counter ?? 0 ;
}
/**
* Get current node's sibling index (alias for getPosition).
* @returns {number}
* @deprecated Use getPosition() or getCounter() instead
*/
getIndex () {
return this . getPosition ();
}
/**
* Get current path depth.
* @returns {number}
*/
getDepth () {
return this . _matcher . path . length ;
}
/**
* Get path as string.
* @param {string} [separator] - Optional separator (uses default if not provided)
* @param {boolean} [includeNamespace=true]
* @returns {string}
*/
toString ( separator , includeNamespace = true ) {
return this . _matcher . toString ( separator , includeNamespace );
}
/**
* Get path as array of tag names.
* @returns {string[]}
*/
toArray () {
return this . _matcher . path . map ( n => n . tag );
}
/**
* Match current path against an Expression.
* @param {Expression} expression
* @returns {boolean}
*/
matches ( expression ) {
return this . _matcher . matches ( expression );
}
/**
* Match any expression in the given set against the current path.
* @param {ExpressionSet} exprSet
* @returns {boolean}
*/
matchesAny ( exprSet ) {
return exprSet . matchesAny ( this . _matcher );
}
}
/**
* 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.
*
* Use {@link Matcher#readOnly} to obtain a {@link MatcherView} safe to pass to
* user callbacks — it always reflects current state with no Proxy overhead.
*
* @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={}]
* @param {string} [options.separator='.'] - Default path separator
*/
constructor ( options = {}) {
this . separator = options . separator || '.' ;
this . path = [];
this . siblingStacks = [];
// Each path node: { tag, values, position, counter, namespace? }
// values only present for current (last) node
// Each siblingStacks entry: Map<tagName, count> tracking occurrences at each level
this . _pathStringCache = null ;
this . _view = new MatcherView ( this );
// Kept-attribute stack: only populated when push() is called with options.keep.
this . _keptAttrs = [];
}
/**
* Push a new tag onto the path.
* @param {string} tagName
* @param {Object|null} [attrValues=null]
* @param {string|null} [namespace=null]
* @param {Object|null} [options=null]
* @param {string[]} [options.keep] - Names of attributes (from attrValues)
*/
push ( tagName , attrValues = null , namespace = null , options = null ) {
this . _pathStringCache = null ;
// Remove values from previous current node (now becoming ancestor)
if ( this . path . length > 0 ) {
this . path [ this . path . length - 1 ]. values = undefined ;
}
// Get or create sibling tracking for current level
const currentLevel = this . path . length ;
let level = this . siblingStacks [ currentLevel ];
if ( ! level ) {
// `counts` tells same-name siblings apart (the "counter" — nth <item>
// among other <item>s). `total` is every child seen at this level so
// far, kept as a running number instead of re-added from `counts` on
// every push — a parent with many differently-named children would
// otherwise cost more per child the more distinct names it has.
level = { counts : new Map (), total : 0 };
this . siblingStacks [ currentLevel ] = level ;
}
// 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 = level . counts . get ( siblingKey ) || 0 ;
// Position = total children at this level seen before this one.
const position = level . total ;
// Update sibling count for this tag, and the level's running total.
level . counts . set ( siblingKey , counter + 1 );
level . total ++ ;
// Create new node
const node = {
tag : tagName ,
position : position ,
counter : counter
};
if ( namespace !== null && namespace !== undefined ) {
node . namespace = namespace ;
}
if ( attrValues !== null && attrValues !== undefined ) {
node . values = attrValues ;
}
this . path . push ( node );
// Depth of the node we just pushed (1-based, matches this.path.length)
const depth = this . path . length ;
// Copy only the requested attributes into the kept-attrs stack. This is
// the one part of push() whose cost scales with input (O(keep.length))
// rather than being O(1) — by design, since the caller is explicitly
// opting in for specific attribute names. No options/keep => zero added
// cost beyond the two property reads below.
const keep = options !== null ? options . keep : null ;
if ( keep !== null && keep !== undefined && keep . length > 0 && attrValues ) {
for ( let i = 0 ; i < keep . length ; i ++ ) {
const name = keep [ i ];
if ( attrValues [ name ] !== undefined ) {
this . _keptAttrs . push ({ depth , name , value : attrValues [ name ] });
}
}
}
}
/**
* Pop the last tag from the path.
* @returns {Object|undefined} The popped node
*/
pop () {
if ( this . path . length === 0 ) return undefined ;
this . _pathStringCache = null ;
const node = this . path . pop ();
if ( this . siblingStacks . length > this . path . length + 1 ) {
this . siblingStacks . length = this . path . length + 1 ;
}
// Drop any kept attributes that belonged to the popped node (or deeper).
// _keptAttrs is depth-ordered (push only ever appends increasing depths),
// so this is a backward scan that stops at the first surviving entry —
// typically O(1) since kept attrs are rare by design.
const poppedDepth = this . path . length + 1 ;
while (
this . _keptAttrs . length > 0 &&
this . _keptAttrs [ this . _keptAttrs . length - 1 ]. depth >= poppedDepth
) {
this . _keptAttrs . pop ();
}
return node ;
}
/**
* Update current node's attribute values.
* Useful when attributes are parsed after push.
* @param {Object} attrValues
*/
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
* @returns {*}
*/
getAttrValue ( attrName ) {
if ( this . path . length === 0 ) return undefined ;
return this . path [ this . path . length - 1 ]. values ? .[ attrName ];
}
/**
* Check if current node has an attribute.
* @param {string} attrName
* @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 the value of a "kept" attribute from the nearest ancestor (or
* current node) that declared it via `push(tag, attrs, ns, { keep: [...] })`.
* Unlike getAttrValue(), this works regardless of how deep the path has
* gone since the attribute was pushed — but only for attribute names that
* were explicitly marked with `keep` at push time. Cost is proportional to
* the number of currently-kept attributes (typically 0-3), not path depth.
* @param {string} attrName
* @returns {*} the value, or undefined if no ancestor kept this attribute
*/
getAnyParentAttr ( attrName ) {
const kept = this . _keptAttrs ;
for ( let i = kept . length - 1 ; i >= 0 ; i -- ) {
if ( kept [ i ]. name === attrName ) return kept [ i ]. value ;
}
return undefined ;
}
/**
* Check whether any ancestor (or the current node) kept the given
* attribute via `push(tag, attrs, ns, { keep: [...] })`.
* @param {string} attrName
* @returns {boolean}
*/
hasAnyParentAttr ( attrName ) {
const kept = this . _keptAttrs ;
for ( let i = kept . length - 1 ; i >= 0 ; i -- ) {
if ( kept [ i ]. name === attrName ) return true ;
}
return false ;
}
/**
* 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).
* @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=true]
* @returns {string}
*/
toString ( separator , includeNamespace = true ) {
const sep = separator || this . separator ;
const isDefault = ( sep === this . separator && includeNamespace === true );
if ( isDefault ) {
if ( this . _pathStringCache !== null ) {
return this . _pathStringCache ;
}
const result = this . path . map ( n =>
( n . namespace ) ? ` ${ n . namespace } : ${ n . tag } ` : n . tag
). join ( sep );
this . _pathStringCache = result ;
return result ;
}
return this . path . map ( n =>
( includeNamespace && n . namespace ) ? ` ${ n . namespace } : ${ n . tag } ` : 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 . _pathStringCache = null ;
this . path = [];
this . siblingStacks = [];
this . _keptAttrs = [];
}
/**
* Match current path against an Expression.
* @param {Expression} expression
* @returns {boolean}
*/
matches ( expression ) {
const segments = expression . segments ;
if ( segments . length === 0 ) {
return false ;
}
if ( expression . hasDeepWildcard ()) {
return this . _matchWithDeepWildcard ( segments );
}
return this . _matchSimple ( segments );
}
/**
* @private
*/
_matchSimple ( segments ) {
if ( this . path . length !== segments . length ) {
return false ;
}
for ( let i = 0 ; i < segments . length ; i ++ ) {
if ( ! this . _matchSegment ( segments [ i ], this . path [ i ], i === this . path . length - 1 )) {
return false ;
}
}
return true ;
}
/**
* @private
*/
_matchWithDeepWildcard ( segments ) {
let pathIdx = this . path . length - 1 ;
let segIdx = segments . length - 1 ;
while ( segIdx >= 0 && pathIdx >= 0 ) {
const segment = segments [ segIdx ];
if ( segment . type === 'deep-wildcard' ) {
segIdx -- ;
if ( segIdx < 0 ) {
return true ;
}
const nextSeg = segments [ segIdx ];
let found = false ;
for ( let i = pathIdx ; i >= 0 ; i -- ) {
if ( this . _matchSegment ( nextSeg , this . path [ i ], i === this . path . length - 1 )) {
pathIdx = i - 1 ;
segIdx -- ;
found = true ;
break ;
}
}
if ( ! found ) {
return false ;
}
} else {
if ( ! this . _matchSegment ( segment , this . path [ pathIdx ], pathIdx === this . path . length - 1 )) {
return false ;
}
pathIdx -- ;
segIdx -- ;
}
}
return segIdx < 0 ;
}
/**
* @private
*/
_matchSegment ( segment , node , isCurrentNode ) {
if ( segment . tag !== '*' && segment . tag !== node . tag ) {
return false ;
}
if ( segment . namespace !== undefined ) {
if ( segment . namespace !== '*' && segment . namespace !== node . namespace ) {
return false ;
}
}
if ( segment . attrName !== undefined ) {
if ( ! isCurrentNode ) {
return false ;
}
if ( ! node . values || ! ( segment . attrName in node . values )) {
return false ;
}
if ( segment . attrValue !== undefined ) {
if ( String ( node . values [ segment . attrName ]) !== String ( segment . attrValue )) {
return false ;
}
}
}
if ( segment . position !== undefined ) {
if ( ! isCurrentNode ) {
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' && counter !== segment . positionValue ) {
return false ;
}
}
return true ;
}
/**
* Match any expression in the given set against the current path.
* @param {ExpressionSet} exprSet
* @returns {boolean}
*/
matchesAny ( exprSet ) {
return exprSet . matchesAny ( this );
}
/**
* Create a snapshot of current state.
* @returns {Object}
*/
snapshot () {
return {
path : this . path . map ( node => ({ ... node })),
siblingStacks : this . siblingStacks . map ( level => level ? { counts : new Map ( level . counts ), total : level . total } : level ),
keptAttrs : this . _keptAttrs . map ( entry => ({ ... entry }))
};
}
/**
* Restore state from snapshot.
* @param {Object} snapshot
*/
restore ( snapshot ) {
this . _pathStringCache = null ;
this . path = snapshot . path . map ( node => ({ ... node }));
this . siblingStacks = snapshot . siblingStacks . map ( level => level ? { counts : new Map ( level . counts ), total : level . total } : level );
this . _keptAttrs = ( snapshot . keptAttrs || []). map ( entry => ({ ... entry }));
}
/**
* Return the read-only {@link MatcherView} for this matcher.
*
* The same instance is returned on every call — no allocation occurs.
* It always reflects the current parser state and is safe to pass to
* user callbacks without risk of accidental mutation.
*
* @returns {MatcherView}
*
* @example
* const view = matcher.readOnly();
* // pass view to callbacks — it stays in sync automatically
* view.matches(expr); // ✓
* view.getCurrentTag(); // ✓
* // view.push(...) // ✗ method does not exist — caught by TypeScript
*/
readOnly () {
return this . _view ;
}
}
/***/ }),
/***/ 4658 :
/***/ (( __unused_webpack___webpack_module__ , __webpack_exports__ , __webpack_require__ ) => {
/* harmony export */ __webpack_require__ . d ( __webpack_exports__ , {
/* harmony export */ fG : () => ( /* binding */ qName ),
/* harmony export */ fH : () => ( /* binding */ createValidator )
/* harmony export */ });
/* unused harmony exports name, ncName, nmToken, nmTokens, validate, validateAll, sanitize */
/**
* xml-naming
* Validates XML Name productions as defined in the XML 1.0 and 1.1 specifications.
* Covers: Name, NCName, QName, NMToken, NMTokens
*
* XML 1.0 spec: https://www.w3.org/TR/xml/#NT-Name
* XML 1.1 spec: https://www.w3.org/TR/xml11/#NT-NameStartChar
* XML NS spec: https://www.w3.org/TR/xml-names/#NT-NCName
*/
// ---------------------------------------------------------------------------
// Character class strings — XML 1.0
//
// NameStartChar ::= ":" | [A-Z] | "_" | [a-z]
// | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF]
// | [#x370-#x37D] | [#x37F-#x1FFF] <- split to exclude #x0487
// | [#x200C-#x200D]
// | [#x2070-#x218F] | [#x2C00-#x2FEF]
// | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD]
//
// NameChar ::= NameStartChar | "-" | "." | [0-9]
// | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
//
// Note: \u0487 (Combining Cyrillic Millions Sign) was added in Unicode 4.0,
// after XML 1.0 was defined against Unicode 2.0. It falls inside the range
// \u037F-\u1FFF but must be excluded. We split that range into
// \u037F-\u0486 and \u0488-\u1FFF to exclude it explicitly.
// ---------------------------------------------------------------------------
const nameStartChar10 =
':A-Za-z_' +
'\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF' +
'\u0370-\u037D' +
'\u037F-\u0486\u0488-\u1FFF' + // split to exclude \u0487
'\u200C-\u200D' +
'\u2070-\u218F' +
'\u2C00-\u2FEF' +
'\u3001-\uD7FF' +
'\uF900-\uFDCF' +
'\uFDF0-\uFFFD' ;
const nameChar10 =
nameStartChar10 +
'\\-\\.\\d' +
'\u00B7' +
'\u0300-\u036F' +
'\u203F-\u2040' ;
// ---------------------------------------------------------------------------
// Character class strings — XML 1.1
//
// Differences from XML 1.0:
//
// NameStartChar:
// 1.0 has split ranges: \u00C0-\u00D6, \u00D8-\u00F6, \u00F8-\u02FF
// 1.1 merges them into: \u00C0-\u02FF
// (\u00D7 x and \u00F7 / are division symbols, excluded in both versions)
//
// 1.0 tops out at \uFFFD (BMP only)
// 1.1 adds \u{10000}-\u{EFFFF} (supplementary planes)
// These require the /u flag on the RegExp — see buildRegexes below.
//
// NameChar:
// 1.1 adds \u0487 (Combining Cyrillic Millions Sign, added in Unicode 4.0)
// ---------------------------------------------------------------------------
const nameStartChar11 =
':A-Za-z_' +
'\u00C0-\u02FF' + // merged — 1.0 had three split ranges here
'\u0370-\u037D' +
'\u037F-\u0486\u0488-\u1FFF' + // split to exclude \u0487 (combining mark, never a NameStartChar)
'\u200C-\u200D' +
'\u2070-\u218F' +
'\u2C00-\u2FEF' +
'\u3001-\uD7FF' +
'\uF900-\uFDCF' +
'\uFDF0-\uFFFD' +
'\u{10000}-\u{EFFFF}' ; // supplementary planes — REQUIRES /u flag on RegExp
const nameChar11 =
nameStartChar11 +
'\\-\\.\\d' +
'\u00B7' +
'\u0300-\u036F' +
'\u0487' + // Combining Cyrillic Millions Sign — valid in 1.1, not 1.0
'\u203F-\u2040' ;
// ---------------------------------------------------------------------------
// Regex builders
//
// XML 1.0 regexes: no flags — BMP only, standard JS regex behaviour.
// XML 1.1 regexes: /u flag — required for \u{10000}-\u{EFFFF} to match actual
// supplementary code points rather than lone surrogates (which are illegal XML).
// ---------------------------------------------------------------------------
const buildRegexes = ( startChar , char , flags = '' ) => {
const ncStart = startChar . replace ( ':' , '' );
const ncChar = char . replace ( ':' , '' );
const ncNamePat = `[ ${ ncStart } ][ ${ ncChar } ]*` ;
return {
name : new RegExp ( `^[ ${ startChar } ][ ${ char } ]*$` , flags ),
ncName : new RegExp ( `^ ${ ncNamePat } $` , flags ),
qName : new RegExp ( `^ ${ ncNamePat } (?:: ${ ncNamePat } )?$` , flags ),
nmToken : new RegExp ( `^[ ${ char } ]+$` , flags ),
nmTokens : new RegExp ( `^[ ${ char } ]+(?:\\s+[ ${ char } ]+)*$` , flags ),
};
};
const regexes10 = buildRegexes ( nameStartChar10 , nameChar10 ); // no /u — BMP only
const regexes11 = buildRegexes ( nameStartChar11 , nameChar11 , 'u' ); // /u — enables \u{10000}-\u{EFFFF}
// ---------------------------------------------------------------------------
// ASCII-only fast path (opt-in, off by default)
//
// The XML 1.0 vs 1.1 NameStartChar/NameChar productions differ *only* in
// their non-ASCII ranges (merged vs split Latin-1 ranges, \u0487, and
// supplementary planes). Restricted to ASCII, both versions collapse to the
// same character classes, so a single regex pair covers both xmlVersion
// values — no /u flag needed.
//
// Rationale: unicode-aware regexes (the /u flag, required for XML 1.1's
// supplementary-plane range) are measurably slower in V8 than plain
// non-unicode regexes on the same input, even when the input is pure ASCII.
// For the common case — HTML/SVG ids, XML tags — names are ASCII, so callers
// who know this can opt in to skip the unicode-aware matching path entirely.
// This is a real but *conditional* win: mainly for XML 1.1 input (avoids /u),
// or at scale where the larger unicode character classes add engine
// overhead. It also changes behaviour (rejects legitimate non-ASCII XML
// 1.0/1.1 names), so it must never be silently enabled — hence off by
// default.
// ---------------------------------------------------------------------------
const nameStartCharAscii = ':A-Za-z_' ;
const nameCharAscii = nameStartCharAscii + '\\-\\.\\d' ;
const regexesAscii = buildRegexes ( nameStartCharAscii , nameCharAscii ); // no /u — ASCII only
const getRegexes = ( xmlVersion = '1.0' , asciiOnly = false ) => {
if ( asciiOnly ) return regexesAscii ;
return xmlVersion === '1.1' ? regexes11 : regexes10 ;
};
// ---------------------------------------------------------------------------
// Boolean validators
// ---------------------------------------------------------------------------
/**
* Returns true if the string is a valid XML Name.
* Colons are allowed anywhere (Name production).
* Used for: DOCTYPE entity names, notation names, DTD element declarations.
*
* @param {{ xmlVersion?: '1.0'|'1.1', asciiOnly?: boolean }} [opts]
* asciiOnly: skip unicode-aware matching, ASCII names only (default false).
*/
const name = ( str , { xmlVersion = '1.0' , asciiOnly = false } = {}) =>
getRegexes ( xmlVersion , asciiOnly ). name . test ( str );
/**
* Returns true if the string is a valid NCName (Non-Colonized Name).
* Colons are not permitted.
* Used for: namespace prefixes, local names, SVG id attributes.
*
* @param {{ xmlVersion?: '1.0'|'1.1', asciiOnly?: boolean }} [opts]
* asciiOnly: skip unicode-aware matching, ASCII names only (default false).
*/
const ncName = ( str , { xmlVersion = '1.0' , asciiOnly = false } = {}) =>
getRegexes ( xmlVersion , asciiOnly ). ncName . test ( str );
/**
* Returns true if the string is a valid QName (Qualified Name).
* Allows exactly one colon as a prefix separator: prefix:localName.
* Used for: element and attribute names in namespace-aware XML/SVG.
*
* @param {{ xmlVersion?: '1.0'|'1.1', asciiOnly?: boolean }} [opts]
* asciiOnly: skip unicode-aware matching, ASCII names only (default false).
*/
const qName = ( str , { xmlVersion = '1.0' , asciiOnly = false } = {}) =>
getRegexes ( xmlVersion , asciiOnly ). qName . test ( str );
/**
* Returns true if the string is a valid NMToken.
* Like Name but no restriction on the first character.
* Used for: DTD NMTOKEN attribute values.
*
* @param {{ xmlVersion?: '1.0'|'1.1', asciiOnly?: boolean }} [opts]
* asciiOnly: skip unicode-aware matching, ASCII names only (default false).
*/
const nmToken = ( str , { xmlVersion = '1.0' , asciiOnly = false } = {}) =>
getRegexes ( xmlVersion , asciiOnly ). nmToken . test ( str );
/**
* Returns true if the string is a valid NMTokens value.
* A whitespace-separated list of NMToken values.
* Used for: DTD NMTOKENS attribute values.
*
* @param {{ xmlVersion?: '1.0'|'1.1', asciiOnly?: boolean }} [opts]
* asciiOnly: skip unicode-aware matching, ASCII names only (default false).
*/
const nmTokens = ( str , { xmlVersion = '1.0' , asciiOnly = false } = {}) =>
getRegexes ( xmlVersion , asciiOnly ). nmTokens . test ( str );
// ---------------------------------------------------------------------------
// Memoized validator factory
//
// Real documents reuse a small vocabulary of tag/attribute names across many
// siblings (e.g. `id`, `class`, `href` repeated across hundreds of elements).
// The plain boolean validators above re-run the regex on every call
// regardless of repeats. `createValidator` returns a closure with a private
// string -> boolean cache, so repeated names after the first become O(1)
// lookups instead of regex tests.
//
// - opts (xmlVersion, asciiOnly) are fixed at creation time, so the regex is
// resolved once, not on every call.
// - The cache is private to the returned closure — no shared/global state,
// no cross-caller pollution.
// - `maxCacheSize` bounds memory: once the cache reaches this many entries,
// it stops accepting new ones (existing entries keep serving hits; new
// misses just fall through to the regex, uncached). This avoids unbounded
// growth against adversarial/high-cardinality input (e.g. validating
// attacker-supplied names with no repeats) without the cost/complexity of
// a full LRU, and without the perf cliff of reset-and-refill thrashing.
// - Call `.reset()` on the returned function to clear the cache manually
// (e.g. between unrelated parse calls).
// ---------------------------------------------------------------------------
const PRODUCTIONS = [ 'name' , 'ncName' , 'qName' , 'nmToken' , 'nmTokens' ];
/**
* Returns a memoized boolean validator function for a single production,
* with opts fixed at creation time.
*
* @param {'name'|'ncName'|'qName'|'nmToken'|'nmTokens'} production
* @param {{ xmlVersion?: '1.0'|'1.1', asciiOnly?: boolean, maxCacheSize?: number }} [opts]
* maxCacheSize: max number of distinct strings to cache (default 2048).
* Once reached, new strings are validated but not cached; existing cached
* entries keep being served.
* @returns {((str: string) => boolean) & { reset: () => void }}
*/
const createValidator = ( production , { xmlVersion = '1.0' , asciiOnly = false , maxCacheSize = 2048 } = {}) => {
if ( ! PRODUCTIONS . includes ( production )) {
throw new TypeError (
`Unknown production " ${ production } ". Must be one of: ${ PRODUCTIONS . join ( ', ' ) } `
);
}
const regex = getRegexes ( xmlVersion , asciiOnly )[ production ];
let cache = new Map ();
const validator = ( str ) => {
const cached = cache . get ( str );
if ( cached !== undefined ) return cached ;
const result = regex . test ( str );
if ( cache . size < maxCacheSize ) cache . set ( str , result );
return result ;
};
validator . reset = () => { cache = new Map (); };
return validator ;
};
// ---------------------------------------------------------------------------
// Diagnostic validator
// ---------------------------------------------------------------------------
/**
* Validates a string against a named production and returns a detailed result.
*
* @param {string} str
* @param {'name'|'ncName'|'qName'|'nmToken'|'nmTokens'} production
* @param {{ xmlVersion?: '1.0'|'1.1', asciiOnly?: boolean }} [opts]
* @returns {{ valid: boolean, production: string, input: string, reason?: string, position?: number }}
*/
const validate = ( str , production , { xmlVersion = '1.0' , asciiOnly = false } = {}) => {
if ( ! PRODUCTIONS . includes ( production )) {
throw new TypeError (
`Unknown production " ${ production } ". Must be one of: ${ PRODUCTIONS . join ( ', ' ) } `
);
}
const validators = { name , ncName , qName , nmToken , nmTokens };
const isValid = validators [ production ]( str , { xmlVersion , asciiOnly });
if ( isValid ) return { valid : true , production , input : str };
let reason = 'Does not match the production rules' ;
let position ;
// Diagnostic fallback char checks must mirror the same character set the
// boolean validator above used, or the reported reason/position could
// contradict the `valid: false` result (e.g. flagging a char as illegal
// that the unicode-aware check would have accepted).
const startCharPattern = asciiOnly ? /^[:A-Za-z_]/ : /^[:A-Za-z_\u00C0-\uFFFD]/ ;
const namePattern = asciiOnly ? /[\w\-\\.:]/ : /[\w\-\\.:\u00B7\u00C0-\uFFFD]/ ;
if ( str . length === 0 ) {
reason = 'Input is empty' ;
} else if ( production === 'ncName' && str . includes ( ':' )) {
position = str . indexOf ( ':' );
reason = 'Colon is not allowed in NCName' ;
} else if ( production === 'qName' && str . startsWith ( ':' )) {
reason = 'QName cannot start with a colon' ;
position = 0 ;
} else if ( production === 'qName' && str . endsWith ( ':' )) {
reason = 'QName cannot end with a colon' ;
position = str . length - 1 ;
} else if ( production === 'qName' && ( str . match ( /:/g ) || []). length > 1 ) {
reason = 'QName can have at most one colon' ;
position = str . lastIndexOf ( ':' );
} else if (
[ 'name' , 'ncName' , 'qName' ]. includes ( production ) &&
! startCharPattern . test ( str [ 0 ])
) {
reason = `First character " ${ str [ 0 ] } " is not a valid NameStartChar` ;
position = 0 ;
} else {
for ( let i = 0 ; i < str . length ; i ++ ) {
if ( ! namePattern . test ( str [ i ])) {
reason = `Character " ${ str [ i ] } " at position ${ i } is not a valid NameChar` ;
position = i ;
break ;
}
}
}
return { valid : false , production , input : str , reason , position };
};
// ---------------------------------------------------------------------------
// Batch validator
// ---------------------------------------------------------------------------
/**
* Validates an array of strings against a named production.
*
* @param {string[]} strings
* @param {'name'|'ncName'|'qName'|'nmToken'|'nmTokens'} production
* @param {{ xmlVersion?: '1.0'|'1.1', asciiOnly?: boolean }} [opts]
* @returns {Array<{ valid: boolean, production: string, input: string, reason?: string, position?: number }>}
*/
const validateAll = ( strings , production , opts = {}) =>
strings . map ( str => validate ( str , production , opts ));
// ---------------------------------------------------------------------------
// Sanitizer
// ---------------------------------------------------------------------------
/**
* Transforms an invalid string into the nearest valid XML name for the given production.
*
* @param {string} str
* @param {'name'|'ncName'|'qName'|'nmToken'|'nmTokens'} production
* @param {{ replacement?: string, asciiOnly?: boolean }} [opts]
* asciiOnly: also replace any non-ASCII character, not just XML-illegal
* ones (default false).
* @returns {string}
*/
const sanitize = ( str , production = 'name' , { replacement = '_' , asciiOnly = false } = {}) => {
if ( ! str ) return replacement ;
let result = str ;
// Strip colons for NCName
if ( production === 'ncName' ) {
result = result . replace ( /:/g , '' );
}
// Replace illegal characters
const allowedCharPattern = asciiOnly ? /[^\w\-\.:]/g : /[^\w\-\.:\u00B7\u00C0-\uFFFD]/g ;
result = result . replace ( allowedCharPattern , replacement );
// Fix invalid start character for Name / NCName / QName
if ( production !== 'nmToken' && production !== 'nmTokens' ) {
if ( /^[\-\.\d]/ . test ( result )) {
result = replacement + result ;
}
}
return result || replacement ;
};
/***/ })
};