Skip to content

Commit 1651078

Browse files
author
Kunal Kukreja
committed
Added namespace validation (default enabled)
1 parent c665954 commit 1651078

File tree

3 files changed

+89
-14
lines changed

3 files changed

+89
-14
lines changed

spec/data_spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ describe("XMLParser", function() {
115115
ignoreAttributes: false,
116116
//parseAttributeValue: true,
117117
allowBooleanAttributes: true
118-
}, true);
118+
}, { ignoreNameSpace: true });
119119

120120
//console.log(JSON.stringify(result,null,4));
121121
expect(result).toEqual(expected);
@@ -138,7 +138,7 @@ describe("XMLParser", function() {
138138
ignoreAttributes: false,
139139
//parseAttributeValue: true,
140140
allowBooleanAttributes: true
141-
}, true);
141+
}, { ignoreNameSpace: true });
142142

143143
//console.log(JSON.stringify(result,null,4));
144144
expect(result).toEqual(expected);

spec/validator_spec.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const path = require("path");
55
const validator = require("../src/validator");
66

77
function validate(xmlData, error, line = 1) {
8-
const result = validator.validate(xmlData);
8+
const result = validator.validate(xmlData, { ignoreNameSpace: true });
99
if (error) {
1010

1111
const keys = Object.keys(error);
@@ -39,7 +39,7 @@ describe("XMLParser", function () {
3939

4040
it("should not validate incomplete xml string", function () {
4141
validate("<rootNode>", {
42-
InvalidXml: "Invalid '[ \"rootNode\"]' found."
42+
InvalidXml: "Invalid '[ { \"name\": \"rootNode\" }]' found."
4343
});
4444
});
4545

@@ -109,7 +109,7 @@ describe("XMLParser", function () {
109109

110110
it("should not validate simple xml string with value but no closing tag", function () {
111111
validate("<root:Node>some value", {
112-
InvalidXml: "Invalid '[ \"root:Node\"]' found."
112+
InvalidXml: "Invalid '[ { \"name\": \"root:Node\" }]' found."
113113
});
114114
});
115115

@@ -154,13 +154,13 @@ describe("XMLParser", function () {
154154

155155
it("should not validate xml with non closing comment", function () {
156156
validate("<rootNode ><!-- <tag> -- <tag>1</tag><tag>val</tag></rootNode>", {
157-
InvalidXml: "Invalid '[ \"rootNode\"]' found."
157+
InvalidXml: "Invalid '[ { \"name\": \"rootNode\" }]' found."
158158
});
159159
});
160160

161161
it("should not validate xml with unclosed tag", function () {
162162
validate("<rootNode abc='123' bc='567'", {
163-
InvalidXml: "Invalid '[ \"rootNode\"]' found."
163+
InvalidXml: "Invalid '[ { \"name\": \"rootNode\" }]' found."
164164
});
165165
});
166166

src/validator.js

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ const util = require('./util');
44

55
const defaultOptions = {
66
allowBooleanAttributes: false, //A tag can have attributes without any value
7+
ignoreNameSpace: false //Ignore namespace verification
78
};
89

9-
const props = ['allowBooleanAttributes'];
10+
const props = ['allowBooleanAttributes', 'ignoreNameSpace'];
1011

1112
//const tagsPattern = new RegExp("<\\/?([\\w:\\-_\.]+)\\s*\/?>","g");
1213
exports.validate = function (xmlData, options) {
@@ -16,6 +17,7 @@ exports.validate = function (xmlData, options) {
1617
//xmlData = xmlData.replace(/(^\s*<\?xml.*?\?>)/g,"");//Remove XML starting tag
1718
//xmlData = xmlData.replace(/(<!DOCTYPE[\s\w\"\.\/\-\:]+(\[.*\])*\s*>)/g,"");//Remove DOCTYPE
1819
const tags = [];
20+
let nameSpaces = [];
1921
let tagFound = false;
2022

2123
//indicates that the root tag has been closed (aka. depth 0 has been reached)
@@ -77,10 +79,26 @@ exports.validate = function (xmlData, options) {
7779
return getErrorObject('InvalidTag', msg, getLineNumberForPosition(xmlData, i));
7880
}
7981

80-
const result = readAttributeStr(xmlData, i);
82+
const result = readAttributeStr(xmlData, i, options);
8183
if (result === false) {
8284
return getErrorObject('InvalidAttr', "Attributes for '"+tagName+"' have open quote.", getLineNumberForPosition(xmlData, i));
8385
}
86+
87+
if (!options.ignoreNameSpace) {
88+
if (result.nsArray.length > 0) {
89+
//Pushing namespaces defined in tag
90+
for (let x=0; x < result.nsArray.length; x++) {
91+
nameSpaces.push(result.nsArray[x]);
92+
}
93+
}
94+
95+
const nsResult = validateNameSpace(tagName, nameSpaces);
96+
97+
if (!nsResult.isValid) {
98+
return getErrorObject('InvalidNS', nsResult.errorMsg, getLineNumberForPosition(xmlData, i));
99+
}
100+
}
101+
84102
let attrStr = result.value;
85103
i = result.index;
86104

@@ -104,8 +122,15 @@ exports.validate = function (xmlData, options) {
104122
return getErrorObject('InvalidTag', "Closing tag '"+tagName+"' can't have attributes or invalid starting.", getLineNumberForPosition(xmlData, i));
105123
} else {
106124
const otg = tags.pop();
107-
if (tagName !== otg) {
108-
return getErrorObject('InvalidTag', "Closing tag '"+otg+"' is expected inplace of '"+tagName+"'.", getLineNumberForPosition(xmlData, i));
125+
if (tagName !== otg.name) {
126+
return getErrorObject('InvalidTag', "Closing tag '"+otg.name+"' is expected inplace of '"+tagName+"'.", getLineNumberForPosition(xmlData, i));
127+
}
128+
129+
if (!options.ignoreNameSpace && otg.nsArray.length > 0) {
130+
//Pushing namespaces defined in tag
131+
for (let x=0; x < otg.nsArray.length; x++) {
132+
nameSpaces.pop(otg.nsArray[x]);
133+
}
109134
}
110135

111136
//when there are no more tags, we reached the root level.
@@ -126,7 +151,15 @@ exports.validate = function (xmlData, options) {
126151
if (reachedRoot === true) {
127152
return getErrorObject('InvalidXml', 'Multiple possible root nodes found.', getLineNumberForPosition(xmlData, i));
128153
} else {
129-
tags.push(tagName);
154+
let tagObject = {
155+
name: tagName
156+
};
157+
158+
if (!options.ignoreNameSpace) {
159+
tagObject["nsArray"] = result.nsArray;
160+
}
161+
162+
tags.push(tagObject);
130163
}
131164
tagFound = true;
132165
}
@@ -255,7 +288,7 @@ var singleQuote = "'";
255288
* @param {string} xmlData
256289
* @param {number} i
257290
*/
258-
function readAttributeStr(xmlData, i) {
291+
function readAttributeStr(xmlData, i, options) {
259292
let attrStr = '';
260293
let startChar = '';
261294
let tagClosed = false;
@@ -281,11 +314,17 @@ function readAttributeStr(xmlData, i) {
281314
return false;
282315
}
283316

284-
return {
317+
let result = {
285318
value: attrStr,
286319
index: i,
287320
tagClosed: tagClosed
288321
};
322+
323+
if (!options.ignoreNameSpace) {
324+
result["nsArray"] = getNameSpaceDefinitions(attrStr);
325+
}
326+
327+
return result;
289328
}
290329

291330
/**
@@ -384,6 +423,42 @@ function validateTagName(tagname) {
384423
return util.isName(tagname) /* && !tagname.match(startsWithXML) */;
385424
}
386425

426+
function validateNameSpace(tagName, nsArray) {
427+
let tagSplit = tagName.split(":");
428+
let isValid, errorMsg;
429+
switch (tagSplit.length){
430+
case 1:
431+
isValid = true;
432+
break;
433+
case 2:
434+
if (nsArray.indexOf(tagSplit[0]) > -1) {
435+
isValid = true;
436+
} else {
437+
isValid = false;
438+
errorMsg = "Namespace prefix '" + tagSplit[0] + "' is not defined for tag '" + tagName + "'";
439+
}
440+
break;
441+
default:
442+
isValid = false;
443+
errorMsg = "Tag '" + tagName + "' cannot have multiple namespace prefixes";
444+
}
445+
return {
446+
isValid: isValid,
447+
errorMsg: errorMsg
448+
}
449+
}
450+
451+
function getNameSpaceDefinitions(attributeString) {
452+
const regexNs = /xmlns:([^=]+)=/g;
453+
let nsArray = [];
454+
let matches = regexNs.exec(attributeString);
455+
while (matches){
456+
nsArray.push(matches[1]);
457+
matches = regexNs.exec(attributeString);
458+
}
459+
return nsArray;
460+
}
461+
387462
//this function returns the line number for the character at the given index
388463
function getLineNumberForPosition(xmlData, index) {
389464
var lines = xmlData.substring(0, index).split(/\r?\n/);

0 commit comments

Comments
 (0)