/*
SoftXpath2 - a JSON equivalent of SoftXpath
Copyright 2013 Mpowered Ventures Ltd.

This program is free software under the terms of the 
GNU General Public License version 2 as published by the Free 
Software Foundation. It is distributed without any warranty.

Dedicated to the memory of Paul Walker who passed away tragically during the dev/testing of this module on Nov 30, 2013.
Thank you for the memories we will forever have of your work in the movie Timeline.
*/

function SoftXpath() {
	var xmljson;
	// TESTING vvvvv
	// to enable testing, uncomment all the testing blocks in the code below
	// make sure /FieldWorksX/softxpath.js exists (this is usually renamed to softxpath.js.nogo)
	// make sure that app.json has the line "path": "../FieldWorksX/softxpath.js"
//	var xpathqa = new SoftXpathQA();
	// TESTING ^^^^^
	
	this.loadXML = function(xmlstring, debug, debugLogCtrlr) {
		// TESTING vvvvv
//		xpathqa.loadXML(xmlstring);
		// TESTING ^^^^^
		if (debug) {
			debugLogCtrlr.logEvent('loadXML: start', true);
		}
		xmlstring=xmlstring.replace(/<\?[^>]*>/g,"").replace(/<\![^>]*>/g,""); // gets rid of <?xml version...
		var root = xmlstring.substring(1, xmlstring.indexOf('>')); // the root node of this xml code, e.g. getauth
		xmljson = xml2json.parser(xmlstring, debug, debugLogCtrlr)[root]; // go down one level from root
		return xmljson;
	};
	
	this.selectNodes = function(xpath) {
		var toeval, i, results = [];
		// create the json eval depending on whether this is a global or fixed query
		if (xpath.substring(0, 4) === '//*/') {
			toeval = 'xmljson' + xpath.substring(3).replace(/\//g, '.'); // the eval string to use to get at the branch
		}
		else if (xpath.substring(2, 1) !== '*') {
			toeval = 'xmljson' + xpath.substring(xpath.lastIndexOf('/')).replace(/\//g, '.'); // the eval string to use to get at the branch
		}
		else {
			console.log('SoftXpath2: unhandled query style: ' + xpath);
			return results; // not handling this kind of a query yet
		}
			
		var tail = toeval.substring(toeval.lastIndexOf('.') + 1); // e.g. for xmljson.thecodes.item, tail would be item
//		console.log('SoftXpath2: toeval: ' + toeval);
//		console.log('SoftXpath2: tail: ' + tail);
//		if (tail === 'txml') {
//			debugger;
//		}
		var text, mexml;
		try {
//			if (toeval === 'xmljson.permcondlist.x') {
//				debugger;
//			}
			var branch = eval(toeval);
			if (typeof branch !== 'undefined') { // found something
				if (branch === null) {
					results[results.length] = {
						nodeName: tail,
						text: '',
						me: {
							xml: '<' + tail + '/>'
						}
					};
				}
				else if (typeof(branch) === 'object') {
					if (branch.length) { // an array
						for (i = 0; i < branch.length; i++) {
							if (branch[i] === null) {
								text = '';
								mexml = '<' + tail + '/>';
							}
							else if (typeof(branch[i]) === 'object') {
								text = 'NA';
								mexml = '<' + tail + '>' + this.json2xml(branch[i]) + '</' + tail + '>';
							}
							else {
								text = this.textUnescape(branch[i]);
								mexml = '<' + tail + '>' + branch[i] + '</' + tail + '>';
							}
							results[results.length] = {
								nodeName: tail,
								text: text,
								me: {
									xml: mexml
								}
							};
						}
					}
					else { // single item, children
						text = 'NA';
						mexml = '<' + tail + '>' + this.json2xml(branch) + '</' + tail + '>';
						results[results.length] = {
							nodeName: tail,
							text: text,
							me: {
								xml: mexml
							}
						};
					}
				}
				else { // single item, no children
					mexml = '<' + tail + '>' + branch + '</' + tail + '>';
					results[results.length] = {
						nodeName: tail,
						text: this.textUnescape(branch),
						me: {
							xml: mexml
						}
					};
				}
			}
		}
		catch (e1) {}
		
		// TESTING vvvvv
/*		console.log('QA TESTING: ON');
		var j, qavalue, myvalue;
		var resultsqa = xpathqa.selectNodes(xpath);
		if (resultsqa.length != results.length) {
			console.log('QA ERROR: toeval: ' + toeval);
			console.log('QA ERROR: results array lengths do not match');
			console.log('QA length: ' + resultsqa.length + '; MY length: ' + results.length);
			debugger;
		}
		else {
			for (i = 0; i < resultsqa.length; i++) {
				qavalue = resultsqa[i].me.xml;
				myvalue = this.testingUnescape(results[i].me.xml);
				if (qavalue !== myvalue) {
					console.log('QA ERROR: toeval: ' + toeval);
					console.log('QA ERROR: xmls do not match at index: ' + i);
					console.log('QA length: ' + qavalue.length + '; MY length: ' + myvalue.length);
					console.log('QA: ' + qavalue);
					console.log('MY: ' + myvalue);
					for (j = 0; j < qavalue.length; j++) {
						if (qavalue.charAt(j) != myvalue.charAt(j)) {
							console.log('MY first varies from QA at position: ' + j);
							console.log('QA: ' + this.string_as_unicode_escape(qavalue.substring(j, j + 10)));
							console.log('MY: ' + this.string_as_unicode_escape(myvalue.substring(j, j + 10)));
							break;
						}
					}
					debugger;
				}
				qavalue = resultsqa[i].nodeName;
				myvalue = results[i].nodeName;
				if (qavalue !== myvalue) {
					console.log('QA ERROR: toeval: ' + toeval);
					console.log('QA ERROR: nodeNames do not match at index: ' + i);
					console.log('QA length: ' + qavalue.length + '; MY length: ' + myvalue.length);
					console.log('QA: ' + qavalue);
					console.log('MY: ' + myvalue);
					for (j = 0; j < qavalue.length; j++) {
						if (qavalue.charAt(j) != myvalue.charAt(j)) {
							console.log('MY first varies from QA at position: ' + j);
							console.log('QA: ' + this.string_as_unicode_escape(qavalue.substring(j, j + 10)));
							console.log('MY: ' + this.string_as_unicode_escape(myvalue.substring(j, j + 10)));
							break;
						}
					}
					debugger;
				}
				qavalue = resultsqa[i].text;
				myvalue = results[i].text;
				if ((myvalue !== 'NA') && (qavalue !== myvalue)) {
					console.log('QA ERROR: toeval: ' + toeval);
					console.log('QA ERROR: texts do not match at index: ' + i);
					console.log('QA length: ' + qavalue.length + '; MY length: ' + myvalue.length);
					console.log('QA: ' + qavalue);
					console.log('MY: ' + myvalue);
					for (j = 0; j < qavalue.length; j++) {
						if (qavalue.charAt(j) != myvalue.charAt(j)) {
							console.log('MY first varies from QA at position: ' + j);
							console.log('QA: ' + this.string_as_unicode_escape(qavalue.substring(j, j + 10)));
							console.log('MY: ' + this.string_as_unicode_escape(myvalue.substring(j, j + 10)));
							break;
						}
					}
					debugger;
				}
			}
		}
*/		// TESTING ^^^^^
		
		return results;
	};
	
	// TESTING vvvvv
/*	this.testingUnescape = function(value) {
		value=value.replace(/&apos;/g, "'");
		value=value.replace(/&quot;/g, '"');
		return value;
	};
	this.string_as_unicode_escape = function(input) {
//		function pad_four(input) {
//			var l = input.length;
//			if (l == 0) return '0000';
//			if (l == 1) return '000' + input;
//			if (l == 2) return '00' + input;
//			if (l == 3) return '0' + input;
//			return input;
//		}
		var output = '';
		for (var i = 0, l = input.length; i < l; i++) {
			//output += '\\u' + pad_four(input.charCodeAt(i).toString(16));
			output += '\\u' + (input.charCodeAt(i) + 0x10000).toString(16).slice(1);
		}
		return output;
	};
	// TESTING ^^^^^
*/
	this.textUnescape = function(value) {
		// in xml, there are only 5 characters that are escaped
		// replace any literal literals, to avoid fubaring ampersands
		value=value.replace(/&amp;amp;/g, "§amp;amp;");
		value=value.replace(/&amp;apos;/g, "§amp;apos;");
		value=value.replace(/&amp;lt;/g, "§amp;lt;");
		value=value.replace(/&amp;gt;/g, "§amp;gt;");
		value=value.replace(/&amp;quot;/g, "§amp;quot;");
		
		// unescape special chars
		value=value.replace(/&amp;/g, "&");
		value=value.replace(/&apos;/g, "'");
		value=value.replace(/&lt;/g, "<");
		value=value.replace(/&gt;/g, ">");
		value=value.replace(/&quot;/g, '"');

		// replace *back* any literal literals
		value=value.replace(/§amp;amp;/g, "&amp;amp;");
		value=value.replace(/§amp;apos;/g, "&amp;apos;");
		value=value.replace(/§amp;lt;/g, "&amp;lt;");
		value=value.replace(/§amp;gt;/g, "&amp;gt;");
		value=value.replace(/§amp;quot;/g, "&amp;quot;");
		
		// unescape any remaining ampersands
		value=value.replace(/&amp;/g, "&");
		
		return value;
	};

	/*	This work is licensed under Creative Commons GNU LGPL License.
		License: http://creativecommons.org/licenses/LGPL/2.1/
	   Version: 0.9
		Author:  Stefan Goessner/2006
		Web:     http://goessner.net/
	*/
	// code url: http://goessner.net/download/prj/jsonxml/
	
	this.json2xml = function(o, name) {
	   var toXml = function(v, name, ind) {
		  var xml = "";
		  if (v instanceof Array) {
			 for (var i=0, n=v.length; i<n; i++) {
				xml += ind + toXml(v[i], name, ind) + "\n";
			 }
		  }
		  else if (typeof(v) == "object") {
			 var hasChild = false;
			 xml += ind + "<" + name;
			 for (var m in v) {
				if (m.charAt(0) == "@") {
				   xml += " " + m.substr(1) + "=\"" + v[m].toString() + "\"";
				}
				else {
				   hasChild = true;
				}
			 }
			 xml += hasChild ? ">" : "/>";
			 if (hasChild) {
				for (m in v) {
				   if (m == "#text") {
					  xml += v[m];
				   }
				   else if (m == "#cdata") {
					  xml += "<![CDATA[" + v[m] + "]]>";
				   }
				   else if (m.charAt(0) != "@") {
					  xml += toXml(v[m], m, ind);
				   }
				}
				xml += (xml.charAt(xml.length-1)=="\n"?ind:"") + "</" + name + ">";
			 }
		  }
		  else {
	  	 	 xml += ind + "<" + name + ">" + v.toString() +  "</" + name + ">";
		  }
		  return xml;
	   }, xml="";
	   for (var m in o) {
			if (o.hasOwnProperty(m)) {
				if (name) {
					xml += toXml(o[m], name, "");
				}
				else {
					xml += toXml(o[m], m, "");
				}
			}
	   }
	   return xml;
	};

} 

