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"]}]});
+ });
+ })
+});