Skip to content

Commit 5bcf183

Browse files
committed
fix (#747): support EMPTY and ANY with ELEMENT in DOCTYPE
1 parent 619b504 commit 5bcf183

File tree

10 files changed

+44
-79
lines changed

10 files changed

+44
-79
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<small>Note: If you find missing information about particular minor version, that version must have been changed without any functional change in this library.</small>
22

3+
**5.2.3 / 2025-05-11**
4+
- fix (#747): support EMPTY and ANY with ELEMENT in DOCTYPE
5+
36
**5.2.2 / 2025-05-05**
47
- fix (#746): update strnum to fix parsing issues related to enotations
58

lib/fxp.cjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

lib/fxp.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/fxp.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/fxparser.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/fxparser.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fast-xml-parser",
3-
"version": "5.2.2",
3+
"version": "5.2.3",
44
"description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
55
"main": "./lib/fxp.cjs",
66
"type": "module",

spec/entities_spec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ describe("XMLParser Entities", function() {
111111
" the open square bracket indicates an internal DTD-->" +
112112
"<!DOCTYPE foo [" +
113113
"<!--define the internal DTD-->" +
114-
"<!ELEMENT foo (#PCDATA)>" +
114+
"<!ELEMENT foo EMPTY>" +
115+
"<!ELEMENT foo ANY>" +
115116
"<!--close the DOCTYPE declaration-->" +
116117
"]>" +
117118
"<foo>Hello World.</foo>";

src/xmlparser/DocTypeReader.js

Lines changed: 31 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default function readDocType(xmlData, i){
1717
let exp = "";
1818
for(;i<xmlData.length;i++){
1919
if (xmlData[i] === '<' && !comment) { //Determine the tag type
20-
if( hasBody && isEntity(xmlData, i)){
20+
if( hasBody && hasSeq(xmlData, "!ENTITY",i)){
2121
i += 7;
2222
let entityName, val;
2323
[entityName, val,i] = readEntityExp(xmlData,i+1);
@@ -27,19 +27,19 @@ export default function readDocType(xmlData, i){
2727
val: val
2828
};
2929
}
30-
else if( hasBody && isElement(xmlData, i)) {
30+
else if( hasBody && hasSeq(xmlData, "!ELEMENT",i)) {
3131
i += 8;//Not supported
3232
const {index} = readElementExp(xmlData,i+1);
3333
i = index;
34-
}else if( hasBody && isAttlist(xmlData, i)){
34+
}else if( hasBody && hasSeq(xmlData, "!ATTLIST",i)){
3535
i += 8;//Not supported
3636
// const {index} = readAttlistExp(xmlData,i+1);
3737
// i = index;
38-
}else if( hasBody && isNotation(xmlData, i)) {
38+
}else if( hasBody && hasSeq(xmlData, "!NOTATION",i)) {
3939
i += 9;//Not supported
4040
const {index} = readNotationExp(xmlData,i+1);
4141
i = index;
42-
}else if( isComment) comment = true;
42+
}else if( hasSeq(xmlData, "!--",i) ) comment = true;
4343
else throw new Error("Invalid DOCTYPE");
4444

4545
angleBracketsCount++;
@@ -188,8 +188,12 @@ function readIdentifierVal(xmlData, i, type) {
188188
}
189189

190190
function readElementExp(xmlData, i) {
191+
// <!ELEMENT br EMPTY>
192+
// <!ELEMENT div ANY>
193+
// <!ELEMENT title (#PCDATA)>
194+
// <!ELEMENT book (title, author+)>
191195
// <!ELEMENT name (content-model)>
192-
196+
193197
// Skip leading whitespace after <!ELEMENT
194198
i = skipWhitespace(xmlData, i);
195199

@@ -207,24 +211,26 @@ function readElementExp(xmlData, i) {
207211

208212
// Skip whitespace after element name
209213
i = skipWhitespace(xmlData, i);
210-
214+
let contentModel = "";
211215
// Expect '(' to start content model
212-
if (xmlData[i] !== "(") {
213-
throw new Error(`Expected '(', found "${xmlData[i]}"`);
214-
}
215-
i++; // Move past '('
216+
if(xmlData[i] === "E" && hasSeq(xmlData, "MPTY",i)) i+=6;
217+
else if(xmlData[i] === "A" && hasSeq(xmlData, "NY",i)) i+=4;
218+
else if (xmlData[i] === "(") {
219+
i++; // Move past '('
216220

217-
// Read content model
218-
let contentModel = "";
219-
while (i < xmlData.length && xmlData[i] !== ")") {
220-
contentModel += xmlData[i];
221-
i++;
222-
}
221+
// Read content model
222+
while (i < xmlData.length && xmlData[i] !== ")") {
223+
contentModel += xmlData[i];
224+
i++;
225+
}
226+
if (xmlData[i] !== ")") {
227+
throw new Error("Unterminated content model");
228+
}
223229

224-
if (xmlData[i] !== ")") {
225-
throw new Error("Unterminated content model");
230+
}else{
231+
throw new Error(`Invalid Element Expression, found "${xmlData[i]}"`);
226232
}
227-
233+
228234
return {
229235
elementName,
230236
contentModel: contentModel.trim(),
@@ -348,56 +354,11 @@ function readAttlistExp(xmlData, i) {
348354
}
349355
}
350356

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){
358-
if(xmlData[i+1] === '!' &&
359-
xmlData[i+2] === 'E' &&
360-
xmlData[i+3] === 'N' &&
361-
xmlData[i+4] === 'T' &&
362-
xmlData[i+5] === 'I' &&
363-
xmlData[i+6] === 'T' &&
364-
xmlData[i+7] === 'Y') return true
365-
return false
366-
}
367-
function isElement(xmlData, i){
368-
if(xmlData[i+1] === '!' &&
369-
xmlData[i+2] === 'E' &&
370-
xmlData[i+3] === 'L' &&
371-
xmlData[i+4] === 'E' &&
372-
xmlData[i+5] === 'M' &&
373-
xmlData[i+6] === 'E' &&
374-
xmlData[i+7] === 'N' &&
375-
xmlData[i+8] === 'T') return true
376-
return false
377-
}
378-
379-
function isAttlist(xmlData, i){
380-
if(xmlData[i+1] === '!' &&
381-
xmlData[i+2] === 'A' &&
382-
xmlData[i+3] === 'T' &&
383-
xmlData[i+4] === 'T' &&
384-
xmlData[i+5] === 'L' &&
385-
xmlData[i+6] === 'I' &&
386-
xmlData[i+7] === 'S' &&
387-
xmlData[i+8] === 'T') return true
388-
return false
389-
}
390-
function isNotation(xmlData, i){
391-
if(xmlData[i+1] === '!' &&
392-
xmlData[i+2] === 'N' &&
393-
xmlData[i+3] === 'O' &&
394-
xmlData[i+4] === 'T' &&
395-
xmlData[i+5] === 'A' &&
396-
xmlData[i+6] === 'T' &&
397-
xmlData[i+7] === 'I' &&
398-
xmlData[i+8] === 'O' &&
399-
xmlData[i+9] === 'N') return true
400-
return false
357+
function hasSeq(data, seq,i){
358+
for(let j=0;j<seq.length;j++){
359+
if(seq[j]!==data[i+j+1]) return false;
360+
}
361+
return true;
401362
}
402363

403364
function validateEntityName(name){

0 commit comments

Comments
 (0)