fast-xml-parser 3.20.3 → 4.0.0-beta.2

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.
@@ -0,0 +1,62 @@
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
+ };
31
+
32
+ const props = [
33
+ 'preserveOrder',
34
+ 'attributeNamePrefix',
35
+ 'attributesGroupName',
36
+ 'textNodeName',
37
+ 'ignoreAttributes',
38
+ 'removeNSPrefix',
39
+ 'allowBooleanAttributes',
40
+ 'parseTagValue',
41
+ 'parseAttributeValue',
42
+ 'trimValues',
43
+ 'cdataPropName',
44
+ 'tagValueProcessor',
45
+ 'attributeValueProcessor',
46
+ 'numberParseOptions',
47
+ 'stopNodes',
48
+ 'alwaysCreateTextNode',
49
+ 'isArray',
50
+ 'commentPropName',
51
+ 'unpairedTags',
52
+ ];
53
+
54
+ const util = require('../util');
55
+
56
+ const buildOptions = function(options) {
57
+ return util.buildOptions(options, defaultOptions, props);
58
+ };
59
+
60
+ exports.buildOptions = buildOptions;
61
+ exports.defaultOptions = defaultOptions;
62
+ exports.props = props;
@@ -0,0 +1,407 @@
1
+ 'use strict';
2
+
3
+ const util = require('../util');
4
+ const xmlNode = require('./xmlNode');
5
+ const toNumber = require("strnum");
6
+
7
+ const regx =
8
+ '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
9
+ .replace(/NAME/g, util.nameRegexp);
10
+
11
+ //const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g");
12
+ //const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
13
+
14
+ //polyfill
15
+ if (!Number.parseInt && window.parseInt) {
16
+ Number.parseInt = window.parseInt;
17
+ }
18
+ if (!Number.parseFloat && window.parseFloat) {
19
+ Number.parseFloat = window.parseFloat;
20
+ }
21
+
22
+
23
+ /**
24
+ * @param {string} val
25
+ * @param {object} options
26
+ * @param {string} tagName
27
+ * @param {string} jPath
28
+ * @param {boolean} dontTrim
29
+ * @param {boolean} hasAttributes
30
+ * @param {boolean} isLeafNode
31
+ */
32
+ function parseValue(val, options, tagName, jPath, dontTrim, hasAttributes, isLeafNode) {
33
+ if (val !== undefined) {
34
+ if (options.trimValues && !dontTrim) {
35
+ val = val.trim();
36
+ }
37
+ if(val.length > 0){
38
+ const newval = options.tagValueProcessor(tagName, val, jPath, hasAttributes, isLeafNode);
39
+ if(newval === null || newval === undefined){
40
+ //don't parse
41
+ return val;
42
+ }else if(typeof newval !== typeof val || newval !== val){
43
+ //overwrite
44
+ return newval;
45
+ }else if(options.trimValues){
46
+ return _parseValue(val, options.parseTagValue, options.numberParseOptions);
47
+ }else{
48
+ const trimmedVal = val.trim();
49
+ if(trimmedVal === val){
50
+ return _parseValue(val, options.parseTagValue, options.numberParseOptions);
51
+ }else{
52
+ return val;
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ function resolveNameSpace(tagname, options) {
60
+ if (options.removeNSPrefix) {
61
+ const tags = tagname.split(':');
62
+ const prefix = tagname.charAt(0) === '/' ? '/' : '';
63
+ if (tags[0] === 'xmlns') {
64
+ return '';
65
+ }
66
+ if (tags.length === 2) {
67
+ tagname = prefix + tags[1];
68
+ }
69
+ }
70
+ return tagname;
71
+ }
72
+
73
+ function _parseValue(val, shouldParse, options) {
74
+ if (shouldParse && typeof val === 'string') {
75
+ //console.log(options)
76
+ const newval = val.trim();
77
+ if(newval === 'true' ) return true;
78
+ else if(newval === 'false' ) return false;
79
+ else return toNumber(val, options);
80
+ } else {
81
+ if (util.isExist(val)) {
82
+ return val;
83
+ } else {
84
+ return '';
85
+ }
86
+ }
87
+ }
88
+
89
+ //TODO: change regex to capture NS
90
+ //const attrsRegx = new RegExp("([\\w\\-\\.\\:]+)\\s*=\\s*(['\"])((.|\n)*?)\\2","gm");
91
+ const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
92
+
93
+ function buildAttributesMap(attrStr, jPath, options) {
94
+ if (!options.ignoreAttributes && typeof attrStr === 'string') {
95
+ // attrStr = attrStr.replace(/\r?\n/g, ' ');
96
+ //attrStr = attrStr || attrStr.trim();
97
+
98
+ const matches = util.getAllMatches(attrStr, attrsRegx);
99
+ const len = matches.length; //don't make it inline
100
+ const attrs = {};
101
+ for (let i = 0; i < len; i++) {
102
+ const attrName = resolveNameSpace(matches[i][1], options);
103
+ let oldVal = matches[i][4];
104
+ const aName = options.attributeNamePrefix + attrName;
105
+ if (attrName.length) {
106
+ if (oldVal !== undefined) {
107
+ if (options.trimValues) {
108
+ oldVal = oldVal.trim();
109
+ }
110
+
111
+ const newVal = options.attributeValueProcessor(attrName, oldVal, jPath);
112
+ if(newVal === null || newVal === undefined){
113
+ //don't parse
114
+ attrs[aName] = oldVal;
115
+ }else if(typeof newVal !== typeof oldVal || newVal !== oldVal){
116
+ //overwrite
117
+ attrs[aName] = newVal;
118
+ }else{
119
+ //parse
120
+ attrs[aName] = _parseValue(
121
+ oldVal,
122
+ options.parseAttributeValue,
123
+ options.numberParseOptions
124
+ );
125
+ }
126
+ } else if (options.allowBooleanAttributes) {
127
+ attrs[aName] = true;
128
+ }
129
+ }
130
+ }
131
+ if (!Object.keys(attrs).length) {
132
+ return;
133
+ }
134
+ if (options.attributesGroupName) {
135
+ const attrCollection = {};
136
+ attrCollection[options.attributesGroupName] = attrs;
137
+ return attrCollection;
138
+ }
139
+ return attrs;
140
+ }
141
+ }
142
+
143
+ const parseToOrderedJsObj = function(xmlData, options) {
144
+ xmlData = xmlData.replace(/\r\n?/g, "\n"); //TODO: remove this line
145
+ const xmlObj = new xmlNode('!xml');
146
+ let currentNode = xmlObj;
147
+ let textData = "";
148
+ const tagsNodeStack = [];
149
+ let jPath = "";
150
+
151
+ for(let i=0; i< xmlData.length; i++){//for each char in XML data
152
+ const ch = xmlData[i];
153
+ if(ch === '<'){
154
+ // const nextIndex = i+1;
155
+ // const _2ndChar = xmlData[nextIndex];
156
+ if( xmlData[i+1] === '/') {//Closing Tag
157
+ const closeIndex = findClosingIndex(xmlData, ">", i, "Closing Tag is not closed.")
158
+ let tagName = xmlData.substring(i+2,closeIndex).trim();
159
+
160
+ if(options.removeNSPrefix){
161
+ const colonIndex = tagName.indexOf(":");
162
+ if(colonIndex !== -1){
163
+ tagName = tagName.substr(colonIndex+1);
164
+ }
165
+ }
166
+
167
+ if(currentNode){
168
+ textData = parseValue(textData
169
+ , options
170
+ , currentNode.tagname
171
+ , jPath
172
+ ,false
173
+ , currentNode.attributes ? Object.keys(currentNode.attributes).length !== 0 : false
174
+ , Object.keys(currentNode.child).length === 0);
175
+ if(textData !== undefined && textData !== "") currentNode.add(options.textNodeName, textData);
176
+ textData = "";
177
+ }
178
+
179
+ if (isItStopNode(options.stopNodes, tagsNodeStack, currentNode.tagname)) { //TODO: namespace
180
+ const top = tagsNodeStack[tagsNodeStack.length - 1];
181
+ const stopNode = top.child[ top.child.length -1 ];
182
+ stopNode[currentNode.tagname] = [ { [options.textNodeName] :xmlData.substr(currentNode.startIndex + 1, i - currentNode.startIndex - 1) }];
183
+ }
184
+
185
+ jPath = jPath.substr(0, jPath.lastIndexOf("."));
186
+
187
+ currentNode = tagsNodeStack.pop();//avoid recurssion, set the parent tag scope
188
+ textData = "";
189
+ i = closeIndex;
190
+ } else if( xmlData[i+1] === '?') {
191
+ i = findClosingIndex(xmlData, "?>", i, "Pi Tag is not closed.")
192
+ } else if(xmlData.substr(i + 1, 3) === '!--') {
193
+ const endIndex = findClosingIndex(xmlData, "-->", i, "Comment is not closed.")
194
+ if(options.commentPropName){
195
+ const comment = xmlData.substring(i + 4, endIndex - 2);
196
+
197
+ //TODO: remove repeated code
198
+ if(textData){ //store previously collected data as textNode
199
+ textData = parseValue(textData
200
+ , options
201
+ , currentNode.tagname
202
+ , jPath
203
+ ,false
204
+ , currentNode.attributes ? Object.keys(currentNode.attributes).length !== 0 : false
205
+ , Object.keys(currentNode.child).length === 0);
206
+
207
+ if(textData !== undefined && textData !== "") currentNode.add(options.textNodeName, textData);
208
+ textData = "";
209
+ }
210
+ currentNode.add(options.commentPropName, [ { [options.textNodeName] : comment } ]);
211
+ }
212
+ i = endIndex;
213
+ } else if( xmlData.substr(i + 1, 2) === '!D') {
214
+ const closeIndex = findClosingIndex(xmlData, ">", i, "DOCTYPE is not closed.")
215
+ const tagExp = xmlData.substring(i, closeIndex);
216
+ if(tagExp.indexOf("[") >= 0){
217
+ i = xmlData.indexOf("]>", i) + 1;
218
+ }else{
219
+ i = closeIndex;
220
+ }
221
+ }else if(xmlData.substr(i + 1, 2) === '![') {
222
+ const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
223
+ const tagExp = xmlData.substring(i + 9,closeIndex);
224
+
225
+ if(textData){ //store previously collected data as textNode
226
+ textData = parseValue(textData
227
+ , options
228
+ , currentNode.tagname
229
+ , jPath
230
+ ,false
231
+ , currentNode.attributes ? Object.keys(currentNode.attributes).length !== 0 : false
232
+ , Object.keys(currentNode.child).length === 0);
233
+
234
+ if(textData !== undefined && textData !== "") currentNode.add(options.textNodeName, textData);
235
+ textData = "";
236
+ }
237
+
238
+ //cdata should be set even if it is 0 length string
239
+ if(options.cdataPropName){
240
+ let val = parseValue(tagExp, options, options.cdataPropName, jPath + "." + options.cdataPropName, true, false, true);
241
+ if(!val) val = "";
242
+ currentNode.add(options.cdataPropName, [ { [options.textNodeName] : val } ]);
243
+ }else{
244
+ let val = parseValue(tagExp, options, currentNode.tagname, jPath, true, false, true);
245
+ if(!val) val = "";
246
+ currentNode.add(options.textNodeName, val);
247
+ }
248
+
249
+ i = closeIndex + 2;
250
+ }else {//Opening tag
251
+ const result = tagExpWithClosingIndex(xmlData, i+1)
252
+ let tagExp = result.data;
253
+ const closeIndex = result.index;
254
+ const separatorIndex = tagExp.search(/\s/);
255
+ let tagName = tagExp;
256
+ let shouldBuildAttributesMap = true;
257
+ if(separatorIndex !== -1){//separate tag name and attributes expression
258
+ tagName = tagExp.substr(0, separatorIndex).replace(/\s\s*$/, '');
259
+ tagExp = tagExp.substr(separatorIndex + 1);
260
+ }
261
+
262
+ if(options.removeNSPrefix){
263
+ const colonIndex = tagName.indexOf(":");
264
+ if(colonIndex !== -1){
265
+ tagName = tagName.substr(colonIndex+1);
266
+ shouldBuildAttributesMap = tagName !== result.data.substr(colonIndex + 1);
267
+ }
268
+ }
269
+
270
+ //save text as child node
271
+ if (currentNode && textData) {
272
+ if(currentNode.tagname !== '!xml'){
273
+ //when nested tag is found
274
+ textData = parseValue(textData
275
+ , options
276
+ , currentNode.tagname
277
+ , jPath
278
+ , false
279
+ , currentNode.attributes ? Object.keys(currentNode.attributes).length !== 0 : false
280
+ , false);
281
+ if(textData !== undefined && textData !== "") currentNode.add(options.textNodeName, textData);
282
+ textData = "";
283
+ }
284
+ }
285
+
286
+ if(tagName !== xmlObj.tagname){
287
+ jPath += jPath ? "." + tagName : tagName;
288
+ }
289
+
290
+ //selfClosing tag
291
+ if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
292
+
293
+ if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
294
+ tagName = tagName.substr(0, tagName.length - 1);
295
+ tagExp = tagName;
296
+ }else{
297
+ tagExp = tagExp.substr(0, tagExp.length - 1);
298
+ }
299
+
300
+ const childNode = new xmlNode(tagName);
301
+ if(tagName !== tagExp && shouldBuildAttributesMap){
302
+ childNode.attributes = buildAttributesMap(tagExp, jPath , options);
303
+ }
304
+ jPath = jPath.substr(0, jPath.lastIndexOf("."));
305
+ // tagsNodeStack.push(currentNode);
306
+ currentNode.addChild(childNode);
307
+ }
308
+ //boolean tags
309
+ else if(options.unpairedTags.indexOf(tagName) !== -1){
310
+ // tagExp = tagExp.substr(0, tagExp.length - 1);
311
+
312
+ const childNode = new xmlNode(tagName);
313
+ if(tagName !== tagExp && shouldBuildAttributesMap){
314
+ childNode.attributes = buildAttributesMap(tagExp, jPath , options);
315
+ }
316
+ jPath = jPath.substr(0, jPath.lastIndexOf("."));
317
+ // tagsNodeStack.push(currentNode);
318
+ currentNode.addChild(childNode);
319
+ }
320
+ //opening tag
321
+ else{
322
+
323
+ const childNode = new xmlNode( tagName);
324
+ tagsNodeStack.push(currentNode);
325
+
326
+ childNode.startIndex=closeIndex; //for further processing
327
+
328
+ if(tagName !== tagExp && shouldBuildAttributesMap){
329
+ childNode.attributes = buildAttributesMap(tagExp, jPath, options);
330
+ }
331
+ currentNode.addChild(childNode);
332
+ currentNode = childNode;
333
+ }
334
+ textData = "";
335
+ i = closeIndex;
336
+ }
337
+ }else{
338
+ textData += xmlData[i];
339
+ }
340
+ }
341
+ return xmlObj.child;
342
+ }
343
+
344
+ //TODO: use jPath to simplify the logic
345
+ /**
346
+ *
347
+ * @param {string[]} stopNodes
348
+ * @param {XmlNode[]} tagsNodeStack
349
+ */
350
+ function isItStopNode(stopNodes, tagsNodeStack, currentTagName){
351
+ const matchingStopNodes = [];
352
+ //filter the list of stopNodes as per current tag
353
+ stopNodes.forEach( jPath => {
354
+ if( jPath.substr( jPath.length - currentTagName.length) === currentTagName) matchingStopNodes.push(jPath);
355
+ });
356
+
357
+ if(matchingStopNodes.length > 0){
358
+ let jPath = "";
359
+ for (let i = 1; i < tagsNodeStack.length; i++) {
360
+ const node = tagsNodeStack[i];
361
+ jPath += "." + node.tagname;
362
+ }
363
+ jPath += "." + currentTagName;
364
+ jPath = jPath.substr(1);
365
+ for (let i = 0; i < matchingStopNodes.length; i++) {
366
+ if(matchingStopNodes[i] === jPath) return true;
367
+ }
368
+ }else return false;
369
+ }
370
+
371
+ /**
372
+ * Returns the tag Expression and where it is ending handling single-dobule quotes situation
373
+ * @param {string} xmlData
374
+ * @param {number} i starting index
375
+ * @returns
376
+ */
377
+ function tagExpWithClosingIndex(xmlData, i){
378
+ let attrBoundary;
379
+ let tagExp = "";
380
+ for (let index = i; index < xmlData.length; index++) {
381
+ let ch = xmlData[index];
382
+ if (attrBoundary) {
383
+ if (ch === attrBoundary) attrBoundary = "";//reset
384
+ } else if (ch === '"' || ch === "'") {
385
+ attrBoundary = ch;
386
+ } else if (ch === '>') {
387
+ return {
388
+ data: tagExp,
389
+ index: index
390
+ }
391
+ } else if (ch === '\t') {
392
+ ch = " "
393
+ }
394
+ tagExp += ch;
395
+ }
396
+ }
397
+
398
+ function findClosingIndex(xmlData, str, i, errMsg){
399
+ const closingIndex = xmlData.indexOf(str, i);
400
+ if(closingIndex === -1){
401
+ throw new Error(errMsg)
402
+ }else{
403
+ return closingIndex + str.length - 1;
404
+ }
405
+ }
406
+
407
+ exports.parseToOrderedJsObj = parseToOrderedJsObj;
@@ -0,0 +1,36 @@
1
+ const { buildOptions} = require("./OptionsBuilder");
2
+ const { parseToOrderedJsObj} = require("./OrderedObjParser");
3
+ const { prettify} = require("./node2json");
4
+ const validator = require('../validator');
5
+
6
+ class XMLParser{
7
+ constructor(options){
8
+ this.options = buildOptions(options);
9
+ }
10
+ /**
11
+ * Parse XML dats to JS object
12
+ * @param {string|Buffer} xmlData
13
+ * @param {boolean|Object} validationOption
14
+ */
15
+ parse(xmlData,validationOption){
16
+ if(typeof xmlData === "string"){
17
+ }else if( xmlData.toString){
18
+ xmlData = xmlData.toString();
19
+ }else{
20
+ throw new Error("XML data is accepted in String or Bytes[] form.")
21
+ }
22
+ if( validationOption){
23
+ if(validationOption === true) validationOption = {}; //validate with default options
24
+
25
+ const result = validator.validate(xmlData, validationOption);
26
+ if (result !== true) {
27
+ throw Error( `${result.err.msg}:${result.err.line}:${result.err.col}` )
28
+ }
29
+ }
30
+ const orderedResult = parseToOrderedJsObj(xmlData, this.options);
31
+ if(this.options.preserveOrder || orderedResult === undefined) return orderedResult;
32
+ else return prettify(orderedResult, this.options);
33
+ }
34
+ }
35
+
36
+ module.exports = XMLParser;
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ *
5
+ * @param {array} node
6
+ * @param {any} options
7
+ * @returns
8
+ */
9
+ function prettify(node, options){
10
+ return compress( node, options);
11
+ }
12
+
13
+ /**
14
+ *
15
+ * @param {array} arr
16
+ * @param {object} options
17
+ * @param {string} jPath
18
+ * @returns object
19
+ */
20
+ function compress(arr, options, jPath){
21
+ let text;
22
+ const compressedObj = {};
23
+ for (let i = 0; i < arr.length; i++) {
24
+ const tagObj = arr[i];
25
+ const property = propName(tagObj);
26
+ let newJpath = "";
27
+ if(jPath === undefined) newJpath = property;
28
+ else newJpath = jPath + "." + property;
29
+
30
+ if(property === options.textNodeName){
31
+ if(text === undefined) text = tagObj[property];
32
+ else text += "" + tagObj[property];
33
+ }else if(property === undefined){
34
+ continue;
35
+ }else if(tagObj[property]){
36
+
37
+ let val = compress(tagObj[property], options, newJpath);
38
+ const isLeaf = isLeafTag(val, options);
39
+
40
+ if(tagObj.attributes){
41
+ assignAttributes( val, tagObj.attributes, newJpath, options);
42
+ }else if(Object.keys(val).length === 1 && val[options.textNodeName] !== undefined && !options.alwaysCreateTextNode){
43
+ val = val[options.textNodeName];
44
+ }else if(Object.keys(val).length === 0){
45
+ if(options.alwaysCreateTextNode) val[options.textNodeName] = "";
46
+ else val = "";
47
+ }
48
+
49
+ if(compressedObj[property] !== undefined) {
50
+ if(!Array.isArray(compressedObj[property])) {
51
+ compressedObj[property] = [ compressedObj[property] ];
52
+ }
53
+ compressedObj[property].push(val);
54
+ }else{
55
+ //TODO: if a node is not an array, then check if it should be an array
56
+ //also determine if it is a leaf node
57
+ if (options.isArray(property, newJpath, isLeaf )) {
58
+ compressedObj[property] = [val];
59
+ }else{
60
+ compressedObj[property] = val;
61
+ }
62
+ }
63
+ }
64
+
65
+ }
66
+ // if(text && text.length > 0) compressedObj[options.textNodeName] = text;
67
+ if(typeof text === "string"){
68
+ if(text.length > 0) compressedObj[options.textNodeName] = text;
69
+ }else if(text !== undefined) compressedObj[options.textNodeName] = text;
70
+ return compressedObj;
71
+ }
72
+
73
+ function propName(obj){
74
+ const keys = Object.keys(obj);
75
+ for (let i = 0; i < keys.length; i++) {
76
+ const key = keys[i];
77
+ if(key !== "attributes") return key;
78
+ }
79
+ }
80
+
81
+ function assignAttributes(obj, attrMap, jpath, options){
82
+ if (attrMap) {
83
+ const keys = Object.keys(attrMap);
84
+ const len = keys.length; //don't make it inline
85
+ for (let i = 0; i < len; i++) {
86
+ const atrrName = keys[i];
87
+ if (options.isArray(atrrName, jpath + "." + atrrName, true, true)) {
88
+ obj[atrrName] = [ attrMap[atrrName] ];
89
+ } else {
90
+ obj[atrrName] = attrMap[atrrName];
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ function isLeafTag(obj, options){
97
+ const propCount = Object.keys(obj).length;
98
+ if( propCount === 0 || (propCount === 1 && obj[options.textNodeName]) ) return true;
99
+ return false;
100
+ }
101
+ exports.prettify = prettify;
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ class XmlNode{
4
+ constructor(tagname) {
5
+ this.tagname = tagname;
6
+ this.child = []; //nested tags, text, cdata, comments in order
7
+ this.attributes = {}; //attributes map
8
+ }
9
+ add(key,val){
10
+ // this.child.push( {name : key, val: val, isCdata: isCdata });
11
+ this.child.push( {[key]: val });
12
+ }
13
+ addChild(node) {
14
+ if(node.attributes && Object.keys(node.attributes).length > 0){
15
+ this.child.push( { [node.tagname]: node.child, attributes: node.attributes });
16
+ }else{
17
+ this.child.push( { [node.tagname]: node.child });
18
+ }
19
+ };
20
+ };
21
+
22
+
23
+ module.exports = XmlNode;