Skip to content

Commit

Permalink
Added namespace validation for attributes and fix for self closing tags
Browse files Browse the repository at this point in the history
  • Loading branch information
Kunal Kukreja committed Jul 5, 2020
1 parent 3d15e7f commit 88c47b3
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 40 deletions.
77 changes: 65 additions & 12 deletions spec/validator_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ const fs = require("fs");
const path = require("path");
const validator = require("../src/validator");

function validate(xmlData, ignoreNameSpace, error, line = 1) {
const result = validator.validate(xmlData, { ignoreNameSpace: ignoreNameSpace });
function validate(xmlData, options, error, line = 1) {
const result = validator.validate(xmlData, options);
if (error) {

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

function validateIgnoringNS(xmlData, error, line) {
validate(xmlData, true, error, line);
validate(xmlData, { ignoreNameSpace: true }, error, line);
}

function validateWithNS(xmlData, error, line) {
validate(xmlData, false, error, line);
validate(xmlData, null, error, line);
}

function validateFile(fileName, ...args) {
Expand Down Expand Up @@ -94,29 +94,82 @@ describe("XMLParser", function () {
InvalidTag: "Closing tag 'rootnode' can't have attributes or invalid starting."
});
});

it("should validate simple xml string with namespace", function () {
it("should validate tag with namespace", function () {
validateWithNS("<root:Node xmlns:root='urn:none'></root:Node>");
});

it("should not validate simple xml string when namespace is not defined", function () {
it("should validate attribute with namespace", function () {
validateWithNS("<rootNode xmlns:ns='urn:none'><tag ns:attr='value' /></rootNode>");
});

it("should validate self closing tag with namespace", function () {
validateWithNS("<rootNode><ns:tag type='self' xmlns:ns='urn:none'/></rootNode>");
});

it("should validate attributes in self closing tag with namespace", function () {
validateWithNS("<rootNode><ns:tag ns:type='self' xmlns:ns='urn:none'/></rootNode>");
});

it("should not validate other tags with namespace when namespace is defined in self closing tag", function () {
validateWithNS("<rootNode><ns:tag type='self' xmlns:ns='urn:none'/><ns:testTag></ns:testTag></rootNode>", {
InvalidTag: "Namespace prefix 'ns' is not defined for 'ns:testTag'"
});
});

it("should not validate attributes outside self closing tag with namespace definition", function () {
validateWithNS("<rootNode><tag type='self' xmlns:ns='urn:none'/><testTag ns:attr='value'></testTag></rootNode>", {
InvalidAttr: "Namespace prefix 'ns' is not defined for 'ns:attr'"
});
});

it("should not validate tags with namespace when namespace is defined in a sibling tag", function () {
validateWithNS("<rootNode><tag1 type='self' xmlns:ns='urn:none'><ns:child1 /></tag1><tag2><ns:child2 /></tag2></rootNode>", {
InvalidTag: "Namespace prefix 'ns' is not defined for 'ns:child2'"
});
});

it("should not validate tag when namespace is not defined", function () {
validateWithNS("<root:Node></root:Node>", {
InvalidNS: "Namespace prefix 'root' is not defined for tag 'root:Node'"
InvalidTag: "Namespace prefix 'root' is not defined for 'root:Node'"
});
});

it("should not validate attribute when namespace is not defined", function () {
validateWithNS("<rootNode ns:attr='value'></rootNode>", {
InvalidAttr: "Namespace prefix 'ns' is not defined for 'ns:attr'"
});
});

it("should not validate xml when namespace is defined later", function () {
it("should not validate tag when namespace is defined later", function () {
validateWithNS(`<root:Node>
<tag xmlns:root="urn:none">
</tag>
</root:Node>`, {
InvalidNS: "Namespace prefix 'root' is not defined for tag 'root:Node'"
InvalidTag: "Namespace prefix 'root' is not defined for 'root:Node'"
});
});

it("should not validate simple xml string when multiple namespace prefixes are present", function () {
it("should not validate attribute when namespace is defined later", function () {
validateWithNS(`<rootNode>
<tag1 ns:attr="value">
</tag1>
<tag2 xmlns:ns="urn:none">
</tag2>
</rootNode>`, {
InvalidAttr: "Namespace prefix 'ns' is not defined for 'ns:attr'"
}, 2);
});

it("should not validate tag when multiple namespace prefixes are present", function () {
validateWithNS("<root:ns:Node></root:ns:Node>", {
InvalidNS: "Tag 'root:ns:Node' cannot have multiple namespace prefixes"
InvalidTag: "'root:ns:Node' cannot have multiple namespace prefixes"
});
});

it("should not validate attribute when multiple namespace prefixes are present", function () {
validateWithNS("<rootNode ns1:ns2:attr='value'></rootNode>", {
InvalidAttr: "'ns1:ns2:attr' cannot have multiple namespace prefixes"
});
});

Expand Down
66 changes: 38 additions & 28 deletions src/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ exports.validate = function (xmlData, options) {

const nsResult = validateNameSpace(tagName, nameSpaces);

if (!nsResult.isValid) {
return getErrorObject('InvalidNS', nsResult.errorMsg, getLineNumberForPosition(xmlData, i));
if (nsResult !== true) {
return getErrorObject('InvalidTag', nsResult, getLineNumberForPosition(xmlData, i));
}
}

Expand All @@ -105,9 +105,16 @@ exports.validate = function (xmlData, options) {
if (attrStr[attrStr.length - 1] === '/') {
//self closing tag
attrStr = attrStr.substring(0, attrStr.length - 1);
const isValid = validateAttributeString(attrStr, options);
const isValid = validateAttributeString(attrStr, nameSpaces, options);
if (isValid === true) {
tagFound = true;

if (!options.ignoreNameSpace && result.nsArray.length > 0) {
//Popping namespaces defined in tag
for (let x=0; x < result.nsArray.length; x++) {
nameSpaces.pop(result.nsArray[x]);
}
}
//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 All @@ -127,7 +134,7 @@ exports.validate = function (xmlData, options) {
}

if (!options.ignoreNameSpace && otg.nsArray.length > 0) {
//Pushing namespaces defined in tag
//Popping namespaces defined in tag
for (let x=0; x < otg.nsArray.length; x++) {
nameSpaces.pop(otg.nsArray[x]);
}
Expand All @@ -139,7 +146,7 @@ exports.validate = function (xmlData, options) {
}
}
} else {
const isValid = validateAttributeString(attrStr, options);
const isValid = validateAttributeString(attrStr, nameSpaces, options);
if (isValid !== true) {
//the result from the nested function returns the position of the error within the attribute
//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
Expand Down Expand Up @@ -334,7 +341,7 @@ const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s

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

function validateAttributeString(attrStr, options) {
function validateAttributeString(attrStr, nsArray, options) {
//console.log("start:"+attrStr+":end");

//if(attrStr.trim().length === 0) return true; //empty string
Expand All @@ -357,6 +364,18 @@ function validateAttributeString(attrStr, options) {
if (!validateAttrName(attrName)) {
return getErrorObject('InvalidAttr', "Attribute '"+attrName+"' is an invalid name.", getPositionFromMatch(attrStr, matches[i][0]));
}

if (!options.ignoreNameSpace) {
const nsDefMatches = util.getAllMatches(matches[i][0], nameSpaceDefinitionRegex);
//Skipping namespace definition attribute
if (!nsDefMatches || nsDefMatches.length === 0 || attrName !== nsDefMatches[0][1]) {
const nsResult = validateNameSpace(attrName, nsArray);
if (nsResult !== true) {
return getErrorObject('InvalidAttr', nsResult, getPositionFromMatch(attrStr, matches[i][0]));
}
}
}

if (!attrNames.hasOwnProperty(attrName)) {
//check for duplicate attribute.
attrNames[attrName] = 1;
Expand Down Expand Up @@ -423,38 +442,29 @@ function validateTagName(tagname) {
return util.isName(tagname) /* && !tagname.match(startsWithXML) */;
}

function validateNameSpace(tagName, nsArray) {
let tagSplit = tagName.split(":");
let isValid, errorMsg;
switch (tagSplit.length){
const nameSpaceDefinitionRegex = new RegExp(/(xmlns:([^:=]+))=/, 'g');

function validateNameSpace(elemName, nsArray) {
let elemSplit = elemName.split(":");
switch (elemSplit.length){
case 1:
isValid = true;
break;
return true;
case 2:
if (nsArray.indexOf(tagSplit[0]) > -1) {
isValid = true;
if (nsArray.indexOf(elemSplit[0]) > -1) {
return true;
} else {
isValid = false;
errorMsg = "Namespace prefix '" + tagSplit[0] + "' is not defined for tag '" + tagName + "'";
return "Namespace prefix '" + elemSplit[0] + "' is not defined for '" + elemName + "'";
}
break;
default:
isValid = false;
errorMsg = "Tag '" + tagName + "' cannot have multiple namespace prefixes";
}
return {
isValid: isValid,
errorMsg: errorMsg
return "'" + elemName + "' cannot have multiple namespace prefixes";
}
}

function getNameSpaceDefinitions(attributeString) {
const regexNs = /xmlns:([^=]+)=/g;
let nsArray = [];
let matches = regexNs.exec(attributeString);
while (matches){
nsArray.push(matches[1]);
matches = regexNs.exec(attributeString);
let matches = util.getAllMatches(attributeString, nameSpaceDefinitionRegex);
for (let i=0; i< matches.length; i++){
nsArray.push(matches[i][2]);
}
return nsArray;
}
Expand Down

0 comments on commit 88c47b3

Please sign in to comment.