/*
* The Zapatec DHTML utils library
*
* Copyright (c) 2004-2005 by Zapatec, Inc.
* http://www.zapatec.com
* 1700 MLK Way, Berkeley, California,
* 94709, U.S.A.
* All rights reserved.
* $Id: utils.js 2245 2006-03-24 03:30:17Z ken $
*
*
* Various utility functions
*/

// This can be defined in other modules
if (typeof Zapatec == 'undefined') {
  /// define the global Zapatec namespace
  Zapatec = {};
}

/// define the Utils namespace
Zapatec.Utils = {};

/// Retrieves the absolute position (relative to <body>) of a given element.
///
/// If it doesn't work in IE 6, try this:
/// \code
/// setTimeout(function() {
///   var objPos = Zapatec.Utils.getAbsolutePos(objElement);
///   do something with objPos
/// }, 0);
/// \endcode
///
/// @param el [HTMLElement] reference to the element.
/// @return [object] { x, y } containing the position.
Zapatec.Utils.getAbsolutePos = function(el) {
	var SL = 0, ST = 0;
	var is_div = /^div$/i.test(el.tagName);
	if (is_div && el.scrollLeft)
		SL = el.scrollLeft;
	if (is_div && el.scrollTop)
		ST = el.scrollTop;
	var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
	if (el.offsetParent) {
		var tmp = this.getAbsolutePos(el.offsetParent);
		r.x += tmp.x;
		r.y += tmp.y;
	}
	return r;
};

/// Modify the position of a box to fit in browser's view.  This function will
/// modify the passed object itself, so it doesn't need to return a value.
///
/// @param [object] box { x, y, width, height } specifying the area.
Zapatec.Utils.fixBoxPosition = function(box) {
	if (box.x < 0)
		box.x = 0;
	if (box.y < 0)
		box.y = 0;
	var cp = Zapatec.Utils.createElement("div");
	var s = cp.style;
	s.position = "absolute";
	s.right = s.bottom = s.width = s.height = "0px";
	window.document.body.appendChild(cp);
	var br = Zapatec.Utils.getAbsolutePos(cp);
	window.document.body.removeChild(cp);
	if (Zapatec.is_ie) {
		br.y += window.document.body.scrollTop;
		br.x += window.document.body.scrollLeft;
	} else {
		br.y += window.scrollY;
		br.x += window.scrollX;
	}
	var tmp = box.x + box.width - br.x;
	if (tmp > 0) box.x -= tmp;
	tmp = box.y + box.height - br.y;
	if (tmp > 0) box.y -= tmp;
};

/// Determines if an event is related to a certain element.  This is a poor
/// substitute for some events that are missing from DOM since forever (like
/// onenter, onleave, which MSIE provides).  Basically onmouseover and
/// onmouseout are fired even if the mouse was already in the element but moved
/// from text to a blank area, so in order not to close a popup element when
/// onmouseout occurs in this situation, one would need to first check if the
/// event is not related to that popup element:
///
/// \code
///      function handler_onMouseOut(event) {
///         if (!Zapatec.Utils.isRelated(this, event)) {
///            /// can safely hide it now
///            this.style.display = "none";
///         }
///      }
/// \endcode
///
/// @param el [HTMLElement] reference to the element to check the event against
/// @param evt [Event] reference to the Event object
/// @return [boolean] true if the event is related to the element
Zapatec.Utils.isRelated = function (el, evt) {
	evt || (evt = window.event);
	var related = evt.relatedTarget;
	if (!related) {
		var type = evt.type;
		if (type == "mouseover") {
			related = evt.fromElement;
		} else if (type == "mouseout") {
			related = evt.toElement;
		}
	}
	try {
		while (related) {
			if (related == el) {
				return true;
			}
			related = related.parentNode;
		}
	} catch(e) {};
	return false;
};

/// Remove a certain [CSS] class from the given element.
/// @param el [HTMLElement] reference to the element.
/// @param className [string] the class to remove.
Zapatec.Utils.removeClass = function(el, className) {
	if (!(el && el.className)) {
		return;
	}
	var cls = el.className.split(" ");
	var ar = [];
	for (var i = cls.length; i > 0;) {
		if (cls[--i] != className) {
			ar[ar.length] = cls[i];
		}
	}
	el.className = ar.join(" ");
};

/// Appends a certain [CSS] class to the given element.
/// @param el [HTMLElement] reference to the element.
/// @param className [string] the class to append.
Zapatec.Utils.addClass = function(el, className) {
	Zapatec.Utils.removeClass(el, className);
	el.className += " " + className;
};

/// Retrieves the current target element for some event (useful when bubbling).
/// This function is not actually very useful, but it's legacy from the old calendar code.
/// @param ev [Event] the event object.
/// @return [HTMLElement] window.event.srcElement for MSIE, ev.currentTarget for other browsers.
Zapatec.Utils.getElement = function(ev) {
	if (Zapatec.is_ie) {
		return window.event.srcElement;
	} else {
		return ev.currentTarget;
	}
};

/// Retrieves the target element for some event (useful when bubbling).
/// This function is not actually very useful, but it's legacy from the old calendar code.
/// @param ev [Event] the event object.
/// @return [HTMLElement] window.event.srcElement for MSIE, ev.target for other browsers.
Zapatec.Utils.getTargetElement = function(ev) {
	if (Zapatec.is_ie) {
		return window.event.srcElement;
	} else {
		return ev.target;
	}
};

/// Stops bubbling and propagation of some event.
/// @param ev [Event] the event object
/// @return false
Zapatec.Utils.stopEvent = function(ev) {
	ev || (ev = window.event);
	if (ev) {
		if (Zapatec.is_ie) {
			ev.cancelBubble = true;
			ev.returnValue = false;
		} else {
			ev.preventDefault();
			ev.stopPropagation();
		}
	}
	return false;
};

/// Adds an event handler to a certain element.  This function adds a handler
/// using the DOM2 addEventListener (or attachEvent for MSIE).  Doing this
/// means that you can add multiple handlers for the same element and same
/// event name, and they will be called in order.
///
/// WARNING: for really old browsers that don't support attachEvent nor
/// addEventListener, it falls back to the default way: el.onclick = func.
/// This means that you CANNOT add multiple handlers in those browsers, as a
/// new one will override the old one.
///
/// @param el [HTMLElement] reference to the element.
/// @param evname [string] the event name, excluding the "on" prefix.
/// @param func event handler function.
Zapatec.Utils.addEvent = function(el, evname, func) {
	if (el.attachEvent) { // IE
		el.attachEvent("on" + evname, func);
	} else if (el.addEventListener) { // Gecko / W3C
		el.addEventListener(evname, func, false);
	} else {
		el["on" + evname] = func;
	}
};

/// Removes an event handler added with Zapatec.Utils.removeEvent().  The
/// prototype scheme is the same.
Zapatec.Utils.removeEvent = function(el, evname, func) {
	if (el.detachEvent) { // IE
		el.detachEvent("on" + evname, func);
	} else if (el.removeEventListener) { // Gecko / W3C
		el.removeEventListener(evname, func, false);
	} else {
		el["on" + evname] = null;
	}
};

/// Create an element of a certain type using document.createElement().  A
/// function was needed in order to add some common attributes to all created
/// elements, but also in order to be able to use it in XHTML too (Gecko and
/// other W3C-compliant browsers).
///
/// This function will create an element of the given type and set certain
/// properties to it: unselectable for IE, and the CSS "-moz-user-select" for
/// Gecko, in order to make the element unselectable in these browsers.
/// Optionally, if the second argument is passed, it will appendChild() the
/// newly created element to its parent.
///
/// @param type [string] the tag name of the new element.
/// @param parent [HTMLElement, optional] a parent for the new element.
/// @param selectable [boolean] the flag to indicate wether element is selectable(rather usefull).
/// @return [HTMLElement] reference to the new element.
Zapatec.Utils.createElement = function(type, parent, selectable) {
	var el = null;
	if (window.self.document.createElementNS)
		// use the XHTML namespace; IE won't normally get here unless
		// _they_ "fix" the DOM2 implementation.
		el = window.self.document.createElementNS("http://www.w3.org/1999/xhtml", type);
	else
		el = window.self.document.createElement(type);
	if (typeof parent != "undefined" &&parent != null)
		parent.appendChild(el);
	if (!selectable) {
		if (Zapatec.is_ie)
			el.setAttribute("unselectable", true);
		if (Zapatec.is_gecko)
			el.style.setProperty("-moz-user-select", "none", "");
	}
	return el;
};

// Cookie management

/// Sets a cooke given certain specifications.  It overrides any existing
/// cookie with the same name.
///
/// @param name [string] the cookie name.
/// @param value [string] the cookie value.
/// @param domain [string, optional] the cookie domain.
/// @param path [string, optional] the cookie path.
/// @param exp_days [number, optional] number of days of cookie validity.
Zapatec.Utils.writeCookie = function(name, value, domain, path, exp_days) {
	value = escape(value);
	var ck = name + "=" + value, exp;
	if (domain)
		ck += ";domain=" + domain;
	if (path)
		ck += ";path=" + path;
	if (exp_days) {
		exp = new Date();
		exp.setTime(exp_days * 86400000 + exp.getTime());
		ck += ";expires=" + exp.toGMTString();
	}
	document.cookie = ck;
};

/**
 * Retrieves the value of a cookie.
 *
 * @param name [string] the cookie name
 * @return [string or null] a string with the cookie value, or null if it can't be found.
 */

/* ? inside regular expression is not supported in IE 5.0
Zapatec.Utils.getCookie = function(name) {
	var re = new RegExp("(^|;\\s*)" + name + "\\s*=(.*?)(;|$)");
	if (re.test(document.cookie)) {
		var value = RegExp.$2;
		value = unescape(value);
		return (value);
	}
	return null;
};
*/

Zapatec.Utils.getCookie = function(name) {
	var pattern = name + "=";
	var tokenPos = 0;
	while (tokenPos < document.cookie.length) {
		var valuePos = tokenPos + pattern.length;
		if (document.cookie.substring(tokenPos, valuePos) == pattern) {
			var endValuePos = document.cookie.indexOf(";", valuePos);
			if (endValuePos == -1) { // Last cookie
				endValuePos = document.cookie.length;
			}
			return unescape(document.cookie.substring(valuePos, endValuePos));
		}
		tokenPos=document.cookie.indexOf(" ",tokenPos)+1;
		if (tokenPos == 0) { // No more tokens
			break;
		}
	}
	return null;
};

/**
 * Given an object, create a string suitable for saving the object in a cookie.
 * This is similar to serialization.  WARNING: it does not support nested
 * objects.
 *
 * @param obj [Object] reference to the object to serialize.
 * @return [string] the serialized object.
 */
Zapatec.Utils.makePref = function(obj) {
	function stringify(val) {
		if (typeof val == "object" && !val)
			return "null";
		else if (typeof val == "number" || typeof val == "boolean")
			return val;
		else if (typeof val == "string")
			return '"' + val.replace(/\22/, "\\22") + '"';
		else return null;
	};
	var txt = "", i;
	for (i in obj)
		txt += (txt ? ",'" : "'") + i + "':" + stringify(obj[i]);
	return txt;
};

/**
 * The reverse of Zapatec.Utils.makePref(), this function unserializes the
 * given string and creates an object from it.
 *
 * @param txt [string] the serialized value.
 * @return [Object] a new object if it was created successfully or null otherwise.
 */
Zapatec.Utils.loadPref = function(txt) {
	var obj = null;
	try {
		eval("obj={" + txt + "}");
	} catch(e) {}
	return obj;
};

/**
 * Merges the values of the source object into the destination object.
 *
 * @param dest [Object] the destination object.
 * @param src [Object] the source object.
 */
Zapatec.Utils.mergeObjects = function(dest, src) {
	for (var i in src)
		dest[i] = src[i];
};

// based on the WCH idea
// http://www.aplus.co.yu/WCH/code3/WCH.js

/// \defgroup WCH functions
//@{

Zapatec.Utils.__wch_id = 0;	/**< [number, static] used to create ID-s for the WCH objects */

/**
 * Create an WCH object.  This function does nothing if the browser is not
 * IE5.5 or IE6.0.  A WCH object is one of the most bizarre tricks to avoid a
 * notorious IE bug: IE normally shows "windowed controls" on top of any HTML
 * elements, regardless of any z-index that might be specified in CSS.  This
 * technique is described at: http://www.aplus.co.yu/WCH/
 *
 * A "WCH object" is actually an HTMLIFrame element having a certain "CSS
 * filter" (proprietary MSIE extension) that forces opacity zero.  This object,
 * displayed on top of a windowed control such as a select box, will completely
 * hide the select box, allowing us to place other HTMLElement objects above.
 *
 * WCH stands for "Windowed Controls Hider".
 *
 * @param element [HTMLElement, optional] -- Create the WCH IFRAME inside this.
 *
 *
 * @return [HTMLIFrame or null] a new WCH object if the browser is "supported", null otherwise.
 */
Zapatec.Utils.createWCH = function(element) {
	var f = null;
	element = element || window.self.document.body;
	if (Zapatec.is_ie && !Zapatec.is_ie5) {
		var filter = 'filter:progid:DXImageTransform.Microsoft.alpha(style=0,opacity=0);';
		var id = "WCH" + (++Zapatec.Utils.__wch_id);
		element.insertAdjacentHTML
			('beforeEnd', '<iframe id="' + id + '" scroll="no" frameborder="0" ' +
			 'style="z-index:0;position:absolute;visibility:hidden;' + filter +
			 'border:0;top:0;left:0;width:0;height:0;" ' +
			 'src="javascript:false;"></iframe>');
		f = window.self.document.getElementById(id);
	}
	return f;
};

/**
 * Configure a given WCH object to be displayed on top of the given element.
 * Optionally, a second element can be passed, and in this case it will setup
 * the WCH object to cover both elements.
 *
 * @param f [HTMLIFrame] the WCH object
 * @param el [HTMLElement] the element to cover.
 * @param el2 [HTMLElement, optional] another element to cover.
 */
Zapatec.Utils.setupWCH_el = function(f, el, el2) {
	if (f) {
		var pos = Zapatec.Utils.getAbsolutePos(el),
			X1 = pos.x,
			Y1 = pos.y,
			X2 = X1 + el.offsetWidth,
			Y2 = Y1 + el.offsetHeight;
		if (el2) {
			var p2 = Zapatec.Utils.getAbsolutePos(el2),
				XX1 = p2.x,
				YY1 = p2.y,
				XX2 = XX1 + el2.offsetWidth,
				YY2 = YY1 + el2.offsetHeight;
			if (X1 > XX1)
				X1 = XX1;
			if (Y1 > YY1)
				Y1 = YY1;
			if (X2 < XX2)
				X2 = XX2;
			if (Y2 < YY2)
				Y2 = YY2;
		}
		Zapatec.Utils.setupWCH(f, X1, Y1, X2-X1, Y2-Y1);
	}
};

/**
 * Configure a WCH object to cover a certain part of the screen.
 *
 * @param f [HTMLIFrame] the WCH object.
 * @param x [number] the X coordinate.
 * @param y [number] the Y coordinate.
 * @param w [number] the width of the area.
 * @param h [number] the height of the area.
 */
Zapatec.Utils.setupWCH = function(f, x, y, w, h) {
	if (f) {
		var s = f.style;
		(typeof x != "undefined") && (s.left = x + "px");
		(typeof y != "undefined") && (s.top = y + "px");
		(typeof w != "undefined") && (s.width = w + "px");
		(typeof h != "undefined") && (s.height = h + "px");
		s.visibility = "inherit";
	}
};

/**
 * Hide a WCH object.
 *
 * @param f [HTMLIFrame] object to hide.
 */
Zapatec.Utils.hideWCH = function(f) {
	if (f)
		f.style.visibility = "hidden";
};

//@}

/// \defgroup Scroll-with-window functions
//@{

/**
 * A generic Utils function that returns the current scroll position.
 *
 */
Zapatec.Utils.getPageScrollY = function() {
	return window.pageYOffset ||
			document.documentElement.scrollTop ||
			(document.body ? document.body.scrollTop : 0) ||
			0;
};

// Object setup.
Zapatec.ScrollWithWindow = {};
Zapatec.ScrollWithWindow.list = [];
// Set to a number between 0 and 1, lower means longer scrolling.
Zapatec.ScrollWithWindow.stickiness = 0.25;

/**
 * Registers a given object to have its style.top set equal to the window
 * scroll position as the browser scrolls.
 *
 * @param node [HTMLElement] -- a reference to the node to scroll.
 */
Zapatec.ScrollWithWindow.register = function(node) {
	var top = parseInt(node.style.top) || 0;
	var scrollY = window.pageYOffset || document.body.scrollTop ||
		document.documentElement.scrollTop || 0;
	top -= scrollY;
	if (top < 0) top = 0;
	Zapatec.ScrollWithWindow.list[Zapatec.ScrollWithWindow.list.length] = {
		node: node,
		origTop: top
	};
};

/**
 * Unregisters a given object.
 *
 * @param node [HTMLElement] -- a reference to the node to scroll.
 */
Zapatec.ScrollWithWindow.unregister = function(node) {
	for (var count = 0; count < Zapatec.ScrollWithWindow.list.length; count++) {
		var elm = Zapatec.ScrollWithWindow.list[count];
		if (node == elm.node) {
			Zapatec.ScrollWithWindow.list.splice(count, 1);
			return;
		}
	}
};

/**
 * \internal Called each time the window is scrolled to set objects' positions.
 *
 * @param newScrollY [number] -- the new window scroll position.
 */
Zapatec.ScrollWithWindow.handler = function(newScrollY) {
	// Move oldScrollY towards newScrollY, evening up if the difference is small.
	oldScrollY += ((newScrollY - oldScrollY) * this.stickiness);
	if (Math.abs(oldScrollY - newScrollY) <= 1) oldScrollY = newScrollY;
	for (var count = 0; count < Zapatec.ScrollWithWindow.list.length; count++) {
		var elm = Zapatec.ScrollWithWindow.list[count];
		var node = elm.node;
		if (!elm.origTop) {
			elm.origTop = Zapatec.Utils.getAbsolutePos(node).y;
			node.style.position = 'absolute';
		}
		node.style.top = elm.origTop + parseInt(oldScrollY) + 'px';
	}
};

// Processed scroll position & Event hook.
var oldScrollY = Zapatec.Utils.getPageScrollY();
setInterval(
	'var newScrollY = Zapatec.Utils.getPageScrollY(); ' +
	'if (newScrollY != oldScrollY) { ' +
		'Zapatec.ScrollWithWindow.handler(newScrollY); ' +
	'}', 50);

//@}

/**
 * Destroys the given element (remove it from the DOM tree) if it's not null
 * and it's parent is not null.
 *
 * @param el [HTMLElement] the element to destroy.
 */
Zapatec.Utils.destroy = function(el) {
	if (el && el.parentNode)
		el.parentNode.removeChild(el);
};

/**
 * Opens a new window at a certain URL and having some properties.
 *
 * @param url [string] the URL to open a new window to.
 * @param windowName [string] the name of the new window (as for target attribute).
 * @param width [number] the width of the new window in pixels.
 * @param height [number] the height of the new window in pixels.
 * @param scrollbars [string] "yes" or "no" for scrollbars.
 *
 * @return [object] the new window
 */
Zapatec.Utils.newCenteredWindow = function(url, windowName, width, height, scrollbars){
	var leftPosition = 0;
	var topPosition = 0;
	if (screen.width)
		leftPosition = (screen.width -  width)/2;
	if (screen.height)
		topPosition = (screen.height -  height)/2;
	var winArgs =
		'height=' + height +
		',width=' + width +
		',top=' + topPosition +
		',left=' + leftPosition +
		',scrollbars=' + scrollbars +
		',resizable';
	var win = window.open(url,windowName,winArgs);
	return win;
};

/**
 * Finds the size of the current web page. This is the usable size
 * and does not include the browser's menu and buttons.
 *
 * @return [object] dimension with the height and width of the window
 */
Zapatec.Utils.getWindowSize = function() {
  var iWidth = 0;
  var iHeight = 0;
  if (document.compatMode && document.compatMode == 'CSS1Compat') {
    // Standards-compliant mode
    if (window.opera) {
    	iWidth = document.body.clientWidth || 0;
    	iHeight = document.body.clientHeight || 0;
    } else {
    	iWidth = document.documentElement.clientWidth || 0;
    	iHeight = document.documentElement.clientHeight || 0;
    }
  } else {
    // Non standards-compliant mode
  	iWidth = window.innerWidth || document.body.clientWidth ||
  	 document.documentElement.clientWidth || 0;
  	iHeight = window.innerHeight || document.body.clientHeight ||
  	 document.documentElement.clientHeight || 0;
  }
	return {
	  width: iWidth,
	  height: iHeight
	};
};


/**
 * Given a reference to a select element, this function will select the option
 * having the given value and optionally will call the default handler for
 * "onchange".
 *
 * @param sel [HTMLSelectElement] reference to the SELECT element.
 * @param val [string] the value that we should select.
 * @param call_default [boolean] true if the default onchange should be called.
 */
Zapatec.Utils.selectOption = function(sel, val, call_default) {
	var a = sel.options, i, o;
	for (i = a.length; --i >= 0;) {
		o = a[i];
		o.selected = (o.val == val);
	}
	sel.value = val;
	if (call_default) {
		if (typeof sel.onchange == "function")
			sel.onchange();
		else if (typeof sel.onchange == "string")
			eval(sel.onchange);
	}
};

/**
 * A more flexible way to get the "nextSibling" of a given element.  If the
 * "tag" argument is passed, then this function will return the next sibling
 * that has a certain tag.  Otherwise it will simply return el.nextSibling.
 *
 * @param el [HTMLElement] reference to the anchor element.
 * @param tag [string] the tag name of the returned node.
 * @param alternateTag [string] the alternate tag name of the returned node.
 *
 * @return [HTMLElement or null] el.nextSibling if tag is not passed, or the
 * first element after el having the specified tag.  Null is returned if no
 * element could be found.
 */
Zapatec.Utils.getNextSibling = function(el, tag, alternateTag) {
	el = el.nextSibling;
	if (!tag) {
		return el;
	}
	tag = tag.toLowerCase();
	if (alternateTag) alternateTag = alternateTag.toLowerCase();
	while (el) {
		if (el.nodeType == 1 && (el.tagName.toLowerCase() == tag ||
		 (alternateTag && el.tagName.toLowerCase() == alternateTag))) {
			return el;
		}
		el = el.nextSibling;
	}
	return el;
};

/**
 * Similar to Zapatec.Utils.getNextSibling(), this function will return the
 * first child of the given element that has a specified tag.
 *
 * @param el [HTMLElement] reference to the anchor element.
 * @param tag [string] the tag name of the returned node.
 * @param alternateTag [string] the alternate tag name of the returned node.
 *
 * @return [HTMLElement] reference to the found node, or null if none could be
 * found.
 */
Zapatec.Utils.getFirstChild = function(el, tag, alternateTag) {
  if (!el) {
    return null;
  }
	el = el.firstChild;
  if (!el) {
    return null;
  }
	if (!tag) {
		return el;
	}
	tag = tag.toLowerCase();
	if (el.nodeType == 1) {
		if (el.tagName.toLowerCase() == tag) {
			return el;
		} else if (alternateTag) {
			alternateTag = alternateTag.toLowerCase();
			if (el.tagName.toLowerCase() == alternateTag) {
				return el;
			}
		}
	}
	return Zapatec.Utils.getNextSibling(el, tag, alternateTag);
};

/**
 * Function that concatenates and returns all text child nodes of the
 * specified node.
 *
 * @param objNode [Node] -- reference to the node.
 * @return [string] -- concatenated text child nodes
 */
Zapatec.Utils.getChildText = function(objNode) {
	if (objNode == null) {
		return '';
	}
	var arrText = [];
	var objChild = objNode.firstChild;
	while (objChild != null) {
		if (objChild.nodeType == 3) { // Node.TEXT_NODE
			arrText.push(objChild.data);
		}
		objChild = objChild.nextSibling;
	}
	return arrText.join(' ');
};

/**
 * Similar to the DOM's built in insertBefore.
 * Insert a node after an existing node.
 *
 * @param el [oldNode] The existing element
 * @param el [newNode] the new element to insert after the old one.
 *
 */
Zapatec.Utils.insertAfter = function(oldNode, newNode) {
	if(oldNode.nextSibling) {
		oldNode.parentNode.insertBefore(newNode, oldNode.nextSibling);
	} else {
		oldNode.parentNode.appendChild(newNode);
	}
}

Zapatec.Utils._ids = {};	/**< [number, static] maintains a list of generated IDs */

/**
 * Generates an unique ID, for a certain code (let's say "class").  If the
 * optional "id" argument is passed, then it just returns the id for that code
 * (no generation).  This function is sometimes useful when we need to create
 * elements and be able to access them later by ID.
 *
 * @param code [string] the class of ids.  User defined, can be anything.
 * @param id [string, optional] specify if the ID is already known.
 *
 * @return [string] the unique ID
 */
Zapatec.Utils.generateID = function(code, id) {
	if (typeof id == "undefined") {
		if (typeof this._ids[code] == "undefined")
			this._ids[code] = 0;
		id = ++this._ids[code];
	}
	return "zapatec-" + code + "-" + id;
};

/**
*  Add a tooltip to the specified element.
*
*  Function that adds a custom tooltip for an element.  The "target" is the
*  element to where the tooltip should be added to, and the "tooltip" is a DIV
*  that contains the tooltip text.  Optionally, the tooltip DIV can have the
*  "title" attribute set; if so, its value will be displayed highlighted as
*  the title of the tooltip.
*
*  @param target  reference to or ID of the target element
*  @param tooltip reference to or ID of the tooltip content element
*/

Zapatec.Utils.addTooltip = function(target, tooltip) {
return new Zapatec.Tooltip(target, tooltip);
};

Zapatec.isLite=true;

Zapatec.Utils.checkActivation = function() {
	if (!Zapatec.isLite)	return true;

	var arrProducts=[]

	add_product=function(script, webdir_in, name_in)
	{
	arrProducts[script]={webdir:webdir_in, name:name_in, bActive:false}
	}

	add_product('calendar.js', 'prod1',   'Calendar')
	add_product('menu.js',     'prod2',   'Menu')
	add_product('tree.js',     'prod3',   'Tree')
	add_product('form.js',     'forms',   'Forms')
	add_product('effects.js',  'effects', 'Effects')
	add_product('hoverer.js',  'effects', 'Effects - Hoverer')
	add_product('slideshow.js','effects', 'Effects - Slidshow')
	add_product('zpgrid.js',   'grid',    'Grid')
	add_product('slider.js',   'slider',  'Slider')
	add_product('zptabs.js',   'tabs',    'Tabs')
	add_product('zptime.js',   'time',    'Time')
	add_product('window.js',   'windows', 'Window')


	var strName, arrName, i
	var bProduct=false // Flag yes if we have a zapatec script
	var scripts = document.getElementsByTagName('script');
	for (i=0; i<scripts.length; i++)
	{
		// If wizard then do NOT do link back check, which makes wizard err out
		if (/wizard.js/i.test(scripts[i].src))
			return true

		arrName=scripts[i].src.split('/')
		if (arrName.length==0)
			strName=scripts[i]
		else
			strName=arrName[arrName.length-1]
		strName=strName.toLowerCase()
		// Get each active product
		if (typeof arrProducts[strName] != 'undefined')
			{
			bProduct=true
			arrProducts[strName].bActive=true
			}
	}

	// Is a LITE product even being used?
	if (!bProduct) return true;


	var anchors = document.getElementsByTagName('A');
	for(i = 0; i < anchors.length; i++)
		if (/(dev|www)\.zapatec\.com/i.test(anchors[i].href))
			return true;

	var strMsg='You are using the Free version of the Zapatec Software.\n'+
	'While using the Free version, a link to www.zapatec.com in this page is required.'

	for (i in arrProducts)
		// Get each active product
		if (arrProducts[i].bActive==true)
			strMsg+='\nTo purchase the Zapatec ' + arrProducts[i].name + ' visit www.zapatec.com/website/main/products/' + arrProducts[i].webdir + '/'

	alert(strMsg)

	return false;
}

// Browser sniffing

/// detect Opera browser
Zapatec.is_opera = /opera/i.test(navigator.userAgent);

/// detect a special case of "web browser"
Zapatec.is_ie = ( /msie/i.test(navigator.userAgent) && !Zapatec.is_opera );

/// detect IE5.0/Win
Zapatec.is_ie5 = ( Zapatec.is_ie && /msie 5\.0/i.test(navigator.userAgent) );

/// detect IE for Macintosh
Zapatec.is_mac_ie = ( /msie.*mac/i.test(navigator.userAgent) && !Zapatec.is_opera );

/// detect KHTML-based browsers
Zapatec.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent);

/// detect Konqueror
Zapatec.is_konqueror = /Konqueror/i.test(navigator.userAgent);

/// detect Gecko
Zapatec.is_gecko = /Gecko/i.test(navigator.userAgent);

/**
 * Simulation of Function call() method that is missing in IE 5.0.
 */
if (!Function.prototype.call) {
	Function.prototype.call = function () {
		var self = arguments[0];
		self._this_func = this;
		var args = new Array();
		for (var i=1; i < arguments.length; i++) {
			args[args.length] = 'arguments[' + i + ']';
		}
		var ret = eval('self._this_func(' + args.join(',') + ')');
		self._this_func = null;
		return ret;
	};
}

/**
 * Simulation of Array pop() method that is missing in IE 5.0.
 */
if (!Array.prototype.pop) {
	Array.prototype.pop = function() {
		var last;
		if (this.length) {
			last = this[this.length - 1];
			this.length -= 1;
		}
		return last;
	};
}

/**
 * Simulation of Array push() method that is missing in IE 5.0
 */
if (!Array.prototype.push) {
	Array.prototype.push = function() {
		for (var i = 0; i < arguments.length; i++) {
			this[this.length] = arguments[i];
		}
		return this.length;
	};
}

/**
 * Simulation of Array shift() method that is missing in IE 5.0.
 */
if (!Array.prototype.shift) {
	Array.prototype.shift = function() {
		var first;
		if (this.length) {
			first = this[0];
			for (var i = 0; i < this.length - 1; i++) {
				this[i] = this[i + 1];
			}
			this.length -= 1;
		}
		return first;
	};
}

/**
 * Simulation of Array unshift() method that is missing in IE 5.0.
 */
if (!Array.prototype.unshift) {
	Array.prototype.unshift = function() {
		if (arguments.length) {
			var i, len = arguments.length;
			for (i = this.length + len - 1; i >= len; i--) {
				this[i] = this[i - len];
			}
			for (i = 0; i < len; i++) {
				this[i] = arguments[i];
			}
		}
		return this.length;
	};
}

/**
 * Simulation of Array splice() method that is missing in IE 5.0.
 */
if (!Array.prototype.splice) {
	Array.prototype.splice = function(index, howMany) {
		var elements = [], removed = [], i;
		for (i = 2; i < arguments.length; i++) {
			elements.push(arguments[i]);
		}
		for (i = index; (i < index + howMany) && (i < this.length); i++) {
			removed.push(this[i]);
		}
		for (i = index + howMany; i < this.length; i++) {
			this[i - howMany] = this[i];
		}
		this.length -= removed.length;
		for (i = this.length + elements.length - 1; i >= index + elements.length;
		 i--) {
			this[i] = this[i - elements.length];
		}
		for (i = 0; i < elements.length; i++) {
			this[index + i] = elements[i];
		}
		return removed;
	};
}

/**
 * Displays error message. Override this if needed.
 *
 * \param objArgs [number] error object:
 * {
 *   severity: [string, optional] error severity,
 *   description: [string] human readable error description
 * }
 */
Zapatec.Log = function(objArgs) {
  // Check arguments
  if (!objArgs) {
    return;
  }
  // Form error message
  var strMessage = objArgs.description;
  if (objArgs.severity) {
    strMessage = objArgs.severity + ':\n' + strMessage;
  }
  // Display error message
  alert(strMessage);
};

/// Zapatec.Utils.Array object which contains additional for arrays method
Zapatec.Utils.Array = {};

/**
 * Inserts the element into array. 
 * It influences the order in which the elements will be iterated in the for...in cycle.
 *
 * @param arr [array] array to work with.
 * @param el [mixed] element to insert.
 * @param key [string] element to insert.
 * @param nextKey [string] element to be inserted before.
 * @return [string] new Array.
 */
Zapatec.Utils.Array.insertBefore = function (arr, el, key, nextKey) {
	var tmp = new Array();
	for(var i in arr) {
		if (i == nextKey) {
			if (key) {
				tmp[key] = el;
			} else {
				tmp.push(el);
			}
		}
		tmp[i] = arr[i];
	}
	return tmp;
}
/**
 * The Zapatec DHTML Calendar
 *
 * Copyright (c) 2004-2005 by Zapatec, Inc.
 * http://www.zapatec.com
 * 1700 MLK Way, Berkeley, California,
 * 94709, U.S.A.
 * All rights reserved.
 *
 *
 * Tree Widget
 */
// if (_zapatec_tree_url)
// 	_zapatec_tree_url = _zapatec_tree_url.replace(/\/*$/, '/');

/**
 * The Zapatec.Tree object constructor.  Pass to it the ID of an UL element (or
 * a reference to the element should you have it already) and an optional
 * configuration object.  This function creates and initializes the tree widget
 * according to data existent in the nested list, and applies the configuration
 * specified.
 *
 * The configuration object may contain the following options (the following
 * shows default values):
 *
 * \code
 * {
 *    hiliteSelectedNode : true,     // boolean
 *    compact            : false,    // boolean
 *    dynamic            : false,    // boolean
 *    initLevel          : false,    // false or number
 *    defaultIcons       : null      // null or string
 * }
 * \endcode
 *
 * - hiliteSelectedNode -- if \b false is passed, the tree will not highlight
 *   the currently selected node.
 * - compact -- if \b true is passed the tree will work in a "compact" mode; in
 *   this mode it automatically closes sections not relevant to the current
 *   one.
 * - dynamic -- if \b true is passed the tree will use the "dynamic initialization"
 *   technique which greatly improves generation time.  Some functionality is
 *   not available in this mode until all the tree was generated.  In "dynamic"
 *   mode the tree is initially collapsed and levels are generated "on the fly"
 *   as the end user expands them.  You can't retrieve nodes by ID (which
 *   implies you can't synchronize to certain nodes) until they have been
 *   generated.
 * - initLevel -- when this is a numeric value, it specifies the maximum
 *   "expand level" that the tree will use initially.  Therefore, if for
 *   instance you specify 1 then the tree will be initially expanded one level.
 *   Pass here 0 to have the tree fully collapsed, or leave it \b false to have
 *   the tree fully expanded.
 * - defaultIcons -- you can pass here a string.  If so, all tree items will
 *   get an additional TD element containing that string in the \b class
 *   attribute.  This helps you to include custom default icons without
 *   specifying them as IMG tags in the tree.  See our examples.
 *
 * @param el [string or HTMLElement] -- the UL element
 * @param config [Object, optional] -- the configuration options
 *
 * @return
 */
Zapatec.Tree = function(el, config) {
	if (typeof config == "undefined"){
		config = {};
	}

	function param_default(name, value) {
		if (typeof config[name] == "undefined"){
			config[name] = value;
		}
	};

	param_default('d_profile', false);
	param_default('hiliteSelectedNode', true);
	param_default('compact', false);
	param_default('dynamic', false);
	param_default('initLevel', false);

	//expand/collapse the tree when the text or image are clicked
	param_default('expandOnLabel', true); 

	//Keep track, using cookies, of the last location the user opened
	param_default('saveState', false); 
	
	if(config.dynamic){
		config.initLevel = 0;
	}

	if(
		config.dynamic ||
		config.saveState && 
		(
			typeof(config.saveId) != "string" || 
			(
				typeof(config.saveId) == "string" &&
				config.saveId.length == 0
			)
		)
	){
		config.saveState = false;
	}

	this.config = config;
	
	// <PROFILE>
	if (this.config.d_profile) {
		var T1 = new Date().getTime();
	
		profile = {
			items : 0,
			trees : 0,
			icons : 0
		};
	}
	// </PROFILE>

	if (typeof el == "string"){
		el = document.getElementById(el);
	}

	this.list = el;
	this.items = {};
	this.trees = {};
	this.selectedItem = null;
	this.id = el.id || Zapatec.Utils.generateID("tree");
	var top = this.top_parent = Zapatec.Utils.createElement("div");
	top.className = "tree tree-top";
	this.createTree(el, top, 0);
	el.parentNode.insertBefore(top, el);
	el.parentNode.removeChild(el);
	Zapatec.Tree.all[this.id] = this;

	// check if we have an initially selected node and sync. the tree if so
	if (this.selectedItem){
		this.sync(this.selectedItem.__msh_item);
	}

	//if we're keeping track of state, and we have saved state information
	if(this.config.saveState) {
		//restore to previous node
		var txt = Zapatec.Utils.getCookie("Zapatec.Tree-" + config.saveId)

		if (txt) {
			this.sync(txt);
		}
	}

	// <PROFILE>
	if (this.config.d_profile) {
		alert("Generated in " + (new Date().getTime() - T1) + " milliseconds\n" +
		      profile.items + " total tree items\n" +
		      profile.trees + " total (sub)trees\n" +
		      profile.icons + " total icons");
	}
	// </PROFILE>
};

/**
 * This global variable keeps a "hash table" (that is, a plain JavaScript
 * object) mapping ID-s to references to Zapatec.Tree objects.  It's helpful if
 * you want to operate on a tree but you don't want to keep a reference to it.
 * Example:
 *
 * \code
 *   // the following makes a tree for the <ul id="tree-id"> element
 *   var tree = new Zapatec.Tree("tree-id");
 *   // ... later
 *   var existing_tree = Zapatec.Tree.all("tree-id");
 *   // and now we can use \b existing_tree the same as we can use \b tree
 *   // the following displays \b true
 *   alert(existing_tree == tree);
 * \endcode
 *
 * So in short, this variable remembers values returned by "new
 * Zapatec.Tree(...)" in case you didn't.
 */
Zapatec.Tree.all = {};

/**
 * \internal Function that creates a (sub)tree.  This function walks the UL
 * element, computes and assigns CSS class names and creates HTML elements for
 * a subtree.  Each time a LI element is encountered, createItem() is called
 * which effectively creates the item.  Beware that createItem() might call
 * back this function in order to create the item's subtree. (so createTree and
 * createItem form an indirect recursion).
 *
 * @param list [HTMLElement] -- reference to the UL element
 * @param parent [HTMLElement] -- reference to the parent element that should hold the (sub)tree
 * @param level [integer] -- the level of this (sub)tree in the main tree.
 *
 * @return id -- the (sub)tree ID; might be automatically generated.
 */
Zapatec.Tree.prototype.createTree = function(list, parent, level) {
	// PROFILE
	if (this.config.d_profile){
		++profile.trees;
	}

	var id = list.id || Zapatec.Utils.generateID("tree.sub");
	var self = this;

	function _makeIt() {
		self.creating_now = true;
		var last_li = null;
		var next_li = null;
		var i = list.firstChild;
        var items = parent.__msh_items = [];

		self.trees[id] = parent;
		parent.__msh_level = level;
		parent.__msh_treeid = id;

		while (i) {
			if (last_li){
				last_li.className += " tree-lines-c";
			}

			if (i.nodeType != 1){
				i = i.nextSibling;
			} else {
				next_li = Zapatec.Utils.getNextSibling(i, 'li');
			
				if (i.tagName.toLowerCase() == 'li') {
					last_li = self.createItem(i, parent, next_li, level);
				
					if (last_li) { //false when webmaster creates malformed tree 
						items[items.length] = last_li.__msh_item;
					}
				}

				i = next_li;
			}
		}

		i = parent.firstChild;
		
		if (i && !level) {
			i.className = i.className.replace(/ tree-lines-./g, "");
			i.className += (i === last_li) ? " tree-lines-s" : " tree-lines-t";
		}
		
		if (last_li && (level || last_li !== i)) {
			last_li.className = last_li.className.replace(/ tree-lines-./g, "");
			last_li.className += " tree-lines-b";
		}
		
		self.creating_now = false;
	};

	if (this.config.dynamic && level > 0){
		(this.trees[id] = _makeIt);
	} else {
		_makeIt();
	}

	return id;
};

/**
 * \internal This function walks through a LI element and creates the HTML
 * elements associated with that tree item.  When it encounters an UL element
 * it calls createTree() in order to create the item's subtree.  This function
 * may also call item_addIcon() in order to add the +/- buttons or icons
 * present in the item definition as IMG tags, or item_addDefaultIcon() if the
 * tree configuration specifies "defaultIcons" and no IMG tag was present.
 *
 * @param li [HTMLElement] -- reference to the LI element
 * @param parent [HTMLElement] -- reference to the parent element where the HTML elements should be created
 * @param next_li [HTMLLiElement] -- reference to the next LI element, if this is not the last one
 * @param level [integer] -- the level of this item in the main tree
 * @param atStart [HTMLElement optional] -- reference to the element DIV with a TABLE object that represents the child following the new child added or inserted   
 *
 * @return [HTMLElement] -- a reference to a DIV element holding the HTML elements of the created item
 */
Zapatec.Tree.prototype.createItem = function(li, parent, next_li, level, atStart) {
    // PROFILE
	if (this.config.d_profile){
		++profile.items;
	}

	if (!li.firstChild){
		return;
	}

	var afterNode = null; 
	if (atStart) { //Optional parameter after the fourth parameter to allow the new created node to be inserted before it, instead of appending to the parent 
		afterNode = atStart; 
	} 

	var
		id = li.id || Zapatec.Utils.generateID("tree.item"),
		item = this.items[id] = ((afterNode == null) ? Zapatec.Utils.createElement("div", parent) : Zapatec.Utils.createElement("div")), //Do not append the new div element to the parent, the new node in the div will be inserted before the 'afterNode'.
		t = Zapatec.Utils.createElement("table", item),
		tb = Zapatec.Utils.createElement("tbody", t),
		tr = Zapatec.Utils.createElement("tr", tb),
		td = Zapatec.Utils.createElement("td", tr),
		is_list,
		tmp,
		i = li.firstChild,
		has_icon = false;

	t.className = "tree-table";
	t.cellSpacing = 0;
	t.cellPadding = 0;
	td.className = "label";
	item.className = li.className + " tree-item";
	item.__msh_item = id;
	item.__msh_tree = this.id;
	item.__msh_parent = parent.__msh_treeid;

	if (afterNode) { //A child node from the same parent is sent in to let the new child node inserted before it. 
		parent.insertBefore(item, afterNode); //New item inserted before the 'afterNode'
	}

	while (i) {
		is_list = i.nodeType == 1 && /^[ou]l$/i.test(i.tagName);

		if (i.nodeType != 1 || !is_list) {
			if (i.nodeType == 3) {
				// remove whitespace, it seems to cause layout trouble
				tmp = i.data.replace(/^\s+/, '');
				tmp = tmp.replace(/\s+$/, '');
				li.removeChild(i);

				if (tmp) {
					i = Zapatec.Utils.createElement("span");
					i.className = "label";
					i.innerHTML = tmp;
					i.onclick = Zapatec.Tree.onItemToggle;
					td.appendChild(i);
				}
			} else if (i.tagName.toLowerCase() == 'img') {
				this.item_addIcon(item, i);
				has_icon = true;
			} else {
				i.onclick = Zapatec.Tree.onItemToggle;
				td.appendChild(i);
			}

			i = li.firstChild;
			
			continue;
		}

		if (is_list) {
			this.item_addIcon(item, null);

			var np; 
			if (afterNode != null) { 
				np = Zapatec.Utils.createElement("div"); 
				parent.insertBefore(np, afterNode); //New item inserted before the 'afterNode' 
			} else { 
				np = Zapatec.Utils.createElement("div", item.parentNode); 
			}

			np.__msh_item = id;
			np.className = "tree";

			if (next_li){
				np.className += " tree-lined";
			}

			item.__msh_subtree = this.createTree(i, np, level + 1);

			if ((this.config.initLevel !== false && this.config.initLevel <= level) ||
				(this.config.compact && !/(^|\s)expanded(\s|$)/i.test(li.className))
				|| /(^|\s)collapsed(\s|$)/i.test(li.className)
			){
				item.className += " tree-item-collapsed";
				this.toggleItem(id, false);
			} else {
				item.className += " tree-item-expanded";
			}
			
			if (/(^|\s)selected(\s|$)/i.test(li.className)){
				this.selectedItem = item;
			}

			break;
		}
	}

	if (!has_icon){
		this.item_addDefaultIcon(item, this.config.defaultIcons);
	}

	return item;
};

/** 
 * Call this function to create a html element with the optional element type specified. 
 * By default, it is a <LI> element. 
 * @param html [string, optional] -- html of the node; may include <UL>, <LI> elements; user is responsible for the content of the html 
 * @param type [string, optional] -- type of the node to be created  
 */ 
Zapatec.Tree.prototype.makeNode = function(html, type) { 
        if (!type) { 
           type = "li"; //Make it a <LI> node if the type is not specified.      
        } 
        var node = Zapatec.Utils.createElement(type); 
        if (html) {  
                node.innerHTML = html; //Assign the inner html of the node if it is specified. 
        } 
        return node; 
} 
 
/** 
 * Call to get the parent to append, insert or remove a child either at start or end position or in between two nodes of the (sub-)tree. 
 * 
 * For insert, if the user selects a tree-item that represents the root node of a subtree, then the new child will be inserted outside the subtree,  
 * i.e. exactly before the root node of the subtree. However, if the user selects the node that represents one of the children in the subtree, 
 * then the new child will be inserted in the subtree under the root. 
 * For remove, if the user selects the root node of a subtree, the root node including its children will be removed. If the user selects a node that is not 
 * a root node of a subtree, then only that node will be deleted. 
 * In the implementation, the p will first retrieve the subtree from Tree.trees. But this is just the object that encapsulates the subtree, not the root of the 
 * entire subtree. For insert and remove,  
 * @param id     [String] -- id of the parent the new child will be added to or inserted before/at. 
 * @param mode   [String optional] -- "I" is for inserting a child node. "R" is for removing a child node.  
 */ 
Zapatec.Tree.prototype.getParent = function(id, mode) { 
        var parent = null; 
        for (var i in this.trees) { 
            //id sent in may be name of the top tree or one of the tree item's or subtree parent's name  
            if ( (this.trees[i].__msh_treeid == id) || (this.trees[i].__msh_item == id) ) { 
               parent = this.trees[i]; //Get the body of a subtree 
               break; 
            } 
        } 
        //For inserting a new child before the referece child, the reference child should be the tree item,  
        //not the subtree under it (in case it has tree nodes under it). This is because inserting a new child  
        //should be 'before' the subtree (if the tree item has tree nodes under it), not inside or become part of the subtree. 
        if ( (mode != null) && ((mode.toUpperCase() == "I") || (mode.toUpperCase() == "R")) ) {  //At this point, if p not null, then it must be body of the subtree. So, get the tree item (root node of the subtree) instead.  
           //Otherwise id doesn't refer to a subtree. In this case, get the tree item instead. 
           if (parent != null) { //A subtree (not include its root) has been retrieved. That means the insertion or removal operation got to be performed before or at the root of the subtree. This warrants the retrival of the root of the subtree. 
              if (parent.className != this.top_parent.className) //As long as p is not the top parent, get the previous sibling or node of p. The previous sibling will be the root of the subtree. 
                 parent = parent.previousSibling; 
           } else parent = this.items[id.toLowerCase()]; //If no subtree is retrieved, then it must an item node, then get it from array this.items. 
        } 
        if (!parent) { //If the node matching the id still cannot be found, then look into each item under a subtree  
           parent = this.items[id.toLowerCase()]; 
        } 
        return parent;   
} 
 
/** 
 * Append a child to the start or end of the given parent. 
 * 
 * @param parent   [HTMLElement] -- reference to a tree node (either as a node at top level or at a subtree level) the new child going to be appended to in the tree. 
 * @param newChild [HTMLElement] -- reference to an HTML element created of type LI, to which futher HTML elements such UL and LI can be included to generate subtrees. 
 * @param atStart  [boolean, optional] -- true if the child going to be added at the start of the parent.  
 */ 
Zapatec.Tree.prototype.appendChild = function(parent, newChild, atStart) { 
	atStart = (atStart == true); 

    // Abort operation when either parent/child is empty or the child node is 
    //   already added to the tree. 
	if (
		parent == null || 
		newChild == null || 
		typeof(parent) == "undefined" ||
		typeof(newChild) == "undefined" ||
		this.items[newChild.id]
	){
		return; 
	}     

	var item = null; 

	if(parent.firstChild == null){
		atStart = false;
	}
	
	if (atStart) { //Append new child before first child of the parent 
		item = this.createItem(newChild, parent, parent.firstChild.nextSibling, parent.__msh_level, parent.firstChild); 
	} else { //Append new child after last child of the parent 
		item = this.createItem(newChild, parent, null, parent.__msh_level); 
	} 
         
	// After adding a child, re-draw tree lines that connect the new child to 
	//   the tree. This is necessary because the tree structure has been 
	//   changed due to addition or insertion of a new child node. 
	if (item) { //Child added has no subtree 
		var this_node = null; 
		var next_node = null; 
		var prev_node = null; 
		var subtree   = false; 
                 
		if (atStart) { //Child appended at start of the tree 
			this_node = parent.childNodes[0]; //Always the first node regardless of whether has a subtree under it 
			
			if (item.__msh_subtree==null) { //New child appended has no subtree 
				next_node = parent.childNodes[1]; //Next node will be the second node - the one after the new child node. 
			} else { //New child appended has a subtree 
				next_node = parent.childNodes[2]; //Next node will be the third node - two nodes after the new child node 
			} 
		} else { //Child appended at end of the tree 
			//Get the appropriate tree node the new node going to attach to 
			if (!item.__msh_subtree) { //Child appended has no subtree 
				this_node = parent.childNodes[parent.childNodes.length-1]; //Last node 

				if(parent.childNodes.length > 1){
					prev_node = parent.childNodes[parent.childNodes.length-2]; 
					subtree = (prev_node.className != null && prev_node.className == "tree"); 
					
					if (subtree) { //prev_node is a not a tree item, it has a subtree 
						prev_node.className += " tree-lined"; //Add vertical tree line to the tree 
						prev_node = parent.childNodes[parent.childNodes.length-3]; //Get its parent instead  
					} 
				}
			} else { 
				this_node = parent.childNodes[parent.childNodes.length-2]; //Second last node 
                                          
				prev_node = parent.childNodes[parent.childNodes.length-3]; 
				subtree = (prev_node.className != null && prev_node.className == "tree"); 
				
				if (subtree) { //prev_node is a not a tree item, it has a subtree 
					prev_node.className += " tree-lined"; //Add vertical tree line to the tree 
					prev_node = parent.childNodes[parent.childNodes.length-4]; //Get its parent instead 
				} 
			} 
		} 
                 
		//Draw tree lines between the child and the parent 
		if (this_node) { //Make sure the child is in the parent (sub-)tree 
			this_node.className = this_node.className.replace(/ tree-lines-./g, ""); 
			
			if (atStart) { 
				this_node.className += " tree-lines-t"; 
				
				if (next_node) { 
					next_node.className = next_node.className.replace(/ tree-lines-./g, ""); 
					next_node.className += " tree-lines-c"; 
				} 
			} else { 
				this_node.className += " tree-lines-b"; 

				if (prev_node) { 
					prev_node.className = prev_node.className.replace(/ tree-lines-./g, ""); 
                                          
					prev_node.className += " tree-lines-c"; 
					
					if (subtree) { 
						prev_node.className += " tree-lines-c"; 
					} 
				} 
			} 
		} 

		return item;
	} 
};  
 
/** 
 * A new child can be inserted between two nodes/children or before one node /children at the same tree level.  
 * Inserting the new child before the first node of a tree is allowed but not allowed  
 * after the last node of the tree, i.e. end of the tree, except before the last node. 
 * @param newChild [HTMLElement] -- New child node to be inserted into the tree 
 * @param refChild [HTMLElement] -- Reference to the child node which the new child node will be inserted before 
 */ 
Zapatec.Tree.prototype.insertBefore = function(newChild, refChild) { 
	//Abort operation when either newChild/refChild is empty or the child node is already added to the tree..   
	if(
		newChild == null || 
		refChild == null || 
		typeof(newChild) == "undefined" ||
		typeof(refChild) == "undefined" || 
		this.items[newChild.id]
	){
		return; 
	} 

	var parent = refChild.parentNode; 
	var item = this.createItem(newChild, parent, parent.firstChild.nextSibling, parent.__msh_level, refChild); 
	var nodeBefore = false, nodeAfter = false; 
	var next_node  = null; 
 
	if(item.previousSibling){
		nodeBefore = true; 
	}

	if(item.nextSibling){
		nodeAfter = true;
	}
 
	item.className = item.className.replace(/ tree-lines-./g, ""); 

	if (nodeBefore && nodeAfter){
		item.className += " tree-lines-c";
	} else if (nodeBefore){
		item.className += " tree-lines-b"; 
	} else if (nodeAfter) {
		//Insert new child at the start of the tree. 
		item.className += " tree-lines-t"; 

		//Since the node now after the new child was the first node before, the tree line from it is not connected to the new child. 
		//So it needs to redraw the tree line for the second node (formerly the first node in the tree). 
		if (item.className.indexOf("tree-item-more tree-item")>-1) { //New child has a subtree under it. 
			next_node = item.nextSibling.nextSibling; //Get the next node at the 'same' level as the new child, skip the new child's subtree. 
		} else { 
			next_node = item.nextSibling; //Get the next node at the 'same' level. 
		} 

		next_node.className = next_node.className.replace(/ tree-lines-./g, ""); 

		//To find out whether next node has next node after it. 
		if (next_node.className.indexOf("tree-item-more tree-item")>-1) { //Next node has a subtree 
			if (next_node.nextSibling.nextSibling != null){
				next_node.className += " tree-lines-c";  
			} else {
				next_node.className += " tree-lines-b"; 
			}
		} else { //Next node is an tree item. 
			if (next_node.nextSibling != null){
				next_node.className += " tree-lines-c";  
			} else {
				next_node.className += " tree-lines-b"; 
			}
		} 
	} 
}; 
 
/** 
 * An old child node in the tree can be removed at any level. 
 * If the old child node happens to be the root of a subtree,  
 * then the entire subtree including child node(s) under the root node will be removed. 
 * If the old child node is just an item node without any child node under it as children,  
 * then the only node removed is the old child node. 
 * Once an item node or a subtree is removed, the node/subtree before and/or after the old  
 * node will be joined together, sometimes with tree lines redrawn. 
 * The only limitations are that the root node of the top tree is not permitted for removal; 
 * the first child node found will be removed if there are more than one tree node with the same id. 
 * @param oldChild [HTMLElement] -- Old child node to be removed from the tree where it can  
 * be an item node without children or a subtree including the old child node as the root. 
 */ 
Zapatec.Tree.prototype.removeChild = function(oldChild) { 
 	if (
		oldChild == null || 
		typeof(oldChild) == "undefined"
	){
		//No child to remove 
		return;
	} else if (oldChild.className == this.top_parent.className) {
		//Top root node is not allowed 
		alert("Removing root node not allowed.") 
		return;  
	} 
         
	//Get the child node's parent node 
	var p = oldChild.parentNode; 
         
	//Remove node(s) - check if the old child represents a root node of a subtree. If it does, remove the child nodes in the subtree as well as the root node. 
	//If it does not represents a root node of a subtree, just remove it from the parent it attached to. 
	//Clean up - remove the subtree's id from this.trees or the item node's id from this.items. 
	if (oldChild.__msh_item && oldChild.__msh_tree && oldChild.__msh_parent) { //Make sure all common attributes of root node (of a subtree) or item node are there. 
		var prev_node = oldChild.previousSibling; 
		var next_node = oldChild.nextSibling; 
		var hasPrevNode = false; 
		var hasNextNode = false; 
            
		//Find out if there is a node before the old node at the same tree level. 
		if (prev_node) { 
			//If the node before the old child is the entire subtree at next level, get the root node of that subtree. 
			//The root node will be at the same level as the old child. Otherwise, the node before the old child is an item node. 

			if (prev_node.__msh_treeid) { //The node before the old child is a tree consists of all tree nodes in a subtree. 
			    //Get the root node of that subtree. It should have the same level as the old child. 
				prev_node = prev_node.previousSibling; 
			} 

			hasPrevNode = true; 
		} else { //No child before the old child at the same level. 
			//Check if the old child has a parent node such as root of a subtree and the parent node is not the top of the tree 
			if (oldChild.parentNode && oldChild.parentNode.className != this.top_parent.className) {                 
			    //This is needed in order to 'not' change any tree line for the child node(s) under a subtree.  
				hasPrevNode = true; 
			} 
		} 
      
		//Find out if there is a node after the old node at the same tree level. 
		if (next_node) { 
			if (oldChild.__msh_subtree) { //Old child is a subtree. So old node is the root of a subtree. Get the node after the subtree then. 
			    //Get the node after the old node at the same level. 
				next_node = next_node.nextSibling; 
				
				if (next_node) {
					//There is a next node at same level as the old node. 
					hasNextNode = true; 
				}
		} else {
			//Old child is an item node. So old node is an item node, not a subtree. Old node and next node are at the same level. 
			hasNextNode = true; //There is a next node at same level as the old node. 
		} 
	} //else no next node 
       
	if (oldChild.__msh_subtree) { //Root node of a subtree; has child node(s) under it. 
	    //Try to get the whole subtree of the old child node 
		var subtreeNode = oldChild.nextSibling; 
          
		if (subtreeNode && oldChild.__msh_subtree == subtreeNode.__msh_treeid) { //Subtree node exists 
			for (var i = 0; i < subtreeNode.childNodes.length; i++) { //Loop through each children of the subtree  
				if (subtreeNode.childNodes[i]){
					//and delete the corresponding object in this.items 
					delete this.items[subtreeNode.childNodes[i].__msh_item];
				}
            } 

			//Remove the corresponding item in this.items for the root of the subtree 
			delete this.items[subtreeNode.__msh_item]; 

			//Remove the whole subtree 
			p.removeChild(subtreeNode); 
		} 
		
		//Remove the corresponding subtree in this.trees 
		delete(this.trees[oldChild.__msh_subtree]); 

		//Remove the child node from its parent
		p.removeChild(oldChild); 
	} else {
		//An item node, not a subtree 
		delete this.items[oldChild.__msh_item]; //Remove the corresponding item in this.items for the item node 
		p.removeChild(oldChild); //Remove the child node from its parent 
	} 

	//Re-draw tree lines nodes/subtrees between removed node if necessary 
	//If there is a previous node and next node between the old node, there is no need to redraw thr tree lines 
	//as the previous and next node will become joined together after the old node is removed. 
	//If 'before' removal of the old node, there is a next node after the old node but no previous node before  
	//the old node, then it needs to redraw the tree line of the next node. The next node thus become the first node of the tree. 
	if (!hasPrevNode && hasNextNode) { 
		if (next_node) { 
			next_node.className = next_node.className.replace(/ tree-lines-./g, ""); 
			next_node.className += " tree-lines-t"; 
		} 
	} else if (hasPrevNode && !hasNextNode) { 
		if (prev_node) { 
			if (prev_node.__msh_subtree) { 
				prev_node.nextSibling.className = prev_node.nextSibling.className.replace(/ tree-lined/g, ""); 
			} //else not a subtree  

			prev_node.className = prev_node.className.replace(/ tree-lines-./g, ""); 
			prev_node.className += " tree-lines-b"; 
		} 
	}
} 
}; 

/**
 * \internal This function adds a TD element having a certain class attribute
 * which helps having a tree containing icons without defining IMG tags for
 * each item.  The class name will be "tgb icon className" (where "className"
 * is the specified parameter).  Further, in order to customize the icons, one
 * should add some CSS lines like this:
 *
 * \code
 *  div.tree-item td.customIcon {
 *    background: url("themes/img/fs/document2.png") no-repeat 0 50%;
 *  }
 *  div.tree-item-expanded td.customIcon {
 *    background: url("themes/img/fs/folder-open.png") no-repeat 0 50%;
 *  }
 *  div.tree-item-collapsed td.customIcon {
 *    background: url("themes/img/fs/folder.png") no-repeat 0 50%;
 *  }
 * \endcode
 *
 * As you can see, it's very easy to customize the default icons for a normal
 * tree item (that has no subtrees) or for expanded or collapsed items.  For
 * the above example to work, one has to pass { defaultIcons: "customIcon" } in
 * the tree configuration object.
 *
 * This function does nothing if the \b className parameter has a false logical
 * value (i.e. is null).
 *
 * @param item [HTMLElement] -- reference to the DIV element holding the item
 * @param className -- a string containing the additional class name
 */
Zapatec.Tree.prototype.item_addDefaultIcon = function(item, className) {
	if (!className){
		return;
	}

	var last_td = item.firstChild.firstChild.firstChild.lastChild, td;
	var td = Zapatec.Utils.createElement("td");
	
	td.className = "tgb icon " + className;
	td.onclick = Zapatec.Tree.onItemToggle;
	last_td.parentNode.insertBefore(td, last_td);
};

/** 
 * \internal This function does different things, depending on whether the \b
 * img parameter is passed or not.  If the \b img is passed, then this function
 * adds it as an icon for the given item.  If not passed, this function creates
 * a "+/-" button for the given item.
 * 
 * @param item [HTMLElement] -- reference to the DIV holding the item elements
 * @param img [HTMLImgElement, optional] -- reference to an IMG element; normally one found in the <LI>
 */
Zapatec.Tree.prototype.item_addIcon = function(item, img) {
	// PROFILE
	if (this.config.d_profile){
		++profile.icons;
	}
	
	var last_td = item.firstChild.firstChild.firstChild, td;
	last_td = img ? last_td.lastChild : last_td.firstChild;
	
	if (!img || !item.__msh_icon) {
		td = Zapatec.Utils.createElement("td");
		td.className = "tgb " + (img ? "icon" : "minus");
		last_td.parentNode.insertBefore(td, last_td);
		td.onclick = Zapatec.Tree.onItemToggle;
	} else {
		td = item.__msh_icon;
		img.style.display = "none";
	}
	
	if (!img) {
		td.innerHTML = "&nbsp;";
		item.className += " tree-item-more";
		item.__msh_state = false; // collapsed
		item.__msh_expand = td;
	} else {
		td.appendChild(img);
		item.__msh_icon = td;
	}
};

/** 
 * This function gets called from a global event handler when some item was
 * clicked.  It selects the item and toggles it if it has a subtree (expands or
 * collapses it).
 * 
 * @param item_id [string] -- the item ID
 */
Zapatec.Tree.prototype.itemClicked = function(item_id, expand) {
        if (! expand) {
		if (this.config.hiliteSelectedNode && this.selectedItem){
			Zapatec.Utils.removeClass(this.selectedItem, "tree-item-selected");
		}
		this.selectedItem = this.items[item_id];
	} else {
		this.toggleItem(item_id, null, expand);
	}
	Zapatec.Utils.writeCookie("Zapatec.Tree-" + this.config.saveId, this.selectedItem.__msh_item, null, '/', 7);
	if (this.config.hiliteSelectedNode && this.selectedItem){
		Zapatec.Utils.addClass(this.selectedItem, "tree-item-selected");
	}
	this.onItemSelect(item_id);
};

/** 
 * This function toggles an item if the \b state parameter is not specified.
 * If \b state is \b true then it expands the item, and if \b state is \b false
 * then it collapses the item.
 * 
 * @param item_id [string] -- the item ID
 * @param state [boolean, optional] -- the desired item state
 * @param expand [boolean, optional] -- should expand/collapse if it is a branch
 * 
 * @return a reference to the item element if found, null otherwise
 */
Zapatec.Tree.prototype.toggleItem = function(item_id, state, expand) {
	if (item_id) {
		var stateDefined = false;

		if(this.config.saveState) {
			stateDefined = true;
		}

		if (this.config.hiliteSelectedNode && this.selectedItem){
			Zapatec.Utils.removeClass(this.selectedItem, "tree-item-selected");
		}
		var item = this.items[item_id];

		if (typeof(state) == "undefined" || state == null) {
			state = !item.__msh_state;
			stateDefined = false;
		}
		
		// Expand is true when the '+' or '-' are clicked. 
		// If expandOnLabel is true we will expand even when the label is clicked
		// We will also expand when the state is explictally passed 

		if (
			(expand || this.config.expandOnLabel || stateDefined)
		){
			var subtree = this._getTree(item.__msh_subtree, this.creating_now);

			if (subtree) {
				subtree.style.display = state ? "block" : "none";
				Zapatec.Utils.removeClass(item, "tree-item-expanded");
				Zapatec.Utils.removeClass(item, "tree-item-collapsed");
				Zapatec.Utils.addClass(item, state ?
					"tree-item-expanded" : "tree-item-collapsed");
			}

			var img = item.__msh_expand;

			if (img){
				img.className = "tgb " + (state ? "minus" : "plus");
			}

			item.__msh_state = state;
			img = item.__msh_icon;

			if (img) {
				img.firstChild.style.display = "none";
				img.appendChild(img.firstChild);
				img.firstChild.style.display = "block";
			}

			if (this.config.compact && state) {
				var a = this._getTree(item.__msh_parent).__msh_items;
				for (var i = a.length; --i >= 0;)
					if (a[i] != item_id)
						this.toggleItem(a[i], false);
			}

			if(/zpLoad(JSON|HTML)=([^ $]*)/.test(item.className) && state){
				var dataType = RegExp.$1;
				var url = RegExp.$2;

				item.className = item.className.replace(/zpLoad(JSON|HTML)=([^ $]*)/g, "");
				var loadingLabel = this.appendChild(this.getParent(item.__msh_item), this.makeNode(XXXLoadingXXX), true);
				var errorFunc = function (objError){
					alert(objError.errorDescription);
					item.className += "zpLoad" + dataType + "=" + url;

					self.removeChild(self.getParent(loadingLabel.__msh_item, "R"));
				}

				var self = this;

				if(dataType == "JSON"){
					Zapatec.Transport.fetchJsonObj({
						url: url,
						method: "GET",
						onLoad: function(objResponse){
							function json2html(json){
								if(json == null || json.length == 0){
									return "";
								}

								var res = "<ul>";

								for(var ii = 0; ii < json.length; ii++){
									var currVal = json[ii];

									if(typeof(currVal) == "string"){
										currVal = [currVal];
									}

									res += "<li>" + currVal[0] + json2html(currVal[1]) + "</li>"
								}

								res += "</ul>";

								return res
							}

							var arr = objResponse;

							for(var ii =0; ii < arr.length; ii++){
								var currVal = arr[ii];

								if(typeof(currVal) == "object" && currVal.length == 1){
									currVal = currVal[0];
								} else if (typeof(currVal) == "object" && currVal.length == 2){
									currVal = currVal[0] + json2html(currVal[1]);
								}
								self.appendChild(self.getParent(item.__msh_item), self.makeNode(currVal));
							}

							self.removeChild(self.getParent(loadingLabel.__msh_item, "R"));
						},
						onError : errorFunc
					});
				} else if(dataType == "HTML"){
					Zapatec.Transport.fetchXmlDoc({
						url: url,
						method: "GET",
						onLoad: function(xmlDoc){
							var cNodes = xmlDoc.documentElement.childNodes;

							for(var jj = 0; jj < cNodes.length; jj++){
								var currentNode = cNodes[jj];

								if(currentNode.nodeType != 1){
									continue;
								}

								var li = self.makeNode(Zapatec.Tree.serializeNode(currentNode, true));

								for (var ii = 0; ii < currentNode.attributes.length; ii++){
									var attr = currentNode.attributes[ii];
									if(attr.name == 'class'){
										li.className = currentNode.getAttribute(attr.name);
									} else {
										li.setAttribute(attr.name, currentNode.getAttribute(attr.name));
									}
								}

								self.appendChild(self.getParent(item.__msh_item), li);
							}

							self.removeChild(self.getParent(loadingLabel.__msh_item, "R"));
						},
						onError : errorFunc
					});
				}
			}
		}

		return item;
	}

	return null;
};

/**
 * Call this function to collapse all items in the tree.
 */
Zapatec.Tree.prototype.collapseAll = function() {
	for (var i in this.trees){
		this.toggleItem(this._getTree(i).__msh_item, false, true);
	}
};

/**
 * Call this function to expand all items in the tree.
 */
Zapatec.Tree.prototype.expandAll = function() {
	for (var i in this.trees) {
		this.toggleItem(this._getTree(i).__msh_item, true, true);
	}
};

/**
 * Call this function to toggle all items in the tree.
 */
Zapatec.Tree.prototype.toggleAll = function() {
	for (var i in this.trees) {
		this.toggleItem(this._getTree(i).__msh_item);
	}
};

/** 
 * Call this function to synchronize the tree to a given item.  This means that
 * all items will be collapsed, except that item and the full path to it.
 * 
 * @param item_id [string] -- the ID of the item to sync to.
 */
Zapatec.Tree.prototype.sync = function(item_id) {
	var item = this.items[item_id];
	
	if (item) {
		this.collapseAll();
		this.selectedItem = item;
		var a = [];
	
		while (item.__msh_parent) {
			a[a.length] = item;
			var pt = this._getTree(item.__msh_parent);
		
			if (pt.__msh_item){
				item = this.items[pt.__msh_item];
			} else {
				break;
			}
		}

		for (var i = a.length; --i >= 0;){
			this.toggleItem(a[i].__msh_item, true);
		}

		Zapatec.Utils.addClass(this.selectedItem, "tree-item-selected");
	}
};

/** 
 * Destroys the tree. Removes all elements. Does not destroy the Zapatec.Tree
 * object itself (actually there's no proper way in JavaScript to do that).
 */
Zapatec.Tree.prototype.destroy = function() {
	var p = this.top_parent;
	p.parentNode.removeChild(p);
};

/** 
 * \internal This function is used when "dynamic initialization" is on.  It
 * retrieves a reference to a subtree if already created, or creates it if it
 * wasn't yet and \b dont_call is \b false (returns null in that case).
 * 
 * @param tree_id [string] the ID of the subtree
 * @param dont_call [boolean] pass true here if you don't want the subtree to be created
 * 
 * @return reference to the tree if it was found or created, null otherwise.
 */
Zapatec.Tree.prototype._getTree = function(tree_id, dont_call) {
	var tree = this.trees[tree_id];

	if (typeof(tree) == "function") {
		if (dont_call){
			tree = null;
		} else {
			tree();
			tree = this.trees[tree_id];
			tree.__msh_state = false;
		}
	}

	return tree;
};

// CUSTOMIZABLE EVENT HANDLERS; default action is "do nothing"

/** 
 * Third party code can override this member in order to add an event handler
 * that gets called each time a tree item is selected.  It receives a single
 * string parameter containing the item ID.
 */
Zapatec.Tree.prototype.onItemSelect = function() {};

// GLOBAL EVENT HANDLERS (to workaround the stupid Microsoft memory leak)

/** 
 * \internal This is a global event handler that gets called when a tree item
 * is clicked.  Don't override! ;-)
 */
Zapatec.Tree.onItemToggle = function() {
	var item = this;
	var body = document.body;
	var expand = false;

	if(/tgb (minus|plus)/.test(this.className)) {
		expand = true;
	}
	
	while (item && item !== body && !/tree-item/.test(item.className)){
		item = item.parentNode;
	}

	Zapatec.Tree.all[item.__msh_tree].itemClicked(item.__msh_item, expand);
};

/** \internal serialize Node object to text
*	@param currentNode [Node] - Node to serialize
*/
Zapatec.Tree.serializeNode = function(currentNode, omitTopLevel){
	if(currentNode == null){
		return;
	}

	if(currentNode.nodeType == 3){ // text node
		return currentNode.nodeValue;
	}

	if(omitTopLevel){
		var res = "";

		if(currentNode.hasChildNodes()){
			for(var ii = 0; ii < currentNode.childNodes.length; ii++){
				res += Zapatec.Tree.serializeNode(currentNode.childNodes[ii]);
			}
		}
	} else {
		var res = "<" + currentNode.tagName;
		
		for (var ii = 0; ii < currentNode.attributes.length; ii++){
			var attr = currentNode.attributes[ii];
			res += " " + attr.name + "=\"" + currentNode.getAttribute(attr.name).replace(/"/g, '\\"') +  "\"";
		}
	    
		if(currentNode.hasChildNodes()){
			res += ">";
	    
			for(var ii = 0; ii < currentNode.childNodes.length; ii++){
				res += Zapatec.Tree.serializeNode(currentNode.childNodes[ii]);
			}
	    
			res += "</" + currentNode.tagName + ">"
		} else {
			res += "/>"
		}
	}
	    
	return res;
}
/**
 * \file transport.js
 * Zapatec Transport library.
 * Used to fetch data from the server, parse and serialize XML and JSON data.
 *
 * Copyright (c) 2004-2005 by Zapatec, Inc.
 * http://www.zapatec.com
 * 1700 MLK Way, Berkeley, California,
 * 94709, U.S.A.
 * All rights reserved.
 */

if (typeof Zapatec == 'undefined') {
  /**
   * Namespace definition.
   */
  Zapatec = {};
}

/**
 * Namespace definition.
 */
Zapatec.Transport = {};

// Determine most current versions of ActiveX objects available
if (typeof ActiveXObject != 'undefined') {

  /**
   * \internal String variable with most current version of XMLDOM ActiveX
   * object name available.
   */
  Zapatec.Transport.XMLDOM = null;

  /**
   * \internal String variable with Most current version of XMLHTTP ActiveX
   * object name available.
   */
  Zapatec.Transport.XMLHTTP = null;

  /**
   * \internal Returns first available ActiveX object name from the given list.
   *
   * \param arrVersions [object] list of ActiveX object names to test.
   * \return [string] first available ActiveX object name or null.
   */
  Zapatec.Transport.pickActiveXVersion = function(arrVersions) {
    for (var iVn = 0; iVn < arrVersions.length; iVn++) {
      try {
        var objDocument = new ActiveXObject(arrVersions[iVn]);
        // If it gets to this point, the string worked
        return arrVersions[iVn];
      } catch (objException) {};
    }
    return null;
  };

  // Get most current version of XMLDOM ActiveX object
  Zapatec.Transport.XMLDOM = Zapatec.Transport.pickActiveXVersion([
    'Msxml2.DOMDocument.4.0',
    'Msxml2.DOMDocument.3.0',
    'MSXML2.DOMDocument',
    'MSXML.DOMDocument',
    'Microsoft.XMLDOM'
  ]);

  // Get most current version of XMLHTTP ActiveX object
  Zapatec.Transport.XMLHTTP = Zapatec.Transport.pickActiveXVersion([
    'Msxml2.XMLHTTP.4.0',
    'MSXML2.XMLHTTP.3.0',
    'MSXML2.XMLHTTP',
    'Microsoft.XMLHTTP'
  ]);

  // We don't need this any more
  Zapatec.Transport.pickActiveXVersion = null;

}

/**
 * Creates cross browser XMLHttpRequest object.
 *
 * \return [object] new XMLHttpRequest object.
 */
Zapatec.Transport.createXmlHttpRequest = function() {
  if (typeof XMLHttpRequest != 'undefined') {
    return new XMLHttpRequest();
  }
  if (typeof ActiveXObject != 'undefined') {
    try {
      return new ActiveXObject(Zapatec.Transport.XMLHTTP);
    } catch (objException) {};
  }
  return null;
};

/**
 * Fetches specified URL using new XMLHttpRequest object.
 *
 * Asynchronous mode is used because it is safer and there is no risk of having
 * your script hang in case of network problem.
 *
 * When request is completed, one of provided callback functions is called:
 * onLoad on success or onError on error.
 *
 * onLoad callback function receives XMLHttpRequest object as argument and may
 * use its various properties like responseText, responseXML, etc.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: server status number (404, etc.) [number],
 *   errorDescription: human readable error description [string]
 * }
 *
 * Note: Some browsers implement caching for GET requests. Caching can be
 * prevented by adding 'r=' + Math.random() parameter to URL.
 *
 * If you use POST method, content argument should be something like
 * 'var1=value1&var2=value2' with urlencoded values. If you wish to send other
 * content, set appropriate contentType. E.g. 'multipart/form-data', 'text/xml',
 * etc.
 *
 * Server response should not contain non-ASCII characters.
 *
 * \param objArgs [object] associative array with arguments:
 * {
 *   url: relative or absolute URL to fetch [string],
 *   method: method ('GET', 'POST', 'HEAD', 'PUT') [string] (optional),
 *   contentType: content type when using POST [string] (optional),
 *   content: postable string or DOM object data when using POST
 *   [string or object] (optional),
 *   onLoad: function reference to call on success [function] (optional),
 *   onError: function reference to call on error [function] (optional),
 *   username: username [string] (optional),
 *   password: password [string] (optional)
 * }
 */
Zapatec.Transport.fetch = function(objArgs) {
  if (objArgs == null || typeof objArgs != 'object') {
    return;
  }
  if (!objArgs.url) {
    return;
  }
  if (!objArgs.method) {
    objArgs.method = 'GET';
  }
  if (!objArgs.contentType && objArgs.method.toUpperCase() == 'POST') {
    objArgs.contentType = 'application/x-www-form-urlencoded';
  }
  if (!objArgs.content) {
    objArgs.content = null;
  }
  if (!objArgs.onLoad) {
    objArgs.onLoad = null;
  }
  if (!objArgs.onError) {
    objArgs.onError = null;
  }
  // Request URL
  var objRequest = Zapatec.Transport.createXmlHttpRequest();
  if (objRequest == null) {
    return;
  }
  /*
    IE 6 calls onreadystatechange and then raises an exception if local file is
    not found. This flag is used to prevent duplicate onError calls.
  */
  var boolErrorDisplayed = false;
  try {
    // Open request
    if (typeof objArgs.username != 'undefined' &&
     typeof objArgs.password != 'undefined') {
      objRequest.open(objArgs.method, objArgs.url, true,
       objArgs.username, objArgs.password);
    } else {
      objRequest.open(objArgs.method, objArgs.url, true);
    }
    // Set onreadystatechange handler
    objRequest.onreadystatechange = function () {
      if (objRequest.readyState == 4) {
        // Request complete
        if (objRequest.status == 200 || objRequest.status == 304 ||
         (location.protocol == 'file:' && !objRequest.status)) {
          // OK or found, but determined unchanged and loaded from cache
          if (typeof objArgs.onLoad == 'function') {
            objArgs.onLoad(objRequest);
          }
        } else if (!boolErrorDisplayed) {
          boolErrorDisplayed = true;
          // 404 Not found, etc.
          Zapatec.Transport.displayError(objRequest.status,
           'Error: Cannot fetch ' + objArgs.url + '.\n' +
           (objRequest.statusText || ''),
           objArgs.onError);
        }
      }
    };
    // Set content type if needed
    if (objArgs.contentType) {
      objRequest.setRequestHeader('Content-Type', objArgs.contentType);
    }
    // Send request
    objRequest.send(objArgs.content);
  } catch (objException) {
    if (!boolErrorDisplayed) {
      boolErrorDisplayed = true;
      if (objException.name &&
       objException.name == 'NS_ERROR_FILE_NOT_FOUND') {
        Zapatec.Transport.displayError(0,
         'Error: Cannot fetch ' + objArgs.url + '.\nFile not found.',
         objArgs.onError);
      } else {
        Zapatec.Transport.displayError(0,
         'Error: Cannot fetch ' + objArgs.url + '.\n' +
         (objException.message || ''),
         objArgs.onError);
      }
    }
  };
};

/**
 * Assigns passed HTML fragment to the specified element's innerHTML property
 * and evaluates in global scope javascripts found in the fragment.
 *
 * \param objArgs [object] following object:
 * {
 *   html: [string] HTML fragment,
 *   container: [object or string, optional] element or id of element to put
 *    HTML fragment into
 * }
 */
Zapatec.Transport.setInnerHtml = function(objArgs) {
  // Check arguments
  if (!objArgs || typeof objArgs.html != 'string') {
    return;
  }
  var strHtml = objArgs.html;
  // Get container
  var objContainer = null;
  if (typeof objArgs.container == 'string') {
    objContainer = document.getElementById(objArgs.container);
  } else if (typeof objArgs.container == 'object') {
    objContainer = objArgs.container;
  }
  // Extract javascripts
  var arrScripts = [];
  var arrScript = null;
  while (arrScript = strHtml.match(/<script([^>]*)>([^<]*)<\/script>/)) {
    strHtml = strHtml.replace(/<script[^>]*>[^<]*<\/script>/, '');
    arrScripts.push(arrScript[2]);
  }
  // Set inner HTML
  if (objContainer) {
    // Opera hack
    objContainer.innerHTML = '<form></form>';
    objContainer.innerHTML = strHtml;
  }
  // Evaluate javascripts in global scope
  setTimeout(arrScripts.join(''), 0);
};

/**
 * Fetches and parses XML document from the specified URL.
 *
 * Asynchronous mode is used because it is safer and there is no risk of having
 * your script hang in case of network problem.
 *
 * When XML document is fetched and parsed, one of provided callback functions
 * is called: onLoad on success or onError on error.
 *
 * onLoad callback function receives XMLDocument object as argument and may use
 * its documentElement and other properties.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: error code [number],
 *   errorDescription: human readable error description [string]
 * }
 * Error code will be 0 unless Zapatec.Transport.fetch was used to fetch URL
 * and there was a problem during fetching.
 *
 * If method argument is not defined, more efficient XMLDOM in IE and
 * document.implementation.createDocument in Mozilla will be used to fetch
 * and parse document. Otherwise Zapatec.Transport.fetch will be used to fetch
 * document and Zapatec.Transport.parseXml to parse.
 *
 * Note: Some browsers implement caching for GET requests. Caching can be
 * prevented by adding 'r=' + Math.random() parameter to URL.
 *
 * If you use POST method, content argument should be something like
 * 'var1=value1&var2=value'. If you wish to send other content, set appropriate
 * contentType. E.g. to send XML string, you should set contentType: 'text/xml'.
 *
 * Server response should not contain non-ASCII characters.
 *
 * \param objArgs [object] associative array with arguments:
 * {
 *   url: relative or absolute URL to fetch [string],
 *   method: method ('GET', 'POST', 'HEAD', 'PUT') [string] (optional),
 *   contentType: content type when using POST [string] (optional),
 *   content: postable string or DOM object data when using POST
 *   [string or object] (optional),
 *   onLoad: function reference to call on success [function],
 *   onError: function reference to call on error [function] (optional),
 *   username: username [string] (optional),
 *   password: password [string] (optional)
 * }
 */
Zapatec.Transport.fetchXmlDoc = function(objArgs) {
  if (objArgs == null || typeof objArgs != 'object') {
    return;
  }
  if (!objArgs.url) {
    return;
  }
  if (!objArgs.onLoad) {
    objArgs.onLoad = null;
  }
  if (!objArgs.onError) {
    objArgs.onError = null;
  }
  if (!objArgs.method && typeof objArgs.username == 'undefined' &&
   typeof objArgs.password == 'undefined') {
    // Try more efficient methods first
    if (document.implementation && document.implementation.createDocument) {
      // Mozilla
      var objDocument = document.implementation.createDocument('', '', null);
      if (objDocument.load) {
        // Opera 8.51 also has document.implementation, but hasn't implemented
        // XMLDOM load method yet
        objDocument.async = true;
        objDocument.onload = function() {
          Zapatec.Transport.onXmlDocLoad(objDocument, objArgs.onLoad,
           objArgs.onError);
        };
        try {
          objDocument.load(objArgs.url);
          return;
        } catch (objException) {
          if (objException.name &&
           objException.name == 'NS_ERROR_FILE_NOT_FOUND') {
            Zapatec.Transport.displayError(0,
             'Error: Cannot fetch ' + objArgs.url + '.\nFile not found.',
             objArgs.onError);
          } else {
            Zapatec.Transport.displayError(0,
             'Error: Cannot fetch ' + objArgs.url + '.\n' +
             objException.toString(),
             objArgs.onError);
          }
        };
      }
    }
    if (typeof ActiveXObject != 'undefined') {
      // IE
      try {
        var objDocument = new ActiveXObject(Zapatec.Transport.XMLDOM);
        objDocument.async = true;
        objDocument.onreadystatechange = function () {
          if (objDocument.readyState == 4) {
            Zapatec.Transport.onXmlDocLoad(objDocument, objArgs.onLoad,
             objArgs.onError);
          }
        };
        objDocument.load(objArgs.url);
        return;
      } catch (objException) {};
    }
  }
  // Try XMLHttpRequest
  var objFetchArgs = {
    url: objArgs.url
  };
  if (typeof objArgs.method != 'undefined') {
    objFetchArgs.method = objArgs.method;
  }
  if (typeof objArgs.contentType != 'undefined') {
    objFetchArgs.contentType = objArgs.contentType;
  }
  if (typeof objArgs.content != 'undefined') {
    objFetchArgs.content = objArgs.content;
  }
  objFetchArgs.onLoad = function(objRequest) {
    Zapatec.Transport.parseXml({
      strXml: objRequest.responseText,
      onLoad: objArgs.onLoad,
      onError: objArgs.onError
    });
  };
  objFetchArgs.onError = objArgs.onError;
  if (typeof objArgs.username != 'undefined') {
    objFetchArgs.username = objArgs.username;
  }
  if (typeof objArgs.password != 'undefined') {
    objFetchArgs.password = objArgs.password;
  }
  Zapatec.Transport.fetch(objFetchArgs);
};

/**
 * Parses XML string into XMLDocument object.
 *
 * When XML string is parsed, one of provided callback functions is called:
 * onLoad on success or onError on error.
 * onLoad callback function receives XMLDocument object as argument and may use
 * its documentElement and other properties.
 * onError callback function receives following object:
 * {
 *   errorCode: error code [number],
 *   errorDescription: human readable error description [string]
 * }
 * Error code will be always 0.
 *
 * Returns XMLDocument object, so onLoad callback function is optional.
 * Returned value and its documentElement property should be checked before
 * use because they can be null or undefined.
 *
 * \param objArgs [object] associative array with arguments:
 * {
 *   strXml: XML string to parse [string],
 *   onLoad: function reference to call on success [function] (optional),
 *   onError: function reference to call on error [function] (optional)
 * }
 * \return [object] XMLDocument object or null.
 */
Zapatec.Transport.parseXml = function(objArgs) {
  if (objArgs == null || typeof objArgs != 'object') {
    return null;
  }
  if (!objArgs.strXml) {
    return null;
  }
  if (!objArgs.onLoad) {
    objArgs.onLoad = null;
  }
  if (!objArgs.onError) {
    objArgs.onError = null;
  }
  if (window.DOMParser) {
    // Mozilla
    try {
      var objDocument = (new DOMParser()).parseFromString(objArgs.strXml,
       'text/xml');
      Zapatec.Transport.onXmlDocLoad(objDocument, objArgs.onLoad,
       objArgs.onError);
      return objDocument;
    } catch (objException) {
      Zapatec.Transport.displayError(0,
       'Error: Cannot parse.\n' +
       'String does not appear to be a valid XML fragment.',
       objArgs.onError);
    };
    return null;
  }
  if (typeof ActiveXObject != 'undefined') {
    // IE
    try {
      var objDocument = new ActiveXObject(Zapatec.Transport.XMLDOM);
      objDocument.loadXML(objArgs.strXml);
      Zapatec.Transport.onXmlDocLoad(objDocument, objArgs.onLoad,
       objArgs.onError);
      return objDocument;
    } catch (objException) {};
  }
};

/**
 * \internal Checks if there were errors during XML document fetching and
 * parsing and calls onLoad or onError callback function correspondingly.
 *
 * \param objDocument [object] XMLDocument object.
 * \param onLoad [function] callback function provided by user.
 * \param onError [function] callback function provided by user.
 */
Zapatec.Transport.onXmlDocLoad = function(objDocument, onLoad, onError) {
  var strError = null;
  if (objDocument.parseError) {
    // Parsing error in IE
    strError = objDocument.parseError.reason;
    if (objDocument.parseError.srcText) {
      strError += 'Location: ' + objDocument.parseError.url +
       '\nLine number ' + objDocument.parseError.line + ', column ' +
       objDocument.parseError.linepos + ':\n' +
       objDocument.parseError.srcText + '\n';
    }
  } else if (objDocument.documentElement &&
   objDocument.documentElement.tagName == 'parsererror') {
    // If an error is caused while parsing, Mozilla doesn't throw an exception.
    // Instead, it creates an XML string containing the details of the error:
    // <parsererror xmlns="http://www.w3.org/1999/xhtml">XML Parsing Error: ...
    // Check if strings has been generated.
    strError = objDocument.documentElement.firstChild.data + '\n' +
     objDocument.documentElement.firstChild.nextSibling.firstChild.data;
  } else if (!objDocument.documentElement) {
    strError = 'String does not appear to be a valid XML fragment.';
  }
  if (strError) {
    // Parsing error
    Zapatec.Transport.displayError(0,
     'Error: Cannot parse.\n' + strError,
     onError);
  } else {
    // Success
    if (typeof onLoad == 'function') {
      onLoad(objDocument);
    }
  }
};

/**
 * Serializes XMLDocument object into XML string.
 *
 * \param objDocument [object] XMLDocument object.
 * \return [string] XML string.
 */
Zapatec.Transport.serializeXmlDoc = function(objDocument) {
  if (window.XMLSerializer) {
    // Mozilla
    return (new XMLSerializer).serializeToString(objDocument);
  }
  if (objDocument.xml) {
    // IE
    return objDocument.xml;
  }
};

/**
 * Fetches and parses JSON object from the specified URL.
 *
 * Asynchronous mode is used because it is safer and there is no risk of having
 * your script hang in case of network problem.
 *
 * When JSON object is fetched and parsed, one of provided callback functions
 * is called: onLoad on success or onError on error.
 *
 * onLoad callback function receives JSON object as argument.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: error code [number],
 *   errorDescription: human readable error description [string]
 * }
 * Error code will be 0 unless there was a problem during fetching.
 *
 * Note: Some browsers implement caching for GET requests. Caching can be
 * prevented by adding 'r=' + Math.random() parameter to URL.
 *
 * If you use POST method, content argument should be something like
 * 'var1=value1&var2=value'. If you wish to send other content, set appropriate
 * contentType. E.g. to send XML string, you should set contentType: 'text/xml'.
 *
 * Server response should not contain non-ASCII characters.
 *
 * \param objArgs [object] associative array with arguments:
 * {
 *   url: relative or absolute URL to fetch [string],
 *   reliable: false (string will be parsed) or true (evaluated) [boolean]
 *   (optional, false by default),
 *   method: method ('GET', 'POST', 'HEAD', 'PUT') [string] (optional),
 *   contentType: content type when using POST [string] (optional),
 *   content: postable string or DOM object data when using POST
 *   [string or object] (optional),
 *   onLoad: function reference to call on success [function],
 *   onError: function reference to call on error [function] (optional),
 *   username: username [string] (optional),
 *   password: password [string] (optional)
 * }
 */
Zapatec.Transport.fetchJsonObj = function(objArgs) {
  if (objArgs == null || typeof objArgs != 'object') {
    return;
  }
  if (!objArgs.url) {
    return;
  }
  var objFetchArgs = {
    url: objArgs.url
  };
  if (typeof objArgs.method != 'undefined') {
    objFetchArgs.method = objArgs.method;
  }
  if (typeof objArgs.contentType != 'undefined') {
    objFetchArgs.contentType = objArgs.contentType;
  }
  if (typeof objArgs.content != 'undefined') {
    objFetchArgs.content = objArgs.content;
  }
  if (!objArgs.reliable) {
    objArgs.reliable = false;
  }
  if (!objArgs.onLoad) {
    objArgs.onLoad = null;
  }
  if (!objArgs.onError) {
    objArgs.onError = null;
  }
  objFetchArgs.onLoad = function(objRequest) {
    Zapatec.Transport.parseJson({
      strJson: objRequest.responseText,
      reliable: objArgs.reliable,
      onLoad: objArgs.onLoad,
      onError: objArgs.onError
    });
  };
  objFetchArgs.onError = objArgs.onError;
  if (typeof objArgs.username != 'undefined') {
    objFetchArgs.username = objArgs.username;
  }
  if (typeof objArgs.password != 'undefined') {
    objFetchArgs.password = objArgs.password;
  }
  Zapatec.Transport.fetch(objFetchArgs);
};

/**
 * Parses JSON string into object.
 *
 * When JSON string is parsed, one of provided callback functions is called:
 * onLoad on success or onError on error.
 *
 * onLoad callback function receives JSON object as argument.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: error code [number],
 *   errorDescription: human readable error description [string]
 * }
 * Error code will be always 0.
 *
 * Returns JSON object, so onLoad callback function is optional.
 * Returned value should be checked before use because it can be null.
 *
 * \param objArgs [object] associative array with arguments:
 * {
 *   strJson: XML string to parse [string],
 *   reliable: false (string will be parsed) or true (evaluated) [boolean]
 *   (optional, false by default),
 *   onLoad: function reference to call on success [function] (optional),
 *   onError: function reference to call on error [function] (optional)
 * }
 * \return [object] JSON object or null.
 */
Zapatec.Transport.parseJson = function(objArgs) {
  if (objArgs == null || typeof objArgs != 'object') {
    return null;
  }
  if (!objArgs.strJson) {
    return null;
  }
  if (!objArgs.reliable) {
    objArgs.reliable = false;
  }
  if (!objArgs.onLoad) {
    objArgs.onLoad = null;
  }
  if (!objArgs.onError) {
    objArgs.onError = null;
  }
  if (objArgs.reliable) {
    try {
      var objJson = eval('(' + objArgs.strJson + ')');
      if (typeof objArgs.onLoad == 'function') {
        objArgs.onLoad(objJson);
      }
      return objJson;
    } catch (objException) {
      Zapatec.Transport.displayError(0,
       'Error: Cannot parse.\n' +
       'String does not appear to be a valid JSON fragment.',
       objArgs.onError);
    };
  } else {
    try {
      var objJson = Zapatec.Transport.parseJsonStr(objArgs.strJson);
      if (typeof objArgs.onLoad == 'function') {
        objArgs.onLoad(objJson);
      }
      return objJson;
    } catch (objException) {
      Zapatec.Transport.displayError(0,
       'Error: Cannot parse.\n' +
       objException.message + '\n' + objException.text,
       objArgs.onError);
    };
  }
  return null;
};

/**
 * \internal Parses JSON string into object.
 *
 * Was taken with changes from http://www.crockford.com/JSON/json.js.
 *
 * Throws exception if parsing error occurs.
 *
 * JSON format is described at http://www.crockford.com/JSON/js.html.
 *
 * \param text [string] JSON string to parse.
 * \return [object] JSON object.
 */
Zapatec.Transport.parseJsonStr = function(text) {
  var p = /^\s*(([,:{}\[\]])|"(\\.|[^\x00-\x1f"\\])*"|-?\d+(\.\d*)?([eE][+-]?\d+)?|true|false|null)\s*/,
      token,
      operator;
  function error(m, t) {
      throw {
          name: 'JSONError',
          message: m,
          text: t || operator || token
      };
  }
  function next(b) {
      if (b && b != operator) {
          error("Expected '" + b + "'");
      }
      if (text) {
          var t = p.exec(text);
          if (t) {
              if (t[2]) {
                  token = null;
                  operator = t[2];
              } else {
                  operator = null;
                  try {
                      token = eval(t[1]);
                  } catch (e) {
                      error("Bad token", t[1]);
                  }
              }
              text = text.substring(t[0].length);
          } else {
              error("Unrecognized token", text);
          }
      } else {
          // undefined changed to null because it is not supported in IE 5.0
          token = operator = null;
      }
  }
  function val() {
      var k, o;
      switch (operator) {
      case '{':
          next('{');
          o = {};
          if (operator != '}') {
              for (;;) {
                  if (operator || typeof token != 'string') {
                      error("Missing key");
                  }
                  k = token;
                  next();
                  next(':');
                  o[k] = val();
                  if (operator != ',') {
                      break;
                  }
                  next(',');
              }
          }
          next('}');
          return o;
      case '[':
          next('[');
          o = [];
          if (operator != ']') {
              for (;;) {
                  o.push(val());
                  if (operator != ',') {
                      break;
                  }
                  next(',');
              }
          }
          next(']');
          return o;
      default:
          if (operator !== null) {
              error("Missing value");
          }
          k = token;
          next();
          return k;
      }
  }
  next();
  return val();
};

/**
 * Serializes JSON object into JSON string.
 *
 * Was taken with changes from http://www.crockford.com/JSON/json.js.
 *
 * \param v [object] JSON object.
 * \return [string] JSON string.
 */
Zapatec.Transport.serializeJsonObj = function(v) {
  var a = [];
  /*
    Emit a string.
  */
  function e(s) {
      a[a.length] = s;
  }
  /*
    Convert a value.
  */
  function g(x) {
      var c, i, l, v;
      switch (typeof x) {
      case 'object':
          if (x) {
              if (x instanceof Array) {
                  e('[');
                  l = a.length;
                  for (i = 0; i < x.length; i += 1) {
                      v = x[i];
                      if (typeof v != 'undefined' &&
                              typeof v != 'function') {
                          if (l < a.length) {
                              e(',');
                          }
                          g(v);
                      }
                  }
                  e(']');
                  return;
              } else if (typeof x.toString != 'undefined') {
                  e('{');
                  l = a.length;
                  for (i in x) {
                      v = x[i];
                      if (x.hasOwnProperty(i) &&
                              typeof v != 'undefined' &&
                              typeof v != 'function') {
                          if (l < a.length) {
                              e(',');
                          }
                          g(i);
                          e(':');
                          g(v);
                      }
                  }
                  return e('}');
              }
          }
          e('null');
          return;
      case 'number':
          e(isFinite(x) ? +x : 'null');
          return;
      case 'string':
          l = x.length;
          e('"');
          for (i = 0; i < l; i += 1) {
              c = x.charAt(i);
              if (c >= ' ') {
                  if (c == '\\' || c == '"') {
                      e('\\');
                  }
                  e(c);
              } else {
                  switch (c) {
                      case '\b':
                          e('\\b');
                          break;
                      case '\f':
                          e('\\f');
                          break;
                      case '\n':
                          e('\\n');
                          break;
                      case '\r':
                          e('\\r');
                          break;
                      case '\t':
                          e('\\t');
                          break;
                      default:
                          c = c.charCodeAt();
                          e('\\u00' + Math.floor(c / 16).toString(16) +
                              (c % 16).toString(16));
                  }
              }
          }
          e('"');
          return;
      case 'boolean':
          e(String(x));
          return;
      default:
          e('null');
          return;
      }
  }
  g(v);
  return a.join('');
};

/**
 * \internal Displays error message.
 *
 * Calls onError callback function provided by user. If there is no onError
 * callback function, displays alert with human readable error description.
 * onError callback function receives following object:
 * {
 *   errorCode: error code [number],
 *   errorDescription: human readable error description [string]
 * }
 *
 * \param iErrCode [number] error code.
 * \param strError [string] human readable error description.
 * \param onError [function] callback function provided by user.
 */
Zapatec.Transport.displayError = function(iErrCode, strError, onError) {
  if (typeof onError == 'function') {
    onError({
      errorCode: iErrCode,
      errorDescription: strError
    });
  } else {
    alert(strError);
  }
};

/**
 * Translates a URL to the URL relative to the specified or to absolute URL.
 *
 * \param objArgs [object] associative array with arguments:
 * {
 *   url: absolute or relative URL to translate [string] (if absolute, will be
 *    returned as is),
 *   relativeTo: "url" will be translated to the URL relative to this absolute
 *    or relative URL [string] (optional, current page URL by default)
 * }
 * \return [string] translated URL
 */
Zapatec.Transport.translateUrl = function(objArgs) {
  if (objArgs == null || typeof objArgs != 'object') {
    return null;
  }
  if (!objArgs.url) {
    return null;
  }
  var strUrl = objArgs.url;
  // Check if it is absolute
  if (strUrl.charAt(0) == '/' || strUrl.indexOf(':') >= 0) {
    return strUrl;
  }
  // Make relative to current page URL by default
  if (!objArgs.relativeTo) {
    objArgs.relativeTo = document.location.toString();
  }
  var arrUrl = strUrl.split('/');
  var arrRelativeTo = objArgs.relativeTo.split('/');
  // Remove file name
  arrRelativeTo.pop();
  // Form new URL
  for (var iToken = 0; iToken < arrUrl.length; iToken++) {
    var strToken = arrUrl[iToken];
    if (strToken == '..') {
      arrRelativeTo.pop();
    } else if (strToken != '.') {
      arrRelativeTo.push(strToken);
    }
  }
  return arrRelativeTo.join('/');
};

/**
 * \internal Associative array to keep list of loaded JS files to prevent
 * duplicate loads.
 */
Zapatec.Transport.loadedJS = {};

/**
 * Fetches JS file using fetch and evaluates it in global scope.
 *
 * When JS file is loaded successfully, onLoad callback function is called
 * without arguments. URL is added into Zapatec.Transport.loadedJS array
 * and will not be fetched again on next function call.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: [number] server status number (404, etc.),
 *   errorDescription: [string] human readable error description
 * }
 *
 * One of the arguments: module or url is required. When url is passed,
 * module argument is ignored.
 *
 * If module argument is used, function gets all "script" elements using
 * getElementsByTagName and searches for the first element having "src"
 * attribute value ending with (relativeModule + ".js") (default relativeModule
 * value is "transport"). Path to the module is taken from that src attribute
 * value and will be the same as path to relativeModule file.
 *
 * \param objArgs [object] following object:
 * {
 *   url: [string, optional] absolute or relative URL of JS file,
 *   module: [string, optional] module name (file name without .js extension),
 *   relativeModule: [string, optional] search module in the same directory as
 *    relative module (default: 'transport') (file name without .js extension),
 *   onLoad: [function, optional] function reference to call on success,
 *   onError: [function, optional] function reference to call on error
 * }
 */
Zapatec.Transport.loadJS = function(objArgs) {
  if (!objArgs || typeof objArgs != 'object') {
    return;
  }
  // Get URL of JS file
  var strUrl = null;
  if (objArgs.url) {
    strUrl = objArgs.url;
  } else if (objArgs.module) {
    var strPath = '';
    var strRelativeModule = 'transport.js';
    if (objArgs.relativeModule) {
      strRelativeModule = objArgs.relativeModule + '.js';
    }
    // Get path to module
    var arrScripts = document.getElementsByTagName('script');
    for (var iScript = 0; iScript < arrScripts.length; iScript++) {
      var strSrc = arrScripts[iScript].getAttribute('src') || '';
      var arrTokens = strSrc.split('/');
      // Remove last token
      var strLastToken = arrTokens.pop();
      if (strLastToken == strRelativeModule) {
        strPath = arrTokens.join('/') + '/';
        break;
      }
    }
    strUrl = strPath + objArgs.module + '.js';
  } else {
    return;
  }
  // Get absolute URL of the JS file
  var strAbsoluteUrl = Zapatec.Transport.translateUrl({url: strUrl});
  // Check arguments
  if (!objArgs.onLoad) {
    objArgs.onLoad = null;
  }
  if (!objArgs.onError) {
    objArgs.onError = null;
  }
  // Check if it is already loaded
  if (Zapatec.Transport.loadedJS[strAbsoluteUrl]) {
    // onLoad callback
    if (typeof objArgs.onLoad == 'function') {
      objArgs.onLoad();
    }
    return;
  }
  var arrScripts = document.getElementsByTagName('script');
  for (var iScript = 0; iScript < arrScripts.length; iScript++) {
    var strSrc = arrScripts[iScript].getAttribute('src') || '';
    if (strSrc == strUrl) {
      // Add this URL to the list of loaded
      Zapatec.Transport.loadedJS[strAbsoluteUrl] = true;
      // onLoad callback
      if (typeof objArgs.onLoad == 'function') {
        objArgs.onLoad();
      }
      return;
    }
  }
  // Load JS file
  Zapatec.Transport.fetch({
    url: strUrl,
    onLoad: function(objRequest) {
      // Can be loaded in two processes simultaneously
      if (!Zapatec.Transport.loadedJS[strAbsoluteUrl]) {
        // Evaluate code in global scope
        eval(objRequest.responseText);
        // Add this URL to the list of loaded
        Zapatec.Transport.loadedJS[strAbsoluteUrl] = true;
      }
      // onLoad callback
      if (typeof objArgs.onLoad == 'function') {
        objArgs.onLoad();
      }
    },
    onError: objArgs.onError
  });
};

/**
 * \internal Associative array to keep list of loaded CSS files to prevent
 * duplicate loads.
 */
Zapatec.Transport.loadedCss = {};

/**
 * Fetches style sheet using fetch and loads it into the document.
 *
 * When stylesheet is loaded successfully, onLoad callback function is called
 * without arguments. URL is added into Zapatec.Transport.loadedCss array
 * and will not be fetched again on next function call.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: server status number (404, etc.) [number],
 *   errorDescription: human readable error description [string]
 * }
 *
 * \param objArgs [object] associative array with arguments:
 * {
 *   url: absolute or relative URL of CSS file [string],
 *   onLoad: function reference to call on success [function] (optional),
 *   onError: function reference to call on error [function] (optional)
 * }
 */
Zapatec.Transport.loadCss = function(objArgs) {
  if (objArgs == null || typeof objArgs != 'object') {
    return;
  }
  // Check arguments
  if (!objArgs.url) {
    return;
  }
  if (!objArgs.onLoad) {
    objArgs.onLoad = null;
  }
  if (!objArgs.onError) {
    objArgs.onError = null;
  }
  // Get absolute URL of the CSS file
  var strAbsoluteUrl = Zapatec.Transport.translateUrl({url: objArgs.url});
  // Check if it is already loaded
  if (Zapatec.Transport.loadedCss[strAbsoluteUrl]) {
    // onLoad callback
    if (typeof objArgs.onLoad == 'function') {
      objArgs.onLoad();
    }
    return;
  }
  var arrLinks = document.getElementsByTagName('link');
  for (var iLnk = 0; iLnk < arrLinks.length; iLnk++) {
    var strHref = arrLinks[iLnk].getAttribute('href') || '';
    if (strHref == objArgs.url) {
      // Add this url to the list of loaded
      Zapatec.Transport.loadedCss[strAbsoluteUrl] = true;
      // onLoad callback
      if (typeof objArgs.onLoad == 'function') {
        objArgs.onLoad();
      }
      return;
    }
  }
  // Load Zapatec.StyleSheet class definition
  Zapatec.Transport.loadJS({
    module: 'stylesheet',
    onLoad: function() {
      // Load CSS file
      Zapatec.Transport.fetch({
        url: objArgs.url,
        onLoad: function(objRequest) {
          // Parse CSS file.
          // Find URLs and translate them to absolute.
          // Find @import rules and load corresponding CSS files.
          var strCss = objRequest.responseText;
          var arrResultCss = [];
          // Will hold image URLs to preload
          var arrImgUrls = [];
          // Will hold CSS URLs to load
          var arrCssUrls = [];
          // Move first cursor to the beginning of the string
          var iPos = 0;
          // Move second cursor to the pattern
          var iNextPos = strCss.indexOf('url(', iPos);
          while (iNextPos >= 0) {
            // Move first cursor to the URL
            iNextPos += 4;
            // Check if this is @import rule
            var strToken = strCss.substring(iPos, iNextPos);
            var boolIsImport = /@import\s+url\($/.test(strToken);
            // Add part of the string before URL
            arrResultCss.push(strToken);
            // Move second cursor to the new location to start the search from
            iPos = iNextPos;
            // Search the end of URL
            iNextPos = strCss.indexOf(')', iPos);
            if (iNextPos >= 0) {
              // Remove quotes
              var strImgUrl = strCss.substring(iPos, iNextPos);
              strImgUrl = strImgUrl.replace(/['"]/g, '');
              // Translate image URL relative to CSS file URL
              strImgUrl = Zapatec.Transport.translateUrl({
                url: strImgUrl,
                relativeTo: objArgs.url
              });
              // Convert to absolute URL
              strImgUrl = Zapatec.Transport.translateUrl({
                url: strImgUrl
              });
              // Add translated URL
              arrResultCss.push(strImgUrl);
              // Add URL to the list
              if (boolIsImport) {
                // Add CSS URL to load list
                arrCssUrls.push(strImgUrl);
              } else {
                // Add image URL to preload list
                arrImgUrls.push(strImgUrl);
              }
              // Move second cursor to the new location to start the search from
              iPos = iNextPos;
              // Search next pattern
              iNextPos = strCss.indexOf('url(', iPos);
            }
          }
          // Add the rest of string
          arrResultCss.push(strCss.substr(iPos));
          // Get translated CSS text
          strCss = arrResultCss.join('');
          // Load CSS files
          Zapatec.Transport.loadCssList({
            urls: arrCssUrls,
            onLoad: function() {
              // Add style sheet rules into the page
              var objStyleSheet = new Zapatec.StyleSheet();
              objStyleSheet.addParse(strCss);
              // onLoad callback
              if (typeof objArgs.onLoad == 'function') {
                objArgs.onLoad();
              }
            }
          });
          // Add this URL to the list of loaded
          Zapatec.Transport.loadedCss[strAbsoluteUrl] = true;
        },
        onError: objArgs.onError
      });
    },
    onError: objArgs.onError
  });
};

/**
 * Loads several CSS files one by one it into the document.
 *
 * This function behaves differently from other Zapatec.Transport functions.
 * onLoad callback function will be called in any case, even if errors occured
 * during loading. If there are multiple errors, onError callback function will
 * be called once for every passed URL that wasn't loaded successfully.
 *
 * onLoad callback function is called without arguments.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: server status number (404, etc.) [number],
 *   errorDescription: human readable error description [string]
 * }
 *
 * \param objArgs [object] associative array with arguments:
 * {
 *   urls: array of absolute or relative URLs of CSS files to load [object]
 *    (files will be loaded in order they appear in the array),
 *   onLoad: function reference to call on completion [function] (optional),
 *   onError: function reference to call on error [function] (optional)
 * }
 */
Zapatec.Transport.loadCssList = function(objArgs) {
  if (objArgs == null || typeof objArgs != 'object') {
    return;
  }
  // Check arguments
  if (!objArgs.onLoad) {
    objArgs.onLoad = null;
  }
  if (!objArgs.onError) {
    objArgs.onError = null;
  }
  if (!objArgs.urls || !objArgs.urls.length) {
    // onLoad callback
    if (typeof objArgs.onLoad == 'function') {
      objArgs.onLoad();
    }
    return;
  }
  // Get first URL in the array
  var strUrl = objArgs.urls.shift();
  // CSS file onLoad handler
  var funcOnLoad = function() {
    // Load the rest of URLs
    Zapatec.Transport.loadCssList({
      urls: objArgs.urls,
      onLoad: objArgs.onLoad,
      onError: objArgs.onError
    });
  };
  // Load CSS file
  Zapatec.Transport.loadCss({
    url: strUrl,
    onLoad: funcOnLoad,
    onError: function(objError) {
      Zapatec.Transport.displayError(objError.errorCode,
       objError.errorDescription, objArgs.onError);
      funcOnLoad();
    }
  });
};

/**
 * \internal Array to hold image preloads.
 */
Zapatec.Transport.imagePreloads = [];

/**
 * Preloads one or several images at once. See Zapatec.PreloadImages class
 * (utils/preloadimages.js) for details.
 *
 * \param objArgs [object] associative array with arguments:
 * {
 *   urls: [object] array of absolute or relative image URLs to preload,
 *   onLoad: [function, optional] onload event handler,
 *   timeout: [number, optional] number of milliseconds to wait for onload
 *    event before forcing it
 * }
 */
Zapatec.Transport.preloadImages = function(objArgs) {
  Zapatec.Transport.loadJS({
    module: 'preloadimages',
    onLoad: function() {
      Zapatec.Transport.imagePreloads.push(new Zapatec.PreloadImages(objArgs));
    }
  });
};

