diff --git a/README.md b/README.md index 517bf30..1873978 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ This library store them in `attributes`, but most importantly, you can change th * **Support Upwards Traversal**: By setting `{addParent: true}` option, an extra property named `parent` will be generated along each element so that its parent can be referenced. -Therefore, anywhere during the traversal of an element node, its children **and** its parent can be easily accessed. +Therefore, anywhere during the traversal of an element, its children **and** its parent can be easily accessed. * **Portable Code**: Written purely in JavaScript which means it can be used in Node environment and **browser** environment (via bundlers like browserify/JSPM/Webpack). @@ -67,7 +67,9 @@ which has merged both `` elements into an array! If you try to convert this b which has not preserved the order of elements! This is an inherit limitation in the compact representation because output like `{a:{_:{x:"1"}}, b:{_:{x:"2"}}, a:{_:{x:"3"}}}` is illegal (same property name `a` should not appear twice in an object). -The non-compact output which is supported by this library will produce more information and always gurantees the order of the elements as they appeared in the XML file. +The non-compact output, which is supported by this library, will produce more information and always gurantees the order of the elements as they appeared in the XML file. + +Another drawback of compact output is the resultant element can be an object or an array and therefore makes the client code a little awkwards in terms of extra check of object type before processing. NOTE: Although non-compact output is more accurate representation of original XML than compact version, the non-compact version is verbose and consumes more space. This library provides both options. Use `{compact: false}` if you are not sure because it preserves everything; @@ -150,14 +152,15 @@ The below options are applicable for both `js2xml()` and `json2xml()` functions. | Option | Default | Description | |:----------------------|:--------|:------------| -| `spaces` | `0` | Number of spaces to be used for indenting XML output. | +| `spaces` | `0` | Number of spaces to be used for indenting XML output. Passing characters like `'`     `'` or `'\t'` are also accpeted. | | `compact` | `false` | Whether the *input* object is in compact form or not. | | `fullTagEmptyElement` | `false` | Whether to produce element without sub-elements as full tag pairs `` rather than self closing tag ``. | +| `indentCdata` | `false` | Whether to write CData in a new line and indent it. Will generate `\n ` instead of ``. | | `ignoreDeclaration` | `false` | Whether to ignore writing declaration directives of xml. For example, `` will be ignored. | | `ignoreAttributes` | `false` | Whether to ignore writing attributes of the elements. For example, `x="1"` in `` will be ignored | | `ignoreComment` | `false` | Whether to ignore writing comments of the elements. That is, no `` will be generated. | | `ignoreCdata` | `false` | Whether to ignore writing CData of the elements. That is, no `` will be generated. | -| `ignoreDoctype` | `false` | Whether to ignore writing Doctype of the elements. That is, no `` will be generated. | +| `ignoreDoctype` | `false` | Whether to ignore writing Doctype of the elements. That is, no `` will be generated. | | `ignoreText` | `false` | Whether to ignore writing texts of the elements. For example, `hi` text in `hi` will be ignored. | ## Convert XML → JS object / JSON @@ -195,7 +198,7 @@ The below option is applicable only for `xml2json()` function. | Option | Default | Description | |:--------------------|:--------|:------------| -| `spaces` | `0` | Number of spaces to be used for indenting JSON output. | +| `spaces` | `0` | Number of spaces to be used for indenting JSON output. Passing characters like `'`     `'` or `'\t'` are also accpeted. | ## Options for Changing Key Names diff --git a/bin/cli.js b/bin/cli.js index 961f993..1c6c115 100644 --- a/bin/cli.js +++ b/bin/cli.js @@ -23,6 +23,7 @@ var optionalArgs = [ {arg: 'trim', type: 'flag', option:'trim', desc: 'Any whitespaces surrounding texts will be trimmed.'}, {arg: 'sanitize', type: 'flag', option:'sanitize', desc: 'Special xml characters will be replaced with entity codes.'}, {arg: 'native-type', type: 'flag', option:'nativeType', desc: 'Numbers and boolean will be converted (coreced) to native type instead of text.'}, + {arg: 'always-array', type: 'flag', option:'alwaysArray', desc: 'Every element will always be an array type (applicable if --compact is set).'}, {arg: 'always-children', type: 'flag', option:'alwaysChildren', desc: 'Every element will always contain sub-elements (applicable if --compact is not set).'}, {arg: 'full-tag', type: 'flag', option:'fullTagEmptyElement', desc: 'XML elements will always be in form.'}, {arg: 'no-decl', type: 'flag', option:'ignoreDeclaration', desc: 'Declaration instruction will be ignored.'}, diff --git a/bin/test.json b/bin/test.json new file mode 100644 index 0000000..8de7ad7 --- /dev/null +++ b/bin/test.json @@ -0,0 +1,23 @@ +{ + "elements": [ + { + "type": "element", + "name": "a", + "attributes": { + "x": "1" + }, + "elements": [ + { + "type": "element", + "name": "b", + "elements": [ + { + "type": "text", + "text": "bye!" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/lib/js2xml.js b/lib/js2xml.js index d2a5341..2e17c03 100644 --- a/lib/js2xml.js +++ b/lib/js2xml.js @@ -9,6 +9,8 @@ function validateOptions (userOptions) { common.ensureFlagExists('ignoreCdata', options); common.ensureFlagExists('ignoreDoctype', options); common.ensureFlagExists('compact', options); + common.ensureFlagExists('indentText', options); + common.ensureFlagExists('indentCdata', options); common.ensureFlagExists('fullTagEmptyElement', options); common.ensureSpacesExists(options); if (typeof options.spaces === 'number') { @@ -44,20 +46,47 @@ function writeDeclaration (declaration, options) { return ''; } -function writeComment (element, options) { - return options.ignoreComment ? '' : ''; +function writeComment (comment, options) { + return options.ignoreComment ? '' : ''; } -function writeCdata (element, options) { - return options.ignoreCdata ? '' : ''; +function writeCdata (cdata, options) { + return options.ignoreCdata ? '' : ''; } -function writeDoctype (element, options) { - return options.ignoreDoctype ? '' : ''; +function writeDoctype (doctype, options) { + return options.ignoreDoctype ? '' : ''; } -function writeText (element, options) { - return options.ignoreText ? '' : element[options.textKey].replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); +function writeText (text, options) { + return options.ignoreText ? '' : text.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); +} + +function hasContent (element, options) { + var i; + if (element.elements && element.elements.length) { + for (i = 0; i < element.elements.length; ++i) { + switch (element.elements[i][options.typeKey]) { + case 'text': + if (options.indentText) { + return true; + } + break; // skip to next key + case 'cdata': + if (options.indentCdata) { + return true; + } + break; // skip to next key + case 'doctype': + case 'comment': + case 'element': + return true; + default: + return true; + } + } + } + return false; } function writeElement (element, options, depth) { @@ -71,7 +100,7 @@ function writeElement (element, options, depth) { if (element[options.elementsKey] && element[options.elementsKey].length) { xml += writeElements(element[options.elementsKey], options, depth + 1); } - xml += (options.spaces && element[options.elementsKey] && element[options.elementsKey].length && (element[options.elementsKey].length > 1 || element[options.elementsKey][0].type !== 'text') ? '\n' + Array(depth + 1).join(options.spaces) : ''); + xml += options.spaces && hasContent(element, options) ? '\n' + Array(depth + 1).join(options.spaces) : ''; xml += ''; } else { xml += '/>'; @@ -80,37 +109,37 @@ function writeElement (element, options, depth) { } function writeElements (elements, options, depth, firstLine) { - var indent = writeIndentation(options, depth, firstLine); return elements.reduce(function (xml, element) { + var indent = writeIndentation(options, depth, firstLine && !xml); switch (element.type) { case 'element': return xml + indent + writeElement(element, options, depth); - case 'comment': return xml + indent + writeComment(element, options); - case 'cdata': return xml + indent + writeCdata(element, options); - case 'doctype': return xml + indent + writeDoctype(element, options); - case 'text': return xml + writeText(element, options); + case 'comment': return xml + indent + writeComment(element[options.commentKey], options); + case 'doctype': return xml + indent + writeDoctype(element[options.doctypeKey], options); + case 'cdata': return xml + (options.indentCdata ? indent : '') + writeCdata(element[options.cdataKey], options); + case 'text': return xml + (options.indentText ? indent : '') + writeText(element[options.textKey], options); } }, ''); } -function hasContent (element, options, skipText) { +function hasContentCompact (element, options, anyContent) { var key; for (key in element) { if (element.hasOwnProperty(key)) { switch (key) { + case options.parentKey: + case options.attributesKey: + break; // skip to next key case options.textKey: - if (!skipText) { + if (options.indentText || anyContent) { return true; } break; // skip to next key - case options.parentKey: - case options.attributesKey: - break; // skip to next key case options.cdataKey: -<<<<<<< HEAD + if (options.indentCdata || anyContent) { + return true; + } + break; // skip to next key case options.doctypeKey: -======= - return false; ->>>>>>> origin/master case options.commentKey: case options.declarationKey: return true; @@ -129,7 +158,7 @@ function writeElementCompact (element, name, options, depth, indent) { if (element[options.attributesKey]) { xml += writeAttributes(element[options.attributesKey]); } - if (options.fullTagEmptyElement || hasContent(element, options) || element[options.attributesKey] && element[options.attributesKey]['xml:space'] === 'preserve') { + if (options.fullTagEmptyElement || hasContentCompact(element, options, true) || element[options.attributesKey] && element[options.attributesKey]['xml:space'] === 'preserve') { xml += '>'; } else { xml += '/>'; @@ -143,31 +172,50 @@ function writeElementCompact (element, name, options, depth, indent) { return xml; } +// function writeElementsCompact (element, options, depth, firstLine) { +// var key, xml = ''; +// for (key in element) { +// if (element.hasOwnProperty(key)) { +// switch (key) { +// case options.declarationKey: xml += writeDeclaration(element[options.declarationKey], options); break; +// case options.attributesKey: case options.parentKey: break; // skip +// case options.textKey: xml += (options.indentText ? writeIndentation(options, depth, firstLine) : '') + writeText(element, options); break; +// case options.cdataKey: xml += (options.indentCdata ? writeIndentation(options, depth, firstLine) : '') + writeCdata(element, options); break; +// case options.doctypeKey: xml += writeIndentation(options, depth, firstLine) + writeDoctype(element, options); break; +// case options.commentKey: xml += writeIndentation(options, depth, firstLine) + writeComment(element, options); break; +// default: +// if (element[key] instanceof Array) { +// element[key].forEach(function (el) { +// xml += writeIndentation(options, depth, firstLine) + writeElementCompact(el, key, options, depth, hasContentCompact(el, options)); +// firstLine = firstLine && !xml; +// }); +// } else { +// xml += writeIndentation(options, depth, firstLine) + writeElementCompact(element[key], key, options, depth, hasContentCompact(element[key], options)); +// } +// } +// firstLine = firstLine && !xml; +// } +// } +// return xml; +// } + function writeElementsCompact (element, options, depth, firstLine) { - var key, xml = ''; + var i, key, nodes, xml = ''; for (key in element) { if (element.hasOwnProperty(key)) { - switch (key) { - case options.declarationKey: xml += writeDeclaration(element[options.declarationKey], options); break; - case options.attributesKey: case options.parentKey: break; // skip - case options.textKey: xml += writeText(element, options); break; -<<<<<<< HEAD - case options.cdataKey: xml += writeIndentation(options, depth, firstLine) + writeCdata(element, options); break; - case options.doctypeKey: xml += writeIndentation(options, depth, firstLine) + writeDoctype(element, options); break; -======= - case options.cdataKey: xml += writeCdata(element, options); break; ->>>>>>> origin/master - case options.commentKey: xml += writeIndentation(options, depth, firstLine) + writeComment(element, options); break; - default: - if (element[key] instanceof Array) { - element[key].forEach(function (el) { - xml += writeIndentation(options, depth, firstLine) + writeElementCompact(el, key, options, depth, hasContent(el, options, true)); - }); - } else { - xml += writeIndentation(options, depth, firstLine) + writeElementCompact(element[key], key, options, depth, hasContent(element[key], options, true)); - } + nodes = element[key] instanceof Array ? element[key] : [element[key]]; + for (i = 0; i < nodes.length; ++i) { + switch (key) { + case options.declarationKey: xml += writeDeclaration(nodes[i], options); break; + case options.attributesKey: case options.parentKey: break; // skip + case options.textKey: xml += (options.indentText ? writeIndentation(options, depth, firstLine) : '') + writeText(nodes[i], options); break; + case options.cdataKey: xml += (options.indentCdata ? writeIndentation(options, depth, firstLine) : '') + writeCdata(nodes[i], options); break; + case options.doctypeKey: xml += writeIndentation(options, depth, firstLine) + writeDoctype(nodes[i], options); break; + case options.commentKey: xml += writeIndentation(options, depth, firstLine) + writeComment(nodes[i], options); break; + default: xml += writeIndentation(options, depth, firstLine) + writeElementCompact(nodes[i], key, options, depth, hasContentCompact(nodes[i], options)); + } + firstLine = firstLine && !xml; } - firstLine = firstLine && !xml; } } return xml; diff --git a/lib/xml2js.js b/lib/xml2js.js index e1de539..556a0f2 100644 --- a/lib/xml2js.js +++ b/lib/xml2js.js @@ -15,6 +15,7 @@ function validateOptions (userOptions) { common.ensureFlagExists('ignoreCdata', options); common.ensureFlagExists('ignoreDoctype', options); common.ensureFlagExists('compact', options); + common.ensureFlagExists('alwaysArray', options); common.ensureFlagExists('alwaysChildren', options); common.ensureFlagExists('addParent', options); common.ensureFlagExists('trim', options); diff --git a/package.json b/package.json index 71f3a71..ad7491c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xml-js", - "version": "1.0.2", + "version": "1.1.0", "description": "A convertor between XML text and Javascript object / JSON text.", "repository": { "type": "git", @@ -55,7 +55,6 @@ "globify": "^2.0.0", "istanbul": "^0.4.5", "jasmine": "^2.5.3", - "node-inspector": "^1.0.0", "nodemon": "^1.11.0", "npm-run-all": "^4.0.1", "typescript": "^2.2.2", @@ -63,12 +62,7 @@ }, "scripts": { "debug": "nodemon --inspect --watch lib/ --watch test/ --debug-brk test/index.js", - "pre-node6.3.1-debug": "npm-run-all --parallel debug:*", - "debug:listener": "node-inspector", - "debug:jasmine": "nodemon --watch lib/ --watch test/ --debug-brk test/index.js", - "xdebug:cli": "nodemon --watch lib/ --debug-brk index.js -- --help", - "debug:live": "browser-sync start --port 8080 --server --startPath ?port=5858 --files lib/ test/ --no-open --no-ui --no-online", - "debug:open": "biased-opener --browser chrome http://localhost:8080/?port=5858", + "debug:cli": "nodemon --inspect --watch lib/ --debug-brk index.js -- --help", "jasmine": "jasmine JASMINE_CONFIG_PATH=./test/jasmine.json", "watch:jasmine": "watch \"npm run jasmine\" lib/ test/", "bundle:jasmine": "globify test/*_test.js --watch --verbose --list --outfile test/browse-jasmine/bundle.js", diff --git a/test/common_test.js b/test/common_test.js index f26f194..9672e3e 100644 --- a/test/common_test.js +++ b/test/common_test.js @@ -9,7 +9,7 @@ describe('Testing common.js:', function () { describe('Copy Options:', function () { - it('Copy no provided options', function () { + it('Copy unprovided options', function () { expect(convert.copyOptions()).toEqual({}); }); diff --git a/test/js2xml_test.js b/test/js2xml_test.js index 5b8dd5b..aeb95fd 100644 --- a/test/js2xml_test.js +++ b/test/js2xml_test.js @@ -30,6 +30,17 @@ describe('Testing js2xml.js:', function () { }); + describe('options = {spaces: 4}', function () { + + var options = {compact: true, spaces: 4}; + testItems(options).forEach(function (test) { + it(test.desc, function () { + expect(convert.js2xml(test.js, options)).toEqual(test.xml); + }); + }); + + }); + describe('options = {spaces: 0}', function () { var options = {compact: true, spaces: 0}; @@ -100,6 +111,17 @@ describe('Testing js2xml.js:', function () { }); + describe('options = {spaces: 4}', function () { + + var options = {spaces: 4, onlyItem: 8}; + testItems(options).forEach(function (test) { + it(test.desc, function () { + expect(convert.js2xml(test.js, options)).toEqual(test.xml); + }); + }); + + }); + describe('options = {spaces: 0}', function () { var options = {spaces: 0}; @@ -194,7 +216,7 @@ describe('Testing js2xml.js:', function () { describe('options = {spaces: 4}', function () { - var options = {spaces: 2}; + var options = {spaces: 4}; testItems(options).forEach(function (test) { it(test.desc, function () { expect(convert.js2xml(test.js, options)).toEqual(test.xml); @@ -205,7 +227,18 @@ describe('Testing js2xml.js:', function () { describe('options = {spaces: \' \'}', function () { - var options = {spaces: 'mm'}; + var options = {spaces: ' '}; + testItems(options).forEach(function (test) { + it(test.desc, function () { + expect(convert.js2xml(test.js, options)).toEqual(test.xml); + }); + }); + + }); + + describe('options = {spaces: \\t}', function () { + + var options = {spaces: '\t'}; testItems(options).forEach(function (test) { it(test.desc, function () { expect(convert.js2xml(test.js, options)).toEqual(test.xml); @@ -262,7 +295,7 @@ describe('Testing js2xml.js:', function () { }); }); - + describe('User reported issues:', function () { describe('case by Jan T. Sott', function () { @@ -284,13 +317,11 @@ describe('Testing js2xml.js:', function () { var xml = '\n' + '\n' + - '\v\n' + - '\v\v\n' + - '\v\n' + + '\v\n' + '\vlog\n' + '\vsource.js\n' + ''; - + it('should output cdata and text unformatted', function () { expect(convert.js2xml(js, {compact: true})).toEqual(xml.replace(/\v|\n/g, '')); }); @@ -306,7 +337,7 @@ describe('Testing js2xml.js:', function () { var js1 = { a: { b: { - _text: 'foo bar', + _text: 'foo bar' } } }; @@ -320,7 +351,7 @@ describe('Testing js2xml.js:', function () { elements: [{ type: 'text', text: 'foo bar' - }], + }] }] }] }; @@ -338,7 +369,75 @@ describe('Testing js2xml.js:', function () { }); + describe('case 1 by Henning Hagmann ', function () { + // see https://github.com/nashwaan/xml-js/issues/14 + var js = { + _declaration: { + _attributes: { + version: '1.0' + } + }, + group: { + name: { + _cdata: 'An example name' + } + } + }; + var xml = '\n' + + '\n' + + '\v\n' + + ''; + + it('should output cdata without preceeding indentation', function () { + expect(convert.js2xml(js, {compact: true, spaces: 4, fullTagEmptyElement: true})).toEqual(xml.replace(/\v/g, ' ')); + }); + + }); + + describe('case 2 by Henning Hagmann ', function () { + // see https://github.com/nashwaan/xml-js/issues/14 + var js = { + declaration: { + attributes: { + version: '1.0' + } + }, + elements: [{ + type: 'element', + name: 'group', + elements: [{ + type: 'element', + name: 'name', + elements: [{ + type: 'text', + text: 'The url ' + }, { + type: 'cdata', + cdata: 'http://www.test.com' + }, { + type: 'text', + text: ' and name ' + }, { + type: 'cdata', + cdata: 'examplename' + }, { + type: 'text', + text: ' are wrapped' + }] + }] + }] + }; + var xml = '\n' + + '\n' + + '\vThe url and name are wrapped\n' + + ''; + + it('should output cdata without preceeding indentation', function () { + expect(convert.js2xml(js, {compact: false, spaces: 4})).toEqual(xml.replace(/\v/g, ' ')); + }); + + }); + }); }); - diff --git a/test/test-items.js b/test/test-items.js index 3ce0ab9..0c08a27 100644 --- a/test/test-items.js +++ b/test/test-items.js @@ -3,17 +3,17 @@ var cases = [ desc: 'declaration ', xml: '', js1: {"_declaration":{}}, - js2: {"declaration":{}}, + js2: {"declaration":{}} }, { desc: 'declaration with attributes', xml: '', js1: {"_declaration":{"_attributes":{"version":"1.0","encoding":"utf-8"}}}, - js2: {"declaration":{"attributes":{"version":"1.0","encoding":"utf-8"}}}, + js2: {"declaration":{"attributes":{"version":"1.0","encoding":"utf-8"}}} }, { desc: 'declaration and element', xml: '\n', js1: {"_declaration":{},"a":{}}, - js2: {"declaration":{},"elements":[{"type":"element","name":"a"}]}, + js2: {"declaration":{},"elements":[{"type":"element","name":"a"}]} }, { desc: 'declaration and elements', xml: '\n\n\v\n', @@ -23,7 +23,7 @@ var cases = [ desc: 'should convert comment', xml: '', js1: {"_comment":" \t Hello, World! \t "}, - js2: {"elements":[{"type":"comment","comment":" \t Hello, World! \t "}]}, + js2: {"elements":[{"type":"comment","comment":" \t Hello, World! \t "}]} }, /*{ desc: 'should convert 2 comments', xml: '', @@ -33,57 +33,63 @@ var cases = [ desc: 'should convert cdata', xml: ' \t ]]>', js1: {"_cdata":" \t \t "}, - js2: {"elements":[{"type":"cdata","cdata":" \t \t "}]}, + js2: {"elements":[{"type":"cdata","cdata":" \t \t "}]} }, /*{ desc: 'should convert 2 cdata', xml: ' " and & \t ]]>', js1: {"_cdata":[" \t data\n< > \" and & \t "]}, js2: {"elements":[{"type":"cdata","cdata":" \t data"},{"type":"cdata","cdata":"< > \" and & \t "}]}, }, */{ + + desc: 'should convert doctype', + xml: ']>', + js1: {"_doctype":" note [\n]"}, + js2: {"elements":[{"type":"doctype","doctype":" note [\n]"}]} + }, { desc: 'should convert element', xml: '', js1: {"a":{}}, - js2: {"elements":[{"type":"element","name":"a"}]}, + js2: {"elements":[{"type":"element","name":"a"}]} }, { desc: 'should convert 2 same elements', - xml: '', + xml: '\n', js1: {"a":[{},{}]}, - js2: {"elements":[{"type":"element","name":"a"},{"type":"element","name":"a"}]}, + js2: {"elements":[{"type":"element","name":"a"},{"type":"element","name":"a"}]} }, { desc: 'should convert 2 different elements', - xml: '', + xml: '\n', js1: {"a":{},"b":{}}, - js2: {"elements":[{"type":"element","name":"a"},{"type":"element","name":"b"}]}, + js2: {"elements":[{"type":"element","name":"a"},{"type":"element","name":"b"}]} }, { desc: 'should convert attribute', xml: '', js1: {"a":{_attributes:{"x":"hello"}}}, - js2: {"elements":[{"type":"element","name":"a","attributes":{"x":"hello"}}]}, + js2: {"elements":[{"type":"element","name":"a","attributes":{"x":"hello"}}]} }, { desc: 'should convert 2 attributes', xml: '', js1: {"a":{_attributes:{"x":"1.234","y":"It\'s"}}}, - js2: {"elements":[{"type":"element","name":"a","attributes":{"x":"1.234","y":"It\'s"}}]}, + js2: {"elements":[{"type":"element","name":"a","attributes":{"x":"1.234","y":"It\'s"}}]} }, { desc: 'should convert text in element', xml: ' \t Hi \t ', js1: {"a":{"_text":" \t Hi \t "}}, - js2: {"elements":[{"type":"element","name":"a","elements":[{"type":"text","text":" \t Hi \t "}]}]}, + js2: {"elements":[{"type":"element","name":"a","elements":[{"type":"text","text":" \t Hi \t "}]}]} }, { desc: 'should convert multi-line text', xml: ' Hi \n There \t ', js1: {"a":{"_text":" Hi \n There \t "}}, - js2: {"elements":[{"type":"element","name":"a","elements":[{"type":"text","text":" Hi \n There \t "}]}]}, + js2: {"elements":[{"type":"element","name":"a","elements":[{"type":"text","text":" Hi \n There \t "}]}]} }, { desc: 'should convert nested elements', xml: '\n\v\n', js1: {"a":{"b":{}}}, - js2: {"elements":[{"type":"element","name":"a","elements":[{"type":"element","name":"b"}]}]}, + js2: {"elements":[{"type":"element","name":"a","elements":[{"type":"element","name":"b"}]}]} }, { desc: 'should convert 3 nested elements', xml: '\n\v\n\v\v\n\v\n', js1: {"a":{"b":{"c":{}}}}, - js2: {"elements":[{"type":"element","name":"a","elements":[{"type":"element","name":"b","elements":[{"type":"element","name":"c"}]}]}]}, + js2: {"elements":[{"type":"element","name":"a","elements":[{"type":"element","name":"b","elements":[{"type":"element","name":"c"}]}]}]} } ];