fast-xml-parser 3.21.1 → 4.0.0-beta.4
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 +434 -0
- package/README.md +87 -277
- package/package.json +9 -36
- package/{cli.js → src/cli/cli.js} +16 -24
- package/src/cli/man.js +12 -0
- package/src/{read.js → cli/read.js} +0 -0
- package/src/fxp.d.ts +84 -0
- package/src/fxp.js +11 -0
- package/src/util.js +2 -21
- package/src/validator.js +16 -3
- package/src/{json2xml.js → xmlbuilder/json2xml.js} +83 -98
- package/src/xmlbuilder/orderedJs2Xml.js +99 -0
- package/src/xmlbuilder/prettifyJs2Xml.js +0 -0
- package/src/xmlparser/DocTypeReader.js +92 -0
- package/src/xmlparser/OptionsBuilder.js +66 -0
- package/src/xmlparser/OrderedObjParser.js +498 -0
- package/src/xmlparser/XMLParser.js +56 -0
- package/src/xmlparser/node2json.js +101 -0
- package/src/xmlparser/xmlNode.js +23 -0
- package/src/nimndata.js +0 -144
- package/src/node2json.js +0 -42
- package/src/node2json_str.js +0 -63
- package/src/parser.d.ts +0 -79
- package/src/parser.js +0 -76
- package/src/xmlNode.js +0 -17
- package/src/xmlstr2xmlnode.js +0 -339
package/src/fxp.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
type X2jOptions = {
|
|
2
|
+
preserveOrder: boolean;
|
|
3
|
+
attributeNamePrefix: string;
|
|
4
|
+
attributesGroupName: false | string;
|
|
5
|
+
textNodeName: string;
|
|
6
|
+
ignoreAttributes: boolean;
|
|
7
|
+
removeNSPrefix: boolean;
|
|
8
|
+
allowBooleanAttributes: boolean;
|
|
9
|
+
parseTagValue: boolean;
|
|
10
|
+
parseAttributeValue: boolean;
|
|
11
|
+
trimValues: boolean;
|
|
12
|
+
cdataPropName: false | string;
|
|
13
|
+
commentPropName: false | string;
|
|
14
|
+
tagValueProcessor: (tagName: string, tagValue: string, jPath: string, hasAttributes: boolean, isLeafNode: boolean) => string;
|
|
15
|
+
attributeValueProcessor: (attrName: string, attrValue: string, jPath: string) => string;
|
|
16
|
+
numberParseOptions: strnumOptions;
|
|
17
|
+
stopNodes: string[];
|
|
18
|
+
unpairedTags: string[];
|
|
19
|
+
alwaysCreateTextNode: boolean;
|
|
20
|
+
isArray: (tagName: string, jPath: string, isLeafNode: boolean, isAttribute: boolean) => boolean;
|
|
21
|
+
processEntities: boolean;
|
|
22
|
+
htmlEntities: boolean;
|
|
23
|
+
};
|
|
24
|
+
type strnumOptions = {
|
|
25
|
+
hex: boolean;
|
|
26
|
+
leadingZeros: boolean,
|
|
27
|
+
skipLike?: RegExp
|
|
28
|
+
}
|
|
29
|
+
type X2jOptionsOptional = Partial<X2jOptions>;
|
|
30
|
+
type validationOptions = {
|
|
31
|
+
allowBooleanAttributes: boolean;
|
|
32
|
+
unpairedTags: string[];
|
|
33
|
+
};
|
|
34
|
+
type validationOptionsOptional = Partial<validationOptions>;
|
|
35
|
+
|
|
36
|
+
type XmlBuilderOptions = {
|
|
37
|
+
attributeNamePrefix: string;
|
|
38
|
+
attributesGroupName: false | string;
|
|
39
|
+
textNodeName: string;
|
|
40
|
+
ignoreAttributes: boolean;
|
|
41
|
+
cdataPropName: false | string;
|
|
42
|
+
commentPropName: false | string;
|
|
43
|
+
format: boolean;
|
|
44
|
+
indentBy: string;
|
|
45
|
+
arrayNodeName: string;
|
|
46
|
+
suppressEmptyNode: boolean;
|
|
47
|
+
preserveOrder: boolean;
|
|
48
|
+
unpairedTags: string[];
|
|
49
|
+
stopNodes: string[];
|
|
50
|
+
tagValueProcessor: (name: string, value: string) => string;
|
|
51
|
+
attributeValueProcessor: (name: string, value: string) => string;
|
|
52
|
+
processEntities: boolean;
|
|
53
|
+
};
|
|
54
|
+
type XmlBuilderOptionsOptional = Partial<XmlBuilderOptions>;
|
|
55
|
+
|
|
56
|
+
type ESchema = string | object | Array<string|object>;
|
|
57
|
+
|
|
58
|
+
type ValidationError = {
|
|
59
|
+
err: {
|
|
60
|
+
code: string;
|
|
61
|
+
msg: string,
|
|
62
|
+
line: number,
|
|
63
|
+
col: number
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export class XMLParser {
|
|
68
|
+
constructor(options?: X2jOptionsOptional);
|
|
69
|
+
parse(xmlData: string | Buffer ,validationOptions?: validationOptionsOptional | boolean): any;
|
|
70
|
+
/**
|
|
71
|
+
* Add Entity which is not by default supported by this library
|
|
72
|
+
* @param entityIndentifier {string} Eg: 'ent' for &ent;
|
|
73
|
+
* @param entityValue {string} Eg: '\r'
|
|
74
|
+
*/
|
|
75
|
+
addEntity(entityIndentifier: string, entityValue: string): void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class XMLValidator{
|
|
79
|
+
static validate( xmlData: string, options?: validationOptionsOptional): true | ValidationError;
|
|
80
|
+
}
|
|
81
|
+
export class XMLBuilder {
|
|
82
|
+
constructor(options: XmlBuilderOptionsOptional);
|
|
83
|
+
build(jObj: any): any;
|
|
84
|
+
}
|
package/src/fxp.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const validator = require('./validator');
|
|
4
|
+
const XMLParser = require('./xmlparser/XMLParser');
|
|
5
|
+
const XMLBuilder = require('./xmlbuilder/json2xml');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
XMLParser: XMLParser,
|
|
9
|
+
XMLValidator: validator,
|
|
10
|
+
XMLBuilder: XMLBuilder
|
|
11
|
+
}
|
package/src/util.js
CHANGED
|
@@ -67,7 +67,7 @@ exports.getValue = function(v) {
|
|
|
67
67
|
// const fakeCall = function(a) {return a;};
|
|
68
68
|
// const fakeCallNoReturn = function() {};
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
const buildOptions = function(options, defaultOptions, props) {
|
|
71
71
|
let newOptions = {};
|
|
72
72
|
if (!options) {
|
|
73
73
|
return defaultOptions; //if there are not options
|
|
@@ -83,26 +83,7 @@ exports.buildOptions = function(options, defaultOptions, props) {
|
|
|
83
83
|
return newOptions;
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
* Check if a tag name should be treated as array
|
|
88
|
-
*
|
|
89
|
-
* @param tagName the node tagname
|
|
90
|
-
* @param arrayMode the array mode option
|
|
91
|
-
* @param parentTagName the parent tag name
|
|
92
|
-
* @returns {boolean} true if node should be parsed as array
|
|
93
|
-
*/
|
|
94
|
-
exports.isTagNameInArrayMode = function (tagName, arrayMode, parentTagName) {
|
|
95
|
-
if (arrayMode === false) {
|
|
96
|
-
return false;
|
|
97
|
-
} else if (arrayMode instanceof RegExp) {
|
|
98
|
-
return arrayMode.test(tagName);
|
|
99
|
-
} else if (typeof arrayMode === 'function') {
|
|
100
|
-
return !!arrayMode(tagName, parentTagName);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return arrayMode === "strict";
|
|
104
|
-
}
|
|
105
|
-
|
|
86
|
+
exports.buildOptions = buildOptions;
|
|
106
87
|
exports.isName = isName;
|
|
107
88
|
exports.getAllMatches = getAllMatches;
|
|
108
89
|
exports.nameRegexp = nameRegexp;
|
package/src/validator.js
CHANGED
|
@@ -4,9 +4,13 @@ const util = require('./util');
|
|
|
4
4
|
|
|
5
5
|
const defaultOptions = {
|
|
6
6
|
allowBooleanAttributes: false, //A tag can have attributes without any value
|
|
7
|
+
unpairedTags: []
|
|
7
8
|
};
|
|
8
9
|
|
|
9
|
-
const props = [
|
|
10
|
+
const props = [
|
|
11
|
+
'allowBooleanAttributes',
|
|
12
|
+
'unpairedTags'
|
|
13
|
+
];
|
|
10
14
|
|
|
11
15
|
//const tagsPattern = new RegExp("<\\/?([\\w:\\-_\.]+)\\s*\/?>","g");
|
|
12
16
|
exports.validate = function (xmlData, options) {
|
|
@@ -25,7 +29,7 @@ exports.validate = function (xmlData, options) {
|
|
|
25
29
|
// check for byte order mark (BOM)
|
|
26
30
|
xmlData = xmlData.substr(1);
|
|
27
31
|
}
|
|
28
|
-
|
|
32
|
+
|
|
29
33
|
for (let i = 0; i < xmlData.length; i++) {
|
|
30
34
|
|
|
31
35
|
if (xmlData[i] === '<' && xmlData[i+1] === '?') {
|
|
@@ -130,6 +134,8 @@ exports.validate = function (xmlData, options) {
|
|
|
130
134
|
//if the root level has been reached before ...
|
|
131
135
|
if (reachedRoot === true) {
|
|
132
136
|
return getErrorObject('InvalidXml', 'Multiple possible root nodes found.', getLineNumberForPosition(xmlData, i));
|
|
137
|
+
} else if(options.unpairedTags.indexOf(tagName) !== -1){
|
|
138
|
+
//don't push into stack
|
|
133
139
|
} else {
|
|
134
140
|
tags.push({tagName, tagStartPos});
|
|
135
141
|
}
|
|
@@ -156,6 +162,10 @@ exports.validate = function (xmlData, options) {
|
|
|
156
162
|
if (afterAmp == -1)
|
|
157
163
|
return getErrorObject('InvalidChar', "char '&' is not expected.", getLineNumberForPosition(xmlData, i));
|
|
158
164
|
i = afterAmp;
|
|
165
|
+
}else{
|
|
166
|
+
if (reachedRoot === true && !isWhiteSpace(xmlData[i])) {
|
|
167
|
+
return getErrorObject('InvalidXml', "Extra text at the end", getLineNumberForPosition(xmlData, i));
|
|
168
|
+
}
|
|
159
169
|
}
|
|
160
170
|
} //end of reading tag text value
|
|
161
171
|
if (xmlData[i] === '<') {
|
|
@@ -163,7 +173,7 @@ exports.validate = function (xmlData, options) {
|
|
|
163
173
|
}
|
|
164
174
|
}
|
|
165
175
|
} else {
|
|
166
|
-
if (
|
|
176
|
+
if ( isWhiteSpace(xmlData[i])) {
|
|
167
177
|
continue;
|
|
168
178
|
}
|
|
169
179
|
return getErrorObject('InvalidChar', "char '"+xmlData[i]+"' is not expected.", getLineNumberForPosition(xmlData, i));
|
|
@@ -183,6 +193,9 @@ exports.validate = function (xmlData, options) {
|
|
|
183
193
|
return true;
|
|
184
194
|
};
|
|
185
195
|
|
|
196
|
+
function isWhiteSpace(char){
|
|
197
|
+
return char === ' ' || char === '\t' || char === '\n' || char === '\r';
|
|
198
|
+
}
|
|
186
199
|
/**
|
|
187
200
|
* Read Processing insstructions and skip
|
|
188
201
|
* @param {*} xmlData
|
|
@@ -1,43 +1,60 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
//parse Empty Node as self closing node
|
|
3
|
-
const buildOptions = require('
|
|
3
|
+
const buildOptions = require('../util').buildOptions;
|
|
4
|
+
const buildFromOrderedJs = require('./orderedJs2Xml');
|
|
4
5
|
|
|
5
6
|
const defaultOptions = {
|
|
6
7
|
attributeNamePrefix: '@_',
|
|
7
|
-
|
|
8
|
+
attributesGroupName: false,
|
|
8
9
|
textNodeName: '#text',
|
|
9
10
|
ignoreAttributes: true,
|
|
10
|
-
|
|
11
|
-
cdataPositionChar: '\\c',
|
|
11
|
+
cdataPropName: false,
|
|
12
12
|
format: false,
|
|
13
13
|
indentBy: ' ',
|
|
14
|
-
|
|
15
|
-
tagValueProcessor: function(a) {
|
|
14
|
+
suppressEmptyNode: false,
|
|
15
|
+
tagValueProcessor: function(key, a) {
|
|
16
16
|
return a;
|
|
17
17
|
},
|
|
18
|
-
|
|
18
|
+
attributeValueProcessor: function(attrName, a) {
|
|
19
19
|
return a;
|
|
20
20
|
},
|
|
21
|
+
preserveOrder: false,
|
|
22
|
+
commentPropName: false,
|
|
23
|
+
unpairedTags: [],
|
|
24
|
+
entities: {
|
|
25
|
+
">" : { regex: new RegExp(">", "g"), val: ">" },
|
|
26
|
+
"<" : { regex: new RegExp("<", "g"), val: "<" },
|
|
27
|
+
"sQuot" : { regex: new RegExp("\'", "g"), val: "'" },
|
|
28
|
+
"dQuot" : { regex: new RegExp("\"", "g"), val: """ }
|
|
29
|
+
},
|
|
30
|
+
processEntities: true,
|
|
31
|
+
stopNodes: []
|
|
21
32
|
};
|
|
22
33
|
|
|
23
34
|
const props = [
|
|
24
35
|
'attributeNamePrefix',
|
|
25
|
-
'
|
|
36
|
+
'attributesGroupName',
|
|
26
37
|
'textNodeName',
|
|
27
38
|
'ignoreAttributes',
|
|
28
|
-
'
|
|
29
|
-
'cdataPositionChar',
|
|
39
|
+
'cdataPropName',
|
|
30
40
|
'format',
|
|
31
41
|
'indentBy',
|
|
32
|
-
'
|
|
42
|
+
'suppressEmptyNode',
|
|
33
43
|
'tagValueProcessor',
|
|
34
|
-
'
|
|
35
|
-
'
|
|
44
|
+
'attributeValueProcessor',
|
|
45
|
+
'arrayNodeName', //when array as root
|
|
46
|
+
'preserveOrder',
|
|
47
|
+
"commentPropName",
|
|
48
|
+
"unpairedTags",
|
|
49
|
+
"entities",
|
|
50
|
+
"processEntities",
|
|
51
|
+
"stopNodes",
|
|
52
|
+
// 'rootNodeName', //when jsObject have multiple properties on root level
|
|
36
53
|
];
|
|
37
54
|
|
|
38
|
-
function
|
|
55
|
+
function Builder(options) {
|
|
39
56
|
this.options = buildOptions(options, defaultOptions, props);
|
|
40
|
-
if (this.options.ignoreAttributes || this.options.
|
|
57
|
+
if (this.options.ignoreAttributes || this.options.attributesGroupName) {
|
|
41
58
|
this.isAttribute = function(/*a*/) {
|
|
42
59
|
return false;
|
|
43
60
|
};
|
|
@@ -45,15 +62,6 @@ function Parser(options) {
|
|
|
45
62
|
this.attrPrefixLen = this.options.attributeNamePrefix.length;
|
|
46
63
|
this.isAttribute = isAttribute;
|
|
47
64
|
}
|
|
48
|
-
if (this.options.cdataTagName) {
|
|
49
|
-
this.isCDATA = isCDATA;
|
|
50
|
-
} else {
|
|
51
|
-
this.isCDATA = function(/*a*/) {
|
|
52
|
-
return false;
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
this.replaceCDATAstr = replaceCDATAstr;
|
|
56
|
-
this.replaceCDATAarr = replaceCDATAarr;
|
|
57
65
|
|
|
58
66
|
this.processTextOrObjNode = processTextOrObjNode
|
|
59
67
|
|
|
@@ -69,7 +77,7 @@ function Parser(options) {
|
|
|
69
77
|
this.newLine = '';
|
|
70
78
|
}
|
|
71
79
|
|
|
72
|
-
if (this.options.
|
|
80
|
+
if (this.options.suppressEmptyNode) {
|
|
73
81
|
this.buildTextNode = buildEmptyTextNode;
|
|
74
82
|
this.buildObjNode = buildEmptyObjNode;
|
|
75
83
|
} else {
|
|
@@ -79,18 +87,24 @@ function Parser(options) {
|
|
|
79
87
|
|
|
80
88
|
this.buildTextValNode = buildTextValNode;
|
|
81
89
|
this.buildObjectNode = buildObjectNode;
|
|
90
|
+
|
|
91
|
+
this.replaceEntitiesValue = replaceEntitiesValue;
|
|
82
92
|
}
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
if(
|
|
86
|
-
jObj
|
|
87
|
-
|
|
94
|
+
Builder.prototype.build = function(jObj) {
|
|
95
|
+
if(this.options.preserveOrder){
|
|
96
|
+
return buildFromOrderedJs(jObj, this.options);
|
|
97
|
+
}else {
|
|
98
|
+
if(Array.isArray(jObj) && this.options.arrayNodeName && this.options.arrayNodeName.length > 1){
|
|
99
|
+
jObj = {
|
|
100
|
+
[this.options.arrayNodeName] : jObj
|
|
101
|
+
}
|
|
88
102
|
}
|
|
103
|
+
return this.j2x(jObj, 0).val;
|
|
89
104
|
}
|
|
90
|
-
return this.j2x(jObj, 0).val;
|
|
91
105
|
};
|
|
92
106
|
|
|
93
|
-
|
|
107
|
+
Builder.prototype.j2x = function(jObj, level) {
|
|
94
108
|
let attrStr = '';
|
|
95
109
|
let val = '';
|
|
96
110
|
for (let key in jObj) {
|
|
@@ -104,57 +118,42 @@ Parser.prototype.j2x = function(jObj, level) {
|
|
|
104
118
|
//premitive type
|
|
105
119
|
const attr = this.isAttribute(key);
|
|
106
120
|
if (attr) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
} else {
|
|
112
|
-
val += this.replaceCDATAstr('', jObj[key]);
|
|
113
|
-
}
|
|
114
|
-
} else {
|
|
121
|
+
let val = this.options.attributeValueProcessor(attr, '' + jObj[key]);
|
|
122
|
+
val = this.replaceEntitiesValue(val);
|
|
123
|
+
attrStr += ' ' + attr + '="' + val + '"';
|
|
124
|
+
}else {
|
|
115
125
|
//tag value
|
|
116
126
|
if (key === this.options.textNodeName) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
} else {
|
|
120
|
-
val += this.options.tagValueProcessor('' + jObj[key]);
|
|
121
|
-
}
|
|
127
|
+
let newval = this.options.tagValueProcessor(key, '' + jObj[key]);
|
|
128
|
+
val += this.replaceEntitiesValue(newval);
|
|
122
129
|
} else {
|
|
123
130
|
val += this.buildTextNode(jObj[key], key, '', level);
|
|
124
131
|
}
|
|
125
132
|
}
|
|
126
133
|
} else if (Array.isArray(jObj[key])) {
|
|
127
134
|
//repeated nodes
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
const arrLen = jObj[key].length;
|
|
136
|
+
for (let j = 0; j < arrLen; j++) {
|
|
137
|
+
const item = jObj[key][j];
|
|
138
|
+
if (typeof item === 'undefined') {
|
|
139
|
+
// supress undefined node
|
|
140
|
+
} else if (item === null) {
|
|
141
|
+
val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
|
|
142
|
+
} else if (typeof item === 'object') {
|
|
143
|
+
val += this.processTextOrObjNode(item, key, level)
|
|
132
144
|
} else {
|
|
133
|
-
val += this.
|
|
134
|
-
}
|
|
135
|
-
} else {
|
|
136
|
-
//nested nodes
|
|
137
|
-
const arrLen = jObj[key].length;
|
|
138
|
-
for (let j = 0; j < arrLen; j++) {
|
|
139
|
-
const item = jObj[key][j];
|
|
140
|
-
if (typeof item === 'undefined') {
|
|
141
|
-
// supress undefined node
|
|
142
|
-
} else if (item === null) {
|
|
143
|
-
val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
|
|
144
|
-
} else if (typeof item === 'object') {
|
|
145
|
-
val += this.processTextOrObjNode(item, key, level)
|
|
146
|
-
} else {
|
|
147
|
-
val += this.buildTextNode(item, key, '', level);
|
|
148
|
-
}
|
|
145
|
+
val += this.buildTextNode(item, key, '', level);
|
|
149
146
|
}
|
|
150
147
|
}
|
|
151
148
|
} else {
|
|
152
149
|
//nested node
|
|
153
|
-
if (this.options.
|
|
150
|
+
if (this.options.attributesGroupName && key === this.options.attributesGroupName) {
|
|
154
151
|
const Ks = Object.keys(jObj[key]);
|
|
155
152
|
const L = Ks.length;
|
|
156
153
|
for (let j = 0; j < L; j++) {
|
|
157
|
-
|
|
154
|
+
let val = this.options.attributeValueProcessor(Ks[j], '' + jObj[key][Ks[j]]);
|
|
155
|
+
val = this.replaceEntitiesValue(val);
|
|
156
|
+
attrStr += ' ' + Ks[j] + '="' + val + '"';
|
|
158
157
|
}
|
|
159
158
|
} else {
|
|
160
159
|
val += this.processTextOrObjNode(jObj[key], key, level)
|
|
@@ -173,27 +172,6 @@ function processTextOrObjNode (object, key, level) {
|
|
|
173
172
|
}
|
|
174
173
|
}
|
|
175
174
|
|
|
176
|
-
function replaceCDATAstr(str, cdata) {
|
|
177
|
-
str = this.options.tagValueProcessor('' + str);
|
|
178
|
-
if (this.options.cdataPositionChar === '' || str === '') {
|
|
179
|
-
return str + '<![CDATA[' + cdata + ']]' + this.tagEndChar;
|
|
180
|
-
} else {
|
|
181
|
-
return str.replace(this.options.cdataPositionChar, '<![CDATA[' + cdata + ']]' + this.tagEndChar);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function replaceCDATAarr(str, cdata) {
|
|
186
|
-
str = this.options.tagValueProcessor('' + str);
|
|
187
|
-
if (this.options.cdataPositionChar === '' || str === '') {
|
|
188
|
-
return str + '<![CDATA[' + cdata.join(']]><![CDATA[') + ']]' + this.tagEndChar;
|
|
189
|
-
} else {
|
|
190
|
-
for (let v in cdata) {
|
|
191
|
-
str = str.replace(this.options.cdataPositionChar, '<![CDATA[' + cdata[v] + ']]>');
|
|
192
|
-
}
|
|
193
|
-
return str + this.newLine;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
175
|
function buildObjectNode(val, key, attrStr, level) {
|
|
198
176
|
if (attrStr && val.indexOf('<') === -1) {
|
|
199
177
|
return (
|
|
@@ -236,21 +214,36 @@ function buildEmptyObjNode(val, key, attrStr, level) {
|
|
|
236
214
|
}
|
|
237
215
|
|
|
238
216
|
function buildTextValNode(val, key, attrStr, level) {
|
|
217
|
+
let textValue = this.options.tagValueProcessor(key, val);
|
|
218
|
+
textValue = this.replaceEntitiesValue(textValue);
|
|
219
|
+
|
|
239
220
|
return (
|
|
240
221
|
this.indentate(level) +
|
|
241
222
|
'<' +
|
|
242
223
|
key +
|
|
243
224
|
attrStr +
|
|
244
225
|
'>' +
|
|
245
|
-
|
|
226
|
+
textValue +
|
|
246
227
|
'</' +
|
|
247
228
|
key +
|
|
248
229
|
this.tagEndChar
|
|
249
230
|
);
|
|
250
231
|
}
|
|
251
232
|
|
|
233
|
+
function replaceEntitiesValue(textValue){
|
|
234
|
+
if(textValue && textValue.length > 0 && this.options.processEntities){
|
|
235
|
+
for (const entityName in this.options.entities) {
|
|
236
|
+
const entity = this.options.entities[entityName];
|
|
237
|
+
textValue = textValue.replace(entity.regex, entity.val);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return textValue;
|
|
241
|
+
}
|
|
242
|
+
|
|
252
243
|
function buildEmptyTextNode(val, key, attrStr, level) {
|
|
253
|
-
if
|
|
244
|
+
if( val === '' && this.options.unpairedTags.indexOf(key) !== -1){
|
|
245
|
+
return this.indentate(level) + '<' + key + attrStr + this.tagEndChar;
|
|
246
|
+
}else if (val !== '') {
|
|
254
247
|
return this.buildTextValNode(val, key, attrStr, level);
|
|
255
248
|
} else {
|
|
256
249
|
return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
|
|
@@ -269,12 +262,4 @@ function isAttribute(name /*, options*/) {
|
|
|
269
262
|
}
|
|
270
263
|
}
|
|
271
264
|
|
|
272
|
-
|
|
273
|
-
return name === this.options.cdataTagName;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
//formatting
|
|
277
|
-
//indentation
|
|
278
|
-
//\n after each closing or self closing tag
|
|
279
|
-
|
|
280
|
-
module.exports = Parser;
|
|
265
|
+
module.exports = Builder;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const {EOL} = require('os');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {array} jArray
|
|
6
|
+
* @param {any} options
|
|
7
|
+
* @returns
|
|
8
|
+
*/
|
|
9
|
+
function toXml(jArray, options){
|
|
10
|
+
return arrToStr( jArray, options, "", 0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function arrToStr(arr, options, jPath, level){
|
|
14
|
+
let xmlStr = "";
|
|
15
|
+
|
|
16
|
+
let indentation = "";
|
|
17
|
+
if(options.format && options.indentBy.length > 0){//TODO: this logic can be avoided for each call
|
|
18
|
+
indentation = EOL + "" + options.indentBy.repeat(level);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < arr.length; i++) {
|
|
22
|
+
const tagObj = arr[i];
|
|
23
|
+
const tagName = propName(tagObj);
|
|
24
|
+
let newJPath = "";
|
|
25
|
+
if(jPath.length === 0) newJPath = tagName
|
|
26
|
+
else newJPath = `${jPath}.${tagName}`;
|
|
27
|
+
|
|
28
|
+
if(tagName === options.textNodeName){
|
|
29
|
+
let tagText = tagObj[tagName];
|
|
30
|
+
if(!isStopNode(newJPath, options)){
|
|
31
|
+
tagText = options.tagValueProcessor( tagName, tagText);
|
|
32
|
+
tagText = replaceEntitiesValue(tagText, options);
|
|
33
|
+
}
|
|
34
|
+
xmlStr += indentation + tagText;
|
|
35
|
+
continue;
|
|
36
|
+
}else if( tagName === options.cdataPropName){
|
|
37
|
+
xmlStr += indentation + `<![CDATA[${tagObj[tagName][0][options.textNodeName]}]]>`;
|
|
38
|
+
continue;
|
|
39
|
+
}else if( tagName === options.commentPropName){
|
|
40
|
+
xmlStr += indentation + `<!--${tagObj[tagName][0][options.textNodeName]}-->`;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const attStr = attr_to_str(tagObj.attributes, options);
|
|
44
|
+
let tagStart = indentation + `<${tagName}${attStr}`;
|
|
45
|
+
let tagValue = arrToStr(tagObj[tagName], options, newJPath, level + 1);
|
|
46
|
+
if( (!tagValue || tagValue.length === 0) && options.suppressEmptyNode){
|
|
47
|
+
if(options.unpairedTags.indexOf(tagName) !== -1){
|
|
48
|
+
xmlStr += tagStart + ">";
|
|
49
|
+
}else{
|
|
50
|
+
xmlStr += tagStart + "/>";
|
|
51
|
+
}
|
|
52
|
+
}else{
|
|
53
|
+
//TODO: node with only text value should not parse the text value in next line
|
|
54
|
+
xmlStr += tagStart + `>${tagValue}${indentation}</${tagName}>` ;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return xmlStr;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function propName(obj){
|
|
62
|
+
const keys = Object.keys(obj);
|
|
63
|
+
for (let i = 0; i < keys.length; i++) {
|
|
64
|
+
const key = keys[i];
|
|
65
|
+
if(key !== "attributes") return key;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function attr_to_str(attrMap, options){
|
|
70
|
+
let attrStr = "";
|
|
71
|
+
if(attrMap && !options.ignoreAttributes){
|
|
72
|
+
for( attr in attrMap){
|
|
73
|
+
let attrVal = options.attributeValueProcessor(attr, attrMap[attr]);
|
|
74
|
+
attrVal = replaceEntitiesValue(attrVal, options);
|
|
75
|
+
attrStr+= ` ${attr.substr(options.attributeNamePrefix.length)}="${attrVal}"`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return attrStr;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isStopNode(jPath, options){
|
|
82
|
+
jPath = jPath.substr(0,jPath.length - options.textNodeName.length - 1);
|
|
83
|
+
let tagName = jPath.substr(jPath.lastIndexOf(".") + 1);
|
|
84
|
+
for(let index in options.stopNodes){
|
|
85
|
+
if(options.stopNodes[index] === jPath || options.stopNodes[index] === "*."+tagName) return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function replaceEntitiesValue(textValue, options){
|
|
91
|
+
if(textValue && textValue.length > 0 && options.processEntities){
|
|
92
|
+
for (const entityName in options.entities) {
|
|
93
|
+
const entity = options.entities[entityName];
|
|
94
|
+
textValue = textValue.replace(entity.regex, entity.val);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return textValue;
|
|
98
|
+
}
|
|
99
|
+
module.exports = toXml;
|
|
File without changes
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//TODO: handle comments
|
|
2
|
+
function readDocType(xmlData, i){
|
|
3
|
+
|
|
4
|
+
const entities = {};
|
|
5
|
+
if( xmlData[i + 3] === 'O' &&
|
|
6
|
+
xmlData[i + 4] === 'C' &&
|
|
7
|
+
xmlData[i + 5] === 'T' &&
|
|
8
|
+
xmlData[i + 6] === 'Y' &&
|
|
9
|
+
xmlData[i + 7] === 'P' &&
|
|
10
|
+
xmlData[i + 8] === 'E')
|
|
11
|
+
{
|
|
12
|
+
i = i+9;
|
|
13
|
+
let angleBracketsCount = 1;
|
|
14
|
+
let hasBody = false, entity = false, comment = false;
|
|
15
|
+
let exp = "";
|
|
16
|
+
for(;i<xmlData.length;i++){
|
|
17
|
+
if (xmlData[i] === '<') {
|
|
18
|
+
if( hasBody &&
|
|
19
|
+
xmlData[i+1] === '!' &&
|
|
20
|
+
xmlData[i+2] === 'E' &&
|
|
21
|
+
xmlData[i+3] === 'N' &&
|
|
22
|
+
xmlData[i+4] === 'T' &&
|
|
23
|
+
xmlData[i+5] === 'I' &&
|
|
24
|
+
xmlData[i+6] === 'T' &&
|
|
25
|
+
xmlData[i+7] === 'Y'
|
|
26
|
+
){
|
|
27
|
+
i += 7;
|
|
28
|
+
entity = true;
|
|
29
|
+
}else if( hasBody &&
|
|
30
|
+
xmlData[i+1] === '!' &&
|
|
31
|
+
xmlData[i+2] === 'E' &&
|
|
32
|
+
xmlData[i+3] === 'L' &&
|
|
33
|
+
xmlData[i+4] === 'E' &&
|
|
34
|
+
xmlData[i+5] === 'M' &&
|
|
35
|
+
xmlData[i+6] === 'E' &&
|
|
36
|
+
xmlData[i+7] === 'N' &&
|
|
37
|
+
xmlData[i+8] === 'T'
|
|
38
|
+
){
|
|
39
|
+
//Not supported
|
|
40
|
+
i += 8;
|
|
41
|
+
}else if( //comment
|
|
42
|
+
xmlData[i+1] === '!' &&
|
|
43
|
+
xmlData[i+2] === '-' &&
|
|
44
|
+
xmlData[i+3] === '-'
|
|
45
|
+
){
|
|
46
|
+
comment = true;
|
|
47
|
+
}else{
|
|
48
|
+
throw new Error("Invalid DOCTYPE");
|
|
49
|
+
}
|
|
50
|
+
angleBracketsCount++;
|
|
51
|
+
exp = "";
|
|
52
|
+
} else if (xmlData[i] === '>') {
|
|
53
|
+
if(comment){
|
|
54
|
+
if( xmlData[i - 1] === "-" && xmlData[i - 2] === "-"){
|
|
55
|
+
comment = false;
|
|
56
|
+
}else{
|
|
57
|
+
throw new Error(`Invalid XML comment in DOCTYPE`);
|
|
58
|
+
}
|
|
59
|
+
}else if(entity){
|
|
60
|
+
parseEntityExp(exp, entities);
|
|
61
|
+
entity = false;
|
|
62
|
+
}
|
|
63
|
+
angleBracketsCount--;
|
|
64
|
+
if (angleBracketsCount === 0) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}else if( xmlData[i] === '['){
|
|
68
|
+
hasBody = true;
|
|
69
|
+
}else{
|
|
70
|
+
exp += xmlData[i];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if(angleBracketsCount !== 0){
|
|
74
|
+
throw new Error(`Unclosed DOCTYPE`);
|
|
75
|
+
}
|
|
76
|
+
}else{
|
|
77
|
+
throw new Error(`Invalid Tag instead of DOCTYPE`);
|
|
78
|
+
}
|
|
79
|
+
return {entities, i};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const entityRegex = RegExp("^\\s([a-zA-z0-0]+)[ \t](['\"])([^&]+)\\2");
|
|
83
|
+
function parseEntityExp(exp, entities){
|
|
84
|
+
const match = entityRegex.exec(exp);
|
|
85
|
+
if(match){
|
|
86
|
+
entities[ match[1] ] = {
|
|
87
|
+
regx : RegExp( `&${match[1]};`,"g"),
|
|
88
|
+
val: match[3]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
module.exports = readDocType;
|