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

NaturalIntelligence / fast-xml-parser / 13532857399

25 Feb 2025 11:15PM UTC coverage: 98.946% (+0.7%) from 98.217%
13532857399

push

github

web-flow
Update node.js.yml

1087 of 1120 branches covered (97.05%)

8639 of 8731 relevant lines covered (98.95%)

408524.79 hits per line

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

92.96
/src/xmlbuilder/json2xml.js
1
'use strict';
4✔
2
//parse Empty Node as self closing node
4✔
3
import buildFromOrderedJs from './orderedJs2Xml.js';
4✔
4
import getIgnoreAttributesFn from "../ignoreAttributes.js";
4✔
5

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

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

252✔
52
  this.processTextOrObjNode = processTextOrObjNode
252✔
53

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

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

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

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

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

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

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

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

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

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

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

4✔
274
function indentate(level) {
348✔
275
  return this.options.indentBy.repeat(level);
348✔
276
}
348✔
277

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

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