fast-xml-parser 4.0.0-beta.6 → 4.0.1

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 CHANGED
@@ -1,19 +1,37 @@
1
1
  Note: If you find missing information about particular minor version, that version must have been changed without any functional change in this library.
2
2
 
3
- ** 4.0.0-beta.6 / 2021-12-05**
3
+ **4.0.1 / 2022-01-08**
4
+ * fix builder for pi tag
5
+ * fix: support suppressBooleanAttrs by builder
6
+
7
+ **4.0.0 / 2022-01-06**
8
+ * Generating different combined, parser only, builder only, validator only browser bundles
9
+ * Keeping cjs modules as they can be imported in cjs and esm modules both. Otherwise refer `esm` branch.
10
+
11
+ **4.0.0-beta.8 / 2021-12-13**
12
+ * call tagValueProcessor for stop nodes
13
+
14
+ **4.0.0-beta.7 / 2021-12-09**
15
+ * fix Validator bug when an attribute has no value but '=' only
16
+ * XML Builder should suppress unpaired tags by default.
17
+ * documents update for missing features
18
+ * refactoring to use Object.assign
19
+ * refactoring to remove repeated code
20
+
21
+ **4.0.0-beta.6 / 2021-12-05**
4
22
  * Support PI Tags processing
5
23
  * Support `suppressBooleanAttributes` by XML Builder for attributes with value `true`.
6
24
 
7
- ** 4.0.0-beta.5 / 2021-12-04**
25
+ **4.0.0-beta.5 / 2021-12-04**
8
26
  * fix: when a tag with name "attributes"
9
27
 
10
- ** 4.0.0-beta.4 / 2021-12-02**
28
+ **4.0.0-beta.4 / 2021-12-02**
11
29
  * Support HTML document parsing
12
30
  * skip stop nodes parsing when building the XML from JS object
13
31
  * Support external entites without DOCTYPE
14
32
  * update dev dependency: strnum v1.0.5 to fix long number issue
15
33
 
16
- ** 4.0.0-beta.3 / 2021-11-30**
34
+ **4.0.0-beta.3 / 2021-11-30**
17
35
  * support global stopNodes expression like "*.stop"
18
36
  * support self-closing and paired unpaired tags
19
37
  * fix: CDATA should not be parsed.
package/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # [fast-xml-parser](https://www.npmjs.com/package/fast-xml-parser)
2
2
  [![Backers on Open Collective](https://opencollective.com/fast-xml-parser/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/fast-xml-parser/sponsors/badge.svg)](#sponsors) [![Known Vulnerabilities](https://snyk.io/test/github/naturalintelligence/fast-xml-parser/badge.svg)](https://snyk.io/test/github/naturalintelligence/fast-xml-parser)
3
3
  [![NPM quality][quality-image]][quality-url]
4
- [![Travis ci Build Status](https://travis-ci.org/NaturalIntelligence/fast-xml-parser.svg?branch=master)](https://travis-ci.org/NaturalIntelligence/fast-xml-parser)
5
4
  [![Coverage Status](https://coveralls.io/repos/github/NaturalIntelligence/fast-xml-parser/badge.svg?branch=master)](https://coveralls.io/github/NaturalIntelligence/fast-xml-parser?branch=master)
6
5
  [<img src="https://img.shields.io/badge/Try-me-blue.svg?colorA=FFA500&colorB=0000FF" alt="Try me"/>](https://naturalintelligence.github.io/fast-xml-parser/)
7
6
  [![NPM total downloads](https://img.shields.io/npm/dt/fast-xml-parser.svg)](https://npm.im/fast-xml-parser)
@@ -90,7 +89,7 @@ const xmlContent = builder.build(jObj);
90
89
 
91
90
  In a HTML page
92
91
  ```html
93
- <script src="path/to/fxparser.js"></script>
92
+ <script src="path/to/fxp.min.js"></script>
94
93
  :
95
94
  <script>
96
95
  const parser = new fxparser.XMLParser();
@@ -98,6 +97,16 @@ In a HTML page
98
97
  </script>
99
98
  ```
100
99
 
100
+ Check lib folder for different browser bundles
101
+
102
+ | Bundle Name | Size |
103
+ | -- | -- |
104
+ | fxbuilder.min.js | 5.2K |
105
+ | fxparser.js | 50K |
106
+ | fxparser.min.js | 17K |
107
+ | fxp.min.js | 22K |
108
+ | fxvalidator.min.js | 5.7K |
109
+
101
110
  ### Documents
102
111
  **v3**
103
112
  * [documents](./docs/v3/docs.md)
@@ -107,9 +116,9 @@ In a HTML page
107
116
  2. [XML Parser](./docs/v4/2.XMLparseOptions.md)
108
117
  3. [XML Builder](./docs/v4/3.XMLBuilder.md)
109
118
  4. [XML Validator](./docs/v4/4.XMLValidator.md)
110
- 5. [Entites](./docs/5.Entities.md)
111
- 6. [HTML Document Parsing](./docs/6.HTMLParsing.md)
112
- 7. [PI Tag processing](./docs/7.PITags.md)
119
+ 5. [Entites](./docs/v4/5.Entities.md)
120
+ 6. [HTML Document Parsing](./docs/v4/6.HTMLParsing.md)
121
+ 7. [PI Tag processing](./docs/v4/7.PITags.md)
113
122
  ## Performance
114
123
 
115
124
  ### XML Parser
@@ -135,6 +144,7 @@ In a HTML page
135
144
  * Run tests for a route or from a route
136
145
  * Customizable reporting
137
146
  * Central dashboard for better monitoring
147
+ * Options to integrate E2E tests with Jira, Github etc using Central dashboard `Tian`.
138
148
  * **[Stubmatic](https://github.com/NaturalIntelligence/Stubmatic)** : Create fake webservices, DynamoDB or S3 servers, Manage fake/mock stub data, Or fake any HTTP(s) call.
139
149
 
140
150
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-xml-parser",
3
- "version": "4.0.0-beta.6",
3
+ "version": "4.0.1",
4
4
  "description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
5
5
  "main": "./src/fxp.js",
6
6
  "scripts": {
@@ -9,7 +9,7 @@
9
9
  "coverage": "nyc report --reporter html --reporter text -t .nyc_output --report-dir .nyc_output/summary",
10
10
  "perf": "node ./benchmark/perfTest3.js",
11
11
  "lint": "eslint src/*.js spec/*.js",
12
- "bundle": "webpack && webpack --config webpack-prod.config.js",
12
+ "bundle": "webpack --config webpack-prod.config.js",
13
13
  "prettier": "prettier --write src/**/*.js",
14
14
  "publish-please": "publish-please",
15
15
  "checkReadiness": "publish-please --dry-run"
@@ -47,6 +47,7 @@
47
47
  "@babel/preset-env": "^7.13.10",
48
48
  "@babel/register": "^7.13.8",
49
49
  "babel-loader": "^8.2.2",
50
+ "cytorus": "^0.2.9",
50
51
  "eslint": "^8.3.0",
51
52
  "he": "^1.2.0",
52
53
  "jasmine": "^3.6.4",
package/src/fxp_cjs ADDED
@@ -0,0 +1,8 @@
1
+
2
+
3
+ const XMLValidator = require('./validator.js');
4
+ const XMLParser = require('./xmlparser/XMLParser.js');
5
+ const XMLBuilder = require('./xmlbuilder/json2xml.js');
6
+
7
+ module.exports = {XMLParser,XMLValidator, XMLBuilder};
8
+
package/src/util.js CHANGED
@@ -67,23 +67,6 @@ exports.getValue = function(v) {
67
67
  // const fakeCall = function(a) {return a;};
68
68
  // const fakeCallNoReturn = function() {};
69
69
 
70
- const buildOptions = function(options, defaultOptions, props) {
71
- let newOptions = {};
72
- if (!options) {
73
- return defaultOptions; //if there are not options
74
- }
75
-
76
- for (let i = 0; i < props.length; i++) {
77
- if (options[props[i]] !== undefined) {
78
- newOptions[props[i]] = options[props[i]];
79
- } else {
80
- newOptions[props[i]] = defaultOptions[props[i]];
81
- }
82
- }
83
- return newOptions;
84
- };
85
-
86
- exports.buildOptions = buildOptions;
87
70
  exports.isName = isName;
88
71
  exports.getAllMatches = getAllMatches;
89
72
  exports.nameRegexp = nameRegexp;
package/src/validator.js CHANGED
@@ -7,14 +7,9 @@ const defaultOptions = {
7
7
  unpairedTags: []
8
8
  };
9
9
 
10
- const props = [
11
- 'allowBooleanAttributes',
12
- 'unpairedTags'
13
- ];
14
-
15
10
  //const tagsPattern = new RegExp("<\\/?([\\w:\\-_\.]+)\\s*\/?>","g");
16
11
  exports.validate = function (xmlData, options) {
17
- options = util.buildOptions(options, defaultOptions, props);
12
+ options = Object.assign({}, defaultOptions, options);
18
13
 
19
14
  //xmlData = xmlData.replace(/(\r\n|\n|\r)/gm,"");//make it single line
20
15
  //xmlData = xmlData.replace(/(^\s*<\?xml.*?\?>)/g,"");//Remove XML starting tag
@@ -331,6 +326,8 @@ function validateAttributeString(attrStr, options) {
331
326
  if (matches[i][1].length === 0) {
332
327
  //nospace before attribute name: a="sd"b="saf"
333
328
  return getErrorObject('InvalidAttr', "Attribute '"+matches[i][2]+"' has no space in starting.", getPositionFromMatch(matches[i]))
329
+ } else if (matches[i][3] !== undefined && matches[i][4] === undefined) {
330
+ return getErrorObject('InvalidAttr', "Attribute '"+matches[i][2]+"' is without value.", getPositionFromMatch(matches[i]));
334
331
  } else if (matches[i][3] === undefined && !options.allowBooleanAttributes) {
335
332
  //independent attribute: ab
336
333
  return getErrorObject('InvalidAttr', "boolean attribute '"+matches[i][2]+"' is not allowed.", getPositionFromMatch(matches[i]));
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
  //parse Empty Node as self closing node
3
- const buildOptions = require('../util').buildOptions;
4
3
  const buildFromOrderedJs = require('./orderedJs2Xml');
5
4
 
6
5
  const defaultOptions = {
@@ -32,30 +31,8 @@ const defaultOptions = {
32
31
  stopNodes: []
33
32
  };
34
33
 
35
- const props = [
36
- 'attributeNamePrefix',
37
- 'attributesGroupName',
38
- 'textNodeName',
39
- 'ignoreAttributes',
40
- 'cdataPropName',
41
- 'format',
42
- 'indentBy',
43
- 'suppressEmptyNode',
44
- 'suppressBooleanAttributes',
45
- 'tagValueProcessor',
46
- 'attributeValueProcessor',
47
- 'arrayNodeName', //when array as root
48
- 'preserveOrder',
49
- "commentPropName",
50
- "unpairedTags",
51
- "entities",
52
- "processEntities",
53
- "stopNodes",
54
- // 'rootNodeName', //when jsObject have multiple properties on root level
55
- ];
56
-
57
34
  function Builder(options) {
58
- this.options = buildOptions(options, defaultOptions, props);
35
+ this.options = Object.assign({}, defaultOptions, options);
59
36
  if (this.options.ignoreAttributes || this.options.attributesGroupName) {
60
37
  this.isAttribute = function(/*a*/) {
61
38
  return false;
@@ -91,6 +68,7 @@ function Builder(options) {
91
68
  this.buildObjectNode = buildObjectNode;
92
69
 
93
70
  this.replaceEntitiesValue = replaceEntitiesValue;
71
+ this.buildAttrPairStr = buildAttrPairStr;
94
72
  }
95
73
 
96
74
  Builder.prototype.build = function(jObj) {
@@ -113,16 +91,16 @@ Builder.prototype.j2x = function(jObj, level) {
113
91
  if (typeof jObj[key] === 'undefined') {
114
92
  // supress undefined node
115
93
  } else if (jObj[key] === null) {
116
- val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
94
+ if(key[0] === "?") val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
95
+ else val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
96
+ // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
117
97
  } else if (jObj[key] instanceof Date) {
118
98
  val += this.buildTextNode(jObj[key], key, '', level);
119
99
  } else if (typeof jObj[key] !== 'object') {
120
100
  //premitive type
121
101
  const attr = this.isAttribute(key);
122
102
  if (attr) {
123
- let val = this.options.attributeValueProcessor(attr, '' + jObj[key]);
124
- val = this.replaceEntitiesValue(val);
125
- attrStr += ' ' + attr + '="' + val + '"';
103
+ attrStr += this.buildAttrPairStr(attr, '' + jObj[key]);
126
104
  }else {
127
105
  //tag value
128
106
  if (key === this.options.textNodeName) {
@@ -140,7 +118,9 @@ Builder.prototype.j2x = function(jObj, level) {
140
118
  if (typeof item === 'undefined') {
141
119
  // supress undefined node
142
120
  } else if (item === null) {
143
- val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
121
+ if(key[0] === "?") val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
122
+ else val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
123
+ // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
144
124
  } else if (typeof item === 'object') {
145
125
  val += this.processTextOrObjNode(item, key, level)
146
126
  } else {
@@ -153,9 +133,7 @@ Builder.prototype.j2x = function(jObj, level) {
153
133
  const Ks = Object.keys(jObj[key]);
154
134
  const L = Ks.length;
155
135
  for (let j = 0; j < L; j++) {
156
- let val = this.options.attributeValueProcessor(Ks[j], '' + jObj[key][Ks[j]]);
157
- val = this.replaceEntitiesValue(val);
158
- attrStr += ' ' + Ks[j] + '="' + val + '"';
136
+ attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]]);
159
137
  }
160
138
  } else {
161
139
  val += this.processTextOrObjNode(jObj[key], key, level)
@@ -165,6 +143,14 @@ Builder.prototype.j2x = function(jObj, level) {
165
143
  return {attrStr: attrStr, val: val};
166
144
  };
167
145
 
146
+ function buildAttrPairStr(attrName, val){
147
+ val = this.options.attributeValueProcessor(attrName, '' + val);
148
+ val = this.replaceEntitiesValue(val);
149
+ if (this.options.suppressBooleanAttributes && val === "true") {
150
+ return ' ' + attrName;
151
+ } else return ' ' + attrName + '="' + val + '"';
152
+ }
153
+
168
154
  function processTextOrObjNode (object, key, level) {
169
155
  const result = this.j2x(object, level + 1);
170
156
  if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
@@ -175,34 +161,24 @@ function processTextOrObjNode (object, key, level) {
175
161
  }
176
162
 
177
163
  function buildObjectNode(val, key, attrStr, level) {
164
+ let tagEndExp = '</' + key + this.tagEndChar;
165
+ let piClosingChar = "";
166
+
167
+ if(key[0] === "?") {
168
+ piClosingChar = "?";
169
+ tagEndExp = "";
170
+ }
171
+
178
172
  if (attrStr && val.indexOf('<') === -1) {
179
173
  return (
180
- this.indentate(level) +
181
- '<' +
182
- key +
183
- attrStr +
184
- '>' +
174
+ this.indentate(level) + '<' + key + attrStr + piClosingChar + '>' +
185
175
  val +
186
- //+ this.newLine
187
- // + this.indentate(level)
188
- '</' +
189
- key +
190
- this.tagEndChar
191
- );
176
+ tagEndExp );
192
177
  } else {
193
178
  return (
194
- this.indentate(level) +
195
- '<' +
196
- key +
197
- attrStr +
198
- this.tagEndChar +
179
+ this.indentate(level) + '<' + key + attrStr + piClosingChar + this.tagEndChar +
199
180
  val +
200
- //+ this.newLine
201
- this.indentate(level) +
202
- '</' +
203
- key +
204
- this.tagEndChar
205
- );
181
+ this.indentate(level) + tagEndExp );
206
182
  }
207
183
  }
208
184
 
@@ -210,8 +186,8 @@ function buildEmptyObjNode(val, key, attrStr, level) {
210
186
  if (val !== '') {
211
187
  return this.buildObjectNode(val, key, attrStr, level);
212
188
  } else {
213
- return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
214
- //+ this.newLine
189
+ if(key[0] === "?") return this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar;
190
+ else return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
215
191
  }
216
192
  }
217
193
 
@@ -220,16 +196,9 @@ function buildTextValNode(val, key, attrStr, level) {
220
196
  textValue = this.replaceEntitiesValue(textValue);
221
197
 
222
198
  return (
223
- this.indentate(level) +
224
- '<' +
225
- key +
226
- attrStr +
227
- '>' +
199
+ this.indentate(level) + '<' + key + attrStr + '>' +
228
200
  textValue +
229
- '</' +
230
- key +
231
- this.tagEndChar
232
- );
201
+ '</' + key + this.tagEndChar );
233
202
  }
234
203
 
235
204
  function replaceEntitiesValue(textValue){
@@ -248,7 +217,8 @@ function buildEmptyTextNode(val, key, attrStr, level) {
248
217
  }else if (val !== '') {
249
218
  return this.buildTextValNode(val, key, attrStr, level);
250
219
  } else {
251
- return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
220
+ if(key[0] === "?") return this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar;
221
+ else return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
252
222
  }
253
223
  }
254
224
 
@@ -1,4 +1,4 @@
1
- const {EOL} = require('os');
1
+ const EOL = "\n";
2
2
 
3
3
  /**
4
4
  *
@@ -47,12 +47,10 @@ function arrToStr(arr, options, jPath, level){
47
47
  const attStr = attr_to_str(tagObj[":@"], options);
48
48
  let tagStart = indentation + `<${tagName}${attStr}`;
49
49
  let tagValue = arrToStr(tagObj[tagName], options, newJPath, level + 1);
50
- if( (!tagValue || tagValue.length === 0) && options.suppressEmptyNode){
51
- if(options.unpairedTags.indexOf(tagName) !== -1){
52
- xmlStr += tagStart + ">";
53
- }else{
54
- xmlStr += tagStart + "/>";
55
- }
50
+ if(options.unpairedTags.indexOf(tagName) !== -1){
51
+ xmlStr += tagStart + ">";
52
+ }else if( (!tagValue || tagValue.length === 0) && options.suppressEmptyNode){
53
+ xmlStr += tagStart + "/>";
56
54
  }else{
57
55
  //TODO: node with only text value should not parse the text value in next line
58
56
  xmlStr += tagStart + `>${tagValue}${indentation}</${tagName}>` ;
@@ -31,36 +31,9 @@ const defaultOptions = {
31
31
  htmlEntities: false,
32
32
  };
33
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
34
  const buildOptions = function(options) {
61
- return util.buildOptions(options, defaultOptions, props);
35
+ return Object.assign({}, defaultOptions, options);
62
36
  };
63
37
 
64
38
  exports.buildOptions = buildOptions;
65
- exports.defaultOptions = defaultOptions;
66
- exports.props = props;
39
+ exports.defaultOptions = defaultOptions;
@@ -49,6 +49,7 @@ class OrderedObjParser{
49
49
  this.isItStopNode = isItStopNode;
50
50
  this.replaceEntitiesValue = replaceEntitiesValue;
51
51
  this.readStopNodeData = readStopNodeData;
52
+ this.saveTextToParentTag = saveTextToParentTag;
52
53
  }
53
54
 
54
55
  }
@@ -71,14 +72,15 @@ function addExternalEntities(externalEntities){
71
72
  * @param {boolean} dontTrim
72
73
  * @param {boolean} hasAttributes
73
74
  * @param {boolean} isLeafNode
75
+ * @param {boolean} escapeEntities
74
76
  */
75
- function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode) {
77
+ function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode, escapeEntities) {
76
78
  if (val !== undefined) {
77
79
  if (this.options.trimValues && !dontTrim) {
78
80
  val = val.trim();
79
81
  }
80
82
  if(val.length > 0){
81
- val = this.replaceEntitiesValue(val);
83
+ if(!escapeEntities) val = this.replaceEntitiesValue(val);
82
84
 
83
85
  const newval = this.options.tagValueProcessor(tagName, val, jPath, hasAttributes, isLeafNode);
84
86
  if(newval === null || newval === undefined){
@@ -192,14 +194,7 @@ const parseXml = function(xmlData) {
192
194
  }
193
195
 
194
196
  if(currentNode){
195
- textData = this.parseTextData(textData
196
- , currentNode.tagname
197
- , jPath
198
- ,false
199
- , currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false
200
- , Object.keys(currentNode.child).length === 0);
201
- if(textData !== undefined && textData !== "") currentNode.add(this.options.textNodeName, textData);
202
- textData = "";
197
+ textData = this.saveTextToParentTag(textData, currentNode, jPath);
203
198
  }
204
199
 
205
200
  jPath = jPath.substr(0, jPath.lastIndexOf("."));
@@ -208,53 +203,26 @@ const parseXml = function(xmlData) {
208
203
  textData = "";
209
204
  i = closeIndex;
210
205
  } else if( xmlData[i+1] === '?') {
211
- let result = readTagExp(xmlData,i, false, "?>");
212
- if(!result) throw new Error("Pi Tag is not closed.");
213
-
214
- let tagName= result.tagName;
215
- let tagExp = result.tagExp;
216
- let attrExpPresent = result.attrExpPresent;
217
- let closeIndex = result.closeIndex;
206
+ let tagData = readTagExp(xmlData,i, false, "?>");
207
+ if(!tagData) throw new Error("Pi Tag is not closed.");
208
+ textData = this.saveTextToParentTag(textData, currentNode, jPath);
218
209
 
219
- //TODO: remove repeated code
220
- if(textData){ //store previously collected data as textNode
221
- textData = this.parseTextData(textData
222
- , currentNode.tagname
223
- , jPath
224
- ,false
225
- , currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false
226
- , Object.keys(currentNode.child).length === 0);
227
-
228
- if(textData !== undefined && textData !== "") currentNode.add(this.options.textNodeName, textData);
229
- textData = "";
230
- }
231
-
232
- const childNode = new xmlNode(tagName);
233
- childNode.add(this.options.textNodeName, "");
234
-
235
- if(tagName !== tagExp && attrExpPresent){
236
- childNode[":@"] = this.buildAttributesMap(tagExp, jPath);
237
- }
238
- currentNode.addChild(childNode);
210
+ const childNode = new xmlNode(tagData.tagName);
211
+ childNode.add(this.options.textNodeName, "");
212
+
213
+ if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
214
+ childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath);
215
+ }
216
+ currentNode.addChild(childNode);
239
217
 
240
- i = closeIndex + 1;
218
+ i = tagData.closeIndex + 1;
241
219
  } else if(xmlData.substr(i + 1, 3) === '!--') {
242
220
  const endIndex = findClosingIndex(xmlData, "-->", i, "Comment is not closed.")
243
221
  if(this.options.commentPropName){
244
222
  const comment = xmlData.substring(i + 4, endIndex - 2);
245
223
 
246
- //TODO: remove repeated code
247
- if(textData){ //store previously collected data as textNode
248
- textData = this.parseTextData(textData
249
- , currentNode.tagname
250
- , jPath
251
- ,false
252
- , currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false
253
- , Object.keys(currentNode.child).length === 0);
254
-
255
- if(textData !== undefined && textData !== "") currentNode.add(this.options.textNodeName, textData);
256
- textData = "";
257
- }
224
+ textData = this.saveTextToParentTag(textData, currentNode, jPath);
225
+
258
226
  currentNode.add(this.options.commentPropName, [ { [this.options.textNodeName] : comment } ]);
259
227
  }
260
228
  i = endIndex;
@@ -266,17 +234,7 @@ const parseXml = function(xmlData) {
266
234
  const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
267
235
  const tagExp = xmlData.substring(i + 9,closeIndex);
268
236
 
269
- if(textData){ //store previously collected data as textNode
270
- textData = this.parseTextData(textData
271
- , currentNode.tagname
272
- , jPath
273
- ,false
274
- , currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false
275
- , Object.keys(currentNode.child).length === 0);
276
-
277
- if(textData !== undefined && textData !== "") currentNode.add(this.options.textNodeName, textData);
278
- textData = "";
279
- }
237
+ textData = this.saveTextToParentTag(textData, currentNode, jPath);
280
238
 
281
239
  //cdata should be set even if it is 0 length string
282
240
  if(this.options.cdataPropName){
@@ -302,14 +260,7 @@ const parseXml = function(xmlData) {
302
260
  if (currentNode && textData) {
303
261
  if(currentNode.tagname !== '!xml'){
304
262
  //when nested tag is found
305
- textData = this.parseTextData(textData
306
- , currentNode.tagname
307
- , jPath
308
- , false
309
- , currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false
310
- , false);
311
- if(textData !== undefined && textData !== "") currentNode.add(this.options.textNodeName, textData);
312
- textData = "";
263
+ textData = this.saveTextToParentTag(textData, currentNode, jPath, false);
313
264
  }
314
265
  }
315
266
 
@@ -342,6 +293,10 @@ const parseXml = function(xmlData) {
342
293
  if(tagName !== tagExp && attrExpPresent){
343
294
  childNode[":@"] = this.buildAttributesMap(tagExp, jPath);
344
295
  }
296
+ if(tagContent) {
297
+ tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
298
+ }
299
+
345
300
  jPath = jPath.substr(0, jPath.lastIndexOf("."));
346
301
  childNode.add(this.options.textNodeName, tagContent);
347
302
 
@@ -405,6 +360,24 @@ const replaceEntitiesValue = function(val){
405
360
  }
406
361
  return val;
407
362
  }
363
+ function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
364
+ if (textData) { //store previously collected data as textNode
365
+ if(isLeafNode === undefined) isLeafNode = Object.keys(currentNode.child).length === 0
366
+
367
+ textData = this.parseTextData(textData,
368
+ currentNode.tagname,
369
+ jPath,
370
+ false,
371
+ currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false,
372
+ isLeafNode);
373
+
374
+ if (textData !== undefined && textData !== "")
375
+ currentNode.add(this.options.textNodeName, textData);
376
+ textData = "";
377
+ }
378
+ return textData;
379
+ }
380
+
408
381
  //TODO: use jPath to simplify the logic
409
382
  /**
410
383
  *