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

NaturalIntelligence / fast-xml-parser / 14973142022

12 May 2025 01:11PM CUT coverage: 97.547%. Remained the same
14973142022

push

github

web-flow
export types in fxp.d.ts for better module usability (#744)

1090 of 1134 branches covered (96.12%)

8908 of 9132 relevant lines covered (97.55%)

488239.21 hits per line

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

62.06
/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
4,045✔
20
                if( hasBody && hasSeq(xmlData, "!ENTITY",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 && hasSeq(xmlData, "!ELEMENT",i))  {
55✔
31
                    i += 8;//Not supported
15✔
32
                    const {index} = readElementExp(xmlData,i+1);
15✔
33
                    i = index;
15✔
34
                }else if( hasBody && hasSeq(xmlData, "!ATTLIST",i)){
55✔
35
                    i += 8;//Not supported
5✔
36
                    // const {index} = readAttlistExp(xmlData,i+1);
5✔
37
                    // i = index;
5✔
38
                }else if( hasBody && hasSeq(xmlData, "!NOTATION",i)) {
40✔
39
                    i += 9;//Not supported
10✔
40
                    const {index} = readNotationExp(xmlData,i+1);
10✔
41
                    i = index;
10✔
42
                }else if( hasSeq(xmlData, "!--",i) ) 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
4,045✔
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,920✔
60
                hasBody = true;
55✔
61
            }else{
3,735✔
62
                exp += xmlData[i];
3,680✔
63
            }
3,680✔
64
        }
4,045✔
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 br EMPTY>
15✔
192
    // <!ELEMENT div ANY>
15✔
193
    // <!ELEMENT title (#PCDATA)>
15✔
194
    // <!ELEMENT book (title, author+)>
15✔
195
    // <!ELEMENT name (content-model)>
15✔
196
    
15✔
197
    // Skip leading whitespace after <!ELEMENT
15✔
198
    i = skipWhitespace(xmlData, i);
15✔
199

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

15✔
207
    // Validate element name
15✔
208
    if (!validateEntityName(elementName)) {
15!
209
        throw new Error(`Invalid element name: "${elementName}"`);
×
210
    }
×
211

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

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

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

5✔
241
function readAttlistExp(xmlData, i) {
×
242
    // Skip leading whitespace after <!ATTLIST
×
243
    i = skipWhitespace(xmlData, i);
×
244

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

×
252
    // Validate element name
×
253
    validateEntityName(elementName)
×
254

×
255
    // Skip whitespace after element name
×
256
    i = skipWhitespace(xmlData, i);
×
257

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

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

×
270
    // Skip whitespace after attribute name
×
271
    i = skipWhitespace(xmlData, i);
×
272

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

×
279
        // Skip whitespace after "NOTATION"
×
280
        i = skipWhitespace(xmlData, i);
×
281

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

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

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

×
303
            allowedNotations.push(notation);
×
304

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

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

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

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

×
333
    // Skip whitespace after attribute type
×
334
    i = skipWhitespace(xmlData, i);
×
335

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

×
348
    return {
×
349
        elementName,
×
350
        attributeName,
×
351
        attributeType,
×
352
        defaultValue,
×
353
        index: i
×
354
    }
×
355
}
×
356

5✔
357
function hasSeq(data, seq,i){
285✔
358
    for(let j=0;j<seq.length;j++){
285✔
359
        if(seq[j]!==data[i+j+1]) return false;
1,160✔
360
    }
1,160✔
361
    return true;
130✔
362
}
285✔
363

5✔
364
function validateEntityName(name){
95✔
365
    if (isName(name))
95✔
366
        return name;
95✔
367
    else
5✔
368
        throw new Error(`Invalid entity name ${name}`);
5✔
369
}
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