/*  DepressedPress.com dpYODEL

Author: Jim Davis, the Depressed Press of Boston
Date: October 2, 2005
Contact: webmaster@depressedpress.com
Website: www.depressedpress.com

dpWDDX provides JavaScript support for the WDDX (Web Distributed Data Exchange) XML dialect.

	+ The function provides serialization and deserialization services compliant with the full WDDX standard.

Copyright (c) 1996-2005, The Depressed Press of Boston (depressedpres.com)

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

+) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 

+) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 

+) Neither the name of the DEPRESSED PRESS OF BOSTON (DEPRESSEDPRESS.COM) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

dpWDDX = function(Action, Input) {

	// Check the "Action" argument
	if ( typeof Action != "string" ) {
		Action = ""
	} else {
		Action = Action.toLowerCase()
	};
	if ( Action != "serialize" && Action != "deserialize" ) {
		throw new Error("dpWDDX could not understand the action passed to it.  Acceptable actions are 'serialize' or 'deserialize'.");
	};
		// Check the "Input" argument against the Action
	if ( Action == "serialize" && typeof Input != "object" ) {
		throw new Error("dpWDDX action 'serialize' requires an object to work with.");
	};
	if ( Action == "deserialize" ) {
		if ( typeof Action != "string" ) {
			throw new Error("dpWDDX action 'deserialize' requires an XML string to work with.");
		} else {
			if( window.ActiveXObject ) {
				var XMLDOM = new ActiveXObject("Microsoft.XMLDOM");
				XMLDOM.async="false";
				XMLDOM.loadXML(Input);
				Input = XMLDOM;
				if ( Input.parseError.errorCode != 0 ) {
					throw new Error("dpWDDX deserialize encountered the following error from the Microsoft.XMLDOM XML parser:\n" + Input.parseError.reason + "\n\n" + Input.parseError.srcText);
				};
			} else if ( document.implementation && document.implementation.createDocument ) {
				Input = new DOMParser().parseFromString(Input, "text/xml");
			} else {
				throw new Error("dpWDDX deserialize could not generate an XML DOM object.");
			};
		};
	};

		// Set the EncodedChars lists var as function globals.
		// Serialization Chars
	if ( typeof SerializeChars != "object" ) {
		var SerializeChars = new Object();
	};
		// Add simple chars (&amp; must come first!)
	SerializeChars["&"] = "&amp;";
	SerializeChars["<"] = "&lt;";
	SerializeChars[">"] = "&gt;";
		// Add codes for Control characters
	for (var CurCode = 0; CurCode < 32; CurCode++ ) {
   		if ( (CurCode != 9) && (CurCode != 10) && (CurCode != 13) ) {
           	var hex = CurCode.toString(16);
               if ( hex.length == 1 ) { hex = "0" + hex };
			SerializeChars[String.fromCharCode(CurCode)] = "<char code='" + hex + "'/>";
		};
	};
	for (var CurCode = 128; CurCode < 256; CurCode++ ) {
          	var hex = CurCode.toString(16);
		SerializeChars[String.fromCharCode(CurCode)] = "&#x" + CurCode.toString(16) + ";";
	};

		// Deserialization Chars
	if ( typeof DeserializeChars != "object" ) {
		var DeserializeChars = new Object();
	};
	DeserializeChars["&amp;"] = "&";
	DeserializeChars["&lt;"] = "<";
	DeserializeChars["&gt;"] = ">";
		// Add codes for Control characters
	for (var CurCode = 0; CurCode < 32; CurCode++ ) {
   		if ( (CurCode != 9) && (CurCode != 10) && (CurCode != 13) ) {
           	var hex = CurCode.toString(16);
               if ( hex.length == 1 ) { hex = "0" + hex };
			DeserializeChars["<char code='" + hex + "'/>"] = String.fromCharCode(CurCode);
		};
	};
	for (var CurCode = 128; CurCode < 256; CurCode++ ) {
          	var hex = CurCode.toString(16);
		DeserializeChars["&#x" + CurCode.toString(16) + ";"] = String.fromCharCode(CurCode);
	};


		// Decide, based on the action, what to call
	switch ( Action ) {
		case "serialize" :
			return "<wddxPacket version=\"1.0\"><header/><data>" + serialize(Input) + "</data></wddxPacket>";
		break;
		case "deserialize" :
			return deserialize(Input);
		break;
	};


		// Escape characters using the Encoded Chars tables
	function convertString(CurString) {
		switch ( Action ) {
			case "serialize" :
				EncodedChars = SerializeChars;
			break;
			case "deserialize" :
				EncodedChars = DeserializeChars;
			break;
		};
		var CurRegEx;
		for ( var CurChar in EncodedChars ) {
			if (typeof EncodedChars[CurChar] != "function") {;
				if ( CurChar != "\\" ) {
					CurRegEx = new RegExp(CurChar, "g");
				} else {
					CurRegEx = /\\/g;
				};
				CurString = CurString.replace(CurRegEx, EncodedChars[CurChar]);
			};
		};
		return CurString;	
	};


		// Convert Dates to iso8601
	function convertDate(CurDate) {

		switch ( Action ) {
			case "serialize" :
					// Check the input parameters
				if ( typeof CurDate != "object" || CurDate.constructor != Date ) {
					return null;
				};
					// Init DatePart vars
				var Year,Month,Day,Hours,Minutes,Seconds,Milliseconds,TimeZoneInfo
					// Get DateParts
				Year = ("000" + CurDate.getFullYear()).slice(-4);
				Month = ("0" + (CurDate.getMonth() + 1)).slice(-2);
				Day = ("0" + CurDate.getDate()).slice(-2);
				Hours = ("0" + CurDate.getHours()).slice(-2);
				Minutes = ("0" + CurDate.getMinutes()).slice(-2);
				Seconds = ("0" + CurDate.getSeconds()).slice(-2);
				Milliseconds = ("00" + CurDate.getMilliseconds()).slice(-3);
					// Get TimeZone Information
				var TimeZoneOffset = CurDate.getTimezoneOffset();
				TimeZoneInfo = (TimeZoneOffset >= 0 ? "-" : "+") + ("0" + (Math.floor(Math.abs(TimeZoneOffset) / 60))).slice(-2) + ":" + ("00" + (Math.abs(TimeZoneOffset) % 60)).slice(-2);
					// Return the date
				return Year + "-" + Month + "-" + Day + "T" + Hours + ":" + Minutes + ":" + Seconds + TimeZoneInfo;
			break;
			case "deserialize" :
					// Check the input parameters
				if ( typeof CurDate != "string" ) {
					return null;
				};
					// Set the fragment expressions
				var S = "[\\-/:.-]";
				var Yr = "((?:1[6-9]|[2-9][0-9])[0-9]{2})";
				var Mo = S + "((?:1[012])|(?:0[1-9])|[1-9])";
				var Dy = S + "((?:3[01])|(?:[12][0-9])|(?:0[1-9])|[1-9])";
				var Hr = "(2[0-4]|[01]?[0-9])";
				var Mn = S + "([0-5]?[0-9])";
				var Sd = "(?:" + S + "([0-5]?[0-9])(?:[.,]([0-9]+))?)?";
				var TZ = "(?:(Z)|(?:([\+\-])(1[012]|[0]?[0-9])(?::?([0-5]?[0-9]))?))?";
					// RegEx the input
					// First check: Just date parts (month and day are optional)
					// Second check: Full date plus time (seconds, milliseconds and TimeZone info are optional)
				var TF;
				if ( TF = new RegExp("^" + Yr + "(?:" + Mo + "(?:" + Dy + ")?)?" + "$").exec(CurDate) ) {} else if ( TF = new RegExp("^" + Yr + Mo + Dy + "T" + Hr + Mn + Sd + TZ + "$").exec(CurDate) ) {};
					// If the date couldn't be parsed, return null
				if ( !TF ) { return null };
					// Default the Time Fragments if they're not present
				if ( !TF[2] ) { TF[2] = 1 } else { TF[2] = TF[2] - 1 };
				if ( !TF[3] ) { TF[3] = 1 };
				if ( !TF[4] ) { TF[4] = 0 };
				if ( !TF[5] ) { TF[5] = 0 };
				if ( !TF[6] ) { TF[6] = 0 };
				if ( !TF[7] ) { TF[7] = 0 };
				if ( !TF[8] ) { TF[8] = null };
				if ( TF[9] != "-" && TF[9] != "+" ) { TF[9] = null };
				if ( !TF[10] ) { TF[10] = 0 } else { TF[10] = TF[9] + TF[10] };
				if ( !TF[11] ) { TF[11] = 0 } else { TF[11] = TF[9] + TF[11] };
					// If there's no timezone info the data is local time
				if ( !TF[8] && !TF[9] ) {
					return new Date(TF[1], TF[2], TF[3], TF[4], TF[5], TF[6], TF[7]);
				};
					// If the UTC indicator is set the date is UTC
				if ( TF[8] == "Z" ) {
					return new Date(Date.UTC(TF[1], TF[2], TF[3], TF[4], TF[5], TF[6], TF[7]));
				};
					// If the date has a timezone offset
				if ( TF[9] == "-" || TF[9] == "+" ) {
						// Get current Timezone information
					var CurTZ = new Date().getTimezoneOffset();
					var CurTZh = TF[10] - ((CurTZ >= 0 ? "-" : "+") + Math.floor(Math.abs(CurTZ) / 60))
					var CurTZm = TF[11] - ((CurTZ >= 0 ? "-" : "+") + (Math.abs(CurTZ) % 60))
						// Return the date
					return new Date(TF[1], TF[2], TF[3], TF[4] - CurTZh, TF[5] - CurTZm, TF[6], TF[7]);
				};
					// If we've reached here we couldn't deal with the input, return null
				return null;
			break;
		};
	};


		// Parse objects to WDDX
	function serialize(CurOb) {

		var Results = "";
		var CurFieldNames = "";

		switch ( getType(CurOb) ) {
	        case "object":
				Results += "<struct>";
				for ( var CurProp in CurOb ) {
					if ( getType(CurOb[CurProp]) != "function"  ) {
						Results += "<var name='" + CurProp + "'>" + serialize(CurOb[CurProp]) + "</var>";
					};
				};
				Results += "</struct>";
				break;
	        case "array":
				Results += "<array length=\'" + CurOb.length + "\'>";
				for ( var Cnt = 0; Cnt < CurOb.length; Cnt++ ) {
					Results += serialize(CurOb[Cnt]);
				};
				Results += "</array>";
				break;
	        case "function":
				break;
	        case "date":
				Results += "<dateTime>" + convertDate(CurOb) + "</dateTime>";
				break;
	        case "number":
				if ( isFinite(CurOb) ) {
					Results += "<number>" + CurOb.toString() + "</number>";
				} else {
	        	    Results += "<null />";
				};
				break;
	        case "string":
				Results += "<string>" + convertString(CurOb) + "</string>";
				break;
	        case "boolean":
				Results += "<boolean value='" + CurOb.toString() + "'/>";
				break;
	        case "binary":
				Results += "<binary length='" + CurOb.length + "'>" + CurOb.toString() + "</binary>";
				break;
	        case "null":
				Results += "<null />";
				break;
	        case "undefined":
				Results += "<null />";
				break;
	        case "unknown":
				Results += "<null />";
				break;
		};

			// Return Results
		return Results;


			// Add the "getType" function which offers more resolution than "typeof"
		function getType(Ob) {
		
			try {
				switch (typeof Ob) {
			        case "object":
						if ( Ob == null ) {
							return "null";
						} else if ( Ob.constructor == Date ) {
							return "date";
						} else if ( Ob.constructor == Array ) {
							return "array";
						} else if ( Ob.constructor == String ) {
							return "string";
						} else if ( Ob.constructor == Number ) {
							return "number";
						} else if ( Ob.constructor == Boolean ) {
							return "boolean";
						} else if ( Ob == undefined ) {
							return "undefined";
						} else {
							return "object";
						};
			        case "function":
						return "function";
			        case "number":
						return "number";
			        case "string":
						return "string";
			        case "boolean":
						return "boolean";
			        case "undefined":
						return "undefined";
			        default:
						return "unknown";
				};
			} catch (CurError) {
				return "unknown";
			};
		
		};

	};


		// Parse objects from WDDX
	function deserialize(CurXML) {

		var CurOutput = null;
	
			// Set a DataRoot shortcut
		var DataRoot = CurXML.getElementsByTagName("data").item(0);
		for ( var Cnt = 0; Cnt < DataRoot.childNodes.length; Cnt++ ) {
			var CurNode = DataRoot.childNodes.item(Cnt);
				// If the node is a valid WDDX node, deserialize it 
			switch ( CurNode.nodeName.toLowerCase() )	{
				case "null": case "boolean": case "number": case "dateTime": case "string": case "array": case "struct": case "recordset": case "binary":
					CurOutput = deserialize_Data(CurNode);
			};
		};

			// Return the output
		return CurOutput;

			// Parse Data
		function deserialize_Data(CurNode, CurMetaData) {

			var Output = null;

				// Construct the output
			switch ( CurNode.nodeName.toLowerCase() ) {
		        case "struct":
					Output = new Object();
					for ( var Cnt = 0; Cnt < CurNode.childNodes.length; Cnt++ ) {
							// Not all nodes are "var"
						if ( CurNode.childNodes.item(Cnt).nodeName == "var" ) {
							var CurVarNode = CurNode.childNodes.item(Cnt);
								// Get Key Name
							var CurKeyName = "";
							for ( var iCnt = 0; iCnt < CurVarNode.attributes.length; iCnt++ ) {
								var CurAttribute = CurVarNode.attributes[iCnt];
								if ( CurAttribute.name == "name" ) {
									CurKeyName = CurAttribute.value;
								};
							};
								// Get the data node
							for ( var iCnt = 0; iCnt < CurVarNode.childNodes.length; iCnt++ ) {
								var CurVarChildNode = CurVarNode.childNodes.item(iCnt);
								switch ( CurVarChildNode.nodeName )	{
									case "null": case "boolean": case "number": case "dateTime": case "string": case "array": case "struct": case "recordset": case "binary":
										// Call for more output recursively
									Output[CurKeyName] = deserialize_Data(CurVarChildNode);
								};
							};
						};
					};
					break;
				case "array":
					Output = new Array();
					for ( var Cnt = 0; Cnt < CurNode.childNodes.length; Cnt++ ) {
						var CurChildNode = CurNode.childNodes.item(Cnt);
						switch ( CurChildNode.nodeName )	{
							case "null": case "boolean": case "number": case "dateTime": case "string": case "array": case "struct": case "recordset": case "binary":
								// Call for more output recursively
							Output[Output.length] = deserialize_Data(CurChildNode);
						};
					};
					break;
		        case "recordset":
					Output = new Object();
					for ( var Cnt = 0; Cnt < CurNode.childNodes.length; Cnt++ ) {
							// Not all nodes are "var"
						if ( CurNode.childNodes.item(Cnt).nodeName == "field" ) {
							var CurFieldNode = CurNode.childNodes.item(Cnt);
								// Get Field Name
							var CurFieldName = "";
							for ( var iCnt = 0; iCnt < CurFieldNode.attributes.length; iCnt++ ) {
								var CurAttribute = CurFieldNode.attributes[iCnt];
								if ( CurAttribute.name == "name" ) {
									CurFieldName = CurAttribute.value;
								};
							};
								// Get the field data
							var FieldArray = new Array();
							for ( var iCnt = 0; iCnt < CurFieldNode.childNodes.length; iCnt++ ) {
								var CurFieldChildNode = CurFieldNode.childNodes.item(iCnt);
								switch ( CurFieldChildNode.nodeName )	{
									case "null": case "boolean": case "number": case "dateTime": case "string": case "array": case "struct": case "recordset": case "binary":
										// Call for more output recursively
									FieldArray[FieldArray.length] = deserialize_Data(CurFieldChildNode);
								};
							};
								// Add the array to the object
							Output[CurFieldName] = FieldArray;
						};
					};
					break;
		        case "datetime":
					if ( CurNode.childNodes.length == 0 ) {
						Output = new Date();
					} else if ( CurNode.childNodes.item(0).nodeType == 3 ) {
						var CurVal = CurNode.childNodes.item(0).nodeValue;
						Output = convertDate(CurVal);
					};
					break;
		        case "number":
					if ( CurNode.childNodes.length == 0 ) {
						Output = new Number();
					} else if ( CurNode.childNodes.item(0).nodeType == 3 ) {
						var CurVal = CurNode.childNodes.item(0).nodeValue;
						if ( CurVal == parseInt(CurVal) ) {
							Output = parseInt(CurVal);
						} else if( CurVal == parseFloat(CurVal) ) {
							Output = parseFloat(CurVal);
						};
					};
					break;
		        case "string":
					if ( CurNode.childNodes.length == 0 ) {
						Output = new String();
					} else if ( CurNode.childNodes.item(0).nodeType == 3 ) {
						Output = convertString(CurNode.childNodes.item(0).nodeValue);
					};
					break;
		        case "boolean":
					for ( var Cnt = 0; Cnt < CurNode.attributes.length; Cnt++ ) {
						var CurAttribute = CurNode.attributes[Cnt];
						if ( CurAttribute.name == "value" ) {
							Output = CurAttribute.value;
						};
					};
					break;
		        case "binary":
					Output = null;
					break;
		        case "null":
					Output = null;
					break;
			};

			return Output;

		};
	
	};


};
