@@ -4,9 +4,10 @@ const util = require('./util');
44
55const 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");
1213exports . 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 = / x m l n s : ( [ ^ = ] + ) = / 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
388463function getLineNumberForPosition ( xmlData , index ) {
389464 var lines = xmlData . substring ( 0 , index ) . split ( / \r ? \n / ) ;
0 commit comments