diff --git a/lib/xml2js.js b/lib/xml2js.js index 452c0ebe..b5b950ce 100644 --- a/lib/xml2js.js +++ b/lib/xml2js.js @@ -106,7 +106,8 @@ headless: false, chunkSize: 10000, emptyTag: '', - cdata: false + cdata: false, + sortkey: '#index' } }; @@ -139,9 +140,10 @@ } Builder.prototype.buildObject = function(rootObj) { - var attrkey, charkey, render, rootElement, rootName; + var attrkey, charkey, render, rootElement, rootName, sortkey; attrkey = this.options.attrkey; charkey = this.options.charkey; + sortkey = this.options.sortkey; if ((Object.keys(rootObj).length === 1) && (this.options.rootName === exports.defaults['0.2'].rootName)) { rootName = Object.keys(rootObj)[0]; rootObj = rootObj[rootName]; @@ -150,7 +152,8 @@ } render = (function(_this) { return function(element, obj) { - var attr, child, entry, index, key, value; + var attr, child, entry, index, key, list, value; + list = []; if (typeof obj !== 'object') { if (_this.options.cdata && requiresCDATA(obj)) { element.raw(wrapCDATA(obj)); @@ -174,7 +177,35 @@ } else { element = element.txt(child); } - } else if (Array.isArray(child)) { + } else if (key === sortkey) { + + } else { + if (Array.isArray(child)) { + for (index in child) { + if (!hasProp.call(child, index)) continue; + entry = child[index]; + list.push({ + key: key, + child: entry + }); + } + } else { + list.push({ + key: key, + child: child + }); + } + } + } + list.sort(function(a, b) { + return (a.child[sortkey] || -1) - (b.child[sortkey] || -1); + }); + for (index in list) { + if (!hasProp.call(list, index)) continue; + entry = list[index]; + key = entry.key; + child = entry.child; + if (Array.isArray(child)) { for (index in child) { if (!hasProp.call(child, index)) continue; entry = child[index]; @@ -335,7 +366,7 @@ })(this); this.saxParser.onclosetag = (function(_this) { return function() { - var cdata, emptyStr, err, key, node, nodeName, obj, objClone, old, s, xpath; + var cdata, emptyStr, err, index, key, node, nodeName, obj, objClone, old, s, xpath; obj = stack.pop(); nodeName = obj["#name"]; if (!_this.options.explicitChildren || !_this.options.preserveChildrenOrder) { @@ -409,6 +440,9 @@ obj = obj[charkey]; } } + } else if (_this.options.preserveChildrenOrder) { + index = s ? Math.max(Object.keys(s).length - 2, 0) : 0; + obj[_this.options.sortkey] = index; } if (stack.length > 0) { return _this.assignOrPush(s, nodeName, obj); diff --git a/src/xml2js.coffee b/src/xml2js.coffee index bc0d6372..8661d50a 100644 --- a/src/xml2js.coffee +++ b/src/xml2js.coffee @@ -99,6 +99,7 @@ exports.defaults = chunkSize: 10000 emptyTag: '' cdata: false + sortkey: '#index' class exports.ValidationError extends Error constructor: (message) -> @@ -115,6 +116,7 @@ class exports.Builder buildObject: (rootObj) -> attrkey = @options.attrkey charkey = @options.charkey + sortkey = @options.sortkey # If there is a sane-looking first element to use as the root, # and the user hasn't specified a non-default rootName, @@ -127,6 +129,8 @@ class exports.Builder rootName = @options.rootName render = (element, obj) => + list = []; + if typeof obj isnt 'object' # single element, just append it as text if @options.cdata && requiresCDATA obj @@ -149,8 +153,25 @@ class exports.Builder else element = element.txt child + else if key is sortkey + # console.log(key) + # noop + else + if Array.isArray child + for own index, entry of child + list.push {key:key, child:entry} + else + list.push {key:key, child: child} + + list.sort (a, b) -> + return (a.child[sortkey] || -1) - (b.child[sortkey] || -1) + + for own index, entry of list + key = entry.key + child = entry.child + # Case #3 Array data - else if Array.isArray child + if Array.isArray child for own index, entry of child if typeof entry is 'string' if @options.cdata && requiresCDATA entry @@ -337,6 +358,10 @@ class exports.Parser extends events.EventEmitter if Object.keys(obj).length == 1 and charkey of obj and not @EXPLICIT_CHARKEY obj = obj[charkey] + else if @options.preserveChildrenOrder + index = if s then Math.max(Object.keys(s).length - 2, 0) else 0 + obj[@options.sortkey] = index + # check whether we closed all the open tags if stack.length > 0 @assignOrPush s, nodeName, obj