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

NaturalIntelligence / fast-xml-parser / 22910077419

10 Mar 2026 03:23PM UTC coverage: 97.837% (+0.1%) from 97.689%
22910077419

push

github

amitguptagwl
update dependency

1110 of 1154 branches covered (96.19%)

9228 of 9432 relevant lines covered (97.84%)

473155.51 hits per line

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

99.43
/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,440✔
15
  if (!attrs || typeof attrs !== 'object') return {};
5,440!
16
  if (!prefix) return attrs;
5,440✔
17

4,875✔
18
  const rawAttrs = {};
4,875✔
19
  for (const key in attrs) {
5,440✔
20
    if (key.startsWith(prefix)) {
2,851✔
21
      const rawName = key.substring(prefix.length);
590✔
22
      rawAttrs[rawName] = attrs[key];
590✔
23
    } else {
2,851✔
24
      // Attribute without prefix (shouldn't normally happen, but be safe)
2,261✔
25
      rawAttrs[key] = attrs[key];
2,261✔
26
    }
2,261✔
27
  }
2,851✔
28
  return rawAttrs;
4,875✔
29
}
5,440✔
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) {
5✔
39
  return compress(node, options, matcher);
1,410✔
40
}
1,410✔
41

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

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

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

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

5,440✔
75
      if (tagObj[":@"]) {
5,440✔
76
        assignAttributes(val, tagObj[":@"], matcher, options);
670✔
77
      } else if (Object.keys(val).length === 1 && val[options.textNodeName] !== undefined && !options.alwaysCreateTextNode) {
5,440✔
78
        val = val[options.textNodeName];
2,475✔
79
      } else if (Object.keys(val).length === 0) {
4,770✔
80
        if (options.alwaysCreateTextNode) val[options.textNodeName] = "";
365✔
81
        else val = "";
350✔
82
      }
365✔
83

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

5,440✔
88

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

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

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

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

6,850✔
119

6,850✔
120
  return compressedObj;
6,850✔
121
}
6,850✔
122

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

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

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

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

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

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

5,440✔
162
  if (propCount === 0) {
5,440✔
163
    return true;
605✔
164
  }
605✔
165

4,835✔
166
  if (
4,835✔
167
    propCount === 1 &&
5,440✔
168
    (obj[textNodeName] || typeof obj[textNodeName] === "boolean" || obj[textNodeName] === 0)
3,755✔
169
  ) {
5,440✔
170
    return true;
2,755✔
171
  }
2,755✔
172

2,080✔
173
  return false;
2,080✔
174
}
5,440✔
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