Small JavaScript Goodies: WDDX

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Dormilich
    Recognized Expert Expert
    • Aug 2008
    • 8694

    Small JavaScript Goodies: WDDX

    if you want to use WDDX (Web Distributed Data Exchange), several programming languages offer functions to use it. However, WDDX is hardly used with JavaScript (I only found one useful library, which used the XML string), thus I wrote a WDDX library that directly operates on the parsed XML. Normally you get such a file via an XMLHttpRequest and by using the responseXML property you already have parsed XML document.

    here is the derserializer
    Code:
    /**
     * WDDX Deserializer for Javascript
     * File: mod_wddx_des.js
     * Author: Bertold von Dormilich (Dormilich@netscape.net), 2009
     * Contributor: Nate Weiss (nweiss@icesinc.com), 1999
     * See www.wddx.org for usage and updates
     */
    
    // Most of the code used in the simple data type conversion (bool, null, number, 
    // date) is taken from the original code. considerable changes were made for 
    // string (the text is already parsed), array and object (using a different 
    // parser model).
    
    // And of course the OOP style was changed to a more modern approach.
    // The main advantage over Nate's WDDX string parser is, that I don't 
    // have to parse the WDDX file, thus I can use DOM.
    
    // used K&R indent style, so it looks somewhat condensed...
    
    /**
     * Prototyping into the DOM interface. Although Firefox provides some 
     * properties that do the same, I can't rely on those yet.
     */
    Element.prototype.getElementChildren = function(tagname)
    {
    	var c, i = 0, elem = [];
    	tagname = tagname || "*";
    	while (c = this.childNodes[i++]) {
    		var tag = ("*" == tagname) ? true : (tagname == c.tagName);
    		if (1 == c.nodeType && tag) {
    			elem.push(c);
    		}
    	}
    	return elem;
    }
    
    Element.prototype.getFirstElementChild = function(tagname)
    {
    	var c, i = 0;
    	tagname = tagname || "*";
    	while (c = this.childNodes[i++]) {
    		var tag = ("*" == tagname) ? true : (tagname == c.tagName);
    		if (1 == c.nodeType && tag) {
    			return c;
    		}
    	}
    	return undefined;
    }
    
    /**
     * Wrapper object for deserialization.
     *
     * @param (Document) xmldoc     an XML document
     * @return (void)
     * @throws (Error)              source not conforming to the DOM
     */
    function WDDX(xmldoc)
    {
    	if (!(xmldoc instanceof Document)) {
    		throw new Error("Source is not a Document.");
    	}
    	// get the data & comment element
    	var data = xmldoc.getElementsByTagName("data")[0];
    	var comt = xmldoc.getElementsByTagName("comment")[0];
    	if (!data) {
    		throw new Error("WDDX document does not contain data.");
    	}
    	this.content = new WDDXNode(data.getFirstElementChild());
    	if (comt) {
    		this.comment = comt.firstChild.data;
    	}
    	this.setApplicationType();
    }
    
    /**
     * Setting a global for using timezone info.
     *
     * @param (bool) use            the useTimezonInfo variable used in getDate()
     * @return (void)
     */
    WDDX.prototype.setTimezoneInfo = function(use)
    {
    	window.useTimezoneInfo = Boolean(use);
    }
    
    /**
     * Setting a global for object classname detection. (some languages save it
     * as type attribute, some in the first <var>)
     *
     * @param (bool) app            application id setting the classname. defaults
     *                              to javascript.
     * @return (void)
     */
    WDDX.prototype.setApplicationType = function(app)
    {
    	window.wddx_application = app || "javascript";
    }
    
    /**
     * Start the deserialization.
     *
     * @return (void)
     */
    WDDX.prototype.deserialize = function()
    {
    	return this.content.parse();
    }
    
    /**
     * Object representing a node of the XML document. Checks for DOM 
     * compliance and extracting some core data (tag name, text content, 
     * element children).
     *
     * @param (Element) knoten      an XML Element node
     * @return (void)
     * @throws (Error)              source not conforming to the DOM
     */
    function WDDXNode(knoten)
    {
    	if (!(knoten instanceof Element)) {
    		throw new Error("Source is not an Element.");
    	}
    	// save the node for DOM manipulations
    	this.node = knoten;
    	// get basic properties
    	this.name = knoten.tagName.toLowerCase();
    	this.text = (knoten.firstChild) ? knoten.firstChild.data : null;
    	this.elements = knoten.getElementChildren();
    }
    
    /**
     * Get the Javascript types & values from the WDDX element.
     *
     * @return (mixed)              deserialized value/object
     * @throws (Error)              unsupported data type
     */
    WDDXNode.prototype.parse = function()
    {
    	switch (this.name) {
    		case "array"   : return this.getArray();
    		case "boolean" : return this.getBoolean();
    		case "datetime": return this.getDate();
    		case "null"    : return this.getNull();
    		case "number"  : return this.getNumber();
    		case "string"  : return this.getString();
    		case "struct"  : return this.getStruct();
    		// just in case...
    		case "var"     : 
    			var vn = new WDDXNode(this.elements[0]);
    			return vn.parse();
    		default: 
    			throw new Error("This type is not supported yet.");
    	}
    }
    
    /**
     * return the boolean value.
     *
     * @return (bool)               deserialized Boolean
     * @throws (Error)              WDDX element not a boolean type
     */
    WDDXNode.prototype.getBoolean = function()
    {
    	if ("boolean" != this.name) {
    		throw new Error("WDDX node is not a Boolean.");
    	}
    	return ("true" == this.node.getAttribute("value"));
    }
    
    /**
     * return the numeric value.
     *
     * @return (float)              deserialized Number
     * @throws (Error)              WDDX element not a number type
     */
    WDDXNode.prototype.getNumber = function()
    {
    	if ("number" != this.name) {
    		throw new Error("WDDX node is not a Number.");
    	}
    	return parseFloat(this.text);
    }
    
    /**
     * return null.
     *
     * @return (null)               deserialized value
     * @throws (Error)              WDDX element not a null type
     */
    WDDXNode.prototype.getNull = function()
    {
    	if ("null" != this.name) {
    		throw new Error("WDDX node is not a Null type.");
    	}
    	return null;
    }
    
    /**
     * return a Date object.
     *
     * @return (Date)               deserialized dateTime
     * @throws (Error)              WDDX element not a dateTime type
     */
    WDDXNode.prototype.getDate = function()
    {
    	if ("datetime" != this.name) {
    		throw new Error("WDDX node is not a Date.");
    	}
    	var NewDate, TempDate = new Date;
    	this.timezoneOffset = TempDate.getTimezoneOffset();
    	this.timezoneOffsetHours = Math.round(this.timezoneOffset/60);
    	this.timezoneOffsetMinutes = (this.timezoneOffset % 60);
    	
    	// Split date string into component parts
    	var Value = this.text.split("T");
    	var dtDateParts = Value[0].split("-");
    	if ( (Value[1].indexOf("-") == -1) && (Value[1].indexOf("+") == -1) ) {
    		// create native JS Date object
    		var dtTimeParts = Value[1].split(":");
    		NewDate = new Date(dtDateParts[0], dtDateParts[1]-1, dtDateParts[2], dtTimeParts[0], dtTimeParts[1], dtTimeParts[2]);
    	} else {  
    		// There is timezone info for this <dateTime></dateTime> element.
    		// Get just the timezone info by getting everything after the "-" or "+"
    		if (Value[1].indexOf("-") > -1) {
    			dtTimeTZParts = Value[1].split("-");
    		} else {
    			dtTimeTZParts = Value[1].split("+");
    		}
    		var dtTimeParts = dtTimeTZParts[0].split(":");
    		
    		// Create a new JS date object (no timezone offsetting has happened yet)
    		NewDate = new Date(dtDateParts[0], dtDateParts[1]-1, dtDateParts[2], dtTimeParts[0], dtTimeParts[1], dtTimeParts[2]);
    		
    		// If we are supposed to do timezone offsetting
    		if (true == window.useTimezoneInfo) { 
    			var dtTZParts = dtTimeTZParts[1].split(":");
    			var dtOffsetHours = parseInt(dtTZParts[0]);
    			var dtOffsetMins  = parseInt(dtTZParts[1]);
    			if (Value[1].indexOf("-") > -1) {
    				dtOffsetHours = this.timezoneOffsetHours - dtOffsetHours;
    				dtOffsetMins  = this.timezoneOffsetMinutes - dtOffsetMins;
    			} else {
    				dtOffsetHours += this.timezoneOffsetHours;
    				dtOffsetMins  += this.timezoneOffsetMinutes;
    			}
    			NewDate.setHours(NewDate.getHours() - dtOffsetHours);
    			NewDate.setMinutes(NewDate.getMinutes() - dtOffsetMins);
    		}  
    	}
    	return NewDate;
    }
    
    /**
     * return the string value.
     *
     * @return (strin)              deserialized value
     * @throws (Error)              WDDX element not a string type
     */
    WDDXNode.prototype.getString = function()
    {
    	if ("string" != this.name) {
    		throw new Error("WDDX node is not a String.");
    	}
    	var Value = this.text;
    	// check for text and CDATA nodes
    	var l = this.node.childNodes.length;
    	if (l > 1) {
    		Value = "";
    		for (var i=0; i<l; i++) {
    			var knoten = this.node.childNodes[i];
    			if (3 == knoten.nodeType || 4 == knoten.nodeType) {
    				Value += String(knoten.data);
    			}
    		}
    	}
    	return Value;
    }
    
    /**
     * return an Array object.
     *
     * @return (Array)              deserialized array
     * @throws (Error)              WDDX element not a boolean type
     */
    WDDXNode.prototype.getArray = function()
    {
    	var JSArray = new Array;
    	// ArrayLength is the length of the WDDX-style array to parse
    	var ArrayLength = parseInt(this.node.getAttribute("length"));
    	// check the real size of the WDDX array
    	var leng  = this.elements.length;
    	// For each element in the WDDX array, set the corresponding
    	// Element in the JS array to the WDDX element's parsed value  
    	for (var c = 0; c < ArrayLength; c++) { 
    		// if the denoted array length is larger than the real length, 
    		// pad the array with undefined
    		if (c >= leng) {
    			JSArray[c] = undefined;
    		} else {
    			// parse the array elements
    			var item = new WDDXNode(this.elements[c]);
    			JSArray[c] = item.parse();
    		} 
    	}
    	return JSArray;
    }
    
    /**
     * return an Object object.
     *
     * @return (Object)             deserialized object
     * @throws (Error)              WDDX element not a boolean type
     */
    WDDXNode.prototype.getStruct = function()
    {
    	var JSObject, StructIndex, items;
    	// Call object constructor if a "type" attribute has been provided
    	if ("javascript" == window.wddx_application) {
    		type = String(this.node.getAttribute("type"));
    	}
    	// check if a php_class_name is provided--and should be used
    	var pcn = this.node.getFirstElementChild("var");
    	if ("php_class_name" == pcn.getAttribute("name")) {
    		if ("php" == window.wddx_application) {
    			try {
    				var kn = new WDDXNode(pcn.getFirstElementChild());
    				type = String(kn.getString());
    			} catch (e) {
    				type = false;
    			}
    		}
    		// always remove php_class_name variable
    		this.node.removeChild(pcn);
    	}
    	// call object constructor if provided
    	if (type && "function" == typeof window[type]) {
    		JSObject = new window[type];
    	} else {
    		JSObject = new Object;
    	}
    
    	// there should only be <var> children, but you never know...
    	items = this.node.getElementChildren("var");
    	for (var l, c = 0, l = items.length; c < l; c++) { 
    		// use the <var> element's child node 
    		var item = new WDDXNode(items[c].getFirstElementChild());
    		StructIndex = items[c].getAttribute("name");
    		JSObject[StructIndex] = item.parse();
    	}
    	return JSObject;
    }
    and this is the serializer
    Code:
    /**
     * WDDX Serializer for Javascript
     * File: mod_wddx_ser.js
     * Author: Bertold von Dormilich (Dormilich@netscape.net), 2009
     */
    
    /**
     * formats a number to have leading zeros if the number length 
     * is smaller than num.
     *
     * @param (int)                 length of the final number string
     * @return (string)             number with leading zeroes
     */
    Number.prototype.toDigits = function(num)
    {
    	var zahl = new String(this.valueOf());
    	for (var i=zahl.length; i<num; i++) {
    		zahl = "0" + zahl;
    	}
    	return zahl;
    }
    
    /**
     * returns the XML source code of an XML tag.
     *
     * @return (string)             source code of the element
     */
    Element.prototype.getString = function()
    {
    	// lowercase the tag names
    	var tag = this.tagName.toLowerCase();
    	// fix dateTime & wddxPacket
    	tag = ("datetime" == tag) ? "dateTime" : tag;
    	tag = ("wddxpacket" == tag) ? "wddxPacket" : tag;
    	// opening tag
    	var str = "<" + tag;
    	// attributes
    	var atb = this.attributes;
    	for (var l, i=0, l=atb.length; i<l; i++) {
    		if (i in atb) {
    			str += " " + atb[i].name + '="' + atb[i].value + '"';
    		}
    	}
    	var cn  = this.childNodes;
    	// if empty element
    	if (0 == cn.length) {
    		return str + "/>";
    	} else {
    		str += ">";
    	}
    	// loop through child nodes (text/cdata/elements)
    	for (var l, i=0, l=cn.length; i<l; i++) {
    		// text or cdata
    		if (3 == cn[i].nodeType || 4  == cn[i].nodeType) {
    			if (String.trim) {
    				str += cn[i].data.trim();
    			} else {
    				str += cn[i].data;
    			}
    		}
    		// elements (recursive)
    		else if (1 == cn[i].nodeType) {
    			str += cn[i].getString();
    		}
    	}
    	// closing tag
    	return str + "</" + tag + ">";
    }
    
    // http://www.webreference.com/programming/javascript/definitive2/index.html
    /**
     * Create a new Document object. If no arguments are specified,
     * the document will be empty. If a root tag is specified, the document
     * will contain that single root tag. If the root tag has a namespace
     * prefix, the second argument must specify the URL that identifies the
     * namespace.
     *
     * @param (string) rootTagName  tag name of the root element
     * @param (string) namespaceURL root namespace
     * @return (Document)           XML document
     */
    XML.newDocument = function(rootTagName, namespaceURL) {
    	if (!rootTagName) rootTagName = "";
    	if (!namespaceURL) namespaceURL = "";
    	if (document.implementation && document.implementation.createDocument) {
    		// This is the W3C standard way to do it
    		return document.implementation.createDocument(namespaceURL, rootTagName, null);
    	} else { // This is the IE way to do it
    		// Create an empty document as an ActiveX object
    		// If there is no root element, this is all we have to do
    		var doc = new ActiveXObject("MSXML2.DOMDocument");
    		// If there is a root tag, initialize the document
    		if (rootTagName) {
    			// Look for a namespace prefix
    			var prefix = "";
    			var tagname = rootTagName;
    			var p = rootTagName.indexOf(':');
    			if (p != -1) {
    				prefix = rootTagName.substring(0, p);
    				tagname = rootTagName.substring(p+1);
    			}
    			// If we have a namespace, we must have a namespace prefix
    			// If we don't have a namespace, we discard any prefix
    			if (namespaceURL) {
    				if (!prefix) prefix = "a0"; // What Firefox uses
    			}
    			else prefix = "";
    			// Create the root element (with optional namespace) as a
    			// string of text
    			var text = "<" + (prefix?(prefix+":"):"") +	 tagname +
    					(namespaceURL
    					 ?(" xmlns:" + prefix + '="' + namespaceURL +'"')
    					 :"") +
    					"/>";
    			// And parse that text into the empty document
    			doc.loadXML(text);
    		}
    		return doc;
    	}
    };
    
    /**
     * create a new (empty) WDDX document.
     *
     * @param (string) comment      comment text
     * @return (void)
     */
    function WDDXDocument(comment)
    {
    	// create a new XML document 
    	// <wddxpacket>
    	var doc = XML.newDocument("wddxPacket");
    	// <header>
    	var hd  = document.createElement("header");
    	if (comment) {
    		var cm = document.createElement("comment");
    		cm.appendChild(document.createTextNode(comment));
    		hd.appendChild(cm);
    	}
    	// <data>
    	this.data = document.createElement("data");
    	this.root = doc.firstChild;
    	this.root.appendChild(hd);
    	this.root.appendChild(this.data);
    	this.setTargetApplicationType();
    }
    /**
     * set flag for additional class names (if the type attribute is not used).
     * 
     * @param (string)              application identifyer
     * @return (void)
     */
    WDDXDocument.prototype.setTargetApplicationType = function(app)
    {
    	var app = (new String(app)).toLowerCase().valueOf();
    	this.applicationType = app || "javascript";
    }
    
    /**
     * return the WDDX document source code. 
     *
     * @return (string)             WDDX Document source code
     */
    WDDXDocument.prototype.toString = function()
    {
    	var str = '<?xml version="1.0" encoding="UTF-8" ?>';
    	return str + this.root.getString();
    }
    
    /**
     * public method to serialize a value and add it to the WDDX document.
     *
     * @param (mixed) value         the variable to serialize
     * @param (string) klasse       optional class name to use for deserialization
     * @return (void)
     */
    WDDXDocument.prototype.serialize = function(value, klasse)
    {
    	var wsv = this.serializeValue(value, klasse);
    	this.data.appendChild(wsv);
    }
    
    /**
     * internal method to serialize the passed value.
     *
     * @param (mixed) obj           variable to serialize
     * @param (string) klasse       optional class name to use for deserialization
     * @return (Element)            serialized value as WDDX element
     * @throws (Error)              invalid parameter type
     * @throws (Error)              parameter is an error instance
     */
    WDDXDocument.prototype.serializeValue = function(obj, klasse)
    {
    	// DOM objects
    	if (obj instanceof Node || obj instanceof NodeList || obj instanceof NamedNodeMap) {
    		throw new Error("DOM elements cannot be serialized.");
    	}
    	// default JavaScript objects
    	if (obj instanceof Error) {
    		throw obj;
    	}
    	if (obj instanceof Array) {
    		return this.serializeArray(obj);
    	}
    	if (obj instanceof Boolean) {
    		return this.serializeBool(obj);
    	}
    	if (obj instanceof Date) {
    		return this.serializeDate(obj);
    	}
    	if (obj instanceof Number) {
    		return this.serializeNum(obj);
    	}
    	if (obj instanceof String) {
    		return this.serializeString(obj);
    	}
    	if (obj instanceof Function || obj instanceof RegExp) {
    		throw new Error("Functions cannot be serialized.");
    	}
    	if (obj instanceof Object) {
    		return this.serializeObject(obj, klasse);
    	}
    	// primitives
    	if (undefined === obj) {
    		throw new Error("Undefined variables cannot be serialized.");
    	}
    	if (null === obj) {
    		return document.createElement("null");
    	}
    	if (false === obj || true === obj) {
    		return this.serializeBool(new Boolean(obj));
    	}
    	if (isFinite(obj)) {
    		return this.serializeNum(new Number(obj));
    	} else if (obj.charAt) {
    		return this.serializeString(new String(obj));
    	}
    }
    
    /**
     * serialize an Array.
     *
     * @param (Array) obj           array to serialize
     * @return (Element)            serialized value as WDDX element
     * @throws (TypeError)          obj not an array
     */
    WDDXDocument.prototype.serializeArray = function(obj)
    {
    	if (!(obj instanceof Array)) {
    		throw new TypeError("Supplied value is not an Array.");
    	}
    	var AE, LA, l = obj.length;
    	// create <array> element
    	LA = document.createAttribute("length");
    	LA.value = l;
    	AE = document.createElement("array");
    	AE.setAttributeNode(LA);
    	// create data elements of the array
    	for (var i=0; i<l; i++) {
    		try {
    			AE.appendChild(this.serializeValue(obj[i]));
    		} catch (e) {
    			alert(e.message);
    		}
    	}
    	return AE;
    }
    
    /**
     * serialize a Boolean.
     *
     * @param (bool) obj            boolean to serialize
     * @return (Element)            serialized value as WDDX element
     * @throws (TypeError)          obj not a boolean
     */
    WDDXDocument.prototype.serializeBool = function(obj)
    {
    	if (!(obj instanceof Boolean)) {
    		throw new TypeError("Supplied value is not a Boolean.");
    	}
    	var BE, VA;
    	// create <boolean>
    	VA = document.createAttribute("value");
    	VA.value = obj.toString();
    	BE = document.createElement("boolean");
    	BE.setAttributeNode(VA);
    	return BE;
    }
    
    /**
     * serialize a Date.
     *
     * @param (Date) obj            date to serialize
     * @return (Element)            serialized value as WDDX element
     * @throws (TypeError)          obj not a date object
     */
    WDDXDocument.prototype.serializeDate = function(obj)
    {
    	if (!(obj instanceof Date)) {
    		throw new TypeError("Supplied value is not a Date.");
    	}
    	// get UTC date parts 
    	var year = obj.getUTCFullYear();
    	var mon  = obj.getUTCMonth() + 1;
    	var day  = obj.getUTCDate().toDigits(2);
    	var hour = obj.getUTCHours().toDigits(2);
    	var min  = obj.getUTCMinutes().toDigits(2);
    	var sec  = obj.getUTCSeconds().toDigits(2);
    	var tzo  = obj.getTimezoneOffset();
    	// yyyy-mm-ddThh:mm:ss+hh:mm
    	var time = year + "-" + mon.toDigits(2) + "-" + day + "T" 
    	         + hour + ":" + min + ":" + sec + (tzo > 0 ? "-" : "+") 
    	         + Math.floor(Math.abs(tzo/60)).toDigits(2) + ":" + (tzo % 60).toDigits(2);
    	var DE;
    	// create <dateTime>
    	DE = document.createElement("dateTime");
    	DE.appendChild(document.createTextNode(time));
    	return DE;
    }
    
    /**
     * serialize a Number.
     *
     * @param (Number) obj          number to serialize
     * @return (Element)            serialized value as WDDX element
     * @throws (TypeError)          obj not a Number
     */
    WDDXDocument.prototype.serializeNum = function(obj)
    {
    	if (!(obj instanceof Number)) {
    		throw new TypeError("Supplied value is not a Number.");
    	}
    	var NE;
    	// create <number>
    	NE = document.createElement("number");
    	NE.appendChild(document.createTextNode(obj.toString()));
    	return NE;
    }
    
    /**
     * serialize a String.
     *
     * @param (string) obj          string to serialize
     * @return (Element)            serialized value as WDDX element
     * @throws (TypeError)          obj not a string
     */
    WDDXDocument.prototype.serializeString = function(obj)
    {
    	if (!(obj instanceof String)) {
    		throw new TypeError("Supplied value is not a String.");
    	}
    	var SE;
    	// create <string>
    	SE = document.createElement("string");
    	SE.appendChild(document.createTextNode(obj.toString()));
    	return SE;
    }
    
    /**
     * serialize an Object. except null, undefined everything
     * can be converted into an object.
     *
     * @param (object) obj          object to serialize
     * @return (Element)            serialized value as WDDX element
     * @throws (TypeError)          object is NULL or UNDEFINED
     */
    WDDXDocument.prototype.serializeObject = function(obj, klasse)
    {
    	if (null === obj || undefined === obj) {
    		throw new TypeError("Supplied value is a Primitive.");
    	}
    	var OE, VE, NA, TA;
    	// create <struct>
    	TA = document.createAttribute("type");
    //	TA.value = klasse || obj.constructor.name;
    	TA.value = obj.constructor.name;	// use native object for class name
    	OE = document.createElement("struct");
    	OE.setAttributeNode(TA);
    	// create target application class name
    	if ("javascript" != this.applicationType) {
    		try {
    			OE.appendChild(this.createClassName(klasse));
    		} catch (e) {
    			if (console) console.log(e.message);
    		}
    	}
    	for (var i in obj) {
    		if ("function" == typeof obj[i]) continue;
    		try {
    			// create <var>
    			NA = document.createAttribute("name");
    			NA.value = i;
    			VE = document.createElement("var");
    			VE.setAttributeNode(NA);
    			// create serialized value
    			VE.appendChild(this.serializeValue(obj[i]));
    			OE.appendChild(VE);
    		} catch (e) {
    			alert(e.message);
    		}
    	}
    	return OE;
    }
    
    /**
     * create a variable bearing the class name for the target application.
     *
     * @param (string) klasse       class name to serialize
     * @return (Element)            serialized class name (<var>)
     * @throws (Error)              unsupported appliction type
     */
    WDDXDocument.prototype.createClassName = function(klasse)
    {
    	var app = document.createElement("var");
    	var acn = document.createAttribute("name");
    	if ("php" == this.applicationType) {
    		acn.value = "php_class_name";
    	} else {
    		throw new Error("Unsupported Target Application Type.");
    	}
    	app.setAttributeNode(acn);
    	app.appendChild(this.serializeString(new String(klasse)));
    	return app;
    }
    because I’m too lazy, I just use some of my website’s JS to demonstrate:
    (note: XHR stands for the XMLHttpResponse object)
    Code:
    Artikel = function() {
    	function writeToDoc(paket, ident) { // the function to be executed, once the XHR returns
    		var base = document.getElementById(ident);
    		base.appendElement("blockquote", '', false);
    		[B]var xml = new WDDX(paket);[/B]
    		[B]var doc = xml.deserialize();[/B] // the deserialisation part
    		var bq = base.getElementsByTagName("blockquote")[0];
    		bq.appendElement("h5", doc.shift(), false);
    		for (var i=0, l = doc.length; i<l; i++) {
    			bq.appendElement([U]doc[i].name, doc[i].content, doc[i].attributes[/U]); // using the deserialized values
    		}
    	}
    
    	function doRequest() {
    		var art = new Ajax(Artikel.url);
    		[B]art.answer = function(xml, id) {[/B] // here is the XHR.responseXML passed down
    			[B]writeToDoc(xml, id);
    		}[/B]
    		this.removeAttribute("href"); // some HTML cosmetics
    		this.style.cursor = "pointer";
    		this.style.color = "#0080FF";
    		art.create(this.parentNode.id); // create the XHR object
    		var go = function() {
    			var sbm = {};
    			sbm[Artikel.qry] = this.parentNode.id;
    			art.submit(sbm); // send the query
    		}
    		this.addEvent("click", go); // set the Event Listener
    	}
    
    	return {
    		// …
    		}
    	};
    }();
    This is an example WDDX file returned from the server (I cut some of the lengthy text). Basicly it’s a serialized array of objects, containing the properties name (text), content (more text) & attributes (plain object).
    Code:
    <?xml version="1.0" encoding="UTF-8" ?><wddxPacket version='1.0'><header><comment>Zeitungsausschnitte (Text)</comment></header>
    <data><array length='6'><string>mitreißender Theater-Sturm</string><struct><var name='php_class_name'><string>wddx_presse</string></var>
    <var name='name'><string>p</string></var><var name='content'><string>Ein Schiff in Seenot, eine Mann*schaft, die ertrinkt, bis auf einige wenige, […]</string>
    </var><var name='attributes'><array length='0'></array></var><var name='space'><string></string></var></struct><struct><var name='php_class_name'>
    <string>wddx_presse</string></var><var name='name'><string>p</string></var><var name='content'><string>Der Sturm geht auf das Konto Prosperos, […]
    </string></var><var name='attributes'><array length='0'></array></var><var name='space'><string></string></var></struct><struct><var name='php_class_name'>
    <string>wddx_presse</string></var><var name='name'><string>p</string></var><var name='content'><string>Wie er da steht, den Blick ungerührt […].</string>
    </var><var name='attributes'><array length='0'></array></var><var name='space'><string></string></var></struct><struct><var name='php_class_name'>
    <string>wddx_presse</string></var><var name='name'><string>p</string></var><var name='content'><string>Witzig, aber nicht übertrieben, […]</string>
    </var><var name='attributes'><array length='0'></array></var><var name='space'><string></string></var></struct><struct><var name='php_class_name'>
    <string>wddx_presse</string></var><var name='name'><string>p</string></var><var name='content'><string>Juliane Lochner</string></var>
    <var name='attributes'><struct><var name='class'><string>rechts</string></var></struct></var><var name='space'><string></string></var></struct></array>
    </data></wddxPacket>
  • Niheel
    Recognized Expert Moderator Top Contributor
    • Jul 2005
    • 2432

    #2
    on this and the rest of the series, can you also include examples on how to use it:

    you've got the description and the code, a quick example on how to call the functions and use them it and i think it would work out well
    niheel @ bytes

    Comment

    • Dormilich
      Recognized Expert Expert
      • Aug 2008
      • 8694

      #3
      I have updated the serializer (using a common method for Number, String and Boolean, as well as some cleanup in the type switch)
      Code:
      /**
       * WDDX Deserializer for Javascript
       * File: *.mod_wddx_des.js
       * Author: Bertold von Dormilich (Dormilich@netscape.net), 2009
       */
      
      /**
       * formats a number to have leading zeros if the number length 
       * is smaller than num.
       *
       * @param (int)                 length of the final number string
       * @return (string)             number with leading zeroes
       */
      Number.prototype.toDigits = function(num)
      {
      	var zahl = new String(this.valueOf());
      	for (var i=zahl.length; i<num; i++) {
      		zahl = "0" + zahl;
      	}
      	return zahl;
      }
      
      /**
       * returns the XML source code of an XML tag.
       *
       * @return (string)             source code of the element
       */
      Element.prototype.getString = function()
      {
      	// lowercase the tag names
      	var tag = this.tagName.toLowerCase();
      	// fix dateTime & wddxPacket
      	tag = ("datetime" == tag) ? "dateTime" : tag;
      	tag = ("wddxpacket" == tag) ? "wddxPacket" : tag;
      	// opening tag
      	var str = "<" + tag;
      	// attributes
      	var atb = this.attributes;
      	for (var l, i=0, l=atb.length; i<l; i++) {
      		if (i in atb) {
      			str += " " + atb[i].name + '="' + atb[i].value + '"';
      		}
      	}
      	var cn  = this.childNodes;
      	// if empty element
      	if (0 == cn.length) {
      		return str + "/>";
      	} else {
      		str += ">";
      	}
      	// loop through child nodes (text/cdata/elements)
      	for (var l, i=0, l=cn.length; i<l; i++) {
      		// text or cdata
      		if (cn[i] instanceof Text) {
      			if (String.trim) {
      				str += cn[i].data.trim();
      			} else {
      				str += cn[i].data;
      			}
      		}
      		// elements (recursive)
      		else if (cn[i] instanceof Element) {
      			str += cn[i].getString();
      		}
      	}
      	// closing tag
      	return str + "</" + tag + ">";
      }
      
      // http://www.webreference.com/programming/javascript/definitive2/index.html
      /**
       * Create a new Document object. If no arguments are specified,
       * the document will be empty. If a root tag is specified, the document
       * will contain that single root tag. If the root tag has a namespace
       * prefix, the second argument must specify the URL that identifies the
       * namespace.
       *
       * @param (string) rootTagName  tag name of the root element
       * @param (string) namespaceURL root namespace
       * @return (Document)           XML document
       */
      var XML = {};
      XML.newDocument = function(rootTagName, namespaceURL) {
      	if (!rootTagName) rootTagName = "";
      	if (!namespaceURL) namespaceURL = "";
      	if (document.implementation && document.implementation.createDocument) {
      		// This is the W3C standard way to do it
      		return document.implementation.createDocument(namespaceURL, rootTagName, null);
      	} else { // This is the IE way to do it
      		// Create an empty document as an ActiveX object
      		// If there is no root element, this is all we have to do
      		var doc = new ActiveXObject("MSXML2.DOMDocument");
      		// If there is a root tag, initialize the document
      		if (rootTagName) {
      			// Look for a namespace prefix
      			var prefix = "";
      			var tagname = rootTagName;
      			var p = rootTagName.indexOf(':');
      			if (p != -1) {
      				prefix = rootTagName.substring(0, p);
      				tagname = rootTagName.substring(p+1);
      			}
      			// If we have a namespace, we must have a namespace prefix
      			// If we don't have a namespace, we discard any prefix
      			if (namespaceURL) {
      				if (!prefix) prefix = "a0"; // What Firefox uses
      			}
      			else prefix = "";
      			// Create the root element (with optional namespace) as a
      			// string of text
      			var text = "<" + (prefix?(prefix+":"):"") +	 tagname +
      					(namespaceURL
      					 ?(" xmlns:" + prefix + '="' + namespaceURL +'"')
      					 :"") +
      					"/>";
      			// And parse that text into the empty document
      			doc.loadXML(text);
      		}
      		return doc;
      	}
      };
      
      /**
       * create a new (empty) WDDX document.
       *
       * @param (string) comment      comment text
       * @return (void)
       */
      function WDDXDocument(comment)
      {
      	// create a new XML document 
      	// <wddxPacket>
      	var doc = XML.newDocument("wddxPacket");
      	// <header>
      	var hd  = document.createElement("header");
      	if (comment) {
      		var cm = document.createElement("comment");
      		cm.appendChild(document.createTextNode(comment));
      		hd.appendChild(cm);
      	}
      	// <data>
      	this.data = document.createElement("data");
      	this.root = doc.firstChild;
      	this.root.appendChild(hd);
      	this.root.appendChild(this.data);
      	this.setTargetApplicationType();
      }
      /**
       * set flag for additional class names (if the type attribute is not used).
       * 
       * @param (string)              application identifyer
       * @return (void)
       */
      WDDXDocument.prototype.setTargetApplicationType = function(app)
      {
      	var app = (new String(app)).toLowerCase().valueOf();
      	this.applicationType = app || "javascript";
      }
      
      /**
       * return the WDDX document source code. 
       *
       * @return (string)             WDDX Document source code
       */
      WDDXDocument.prototype.toString = function()
      {
      	var str = '<?xml version="1.0" encoding="UTF-8" ?>';
      	return str + this.root.getString();
      }
      
      /**
       * public method to serialize a value and add it to the WDDX document.
       *
       * @param (mixed) value         the variable to serialize
       * @param (string) klasse       optional class name to use for deserialization
       * @return (void)
       */
      WDDXDocument.prototype.serialize = function(value, klasse)
      {
      	var wsv = this.serializeValue(value, klasse);
      	this.data.appendChild(wsv);
      }
      
      /**
       * internal method to serialize the passed value.
       *
       * @param (mixed) obj           variable to serialize
       * @param (string) klasse       optional class name to use for deserialization
       * @return (Element)            serialized value as WDDX element
       * @throws (Error)              invalid parameter type
       * @throws (Error)              parameter is an error instance
       */
      WDDXDocument.prototype.serializeValue = function(obj, klasse)
      {
      	// DOM objects
      	// deserialization would be a problem
      	if (obj instanceof Node || obj instanceof NodeList || obj instanceof NamedNodeMap) {
      		throw new Error("DOM elements cannot be serialized.");
      	}
      	// default JavaScript objects
      	if (obj instanceof Array) {
      		return this.serializeArray(obj);
      	}
      	if (obj instanceof Boolean || false === obj || true === obj) {
      		return this.wddxElement("boolean", obj, true);
      	}
      	if (obj instanceof Date) {
      		return this.serializeDate(obj);
      	}
      	if (obj instanceof Number || "number" == typeof obj) {
      		return this.wddxElement("number", obj);
      	}
      	if (obj instanceof String || "string" == typeof obj) {
      		return this.wddxElement("string", obj);
      	}
      	// 1)
      	if (obj instanceof Function || obj instanceof RegExp || obj instanceof Error) {
      		throw new Error("Unsupported Object Type.");
      	}
      	if (obj instanceof Object) {
      		return this.serializeObject(obj, klasse);
      	}
      	// real primitives
      	if (undefined === obj) {
      		throw new Error("Undefined variables cannot be serialized.");
      	}
      	if (null === obj) {
      		return document.createElement("null");
      	}
      	// 1) - does it make sense to de-/serialize these?
      }
      
      /**
       * serialize an Array.
       *
       * @param (Array) obj           array to serialize
       * @return (Element)            serialized value as WDDX element
       * @throws (TypeError)          obj not an array
       */
      WDDXDocument.prototype.serializeArray = function(obj)
      {
      	if (!(obj instanceof Array)) {
      		throw new TypeError("Supplied value is not an Array.");
      	}
      	var AE, LA, l = obj.length;
      	// create <array> element
      	LA = document.createAttribute("length");
      	LA.value = l;
      	AE = document.createElement("array");
      	AE.setAttributeNode(LA);
      	// create data elements of the array
      	for (var i=0; i<l; i++) {
      		try {
      			AE.appendChild(this.serializeValue(obj[i]));
      		} catch (e) {
      			alert(e.message);
      		}
      	}
      	return AE;
      }
      
      /**
       * serialize a Date.
       *
       * @param (Date) obj            date to serialize
       * @return (Element)            serialized value as WDDX element
       * @throws (TypeError)          obj not a date object
       */
      WDDXDocument.prototype.serializeDate = function(obj)
      {
      	if (!(obj instanceof Date)) {
      		throw new TypeError("Supplied value is not a Date.");
      	}
      	// get UTC date parts 
      	var year = obj.getUTCFullYear();
      	var mon  = obj.getUTCMonth() + 1;
      	var day  = obj.getUTCDate().toDigits(2);
      	var hour = obj.getUTCHours().toDigits(2);
      	var min  = obj.getUTCMinutes().toDigits(2);
      	var sec  = obj.getUTCSeconds().toDigits(2);
      	var tzo  = obj.getTimezoneOffset();
      	// yyyy-mm-ddThh:mm:ss+hh:mm
      	var time = year + "-" + mon.toDigits(2) + "-" + day + "T" 
      	         + hour + ":" + min + ":" + sec + (tzo > 0 ? "-" : "+") 
      	         + Math.floor(Math.abs(tzo/60)).toDigits(2) + ":" + (tzo % 60).toDigits(2);
      	var DE;
      	// create <dateTime>
      	DE = document.createElement("dateTime");
      	DE.appendChild(document.createTextNode(time));
      	return DE;
      }
      
      /**
       * serialize an Object. except null, undefined everything
       * can be converted into an object.
       *
       * @param (object) obj          object to serialize
       * @return (Element)            serialized value as WDDX element
       * @throws (TypeError)          object is NULL or UNDEFINED
       */
      WDDXDocument.prototype.serializeObject = function(obj, klasse)
      {
      	if (null === obj || undefined === obj) {
      		throw new TypeError("Supplied value is a Primitive.");
      	}
      	var OE, VE, NA, TA;
      	// create <struct>
      	TA = document.createAttribute("type");
      	TA.value = obj.constructor.name;	// use native object for class name
      	OE = document.createElement("struct");
      	OE.setAttributeNode(TA);
      	// create target application class name
      	if ("javascript" != this.applicationType) {
      		try {
      			OE.appendChild(this.createClassName(klasse));
      		} catch (e) {
      			if (console) console.log(e.message);
      			alert(e.message);
      		}
      	}
      	for (var i in obj) {
      		if (obj[i] instanceof Function) continue;
      		try {
      			// create <var>
      			NA = document.createAttribute("name");
      			NA.value = i;
      			VE = document.createElement("var");
      			VE.setAttributeNode(NA);
      			// create serialized value
      			VE.appendChild(this.serializeValue(obj[i]));
      			OE.appendChild(VE);
      		} catch (e) {
      			if (console) console.log(e.message);
      			alert(e.message);
      		}
      	}
      	return OE;
      }
      
      /**
       * create a variable bearing the class name for the target application.
       *
       * @param (string) klasse       class name to serialize
       * @return (Element)            serialized class name (<var>)
       * @throws (Error)              unsupported appliction type
       */
      WDDXDocument.prototype.createClassName = function(klasse)
      {
      	var app = document.createElement("var");
      	var acn = document.createAttribute("name");
      	if ("php" == this.applicationType) {
      		acn.value = "php_class_name";
      	} else {
      		throw new Error("Unsupported Target Application Type.");
      	}
      	app.setAttributeNode(acn);
      	app.appendChild(this.serializeString(new String(klasse)));
      	return app;
      }
      
      /**
       * create a simple WDDX Element.
       *
       * @param (object) obj          object to serialize
       * @param (string) tagname      tag name of the WDDX Element
       * @param (bool) hasAttribute   object value to be saved in text or 
       *                              attribute node
       * @return (Element)            WDDX Element
       */
      WDDXDocument.prototype.wddxElement = function(tagname, obj, hasAttribute)
      {
      	if (null === obj || undefined === obj) {
      		throw new TypeError("Supplied value is a Primitive.");
      	}
      	var WE = document.createElement(tagname);
      	if (hasAttribute) {
      		var WA = document.createAttribute("value");
      		WA.value = obj.toString();
      		WE.setAttributeNode(WA);
      	} else {
      		WE.appendChild(document.createTextNode(obj.toString()));
      	}
      	return WE;
      }

      Comment

      Working...