/*global JSON */
/*
	JsonML2.js
	JsonML builder

	Created: 2006-11-09-0116
	Modified: 2010-03-28-2253

	Copyright (c)2006-2010 Stephen M. McKamey
	Distributed under an open-source license: http://jsonml.org/license

    This file creates a global JsonML object containing these methods:

        JsonML.parse(string|array, filter)

            This method produces a tree of DOM elements from a JsonML tree. The
            array must not contain any cyclical references.

            The optional filter parameter is a function which can filter and
            transform the results. It receives each of the DOM nodes, and
            its return value is used instead of the original value. If it
            returns what it received, then structure is not modified. If it
            returns undefined then the member is deleted.

			This is useful for binding unobtrusive JavaScript to the generated
			DOM elements.

            Example:

            // Parses the structure. If an element has a specific CSS value then
            // takes appropriate action: Remove from results, add special event
            // handlers, or bind to a custom component.

            var myUI = JsonML.parse(myUITemplate, function (elem) {
				if (elem.className.indexOf("Remove-Me") >= 0) {
					// this will remove from resulting DOM tree
					return null;
				}

				if (elem.tagName && elem.tagName.toLowerCase() === "a" &&
					elem.className.indexOf("External-Link") >= 0) {
					// this is the equivalent of target="_blank"
					elem.onclick = function(evt) {
						window.open(elem.href); return false;
					};

				} else if (elem.className.indexOf("Fancy-Widgit") >= 0) {
					// bind to a custom component
					FancyWidgit.bindDOM(elem);
				}
				return elem;
			});

			// Implement onerror to handle any runtime errors while binding:
			JsonML.onerror = function (ex, jml, filter) {
				// display inline error message
				return document.createTextNode("["+ex+"]");
			};

		Utility methods for manipulating JsonML elements:

			// tests if a given object is a valid JsonML element
			bool JsonML.isElement(jml);

			// gets the name of a JsonML element
			string JsonML.getTagName(jml);

			// tests if a given object is a JsonML attributes collection
			bool JsonML.isAttributes(jml);

			// tests if a JsonML element has a JsonML attributes collection
			bool JsonML.hasAttributes(jml);

			// gets the attributes collection for a JsonML element
			object JsonML.getAttributes(jml);

			// sets multiple attributes for a JsonML element
			void JsonML.addAttributes(jml, attr);

			// gets a single attribute for a JsonML element
			object JsonML.getAttribute(jml, key);

			// sets a single attribute for a JsonML element
			void JsonML.setAttribute(jml, key, value);

			// appends a JsonML child node to a parent JsonML element
			void JsonML.appendChild(parent, child);

			// gets an array of the child nodes of a JsonML element
			array JsonML.getChildren(jml);
*/

var JsonML;
if ("undefined" === typeof JsonML) {
	JsonML = {};
}

(function() {

	//attribute name mapping
	var ATTRMAP = {
			rowspan : "rowSpan",
			colspan : "colSpan",
			cellpadding : "cellPadding",
			cellspacing : "cellSpacing",
			tabindex : "tabIndex",
			accesskey : "accessKey",
			hidefocus : "hideFocus",
			usemap : "useMap",
			maxlength : "maxLength",
			readonly : "readOnly",
			contenteditable : "contentEditable"
			// can add more attributes here as needed
		},

		// attribute duplicates
		ATTRDUP = {
			enctype : "encoding",
			onscroll : "DOMMouseScroll"
			// can add more attributes here as needed
		},

		// event names
		EVTS = (function(/*string[]*/ names) {
			var evts = {};
			while (names.length) {
				var evt = names.shift();
				evts["on"+evt.toLowerCase()] = evt;
			}
			return evts;
		})("blur,change,click,dblclick,error,focus,keydown,keypress,keyup,load,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,resize,scroll,select,submit,unload".split(','));

	/*void*/ function addHandler(/*DOM*/ elem, /*string*/ name, /*function*/ handler) {
		if ("string" === typeof handler) {
			/*jslint evil:true */
			handler = new Function("event", handler);
			/*jslint evil:false */
		}

		if ("function" !== typeof handler) {
			return;
		}

		elem[name] = handler;
	}

	/*DOM*/ function addAttributes(/*DOM*/ elem, /*object*/ attr) {
		if (attr.name && document.attachEvent) {
			try {
				// IE fix for not being able to programatically change the name attribute
				var alt = document.createElement("<"+elem.tagName+" name='"+attr.name+"'>");
				// fix for Opera 8.5 and Netscape 7.1 creating malformed elements
				if (elem.tagName === alt.tagName) {
					elem = alt;
				}
			} catch (ex) { }
		}

		// for each attributeName
		for (var name in attr) {
			if (attr.hasOwnProperty(name)) {
				// attributeValue
				var value = attr[name];
				if (name && value) {
					name = ATTRMAP[name.toLowerCase()] || name;
					if (name === "style") {
						if ("undefined" !== typeof elem.style.cssText) {
							elem.style.cssText = value;
						} else {
							elem.style = value;
						}
					} else if (name === "class") {
						elem.className = value;
					} else if (EVTS[name]) {
						addHandler(elem, name, value);

						// also set duplicated events
						if (ATTRDUP[name]) {
							addHandler(elem, ATTRDUP[name], value);
						}
					} else if ("string" === typeof value || "number" === typeof value || "boolean" === typeof value) {
						elem.setAttribute(name, value);

						// also set duplicated attributes
						if (ATTRDUP[name]) {
							elem.setAttribute(ATTRDUP[name], value);
						}
					} else {

						// allow direct setting of complex properties
						elem[name] = value;

						// also set duplicated attributes
						if (ATTRDUP[name]) {
							elem[ATTRDUP[name]] = value;
						}
					}
				}
			}
		}
		return elem;
	}

	/*void*/ function appendChild(/*DOM*/ elem, /*DOM*/ child) {
		if (child) {
			if (elem.tagName && elem.tagName.toLowerCase() === "table" && elem.tBodies) {
				if (!child.tagName) {
					// must unwrap documentFragment for tables
					if (child.nodeType === 11) {
						while (child.firstChild) {
							appendChild(elem, child.removeChild(child.firstChild));
						}
					}
					return;
				}
				// in IE must explicitly nest TRs in TBODY
				var childTag = child.tagName.toLowerCase();// child tagName
				if (childTag && childTag !== "tbody" && childTag !== "thead") {
					// insert in last tbody
					var tBody = elem.tBodies.length > 0 ? elem.tBodies[elem.tBodies.length-1] : null;
					if (!tBody) {
						tBody = document.createElement(childTag === "th" ? "thead" : "tbody");
						elem.appendChild(tBody);
					}
					tBody.appendChild(child);
				} else if (elem.canHaveChildren !== false) {
					elem.appendChild(child);
				}
			} else if (elem.canHaveChildren !== false) {
				elem.appendChild(child);
			} else if (elem.tagName && elem.tagName.toLowerCase() === "object" &&
				child.tagName && child.tagName.toLowerCase() === "param") {
					// IE-only path
					try {
						elem.appendChild(child);
					} catch (ex1) {}
					try {
						if (elem.object) {
							elem.object[child.name] = child.value;
						}
					} catch (ex2) {}
			}
		}
	}

	/*bool*/ function isWhitespace(/*DOM*/ node) {
		return node && (node.nodeType === 3) && (!node.nodeValue || !/\S/.exec(node.nodeValue));
	}

	/*void*/ function trimWhitespace(/*DOM*/ elem) {
		if (elem) {
			while (isWhitespace(elem.firstChild)) {
				// trim leading whitespace text nodes
				elem.removeChild(elem.firstChild);
			}
			while (isWhitespace(elem.lastChild)) {
				// trim trailing whitespace text nodes
				elem.removeChild(elem.lastChild);
			}
		}
	}

	/*DOM*/ function hydrate(/*string*/ value) {
		var wrapper = document.createElement("div");
		wrapper.innerHTML = value;

		// trim extraneous whitespace
		trimWhitespace(wrapper);

		// eliminate wrapper for single nodes
		if (wrapper.childNodes.length === 1) {
			return wrapper.firstChild;
		}

		// create a document fragment to hold elements
		var frag = document.createDocumentFragment ?
			document.createDocumentFragment() :
			document.createElement("");

		while (wrapper.firstChild) {
			frag.appendChild(wrapper.firstChild);
		}
		return frag;
	}

	function Unparsed(/*string*/ value) {
		this.value = value;
	}

	JsonML.raw = function(/*string*/ value) {
		return new Unparsed(value);
	};

	// default error handler
	/*DOM*/ function onError(/*Error*/ ex, /*JsonML*/ jml, /*function*/ filter) {
		return document.createTextNode("["+ex+"]");
	}

	/* override this to perform custom error handling during binding */
	JsonML.onerror = null;

	/*DOM*/ JsonML.parse = function(/*JsonML*/ jml, /*function*/ filter) {
		try {
			if (!jml) {
				return null;
			}
			if ("string" === typeof jml) {
				return document.createTextNode(jml);
			}
			if (jml instanceof Unparsed) {
				return hydrate(jml.value);
			}
			if (!JsonML.isElement(jml)) {
				throw new SyntaxError("invalid JsonML");
			}

			var i;
			var tagName = jml[0]; // tagName
			if (!tagName) {
				// correctly handle a list of JsonML trees
				// create a document fragment to hold elements
				var frag = document.createDocumentFragment ?
					document.createDocumentFragment() :
					document.createElement("");
				for (i=1; i<jml.length; i++) {
					appendChild(frag, JsonML.parse(jml[i], filter));
				}

				// trim extraneous whitespace
				trimWhitespace(frag);

				// eliminate wrapper for single nodes
				if (frag.childNodes.length === 1) {
					return frag.firstChild;
				}
				return frag;
			}

			var css = (tagName.toLowerCase() === "style" && document.createStyleSheet);
			var elem = css ?
				// IE requires this interface for styles
				document.createStyleSheet() :
				document.createElement(tagName);

			for (i=1; i<jml.length; i++) {
				if (jml[i] instanceof Array || "string" === typeof jml[i]) {
					if (css) {
						// IE requires this interface for styles
						elem.cssText = jml[i];
					} else {
						// append children
						appendChild(elem, JsonML.parse(jml[i], filter));
					}
				} else if (jml[i] instanceof Unparsed) {
					appendChild(elem, hydrate(jml[i].value));
				} else if ("object" === typeof jml[i] && jml[i] !== null && elem.nodeType === 1) {
					// add attributes
					elem = addAttributes(elem, jml[i]);
				}
			}

			if (css) {
				// in IE styles are effective immediately
				return null;
			}

			// trim extraneous whitespace
			trimWhitespace(elem);
			return (elem && "function" === typeof filter) ? filter(elem) : elem;
		} catch (ex) {
			try {
				// handle error with complete context
				var err = ("function" === typeof JsonML.onerror) ? JsonML.onerror : onError;
				return err(ex, jml, filter);
			} catch (ex2) {
				return document.createTextNode("["+ex2+"]");
			}
		}
	};

	/* Utility Methods -------------------------*/

	/*bool*/ JsonML.isElement = function(/*JsonML*/ jml) {
		return (jml instanceof Array) && ("string" === typeof jml[0]);
	};

	/*bool*/ JsonML.isFragment = function(/*JsonML*/ jml) {
		return (jml instanceof Array) && (jml[0] === "");
	};

	/*string*/ JsonML.getTagName = function(/*JsonML*/ jml) {
		return jml[0] || "";
	};

	/*bool*/ JsonML.isAttributes = function(/*JsonML*/ jml) {
		return !!jml && ("object" === typeof jml) && !(jml instanceof Array);
	};

	/*bool*/ JsonML.hasAttributes = function(/*JsonML*/ jml) {
		if (!JsonML.isElement(jml)) {
			throw new SyntaxError("invalid JsonML");
		}

		return JsonML.isAttributes(jml[1]);
	};

	/*object*/ JsonML.getAttributes = function(/*JsonML*/ jml, /*bool*/ addIfMissing) {
		if (JsonML.hasAttributes(jml)) {
			return jml[1];
		}

		if (!addIfMissing) {
			return undefined;
		}

		// need to add an attribute object
		var name = jml.shift();
		var attr = {};
		jml.unshift(attr);
		jml.unshift(name||"");
		return attr;
	};

	/*void*/ JsonML.addAttributes = function(/*JsonML*/ jml, /*object*/ attr) {
		if (!JsonML.isElement(jml) || !JsonML.isAttributes(attr)) {
			throw new SyntaxError("invalid JsonML");
		}

		if (!JsonML.isAttributes(jml[1])) {
			// just insert attributes
			var name = jml.shift();
			jml.unshift(attr);
			jml.unshift(name||"");
			return;
		}

		// merge attribute objects
		var old = jml[1];
		for (var key in attr) {
			if (attr.hasOwnProperty(key)) {
				old[key] = attr[key];
			}
		}
	};

	/*string|number|bool*/ JsonML.getAttribute = function(/*JsonML*/ jml, /*string*/ key) {
		if (!JsonML.hasAttributes(jml)) {
			return undefined;
		}
		return jml[1][key];
	};

	/*void*/ JsonML.setAttribute = function(/*JsonML*/ jml, /*string*/ key, /*string|number|bool*/ value) {
		JsonML.getAttributes(jml, true)[key] = value;
	};

	/*void*/ JsonML.appendChild = function(/*JsonML*/ parent, /*array|object|string*/ child) {
		if (child instanceof Array && child[0] === "") {
			// result was multiple JsonML sub-trees (i.e. documentFragment)
			child.shift();// remove fragment ident

			// directly append children
			while (child.length) {
				JsonML.appendChild(parent, child.shift(), arguments[2]);
			}
		} else if (child && "object" === typeof child) {
			if (child instanceof Array) {
				if (!JsonML.isElement(parent) || !JsonML.isElement(child)) {
					throw new SyntaxError("invalid JsonML");
				}

				if ("function" === typeof arguments[2]) {
					// onAppend callback for JBST use
					arguments[2](parent, child);
				}

				// result was a JsonML node
				parent.push(child);
			} else if (child instanceof Unparsed) {
				if (!JsonML.isElement(parent)) {
					throw new SyntaxError("invalid JsonML");
				}

				// result was a JsonML node
				parent.push(child);
			} else {
				// result was JsonML attributes
				JsonML.addAttributes(parent, child);
			}
		} else if ("undefined" !== typeof child && child !== null) {
			if (!(parent instanceof Array)) {
				throw new SyntaxError("invalid JsonML");
			}

			// must convert to string or JsonML will discard
			child = String(child);

			// skip processing empty string literals
			if (child && parent.length > 1 && "string" === typeof parent[parent.length-1]) {
				// combine strings
				parent[parent.length-1] += child;
			} else if (child || !parent.length) {
				// append
				parent.push(child);
			}
		}
	};

	/*array*/ JsonML.getChildren = function(/*JsonML*/ jml) {
		if (JsonML.hasAttributes(jml)) {
			jml.slice(2);
		}

		jml.slice(1);
	};

})();
/*
	JsonML_BST.js
	JsonML + Browser-Side Templating (JBST)

	Created: 2008-07-28-2337
	Modified: 2010-03-28-2253

	Copyright (c)2006-2010 Stephen M. McKamey
	Distributed under an open-source license: http://jsonml.org/license

    This file creates a JsonML.BST type containing these methods:

		// JBST + JSON => DOM
		var dom = JsonML.BST(jbst).bind(data);

		// JBST + JSON => JsonML
		var jsonml = JsonML.BST(jbst).dataBind(data);

		// implement filter to intercept and perform custom filtering of resulting DOM elements
		JsonML.BST.filter = function(elem) {
			if (condition) {
				// this will prevent insertion into resulting DOM tree
				return null;
			}
			return elem;
		};

		// implement onerror event to handle any runtime errors while binding
		JsonML.BST.onerror = function(ex) {
			// access the current context via this.data, this.index, etc.
			// display custom inline error messages
			return "["+ex+"]";
		};

		// implement onbound event to perform custom processing of elements after binding
		JsonML.BST.onbound = function(node) {
			// access the current context via this.data, this.index, etc.
			// watch elements as they are constructed
			if (window.console) {
				console.log(JSON.stringify(output));
			}
		};

		// implement onappend event to perform custom processing of children before being appended
		JsonML.BST.onappend = function(parent, child) {
			// access the current context via this.data, this.index, etc.
			// watch elements as they are added
			if (window.console) {
				console.log(JsonML.getTagName(parent)+" > "+JsonML.getTagName(child));
			}
		};
*/

/* namespace JsonML */
var JsonML;
if ("undefined" === typeof JsonML) {
	JsonML = {};
}

JsonML.BST = (function(){

	var SHOW = "jbst:visible",
		INIT = "jbst:oninit",
		LOAD = "jbst:onload";

	// ensures attribute key contains method or is removed
	// attr: attribute object
	// key: method name
	/*function*/ function ensureMethod(/*object*/ attr, /*string*/ key) {
		var method = attr[key] || null;
		if (method) {
			// ensure is method
			if ("function" !== typeof method) {
				try {
					/*jslint evil:true */
					method = new Function(String(method));
					/*jslint evil:false */
				} catch (ex) {
					// filter
					method = null;
				}
			}
			if (method) {
				// IE doesn't like colon in property names
				attr[key.split(':').join('$')] = method;
			}
			delete attr[key];
		}
		return method;
	}

	// default onerror handler, override JsonML.BST.onerror to change
	/*JsonML*/ function onError(/*Error*/ ex) {
		return "["+ex+"]";
	}

	// retrieve and remove method
	/*function*/ function popMethod(/*DOM*/ elem, /*string*/ key) {
		// IE doesn't like colon in property names
		key = key.split(':').join('$');

		var method = elem[key];
		if (method) {
			try {
				delete elem[key];
			} catch (ex) {
				// sometimes IE doesn't like deleting from DOM
				elem[key] = undefined;
			}
		}
		return method;
	}

	// JsonML Filter
	/*DOM*/ function filter(/*DOM*/ elem) {

		// execute and remove jbst:oninit method
		var method = popMethod(elem, INIT);
		if ("function" === typeof method) {
			// execute in context of element
			method.call(elem);
		}

		// execute and remove jbst:onload method
		method = popMethod(elem, LOAD);
		if ("function" === typeof method) {
			// queue up to execute after insertion into parentNode
			setTimeout(function() {
				// execute in context of element
				method.call(elem);
				method = elem = null;
			}, 0);
		}

		if (JsonML.BST.filter) {
			return JsonML.BST.filter(elem);
		}

		return elem;
	}

	/*object*/ function callContext(
		/*object*/ self,
		/*object*/ data,
		/*int*/ index,
		/*int*/ count,
		/*object*/ args,
		/*function*/ method,
		/*Array*/ methodArgs) {

		try {
			// setup context for code block
			self.data = ("undefined" !== typeof data) ? data : null;
			self.index = isFinite(index) ? Number(index) : NaN;
			self.count = isFinite(count) ? Number(count) : NaN;
			self.args = ("undefined" !== typeof args) ? args : null;

			// execute node in the context of self as "this", passing in any parameters
			return method.apply(self, methodArgs || []);

		} finally {
			// cleanup contextual members
			delete self.count;
			delete self.index;
			delete self.data;
			delete self.args;
		}
	}

	/* ctor */
	function JBST(/*JsonML*/ jbst) {
		if ("undefined" === typeof jbst) {
			throw new Error("JBST tree is undefined");
		}

		var self = this,
			appendChild = JsonML.appendChild;

		// recursively applies dataBind to all nodes of the template graph
		// NOTE: it is very important to replace each node with a copy,
		// otherwise it destroys the original template.
		// node: current template node being data bound
		// data: current data item being bound
		// index: index of current data item
		// count: count of current set of data items
		// args: state object
		// returns: JsonML nodes
		/*object*/ function dataBind(/*JsonML*/ node, /*object*/ data, /*int*/ index, /*int*/ count, /*object*/ args) {
			try {
				// recursively process each node
				if (node) {
					var output;

					if ("function" === typeof node) {
						output = callContext(self, data, index, count, args, node);

						if (output instanceof JBST) {
							// allow returned JBSTs to recursively bind
							// useful for creating "switcher" template methods
							return output.dataBind(data, index, count, args);
						}

						// function result
						return output;
					}

					if (node instanceof Array) {
						var onBound = ("function" === typeof JsonML.BST.onbound) && JsonML.BST.onbound,
							onAppend = ("function" === typeof JsonML.BST.onappend) && JsonML.BST.onappend,
							appendCB = onAppend && function(parent, child) {
								callContext(self, data, index, count, args, onAppend, [parent, child]);
							};

						// JsonML output
						output = [];
						for (var i=0; i<node.length; i++) {
							var child = dataBind(node[i], data, index, count, args);
							appendChild(output, child, appendCB);

							if (!i && !output[0]) {
								onAppend = appendCB = null;
							}
						}

						if (output[0] && onBound) {
							callContext(self, data, index, count, args, onBound, [output]);
						}

						// if output has attributes, check for JBST commands
						if (JsonML.hasAttributes(output)) {
							// visibility JBST command
							var visible = output[1][SHOW];
							if ("undefined" !== typeof visible) {
								// cull any false-y values
								if (!visible) {
									// suppress rendering of entire subtree
									return "";
								}
								// remove attribute
								delete output[1][SHOW];
							}

							// jbst:oninit
							ensureMethod(output[1], INIT);

							// jbst:onload
							ensureMethod(output[1], LOAD);
						}

						// JsonML element
						return output;
					}

					if ("object" === typeof node) {
						output = {};
						// process each property in template node
						for (var property in node) {
							if (node.hasOwnProperty(property)) {
								// evaluate property's value
								var value = dataBind(node[property], data, index, count, args);
								if ("undefined" !== typeof value && value !== null) {
									output[property] = value;
								}
							}
						}
						// attributes object
						return output;
					}
				}

				// rest are simple value types, so return node directly
				return node;
			} catch (ex) {
				try {
					// handle error with complete context
					var err = ("function" === typeof JsonML.BST.onerror) ? JsonML.BST.onerror : onError;
					return callContext(self, data, index, count, args, err, [ex]);
				} catch (ex2) {
					return "["+ex2+"]";
				}
			}
		}

		// the publicly exposed instance method
		// combines JBST and JSON to produce JsonML
		/*JsonML*/ self.dataBind = function(/*object*/ data, /*int*/ index, /*int*/ count, /*object*/ args) {
			if (data instanceof Array) {
				// create a document fragment to hold list
				var output = [""];

				count = data.length;
				for (var i=0; i<count; i++) {
					// apply template to each item in array
					appendChild(output, dataBind(jbst, data[i], i, count, args));
				}
				// document fragment
				return output;
			} else {
				// data is singular so apply template once
				return dataBind(jbst, data, index, count, args);
			}
		};

		/* JBST + JSON => JsonML => DOM */
		/*DOM*/ self.bind = function(/*object*/ data, /*int*/ index, /*int*/ count, /*object*/ args) {

			// databind JSON data to a JBST template, resulting in a JsonML representation
			var jml = self.dataBind(data, index, count, args);

			// hydrate the resulting JsonML, executing callbacks, and user-filter
			return JsonML.parse(jml, filter);
		};

		// replaces a DOM element with element result from binding
		/*void*/ self.replace = function(/*DOM*/ elem, /*object*/ data, /*int*/ index, /*int*/ count, /*object*/ args) {
			if ("string" === typeof elem) {
				elem = document.getElementById(elem);
			}

			if (elem && elem.parentNode) {
				var jml = self.bind(data, index, count, args);
				if (jml) {
					elem.parentNode.replaceChild(jml, elem);
				}
			}
		};
	}

	/* factory method */
	return function(/*JBST*/ jbst) {
		return (jbst instanceof JBST) ? jbst : new JBST(jbst);
	};
})();

/* override to perform default filtering of the resulting DOM tree */
/*function*/ JsonML.BST.filter = null;

/* override to perform custom error handling during binding */
/*function*/ JsonML.BST.onerror = null;

/* override to perform custom processing of each element after adding to parent */
/*function*/ JsonML.BST.onappend = null;

/* override to perform custom processing of each element after binding */
/*function*/ JsonML.BST.onbound = null;

/* JsonFx v1.4.1003.3007 */
/* JsonFx v1.4.1003.3007 */
