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

NaturalIntelligence / fast-xml-parser / 4826002403

pending completion
4826002403

push

github

amit kumar gupta
update package detail

753 of 810 branches covered (92.96%)

2545 of 2591 relevant lines covered (98.22%)

183140.75 hits per line

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

96.28
/src/xmlparser/OrderedObjParser.js
1
'use strict';
2
///@ts-check
3

4
const util = require('../util');
1✔
5
const xmlNode = require('./xmlNode');
1✔
6
const readDocType = require("./DocTypeReader");
1✔
7
const toNumber = require("strnum");
1✔
8

9
const regx =
10
  '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
1✔
11
  .replace(/NAME/g, util.nameRegexp);
12

13
//const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g");
14
//const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
15

16
class OrderedObjParser{
17
  constructor(options){
18
    this.options = options;
154✔
19
    this.currentNode = null;
154✔
20
    this.tagsNodeStack = [];
154✔
21
    this.docTypeEntities = {};
154✔
22
    this.lastEntities = {
154✔
23
      "apos" : { regex: /&(apos|#39|#x27);/g, val : "'"},
24
      "gt" : { regex: /&(gt|#62|#x3E);/g, val : ">"},
25
      "lt" : { regex: /&(lt|#60|#x3C);/g, val : "<"},
26
      "quot" : { regex: /&(quot|#34|#x22);/g, val : "\""},
27
    };
28
    this.ampEntity = { regex: /&(amp|#38|#x26);/g, val : "&"};
154✔
29
    this.htmlEntities = {
154✔
30
      "space": { regex: /&(nbsp|#160);/g, val: " " },
31
      // "lt" : { regex: /&(lt|#60);/g, val: "<" },
32
      // "gt" : { regex: /&(gt|#62);/g, val: ">" },
33
      // "amp" : { regex: /&(amp|#38);/g, val: "&" },
34
      // "quot" : { regex: /&(quot|#34);/g, val: "\"" },
35
      // "apos" : { regex: /&(apos|#39);/g, val: "'" },
36
      "cent" : { regex: /&(cent|#162);/g, val: "¢" },
37
      "pound" : { regex: /&(pound|#163);/g, val: "£" },
38
      "yen" : { regex: /&(yen|#165);/g, val: "Â¥" },
39
      "euro" : { regex: /&(euro|#8364);/g, val: "€" },
40
      "copyright" : { regex: /&(copy|#169);/g, val: "©" },
41
      "reg" : { regex: /&(reg|#174);/g, val: "®" },
42
      "inr" : { regex: /&(inr|#8377);/g, val: "₹" },
43
    };
44
    this.addExternalEntities = addExternalEntities;
154✔
45
    this.parseXml = parseXml;
154✔
46
    this.parseTextData = parseTextData;
154✔
47
    this.resolveNameSpace = resolveNameSpace;
154✔
48
    this.buildAttributesMap = buildAttributesMap;
154✔
49
    this.isItStopNode = isItStopNode;
154✔
50
    this.replaceEntitiesValue = replaceEntitiesValue;
154✔
51
    this.readStopNodeData = readStopNodeData;
154✔
52
    this.saveTextToParentTag = saveTextToParentTag;
154✔
53
    this.addChild = addChild;
154✔
54
  }
55

56
}
57

58
function addExternalEntities(externalEntities){
59
  const entKeys = Object.keys(externalEntities);
154✔
60
  for (let i = 0; i < entKeys.length; i++) {
154✔
61
    const ent = entKeys[i];
3✔
62
    this.lastEntities[ent] = {
3✔
63
       regex: new RegExp("&"+ent+";","g"),
64
       val : externalEntities[ent]
65
    }
66
  }
67
}
68

69
/**
70
 * @param {string} val
71
 * @param {string} tagName
72
 * @param {string} jPath
73
 * @param {boolean} dontTrim
74
 * @param {boolean} hasAttributes
75
 * @param {boolean} isLeafNode
76
 * @param {boolean} escapeEntities
77
 */
78
function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode, escapeEntities) {
79
  if (val !== undefined) {
1,000!
80
    if (this.options.trimValues && !dontTrim) {
1,000✔
81
      val = val.trim();
936✔
82
    }
83
    if(val.length > 0){
1,000✔
84
      if(!escapeEntities) val = this.replaceEntitiesValue(val);
385✔
85
      
86
      const newval = this.options.tagValueProcessor(tagName, val, jPath, hasAttributes, isLeafNode);
385✔
87
      if(newval === null || newval === undefined){
385✔
88
        //don't parse
89
        return val;
10✔
90
      }else if(typeof newval !== typeof val || newval !== val){
375✔
91
        //overwrite
92
        return newval;
4✔
93
      }else if(this.options.trimValues){
371✔
94
        return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
359✔
95
      }else{
96
        const trimmedVal = val.trim();
12✔
97
        if(trimmedVal === val){
12✔
98
          return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
3✔
99
        }else{
100
          return val;
9✔
101
        }
102
      }
103
    }
104
  }
105
}
106

107
function resolveNameSpace(tagname) {
108
  if (this.options.removeNSPrefix) {
246✔
109
    const tags = tagname.split(':');
22✔
110
    const prefix = tagname.charAt(0) === '/' ? '/' : '';
22!
111
    if (tags[0] === 'xmlns') {
22✔
112
      return '';
7✔
113
    }
114
    if (tags.length === 2) {
15✔
115
      tagname = prefix + tags[1];
6✔
116
    }
117
  }
118
  return tagname;
239✔
119
}
120

121
//TODO: change regex to capture NS
122
//const attrsRegx = new RegExp("([\\w\\-\\.\\:]+)\\s*=\\s*(['\"])((.|\n)*?)\\2","gm");
123
const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
1✔
124

125
function buildAttributesMap(attrStr, jPath, tagName) {
126
  if (!this.options.ignoreAttributes && typeof attrStr === 'string') {
204✔
127
    // attrStr = attrStr.replace(/\r?\n/g, ' ');
128
    //attrStr = attrStr || attrStr.trim();
129

130
    const matches = util.getAllMatches(attrStr, attrsRegx);
171✔
131
    const len = matches.length; //don't make it inline
171✔
132
    const attrs = {};
171✔
133
    for (let i = 0; i < len; i++) {
171✔
134
      const attrName = this.resolveNameSpace(matches[i][1]);
246✔
135
      let oldVal = matches[i][4];
246✔
136
      let aName = this.options.attributeNamePrefix + attrName;
246✔
137
      if (attrName.length) {
246✔
138
        if (this.options.transformAttributeName) {
239✔
139
          aName = this.options.transformAttributeName(aName);
4✔
140
        }
141
        if(aName === "__proto__") aName  = "#__proto__";
239!
142
        if (oldVal !== undefined) {
239✔
143
          if (this.options.trimValues) {
222✔
144
            oldVal = oldVal.trim();
217✔
145
          }
146
          oldVal = this.replaceEntitiesValue(oldVal);
222✔
147
          const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPath);
222✔
148
          if(newVal === null || newVal === undefined){
222!
149
            //don't parse
150
            attrs[aName] = oldVal;
×
151
          }else if(typeof newVal !== typeof oldVal || newVal !== oldVal){
222!
152
            //overwrite
153
            attrs[aName] = newVal;
×
154
          }else{
155
            //parse
156
            attrs[aName] = parseValue(
222✔
157
              oldVal,
158
              this.options.parseAttributeValue,
159
              this.options.numberParseOptions
160
            );
161
          }
162
        } else if (this.options.allowBooleanAttributes) {
17✔
163
          attrs[aName] = true;
15✔
164
        }
165
      }
166
    }
167
    if (!Object.keys(attrs).length) {
171✔
168
      return;
18✔
169
    }
170
    if (this.options.attributesGroupName) {
153✔
171
      const attrCollection = {};
12✔
172
      attrCollection[this.options.attributesGroupName] = attrs;
12✔
173
      return attrCollection;
12✔
174
    }
175
    return attrs
141✔
176
  }
177
}
178

179
const parseXml = function(xmlData) {
1✔
180
  xmlData = xmlData.replace(/\r\n?/g, "\n"); //TODO: remove this line
154✔
181
  const xmlObj = new xmlNode('!xml');
154✔
182
  let currentNode = xmlObj;
154✔
183
  let textData = "";
154✔
184
  let jPath = "";
154✔
185
  for(let i=0; i< xmlData.length; i++){//for each char in XML data
154✔
186
    const ch = xmlData[i];
11,620✔
187
    if(ch === '<'){
11,620✔
188
      // const nextIndex = i+1;
189
      // const _2ndChar = xmlData[nextIndex];
190
      if( xmlData[i+1] === '/') {//Closing Tag
1,310✔
191
        const closeIndex = findClosingIndex(xmlData, ">", i, "Closing Tag is not closed.")
543✔
192
        let tagName = xmlData.substring(i+2,closeIndex).trim();
542✔
193

194
        if(this.options.removeNSPrefix){
542✔
195
          const colonIndex = tagName.indexOf(":");
21✔
196
          if(colonIndex !== -1){
21✔
197
            tagName = tagName.substr(colonIndex+1);
10✔
198
          }
199
        }
200

201
        if(this.options.transformTagName) {
542✔
202
          tagName = this.options.transformTagName(tagName);
10✔
203
        }
204

205
        if(currentNode){
542!
206
          textData = this.saveTextToParentTag(textData, currentNode, jPath);
542✔
207
        }
208

209
        //check if last tag of nested tag was unpaired tag
210
        const lastTagName = jPath.substring(jPath.lastIndexOf(".")+1);
542✔
211
        if(tagName && this.options.unpairedTags.indexOf(tagName) !== -1 ){
542✔
212
          throw new Error(`Unpaired tag can not be used as closing tag: </${tagName}>`);
1✔
213
        }
214
        let propIndex = 0
541✔
215
        if(lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1 ){
541✔
216
          propIndex = jPath.lastIndexOf('.', jPath.lastIndexOf('.')-1)
15✔
217
          this.tagsNodeStack.pop();
15✔
218
        }else{
219
          propIndex = jPath.lastIndexOf(".");
526✔
220
        }
221
        jPath = jPath.substring(0, propIndex);
541✔
222

223
        currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
541✔
224
        textData = "";
541✔
225
        i = closeIndex;
541✔
226
      } else if( xmlData[i+1] === '?') {
767✔
227

228
        let tagData = readTagExp(xmlData,i, false, "?>");
44✔
229
        if(!tagData) throw new Error("Pi Tag is not closed.");
44✔
230

231
        textData = this.saveTextToParentTag(textData, currentNode, jPath);
42✔
232
        if( (this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags){
42✔
233

234
        }else{
235
  
236
          const childNode = new xmlNode(tagData.tagName);
37✔
237
          childNode.add(this.options.textNodeName, "");
37✔
238
          
239
          if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
37✔
240
            childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath, tagData.tagName);
35✔
241
          }
242
          this.addChild(currentNode, childNode, jPath)
37✔
243

244
        }
245

246

247
        i = tagData.closeIndex + 1;
42✔
248
      } else if(xmlData.substr(i + 1, 3) === '!--') {
723✔
249
        const endIndex = findClosingIndex(xmlData, "-->", i+4, "Comment is not closed.")
12✔
250
        if(this.options.commentPropName){
11✔
251
          const comment = xmlData.substring(i + 4, endIndex - 2);
5✔
252

253
          textData = this.saveTextToParentTag(textData, currentNode, jPath);
5✔
254

255
          currentNode.add(this.options.commentPropName, [ { [this.options.textNodeName] : comment } ]);
5✔
256
        }
257
        i = endIndex;
11✔
258
      } else if( xmlData.substr(i + 1, 2) === '!D') {
711✔
259
        const result = readDocType(xmlData, i);
11✔
260
        this.docTypeEntities = result.entities;
10✔
261
        i = result.i;
10✔
262
      }else if(xmlData.substr(i + 1, 2) === '![') {
700✔
263
        const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
50✔
264
        const tagExp = xmlData.substring(i + 9,closeIndex);
49✔
265

266
        textData = this.saveTextToParentTag(textData, currentNode, jPath);
49✔
267

268
        //cdata should be set even if it is 0 length string
269
        if(this.options.cdataPropName){
49✔
270
          // let val = this.parseTextData(tagExp, this.options.cdataPropName, jPath + "." + this.options.cdataPropName, true, false, true);
271
          // if(!val) val = "";
272
          currentNode.add(this.options.cdataPropName, [ { [this.options.textNodeName] : tagExp } ]);
13✔
273
        }else{
274
          let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true);
36✔
275
          if(val == undefined) val = "";
36✔
276
          currentNode.add(this.options.textNodeName, val);
36✔
277
        }
278
        
279
        i = closeIndex + 2;
49✔
280
      }else {//Opening tag
281
        let result = readTagExp(xmlData,i, this.options.removeNSPrefix);
650✔
282
        let tagName= result.tagName;
650✔
283
        let tagExp = result.tagExp;
650✔
284
        let attrExpPresent = result.attrExpPresent;
650✔
285
        let closeIndex = result.closeIndex;
650✔
286

287
        if (this.options.transformTagName) {
650✔
288
          tagName = this.options.transformTagName(tagName);
10✔
289
        }
290
        
291
        //save text as child node
292
        if (currentNode && textData) {
650✔
293
          if(currentNode.tagname !== '!xml'){
473✔
294
            //when nested tag is found
295
            textData = this.saveTextToParentTag(textData, currentNode, jPath, false);
433✔
296
          }
297
        }
298

299
        //check if last tag was unpaired tag
300
        const lastTag = currentNode;
650✔
301
        if(lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1 ){
650✔
302
          currentNode = this.tagsNodeStack.pop();
24✔
303
          jPath = jPath.substring(0, jPath.lastIndexOf("."));
24✔
304
        }
305
        if(tagName !== xmlObj.tagname){
650!
306
          jPath += jPath ? "." + tagName : tagName;
650✔
307
        }
308
        if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) { //TODO: namespace
650✔
309
          let tagContent = "";
22✔
310
          //self-closing tag
311
          if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
22✔
312
            i = result.closeIndex;
1✔
313
          }
314
          //unpaired tag
315
          else if(this.options.unpairedTags.indexOf(tagName) !== -1){
21✔
316
            i = result.closeIndex;
1✔
317
          }
318
          //normal tag
319
          else{
320
            //read until closing tag is found
321
            const result = this.readStopNodeData(xmlData, tagName, closeIndex + 1);
20✔
322
            if(!result) throw new Error(`Unexpected end of ${tagName}`);
20!
323
            i = result.i;
20✔
324
            tagContent = result.tagContent;
20✔
325
          }
326

327
          const childNode = new xmlNode(tagName);
22✔
328
          if(tagName !== tagExp && attrExpPresent){
22✔
329
            childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
8✔
330
          }
331
          if(tagContent) {
22✔
332
            tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
16✔
333
          }
334
          
335
          jPath = jPath.substr(0, jPath.lastIndexOf("."));
22✔
336
          childNode.add(this.options.textNodeName, tagContent);
22✔
337
          
338
          this.addChild(currentNode, childNode, jPath)
22✔
339
        }else{
340
  //selfClosing tag
341
          if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
628✔
342
            if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
44✔
343
              tagName = tagName.substr(0, tagName.length - 1);
13✔
344
              tagExp = tagName;
13✔
345
            }else{
346
              tagExp = tagExp.substr(0, tagExp.length - 1);
31✔
347
            }
348
            
349
            if(this.options.transformTagName) {
44!
350
              tagName = this.options.transformTagName(tagName);
×
351
            }
352

353
            const childNode = new xmlNode(tagName);
44✔
354
            if(tagName !== tagExp && attrExpPresent){
44✔
355
              childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
31✔
356
            }
357
            this.addChild(currentNode, childNode, jPath)
44✔
358
            jPath = jPath.substr(0, jPath.lastIndexOf("."));
44✔
359
          }
360
    //opening tag
361
          else{
362
            const childNode = new xmlNode( tagName);
584✔
363
            this.tagsNodeStack.push(currentNode);
584✔
364
            
365
            if(tagName !== tagExp && attrExpPresent){
584✔
366
              childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
130✔
367
            }
368
            this.addChild(currentNode, childNode, jPath)
584✔
369
            currentNode = childNode;
584✔
370
          }
371
          textData = "";
628✔
372
          i = closeIndex;
628✔
373
        }
374
      }
375
    }else{
376
      textData += xmlData[i];
10,310✔
377
    }
378
  }
379
  return xmlObj.child;
147✔
380
}
381

382
function addChild(currentNode, childNode, jPath){
383
  const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"])
687✔
384
  if(result === false){
687✔
385
  }else if(typeof result === "string"){
684!
386
    childNode.tagname = result
684✔
387
    currentNode.addChild(childNode);
684✔
388
  }else{
389
    currentNode.addChild(childNode);
×
390
  }
391
}
392

393
const replaceEntitiesValue = function(val){
1✔
394

395
  if(this.options.processEntities){
591✔
396
    for(let entityName in this.docTypeEntities){
578✔
397
      const entity = this.docTypeEntities[entityName];
42✔
398
      val = val.replace( entity.regx, entity.val);
42✔
399
    }
400
    for(let entityName in this.lastEntities){
578✔
401
      const entity = this.lastEntities[entityName];
2,313✔
402
      val = val.replace( entity.regex, entity.val);
2,313✔
403
    }
404
    if(this.options.htmlEntities){
578✔
405
      for(let entityName in this.htmlEntities){
24✔
406
        const entity = this.htmlEntities[entityName];
192✔
407
        val = val.replace( entity.regex, entity.val);
192✔
408
      }
409
    }
410
    val = val.replace( this.ampEntity.regex, this.ampEntity.val);
578✔
411
  }
412
  return val;
591✔
413
}
414
function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
415
  if (textData) { //store previously collected data as textNode
1,071✔
416
    if(isLeafNode === undefined) isLeafNode = Object.keys(currentNode.child).length === 0
948✔
417
    
418
    textData = this.parseTextData(textData,
948✔
419
      currentNode.tagname,
420
      jPath,
421
      false,
422
      currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false,
948✔
423
      isLeafNode);
424

425
    if (textData !== undefined && textData !== "")
948✔
426
      currentNode.add(this.options.textNodeName, textData);
335✔
427
    textData = "";
948✔
428
  }
429
  return textData;
1,071✔
430
}
431

432
//TODO: use jPath to simplify the logic
433
/**
434
 * 
435
 * @param {string[]} stopNodes 
436
 * @param {string} jPath
437
 * @param {string} currentTagName 
438
 */
439
function isItStopNode(stopNodes, jPath, currentTagName){
440
  const allNodesExp = "*." + currentTagName;
650✔
441
  for (const stopNodePath in stopNodes) {
650✔
442
    const stopNodeExp = stopNodes[stopNodePath];
106✔
443
    if( allNodesExp === stopNodeExp || jPath === stopNodeExp  ) return true;
106✔
444
  }
445
  return false;
628✔
446
}
447

448
/**
449
 * Returns the tag Expression and where it is ending handling single-double quotes situation
450
 * @param {string} xmlData 
451
 * @param {number} i starting index
452
 * @returns 
453
 */
454
function tagExpWithClosingIndex(xmlData, i, closingChar = ">"){
×
455
  let attrBoundary;
456
  let tagExp = "";
720✔
457
  for (let index = i; index < xmlData.length; index++) {
720✔
458
    let ch = xmlData[index];
9,522✔
459
    if (attrBoundary) {
9,522✔
460
        if (ch === attrBoundary) attrBoundary = "";//reset
2,473✔
461
    } else if (ch === '"' || ch === "'") {
7,049✔
462
        attrBoundary = ch;
270✔
463
    } else if (ch === closingChar[0]) {
6,779✔
464
      if(closingChar[1]){
761✔
465
        if(xmlData[index + 1] === closingChar[1]){
86✔
466
          return {
42✔
467
            data: tagExp,
468
            index: index
469
          }
470
        }
471
      }else{
472
        return {
675✔
473
          data: tagExp,
474
          index: index
475
        }
476
      }
477
    } else if (ch === '\t') {
6,018✔
478
      ch = " "
4✔
479
    }
480
    tagExp += ch;
8,805✔
481
  }
482
}
483

484
function findClosingIndex(xmlData, str, i, errMsg){
485
  const closingIndex = xmlData.indexOf(str, i);
651✔
486
  if(closingIndex === -1){
651✔
487
    throw new Error(errMsg)
3✔
488
  }else{
489
    return closingIndex + str.length - 1;
648✔
490
  }
491
}
492

493
function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
676✔
494
  const result = tagExpWithClosingIndex(xmlData, i+1, closingChar);
720✔
495
  if(!result) return;
720✔
496
  let tagExp = result.data;
717✔
497
  const closeIndex = result.index;
717✔
498
  const separatorIndex = tagExp.search(/\s/);
717✔
499
  let tagName = tagExp;
717✔
500
  let attrExpPresent = true;
717✔
501
  if(separatorIndex !== -1){//separate tag name and attributes expression
717✔
502
    tagName = tagExp.substr(0, separatorIndex).replace(/\s\s*$/, '');
216✔
503
    tagExp = tagExp.substr(separatorIndex + 1);
216✔
504
  }
505

506
  if(removeNSPrefix){
717✔
507
    const colonIndex = tagName.indexOf(":");
48✔
508
    if(colonIndex !== -1){
48✔
509
      tagName = tagName.substr(colonIndex+1);
12✔
510
      attrExpPresent = tagName !== result.data.substr(colonIndex + 1);
12✔
511
    }
512
  }
513

514
  return {
717✔
515
    tagName: tagName,
516
    tagExp: tagExp,
517
    closeIndex: closeIndex,
518
    attrExpPresent: attrExpPresent,
519
  }
520
}
521
/**
522
 * find paired tag for a stop node
523
 * @param {string} xmlData 
524
 * @param {string} tagName 
525
 * @param {number} i 
526
 */
527
function readStopNodeData(xmlData, tagName, i){
528
  const startIndex = i;
20✔
529
  // Starting at 1 since we already have an open tag
530
  let openTagCount = 1;
20✔
531

532
  for (; i < xmlData.length; i++) {
20✔
533
    if( xmlData[i] === "<"){ 
896✔
534
      if (xmlData[i+1] === "/") {//close tag
72✔
535
          const closeIndex = findClosingIndex(xmlData, ">", i, `${tagName} is not closed`);
44✔
536
          let closeTagName = xmlData.substring(i+2,closeIndex).trim();
44✔
537
          if(closeTagName === tagName){
44✔
538
            openTagCount--;
21✔
539
            if (openTagCount === 0) {
21✔
540
              return {
20✔
541
                tagContent: xmlData.substring(startIndex, i),
542
                i : closeIndex
543
              }
544
            }
545
          }
546
          i=closeIndex;
24✔
547
        } else if(xmlData[i+1] === '?') { 
28!
548
          const closeIndex = findClosingIndex(xmlData, "?>", i+1, "StopNode is not closed.")
×
549
          i=closeIndex;
×
550
        } else if(xmlData.substr(i + 1, 3) === '!--') { 
28✔
551
          const closeIndex = findClosingIndex(xmlData, "-->", i+3, "StopNode is not closed.")
1✔
552
          i=closeIndex;
1✔
553
        } else if(xmlData.substr(i + 1, 2) === '![') { 
27✔
554
          const closeIndex = findClosingIndex(xmlData, "]]>", i, "StopNode is not closed.") - 2;
1✔
555
          i=closeIndex;
1✔
556
        } else {
557
          const tagData = readTagExp(xmlData, i, '>')
26✔
558

559
          if (tagData) {
26✔
560
            const openTagName = tagData && tagData.tagName;
25✔
561
            if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length-1] !== "/") {
25✔
562
              openTagCount++;
1✔
563
            }
564
            i=tagData.closeIndex;
25✔
565
          }
566
        }
567
      }
568
  }//end for loop
569
}
570

571
function parseValue(val, shouldParse, options) {
572
  if (shouldParse && typeof val === 'string') {
584✔
573
    //console.log(options)
574
    const newval = val.trim();
400✔
575
    if(newval === 'true' ) return true;
400✔
576
    else if(newval === 'false' ) return false;
393✔
577
    else return toNumber(val, options);
390✔
578
  } else {
579
    if (util.isExist(val)) {
184!
580
      return val;
184✔
581
    } else {
582
      return '';
×
583
    }
584
  }
585
}
586

587

588
module.exports = OrderedObjParser;
1✔
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

© 2026 Coveralls, Inc