/*
	This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
	
	Author: Sam Tsvilik
	Version: 3.0 Beta
	Last Modified: 10/31/2008
*/
(function($) {
    //Converts XML DOM to JSON
    $.extend(
		(function() {
		    var _nodeHandler = null;
		    var _features = null;
		    var _outputTypes = { complex: 0, lite: 1 };
		    //Helper functions
		    var clone = function(obj) {
		        if (!!obj && typeof (obj) === "object") {
		            function F() { }
		            F.prototype = obj;
		            return new F();
		        }
		    };
		    var isNumeric = function(s) {
		        var testStr = "";
		        if (s && typeof s == "string") { testStr = s; }
		        var pattern = /^((-)?([0-9]*)((\.{0,1})([0-9]+))?$)/;
		        return pattern.test(testStr);
		    };
		    var formatName = function(name) {
		        var regEx = /-/g;
		        var tName = String(name).replace(regEx, "_");
		        return tName;
		    }; //Alters attribute and collection names to comply with JS
		    //Node Prototype
		    var _node = (function() {
		        var _self = {
		            activate: function() {
		                var nodes = [];
		                if (!!nodes && _features.outputType === _outputTypes.complex) {
		                    nodes.getNodesByAttribute = function(attr, obj) {
		                        if (!!nodes && nodes.length > 0) {
		                            var out = [];
		                            var cNode;
		                            var maxLen = nodes.length - 1;
		                            if (maxLen >= 0) {
		                                do {
		                                    cNode = nodes[maxLen];
		                                    if (!!cNode && cNode[attr] === obj) { out.push(cNode); }
		                                } while (maxLen--);
		                            }
		                            out.reverse();
		                            return out;
		                        }
		                    };
		                    nodes.getNodeByAttribute = function(attr, obj) {
		                        if (!!nodes && nodes.length > 0) {
		                            var cNode = null;
		                            var maxLen = nodes.length - 1;
		                            if (maxLen >= 0) {
		                                do {
		                                    cNode = nodes[maxLen];
		                                    if (!!cNode && cNode[attr] === obj) { break; }
		                                } while (maxLen--);
		                            }
		                            return cNode;
		                        }
		                    };
		                    nodes.getNodesByValue = function(obj) {
		                        if (!!nodes && nodes.length > 0) {
		                            var out = [];
		                            var cNode;
		                            var maxLen = nodes.length - 1;
		                            if (maxLen >= 0) {
		                                do {
		                                    cNode = nodes[maxLen];
		                                    if (!!cNode && !!cNode.Text && cNode.Text === obj) { out.push(cNode); }
		                                } while (maxLen--);
		                            }
		                            out.reverse();
		                            return out;
		                        }
		                    };
		                    nodes.contains = function(attr, obj) {
		                        if (!!nodes && nodes.length > 0) {
		                            var out = false;
		                            var maxLen = nodes.length - 1;
		                            if (maxLen >= 0) {
		                                do {
		                                    if (nodes[maxLen][attr] === obj) { out = true; break; }
		                                } while (maxLen--);
		                            }
		                            return out;
		                        }
		                    };
		                    nodes.indexOf = function(attr, obj) {
		                        var pos = -1;
		                        if (!!nodes && nodes.length > 0) {
		                            var maxLen = nodes.length - 1;
		                            if (maxLen >= 0) {
		                                do {
		                                    if (nodes[maxLen][attr] === obj) { pos = maxLen; break; }
		                                } while (maxLen--);
		                            }
		                            return pos;
		                        }
		                    };
		                    nodes.SortByAttribute = function(col, dir) {
		                        if (!!nodes && nodes.length > 0) {
		                            nodes.sort(function(a, b) {
		                                var _a = (a[col]),
											_b = (b[col]);
		                                _a = isNumeric(_a) ? parseFloat(_a) : _a;
		                                _b = isNumeric(_b) ? parseFloat(_b) : _b;
		                                var _out = (_a < _b) ? -1 : (_b < _a) ? 1 : 0;
		                                _out = (!!dir && dir.toUpperCase() === "DESC") ? (0 - _out) : _out;
		                                return _out;
		                            });
		                        }
		                    };
		                    nodes.SortByValue = function(dir) {
		                        if (!!nodes && nodes.length > 0) {
		                            nodes.sort(function(a, b) {
		                                var _a = (a.Text),
											_b = (b.Text);
		                                _a = isNumeric(_a) ? parseFloat(_a) : _a;
		                                _b = isNumeric(_b) ? parseFloat(_b) : _b;
		                                var _out = (_a < _b) ? -1 : (_b < _a) ? 1 : 0;
		                                _out = (!!dir && dir.toUpperCase() === "DESC") ? (0 - _out) : _out;
		                                return _out;
		                            });
		                        }
		                    };
		                    nodes.SortByNode = function(node, dir) {
		                        if (!!nodes && nodes.length > 0) {
		                            nodes.sort(function(a, b) {
		                                var _a = (a[node][0].Text),
											_b = (b[node][0].Text);
		                                _a = isNumeric(_a) ? parseFloat(_a) : _a;
		                                _b = isNumeric(_b) ? parseFloat(_b) : _b;
		                                var _out = (_a < _b) ? -1 : (_b < _a) ? 1 : 0;
		                                _out = (!!dir && dir.toUpperCase() === "DESC") ? (0 - _out) : _out;
		                                return _out;
		                            });
		                        }
		                    };
		                }
		                return nodes;
		            }
		        };
		        return _self;
		    })();
		    //Makes a new node of type _node;
		    //Creates a nodes collection
		    var makeNodeContainer = function() {
		        var _fn = clone(_node);
		        return _fn.activate();
		    };
		    //Creates a single node component
		    var makeNode = function(node) {
		        var _self = { Text: "", _processChildren: true, _processAttributes: true };
		        var _customNode = null;
		        if (typeof (_nodeHandler) === "function") { //Check to see if custom node handler is present
		            try { _customNode = _nodeHandler(node); } catch (e) { }
		        }
		        if (!!_customNode && _customNode === -1) { //Skip node entirely
		            _self = null;
		        } else if (!!_customNode) { //Use custom node
		            _self = _customNode;
		            _self._processChildren = (typeof (_self._processChildren) === "undefined") ? true : _self._processChildren;
		            _self._processAttributes = (typeof (_self._processAttributes) === "undefined") ? true : _self._processAttributes;
		            if (!_self.Text) { _self.Text = ""; }
		            if (!_self._namespace) { if (!!node.prefix && _features.outputType === _outputTypes.complex) { _self._namespace = node.prefix; } }
		        } else { //Normal operation
		            //SOAP XML FIX to remove namespaces (i.e. soapenv:)
		            var elemName = (!!node.localName) ? node.localName : node.baseName;
		            elemName = formatName(elemName);
		            _self._nodeName = elemName;
		            if (!!node.prefix) { _self._namespace = node.prefix; }
		        }
		        return _self;
		    };
		    //Sets node attributes
		    var setAttributes = function(jNode, xNode) { //Set Attributes of an object
		        if (xNode.attributes.length > 0) {
		            if (_features.outputType === _outputTypes.complex) { jNode._attributes = []; }
		            var a = xNode.attributes.length - 1, attName = null, jParent = null;
		            do { //Order is irrelevant (speed-up)
		                attName = String(formatName(xNode.attributes[a].name));
		                jParent = jNode._parent || false;
		                //Prevents name collisions with node names
		                if (!!jParent && !!jParent[jNode._nodeName][0][attName] && typeof (jParent[jNode._nodeName][0][attName]) === "object") { attName = "attr_" + attName; }
		                if (_features.outputType === _outputTypes.complex) { jNode._attributes.push(attName); }
		                jNode[attName] = $.trim(xNode.attributes[a].value);
		            } while (a--);
		        }
		    };
		    var _self = {
		        JSONOutputType: _outputTypes,
		        xmlToJSON: function(xdoc, features) {
		            _features = features || { outputType: _self.JSONOutputType.complex };
		            _nodeHandler = _features && _features.nodeHandler;
		            var _xdoc = null; //xdoc now accepts xml string and xml dom object
		            if (typeof (xdoc) === "string") { _xdoc = _self.textToXML(xdoc); } else if (typeof (xdoc) === "object" && !!xdoc.nodeType) { _xdoc = xdoc; } else { _xdoc = null; }
		            if (!_xdoc) { return null; }
		            var xroot = (_xdoc.nodeType == 9) ? _xdoc.documentElement : _xdoc;
		            //JSON Object holder
		            var tmpObj = makeNode(xroot);
		            tmpObj.typeOf = "JSXBObject";
		            tmpObj.RootName = tmpObj._nodeName; //Backwards compat.						
		            if (_xdoc.nodeType == 3 || _xdoc.nodeType == 4) {
		                return _xdoc.nodeValue;
		            }
		            //Recursive JSON Assembler
		            function setObjects(obj, node) {
		                var cnode; //Current Node
		                var tObj; //New subnode
		                var cName = "";
		                if (!node) { return null; }
		                if (node.hasChildNodes()) {
		                    var nodeCount = node.childNodes.length - 1;
		                    var n = 0;
		                    do {
		                        cnode = node.childNodes[n];
		                        switch (cnode.nodeType) {
		                            case 1: //Node									
		                                //Process child nodes
		                                if (_features.outputType === _outputTypes.complex) { obj._children = (!!obj._children) ? obj._children : []; }
		                                //Create a single node
		                                tObj = makeNode(cnode);
		                                if (!!tObj) {
		                                    if (_features.outputType === _outputTypes.complex) { tObj._parent = obj; if (cName !== tObj._nodeName) { obj._children.push(tObj._nodeName); } }
		                                    //Create sub elemns array
		                                    if (!obj[tObj._nodeName]) { //If node is new then create a new container for nodes
		                                        obj[tObj._nodeName] = makeNodeContainer(); //Create Collection
		                                    }
		                                    //Add empty node holder to the container
		                                    obj[tObj._nodeName].push(tObj);
		                                    //Recursive call if node contains children										
		                                    if (!!tObj._processChildren && cnode.hasChildNodes()) { setObjects(tObj, cnode); } delete tObj._processChildren;
		                                    //Set node attributes after children are added
		                                    if (!!tObj._processAttributes) { setAttributes(tObj, cnode); } delete tObj._processAttributes;
		                                    cName = tObj._nodeName;
		                                    if (_features.outputType === _outputTypes.lite) { delete tObj._nodeName; }
		                                }
		                                break;
		                            case 3: //Text Value
		                                obj.Text += $.trim(cnode.nodeValue);
		                                break;
		                            case 4: //CDATA
		                                obj.Text += (!!cnode.text) ? $.trim(cnode.text) : $.trim(cnode.nodeValue);
		                                break;
		                        }
		                    } while (n++ < nodeCount);
		                }
		            }
		            //Set root attributes if any
		            setAttributes(tmpObj, xroot);
		            //Start assembling JSON
		            setObjects(tmpObj, xroot);
		            //Clean-up memmory
		            if (_features.outputType === _outputTypes.lite) { delete tmpObj._nodeName; }
		            delete tmpObj._processChildren;
		            delete tmpObj._processAttributes;
		            _xdoc = null;
		            xroot = null;
		            return tmpObj;
		        },
		        //Converts Text to XML DOM
		        textToXML: function(strXML) {
		            var xmlDoc = null;
		            try {
		                xmlDoc = ($.browser.msie) ? new ActiveXObject("Microsoft.XMLDOM") : new DOMParser();
		                xmlDoc.async = false;
		            } catch (e) { throw new Error("XML Parser could not be instantiated"); }
		            var out = null, isParsed = true;
		            if ($.browser.msie) {
		                isParsed = xmlDoc.loadXML(strXML);
		                out = (isParsed) ? xmlDoc : false;
		            } else {
		                out = xmlDoc.parseFromString(strXML, "text/xml");
		                isParsed = (out.documentElement.tagName !== "parsererror");
		            }
		            if (!isParsed) { throw new Error("Error parsing XML string"); }
		            return out;
		        }
		    };
		    return _self;
		})()
	);
})(jQuery);
