fast-xml-parser 4.5.1 → 4.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +3 -3
- package/package.json +8 -9
- package/src/cli/cli.js +1 -1
- package/src/v6/CharsSymbol.js +16 -0
- package/src/v6/EntitiesParser.js +104 -0
- package/src/v6/OptionsBuilder.js +61 -0
- package/src/v6/OutputBuilders/BaseOutputBuilder.js +69 -0
- package/src/v6/OutputBuilders/JsArrBuilder.js +103 -0
- package/src/v6/OutputBuilders/JsMinArrBuilder.js +100 -0
- package/src/v6/OutputBuilders/JsObjBuilder.js +154 -0
- package/src/v6/OutputBuilders/ParserOptionsBuilder.js +94 -0
- package/src/v6/Report.js +0 -0
- package/src/v6/TagPath.js +81 -0
- package/src/v6/TagPathMatcher.js +13 -0
- package/src/v6/XMLParser.js +83 -0
- package/src/v6/Xml2JsParser.js +235 -0
- package/src/v6/XmlPartReader.js +210 -0
- package/src/v6/XmlSpecialTagsReader.js +111 -0
- package/src/v6/inputSource/BufferSource.js +116 -0
- package/src/v6/inputSource/StringSource.js +121 -0
- package/src/v6/valueParsers/EntitiesParser.js +105 -0
- package/src/v6/valueParsers/booleanParser.js +22 -0
- package/src/v6/valueParsers/booleanParserExt.js +19 -0
- package/src/v6/valueParsers/currency.js +38 -0
- package/src/v6/valueParsers/join.js +13 -0
- package/src/v6/valueParsers/number.js +14 -0
- package/src/v6/valueParsers/trim.js +6 -0
- package/src/xmlbuilder/json2xml.js +2 -0
- package/src/xmlparser/OrderedObjParser.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<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>
|
|
2
2
|
|
|
3
|
+
**4.5.2 / 2025-02-18**
|
|
4
|
+
- Fix null CDATA to comply with undefined behavior (#701) (By [Matthieu BOHEAS](https://github.com/Kelgors))
|
|
5
|
+
- Fix(performance): Update check for leaf node in saveTextToParentTag function in OrderedObjParser.js (#707) (By [...](https://github.com/tomingtoming))
|
|
6
|
+
- Fix: emit full JSON string from CLI when no output filename specified (#710) (By [Matt Benson](https://github.com/mbenson))
|
|
7
|
+
|
|
3
8
|
**4.5.1 / 2024-12-15**
|
|
4
9
|
- Fix empty tag key name for v5 (#697). no impact on v4
|
|
5
10
|
- Fixes entity parsing when used in strict mode (#699)
|
package/README.md
CHANGED
|
@@ -9,8 +9,7 @@ Validate XML, Parse XML to JS Object, or Build XML from JS Object without C/C++
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
<
|
|
13
|
-
I had recently published a book, The Power Glasses. Please have a look. Your feedback would be helpful. You can [mail](githubissues@proton.me) me for a free copy.
|
|
12
|
+
<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>
|
|
14
13
|
<br>
|
|
15
14
|
|
|
16
15
|
Sponsor this project
|
|
@@ -51,8 +50,9 @@ Through OpenCollective
|
|
|
51
50
|
-->
|
|
52
51
|
|
|
53
52
|

|
|
54
|
-
<a href="https://github.com/cocopon" target="_blank">Hiroki Kokubun</a>
|
|
53
|
+
- <a href="https://github.com/cocopon" target="_blank">Hiroki Kokubun</a>
|
|
55
54
|
|
|
55
|
+
> This is a donation. No goods or services are expected in return. Any requests for refunds for those purposes will be rejected.
|
|
56
56
|
## Users
|
|
57
57
|
|
|
58
58
|
<a href="https://github.com/renovatebot/renovate" title="renovate" ><img src="https://avatars1.githubusercontent.com/u/38656520" width="60px" ></a>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-xml-parser",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.3",
|
|
4
4
|
"description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
|
|
5
5
|
"main": "./src/fxp.js",
|
|
6
6
|
"scripts": {
|
|
@@ -60,14 +60,13 @@
|
|
|
60
60
|
"webpack-cli": "^4.9.1"
|
|
61
61
|
},
|
|
62
62
|
"typings": "src/fxp.d.ts",
|
|
63
|
-
"funding": [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}],
|
|
63
|
+
"funding": [
|
|
64
|
+
{
|
|
65
|
+
"type": "github",
|
|
66
|
+
"url": "https://github.com/sponsors/NaturalIntelligence"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
70
69
|
"dependencies": {
|
|
71
|
-
"strnum": "^1.
|
|
70
|
+
"strnum": "^1.1.1"
|
|
72
71
|
}
|
|
73
72
|
}
|
package/src/cli/cli.js
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
"<" : "<", //tag start
|
|
3
|
+
">" : ">", //tag end
|
|
4
|
+
"/" : "/", //close tag
|
|
5
|
+
"!" : "!", //comment or docttype
|
|
6
|
+
"!--" : "!--", //comment
|
|
7
|
+
"-->" : "-->", //comment end
|
|
8
|
+
"?" : "?", //pi
|
|
9
|
+
"?>" : "?>", //pi end
|
|
10
|
+
"?xml" : "?xml", //pi end
|
|
11
|
+
"![" : "![", //cdata
|
|
12
|
+
"]]>" : "]]>", //cdata end
|
|
13
|
+
"[" : "[",
|
|
14
|
+
"-" : "-",
|
|
15
|
+
"D" : "D",
|
|
16
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const ampEntity = { regex: /&(amp|#38|#x26);/g, val : "&"};
|
|
2
|
+
const htmlEntities = {
|
|
3
|
+
"space": { regex: /&(nbsp|#160);/g, val: " " },
|
|
4
|
+
// "lt" : { regex: /&(lt|#60);/g, val: "<" },
|
|
5
|
+
// "gt" : { regex: /&(gt|#62);/g, val: ">" },
|
|
6
|
+
// "amp" : { regex: /&(amp|#38);/g, val: "&" },
|
|
7
|
+
// "quot" : { regex: /&(quot|#34);/g, val: "\"" },
|
|
8
|
+
// "apos" : { regex: /&(apos|#39);/g, val: "'" },
|
|
9
|
+
"cent" : { regex: /&(cent|#162);/g, val: "¢" },
|
|
10
|
+
"pound" : { regex: /&(pound|#163);/g, val: "£" },
|
|
11
|
+
"yen" : { regex: /&(yen|#165);/g, val: "¥" },
|
|
12
|
+
"euro" : { regex: /&(euro|#8364);/g, val: "€" },
|
|
13
|
+
"copyright" : { regex: /&(copy|#169);/g, val: "©" },
|
|
14
|
+
"reg" : { regex: /&(reg|#174);/g, val: "®" },
|
|
15
|
+
"inr" : { regex: /&(inr|#8377);/g, val: "₹" },
|
|
16
|
+
"num_dec": { regex: /&#([0-9]{1,7});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 10)) },
|
|
17
|
+
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 16)) },
|
|
18
|
+
};
|
|
19
|
+
export default class EntitiesParser{
|
|
20
|
+
constructor(replaceHtmlEntities) {
|
|
21
|
+
this.replaceHtmlEntities = replaceHtmlEntities;
|
|
22
|
+
this.docTypeEntities = {};
|
|
23
|
+
this.lastEntities = {
|
|
24
|
+
"apos" : { regex: /&(apos|#39|#x27);/g, val : "'"},
|
|
25
|
+
"gt" : { regex: /&(gt|#62|#x3E);/g, val : ">"},
|
|
26
|
+
"lt" : { regex: /&(lt|#60|#x3C);/g, val : "<"},
|
|
27
|
+
"quot" : { regex: /&(quot|#34|#x22);/g, val : "\""},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
addExternalEntities(externalEntities){
|
|
32
|
+
const entKeys = Object.keys(externalEntities);
|
|
33
|
+
for (let i = 0; i < entKeys.length; i++) {
|
|
34
|
+
const ent = entKeys[i];
|
|
35
|
+
this.addExternalEntity(ent,externalEntities[ent])
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
addExternalEntity(key,val){
|
|
39
|
+
validateEntityName(key);
|
|
40
|
+
if(val.indexOf("&") !== -1) {
|
|
41
|
+
reportWarning(`Entity ${key} is not added as '&' is found in value;`)
|
|
42
|
+
return;
|
|
43
|
+
}else{
|
|
44
|
+
this.lastEntities[ent] = {
|
|
45
|
+
regex: new RegExp("&"+key+";","g"),
|
|
46
|
+
val : val
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
addDocTypeEntities(entities){
|
|
52
|
+
const entKeys = Object.keys(entities);
|
|
53
|
+
for (let i = 0; i < entKeys.length; i++) {
|
|
54
|
+
const ent = entKeys[i];
|
|
55
|
+
this.docTypeEntities[ent] = {
|
|
56
|
+
regex: new RegExp("&"+ent+";","g"),
|
|
57
|
+
val : entities[ent]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
parse(val){
|
|
63
|
+
return this.replaceEntitiesValue(val)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 1. Replace DOCTYPE entities
|
|
68
|
+
* 2. Replace external entities
|
|
69
|
+
* 3. Replace HTML entities if asked
|
|
70
|
+
* @param {string} val
|
|
71
|
+
*/
|
|
72
|
+
replaceEntitiesValue(val){
|
|
73
|
+
if(typeof val === "string" && val.length > 0){
|
|
74
|
+
for(let entityName in this.docTypeEntities){
|
|
75
|
+
const entity = this.docTypeEntities[entityName];
|
|
76
|
+
val = val.replace( entity.regx, entity.val);
|
|
77
|
+
}
|
|
78
|
+
for(let entityName in this.lastEntities){
|
|
79
|
+
const entity = this.lastEntities[entityName];
|
|
80
|
+
val = val.replace( entity.regex, entity.val);
|
|
81
|
+
}
|
|
82
|
+
if(this.replaceHtmlEntities){
|
|
83
|
+
for(let entityName in htmlEntities){
|
|
84
|
+
const entity = htmlEntities[entityName];
|
|
85
|
+
val = val.replace( entity.regex, entity.val);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
val = val.replace( ampEntity.regex, ampEntity.val);
|
|
89
|
+
}
|
|
90
|
+
return val;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
//an entity name should not contains special characters that may be used in regex
|
|
95
|
+
//Eg !?\\\/[]$%{}^&*()<>
|
|
96
|
+
const specialChar = "!?\\\/[]$%{}^&*()<>|+";
|
|
97
|
+
|
|
98
|
+
function validateEntityName(name){
|
|
99
|
+
for (let i = 0; i < specialChar.length; i++) {
|
|
100
|
+
const ch = specialChar[i];
|
|
101
|
+
if(name.indexOf(ch) !== -1) throw new Error(`Invalid character ${ch} in entity name`);
|
|
102
|
+
}
|
|
103
|
+
return name;
|
|
104
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
|
|
2
|
+
import {JsObjOutputBuilder} from './OutputBuilders/JsObjBuilder.js';
|
|
3
|
+
|
|
4
|
+
export const defaultOptions = {
|
|
5
|
+
preserveOrder: false,
|
|
6
|
+
removeNSPrefix: false, // remove NS from tag name or attribute name if true
|
|
7
|
+
//ignoreRootElement : false,
|
|
8
|
+
stopNodes: [], //nested tags will not be parsed even for errors
|
|
9
|
+
// isArray: () => false, //User will set it
|
|
10
|
+
htmlEntities: false,
|
|
11
|
+
// skipEmptyListItem: false
|
|
12
|
+
tags:{
|
|
13
|
+
unpaired: [],
|
|
14
|
+
nameFor:{
|
|
15
|
+
cdata: false,
|
|
16
|
+
comment: false,
|
|
17
|
+
text: '#text'
|
|
18
|
+
},
|
|
19
|
+
separateTextProperty: false,
|
|
20
|
+
},
|
|
21
|
+
attributes:{
|
|
22
|
+
ignore: false,
|
|
23
|
+
booleanType: true,
|
|
24
|
+
entities: true,
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
// select: ["img[src]"],
|
|
28
|
+
// stop: ["anim", "[ads]"]
|
|
29
|
+
only: [], // rest tags will be skipped. It will result in flat array
|
|
30
|
+
hierarchy: false, //will be used when a particular tag is set to be parsed.
|
|
31
|
+
skip: [], // will be skipped from parse result. on('skip') will be triggered
|
|
32
|
+
|
|
33
|
+
select: [], // on('select', tag => tag ) will be called if match
|
|
34
|
+
stop: [], //given tagPath will not be parsed. innerXML will be set as string value
|
|
35
|
+
OutputBuilder: new JsObjOutputBuilder(),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const buildOptions = function(options) {
|
|
39
|
+
const finalOptions = { ... defaultOptions};
|
|
40
|
+
copyProperties(finalOptions,options)
|
|
41
|
+
return finalOptions;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function copyProperties(target, source) {
|
|
45
|
+
for (let key in source) {
|
|
46
|
+
if (source.hasOwnProperty(key)) {
|
|
47
|
+
if (key === 'OutputBuilder') {
|
|
48
|
+
target[key] = source[key];
|
|
49
|
+
}else if (typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
50
|
+
// Recursively copy nested properties
|
|
51
|
+
if (typeof target[key] === 'undefined') {
|
|
52
|
+
target[key] = {};
|
|
53
|
+
}
|
|
54
|
+
copyProperties(target[key], source[key]);
|
|
55
|
+
} else {
|
|
56
|
+
// Copy non-nested properties
|
|
57
|
+
target[key] = source[key];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export default class BaseOutputBuilder{
|
|
2
|
+
constructor(){
|
|
3
|
+
// this.attributes = {};
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
addAttribute(name, value){
|
|
7
|
+
if(this.options.onAttribute){
|
|
8
|
+
//TODO: better to pass tag path
|
|
9
|
+
const v = this.options.onAttribute(name, value, this.tagName);
|
|
10
|
+
if(v) this.attributes[v.name] = v.value;
|
|
11
|
+
}else{
|
|
12
|
+
name = this.options.attributes.prefix + name + this.options.attributes.suffix;
|
|
13
|
+
this.attributes[name] = this.parseValue(value, this.options.attributes.valueParsers);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* parse value by chain of parsers
|
|
19
|
+
* @param {string} val
|
|
20
|
+
* @returns {any} parsed value if matching parser found
|
|
21
|
+
*/
|
|
22
|
+
parseValue = function(val, valParsers){
|
|
23
|
+
for (let i = 0; i < valParsers.length; i++) {
|
|
24
|
+
let valParser = valParsers[i];
|
|
25
|
+
if(typeof valParser === "string"){
|
|
26
|
+
valParser = this.registeredParsers[valParser];
|
|
27
|
+
}
|
|
28
|
+
if(valParser){
|
|
29
|
+
val = valParser.parse(val);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return val;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* To add a nested empty tag.
|
|
37
|
+
* @param {string} key
|
|
38
|
+
* @param {any} val
|
|
39
|
+
*/
|
|
40
|
+
_addChild(key, val){}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* skip the comment if property is not set
|
|
44
|
+
*/
|
|
45
|
+
addComment(text){
|
|
46
|
+
if(this.options.nameFor.comment)
|
|
47
|
+
this._addChild(this.options.nameFor.comment, text);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//store CDATA separately if property is set
|
|
51
|
+
//otherwise add to tag's value
|
|
52
|
+
addCdata(text){
|
|
53
|
+
if (this.options.nameFor.cdata) {
|
|
54
|
+
this._addChild(this.options.nameFor.cdata, text);
|
|
55
|
+
} else {
|
|
56
|
+
this.addRawValue(text || "");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
addRawValue = text => this.addValue(text);
|
|
61
|
+
|
|
62
|
+
addDeclaration(){
|
|
63
|
+
if(!this.options.declaration){
|
|
64
|
+
}else{
|
|
65
|
+
this.addPi("?xml");
|
|
66
|
+
}
|
|
67
|
+
this.attributes = {}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {buildOptions,registerCommonValueParsers} from './ParserOptionsBuilder.js';
|
|
2
|
+
|
|
3
|
+
export default class OutputBuilder{
|
|
4
|
+
constructor(options){
|
|
5
|
+
this.options = buildOptions(options);
|
|
6
|
+
this.registeredParsers = registerCommonValueParsers(this.options);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
registerValueParser(name,parserInstance){//existing name will override the parser without warning
|
|
10
|
+
this.registeredParsers[name] = parserInstance;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getInstance(parserOptions){
|
|
14
|
+
return new JsArrBuilder(parserOptions, this.options, this.registeredParsers);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const rootName = '!js_arr';
|
|
19
|
+
import BaseOutputBuilder from './BaseOutputBuilder.js';
|
|
20
|
+
|
|
21
|
+
class JsArrBuilder extends BaseOutputBuilder{
|
|
22
|
+
|
|
23
|
+
constructor(parserOptions, options,registeredParsers) {
|
|
24
|
+
super();
|
|
25
|
+
this.tagsStack = [];
|
|
26
|
+
this.parserOptions = parserOptions;
|
|
27
|
+
this.options = options;
|
|
28
|
+
this.registeredParsers = registeredParsers;
|
|
29
|
+
|
|
30
|
+
this.root = new Node(rootName);
|
|
31
|
+
this.currentNode = this.root;
|
|
32
|
+
this.attributes = {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
addTag(tag){
|
|
36
|
+
//when a new tag is added, it should be added as child of current node
|
|
37
|
+
//TODO: shift this check to the parser
|
|
38
|
+
if(tag.name === "__proto__") tag.name = "#__proto__";
|
|
39
|
+
|
|
40
|
+
this.tagsStack.push(this.currentNode);
|
|
41
|
+
this.currentNode = new Node(tag.name, this.attributes);
|
|
42
|
+
this.attributes = {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if the node should be added by checking user's preference
|
|
47
|
+
* @param {Node} node
|
|
48
|
+
* @returns boolean: true if the node should not be added
|
|
49
|
+
*/
|
|
50
|
+
closeTag(){
|
|
51
|
+
const node = this.currentNode;
|
|
52
|
+
this.currentNode = this.tagsStack.pop(); //set parent node in scope
|
|
53
|
+
if(this.options.onClose !== undefined){
|
|
54
|
+
//TODO TagPathMatcher
|
|
55
|
+
const resultTag = this.options.onClose(node,
|
|
56
|
+
new TagPathMatcher(this.tagsStack,node));
|
|
57
|
+
|
|
58
|
+
if(resultTag) return;
|
|
59
|
+
}
|
|
60
|
+
this.currentNode.child.push(node); //to parent node
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//Called by parent class methods
|
|
64
|
+
_addChild(key, val){
|
|
65
|
+
// if(key === "__proto__") tagName = "#__proto__";
|
|
66
|
+
this.currentNode.child.push( {[key]: val });
|
|
67
|
+
// this.currentNode.leafType = false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Add text value child node
|
|
72
|
+
* @param {string} text
|
|
73
|
+
*/
|
|
74
|
+
addValue(text){
|
|
75
|
+
this.currentNode.child.push( {[this.options.nameFor.text]: this.parseValue(text, this.options.tags.valueParsers) });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
addPi(name){
|
|
79
|
+
//TODO: set pi flag
|
|
80
|
+
if(!this.options.ignorePiTags){
|
|
81
|
+
const node = new Node(name, this.attributes);
|
|
82
|
+
this.currentNode[":@"] = this.attributes;
|
|
83
|
+
this.currentNode.child.push(node);
|
|
84
|
+
}
|
|
85
|
+
this.attributes = {};
|
|
86
|
+
}
|
|
87
|
+
getOutput(){
|
|
88
|
+
return this.root.child[0];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Node{
|
|
95
|
+
constructor(tagname, attributes){
|
|
96
|
+
this.tagname = tagname;
|
|
97
|
+
this.child = []; //nested tags, text, cdata, comments
|
|
98
|
+
if(attributes && Object.keys(attributes).length > 0)
|
|
99
|
+
this[":@"] = attributes;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = OutputBuilder;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {buildOptions,registerCommonValueParsers} from"./ParserOptionsBuilder";
|
|
2
|
+
|
|
3
|
+
export default class OutputBuilder{
|
|
4
|
+
constructor(options){
|
|
5
|
+
this.options = buildOptions(options);
|
|
6
|
+
this.registeredParsers = registerCommonValueParsers(this.options);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
registerValueParser(name,parserInstance){//existing name will override the parser without warning
|
|
10
|
+
this.registeredParsers[name] = parserInstance;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getInstance(parserOptions){
|
|
14
|
+
return new JsMinArrBuilder(parserOptions, this.options, this.registeredParsers);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
import BaseOutputBuilder from "./BaseOutputBuilder.js";
|
|
19
|
+
const rootName = '^';
|
|
20
|
+
|
|
21
|
+
class JsMinArrBuilder extends BaseOutputBuilder{
|
|
22
|
+
|
|
23
|
+
constructor(parserOptions, options,registeredParsers) {
|
|
24
|
+
super();
|
|
25
|
+
this.tagsStack = [];
|
|
26
|
+
this.parserOptions = parserOptions;
|
|
27
|
+
this.options = options;
|
|
28
|
+
this.registeredParsers = registeredParsers;
|
|
29
|
+
|
|
30
|
+
this.root = {[rootName]: []};
|
|
31
|
+
this.currentNode = this.root;
|
|
32
|
+
this.currentNodeTagName = rootName;
|
|
33
|
+
this.attributes = {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
addTag(tag){
|
|
37
|
+
//when a new tag is added, it should be added as child of current node
|
|
38
|
+
//TODO: shift this check to the parser
|
|
39
|
+
if(tag.name === "__proto__") tag.name = "#__proto__";
|
|
40
|
+
|
|
41
|
+
this.tagsStack.push([this.currentNodeTagName,this.currentNode]); //this.currentNode is parent node here
|
|
42
|
+
this.currentNodeTagName = tag.name;
|
|
43
|
+
this.currentNode = { [tag.name]:[]}
|
|
44
|
+
if(Object.keys(this.attributes).length > 0){
|
|
45
|
+
this.currentNode[":@"] = this.attributes;
|
|
46
|
+
this.attributes = {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if the node should be added by checking user's preference
|
|
52
|
+
* @param {Node} node
|
|
53
|
+
* @returns boolean: true if the node should not be added
|
|
54
|
+
*/
|
|
55
|
+
closeTag(){
|
|
56
|
+
const node = this.currentNode;
|
|
57
|
+
const nodeName = this.currentNodeTagName;
|
|
58
|
+
const arr = this.tagsStack.pop(); //set parent node in scope
|
|
59
|
+
this.currentNodeTagName = arr[0];
|
|
60
|
+
this.currentNode = arr[1];
|
|
61
|
+
|
|
62
|
+
if(this.options.onClose !== undefined){
|
|
63
|
+
//TODO TagPathMatcher
|
|
64
|
+
const resultTag = this.options.onClose(node,
|
|
65
|
+
new TagPathMatcher(this.tagsStack,node));
|
|
66
|
+
|
|
67
|
+
if(resultTag) return;
|
|
68
|
+
}
|
|
69
|
+
this.currentNode[this.currentNodeTagName].push(node); //to parent node
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//Called by parent class methods
|
|
73
|
+
_addChild(key, val){
|
|
74
|
+
// if(key === "__proto__") tagName = "#__proto__";
|
|
75
|
+
this.currentNode.push( {[key]: val });
|
|
76
|
+
// this.currentNode.leafType = false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Add text value child node
|
|
81
|
+
* @param {string} text
|
|
82
|
+
*/
|
|
83
|
+
addValue(text){
|
|
84
|
+
this.currentNode[this.currentNodeTagName].push( {[this.options.nameFor.text]: this.parseValue(text, this.options.tags.valueParsers) });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
addPi(name){
|
|
88
|
+
if(!this.options.ignorePiTags){
|
|
89
|
+
const node = { [name]:[]}
|
|
90
|
+
if(this.attributes){
|
|
91
|
+
node[":@"] = this.attributes;
|
|
92
|
+
}
|
|
93
|
+
this.currentNode.push(node);
|
|
94
|
+
}
|
|
95
|
+
this.attributes = {};
|
|
96
|
+
}
|
|
97
|
+
getOutput(){
|
|
98
|
+
return this.root[rootName];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import {buildOptions,registerCommonValueParsers} from './ParserOptionsBuilder.js';
|
|
4
|
+
|
|
5
|
+
export default class OutputBuilder{
|
|
6
|
+
constructor(builderOptions){
|
|
7
|
+
this.options = buildOptions(builderOptions);
|
|
8
|
+
this.registeredParsers = registerCommonValueParsers(this.options);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
registerValueParser(name,parserInstance){//existing name will override the parser without warning
|
|
12
|
+
this.registeredParsers[name] = parserInstance;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getInstance(parserOptions){
|
|
16
|
+
return new JsObjBuilder(parserOptions, this.options, this.registeredParsers);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
import BaseOutputBuilder from './BaseOutputBuilder.js';
|
|
21
|
+
const rootName = '^';
|
|
22
|
+
|
|
23
|
+
class JsObjBuilder extends BaseOutputBuilder{
|
|
24
|
+
|
|
25
|
+
constructor(parserOptions, builderOptions,registeredParsers) {
|
|
26
|
+
super();
|
|
27
|
+
//hold the raw detail of a tag and sequence with reference to the output
|
|
28
|
+
this.tagsStack = [];
|
|
29
|
+
this.parserOptions = parserOptions;
|
|
30
|
+
this.options = builderOptions;
|
|
31
|
+
this.registeredParsers = registeredParsers;
|
|
32
|
+
|
|
33
|
+
this.root = {};
|
|
34
|
+
this.parent = this.root;
|
|
35
|
+
this.tagName = rootName;
|
|
36
|
+
this.value = {};
|
|
37
|
+
this.textValue = "";
|
|
38
|
+
this.attributes = {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
addTag(tag){
|
|
42
|
+
|
|
43
|
+
let value = "";
|
|
44
|
+
if( !isEmpty(this.attributes)){
|
|
45
|
+
value = {};
|
|
46
|
+
if(this.options.attributes.groupBy){
|
|
47
|
+
value[this.options.attributes.groupBy] = this.attributes;
|
|
48
|
+
}else{
|
|
49
|
+
value = this.attributes;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.tagsStack.push([this.tagName, this.textValue, this.value]); //parent tag, parent text value, parent tag value (jsobj)
|
|
54
|
+
this.tagName = tag.name;
|
|
55
|
+
this.value = value;
|
|
56
|
+
this.textValue = "";
|
|
57
|
+
this.attributes = {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if the node should be added by checking user's preference
|
|
62
|
+
* @param {Node} node
|
|
63
|
+
* @returns boolean: true if the node should not be added
|
|
64
|
+
*/
|
|
65
|
+
closeTag(){
|
|
66
|
+
const tagName = this.tagName;
|
|
67
|
+
let value = this.value;
|
|
68
|
+
let textValue = this.textValue;
|
|
69
|
+
|
|
70
|
+
//update tag text value
|
|
71
|
+
if(typeof value !== "object" && !Array.isArray(value)){
|
|
72
|
+
value = this.parseValue(textValue.trim(), this.options.tags.valueParsers);
|
|
73
|
+
}else if(textValue.length > 0){
|
|
74
|
+
value[this.options.nameFor.text] = this.parseValue(textValue.trim(), this.options.tags.valueParsers);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
let resultTag= {
|
|
79
|
+
tagName: tagName,
|
|
80
|
+
value: value
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if(this.options.onTagClose !== undefined){
|
|
84
|
+
//TODO TagPathMatcher
|
|
85
|
+
resultTag = this.options.onClose(tagName, value, this.textValue, new TagPathMatcher(this.tagsStack,node));
|
|
86
|
+
|
|
87
|
+
if(!resultTag) return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//set parent node in scope
|
|
91
|
+
let arr = this.tagsStack.pop();
|
|
92
|
+
let parentTag = arr[2];
|
|
93
|
+
parentTag=this._addChildTo(resultTag.tagName, resultTag.value, parentTag);
|
|
94
|
+
|
|
95
|
+
this.tagName = arr[0];
|
|
96
|
+
this.textValue = arr[1];
|
|
97
|
+
this.value = parentTag;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_addChild(key, val){
|
|
101
|
+
if(typeof this.value === "string"){
|
|
102
|
+
this.value = { [this.options.nameFor.text] : this.value };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this._addChildTo(key, val, this.value);
|
|
106
|
+
// this.currentNode.leafType = false;
|
|
107
|
+
this.attributes = {};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_addChildTo(key, val, node){
|
|
111
|
+
if(typeof node === 'string') node = {};
|
|
112
|
+
if(!node[key]){
|
|
113
|
+
node[key] = val;
|
|
114
|
+
}else{ //Repeated
|
|
115
|
+
if(!Array.isArray(node[key])){ //but not stored as array
|
|
116
|
+
node[key] = [node[key]];
|
|
117
|
+
}
|
|
118
|
+
node[key].push(val);
|
|
119
|
+
}
|
|
120
|
+
return node;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Add text value child node
|
|
126
|
+
* @param {string} text
|
|
127
|
+
*/
|
|
128
|
+
addValue(text){
|
|
129
|
+
//TODO: use bytes join
|
|
130
|
+
if(this.textValue.length > 0) this.textValue += " " + text;
|
|
131
|
+
else this.textValue = text;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
addPi(name){
|
|
135
|
+
let value = "";
|
|
136
|
+
if( !isEmpty(this.attributes)){
|
|
137
|
+
value = {};
|
|
138
|
+
if(this.options.attributes.groupBy){
|
|
139
|
+
value[this.options.attributes.groupBy] = this.attributes;
|
|
140
|
+
}else{
|
|
141
|
+
value = this.attributes;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this._addChild(name, value);
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
getOutput(){
|
|
148
|
+
return this.value;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isEmpty(obj) {
|
|
153
|
+
return Object.keys(obj).length === 0;
|
|
154
|
+
}
|