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

NaturalIntelligence / fast-xml-parser / 5703658169

pending completion
5703658169

push

github

web-flow
Fix for null and undefined attributes when building xml (#585) (#598)

* fix (xmlbuilder): null and undefined attributes are suppressed in the built xml (#585)

* test (xmlbuilder): add tests on null and undefined attributes building (#585)

Null and Undefined attributes should be suppressed when ignoreAttributes is false
both when format is true and when it is false

766 of 823 branches covered (93.07%)

32 of 32 new or added lines in 2 files covered. (100.0%)

2595 of 2642 relevant lines covered (98.22%)

538821.74 hits per line

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

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

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

39
function Builder(options) {
40
  this.options = Object.assign({}, defaultOptions, options);
165✔
41
  if (this.options.ignoreAttributes || this.options.attributesGroupName) {
165✔
42
    this.isAttribute = function(/*a*/) {
69✔
43
      return false;
147✔
44
    };
45
  } else {
46
    this.attrPrefixLen = this.options.attributeNamePrefix.length;
96✔
47
    this.isAttribute = isAttribute;
96✔
48
  }
49

50
  this.processTextOrObjNode = processTextOrObjNode
165✔
51

52
  if (this.options.format) {
165✔
53
    this.indentate = indentate;
63✔
54
    this.tagEndChar = '>\n';
63✔
55
    this.newLine = '\n';
63✔
56
  } else {
57
    this.indentate = function() {
102✔
58
      return '';
447✔
59
    };
60
    this.tagEndChar = '>';
102✔
61
    this.newLine = '';
102✔
62
  }
63
}
64

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

78
Builder.prototype.j2x = function(jObj, level) {
3✔
79
  let attrStr = '';
402✔
80
  let val = '';
402✔
81
  for (let key in jObj) {
402✔
82
    if (typeof jObj[key] === 'undefined') {
738✔
83
      // supress undefined node only if it is not an attribute
84
      if (this.isAttribute(key)) {
33✔
85
        val += '';
30✔
86
      }
87
    } else if (jObj[key] === null) {
705✔
88
      // null attribute should be ignored by the attribute list, but should not cause the tag closing
89
      if (this.isAttribute(key)) {
33✔
90
        val += '';
30✔
91
      } else if (key[0] === '?') {
3!
92
        val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
×
93
      } else {
94
        val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
3✔
95
      }
96
      // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
97
    } else if (jObj[key] instanceof Date) {
672✔
98
      val += this.buildTextValNode(jObj[key], key, '', level);
6✔
99
    } else if (typeof jObj[key] !== 'object') {
666✔
100
      //premitive type
101
      const attr = this.isAttribute(key);
366✔
102
      if (attr) {
366✔
103
        attrStr += this.buildAttrPairStr(attr, '' + jObj[key]);
75✔
104
      }else {
105
        //tag value
106
        if (key === this.options.textNodeName) {
291✔
107
          let newval = this.options.tagValueProcessor(key, '' + jObj[key]);
129✔
108
          val += this.replaceEntitiesValue(newval);
129✔
109
        } else {
110
          val += this.buildTextValNode(jObj[key], key, '', level);
162✔
111
        }
112
      }
113
    } else if (Array.isArray(jObj[key])) {
300✔
114
      //repeated nodes
115
      const arrLen = jObj[key].length;
57✔
116
      let listTagVal = "";
57✔
117
      for (let j = 0; j < arrLen; j++) {
57✔
118
        const item = jObj[key][j];
180✔
119
        if (typeof item === 'undefined') {
180✔
120
          // supress undefined node
121
        } else if (item === null) {
177!
122
          if(key[0] === "?") val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
×
123
          else val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
×
124
          // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
125
        } else if (typeof item === 'object') {
177✔
126
          if(this.options.oneListGroup ){
108✔
127
            listTagVal += this.j2x(item, level + 1).val;
6✔
128
          }else{
129
            listTagVal += this.processTextOrObjNode(item, key, level)
102✔
130
          }
131
        } else {
132
          listTagVal += this.buildTextValNode(item, key, '', level);
69✔
133
        }
134
      }
135
      if(this.options.oneListGroup){
57✔
136
        listTagVal = this.buildObjectNode(listTagVal, key, '', level);
3✔
137
      }
138
      val += listTagVal;
57✔
139
    } else {
140
      //nested node
141
      if (this.options.attributesGroupName && key === this.options.attributesGroupName) {
243✔
142
        const Ks = Object.keys(jObj[key]);
48✔
143
        const L = Ks.length;
48✔
144
        for (let j = 0; j < L; j++) {
48✔
145
          attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]]);
78✔
146
        }
147
      } else {
148
        val += this.processTextOrObjNode(jObj[key], key, level)
195✔
149
      }
150
    }
151
  }
152
  return {attrStr: attrStr, val: val};
402✔
153
};
154

155
Builder.prototype.buildAttrPairStr = function(attrName, val){
3✔
156
  val = this.options.attributeValueProcessor(attrName, '' + val);
153✔
157
  val = this.replaceEntitiesValue(val);
153✔
158
  if (this.options.suppressBooleanAttributes && val === "true") {
153✔
159
    return ' ' + attrName;
9✔
160
  } else return ' ' + attrName + '="' + val + '"';
144✔
161
}
162

163
function processTextOrObjNode (object, key, level) {
164
  const result = this.j2x(object, level + 1);
297✔
165
  if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
297✔
166
    return this.buildTextValNode(object[this.options.textNodeName], key, result.attrStr, level);
27✔
167
  } else {
168
    return this.buildObjectNode(result.val, key, result.attrStr, level);
270✔
169
  }
170
}
171

172
Builder.prototype.buildObjectNode = function(val, key, attrStr, level) {
3✔
173
  if(val === ""){
273✔
174
    if(key[0] === "?") return  this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar;
30✔
175
    else {
176
      return this.indentate(level) + '<' + key + attrStr + this.closeTag(key) + this.tagEndChar;
24✔
177
    }
178
  }else{
179

180
    let tagEndExp = '</' + key + this.tagEndChar;
243✔
181
    let piClosingChar = "";
243✔
182
    
183
    if(key[0] === "?") {
243!
184
      piClosingChar = "?";
×
185
      tagEndExp = "";
×
186
    }
187
  
188
    // attrStr is an empty string in case the attribute came as undefined or null
189
    if ((attrStr || attrStr === '') && val.indexOf('<') === -1) {
243✔
190
      return ( this.indentate(level) + '<' +  key + attrStr + piClosingChar + '>' + val + tagEndExp );
75✔
191
    } else if (this.options.commentPropName !== false && key === this.options.commentPropName && piClosingChar.length === 0) {
168!
192
      return this.indentate(level) + `<!--${val}-->` + this.newLine;
×
193
    }else {
194
      return (
168✔
195
        this.indentate(level) + '<' + key + attrStr + piClosingChar + this.tagEndChar +
196
        val +
197
        this.indentate(level) + tagEndExp    );
198
    }
199
  }
200
}
201

202
Builder.prototype.closeTag = function(key){
3✔
203
  let closeTag = "";
63✔
204
  if(this.options.unpairedTags.indexOf(key) !== -1){ //unpaired
63✔
205
    if(!this.options.suppressUnpairedNode) closeTag = "/"
21✔
206
  }else if(this.options.suppressEmptyNode){ //empty
42✔
207
    closeTag = "/";
21✔
208
  }else{
209
    closeTag = `></${key}`
21✔
210
  }
211
  return closeTag;
63✔
212
}
213

214
function buildEmptyObjNode(val, key, attrStr, level) {
215
  if (val !== '') {
×
216
    return this.buildObjectNode(val, key, attrStr, level);
×
217
  } else {
218
    if(key[0] === "?") return  this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar;
×
219
    else {
220
      return  this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
×
221
      // return this.buildTagStr(level,key, attrStr);
222
    }
223
  }
224
}
225

226
Builder.prototype.buildTextValNode = function(val, key, attrStr, level) {
3✔
227
  if (this.options.cdataPropName !== false && key === this.options.cdataPropName) {
264✔
228
    return this.indentate(level) + `<![CDATA[${val}]]>` +  this.newLine;
12✔
229
  }else if (this.options.commentPropName !== false && key === this.options.commentPropName) {
252✔
230
    return this.indentate(level) + `<!--${val}-->` +  this.newLine;
12✔
231
  }else if(key[0] === "?") {//PI tag
240!
232
    return  this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar; 
×
233
  }else{
234
    let textValue = this.options.tagValueProcessor(key, val);
240✔
235
    textValue = this.replaceEntitiesValue(textValue);
240✔
236
  
237
    if( textValue === ''){
240✔
238
      return this.indentate(level) + '<' + key + attrStr + this.closeTag(key) + this.tagEndChar;
39✔
239
    }else{
240
      return this.indentate(level) + '<' + key + attrStr + '>' +
201✔
241
         textValue +
242
        '</' + key + this.tagEndChar;
243
    }
244
  }
245
}
246

247
Builder.prototype.replaceEntitiesValue = function(textValue){
3✔
248
  if(textValue && textValue.length > 0 && this.options.processEntities){
522✔
249
    for (let i=0; i<this.options.entities.length; i++) {
378✔
250
      const entity = this.options.entities[i];
1,890✔
251
      textValue = textValue.replace(entity.regex, entity.val);
1,890✔
252
    }
253
  }
254
  return textValue;
522✔
255
}
256

257
function indentate(level) {
258
  return this.options.indentBy.repeat(level);
261✔
259
}
260

261
function isAttribute(name /*, options*/) {
262
  if (name.startsWith(this.options.attributeNamePrefix) && name !== this.options.textNodeName) {
285✔
263
    return name.substr(this.attrPrefixLen);
135✔
264
  } else {
265
    return false;
150✔
266
  }
267
}
268

269
module.exports = Builder;
3✔
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