Skip to content

Commit 88c47b3

Browse files
author
Kunal Kukreja
committed
Added namespace validation for attributes and fix for self closing tags
1 parent 3d15e7f commit 88c47b3

File tree

2 files changed

+103
-40
lines changed

2 files changed

+103
-40
lines changed

spec/validator_spec.js

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ const fs = require("fs");
44
const path = require("path");
55
const validator = require("../src/validator");
66

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

1111
const keys = Object.keys(error);
@@ -21,11 +21,11 @@ function validate(xmlData, ignoreNameSpace, error, line = 1) {
2121
}
2222

2323
function validateIgnoringNS(xmlData, error, line) {
24-
validate(xmlData, true, error, line);
24+
validate(xmlData, { ignoreNameSpace: true }, error, line);
2525
}
2626

2727
function validateWithNS(xmlData, error, line) {
28-
validate(xmlData, false, error, line);
28+
validate(xmlData, null, error, line);
2929
}
3030

3131
function validateFile(fileName, ...args) {
@@ -94,29 +94,82 @@ describe("XMLParser", function () {
9494
InvalidTag: "Closing tag 'rootnode' can't have attributes or invalid starting."
9595
});
9696
});
97-
98-
it("should validate simple xml string with namespace", function () {
97+
98+
it("should validate tag with namespace", function () {
9999
validateWithNS("<root:Node xmlns:root='urn:none'></root:Node>");
100100
});
101101

102-
it("should not validate simple xml string when namespace is not defined", function () {
102+
it("should validate attribute with namespace", function () {
103+
validateWithNS("<rootNode xmlns:ns='urn:none'><tag ns:attr='value' /></rootNode>");
104+
});
105+
106+
it("should validate self closing tag with namespace", function () {
107+
validateWithNS("<rootNode><ns:tag type='self' xmlns:ns='urn:none'/></rootNode>");
108+
});
109+
110+
it("should validate attributes in self closing tag with namespace", function () {
111+
validateWithNS("<rootNode><ns:tag ns:type='self' xmlns:ns='urn:none'/></rootNode>");
112+
});
113+
114+
it("should not validate other tags with namespace when namespace is defined in self closing tag", function () {
115+
validateWithNS("<rootNode><ns:tag type='self' xmlns:ns='urn:none'/><ns:testTag></ns:testTag></rootNode>", {
116+
InvalidTag: "Namespace prefix 'ns' is not defined for 'ns:testTag'"
117+
});
118+
});
119+
120+
it("should not validate attributes outside self closing tag with namespace definition", function () {
121+
validateWithNS("<rootNode><tag type='self' xmlns:ns='urn:none'/><testTag ns:attr='value'></testTag></rootNode>", {
122+
InvalidAttr: "Namespace prefix 'ns' is not defined for 'ns:attr'"
123+
});
124+
});
125+
126+
it("should not validate tags with namespace when namespace is defined in a sibling tag", function () {
127+
validateWithNS("<rootNode><tag1 type='self' xmlns:ns='urn:none'><ns:child1 /></tag1><tag2><ns:child2 /></tag2></rootNode>", {
128+
InvalidTag: "Namespace prefix 'ns' is not defined for 'ns:child2'"
129+
});
130+
});
131+
132+
it("should not validate tag when namespace is not defined", function () {
103133
validateWithNS("<root:Node></root:Node>", {
104-
InvalidNS: "Namespace prefix 'root' is not defined for tag 'root:Node'"
134+
InvalidTag: "Namespace prefix 'root' is not defined for 'root:Node'"
135+
});
136+
});
137+
138+
it("should not validate attribute when namespace is not defined", function () {
139+
validateWithNS("<rootNode ns:attr='value'></rootNode>", {
140+
InvalidAttr: "Namespace prefix 'ns' is not defined for 'ns:attr'"
105141
});
106142
});
107143

108-
it("should not validate xml when namespace is defined later", function () {
144+
it("should not validate tag when namespace is defined later", function () {
109145
validateWithNS(`<root:Node>
110146
<tag xmlns:root="urn:none">
111147
</tag>
112148
</root:Node>`, {
113-
InvalidNS: "Namespace prefix 'root' is not defined for tag 'root:Node'"
149+
InvalidTag: "Namespace prefix 'root' is not defined for 'root:Node'"
114150
});
115151
});
116152

117-
it("should not validate simple xml string when multiple namespace prefixes are present", function () {
153+
it("should not validate attribute when namespace is defined later", function () {
154+
validateWithNS(`<rootNode>
155+
<tag1 ns:attr="value">
156+
</tag1>
157+
<tag2 xmlns:ns="urn:none">
158+
</tag2>
159+
</rootNode>`, {
160+
InvalidAttr: "Namespace prefix 'ns' is not defined for 'ns:attr'"
161+
}, 2);
162+
});
163+
164+
it("should not validate tag when multiple namespace prefixes are present", function () {
118165
validateWithNS("<root:ns:Node></root:ns:Node>", {
119-
InvalidNS: "Tag 'root:ns:Node' cannot have multiple namespace prefixes"
166+
InvalidTag: "'root:ns:Node' cannot have multiple namespace prefixes"
167+
});
168+
});
169+
170+
it("should not validate attribute when multiple namespace prefixes are present", function () {
171+
validateWithNS("<rootNode ns1:ns2:attr='value'></rootNode>", {
172+
InvalidAttr: "'ns1:ns2:attr' cannot have multiple namespace prefixes"
120173
});
121174
});
122175

src/validator.js

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ exports.validate = function (xmlData, options) {
9494

9595
const nsResult = validateNameSpace(tagName, nameSpaces);
9696

97-
if (!nsResult.isValid) {
98-
return getErrorObject('InvalidNS', nsResult.errorMsg, getLineNumberForPosition(xmlData, i));
97+
if (nsResult !== true) {
98+
return getErrorObject('InvalidTag', nsResult, getLineNumberForPosition(xmlData, i));
9999
}
100100
}
101101

@@ -105,9 +105,16 @@ exports.validate = function (xmlData, options) {
105105
if (attrStr[attrStr.length - 1] === '/') {
106106
//self closing tag
107107
attrStr = attrStr.substring(0, attrStr.length - 1);
108-
const isValid = validateAttributeString(attrStr, options);
108+
const isValid = validateAttributeString(attrStr, nameSpaces, options);
109109
if (isValid === true) {
110110
tagFound = true;
111+
112+
if (!options.ignoreNameSpace && result.nsArray.length > 0) {
113+
//Popping namespaces defined in tag
114+
for (let x=0; x < result.nsArray.length; x++) {
115+
nameSpaces.pop(result.nsArray[x]);
116+
}
117+
}
111118
//continue; //text may presents after self closing tag
112119
} else {
113120
//the result from the nested function returns the position of the error within the attribute
@@ -127,7 +134,7 @@ exports.validate = function (xmlData, options) {
127134
}
128135

129136
if (!options.ignoreNameSpace && otg.nsArray.length > 0) {
130-
//Pushing namespaces defined in tag
137+
//Popping namespaces defined in tag
131138
for (let x=0; x < otg.nsArray.length; x++) {
132139
nameSpaces.pop(otg.nsArray[x]);
133140
}
@@ -139,7 +146,7 @@ exports.validate = function (xmlData, options) {
139146
}
140147
}
141148
} else {
142-
const isValid = validateAttributeString(attrStr, options);
149+
const isValid = validateAttributeString(attrStr, nameSpaces, options);
143150
if (isValid !== true) {
144151
//the result from the nested function returns the position of the error within the attribute
145152
//in order to get the 'true' error line, we need to calculate the position where the attribute begins (i - attrStr.length) and then add the position within the attribute
@@ -334,7 +341,7 @@ const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s
334341

335342
//attr, ="sd", a="amit's", a="sd"b="saf", ab cd=""
336343

337-
function validateAttributeString(attrStr, options) {
344+
function validateAttributeString(attrStr, nsArray, options) {
338345
//console.log("start:"+attrStr+":end");
339346

340347
//if(attrStr.trim().length === 0) return true; //empty string
@@ -357,6 +364,18 @@ function validateAttributeString(attrStr, options) {
357364
if (!validateAttrName(attrName)) {
358365
return getErrorObject('InvalidAttr', "Attribute '"+attrName+"' is an invalid name.", getPositionFromMatch(attrStr, matches[i][0]));
359366
}
367+
368+
if (!options.ignoreNameSpace) {
369+
const nsDefMatches = util.getAllMatches(matches[i][0], nameSpaceDefinitionRegex);
370+
//Skipping namespace definition attribute
371+
if (!nsDefMatches || nsDefMatches.length === 0 || attrName !== nsDefMatches[0][1]) {
372+
const nsResult = validateNameSpace(attrName, nsArray);
373+
if (nsResult !== true) {
374+
return getErrorObject('InvalidAttr', nsResult, getPositionFromMatch(attrStr, matches[i][0]));
375+
}
376+
}
377+
}
378+
360379
if (!attrNames.hasOwnProperty(attrName)) {
361380
//check for duplicate attribute.
362381
attrNames[attrName] = 1;
@@ -423,38 +442,29 @@ function validateTagName(tagname) {
423442
return util.isName(tagname) /* && !tagname.match(startsWithXML) */;
424443
}
425444

426-
function validateNameSpace(tagName, nsArray) {
427-
let tagSplit = tagName.split(":");
428-
let isValid, errorMsg;
429-
switch (tagSplit.length){
445+
const nameSpaceDefinitionRegex = new RegExp(/(xmlns:([^:=]+))=/, 'g');
446+
447+
function validateNameSpace(elemName, nsArray) {
448+
let elemSplit = elemName.split(":");
449+
switch (elemSplit.length){
430450
case 1:
431-
isValid = true;
432-
break;
451+
return true;
433452
case 2:
434-
if (nsArray.indexOf(tagSplit[0]) > -1) {
435-
isValid = true;
453+
if (nsArray.indexOf(elemSplit[0]) > -1) {
454+
return true;
436455
} else {
437-
isValid = false;
438-
errorMsg = "Namespace prefix '" + tagSplit[0] + "' is not defined for tag '" + tagName + "'";
456+
return "Namespace prefix '" + elemSplit[0] + "' is not defined for '" + elemName + "'";
439457
}
440-
break;
441458
default:
442-
isValid = false;
443-
errorMsg = "Tag '" + tagName + "' cannot have multiple namespace prefixes";
444-
}
445-
return {
446-
isValid: isValid,
447-
errorMsg: errorMsg
459+
return "'" + elemName + "' cannot have multiple namespace prefixes";
448460
}
449461
}
450462

451463
function getNameSpaceDefinitions(attributeString) {
452-
const regexNs = /xmlns:([^=]+)=/g;
453464
let nsArray = [];
454-
let matches = regexNs.exec(attributeString);
455-
while (matches){
456-
nsArray.push(matches[1]);
457-
matches = regexNs.exec(attributeString);
465+
let matches = util.getAllMatches(attributeString, nameSpaceDefinitionRegex);
466+
for (let i=0; i< matches.length; i++){
467+
nsArray.push(matches[i][2]);
458468
}
459469
return nsArray;
460470
}

0 commit comments

Comments
 (0)