初始化环境文件

This commit is contained in:
CN-JS-HuiBai
2026-04-04 12:49:09 +08:00
parent 07742d2688
commit c607af6fac
5971 changed files with 515160 additions and 18 deletions

759
node_modules/fast-xml-parser/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,759 @@
<small>Note: If you find missing information about particular minor version, that version must have been changed without any functional change in this library.</small>
Note: Due to some last quick changes on v4, detail of v4.5.3 & v4.5.4 are not updated here. v4.5.4x is the last tag of v4 in github repository. I'm extremely sorry for the confusion
**5.5.8 / 2026-03-20**
- pass read only matcher in callback
**5.5.7 / 2026-03-19**
- fix: entity expansion limits
- update strnum package to 2.2.0
**5.5.6 / 2026-03-16**
- update builder dependency
- fix incorrect regex to replace \. in entity name
- fix check for entitiy expansion for lastEntities and html entities too
**5.5.5 / 2026-03-13**
- sanitize dangerous tag or attribute name
- error on critical property name
- support onDangerousProperty option
**5.5.4 / 2026-03-13**
- declare Matcher & Expression as unknown so user is not forced to install path-expression-matcher
**5.5.3 / 2026-03-11**
- upgrade builder
**5.5.2 / 2026-03-11**
- update dependency to fix typings
**5.5.1 / 2026-03-10**
- fix dependency
**5.5.0 / 2026-03-10**
- support path-expression-matcher
- fix: stopNode should not be parsed
- performance improvement for stopNode checking
**5.4.2 / 2026-03-03**
- support maxEntityCount option
**5.4.1 / 2026-02-25**
- fix (#785) unpairedTag node should not have tag content
**5.4.0 / 2026-02-25**
- migrate to fast-xml-builder
**5.3.9 / 2026-02-25**
- support strictReservedNames
**5.3.8 / 2026-02-25**
- support maxNestedTags
- handle non-array input for XML builder when preserveOrder is true (By [Angelo Coetzee](https://github.com/Angelopvtac))
- save use of js properies
**5.3.7 / 2026-02-20**
- fix typings for CJS (By [Corentin Girard](https://github.com/Drarig29))
**5.3.6 / 2026-02-14**
- Improve security and performance of entity processing
- new options `maxEntitySize`, `maxExpansionDepth`, `maxTotalExpansions`, `maxExpandedLength`, `allowedTags`,`tagFilter`
- fast return when no edtity is present
- improvement replacement logic to reduce number of calls
**5.3.5 / 2026-02-08**
- fix: Escape regex char in entity name
- update strnum to 2.1.2
- add missing exports in CJS typings
**5.3.4 / 2026-01-30**
- fix: handle HTML numeric and hex entities when out of range
**5.3.3 / 2025-12-12**
- fix #775: transformTagName with allowBooleanAttributes adds an unnecessary attribute
**5.3.2 / 2025-11-14**
- fix for import statement for v6
**5.3.1 / 2025-11-03**
- Performance improvement for stopNodes (By [Maciek Lamberski](https://github.com/macieklamberski))
**5.3.0 / 2025-10-03**
- Use `Uint8Array` in place of `Buffer` in Parser
**5.2.5 / 2025-06-08**
- Inform user to use [fxp-cli](https://github.com/NaturalIntelligence/fxp-cli) instead of in-built CLI feature
- Export typings for direct use
**5.2.4 / 2025-06-06**
- fix (#747): fix EMPTY and ANY with ELEMENT in DOCTYPE
**5.2.3 / 2025-05-11**
- fix (#747): support EMPTY and ANY with ELEMENT in DOCTYPE
**5.2.2 / 2025-05-05**
- fix (#746): update strnum to fix parsing issues related to enotations
**5.2.1 / 2025-04-22**
- fix: read DOCTYPE entity value correctly
- read DOCTYPE NOTATION, ELEMENT exp but not using read values
**5.2.0 / 2025-04-03**
- feat: support metadata on nodes (#593) (By [Steven R. Loomis](https://github.com/srl295))
**5.1.0 / 2025-04-02**
- feat: declare package as side-effect free (#738) (By [Thomas Bouffard](https://github.com/tbouffard))
- fix cjs build mode
- fix builder return type to string
-
**5.0.9 / 2025-03-14**
- fix: support numeric entities with values over 0xFFFF (#726) (By [Marc Durdin](https://github.com/mcdurdin))
- fix: update strnum to fix parsing 0 if skiplike option is used
**5.0.8 / 2025-02-27**
- fix parsing 0 if skiplike option is used.
- updating strnum dependency
**5.0.7 / 2025-02-25**
- fix (#724) typings for cjs.
**5.0.6 / 2025-02-20**
- fix cli output (By [Angel Delgado](https://github.com/angeld7))
- remove multiple JSON parsing
**5.0.5 / 2025-02-20**
- fix parsing of string starting with 'e' or 'E' by updating strnum
**5.0.4 / 2025-02-20**
- fix CLI to support all the versions of node js when displaying library version.
- fix CJS import in v5
- by fixing webpack config
**5.0.3 / 2025-02-20**
- Using strnum ESM module
- new fixes in strum may break your experience
**5.0.2 / 2025-02-20**
- fix: include CommonJS resources in the npm package #714 (By [Thomas Bouffard](https://github.com/tbouffard))
- fix: move babel deps to dev deps
**5.0.1 / 2025-02-19**
- fix syntax error for CLI command
**5.0.0 / 2025-02-19**
- ESM support
- no change in the functionality, syntax, APIs, options, or documentation.
**4.5.2 / 2025-02-18**
- Fix null CDATA to comply with undefined behavior (#701) (By [Matthieu BOHEAS](https://github.com/Kelgors))
- Fix(performance): Update check for leaf node in saveTextToParentTag function in OrderedObjParser.js (#707) (By [...](https://github.com/tomingtoming))
- Fix: emit full JSON string from CLI when no output filename specified (#710) (By [Matt Benson](https://github.com/mbenson))
**4.5.1 / 2024-12-15**
- Fix empty tag key name for v5 (#697). no impact on v4
- Fixes entity parsing when used in strict mode (#699)
**4.5.0 / 2024-09-03**
- feat #666: ignoreAttributes support function, and array of string or regex (By [ArtemM](https://github.com/mav-rik))
**4.4.1 / 2024-07-28**
- v5 fix: maximum length limit to currency value
- fix #634: build attributes with oneListGroup and attributesGroupName (#653)(By [Andreas Naziris](https://github.com/a-rasin))
- fix: get oneListGroup to work as expected for array of strings (#662)(By [Andreas Naziris](https://github.com/a-rasin))
**4.4.0 / 2024-05-18**
- fix #654: parse attribute list correctly for self closing stop node.
- fix: validator bug when closing tag is not opened. (#647) (By [Ryosuke Fukatani](https://github.com/RyosukeFukatani))
- fix #581: typings; return type of `tagValueProcessor` & `attributeValueProcessor` (#582) (By [monholm]())
**4.3.6 / 2024-03-16**
- Add support for parsing HTML numeric entities (#645) (By [Jonas Schade ](https://github.com/DerZade))
**4.3.5 / 2024-02-24**
- code for v5 is added for experimental use
**4.3.4 / 2024-01-10**
- fix: Don't escape entities in CDATA sections (#633) (By [wackbyte](https://github.com/wackbyte))
**4.3.3 / 2024-01-10**
- Remove unnecessary regex
**4.3.2 / 2023-10-02**
- fix `jObj.hasOwnProperty` when give input is null (By [Arda TANRIKULU](https://github.com/ardatan))
**4.3.1 / 2023-09-24**
- revert back "Fix typings for builder and parser to make return type generic" to avoid failure of existing projects. Need to decide a common approach.
**4.3.0 / 2023-09-20**
- Fix stopNodes to work with removeNSPrefix (#607) (#608) (By [Craig Andrews]https://github.com/candrews))
- Fix #610 ignore properties set to Object.prototype
- Fix typings for builder and parser to make return type generic (By [Sarah Dayan](https://github.com/sarahdayan))
**4.2.7 / 2023-07-30**
- Fix: builder should set text node correctly when only textnode is present (#589) (By [qianqing](https://github.com/joneqian))
- Fix: Fix for null and undefined attributes when building xml (#585) (#598). A null or undefined value should be ignored. (By [Eugenio Ceschia](https://github.com/cecia234))
**4.2.6 / 2023-07-17**
- Fix: Remove trailing slash from jPath for self-closing tags (#595) (By [Maciej Radzikowski](https://github.com/m-radzikowski))
**4.2.5 / 2023-06-22**
- change code implementation
**4.2.4 / 2023-06-06**
- fix security bug
**4.2.3 / 2023-06-05**
- fix security bug
**4.2.2 / 2023-04-18**
- fix #562: fix unpaired tag when it comes in last of a nested tag. Also throw error when unpaired tag is used as closing tag
**4.2.1 / 2023-04-18**
- fix: jpath after unpaired tags
**4.2.0 / 2023-04-09**
- support `updateTag` parser property
**4.1.4 / 2023-04-08**
- update typings to let user create XMLBuilder instance without options (#556) (By [Patrick](https://github.com/omggga))
- fix: IsArray option isn't parsing tags with 0 as value correctly #490 (#557) (By [Aleksandr Murashkin](https://github.com/p-kuen))
- feature: support `oneListGroup` to group repeated children tags udder single group
**4.1.3 / 2023-02-26**
- fix #546: Support complex entity value
**4.1.2 / 2023-02-12**
- Security Fix
**4.1.1 / 2023-02-03**
- Fix #540: ignoreAttributes breaks unpairedTags
- Refactor XML builder code
**4.1.0 / 2023-02-02**
- Fix '<' or '>' in DTD comment throwing an error. (#533) (By [Adam Baker](https://github.com/Cwazywierdo))
- Set "eNotation" to 'true' as default
**4.0.15 / 2023-01-25**
- make "eNotation" optional
**4.0.14 / 2023-01-22**
- fixed: add missed typing "eNotation" to parse values
**4.0.13 / 2023-01-07**
- preserveorder formatting (By [mdeknowis](https://github.com/mdeknowis))
- support `transformAttributeName` (By [Erik Rothoff Andersson](https://github.com/erkie))
**4.0.12 / 2022-11-19**
- fix typescript
**4.0.11 / 2022-10-05**
- fix #501: parse for entities only once
**4.0.10 / 2022-09-14**
- fix broken links in demo site (By [Yannick Lang](https://github.com/layaxx))
- fix #491: tagValueProcessor type definition (By [Andrea Francesco Speziale](https://github.com/andreafspeziale))
- Add jsdocs for tagValueProcessor
**4.0.9 / 2022-07-10**
- fix #470: stop-tag can have self-closing tag with same name
- fix #472: stopNode can have any special tag inside
- Allow !ATTLIST and !NOTATION with DOCTYPE
- Add transformTagName option to transform tag names when parsing (#469) (By [Erik Rothoff Andersson](https://github.com/erkie))
**4.0.8 / 2022-05-28**
- Fix CDATA parsing returning empty string when value = 0 (#451) (By [ndelanou](https://github.com/ndelanou))
- Fix stopNodes when same tag appears inside node (#456) (By [patrickshipe](https://github.com/patrickshipe))
- fix #468: prettify own properties only
**4.0.7 / 2022-03-18**
- support CDATA even if tag order is not preserved
- support Comments even if tag order is not preserved
- fix #446: XMLbuilder should not indent XML declaration
**4.0.6 / 2022-03-08**
- fix: call tagValueProcessor only once for array items
- fix: missing changed for #437
**4.0.5 / 2022-03-06**
- fix #437: call tagValueProcessor from XML builder
**4.0.4 / 2022-03-03**
- fix #435: should skip unpaired and self-closing nodes when set as stopnodes
**4.0.3 / 2022-02-15**
- fix: ReferenceError when Bundled with Strict (#431) (By [Andreas Heissenberger](https://github.com/aheissenberger))
**4.0.2 / 2022-02-04**
- builder supports `suppressUnpairedNode`
- parser supports `ignoreDeclaration` and `ignorePiTags`
- fix: when comment is parsed as text value if given as `<!--> ...` #423
- builder supports decoding `&`
**4.0.1 / 2022-01-08**
- fix builder for pi tag
- fix: support suppressBooleanAttrs by builder
**4.0.0 / 2022-01-06**
- Generating different combined, parser only, builder only, validator only browser bundles
- Keeping cjs modules as they can be imported in cjs and esm modules both. Otherwise refer `esm` branch.
**4.0.0-beta.8 / 2021-12-13**
- call tagValueProcessor for stop nodes
**4.0.0-beta.7 / 2021-12-09**
- fix Validator bug when an attribute has no value but '=' only
- XML Builder should suppress unpaired tags by default.
- documents update for missing features
- refactoring to use Object.assign
- refactoring to remove repeated code
**4.0.0-beta.6 / 2021-12-05**
- Support PI Tags processing
- Support `suppressBooleanAttributes` by XML Builder for attributes with value `true`.
**4.0.0-beta.5 / 2021-12-04**
- fix: when a tag with name "attributes"
**4.0.0-beta.4 / 2021-12-02**
- Support HTML document parsing
- skip stop nodes parsing when building the XML from JS object
- Support external entites without DOCTYPE
- update dev dependency: strnum v1.0.5 to fix long number issue
**4.0.0-beta.3 / 2021-11-30**
- support global stopNodes expression like "*.stop"
- support self-closing and paired unpaired tags
- fix: CDATA should not be parsed.
- Fix typings for XMLBuilder (#396)(By [Anders Emil Salvesen](https://github.com/andersem))
- supports XML entities, HTML entities, DOCTYPE entities
**⚠️ 4.0.0-beta.2 / 2021-11-19**
- rename `attrMap` to `attibutes` in parser output when `preserveOrder:true`
- supports unpairedTags
**⚠️ 4.0.0-beta.1 / 2021-11-18**
- Parser returns an array now
- to make the structure common
- and to return root level detail
- renamed `cdataTagName` to `cdataPropName`
- Added `commentPropName`
- fix typings
**⚠️ 4.0.0-beta.0 / 2021-11-16**
- Name change of many configuration properties.
- `attrNodeName` to `attributesGroupName`
- `attrValueProcessor` to `attributeValueProcessor`
- `parseNodeValue` to `parseTagValue`
- `ignoreNameSpace` to `removeNSPrefix`
- `numParseOptions` to `numberParseOptions`
- spelling correction for `suppressEmptyNode`
- Name change of cli and browser bundle to **fxparser**
- `isArray` option is added to parse a tag into array
- `preserveOrder` option is added to render XML in such a way that the result js Object maintains the order of properties same as in XML.
- Processing behaviour of `tagValueProcessor` and `attributeValueProcessor` are changes with extra input parameters
- j2xparser is renamed to XMLBuilder.
- You need to build XML parser instance for given options first before parsing XML.
- fix #327, #336: throw error when extra text after XML content
- fix #330: attribute value can have '\n',
- fix #350: attrbiutes can be separated by '\n' from tagname
3.21.1 / 2021-10-31
- Correctly format JSON elements with a text prop but no attribute props ( By [haddadnj](https://github.com/haddadnj) )
3.21.0 / 2021-10-25
- feat: added option `rootNodeName` to set tag name for array input when converting js object to XML.
- feat: added option `alwaysCreateTextNode` to force text node creation (by: *@massimo-ua*)
- ⚠️ feat: Better error location for unclosed tags. (by *@Gei0r*)
- Some error messages would be changed when validating XML. Eg
- `{ InvalidXml: "Invalid '[ \"rootNode\"]' found." }``{InvalidTag: "Unclosed tag 'rootNode'."}`
- `{ InvalidTag: "Closing tag 'rootNode' is expected inplace of 'rootnode'." }``{ InvalidTag: "Expected closing tag 'rootNode' (opened in line 1) instead of closing tag 'rootnode'."}`
- ⚠️ feat: Column in error response when validating XML
```js
{
"code": "InvalidAttr",
"msg": "Attribute 'abc' is repeated.",
"line": 1,
"col": 22
}
```
3.20.1 / 2021-09-25
- update strnum package
3.20.0 / 2021-09-10
- Use strnum npm package to parse string to number
- breaking change: long number will be parsed to scientific notation.
3.19.0 / 2021-03-14
- License changed to MIT original
- Fix #321 : namespace tag parsing
3.18.0 / 2021-02-05
- Support RegEx and function in arrayMode option
- Fix #317 : validate nested PI tags
3.17.4 / 2020-06-07
- Refactor some code to support IE11
- Fix: `<tag >` space as attribute string
3.17.3 / 2020-05-23
- Fix: tag name separated by \n \t
- Fix: throw error for unclosed tags
3.17.2 / 2020-05-23
- Fixed an issue in processing doctype tag
- Fixed tagName where it should not have whitespace chars
3.17.1 / 2020-05-19
- Fixed an issue in checking opening tag
3.17.0 / 2020-05-18
- parser: fix '<' issue when it comes in aatr value
- parser: refactoring to remove dependency from regex
- validator: fix IE 11 issue for error messages
- updated dev dependencies
- separated benchmark module to sub-module
- breaking change: comments will not be removed from CDATA data
3.16.0 / 2020-01-12
- validaor: fix for ampersand characters (#215)
- refactoring to support unicode chars in tag name
- update typing for validator error
3.15.1 / 2019-12-09
- validaor: fix multiple roots are not allowed
3.15.0 / 2019-11-23
- validaor: improve error messaging
- validator: add line number in case of error
- validator: add more error scenarios to make it more descriptive
3.14.0 / 2019-10-25
- arrayMode for XML to JS obj parsing
3.13.0 / 2019-10-02
- pass tag/attr name to tag/attr value processor
- inbuilt optional validation with XML parser
3.12.21 / 2019-10-02
- Fix validator for unclosed XMLs
- move nimnjs dependency to dev dependency
- update dependencies
3.12.20 / 2019-08-16
- Revert: Fix #167: '>' in attribute value as it is causing high performance degrade.
3.12.19 / 2019-07-28
- Fix js to xml parser should work for date values. (broken: `tagValueProcessor` will receive the original value instead of string always) (breaking change)
3.12.18 / 2019-07-27
- remove configstore dependency
3.12.17 / 2019-07-14
- Fix #167: '>' in attribute value
3.12.16 / 2019-03-23
- Support a new option "stopNodes". (#150)
Accept the list of tags which are not required to be parsed. Instead, all the nested tag and data will be assigned as string.
- Don't show post-install message
3.12.12 / 2019-01-11
- fix : IE parseInt, parseFloat error
3.12.11 / 2018-12-24
- fix #132: "/" should not be parsed as boolean attr in case of self closing tags
3.12.9 / 2018-11-23
- fix #129 : validator should not fail when an atrribute name is 'length'
3.12.8 / 2018-11-22
- fix #128 : use 'attrValueProcessor' to process attribute value in json2xml parser
3.12.6 / 2018-11-10
- Fix #126: check for type
3.12.4 / 2018-09-12
- Fix: include tasks in npm package
3.12.3 / 2018-09-12
- Fix CLI issue raised in last PR
3.12.2 / 2018-09-11
- Fix formatting for JSON to XML output
- Migrate to webpack (PR merged)
- fix cli (PR merged)
3.12.0 / 2018-08-06
- Support hexadecimal values
- Support true number parsing
3.11.2 / 2018-07-23
- Update Demo for more options
- Update license information
- Update readme for formatting, users, and spelling mistakes
- Add missing typescript definition for j2xParser
- refactoring: change filenames
3.11.1 / 2018-06-05
- fix #93: read the text after self closing tag
3.11.0 / 2018-05-20
- return defaultOptions if there are not options in buildOptions function
- added localeRange declaration in parser.d.ts
- Added support of cyrillic characters in validator XML
- fixed bug in validator work when XML data with byte order marker
3.10.0 / 2018-05-13
- Added support of cyrillic characters in parsing XML to JSON
3.9.11 / 2018-05-09
- fix https://github.com/NaturalIntelligence/fast-xml-parser/issues/80 fix nimn chars
- update package information
- fix https://github.com/NaturalIntelligence/fast-xml-parser/issues/86: json 2 xml parser : property with null value should be parsed to self closing tag.
- update online demo
- revert zombiejs to old version to support old version of node
- update dependencies
3.3.10 / 2018-04-23
- fix #77 : parse even if closing tag has space before '>'
- include all css & js lib in demo app
- remove babel dependencies until needed
3.3.9 / 2018-04-18
- fix #74 : TS2314 TypeScript compiler error
3.3.8 / 2018-04-17
- fix #73 : IE doesn't support Object.assign
3.3.7 / 2018-04-14
- fix: use let insted of const in for loop of validator
- Merge pull request
https://github.com/NaturalIntelligence/fast-xml-parser/issues/71 from bb/master
first draft of typings for typescript
https://github.com/NaturalIntelligence/fast-xml-parser/issues/69
- Merge pull request
https://github.com/NaturalIntelligence/fast-xml-parser/issues/70 from bb/patch-1
fix some typos in readme
3.3.6 / 2018-03-21
- change arrow functions to full notation for IE compatibility
3.3.5 / 2018-03-15
- fix https://github.com/NaturalIntelligence/fast-xml-parser/issues/67 : attrNodeName invalid behavior
- fix: remove decodeHTML char condition
3.3.4 / 2018-03-14
- remove dependency on "he" package
- refactor code to separate methods in separate files.
- draft code for transforming XML to json string. It is not officially documented due to performance issue.
3.3.0 / 2018-03-05
- use common default options for XML parsing for consistency. And add `parseToNimn` method.
- update nexttodo
- update README about XML to Nimn transformation and remove special notes about 3.x release
- update CONTRIBUTING.ms mentioning nexttodo
- add negative case for XML PIs
- validate xml processing instruction tags https://github.com/NaturalIntelligence/fast-xml-parser/issues/62
- nimndata: handle array with object
- nimndata: node with nested node and text node
- nimndata: handle attributes and text node
- nimndata: add options, handle array
- add xml to nimn data converter
- x2j: direct access property with tagname
- update changelog
- fix validator when single quote presents in value enclosed with double quotes or vice versa
- Revert "remove unneded nimnjs dependency, move opencollective to devDependencies and replace it
with more light opencollective-postinstall"
This reverts commit d47aa7181075d82db4fee97fd8ea32b056fe3f46.
- Merge pull request: https://github.com/NaturalIntelligence/fast-xml-parser/issues/63 from HaroldPutman/suppress-undefined
Keep undefined nodes out of the XML output : This is useful when you are deleting nodes from the JSON and rewriting XML.
3.2.4 / 2018-03-01
- fix #59 fix in validator when open quote presents in attribute value
- Create nexttodo.md
- exclude static from bitHound tests
- add package lock
3.2.3 / 2018-02-28
- Merge pull request from Delagen/master: fix namespaces can contain the same characters as xml names
3.2.2 / 2018-02-22
- fix: attribute xmlns should not be removed if ignoreNameSpace is false
- create CONTRIBUTING.md
3.2.1 / 2018-02-17
- fix: empty attribute should be parsed
3.2.0 / 2018-02-16
- Merge pull request : Dev to Master
- Update README and version
- j2x:add performance test
- j2x: Remove extra empty line before closing tag
- j2x: suppress empty nodes to self closing node if configured
- j2x: provide option to give indentation depth
- j2x: make optional formatting
- j2x: encodeHTMLchat
- j2x: handle cdata tag
- j2x: handle grouped attributes
- convert json to xml
- nested object
- array
- attributes
- text value
- small refactoring
- Merge pull request: Update cli.js to let user validate XML file or data
- Add option for rendering CDATA as separate property
3.0.1 / 2018-02-09
- fix CRLF: replace it with single space in attributes value only.
3.0.0 / 2018-02-08
- change online tool with new changes
- update info about new options
- separate tag value processing to separate function
- make HTML decoding optional
- give an option to allow boolean attributes
- change cli options as per v3
- Correct comparison table format on README
- update v3 information
- some performance improvement changes
- Make regex object local to the method and move some common methods to util
- Change parser to
- handle multiple instances of CDATA
- make triming of value optionals
- HTML decode attribute and text value
- refactor code to separate files
- Ignore newline chars without RE (in validator)
- validate for XML prolog
- Validate DOCTYPE without RE
- Update validator to return error response
- Update README to add detail about V3
- Separate xmlNode model class
- include vscode debug config
- fix for repeated object
- fix attribute regex for boolean attributes
- Fix validator for invalid attributes
2.9.4 / 2018-02-02
- Merge pull request: Decode HTML characters
- refactor source folder name
- ignore bundle / browser js to be published to npm
2.9.3 / 2018-01-26
- Merge pull request: Correctly remove CRLF line breaks
- Enable to parse attribute in online editor
- Fix testing demo app test
- Describe parsing options
- Add options for online demo
2.9.2 / 2018-01-18
- Remove check if tag starting with "XML"
- Fix: when there are spaces before / after CDATA
2.9.1 / 2018-01-16
- Fix: newline should be replaced with single space
- Fix: for single and multiline comments
- validate xml with CDATA
- Fix: the issue when there is no space between 2 attributes
- Fix: https://github.com/NaturalIntelligence/fast-xml-parser/issues/33: when there is newline char in attr val, it doesn't parse
- Merge pull request: fix ignoreNamespace
- fix: don't wrap attributes if only namespace attrs
- fix: use portfinder for run tests, update deps
- fix: don't treat namespaces as attributes when ignoreNamespace enabled
2.9.0 / 2018-01-10
- Rewrite the validator to handle large files.
Ignore DOCTYPE validation.
- Fix: When attribute value has equal sign
2.8.3 / 2017-12-15
- Fix: when a tag has value along with subtags
2.8.2 / 2017-12-04
- Fix value parsing for IE
2.8.1 / 2017-12-01
- fix: validator should return false instead of err when invalid XML
2.8.0 / 2017-11-29
- Add CLI option to ignore value conversion
- Fix variable name when filename is given on CLI
- Update CLI help text
- Merge pull request: xml2js: Accept standard input
- Test Node 8
- Update dependencies
- Bundle readToEnd
- Add ability to read from standard input
2.7.4 / 2017-09-22
- Merge pull request: Allow wrap attributes with subobject to compatible with other parsers output
2.7.3 / 2017-08-02
- fix: handle CDATA with regx
2.7.2 / 2017-07-30
- Change travis config for yarn caching
- fix validator: when tag property is same as array property
- Merge pull request: Failing test case in validator for valid SVG
2.7.1 / 2017-07-26
- Fix: Handle val 0
2.7.0 / 2017-07-25
- Fix test for arrayMode
- Merge pull request: Add arrayMode option to parse any nodes as arrays
2.6.0 / 2017-07-14
- code improvement
- Add unit tests for value conversion for attr
- Merge pull request: option of an attribute value conversion to a number (textAttrConversion) the same way as the textNodeConversion option does. Default value is false.
2.5.1 / 2017-07-01
- Fix XML element name pattern
- Fix XML element name pattern while parsing
- Fix validation for xml tag element
2.5.0 / 2017-06-25
- Improve Validator performance
- update attr matching regex
- Add perf tests
- Improve atrr regex to handle all cases
2.4.4 / 2017-06-08
- Bug fix: when an attribute has single or double quote in value
2.4.3 / 2017-06-05
- Bug fix: when multiple CDATA tags are given
- Merge pull request: add option "textNodeConversion"
- add option "textNodeConversion"
2.4.1 / 2017-04-14
- fix tests
- Bug fix: preserve initial space of node value
- Handle CDATA
2.3.1 / 2017-03-15
- Bug fix: when single self closing tag
- Merge pull request: fix .codeclimate.yml
- Update .codeclimate.yml - Fixed config so it does not error anymore.
- Update .codeclimate.yml
2.3.0 / 2017-02-26
- Code improvement
- add bithound config
- Update usage
- Update travis to generate bundle js before running tests
- 1.Browserify, 2. add more tests for validator
- Add validator
- Fix CLI default parameter bug
2.2.1 / 2017-02-05
- Bug fix: CLI default option

21
node_modules/fast-xml-parser/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Amit Kumar Gupta
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

225
node_modules/fast-xml-parser/README.md generated vendored Normal file
View File

@@ -0,0 +1,225 @@
# [fast-xml-parser](https://www.npmjs.com/package/fast-xml-parser)
[![NPM total downloads](https://img.shields.io/npm/dt/fast-xml-parser.svg)](https://npm.im/fast-xml-parser)
Validate XML, Parse XML to JS Object, or Build XML from JS Object without C/C++ based libraries and no callback.
<img align="right" src="static/img/fxp_logo.png" width="180px" alt="FXP logo"/>
* Validate XML data syntactically. Use [detailed-xml-validator](https://github.com/NaturalIntelligence/detailed-xml-validator/) to verify business rules.
* Parse XML to JS Objects and vice versa
* Common JS, ESM, and browser compatible
* Faster than any other pure JS implementation.
It can handle big files (tested up to 100mb). XML Entities, HTML entities, and DOCTYPE entites are supported. Unpaired tags (Eg `<br>` in HTML), stop nodes (Eg `<script>` in HTML) are supported. It can also preserve Order of tags in JS object
---
# Your Support, Our Motivation
## Try out our New Thoughts
- WishIn - You need it if negative thoughts take over all the time <br>
<a href="https://play.google.com/store/apps/details?id=com.solothought.wishin"> <img src="https://solothought.com/products/assets/images/wishin/YouTubeThumbnail.png" width="500px"/> </a>
- **Flowgger**: 90% less logs size and 90% less debugging time<br>
<a href="https://github.com/solothought/flowgger"> <img src="static/img/flowgger_h.webp" alt="Flowgger Logging Framework" width="300px"/></a>
- [Text2Chart](https://solothought.com/text2chart/flow): interactive flow chart out of simple text.
## Financial Support
Sponsor this project
<a href="https://github.com/sponsors/NaturalIntelligence">
<img src="https://raw.githubusercontent.com/NaturalIntelligence/ThankYouBackers/main/github_sponsor.png" width="180" />
</a>
<a href="https://opencollective.com/fast-xml-parser/donate" target="_blank">
<img src="https://opencollective.com/fast-xml-parser/donate/button@2x.png?color=blue" width=180 />
</a>
<a href="https://paypal.me/naturalintelligence"> <img src="static/img/support_paypal.svg" alt="donate button" width="180"/></a>
<br>
<br>
<br>
<!--
### Current Sponsors
Check the complete list at [ThankYouBackers](https://github.com/NaturalIntelligence/ThankYouBackers) for our sponsors and supporters.
Through Github
<a href="https://github.com/skunkteam" target="_blank"><img src="https://avatars.githubusercontent.com/u/46373671?s=60" width="60px"></a>
<a href="https://github.com/getsentry" target="_blank"><img src="https://avatars.githubusercontent.com/u/1396951?s=60" width="60px"></a>
Through OpenCollective
<a href="https://opencollective.com/fast-xml-parser/sponsor/0/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/fast-xml-parser/sponsor/1/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/fast-xml-parser/sponsor/2/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/fast-xml-parser/sponsor/3/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/fast-xml-parser/sponsor/4/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/fast-xml-parser/sponsor/5/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/fast-xml-parser/sponsor/6/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/fast-xml-parser/sponsor/7/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/fast-xml-parser/sponsor/8/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/fast-xml-parser/sponsor/9/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/9/avatar.svg"></a>
-->
![fxp_sponsors](https://raw.githubusercontent.com/NaturalIntelligence/ThankYouBackers/main/assets/NI_sponsors.jpg)
> This is a donation. No goods or services are expected in return. Any requests for refunds for those purposes will be rejected.
## Users
<a href="https://github.com/renovatebot/renovate" title="renovate" ><img src="https://avatars1.githubusercontent.com/u/38656520" width="60px" ></a>
<a href="https://vmware.com/" title="vmware" > <img src="https://avatars0.githubusercontent.com/u/473334" width="60px" ></a>
<a href="https://opensource.microsoft.com/" title="microsoft" > <img src="https://avatars0.githubusercontent.com/u/6154722" width="60px" ></a>
<a href="http://ibm.github.io/" title="IBM" > <img src="https://avatars2.githubusercontent.com/u/1459110" width="60px" ></a>
<a href="http://www.smartbear.com" title="SmartBear Software" > <img src="https://avatars2.githubusercontent.com/u/1644671" width="60px" ></a>
<a href="http://nasa.github.io/" title="NASA" > <img src="https://avatars0.githubusercontent.com/u/848102" width="60px" ></a>
<a href="https://github.com/prettier" title="Prettier" > <img src="https://avatars0.githubusercontent.com/u/25822731" width="60px" ></a>
<a href="http://brain.js.org/" title="brain.js" > <img src="https://avatars2.githubusercontent.com/u/23732838" width="60px" ></a>
<a href="https://github.com/aws" title="AWS SDK" > <img src="https://avatars.githubusercontent.com/u/2232217" width="60px" ></a>
<a href="http://www.fda.gov/" title="Food and Drug Administration " > <img src="https://avatars2.githubusercontent.com/u/6471964" width="60px" ></a>
<a href="http://www.magento.com/" title="Magento" > <img src="https://avatars2.githubusercontent.com/u/168457" width="60px" ></a>
<a href="https://github.com/SAP" title="SAP" > <img src="https://user-images.githubusercontent.com/7692328/204835214-d9d25b58-e3df-408d-87a3-c7d36b578ee4.png" width="60px" ></a>
<a href="https://github.com/postmanlabs" title="postman" > <img src="https://user-images.githubusercontent.com/7692328/204835529-e9e290ad-696a-49ad-9d34-08e955704715.png" width="60px" ></a>
<a href="https://github.com/react-native-community" title="React Native Community" > <img src="https://avatars.githubusercontent.com/u/20269980?v=4" width="60px" ></a>
<a href="https://github.com/googleapis" title="Google APIs" > <img src="https://avatars.githubusercontent.com/u/16785467?v=4" width="60px" ></a>
<a href="https://github.com/langchain-ai" title="Langchain AI" > <img src="https://avatars.githubusercontent.com/u/126733545?v=4" width="60px" ></a>
<a href="https://github.com/withastro" title="Astro websie builder" > <img src="https://avatars.githubusercontent.com/u/44914786?v=4" width="60px" ></a>
<a href="https://github.com/baidu" title="Baidu" > <img src="https://avatars.githubusercontent.com/u/13245940?v=4" width="60px" ></a>
[more](./USERs.md)
<small>The list of users are mostly published by Github or communicated directly. Feel free to contact if you find any information wrong.</small>
---
# More about this library
## How to use
To use as package dependency
`$ npm install fast-xml-parser`
or
`$ yarn add fast-xml-parser`
To use as system command
`$ npm install fast-xml-parser -g`
To use it on a **webpage** include it from a [CDN](https://cdnjs.com/libraries/fast-xml-parser)
**Example**
As CLI command
```bash
$ fxparser some.xml
```
In a node js project
```js
const { XMLParser, XMLBuilder, XMLValidator} = require("fast-xml-parser");
const parser = new XMLParser();
let jObj = parser.parse(XMLdata);
const builder = new XMLBuilder();
const xmlContent = builder.build(jObj);
```
In a HTML page
```html
<script src="path/to/fxp.min.js"></script>
:
<script>
const parser = new fxparser.XMLParser();
parser.parse(xmlContent);
</script>
```
Bundle size
| Bundle Name | Size |
| ------------------ | ---- |
| fxbuilder.min.js | 6.5K |
| fxparser.min.js | 20K |
| fxp.min.js | 26K |
| fxvalidator.min.js | 5.7K |
## Documents
<table>
<tr><td>v3</td><td>v4 and v5</td><td>v6</td></tr>
<tr>
<td>
<a href="./docs/v3/docs.md">documents</a>
</td>
<td><ol>
<li><a href="./docs/v4,v5/1.GettingStarted.md">Getting Started</a></li>
<li><a href="./docs/v4,v5/2.XMLparseOptions.md">XML Parser</a></li>
<li><a href="./docs/v4,v5/3.XMLBuilder.md">XML Builder</a></li>
<li><a href="./docs/v4,v5/4.XMLValidator.md">XML Validator</a></li>
<li><a href="./docs/v4,v5/5.Entities.md">Entities</a></li>
<li><a href="./docs/v4,v5/6.HTMLParsing.md">HTML Document Parsing</a></li>
<li><a href="./docs/v4,v5/7.PITags.md">PI Tag processing</a></li>
<li><a href="./docs/v4,v5/8.PathExpression.md">Path Expression</a></li>
</ol></td>
<td><ol>
<li></li><a href="./docs/v6/1.GettingStarted.md">Getting Started</a></li>
<li><a href="./docs/v6/2.Features.md">Features</a></li>
<li><a href="./docs/v6/3.Options.md">Options</a></li>
<li><a href="./docs/v6/4.OutputBuilders.md">Output Builders</a></li>
<li><a href="./docs/v6/5.ValueParsers.md">Value Parsers</a></li>
</ol></td>
</tr>
</table>
**note**:
- Version 6 is released with version 4 for experimental use. Based on its demand, it'll be developed and the features can be different in final release.
- Version 5 has the same functionalities as version 4.
## Performance
<small>negative means error</small>
### XML Parser
<img align="left" src="./docs/imgs/XMLParser_v4.png" width="45%" />
<img src="./docs/imgs/XMLParser_large_v4.png" width="47%" />
* Y-axis: requests per second
* X-axis: File size
### XML Builder
<img src="./docs/imgs/XMLBuilder_v4.png" width="50%" />
* Y-axis: requests per second
<!-- [![](static/img/ni_ads_ads.gif)](https://github.com/NaturalIntelligence/ads/) -->
---
## Usage Trend
[Usage Trend of fast-xml-parser](https://npm-compare.com/fast-xml-parser#timeRange=THREE_YEARS)
<a href="https://npm-compare.com/fast-xml-parser#timeRange=THREE_YEARS" target="_blank">
<img src="https://npm-compare.com/img/npm-trend/THREE_YEARS/fast-xml-parser.png" width="50%" alt="NPM Usage Trend of fast-xml-parser" />
</a>
# Supporters
#### Contributors
This project exists thanks to [all](graphs/contributors) the people who contribute. [[Contribute](docs/CONTRIBUTING.md)].
<a href="graphs/contributors"><img src="https://opencollective.com/fast-xml-parser/contributors.svg?width=890&button=false" /></a>
#### Backers from Open collective
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/fast-xml-parser#backer)]
<a href="https://opencollective.com/fast-xml-parser#backers" target="_blank"><img src="https://opencollective.com/fast-xml-parser/backers.svg?width=890"></a>
# License
* MIT License
![Donate $5](static/img/donation_quote.png)

2
node_modules/fast-xml-parser/lib/fxbuilder.min.js generated vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
node_modules/fast-xml-parser/lib/fxp.cjs generated vendored Normal file

File diff suppressed because one or more lines are too long

603
node_modules/fast-xml-parser/lib/fxp.d.cts generated vendored Normal file
View File

@@ -0,0 +1,603 @@
import type pem = require('./pem');
type Expression = pem.Expression;
type ReadonlyMatcher = pem.ReadonlyMatcher;
// jPath: true → string
// jPath: false → ReadonlyMatcher
type JPathOrMatcher = string | ReadonlyMatcher;
type JPathOrExpression = string | Expression;
type ProcessEntitiesOptions = {
/**
* Whether to enable entity processing
*
* Defaults to `true`
*/
enabled?: boolean;
/**
* Maximum size in characters for a single entity definition
*
* Defaults to `10000`
*/
maxEntitySize?: number;
/**
* Maximum depth for nested entity references (reserved for future use)
*
* Defaults to `10`
*/
maxExpansionDepth?: number;
/**
* Maximum total number of entity expansions allowed
*
* Defaults to `1000`
*/
maxTotalExpansions?: number;
/**
* Maximum total expanded content length in characters
*
* Defaults to `100000`
*/
maxExpandedLength?: number;
/**
* Maximum number of entities allowed in the XML
*
* Defaults to `100`
*/
maxEntityCount?: number;
/**
* Array of tag names where entity replacement is allowed.
* If null, entities are replaced in all tags.
*
* Defaults to `null`
*/
allowedTags?: string[] | null;
/**
* Custom filter function to determine if entities should be replaced in a tag
*
* @param tagName - The name of the current tag
* @param jPathOrMatcher - The jPath string (if jPath: true) or Matcher instance (if jPath: false)
* @returns `true` to allow entity replacement, `false` to skip
*
* Defaults to `null`
*/
tagFilter?: ((tagName: string, jPathOrMatcher: JPathOrMatcher) => boolean) | null;
};
type X2jOptions = {
/**
* Preserve the order of tags in resulting JS object
*
* Defaults to `false`
*/
preserveOrder?: boolean;
/**
* Give a prefix to the attribute name in the resulting JS object
*
* Defaults to '@_'
*/
attributeNamePrefix?: string;
/**
* A name to group all attributes of a tag under, or `false` to disable
*
* Defaults to `false`
*/
attributesGroupName?: false | string;
/**
* The name of the next node in the resulting JS
*
* Defaults to `#text`
*/
textNodeName?: string;
/**
* Whether to ignore attributes when parsing
*
* When `true` - ignores all the attributes
*
* When `false` - parses all the attributes
*
* When `Array<string | RegExp>` - filters out attributes that match provided patterns
*
* When `Function` - calls the function for each attribute and filters out those for which the function returned `true`
*
* Defaults to `true`
*/
ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPathOrMatcher: JPathOrMatcher) => boolean);
/**
* Whether to remove namespace string from tag and attribute names
*
* Defaults to `false`
*/
removeNSPrefix?: boolean;
/**
* Whether to allow attributes without value
*
* Defaults to `false`
*/
allowBooleanAttributes?: boolean;
/**
* Whether to parse tag value with `strnum` package
*
* Defaults to `true`
*/
parseTagValue?: boolean;
/**
* Whether to parse attribute value with `strnum` package
*
* Defaults to `false`
*/
parseAttributeValue?: boolean;
/**
* Whether to remove surrounding whitespace from tag or attribute value
*
* Defaults to `true`
*/
trimValues?: boolean;
/**
* Give a property name to set CDATA values to instead of merging to tag's text value
*
* Defaults to `false`
*/
cdataPropName?: false | string;
/**
* If set, parse comments and set as this property
*
* Defaults to `false`
*/
commentPropName?: false | string;
/**
* Control how tag value should be parsed. Called only if tag value is not empty
*
* @param tagName - The name of the tag
* @param tagValue - The value of the tag
* @param jPathOrMatcher - The jPath string (if jPath: true) or Matcher instance (if jPath: false)
* @param hasAttributes - Whether the tag has attributes
* @param isLeafNode - Whether the tag is a leaf node
* @returns {undefined|null} `undefined` or `null` to set original value.
* @returns {unknown}
*
* 1. Different value or value with different data type to set new value.
* 2. Same value to set parsed value if `parseTagValue: true`.
*
* Defaults to `(tagName, val, jPathOrMatcher, hasAttributes, isLeafNode) => val`
*/
tagValueProcessor?: (tagName: string, tagValue: string, jPathOrMatcher: JPathOrMatcher, hasAttributes: boolean, isLeafNode: boolean) => unknown;
/**
* Control how attribute value should be parsed
*
* @param attrName - The name of the attribute
* @param attrValue - The value of the attribute
* @param jPathOrMatcher - The jPath string (if jPath: true) or Matcher instance (if jPath: false)
* @returns {undefined|null} `undefined` or `null` to set original value
* @returns {unknown}
*
* Defaults to `(attrName, val, jPathOrMatcher) => val`
*/
attributeValueProcessor?: (attrName: string, attrValue: string, jPathOrMatcher: JPathOrMatcher) => unknown;
/**
* Options to pass to `strnum` for parsing numbers
*
* Defaults to `{ hex: true, leadingZeros: true, eNotation: true }`
*/
numberParseOptions?: strnumOptions;
/**
* Nodes to stop parsing at
*
* Accepts string patterns or Expression objects from path-expression-matcher
*
* String patterns starting with "*." are automatically converted to ".." for backward compatibility
*
* Defaults to `[]`
*/
stopNodes?: JPathOrExpression[];
/**
* List of tags without closing tags
*
* Defaults to `[]`
*/
unpairedTags?: string[];
/**
* Whether to always create a text node
*
* Defaults to `false`
*/
alwaysCreateTextNode?: boolean;
/**
* Determine whether a tag should be parsed as an array
*
* @param tagName - The name of the tag
* @param jPathOrMatcher - The jPath string (if jPath: true) or Matcher instance (if jPath: false)
* @param isLeafNode - Whether the tag is a leaf node
* @param isAttribute - Whether this is an attribute
* @returns {boolean}
*
* Defaults to `() => false`
*/
isArray?: (tagName: string, jPathOrMatcher: JPathOrMatcher, isLeafNode: boolean, isAttribute: boolean) => boolean;
/**
* Whether to process default and DOCTYPE entities
*
* When `true` - enables entity processing with default limits
*
* When `false` - disables all entity processing
*
* When `ProcessEntitiesOptions` - enables entity processing with custom configuration
*
* Defaults to `true`
*/
processEntities?: boolean | ProcessEntitiesOptions;
/**
* Whether to process HTML entities
*
* Defaults to `false`
*/
htmlEntities?: boolean;
/**
* Whether to ignore the declaration tag from output
*
* Defaults to `false`
*/
ignoreDeclaration?: boolean;
/**
* Whether to ignore Pi tags
*
* Defaults to `false`
*/
ignorePiTags?: boolean;
/**
* Transform tag names
*
* Defaults to `false`
*/
transformTagName?: ((tagName: string) => string) | false;
/**
* Transform attribute names
*
* Defaults to `false`
*/
transformAttributeName?: ((attributeName: string) => string) | false;
/**
* Change the tag name when a different name is returned. Skip the tag from parsed result when false is returned.
* Modify `attrs` object to control attributes for the given tag.
*
* @param tagName - The name of the tag
* @param jPathOrMatcher - The jPath string (if jPath: true) or Matcher instance (if jPath: false)
* @param attrs - The attributes object
* @returns {string} new tag name.
* @returns false to skip the tag
*
* Defaults to `(tagName, jPathOrMatcher, attrs) => tagName`
*/
updateTag?: (tagName: string, jPathOrMatcher: JPathOrMatcher, attrs: { [k: string]: string }) => string | boolean;
/**
* If true, adds a Symbol to all object nodes, accessible by {@link XMLParser.getMetaDataSymbol} with
* metadata about each the node in the XML file.
*/
captureMetaData?: boolean;
/**
* Maximum number of nested tags
*
* Defaults to `100`
*/
maxNestedTags?: number;
/**
* Whether to strictly validate tag names
*
* Defaults to `true`
*/
strictReservedNames?: boolean;
/**
* Controls whether callbacks receive jPath as string or Matcher instance
*
* When `true` - callbacks receive jPath as string (backward compatible)
*
* When `false` - callbacks receive Matcher instance for advanced pattern matching
*
* Defaults to `true`
*/
jPath?: boolean;
/**
* Function to sanitize dangerous property names
*
* @param name - The name of the property
* @returns {string} The sanitized name
*
* Defaults to `(name) => __name`
*/
onDangerousProperty?: (name: string) => string;
};
type strnumOptions = {
hex: boolean;
leadingZeros: boolean,
skipLike?: RegExp,
eNotation?: boolean
}
type validationOptions = {
/**
* Whether to allow attributes without value
*
* Defaults to `false`
*/
allowBooleanAttributes?: boolean;
/**
* List of tags without closing tags
*
* Defaults to `[]`
*/
unpairedTags?: string[];
};
type XmlBuilderOptions = {
/**
* Give a prefix to the attribute name in the resulting JS object
*
* Defaults to '@_'
*/
attributeNamePrefix?: string;
/**
* A name to group all attributes of a tag under, or `false` to disable
*
* Defaults to `false`
*/
attributesGroupName?: false | string;
/**
* The name of the next node in the resulting JS
*
* Defaults to `#text`
*/
textNodeName?: string;
/**
* Whether to ignore attributes when building
*
* When `true` - ignores all the attributes
*
* When `false` - builds all the attributes
*
* When `Array<string | RegExp>` - filters out attributes that match provided patterns
*
* When `Function` - calls the function for each attribute and filters out those for which the function returned `true`
*
* Defaults to `true`
*/
ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPath: string) => boolean);
/**
* Give a property name to set CDATA values to instead of merging to tag's text value
*
* Defaults to `false`
*/
cdataPropName?: false | string;
/**
* If set, parse comments and set as this property
*
* Defaults to `false`
*/
commentPropName?: false | string;
/**
* Whether to make output pretty instead of single line
*
* Defaults to `false`
*/
format?: boolean;
/**
* If `format` is set to `true`, sets the indent string
*
* Defaults to ` `
*/
indentBy?: string;
/**
* Give a name to a top-level array
*
* Defaults to `undefined`
*/
arrayNodeName?: string;
/**
* Create empty tags for tags with no text value
*
* Defaults to `false`
*/
suppressEmptyNode?: boolean;
/**
* Suppress an unpaired tag
*
* Defaults to `true`
*/
suppressUnpairedNode?: boolean;
/**
* Don't put a value for boolean attributes
*
* Defaults to `true`
*/
suppressBooleanAttributes?: boolean;
/**
* Preserve the order of tags in resulting JS object
*
* Defaults to `false`
*/
preserveOrder?: boolean;
/**
* List of tags without closing tags
*
* Defaults to `[]`
*/
unpairedTags?: string[];
/**
* Nodes to stop parsing at
*
* Accepts string patterns or Expression objects from path-expression-matcher
*
* Defaults to `[]`
*/
stopNodes?: JPathOrExpression[];
/**
* Control how tag value should be parsed. Called only if tag value is not empty
*
* @returns {undefined|null} `undefined` or `null` to set original value.
* @returns {unknown}
*
* 1. Different value or value with different data type to set new value.
* 2. Same value to set parsed value if `parseTagValue: true`.
*
* Defaults to `(tagName, val, jPath, hasAttributes, isLeafNode) => val`
*/
tagValueProcessor?: (name: string, value: unknown) => unknown;
/**
* Control how attribute value should be parsed
*
* @param attrName
* @param attrValue
* @param jPath
* @returns {undefined|null} `undefined` or `null` to set original value
* @returns {unknown}
*
* Defaults to `(attrName, val, jPath) => val`
*/
attributeValueProcessor?: (name: string, value: unknown) => unknown;
/**
* Whether to process default and DOCTYPE entities
*
* Defaults to `true`
*/
processEntities?: boolean;
oneListGroup?: boolean;
/**
* Maximum number of nested tags
*
* Defaults to `100`
*/
maxNestedTags?: number;
};
type ESchema = string | object | Array<string | object>;
type ValidationError = {
err: {
code: string;
msg: string,
line: number,
col: number
};
};
declare class XMLParser {
constructor(options?: X2jOptions);
parse(xmlData: string | Uint8Array, validationOptions?: validationOptions | boolean): any;
/**
* Add Entity which is not by default supported by this library
* @param entityIdentifier {string} Eg: 'ent' for &ent;
* @param entityValue {string} Eg: '\r'
*/
addEntity(entityIdentifier: string, entityValue: string): void;
/**
* Returns a Symbol that can be used to access the {@link XMLMetaData}
* 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 {@link X2jOptions.captureMetaData}
* is true in the options.
*/
static getMetaDataSymbol(): Symbol;
}
declare class XMLValidator {
static validate(xmlData: string, options?: validationOptions): true | ValidationError;
}
declare class XMLBuilder {
constructor(options?: XmlBuilderOptions);
build(jObj: any): string;
}
/**
* This object is available on nodes via the symbol {@link XMLParser.getMetaDataSymbol}
* when {@link X2jOptions.captureMetaData} is true.
*/
declare interface XMLMetaData {
/** The index, if available, of the character where the XML node began in the input stream. */
startIndex?: number;
}
declare namespace fxp {
export {
XMLParser,
XMLValidator,
XMLBuilder,
XMLMetaData,
XmlBuilderOptions,
X2jOptions,
ESchema,
ValidationError,
strnumOptions,
validationOptions,
ProcessEntitiesOptions,
Expression,
ReadonlyMatcher,
JPathOrMatcher,
JPathOrExpression,
}
}
export = fxp;

2
node_modules/fast-xml-parser/lib/fxp.min.js generated vendored Normal file

File diff suppressed because one or more lines are too long

1
node_modules/fast-xml-parser/lib/fxp.min.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

2
node_modules/fast-xml-parser/lib/fxparser.min.js generated vendored Normal file

File diff suppressed because one or more lines are too long

1
node_modules/fast-xml-parser/lib/fxparser.min.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

2
node_modules/fast-xml-parser/lib/fxvalidator.min.js generated vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

148
node_modules/fast-xml-parser/lib/pem.d.cts generated vendored Normal file
View File

@@ -0,0 +1,148 @@
/**
* Types copied from path-expression-matcher
* @version <version>
* @updated <date>
*
* Update this file when path-expression-matcher releases a new version.
* Source: https://github.com/NaturalIntelligence/path-expression-matcher
*/
/**
* Options for creating an Expression
*/
interface ExpressionOptions {
/**
* Path separator character
* @default '.'
*/
separator?: string;
}
/**
* Parsed segment from an expression pattern
*/
interface Segment {
type: 'tag' | 'deep-wildcard';
tag?: string;
namespace?: string;
attrName?: string;
attrValue?: string;
position?: 'first' | 'last' | 'odd' | 'even' | 'nth';
positionValue?: number;
}
/**
* Expression - Parses and stores a tag pattern expression.
* Patterns are parsed once and stored in an optimized structure for fast matching.
*
* @example
* ```typescript
* const expr = new Expression("root.users.user");
* const expr2 = new Expression("..user[id]:first");
* const expr3 = new Expression("root/users/user", { separator: '/' });
* ```
*
* Pattern Syntax:
* - `root.users.user` — Match exact path
* - `..user` — Match "user" at any depth (deep wildcard)
* - `user[id]` — Match user tag with "id" attribute
* - `user[id=123]` — Match user tag where id="123"
* - `user:first` — Match first occurrence of user tag
* - `ns::user` — Match user tag with namespace "ns"
* - `ns::user[id]:first` — Combine namespace, attribute, and position
*/
declare class Expression {
readonly pattern: string;
readonly separator: string;
readonly segments: Segment[];
constructor(pattern: string, options?: ExpressionOptions);
get length(): number;
hasDeepWildcard(): boolean;
hasAttributeCondition(): boolean;
hasPositionSelector(): boolean;
toString(): string;
}
// ---------------------------------------------------------------------------
// ReadonlyMatcher
// ---------------------------------------------------------------------------
/**
* A live read-only view of a Matcher instance, returned by Matcher.readOnly().
*
* All query and inspection methods work normally and always reflect the current
* state of the underlying matcher. State-mutating methods (`push`, `pop`,
* `reset`, `updateCurrent`, `restore`) are not present — calling them on the
* underlying Proxy throws a `TypeError` at runtime.
*
* This is the type received by all FXP user callbacks when `jPath: false`.
*/
interface ReadonlyMatcher {
readonly separator: string;
/** Check if current path matches an Expression. */
matches(expression: Expression): boolean;
/** Get current tag name, or `undefined` if path is empty. */
getCurrentTag(): string | undefined;
/** Get current namespace, or `undefined` if not present. */
getCurrentNamespace(): string | undefined;
/** Get attribute value of the current node. */
getAttrValue(attrName: string): any;
/** Check if the current node has a given attribute. */
hasAttr(attrName: string): boolean;
/** Sibling position of the current node (child index in parent). */
getPosition(): number;
/** Occurrence counter of the current tag name at this level. */
getCounter(): number;
/** Number of nodes in the current path. */
getDepth(): number;
/** Current path as a string (e.g. `"root.users.user"`). */
toString(separator?: string, includeNamespace?: boolean): string;
/** Current path as an array of tag names. */
toArray(): string[];
/**
* Create a snapshot of the current state.
* The snapshot can be passed to the real Matcher.restore() if needed.
*/
snapshot(): MatcherSnapshot;
}
/** Internal node structure — exposed via snapshot only. */
interface PathNode {
tag: string;
namespace?: string;
position: number;
counter: number;
values?: Record<string, any>;
}
/** Snapshot of matcher state returned by `snapshot()` and `readOnly().snapshot()`. */
interface MatcherSnapshot {
path: PathNode[];
siblingStacks: Map<string, number>[];
}
declare namespace pem {
export {
Expression,
ExpressionOptions,
Segment,
ReadonlyMatcher,
PathNode,
MatcherSnapshot,
}
}
export = pem;

94
node_modules/fast-xml-parser/package.json generated vendored Normal file
View File

@@ -0,0 +1,94 @@
{
"name": "fast-xml-parser",
"version": "5.5.8",
"description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
"main": "./lib/fxp.cjs",
"type": "module",
"sideEffects": false,
"module": "./src/fxp.js",
"types": "./src/fxp.d.ts",
"exports": {
".": {
"import": {
"types": "./src/fxp.d.ts",
"default": "./src/fxp.js"
},
"require": {
"types": "./lib/fxp.d.cts",
"default": "./lib/fxp.cjs"
}
}
},
"scripts": {
"test": "c8 --reporter=lcov --reporter=text jasmine spec/*spec.js",
"test-types": "tsc --noEmit spec/typings/typings-test.ts",
"unit": "jasmine",
"coverage": "nyc report --reporter html --reporter text -t .nyc_output --report-dir .nyc_output/summary",
"perf": "node ./benchmark/perfTest3.js",
"lint": "eslint src/**/*.js spec/**/*.js benchmark/**/*.js",
"bundle": "webpack --config webpack.cjs.config.js",
"prettier": "prettier --write src/**/*.js",
"checkReadiness": "publish-please --dry-run",
"publish-please": "publish-please",
"prepublishOnly": "publish-please guard"
},
"bin": {
"fxparser": "src/cli/cli.js"
},
"files": [
"lib",
"src",
"CHANGELOG.md"
],
"repository": {
"type": "git",
"url": "git+https://github.com/NaturalIntelligence/fast-xml-parser.git"
},
"keywords": [
"fast",
"xml",
"json",
"parser",
"xml2js",
"x2js",
"xml2json",
"js",
"validator",
"validate",
"transformer",
"assert",
"js2xml",
"json2xml",
"html"
],
"author": "Amit Gupta (https://solothought.com)",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.13.10",
"@babel/plugin-transform-runtime": "^7.13.10",
"@babel/preset-env": "^7.13.10",
"@babel/register": "^7.13.8",
"@types/node": "20",
"babel-loader": "^8.2.2",
"c8": "^10.1.3",
"eslint": "^8.3.0",
"he": "^1.2.0",
"jasmine": "^5.6.0",
"prettier": "^3.5.1",
"publish-please": "^5.5.2",
"typescript": "5",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1"
},
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"dependencies": {
"fast-xml-builder": "^1.1.4",
"path-expression-matcher": "^1.2.0",
"strnum": "^2.2.0"
}
}

97
node_modules/fast-xml-parser/src/cli/cli.js generated vendored Normal file
View File

@@ -0,0 +1,97 @@
#!/usr/bin/env node
'use strict';
/*eslint-disable no-console*/
import fs from 'fs';
import { resolve } from 'path';
import {XMLParser, XMLValidator} from "../fxp.js";
import ReadToEnd from './read.js';
import cmdDetail from "./man.js"
console.warn("\x1b[33m%s\x1b[0m", "⚠️ Warning: The built-in CLI interface is now deprecated.");
console.warn("Please install the dedicated CLI package instead:");
console.warn(" npm install -g fxp-cli");
if (process.argv[2] === '--help' || process.argv[2] === '-h') {
console.log(cmdDetail);
} else if (process.argv[2] === '--version') {
const packageJsonPath = resolve(process.cwd(), 'package.json');
const version = JSON.parse(fs.readFileSync(packageJsonPath).toString()).version;
console.log(version);
} else {
const options = {
removeNSPrefix: true,
ignoreAttributes: false,
parseTagValue: true,
parseAttributeValue: true,
};
let fileName = '';
let outputFileName;
let validate = false;
let validateOnly = false;
for (let i = 2; i < process.argv.length; i++) {
if (process.argv[i] === '-ns') {
options.removeNSPrefix = false;
} else if (process.argv[i] === '-a') {
options.ignoreAttributes = true;
} else if (process.argv[i] === '-c') {
options.parseTagValue = false;
options.parseAttributeValue = false;
} else if (process.argv[i] === '-o') {
outputFileName = process.argv[++i];
} else if (process.argv[i] === '-v') {
validate = true;
} else if (process.argv[i] === '-V') {
validateOnly = true;
} else {
//filename
fileName = process.argv[i];
}
}
const callback = function(xmlData) {
let output = '';
if (validateOnly) {
output = XMLValidator.validate(xmlData);
process.exitCode = output === true ? 0 : 1;
} else {
const parser = new XMLParser(options);
output = JSON.stringify(parser.parse(xmlData,validate), null, 4);
}
if (outputFileName) {
writeToFile(outputFileName, output);
} else {
console.log(output);
}
};
try {
if (!fileName) {
ReadToEnd.readToEnd(process.stdin, function(err, data) {
if (err) {
throw err;
}
callback(data.toString());
});
} else {
fs.readFile(fileName, function(err, data) {
if (err) {
throw err;
}
callback(data.toString());
});
}
} catch (e) {
console.log('Seems an invalid file or stream.' + e);
}
}
function writeToFile(fileName, data) {
fs.writeFile(fileName, data, function(err) {
if (err) {
throw err;
}
console.log('JSON output has been written to ' + fileName);
});
}

17
node_modules/fast-xml-parser/src/cli/man.js generated vendored Normal file
View File

@@ -0,0 +1,17 @@
import fs from 'fs';
import { resolve } from 'path';
const packageJsonPath = resolve(process.cwd(), 'package.json');
const version = JSON.parse(fs.readFileSync(packageJsonPath).toString()).version;
export default `Fast XML Parser ${version}
----------------
$ fxparser [-ns|-a|-c|-v|-V] <filename> [-o outputfile.json]
$ cat xmlfile.xml | fxparser [-ns|-a|-c|-v|-V] [-o outputfile.json]
Options
----------------
-ns: remove namespace from tag and atrribute name.
-a: don't parse attributes.
-c: parse values to premitive type.
-v: validate before parsing.
-V: validate only.`

43
node_modules/fast-xml-parser/src/cli/read.js generated vendored Normal file
View File

@@ -0,0 +1,43 @@
'use strict';
import { Transform } from 'stream';
export default class ReadToEnd extends Transform {
constructor(options = {}) {
super(options);
this._encoding = options.encoding || 'utf8';
this._buffer = '';
}
_transform(chunk, encoding, done) {
this._buffer += chunk.toString(this._encoding);
this.push(chunk);
done();
}
_flush(done) {
this.emit('complete', null, this._buffer);
done();
}
static readToEnd(stream, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
const dest = new ReadToEnd(options);
stream.pipe(dest);
stream.on('error', (err) => {
stream.unpipe(dest);
callback(err);
});
dest.on('complete', callback);
dest.resume();
return dest;
}
}

579
node_modules/fast-xml-parser/src/fxp.d.ts generated vendored Normal file
View File

@@ -0,0 +1,579 @@
import type { Expression, ReadonlyMatcher } from './pem';
// jPath: true → string
// jPath: false → ReadonlyMatcher
type JPathOrMatcher = string | ReadonlyMatcher;
type JPathOrExpression = string | Expression;
export type ProcessEntitiesOptions = {
/**
* Whether to enable entity processing
*
* Defaults to `true`
*/
enabled?: boolean;
/**
* Maximum size in characters for a single entity definition
*
* Defaults to `10000`
*/
maxEntitySize?: number;
/**
* Maximum depth for nested entity references (reserved for future use)
*
* Defaults to `10`
*/
maxExpansionDepth?: number;
/**
* Maximum total number of entity expansions allowed
*
* Defaults to `1000`
*/
maxTotalExpansions?: number;
/**
* Maximum total expanded content length in characters
*
* Defaults to `100000`
*/
maxExpandedLength?: number;
/**
* Maximum number of entities allowed in the XML
*
* Defaults to `100`
*/
maxEntityCount?: number;
/**
* Array of tag names where entity replacement is allowed.
* If null, entities are replaced in all tags.
*
* Defaults to `null`
*/
allowedTags?: string[] | null;
/**
* Custom filter function to determine if entities should be replaced in a tag
*
* @param tagName - The name of the current tag
* @param jPathOrMatcher - The jPath string (if jPath: true) or Matcher instance (if jPath: false)
* @returns `true` to allow entity replacement, `false` to skip
*
* Defaults to `null`
*/
tagFilter?: ((tagName: string, jPathOrMatcher: JPathOrMatcher) => boolean) | null;
};
export type X2jOptions = {
/**
* Preserve the order of tags in resulting JS object
*
* Defaults to `false`
*/
preserveOrder?: boolean;
/**
* Give a prefix to the attribute name in the resulting JS object
*
* Defaults to '@_'
*/
attributeNamePrefix?: string;
/**
* A name to group all attributes of a tag under, or `false` to disable
*
* Defaults to `false`
*/
attributesGroupName?: false | string;
/**
* The name of the next node in the resulting JS
*
* Defaults to `#text`
*/
textNodeName?: string;
/**
* Whether to ignore attributes when parsing
*
* When `true` - ignores all the attributes
*
* When `false` - parses all the attributes
*
* When `Array<string | RegExp>` - filters out attributes that match provided patterns
*
* When `Function` - calls the function for each attribute and filters out those for which the function returned `true`
*
* Defaults to `true`
*/
ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPathOrMatcher: JPathOrMatcher) => boolean);
/**
* Whether to remove namespace string from tag and attribute names
*
* Defaults to `false`
*/
removeNSPrefix?: boolean;
/**
* Whether to allow attributes without value
*
* Defaults to `false`
*/
allowBooleanAttributes?: boolean;
/**
* Whether to parse tag value with `strnum` package
*
* Defaults to `true`
*/
parseTagValue?: boolean;
/**
* Whether to parse attribute value with `strnum` package
*
* Defaults to `false`
*/
parseAttributeValue?: boolean;
/**
* Whether to remove surrounding whitespace from tag or attribute value
*
* Defaults to `true`
*/
trimValues?: boolean;
/**
* Give a property name to set CDATA values to instead of merging to tag's text value
*
* Defaults to `false`
*/
cdataPropName?: false | string;
/**
* If set, parse comments and set as this property
*
* Defaults to `false`
*/
commentPropName?: false | string;
/**
* Control how tag value should be parsed. Called only if tag value is not empty
*
* @param tagName - The name of the tag
* @param tagValue - The value of the tag
* @param jPathOrMatcher - The jPath string (if jPath: true) or Matcher instance (if jPath: false)
* @param hasAttributes - Whether the tag has attributes
* @param isLeafNode - Whether the tag is a leaf node
* @returns {undefined|null} `undefined` or `null` to set original value.
* @returns {unknown}
*
* 1. Different value or value with different data type to set new value.
* 2. Same value to set parsed value if `parseTagValue: true`.
*
* Defaults to `(tagName, val, jPathOrMatcher, hasAttributes, isLeafNode) => val`
*/
tagValueProcessor?: (tagName: string, tagValue: string, jPathOrMatcher: JPathOrMatcher, hasAttributes: boolean, isLeafNode: boolean) => unknown;
/**
* Control how attribute value should be parsed
*
* @param attrName - The name of the attribute
* @param attrValue - The value of the attribute
* @param jPathOrMatcher - The jPath string (if jPath: true) or Matcher instance (if jPath: false)
* @returns {undefined|null} `undefined` or `null` to set original value
* @returns {unknown}
*
* Defaults to `(attrName, val, jPathOrMatcher) => val`
*/
attributeValueProcessor?: (attrName: string, attrValue: string, jPathOrMatcher: JPathOrMatcher) => unknown;
/**
* Options to pass to `strnum` for parsing numbers
*
* Defaults to `{ hex: true, leadingZeros: true, eNotation: true }`
*/
numberParseOptions?: strnumOptions;
/**
* Nodes to stop parsing at
*
* Accepts string patterns or Expression objects from path-expression-matcher
*
* String patterns starting with "*." are automatically converted to ".." for backward compatibility
*
* Defaults to `[]`
*/
stopNodes?: JPathOrExpression[];
/**
* List of tags without closing tags
*
* Defaults to `[]`
*/
unpairedTags?: string[];
/**
* Whether to always create a text node
*
* Defaults to `false`
*/
alwaysCreateTextNode?: boolean;
/**
* Determine whether a tag should be parsed as an array
*
* @param tagName - The name of the tag
* @param jPathOrMatcher - The jPath string (if jPath: true) or Matcher instance (if jPath: false)
* @param isLeafNode - Whether the tag is a leaf node
* @param isAttribute - Whether this is an attribute
* @returns {boolean}
*
* Defaults to `() => false`
*/
isArray?: (tagName: string, jPathOrMatcher: JPathOrMatcher, isLeafNode: boolean, isAttribute: boolean) => boolean;
/**
* Whether to process default and DOCTYPE entities
*
* When `true` - enables entity processing with default limits
*
* When `false` - disables all entity processing
*
* When `ProcessEntitiesOptions` - enables entity processing with custom configuration
*
* Defaults to `true`
*/
processEntities?: boolean | ProcessEntitiesOptions;
/**
* Whether to process HTML entities
*
* Defaults to `false`
*/
htmlEntities?: boolean;
/**
* Whether to ignore the declaration tag from output
*
* Defaults to `false`
*/
ignoreDeclaration?: boolean;
/**
* Whether to ignore Pi tags
*
* Defaults to `false`
*/
ignorePiTags?: boolean;
/**
* Transform tag names
*
* Defaults to `false`
*/
transformTagName?: ((tagName: string) => string) | false;
/**
* Transform attribute names
*
* Defaults to `false`
*/
transformAttributeName?: ((attributeName: string) => string) | false;
/**
* Change the tag name when a different name is returned. Skip the tag from parsed result when false is returned.
* Modify `attrs` object to control attributes for the given tag.
*
* @param tagName - The name of the tag
* @param jPathOrMatcher - The jPath string (if jPath: true) or Matcher instance (if jPath: false)
* @param attrs - The attributes object
* @returns {string} new tag name.
* @returns false to skip the tag
*
* Defaults to `(tagName, jPathOrMatcher, attrs) => tagName`
*/
updateTag?: (tagName: string, jPathOrMatcher: JPathOrMatcher, attrs: { [k: string]: string }) => string | boolean;
/**
* If true, adds a Symbol to all object nodes, accessible by {@link XMLParser.getMetaDataSymbol} with
* metadata about each the node in the XML file.
*/
captureMetaData?: boolean;
/**
* Maximum number of nested tags
*
* Defaults to `100`
*/
maxNestedTags?: number;
/**
* Whether to strictly validate tag names
*
* Defaults to `true`
*/
strictReservedNames?: boolean;
/**
* Controls whether callbacks receive jPath as string or Matcher instance
*
* When `true` - callbacks receive jPath as string (backward compatible)
*
* When `false` - callbacks receive Matcher instance for advanced pattern matching
*
* Defaults to `true`
*/
jPath?: boolean;
/**
* Function to sanitize dangerous property names
*
* @param name - The name of the property
* @returns {string} The sanitized name
*
* Defaults to `(name) => __name`
*/
onDangerousProperty?: (name: string) => string;
};
export type strnumOptions = {
hex: boolean;
leadingZeros: boolean,
skipLike?: RegExp,
eNotation?: boolean
}
export type validationOptions = {
/**
* Whether to allow attributes without value
*
* Defaults to `false`
*/
allowBooleanAttributes?: boolean;
/**
* List of tags without closing tags
*
* Defaults to `[]`
*/
unpairedTags?: string[];
};
export type XmlBuilderOptions = {
/**
* Give a prefix to the attribute name in the resulting JS object
*
* Defaults to '@_'
*/
attributeNamePrefix?: string;
/**
* A name to group all attributes of a tag under, or `false` to disable
*
* Defaults to `false`
*/
attributesGroupName?: false | string;
/**
* The name of the next node in the resulting JS
*
* Defaults to `#text`
*/
textNodeName?: string;
/**
* Whether to ignore attributes when building
*
* When `true` - ignores all the attributes
*
* When `false` - builds all the attributes
*
* When `Array<string | RegExp>` - filters out attributes that match provided patterns
*
* When `Function` - calls the function for each attribute and filters out those for which the function returned `true`
*
* Defaults to `true`
*/
ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPath: string) => boolean);
/**
* Give a property name to set CDATA values to instead of merging to tag's text value
*
* Defaults to `false`
*/
cdataPropName?: false | string;
/**
* If set, parse comments and set as this property
*
* Defaults to `false`
*/
commentPropName?: false | string;
/**
* Whether to make output pretty instead of single line
*
* Defaults to `false`
*/
format?: boolean;
/**
* If `format` is set to `true`, sets the indent string
*
* Defaults to ` `
*/
indentBy?: string;
/**
* Give a name to a top-level array
*
* Defaults to `undefined`
*/
arrayNodeName?: string;
/**
* Create empty tags for tags with no text value
*
* Defaults to `false`
*/
suppressEmptyNode?: boolean;
/**
* Suppress an unpaired tag
*
* Defaults to `true`
*/
suppressUnpairedNode?: boolean;
/**
* Don't put a value for boolean attributes
*
* Defaults to `true`
*/
suppressBooleanAttributes?: boolean;
/**
* Preserve the order of tags in resulting JS object
*
* Defaults to `false`
*/
preserveOrder?: boolean;
/**
* List of tags without closing tags
*
* Defaults to `[]`
*/
unpairedTags?: string[];
/**
* Nodes to stop parsing at
*
* Accepts string patterns or Expression objects from path-expression-matcher
*
* Defaults to `[]`
*/
stopNodes?: JPathOrExpression[];
/**
* Control how tag value should be parsed. Called only if tag value is not empty
*
* @returns {undefined|null} `undefined` or `null` to set original value.
* @returns {unknown}
*
* 1. Different value or value with different data type to set new value.
* 2. Same value to set parsed value if `parseTagValue: true`.
*
* Defaults to `(tagName, val, jPath, hasAttributes, isLeafNode) => val`
*/
tagValueProcessor?: (name: string, value: unknown) => unknown;
/**
* Control how attribute value should be parsed
*
* @param attrName
* @param attrValue
* @param jPath
* @returns {undefined|null} `undefined` or `null` to set original value
* @returns {unknown}
*
* Defaults to `(attrName, val, jPath) => val`
*/
attributeValueProcessor?: (name: string, value: unknown) => unknown;
/**
* Whether to process default and DOCTYPE entities
*
* Defaults to `true`
*/
processEntities?: boolean;
oneListGroup?: boolean;
/**
* Maximum number of nested tags
*
* Defaults to `100`
*/
maxNestedTags?: number;
};
type ESchema = string | object | Array<string | object>;
export type ValidationError = {
err: {
code: string;
msg: string,
line: number,
col: number
};
};
export class XMLParser {
constructor(options?: X2jOptions);
parse(xmlData: string | Uint8Array, validationOptions?: validationOptions | boolean): any;
/**
* Add Entity which is not by default supported by this library
* @param entityIdentifier {string} Eg: 'ent' for &ent;
* @param entityValue {string} Eg: '\r'
*/
addEntity(entityIdentifier: string, entityValue: string): void;
/**
* Returns a Symbol that can be used to access the {@link XMLMetaData}
* 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 {@link X2jOptions.captureMetaData}
* is true in the options.
*/
static getMetaDataSymbol(): Symbol;
}
export class XMLValidator {
static validate(xmlData: string, options?: validationOptions): true | ValidationError;
}
export class XMLBuilder {
constructor(options?: XmlBuilderOptions);
build(jObj: any): string;
}
/**
* This object is available on nodes via the symbol {@link XMLParser.getMetaDataSymbol}
* when {@link X2jOptions.captureMetaData} is true.
*/
export interface XMLMetaData {
/** The index, if available, of the character where the XML node began in the input stream. */
startIndex?: number;
}

14
node_modules/fast-xml-parser/src/fxp.js generated vendored Normal file
View File

@@ -0,0 +1,14 @@
'use strict';
import { validate } from './validator.js';
import XMLParser from './xmlparser/XMLParser.js';
import XMLBuilder from './xmlbuilder/json2xml.js';
const XMLValidator = {
validate: validate
}
export {
XMLParser,
XMLValidator,
XMLBuilder
};

18
node_modules/fast-xml-parser/src/ignoreAttributes.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
export default 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
}

135
node_modules/fast-xml-parser/src/pem.d.ts generated vendored Normal file
View File

@@ -0,0 +1,135 @@
/**
* Types copied from path-expression-matcher
* @version <version>
* @updated <date>
*
* Update this file when path-expression-matcher releases a new version.
* Source: https://github.com/NaturalIntelligence/path-expression-matcher
*/
/**
* Options for creating an Expression
*/
export interface ExpressionOptions {
/**
* Path separator character
* @default '.'
*/
separator?: string;
}
/**
* Parsed segment from an expression pattern
*/
export interface Segment {
type: 'tag' | 'deep-wildcard';
tag?: string;
namespace?: string;
attrName?: string;
attrValue?: string;
position?: 'first' | 'last' | 'odd' | 'even' | 'nth';
positionValue?: number;
}
/**
* Expression - Parses and stores a tag pattern expression.
* Patterns are parsed once and stored in an optimized structure for fast matching.
*
* @example
* ```typescript
* const expr = new Expression("root.users.user");
* const expr2 = new Expression("..user[id]:first");
* const expr3 = new Expression("root/users/user", { separator: '/' });
* ```
*
* Pattern Syntax:
* - `root.users.user` — Match exact path
* - `..user` — Match "user" at any depth (deep wildcard)
* - `user[id]` — Match user tag with "id" attribute
* - `user[id=123]` — Match user tag where id="123"
* - `user:first` — Match first occurrence of user tag
* - `ns::user` — Match user tag with namespace "ns"
* - `ns::user[id]:first` — Combine namespace, attribute, and position
*/
export class Expression {
readonly pattern: string;
readonly separator: string;
readonly segments: Segment[];
constructor(pattern: string, options?: ExpressionOptions);
get length(): number;
hasDeepWildcard(): boolean;
hasAttributeCondition(): boolean;
hasPositionSelector(): boolean;
toString(): string;
}
// ---------------------------------------------------------------------------
// ReadonlyMatcher
// ---------------------------------------------------------------------------
/**
* A live read-only view of a {@link Matcher} instance, returned by {@link Matcher.readOnly}.
*
* All query and inspection methods work normally and always reflect the current
* state of the underlying matcher. State-mutating methods (`push`, `pop`,
* `reset`, `updateCurrent`, `restore`) are not present — calling them on the
* underlying Proxy throws a `TypeError` at runtime.
*
* This is the type received by all FXP user callbacks when `jPath: false`.
*/
export interface ReadonlyMatcher {
readonly separator: string;
/** Check if current path matches an Expression. */
matches(expression: Expression): boolean;
/** Get current tag name, or `undefined` if path is empty. */
getCurrentTag(): string | undefined;
/** Get current namespace, or `undefined` if not present. */
getCurrentNamespace(): string | undefined;
/** Get attribute value of the current node. */
getAttrValue(attrName: string): any;
/** Check if the current node has a given attribute. */
hasAttr(attrName: string): boolean;
/** Sibling position of the current node (child index in parent). */
getPosition(): number;
/** Occurrence counter of the current tag name at this level. */
getCounter(): number;
/** Number of nodes in the current path. */
getDepth(): number;
/** Current path as a string (e.g. `"root.users.user"`). */
toString(separator?: string, includeNamespace?: boolean): string;
/** Current path as an array of tag names. */
toArray(): string[];
/**
* Create a snapshot of the current state.
* The snapshot can be passed to the real {@link Matcher.restore} if needed.
*/
snapshot(): MatcherSnapshot;
}
/** Internal node structure — exposed via snapshot only. */
export interface PathNode {
tag: string;
namespace?: string;
position: number;
counter: number;
values?: Record<string, any>;
}
/** Snapshot of matcher state returned by `snapshot()` and `readOnly().snapshot()`. */
export interface MatcherSnapshot {
path: PathNode[];
siblingStacks: Map<string, number>[];
}

61
node_modules/fast-xml-parser/src/util.js generated vendored Normal file
View File

@@ -0,0 +1,61 @@
'use strict';
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';
export const nameRegexp = '[' + nameStartChar + '][' + nameChar + ']*';
const regexName = new RegExp('^' + nameRegexp + '$');
export 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;
}
export const isName = function (string) {
const match = regexName.exec(string);
return !(match === null || typeof match === 'undefined');
}
export function isExist(v) {
return typeof v !== 'undefined';
}
export function isEmptyObject(obj) {
return Object.keys(obj).length === 0;
}
export function getValue(v) {
if (exports.isExist(v)) {
return v;
} else {
return '';
}
}
/**
* Dangerous property names that could lead to prototype pollution or security issues
*/
export const DANGEROUS_PROPERTY_NAMES = [
// '__proto__',
// 'constructor',
// 'prototype',
'hasOwnProperty',
'toString',
'valueOf',
'__defineGetter__',
'__defineSetter__',
'__lookupGetter__',
'__lookupSetter__'
];
export const criticalProperties = ["__proto__", "constructor", "prototype"];

16
node_modules/fast-xml-parser/src/v6/CharsSymbol.js generated vendored Normal file
View File

@@ -0,0 +1,16 @@
export default {
"<" : "<", //tag start
">" : ">", //tag end
"/" : "/", //close tag
"!" : "!", //comment or docttype
"!--" : "!--", //comment
"-->" : "-->", //comment end
"?" : "?", //pi
"?>" : "?>", //pi end
"?xml" : "?xml", //pi end
"![" : "![", //cdata
"]]>" : "]]>", //cdata end
"[" : "[",
"-" : "-",
"D" : "D",
}

106
node_modules/fast-xml-parser/src/v6/EntitiesParser.js generated vendored Normal file
View File

@@ -0,0 +1,106 @@
const ampEntity = { regex: /&(amp|#38|#x26);/g, val: "&" };
const htmlEntities = {
"space": { regex: /&(nbsp|#160);/g, val: " " },
// "lt" : { regex: /&(lt|#60);/g, val: "<" },
// "gt" : { regex: /&(gt|#62);/g, val: ">" },
// "amp" : { regex: /&(amp|#38);/g, val: "&" },
// "quot" : { regex: /&(quot|#34);/g, val: "\"" },
// "apos" : { regex: /&(apos|#39);/g, val: "'" },
"cent": { regex: /&(cent|#162);/g, val: "¢" },
"pound": { regex: /&(pound|#163);/g, val: "£" },
"yen": { regex: /&(yen|#165);/g, val: "¥" },
"euro": { regex: /&(euro|#8364);/g, val: "€" },
"copyright": { regex: /&(copy|#169);/g, val: "©" },
"reg": { regex: /&(reg|#174);/g, val: "®" },
"inr": { regex: /&(inr|#8377);/g, val: "₹" },
"num_dec": { regex: /&#([0-9]{1,7});/g, val: (_, str) => String.fromCodePoint(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val: (_, str) => String.fromCodePoint(Number.parseInt(str, 16)) },
};
export default class EntitiesParser {
constructor(replaceHtmlEntities) {
this.replaceHtmlEntities = replaceHtmlEntities;
this.docTypeEntities = {};
this.lastEntities = {
"apos": { regex: /&(apos|#39|#x27);/g, val: "'" },
"gt": { regex: /&(gt|#62|#x3E);/g, val: ">" },
"lt": { regex: /&(lt|#60|#x3C);/g, val: "<" },
"quot": { regex: /&(quot|#34|#x22);/g, val: "\"" },
};
}
addExternalEntities(externalEntities) {
const entKeys = Object.keys(externalEntities);
for (let i = 0; i < entKeys.length; i++) {
const ent = entKeys[i];
this.addExternalEntity(ent, externalEntities[ent])
}
}
addExternalEntity(key, val) {
validateEntityName(key);
const escaped = key.replace(/[.\-+*:]/g, '\\.');
if (val.indexOf("&") !== -1) {
reportWarning(`Entity ${key} is not added as '&' is found in value;`)
return;
} else {
this.lastEntities[key] = {
regex: new RegExp("&" + escaped + ";", "g"),
val: val
}
}
}
addDocTypeEntities(entities) {
const entKeys = Object.keys(entities);
for (let i = 0; i < entKeys.length; i++) {
const ent = entKeys[i];
const escaped = ent.replace(/[.\-+*:]/g, '\\.');
this.docTypeEntities[ent] = {
regex: new RegExp("&" + escaped + ";", "g"),
val: entities[ent]
}
}
}
parse(val) {
return this.replaceEntitiesValue(val)
}
/**
* 1. Replace DOCTYPE entities
* 2. Replace external entities
* 3. Replace HTML entities if asked
* @param {string} val
*/
replaceEntitiesValue(val) {
if (typeof val === "string" && val.length > 0) {
for (let entityName in this.docTypeEntities) {
const entity = this.docTypeEntities[entityName];
val = val.replace(entity.regx, entity.val);
}
for (let entityName in this.lastEntities) {
const entity = this.lastEntities[entityName];
val = val.replace(entity.regex, entity.val);
}
if (this.replaceHtmlEntities) {
for (let entityName in htmlEntities) {
const entity = htmlEntities[entityName];
val = val.replace(entity.regex, entity.val);
}
}
val = val.replace(ampEntity.regex, ampEntity.val);
}
return val;
}
}
//an entity name should not contains special characters that may be used in regex
//Eg !?\\\/[]$%{}^&*()<>
const specialChar = "!?\\/[]$%{}^&*()<>|+";
function validateEntityName(name) {
for (let i = 0; i < specialChar.length; i++) {
const ch = specialChar[i];
if (name.indexOf(ch) !== -1) throw new Error(`Invalid character ${ch} in entity name`);
}
return name;
}

61
node_modules/fast-xml-parser/src/v6/OptionsBuilder.js generated vendored Normal file
View File

@@ -0,0 +1,61 @@
import { JsObjOutputBuilder } from './OutputBuilders/JsObjBuilder.js';
export const defaultOptions = {
preserveOrder: false,
removeNSPrefix: false, // remove NS from tag name or attribute name if true
//ignoreRootElement : false,
stopNodes: [], //nested tags will not be parsed even for errors
// isArray: () => false, //User will set it
htmlEntities: false,
// skipEmptyListItem: false
tags: {
unpaired: [],
nameFor: {
cdata: false,
comment: false,
text: '#text'
},
separateTextProperty: false,
},
attributes: {
ignore: false,
booleanType: true,
entities: true,
},
// select: ["img[src]"],
// stop: ["anim", "[ads]"]
only: [], // rest tags will be skipped. It will result in flat array
hierarchy: false, //will be used when a particular tag is set to be parsed.
skip: [], // will be skipped from parse result. on('skip') will be triggered
select: [], // on('select', tag => tag ) will be called if match
stop: [], //given tagPath will not be parsed. innerXML will be set as string value
OutputBuilder: new JsObjOutputBuilder(),
};
export const buildOptions = function (options) {
const finalOptions = { ...defaultOptions };
copyProperties(finalOptions, options)
return finalOptions;
};
function copyProperties(target, source) {
for (let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (key === 'OutputBuilder') {
target[key] = source[key];
} else if (typeof source[key] === 'object' && !Array.isArray(source[key])) {
// Recursively copy nested properties
if (typeof target[key] === 'undefined') {
target[key] = {};
}
copyProperties(target[key], source[key]);
} else {
// Copy non-nested properties
target[key] = source[key];
}
}
}
}

View File

@@ -0,0 +1,69 @@
export default class BaseOutputBuilder {
constructor() {
// this.attributes = {};
}
addAttribute(name, value) {
if (this.options.onAttribute) {
//TODO: better to pass tag path
const v = this.options.onAttribute(name, value, this.tagName);
if (v) this.attributes[v.name] = v.value;
} else {
name = this.options.attributes.prefix + name + this.options.attributes.suffix;
this.attributes[name] = this.parseValue(value, this.options.attributes.valueParsers);
}
}
/**
* parse value by chain of parsers
* @param {string} val
* @returns {any} parsed value if matching parser found
*/
parseValue = function (val, valParsers) {
for (let i = 0; i < valParsers.length; i++) {
let valParser = valParsers[i];
if (typeof valParser === "string") {
valParser = this.registeredParsers[valParser];
}
if (valParser) {
val = valParser.parse(val);
}
}
return val;
}
/**
* To add a nested empty tag.
* @param {string} key
* @param {any} val
*/
_addChild(key, val) { }
/**
* skip the comment if property is not set
*/
addComment(text) {
if (this.options.nameFor.comment)
this._addChild(this.options.nameFor.comment, text);
}
//store CDATA separately if property is set
//otherwise add to tag's value
addCdata(text) {
if (this.options.nameFor.cdata) {
this._addChild(this.options.nameFor.cdata, text);
} else {
this.addRawValue(text || "");
}
}
addRawValue = text => this.addValue(text);
addDeclaration() {
if (!this.options.declaration) {
} else {
this.addPi("?xml");
}
this.attributes = {}
}
}

View File

@@ -0,0 +1,103 @@
import { buildOptions, registerCommonValueParsers } from './ParserOptionsBuilder.js';
export default class OutputBuilder {
constructor(options) {
this.options = buildOptions(options);
this.registeredParsers = registerCommonValueParsers(this.options);
}
registerValueParser(name, parserInstance) {//existing name will override the parser without warning
this.registeredParsers[name] = parserInstance;
}
getInstance(parserOptions) {
return new JsArrBuilder(parserOptions, this.options, this.registeredParsers);
}
}
const rootName = '!js_arr';
import BaseOutputBuilder from './BaseOutputBuilder.js';
class JsArrBuilder extends BaseOutputBuilder {
constructor(parserOptions, options, registeredParsers) {
super();
this.tagsStack = [];
this.parserOptions = parserOptions;
this.options = options;
this.registeredParsers = registeredParsers;
this.root = new Node(rootName);
this.currentNode = this.root;
this.attributes = {};
}
addTag(tag) {
//when a new tag is added, it should be added as child of current node
//TODO: shift this check to the parser
if (tag.name === "__proto__") tag.name = "#__proto__";
this.tagsStack.push(this.currentNode);
this.currentNode = new Node(tag.name, this.attributes);
this.attributes = {};
}
/**
* Check if the node should be added by checking user's preference
* @param {Node} node
* @returns boolean: true if the node should not be added
*/
closeTag() {
const node = this.currentNode;
this.currentNode = this.tagsStack.pop(); //set parent node in scope
if (this.options.onClose !== undefined) {
//TODO TagPathMatcher
const resultTag = this.options.onClose(node,
new TagPathMatcher(this.tagsStack, node));
if (resultTag) return;
}
this.currentNode.child.push(node); //to parent node
}
//Called by parent class methods
_addChild(key, val) {
// if(key === "__proto__") tagName = "#__proto__";
this.currentNode.child.push({ [key]: val });
// this.currentNode.leafType = false;
}
/**
* Add text value child node
* @param {string} text
*/
addValue(text) {
this.currentNode.child.push({ [this.options.nameFor.text]: this.parseValue(text, this.options.tags.valueParsers) });
}
addPi(name) {
//TODO: set pi flag
if (!this.options.ignorePiTags) {
const node = new Node(name, this.attributes);
this.currentNode[":@"] = this.attributes;
this.currentNode.child.push(node);
}
this.attributes = {};
}
getOutput() {
return this.root.child[0];
}
}
class Node {
constructor(tagname, attributes) {
this.tagname = tagname;
this.child = []; //nested tags, text, cdata, comments
if (attributes && Object.keys(attributes).length > 0)
this[":@"] = attributes;
}
}
module.exports = OutputBuilder;

View File

@@ -0,0 +1,100 @@
import {buildOptions,registerCommonValueParsers} from"./ParserOptionsBuilder.js";
export default class OutputBuilder{
constructor(options){
this.options = buildOptions(options);
this.registeredParsers = registerCommonValueParsers(this.options);
}
registerValueParser(name,parserInstance){//existing name will override the parser without warning
this.registeredParsers[name] = parserInstance;
}
getInstance(parserOptions){
return new JsMinArrBuilder(parserOptions, this.options, this.registeredParsers);
}
}
import BaseOutputBuilder from "./BaseOutputBuilder.js";
const rootName = '^';
class JsMinArrBuilder extends BaseOutputBuilder{
constructor(parserOptions, options,registeredParsers) {
super();
this.tagsStack = [];
this.parserOptions = parserOptions;
this.options = options;
this.registeredParsers = registeredParsers;
this.root = {[rootName]: []};
this.currentNode = this.root;
this.currentNodeTagName = rootName;
this.attributes = {};
}
addTag(tag){
//when a new tag is added, it should be added as child of current node
//TODO: shift this check to the parser
if(tag.name === "__proto__") tag.name = "#__proto__";
this.tagsStack.push([this.currentNodeTagName,this.currentNode]); //this.currentNode is parent node here
this.currentNodeTagName = tag.name;
this.currentNode = { [tag.name]:[]}
if(Object.keys(this.attributes).length > 0){
this.currentNode[":@"] = this.attributes;
this.attributes = {};
}
}
/**
* Check if the node should be added by checking user's preference
* @param {Node} node
* @returns boolean: true if the node should not be added
*/
closeTag(){
const node = this.currentNode;
const nodeName = this.currentNodeTagName;
const arr = this.tagsStack.pop(); //set parent node in scope
this.currentNodeTagName = arr[0];
this.currentNode = arr[1];
if(this.options.onClose !== undefined){
//TODO TagPathMatcher
const resultTag = this.options.onClose(node,
new TagPathMatcher(this.tagsStack,node));
if(resultTag) return;
}
this.currentNode[this.currentNodeTagName].push(node); //to parent node
}
//Called by parent class methods
_addChild(key, val){
// if(key === "__proto__") tagName = "#__proto__";
this.currentNode.push( {[key]: val });
// this.currentNode.leafType = false;
}
/**
* Add text value child node
* @param {string} text
*/
addValue(text){
this.currentNode[this.currentNodeTagName].push( {[this.options.nameFor.text]: this.parseValue(text, this.options.tags.valueParsers) });
}
addPi(name){
if(!this.options.ignorePiTags){
const node = { [name]:[]}
if(this.attributes){
node[":@"] = this.attributes;
}
this.currentNode.push(node);
}
this.attributes = {};
}
getOutput(){
return this.root[rootName];
}
}

View File

@@ -0,0 +1,154 @@
import { buildOptions, registerCommonValueParsers } from './ParserOptionsBuilder.js';
export default class OutputBuilder {
constructor(builderOptions) {
this.options = buildOptions(builderOptions);
this.registeredParsers = registerCommonValueParsers(this.options);
}
registerValueParser(name, parserInstance) {//existing name will override the parser without warning
this.registeredParsers[name] = parserInstance;
}
getInstance(parserOptions) {
return new JsObjBuilder(parserOptions, this.options, this.registeredParsers);
}
}
import BaseOutputBuilder from './BaseOutputBuilder.js';
const rootName = '^';
class JsObjBuilder extends BaseOutputBuilder {
constructor(parserOptions, builderOptions, registeredParsers) {
super();
//hold the raw detail of a tag and sequence with reference to the output
this.tagsStack = [];
this.parserOptions = parserOptions;
this.options = builderOptions;
this.registeredParsers = registeredParsers;
this.root = {};
this.parent = this.root;
this.tagName = rootName;
this.value = {};
this.textValue = "";
this.attributes = {};
}
addTag(tag) {
let value = "";
if (!isEmpty(this.attributes)) {
value = {};
if (this.options.attributes.groupBy) {
value[this.options.attributes.groupBy] = this.attributes;
} else {
value = this.attributes;
}
}
this.tagsStack.push([this.tagName, this.textValue, this.value]); //parent tag, parent text value, parent tag value (jsobj)
this.tagName = tag.name;
this.value = value;
this.textValue = "";
this.attributes = {};
}
/**
* Check if the node should be added by checking user's preference
* @param {Node} node
* @returns boolean: true if the node should not be added
*/
closeTag() {
const tagName = this.tagName;
let value = this.value;
let textValue = this.textValue;
//update tag text value
if (typeof value !== "object" && !Array.isArray(value)) {
value = this.parseValue(textValue.trim(), this.options.tags.valueParsers);
} else if (textValue.length > 0) {
value[this.options.nameFor.text] = this.parseValue(textValue.trim(), this.options.tags.valueParsers);
}
let resultTag = {
tagName: tagName,
value: value
};
if (this.options.onTagClose !== undefined) {
//TODO TagPathMatcher
resultTag = this.options.onClose(tagName, value, this.textValue, new TagPathMatcher(this.tagsStack, node));
if (!resultTag) return;
}
//set parent node in scope
let arr = this.tagsStack.pop();
let parentTag = arr[2];
parentTag = this._addChildTo(resultTag.tagName, resultTag.value, parentTag);
this.tagName = arr[0];
this.textValue = arr[1];
this.value = parentTag;
}
_addChild(key, val) {
if (typeof this.value === "string") {
this.value = { [this.options.nameFor.text]: this.value };
}
this._addChildTo(key, val, this.value);
// this.currentNode.leafType = false;
this.attributes = {};
}
_addChildTo(key, val, node) {
if (typeof node === 'string') node = {};
if (!node[key]) {
node[key] = val;
} else { //Repeated
if (!Array.isArray(node[key])) { //but not stored as array
node[key] = [node[key]];
}
node[key].push(val);
}
return node;
}
/**
* Add text value child node
* @param {string} text
*/
addValue(text) {
//TODO: use bytes join
if (this.textValue.length > 0) this.textValue += " " + text;
else this.textValue = text;
}
addPi(name) {
let value = "";
if (!isEmpty(this.attributes)) {
value = {};
if (this.options.attributes.groupBy) {
value[this.options.attributes.groupBy] = this.attributes;
} else {
value = this.attributes;
}
}
this._addChild(name, value);
}
getOutput() {
return this.value;
}
}
function isEmpty(obj) {
return Object.keys(obj).length === 0;
}

View File

@@ -0,0 +1,94 @@
import trimParser from "../valueParsers/trim.js";
import booleanParser from "../valueParsers/booleanParser.js";
import currencyParser from "../valueParsers/currency.js";
import numberParser from "../valueParsers/number.js";
const defaultOptions = {
nameFor: {
text: "#text",
comment: "",
cdata: "",
},
// onTagClose: () => {},
// onAttribute: () => {},
piTag: false,
declaration: false, //"?xml"
tags: {
valueParsers: [
// "trim",
// "boolean",
// "number",
// "currency",
// "date",
]
},
attributes: {
prefix: "@_",
suffix: "",
groupBy: "",
valueParsers: [
// "trim",
// "boolean",
// "number",
// "currency",
// "date",
]
},
dataType: {
}
}
//TODO
const withJoin = ["trim", "join", /*"entities",*/"number", "boolean", "currency"/*, "date"*/]
const withoutJoin = ["trim", /*"entities",*/"number", "boolean", "currency"/*, "date"*/]
export function buildOptions(options) {
//clone
const finalOptions = { ...defaultOptions };
//add config missed in cloning
finalOptions.tags.valueParsers.push(...withJoin)
if (!this.preserveOrder)
finalOptions.tags.valueParsers.push(...withoutJoin);
//add config missed in cloning
finalOptions.attributes.valueParsers.push(...withJoin)
//override configuration
copyProperties(finalOptions, options);
return finalOptions;
}
function copyProperties(target, source) {
for (let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (typeof source[key] === 'object' && !Array.isArray(source[key])) {
// Recursively copy nested properties
if (typeof target[key] === 'undefined') {
target[key] = {};
}
copyProperties(target[key], source[key]);
} else {
// Copy non-nested properties
target[key] = source[key];
}
}
}
}
export function registerCommonValueParsers(options) {
return {
"trim": new trimParser(),
// "join": this.entityParser.parse,
"boolean": new booleanParser(),
"number": new numberParser({
hex: true,
leadingZeros: true,
eNotation: true
}),
"currency": new currencyParser(),
// "date": this.entityParser.parse,
}
}

0
node_modules/fast-xml-parser/src/v6/Report.js generated vendored Normal file
View File

81
node_modules/fast-xml-parser/src/v6/TagPath.js generated vendored Normal file
View File

@@ -0,0 +1,81 @@
export default class TagPath{
constructor(pathStr){
let text = "";
let tName = "";
let pos;
let aName = "";
let aVal = "";
this.stack = []
for (let i = 0; i < pathStr.length; i++) {
let ch = pathStr[i];
if(ch === " ") {
if(text.length === 0) continue;
tName = text; text = "";
}else if(ch === "["){
if(tName.length === 0){
tName = text; text = "";
}
i++;
for (; i < pathStr.length; i++) {
ch = pathStr[i];
if(ch=== "=") continue;
else if(ch=== "]") {aName = text.trim(); text=""; break; i--;}
else if(ch === "'" || ch === '"'){
let attrEnd = pathStr.indexOf(ch,i+1);
aVal = pathStr.substring(i+1, attrEnd);
i = attrEnd;
}else{
text +=ch;
}
}
}else if(ch !== " " && text.length === 0 && tName.length > 0){//reading tagName
//save previous tag
this.stack.push(new TagPathNode(tName,pos,aName,aVal));
text = ch; tName = ""; aName = ""; aVal = "";
}else{
text+=ch;
}
}
//last tag in the path
if(tName.length >0 || text.length>0){
this.stack.push(new TagPathNode(text||tName,pos,aName,aVal));
}
}
match(tagStack,node){
if(this.stack[0].name !== "*"){
if(this.stack.length !== tagStack.length +1) return false;
//loop through tagPath and tagStack and match
for (let i = 0; i < this.tagStack.length; i++) {
if(!this.stack[i].match(tagStack[i])) return false;
}
}
if(!this.stack[this.stack.length - 1].match(node)) return false;
return true;
}
}
class TagPathNode{
constructor(name,position,attrName,attrVal){
this.name = name;
this.position = position;
this.attrName = attrName,
this.attrVal = attrVal;
}
match(node){
let matching = true;
matching = node.name === this.name;
if(this.position) matching = node.position === this.position;
if(this.attrName) matching = node.attrs[this.attrName !== undefined];
if(this.attrVal) matching = node.attrs[this.attrName !== this.attrVal];
return matching;
}
}
// console.log((new TagPath("* b[b]")).stack);
// console.log((new TagPath("a[a] b[b] c")).stack);
// console.log((new TagPath(" b [ b= 'cf sdadwa' ] a ")).stack);

13
node_modules/fast-xml-parser/src/v6/TagPathMatcher.js generated vendored Normal file
View File

@@ -0,0 +1,13 @@
import {TagPath} from './TagPath.js';
export default class TagPathMatcher{
constructor(stack,node){
this.stack = stack;
this.node= node;
}
match(path){
const tagPath = new TagPath(path);
return tagPath.match(this.stack, this.node);
}
}

83
node_modules/fast-xml-parser/src/v6/XMLParser.js generated vendored Normal file
View File

@@ -0,0 +1,83 @@
import { buildOptions } from './OptionsBuilder.js';
import Xml2JsParser from './Xml2JsParser.js';
export default class XMLParser {
constructor(options) {
this.externalEntities = {};
this.options = buildOptions(options);
// console.log(this.options)
}
/**
* Parse XML data string to JS object
* @param {string|Buffer} xmlData
* @param {boolean|Object} validationOption
*/
parse(xmlData) {
if (Array.isArray(xmlData) && xmlData.byteLength !== undefined) {
return this.parse(xmlData);
} else if (xmlData.toString) {
xmlData = xmlData.toString();
} else {
throw new Error("XML data is accepted in String or Bytes[] form.")
}
// if( validationOption){
// if(validationOption === true) validationOption = {}; //validate with default options
// const result = validator.validate(xmlData, validationOption);
// if (result !== true) {
// throw Error( `${result.err.msg}:${result.err.line}:${result.err.col}` )
// }
// }
const parser = new Xml2JsParser(this.options);
parser.entityParser.addExternalEntities(this.externalEntities);
return parser.parse(xmlData);
}
/**
* Parse XML data buffer to JS object
* @param {string|Buffer} xmlData
* @param {boolean|Object} validationOption
*/
parseBytesArr(xmlData) {
if (Array.isArray(xmlData) && xmlData.byteLength !== undefined) {
} else {
throw new Error("XML data is accepted in Bytes[] form.")
}
const parser = new Xml2JsParser(this.options);
parser.entityParser.addExternalEntities(this.externalEntities);
return parser.parseBytesArr(xmlData);
}
/**
* Parse XML data stream to JS object
* @param {fs.ReadableStream} xmlDataStream
*/
parseStream(xmlDataStream) {
if (!isStream(xmlDataStream)) throw new Error("FXP: Invalid stream input");
const orderedObjParser = new Xml2JsParser(this.options);
orderedObjParser.entityParser.addExternalEntities(this.externalEntities);
return orderedObjParser.parseStream(xmlDataStream);
}
/**
* 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 '&#xD;'")
} else if (value === "&") {
throw new Error("An entity with value '&' is not permitted");
} else {
this.externalEntities[key] = value;
}
}
}
function isStream(stream) {
if (stream && typeof stream.read === "function" && typeof stream.on === "function" && typeof stream.readableEnded === "boolean") return true;
return false;
}

235
node_modules/fast-xml-parser/src/v6/Xml2JsParser.js generated vendored Normal file
View File

@@ -0,0 +1,235 @@
import StringSource from './inputSource/StringSource.js';
import BufferSource from './inputSource/BufferSource.js';
import {readTagExp,readClosingTagName} from './XmlPartReader.js';
import {readComment, readCdata,readDocType,readPiTag} from './XmlSpecialTagsReader.js';
import TagPath from './TagPath.js';
import TagPathMatcher from './TagPathMatcher.js';
import EntitiesParser from './EntitiesParser.js';
//To hold the data of current tag
//This is usually used to compare jpath expression against current tag
class TagDetail{
constructor(name){
this.name = name;
this.position = 0;
// this.attributes = {};
}
}
export default class Xml2JsParser {
constructor(options) {
this.options = options;
this.currentTagDetail = null;
this.tagTextData = "";
this.tagsStack = [];
this.entityParser = new EntitiesParser(options.htmlEntities);
this.stopNodes = [];
for (let i = 0; i < this.options.stopNodes.length; i++) {
this.stopNodes.push(new TagPath(this.options.stopNodes[i]));
}
}
parse(strData) {
this.source = new StringSource(strData);
this.parseXml();
return this.outputBuilder.getOutput();
}
parseBytesArr(data) {
this.source = new BufferSource(data );
this.parseXml();
return this.outputBuilder.getOutput();
}
parseXml() {
//TODO: Separate TagValueParser as separate class. So no scope issue in node builder class
//OutputBuilder should be set in XML Parser
this.outputBuilder = this.options.OutputBuilder.getInstance(this.options);
this.root = { root: true};
this.currentTagDetail = this.root;
while(this.source.canRead()){
let ch = this.source.readCh();
if (ch === "") break;
if(ch === "<"){//tagStart
let nextChar = this.source.readChAt(0);
if (nextChar === "" ) throw new Error("Unexpected end of source");
if(nextChar === "!" || nextChar === "?"){
this.source.updateBufferBoundary();
//previously collected text should be added to current node
this.addTextNode();
this.readSpecialTag(nextChar);// Read DOCTYPE, comment, CDATA, PI tag
}else if(nextChar === "/"){
this.source.updateBufferBoundary();
this.readClosingTag();
// console.log(this.source.buffer.length, this.source.readable);
// console.log(this.tagsStack.length);
}else{//opening tag
this.readOpeningTag();
}
}else{
this.tagTextData += ch;
}
}//End While loop
if(this.tagsStack.length > 0 || ( this.tagTextData !== "undefined" && this.tagTextData.trimEnd().length > 0) ) throw new Error("Unexpected data in the end of document");
}
/**
* read closing paired tag. Set parent tag in scope.
* skip a node on user's choice
*/
readClosingTag(){
const tagName = this.processTagName(readClosingTagName(this.source));
// console.log(tagName, this.tagsStack.length);
this.validateClosingTag(tagName);
// All the text data collected, belongs to current tag.
if(!this.currentTagDetail.root) this.addTextNode();
this.outputBuilder.closeTag();
// Since the tag is closed now, parent tag comes in scope
this.currentTagDetail = this.tagsStack.pop();
}
validateClosingTag(tagName){
// This can't be unpaired tag, or a stop tag.
if(this.isUnpaired(tagName) || this.isStopNode(tagName)) throw new Error(`Unexpected closing tag '${tagName}'`);
// This must match with last opening tag
else if(tagName !== this.currentTagDetail.name)
throw new Error(`Unexpected closing tag '${tagName}' expecting '${this.currentTagDetail.name}'`)
}
/**
* Read paired, unpaired, self-closing, stop and special tags.
* Create a new node
* Push paired tag in stack.
*/
readOpeningTag(){
//save previously collected text data to current node
this.addTextNode();
//create new tag
let tagExp = readTagExp(this, ">" );
// process and skip from tagsStack For unpaired tag, self closing tag, and stop node
const tagDetail = new TagDetail(tagExp.tagName);
if(this.isUnpaired(tagExp.tagName)) {
//TODO: this will lead 2 extra stack operation
this.outputBuilder.addTag(tagDetail);
this.outputBuilder.closeTag();
} else if(tagExp.selfClosing){
this.outputBuilder.addTag(tagDetail);
this.outputBuilder.closeTag();
} else if(this.isStopNode(this.currentTagDetail)){
// TODO: let's user set a stop node boundary detector for complex contents like script tag
//TODO: pass tag name only to avoid string operations
const content = source.readUptoCloseTag(`</${tagExp.tagName}`);
this.outputBuilder.addTag(tagDetail);
this.outputBuilder.addValue(content);
this.outputBuilder.closeTag();
}else{//paired tag
//set new nested tag in scope.
this.tagsStack.push(this.currentTagDetail);
this.outputBuilder.addTag(tagDetail);
this.currentTagDetail = tagDetail;
}
// console.log(tagExp.tagName,this.tagsStack.length);
// this.options.onClose()
}
readSpecialTag(startCh){
if(startCh == "!"){
let nextChar = this.source.readCh();
if (nextChar === null || nextChar === undefined) throw new Error("Unexpected ending of the source");
if(nextChar === "-"){//comment
readComment(this);
}else if(nextChar === "["){//CDATA
readCdata(this);
}else if(nextChar === "D"){//DOCTYPE
readDocType(this);
}
}else if(startCh === "?"){
readPiTag(this);
}else{
throw new Error(`Invalid tag '<${startCh}' at ${this.source.line}:${this.source.col}`)
}
}
addTextNode = function() {
// if(this.currentTagDetail){
//save text as child node
// if(this.currentTagDetail.tagname !== '!xml')
if (this.tagTextData !== undefined && this.tagTextData !== "") { //store previously collected data as textNode
if(this.tagTextData.trim().length > 0){
//TODO: shift parsing to output builder
this.outputBuilder.addValue(this.replaceEntities(this.tagTextData));
}
this.tagTextData = "";
}
// }
}
processAttrName(name){
if(name === "__proto__") name = "#__proto__";
name = resolveNameSpace(name, this.removeNSPrefix);
return name;
}
processTagName(name){
if(name === "__proto__") name = "#__proto__";
name = resolveNameSpace(name, this.removeNSPrefix);
return name;
}
/**
* Generate tags path from tagsStack
*/
tagsPath(tagName){
//TODO: return TagPath Object. User can call match method with path
return "";
}
isUnpaired(tagName){
return this.options.tags.unpaired.indexOf(tagName) !== -1;
}
/**
* valid expressions are
* tag nested
* * nested
* tag nested[attribute]
* tag nested[attribute=""]
* tag nested[attribute!=""]
* tag nested:0 //for future
* @param {string} tagName
* @returns
*/
isStopNode(node){
for (let i = 0; i < this.stopNodes.length; i++) {
const givenPath = this.stopNodes[i];
if(givenPath.match(this.tagsStack, node)) return true;
}
return false
}
replaceEntities(text){
//TODO: if option is set then replace entities
return this.entityParser.parse(text)
}
}
function resolveNameSpace(name, removeNSPrefix) {
if (removeNSPrefix) {
const parts = name.split(':');
if(parts.length === 2){
if (parts[0] === 'xmlns') return '';
else return parts[1];
}else reportError(`Multiple namespaces ${name}`)
}
return name;
}

210
node_modules/fast-xml-parser/src/v6/XmlPartReader.js generated vendored Normal file
View File

@@ -0,0 +1,210 @@
'use strict';
/**
* find paired tag for a stop node
* @param {string} xmlDoc
* @param {string} tagName
* @param {number} i : start index
*/
export function readStopNode(xmlDoc, tagName, i){
const startIndex = i;
// Starting at 1 since we already have an open tag
let openTagCount = 1;
for (; i < xmlDoc.length; i++) {
if( xmlDoc[i] === "<"){
if (xmlDoc[i+1] === "/") {//close tag
const closeIndex = findSubStrIndex(xmlDoc, ">", i, `${tagName} is not closed`);
let closeTagName = xmlDoc.substring(i+2,closeIndex).trim();
if(closeTagName === tagName){
openTagCount--;
if (openTagCount === 0) {
return {
tagContent: xmlDoc.substring(startIndex, i),
i : closeIndex
}
}
}
i=closeIndex;
} else if(xmlDoc[i+1] === '?') {
const closeIndex = findSubStrIndex(xmlDoc, "?>", i+1, "StopNode is not closed.")
i=closeIndex;
} else if(xmlDoc.substr(i + 1, 3) === '!--') {
const closeIndex = findSubStrIndex(xmlDoc, "-->", i+3, "StopNode is not closed.")
i=closeIndex;
} else if(xmlDoc.substr(i + 1, 2) === '![') {
const closeIndex = findSubStrIndex(xmlDoc, "]]>", i, "StopNode is not closed.") - 2;
i=closeIndex;
} else {
const tagData = readTagExp(xmlDoc, i, '>')
if (tagData) {
const openTagName = tagData && tagData.tagName;
if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length-1] !== "/") {
openTagCount++;
}
i=tagData.closeIndex;
}
}
}
}//end for loop
}
/**
* Read closing tag name
* @param {Source} source
* @returns tag name
*/
export function readClosingTagName(source){
let text = ""; //temporary data
while(source.canRead()){
let ch = source.readCh();
// if (ch === null || ch === undefined) break;
// source.updateBuffer();
if (ch === ">") return text.trimEnd();
else text += ch;
}
throw new Error(`Unexpected end of source. Reading '${substr}'`);
}
/**
* Read XML tag and build attributes map
* This function can be used to read normal tag, pi tag.
* This function can't be used to read comment, CDATA, DOCTYPE.
* Eg <tag attr = ' some"' attr= ">" bool>
* @param {string} xmlDoc
* @param {number} startIndex starting index
* @returns tag expression includes tag name & attribute string
*/
export function readTagExp(parser) {
let inSingleQuotes = false;
let inDoubleQuotes = false;
let i;
let EOE = false;
for (i = 0; parser.source.canRead(i); i++) {
const char = parser.source.readChAt(i);
if (char === "'" && !inDoubleQuotes) {
inSingleQuotes = !inSingleQuotes;
} else if (char === '"' && !inSingleQuotes) {
inDoubleQuotes = !inDoubleQuotes;
} else if (char === '>' && !inSingleQuotes && !inDoubleQuotes) {
// If not inside quotes, stop reading at '>'
EOE = true;
break;
}
}
if(inSingleQuotes || inDoubleQuotes){
throw new Error("Invalid attribute expression. Quote is not properly closed");
}else if(!EOE) throw new Error("Unexpected closing of source. Waiting for '>'");
const exp = parser.source.readStr(i);
parser.source.updateBufferBoundary(i + 1);
return buildTagExpObj(exp, parser)
}
export function readPiExp(parser) {
let inSingleQuotes = false;
let inDoubleQuotes = false;
let i;
let EOE = false;
for (i = 0; parser.source.canRead(i) ; i++) {
const currentChar = parser.source.readChAt(i);
const nextChar = parser.source.readChAt(i+1);
if (currentChar === "'" && !inDoubleQuotes) {
inSingleQuotes = !inSingleQuotes;
} else if (currentChar === '"' && !inSingleQuotes) {
inDoubleQuotes = !inDoubleQuotes;
}
if (!inSingleQuotes && !inDoubleQuotes) {
if (currentChar === '?' && nextChar === '>') {
EOE = true;
break; // Exit the loop when '?>' is found
}
}
}
if(inSingleQuotes || inDoubleQuotes){
throw new Error("Invalid attribute expression. Quote is not properly closed in PI tag expression");
}else if(!EOE) throw new Error("Unexpected closing of source. Waiting for '?>'");
if(!parser.options.attributes.ignore){
//TODO: use regex to verify attributes if not set to ignore
}
const exp = parser.source.readStr(i);
parser.source.updateBufferBoundary(i + 1);
return buildTagExpObj(exp, parser)
}
function buildTagExpObj(exp, parser){
const tagExp = {
tagName: "",
selfClosing: false
};
let attrsExp = "";
// Check for self-closing tag before setting the name
if(exp[exp.length -1] === "/") {
tagExp.selfClosing = true;
exp = exp.slice(0, -1); // Remove the trailing slash
}
//separate tag name
let i = 0;
for (; i < exp.length; i++) {
const char = exp[i];
if(char === " "){
tagExp.tagName = exp.substring(0, i);
attrsExp = exp.substring(i + 1);
break;
}
}
//only tag
if(tagExp.tagName.length === 0 && i === exp.length)tagExp.tagName = exp;
tagExp.tagName = tagExp.tagName.trimEnd();
if(!parser.options.attributes.ignore && attrsExp.length > 0){
parseAttributesExp(attrsExp,parser)
}
return tagExp;
}
const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
function parseAttributesExp(attrStr, parser) {
const matches = getAllMatches(attrStr, attrsRegx);
const len = matches.length; //don't make it inline
for (let i = 0; i < len; i++) {
let attrName = parser.processAttrName(matches[i][1]);
let attrVal = parser.replaceEntities(matches[i][4] || true);
parser.outputBuilder.addAttribute(attrName, attrVal);
}
}
const getAllMatches = function(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;
};

View File

@@ -0,0 +1,111 @@
import {readPiExp} from './XmlPartReader.js';
export function readCdata(parser){
//<![ are already read till this point
let str = parser.source.readStr(6); //CDATA[
parser.source.updateBufferBoundary(6);
if(str !== "CDATA[") throw new Error(`Invalid CDATA expression at ${parser.source.line}:${parser.source.cols}`);
let text = parser.source.readUpto("]]>");
parser.outputBuilder.addCdata(text);
}
export function readPiTag(parser){
//<? are already read till this point
let tagExp = readPiExp(parser, "?>");
if(!tagExp) throw new Error("Invalid Pi Tag expression.");
if (tagExp.tagName === "?xml") {//TODO: test if tagName is just xml
parser.outputBuilder.addDeclaration();
} else {
parser.outputBuilder.addPi("?"+tagExp.tagName);
}
}
export function readComment(parser){
//<!- are already read till this point
let ch = parser.source.readCh();
if(ch !== "-") throw new Error(`Invalid comment expression at ${parser.source.line}:${parser.source.cols}`);
let text = parser.source.readUpto("-->");
parser.outputBuilder.addComment(text);
}
const DOCTYPE_tags = {
"EL":/^EMENT\s+([^\s>]+)\s+(ANY|EMPTY|\(.+\)\s*$)/m,
"AT":/^TLIST\s+[^\s]+\s+[^\s]+\s+[^\s]+\s+[^\s]+\s+$/m,
"NO":/^TATION.+$/m
}
export function readDocType(parser){
//<!D are already read till this point
let str = parser.source.readStr(6); //OCTYPE
parser.source.updateBufferBoundary(6);
if(str !== "OCTYPE") throw new Error(`Invalid DOCTYPE expression at ${parser.source.line}:${parser.source.cols}`);
let hasBody = false, lastch = "";
while(parser.source.canRead()){
//TODO: use readChAt like used in partReader
let ch = parser.source.readCh();
if(hasBody){
if (ch === '<') { //Determine the tag type
let str = parser.source.readStr(2);
parser.source.updateBufferBoundary(2);
if(str === "EN"){ //ENTITY
let str = parser.source.readStr(4);
parser.source.updateBufferBoundary(4);
if(str !== "TITY") throw new Error("Invalid DOCTYPE ENTITY expression");
registerEntity(parser);
}else if(str === "!-") {//comment
readComment(parser);
}else{ //ELEMENT, ATTLIST, NOTATION
let dTagExp = parser.source.readUpto(">");
const regx = DOCTYPE_tags[str];
if(regx){
const match = dTagExp.match(regx);
if(!match) throw new Error("Invalid DOCTYPE");
}else throw new Error("Invalid DOCTYPE");
}
}else if( ch === '>' && lastch === "]"){//end of doctype
return;
}
}else if( ch === '>'){//end of doctype
return;
}else if( ch === '['){
hasBody = true;
}else{
lastch = ch;
}
}//End While loop
}
function registerEntity(parser){
//read Entity
let attrBoundary="";
let name ="", val ="";
while(source.canRead()){
let ch = source.readCh();
if(attrBoundary){
if (ch === attrBoundary){
val = text;
text = ""
}
}else if(ch === " " || ch === "\t"){
if(!name){
name = text.trimStart();
text = "";
}
}else if (ch === '"' || ch === "'") {//start of attrBoundary
attrBoundary = ch;
}else if(ch === ">"){
parser.entityParser.addExternalEntity(name,val);
return;
}else{
text+=ch;
}
}
}

View File

@@ -0,0 +1,116 @@
const Constants = {
space: 32,
tab: 9
}
export default class BufferSource{
constructor(bytesArr){
this.line = 1;
this.cols = 0;
this.buffer = bytesArr;
this.startIndex = 0;
}
readCh() {
return String.fromCharCode(this.buffer[this.startIndex++]);
}
readChAt(index) {
return String.fromCharCode(this.buffer[this.startIndex+index]);
}
readStr(n,from){
if(typeof from === "undefined") from = this.startIndex;
return this.buffer.slice(from, from + n).toString();
}
readUpto(stopStr) {
const inputLength = this.buffer.length;
const stopLength = stopStr.length;
const stopBuffer = Buffer.from(stopStr);
for (let i = this.startIndex; i < inputLength; i++) {
let match = true;
for (let j = 0; j < stopLength; j++) {
if (this.buffer[i + j] !== stopBuffer[j]) {
match = false;
break;
}
}
if (match) {
const result = this.buffer.slice(this.startIndex, i).toString();
this.startIndex = i + stopLength;
return result;
}
}
throw new Error(`Unexpected end of source. Reading '${stopStr}'`);
}
readUptoCloseTag(stopStr) { //stopStr: "</tagname"
const inputLength = this.buffer.length;
const stopLength = stopStr.length;
const stopBuffer = Buffer.from(stopStr);
let stopIndex = 0;
//0: non-matching, 1: matching stop string, 2: matching closing
let match = 0;
for (let i = this.startIndex; i < inputLength; i++) {
if(match === 1){//initial part matched
if(stopIndex === 0) stopIndex = i;
if(this.buffer[i] === Constants.space || this.buffer[i] === Constants.tab) continue;
else if(this.buffer[i] === '>'){ //TODO: if it should be equivalent ASCII
match = 2;
//tag boundary found
// this.startIndex
}
}else{
match = 1;
for (let j = 0; j < stopLength; j++) {
if (this.buffer[i + j] !== stopBuffer[j]) {
match = 0;
break;
}
}
}
if (match === 2) {//matched closing part
const result = this.buffer.slice(this.startIndex, stopIndex - 1 ).toString();
this.startIndex = i + 1;
return result;
}
}
throw new Error(`Unexpected end of source. Reading '${stopStr}'`);
}
readFromBuffer(n, shouldUpdate) {
let ch;
if (n === 1) {
ch = this.buffer[this.startIndex];
if (ch === 10) {
this.line++;
this.cols = 1;
} else {
this.cols++;
}
ch = String.fromCharCode(ch);
} else {
this.cols += n;
ch = this.buffer.slice(this.startIndex, this.startIndex + n).toString();
}
if (shouldUpdate) this.updateBuffer(n);
return ch;
}
updateBufferBoundary(n = 1) { //n: number of characters read
this.startIndex += n;
}
canRead(n){
n = n || this.startIndex;
return this.buffer.length - n + 1 > 0;
}
}

View File

@@ -0,0 +1,121 @@
const whiteSpaces = [" ", "\n", "\t"];
export default class StringSource{
constructor(str){
this.line = 1;
this.cols = 0;
this.buffer = str;
//a boundary pointer to indicate where from the buffer dat should be read
// data before this pointer can be deleted to free the memory
this.startIndex = 0;
}
readCh() {
return this.buffer[this.startIndex++];
}
readChAt(index) {
return this.buffer[this.startIndex+index];
}
readStr(n,from){
if(typeof from === "undefined") from = this.startIndex;
return this.buffer.substring(from, from + n);
}
readUpto(stopStr) {
const inputLength = this.buffer.length;
const stopLength = stopStr.length;
for (let i = this.startIndex; i < inputLength; i++) {
let match = true;
for (let j = 0; j < stopLength; j++) {
if (this.buffer[i + j] !== stopStr[j]) {
match = false;
break;
}
}
if (match) {
const result = this.buffer.substring(this.startIndex, i);
this.startIndex = i + stopLength;
return result;
}
}
throw new Error(`Unexpected end of source. Reading '${stopStr}'`);
}
readUptoCloseTag(stopStr) { //stopStr: "</tagname"
const inputLength = this.buffer.length;
const stopLength = stopStr.length;
let stopIndex = 0;
//0: non-matching, 1: matching stop string, 2: matching closing
let match = 0;
for (let i = this.startIndex; i < inputLength; i++) {
if(match === 1){//initial part matched
if(stopIndex === 0) stopIndex = i;
if(this.buffer[i] === ' ' || this.buffer[i] === '\t') continue;
else if(this.buffer[i] === '>'){
match = 2;
//tag boundary found
// this.startIndex
}
}else{
match = 1;
for (let j = 0; j < stopLength; j++) {
if (this.buffer[i + j] !== stopStr[j]) {
match = 0;
break;
}
}
}
if (match === 2) {//matched closing part
const result = this.buffer.substring(this.startIndex, stopIndex - 1 );
this.startIndex = i + 1;
return result;
}
}
throw new Error(`Unexpected end of source. Reading '${stopStr}'`);
}
readFromBuffer(n, updateIndex){
let ch;
if(n===1){
ch = this.buffer[this.startIndex];
// if(ch === "\n") {
// this.line++;
// this.cols = 1;
// }else{
// this.cols++;
// }
}else{
ch = this.buffer.substring(this.startIndex, this.startIndex + n);
// if("".indexOf("\n") !== -1){
// //TODO: handle the scenario when there are multiple lines
// //TODO: col should be set to number of chars after last '\n'
// // this.cols = 1;
// }else{
// this.cols += n;
// }
}
if(updateIndex) this.updateBufferBoundary(n);
return ch;
}
//TODO: rename to updateBufferReadIndex
updateBufferBoundary(n = 1) { //n: number of characters read
this.startIndex += n;
}
canRead(n){
n = n || this.startIndex;
return this.buffer.length - n + 1 > 0;
}
}

View File

@@ -0,0 +1,105 @@
const ampEntity = { regex: /&(amp|#38|#x26);/g, val: "&" };
const htmlEntities = {
"space": { regex: /&(nbsp|#160);/g, val: " " },
// "lt" : { regex: /&(lt|#60);/g, val: "<" },
// "gt" : { regex: /&(gt|#62);/g, val: ">" },
// "amp" : { regex: /&(amp|#38);/g, val: "&" },
// "quot" : { regex: /&(quot|#34);/g, val: "\"" },
// "apos" : { regex: /&(apos|#39);/g, val: "'" },
"cent": { regex: /&(cent|#162);/g, val: "¢" },
"pound": { regex: /&(pound|#163);/g, val: "£" },
"yen": { regex: /&(yen|#165);/g, val: "¥" },
"euro": { regex: /&(euro|#8364);/g, val: "€" },
"copyright": { regex: /&(copy|#169);/g, val: "©" },
"reg": { regex: /&(reg|#174);/g, val: "®" },
"inr": { regex: /&(inr|#8377);/g, val: "₹" },
"num_dec": { regex: /&#([0-9]{1,7});/g, val: (_, str) => String.fromCodePoint(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val: (_, str) => String.fromCodePoint(Number.parseInt(str, 16)) },
};
export default class EntitiesParser {
constructor(replaceHtmlEntities) {
this.replaceHtmlEntities = replaceHtmlEntities;
this.docTypeEntities = {};
this.lastEntities = {
"apos": { regex: /&(apos|#39|#x27);/g, val: "'" },
"gt": { regex: /&(gt|#62|#x3E);/g, val: ">" },
"lt": { regex: /&(lt|#60|#x3C);/g, val: "<" },
"quot": { regex: /&(quot|#34|#x22);/g, val: "\"" },
};
}
addExternalEntities(externalEntities) {
const entKeys = Object.keys(externalEntities);
for (let i = 0; i < entKeys.length; i++) {
const ent = entKeys[i];
this.addExternalEntity(ent, externalEntities[ent])
}
}
addExternalEntity(key, val) {
validateEntityName(key);
if (val.indexOf("&") !== -1) {
reportWarning(`Entity ${key} is not added as '&' is found in value;`)
return;
} else {
this.lastEntities[ent] = {
regex: new RegExp("&" + key + ";", "g"),
val: val
}
}
}
addDocTypeEntities(entities) {
const entKeys = Object.keys(entities);
for (let i = 0; i < entKeys.length; i++) {
const ent = entKeys[i];
this.docTypeEntities[ent] = {
regex: new RegExp("&" + ent + ";", "g"),
val: entities[ent]
}
}
}
parse(val) {
return this.replaceEntitiesValue(val)
}
/**
* 1. Replace DOCTYPE entities
* 2. Replace external entities
* 3. Replace HTML entities if asked
* @param {string} val
*/
replaceEntitiesValue(val) {
if (typeof val === "string" && val.length > 0) {
for (let entityName in this.docTypeEntities) {
const entity = this.docTypeEntities[entityName];
val = val.replace(entity.regx, entity.val);
}
for (let entityName in this.lastEntities) {
const entity = this.lastEntities[entityName];
val = val.replace(entity.regex, entity.val);
}
if (this.replaceHtmlEntities) {
for (let entityName in htmlEntities) {
const entity = htmlEntities[entityName];
val = val.replace(entity.regex, entity.val);
}
}
val = val.replace(ampEntity.regex, ampEntity.val);
}
return val;
}
};
//an entity name should not contains special characters that may be used in regex
//Eg !?\\\/[]$%{}^&*()<>
const specialChar = "!?\\\/[]$%{}^&*()<>|+";
function validateEntityName(name) {
for (let i = 0; i < specialChar.length; i++) {
const ch = specialChar[i];
if (name.indexOf(ch) !== -1) throw new Error(`Invalid character ${ch} in entity name`);
}
return name;
}

View File

@@ -0,0 +1,22 @@
export default class boolParser{
constructor(trueList, falseList){
if(trueList)
this.trueList = trueList;
else
this.trueList = ["true"];
if(falseList)
this.falseList = falseList;
else
this.falseList = ["false"];
}
parse(val){
if (typeof val === 'string') {
//TODO: performance: don't convert
const temp = val.toLowerCase();
if(this.trueList.indexOf(temp) !== -1) return true;
else if(this.falseList.indexOf(temp) !== -1 ) return false;
}
return val;
}
}

View File

@@ -0,0 +1,19 @@
export default function boolParserExt(val){
if(isArray(val)){
for (let i = 0; i < val.length; i++) {
val[i] = parse(val[i])
}
}else{
val = parse(val)
}
return val;
}
function parse(val){
if (typeof val === 'string') {
const temp = val.toLowerCase();
if(temp === 'true' || temp ==="yes" || temp==="1") return true;
else if(temp === 'false' || temp ==="no" || temp==="0") return false;
}
return val;
}

View File

@@ -0,0 +1,38 @@
const defaultOptions = {
maxLength: 200,
// locale: "en-IN"
}
const localeMap = {
"$":"en-US",
"€":"de-DE",
"£":"en-GB",
"¥":"ja-JP",
"₹":"en-IN",
}
const sign = "(?:-|\+)?";
const digitsAndSeparator = "(?:\d+|\d{1,3}(?:,\d{3})+)";
const decimalPart = "(?:\.\d{1,2})?";
const symbol = "(?:\$|€|¥|₹)?";
const currencyCheckRegex = /^\s*(?:-|\+)?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d{1,2})?\s*(?:\$|€|¥|₹)?\s*$/u;
export default class CurrencyParser{
constructor(options){
this.options = options || defaultOptions;
}
parse(val){
if (typeof val === 'string' && val.length <= this.options.maxLength) {
if(val.indexOf(",,") !== -1 && val.indexOf(".." !== -1)){
const match = val.match(currencyCheckRegex);
if(match){
const locale = this.options.locale || localeMap[match[2]||match[5]||"₹"];
const formatter = new Intl.NumberFormat(locale)
val = val.replace(/[^0-9,.]/g, '').trim();
val = Number(val.replace(formatter.format(1000)[1], ''));
}
}
}
return val;
}
}
CurrencyParser.defaultOptions = defaultOptions;

View File

@@ -0,0 +1,13 @@
/**
*
* @param {array} val
* @param {string} by
* @returns
*/
export default function join(val, by=" "){
if(isArray(val)){
val.join(by)
}
return val;
}

View File

@@ -0,0 +1,14 @@
import toNumber from "strnum";
export default class numParser{
constructor(options){
this.options = options;
}
parse(val){
if (typeof val === 'string') {
val = toNumber(val,this.options);
}
return val;
}
}

View File

@@ -0,0 +1,6 @@
export default class trimmer{
parse(val){
if(typeof val === "string") return val.trim();
else return val;
}
}

425
node_modules/fast-xml-parser/src/validator.js generated vendored Normal file
View File

@@ -0,0 +1,425 @@
'use strict';
import { getAllMatches, isName } from './util.js';
const defaultOptions = {
allowBooleanAttributes: false, //A tag can have attributes without any value
unpairedTags: []
};
//const tagsPattern = new RegExp("<\\/?([\\w:\\-_\.]+)\\s*\/?>","g");
export 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 = getAllMatches(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 isName(attrName);
}
// const startsWithXML = /^xml/i;
function validateTagName(tagname) {
return isName(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;
}

View File

@@ -0,0 +1,6 @@
// Re-export from fast-xml-builder for backward compatibility
import XMLBuilder from 'fast-xml-builder';
export default XMLBuilder;
// If there are any named exports you also want to re-export:
export * from 'fast-xml-builder';

View File

@@ -0,0 +1,410 @@
import { isName } from '../util.js';
export default class DocTypeReader {
constructor(options) {
this.suppressValidationErr = !options;
this.options = options;
}
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;
let exp = "";
for (; i < xmlData.length; i++) {
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] = {
regx: RegExp(`&${escaped};`, "g"),
val: 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];
}
}
if (angleBracketsCount !== 0) {
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);
// 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);
// 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 && !isName(elementName)) {
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)
// 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)) {
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)) {
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) {
if (isName(name))
return name;
else
throw new Error(`Invalid entity name ${name}`);
}

View File

@@ -0,0 +1,159 @@
import { DANGEROUS_PROPERTY_NAMES, criticalProperties } from "../util.js";
const defaultOnDangerousProperty = (name) => {
if (DANGEROUS_PROPERTY_NAMES.includes(name)) {
return "__" + name;
}
return name;
};
export 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
},
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,
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 (DANGEROUS_PROPERTY_NAMES.some(dangerous => normalized === dangerous.toLowerCase())) {
throw new Error(
`[SECURITY] Invalid ${optionName}: "${propertyName}" is a reserved JavaScript keyword that could cause prototype pollution`
);
}
if (criticalProperties.some(dangerous => normalized === dangerous.toLowerCase())) {
throw new Error(
`[SECURITY] Invalid ${optionName}: "${propertyName}" is a reserved JavaScript keyword that could cause prototype pollution`
);
}
}
/**
* Normalizes processEntities option for backward compatibility
* @param {boolean|object} value
* @returns {object} Always returns normalized object
*/
function normalizeProcessEntities(value) {
// Boolean backward compatibility
if (typeof value === 'boolean') {
return {
enabled: value, // true or false
maxEntitySize: 10000,
maxExpansionDepth: 10,
maxTotalExpansions: 1000,
maxExpandedLength: 100000,
maxEntityCount: 100,
allowedTags: null,
tagFilter: null
};
}
// 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 ?? 10),
maxTotalExpansions: Math.max(1, value.maxTotalExpansions ?? 1000),
maxExpandedLength: Math.max(1, value.maxExpandedLength ?? 100000),
maxEntityCount: Math.max(1, value.maxEntityCount ?? 100),
allowedTags: value.allowedTags ?? null,
tagFilter: value.tagFilter ?? null
};
}
// Default to enabled with limits
return normalizeProcessEntities(true);
}
export const buildOptions = function (options) {
const built = Object.assign({}, defaultOptions, options);
// Validate property names to prevent prototype pollution
const propertyNameOptions = [
{ value: built.attributeNamePrefix, name: 'attributeNamePrefix' },
{ value: built.attributesGroupName, name: 'attributesGroupName' },
{ value: built.textNodeName, name: 'textNodeName' },
{ value: built.cdataPropName, name: 'cdataPropName' },
{ value: built.commentPropName, name: 'commentPropName' }
];
for (const { value, name } of propertyNameOptions) {
if (value) {
validatePropertyName(value, name);
}
}
if (built.onDangerousProperty === null) {
built.onDangerousProperty = defaultOnDangerousProperty;
}
// Always normalize processEntities for backward compatibility and validation
built.processEntities = normalizeProcessEntities(built.processEntities);
// Convert old-style stopNodes for backward compatibility
if (built.stopNodes && Array.isArray(built.stopNodes)) {
built.stopNodes = built.stopNodes.map(node => {
if (typeof node === 'string' && node.startsWith('*.')) {
// Old syntax: *.tagname meant "tagname anywhere"
// Convert to new syntax: ..tagname
return '..' + node.substring(2);
}
return node;
});
}
//console.debug(built.processEntities)
return built;
};

View File

@@ -0,0 +1,911 @@
'use strict';
///@ts-check
import { getAllMatches, isExist, DANGEROUS_PROPERTY_NAMES, criticalProperties } from '../util.js';
import xmlNode from './xmlNode.js';
import DocTypeReader from './DocTypeReader.js';
import toNumber from "strnum";
import getIgnoreAttributesFn from "../ignoreAttributes.js";
import { Expression, Matcher } from 'path-expression-matcher';
// 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;
}
export default class OrderedObjParser {
constructor(options) {
this.options = options;
this.currentNode = null;
this.tagsNodeStack = [];
this.docTypeEntities = {};
this.lastEntities = {
"apos": { regex: /&(apos|#39|#x27);/g, val: "'" },
"gt": { regex: /&(gt|#62|#x3E);/g, val: ">" },
"lt": { regex: /&(lt|#60|#x3C);/g, val: "<" },
"quot": { regex: /&(quot|#34|#x22);/g, val: "\"" },
};
this.ampEntity = { regex: /&(amp|#38|#x26);/g, val: "&" };
this.htmlEntities = {
"space": { regex: /&(nbsp|#160);/g, val: " " },
// "lt" : { regex: /&(lt|#60);/g, val: "<" },
// "gt" : { regex: /&(gt|#62);/g, val: ">" },
// "amp" : { regex: /&(amp|#38);/g, val: "&" },
// "quot" : { regex: /&(quot|#34);/g, val: "\"" },
// "apos" : { regex: /&(apos|#39);/g, val: "'" },
"cent": { regex: /&(cent|#162);/g, val: "¢" },
"pound": { regex: /&(pound|#163);/g, val: "£" },
"yen": { regex: /&(yen|#165);/g, val: "¥" },
"euro": { regex: /&(euro|#8364);/g, val: "€" },
"copyright": { regex: /&(copy|#169);/g, val: "©" },
"reg": { regex: /&(reg|#174);/g, val: "®" },
"inr": { regex: /&(inr|#8377);/g, val: "₹" },
"num_dec": { regex: /&#([0-9]{1,7});/g, val: (_, str) => fromCodePoint(str, 10, "&#") },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val: (_, str) => fromCodePoint(str, 16, "&#x") },
};
this.addExternalEntities = addExternalEntities;
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;
// Initialize path matcher for path-expression-matcher
this.matcher = new Matcher();
// Live read-only proxy of matcher — PEM creates and caches this internally.
// All user callbacks receive this instead of the mutable matcher.
this.readonlyMatcher = this.matcher.readOnly();
// Flag to track if current node is a stop node (optimization)
this.isCurrentNodeStopNode = false;
// Pre-compile stopNodes expressions
if (this.options.stopNodes && this.options.stopNodes.length > 0) {
this.stopNodeExpressions = [];
for (let i = 0; i < this.options.stopNodes.length; i++) {
const stopNodeExp = this.options.stopNodes[i];
if (typeof stopNodeExp === 'string') {
// Convert string to Expression object
this.stopNodeExpressions.push(new Expression(stopNodeExp));
} else if (stopNodeExp instanceof Expression) {
// Already an Expression object
this.stopNodeExpressions.push(stopNodeExp);
}
}
}
}
}
function addExternalEntities(externalEntities) {
const entKeys = Object.keys(externalEntities);
for (let i = 0; i < entKeys.length; i++) {
const ent = entKeys[i];
const escaped = ent.replace(/[.\-+*:]/g, '\\.');
this.lastEntities[ent] = {
regex: new RegExp("&" + escaped + ";", "g"),
val: externalEntities[ent]
}
}
}
/**
* @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) {
if (val !== undefined) {
if (this.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 = this.options.jPath ? jPath.toString() : jPath;
const newval = this.options.tagValueProcessor(tagName, val, jPathOrMatcher, hasAttributes, isLeafNode);
if (newval === null || newval === undefined) {
//don't parse
return val;
} else if (typeof newval !== typeof val || newval !== val) {
//overwrite
return newval;
} else if (this.options.trimValues) {
return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
} else {
const trimmedVal = val.trim();
if (trimmedVal === val) {
return parseValue(val, this.options.parseTagValue, this.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) {
if (this.options.ignoreAttributes !== true && typeof attrStr === 'string') {
// attrStr = attrStr.replace(/\r?\n/g, ' ');
//attrStr = attrStr || attrStr.trim();
const matches = getAllMatches(attrStr, attrsRegx);
const len = matches.length; //don't make it inline
const attrs = {};
// First pass: parse all attributes and update matcher with raw values
// This ensures the matcher has all attribute values when processors run
const rawAttrsForMatcher = {};
for (let i = 0; i < len; i++) {
const attrName = this.resolveNameSpace(matches[i][1]);
const oldVal = matches[i][4];
if (attrName.length && oldVal !== undefined) {
let parsedVal = oldVal;
if (this.options.trimValues) {
parsedVal = parsedVal.trim();
}
parsedVal = this.replaceEntitiesValue(parsedVal, tagName, this.readonlyMatcher);
rawAttrsForMatcher[attrName] = parsedVal;
}
}
// Update matcher with raw attribute values BEFORE running processors
if (Object.keys(rawAttrsForMatcher).length > 0 && typeof jPath === 'object' && jPath.updateCurrent) {
jPath.updateCurrent(rawAttrsForMatcher);
}
// Second pass: now process attributes with matcher having full attribute context
for (let i = 0; i < len; i++) {
const attrName = this.resolveNameSpace(matches[i][1]);
// Convert jPath to string if needed for ignoreAttributesFn
const jPathStr = this.options.jPath ? jPath.toString() : this.readonlyMatcher;
if (this.ignoreAttributesFn(attrName, jPathStr)) {
continue
}
let oldVal = matches[i][4];
let aName = this.options.attributeNamePrefix + attrName;
if (attrName.length) {
if (this.options.transformAttributeName) {
aName = this.options.transformAttributeName(aName);
}
//if (aName === "__proto__") aName = "#__proto__";
aName = sanitizeName(aName, this.options);
if (oldVal !== undefined) {
if (this.options.trimValues) {
oldVal = oldVal.trim();
}
oldVal = this.replaceEntitiesValue(oldVal, tagName, this.readonlyMatcher);
// Pass jPath string or readonlyMatcher based on options.jPath setting
const jPathOrMatcher = this.options.jPath ? jPath.toString() : this.readonlyMatcher;
const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPathOrMatcher);
if (newVal === null || newVal === undefined) {
//don't parse
attrs[aName] = oldVal;
} else if (typeof newVal !== typeof oldVal || newVal !== oldVal) {
//overwrite
attrs[aName] = newVal;
} else {
//parse
attrs[aName] = parseValue(
oldVal,
this.options.parseAttributeValue,
this.options.numberParseOptions
);
}
} else if (this.options.allowBooleanAttributes) {
attrs[aName] = true;
}
}
}
if (!Object.keys(attrs).length) {
return;
}
if (this.options.attributesGroupName) {
const attrCollection = {};
attrCollection[this.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();
// Reset entity expansion counters for this document
this.entityExpansionCount = 0;
this.currentExpandedLength = 0;
const docTypeReader = new DocTypeReader(this.options.processEntities);
for (let i = 0; i < xmlData.length; i++) {//for each char in XML data
const ch = xmlData[i];
if (ch === '<') {
// const nextIndex = i+1;
// const _2ndChar = xmlData[nextIndex];
if (xmlData[i + 1] === '/') {//Closing Tag
const closeIndex = findClosingIndex(xmlData, ">", i, "Closing Tag is not closed.")
let tagName = xmlData.substring(i + 2, closeIndex).trim();
if (this.options.removeNSPrefix) {
const colonIndex = tagName.indexOf(":");
if (colonIndex !== -1) {
tagName = tagName.substr(colonIndex + 1);
}
}
tagName = transformTagName(this.options.transformTagName, tagName, "", this.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 && this.options.unpairedTags.indexOf(tagName) !== -1) {
throw new Error(`Unpaired tag can not be used as closing tag: </${tagName}>`);
}
if (lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1) {
// 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
currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
textData = "";
i = closeIndex;
} else if (xmlData[i + 1] === '?') {
let tagData = readTagExp(xmlData, i, false, "?>");
if (!tagData) throw new Error("Pi Tag is not closed.");
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
if ((this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags) {
//do nothing
} else {
const childNode = new xmlNode(tagData.tagName);
childNode.add(this.options.textNodeName, "");
if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent) {
childNode[":@"] = this.buildAttributesMap(tagData.tagExp, this.matcher, tagData.tagName);
}
this.addChild(currentNode, childNode, this.readonlyMatcher, i);
}
i = tagData.closeIndex + 1;
} else if (xmlData.substr(i + 1, 3) === '!--') {
const endIndex = findClosingIndex(xmlData, "-->", i + 4, "Comment is not closed.")
if (this.options.commentPropName) {
const comment = xmlData.substring(i + 4, endIndex - 2);
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
currentNode.add(this.options.commentPropName, [{ [this.options.textNodeName]: comment }]);
}
i = endIndex;
} else if (xmlData.substr(i + 1, 2) === '!D') {
const result = docTypeReader.readDocType(xmlData, i);
this.docTypeEntities = result.entities;
i = result.i;
} else if (xmlData.substr(i + 1, 2) === '![') {
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 (this.options.cdataPropName) {
currentNode.add(this.options.cdataPropName, [{ [this.options.textNodeName]: tagExp }]);
} else {
currentNode.add(this.options.textNodeName, val);
}
i = closeIndex + 2;
} else {//Opening tag
let result = readTagExp(xmlData, i, this.options.removeNSPrefix);
// Safety check: readTagExp can return undefined
if (!result) {
// Log context for debugging
const context = xmlData.substring(Math.max(0, i - 50), Math.min(xmlData.length, i + 50));
throw new Error(`readTagExp returned undefined at position ${i}. Context: "${context}"`);
}
let tagName = result.tagName;
const rawTagName = result.rawTagName;
let tagExp = result.tagExp;
let attrExpPresent = result.attrExpPresent;
let closeIndex = result.closeIndex;
({ tagName, tagExp } = transformTagName(this.options.transformTagName, tagName, tagExp, this.options));
if (this.options.strictReservedNames &&
(tagName === this.options.commentPropName
|| tagName === this.options.cdataPropName
|| tagName === this.options.textNodeName
|| tagName === this.options.attributesGroupName
)) {
throw new Error(`Invalid tag name: ${tagName}`);
}
//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 && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1) {
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
rawAttrs = extractRawAttributes(prefixedAttrs, this.options);
}
}
// Now check if this is a stop node (after attributes are set)
if (tagName !== xmlObj.tagname) {
this.isCurrentNodeStopNode = this.isItStopNode(this.stopNodeExpressions, this.matcher);
}
const startIndex = i;
if (this.isCurrentNodeStopNode) {
let tagContent = "";
// For self-closing tags, content is empty
if (isSelfClosing) {
i = result.closeIndex;
}
//unpaired tag
else if (this.options.unpairedTags.indexOf(tagName) !== -1) {
i = result.closeIndex;
}
//normal tag
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(this.options.textNodeName, tagContent);
this.matcher.pop(); // Pop the stop node tag
this.isCurrentNodeStopNode = false; // Reset flag
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
} else {
//selfClosing tag
if (isSelfClosing) {
({ tagName, tagExp } = transformTagName(this.options.transformTagName, tagName, tagExp, this.options));
const childNode = new xmlNode(tagName);
if (prefixedAttrs) {
childNode[":@"] = prefixedAttrs;
}
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
this.matcher.pop(); // Pop self-closing tag
this.isCurrentNodeStopNode = false; // Reset flag
}
else if (this.options.unpairedTags.indexOf(tagName) !== -1) {//unpaired tag
const childNode = new xmlNode(tagName);
if (prefixedAttrs) {
childNode[":@"] = prefixedAttrs;
}
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
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 > this.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
}
}
// Replace DOCTYPE entities
for (const entityName of Object.keys(this.docTypeEntities)) {
const entity = this.docTypeEntities[entityName];
const matches = val.match(entity.regx);
if (matches) {
// Track expansions
this.entityExpansionCount += matches.length;
// Check expansion limit
if (entityConfig.maxTotalExpansions &&
this.entityExpansionCount > entityConfig.maxTotalExpansions) {
throw new Error(
`Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
);
}
// Store length before replacement
const lengthBefore = val.length;
val = val.replace(entity.regx, entity.val);
// Check expanded length immediately after replacement
if (entityConfig.maxExpandedLength) {
this.currentExpandedLength += (val.length - lengthBefore);
if (this.currentExpandedLength > entityConfig.maxExpandedLength) {
throw new Error(
`Total expanded content size exceeded: ${this.currentExpandedLength} > ${entityConfig.maxExpandedLength}`
);
}
}
}
}
// Replace standard entities
for (const entityName of Object.keys(this.lastEntities)) {
const entity = this.lastEntities[entityName];
const matches = val.match(entity.regex);
if (matches) {
this.entityExpansionCount += matches.length;
if (entityConfig.maxTotalExpansions &&
this.entityExpansionCount > entityConfig.maxTotalExpansions) {
throw new Error(
`Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
);
}
}
val = val.replace(entity.regex, entity.val);
}
if (val.indexOf('&') === -1) return val;
// Replace HTML entities if enabled
if (this.options.htmlEntities) {
for (const entityName of Object.keys(this.htmlEntities)) {
const entity = this.htmlEntities[entityName];
const matches = val.match(entity.regex);
if (matches) {
//console.log(matches);
this.entityExpansionCount += matches.length;
if (entityConfig.maxTotalExpansions &&
this.entityExpansionCount > entityConfig.maxTotalExpansions) {
throw new Error(
`Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
);
}
}
val = val.replace(entity.regex, entity.val);
}
}
// Replace ampersand entity last
val = val.replace(this.ampEntity.regex, this.ampEntity.val);
return 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;
}
//TODO: use jPath to simplify the logic
/**
* @param {Array<Expression>} stopNodeExpressions - Array of compiled Expression objects
* @param {Matcher} matcher - Current path matcher
*/
function isItStopNode(stopNodeExpressions, matcher) {
if (!stopNodeExpressions || stopNodeExpressions.length === 0) return false;
for (let i = 0; i < stopNodeExpressions.length; i++) {
if (matcher.matches(stopNodeExpressions[i])) {
return true;
}
}
return false;
}
/**
* 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 = ">") {
let attrBoundary;
let tagExp = "";
for (let index = i; index < xmlData.length; index++) {
let ch = xmlData[index];
if (attrBoundary) {
if (ch === attrBoundary) attrBoundary = "";//reset
} else if (ch === '"' || ch === "'") {
attrBoundary = ch;
} else if (ch === closingChar[0]) {
if (closingChar[1]) {
if (xmlData[index + 1] === closingChar[1]) {
return {
data: tagExp,
index: index
}
}
} else {
return {
data: tagExp,
index: index
}
}
} else if (ch === '\t') {
ch = " "
}
tagExp += ch;
}
}
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 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;
for (; i < xmlData.length; i++) {
if (xmlData[i] === "<") {
if (xmlData[i + 1] === "/") {//close tag
const closeIndex = findClosingIndex(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 (xmlData[i + 1] === '?') {
const closeIndex = findClosingIndex(xmlData, "?>", i + 1, "StopNode is not closed.")
i = closeIndex;
} else if (xmlData.substr(i + 1, 3) === '!--') {
const closeIndex = findClosingIndex(xmlData, "-->", i + 3, "StopNode is not closed.")
i = closeIndex;
} else if (xmlData.substr(i + 1, 2) === '![') {
const closeIndex = findClosingIndex(xmlData, "]]>", i, "StopNode is not closed.") - 2;
i = closeIndex;
} else {
const tagData = readTagExp(xmlData, i, '>')
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 (isExist(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 (criticalProperties.includes(name)) {
throw new Error(`[SECURITY] Invalid name: "${name}" is a reserved JavaScript keyword that could cause prototype pollution`);
} else if (DANGEROUS_PROPERTY_NAMES.includes(name)) {
return options.onDangerousProperty(name);
}
return name;
}

View File

@@ -0,0 +1,71 @@
import { buildOptions } from './OptionsBuilder.js';
import OrderedObjParser from './OrderedObjParser.js';
import prettify from './node2json.js';
import { validate } from "../validator.js";
import XmlNode from './xmlNode.js';
export default 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 = validate(xmlData, validationOption);
if (result !== true) {
throw Error(`${result.err.msg}:${result.err.line}:${result.err.col}`)
}
}
const orderedObjParser = new OrderedObjParser(this.options);
orderedObjParser.addExternalEntities(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 '&#xD;'")
} 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();
}
}

173
node_modules/fast-xml-parser/src/xmlparser/node2json.js generated vendored Normal file
View File

@@ -0,0 +1,173 @@
'use strict';
import XmlNode from './xmlNode.js';
import { Matcher } from 'path-expression-matcher';
const METADATA_SYMBOL = XmlNode.getMetaDataSymbol();
/**
* Helper function to strip attribute prefix from attribute map
* @param {object} attrs - Attributes with prefix (e.g., {"@_class": "code"})
* @param {string} prefix - Attribute prefix to remove (e.g., "@_")
* @returns {object} Attributes without prefix (e.g., {"class": "code"})
*/
function stripAttributePrefix(attrs, prefix) {
if (!attrs || typeof attrs !== 'object') return {};
if (!prefix) return attrs;
const rawAttrs = {};
for (const key in attrs) {
if (key.startsWith(prefix)) {
const rawName = key.substring(prefix.length);
rawAttrs[rawName] = attrs[key];
} else {
// Attribute without prefix (shouldn't normally happen, but be safe)
rawAttrs[key] = attrs[key];
}
}
return rawAttrs;
}
/**
*
* @param {array} node
* @param {any} options
* @param {Matcher} matcher - Path matcher instance
* @returns
*/
export default 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 (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[METADATA_SYMBOL] !== undefined && typeof val === "object" && val !== null) {
val[METADATA_SYMBOL] = tagObj[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;
}

40
node_modules/fast-xml-parser/src/xmlparser/xmlNode.js generated vendored Normal file
View File

@@ -0,0 +1,40 @@
'use strict';
let METADATA_SYMBOL;
if (typeof Symbol !== "function") {
METADATA_SYMBOL = "@@xmlMetadata";
} else {
METADATA_SYMBOL = Symbol("XML Node Metadata");
}
export default 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
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 };
}
}
/** symbol used for metadata */
static getMetaDataSymbol() {
return METADATA_SYMBOL;
}
}