fast-xml-parser 3.20.0 → 4.0.0-beta.0
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 +409 -0
- package/README.md +79 -268
- package/package.json +10 -39
- 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 +71 -0
- package/src/fxp.js +11 -0
- package/src/util.js +4 -22
- package/src/validator.js +46 -26
- package/src/{json2xml.js → xmlbuilder/json2xml.js} +55 -91
- package/src/xmlbuilder/orderedJs2Xml.js +58 -0
- package/src/xmlbuilder/prettifyJs2Xml.js +0 -0
- package/src/xmlparser/OptionsBuilder.js +58 -0
- package/src/xmlparser/OrderedObjParser.js +373 -0
- package/src/xmlparser/XMLParser.js +36 -0
- package/src/xmlparser/node2json.js +95 -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 -78
- package/src/parser.js +0 -76
- package/src/xmlNode.js +0 -17
- package/src/xmlstr2xmlnode.js +0 -337
package/src/fxp.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
cdataTagName: false | string;
|
|
13
|
+
tagValueProcessor: (tagName: string, tagValue: string, jPath: string, hasAttributes: boolean, isLeafNode: boolean) => string;
|
|
14
|
+
attributeValueProcessor: (attrName: string, attrValue: string, jPath: string) => string;
|
|
15
|
+
numberParseOptions: strnumOptions;
|
|
16
|
+
stopNodes: string[];
|
|
17
|
+
alwaysCreateTextNode: boolean;
|
|
18
|
+
isArray: (tagName: string, jPath: string, isLeafNode: boolean, isAttribute: boolean) => boolean;
|
|
19
|
+
};
|
|
20
|
+
type strnumOptions = {
|
|
21
|
+
hex: boolean;
|
|
22
|
+
leadingZeros: boolean,
|
|
23
|
+
skipLike?: RegExp
|
|
24
|
+
}
|
|
25
|
+
type X2jOptionsOptional = Partial<X2jOptions>;
|
|
26
|
+
type validationOptions = {
|
|
27
|
+
allowBooleanAttributes: boolean;
|
|
28
|
+
};
|
|
29
|
+
type validationOptionsOptional = Partial<validationOptions>;
|
|
30
|
+
|
|
31
|
+
type XmlBuilderOptions = {
|
|
32
|
+
attributeNamePrefix: string;
|
|
33
|
+
attributesGroupName: false | string;
|
|
34
|
+
textNodeName: string;
|
|
35
|
+
ignoreAttributes: boolean;
|
|
36
|
+
cdataTagName: false | string;
|
|
37
|
+
format: boolean;
|
|
38
|
+
indentBy: string;
|
|
39
|
+
arrayNodeName: string;
|
|
40
|
+
suppressEmptyNode: boolean;
|
|
41
|
+
preserveOrder: boolean;
|
|
42
|
+
tagValueProcessor: (name: string, value: string) => string;
|
|
43
|
+
attributeValueProcessor: (name: string, value: string) => string;
|
|
44
|
+
};
|
|
45
|
+
type XmlBuilderOptionsOptional = Partial<XmlBuilderOptions>;
|
|
46
|
+
|
|
47
|
+
type ESchema = string | object | Array<string|object>;
|
|
48
|
+
|
|
49
|
+
type ValidationError = {
|
|
50
|
+
err: {
|
|
51
|
+
code: string;
|
|
52
|
+
msg: string,
|
|
53
|
+
line: number,
|
|
54
|
+
col: number
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export class XMLParser {
|
|
59
|
+
constructor(options?: X2jOptionsOptional);
|
|
60
|
+
parse(xmlData: string | Buffer ,validationOptions?: validationOptionsOptional | boolean);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function XMLValidator(
|
|
64
|
+
xmlData: string,
|
|
65
|
+
options?: validationOptionsOptional
|
|
66
|
+
): true | ValidationError;
|
|
67
|
+
|
|
68
|
+
export class XMLBuilder {
|
|
69
|
+
constructor(options: XmlBuilderOptionsOptional);
|
|
70
|
+
parse(options: any): any;
|
|
71
|
+
}
|
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
|
@@ -10,6 +10,7 @@ const getAllMatches = function(string, regex) {
|
|
|
10
10
|
let match = regex.exec(string);
|
|
11
11
|
while (match) {
|
|
12
12
|
const allmatches = [];
|
|
13
|
+
allmatches.startIndex = regex.lastIndex - match[0].length;
|
|
13
14
|
const len = match.length;
|
|
14
15
|
for (let index = 0; index < len; index++) {
|
|
15
16
|
allmatches.push(match[index]);
|
|
@@ -66,8 +67,8 @@ exports.getValue = function(v) {
|
|
|
66
67
|
// const fakeCall = function(a) {return a;};
|
|
67
68
|
// const fakeCallNoReturn = function() {};
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
const buildOptions = function(options, defaultOptions, props) {
|
|
71
|
+
let newOptions = {};
|
|
71
72
|
if (!options) {
|
|
72
73
|
return defaultOptions; //if there are not options
|
|
73
74
|
}
|
|
@@ -82,26 +83,7 @@ exports.buildOptions = function(options, defaultOptions, props) {
|
|
|
82
83
|
return newOptions;
|
|
83
84
|
};
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
* Check if a tag name should be treated as array
|
|
87
|
-
*
|
|
88
|
-
* @param tagName the node tagname
|
|
89
|
-
* @param arrayMode the array mode option
|
|
90
|
-
* @param parentTagName the parent tag name
|
|
91
|
-
* @returns {boolean} true if node should be parsed as array
|
|
92
|
-
*/
|
|
93
|
-
exports.isTagNameInArrayMode = function (tagName, arrayMode, parentTagName) {
|
|
94
|
-
if (arrayMode === false) {
|
|
95
|
-
return false;
|
|
96
|
-
} else if (arrayMode instanceof RegExp) {
|
|
97
|
-
return arrayMode.test(tagName);
|
|
98
|
-
} else if (typeof arrayMode === 'function') {
|
|
99
|
-
return !!arrayMode(tagName, parentTagName);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return arrayMode === "strict";
|
|
103
|
-
}
|
|
104
|
-
|
|
86
|
+
exports.buildOptions = buildOptions;
|
|
105
87
|
exports.isName = isName;
|
|
106
88
|
exports.getAllMatches = getAllMatches;
|
|
107
89
|
exports.nameRegexp = nameRegexp;
|
package/src/validator.js
CHANGED
|
@@ -25,7 +25,7 @@ exports.validate = function (xmlData, options) {
|
|
|
25
25
|
// check for byte order mark (BOM)
|
|
26
26
|
xmlData = xmlData.substr(1);
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
for (let i = 0; i < xmlData.length; i++) {
|
|
30
30
|
|
|
31
31
|
if (xmlData[i] === '<' && xmlData[i+1] === '?') {
|
|
@@ -35,7 +35,7 @@ exports.validate = function (xmlData, options) {
|
|
|
35
35
|
}else if (xmlData[i] === '<') {
|
|
36
36
|
//starting of tag
|
|
37
37
|
//read until you reach to '>' avoiding any '>' in attribute value
|
|
38
|
-
|
|
38
|
+
let tagStartPos = i;
|
|
39
39
|
i++;
|
|
40
40
|
|
|
41
41
|
if (xmlData[i] === '!') {
|
|
@@ -71,7 +71,7 @@ exports.validate = function (xmlData, options) {
|
|
|
71
71
|
if (!validateTagName(tagName)) {
|
|
72
72
|
let msg;
|
|
73
73
|
if (tagName.trim().length === 0) {
|
|
74
|
-
msg = "
|
|
74
|
+
msg = "Invalid space after '<'.";
|
|
75
75
|
} else {
|
|
76
76
|
msg = "Tag '"+tagName+"' is an invalid name.";
|
|
77
77
|
}
|
|
@@ -87,6 +87,7 @@ exports.validate = function (xmlData, options) {
|
|
|
87
87
|
|
|
88
88
|
if (attrStr[attrStr.length - 1] === '/') {
|
|
89
89
|
//self closing tag
|
|
90
|
+
const attrStrStart = i - attrStr.length;
|
|
90
91
|
attrStr = attrStr.substring(0, attrStr.length - 1);
|
|
91
92
|
const isValid = validateAttributeString(attrStr, options);
|
|
92
93
|
if (isValid === true) {
|
|
@@ -96,17 +97,20 @@ exports.validate = function (xmlData, options) {
|
|
|
96
97
|
//the result from the nested function returns the position of the error within the attribute
|
|
97
98
|
//in order to get the 'true' error line, we need to calculate the position where the attribute begins (i - attrStr.length) and then add the position within the attribute
|
|
98
99
|
//this gives us the absolute index in the entire xml, which we can use to find the line at last
|
|
99
|
-
return getErrorObject(isValid.err.code, isValid.err.msg, getLineNumberForPosition(xmlData,
|
|
100
|
+
return getErrorObject(isValid.err.code, isValid.err.msg, getLineNumberForPosition(xmlData, attrStrStart + isValid.err.line));
|
|
100
101
|
}
|
|
101
102
|
} else if (closingTag) {
|
|
102
103
|
if (!result.tagClosed) {
|
|
103
104
|
return getErrorObject('InvalidTag', "Closing tag '"+tagName+"' doesn't have proper closing.", getLineNumberForPosition(xmlData, i));
|
|
104
105
|
} else if (attrStr.trim().length > 0) {
|
|
105
|
-
return getErrorObject('InvalidTag', "Closing tag '"+tagName+"' can't have attributes or invalid starting.", getLineNumberForPosition(xmlData,
|
|
106
|
+
return getErrorObject('InvalidTag', "Closing tag '"+tagName+"' can't have attributes or invalid starting.", getLineNumberForPosition(xmlData, tagStartPos));
|
|
106
107
|
} else {
|
|
107
108
|
const otg = tags.pop();
|
|
108
|
-
if (tagName !== otg) {
|
|
109
|
-
|
|
109
|
+
if (tagName !== otg.tagName) {
|
|
110
|
+
let openPos = getLineNumberForPosition(xmlData, otg.tagStartPos);
|
|
111
|
+
return getErrorObject('InvalidTag',
|
|
112
|
+
"Expected closing tag '"+otg.tagName+"' (opened in line "+openPos.line+", col "+openPos.col+") instead of closing tag '"+tagName+"'.",
|
|
113
|
+
getLineNumberForPosition(xmlData, tagStartPos));
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
//when there are no more tags, we reached the root level.
|
|
@@ -127,7 +131,7 @@ exports.validate = function (xmlData, options) {
|
|
|
127
131
|
if (reachedRoot === true) {
|
|
128
132
|
return getErrorObject('InvalidXml', 'Multiple possible root nodes found.', getLineNumberForPosition(xmlData, i));
|
|
129
133
|
} else {
|
|
130
|
-
tags.push(tagName);
|
|
134
|
+
tags.push({tagName, tagStartPos});
|
|
131
135
|
}
|
|
132
136
|
tagFound = true;
|
|
133
137
|
}
|
|
@@ -152,6 +156,10 @@ exports.validate = function (xmlData, options) {
|
|
|
152
156
|
if (afterAmp == -1)
|
|
153
157
|
return getErrorObject('InvalidChar', "char '&' is not expected.", getLineNumberForPosition(xmlData, i));
|
|
154
158
|
i = afterAmp;
|
|
159
|
+
}else{
|
|
160
|
+
if (reachedRoot === true && !isWhiteSpace(xmlData[i])) {
|
|
161
|
+
return getErrorObject('InvalidXml', "Extra text at the end", getLineNumberForPosition(xmlData, i));
|
|
162
|
+
}
|
|
155
163
|
}
|
|
156
164
|
} //end of reading tag text value
|
|
157
165
|
if (xmlData[i] === '<') {
|
|
@@ -159,7 +167,7 @@ exports.validate = function (xmlData, options) {
|
|
|
159
167
|
}
|
|
160
168
|
}
|
|
161
169
|
} else {
|
|
162
|
-
if (
|
|
170
|
+
if ( isWhiteSpace(xmlData[i])) {
|
|
163
171
|
continue;
|
|
164
172
|
}
|
|
165
173
|
return getErrorObject('InvalidChar', "char '"+xmlData[i]+"' is not expected.", getLineNumberForPosition(xmlData, i));
|
|
@@ -168,24 +176,31 @@ exports.validate = function (xmlData, options) {
|
|
|
168
176
|
|
|
169
177
|
if (!tagFound) {
|
|
170
178
|
return getErrorObject('InvalidXml', 'Start tag expected.', 1);
|
|
171
|
-
}
|
|
172
|
-
|
|
179
|
+
}else if (tags.length == 1) {
|
|
180
|
+
return getErrorObject('InvalidTag', "Unclosed tag '"+tags[0].tagName+"'.", getLineNumberForPosition(xmlData, tags[0].tagStartPos));
|
|
181
|
+
}else if (tags.length > 0) {
|
|
182
|
+
return getErrorObject('InvalidXml', "Invalid '"+
|
|
183
|
+
JSON.stringify(tags.map(t => t.tagName), null, 4).replace(/\r?\n/g, '')+
|
|
184
|
+
"' found.", {line: 1, col: 1});
|
|
173
185
|
}
|
|
174
186
|
|
|
175
187
|
return true;
|
|
176
188
|
};
|
|
177
189
|
|
|
190
|
+
function isWhiteSpace(char){
|
|
191
|
+
return char === ' ' || char === '\t' || char === '\n' || char === '\r';
|
|
192
|
+
}
|
|
178
193
|
/**
|
|
179
194
|
* Read Processing insstructions and skip
|
|
180
195
|
* @param {*} xmlData
|
|
181
196
|
* @param {*} i
|
|
182
197
|
*/
|
|
183
198
|
function readPI(xmlData, i) {
|
|
184
|
-
|
|
199
|
+
const start = i;
|
|
185
200
|
for (; i < xmlData.length; i++) {
|
|
186
201
|
if (xmlData[i] == '?' || xmlData[i] == ' ') {
|
|
187
202
|
//tagname
|
|
188
|
-
|
|
203
|
+
const tagname = xmlData.substr(start, i - start);
|
|
189
204
|
if (i > 5 && tagname === 'xml') {
|
|
190
205
|
return getErrorObject('InvalidXml', 'XML declaration allowed only at the start of the document.', getLineNumberForPosition(xmlData, i));
|
|
191
206
|
} else if (xmlData[i] == '?' && xmlData[i + 1] == '>') {
|
|
@@ -251,8 +266,8 @@ function readCommentAndCDATA(xmlData, i) {
|
|
|
251
266
|
return i;
|
|
252
267
|
}
|
|
253
268
|
|
|
254
|
-
|
|
255
|
-
|
|
269
|
+
const doubleQuote = '"';
|
|
270
|
+
const singleQuote = "'";
|
|
256
271
|
|
|
257
272
|
/**
|
|
258
273
|
* Keep reading xmlData until '<' is found outside the attribute value.
|
|
@@ -269,7 +284,6 @@ function readAttributeStr(xmlData, i) {
|
|
|
269
284
|
startChar = xmlData[i];
|
|
270
285
|
} else if (startChar !== xmlData[i]) {
|
|
271
286
|
//if vaue is enclosed with double quote then single quotes are allowed inside the value and vice versa
|
|
272
|
-
continue;
|
|
273
287
|
} else {
|
|
274
288
|
startChar = '';
|
|
275
289
|
}
|
|
@@ -310,23 +324,23 @@ function validateAttributeString(attrStr, options) {
|
|
|
310
324
|
for (let i = 0; i < matches.length; i++) {
|
|
311
325
|
if (matches[i][1].length === 0) {
|
|
312
326
|
//nospace before attribute name: a="sd"b="saf"
|
|
313
|
-
return getErrorObject('InvalidAttr', "Attribute '"+matches[i][2]+"' has no space in starting.", getPositionFromMatch(
|
|
327
|
+
return getErrorObject('InvalidAttr', "Attribute '"+matches[i][2]+"' has no space in starting.", getPositionFromMatch(matches[i]))
|
|
314
328
|
} else if (matches[i][3] === undefined && !options.allowBooleanAttributes) {
|
|
315
329
|
//independent attribute: ab
|
|
316
|
-
return getErrorObject('InvalidAttr', "boolean attribute '"+matches[i][2]+"' is not allowed.", getPositionFromMatch(
|
|
330
|
+
return getErrorObject('InvalidAttr', "boolean attribute '"+matches[i][2]+"' is not allowed.", getPositionFromMatch(matches[i]));
|
|
317
331
|
}
|
|
318
332
|
/* else if(matches[i][6] === undefined){//attribute without value: ab=
|
|
319
333
|
return { err: { code:"InvalidAttr",msg:"attribute " + matches[i][2] + " has no value assigned."}};
|
|
320
334
|
} */
|
|
321
335
|
const attrName = matches[i][2];
|
|
322
336
|
if (!validateAttrName(attrName)) {
|
|
323
|
-
return getErrorObject('InvalidAttr', "Attribute '"+attrName+"' is an invalid name.", getPositionFromMatch(
|
|
337
|
+
return getErrorObject('InvalidAttr', "Attribute '"+attrName+"' is an invalid name.", getPositionFromMatch(matches[i]));
|
|
324
338
|
}
|
|
325
339
|
if (!attrNames.hasOwnProperty(attrName)) {
|
|
326
340
|
//check for duplicate attribute.
|
|
327
341
|
attrNames[attrName] = 1;
|
|
328
342
|
} else {
|
|
329
|
-
return getErrorObject('InvalidAttr', "Attribute '"+attrName+"' is repeated.", getPositionFromMatch(
|
|
343
|
+
return getErrorObject('InvalidAttr', "Attribute '"+attrName+"' is repeated.", getPositionFromMatch(matches[i]));
|
|
330
344
|
}
|
|
331
345
|
}
|
|
332
346
|
|
|
@@ -373,7 +387,8 @@ function getErrorObject(code, message, lineNumber) {
|
|
|
373
387
|
err: {
|
|
374
388
|
code: code,
|
|
375
389
|
msg: message,
|
|
376
|
-
line: lineNumber,
|
|
390
|
+
line: lineNumber.line || lineNumber,
|
|
391
|
+
col: lineNumber.col,
|
|
377
392
|
},
|
|
378
393
|
};
|
|
379
394
|
}
|
|
@@ -390,11 +405,16 @@ function validateTagName(tagname) {
|
|
|
390
405
|
|
|
391
406
|
//this function returns the line number for the character at the given index
|
|
392
407
|
function getLineNumberForPosition(xmlData, index) {
|
|
393
|
-
|
|
394
|
-
return
|
|
408
|
+
const lines = xmlData.substring(0, index).split(/\r?\n/);
|
|
409
|
+
return {
|
|
410
|
+
line: lines.length,
|
|
411
|
+
|
|
412
|
+
// column number is last line's length + 1, because column numbering starts at 1:
|
|
413
|
+
col: lines[lines.length - 1].length + 1
|
|
414
|
+
};
|
|
395
415
|
}
|
|
396
416
|
|
|
397
|
-
//this function returns the position of the
|
|
398
|
-
function getPositionFromMatch(
|
|
399
|
-
return
|
|
417
|
+
//this function returns the position of the first character of match within attrStr
|
|
418
|
+
function getPositionFromMatch(match) {
|
|
419
|
+
return match.startIndex + match[1].length;
|
|
400
420
|
}
|
|
@@ -1,42 +1,45 @@
|
|
|
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
|
cdataTagName: false,
|
|
11
|
-
cdataPositionChar: '\\c',
|
|
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
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
const props = [
|
|
24
25
|
'attributeNamePrefix',
|
|
25
|
-
'
|
|
26
|
+
'attributesGroupName',
|
|
26
27
|
'textNodeName',
|
|
27
28
|
'ignoreAttributes',
|
|
28
29
|
'cdataTagName',
|
|
29
|
-
'cdataPositionChar',
|
|
30
30
|
'format',
|
|
31
31
|
'indentBy',
|
|
32
|
-
'
|
|
32
|
+
'suppressEmptyNode',
|
|
33
33
|
'tagValueProcessor',
|
|
34
|
-
'
|
|
34
|
+
'attributeValueProcessor',
|
|
35
|
+
'arrayNodeName', //when array as root
|
|
36
|
+
'preserveOrder',
|
|
37
|
+
// 'rootNodeName', //when jsObject have multiple properties on root level
|
|
35
38
|
];
|
|
36
39
|
|
|
37
|
-
function
|
|
40
|
+
function Builder(options) {
|
|
38
41
|
this.options = buildOptions(options, defaultOptions, props);
|
|
39
|
-
if (this.options.ignoreAttributes || this.options.
|
|
42
|
+
if (this.options.ignoreAttributes || this.options.attributesGroupName) {
|
|
40
43
|
this.isAttribute = function(/*a*/) {
|
|
41
44
|
return false;
|
|
42
45
|
};
|
|
@@ -44,15 +47,8 @@ function Parser(options) {
|
|
|
44
47
|
this.attrPrefixLen = this.options.attributeNamePrefix.length;
|
|
45
48
|
this.isAttribute = isAttribute;
|
|
46
49
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
} else {
|
|
50
|
-
this.isCDATA = function(/*a*/) {
|
|
51
|
-
return false;
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
this.replaceCDATAstr = replaceCDATAstr;
|
|
55
|
-
this.replaceCDATAarr = replaceCDATAarr;
|
|
50
|
+
|
|
51
|
+
this.processTextOrObjNode = processTextOrObjNode
|
|
56
52
|
|
|
57
53
|
if (this.options.format) {
|
|
58
54
|
this.indentate = indentate;
|
|
@@ -66,7 +62,7 @@ function Parser(options) {
|
|
|
66
62
|
this.newLine = '';
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
if (this.options.
|
|
65
|
+
if (this.options.suppressEmptyNode) {
|
|
70
66
|
this.buildTextNode = buildEmptyTextNode;
|
|
71
67
|
this.buildObjNode = buildEmptyObjNode;
|
|
72
68
|
} else {
|
|
@@ -78,17 +74,23 @@ function Parser(options) {
|
|
|
78
74
|
this.buildObjectNode = buildObjectNode;
|
|
79
75
|
}
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
Builder.prototype.build = function(jObj) {
|
|
78
|
+
if(this.options.preserveOrder){
|
|
79
|
+
return buildFromOrderedJs(jObj, this.options);
|
|
80
|
+
}else {
|
|
81
|
+
if(Array.isArray(jObj) && this.options.arrayNodeName && this.options.arrayNodeName.length > 1){
|
|
82
|
+
jObj = {
|
|
83
|
+
[this.options.arrayNodeName] : jObj
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return this.j2x(jObj, 0).val;
|
|
87
|
+
}
|
|
83
88
|
};
|
|
84
89
|
|
|
85
|
-
|
|
90
|
+
Builder.prototype.j2x = function(jObj, level) {
|
|
86
91
|
let attrStr = '';
|
|
87
92
|
let val = '';
|
|
88
|
-
|
|
89
|
-
const len = keys.length;
|
|
90
|
-
for (let i = 0; i < len; i++) {
|
|
91
|
-
const key = keys[i];
|
|
93
|
+
for (let key in jObj) {
|
|
92
94
|
if (typeof jObj[key] === 'undefined') {
|
|
93
95
|
// supress undefined node
|
|
94
96
|
} else if (jObj[key] === null) {
|
|
@@ -99,91 +101,57 @@ Parser.prototype.j2x = function(jObj, level) {
|
|
|
99
101
|
//premitive type
|
|
100
102
|
const attr = this.isAttribute(key);
|
|
101
103
|
if (attr) {
|
|
102
|
-
attrStr += ' ' + attr + '="' + this.options.
|
|
103
|
-
}
|
|
104
|
-
if (jObj[this.options.textNodeName]) {
|
|
105
|
-
val += this.replaceCDATAstr(jObj[this.options.textNodeName], jObj[key]);
|
|
106
|
-
} else {
|
|
107
|
-
val += this.replaceCDATAstr('', jObj[key]);
|
|
108
|
-
}
|
|
109
|
-
} else {
|
|
104
|
+
attrStr += ' ' + attr + '="' + this.options.attributeValueProcessor(attr, '' + jObj[key]) + '"';
|
|
105
|
+
}else {
|
|
110
106
|
//tag value
|
|
111
107
|
if (key === this.options.textNodeName) {
|
|
112
|
-
|
|
113
|
-
//value will added while processing cdata
|
|
114
|
-
} else {
|
|
115
|
-
val += this.options.tagValueProcessor('' + jObj[key]);
|
|
116
|
-
}
|
|
108
|
+
val += this.options.tagValueProcessor(key, '' + jObj[key]);
|
|
117
109
|
} else {
|
|
118
110
|
val += this.buildTextNode(jObj[key], key, '', level);
|
|
119
111
|
}
|
|
120
112
|
}
|
|
121
113
|
} else if (Array.isArray(jObj[key])) {
|
|
122
114
|
//repeated nodes
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
115
|
+
const arrLen = jObj[key].length;
|
|
116
|
+
for (let j = 0; j < arrLen; j++) {
|
|
117
|
+
const item = jObj[key][j];
|
|
118
|
+
if (typeof item === 'undefined') {
|
|
119
|
+
// supress undefined node
|
|
120
|
+
} else if (item === null) {
|
|
121
|
+
val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
|
|
122
|
+
} else if (typeof item === 'object') {
|
|
123
|
+
val += this.processTextOrObjNode(item, key, level)
|
|
127
124
|
} else {
|
|
128
|
-
val += this.
|
|
129
|
-
}
|
|
130
|
-
} else {
|
|
131
|
-
//nested nodes
|
|
132
|
-
const arrLen = jObj[key].length;
|
|
133
|
-
for (let j = 0; j < arrLen; j++) {
|
|
134
|
-
const item = jObj[key][j];
|
|
135
|
-
if (typeof item === 'undefined') {
|
|
136
|
-
// supress undefined node
|
|
137
|
-
} else if (item === null) {
|
|
138
|
-
val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
|
|
139
|
-
} else if (typeof item === 'object') {
|
|
140
|
-
const result = this.j2x(item, level + 1);
|
|
141
|
-
val += this.buildObjNode(result.val, key, result.attrStr, level);
|
|
142
|
-
} else {
|
|
143
|
-
val += this.buildTextNode(item, key, '', level);
|
|
144
|
-
}
|
|
125
|
+
val += this.buildTextNode(item, key, '', level);
|
|
145
126
|
}
|
|
146
127
|
}
|
|
147
128
|
} else {
|
|
148
129
|
//nested node
|
|
149
|
-
if (this.options.
|
|
130
|
+
if (this.options.attributesGroupName && key === this.options.attributesGroupName) {
|
|
150
131
|
const Ks = Object.keys(jObj[key]);
|
|
151
132
|
const L = Ks.length;
|
|
152
133
|
for (let j = 0; j < L; j++) {
|
|
153
|
-
attrStr += ' ' + Ks[j] + '="' + this.options.
|
|
134
|
+
attrStr += ' ' + Ks[j] + '="' + this.options.attributeValueProcessor(Ks[j], '' + jObj[key][Ks[j]]) + '"';
|
|
154
135
|
}
|
|
155
136
|
} else {
|
|
156
|
-
|
|
157
|
-
val += this.buildObjNode(result.val, key, result.attrStr, level);
|
|
137
|
+
val += this.processTextOrObjNode(jObj[key], key, level)
|
|
158
138
|
}
|
|
159
139
|
}
|
|
160
140
|
}
|
|
161
141
|
return {attrStr: attrStr, val: val};
|
|
162
142
|
};
|
|
163
143
|
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
if (this.options.
|
|
167
|
-
return
|
|
168
|
-
} else {
|
|
169
|
-
return str.replace(this.options.cdataPositionChar, '<![CDATA[' + cdata + ']]' + this.tagEndChar);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function replaceCDATAarr(str, cdata) {
|
|
174
|
-
str = this.options.tagValueProcessor('' + str);
|
|
175
|
-
if (this.options.cdataPositionChar === '' || str === '') {
|
|
176
|
-
return str + '<![CDATA[' + cdata.join(']]><![CDATA[') + ']]' + this.tagEndChar;
|
|
144
|
+
function processTextOrObjNode (object, key, level) {
|
|
145
|
+
const result = this.j2x(object, level + 1);
|
|
146
|
+
if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
|
|
147
|
+
return this.buildTextNode(result.val, key, result.attrStr, level);
|
|
177
148
|
} else {
|
|
178
|
-
|
|
179
|
-
str = str.replace(this.options.cdataPositionChar, '<![CDATA[' + cdata[v] + ']]>');
|
|
180
|
-
}
|
|
181
|
-
return str + this.newLine;
|
|
149
|
+
return this.buildObjNode(result.val, key, result.attrStr, level);
|
|
182
150
|
}
|
|
183
151
|
}
|
|
184
152
|
|
|
185
153
|
function buildObjectNode(val, key, attrStr, level) {
|
|
186
|
-
if (attrStr &&
|
|
154
|
+
if (attrStr && val.indexOf('<') === -1) {
|
|
187
155
|
return (
|
|
188
156
|
this.indentate(level) +
|
|
189
157
|
'<' +
|
|
@@ -230,7 +198,7 @@ function buildTextValNode(val, key, attrStr, level) {
|
|
|
230
198
|
key +
|
|
231
199
|
attrStr +
|
|
232
200
|
'>' +
|
|
233
|
-
this.options.tagValueProcessor(val) +
|
|
201
|
+
this.options.tagValueProcessor(key, val) +
|
|
234
202
|
'</' +
|
|
235
203
|
key +
|
|
236
204
|
this.tagEndChar
|
|
@@ -257,12 +225,8 @@ function isAttribute(name /*, options*/) {
|
|
|
257
225
|
}
|
|
258
226
|
}
|
|
259
227
|
|
|
260
|
-
function isCDATA(name) {
|
|
261
|
-
return name === this.options.cdataTagName;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
228
|
//formatting
|
|
265
229
|
//indentation
|
|
266
230
|
//\n after each closing or self closing tag
|
|
267
231
|
|
|
268
|
-
module.exports =
|
|
232
|
+
module.exports = Builder;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const {EOL} = require('os');
|
|
2
|
+
|
|
3
|
+
function toXml(jObj, options){
|
|
4
|
+
return arrToStr( [jObj], options, 0);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function arrToStr(arr, options, level){
|
|
8
|
+
let xmlStr = "";
|
|
9
|
+
|
|
10
|
+
let indentation = "";
|
|
11
|
+
if(options.format && options.indentBy.length > 0){//TODO: this logic can be avoided for each call
|
|
12
|
+
indentation = EOL + "" + options.indentBy.repeat(level);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < arr.length; i++) {
|
|
16
|
+
const tagObj = arr[i];
|
|
17
|
+
const tagName = propName(tagObj);
|
|
18
|
+
|
|
19
|
+
if(tagName === options.textNodeName){
|
|
20
|
+
xmlStr += indentation + options.tagValueProcessor( tagName, tagObj[tagName]);
|
|
21
|
+
continue;
|
|
22
|
+
}else if( tagName === options.cdataTagName){
|
|
23
|
+
xmlStr += indentation + `<![CDATA[${tagObj[tagName][0][options.textNodeName]}]]>`;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const attStr = attr_to_str(tagObj.attributes, options);
|
|
27
|
+
let tagStart = indentation + `<${tagName}${attStr}`;
|
|
28
|
+
let tagValue = arrToStr(tagObj[tagName], options, level + 1);
|
|
29
|
+
if( (!tagValue || tagValue.length === 0) && options.suppressEmptyNode){
|
|
30
|
+
xmlStr += tagStart + "/>";
|
|
31
|
+
}else{
|
|
32
|
+
//TODO: node with only text value should not parse the text value in next line
|
|
33
|
+
xmlStr += tagStart + `>${tagValue}${indentation}</${tagName}>` ;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return xmlStr;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function propName(obj){
|
|
41
|
+
const keys = Object.keys(obj);
|
|
42
|
+
for (let i = 0; i < keys.length; i++) {
|
|
43
|
+
const key = keys[i];
|
|
44
|
+
if(key !== "attributes") return key;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function attr_to_str(attrMap, options){
|
|
49
|
+
let attrStr = "";
|
|
50
|
+
if(attrMap && !options.ignoreAttributes){
|
|
51
|
+
for( attr in attrMap){
|
|
52
|
+
attrStr+= ` ${attr.substr(options.attributeNamePrefix.length)}="${options.attributeValueProcessor(attr, attrMap[attr])}"`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return attrStr;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = toXml;
|
|
File without changes
|