Ext.ns('json2xml');

json2xml.defaultSettings = {
	formatOutput: false
	,formatTextNodes: false
	,indentString: '  '
	,rootTagName: 'root'
  ,rootTagAttributes:''
	,ignore: []
	,replace: []
	,nodes: []
};

json2xml.convert = function(json, options) {
  json2xml.settings = Ext.apply(json2xml.defaultSettings, options);
  return json2xml.convertToXml(json, json2xml.settings.rootTagName, '', 0);
}
json2xml.convertToXml = function(json, tagName, parentPath, depth) {
	var suffix = (json2xml.settings.formatOutput) ? '\r\n' : '';
	var indent = (json2xml.settings.formatOutput) ? json2xml.getIndent(depth) : '';
	var xmlTag = (tagName == json2xml.settings.rootTagName) ? indent + '<' + tagName + ' ' + json2xml.settings.rootTagAttributes : indent + '<' + tagName;
	var children = '';
		
	for (var key in json) {
		if (json.hasOwnProperty(key)) {
			var propertyPath = parentPath + key;
			var propertyName = json2xml.getPropertyName(parentPath, key);
			// element not in ignore array, process
			if (json2xml.settings.ignore.indexOf(propertyPath) == -1) {
				// array, create new child element
				if (Ext.isArray(json[key])) {
					children += json2xml.createNodeFromArray(json[key], propertyName, 
							propertyPath + '.', depth + 1, suffix);
				}
				// object, new child element aswell
				else if (typeof(json[key]) === 'object') {
					children += json2xml.convertToXml(json[key], propertyName, 
							propertyPath + '.', depth + 1);
				}
				// primitive value property as attribute
				else {
					// unless it's explicitly defined it should be node
					if (json2xml.settings.nodes.indexOf(propertyPath) != -1) {
						children += json2xml.createTextNode(propertyName, json[key], 
								depth, suffix);
					}
					else {
						xmlTag += ' ' + propertyName + '="' +  json[key] + '"';
					}
				}
			}
		}
	}
	// close tag properly
	if (children !== '') {
		xmlTag += '>' + suffix + children + indent + '</' + tagName + '>' + suffix;
	}
	else {
		xmlTag += '/>' + suffix;
	}
	return xmlTag;		
};
	
/**
	* Creates indent string for provided depth value. See settings for details.
	* 
	* @param depth
	* @return indent string 
	*/
json2xml.getIndent = function(depth) {
	var output = '';
	for (var i = 0; i < depth; i++) {
		output += json2xml.settings.indentString;
	}
	return output;	
};
	
/**
	* Checks settings.replace array for provided name, if it exists returns
	* replacement name. Else, original name is returned.
	* 
	* @param parentPath path to this element's parent
	* @param name name of element to look up
	* @return element's final name
	*/
json2xml.getPropertyName = function(parentPath, name) {
	var index = json2xml.settings.replace.length;
	var searchName = parentPath + name;
	while (index--) {
		// settings.replace array consists of {original : replacement} 
		// objects 
		if (json2xml.settings.replace[index].hasOwnProperty(searchName)) {
			return json2xml.settings.replace[index][searchName];
		}
	}
	return name;
};
	
/**
	* Creates XML node from javascript array object.
	* 
	* @param source 
	* @param name XML element name
	* @param path parent element path string
	* @param depth
	* @param suffix node suffix (whether to format output or not)
	* @return XML tag string for provided array
	*/
json2xml.createNodeFromArray = function(source, name, path, depth, suffix) {
	var xmlNode = '';
	if (source.length > 0) {
		for (var index in source) {
	    if (typeof(source[index]) !== 'function') {
			  // array's element isn't object - it's primitive value, which
			  // means array might need to be converted to text nodes
	      if (typeof(source[index]) !== 'object') {
	        // empty strings will be converted to empty nodes
	        if (source[index] === "") {
	          xmlNode += json2xml.getIndent(depth) + '<' + name + '/>' + suffix;                    
	        }
	        else {
	          var textPrefix = (json2xml.settings.formatTextNodes) 
              ? suffix + json2xml.getIndent(depth + 1) : '';
            var textSuffix = (json2xml.settings.formatTextNodes)
        	    ? suffix + json2xml.getIndent(depth) : '';	        				
	              xmlNode += json2xml.getIndent(depth) + '<' + name + '>' 
	                  + textPrefix + source[index] + textSuffix 
	                  + '</' + name + '>' + suffix;                                              
          }
	      }
	      // else regular conversion applies
	      else {
	        xmlNode += json2xml.convertToXml(source[index], name, path, depth);
	      }					
  		}
		}
	}
	// array is empty, also creating empty XML node		
	else {
		xmlNode += json2xml.getIndent(depth) + '<' + name + '/>' + suffix;
	}
	return xmlNode;
};	
	
/**
	* Creates node containing text only.
	* 
	* @param name node's name
	* @param text node text string
	* @param parentDepth this node's parent element depth
	* @param suffix node suffix (whether to format output or not)
	* @return XML tag string
	*/
json2xml.createTextNode = function(name, text, parentDepth, suffix) {
	// unformatted text node: <node>value</node>
	// formatting includes value indentation and new lines
	var textPrefix = (json2xml.settings.formatTextNodes) 
		? suffix + json2xml.getIndent(parentDepth + 2) : ''; 
	var textSuffix = (json2xml.settings.formatTextNodes)
		? suffix + json2xml.getIndent(parentDepth + 1) : '';
	var xmlNode = json2xml.getIndent(parentDepth + 1) + '<' + name + '>'
				+ textPrefix + text + textSuffix 
				+ '</' + name + '>' + suffix;
	return xmlNode;
};