├── .gitignore ├── spec ├── support │ └── jasmine.json ├── mockXML │ └── index.js └── traverse-spec.js ├── bin └── tojson.js ├── lib ├── validator.js ├── cleanXML.js ├── xmlToJsonStream.js └── xmlToJson.js ├── package.json ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | playground.js 3 | test.js 4 | test.xml 5 | .DS_Store 6 | .vscode/ -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": true 11 | } 12 | -------------------------------------------------------------------------------- /bin/tojson.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const xmlToJson = require('../index') 4 | const [,, ...args] = process.argv; 5 | 6 | const attributeMode = args.includes("-no-attr") ? false : true; 7 | 8 | const parser = xmlToJson({attributeMode}) 9 | 10 | const stream = parser.createStream(); 11 | 12 | process.stdin.pipe(stream).pipe(process.stdout) -------------------------------------------------------------------------------- /lib/validator.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function(tag) { 4 | if((tag.charAt(0) === '<' && tag.charAt(1) === '?') && (tag.charAt(tag.length-1) === '>' && tag.charAt(tag.length-2) === '?')) { 5 | return true; 6 | } 7 | 8 | if(tag.charAt(0) === '<' && (tag.charAt(tag.length-2)+tag.charAt(tag.length-1) === '/>' || tag.charAt(tag.length-1) === '>')) { 9 | return true; 10 | } 11 | 12 | return false; 13 | } -------------------------------------------------------------------------------- /lib/cleanXML.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function cleanXML(xml) { 3 | return xml.replace(/>\s*<') //remove white spaces between elements 4 | .replace(/<\?xml.*\?>/g, '') //remove the root element 5 | .replace(//g,'') //remove comments 6 | .replace(/>\s*/g, '>') // remove any white spaces at the end of the xml string if any 7 | .replace(/\s* 2 | Alex 3 | `; 4 | 5 | const TEST2 = `Alex 6 | Jon` 7 | 8 | const TEST3 = ` 9 | Alex 10 | 25 11 | ` 12 | 13 | const TEST4 = ` 14 | ` 15 | 16 | const TEST5 = ` 17 | 18 | Software Dev 19 | 20 | `; 21 | 22 | const TEST6 = ` 23 | 24 | Alex 25 | La Bianca 26 | 27 | 28 | Ash 29 | Thrasher 30 | 31 | 32 | Jon 33 | Andrews 34 | 35 | `; 36 | 37 | const TEST7 = ` 38 | 39 | 40 | 41 | 42 | 43 | ` 44 | const TEST8 = ` 45 | 46 | Alex 47 | US 48 | Troy 49 | 50 | ` 51 | 52 | const TEST9 = `Alex` 53 | 54 | //Invalid XML 55 | const TEST10 = `AlexAlex` 57 | const TEST12 = `Jon` 58 | 59 | //nested repetition 60 | const TEST13 = ``; 61 | const TEST14 = `Alex` 62 | 63 | const TEST15 = ` 64 | 65 | ccc 66 | yyy 67 | 68 | 69 | ddd 70 | yyy 71 | 72 | `; 73 | 74 | const TEST16 = ` 75 | 76 | ccc 77 | bbb1 78 | 79 | 80 | ddd 81 | bbb2 82 | 83 | ` 84 | 85 | const TEST17 = ` 86 | 87 | ccc 88 | bbb1 89 | 90 | 91 | ddd 92 | bbb2 93 | 94 | ` 95 | 96 | 97 | module.exports.MOCK_DATA = { 98 | TEST1: TEST1, 99 | TEST2: TEST2, 100 | TEST3: TEST3, 101 | TEST4: TEST4, 102 | TEST5: TEST5, 103 | TEST6: TEST6, 104 | TEST7: TEST7, 105 | TEST8: TEST8, 106 | TEST9: TEST9, 107 | I_TEST10: TEST10, 108 | I_TEST11: TEST11, 109 | I_TEST12: TEST12, 110 | TEST13: TEST13, 111 | TEST14: TEST14, 112 | TEST15: TEST15, 113 | TEST16: TEST16, 114 | TEST17: TEST17 115 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xml-to-json-stream 2 | 3 | Simple module to convert XML to JSON with javascript 4 | 5 | ## Download 6 | `npm install xml-to-json-stream` or `npm install xml-to-json-stream -g` 7 | Installing the package globally allows you to convert xml file to json files via the command line. 8 | `cat config.xml | tojson > config.json` . If you want to ignore xml attribute simply add the `-no-attr` flag. 9 | 10 | ## Public API 11 | 12 | xml-to-json-stream only has two public methods `xmlToJson` and `createStream` . 13 | Use xmlToJson if you simply want to pass in some XML and access the resulting JSON in the callback function. 14 | Use createStream if you need to pipe some readable XML stream into a writable destination and convert the XML to JSON along the way. 15 | 16 | The module currently accepts `attributeMode` as an option, which defaults to `true ` . `attributeMode` decides whether XML attributes should 17 | be ignored or not. 18 | 19 | ```javascript 20 | const xmlToJson = require('xml-to-json-stream'); 21 | const parser = xmlToJson({attributeMode:false}); 22 | 23 | const xml = ` 24 | 25 | Alex 26 | 27 | ` 28 | 29 | parser.xmlToJson(xml, (err,json)=>{ 30 | if(err) { 31 | //error handling 32 | } 33 | 34 | //json 35 | //{ 36 | // employee: { 37 | // name: "Alex" 38 | // } 39 | //} 40 | }); 41 | 42 | //the 'id' attribute of employee is ignored. If attributeMode was set to true or omitted, the json would have been: 43 | // { 44 | // employee: { 45 | // id: "123456", 46 | // name: "Alex" 47 | // } 48 | // } 49 | 50 | ``` 51 | 52 | ### xmlToJson 53 | 54 | ```javascript 55 | const xmlToJson = require('xml-to-json-stream'); 56 | const parser = xmlToJson({attributeMode:false}); 57 | 58 | parser.xmlToJson(xml, (err,json)=>{ 59 | if(err) { 60 | //error handling 61 | } 62 | 63 | //json is converted xml 64 | }); 65 | ``` 66 | 67 | ### createStream 68 | 69 | `createStream` allows you to create a Node.js Transform stream that can be written to and read from. 70 | Consider a hypothetical weather service that only responds with XML data. `createStream` will remove some of the boilerplate for you by 71 | letting you pipe service responses through the parser/stream. 72 | 73 | ```javascript 74 | const xmlToJson = require('xml-to-json-stream'); 75 | const parser = xmlToJson(); 76 | const stream = parser.createStream(); 77 | 78 | const server = http.createServer((req,res)=>{ 79 | 80 | http.get('http://someHypothecialXMLWeatherService/api', (response)=>{ //since response is a readable stream we can pipe it through our stream 81 | response.pipe(stream).pipe(res); //the client will receive the json representation of the XML response 82 | }) 83 | }) 84 | ``` 85 | 86 | #### Or... 87 | ...If you have an XML file you want to convert on the fly via the command line `$ cat file.xml | node app` 88 | 89 | ```javascript 90 | const xmlToJson = require('xml-to-json-stream'); 91 | const fs = require('fs'); 92 | 93 | const parser = xmlToJson(); 94 | const stream = parser.createStream(); 95 | const jsonFile = fs.createWriteStream('jsonFile.json') 96 | 97 | process.stdin.pipe(stream).pipe(jsonFile); 98 | ``` 99 | 100 | 101 | #### Additional Considerations 102 | 103 | * Comments are removed 104 | * Root elements are removed 105 | * If an element occurs multiple times in the same depth then that element will be represented as an array in the JSON 106 | ``` 107 | 108 | 109 | 110 | 111 | 112 | 113 | will turn into... 114 | 115 | { 116 | xml: { 117 | employee: [ 118 | { 119 | id: "123", 120 | name: "alex" 121 | }, 122 | { 123 | id: "456", 124 | name: "jon" 125 | }, 126 | { 127 | id: "789", 128 | name: "ashley" 129 | } 130 | ] 131 | } 132 | } 133 | ``` 134 | * If an element contains a textNode and attributes the textNode will have the key textNode 135 | ``` 136 | Alex 137 | 138 | will turn into... 139 | 140 | employee: { 141 | id: "98765", 142 | textNode: "Alex" 143 | } 144 | 145 | and ... 146 | 147 | Alex 148 | 149 | will turn into... 150 | 151 | employee: "Alex" 152 | ``` 153 | 154 | 155 | 156 | License: MIT 157 | 158 | -------------------------------------------------------------------------------- /lib/xmlToJson.js: -------------------------------------------------------------------------------- 1 | module.exports = traverse; 2 | 3 | function traverse(xml,attributeMode) { 4 | const tagFinder = new RegExp('<(.*?)[>|\\s|/]', 'g'); //find the current tag we are working on 5 | 6 | const json = {}; 7 | let tagShouldBeArray = false; 8 | 9 | //recursion base case 10 | if(xml === '' || (xml.charAt(0) !== '<' && xml.charAt(xml.length-1) !== '>')) { 11 | return xml; 12 | } 13 | 14 | var currentLevelTags; 15 | var skip = 0; 16 | while((currentLevelTags = tagFinder.exec(xml)) !== null) { 17 | let selfClosing = false; 18 | const tag = currentLevelTags[1]; 19 | 20 | const finishTag = ''; 21 | const input = currentLevelTags.input; 22 | const tagLength = input.indexOf('>',skip)+1; 23 | 24 | const start = currentLevelTags.index; 25 | const end = currentLevelTags.input.indexOf('>',start)+1; 26 | const currentTag = currentLevelTags.input.substring(start,end); 27 | 28 | selfClosing = isSelfClosing(currentTag); 29 | 30 | if(!validate(currentTag)) { 31 | const err = new Error('Invalid XML tag'); 32 | throw err; 33 | } 34 | //const closingTagIndex = input.indexOf(finishTag,tagLength); 35 | const closingTagIndex = findClosingIndex(input,finishTag,tagLength); 36 | if(selfClosing === false && closingTagIndex < 0) { 37 | const err = new Error('Invalid XML'); 38 | throw err; 39 | } 40 | 41 | let substring; //substring will be either all child tags or if self closing tag just a blank string. i.e: Alex : Alex will be the substring of the parent tag 42 | if(selfClosing) { 43 | substring = ''; 44 | skip = currentTag.length + skip; 45 | 46 | } else { 47 | substring = input.substring(input.indexOf('>', skip)+1, closingTagIndex); 48 | skip = tagLength + substring.length + finishTag.length; 49 | } 50 | 51 | 52 | tagFinder.lastIndex = skip; //skip all child tags of current level 53 | 54 | if(!json[tag]) { 55 | json[tag] = {}; 56 | } else { 57 | tagShouldBeArray = true; 58 | } 59 | 60 | 61 | let temporary = {}; 62 | let attributes; 63 | if(attributeMode) { 64 | attributes = collectAttributes(currentTag); 65 | } 66 | 67 | //if currentTag contains attributes and attributeMode is enabled, attach them to json 68 | if(tagShouldBeArray && attributeMode) { 69 | temporary = attributes; 70 | 71 | } else if(!tagShouldBeArray && attributeMode) { 72 | for(let key in attributes) { 73 | json[tag][key] = attributes[key]; 74 | } 75 | } 76 | 77 | 78 | //go one level deeper 79 | const next = traverse(substring,attributeMode); 80 | 81 | //when returning from recursion, build up the json 82 | 83 | if(typeof next === 'object') { 84 | //const key = Object.keys(next)[0]; 85 | if(tagShouldBeArray && !json[tag].length) { 86 | const temp = json[tag]; 87 | json[tag] = [temp]; 88 | const nextObj = {} 89 | for(let key in next) { 90 | nextObj[key] = next[key]; 91 | } 92 | temporary = {...temporary,...nextObj}; 93 | json[tag].push(temporary); 94 | }else if(tagShouldBeArray) { 95 | const nextObj = {}; 96 | for(let key in next) { 97 | nextObj[key] = next[key]; 98 | } 99 | temporary = {...temporary,...nextObj}; 100 | json[tag].push(temporary); 101 | }else { 102 | for(let key in next) { 103 | json[tag][key] = next[key]; 104 | } 105 | } 106 | 107 | 108 | } else if(Object.keys(json[tag]).length>0) { 109 | 110 | if((tagShouldBeArray && !json[tag].length) || typeof json[tag] === 'string') { 111 | const temp = json[tag]; 112 | json[tag] = [temp]; 113 | 114 | if(typeof next !== 'object') { 115 | if(Object.keys(temporary).length === 0) { 116 | json[tag].push(next); 117 | } else { 118 | // temporary['data'] = next; 119 | if(next !== '') { 120 | temporary['textNode'] = next; 121 | } 122 | json[tag].push(temporary); 123 | } 124 | 125 | 126 | } else { 127 | temporary = {...temporary,next}; 128 | json[tag].push(next); 129 | } 130 | //json[tag].push(next); 131 | 132 | } else if(tagShouldBeArray) { 133 | //json[tag].push(next); 134 | if(typeof next !== 'object') { 135 | if(Object.keys(temporary).length === 0) { 136 | json[tag].push(next); 137 | } else { 138 | //temporary['data'] = next; 139 | if(next !== '') { 140 | temporary['textNode'] = next; 141 | } 142 | json[tag].push(temporary); 143 | } 144 | 145 | 146 | } else { 147 | temporary = {...temporary,next}; 148 | json[tag].push(next); 149 | } 150 | 151 | } else { 152 | if(next !== '') { 153 | json[tag] = { 154 | ...json[tag], 155 | textNode: next 156 | } 157 | } 158 | 159 | } 160 | 161 | } else { 162 | if(tagShouldBeArray && typeof json[tag] !== 'object') { 163 | const temp = json[tag]; 164 | json[tag] = []; 165 | json[tag].push(...temp,next); 166 | }else { 167 | json[tag] = next; 168 | } 169 | //json[tag] = next; 170 | } 171 | 172 | } 173 | 174 | 175 | return json; 176 | } 177 | 178 | 179 | 180 | 181 | //Helper methods 182 | 183 | //Determine if a tag is self closing or not. Could be improved 184 | function isSelfClosing(currentTag) { 185 | if(currentTag.indexOf('/>') > -1) { 186 | return true; 187 | } 188 | return false; 189 | } 190 | 191 | //Collect all the attributes of the current tag and return an object in form of {attribute:values} 192 | function collectAttributes(currentTag) { 193 | const attributeFinder = new RegExp('(\\S*)="(.*?)"', 'g'); 194 | const foundAttributes = {}; 195 | 196 | let attributes 197 | while((attributes = attributeFinder.exec(currentTag)) !== null) { 198 | const key = attributes[1]; 199 | const value = attributes[2]; 200 | 201 | foundAttributes[key] = value; 202 | } 203 | 204 | return foundAttributes; 205 | } 206 | 207 | function validate(currentTag) { 208 | if((currentTag.charAt(0) === '<' && currentTag.charAt(1) === '?') && (currentTag.charAt(currentTag.length-1) === '>' && currentTag.charAt(currentTag.length-2) === '?')) { 209 | return true; 210 | } 211 | 212 | if(currentTag.charAt(0) === '<' && (currentTag.charAt(currentTag.length-2)+currentTag.charAt(currentTag.length-1) === '/>' || currentTag.charAt(currentTag.length-1) === '>')) { 213 | return true; 214 | } 215 | 216 | return false; 217 | } 218 | 219 | 220 | function findClosingIndex(searchString,tag,start) { 221 | 222 | const openinTag = tag.replace('', ''); 223 | let closingIndex = searchString.indexOf(tag,start); 224 | let openingIndex = searchString.indexOf(openinTag,start); 225 | 226 | if(closingIndex < openingIndex) { 227 | return closingIndex; 228 | } 229 | 230 | const sub = searchString.substr(openingIndex,closingIndex-openingIndex); 231 | 232 | if(!sub.match(new RegExp(openinTag + "\\W"))) { 233 | return closingIndex; 234 | } 235 | 236 | while(closingIndex > 0) { 237 | const tempIndex = searchString.indexOf(tag,closingIndex+1); 238 | if(tempIndex > 0) { 239 | closingIndex = tempIndex; 240 | } else { 241 | break; 242 | } 243 | } 244 | 245 | return closingIndex; 246 | } -------------------------------------------------------------------------------- /spec/traverse-spec.js: -------------------------------------------------------------------------------- 1 | const traverse = require('../lib/xmlToJson'); 2 | const mockData = require('./mockXML').MOCK_DATA; 3 | const clean = require('../lib/cleanXML'); 4 | 5 | 6 | describe('TRAVERSE: With Attributes', ()=>{ 7 | const attributeMode = true; 8 | 9 | 10 | it('should collect all 3 attributes of the "employee" tag', ()=>{ 11 | 12 | const cleanXML = clean(mockData.TEST1) 13 | const json = traverse(cleanXML,attributeMode); 14 | 15 | const result = { 16 | employee : { 17 | id: "12345", 18 | building: '1', 19 | geo: "US", 20 | name: "Alex" 21 | } 22 | } 23 | 24 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)); 25 | }); 26 | 27 | it('should create an array if the same tag exist on the same level', ()=>{ 28 | //const cleanXML = mockData.TEST2.replace(/>\s*<'); 29 | const cleanXML = clean(mockData.TEST2) 30 | const json = traverse(cleanXML,attributeMode); 31 | 32 | const result = { 33 | employee: [ 34 | { 35 | id: '12345', 36 | name: 'Alex', 37 | }, 38 | { 39 | id: '56789', 40 | name: 'Jon' 41 | } 42 | ] 43 | } 44 | 45 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)); 46 | }); 47 | 48 | it('should parse xml without attributes even if attributeMode is enabled', ()=>{ 49 | const cleanXML = clean(mockData.TEST3) 50 | const json = traverse(cleanXML,attributeMode); 51 | 52 | const result = { 53 | employee: { 54 | name: 'Alex', 55 | age: '25' 56 | } 57 | } 58 | 59 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)) 60 | }) 61 | 62 | it('should read the single attribute', ()=>{ 63 | const cleanXML = clean(mockData.TEST4) 64 | const json = traverse(cleanXML,attributeMode); 65 | 66 | const result = { 67 | employee: { 68 | id: '12345' 69 | } 70 | } 71 | 72 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)) 73 | }); 74 | 75 | it('should pass sanity check', ()=>{ 76 | 77 | const cleanXML = clean(mockData.TEST5) 78 | const converted = traverse(cleanXML, attributeMode); 79 | 80 | const result = { 81 | employee: { 82 | name: "Alex" 83 | }, 84 | role: "Software Dev", 85 | locality: { 86 | country: "US", 87 | region: "TX", 88 | city: "Austin" 89 | } 90 | } 91 | 92 | expect(JSON.stringify(converted)).toBe(JSON.stringify(result)); 93 | }) 94 | 95 | it('should create an array of employees where each contain an array of names', ()=>{ 96 | const cleanXML = clean(mockData.TEST6); 97 | const json = traverse(cleanXML,attributeMode); 98 | 99 | const result = { 100 | employee: [ 101 | { 102 | id: "12345", 103 | name: [ 104 | { type: 'first', textNode: 'Alex'}, 105 | { type: 'last', textNode: 'La Bianca'} 106 | ] 107 | 108 | 109 | }, 110 | { 111 | id: "98765", 112 | name: [ 113 | { type: 'first', textNode: 'Ash'}, 114 | { type: 'last', textNode: 'Thrasher'} 115 | ] 116 | }, 117 | { 118 | id: "12332", 119 | name: [ 120 | { type: 'first', textNode: 'Jon'}, 121 | { type: 'last', textNode: 'Andrews'} 122 | ] 123 | } 124 | ] 125 | }; 126 | 127 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)) 128 | }); 129 | 130 | 131 | it('should create an object with xml key that contains one property which is an array of length 3', ()=>{ 132 | const cleanXML = clean(mockData.TEST7); 133 | const json = traverse(cleanXML,attributeMode); 134 | 135 | const result = { 136 | xml: { 137 | employee: [ 138 | { 139 | id: '123', 140 | name: 'alex' 141 | }, 142 | { 143 | id: '456', 144 | name: 'jon' 145 | }, 146 | { 147 | id: '789', 148 | name: 'ashley' 149 | } 150 | ] 151 | } 152 | } 153 | 154 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)) 155 | }); 156 | 157 | it('should process an array like element if it is out of order', ()=>{ 158 | const cleanXML = clean(mockData.TEST8); 159 | const json = traverse(cleanXML,attributeMode); 160 | 161 | const result = { 162 | xml: { 163 | employee: [ 164 | {name: 'Alex'}, 165 | {name: 'Troy'} 166 | ], 167 | location: 'US' 168 | } 169 | }; 170 | 171 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)); 172 | }) 173 | 174 | it('should process a single xml element', ()=>{ 175 | const cleanXML = clean(mockData.TEST9); 176 | const json = traverse(cleanXML,attributeMode); 177 | 178 | const result = { 179 | employee: { 180 | id: "98765", 181 | textNode: "Alex" 182 | } 183 | } 184 | 185 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)); 186 | }) 187 | 188 | 189 | }); 190 | 191 | 192 | 193 | 194 | describe('TRAVERSE: Without Attributes', ()=>{ 195 | const attributeMode = false; 196 | 197 | it('should not collect any attributes', ()=>{ 198 | const cleanXML = clean(mockData.TEST1) 199 | const json = traverse(cleanXML,attributeMode); 200 | 201 | const result = { 202 | employee : { 203 | name: "Alex" 204 | } 205 | } 206 | 207 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)); 208 | }); 209 | 210 | it('should creeate an array', ()=>{ 211 | const cleanXML = clean(mockData.TEST2) 212 | const json = traverse(cleanXML,attributeMode); 213 | 214 | const result = { 215 | employee: [ 216 | { 217 | name: 'Alex', 218 | }, 219 | { 220 | name: 'Jon' 221 | } 222 | ] 223 | } 224 | 225 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)); 226 | }); 227 | 228 | it('should not read the single attribute', ()=>{ 229 | const cleanXML = clean(mockData.TEST4) 230 | const json = traverse(cleanXML,attributeMode); 231 | 232 | const result = { 233 | employee: "" 234 | } 235 | 236 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)) 237 | }); 238 | 239 | 240 | it('should create an array of employees where each contain an array of names', ()=>{ 241 | const cleanXML = clean(mockData.TEST6); 242 | const json = traverse(cleanXML,attributeMode); 243 | 244 | const result = { 245 | employee: [ 246 | { 247 | name: [ 248 | "Alex", 249 | "La Bianca" 250 | ] 251 | 252 | 253 | }, 254 | { 255 | name: [ 256 | "Ash", 257 | "Thrasher" 258 | ] 259 | }, 260 | { 261 | name: [ 262 | "Jon", 263 | "Andrews" 264 | ] 265 | } 266 | ] 267 | }; 268 | 269 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)) 270 | }) 271 | 272 | it('should create an object with xml key that contains one property which is an array of length 3', ()=>{ 273 | const cleanXML = clean(mockData.TEST7); 274 | const json = traverse(cleanXML,attributeMode); 275 | 276 | const result = { 277 | xml: { 278 | employee: "" 279 | } 280 | } 281 | 282 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)) 283 | }); 284 | 285 | it('should process a single xml element', ()=>{ 286 | const cleanXML = clean(mockData.TEST9); 287 | const json = traverse(cleanXML,attributeMode); 288 | 289 | const result = { 290 | employee: "Alex" 291 | } 292 | 293 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)); 294 | }); 295 | }); 296 | 297 | 298 | describe('ERRORS: Invalid XML', ()=>{ 299 | const attributeMode = true; 300 | 301 | it('should throw an error with invalid xml 1', ()=>{ 302 | const cleanXML = clean(mockData.I_TEST10); 303 | 304 | expect(traverse.bind(null,cleanXML,attributeMode)).toThrowError(Error, "Invalid XML") 305 | 306 | }); 307 | 308 | it('should throw an error with invalid xml 2', ()=>{ 309 | const cleanXML = clean(mockData.I_TEST11); 310 | 311 | expect(traverse.bind(null,cleanXML,attributeMode)).toThrowError(Error, "Invalid XML") 312 | 313 | }); 314 | 315 | it('should throw an error with invalid xml 3', ()=>{ 316 | const cleanXML = clean(mockData.I_TEST12); 317 | 318 | expect(traverse.bind(null,cleanXML,attributeMode)).toThrowError(Error, "Invalid XML") 319 | 320 | }); 321 | }); 322 | 323 | 324 | 325 | describe('NESTING: Repetions', ()=>{ 326 | const attributeMode = true; 327 | 328 | it('should correctly parse nested repeated xml tags', ()=>{ 329 | const cleanXML = clean(mockData.TEST13); 330 | const json = traverse(cleanXML,attributeMode); 331 | 332 | const result = { 333 | employee: { 334 | id: "98765", 335 | name: "alex", 336 | employee: { 337 | id: "123", 338 | name: "jon" 339 | } 340 | } 341 | } 342 | 343 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)); 344 | 345 | 346 | }); 347 | 348 | it('should throw an error with invalid xml 2', ()=>{ 349 | const cleanXML = clean(mockData.TEST14); 350 | const json = traverse(cleanXML,attributeMode); 351 | 352 | const result = { 353 | employee: { 354 | id: "98765", 355 | name: "alex", 356 | employee: "Alex" 357 | } 358 | } 359 | 360 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)); 361 | 362 | }); 363 | 364 | it('should distinguish between similar tags ( vs. )', ()=>{ 365 | const cleanXML = clean(mockData.TEST15); 366 | const json = traverse(cleanXML,attributeMode); 367 | 368 | const result = { 369 | aaa: { 370 | bbb: [ 371 | { 372 | bbb1: "ccc", 373 | yyy: "yyy" 374 | }, 375 | { 376 | ddd: "ddd", 377 | yyy: "yyy" 378 | } 379 | ] 380 | } 381 | } 382 | 383 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)); 384 | }); 385 | 386 | it('should distinguis between similar tags [2] ( vs )', ()=>{ 387 | const cleanXML = clean(mockData.TEST16); 388 | const json = traverse(cleanXML,attributeMode); 389 | 390 | const result = { 391 | aaa: { 392 | bbb: [ 393 | { 394 | ccc: "ccc", 395 | bbb1: "bbb1" 396 | }, 397 | { 398 | ddd: "ddd", 399 | bbb2: "bbb2" 400 | } 401 | ] 402 | } 403 | } 404 | 405 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)); 406 | }) 407 | 408 | 409 | it('should distinguis between similar tags [3] [attribute=true] ( vs )', ()=>{ 410 | const cleanXML = clean(mockData.TEST17); 411 | const json = traverse(cleanXML,attributeMode); 412 | 413 | const result = { 414 | aaa: { 415 | bbb: [ 416 | { 417 | locale: "Austin", 418 | ccc: "ccc", 419 | bbb1: { 420 | locale: "Berlin", 421 | textNode: "bbb1" 422 | }, 423 | }, 424 | { 425 | locale: "San Francisco", 426 | ddd: "ddd", 427 | bbb2: { 428 | locale: "London", 429 | textNode: "bbb2" 430 | }, 431 | } 432 | ] 433 | } 434 | } 435 | 436 | expect(JSON.stringify(json)).toBe(JSON.stringify(result)); 437 | }) 438 | 439 | }); 440 | 441 | 442 | --------------------------------------------------------------------------------