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

NaturalIntelligence / fast-xml-parser / 18221162996

03 Oct 2025 11:30AM UTC coverage: 97.561% (+0.01%) from 97.547%
18221162996

push

github

amitguptagwl
Use `Uint8Array` in place of `Buffer` in Parser

1107 of 1149 branches covered (96.34%)

189 of 304 new or added lines in 3 files covered. (62.17%)

1 existing line in 1 file now uncovered.

8920 of 9143 relevant lines covered (97.56%)

487652.08 hits per line

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

63.32
/src/xmlparser/DocTypeReader.js
1
import {isName} from '../util.js';
5✔
2

5✔
3
export default class DocTypeReader{
5✔
4
    constructor(processEntities){
5✔
5
        this.suppressValidationErr = !processEntities;
845✔
6
    }
845✔
7
    
5✔
8
    readDocType(xmlData, i){
5✔
9
    
70✔
10
        const entities = {};
70✔
11
        if( xmlData[i + 3] === 'O' &&
70✔
12
            xmlData[i + 4] === 'C' &&
70✔
13
            xmlData[i + 5] === 'T' &&
70✔
14
            xmlData[i + 6] === 'Y' &&
70✔
15
            xmlData[i + 7] === 'P' &&
70✔
16
            xmlData[i + 8] === 'E')
70✔
17
        {    
70✔
18
            i = i+9;
70✔
19
            let angleBracketsCount = 1;
70✔
20
            let hasBody = false, comment = false;
70✔
21
            let exp = "";
70✔
22
            for(;i<xmlData.length;i++){
70✔
23
                if (xmlData[i] === '<' && !comment) { //Determine the tag type
3,975✔
24
                    if( hasBody && hasSeq(xmlData, "!ENTITY",i)){
130✔
25
                        i += 7; 
70✔
26
                        let entityName, val;
70✔
27
                        [entityName, val,i] = this.readEntityExp(xmlData,i+1,this.suppressValidationErr);
70✔
28
                        if(val.indexOf("&") === -1) //Parameter entities are not supported
70✔
29
                            entities[ entityName ] = {
70✔
30
                                regx : RegExp( `&${entityName};`,"g"),
60✔
31
                                val: val
60✔
32
                            };
60✔
33
                    }
70✔
34
                    else if( hasBody && hasSeq(xmlData, "!ELEMENT",i))  {
60✔
35
                        i += 8;//Not supported
20✔
36
                        const {index} = this.readElementExp(xmlData,i+1);
20✔
37
                        i = index;
20✔
38
                    }else if( hasBody && hasSeq(xmlData, "!ATTLIST",i)){
60✔
39
                        i += 8;//Not supported
5✔
40
                        // const {index} = this.readAttlistExp(xmlData,i+1);
5✔
41
                        // i = index;
5✔
42
                    }else if( hasBody && hasSeq(xmlData, "!NOTATION",i)) {
40✔
43
                        i += 9;//Not supported
10✔
44
                        const {index} = this.readNotationExp(xmlData,i+1,this.suppressValidationErr);
10✔
45
                        i = index;
10✔
46
                    }else if( hasSeq(xmlData, "!--",i) ) comment = true;
35✔
NEW
47
                    else throw new Error(`Invalid DOCTYPE`);
×
48

125✔
49
                    angleBracketsCount++;
125✔
50
                    exp = "";
125✔
51
                } else if (xmlData[i] === '>') { //Read tag content
3,975✔
52
                    if(comment){
190✔
53
                        if( xmlData[i - 1] === "-" && xmlData[i - 2] === "-"){
30✔
54
                            comment = false;
25✔
55
                            angleBracketsCount--;
25✔
56
                        }
25✔
57
                    }else{
190✔
58
                        angleBracketsCount--;
160✔
59
                    }
160✔
60
                    if (angleBracketsCount === 0) {
190✔
61
                    break;
60✔
62
                    }
60✔
63
                }else if( xmlData[i] === '['){
3,845✔
64
                    hasBody = true;
55✔
65
                }else{
3,655✔
66
                    exp += xmlData[i];
3,600✔
67
                }
3,600✔
68
            }
3,975✔
69
            if(angleBracketsCount !== 0){
70✔
70
                throw new Error(`Unclosed DOCTYPE`);
5✔
71
            }
5✔
72
        }else{
70!
NEW
73
            throw new Error(`Invalid Tag instead of DOCTYPE`);
×
UNCOV
74
        }
×
75
        return {entities, i};
60✔
76
    }
70✔
77
    readEntityExp(xmlData, i) {    
5✔
78
        //External entities are not supported
70✔
79
        //    <!ENTITY ext SYSTEM "http://normal-website.com" >
70✔
80

70✔
81
        //Parameter entities are not supported
70✔
82
        //    <!ENTITY entityname "&anotherElement;">
70✔
83

70✔
84
        //Internal entities are supported
70✔
85
        //    <!ENTITY entityname "replacement text">
70✔
86

70✔
87
        // Skip leading whitespace after <!ENTITY
70✔
88
        i = skipWhitespace(xmlData, i);
70✔
89

70✔
90
        // Read entity name
70✔
91
        let entityName = "";
70✔
92
        while (i < xmlData.length && !/\s/.test(xmlData[i]) && xmlData[i] !== '"' && xmlData[i] !== "'") {
70✔
93
            entityName += xmlData[i];
370✔
94
            i++;
370✔
95
        }
370✔
96
        validateEntityName(entityName);
70✔
97

70✔
98
        // Skip whitespace after entity name
70✔
99
        i = skipWhitespace(xmlData, i);
70✔
100

70✔
101
        // Check for unsupported constructs (external entities or parameter entities)
70✔
102
        if(!this.suppressValidationErr){
70✔
103
            if (xmlData.substring(i, i + 6).toUpperCase() === "SYSTEM") {
65!
NEW
104
                throw new Error("External entities are not supported");
×
105
            }else if (xmlData[i] === "%") {
65!
NEW
106
                throw new Error("Parameter entities are not supported");
×
NEW
107
            }
×
108
        }
65✔
109

65✔
110
        // Read entity value (internal entity)
65✔
111
        let entityValue = "";
65✔
112
        [i, entityValue] = this.readIdentifierVal(xmlData, i, "entity");
65✔
113
        i--;
65✔
114
        return [entityName, entityValue, i ];
65✔
115
    }
70✔
116

5✔
117
    readNotationExp(xmlData, i) {
5✔
118
        // Skip leading whitespace after <!NOTATION
10✔
119
        i = skipWhitespace(xmlData, i);
10✔
120

10✔
121
        // Read notation name
10✔
122
        let notationName = "";
10✔
123
        while (i < xmlData.length && !/\s/.test(xmlData[i])) {
10✔
124
            notationName += xmlData[i];
45✔
125
            i++;
45✔
126
        }
45✔
127
        !this.suppressValidationErr && validateEntityName(notationName);
10✔
128

10✔
129
        // Skip whitespace after notation name
10✔
130
        i = skipWhitespace(xmlData, i);
10✔
131

10✔
132
        // Check identifier type (SYSTEM or PUBLIC)
10✔
133
        const identifierType = xmlData.substring(i, i + 6).toUpperCase();
10✔
134
        if (!this.suppressValidationErr && identifierType !== "SYSTEM" && identifierType !== "PUBLIC") {
10!
NEW
135
            throw new Error(`Expected SYSTEM or PUBLIC, found "${identifierType}"`);
×
NEW
136
        }
×
137
        i += identifierType.length;
10✔
138

10✔
139
        // Skip whitespace after identifier type
10✔
140
        i = skipWhitespace(xmlData, i);
10✔
141

10✔
142
        // Read public identifier (if PUBLIC)
10✔
143
        let publicIdentifier = null;
10✔
144
        let systemIdentifier = null;
10✔
145

10✔
146
        if (identifierType === "PUBLIC") {
10✔
147
            [i, publicIdentifier ] = this.readIdentifierVal(xmlData, i, "publicIdentifier");
10✔
148

10✔
149
            // Skip whitespace after public identifier
10✔
150
            i = skipWhitespace(xmlData, i);
10✔
151

10✔
152
            // Optionally read system identifier
10✔
153
            if (xmlData[i] === '"' || xmlData[i] === "'") {
10✔
154
                [i, systemIdentifier ] = this.readIdentifierVal(xmlData, i,"systemIdentifier");
5✔
155
            }
5✔
156
        } else if (identifierType === "SYSTEM") {
10!
NEW
157
            // Read system identifier (mandatory for SYSTEM)
×
NEW
158
            [i, systemIdentifier ] = this.readIdentifierVal(xmlData, i, "systemIdentifier");
×
159

×
NEW
160
            if (!this.suppressValidationErr && !systemIdentifier) {
×
NEW
161
                throw new Error("Missing mandatory system identifier for SYSTEM notation");
×
NEW
162
            }
×
163
        }
×
164
        
10✔
165
        return {notationName, publicIdentifier, systemIdentifier, index: --i};
10✔
166
    }
10✔
167

5✔
168
    readIdentifierVal(xmlData, i, type) {
5✔
169
        let identifierVal = "";
80✔
170
        const startChar = xmlData[i];
80✔
171
        if (startChar !== '"' && startChar !== "'") {
80!
NEW
172
            throw new Error(`Expected quoted string, found "${startChar}"`);
×
NEW
173
        }
×
174
        i++;
80✔
175

80✔
176
        while (i < xmlData.length && xmlData[i] !== startChar) {
80✔
177
            identifierVal += xmlData[i];
2,700✔
178
            i++;
2,700✔
179
        }
2,700✔
180

80✔
181
        if (xmlData[i] !== startChar) {
80!
NEW
182
            throw new Error(`Unterminated ${type} value`);
×
NEW
183
        }
×
184
        i++;
80✔
185
        return [i, identifierVal];
80✔
186
    }
80✔
187

5✔
188
    readElementExp(xmlData, i) {
5✔
189
        // <!ELEMENT br EMPTY>
20✔
190
        // <!ELEMENT div ANY>
20✔
191
        // <!ELEMENT title (#PCDATA)>
20✔
192
        // <!ELEMENT book (title, author+)>
20✔
193
        // <!ELEMENT name (content-model)>
20✔
194
        
20✔
195
        // Skip leading whitespace after <!ELEMENT
20✔
196
        i = skipWhitespace(xmlData, i);
20✔
197

20✔
198
        // Read element name
20✔
199
        let elementName = "";
20✔
200
        while (i < xmlData.length && !/\s/.test(xmlData[i])) {
20✔
201
            elementName += xmlData[i];
65✔
202
            i++;
65✔
203
        }
65✔
204

20✔
205
        // Validate element name
20✔
206
        if (!this.suppressValidationErr && !isName(elementName)) {
20!
NEW
207
            throw new Error(`Invalid element name: "${elementName}"`);
×
NEW
208
        }
×
209

20✔
210
        // Skip whitespace after element name
20✔
211
        i = skipWhitespace(xmlData, i);
20✔
212
        let contentModel = "";
20✔
213
        // Expect '(' to start content model
20✔
214
        if(xmlData[i] === "E" && hasSeq(xmlData, "MPTY",i)) i+=4;
20✔
215
        else if(xmlData[i] === "A" && hasSeq(xmlData, "NY",i)) i+=2;
15✔
216
        else if (xmlData[i] === "(") {
10✔
217
            i++; // Move past '('
10✔
218

10✔
219
            // Read content model
10✔
220
            while (i < xmlData.length && xmlData[i] !== ")") {
10✔
221
                contentModel += xmlData[i];
70✔
222
                i++;
70✔
223
            }
70✔
224
            if (xmlData[i] !== ")") {
10!
NEW
225
                throw new Error("Unterminated content model");
×
NEW
226
            }
×
227

10✔
228
        }else if(!this.suppressValidationErr){
10!
NEW
229
            throw new Error(`Invalid Element Expression, found "${xmlData[i]}"`);
×
NEW
230
        }
×
231
        
20✔
232
        return {
20✔
233
            elementName,
20✔
234
            contentModel: contentModel.trim(),
20✔
235
            index: i
20✔
236
        };
20✔
237
    }
20✔
238

5✔
239
    readAttlistExp(xmlData, i) {
5✔
NEW
240
        // Skip leading whitespace after <!ATTLIST
×
NEW
241
        i = skipWhitespace(xmlData, i);
×
242

×
NEW
243
        // Read element name
×
NEW
244
        let elementName = "";
×
NEW
245
        while (i < xmlData.length && !/\s/.test(xmlData[i])) {
×
NEW
246
            elementName += xmlData[i];
×
NEW
247
            i++;
×
NEW
248
        }
×
249

×
NEW
250
        // Validate element name
×
NEW
251
        validateEntityName(elementName)
×
252

×
NEW
253
        // Skip whitespace after element name
×
NEW
254
        i = skipWhitespace(xmlData, i);
×
255

×
NEW
256
        // Read attribute name
×
NEW
257
        let attributeName = "";
×
NEW
258
        while (i < xmlData.length && !/\s/.test(xmlData[i])) {
×
NEW
259
            attributeName += xmlData[i];
×
NEW
260
            i++;
×
NEW
261
        }
×
262

×
NEW
263
        // Validate attribute name
×
NEW
264
        if (!validateEntityName(attributeName)) {
×
NEW
265
            throw new Error(`Invalid attribute name: "${attributeName}"`);
×
NEW
266
        }
×
267

×
NEW
268
        // Skip whitespace after attribute name
×
269
        i = skipWhitespace(xmlData, i);
×
270

×
NEW
271
        // Read attribute type
×
NEW
272
        let attributeType = "";
×
NEW
273
        if (xmlData.substring(i, i + 8).toUpperCase() === "NOTATION") {
×
NEW
274
            attributeType = "NOTATION";
×
NEW
275
            i += 8; // Move past "NOTATION"
×
NEW
276

×
NEW
277
            // Skip whitespace after "NOTATION"
×
NEW
278
            i = skipWhitespace(xmlData, i);
×
NEW
279

×
NEW
280
            // Expect '(' to start the list of notations
×
NEW
281
            if (xmlData[i] !== "(") {
×
NEW
282
                throw new Error(`Expected '(', found "${xmlData[i]}"`);
×
283
            }
×
NEW
284
            i++; // Move past '('
×
NEW
285

×
NEW
286
            // Read the list of allowed notations
×
NEW
287
            let allowedNotations = [];
×
NEW
288
            while (i < xmlData.length && xmlData[i] !== ")") {
×
NEW
289
                let notation = "";
×
NEW
290
                while (i < xmlData.length && xmlData[i] !== "|" && xmlData[i] !== ")") {
×
NEW
291
                    notation += xmlData[i];
×
NEW
292
                    i++;
×
NEW
293
                }
×
NEW
294

×
NEW
295
                // Validate notation name
×
NEW
296
                notation = notation.trim();
×
NEW
297
                if (!validateEntityName(notation)) {
×
NEW
298
                    throw new Error(`Invalid notation name: "${notation}"`);
×
NEW
299
                }
×
NEW
300

×
NEW
301
                allowedNotations.push(notation);
×
302

×
NEW
303
                // Skip '|' separator or exit loop
×
NEW
304
                if (xmlData[i] === "|") {
×
NEW
305
                    i++; // Move past '|'
×
NEW
306
                    i = skipWhitespace(xmlData, i); // Skip optional whitespace after '|'
×
NEW
307
                }
×
308
            }
×
309

×
NEW
310
            if (xmlData[i] !== ")") {
×
NEW
311
                throw new Error("Unterminated list of notations");
×
NEW
312
            }
×
NEW
313
            i++; // Move past ')'
×
NEW
314

×
NEW
315
            // Store the allowed notations as part of the attribute type
×
NEW
316
            attributeType += " (" + allowedNotations.join("|") + ")";
×
NEW
317
        } else {
×
NEW
318
            // Handle simple types (e.g., CDATA, ID, IDREF, etc.)
×
NEW
319
            while (i < xmlData.length && !/\s/.test(xmlData[i])) {
×
NEW
320
                attributeType += xmlData[i];
×
NEW
321
                i++;
×
NEW
322
            }
×
323

×
NEW
324
            // Validate simple attribute type
×
NEW
325
            const validTypes = ["CDATA", "ID", "IDREF", "IDREFS", "ENTITY", "ENTITIES", "NMTOKEN", "NMTOKENS"];
×
NEW
326
            if (!this.suppressValidationErr && !validTypes.includes(attributeType.toUpperCase())) {
×
NEW
327
                throw new Error(`Invalid attribute type: "${attributeType}"`);
×
328
            }
×
329
        }
×
330

×
NEW
331
        // Skip whitespace after attribute type
×
NEW
332
        i = skipWhitespace(xmlData, i);
×
333

×
NEW
334
        // Read default value
×
NEW
335
        let defaultValue = "";
×
NEW
336
        if (xmlData.substring(i, i + 8).toUpperCase() === "#REQUIRED") {
×
NEW
337
            defaultValue = "#REQUIRED";
×
NEW
338
            i += 8;
×
NEW
339
        } else if (xmlData.substring(i, i + 7).toUpperCase() === "#IMPLIED") {
×
NEW
340
            defaultValue = "#IMPLIED";
×
NEW
341
            i += 7;
×
NEW
342
        } else {
×
NEW
343
            [i, defaultValue] = this.readIdentifierVal(xmlData, i, "ATTLIST");
×
344
        }
×
345

×
NEW
346
        return {
×
NEW
347
            elementName,
×
NEW
348
            attributeName,
×
NEW
349
            attributeType,
×
NEW
350
            defaultValue,
×
NEW
351
            index: i
×
352
        }
×
353
    }
×
354
}
5✔
355

5✔
356

5✔
357

5✔
358
const skipWhitespace = (data, index) => {
5✔
359
    while (index < data.length && /\s/.test(data[index])) {
215✔
360
        index++;
220✔
361
    }
220✔
362
    return index;
215✔
363
};
5✔
364

5✔
365

5✔
366

5✔
367
function hasSeq(data, seq,i){
300✔
368
    for(let j=0;j<seq.length;j++){
300✔
369
        if(seq[j]!==data[i+j+1]) return false;
1,225✔
370
    }
1,225✔
371
    return true;
140✔
372
}
300✔
373

5✔
374
function validateEntityName(name){
80✔
375
    if (isName(name))
80✔
376
            return name;
80✔
377
    else
5✔
378
        throw new Error(`Invalid entity name ${name}`);
5✔
379
}
80✔
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