// xmlhttp.js
// Created by Shui Hung Kwok, Nov 8, 2005 (C)

// Include this file in main HTML code.
// <script type=text/javascript src="xmlhttp.js">
// </script>

// This class wraps XMLHttpRequest and has methods to handle XML.
function AjaxClass ()
{
	var xmlhttp=false;
	if (typeof XMLHttpRequest != 'undefined')
	{
		xmlhttp = new XMLHttpRequest();
	}
	else
	{
		try 
		{
			xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
		} 
		catch (e1) 
		{
			try 
			{
				xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
			} 
			catch (e2) 
			{
				xmlhttp = false;
			}
		}
	}
	this.xmlHttp = xmlhttp;
} // AjaxClass

// The sendRequest (query, callback) method sends a POST request to the
// web server and registers the callback function to be invoked when
// a response is received. The callback function has one parameter:
// the returned object.
AjaxClass.prototype.sendRequest = function (query, callback, content)
{
	var xmlhttp = this.xmlHttp;
	try { xmlhttp.abort (); } catch (e) {}
	xmlhttp.onreadystatechange = function () { callback (xmlhttp); };
	xmlhttp.open("GET", query, true); // true for asyncrhonuous
	xmlhttp.setRequestHeader ("Content-Type", "text/xml");
	xmlhttp.send (content);
} // sendRequest

// Class XmlUtil, a collection of utilities 
function XmlUtil (xobj) 
{
	this.xroot = xobj;
} // XmlUtil

// Finds attributes and returns them as an associative array.
// Given an element, an object we know little about,
// this method gets the list of attributes names and then
// uses getAttribute (name) to retrieve the content associated 
// with that name.
// Returns an associative array.
XmlUtil.prototype.getAttributesArray = function (xelem)
{
	var ats;
	var stop, i;
	var attr;
	var res = new Array ();

	ats = xelem.attributes;
	if (ats == null) return '';
	stop = ats.length;

	for (i = 0; i < stop; ++i)
	{
		var name = ats[i].name;
		res[name] = xelem.getAttribute (name);
	}
	return res;
} // getAttributesArray

// Traverses the xml tree, recursively.
// Each node is visited three times, each time callback() is called
// with visit set to -1, 0, and 1, for before, within, and after.
// The callback() function is defined as callback (obj, level, visit)
XmlUtil.prototype.traverse = function (xobj, level, callback)
{
	if (xobj == null) return;
	var i, stop;
	var children = xobj.childNodes;

	stop = children.length;

	for (i = 0; i < stop; ++i)
	{
		var child = children[i];
		var data = child.data;
		if (typeof data != 'string')
		{
			if (typeof child.tagName != 'undefined')
			{
				callback (child, level, -1);
				this.traverse (child, level + 1, callback);
				callback (child, level, 1);
			}
		}
		else 
		{
			callback (child, level, 0);
		}	
	}
} // traverse

// Obj2strShort is similar to traverse. It builds a string
// that represents the xml tree.
// This is good for debugging.
XmlUtil.prototype.obj2strShort = function (xobj, ident, outstr)
{
	var i;
	var stop;
	var children;
	var elem, data;
	var attrs;

	if (xobj == null) return 'null<br>';
	children = xobj.childNodes;
	stop = children.length;

	for (i = 0; i < stop; ++i)
	{
		elem = children[i];
		data = elem.data;
		if (typeof data == 'string')
		{
			if (data.length > 0)
			{
				outstr += data;
			}
		}
		else
		{
			attrsArray = this.getAttributesArray (elem);
			attrs = '';
			for (var at in attrsArray)
			{
				st = attrsArray[at];
				st = st.replace ('"', '\\"');
				attrs += ' ' + at + '="' + st + '"';
			}
			eid = '<' + elem.tagName + attrs + '>';
			outstr += eid;
			outstr += this.obj2strShort (elem, ident, '');
			eid = '</' + elem.tagName + '>';
			outstr += eid;
		}
	}
	return outstr;
} // obj2strShort

// Obj2str is similar to traverse. It builds a string
// that represents the xml tree.
// This is good for debugging.
XmlUtil.prototype.obj2str = function (xobj, ident, outstr)
{
	var i;
	var stop;
	var children;
	var elem, data;
	var attrs;

	if (xobj == null) return 'null<br>';
	children = xobj.childNodes;
	stop = children.length;

	for (i = 0; i < stop; ++i)
	{
		elem = children[i];
		data = elem.data;
		if (typeof data == 'string')
		{
			// Get ride of blank spaces.
			data = data.replace (/(\n| |\t)*/, '');
			if (data.length > 0)
			{
				outstr += ident + '<li>' + data;
			}
		}
		else
		{
			attrsArray = this.getAttributesArray (elem);
			attrs = '';
			for (var at in attrsArray)
			{
				st = attrsArray[at];
				st = st.replace ('"', '\\"');
				attrs += ' ' + at + '="' + st + '"';
			}
			eid = ident + '<ul>' + '&lt;' + elem.tagName + attrs + '&gt;';
			outstr += eid;
			outstr += this.obj2str (elem, ident, '');
			eid = ident + '<br>&lt;/' + elem.tagName + '&gt;</ul>';
			outstr += eid;
		}
	}
	return outstr;
} // obj2str

// Given a path in the form "/Level1/Level2/Level3",
// returns the node that matches that path.
// This is a trivial version of xpath.
XmlUtil.prototype.getNodeByPath = function (path, node)
{
    var slist = path.split ('\/');
    var sidx = 1;
    var res = '';
    var currnode = null;
    var currtag;
    var tidx;
    var children;
    var i, stop;
    var cnt;

    while (sidx < slist.length)
    {
        currtag = slist[sidx];
        tidx = 0;
		// Test if there are brakets, for ex. ABC[2].
        var mstr = currtag.match (/(\w*)\[(\d+)\]/);
        if (mstr != null)
        {
            currtag = mstr[1];
            tidx = mstr[2];
        }
            
		// Find the childNode that matches currtag.
        children = node.childNodes;
        stop = children.length;
        cnt = 0;
        for (i = 0; i < stop; ++i)
        {
            currnode = children[i];
            if (currnode.tagName == currtag)
            {
                if (cnt == tidx)
                {
                    node = currnode;
                    break;
                }
                ++cnt;
            }
        } // for
		// No childNode matches currtag, so return null
        if (i == stop)
            return null;
		// If a match is found, try to match next section of the path.
        ++sidx;
    }
    return currnode;
} // getNodeByPath

// Returns the content of the node addressed by the path.
// The content of a node is everything that is not a node.
XmlUtil.prototype.getContentByPath = function (path, node)
{
	var currnode = this.getNodeByPath (path, node);
	if (currnode == null) return null;

	// Only the first part of the content is returned.
	// Alternatively, everything but subnodes can be returned.
	return currnode.childNodes[0].data;
} // getContentByPath

