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

NaturalIntelligence / fast-xml-parser / 14594236992

22 Apr 2025 11:28AM UTC coverage: 97.492% (-1.5%) from 98.954%
14594236992

push

github

amitguptagwl
update docs and package info

1131 of 1173 branches covered (96.42%)

8940 of 9170 relevant lines covered (97.49%)

486216.26 hits per line

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

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

5✔
3
//TODO: handle comments
5✔
4
export default function readDocType(xmlData, i){
5✔
5
    
70✔
6
    const entities = {};
70✔
7
    if( xmlData[i + 3] === 'O' &&
70✔
8
         xmlData[i + 4] === 'C' &&
70✔
9
         xmlData[i + 5] === 'T' &&
70✔
10
         xmlData[i + 6] === 'Y' &&
70✔
11
         xmlData[i + 7] === 'P' &&
70✔
12
         xmlData[i + 8] === 'E')
70✔
13
    {    
70✔
14
        i = i+9;
70✔
15
        let angleBracketsCount = 1;
70✔
16
        let hasBody = false, comment = false;
70✔
17
        let exp = "";
70✔
18
        for(;i<xmlData.length;i++){
70✔
19
            if (xmlData[i] === '<' && !comment) { //Determine the tag type
3,965✔
20
                if( hasBody && isEntity(xmlData, i)){
125✔
21
                    i += 7; 
70✔
22
                    let entityName, val;
70✔
23
                    [entityName, val,i] = readEntityExp(xmlData,i+1);
70✔
24
                    if(val.indexOf("&") === -1) //Parameter entities are not supported
70✔
25
                        entities[ entityName ] = {
70✔
26
                            regx : RegExp( `&${entityName};`,"g"),
60✔
27
                            val: val
60✔
28
                        };
60✔
29
                }
70✔
30
                else if( hasBody && isElement(xmlData, i))  {
55✔
31
                    i += 8;//Not supported
15✔
32
                    const {index} = readElementExp(xmlData,i+1);
15✔
33
                    i = index;
15✔
34
                }else if( hasBody && isAttlist(xmlData, i)){
55✔
35
                    i += 8;//Not supported
5✔
36
                    // const {index} = readAttlistExp(xmlData,i+1);
5✔
37
                    // i = index;
5✔
38
                }else if( hasBody && isNotation(xmlData, i)) {
40✔
39
                    i += 9;//Not supported
10✔
40
                    const {index} = readNotationExp(xmlData,i+1);
10✔
41
                    i = index;
10✔
42
                }else if( isComment) comment = true;
35✔
43
                else throw new Error("Invalid DOCTYPE");
×
44

120✔
45
                angleBracketsCount++;
120✔
46
                exp = "";
120✔
47
            } else if (xmlData[i] === '>') { //Read tag content
3,965✔
48
                if(comment){
185✔
49
                    if( xmlData[i - 1] === "-" && xmlData[i - 2] === "-"){
30✔
50
                        comment = false;
25✔
51
                        angleBracketsCount--;
25✔
52
                    }
25✔
53
                }else{
185✔
54
                    angleBracketsCount--;
155✔
55
                }
155✔
56
                if (angleBracketsCount === 0) {
185✔
57
                  break;
60✔
58
                }
60✔
59
            }else if( xmlData[i] === '['){
3,840✔
60
                hasBody = true;
55✔
61
            }else{
3,655✔
62
                exp += xmlData[i];
3,600✔
63
            }
3,600✔
64
        }
3,965✔
65
        if(angleBracketsCount !== 0){
70✔
66
            throw new Error(`Unclosed DOCTYPE`);
5✔
67
        }
5✔
68
    }else{
70!
69
        throw new Error(`Invalid Tag instead of DOCTYPE`);
×
70
    }
×
71
    return {entities, i};
60✔
72
}
70✔
73

5✔
74
const skipWhitespace = (data, index) => {
5✔
75
    while (index < data.length && /\s/.test(data[index])) {
205✔
76
        index++;
210✔
77
    }
210✔
78
    return index;
205✔
79
};
5✔
80

5✔
81
function readEntityExp(xmlData, i) {    
70✔
82
    //External entities are not supported
70✔
83
    //    <!ENTITY ext SYSTEM "http://normal-website.com" >
70✔
84

70✔
85
    //Parameter entities are not supported
70✔
86
    //    <!ENTITY entityname "&anotherElement;">
70✔
87

70✔
88
    //Internal entities are supported
70✔
89
    //    <!ENTITY entityname "replacement text">
70✔
90

70✔
91
    // Skip leading whitespace after <!ENTITY
70✔
92
    i = skipWhitespace(xmlData, i);
70✔
93

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

70✔
102
    // Skip whitespace after entity name
70✔
103
    i = skipWhitespace(xmlData, i);
70✔
104

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

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

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

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

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

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

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

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

10✔
148
    if (identifierType === "PUBLIC") {
10✔
149
        [i, publicIdentifier ] = readIdentifierVal(xmlData, i, "publicIdentifier");
10✔
150

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

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

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

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

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

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

5✔
190
function readElementExp(xmlData, i) {
15✔
191
    // <!ELEMENT name (content-model)>
15✔
192

15✔
193
    // Skip leading whitespace after <!ELEMENT
15✔
194
    i = skipWhitespace(xmlData, i);
15✔
195

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

15✔
203
    // Validate element name
15✔
204
    if (!validateEntityName(elementName)) {
15!
205
        throw new Error(`Invalid element name: "${elementName}"`);
×
206
    }
×
207

15✔
208
    // Skip whitespace after element name
15✔
209
    i = skipWhitespace(xmlData, i);
15✔
210

15✔
211
    // Expect '(' to start content model
15✔
212
    if (xmlData[i] !== "(") {
15!
213
        throw new Error(`Expected '(', found "${xmlData[i]}"`);
×
214
    }
×
215
    i++; // Move past '('
15✔
216

15✔
217
    // Read content model
15✔
218
    let contentModel = "";
15✔
219
    while (i < xmlData.length && xmlData[i] !== ")") {
15✔
220
        contentModel += xmlData[i];
105✔
221
        i++;
105✔
222
    }
105✔
223

15✔
224
    if (xmlData[i] !== ")") {
15!
225
        throw new Error("Unterminated content model");
×
226
    }
×
227

15✔
228
    return {
15✔
229
        elementName,
15✔
230
        contentModel: contentModel.trim(),
15✔
231
        index: i
15✔
232
    };
15✔
233
}
15✔
234

5✔
235
function readAttlistExp(xmlData, i) {
×
236
    // Skip leading whitespace after <!ATTLIST
×
237
    i = skipWhitespace(xmlData, i);
×
238

×
239
    // Read element name
×
240
    let elementName = "";
×
241
    while (i < xmlData.length && !/\s/.test(xmlData[i])) {
×
242
        elementName += xmlData[i];
×
243
        i++;
×
244
    }
×
245

×
246
    // Validate element name
×
247
    validateEntityName(elementName)
×
248

×
249
    // Skip whitespace after element name
×
250
    i = skipWhitespace(xmlData, i);
×
251

×
252
    // Read attribute name
×
253
    let attributeName = "";
×
254
    while (i < xmlData.length && !/\s/.test(xmlData[i])) {
×
255
        attributeName += xmlData[i];
×
256
        i++;
×
257
    }
×
258

×
259
    // Validate attribute name
×
260
    if (!validateEntityName(attributeName)) {
×
261
        throw new Error(`Invalid attribute name: "${attributeName}"`);
×
262
    }
×
263

×
264
    // Skip whitespace after attribute name
×
265
    i = skipWhitespace(xmlData, i);
×
266

×
267
    // Read attribute type
×
268
    let attributeType = "";
×
269
    if (xmlData.substring(i, i + 8).toUpperCase() === "NOTATION") {
×
270
        attributeType = "NOTATION";
×
271
        i += 8; // Move past "NOTATION"
×
272

×
273
        // Skip whitespace after "NOTATION"
×
274
        i = skipWhitespace(xmlData, i);
×
275

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

×
282
        // Read the list of allowed notations
×
283
        let allowedNotations = [];
×
284
        while (i < xmlData.length && xmlData[i] !== ")") {
×
285
            let notation = "";
×
286
            while (i < xmlData.length && xmlData[i] !== "|" && xmlData[i] !== ")") {
×
287
                notation += xmlData[i];
×
288
                i++;
×
289
            }
×
290

×
291
            // Validate notation name
×
292
            notation = notation.trim();
×
293
            if (!validateEntityName(notation)) {
×
294
                throw new Error(`Invalid notation name: "${notation}"`);
×
295
            }
×
296

×
297
            allowedNotations.push(notation);
×
298

×
299
            // Skip '|' separator or exit loop
×
300
            if (xmlData[i] === "|") {
×
301
                i++; // Move past '|'
×
302
                i = skipWhitespace(xmlData, i); // Skip optional whitespace after '|'
×
303
            }
×
304
        }
×
305

×
306
        if (xmlData[i] !== ")") {
×
307
            throw new Error("Unterminated list of notations");
×
308
        }
×
309
        i++; // Move past ')'
×
310

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

×
320
        // Validate simple attribute type
×
321
        const validTypes = ["CDATA", "ID", "IDREF", "IDREFS", "ENTITY", "ENTITIES", "NMTOKEN", "NMTOKENS"];
×
322
        if (!validTypes.includes(attributeType.toUpperCase())) {
×
323
            throw new Error(`Invalid attribute type: "${attributeType}"`);
×
324
        }
×
325
    }
×
326

×
327
    // Skip whitespace after attribute type
×
328
    i = skipWhitespace(xmlData, i);
×
329

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

×
342
    return {
×
343
        elementName,
×
344
        attributeName,
×
345
        attributeType,
×
346
        defaultValue,
×
347
        index: i
×
348
    }
×
349
}
×
350

5✔
351
function isComment(xmlData, i){
×
352
    if(xmlData[i+1] === '!' &&
×
353
    xmlData[i+2] === '-' &&
×
354
    xmlData[i+3] === '-') return true
×
355
    return false
×
356
}
×
357
function isEntity(xmlData, i){
125✔
358
    if(xmlData[i+1] === '!' &&
125✔
359
    xmlData[i+2] === 'E' &&
125✔
360
    xmlData[i+3] === 'N' &&
125✔
361
    xmlData[i+4] === 'T' &&
125✔
362
    xmlData[i+5] === 'I' &&
125✔
363
    xmlData[i+6] === 'T' &&
125✔
364
    xmlData[i+7] === 'Y') return true
125✔
365
    return false
55✔
366
}
125✔
367
function isElement(xmlData, i){
55✔
368
    if(xmlData[i+1] === '!' &&
55✔
369
    xmlData[i+2] === 'E' &&
55✔
370
    xmlData[i+3] === 'L' &&
55✔
371
    xmlData[i+4] === 'E' &&
55✔
372
    xmlData[i+5] === 'M' &&
55✔
373
    xmlData[i+6] === 'E' &&
55✔
374
    xmlData[i+7] === 'N' &&
55✔
375
    xmlData[i+8] === 'T') return true
55✔
376
    return false
40✔
377
}
55✔
378

5✔
379
function isAttlist(xmlData, i){
40✔
380
    if(xmlData[i+1] === '!' &&
40✔
381
    xmlData[i+2] === 'A' &&
40✔
382
    xmlData[i+3] === 'T' &&
40✔
383
    xmlData[i+4] === 'T' &&
40✔
384
    xmlData[i+5] === 'L' &&
40✔
385
    xmlData[i+6] === 'I' &&
40✔
386
    xmlData[i+7] === 'S' &&
40✔
387
    xmlData[i+8] === 'T') return true
40✔
388
    return false
35✔
389
}
40✔
390
function isNotation(xmlData, i){
35✔
391
    if(xmlData[i+1] === '!' &&
35✔
392
    xmlData[i+2] === 'N' &&
35✔
393
    xmlData[i+3] === 'O' &&
35✔
394
    xmlData[i+4] === 'T' &&
35✔
395
    xmlData[i+5] === 'A' &&
35✔
396
    xmlData[i+6] === 'T' &&
35✔
397
    xmlData[i+7] === 'I' &&
35✔
398
    xmlData[i+8] === 'O' &&
35✔
399
    xmlData[i+9] === 'N') return true
35✔
400
    return false
25✔
401
}
35✔
402

5✔
403
function validateEntityName(name){
95✔
404
    if (isName(name))
95✔
405
        return name;
95✔
406
    else
5✔
407
        throw new Error(`Invalid entity name ${name}`);
5✔
408
}
95✔
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