update, text, response

This commit is contained in:
2025-11-02 11:09:14 +01:00
parent 14776c86b0
commit eed8a4ddcf
2794 changed files with 156786 additions and 129204 deletions

View File

@@ -1,186 +1,231 @@
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: If you find missing information about particular minor version, that version must have been changed without any functional change in this library.</small>
**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))
- 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))
- 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
- change code implementation
**4.2.4 / 2023-06-06**
* fix security bug
- fix security bug
**4.2.3 / 2023-06-05**
* fix security bug
- 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
- 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
- fix: jpath after unpaired tags
**4.2.0 / 2023-04-09**
* support `updateTag` parser property
- 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
- 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
- fix #546: Support complex entity value
**4.1.2 / 2023-02-12**
* Security Fix
- Security Fix
**4.1.1 / 2023-02-03**
* Fix #540: ignoreAttributes breaks unpairedTags
* Refactor XML builder code
- 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
- 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
- make "eNotation" optional
**4.0.14 / 2023-01-22**
* fixed: add missed typing "eNotation" to parse values
- 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))
- 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
- fix typescript
**4.0.11 / 2022-10-05**
* fix #501: parse for entities only once
- 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
- 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))
- 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
- 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
- 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
- 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
- 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
- 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))
- 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 `&`
- 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
- 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.
- 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
- 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
- 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`.
- 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"
- 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
- 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
- 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
- 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
- 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
- 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) )
- 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
- 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",
@@ -191,371 +236,371 @@ Note: If you find missing information about particular minor version, that versi
```
3.20.1 / 2021-09-25
* update strnum package
- 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.
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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.
- 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)
- 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
- remove configstore dependency
3.12.17 / 2019-07-14
* Fix #167: '>' in attribute value
- Fix #167: '>' in attribute value
3.12.16 / 2019-03-23
* Support a new option "stopNodes". (#150)
- 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
- Don't show post-install message
3.12.12 / 2019-01-11
* fix : IE parseInt, parseFloat error
- 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
- 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'
- 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
- fix #128 : use 'attrValueProcessor' to process attribute value in json2xml parser
3.12.6 / 2018-11-10
* Fix #126: check for type
- Fix #126: check for type
3.12.4 / 2018-09-12
* Fix: include tasks in npm package
- Fix: include tasks in npm package
3.12.3 / 2018-09-12
* Fix CLI issue raised in last PR
- 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)
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- fix #74 : TS2314 TypeScript compiler error
3.3.8 / 2018-04-17
* fix #73 : IE doesn't support Object.assign
- 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
- 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
- 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
- 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
- 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.
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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.
- 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
- 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
- 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
- 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
- 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
- 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
- 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.
- Rewrite the validator to handle large files.
Ignore DOCTYPE validation.
* Fix: When attribute value has equal sign
- Fix: When attribute value has equal sign
2.8.3 / 2017-12-15
* Fix: when a tag has value along with subtags
- Fix: when a tag has value along with subtags
2.8.2 / 2017-12-04
* Fix value parsing for IE
- Fix value parsing for IE
2.8.1 / 2017-12-01
* fix: validator should return false instead of err when invalid XML
- 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
- 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
- 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
- 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
- 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
- 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
- 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.
- 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
- 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
- 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
- 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"
- 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
- 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
- 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
- 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
- Bug fix: CLI default option

View File

@@ -1,35 +1,39 @@
# [fast-xml-parser](https://www.npmjs.com/package/fast-xml-parser)
[![Known Vulnerabilities](https://snyk.io/test/github/naturalintelligence/fast-xml-parser/badge.svg)](https://snyk.io/test/github/naturalintelligence/fast-xml-parser)
[![NPM quality][quality-image]][quality-url]
[![Coverage Status](https://coveralls.io/repos/github/NaturalIntelligence/fast-xml-parser/badge.svg?branch=master)](https://coveralls.io/github/NaturalIntelligence/fast-xml-parser?branch=master)
[<img src="https://img.shields.io/badge/Try-me-blue.svg?colorA=FFA500&colorB=0000FF" alt="Try me"/>](https://naturalintelligence.github.io/fast-xml-parser/)
[![NPM total downloads](https://img.shields.io/npm/dt/fast-xml-parser.svg)](https://npm.im/fast-xml-parser)
[quality-image]: http://npm.packagequality.com/shield/fast-xml-parser.svg?style=flat-square
[quality-url]: http://packagequality.com/#?package=fast-xml-parser
Validate XML, Parse XML to JS Object, or Build XML from JS Object without C/C++ based libraries and no callback.
<font size="6">I need a Career advice. I've posted the query on my <a href="github.com/amitguptagwl">profile</a>. Your support would be appreciable.</font>
---
<small>Checkout our new library [Text2Chart](https://solothought.com/text2chart/flow) that constructs flow chart out of simple text. Very helpful in creating or alayzing an algorithm, and documentation purpose</small>
<br>
Sponsor this project
Sponsor this project 👉
<a href="https://github.com/sponsors/NaturalIntelligence">
<img src="https://raw.githubusercontent.com/NaturalIntelligence/ThankYouBackers/main/github_sponsor.png" width="200" />
<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=200 />
<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="Stubmatic donate button" width="200"/></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/getsentry" target="_blank"><img src="https://user-images.githubusercontent.com/7692328/204701653-ce369b29-6fdb-48f2-9848-593d04628a2b.png" width="60px"></a>
<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
@@ -43,8 +47,12 @@ Through OpenCollective
<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)
- <a href="https://github.com/cocopon" target="_blank">Hiroki Kokubun</a>
> 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>
@@ -61,14 +69,15 @@ Through OpenCollective
<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)
Check the bigger [list](./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>
<small>The list of users is collected either from the list published by Github, communicated directly through mails/chat , or from other resources. If you feel that your name in the above list is incorrectly published or you're not the user of this library anymore then you can inform us to remove it. We'll do the necessary changes ASAP.</small>
If you want to be an anonymous user of this application and don't want to be highlighted anywhere then you can contact me at
- githubissues@proton.me
- https://linkedin.com/in/amitguptagwl/
---
## Main Features
@@ -77,7 +86,7 @@ If you want to be an anonymous user of this application and don't want to be hig
* Validate XML data syntactically
* Parse XML to JS Object
* Build XML from JS Object
* Works with node packages, in browser, and in CLI (press try me button above for demo)
* Compatible to node js packages, in browser, and in CLI (click try me button above for demo)
* Faster than any other pure JS implementation.
* It can handle big files (tested up to 100mb).
* Controlled parsing using various options
@@ -90,6 +99,11 @@ If you want to be an anonymous user of this application and don't want to be hig
* Supports parsing of PI (Processing Instruction) tags with XML declaration tags
* And many more other features.
## v5
I developed v5 in Apr 2023. And I didn't get the chance to complete all the features. I've ensured that new features don't impact performance. With v5, you have more control on parsing output. Check [docs](./docs/v5) for syntax help and basic understanding.
Please leave a comment in discussion forum for your suggestions and if you really need v5.
## How to use
To use as package dependency
@@ -130,62 +144,71 @@ In a HTML page
</script>
```
Check lib folder for different browser bundles
Bundle size
| Bundle Name | Size |
| ------------------ | ---- |
| fxbuilder.min.js | 5.2K |
| fxparser.js | 50K |
| fxparser.min.js | 17K |
| fxp.min.js | 22K |
| fxbuilder.min.js | 6.5K |
| fxparser.min.js | 20K |
| fxp.min.js | 26K |
| fxvalidator.min.js | 5.7K |
### Documents
**v3**
* [documents](./docs/v3/docs.md)
<table>
<tr><td>v3</td><td>v4</td><td>v5</td></tr>
<tr>
<td>
<a href="./docs/v3/docs.md">documents</a>
</td>
<td><ol>
<li><a href="./docs/v4/1.GettingStarted.md">Getting Started</a></li>
<li><a href="./docs/v4/2.XMLparseOptions.md">XML Parser</a></li>
<li><a href="./docs/v4/3.XMLBuilder.md">XML Builder</a></li>
<li><a href="./docs/v4/4.XMLValidator.md">XML Validator</a></li>
<li><a href="./docs/v4/5.Entities.md">Entities</a></li>
<li><a href="./docs/v4/6.HTMLParsing.md">HTML Document Parsing</a></li>
<li><a href="./docs/v4/7.PITags.md">PI Tag processing</a></li>
</ol></td>
<td><ol>
<li></li><a href="./docs/v5/1.GettingStarted.md">Getting Started</a></li>
<li><a href="./docs/v5/2.Features.md">Features</a></li>
<li><a href="./docs/v5/3.Options.md">Options</a></li>
<li><a href="./docs/v5/4.OutputBuilders.md">Output Builders</a></li>
<li><a href="./docs/v5/5.ValueParsers.md">Value Parsers</a></li>
</ol></td>
</tr>
</table>
**note**: version 5 is released with version 4 tfor experimental use. Based on it's demand, it'll be developed and the features can be different in final release.
**v4**
1. [GettingStarted.md](./docs/v4/1.GettingStarted.md)
2. [XML Parser](./docs/v4/2.XMLparseOptions.md)
3. [XML Builder](./docs/v4/3.XMLBuilder.md)
4. [XML Validator](./docs/v4/4.XMLValidator.md)
5. [Entities](./docs/v4/5.Entities.md)
6. [HTML Document Parsing](./docs/v4/6.HTMLParsing.md)
7. [PI Tag processing](./docs/v4/7.PITags.md)
## Performance
<small>negative means error</small>
### XML Parser
![](./docs/imgs/XMLParser_v4.png)
<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
**Large files**
![](./docs/imgs/XMLParser_large_v4.png)
* Y-axis: requests per second
* X-axis: File size
### XML Builder
![](./docs/imgs/XMLBuilder_v4.png)
<img src="./docs/imgs/XMLBuilder_v4.png" width="50%" />
* Y-axis: requests per second
<small>negative means error</small>
[![](static/img/ni_ads_ads.gif)](https://github.com/NaturalIntelligence/ads/)
## Our other projects and research you must try
<!-- [![](static/img/ni_ads_ads.gif)](https://github.com/NaturalIntelligence/ads/) -->
* **[BigBit standard](https://github.com/amitguptagwl/bigbit)** :
* Single text encoding to replace UTF-8, UTF-16, UTF-32 and more with less memory.
* Single Numeric datatype alternative of integer, float, double, long, decimal and more without precision loss.
* **[Cytorus](https://github.com/NaturalIntelligence/cytorus)**: Be specific and flexible while running E2E tests.
* Run tests only for a particular User Story
* Run tests for a route or from a route
* Customizable reporting
* Central dashboard for better monitoring
* Options to integrate E2E tests with Jira, Github etc using Central dashboard `Tian`.
* **[Stubmatic](https://github.com/NaturalIntelligence/Stubmatic)** : Create fake webservices, DynamoDB or S3 servers, Manage fake/mock stub data, Or fake any HTTP(s) call.
## 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

View File

@@ -1,10 +1,11 @@
{
"name": "fast-xml-parser",
"version": "4.2.7",
"version": "4.5.3",
"description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
"main": "./src/fxp.js",
"scripts": {
"test": "nyc --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",
@@ -39,33 +40,33 @@
"json2xml",
"html"
],
"author": "Amit Gupta (https://amitguptagwl.github.io)",
"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",
"cytorus": "^0.2.9",
"eslint": "^8.3.0",
"he": "^1.2.0",
"jasmine": "^3.6.4",
"nyc": "^15.1.0",
"prettier": "^1.19.1",
"publish-please": "^5.5.2",
"typescript": "5",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1"
},
"typings": "src/fxp.d.ts",
"funding": [{
"type": "paypal",
"url": "https://paypal.me/naturalintelligence"
},{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}],
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"dependencies": {
"strnum": "^1.0.5"
"strnum": "^1.1.1"
}
}

View File

@@ -57,7 +57,7 @@ if (process.argv[2] === '--help' || process.argv[2] === '-h') {
if (outputFileName) {
writeToFile(outputFileName, output);
} else {
console.log(output);
console.log(JSON.stringify(output, null, 2));
}
};

View File

@@ -1,81 +1,391 @@
type X2jOptions = {
preserveOrder: boolean;
attributeNamePrefix: string;
attributesGroupName: false | string;
textNodeName: string;
ignoreAttributes: boolean;
removeNSPrefix: boolean;
allowBooleanAttributes: boolean;
parseTagValue: boolean;
parseAttributeValue: boolean;
trimValues: boolean;
cdataPropName: false | string;
commentPropName: false | string;
/**
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. <br>
2. Same value to set parsed value if `parseTagValue: true`.
/**
* Preserve the order of tags in resulting JS object
*
* Defaults to `false`
*/
tagValueProcessor: (tagName: string, tagValue: string, jPath: string, hasAttributes: boolean, isLeafNode: boolean) => unknown;
attributeValueProcessor: (attrName: string, attrValue: string, jPath: string) => unknown;
numberParseOptions: strnumOptions;
stopNodes: string[];
unpairedTags: string[];
alwaysCreateTextNode: boolean;
isArray: (tagName: string, jPath: string, isLeafNode: boolean, isAttribute: boolean) => boolean;
processEntities: boolean;
htmlEntities: boolean;
ignoreDeclaration: boolean;
ignorePiTags: boolean;
transformTagName: ((tagName: string) => string) | 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.
preserveOrder?: boolean;
@returns {string} new tag name.
@returns false to skip the tag
/**
* 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`
*/
updateTag: (tagName: string, jPath: string, attrs: {[k: string]: string}) => string | boolean;
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, jPath: string) => 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 tag 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
*
* @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?: (tagName: string, tagValue: string, jPath: string, hasAttributes: boolean, isLeafNode: boolean) => 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?: (attrName: string, attrValue: string, jPath: string) => unknown;
/**
* Options to pass to `strnum` for parsing numbers
*
* Defaults to `{ hex: true, leadingZeros: true, eNotation: true }`
*/
numberParseOptions?: strnumOptions;
/**
* Nodes to stop parsing at
*
* Defaults to `[]`
*/
stopNodes?: string[];
/**
* 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
* @param jPath
* @param isLeafNode
* @param isAttribute
* @returns {boolean}
*
* Defaults to `() => false`
*/
isArray?: (tagName: string, jPath: string, isLeafNode: boolean, isAttribute: boolean) => boolean;
/**
* Whether to process default and DOCTYPE entities
*
* Defaults to `true`
*/
processEntities?: boolean;
/**
* 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.
*
* @returns {string} new tag name.
* @returns false to skip the tag
*
* Defaults to `(tagName, jPath, attrs) => tagName`
*/
updateTag?: (tagName: string, jPath: string, attrs: {[k: string]: string}) => string | boolean;
};
type strnumOptions = {
hex: boolean;
leadingZeros: boolean,
skipLike?: RegExp,
eNotation?: boolean
}
type X2jOptionsOptional = Partial<X2jOptions>;
type validationOptions = {
allowBooleanAttributes: boolean;
unpairedTags: string[];
/**
* Whether to allow attributes without value
*
* Defaults to `false`
*/
allowBooleanAttributes?: boolean;
/**
* List of tags without closing tags
*
* Defaults to `[]`
*/
unpairedTags?: string[];
};
type validationOptionsOptional = Partial<validationOptions>;
type XmlBuilderOptions = {
attributeNamePrefix: string;
attributesGroupName: false | string;
textNodeName: string;
ignoreAttributes: boolean;
cdataPropName: false | string;
commentPropName: false | string;
format: boolean;
indentBy: string;
arrayNodeName: string;
suppressEmptyNode: boolean;
suppressUnpairedNode: boolean;
suppressBooleanAttributes: boolean;
preserveOrder: boolean;
unpairedTags: string[];
stopNodes: string[];
tagValueProcessor: (name: string, value: unknown) => string;
attributeValueProcessor: (name: string, value: unknown) => string;
processEntities: boolean;
oneListGroup: 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 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
*
* Defaults to `[]`
*/
stopNodes?: string[];
/**
* 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;
};
type XmlBuilderOptionsOptional = Partial<XmlBuilderOptions>;
type ESchema = string | object | Array<string|object>;
@@ -89,20 +399,20 @@ type ValidationError = {
};
export class XMLParser {
constructor(options?: X2jOptionsOptional);
parse(xmlData: string | Buffer ,validationOptions?: validationOptionsOptional | boolean): any;
constructor(options?: X2jOptions);
parse(xmlData: string | Buffer ,validationOptions?: validationOptions | boolean): any;
/**
* Add Entity which is not by default supported by this library
* @param entityIndentifier {string} Eg: 'ent' for &ent;
* @param entityIdentifier {string} Eg: 'ent' for &ent;
* @param entityValue {string} Eg: '\r'
*/
addEntity(entityIndentifier: string, entityValue: string): void;
addEntity(entityIdentifier: string, entityValue: string): void;
}
export class XMLValidator{
static validate( xmlData: string, options?: validationOptionsOptional): true | ValidationError;
static validate( xmlData: string, options?: validationOptions): true | ValidationError;
}
export class XMLBuilder {
constructor(options?: XmlBuilderOptionsOptional);
constructor(options?: XmlBuilderOptions);
build(jObj: any): any;
}

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

@@ -0,0 +1,20 @@
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
}
module.exports = getIgnoreAttributesFn

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

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

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

@@ -0,0 +1,107 @@
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.fromCharCode(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 16)) },
};
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;
}
module.exports = EntitiesParser;

64
node_modules/fast-xml-parser/src/v5/OptionsBuilder.js generated vendored Executable file
View File

@@ -0,0 +1,64 @@
const JsObjOutputBuilder = require("./OutputBuilders/JsObjBuilder");
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(),
};
const buildOptions = function(options) {
const finalOptions = { ... defaultOptions};
copyProperties(finalOptions,options)
return finalOptions;
};
function copyProperties(target, source) {
for (let key in source) {
if (source.hasOwnProperty(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];
}
}
}
}
exports.buildOptions = buildOptions;
exports.defaultOptions = defaultOptions;

View File

@@ -0,0 +1,71 @@
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 = {}
}
}
module.exports = BaseOutputBuilder;

View File

@@ -0,0 +1,103 @@
const {buildOptions,registerCommonValueParsers} = require("./ParserOptionsBuilder");
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';
const BaseOutputBuilder = require("./BaseOutputBuilder");
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,102 @@
const {buildOptions,registerCommonValueParsers} = require("./ParserOptionsBuilder");
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);
}
}
const BaseOutputBuilder = require("./BaseOutputBuilder");
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];
}
}
module.exports = OutputBuilder;

View File

@@ -0,0 +1,156 @@
const {buildOptions,registerCommonValueParsers} = require("./ParserOptionsBuilder");
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);
}
}
const BaseOutputBuilder = require("./BaseOutputBuilder");
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;
}
module.exports = OutputBuilder;

View File

@@ -0,0 +1,99 @@
const trimParser = require("../valueParsers/trim")
const booleanParser = require("../valueParsers/booleanParser")
const currencyParser = require("../valueParsers/currency")
const numberParser = require("../valueParsers/number")
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"*/]
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 (source.hasOwnProperty(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];
}
}
}
}
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,
}
}
module.exports = {
buildOptions : buildOptions,
registerCommonValueParsers: registerCommonValueParsers
}

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

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

@@ -0,0 +1,81 @@
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);

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

@@ -0,0 +1,15 @@
const TagPath = require("./TagPath");
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);
}
}
module.exports = TagPathMatcher;

85
node_modules/fast-xml-parser/src/v5/XMLParser.js generated vendored Executable file
View File

@@ -0,0 +1,85 @@
const { buildOptions} = require("./OptionsBuilder");
const Xml2JsParser = require("./Xml2JsParser");
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;
}
module.exports = XMLParser;

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

@@ -0,0 +1,237 @@
const StringSource = require("./inputSource/StringSource");
const BufferSource = require("./inputSource/BufferSource");
const {readTagExp,readClosingTagName} = require("./XmlPartReader");
const {readComment, readCdata,readDocType,readPiTag} = require("./XmlSpecialTagsReader");
const TagPath = require("./TagPath");
const TagPathMatcher = require("./TagPathMatcher");
const EntitiesParser = require('./EntitiesParser');
//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 = {};
}
}
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;
}
module.exports = Xml2JsParser;

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

@@ -0,0 +1,216 @@
'use strict';
/**
* find paired tag for a stop node
* @param {string} xmlDoc
* @param {string} tagName
* @param {number} i : start index
*/
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
*/
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
*/
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)
}
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;
};
module.exports = {
readStopNode: readStopNode,
readClosingTagName: readClosingTagName,
readTagExp: readTagExp,
readPiExp: readPiExp,
}

View File

@@ -0,0 +1,118 @@
const {readPiExp} = require("./XmlPartReader");
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);
}
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);
}
}
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
}
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;
}
}
}
module.exports = {
readCdata: readCdata,
readComment:readComment,
readDocType:readDocType,
readPiTag:readPiTag
}

View File

@@ -0,0 +1,118 @@
const Constants = {
space: 32,
tab: 9
}
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;
}
}
module.exports = BufferSource;

View File

@@ -0,0 +1,123 @@
const whiteSpaces = [" ", "\n", "\t"];
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;
}
}
module.exports = StringSource;

View File

@@ -0,0 +1,107 @@
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.fromCharCode(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 16)) },
};
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;
}
module.exports = EntitiesParser;

View File

@@ -0,0 +1,23 @@
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;
}
}
module.exports = boolParser;

View File

@@ -0,0 +1,20 @@
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;
}
module.exports = boolParserExt;

View File

@@ -0,0 +1,40 @@
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;
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;
module.exports = CurrencyParser;

View File

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

View File

@@ -0,0 +1,16 @@
const toNumber = require("strnum");
class numParser{
constructor(options){
this.options = options;
}
parse(val){
if (typeof val === 'string') {
val = toNumber(val,this.options);
}
return val;
}
}
module.exports = numParser;

View File

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

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",
}

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

@@ -0,0 +1,104 @@
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.fromCharCode(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCharCode(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;
}

61
node_modules/fast-xml-parser/src/v6/OptionsBuilder.js generated vendored Executable 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 (source.hasOwnProperty(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";
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";
import booleanParser from "../valueParsers/booleanParser";
import currencyParser from "../valueParsers/currency";
import numberParser from "../valueParsers/number";
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 (source.hasOwnProperty(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 Executable 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.fromCharCode(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCharCode(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;
}
}

View File

@@ -103,6 +103,8 @@ exports.validate = function (xmlData, options) {
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) {

View File

@@ -1,6 +1,7 @@
'use strict';
//parse Empty Node as self closing node
const buildFromOrderedJs = require('./orderedJs2Xml');
const getIgnoreAttributesFn = require('../ignoreAttributes')
const defaultOptions = {
attributeNamePrefix: '@_',
@@ -38,11 +39,12 @@ const defaultOptions = {
function Builder(options) {
this.options = Object.assign({}, defaultOptions, options);
if (this.options.ignoreAttributes || this.options.attributesGroupName) {
if (this.options.ignoreAttributes === true || this.options.attributesGroupName) {
this.isAttribute = function(/*a*/) {
return false;
};
} else {
this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
this.attrPrefixLen = this.options.attributeNamePrefix.length;
this.isAttribute = isAttribute;
}
@@ -71,14 +73,16 @@ Builder.prototype.build = function(jObj) {
[this.options.arrayNodeName] : jObj
}
}
return this.j2x(jObj, 0).val;
return this.j2x(jObj, 0, []).val;
}
};
Builder.prototype.j2x = function(jObj, level) {
Builder.prototype.j2x = function(jObj, level, ajPath) {
let attrStr = '';
let val = '';
const jPath = ajPath.join('.')
for (let key in jObj) {
if(!Object.prototype.hasOwnProperty.call(jObj, key)) continue;
if (typeof jObj[key] === 'undefined') {
// supress undefined node only if it is not an attribute
if (this.isAttribute(key)) {
@@ -88,6 +92,8 @@ Builder.prototype.j2x = function(jObj, level) {
// null attribute should be ignored by the attribute list, but should not cause the tag closing
if (this.isAttribute(key)) {
val += '';
} else if (key === this.options.cdataPropName) {
val += '';
} else if (key[0] === '?') {
val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
} else {
@@ -99,9 +105,9 @@ Builder.prototype.j2x = function(jObj, level) {
} else if (typeof jObj[key] !== 'object') {
//premitive type
const attr = this.isAttribute(key);
if (attr) {
if (attr && !this.ignoreAttributesFn(attr, jPath)) {
attrStr += this.buildAttrPairStr(attr, '' + jObj[key]);
}else {
} else if (!attr) {
//tag value
if (key === this.options.textNodeName) {
let newval = this.options.tagValueProcessor(key, '' + jObj[key]);
@@ -114,6 +120,7 @@ Builder.prototype.j2x = function(jObj, level) {
//repeated nodes
const arrLen = jObj[key].length;
let listTagVal = "";
let listTagAttr = "";
for (let j = 0; j < arrLen; j++) {
const item = jObj[key][j];
if (typeof item === 'undefined') {
@@ -123,17 +130,27 @@ Builder.prototype.j2x = function(jObj, level) {
else val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
// val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
} else if (typeof item === 'object') {
if(this.options.oneListGroup ){
listTagVal += this.j2x(item, level + 1).val;
if(this.options.oneListGroup){
const result = this.j2x(item, level + 1, ajPath.concat(key));
listTagVal += result.val;
if (this.options.attributesGroupName && item.hasOwnProperty(this.options.attributesGroupName)) {
listTagAttr += result.attrStr
}
}else{
listTagVal += this.processTextOrObjNode(item, key, level)
listTagVal += this.processTextOrObjNode(item, key, level, ajPath)
}
} else {
listTagVal += this.buildTextValNode(item, key, '', level);
if (this.options.oneListGroup) {
let textValue = this.options.tagValueProcessor(key, item);
textValue = this.replaceEntitiesValue(textValue);
listTagVal += textValue;
} else {
listTagVal += this.buildTextValNode(item, key, '', level);
}
}
}
if(this.options.oneListGroup){
listTagVal = this.buildObjectNode(listTagVal, key, '', level);
listTagVal = this.buildObjectNode(listTagVal, key, listTagAttr, level);
}
val += listTagVal;
} else {
@@ -145,7 +162,7 @@ Builder.prototype.j2x = function(jObj, level) {
attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]]);
}
} else {
val += this.processTextOrObjNode(jObj[key], key, level)
val += this.processTextOrObjNode(jObj[key], key, level, ajPath)
}
}
}
@@ -160,8 +177,8 @@ Builder.prototype.buildAttrPairStr = function(attrName, val){
} else return ' ' + attrName + '="' + val + '"';
}
function processTextOrObjNode (object, key, level) {
const result = this.j2x(object, level + 1);
function processTextOrObjNode (object, key, level, ajPath) {
const result = this.j2x(object, level + 1, ajPath.concat(key));
if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
return this.buildTextValNode(object[this.options.textNodeName], key, result.attrStr, level);
} else {

View File

@@ -21,6 +21,8 @@ function arrToStr(arr, options, jPath, indentation) {
for (let i = 0; i < arr.length; i++) {
const tagObj = arr[i];
const tagName = propName(tagObj);
if(tagName === undefined) continue;
let newJPath = "";
if (jPath.length === 0) newJPath = tagName
else newJPath = `${jPath}.${tagName}`;
@@ -90,6 +92,7 @@ function propName(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if(!obj.hasOwnProperty(key)) continue;
if (key !== ":@") return key;
}
}
@@ -98,6 +101,7 @@ function attr_to_str(attrMap, options) {
let attrStr = "";
if (attrMap && !options.ignoreAttributes) {
for (let attr in attrMap) {
if(!attrMap.hasOwnProperty(attr)) continue;
let attrVal = options.attributeValueProcessor(attr, attrMap[attr]);
attrVal = replaceEntitiesValue(attrVal, options);
if (attrVal === true && options.suppressBooleanAttributes) {

View File

@@ -19,6 +19,7 @@ function readDocType(xmlData, i){
if (xmlData[i] === '<' && !comment) { //Determine the tag type
if( hasBody && isEntity(xmlData, i)){
i += 7;
let entityName, val;
[entityName, val,i] = readEntityExp(xmlData,i+1);
if(val.indexOf("&") === -1) //Parameter entities are not supported
entities[ validateEntityName(entityName) ] = {

View File

@@ -5,10 +5,11 @@ const util = require('../util');
const xmlNode = require('./xmlNode');
const readDocType = require("./DocTypeReader");
const toNumber = require("strnum");
const getIgnoreAttributesFn = require('../ignoreAttributes')
const regx =
'<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
.replace(/NAME/g, util.nameRegexp);
// 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");
@@ -40,6 +41,8 @@ class OrderedObjParser{
"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.fromCharCode(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 16)) },
};
this.addExternalEntities = addExternalEntities;
this.parseXml = parseXml;
@@ -51,6 +54,7 @@ class OrderedObjParser{
this.readStopNodeData = readStopNodeData;
this.saveTextToParentTag = saveTextToParentTag;
this.addChild = addChild;
this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
}
}
@@ -123,7 +127,7 @@ function resolveNameSpace(tagname) {
const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
function buildAttributesMap(attrStr, jPath, tagName) {
if (!this.options.ignoreAttributes && typeof attrStr === 'string') {
if (this.options.ignoreAttributes !== true && typeof attrStr === 'string') {
// attrStr = attrStr.replace(/\r?\n/g, ' ');
//attrStr = attrStr || attrStr.trim();
@@ -132,6 +136,9 @@ function buildAttributesMap(attrStr, jPath, tagName) {
const attrs = {};
for (let i = 0; i < len; i++) {
const attrName = this.resolveNameSpace(matches[i][1]);
if (this.ignoreAttributesFn(attrName, jPath)) {
continue
}
let oldVal = matches[i][4];
let aName = this.options.attributeNamePrefix + attrName;
if (attrName.length) {
@@ -265,14 +272,13 @@ const parseXml = function(xmlData) {
textData = this.saveTextToParentTag(textData, currentNode, jPath);
let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true, true);
if(val == undefined) val = "";
//cdata should be set even if it is 0 length string
if(this.options.cdataPropName){
// let val = this.parseTextData(tagExp, this.options.cdataPropName, jPath + "." + this.options.cdataPropName, true, false, true);
// if(!val) val = "";
currentNode.add(this.options.cdataPropName, [ { [this.options.textNodeName] : tagExp } ]);
}else{
let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true);
if(val == undefined) val = "";
currentNode.add(this.options.textNodeName, val);
}
@@ -280,6 +286,7 @@ const parseXml = function(xmlData) {
}else {//Opening tag
let result = readTagExp(xmlData,i, this.options.removeNSPrefix);
let tagName= result.tagName;
const rawTagName = result.rawTagName;
let tagExp = result.tagExp;
let attrExpPresent = result.attrExpPresent;
let closeIndex = result.closeIndex;
@@ -305,21 +312,29 @@ const parseXml = function(xmlData) {
if(tagName !== xmlObj.tagname){
jPath += jPath ? "." + tagName : tagName;
}
if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) { //TODO: namespace
if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) {
let tagContent = "";
//self-closing tag
if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
tagName = tagName.substr(0, tagName.length - 1);
jPath = jPath.substr(0, jPath.length - 1);
tagExp = tagName;
}else{
tagExp = tagExp.substr(0, tagExp.length - 1);
}
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, tagName, closeIndex + 1);
if(!result) throw new Error(`Unexpected end of ${tagName}`);
const result = this.readStopNodeData(xmlData, rawTagName, closeIndex + 1);
if(!result) throw new Error(`Unexpected end of ${rawTagName}`);
i = result.i;
tagContent = result.tagContent;
}
@@ -414,7 +429,7 @@ const replaceEntitiesValue = function(val){
}
function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
if (textData) { //store previously collected data as textNode
if(isLeafNode === undefined) isLeafNode = Object.keys(currentNode.child).length === 0
if(isLeafNode === undefined) isLeafNode = currentNode.child.length === 0
textData = this.parseTextData(textData,
currentNode.tagname,
@@ -500,10 +515,11 @@ function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
let tagName = tagExp;
let attrExpPresent = true;
if(separatorIndex !== -1){//separate tag name and attributes expression
tagName = tagExp.substr(0, separatorIndex).replace(/\s\s*$/, '');
tagExp = tagExp.substr(separatorIndex + 1);
tagName = tagExp.substring(0, separatorIndex);
tagExp = tagExp.substring(separatorIndex + 1).trimStart();
}
const rawTagName = tagName;
if(removeNSPrefix){
const colonIndex = tagName.indexOf(":");
if(colonIndex !== -1){
@@ -517,6 +533,7 @@ function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
tagExp: tagExp,
closeIndex: closeIndex,
attrExpPresent: attrExpPresent,
rawTagName: rawTagName,
}
}
/**