• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NaturalIntelligence / fast-xml-parser / 12317922166

13 Dec 2024 02:55PM UTC coverage: 98.212%. Remained the same
12317922166

Pull #699

github

web-flow
Merge ae43748a5 into 408290231
Pull Request #699: Fixes entity parsing when used in strict mode

804 of 863 branches covered (93.16%)

2746 of 2796 relevant lines covered (98.21%)

682647.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.36
/src/xmlbuilder/json2xml.js
1
'use strict';
2
//parse Empty Node as self closing node
3
const buildFromOrderedJs = require('./orderedJs2Xml');
4✔
4
const getIgnoreAttributesFn = require('../ignoreAttributes')
4✔
5

6
const defaultOptions = {
4✔
7
  attributeNamePrefix: '@_',
8
  attributesGroupName: false,
9
  textNodeName: '#text',
10
  ignoreAttributes: true,
11
  cdataPropName: false,
12
  format: false,
13
  indentBy: '  ',
14
  suppressEmptyNode: false,
15
  suppressUnpairedNode: true,
16
  suppressBooleanAttributes: true,
17
  tagValueProcessor: function(key, a) {
18
    return a;
704✔
19
  },
20
  attributeValueProcessor: function(attrName, a) {
21
    return a;
456✔
22
  },
23
  preserveOrder: false,
24
  commentPropName: false,
25
  unpairedTags: [],
26
  entities: [
27
    { regex: new RegExp("&", "g"), val: "&" },//it must be on top
28
    { regex: new RegExp(">", "g"), val: ">" },
29
    { regex: new RegExp("<", "g"), val: "&lt;" },
30
    { regex: new RegExp("\'", "g"), val: "&apos;" },
31
    { regex: new RegExp("\"", "g"), val: "&quot;" }
32
  ],
33
  processEntities: true,
34
  stopNodes: [],
35
  // transformTagName: false,
36
  // transformAttributeName: false,
37
  oneListGroup: false
38
};
39

40
function Builder(options) {
41
  this.options = Object.assign({}, defaultOptions, options);
248✔
42
  if (this.options.ignoreAttributes === true || this.options.attributesGroupName) {
248✔
43
    this.isAttribute = function(/*a*/) {
104✔
44
      return false;
208✔
45
    };
46
  } else {
47
    this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
144✔
48
    this.attrPrefixLen = this.options.attributeNamePrefix.length;
144✔
49
    this.isAttribute = isAttribute;
144✔
50
  }
51

52
  this.processTextOrObjNode = processTextOrObjNode
248✔
53

54
  if (this.options.format) {
248✔
55
    this.indentate = indentate;
84✔
56
    this.tagEndChar = '>\n';
84✔
57
    this.newLine = '\n';
84✔
58
  } else {
59
    this.indentate = function() {
164✔
60
      return '';
676✔
61
    };
62
    this.tagEndChar = '>';
164✔
63
    this.newLine = '';
164✔
64
  }
65
}
66

67
Builder.prototype.build = function(jObj) {
4✔
68
  if(this.options.preserveOrder){
248✔
69
    return buildFromOrderedJs(jObj, this.options);
88✔
70
  }else {
71
    if(Array.isArray(jObj) && this.options.arrayNodeName && this.options.arrayNodeName.length > 1){
160✔
72
      jObj = {
4✔
73
        [this.options.arrayNodeName] : jObj
74
      }
75
    }
76
    return this.j2x(jObj, 0, []).val;
160✔
77
  }
78
};
79

80
Builder.prototype.j2x = function(jObj, level, ajPath) {
4✔
81
  let attrStr = '';
612✔
82
  let val = '';
612✔
83
  const jPath = ajPath.join('.')
612✔
84
  for (let key in jObj) {
612✔
85
    if(!Object.prototype.hasOwnProperty.call(jObj, key)) continue;
1,669✔
86
    if (typeof jObj[key] === 'undefined') {
1,160✔
87
      // supress undefined node only if it is not an attribute
88
      if (this.isAttribute(key)) {
44✔
89
        val += '';
40✔
90
      }
91
    } else if (jObj[key] === null) {
1,116✔
92
      // null attribute should be ignored by the attribute list, but should not cause the tag closing
93
      if (this.isAttribute(key)) {
44✔
94
        val += '';
40✔
95
      } else if (key[0] === '?') {
4!
96
        val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
×
97
      } else {
98
        val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
4✔
99
      }
100
      // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
101
    } else if (jObj[key] instanceof Date) {
1,072✔
102
      val += this.buildTextValNode(jObj[key], key, '', level);
8✔
103
    } else if (typeof jObj[key] !== 'object') {
1,064✔
104
      //premitive type
105
      const attr = this.isAttribute(key);
616✔
106
      if (attr && !this.ignoreAttributesFn(attr, jPath)) {
616✔
107
        attrStr += this.buildAttrPairStr(attr, '' + jObj[key]);
156✔
108
      } else if (!attr) {
460✔
109
        //tag value
110
        if (key === this.options.textNodeName) {
404✔
111
          let newval = this.options.tagValueProcessor(key, '' + jObj[key]);
176✔
112
          val += this.replaceEntitiesValue(newval);
176✔
113
        } else {
114
          val += this.buildTextValNode(jObj[key], key, '', level);
228✔
115
        }
116
      }
117
    } else if (Array.isArray(jObj[key])) {
448✔
118
      //repeated nodes
119
      const arrLen = jObj[key].length;
84✔
120
      let listTagVal = "";
84✔
121
      let listTagAttr = "";
84✔
122
      for (let j = 0; j < arrLen; j++) {
84✔
123
        const item = jObj[key][j];
260✔
124
        if (typeof item === 'undefined') {
260✔
125
          // supress undefined node
126
        } else if (item === null) {
256!
127
          if(key[0] === "?") val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
×
128
          else val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
×
129
          // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
130
        } else if (typeof item === 'object') {
256✔
131
          if(this.options.oneListGroup){
156✔
132
            const result = this.j2x(item, level + 1, ajPath.concat(key));
20✔
133
            listTagVal += result.val;
20✔
134
            if (this.options.attributesGroupName && item.hasOwnProperty(this.options.attributesGroupName)) {
20✔
135
              listTagAttr += result.attrStr
4✔
136
            }
137
          }else{
138
            listTagVal += this.processTextOrObjNode(item, key, level, ajPath)
136✔
139
          }
140
        } else {
141
          if (this.options.oneListGroup) {
100✔
142
            let textValue = this.options.tagValueProcessor(key, item);
8✔
143
            textValue = this.replaceEntitiesValue(textValue);
8✔
144
            listTagVal += textValue;
8✔
145
          } else {
146
            listTagVal += this.buildTextValNode(item, key, '', level);
92✔
147
          }
148
        }
149
      }
150
      if(this.options.oneListGroup){
84✔
151
        listTagVal = this.buildObjectNode(listTagVal, key, listTagAttr, level);
12✔
152
      }
153
      val += listTagVal;
84✔
154
    } else {
155
      //nested node
156
      if (this.options.attributesGroupName && key === this.options.attributesGroupName) {
364✔
157
        const Ks = Object.keys(jObj[key]);
68✔
158
        const L = Ks.length;
68✔
159
        for (let j = 0; j < L; j++) {
68✔
160
          attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]]);
116✔
161
        }
162
      } else {
163
        val += this.processTextOrObjNode(jObj[key], key, level, ajPath)
296✔
164
      }
165
    }
166
  }
167
  return {attrStr: attrStr, val: val};
612✔
168
};
169

170
Builder.prototype.buildAttrPairStr = function(attrName, val){
4✔
171
  val = this.options.attributeValueProcessor(attrName, '' + val);
272✔
172
  val = this.replaceEntitiesValue(val);
272✔
173
  if (this.options.suppressBooleanAttributes && val === "true") {
272✔
174
    return ' ' + attrName;
12✔
175
  } else return ' ' + attrName + '="' + val + '"';
260✔
176
}
177

178
function processTextOrObjNode (object, key, level, ajPath) {
179
  const result = this.j2x(object, level + 1, ajPath.concat(key));
432✔
180
  if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
432✔
181
    return this.buildTextValNode(object[this.options.textNodeName], key, result.attrStr, level);
36✔
182
  } else {
183
    return this.buildObjectNode(result.val, key, result.attrStr, level);
396✔
184
  }
185
}
186

187
Builder.prototype.buildObjectNode = function(val, key, attrStr, level) {
4✔
188
  if(val === ""){
408✔
189
    if(key[0] === "?") return  this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar;
52✔
190
    else {
191
      return this.indentate(level) + '<' + key + attrStr + this.closeTag(key) + this.tagEndChar;
44✔
192
    }
193
  }else{
194

195
    let tagEndExp = '</' + key + this.tagEndChar;
356✔
196
    let piClosingChar = "";
356✔
197
    
198
    if(key[0] === "?") {
356!
199
      piClosingChar = "?";
×
200
      tagEndExp = "";
×
201
    }
202
  
203
    // attrStr is an empty string in case the attribute came as undefined or null
204
    if ((attrStr || attrStr === '') && val.indexOf('<') === -1) {
356✔
205
      return ( this.indentate(level) + '<' +  key + attrStr + piClosingChar + '>' + val + tagEndExp );
108✔
206
    } else if (this.options.commentPropName !== false && key === this.options.commentPropName && piClosingChar.length === 0) {
248!
207
      return this.indentate(level) + `<!--${val}-->` + this.newLine;
×
208
    }else {
209
      return (
248✔
210
        this.indentate(level) + '<' + key + attrStr + piClosingChar + this.tagEndChar +
211
        val +
212
        this.indentate(level) + tagEndExp    );
213
    }
214
  }
215
}
216

217
Builder.prototype.closeTag = function(key){
4✔
218
  let closeTag = "";
96✔
219
  if(this.options.unpairedTags.indexOf(key) !== -1){ //unpaired
96✔
220
    if(!this.options.suppressUnpairedNode) closeTag = "/"
28✔
221
  }else if(this.options.suppressEmptyNode){ //empty
68✔
222
    closeTag = "/";
28✔
223
  }else{
224
    closeTag = `></${key}`
40✔
225
  }
226
  return closeTag;
96✔
227
}
228

229
function buildEmptyObjNode(val, key, attrStr, level) {
230
  if (val !== '') {
×
231
    return this.buildObjectNode(val, key, attrStr, level);
×
232
  } else {
233
    if(key[0] === "?") return  this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar;
×
234
    else {
235
      return  this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
×
236
      // return this.buildTagStr(level,key, attrStr);
237
    }
238
  }
239
}
240

241
Builder.prototype.buildTextValNode = function(val, key, attrStr, level) {
4✔
242
  if (this.options.cdataPropName !== false && key === this.options.cdataPropName) {
364✔
243
    return this.indentate(level) + `<![CDATA[${val}]]>` +  this.newLine;
16✔
244
  }else if (this.options.commentPropName !== false && key === this.options.commentPropName) {
348✔
245
    return this.indentate(level) + `<!--${val}-->` +  this.newLine;
16✔
246
  }else if(key[0] === "?") {//PI tag
332!
247
    return  this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar; 
×
248
  }else{
249
    let textValue = this.options.tagValueProcessor(key, val);
332✔
250
    textValue = this.replaceEntitiesValue(textValue);
332✔
251
  
252
    if( textValue === ''){
332✔
253
      return this.indentate(level) + '<' + key + attrStr + this.closeTag(key) + this.tagEndChar;
52✔
254
    }else{
255
      return this.indentate(level) + '<' + key + attrStr + '>' +
280✔
256
         textValue +
257
        '</' + key + this.tagEndChar;
258
    }
259
  }
260
}
261

262
Builder.prototype.replaceEntitiesValue = function(textValue){
4✔
263
  if(textValue && textValue.length > 0 && this.options.processEntities){
788✔
264
    for (let i=0; i<this.options.entities.length; i++) {
592✔
265
      const entity = this.options.entities[i];
2,960✔
266
      textValue = textValue.replace(entity.regex, entity.val);
2,960✔
267
    }
268
  }
269
  return textValue;
788✔
270
}
271

272
function indentate(level) {
273
  return this.options.indentBy.repeat(level);
348✔
274
}
275

276
function isAttribute(name /*, options*/) {
277
  if (name.startsWith(this.options.attributeNamePrefix) && name !== this.options.textNodeName) {
496✔
278
    return name.substr(this.attrPrefixLen);
292✔
279
  } else {
280
    return false;
204✔
281
  }
282
}
283

284
module.exports = Builder;
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc