diff --git a/README.md b/README.md index 53fb2f2..07f67a2 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,43 @@ JavaScript: } ``` +## Force array to provide uniform structer + +Useful when have to handle elements with array of items that sometimes have one element inside. + +XML: + +```xml + + + BAZ + + +``` + +Code: + +```js +fromXml(xml, {forceArray:true}); +``` + +JavaScript: + +```json +{ + "xml": [ + { + "foo": [ + { + "@bar": "BAR", + "#": "BAZ" + } + ] + } + ] +} +``` + ## CLI ```sh diff --git a/dist/from-xml.min.js b/dist/from-xml.min.js index dd9e0c1..6d91d75 100644 --- a/dist/from-xml.min.js +++ b/dist/from-xml.min.js @@ -1 +1 @@ -var fromXML;!function(r){function t(r,t){return a(n(r),t)}function n(r){function t(r){var t=r.length,f=r[0];if("/"===f)for(var u=r.replace(/^\/|[\s\/].*$/g,"").toLowerCase();c.length;){var a=l.n&&l.n.toLowerCase();if(l=c.pop(),a===u)break}else if("?"===f)n({n:"?",r:r.substr(1,t-2)});else if("!"===f)"[CDATA["===r.substr(1,7)&&"]]"===r.substr(-2)?e(r.substr(8,t-10)):n({n:"!",r:r.substr(1)});else{var o=s(r);n(o),"/"===r[t-1]?o.c=1:(c.push(l),l=o)}}function n(r){l.f.push(r)}function e(r){r=f(r),r&&n(u(r))}for(var a=String.prototype.split.call(r,/<([^!<>?](?:'[\S\s]*?'|"[\S\s]*?"|[^'"<>])*|!(?:--[\S\s]*?--|\[[^\[\]'"<>]+\[[\S\s]*?]]|DOCTYPE[^\[<>]*?\[[\S\s]*?]|(?:ENTITY[^"<>]*?"[\S\s]*?")?[\S\s]*?)|\?[\S\s]*?\?)>/),o=a.length,i={f:[]},l=i,c=[],p=0;p-1)return String.fromCharCode(t)}return i[r]||r})}function a(r,t){if("string"==typeof r)return r;var n=r.r;if(n)return n;var s,f=e(r,t),u=r.f,i=u.length;if(f||i>1)s=f||{},u.forEach(function(r){"string"==typeof r?o(s,c,r):o(s,r.n,a(r,t))});else if(i){var l=u[0];if(s=a(l,t),l.n){var p={};p[l.n]=s,s=p}}else s=r.c?null:"";return t&&(s=t(r.n||"",s)),s}function o(r,t,n){if("undefined"!=typeof n){var s=r[t];s instanceof Array?s.push(n):t in r?r[t]=[s,n]:r[t]=n}}var i={"&":"&","<":"<",">":">","'":"'",""":'"'},l="@",c="#";r.fromXML=fromXML=t}("object"==typeof exports&&exports||{}); \ No newline at end of file +var fromXML;!function(r){function t(r,t){return"function"==typeof t&&(t={reviver:t}),t=Object.assign({},{forceArray:!1},t),o(n(r),t.reviver,t.forceArray)}function n(r){function t(r){i.f.push(r)}function n(r){(r=f(r))&&t(a(r))}for(var e=String.prototype.split.call(r,/<([^!<>?](?:'[\S\s]*?'|"[\S\s]*?"|[^'"<>])*|!(?:--[\S\s]*?--|\[[^\[\]'"<>]+\[[\S\s]*?]]|DOCTYPE[^\[<>]*?\[[\S\s]*?]|(?:ENTITY[^"<>]*?"[\S\s]*?")?[\S\s]*?)|\?[\S\s]*?\?)>/),o=e.length,u={f:[]},i=u,c=[],l=0;l-1)return String.fromCharCode(t)}return i[r]||r})}function o(r,t,n){if("string"==typeof r)return r;var s=r.r;if(s)return s;var f,a=e(r,t,!1),i=r.f,c=i.length;if(a||c>1)f=a||{},i.forEach(function(r){"string"==typeof r?u(f,l,r,n):u(f,r.n,o(r,t,n),n)});else if(c){var p=i[0];if(f=o(p,t,n),p.n){var v={};v[p.n]=n?[f]:f,f=v}}else f=r.c?null:"";return t&&(f=t(r.n||"",f)),f}function u(r,t,n,s){if(void 0!==n){var e=r[t];e instanceof Array?e.push(n):r[t]=t in r?[e,n]:s&&"#"!==t?[n]:n}}var i={"&":"&","<":"<",">":">","'":"'",""":'"'},c="@",l="#";r.fromXML=fromXML=t}("object"==typeof exports&&exports||{}); \ No newline at end of file diff --git a/from-xml.js b/from-xml.js index 0e34b54..d9bd32d 100644 --- a/from-xml.js +++ b/from-xml.js @@ -4,8 +4,10 @@ * * @function fromXML * @param text {String} The string to parse as XML - * @param [reviver] {Function} If a function, prescribes how the value + * @param [options.reviver] {Function} If a function, prescribes how the value * originally produced by parsing is transformed, before being returned. + * @param [options.forceArray] {Boolean} = false If true, all childs will be always array event if contains one node. + * @param [options] {Function|Object} If function -> @see param [options.reviver] * @returns {Object} */ @@ -25,8 +27,14 @@ var fromXML; exports.fromXML = fromXML = _fromXML; - function _fromXML(text, reviver) { - return toObject(parseXML(text), reviver); + function _fromXML(text, options) { + if(typeof options === 'function' ) { + options = { reviver: options }; + } + + options = Object.assign({}, { forceArray: false }, options); + + return toObject(parseXML(text), options.reviver, options.forceArray); } function parseXML(text) { @@ -109,7 +117,7 @@ var fromXML; return elem; } - function parseAttribute(elem, reviver) { + function parseAttribute(elem, reviver, forceArray) { if (!elem.t) return; var list = elem.t.split(/([^\s='"]+(?:\s*=\s*(?:'[\S\s]*?'|"[\S\s]*?"|[^\s'"]*))?)/); var length = list.length; @@ -145,7 +153,7 @@ var fromXML; if (reviver) { val = reviver(str, val); } - addObject(attributes, str, val); + addObject(attributes, str, val, forceArray); } return attributes; @@ -165,13 +173,13 @@ var fromXML; }); } - function toObject(elem, reviver) { + function toObject(elem, reviver, forceArray) { if ("string" === typeof elem) return elem; var raw = elem.r; if (raw) return raw; - var attributes = parseAttribute(elem, reviver); + var attributes = parseAttribute(elem, reviver, false); var object; var childList = elem.f; var childLength = childList.length; @@ -181,18 +189,18 @@ var fromXML; object = attributes || {}; childList.forEach(function(child) { if ("string" === typeof child) { - addObject(object, CHILD_NODE_KEY, child); + addObject(object, CHILD_NODE_KEY, child, forceArray); } else { - addObject(object, child.n, toObject(child, reviver)); + addObject(object, child.n, toObject(child, reviver, forceArray), forceArray); } }); } else if (childLength) { // the node has single child node but no attribute var child = childList[0]; - object = toObject(child, reviver); + object = toObject(child, reviver, forceArray); if (child.n) { var wrap = {}; - wrap[child.n] = object; + wrap[child.n] = forceArray ? [object] : object; object = wrap; } } else { @@ -207,7 +215,7 @@ var fromXML; return object; } - function addObject(object, key, val) { + function addObject(object, key, val, forceArray) { if ("undefined" === typeof val) return; var prev = object[key]; if (prev instanceof Array) { @@ -215,7 +223,7 @@ var fromXML; } else if (key in object) { object[key] = [prev, val]; } else { - object[key] = val; + object[key] = forceArray && key !== "#" ? [val] : val; } } })(typeof exports === "object" && exports || {}); diff --git a/test/10.from-xml.js b/test/10.from-xml.js index 471b30f..a74f748 100644 --- a/test/10.from-xml.js +++ b/test/10.from-xml.js @@ -349,4 +349,34 @@ describe('fromXML', function() { assert.deepEqual(fromXML('BARBAZ'), {"foo": {"bar": "BAR", "baz": "BAZ"}}); }); -}); \ No newline at end of file + + describe('with forceArray flag', function() { + it('multiple attributes', function () { + assert.deepEqual(fromXML('', {forceArray:true}), + {foo: [{"@bar": "BAR", "@baz": "BAZ"}]}); + assert.deepEqual(fromXML('', {forceArray:true}), + {foo: [{"@bar": "BAR", "@baz": "BAZ"}]}); + assert.deepEqual(fromXML('', {forceArray:true}), + {foo: [{"@bar": null, "@baz": null}]}); + assert.deepEqual(fromXML('', {forceArray:true}), + {foo: [{"@bar": ["BAR", "BAZ"]}]}); + assert.deepEqual(fromXML('', {forceArray:true}), + {foo: [{"@bar": [null, null]}]}); + }); + + it('attributes and child elements', function () { + assert.deepEqual(fromXML('FOO', {forceArray:true}), + {foo: [{"@bar": "BAR", "#": "FOO"}]}); + assert.deepEqual(fromXML('FOO', {forceArray:true}), + {foo: [{"@bar": "BAR", "@baz": "BAZ", "#": "FOO"}]}); + assert.deepEqual(fromXML('QUX', {forceArray:true}), + {foo: [{"@bar": "BAR", "@baz": "BAZ", "qux": ["QUX"]}]}); + assert.deepEqual(fromXML('QUXQUUX', {forceArray:true}), + {foo: [{"@bar": "BAR", "@baz": "BAZ", "qux": ["QUX"], "#": "QUUX"}]}); + assert.deepEqual(fromXML('QUXQUUX', {forceArray:true}), + {foo: [{"@bar": "BAR", "@baz": "BAZ", "qux": ["QUX"], "quux": ["QUUX"]}]}); + assert.deepEqual(fromXML('QUXQUX2QUUX', {forceArray:true}), + {foo: [{"@bar": "BAR", "@baz": "BAZ", "qux": ["QUX", "QUX2"], "quux": ["QUUX"]}]}); + }); + }) +});