Skip to content

Commit

Permalink
1. Added support for validating duplicate attributes with namespace
Browse files Browse the repository at this point in the history
2. Added check for empty namespace URI
  • Loading branch information
Kunal Kukreja committed Jul 11, 2020
1 parent d5e4ea0 commit ca8ffd5
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 12 deletions.
38 changes: 37 additions & 1 deletion spec/validator_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ describe("XMLParser", function () {
validateWithNS("<rootNode xmlns:ns='urn:none'><tag ns:attr='value' /></rootNode>");
});

it("should not validate namespace attribute with empty URI", function () {
validateWithNS("<root:Node xmlns:root=''></root:Node>", {
InvalidAttr: "Invalid URI for namespace root"
});
});

it("should validate all namespaces defined in a tag", function () {
validateWithNS(`<rootNode xmlns:ns1='urn:none' xmlns:ns2='urn:none'>
<ns1:tag>
<ns2:child></ns2:child>
</ns1:tag>
</rootNode>`);
});

it("should validate self closing tag with namespace", function () {
validateWithNS("<rootNode><ns:tag type='self' xmlns:ns='urn:none'/></rootNode>");
});
Expand Down Expand Up @@ -168,11 +182,27 @@ describe("XMLParser", function () {
});

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

it("should not validate attributes with same name and same namespace prefix", function () {
validateWithNS("<rootNode xmlns:ns1='urn:none' xmlns:ns2='a'><tag ns1:attr='value' ns1:attr='value2' /></rootNode>", {
InvalidAttr: "Attribute 'attr' in namespace 'urn:none' is repeated."
});
});

it("should not validate attributes with same name and same namespace", function () {
validateWithNS("<rootNode xmlns:ns1='urn:none' xmlns:ns2='urn:none'><tag ns1:attr='value' ns2:attr='value2' /></rootNode>",{
InvalidAttr: "Attribute 'attr' in namespace 'urn:none' is repeated."
});
});

it("should validate attributes with same name and different namespace", function () {
validateWithNS("<rootNode xmlns:ns1='urn:none' xmlns:ns2='a'><tag ns1:attr='value' ns2:attr='value2' /></rootNode>");
});

it("should not validate xml string with namespace when closing tag is diffrent", function () {
validateIgnoringNS("<root:Node></root:node>", {
InvalidTag: "Closing tag 'root:Node' is expected inplace of 'root:node'."
Expand Down Expand Up @@ -374,6 +404,12 @@ describe("XMLParser", function () {
});
});

it("should not validate attributes with same name and different namespace prefix, if namespace is ignored", function () {
validateIgnoringNS("<rootNode xmlns:ns1='urn:none' xmlns:ns2=''><tag ns1:attr='value' ns2:attr='value2'></rootNode>", {
InvalidAttr: "Attribute 'attr' is repeated."
});
});

it('should validate xml with a tag attribute splitted on more lines', () => {
validateIgnoringNS(`
<name
Expand Down
50 changes: 39 additions & 11 deletions src/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ exports.validate = function (xmlData, options) {
}

if (!options.ignoreNameSpace) {
if (result.nsError) {
return getErrorObject('InvalidAttr', result.nsError, getLineNumberForPosition(xmlData, i));
}

//Pushing namespaces defined in tag
Array.prototype.push.apply(nameSpaces, result.nsArray);

Expand Down Expand Up @@ -320,7 +324,12 @@ function readAttributeStr(xmlData, i, options) {
};

if (!options.ignoreNameSpace) {
result["nsArray"] = getNameSpaceDefinitions(attrStr);
const nsResult = getNameSpaceDefinitions(attrStr);
if (Array.isArray(nsResult)) {
result["nsArray"] = nsResult;
} else {
result["nsError"] = nsResult;
}
}

return result;
Expand Down Expand Up @@ -352,7 +361,7 @@ function validateAttributeString(attrStr, nsArray, options) {
/* else if(matches[i][6] === undefined){//attribute without value: ab=
return { err: { code:"InvalidAttr",msg:"attribute " + matches[i][2] + " has no value assigned."}};
} */
const attrName = matches[i][2];
let attrName = matches[i][2];
if (!validateAttrName(attrName)) {
return getErrorObject('InvalidAttr', "Attribute '"+attrName+"' is an invalid name.", getPositionFromMatch(attrStr, matches[i][0]));
}
Expand All @@ -366,13 +375,27 @@ function validateAttributeString(attrStr, nsArray, options) {
return getErrorObject('InvalidAttr', nsResult, getPositionFromMatch(attrStr, matches[i][0]));
}
}
const attrSplit = attrName.split(":");
if (attrSplit.length > 1) {
for (let i=0; i < nsArray.length; i++) {
const nsSplit = nsArray[i].split("'");
if (nsSplit[0] === attrSplit[0]) {
attrName = nsSplit[1] + "'" + attrSplit[1];
break;
}
}
}
} else {
attrName = attrName.replace(/[^:]+:/, "");
}

if (!attrNames.hasOwnProperty(attrName)) {
//check for duplicate attribute.
attrNames[attrName] = 1;
//check for duplicate attribute.
attrNames[attrName] = 1;
} else {
return getErrorObject('InvalidAttr', "Attribute '"+attrName+"' is repeated.", getPositionFromMatch(attrStr, matches[i][0]));
const attrSplit = attrName.split("'");
const msg = attrSplit.length === 2 ? "'" + attrSplit[1] + "' in namespace '" + attrSplit[0] + "'": "'" + attrName + "'";
return getErrorObject('InvalidAttr', "Attribute "+ msg +" is repeated.", getPositionFromMatch(attrStr, matches[i][0]));
}
}

Expand Down Expand Up @@ -434,19 +457,20 @@ function validateTagName(tagname) {
return util.isName(tagname) /* && !tagname.match(startsWithXML) */;
}

const nameSpaceDefinitionRegex = new RegExp(/(xmlns:([^:=]+))=/, 'g');
const nameSpaceDefinitionRegex = new RegExp(/(xmlns:([^:=]+))=['"]([^'"]*)['"]/, 'g');

function validateNameSpace(elemName, nsArray) {
let elemSplit = elemName.split(":");
switch (elemSplit.length){
case 1:
return true;
case 2:
if (nsArray.indexOf(elemSplit[0]) > -1) {
return true;
} else {
return "Namespace prefix '" + elemSplit[0] + "' is not defined for '" + elemName + "'";
for (let i=0; i< nsArray.length; i++) {
if (nsArray[i].split("'")[0] === elemSplit[0]){
return true;
}
}
return "Namespace prefix '" + elemSplit[0] + "' is not defined for '" + elemName + "'";
default:
return "'" + elemName + "' cannot have multiple namespace prefixes";
}
Expand All @@ -456,7 +480,11 @@ function getNameSpaceDefinitions(attributeString) {
let nsArray = [];
let matches = util.getAllMatches(attributeString, nameSpaceDefinitionRegex);
for (let i=0; i< matches.length; i++){
nsArray.push(matches[i][2]);
if (matches[i][3]) {
nsArray.push(matches[i][2] + "'" + matches[i][3]);
} else {
return "Invalid URI for namespace " + matches[i][2];
}
}
return nsArray;
}
Expand Down

0 comments on commit ca8ffd5

Please sign in to comment.