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

NaturalIntelligence / fast-xml-parser / 14973142022

12 May 2025 01:11PM CUT coverage: 97.547%. Remained the same
14973142022

push

github

web-flow
export types in fxp.d.ts for better module usability (#744)

1090 of 1134 branches covered (96.12%)

8908 of 9132 relevant lines covered (97.55%)

488239.21 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';
5✔
2
//parse Empty Node as self closing node
5✔
3
import buildFromOrderedJs from './orderedJs2Xml.js';
5✔
4
import getIgnoreAttributesFn from "../ignoreAttributes.js";
5✔
5

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

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

315✔
52
  this.processTextOrObjNode = processTextOrObjNode
315✔
53

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

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

5✔
80
Builder.prototype.j2x = function(jObj, level, ajPath) {
5✔
81
  let attrStr = '';
780✔
82
  let val = '';
780✔
83
  const jPath = ajPath.join('.')
780✔
84
  for (let key in jObj) {
780✔
85
    if(!Object.prototype.hasOwnProperty.call(jObj, key)) continue;
2,114✔
86
    if (typeof jObj[key] === 'undefined') {
2,114✔
87
      // supress undefined node only if it is not an attribute
60✔
88
      if (this.isAttribute(key)) {
60✔
89
        val += '';
50✔
90
      }
50✔
91
    } else if (jObj[key] === null) {
2,114✔
92
      // null attribute should be ignored by the attribute list, but should not cause the tag closing
60✔
93
      if (this.isAttribute(key)) {
60✔
94
        val += '';
50✔
95
      } else if (key === this.options.cdataPropName) {
60✔
96
        val += '';
5✔
97
      } else if (key[0] === '?') {
5!
98
        val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
×
99
      } else {
5✔
100
        val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
5✔
101
      }
5✔
102
      // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
60✔
103
    } else if (jObj[key] instanceof Date) {
1,410✔
104
      val += this.buildTextValNode(jObj[key], key, '', level);
10✔
105
    } else if (typeof jObj[key] !== 'object') {
1,350✔
106
      //premitive type
770✔
107
      const attr = this.isAttribute(key);
770✔
108
      if (attr && !this.ignoreAttributesFn(attr, jPath)) {
770✔
109
        attrStr += this.buildAttrPairStr(attr, '' + jObj[key]);
195✔
110
      } else if (!attr) {
770✔
111
        //tag value
505✔
112
        if (key === this.options.textNodeName) {
505✔
113
          let newval = this.options.tagValueProcessor(key, '' + jObj[key]);
220✔
114
          val += this.replaceEntitiesValue(newval);
220✔
115
        } else {
505✔
116
          val += this.buildTextValNode(jObj[key], key, '', level);
285✔
117
        }
285✔
118
      }
505✔
119
    } else if (Array.isArray(jObj[key])) {
1,340✔
120
      //repeated nodes
105✔
121
      const arrLen = jObj[key].length;
105✔
122
      let listTagVal = "";
105✔
123
      let listTagAttr = "";
105✔
124
      for (let j = 0; j < arrLen; j++) {
105✔
125
        const item = jObj[key][j];
325✔
126
        if (typeof item === 'undefined') {
325✔
127
          // supress undefined node
5✔
128
        } else if (item === null) {
325!
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') {
320✔
133
          if(this.options.oneListGroup){
195✔
134
            const result = this.j2x(item, level + 1, ajPath.concat(key));
25✔
135
            listTagVal += result.val;
25✔
136
            if (this.options.attributesGroupName && item.hasOwnProperty(this.options.attributesGroupName)) {
25✔
137
              listTagAttr += result.attrStr
5✔
138
            }
5✔
139
          }else{
195✔
140
            listTagVal += this.processTextOrObjNode(item, key, level, ajPath)
170✔
141
          }
170✔
142
        } else {
320✔
143
          if (this.options.oneListGroup) {
125✔
144
            let textValue = this.options.tagValueProcessor(key, item);
10✔
145
            textValue = this.replaceEntitiesValue(textValue);
10✔
146
            listTagVal += textValue;
10✔
147
          } else {
125✔
148
            listTagVal += this.buildTextValNode(item, key, '', level);
115✔
149
          }
115✔
150
        }
125✔
151
      }
325✔
152
      if(this.options.oneListGroup){
105✔
153
        listTagVal = this.buildObjectNode(listTagVal, key, listTagAttr, level);
15✔
154
      }
15✔
155
      val += listTagVal;
105✔
156
    } else {
570✔
157
      //nested node
465✔
158
      if (this.options.attributesGroupName && key === this.options.attributesGroupName) {
465✔
159
        const Ks = Object.keys(jObj[key]);
85✔
160
        const L = Ks.length;
85✔
161
        for (let j = 0; j < L; j++) {
85✔
162
          attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]]);
145✔
163
        }
145✔
164
      } else {
465✔
165
        val += this.processTextOrObjNode(jObj[key], key, level, ajPath)
380✔
166
      }
380✔
167
    }
465✔
168
  }
2,114✔
169
  return {attrStr: attrStr, val: val};
780✔
170
};
5✔
171

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

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

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

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

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

5✔
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

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

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

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

5✔
278
function isAttribute(name /*, options*/) {
620✔
279
  if (name.startsWith(this.options.attributeNamePrefix) && name !== this.options.textNodeName) {
620✔
280
    return name.substr(this.attrPrefixLen);
365✔
281
  } else {
620✔
282
    return false;
255✔
283
  }
255✔
284
}
620✔
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