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

NaturalIntelligence / fast-xml-parser / 15517131831

08 Jun 2025 08:04AM UTC coverage: 97.547%. Remained the same
15517131831

push

github

amitguptagwl
deprecate in-built CLI

1105 of 1147 branches covered (96.34%)

8908 of 9132 relevant lines covered (97.55%)

390591.26 hits per line

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

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

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

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

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

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

5✔
60
}
5✔
61

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

5✔
73
/**
5✔
74
 * @param {string} val
5✔
75
 * @param {string} tagName
5✔
76
 * @param {string} jPath
5✔
77
 * @param {boolean} dontTrim
5✔
78
 * @param {boolean} hasAttributes
5✔
79
 * @param {boolean} isLeafNode
5✔
80
 * @param {boolean} escapeEntities
5✔
81
 */
5✔
82
function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode, escapeEntities) {
5,225✔
83
  if (val !== undefined) {
5,225✔
84
    if (this.options.trimValues && !dontTrim) {
5,225✔
85
      val = val.trim();
4,815✔
86
    }
4,815✔
87
    if(val.length > 0){
5,225✔
88
      if(!escapeEntities) val = this.replaceEntitiesValue(val);
2,070✔
89
      
2,070✔
90
      const newval = this.options.tagValueProcessor(tagName, val, jPath, hasAttributes, isLeafNode);
2,070✔
91
      if(newval === null || newval === undefined){
2,070✔
92
        //don't parse
50✔
93
        return val;
50✔
94
      }else if(typeof newval !== typeof val || newval !== val){
2,070✔
95
        //overwrite
20✔
96
        return newval;
20✔
97
      }else if(this.options.trimValues){
2,020✔
98
        return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
1,940✔
99
      }else{
2,000✔
100
        const trimmedVal = val.trim();
60✔
101
        if(trimmedVal === val){
60✔
102
          return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
15✔
103
        }else{
60✔
104
          return val;
45✔
105
        }
45✔
106
      }
60✔
107
    }
2,070✔
108
  }
5,225✔
109
}
5,225✔
110

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

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

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

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

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

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

2,820✔
208
        if(this.options.transformTagName) {
2,825✔
209
          tagName = this.options.transformTagName(tagName);
50✔
210
        }
50✔
211

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

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

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

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

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

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

225✔
252

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

25✔
259
          textData = this.saveTextToParentTag(textData, currentNode, jPath);
25✔
260

25✔
261
          currentNode.add(this.options.commentPropName, [ { [this.options.textNodeName] : comment } ]);
25✔
262
        }
25✔
263
        i = endIndex;
55✔
264
      } else if( xmlData.substr(i + 1, 2) === '!D') {
3,840✔
265
        const result = readDocType(xmlData, i);
70✔
266
        this.docTypeEntities = result.entities;
70✔
267
        i = result.i;
70✔
268
      }else if(xmlData.substr(i + 1, 2) === '![') {
3,780✔
269
        const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
260✔
270
        const tagExp = xmlData.substring(i + 9,closeIndex);
260✔
271

260✔
272
        textData = this.saveTextToParentTag(textData, currentNode, jPath);
260✔
273

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

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

3,450✔
293
        if (this.options.transformTagName) {
3,450✔
294
          tagName = this.options.transformTagName(tagName);
50✔
295
        }
50✔
296
        
3,450✔
297
        //save text as child node
3,450✔
298
        if (currentNode && textData) {
3,450✔
299
          if(currentNode.tagname !== '!xml'){
2,440✔
300
            //when nested tag is found
2,205✔
301
            textData = this.saveTextToParentTag(textData, currentNode, jPath, false);
2,205✔
302
          }
2,205✔
303
        }
2,440✔
304

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

130✔
342
          const childNode = new xmlNode(tagName);
130✔
343

130✔
344
          if(tagName !== tagExp && attrExpPresent){
130✔
345
            childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
45✔
346
          }
45✔
347
          if(tagContent) {
130✔
348
            tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
95✔
349
          }
95✔
350
          
130✔
351
          jPath = jPath.substr(0, jPath.lastIndexOf("."));
130✔
352
          childNode.add(this.options.textNodeName, tagContent);
130✔
353
          
130✔
354
          this.addChild(currentNode, childNode, jPath, startIndex);
130✔
355
        }else{
3,450✔
356
  //selfClosing tag
3,320✔
357
          if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
3,320✔
358
            if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
285✔
359
              tagName = tagName.substr(0, tagName.length - 1);
100✔
360
              jPath = jPath.substr(0, jPath.length - 1);
100✔
361
              tagExp = tagName;
100✔
362
            }else{
285✔
363
              tagExp = tagExp.substr(0, tagExp.length - 1);
185✔
364
            }
185✔
365
            
285✔
366
            if(this.options.transformTagName) {
285!
367
              tagName = this.options.transformTagName(tagName);
×
368
            }
×
369

285✔
370
            const childNode = new xmlNode(tagName);
285✔
371
            if(tagName !== tagExp && attrExpPresent){
285✔
372
              childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
185✔
373
            }
185✔
374
            this.addChild(currentNode, childNode, jPath, startIndex);
285✔
375
            jPath = jPath.substr(0, jPath.lastIndexOf("."));
285✔
376
          }
285✔
377
    //opening tag
3,035✔
378
          else{
3,035✔
379
            const childNode = new xmlNode( tagName);
3,035✔
380
            this.tagsNodeStack.push(currentNode);
3,035✔
381
            
3,035✔
382
            if(tagName !== tagExp && attrExpPresent){
3,035✔
383
              childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
700✔
384
            }
700✔
385
            this.addChild(currentNode, childNode, jPath, startIndex);
3,035✔
386
            currentNode = childNode;
3,035✔
387
          }
3,035✔
388
          textData = "";
3,320✔
389
          i = closeIndex;
3,320✔
390
        }
3,320✔
391
      }
3,450✔
392
    }else{
60,725✔
393
      textData += xmlData[i];
53,825✔
394
    }
53,825✔
395
  }
60,725✔
396
  return xmlObj.child;
805✔
397
}
845✔
398

5✔
399
function addChild(currentNode, childNode, jPath, startIndex){
3,650✔
400
  // unset startIndex if not requested
3,650✔
401
  if (!this.options.captureMetaData) startIndex = undefined;
3,650✔
402
  const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"])
3,650✔
403
  if(result === false){
3,650✔
404
  } else if(typeof result === "string"){
3,650✔
405
    childNode.tagname = result
3,625✔
406
    currentNode.addChild(childNode, startIndex);
3,625✔
407
  }else{
3,625!
408
    currentNode.addChild(childNode, startIndex);
×
409
  }
×
410
}
3,650✔
411

5✔
412
const replaceEntitiesValue = function(val){
5✔
413

2,970✔
414
  if(this.options.processEntities){
2,970✔
415
    for(let entityName in this.docTypeEntities){
2,920✔
416
      const entity = this.docTypeEntities[entityName];
2,364✔
417
      val = val.replace( entity.regx, entity.val);
2,364✔
418
    }
2,364✔
419
    for(let entityName in this.lastEntities){
2,920✔
420
      const entity = this.lastEntities[entityName];
13,804✔
421
      val = val.replace( entity.regex, entity.val);
13,804✔
422
    }
13,804✔
423
    if(this.options.htmlEntities){
2,920✔
424
      for(let entityName in this.htmlEntities){
175✔
425
        const entity = this.htmlEntities[entityName];
1,908✔
426
        val = val.replace( entity.regex, entity.val);
1,908✔
427
      }
1,908✔
428
    }
175✔
429
    val = val.replace( this.ampEntity.regex, this.ampEntity.val);
2,920✔
430
  }
2,920✔
431
  return val;
2,970✔
432
}
2,970✔
433
function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
5,530✔
434
  if (textData) { //store previously collected data as textNode
5,530✔
435
    if(isLeafNode === undefined) isLeafNode = currentNode.child.length === 0
4,875✔
436
    
4,875✔
437
    textData = this.parseTextData(textData,
4,875✔
438
      currentNode.tagname,
4,875✔
439
      jPath,
4,875✔
440
      false,
4,875✔
441
      currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false,
4,875✔
442
      isLeafNode);
4,875✔
443

4,875✔
444
    if (textData !== undefined && textData !== "")
4,875✔
445
      currentNode.add(this.options.textNodeName, textData);
4,875✔
446
    textData = "";
4,875✔
447
  }
4,875✔
448
  return textData;
5,530✔
449
}
5,530✔
450

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

5✔
467
/**
5✔
468
 * Returns the tag Expression and where it is ending handling single-double quotes situation
5✔
469
 * @param {string} xmlData 
5✔
470
 * @param {number} i starting index
5✔
471
 * @returns 
5✔
472
 */
5✔
473
function tagExpWithClosingIndex(xmlData, i, closingChar = ">"){
3,840✔
474
  let attrBoundary;
3,840✔
475
  let tagExp = "";
3,840✔
476
  for (let index = i; index < xmlData.length; index++) {
3,840✔
477
    let ch = xmlData[index];
52,925✔
478
    if (attrBoundary) {
52,925✔
479
        if (ch === attrBoundary) attrBoundary = "";//reset
13,845✔
480
    } else if (ch === '"' || ch === "'") {
52,925✔
481
        attrBoundary = ch;
1,540✔
482
    } else if (ch === closingChar[0]) {
39,080✔
483
      if(closingChar[1]){
4,060✔
484
        if(xmlData[index + 1] === closingChar[1]){
460✔
485
          return {
225✔
486
            data: tagExp,
225✔
487
            index: index
225✔
488
          }
225✔
489
        }
225✔
490
      }else{
4,060✔
491
        return {
3,600✔
492
          data: tagExp,
3,600✔
493
          index: index
3,600✔
494
        }
3,600✔
495
      }
3,600✔
496
    } else if (ch === '\t') {
37,540✔
497
      ch = " "
20✔
498
    }
20✔
499
    tagExp += ch;
49,100✔
500
  }
49,100✔
501
}
3,840✔
502

5✔
503
function findClosingIndex(xmlData, str, i, errMsg){
3,415✔
504
  const closingIndex = xmlData.indexOf(str, i);
3,415✔
505
  if(closingIndex === -1){
3,415✔
506
    throw new Error(errMsg)
15✔
507
  }else{
3,415✔
508
    return closingIndex + str.length - 1;
3,400✔
509
  }
3,400✔
510
}
3,415✔
511

5✔
512
function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
3,840✔
513
  const result = tagExpWithClosingIndex(xmlData, i+1, closingChar);
3,840✔
514
  if(!result) return;
3,840✔
515
  let tagExp = result.data;
3,825✔
516
  const closeIndex = result.index;
3,825✔
517
  const separatorIndex = tagExp.search(/\s/);
3,825✔
518
  let tagName = tagExp;
3,825✔
519
  let attrExpPresent = true;
3,825✔
520
  if(separatorIndex !== -1){//separate tag name and attributes expression
3,840✔
521
    tagName = tagExp.substring(0, separatorIndex);
1,190✔
522
    tagExp = tagExp.substring(separatorIndex + 1).trimStart();
1,190✔
523
  }
1,190✔
524

3,825✔
525
  const rawTagName = tagName;
3,825✔
526
  if(removeNSPrefix){
3,840✔
527
    const colonIndex = tagName.indexOf(":");
280✔
528
    if(colonIndex !== -1){
280✔
529
      tagName = tagName.substr(colonIndex+1);
65✔
530
      attrExpPresent = tagName !== result.data.substr(colonIndex + 1);
65✔
531
    }
65✔
532
  }
280✔
533

3,825✔
534
  return {
3,825✔
535
    tagName: tagName,
3,825✔
536
    tagExp: tagExp,
3,825✔
537
    closeIndex: closeIndex,
3,825✔
538
    attrExpPresent: attrExpPresent,
3,825✔
539
    rawTagName: rawTagName,
3,825✔
540
  }
3,825✔
541
}
3,840✔
542
/**
5✔
543
 * find paired tag for a stop node
5✔
544
 * @param {string} xmlData 
5✔
545
 * @param {string} tagName 
5✔
546
 * @param {number} i 
5✔
547
 */
5✔
548
function readStopNodeData(xmlData, tagName, i){
115✔
549
  const startIndex = i;
115✔
550
  // Starting at 1 since we already have an open tag
115✔
551
  let openTagCount = 1;
115✔
552

115✔
553
  for (; i < xmlData.length; i++) {
115✔
554
    if( xmlData[i] === "<"){ 
4,725✔
555
      if (xmlData[i+1] === "/") {//close tag
425✔
556
          const closeIndex = findClosingIndex(xmlData, ">", i, `${tagName} is not closed`);
260✔
557
          let closeTagName = xmlData.substring(i+2,closeIndex).trim();
260✔
558
          if(closeTagName === tagName){
260✔
559
            openTagCount--;
120✔
560
            if (openTagCount === 0) {
120✔
561
              return {
115✔
562
                tagContent: xmlData.substring(startIndex, i),
115✔
563
                i : closeIndex
115✔
564
              }
115✔
565
            }
115✔
566
          }
120✔
567
          i=closeIndex;
145✔
568
        } else if(xmlData[i+1] === '?') { 
425!
569
          const closeIndex = findClosingIndex(xmlData, "?>", i+1, "StopNode is not closed.")
×
570
          i=closeIndex;
×
571
        } else if(xmlData.substr(i + 1, 3) === '!--') { 
165✔
572
          const closeIndex = findClosingIndex(xmlData, "-->", i+3, "StopNode is not closed.")
5✔
573
          i=closeIndex;
5✔
574
        } else if(xmlData.substr(i + 1, 2) === '![') { 
165✔
575
          const closeIndex = findClosingIndex(xmlData, "]]>", i, "StopNode is not closed.") - 2;
5✔
576
          i=closeIndex;
5✔
577
        } else {
160✔
578
          const tagData = readTagExp(xmlData, i, '>')
155✔
579

155✔
580
          if (tagData) {
155✔
581
            const openTagName = tagData && tagData.tagName;
150✔
582
            if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length-1] !== "/") {
150✔
583
              openTagCount++;
5✔
584
            }
5✔
585
            i=tagData.closeIndex;
150✔
586
          }
150✔
587
        }
155✔
588
      }
425✔
589
  }//end for loop
115✔
590
}
115✔
591

5✔
592
function parseValue(val, shouldParse, options) {
3,185✔
593
  if (shouldParse && typeof val === 'string') {
3,185✔
594
    //console.log(options)
2,175✔
595
    const newval = val.trim();
2,175✔
596
    if(newval === 'true' ) return true;
2,175✔
597
    else if(newval === 'false' ) return false;
2,140✔
598
    else return toNumber(val, options);
2,125✔
599
  } else {
3,185✔
600
    if (isExist(val)) {
1,010✔
601
      return val;
1,010✔
602
    } else {
1,010!
603
      return '';
×
604
    }
×
605
  }
1,010✔
606
}
3,185✔
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