fast-xml-parser 3.21.0 → 4.0.0-beta.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 +428 -0
- package/README.md +85 -276
- package/package.json +8 -35
- 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 +77 -0
- package/src/fxp.js +11 -0
- package/src/util.js +2 -21
- package/src/validator.js +16 -3
- package/src/xmlbuilder/json2xml.js +263 -0
- package/src/xmlbuilder/orderedJs2Xml.js +84 -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 +486 -0
- package/src/xmlparser/XMLParser.js +37 -0
- package/src/xmlparser/node2json.js +101 -0
- package/src/xmlparser/xmlNode.js +23 -0
- package/src/json2xml.js +0 -274
- 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,77 @@
|
|
|
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
|
+
tagValueProcessor: (name: string, value: string) => string;
|
|
50
|
+
attributeValueProcessor: (name: string, value: string) => string;
|
|
51
|
+
processEntities: boolean;
|
|
52
|
+
};
|
|
53
|
+
type XmlBuilderOptionsOptional = Partial<XmlBuilderOptions>;
|
|
54
|
+
|
|
55
|
+
type ESchema = string | object | Array<string|object>;
|
|
56
|
+
|
|
57
|
+
type ValidationError = {
|
|
58
|
+
err: {
|
|
59
|
+
code: string;
|
|
60
|
+
msg: string,
|
|
61
|
+
line: number,
|
|
62
|
+
col: number
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export class XMLParser {
|
|
67
|
+
constructor(options?: X2jOptionsOptional);
|
|
68
|
+
parse(xmlData: string | Buffer ,validationOptions?: validationOptionsOptional | boolean): any;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class XMLValidator{
|
|
72
|
+
static validate( xmlData: string, options?: validationOptionsOptional): true | ValidationError;
|
|
73
|
+
}
|
|
74
|
+
export class XMLBuilder {
|
|
75
|
+
constructor(options: XmlBuilderOptionsOptional);
|
|
76
|
+
build(jObj: any): any;
|
|
77
|
+
}
|
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
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
//parse Empty Node as self closing node
|
|
3
|
+
const buildOptions = require('../util').buildOptions;
|
|
4
|
+
const buildFromOrderedJs = require('./orderedJs2Xml');
|
|
5
|
+
|
|
6
|
+
const defaultOptions = {
|
|
7
|
+
attributeNamePrefix: '@_',
|
|
8
|
+
attributesGroupName: false,
|
|
9
|
+
textNodeName: '#text',
|
|
10
|
+
ignoreAttributes: true,
|
|
11
|
+
cdataPropName: false,
|
|
12
|
+
format: false,
|
|
13
|
+
indentBy: ' ',
|
|
14
|
+
suppressEmptyNode: false,
|
|
15
|
+
tagValueProcessor: function(key, a) {
|
|
16
|
+
return a;
|
|
17
|
+
},
|
|
18
|
+
attributeValueProcessor: function(attrName, a) {
|
|
19
|
+
return a;
|
|
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
|
+
};
|
|
32
|
+
|
|
33
|
+
const props = [
|
|
34
|
+
'attributeNamePrefix',
|
|
35
|
+
'attributesGroupName',
|
|
36
|
+
'textNodeName',
|
|
37
|
+
'ignoreAttributes',
|
|
38
|
+
'cdataPropName',
|
|
39
|
+
'format',
|
|
40
|
+
'indentBy',
|
|
41
|
+
'suppressEmptyNode',
|
|
42
|
+
'tagValueProcessor',
|
|
43
|
+
'attributeValueProcessor',
|
|
44
|
+
'arrayNodeName', //when array as root
|
|
45
|
+
'preserveOrder',
|
|
46
|
+
"commentPropName",
|
|
47
|
+
"unpairedTags",
|
|
48
|
+
"entities",
|
|
49
|
+
"processEntities",
|
|
50
|
+
// 'rootNodeName', //when jsObject have multiple properties on root level
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
function Builder(options) {
|
|
54
|
+
this.options = buildOptions(options, defaultOptions, props);
|
|
55
|
+
if (this.options.ignoreAttributes || this.options.attributesGroupName) {
|
|
56
|
+
this.isAttribute = function(/*a*/) {
|
|
57
|
+
return false;
|
|
58
|
+
};
|
|
59
|
+
} else {
|
|
60
|
+
this.attrPrefixLen = this.options.attributeNamePrefix.length;
|
|
61
|
+
this.isAttribute = isAttribute;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.processTextOrObjNode = processTextOrObjNode
|
|
65
|
+
|
|
66
|
+
if (this.options.format) {
|
|
67
|
+
this.indentate = indentate;
|
|
68
|
+
this.tagEndChar = '>\n';
|
|
69
|
+
this.newLine = '\n';
|
|
70
|
+
} else {
|
|
71
|
+
this.indentate = function() {
|
|
72
|
+
return '';
|
|
73
|
+
};
|
|
74
|
+
this.tagEndChar = '>';
|
|
75
|
+
this.newLine = '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this.options.suppressEmptyNode) {
|
|
79
|
+
this.buildTextNode = buildEmptyTextNode;
|
|
80
|
+
this.buildObjNode = buildEmptyObjNode;
|
|
81
|
+
} else {
|
|
82
|
+
this.buildTextNode = buildTextValNode;
|
|
83
|
+
this.buildObjNode = buildObjectNode;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.buildTextValNode = buildTextValNode;
|
|
87
|
+
this.buildObjectNode = buildObjectNode;
|
|
88
|
+
|
|
89
|
+
this.replaceEntitiesValue = replaceEntitiesValue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Builder.prototype.build = function(jObj) {
|
|
93
|
+
if(this.options.preserveOrder){
|
|
94
|
+
return buildFromOrderedJs(jObj, this.options);
|
|
95
|
+
}else {
|
|
96
|
+
if(Array.isArray(jObj) && this.options.arrayNodeName && this.options.arrayNodeName.length > 1){
|
|
97
|
+
jObj = {
|
|
98
|
+
[this.options.arrayNodeName] : jObj
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return this.j2x(jObj, 0).val;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
Builder.prototype.j2x = function(jObj, level) {
|
|
106
|
+
let attrStr = '';
|
|
107
|
+
let val = '';
|
|
108
|
+
for (let key in jObj) {
|
|
109
|
+
if (typeof jObj[key] === 'undefined') {
|
|
110
|
+
// supress undefined node
|
|
111
|
+
} else if (jObj[key] === null) {
|
|
112
|
+
val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
|
|
113
|
+
} else if (jObj[key] instanceof Date) {
|
|
114
|
+
val += this.buildTextNode(jObj[key], key, '', level);
|
|
115
|
+
} else if (typeof jObj[key] !== 'object') {
|
|
116
|
+
//premitive type
|
|
117
|
+
const attr = this.isAttribute(key);
|
|
118
|
+
if (attr) {
|
|
119
|
+
let val = this.options.attributeValueProcessor(attr, '' + jObj[key]);
|
|
120
|
+
val = this.replaceEntitiesValue(val);
|
|
121
|
+
attrStr += ' ' + attr + '="' + val + '"';
|
|
122
|
+
}else {
|
|
123
|
+
//tag value
|
|
124
|
+
if (key === this.options.textNodeName) {
|
|
125
|
+
let newval = this.options.tagValueProcessor(key, '' + jObj[key]);
|
|
126
|
+
val += this.replaceEntitiesValue(newval);
|
|
127
|
+
} else {
|
|
128
|
+
val += this.buildTextNode(jObj[key], key, '', level);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} else if (Array.isArray(jObj[key])) {
|
|
132
|
+
//repeated nodes
|
|
133
|
+
const arrLen = jObj[key].length;
|
|
134
|
+
for (let j = 0; j < arrLen; j++) {
|
|
135
|
+
const item = jObj[key][j];
|
|
136
|
+
if (typeof item === 'undefined') {
|
|
137
|
+
// supress undefined node
|
|
138
|
+
} else if (item === null) {
|
|
139
|
+
val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
|
|
140
|
+
} else if (typeof item === 'object') {
|
|
141
|
+
val += this.processTextOrObjNode(item, key, level)
|
|
142
|
+
} else {
|
|
143
|
+
val += this.buildTextNode(item, key, '', level);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
//nested node
|
|
148
|
+
if (this.options.attributesGroupName && key === this.options.attributesGroupName) {
|
|
149
|
+
const Ks = Object.keys(jObj[key]);
|
|
150
|
+
const L = Ks.length;
|
|
151
|
+
for (let j = 0; j < L; j++) {
|
|
152
|
+
let val = this.options.attributeValueProcessor(Ks[j], '' + jObj[key][Ks[j]]);
|
|
153
|
+
val = this.replaceEntitiesValue(val);
|
|
154
|
+
attrStr += ' ' + Ks[j] + '="' + val + '"';
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
val += this.processTextOrObjNode(jObj[key], key, level)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {attrStr: attrStr, val: val};
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
function processTextOrObjNode (object, key, level) {
|
|
165
|
+
const result = this.j2x(object, level + 1);
|
|
166
|
+
if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
|
|
167
|
+
return this.buildTextNode(result.val, key, result.attrStr, level);
|
|
168
|
+
} else {
|
|
169
|
+
return this.buildObjNode(result.val, key, result.attrStr, level);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function buildObjectNode(val, key, attrStr, level) {
|
|
174
|
+
if (attrStr && val.indexOf('<') === -1) {
|
|
175
|
+
return (
|
|
176
|
+
this.indentate(level) +
|
|
177
|
+
'<' +
|
|
178
|
+
key +
|
|
179
|
+
attrStr +
|
|
180
|
+
'>' +
|
|
181
|
+
val +
|
|
182
|
+
//+ this.newLine
|
|
183
|
+
// + this.indentate(level)
|
|
184
|
+
'</' +
|
|
185
|
+
key +
|
|
186
|
+
this.tagEndChar
|
|
187
|
+
);
|
|
188
|
+
} else {
|
|
189
|
+
return (
|
|
190
|
+
this.indentate(level) +
|
|
191
|
+
'<' +
|
|
192
|
+
key +
|
|
193
|
+
attrStr +
|
|
194
|
+
this.tagEndChar +
|
|
195
|
+
val +
|
|
196
|
+
//+ this.newLine
|
|
197
|
+
this.indentate(level) +
|
|
198
|
+
'</' +
|
|
199
|
+
key +
|
|
200
|
+
this.tagEndChar
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function buildEmptyObjNode(val, key, attrStr, level) {
|
|
206
|
+
if (val !== '') {
|
|
207
|
+
return this.buildObjectNode(val, key, attrStr, level);
|
|
208
|
+
} else {
|
|
209
|
+
return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
|
|
210
|
+
//+ this.newLine
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function buildTextValNode(val, key, attrStr, level) {
|
|
215
|
+
let textValue = this.options.tagValueProcessor(key, val);
|
|
216
|
+
textValue = this.replaceEntitiesValue(textValue);
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
this.indentate(level) +
|
|
220
|
+
'<' +
|
|
221
|
+
key +
|
|
222
|
+
attrStr +
|
|
223
|
+
'>' +
|
|
224
|
+
textValue +
|
|
225
|
+
'</' +
|
|
226
|
+
key +
|
|
227
|
+
this.tagEndChar
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function replaceEntitiesValue(textValue){
|
|
232
|
+
if(textValue && textValue.length > 0 && this.options.processEntities){
|
|
233
|
+
for (const entityName in this.options.entities) {
|
|
234
|
+
const entity = this.options.entities[entityName];
|
|
235
|
+
textValue = textValue.replace(entity.regex, entity.val);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return textValue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function buildEmptyTextNode(val, key, attrStr, level) {
|
|
242
|
+
if( val === '' && this.options.unpairedTags.indexOf(key) !== -1){
|
|
243
|
+
return this.indentate(level) + '<' + key + attrStr + this.tagEndChar;
|
|
244
|
+
}else if (val !== '') {
|
|
245
|
+
return this.buildTextValNode(val, key, attrStr, level);
|
|
246
|
+
} else {
|
|
247
|
+
return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function indentate(level) {
|
|
252
|
+
return this.options.indentBy.repeat(level);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function isAttribute(name /*, options*/) {
|
|
256
|
+
if (name.startsWith(this.options.attributeNamePrefix)) {
|
|
257
|
+
return name.substr(this.attrPrefixLen);
|
|
258
|
+
} else {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = Builder;
|
|
@@ -0,0 +1,84 @@
|
|
|
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, 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
|
+
|
|
25
|
+
if(tagName === options.textNodeName){
|
|
26
|
+
let tagText = options.tagValueProcessor( tagName, tagObj[tagName]);
|
|
27
|
+
tagText = replaceEntitiesValue(tagText, options);
|
|
28
|
+
xmlStr += indentation + tagText;
|
|
29
|
+
continue;
|
|
30
|
+
}else if( tagName === options.cdataPropName){
|
|
31
|
+
xmlStr += indentation + `<![CDATA[${tagObj[tagName][0][options.textNodeName]}]]>`;
|
|
32
|
+
continue;
|
|
33
|
+
}else if( tagName === options.commentPropName){
|
|
34
|
+
xmlStr += indentation + `<!--${tagObj[tagName][0][options.textNodeName]}-->`;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const attStr = attr_to_str(tagObj.attributes, options);
|
|
38
|
+
let tagStart = indentation + `<${tagName}${attStr}`;
|
|
39
|
+
let tagValue = arrToStr(tagObj[tagName], options, level + 1);
|
|
40
|
+
if( (!tagValue || tagValue.length === 0) && options.suppressEmptyNode){
|
|
41
|
+
if(options.unpairedTags.indexOf(tagName) !== -1){
|
|
42
|
+
xmlStr += tagStart + ">";
|
|
43
|
+
}else{
|
|
44
|
+
xmlStr += tagStart + "/>";
|
|
45
|
+
}
|
|
46
|
+
}else{
|
|
47
|
+
//TODO: node with only text value should not parse the text value in next line
|
|
48
|
+
xmlStr += tagStart + `>${tagValue}${indentation}</${tagName}>` ;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return xmlStr;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function propName(obj){
|
|
56
|
+
const keys = Object.keys(obj);
|
|
57
|
+
for (let i = 0; i < keys.length; i++) {
|
|
58
|
+
const key = keys[i];
|
|
59
|
+
if(key !== "attributes") return key;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function attr_to_str(attrMap, options){
|
|
64
|
+
let attrStr = "";
|
|
65
|
+
if(attrMap && !options.ignoreAttributes){
|
|
66
|
+
for( attr in attrMap){
|
|
67
|
+
let attrVal = options.attributeValueProcessor(attr, attrMap[attr]);
|
|
68
|
+
attrVal = replaceEntitiesValue(attrVal, options);
|
|
69
|
+
attrStr+= ` ${attr.substr(options.attributeNamePrefix.length)}="${attrVal}"`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return attrStr;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function replaceEntitiesValue(textValue, options){
|
|
76
|
+
if(textValue && textValue.length > 0 && options.processEntities){
|
|
77
|
+
for (const entityName in options.entities) {
|
|
78
|
+
const entity = options.entities[entityName];
|
|
79
|
+
textValue = textValue.replace(entity.regex, entity.val);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return textValue;
|
|
83
|
+
}
|
|
84
|
+
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;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
|
|
2
|
+
const defaultOptions = {
|
|
3
|
+
preserveOrder: false,
|
|
4
|
+
attributeNamePrefix: '@_',
|
|
5
|
+
attributesGroupName: false,
|
|
6
|
+
textNodeName: '#text',
|
|
7
|
+
ignoreAttributes: true,
|
|
8
|
+
removeNSPrefix: false, // remove NS from tag name or attribute name if true
|
|
9
|
+
allowBooleanAttributes: false, //a tag can have attributes without any value
|
|
10
|
+
//ignoreRootElement : false,
|
|
11
|
+
parseTagValue: true,
|
|
12
|
+
parseAttributeValue: false,
|
|
13
|
+
trimValues: true, //Trim string values of tag and attributes
|
|
14
|
+
cdataPropName: false,
|
|
15
|
+
numberParseOptions: {
|
|
16
|
+
hex: true,
|
|
17
|
+
leadingZeros: true
|
|
18
|
+
},
|
|
19
|
+
tagValueProcessor: function(tagName, val) {
|
|
20
|
+
return val;
|
|
21
|
+
},
|
|
22
|
+
attributeValueProcessor: function(attrName, val) {
|
|
23
|
+
return val;
|
|
24
|
+
},
|
|
25
|
+
stopNodes: [], //nested tags will not be parsed even for errors
|
|
26
|
+
alwaysCreateTextNode: false,
|
|
27
|
+
isArray: () => false,
|
|
28
|
+
commentPropName: false,
|
|
29
|
+
unpairedTags: [],
|
|
30
|
+
processEntities: true,
|
|
31
|
+
htmlEntities: false,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const props = [
|
|
35
|
+
'preserveOrder',
|
|
36
|
+
'attributeNamePrefix',
|
|
37
|
+
'attributesGroupName',
|
|
38
|
+
'textNodeName',
|
|
39
|
+
'ignoreAttributes',
|
|
40
|
+
'removeNSPrefix',
|
|
41
|
+
'allowBooleanAttributes',
|
|
42
|
+
'parseTagValue',
|
|
43
|
+
'parseAttributeValue',
|
|
44
|
+
'trimValues',
|
|
45
|
+
'cdataPropName',
|
|
46
|
+
'tagValueProcessor',
|
|
47
|
+
'attributeValueProcessor',
|
|
48
|
+
'numberParseOptions',
|
|
49
|
+
'stopNodes',
|
|
50
|
+
'alwaysCreateTextNode',
|
|
51
|
+
'isArray',
|
|
52
|
+
'commentPropName',
|
|
53
|
+
'unpairedTags',
|
|
54
|
+
'processEntities',
|
|
55
|
+
'htmlEntities'
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const util = require('../util');
|
|
59
|
+
|
|
60
|
+
const buildOptions = function(options) {
|
|
61
|
+
return util.buildOptions(options, defaultOptions, props);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
exports.buildOptions = buildOptions;
|
|
65
|
+
exports.defaultOptions = defaultOptions;
|
|
66
|
+
exports.props = props;
|