//These files do not work with Safari 3.0.3
//Check the version of browser and browser type
//if (BrowserDetect.browser == "Safari" && BrowserDetect.version < 3.2){
//	alert(BrowserDetect.browser + " " + BrowserDetect.version);
//}
//Download the script from browser.js in this folder or else
//http://www.quirksmode.org/js/detect.html

// clusterData will attempt to create categories and subcategories (if possible) for a data set.
// input:
//		data must be in the form of a matrix
//				the first row must contain the field names used
//		
//		clusterArray must be a 1d array of field names
//				if any field name is not found in data, an error will be thrown
//
// clusterData will search through the values in the matrix and attempt to sort them into 
// clusters, the subcategories will only be used in cases where there is more than one value
// in the clusterArray
function clusterData(data, clusterArray){
// If clustering by DATE, the DATE standard that is assumed is:
//		start time/date fieldname = iso_8601_start
//		end time/date fieldname = iso_8601_end
//		iso 8601 whose form looks like 2009-06-22T08:30:00 meaning June 22nd, 2009 @ 8:30am
//
// This standard must be adhered to or the clusterification will not work correctly
// When specifying an end date, please follow the same form.
	var inds = [];	//The array of indices that hold the fields
// Cycle through the clusterArray to find all indices for the fields in the data matrix
	for (var i = 0; i < clusterArray.length; i++){
// Use indexOfArr to find if a field specified in clusterArray is one of the fields in the data 
// matrix
// If clustering by a field called date, then go to a special function to perform the clustering
		if (clusterArray[i].toLowerCase() == "date"){
			var l = indexOfArr("iso_8601_start",data[0]);
// If a field is not found, return -1 and alert the failure/return
			if (l == -1){
				alert("'iso_8601_start' not found in data, required for sorting by date");
				return "";
			}
			var m = indexOfArr("iso_8601_end",data[0]);
// If a field is not found, return -1 and alert the failure/return
			if (m == -1){
				alert("'iso_8601_end' not found in data, required for sorting by date");
				return "";
			}
// Since both of the start and end date/times were found, add it to the list of indices to 
// cluster.  Also, the * will be used to denote the first index with the date time mix.
			inds[inds.length] = l;
			inds[inds.length] = m;
			
// Date is required therefore, the data must be converted to a form that can be clustered.
			
			data = clusterconvertDates(data, l, m);
			
		} else {
			if (i+1 < clusterArray.length){
				
				if (clusterArray[i].toLowerCase() == clusterArray[i+1].toLowerCase()){
					var l = indexOfArr(clusterArray[i],data[0]);
	// If a field is not found, return -1 and alert the failure/return
					if (l == -1){
						alert(cluster[i] + " not found in data");
						return "";
					}
					inds[inds.length] = "alphabetize";
					var l = indexOfArr(clusterArray[i].toLowerCase(),data[0]);
		// If a field is not found, return -1 and alert the failure/return
					if (l == -1){
						alert(cluster[i] + " not found in data");
						return "";
					}
					inds[inds.length] = l;
				} else {
					var l = indexOfArr(clusterArray[i],data[0]);
		// If a field is not found, return -1 and alert the failure/return
					if (l == -1){
						alert(cluster[i] + " not found in data");
						return "";
					}
		// Since the field we want to cluster was found, add it to the list of indices to cluster
					inds[inds.length] = l;
				}
			} else {
				var l = indexOfArr(clusterArray[i],data[0]);
	// If a field is not found, return -1 and alert the failure/return
				if (l == -1){
					alert(cluster[i] + " not found in data");
					return "";
				}
	// Since the field we want to cluster was found, add it to the list of indices to cluster
				inds[inds.length] = l;
			}
		}

	}
//---------------------------------------------------------------------------------------
// The work horse of the clustering algorithm
//---------------------------------------------------------------------------------------

// The clusters will be length of clusterArray deep, precedence in clusterArray will denote how 
// the items are clustered
// clusters will have the structure
//
// clusters ['a', subcluster_1, 'b', subcluster_2, ... , 'n', subcluster_n]
// subcluster1 ['1', subcluster_1, '2', subcluster_2, ... , 'n', subcluster_n]
// subsubcluster1 ['i', subcluster_1, 'ii', subcluster_2, ... , 'n', subcluster_n]
//		.
//		.
//		.
	var clusters = [];
	var tempLength = 0;
// Start the cluster at row 1 because row 0 contains the field names for the data matrix
	for (var i = 1; i < data.length; i++) {
		clusters = clusterAdd(clusters, 0, inds, data[i]);
		if (tempLength == 0){
			if (clusters[0] == null){
				clusters = clusters[1];
			}
			tempLength = clusters.length;
		} else if (clusters[clusters.length - tempLength] == null) {
			clusters[clusters.length - tempLength] = clusters[clusters.length - tempLength+1][0];
			clusters[clusters.length - tempLength+1] = clusters[clusters.length-tempLength+1][1];
		}
	}
	
// Return the clusters
	return clusters;
}

// clusterAdd will add a point: pt to the current cluster matrix curr
// this will recursively find a place to place the cluster
// cInd is the current field index to look at
// It follows the precedence in inds (holds the field name indices or the keys to the fields)
function clusterAdd(curr, cInd, inds, pt){
// If cInds gets too deep, return the current cluster matrix
	if (cInd >= inds.length) {
		return ""; 
	}
	
// Cycle through the current level and look for a subcluster that would be where to 
// place the new point
	for (var i = 0; i < curr.length; i += 2){
		var ast = /\*/.test(inds[cInd]);
		if (!ast){
			if (inds[cInd] == "alphabetize"){
				if (curr[i] == pt[inds[cInd+1]].slice(0,1)){
					curr[i+1] = clusterAdd(curr[i+1], cInd+2, inds, pt);
					return curr;
				}
			}
			if (pt[inds[cInd]] == curr[i]){
				curr[i+1] = clusterAdd(curr[i+1], cInd+1, inds, pt);
				return curr;
			}
		} else {
			slicedInd = inds[cInd].slice(0,inds[cInd].length-1);
			date = pt[slicedInd] + " &ndash; " + pt[inds[cInd+1]];
			if (date == curr[i]){
				curr[i+1] = clusterAdd(curr[i+1], cInd+2, inds, pt);
				return curr;
			}
			time = pt[slicedInd].slice(pt[slicedInd].indexOf("T"));
			nTime = pt[inds[cInd+1]].slice(pt[slicedInd].indexOf("T"));
		}
	} 

// Since none of the subclusters were a good place to put the point, make a new subcluster
// Recursively add the new point to the subcluster
// Test if the index in question has the asterisk that denotes whether or not this is a date
	var ast = /\*/.test(inds[cInd]);
	if (ast){
// Slice off the asterisk and create the cluster name with a dash between the two dates.  These
// these will be changed to a calendar date for the print, not at the moment.
		slicedInd = inds[cInd].slice(0,inds[cInd].length-1);
		date = pt[slicedInd] + " &ndash; " + pt[inds[cInd+1]];
		curr[curr.length] = date;
		curr[curr.length] = [];
		curr[curr.length-1] = clusterAdd([], cInd+2, inds, pt);
		
//To make the pt's data available and not printed, the deepest cluster is
//an array with a space to start with and the data following.
		if (curr[curr.length-1] == "") {
			curr[curr.length-1] = [""].concat(pt);
		}
		return curr;
	}
	//alert(inds[cInd-1]);
	if (inds[cInd-1] == "alphabetize"){
		//alert(pt[inds[cInd+1]].slice(0));
		//alert(inds)
		curr[curr.length] = pt[inds[cInd]].slice(0,1);
	} else {
		curr[curr.length] = pt[inds[cInd]];
	}
	
	curr[curr.length] = [];
	curr[curr.length-1] = clusterAdd([], cInd+1, inds, pt);
	
//To make the pt's data available and not printed, the deepest cluster is
//an array with a space to start with and the data following.
	if (curr[curr.length-1] == "") {
		curr[curr.length-1] = [""].concat(pt);
	}
	return curr;
}

// clusterConvertDates will convert all dates from iso_8601_start and iso_8601_end to dates that 
// can be clustered in the form of two columns.
function clusterconvertDates(data, startInd, endInd) { 
// Cycle through dates
// convert startInd to the date
// convert endInd to the time	
	for (var i = 0 ; i < data.length; i++) {
// Slice off the dates for the start and end dates
		dateS = data[i][startInd].slice(0, data[i][startInd].indexOf("T"));
		dateE = data[i][endInd].slice(0, data[i][endInd].indexOf("T"));
		var date =  convertDate(dateS);
		if (!isNaN(date)){
			// Change the column values
			data[i][startInd] = data[i][startInd];
			data[i][endInd] = " ";		
		} else {
			if (dateS != dateE){
				date += " &ndash; " + convertDate(dateE);
			}
	// Slice off the times for the start and end dates
			timeS = data[i][startInd].slice(data[i][startInd].indexOf("T")+1);
			timeE = data[i][endInd].slice(data[i][endInd].indexOf("T")+1);
			time = convertTime(timeS);
			if (timeS != timeE){
				time += " &ndash; " + convertTime(timeE);
			}
			
	// Change the column values
			data[i][startInd] = date;
			data[i][endInd] = time;
		}
	}
	
	return data;
}

// clusterPrint will recursively print whatever is in the cluster
// styles = contain an array of tags that need to applied to a certain level
// styles format = ["<opening tag>", "</closing tag>", level, ...];
// 					i.e.
//					["<strong>", "</strong>", 1, "<p>", "</p>", 3];
// results in
// <strong>Level 1 cluster</strong>
// <ul>
//   <li>Level 2 Cluster
//	     <ul>
//			<li><p>Level 3 cluster</p>
//				<ul>...</ul>
//			</li>
//		 </ul>
//	 </li>
// </ul>
// throws error if level is not valid ie not a number, or negative.
function clusterPrint(cluster, flag, styles, depth){

// Create an HTML representation of the current level in the cluster
	var HTML = "";
	if (cluster != null && cluster[0] != "") {
		//flag is used to denote whether we should add an ordered list to the current cluster name
		//flag == false for the highest cluster
		//flag == true for any subcluster
		//if we reach the last cluster than do not start a new list
		if (flag && cluster.length > 0) {
			HTML += "<ul class=\"unorderList"+depth+"\" id=\"unorderList"+depth+cluster[0].replace(/ /g, "").replace(/'/g,"").replace(/\"/g,"")+"\">";
		}
		//cluster is set up such that the name is every even entry
		//the subcluster is every odd entry directly after that
		for (var i = 0; i < cluster.length; i += 2) {
			//addLi is used to denote if the li opening and closing tags should be used
			//This gives the user the ability to specify their own li tags with classes
			//
			//If the li tag is not found in the styles for a certain depth then add just the 
			//normal li else let user specify the li
			
			// Try to find the depth if it is in the array of styles
			var ind = indexOfArr(depth, styles);
			if (ind != -1){
				//We have found a level, now we can either execute some javascript or simply
				// attach the style
				openingTag = styles[ind-2];
				closingTag = styles[ind-1];
				
				// Look to see if the user specified the li tag for a given depth
				// Also the flag will be used for any depth > 1
				if (flag && openingTag.indexOf("<li") == -1) {
					HTML += "<li>";
				}
				
				//Find the start of the place to evaluate javascript
				evalStart = openingTag.indexOf("eval(");
				if (evalStart != -1){
					//Slice the code out of the string and evaluate it
					tempStart = evalStart;
					tempEnd = evalStart + 1;
					tempCount = 1;
					while (tempCount > 0){
						tempStart = openingTag.indexOf("(", tempStart+1);
						if (tempStart != -1){
							tempCount += 1;
						}
						tempEnd = openingTag.indexOf(")", tempEnd+1);
						if (tempEnd != -1){
							tempCount -= 1;
						}
					}
					evalEnd = tempEnd-1;
					//evalEnd = openingTag.lastIndexOf(")");
					code = openingTag.slice(evalStart, evalEnd+1);	
					
					//Add the openingTag
					HTML += openingTag.slice(0, evalStart);
					
					//Evaluate the Code
					eval(code);
					
					//Add the rest of the tag
					HTML += openingTag.slice(evalEnd+1) + cluster[i] + closingTag;
				} else {
					HTML += openingTag + cluster[i] + closingTag;
				}
			} else {
				if (flag) {
					HTML += "<li>";
				}
				HTML += cluster[i];
			}
			//Need to add the link for all of the deepest subclusters
			HTML += clusterPrint(cluster[i+1], true, styles, depth+1);
			if (flag) {
				HTML += "</li>";
			}
		}
		if (flag && cluster.length > 0) {
			HTML += "</ul>";
		}
	}
// Return the current level in the cluster
	return HTML;
}

//Compare two numbers
//If equal return 0
//a < b return -1
//a > b return 1
function compareNums(a, b){
	if (a == b){ return 0; } else if (a < b){ return -1; } else { return 1;}
}

// format of the time is yyyy-mm-dd
// convert it to a date that is based on the 365 day calendar with date of week
function convertDate (date) {
//Test to see if the date is actually in the format we need, if not, then return nothing
	var iso8601 = /(\d\d\d\d?)-(\d\d?)-(\d\d?)/;
	if (!date.match(iso8601)){
		return "";
	}	
	
//Date object contains methods to convert from yyyy-mm-dd to a day index [0-6]
//This will give us a chance to get the day of the week and put the date into a form we want
	
//Month array will hold the potential month, start the array at 1 so that it coincides with  
//the month given in the date
	var d = new Date();
	var dayArr = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
	var monthArr = ["", "January", "Febuary", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
	
//slice date to give it to the different modifier functions
	year = date.slice(0,date.indexOf("-"));
	date = date.slice(date.indexOf("-")+1); 
	
	month = date.slice(0,date.indexOf("-"));
	//To fix a very peculiar bug with parseInt, this will make sure it parses correctly
	//08 and 09 produce 0 when put into parseInt
	//07 and 10 produce 7 and 10 respectively.  ODD
	
	if (month == "08" || month == "09"){
		month = month.slice(1);
	}
	day = date.slice(date.indexOf("-")+1);
	
	if (day == "08" || day == "09"){
		day = day.slice(1);
	}
	day = parseInt(day);
	
	d.setFullYear(year, month-1, day);
	
// Return the day of the week for the given date
	dOfWeek = dayArr[d.getDay()];
	
//Convert the above variables to string in the form:
// Monday, June 23
// dOfWeek, Month Day
	date = dOfWeek + ", " + monthArr[parseInt(month)] + " " + day;
	
	return date;
}

//format of arr is: [yyyy-mm-ddThh:mm:ss.ms, ... ,yyyy-mm-ddThh:mm:ss.ms]
// convert the array into these times and dates into - separated values
// the array is not sorted, so the order of the array is the order of the output
function convertMultipleDates (arr){
	var str = "";
	var prevTime = "";
	var prevDate = "";
	for (var i=0; i < arr.length; i++){
		var temp = arr[i][0];
		var date = temp.slice(0, temp.indexOf("T"));
		var time = temp.slice(temp.indexOf("T")+1);
		time = convertTime(time);
		date = convertDate(date);
		//If the dates or times are bad, then just return the ith element
		if (time == "" || date == ""){
			return arr[i];
		}
		
		if (i != 0){
			//If the previous time and date are exactly the same do nothing
			if (prevTime == time && prevDate == date){
				var do_nothing = true;
			//If the previous date is the same, do not include it, redundant
			} else if (prevDate == date){
				str += time;
			//Else just add the new date/time
			} else {
				str += date + " " + time;
			}
		} else {
			str += date + " <br />" + time;
		}
		prevDate = date;
		prevTime = time
		if (i+1 < arr.length){
			str += " &ndash; ";
		}
	}
	return str;
}

// format of the time is:  hh:mm:ss.ms
// convert it to a time that is not based on the 24-hour clock
function convertTime (time) { 
	var nTime = "";
	if (parseInt(time.slice(0,2)) > 12) {
		nTime += (parseInt(time.slice(0,2)) - 12) + time.slice(2) + "pm";
	} else {
		if (parseInt(time.slice(0,2)) < 10){
			nTime = parseInt(time.slice(1,2)).toString() + time.slice(2) + "am";
		} else {
			nTime = parseInt(time.slice(0,2)).toString() + time.slice(2) + "am";
		}
	}
	
	return nTime;
}

function contains1d(value, array1d){
	for (var j=0; j < array1d.length; j++){
		if (array1d[j] == value){
			return true;
		}
	}
	return false;
}

function indexOfArr(value, array1d){
	if (value == null) {
		return -1;
	}
	
	for (var j=0; j < array1d.length; j++){
		try {  // Try to see if the value is a string
			if (array1d[j] == value.toLowerCase()){
				return j;
			}
		} catch (e) { // Value is not a string
			try { //Try to see if the value is an integer
				if (array1d[j] == value){
					return j;
				}
			} catch (e) { //No idea, return error
				alert("Problem with value or array in indexOfArr(value, array1d)");
			}
		}
	}
	return -1;
}

function getDataMatrix(xmlDB, selectedCols, selectedValues){
	//getData(xmlDoc) gets the data from an XML database and formats it into a matrix
	//it walks through the data and then analyzes the data to put it into matrix format
	//
	//xmlDB is the XML Database that will be scraped
	//
	//Outputs the data in an matrix
	//The first row of the matrix are the field names
	//The 2nd - Nth rows are the items in the Database
	
	var rawArray = recurseFieldsIntoArray(xmlDB.childNodes, [], "");
	
	var dataMatrix = [];
	var fieldArray = [];
	fieldArray[0] = [];
	var allFields = [];
	var indicesToPrint = [];
	
	//Search through the field array: raw array for each column in selectedCols
	//If it is not there, then alert the user
	for(var i=0; i < selectedCols.length; i++){
		if (!contains1d(selectedCols[i], rawArray)){
			alert("Invalid Column Name: " + selectedCols[i]);
			return "";
		}							
	}
	
	//Search through the field array: raw array and look for all the fields
	for (var i=0; i < rawArray.length; i++){
		var flag = false;
		if (contains1d(rawArray[i], selectedCols)){
			indicesToPrint[indicesToPrint.length] = i;
			flag = true;
			j = selectedCols.length;
		}
		if (flag || selectedCols.length == 0){
			fieldArray[0][fieldArray[0].length] = rawArray[i];
			fieldArray[rawArray[i]] = i;
			fieldArray.length += 1;
		}
		allFields[allFields.length] = rawArray[i];
	}

	valsIndsToPrint = [];
	
	for (var k=0; k < selectedValues[0].length; k++){
		if (fieldArray[selectedValues[0][k]] == null){
			alert("Invalid Field to Search for: " + selectedValues[0][k]);
			return "";
		} else {
			valsIndsToPrint[valsIndsToPrint.length] = fieldArray[selectedValues[0][k]];
		}
	}
	dataMatrix[dataMatrix.length] = fieldArray[0];

	var rawArray = recurseFieldValuesIntoArray(xmlDB.childNodes, []);

	var tempArray = [];
	var flag;
	var noVals = false;
	for (var i=0; i < selectedValues.length; i += 1){
		if (selectedValues[i] == ""){
			noVals = true;
		}
	}
	
	for (var i=0; i < rawArray.length; i += allFields.length){
		flag = false;
		if (tempArray.length != 0){
			dataMatrix[dataMatrix.length] = tempArray;
			tempArray = [];
		}
		if (noVals){
			flag = true;
		} else {
			for (var j = i; j < i+allFields.length; j++){
				for (var k = 0; k < valsIndsToPrint.length; k++){
					if (j%allFields.length == valsIndsToPrint[k] && rawArray[j].toLowerCase() == selectedValues[selectedValues[0][k]]) {
						flag = true;
					}
				}
			}
		}
		
		if (selectedValues.length == 0){
			flag = true;
		}
		
		//This item should be added to the matrix
		if (flag){
			//Cycle through the item's values and add them to the array
			for (var j = i; j < i+allFields.length; j++){
				if (contains1d(allFields[j%allFields.length], fieldArray[0])){
					tempArray[tempArray.length] = rawArray[j];
				}
			}
		}
	}
	
	if (tempArray.length != 0){
		//This captures the last entry in the rawArray
		dataMatrix[dataMatrix.length] = tempArray;
	}
	
	//The data requested is too specific or not correct for the database
	if (dataMatrix.length == 1){
		alert("No results");
		return "";
	}

	dataMatrix[0]=dataMatrix[0].slice(0,dataMatrix[1].length);
	return dataMatrix;
}

function isNumeric(str){
	var num =/^[\d*|.]\d*$/.match(str);
	return num;
}

function LoadXML(xmlFile){
	alert("LOAD");
	var xmlDoc;
	if (window.ActiveXObject) {
		xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
		xmlDoc.async=false;
		xmlDoc.load(xmlFile);
	} else if(window.XMLHttpRequest) {
		var d = new XMLHttpRequest();
		d.open("GET", xmlFile, false);
		d.send(null);
		xmlDoc=d.responseXML;
	} else {
		xmlDoc = document.implementation.createDocument("","",null);
		xmlDoc.async=false;
		xmlDoc.load(xmlFile);
	}
	return xmlDoc;
}

function printDataMatrix(data, doNotPrintVector){
	//The first row of the data matrix must have field names for the do not print vector variable to work correctly
	//Get the indices of the fields that are not to be printed
	indices = [];
	for(var i=0; i < doNotPrintVector.length; i++){
		for(var j=0; j < data[0].length; j++){
			if (doNotPrintVector[i] == data[0][j]){
				indices[indices.length]=j;
			}
		}
	}

	document.write("<table cellspacing='2' cellpadding='2'>");
	for(var i=0; i < data.length; i++){
		document.write("<tr>");
		var k = 0;
		for(var j=0; j < data[i].length; j++){
			if (indices.length != 0 && k < indices.length){
				if (indices[k] != j){
					document.write("<td align='center'>" + data[i][j] + "</td>");
				} else {
					k = k + 1;
				}
			} else {
				document.write("<td align='center'>" + data[i][j] + "</td>");
			}
		}
		document.write("</tr>");
	}
	document.write("</table>");
}

function recurseChildNodes(childNs, spacing){
	for (var i=0; i < childNs.length; i++){
		if (childNs[i].nodeName == "#text"){
			var name = trim(childNs[i].nodeValue.replace("\n",""));
		}
		if (childNs[i].nodeName != "#text"){
			document.write(spacing + "<strong>" + childNs[i].nodeName + "</strong><br />");
		} else if (name.length != 0){
			document.write(spacing + name + "<br />");
		}
		recurseChildNodes(childNs[i].childNodes, spacing+"&nbsp;&nbsp;&nbsp;&nbsp;");
	}
	return;	
}

function recurseChildNodesAsList(childNs, layerNum){
	for (var i=0; i < childNs.length; i++){
		if (childNs[i].nodeName == "#text"){
			var name = trim(childNs[i].nodeValue.replace("\n",""));
		}
		if (childNs[i].nodeName != "#text"){
			document.write("<li><span class='Layer"+layerNum+"'>" + childNs[i].nodeName + "</span>");
			document.write("<ul>");
			recurseChildNodesAsList(childNs[i].childNodes, layerNum+1);
		} else if (name.length != 0){
			document.write("<li><em>" + name + "</em>");
		}
		
	}
	document.write("</li></ul>");
	return;	
}

function recurseFieldValuesIntoArray(childNs, array){
	if (childNs.length == 0) { return ""; }
	for (var i=0; i < childNs.length; i++){
		if (childNs[i].nodeName == "#text"){
			var value = trim(childNs[i].nodeValue.replace("\n",""));
			value = value.toString().replace(/\\break\\/g,"<br />");
			value = value.replace(/\\\\/g, "<").replace(/\$\$/g, ">");
		}
		
		if (childNs[i].nodeName != "#text"){
			var temp = recurseFieldValuesIntoArray(childNs[i].childNodes, array);
			if (temp != array){
				 array[array.length] = temp;
			}
		} else if (value.length != 0){
			return value;
		}
	}
	return array;	
}

function recurseFieldsIntoArray(childNs, array, name){
	if (childNs.length == 0 && name != "#text" && name != "xml") { return name; }
	for (var i=0; i < childNs.length; i++){
		if (childNs[i].nodeName == "#text"){
			var value = trim(childNs[i].nodeValue.replace("\n",""));
			if (value.length != 0){
				return childNs[i].parentNode.nodeName;
			}
		}
		var temp = recurseFieldsIntoArray(childNs[i].childNodes, array, childNs[i].nodeName);
		if (temp != "" && temp.constructor.toString().indexOf("Array") == -1){
			if (!contains1d(temp, array)){
				array[array.length] = temp;
			}
		}
	}
	return array;	
}

function sortData(datMatrix, field, direction){
	//datMatrix is the data matrix you want to sort
	//datMatrix must contain the fields as a first row
	//
	//field is the column that you are trying to sort
	//direction can be:
	//			ASC		-	"ASC"	-	Sort Field in Ascending Order
	//			DESC	-	"DESC"	-	Sort Field in Descending Order
	//			null	-	null	-	Do not Sort
	//Returns a data matrix with the information sorted or a null value
	if (direction != "ASC" && direction != "DESC" && direction != null){
		alert("The direction " + direction + " is not valid");
		return;
	}
	
	var flag = true;			//A flag to use for telling whether or not a field is there
								//Keep track of the index of the field we are trying to sort
	var fieldIndex = indexOfArr(field,datMatrix[0]);
	
	//fieldIndex must not equal -1 or else the field we are sorting by is invalid
	if (fieldIndex == -1){
		alert("The field: " + field + " is not valid to sort by");
		return;
	}
	
	//If the direction is null just return the data
	if (direction == null){
		return data;
	}
	
	var row_array = []			//Prepare a matrix that can be sorted
	for(var i = 1; i < datMatrix.length; i++){
//Row_array will hold the value of the field index in the first position and the rest of
//the information for the row after.
		row_array[row_array.length] = [datMatrix[i][fieldIndex], datMatrix[i]];
	}
	
	var func = guessType(datMatrix, fieldIndex);
	
	row_array.sort(func);	

	if (direction == "DESC"){
		row_array.reverse();
	}
	
	for (var i=0; i<row_array.length; i++) {
	    datMatrix[i+1] = row_array[i][1];	//The first row contains the fields
	}
	
	return datMatrix;
}

/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
//Adapted from http://www.kryogenix.org/code/browser/sorttable/
function guessType(data, fieldIndex) {
    // guess the type of a column based on its first non-blank row
    var sortfn = sort_alpha;
	var DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
	var time = /[\d|\d\d]:(\d\d)[am|pm]/;
	var iso8601 = /(\d\d\d\d?)-(\d\d?)-(\d\d?)T(\d\d?):(\d\d?):(\d\d?)/;
    for (var i=1; i<data.length; i++) {
      var text = data[i][fieldIndex];
      if (text != '') {
		if (text.match(iso8601)){
			return sort_iso8601;
		}
        if (text.match(time)){
			return sort_time;
		}
		if (text.match(/^-?[£$¤]?[\d,.]+%?$/)) {
        	return sort_numeric;
        }
        // check for a date: dd/mm/yyyy or dd/mm/yy 
        // can have / or . or - as separator
        // can be mm/dd as well
        possdate = text.match(DATE_RE)
        if (possdate) {
          // looks like a date
          first = parseInt(possdate[1]);
          second = parseInt(possdate[2]);
          if (first > 12) {
            // definitely dd/mm
            return sort_ddmm;
          } else if (second > 12) {
            return sort_mmdd;
          } else {
            // looks like a date, but we can't tell which, so assume
            // that it's dd/mm (English imperialism!) and keep looking
            sortfn = sort_ddmm;
          }
        }
      }
    }
    return sortfn;
  }
  /* sort functions
     each sort function takes two parameters, a and b
     you are comparing a[0] and b[0] */
function sort_numeric(a,b) {
    aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));
    if (isNaN(aa)) aa = 0;
    bb = parseFloat(b[0].replace(/[^0-9.-]/g,'')); 
    if (isNaN(bb)) bb = 0;
    return aa-bb;
}
function sort_alpha(a,b) {
    if (a[0]==b[0]) return 0;
    if (a[0]<b[0]) return -1;
    return 1;
}
function sort_ddmm(a,b) {
	var DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
    mtch = a[0].match(DATE_RE);
    y = mtch[3]; m = mtch[2]; d = mtch[1];
    if (m.length == 1) m = '0'+m;
    if (d.length == 1) d = '0'+d;
    dt1 = y+m+d;
    mtch = b[0].match(DATE_RE);
    y = mtch[3]; m = mtch[2]; d = mtch[1];
    if (m.length == 1) m = '0'+m;
    if (d.length == 1) d = '0'+d;
    dt2 = y+m+d;
    if (dt1==dt2) return 0;
    if (dt1<dt2) return -1;
    return 1;
}

function sort_mmdd(a,b) {
	var DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
    mtch = a[0].match(DATE_RE);
    y = mtch[3]; d = mtch[2]; m = mtch[1];
    if (m.length == 1) m = '0'+m;
    if (d.length == 1) d = '0'+d;
    dt1 = y+m+d;
    mtch = b[0].match(DATE_RE);
    y = mtch[3]; d = mtch[2]; m = mtch[1];
    if (m.length == 1) m = '0'+m;
    if (d.length == 1) d = '0'+d;
    dt2 = y+m+d;
    if (dt1==dt2) return 0;
    if (dt1<dt2) return -1;
    return 1;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
function sort_time(a,b){
	var am = /.*am/;
	var pm = /.*pm/;
    amA = am.test(a[0]);
	pmA = pm.test(a[0]);
	if (!amA && !pmA){
		alert("Times Must Contain AM or PM");
		return;
	}
    amB = am.test(b[0]);
	pmB = pm.test(b[0])
	if (!amB && ! pmB){
		alert("Times Must Contain AM or PM");
		return;
	}
	var time = /(\d\d?):(\d\d?)[am|pm]/;
	if ((amA && amA == amB) || (pmA && pmA == pmB)){
		mtch = a[0].match(time);
    	if (mtch[1].length == 1){ mtch[1] = '0'+mtch[1]; }
		t1 = mtch[1] + mtch[2];
		mtch = b[0].match(time);
		if (mtch[1].length == 1){ mtch[1] = '0'+mtch[1]; }
		t2 = mtch[1] + mtch[2];
	} else {
		if (amA && pmB){
			return -1;
		} else if (pmA && amB){
			return 1;
		}
	}
    if (t1==t2) return 0;
    if (t1<t2) return -1;
    return 1;	
}

function sort_iso8601(a, b){
	var iso8601 = /(\d\d\d\d?)-(\d\d?)-(\d\d?)T(\d\d?):(\d\d?):(\d\d?)(.\d*?)/;
	mtchA = a[0].match(iso8601);
	mtchB = b[0].match(iso8601);
	for (var i = 1; i < mtchA.length; i++){
		var temp = compareNums(mtchA[i], mtchB[i]);
		if (temp != 0){
			return temp;
		}
	}
	return 0;
}

function trim(str){
	return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}

function urlencode(str){
    // http://kevin.vanzonneveld.net
    // +   original by: Philip Peterson
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +      input by: AJ
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // %          note: info on what encoding functions to use from: http://xkr.us/articles/javascript/encode-compare/
    // *     example 1: urlencode('Kevin van Zonneveld!');
    // *     returns 1: 'Kevin+van+Zonneveld%21'
    // *     example 2: urlencode('http://kevin.vanzonneveld.net/');
    // *     returns 2: 'http%3A%2F%2Fkevin.vanzonneveld.net%2F'
    // *     example 3: urlencode('http://www.google.nl/search?q=php.js&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a');
    // *     returns 3: 'http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3Dphp.js%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a'

    var histogram = {}, histogram_r = {}, code = 0, tmp_arr = [];
    var ret = str.toString();
    
    var replacer = function(search, replace, str) {
        var tmp_arr = [];
        tmp_arr = str.split(search);
        return tmp_arr.join(replace);
    };
    
    // The histogram is identical to the one in urldecode.
    histogram['!']   = '%21';
    histogram['%20'] = '+';
    
    // Begin with encodeURIComponent, which most resembles PHP's encoding functions
    ret = encodeURIComponent(ret);
    
    for (search in histogram) {
        replace = histogram[search];
        ret = replacer(search, replace, ret) // Custom replace. No regexing
    }
    
    // Uppercase for full PHP compatibility
    return ret.replace(/(\%([a-z0-9]{2}))/g, function(full, m1, m2) {
        return "%"+m2.toUpperCase();
    });
    
    return ret;
}

function walkDB(xmlDB, asList){
	if (asList){
		document.write("<ul>");
		recurseChildNodesAsList(xmlDB.childNodes, 0);
	} else {
		recurseChildNodes(xmlDB.childNodes, "");
	}
}
