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

NaturalIntelligence / fast-xml-parser / 9139494263

18 May 2024 11:19AM UTC coverage: 98.162% (-0.1%) from 98.26%
9139494263

push

github

amitguptagwl
update package detail

776 of 835 branches covered (92.93%)

2670 of 2720 relevant lines covered (98.16%)

701705.55 hits per line

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

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

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

9
// const regx =
10
//   '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
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;
648✔
19
    this.currentNode = null;
648✔
20
    this.tagsNodeStack = [];
648✔
21
    this.docTypeEntities = {};
648✔
22
    this.lastEntities = {
648✔
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 : "&"};
648✔
29
    this.htmlEntities = {
648✔
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
      "num_dec": { regex: /&#([0-9]{1,7});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 10)) },
4✔
44
      "num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 16)) },
20✔
45
    };
46
    this.addExternalEntities = addExternalEntities;
648✔
47
    this.parseXml = parseXml;
648✔
48
    this.parseTextData = parseTextData;
648✔
49
    this.resolveNameSpace = resolveNameSpace;
648✔
50
    this.buildAttributesMap = buildAttributesMap;
648✔
51
    this.isItStopNode = isItStopNode;
648✔
52
    this.replaceEntitiesValue = replaceEntitiesValue;
648✔
53
    this.readStopNodeData = readStopNodeData;
648✔
54
    this.saveTextToParentTag = saveTextToParentTag;
648✔
55
    this.addChild = addChild;
648✔
56
  }
57

58
}
59

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

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

109
function resolveNameSpace(tagname) {
110
  if (this.options.removeNSPrefix) {
1,012✔
111
    const tags = tagname.split(':');
88✔
112
    const prefix = tagname.charAt(0) === '/' ? '/' : '';
88!
113
    if (tags[0] === 'xmlns') {
88✔
114
      return '';
28✔
115
    }
116
    if (tags.length === 2) {
60✔
117
      tagname = prefix + tags[1];
24✔
118
    }
119
  }
120
  return tagname;
984✔
121
}
122

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

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

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

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

196
        if(this.options.removeNSPrefix){
2,212✔
197
          const colonIndex = tagName.indexOf(":");
92✔
198
          if(colonIndex !== -1){
92✔
199
            tagName = tagName.substr(colonIndex+1);
40✔
200
          }
201
        }
202

203
        if(this.options.transformTagName) {
2,212✔
204
          tagName = this.options.transformTagName(tagName);
40✔
205
        }
206

207
        if(currentNode){
2,212!
208
          textData = this.saveTextToParentTag(textData, currentNode, jPath);
2,212✔
209
        }
210

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

225
        currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
2,208✔
226
        textData = "";
2,208✔
227
        i = closeIndex;
2,208✔
228
      } else if( xmlData[i+1] === '?') {
3,160✔
229

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

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

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

246
        }
247

248

249
        i = tagData.closeIndex + 1;
180✔
250
      } else if(xmlData.substr(i + 1, 3) === '!--') {
2,972✔
251
        const endIndex = findClosingIndex(xmlData, "-->", i+4, "Comment is not closed.")
48✔
252
        if(this.options.commentPropName){
44✔
253
          const comment = xmlData.substring(i + 4, endIndex - 2);
20✔
254

255
          textData = this.saveTextToParentTag(textData, currentNode, jPath);
20✔
256

257
          currentNode.add(this.options.commentPropName, [ { [this.options.textNodeName] : comment } ]);
20✔
258
        }
259
        i = endIndex;
44✔
260
      } else if( xmlData.substr(i + 1, 2) === '!D') {
2,924✔
261
        const result = readDocType(xmlData, i);
52✔
262
        this.docTypeEntities = result.entities;
44✔
263
        i = result.i;
44✔
264
      }else if(xmlData.substr(i + 1, 2) === '![') {
2,872✔
265
        const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
204✔
266
        const tagExp = xmlData.substring(i + 9,closeIndex);
200✔
267

268
        textData = this.saveTextToParentTag(textData, currentNode, jPath);
200✔
269

270
        let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true, true);
200✔
271
        if(val == undefined) val = "";
200✔
272

273
        //cdata should be set even if it is 0 length string
274
        if(this.options.cdataPropName){
200✔
275
          currentNode.add(this.options.cdataPropName, [ { [this.options.textNodeName] : tagExp } ]);
52✔
276
        }else{
277
          currentNode.add(this.options.textNodeName, val);
148✔
278
        }
279
        
280
        i = closeIndex + 2;
200✔
281
      }else {//Opening tag
282
        let result = readTagExp(xmlData,i, this.options.removeNSPrefix);
2,668✔
283
        let tagName= result.tagName;
2,668✔
284
        const rawTagName = result.rawTagName;
2,668✔
285
        let tagExp = result.tagExp;
2,668✔
286
        let attrExpPresent = result.attrExpPresent;
2,668✔
287
        let closeIndex = result.closeIndex;
2,668✔
288

289
        if (this.options.transformTagName) {
2,668✔
290
          tagName = this.options.transformTagName(tagName);
40✔
291
        }
292
        
293
        //save text as child node
294
        if (currentNode && textData) {
2,668✔
295
          if(currentNode.tagname !== '!xml'){
1,924✔
296
            //when nested tag is found
297
            textData = this.saveTextToParentTag(textData, currentNode, jPath, false);
1,756✔
298
          }
299
        }
300

301
        //check if last tag was unpaired tag
302
        const lastTag = currentNode;
2,668✔
303
        if(lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1 ){
2,668✔
304
          currentNode = this.tagsNodeStack.pop();
96✔
305
          jPath = jPath.substring(0, jPath.lastIndexOf("."));
96✔
306
        }
307
        if(tagName !== xmlObj.tagname){
2,668!
308
          jPath += jPath ? "." + tagName : tagName;
2,668✔
309
        }
310
        if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) {
2,668✔
311
          let tagContent = "";
100✔
312
          //self-closing tag
313
          if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
100✔
314
            if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
8!
315
              tagName = tagName.substr(0, tagName.length - 1);
×
316
              jPath = jPath.substr(0, jPath.length - 1);
×
317
              tagExp = tagName;
×
318
            }else{
319
              tagExp = tagExp.substr(0, tagExp.length - 1);
8✔
320
            }
321
            i = result.closeIndex;
8✔
322
          }
323
          //unpaired tag
324
          else if(this.options.unpairedTags.indexOf(tagName) !== -1){
92✔
325
            
326
            i = result.closeIndex;
4✔
327
          }
328
          //normal tag
329
          else{
330
            //read until closing tag is found
331
            const result = this.readStopNodeData(xmlData, rawTagName, closeIndex + 1);
88✔
332
            if(!result) throw new Error(`Unexpected end of ${rawTagName}`);
88!
333
            i = result.i;
88✔
334
            tagContent = result.tagContent;
88✔
335
          }
336

337
          const childNode = new xmlNode(tagName);
100✔
338
          if(tagName !== tagExp && attrExpPresent){
100✔
339
            childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
36✔
340
          }
341
          if(tagContent) {
100✔
342
            tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
72✔
343
          }
344
          
345
          jPath = jPath.substr(0, jPath.lastIndexOf("."));
100✔
346
          childNode.add(this.options.textNodeName, tagContent);
100✔
347
          
348
          this.addChild(currentNode, childNode, jPath)
100✔
349
        }else{
350
  //selfClosing tag
351
          if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
2,568✔
352
            if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
188✔
353
              tagName = tagName.substr(0, tagName.length - 1);
64✔
354
              jPath = jPath.substr(0, jPath.length - 1);
64✔
355
              tagExp = tagName;
64✔
356
            }else{
357
              tagExp = tagExp.substr(0, tagExp.length - 1);
124✔
358
            }
359
            
360
            if(this.options.transformTagName) {
188!
361
              tagName = this.options.transformTagName(tagName);
×
362
            }
363

364
            const childNode = new xmlNode(tagName);
188✔
365
            if(tagName !== tagExp && attrExpPresent){
188✔
366
              childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
124✔
367
            }
368
            this.addChild(currentNode, childNode, jPath)
188✔
369
            jPath = jPath.substr(0, jPath.lastIndexOf("."));
188✔
370
          }
371
    //opening tag
372
          else{
373
            const childNode = new xmlNode( tagName);
2,380✔
374
            this.tagsNodeStack.push(currentNode);
2,380✔
375
            
376
            if(tagName !== tagExp && attrExpPresent){
2,380✔
377
              childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
528✔
378
            }
379
            this.addChild(currentNode, childNode, jPath)
2,380✔
380
            currentNode = childNode;
2,380✔
381
          }
382
          textData = "";
2,568✔
383
          i = closeIndex;
2,568✔
384
        }
385
      }
386
    }else{
387
      textData += xmlData[i];
42,296✔
388
    }
389
  }
390
  return xmlObj.child;
616✔
391
}
392

393
function addChild(currentNode, childNode, jPath){
394
  const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"])
2,828✔
395
  if(result === false){
2,828✔
396
  }else if(typeof result === "string"){
2,808!
397
    childNode.tagname = result
2,808✔
398
    currentNode.addChild(childNode);
2,808✔
399
  }else{
400
    currentNode.addChild(childNode);
×
401
  }
402
}
403

404
const replaceEntitiesValue = function(val){
4✔
405

406
  if(this.options.processEntities){
2,280✔
407
    for(let entityName in this.docTypeEntities){
2,240✔
408
      const entity = this.docTypeEntities[entityName];
1,768✔
409
      val = val.replace( entity.regx, entity.val);
1,768✔
410
    }
411
    for(let entityName in this.lastEntities){
2,240✔
412
      const entity = this.lastEntities[entityName];
10,540✔
413
      val = val.replace( entity.regex, entity.val);
10,540✔
414
    }
415
    if(this.options.htmlEntities){
2,240✔
416
      for(let entityName in this.htmlEntities){
136✔
417
        const entity = this.htmlEntities[entityName];
1,496✔
418
        val = val.replace( entity.regex, entity.val);
1,496✔
419
      }
420
    }
421
    val = val.replace( this.ampEntity.regex, this.ampEntity.val);
2,240✔
422
  }
423
  return val;
2,280✔
424
}
425
function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
426
  if (textData) { //store previously collected data as textNode
4,368✔
427
    if(isLeafNode === undefined) isLeafNode = Object.keys(currentNode.child).length === 0
3,860✔
428
    
429
    textData = this.parseTextData(textData,
3,860✔
430
      currentNode.tagname,
431
      jPath,
432
      false,
433
      currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false,
3,860✔
434
      isLeafNode);
435

436
    if (textData !== undefined && textData !== "")
3,860✔
437
      currentNode.add(this.options.textNodeName, textData);
1,364✔
438
    textData = "";
3,860✔
439
  }
440
  return textData;
4,368✔
441
}
442

443
//TODO: use jPath to simplify the logic
444
/**
445
 * 
446
 * @param {string[]} stopNodes 
447
 * @param {string} jPath
448
 * @param {string} currentTagName 
449
 */
450
function isItStopNode(stopNodes, jPath, currentTagName){
451
  const allNodesExp = "*." + currentTagName;
2,668✔
452
  for (const stopNodePath in stopNodes) {
2,668✔
453
    const stopNodeExp = stopNodes[stopNodePath];
2,366✔
454
    if( allNodesExp === stopNodeExp || jPath === stopNodeExp  ) return true;
2,366✔
455
  }
456
  return false;
2,568✔
457
}
458

459
/**
460
 * Returns the tag Expression and where it is ending handling single-double quotes situation
461
 * @param {string} xmlData 
462
 * @param {number} i starting index
463
 * @returns 
464
 */
465
function tagExpWithClosingIndex(xmlData, i, closingChar = ">"){
×
466
  let attrBoundary;
467
  let tagExp = "";
2,976✔
468
  for (let index = i; index < xmlData.length; index++) {
2,976✔
469
    let ch = xmlData[index];
39,460✔
470
    if (attrBoundary) {
39,460✔
471
        if (ch === attrBoundary) attrBoundary = "";//reset
10,260✔
472
    } else if (ch === '"' || ch === "'") {
29,200✔
473
        attrBoundary = ch;
1,124✔
474
    } else if (ch === closingChar[0]) {
28,076✔
475
      if(closingChar[1]){
3,152✔
476
        if(xmlData[index + 1] === closingChar[1]){
368✔
477
          return {
180✔
478
            data: tagExp,
479
            index: index
480
          }
481
        }
482
      }else{
483
        return {
2,784✔
484
          data: tagExp,
485
          index: index
486
        }
487
      }
488
    } else if (ch === '\t') {
24,924✔
489
      ch = " "
16✔
490
    }
491
    tagExp += ch;
36,496✔
492
  }
493
}
494

495
function findClosingIndex(xmlData, str, i, errMsg){
496
  const closingIndex = xmlData.indexOf(str, i);
2,676✔
497
  if(closingIndex === -1){
2,676✔
498
    throw new Error(errMsg)
12✔
499
  }else{
500
    return closingIndex + str.length - 1;
2,664✔
501
  }
502
}
503

504
function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
2,788✔
505
  const result = tagExpWithClosingIndex(xmlData, i+1, closingChar);
2,976✔
506
  if(!result) return;
2,976✔
507
  let tagExp = result.data;
2,964✔
508
  const closeIndex = result.index;
2,964✔
509
  const separatorIndex = tagExp.search(/\s/);
2,964✔
510
  let tagName = tagExp;
2,964✔
511
  let attrExpPresent = true;
2,964✔
512
  if(separatorIndex !== -1){//separate tag name and attributes expression
2,964✔
513
    tagName = tagExp.substring(0, separatorIndex);
896✔
514
    tagExp = tagExp.substring(separatorIndex + 1).trimStart();
896✔
515
  }
516

517
  const rawTagName = tagName;
2,964✔
518
  if(removeNSPrefix){
2,964✔
519
    const colonIndex = tagName.indexOf(":");
220✔
520
    if(colonIndex !== -1){
220✔
521
      tagName = tagName.substr(colonIndex+1);
52✔
522
      attrExpPresent = tagName !== result.data.substr(colonIndex + 1);
52✔
523
    }
524
  }
525

526
  return {
2,964✔
527
    tagName: tagName,
528
    tagExp: tagExp,
529
    closeIndex: closeIndex,
530
    attrExpPresent: attrExpPresent,
531
    rawTagName: rawTagName,
532
  }
533
}
534
/**
535
 * find paired tag for a stop node
536
 * @param {string} xmlData 
537
 * @param {string} tagName 
538
 * @param {number} i 
539
 */
540
function readStopNodeData(xmlData, tagName, i){
541
  const startIndex = i;
88✔
542
  // Starting at 1 since we already have an open tag
543
  let openTagCount = 1;
88✔
544

545
  for (; i < xmlData.length; i++) {
88✔
546
    if( xmlData[i] === "<"){ 
3,688✔
547
      if (xmlData[i+1] === "/") {//close tag
328✔
548
          const closeIndex = findClosingIndex(xmlData, ">", i, `${tagName} is not closed`);
200✔
549
          let closeTagName = xmlData.substring(i+2,closeIndex).trim();
200✔
550
          if(closeTagName === tagName){
200✔
551
            openTagCount--;
92✔
552
            if (openTagCount === 0) {
92✔
553
              return {
88✔
554
                tagContent: xmlData.substring(startIndex, i),
555
                i : closeIndex
556
              }
557
            }
558
          }
559
          i=closeIndex;
112✔
560
        } else if(xmlData[i+1] === '?') { 
128!
561
          const closeIndex = findClosingIndex(xmlData, "?>", i+1, "StopNode is not closed.")
×
562
          i=closeIndex;
×
563
        } else if(xmlData.substr(i + 1, 3) === '!--') { 
128✔
564
          const closeIndex = findClosingIndex(xmlData, "-->", i+3, "StopNode is not closed.")
4✔
565
          i=closeIndex;
4✔
566
        } else if(xmlData.substr(i + 1, 2) === '![') { 
124✔
567
          const closeIndex = findClosingIndex(xmlData, "]]>", i, "StopNode is not closed.") - 2;
4✔
568
          i=closeIndex;
4✔
569
        } else {
570
          const tagData = readTagExp(xmlData, i, '>')
120✔
571

572
          if (tagData) {
120✔
573
            const openTagName = tagData && tagData.tagName;
116✔
574
            if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length-1] !== "/") {
116✔
575
              openTagCount++;
4✔
576
            }
577
            i=tagData.closeIndex;
116✔
578
          }
579
        }
580
      }
581
  }//end for loop
582
}
583

584
function parseValue(val, shouldParse, options) {
585
  if (shouldParse && typeof val === 'string') {
2,444✔
586
    //console.log(options)
587
    const newval = val.trim();
1,680✔
588
    if(newval === 'true' ) return true;
1,680✔
589
    else if(newval === 'false' ) return false;
1,652✔
590
    else return toNumber(val, options);
1,640✔
591
  } else {
592
    if (util.isExist(val)) {
764!
593
      return val;
764✔
594
    } else {
595
      return '';
×
596
    }
597
  }
598
}
599

600

601
module.exports = OrderedObjParser;
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