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
and this is the serializer
because I’m too lazy, I just use some of my website’s JS to demonstrate:
(note: XHR stands for the XMLHttpResponse object)
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).
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;
}
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;
}
(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 {
// …
}
};
}();
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>
Comment