Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion spec/validator_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import fs from "fs";
import path from "path";
import {XMLValidator} from "../src/fxp.js";
import {XMLParser, XMLValidator} from "../src/fxp.js";
import { fileURLToPath } from 'url';
import { dirname } from 'path';

Expand Down Expand Up @@ -405,6 +405,31 @@ describe("should not validate XML documents with multiple root nodes", () => {
});
});

describe("should validate only allowed content after the root element", () => {
const invalidContent = ['&amp;', '&#65;', '<![CDATA[x]]>', '<!DOCTYPE x>'];
const roots = ['<xml></xml>', '<xml/>'];

it('rejects references, CDATA and DOCTYPE in the epilog', () => {
for (const root of roots) for (const content of invalidContent) {
expect(XMLValidator.validate(`${root}${content}`)).not.toBe(true);
}
expect(XMLValidator.validate('<xml/><xml/>')).not.toBe(true);
});

it('rejects the same content when parsing with validation enabled', () => {
const parser = new XMLParser();
for (const root of roots) for (const content of invalidContent) {
expect(() => parser.parse(`${root}${content}`, true)).toThrow();
}
});

it('allows whitespace, comments and processing instructions in the epilog', () => {
for (const root of roots) {
validate(`${root} \n<!--comment--><?pi?>`);
}
});
});

describe("should report correct line numbers for unclosed tags", () => {

it('- child tag', () =>
Expand Down
12 changes: 12 additions & 0 deletions src/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,18 @@ export function validate(xmlData, options) {
i = result.index;

if (attrStr[attrStr.length - 1] === '/') {
if (reachedRoot === true) {
return getErrorObject('InvalidXml', 'Multiple possible root nodes found.', getLineNumberForPosition(xmlData, i));
}
//self closing tag
const attrStrStart = i - attrStr.length;
attrStr = attrStr.substring(0, attrStr.length - 1);
const isValid = validateAttributeString(attrStr, options);
if (isValid === true) {
tagFound = true;
if (tags.length === 0) {
reachedRoot = true;
}
//continue; //text may presents after self closing tag
} else {
//the result from the nested function returns the position of the error within the attribute
Expand Down Expand Up @@ -144,6 +150,9 @@ export function validate(xmlData, options) {
for (i++; i < xmlData.length; i++) {
if (xmlData[i] === '<') {
if (xmlData[i + 1] === '!') {
if (reachedRoot === true && !xmlData.startsWith('<!--', i)) {
return getErrorObject('InvalidXml', "Extra text at the end", getLineNumberForPosition(xmlData, i));
}
//comment or CADATA
i++;
i = readCommentAndCDATA(xmlData, i);
Expand All @@ -155,6 +164,9 @@ export function validate(xmlData, options) {
break;
}
} else if (xmlData[i] === '&') {
if (reachedRoot === true) {
return getErrorObject('InvalidXml', "Extra text at the end", getLineNumberForPosition(xmlData, i));
}
const afterAmp = validateAmpersand(xmlData, i);
if (afterAmp == -1)
return getErrorObject('InvalidChar', "char '&' is not expected.", getLineNumberForPosition(xmlData, i));
Expand Down