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

NaturalIntelligence / fast-xml-parser / 24387099185

14 Apr 2026 07:41AM UTC coverage: 97.617% (-0.08%) from 97.694%
24387099185

push

github

amitguptagwl
use @nodable/entities to replace entities

1165 of 1214 branches covered (95.96%)

21 of 21 new or added lines in 4 files covered. (100.0%)

12 existing lines in 2 files now uncovered.

9298 of 9525 relevant lines covered (97.62%)

468518.77 hits per line

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

99.42
/src/xmlparser/node2json.js
1
'use strict';
5✔
2

5✔
3
import XmlNode from './xmlNode.js';
5✔
4
import { Matcher } from 'path-expression-matcher';
5✔
5

5✔
6
const METADATA_SYMBOL = XmlNode.getMetaDataSymbol();
5✔
7

5✔
8
/**
5✔
9
 * Helper function to strip attribute prefix from attribute map
5✔
10
 * @param {object} attrs - Attributes with prefix (e.g., {"@_class": "code"})
5✔
11
 * @param {string} prefix - Attribute prefix to remove (e.g., "@_")
5✔
12
 * @returns {object} Attributes without prefix (e.g., {"class": "code"})
5✔
13
 */
5✔
14
function stripAttributePrefix(attrs, prefix) {
5,475✔
15
  if (!attrs || typeof attrs !== 'object') return {};
5,475!
16
  if (!prefix) return attrs;
5,475✔
17

4,910✔
18
  const rawAttrs = {};
4,910✔
19
  for (const key in attrs) {
5,475✔
20
    if (key.startsWith(prefix)) {
2,956✔
21
      const rawName = key.substring(prefix.length);
590✔
22
      rawAttrs[rawName] = attrs[key];
590✔
23
    } else {
2,956!
UNCOV
24
      // Attribute without prefix (shouldn't normally happen, but be safe)
2,366✔
UNCOV
25
      rawAttrs[key] = attrs[key];
2,366✔
UNCOV
26
    }
2,366✔
27
  }
2,956✔
28
  return rawAttrs;
4,910✔
29
}
5,475✔
30

5✔
31
/**
5✔
32
 * 
5✔
33
 * @param {array} node 
5✔
34
 * @param {any} options 
5✔
35
 * @param {Matcher} matcher - Path matcher instance
5✔
36
 * @returns 
5✔
37
 */
5✔
38
export default function prettify(node, options, matcher, readonlyMatcher) {
5✔
39
  return compress(node, options, matcher, readonlyMatcher);
1,435✔
40
}
1,435✔
41

5✔
42
/**
5✔
43
 * @param {array} arr 
5✔
44
 * @param {object} options 
5✔
45
 * @param {Matcher} matcher - Path matcher instance
5✔
46
 * @returns object
5✔
47
 */
5✔
48
function compress(arr, options, matcher, readonlyMatcher) {
6,910✔
49
  let text;
6,910✔
50
  const compressedObj = {}; //This is intended to be a plain object
6,910✔
51
  for (let i = 0; i < arr.length; i++) {
6,910✔
52
    const tagObj = arr[i];
8,655✔
53
    const property = propName(tagObj);
8,655✔
54

8,655✔
55
    // Push current property to matcher WITH RAW ATTRIBUTES (no prefix)
8,655✔
56
    if (property !== undefined && property !== options.textNodeName) {
8,655✔
57
      const rawAttrs = stripAttributePrefix(
5,475✔
58
        tagObj[":@"] || {},
5,475✔
59
        options.attributeNamePrefix
5,475✔
60
      );
5,475✔
61
      matcher.push(property, rawAttrs);
5,475✔
62
    }
5,475✔
63

8,655✔
64
    if (property === options.textNodeName) {
8,655✔
65
      if (text === undefined) text = tagObj[property];
3,180✔
66
      else text += "" + tagObj[property];
170✔
67
    } else if (property === undefined) {
8,655!
68
      continue;
×
69
    } else if (tagObj[property]) {
5,475✔
70

5,475✔
71
      let val = compress(tagObj[property], options, matcher, readonlyMatcher);
5,475✔
72
      const isLeaf = isLeafTag(val, options);
5,475✔
73

5,475✔
74
      if (tagObj[":@"]) {
5,475✔
75
        assignAttributes(val, tagObj[":@"], readonlyMatcher, options);
670✔
76
      } else if (Object.keys(val).length === 1 && val[options.textNodeName] !== undefined && !options.alwaysCreateTextNode) {
5,475✔
77
        val = val[options.textNodeName];
2,495✔
78
      } else if (Object.keys(val).length === 0) {
4,805✔
79
        if (options.alwaysCreateTextNode) val[options.textNodeName] = "";
355✔
80
        else val = "";
340✔
81
      }
355✔
82

5,475✔
83
      if (tagObj[METADATA_SYMBOL] !== undefined && typeof val === "object" && val !== null) {
5,475✔
84
        val[METADATA_SYMBOL] = tagObj[METADATA_SYMBOL]; // copy over metadata
45✔
85
      }
45✔
86

5,475✔
87

5,475✔
88
      if (compressedObj[property] !== undefined && Object.prototype.hasOwnProperty.call(compressedObj, property)) {
5,475✔
89
        if (!Array.isArray(compressedObj[property])) {
360✔
90
          compressedObj[property] = [compressedObj[property]];
270✔
91
        }
270✔
92
        compressedObj[property].push(val);
360✔
93
      } else {
5,475✔
94
        //TODO: if a node is not an array, then check if it should be an array
5,115✔
95
        //also determine if it is a leaf node
5,115✔
96

5,115✔
97
        // Pass jPath string or readonlyMatcher based on options.jPath setting
5,115✔
98
        const jPathOrMatcher = options.jPath ? readonlyMatcher.toString() : readonlyMatcher;
5,115✔
99
        if (options.isArray(property, jPathOrMatcher, isLeaf)) {
5,115✔
100
          compressedObj[property] = [val];
105✔
101
        } else {
5,115✔
102
          compressedObj[property] = val;
5,010✔
103
        }
5,010✔
104
      }
5,115✔
105

5,475✔
106
      // Pop property from matcher after processing
5,475✔
107
      if (property !== undefined && property !== options.textNodeName) {
5,475✔
108
        matcher.pop();
5,475✔
109
      }
5,475✔
110
    }
5,475✔
111

8,655✔
112
  }
8,655✔
113
  // if(text && text.length > 0) compressedObj[options.textNodeName] = text;
6,910✔
114
  if (typeof text === "string") {
6,910✔
115
    if (text.length > 0) compressedObj[options.textNodeName] = text;
2,580✔
116
  } else if (text !== undefined) compressedObj[options.textNodeName] = text;
6,910✔
117

6,910✔
118

6,910✔
119
  return compressedObj;
6,910✔
120
}
6,910✔
121

5✔
122
function propName(obj) {
8,655✔
123
  const keys = Object.keys(obj);
8,655✔
124
  for (let i = 0; i < keys.length; i++) {
8,655✔
125
    const key = keys[i];
8,655✔
126
    if (key !== ":@") return key;
8,655✔
127
  }
8,655✔
128
}
8,655!
129

5✔
130
function assignAttributes(obj, attrMap, readonlyMatcher, options) {
670✔
131
  if (attrMap) {
670✔
132
    const keys = Object.keys(attrMap);
670✔
133
    const len = keys.length; //don't make it inline
670✔
134
    for (let i = 0; i < len; i++) {
670✔
135
      const atrrName = keys[i];  // This is the PREFIXED name (e.g., "@_class")
970✔
136

970✔
137
      // Strip prefix for matcher path (for isArray callback)
970✔
138
      const rawAttrName = atrrName.startsWith(options.attributeNamePrefix)
970✔
139
        ? atrrName.substring(options.attributeNamePrefix.length)
970✔
140
        : atrrName;
970!
141

970✔
142
      // For attributes, we need to create a temporary path
970✔
143
      // Pass jPath string or matcher based on options.jPath setting
970✔
144
      const jPathOrMatcher = options.jPath
970✔
145
        ? readonlyMatcher.toString() + "." + rawAttrName
970✔
146
        : readonlyMatcher;
970✔
147

970✔
148
      if (options.isArray(atrrName, jPathOrMatcher, true, true)) {
970✔
149
        obj[atrrName] = [attrMap[atrrName]];
20✔
150
      } else {
970✔
151
        obj[atrrName] = attrMap[atrrName];
950✔
152
      }
950✔
153
    }
970✔
154
  }
670✔
155
}
670✔
156

5✔
157
function isLeafTag(obj, options) {
5,475✔
158
  const { textNodeName } = options;
5,475✔
159
  const propCount = Object.keys(obj).length;
5,475✔
160

5,475✔
161
  if (propCount === 0) {
5,475✔
162
    return true;
595✔
163
  }
595✔
164

4,880✔
165
  if (
4,880✔
166
    propCount === 1 &&
5,475✔
167
    (obj[textNodeName] || typeof obj[textNodeName] === "boolean" || obj[textNodeName] === 0)
3,810✔
168
  ) {
5,475✔
169
    return true;
2,775✔
170
  }
2,775✔
171

2,105✔
172
  return false;
2,105✔
173
}
5,475✔
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