• 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

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

4✔
4
import {getAllMatches, isExist} from '../util.js';
4✔
5
import xmlNode from './xmlNode.js';
4✔
6
import readDocType from './DocTypeReader.js';
4✔
7
import toNumber from "strnum";
4✔
8
import getIgnoreAttributesFn from "../ignoreAttributes.js";
4✔
9

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

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

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

4✔
60
}
4✔
61

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

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

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

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

4✔
129
function buildAttributesMap(attrStr, jPath, tagName) {
856✔
130
  if (this.options.ignoreAttributes !== true && typeof attrStr === 'string') {
856✔
131
    // attrStr = attrStr.replace(/\r?\n/g, ' ');
720✔
132
    //attrStr = attrStr || attrStr.trim();
720✔
133

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

4✔
186
const parseXml = function(xmlData) {
4✔
187
  xmlData = xmlData.replace(/\r\n?/g, "\n"); //TODO: remove this line
660✔
188
  const xmlObj = new xmlNode('!xml');
660✔
189
  let currentNode = xmlObj;
660✔
190
  let textData = "";
660✔
191
  let jPath = "";
660✔
192
  for(let i=0; i< xmlData.length; i++){//for each char in XML data
660✔
193
    const ch = xmlData[i];
48,096✔
194
    if(ch === '<'){
48,096✔
195
      // const nextIndex = i+1;
5,408✔
196
      // const _2ndChar = xmlData[nextIndex];
5,408✔
197
      if( xmlData[i+1] === '/') {//Closing Tag
5,408✔
198
        const closeIndex = findClosingIndex(xmlData, ">", i, "Closing Tag is not closed.")
2,232✔
199
        let tagName = xmlData.substring(i+2,closeIndex).trim();
2,232✔
200

2,232✔
201
        if(this.options.removeNSPrefix){
2,232✔
202
          const colonIndex = tagName.indexOf(":");
92✔
203
          if(colonIndex !== -1){
92✔
204
            tagName = tagName.substr(colonIndex+1);
40✔
205
          }
40✔
206
        }
92✔
207

2,228✔
208
        if(this.options.transformTagName) {
2,232✔
209
          tagName = this.options.transformTagName(tagName);
40✔
210
        }
40✔
211

2,228✔
212
        if(currentNode){
2,228✔
213
          textData = this.saveTextToParentTag(textData, currentNode, jPath);
2,228✔
214
        }
2,228✔
215

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

2,224✔
230
        currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
2,224✔
231
        textData = "";
2,224✔
232
        i = closeIndex;
2,224✔
233
      } else if( xmlData[i+1] === '?') {
5,408✔
234

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

180✔
238
        textData = this.saveTextToParentTag(textData, currentNode, jPath);
180✔
239
        if( (this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags){
188✔
240

20✔
241
        }else{
188✔
242
  
160✔
243
          const childNode = new xmlNode(tagData.tagName);
160✔
244
          childNode.add(this.options.textNodeName, "");
160✔
245
          
160✔
246
          if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
160✔
247
            childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath, tagData.tagName);
152✔
248
          }
152✔
249
          this.addChild(currentNode, childNode, jPath)
160✔
250

160✔
251
        }
160✔
252

180✔
253

180✔
254
        i = tagData.closeIndex + 1;
180✔
255
      } else if(xmlData.substr(i + 1, 3) === '!--') {
3,176✔
256
        const endIndex = findClosingIndex(xmlData, "-->", i+4, "Comment is not closed.")
48✔
257
        if(this.options.commentPropName){
48✔
258
          const comment = xmlData.substring(i + 4, endIndex - 2);
20✔
259

20✔
260
          textData = this.saveTextToParentTag(textData, currentNode, jPath);
20✔
261

20✔
262
          currentNode.add(this.options.commentPropName, [ { [this.options.textNodeName] : comment } ]);
20✔
263
        }
20✔
264
        i = endIndex;
44✔
265
      } else if( xmlData.substr(i + 1, 2) === '!D') {
2,988✔
266
        const result = readDocType(xmlData, i);
52✔
267
        this.docTypeEntities = result.entities;
52✔
268
        i = result.i;
52✔
269
      }else if(xmlData.substr(i + 1, 2) === '![') {
2,940✔
270
        const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
204✔
271
        const tagExp = xmlData.substring(i + 9,closeIndex);
204✔
272

204✔
273
        textData = this.saveTextToParentTag(textData, currentNode, jPath);
204✔
274

204✔
275
        let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true, true);
204✔
276
        if(val == undefined) val = "";
204✔
277

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

2,684✔
294
        if (this.options.transformTagName) {
2,684✔
295
          tagName = this.options.transformTagName(tagName);
40✔
296
        }
40✔
297
        
2,684✔
298
        //save text as child node
2,684✔
299
        if (currentNode && textData) {
2,684✔
300
          if(currentNode.tagname !== '!xml'){
1,940✔
301
            //when nested tag is found
1,760✔
302
            textData = this.saveTextToParentTag(textData, currentNode, jPath, false);
1,760✔
303
          }
1,760✔
304
        }
1,940✔
305

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

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

188✔
369
            const childNode = new xmlNode(tagName);
188✔
370
            if(tagName !== tagExp && attrExpPresent){
188✔
371
              childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
124✔
372
            }
124✔
373
            this.addChild(currentNode, childNode, jPath)
188✔
374
            jPath = jPath.substr(0, jPath.lastIndexOf("."));
188✔
375
          }
188✔
376
    //opening tag
2,396✔
377
          else{
2,396✔
378
            const childNode = new xmlNode( tagName);
2,396✔
379
            this.tagsNodeStack.push(currentNode);
2,396✔
380
            
2,396✔
381
            if(tagName !== tagExp && attrExpPresent){
2,396✔
382
              childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
544✔
383
            }
544✔
384
            this.addChild(currentNode, childNode, jPath)
2,396✔
385
            currentNode = childNode;
2,396✔
386
          }
2,396✔
387
          textData = "";
2,584✔
388
          i = closeIndex;
2,584✔
389
        }
2,584✔
390
      }
2,684✔
391
    }else{
48,096✔
392
      textData += xmlData[i];
42,688✔
393
    }
42,688✔
394
  }
48,096✔
395
  return xmlObj.child;
628✔
396
}
660✔
397

4✔
398
function addChild(currentNode, childNode, jPath){
2,844✔
399
  const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"])
2,844✔
400
  if(result === false){
2,844✔
401
  }else if(typeof result === "string"){
2,844✔
402
    childNode.tagname = result
2,824✔
403
    currentNode.addChild(childNode);
2,824✔
404
  }else{
2,824!
405
    currentNode.addChild(childNode);
×
406
  }
×
407
}
2,844✔
408

4✔
409
const replaceEntitiesValue = function(val){
4✔
410

2,316✔
411
  if(this.options.processEntities){
2,316✔
412
    for(let entityName in this.docTypeEntities){
2,276✔
413
      const entity = this.docTypeEntities[entityName];
2,131✔
414
      val = val.replace( entity.regx, entity.val);
2,131✔
415
    }
2,131✔
416
    for(let entityName in this.lastEntities){
2,276✔
417
      const entity = this.lastEntities[entityName];
11,047✔
418
      val = val.replace( entity.regex, entity.val);
11,047✔
419
    }
11,047✔
420
    if(this.options.htmlEntities){
2,276✔
421
      for(let entityName in this.htmlEntities){
136✔
422
        const entity = this.htmlEntities[entityName];
1,480✔
423
        val = val.replace( entity.regex, entity.val);
1,480✔
424
      }
1,480✔
425
    }
136✔
426
    val = val.replace( this.ampEntity.regex, this.ampEntity.val);
2,276✔
427
  }
2,276✔
428
  return val;
2,316✔
429
}
2,316✔
430
function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
4,388✔
431
  if (textData) { //store previously collected data as textNode
4,388✔
432
    if(isLeafNode === undefined) isLeafNode = currentNode.child.length === 0
3,880✔
433
    
3,880✔
434
    textData = this.parseTextData(textData,
3,880✔
435
      currentNode.tagname,
3,880✔
436
      jPath,
3,880✔
437
      false,
3,880✔
438
      currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false,
3,880✔
439
      isLeafNode);
3,880✔
440

3,880✔
441
    if (textData !== undefined && textData !== "")
3,880✔
442
      currentNode.add(this.options.textNodeName, textData);
3,880✔
443
    textData = "";
3,880✔
444
  }
3,880✔
445
  return textData;
4,388✔
446
}
4,388✔
447

4✔
448
//TODO: use jPath to simplify the logic
4✔
449
/**
4✔
450
 * 
4✔
451
 * @param {string[]} stopNodes 
4✔
452
 * @param {string} jPath
4✔
453
 * @param {string} currentTagName 
4✔
454
 */
4✔
455
function isItStopNode(stopNodes, jPath, currentTagName){
2,684✔
456
  const allNodesExp = "*." + currentTagName;
2,684✔
457
  for (const stopNodePath in stopNodes) {
2,684✔
458
    const stopNodeExp = stopNodes[stopNodePath];
2,666✔
459
    if( allNodesExp === stopNodeExp || jPath === stopNodeExp  ) return true;
2,666✔
460
  }
2,666✔
461
  return false;
2,584✔
462
}
2,684✔
463

4✔
464
/**
4✔
465
 * Returns the tag Expression and where it is ending handling single-double quotes situation
4✔
466
 * @param {string} xmlData 
4✔
467
 * @param {number} i starting index
4✔
468
 * @returns 
4✔
469
 */
4✔
470
function tagExpWithClosingIndex(xmlData, i, closingChar = ">"){
2,992✔
471
  let attrBoundary;
2,992✔
472
  let tagExp = "";
2,992✔
473
  for (let index = i; index < xmlData.length; index++) {
2,992✔
474
    let ch = xmlData[index];
41,416✔
475
    if (attrBoundary) {
41,416✔
476
        if (ch === attrBoundary) attrBoundary = "";//reset
10,836✔
477
    } else if (ch === '"' || ch === "'") {
41,416✔
478
        attrBoundary = ch;
1,188✔
479
    } else if (ch === closingChar[0]) {
30,580✔
480
      if(closingChar[1]){
3,168✔
481
        if(xmlData[index + 1] === closingChar[1]){
368✔
482
          return {
180✔
483
            data: tagExp,
180✔
484
            index: index
180✔
485
          }
180✔
486
        }
180✔
487
      }else{
3,168✔
488
        return {
2,800✔
489
          data: tagExp,
2,800✔
490
          index: index
2,800✔
491
        }
2,800✔
492
      }
2,800✔
493
    } else if (ch === '\t') {
29,392✔
494
      ch = " "
16✔
495
    }
16✔
496
    tagExp += ch;
38,436✔
497
  }
38,436✔
498
}
2,992✔
499

4✔
500
function findClosingIndex(xmlData, str, i, errMsg){
2,692✔
501
  const closingIndex = xmlData.indexOf(str, i);
2,692✔
502
  if(closingIndex === -1){
2,692✔
503
    throw new Error(errMsg)
12✔
504
  }else{
2,692✔
505
    return closingIndex + str.length - 1;
2,680✔
506
  }
2,680✔
507
}
2,692✔
508

4✔
509
function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
2,992✔
510
  const result = tagExpWithClosingIndex(xmlData, i+1, closingChar);
2,992✔
511
  if(!result) return;
2,992✔
512
  let tagExp = result.data;
2,980✔
513
  const closeIndex = result.index;
2,980✔
514
  const separatorIndex = tagExp.search(/\s/);
2,980✔
515
  let tagName = tagExp;
2,980✔
516
  let attrExpPresent = true;
2,980✔
517
  if(separatorIndex !== -1){//separate tag name and attributes expression
2,992✔
518
    tagName = tagExp.substring(0, separatorIndex);
912✔
519
    tagExp = tagExp.substring(separatorIndex + 1).trimStart();
912✔
520
  }
912✔
521

2,980✔
522
  const rawTagName = tagName;
2,980✔
523
  if(removeNSPrefix){
2,992✔
524
    const colonIndex = tagName.indexOf(":");
220✔
525
    if(colonIndex !== -1){
220✔
526
      tagName = tagName.substr(colonIndex+1);
52✔
527
      attrExpPresent = tagName !== result.data.substr(colonIndex + 1);
52✔
528
    }
52✔
529
  }
220✔
530

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

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

120✔
577
          if (tagData) {
120✔
578
            const openTagName = tagData && tagData.tagName;
116✔
579
            if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length-1] !== "/") {
116✔
580
              openTagCount++;
4✔
581
            }
4✔
582
            i=tagData.closeIndex;
116✔
583
          }
116✔
584
        }
120✔
585
      }
328✔
586
  }//end for loop
88✔
587
}
88✔
588

4✔
589
function parseValue(val, shouldParse, options) {
2,480✔
590
  if (shouldParse && typeof val === 'string') {
2,480✔
591
    //console.log(options)
1,716✔
592
    const newval = val.trim();
1,716✔
593
    if(newval === 'true' ) return true;
1,716✔
594
    else if(newval === 'false' ) return false;
1,688✔
595
    else return toNumber(val, options);
1,676✔
596
  } else {
2,480✔
597
    if (isExist(val)) {
764✔
598
      return val;
764✔
599
    } else {
764!
600
      return '';
×
601
    }
×
602
  }
764✔
603
}
2,480✔
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