/* +JJSDOC
<class>
	<name>xml_manager</name>
	<params>
		<param name='xml_url' type='string' optional='yes' default='false'>
		A URL/filename from which XML will be read and parsed
		</param>
		<param name='xml_str' type='string' optional='yes' default='false'>
		A string containing (valid) XML which will be parsed
		</param>
		<param name='xml_obj' type='string' optional='yes' default='false'>
		A "raw" XML object which needs to be wrapped
		</param>
		<param name='debug_level' type='Integer' optional='yes'>
		The level of debug reporting to be activated
		</param>
	</params>

	<attributes>
		<attribute name='firstChild' type='Object' visibility='public'>
		A pointer to the first node in the child_nodes array
		</attribute>

		<attribute name='lastChild' type='Object' visibility='public'>
		A pointer to the last node in the child_nodes array
		</attribute>

		<attribute name='xml_obj' type='Object' visibility='private'>
		The "raw" XML object: direct manipulation of this is not recommended
		</attribute>

		<attribute name='ie_xml_obj' type='Object' visibility='private'>
		Support object for Internet Explorer: direct manipulation of this is not recommended
		</attribute>

		<attribute name='parent_xml_obj' type='Object' visibility='private'>
		Support object for internal use: direct manipulation of this is not recommended
		</attribute>

		<attribute name='debug_level' type='Integer' visibility='public'>
		"Global" variable used to action debug alerts and activities
		</attribute>

		<attribute name='child_nodes' type='Object' visibility='public'>
		A "wrapped" list of all the child nodes found under the current node
		</attribute>

		<attribute name='position' type='Integer' visibility='private'>
		For XML fragments only: the position of the current node relative to the other
		child-nodes under the same parent
		</attribute>

		<attribute name='tag_name' type='String' visibility='public'>
		The tag-name associated with the current node
		</attribute>

		<attribute name='node_name' type='String' visibility='public'>
		The node-name associated with the current node: generally the same as tag_node
		</attribute>

		<attribute name='node_type' type='Integer' visibility='public'>
		The node type: see 
		<a href='http://www.w3schools.com/Dom/dom_nodetype.asp'>http://www.w3schools.com/Dom/dom_nodetype.asp</a> for a list
		of types.
		</attribute>

		<attribute name='xsl_cache' type='String' visibility='private'>
		A cache of previously parsed XSL stylesheets
		</attribute>

		<attribute name='xsl_cache_enabled' type='Boolean' visibility='private'>
		If set to false, disables use of the XSL cache
		</attribute>
	</attributes>

	<returns>XML object</returns>

	<comment><![CDATA[
	xml_manager is intended to simplify the use of XML/XSL in template-driven websites.

	<p>
	Actions (adding/removing/editing/parsing nodes) can be carried out via the wrapper and fragments of the original XML can 
	also be extracted and actioned.
	</p>
	
	<p>
	The first non-false parameter will be used to generate the XML object (i.e. URL, string, object).  If a URL
	or filename is being used, the data must be on the same machine as the caller: JavaScript's security model
	does not permit access to remotely stored data
	</p>
	]]></comment>

	<example>
	// Load from URL
	var xml_obj = new xml_manager ('test.xml');

	// Load from String
	var xml_obj = new xml_manager (false, '<test>this was a triumph</test>');
	xml_obj.insert_node ('test_name', 'test_value);
	var html_str 	= child_node.parse_with_xsl('test.xsl');
	
	// Parse fragment
	var child_node	= xml_obj.get_elements_by_tag_name['test_node'][0];
	child_node.set_attribute ('test_name', 'test_value);
	var xml_str 	= child_node.parse_with_xsl('test.xsl');
	</example>
</class>
 * -JJSDOC
 */

// EXAMPLE
/* +JJSDOC
<method parent='blank' visibility='public'>
	<name>blank</name>
	<class_name>blank</class_name>
	<params>
		<param name='blank' type='blank' optional='no' default=''>
		blank
		</param>
	</params>

	<returns>blank</returns>

	<comment><![CDATA[
	blank
	]]></comment>

	<example>
	blank
	</example>
</method>
 * -JJSDOC
 */

function xml_manager (xml_url, xml_str, xml_obj, debug_level)
{
	// Basic wrapper to handle the complexity of parsing XML

	// Methods
	this.load_from_url			= xml_load_from_url;
	this.load_from_string			= xml_load_from_string;
	this.load_from_object			= xml_load_from_object;
	this.initialise				= xml_initialise;
	this.initialise_child_node		= xml_initialise_child_node;
	this.refresh				= xml_refresh;

	this.get_elements_by_tag_name		= xml_get_elements_by_tag_name;

	this.update_node			= xml_update_node;
	this.insert_node			= xml_insert_node;
	this.delete_node			= xml_delete_node;
	this.delete_node_by_id			= xml_delete_node_by_id;
	this.delete_nodes_by_tag_name		= xml_delete_nodes_by_tag_name;
	this.delete_nodes_by_attribute		= xml_delete_nodes_by_attribute;
	this.get_nodes_by_attribute		= xml_get_nodes_by_attribute;
	this.get_node_value			= xml_get_node_value;
	this.get_child_node_value		= xml_get_child_node_value;
	this.get_child_node_attribute		= xml_get_child_node_attribute;
	this.set_child_node_attribute		= xml_set_child_node_attribute;

	this.get_attribute			= xml_get_attribute;
	this.set_attribute			= xml_set_attribute;
	this.delete_attribute			= xml_delete_attribute;
	this.get_all_attributes			= xml_get_all_attributes;
	this.set_all_attributes			= xml_set_all_attributes;
	this.delete_all_attributes		= xml_delete_all_attributes;

	this.inspect_internals			= xml_inspect_internals;
	this.inspect_wrapper			= xml_inspect_wrapper;

	this.render_with_xsl			= xml_render_with_xsl;
	this.load_xsl_contents			= xml_load_xsl_contents;
	this.load_url_contents			= xml_load_url_contents;

	this.is_text_node			= xml_is_text_node;
	this.debug				= xml_debug;
	this.debug_values			= xml_debug_values;
	this.set_debug_level			= xml_set_debug_level;
	this.wrapped_check			= xml_wrapped_check;

	this.firstChild				= false;
	this.lastChild				= false;

	// attributes
	this.xml_obj				= false;
	this.ie_xml_obj				= false;
	this.parent_xml_obj			= false;
	this.debug_level			= 0;
	this.child_nodes			= false;

	this.position				= -1;
	this.tag_name				= false;
	this.node_name				= false;
	this.node_type				= false;
	this.xsl_cache				= new Object();
	this.xsl_cache_enabled			= true;
	this.wrapped				= false;

	if (typeof(debug_level) != 'undefined')
		this.set_debug_level(debug_level);

	// Load the XML data from the appropriate channel
	if (typeof(xml_url) != 'undefined' && xml_url)
		this.load_from_url (xml_url);
	else if (typeof(xml_str) != 'undefined' && xml_str)
		this.load_from_string (xml_str);
	else if (typeof(xml_obj) != 'undefined' && xml_obj)
		this.load_from_object (xml_obj);
}

function check_wrapped()
{
	if (!this.wrapped)
	{
		this.initialise();
	}
}

/* +JJSDOC
<method parent='xml_manager' visibility='private'>
	<name>xml_set_debug_level</name>
	<class_name>set_debug_level</class_name>
	<params>
		<param name='debug_level' type='Integer' optional='no' default=''>
		Set the debug level for the XML object
		</param>
	</params>

	<returns>XML object</returns>

	<comment><![CDATA[
	Set the debug level for the object: various details about the inner workings will then
	be reported via alert().

	<p>
	Available levels:
		<ul>
		0: no debug<br/>
		1: information<br/>
		2: warnings<br/>
		3: everything<br/>
		</ul>
	</p>
	]]></comment>

	<example>
	xml_obj.set_debug_level (4);
	</example>
</method>
 * -JJSDOC
 */
function xml_set_debug_level (debug_level)
{
	this.debug_level = debug_level;

	if (this.debug_level > 4)
		alert ('xml_manager: debug enabled');
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_refresh</name>
	<class_name>refresh</class_name>
	<params>
		<param name='recurse' type='boolean' optional='yes' default='false'>
		Instructs initialise() to not recurse through all child nodes
		</param>
	</params>

	<returns></returns>

	<comment><![CDATA[
	Wrapper to initialise() used for clarity
	]]></comment>

	<example>
	xml_obj.refresh();
	</example>
</method>
 * -JJSDOC
 */
function xml_refresh (rebuild_child_nodes_only)
{
	// Simple wrapper for ease of understanding
	this.initialise (rebuild_child_nodes_only);
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_initialise</name>
	<class_name>initialise</class_name>
	<params>
		<param name='recurse' type='boolean' optional='yes' default='false'>
		Instructs initialise() to recurse through the child nodes
		</param>
	</params>

	<returns></returns>

	<comment><![CDATA[
	Parses the "raw" XML data and generates appropriate metadata and data-wrappers for use by xml_manager.

	<p>
	Note: this method needs to inspect and wrap all nodes in the XML structure, so processing time is dependent on the
	total number of nodes in the structure (i.e. O(N)).  The physical size of the raw XML (i.e. filesize, string length)
	may also be a factor on processing time.
	</p>
	]]></comment>

	<example>
	xml_obj.initialise();
	</example>
</method>
 * -JJSDOC
 */
function xml_initialise (recurse)
{
	// By default, we rebuild all the xml_obj information
	recurse = (typeof(recurse) != 'undefined' ? recurse: true);

	var i	= 0;
	var cn	= 0;
	var xo	= false;
	var co	= false;

	if (this.xml_obj)
	{
		if (!this.wrapped)
		{
			// We need to parse all the child nodes and ensure they're all appropriately wrapped
			// so that we can extract and use all standard functions on any node set 
			// xml_manager will automatically recurse through all the child nodes
			xo = this.xml_obj;

			// Useful metadata for the current node
			this.tag_name 	= (typeof (xo.tagName) != 'undefined' ? xo.tagName : '');
			this.node_type 	= xo.nodeType;
			this.node_name	= (typeof(xo.nodeName) != 'undefined' ? xo.nodeName : this.tag_name);

			if (recurse)
			{
				try
				{
					this.child_nodes = new Array();
					cn = xo.childNodes.length;

					for (i = 0; i < cn; i++)
					{
						// We need to ensure that we maintain a link back to the original XML object
						// to prevent namespace issues in the event of nodes being added to the child
						// See xml_insert_node for more details
						// We also need to maintain a link to the IE xml_obj...
						this.initialise_child_node (xo.childNodes[i]);
					}

					// Useful shortcuts...
					i = this.child_nodes.length;
					if (i > 0)
					{
						this.firstChild	= this.child_nodes[0];
						this.lastChild 	= this.child_nodes[(i - 1)];
					}
				}
				catch (err_obj)
				{
					alert ('xml_initialise: failure parsing XML: ' + err_obj.description);
					throw (err_obj);
				}
			}

			this.wrapped = true;
		}
	}
	else
	{
		alert ('xml_initialise: XML object has not been loaded: no actions possible');
	}
}

function xml_wrapped_check ()
{
	if (!this.wrapped)
	{
		this.initialise();
		this.wrapped = true;
	}
}

/* +JJSDOC
<method parent='xml_manager' visibility='private'>
	<name>xml_initialise_child_node</name>
	<class_name>initialise_child_node</class_name>
	<params>
		<param name='child_node' type='Object' optional='no' default=''>
		A "raw" XML node which needs to be processed
		</param>
	</params>

	<returns>A xml_manager-wrapped XML object</returns>

	<comment><![CDATA[
	Provides "raw" XML nodes with an xml_manager wrapper to ensure consistent behaviour across browsers.
	]]></comment>

	<example>
	this.initialise_child_node(child_node);
	</example>
</method>
 * -JJSDOC
 */

function xml_initialise_child_node (child_node)
{
	var co 	= new xml_manager();
	var pxo	= false;

	try
	{
		if (this.parent_xml_obj != false)
			pxo = this.parent_xml_obj;
		else
			pxo = this.xml_obj;
	}
	catch (eo)
	{
		// IE trap...
		pxo = false;
	}

	// This will trigger a call to initialise(), which in turn will call initialise_child_node()
	// until all child nodes have been recursed through
	co.load_from_object(child_node, pxo, this.ie_xml_obj);

	// The expectation is that new child_nodes will always be inserted at the *END* of the xml structure
	// (and/or we will always process existing node lists from 0..N)
	// We therefore always push the "wrapper" onto the end of the array...
	this.child_nodes.push(co);

	return co;
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_get_attribute</name>
	<class_name>get_attribute</class_name>
	<params>
		<param name='a_name' type='String' optional='no' default=''>
		The name of the attribute being searched for
		</param>
		<param name='default_value' type='String' optional='yes' default=''>
		The value to return if the attribute is not found
		</param>
	</params>

	<returns>A string</returns>

	<comment><![CDATA[
	Simple wrapper to xml_node.getAttribute().  The default return value can be set to a
	boolean for simple processing flow control
	]]></comment>

	<example>
	// Standard usage
	attribute_value_1 = xml_obj.get_attribute('test');

	// Flow control
	if (xml_obj.get_attribute('test', false))
	{
		do_something();
	}
	</example>
</method>
 * -JJSDOC
 */

function xml_get_attribute (a_name, default_value)
{
	var err_obj	= false;
	var str 	= '';

	// TODO: check for special cases with sub-nodes, IE, etc...
	try
	{
		if (!this.is_text_node())
		{
			str = this.xml_obj.getAttribute(a_name);

			if (str == null && typeof (default_value) != undefined)
				str = default_value;
		}
	}
	catch (err_obj)
	{
		alert ('xml_get_attribute: problem extracting attribute ' 
			+ a_name + ' for xml object ' + this.node_name + '\n' + err_obj.description);

		throw err_obj;
	}

	return str;
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_get_all_attributes</name>
	<class_name>get_all_attributes</class_name>
	<params>
	</params>

	<returns>A list of attributes</returns>

	<comment><![CDATA[
	Extracts a list of all the attributes set for the current node and returns them in a standard
	JavaScript object.  Can be used in conjunction with xml_set_all_attributes to perform bulk updates
	]]></comment>

	<example>
	attr_list = xml_obj.get_all_attributes();

	for (a_name in attr_list)
	{
		alert (attr_list[a_name]);
	}
	</example>
</method>
 * -JJSDOC
 */
function xml_get_all_attributes ()
{
	var err_obj	= false;
	var i		= 0;
	var a_node	= false;
	var a_list	= new Object();

	try
	{
		for (i = 0; i < this.xml_obj.attributes.length; i++)
		{
			a_node = this.xml_obj.attributes[i];
			a_list[a_node.nodeName] = a_node.nodeValue;
		}
	}
	catch (err_obj)
	{
		alert ('xml_get_all_attributes: problem reading attributes ' 
			+ i + ' for xml object ' + this.node_name + '\n' + err_obj.description);

		throw err_obj;
	}

	return a_list;
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_set_attribute</name>
	<class_name>set_attribute</class_name>
	<params>
		<param name='a_name' type='String' optional='no' default=''>
		The name of the attribute to be set
		</param>
		<param name='a_value' type='String' optional='no' default=''>
		The value of the attribute
		</param>
	</params>

	<returns></returns>

	<comment><![CDATA[
	Simple wrapper to xml_node.setAttribute().  The value being set must be a string (or convertable to a string
	via toString()).
	]]></comment>

	<example>
	xml_obj.set_attribute('test', 'new value');
	</example>
</method>
 * -JJSDOC
 */


function xml_set_attribute (a_name, a_value)
{
	var err_obj	= false;

	// TODO: check for special cases with sub-nodes, IE, etc...
	try
	{
		this.xml_obj.setAttribute(a_name, a_value);
	}
	catch (err_obj)
	{
		alert ('xml_set_attribute: problem setting attribute ' 
			+ a_name + ' for xml object ' + this.node_name + '\n' + err_obj.description);

		throw err_obj;
	}
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_delete_attribute</name>
	<class_name>delete_attribute</class_name>
	<params>
		<param name='a_name' type='String' optional='no' default=''>
		The name of the attribute to be deleted
		</param>
	</params>

	<returns></returns>

	<comment><![CDATA[
	Simple wrapper to xml_node.removeAttribute()
	]]></comment>

	<example>
	xml_obj.delete_attribute('test');
	</example>
</method>
 * -JJSDOC
 */
function xml_delete_attribute (a_name)
{
	var xo 		= this.xml_obj;
	var err_obj	= false;

	// TODO: check for special cases with sub-nodes, IE, etc...
	try
	{
		xo.removeAttribute(a_name);
	}
	catch (err_obj)
	{
		alert ('xml_delete_attribute: problem removing attribute ' 
			+ a_name + ' for xml object ' + this.node_name + '\n' + err_obj.description);

		throw err_obj;
	}
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_delete_all_attributes</name>
	<class_name>delete_all_attributes</class_name>
	<params>
	</params>

	<returns></returns>

	<comment><![CDATA[
	This method iterates over all the attribute child-nodes on the current node.  Note that it is not recursive:
	only those attributes which are children of the current node are removed.
	]]></comment>

	<example>
	xml_obj.delete_all_attributes();
	</example>
</method>
 * -JJSDOC
 */
function xml_delete_all_attributes ()
{
	var xo 		= this.xml_obj;
	var err_obj	= false;
	var i		= 0;
	var a_node	= false;

	try
	{
		// TODO: rewrite to use wrappers
		// Not particularly an issue at present, but...
		for (i = 0; i < xo.attributes.length; i++)
		{
			a_node = xo.attributes[i];
			xo.removeAttributeNode(a_node);
		}
	}
	catch (err_obj)
	{
		alert ('xml_delete_all_attributes: problem removing attribute ' 
			+ i + ' for xml object ' + this.node_name + '\n' + err_obj.description);

		throw err_obj;
	}
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_set_all_attributes</name>
	<class_name>set_all_attributes</class_name>
	<params>
		<param name='attributes_hash' type='Object' optional='no' default=''>
		A list of attribute key/value pairs which need to be set
		</param>
		<param name='delete_existing' type='Boolean' optional='yes' default='false'>
		If set, all existing attributes on the node will be removed prior to the new attributes being added
		</param>
	</params>

	<returns></returns>

	<comment><![CDATA[
	Simple wrapper to allow batch-processing of new attributes
	]]></comment>

	<example>
	var a_hash = new Object();
	a_hash['one'] = 'test';
	a_hash['two'] = 'trial';
	xml_obj.set_all_attributes (a_hash);
	</example>
</method>
 * -JJSDOC
 */
function xml_set_all_attributes (attributes_hash, delete_existing)
{
	var xo 		= this.xml_obj;
	var err_obj	= false;
	var a_name	= '';
	var a_value	= '';

	if (delete_existing)
	{
		try
		{
			this.delete_all_attributes ();
		}
		catch (err_obj)
		{
			alert ('xml_set_all_attributes: problem removing existing attributes ' 
				+ ' for xml object ' + this.node_name + '\n' + err_obj.description);
	
			throw err_obj;
		}
	}

	try
	{
		for (a_name in attributes_hash)
		{
			a_value = attributes_hash[a_name];
			this.set_attribute (a_name, a_value);
		}
	}
	catch (err_obj)
	{
		alert ('xml_set_all_attributes: problem setting attribute ' 
			+ a_name + '/' + a_value + ' for xml object ' + this.node_name + '\n' + err_obj.description);

		throw err_obj;
	}
}

/* +JJSDOC
<method parent='xml_manager' visibility='private'>
	<name>xml_is_text_node</name>
	<class_name>is_text_node</class_name>
	<params>
		<param name='include_comments' type='Boolean' optional='no' default='false'>
		Whether or not to class Comment nodes as text
		</param>
	</params>

	<returns></returns>

	<comment><![CDATA[
	Indicates whether the current node is a text node or not - used when generating debug, etc.
	]]></comment>

	<example>
	if (xml_obj.is_text_node())
	{
		do_something();
	}
	</example>
</method>
 * -JJSDOC
 */
function xml_is_text_node (include_comments)
{
	// Node types: (http://www.w3schools.com/Dom/dom_nodetype.asp)
	//	1: <child node>
	//	3: #text
	//	4: #cdata-section
	//	8: #comment

	var retval = false;
	switch (this.node_type)
	{
		case 3:
		case 4:
			retval = true;
			break;

		case 8:
			if (include_comments)
				retval = true;
			break;

		default:
			retval = false;
	}

	return retval;
}

/* +JJSDOC
<method parent='xml_manager' visibility='private'>
	<name>xml_load_from_url</name>
	<class_name>load_from_url</class_name>
	<params>
		<param name='xml_url' type='String' optional='no' default=''>
		The URL from which to load the XML
		</param>
		<param name='suppress_warnings' type='Boolean' optional='yes' default='false'>
		Indicates that failure to load the XML should not trigger user-level warnings
		</param>
	</params>

	<returns></returns>

	<comment><![CDATA[
	Loads an XML structure from a given URL.  The URL must be on the same domain/server as the caller.

	<p>
	Firefox 3 has a surprising quirk: the default security model bars access to XML files held 
	in other directories when running code on the local machine.
	</p>

	<p>
	See <a href='http://kb.mozillazine.org/Security.fileuri.strict_origin_policy'>here</a> for details
	This is a browser-level issue and <b>cannot</b> be addressed in javascript!
	</p>
	]]></comment>

	<example>
	if (xml_obj.load_from_url('review_list.xml'))
	{
		do_something();
	}
	</example>
</method>
 * -JJSDOC
 */

function xml_load_from_url (xml_url, suppress_warnings)
{
	// Code adapted from 
	// 	http://www.w3schools.com/xml/tryit.asp?filename=tryxml_parse_file_crossbrowser
	// 	http://www.codetoad.com/xml_javascripti_tutorial.asp
	// 	http://chromespot.com/showthread.php?t=1063
	var err_obj 	= false;
	var t_req	= false;
	var t_list	= false;
	var t_xml_obj	= false;
	var bool	= true;

	this.xml_obj	= false;
	this.ie_xml_obj	= false;

	// IE is a special case: it doesn't support native DOM access, so has to load XML via an
	// ActiveX interface.  Two side effects of this are a) non-standard methods and b) the "virtual" JS object can't
	// be interrogated...

	if (typeof(ActiveXObject) != 'undefined')
	{
		// IE path
		// See http://www.brainbell.com/tutors/XML/XML_Book_B/XMLDOMDocument_Object.htm for details
		// on the available attributes and methods
		try
		{
			t_xml_obj = new ActiveXObject ("Microsoft.XMLDOM");

			// We appear to need to set async before triggering the load...
			t_xml_obj.async = false;
			t_xml_obj.load (xml_url);

			if (this.debug_level > 4)
				alert ('xml_load_from_url: IE path used: remapped XML parent node');

			// For IE, the XML object hierarchy is stored under xml_obj.documentElement
			// e.g. xml_obj.documentElement.primary.secondary.tertiary
			// We therefore map "this.xml_obj" to t_xml_obj.documentElement instead of t_xml_obj
			this.ie_xml_obj	= t_xml_obj;
			this.xml_obj 	= t_xml_obj.documentElement;
		}
		catch (err_obj)
		{
			if (!suppress_warnings)
			{
				alert ('xml_load_from_url: IE path failed: ' + err_obj.description);
				throw (err_obj);
			}

			bool = false;
		}
	}
	else
	{
		// Firefox/Opera/Chrome
		// Childnodes are available at the top level
		// e.g. xml_obj.primary.secondary.tertiary
		try
		{
			t_req = new XMLHttpRequest();
			t_req.open ("GET", xml_url, false);
			t_req.send (null);

			this.xml_obj = t_req.responseXML.documentElement;
			//this.xml_obj.async = false;
		}
		catch (err_obj)
		{
			if (!suppress_warnings)
			{
				alert ('xml_load_from_url: Unable to load XML file ' + xml_url + ':\n' +
					err_obj.description + '\nThis may be a local security issue?');

				throw (err_obj);
			}
			bool = false;
		}
	}



	// Now we have the XML object, parse it and extract useful information
	// Some loading scenarios will fail silently, so we do a final sanity check
	if (this.xml_obj)
	{
		this.initialise();

		// Sanity check!
		// XML parsing failure doesn't appear to trigger a critical error, though it is logged in the 
		// audit trail.  Instead, the information is returned as a node in the object returned
		// We therefore need a manual check
		if (this.node_name == 'parsererror')
		{
			if (!suppress_warnings)
			{
				alert ('Parse failure when reading URL contents:\n' +
						this.get_node_value() + '\n' +
						this.get_child_node_value ('sourcetext') + '\n'
					);
			}

			this.xml_obj = false;
			bool = false;
		}
	}
	else
	{
		bool = false;
	}
	
	if (!bool && !suppress_warnings)
			alert ('Failed to load XML object from url ' + xml_url);

	return bool;
}

/* +JJSDOC
<method parent='xml_manager' visibility='private'>
	<name>xml_load_from_string</name>
	<class_name>load_from_string</class_name>
	<params>
		<param name='str' type='String' optional='no' default=''>
		The string which needs to be converted into XML
		</param>
		<param name='root_node_name' type='String' optional='yes' default=''>
		The name of the root node which is to be used
		</param>
	</params>

	<returns></returns>

	<comment><![CDATA[
	Allows XML to be dynamically generated from a string.  There must be a single root node: this can be automatically
	generated by the function if required.

	<p>
	The string is passed to the internal JavaScript XML parser and must therefore pass all the standard validation tests
	</p>
	]]></comment>

	<example>
	str = "<root><example>this is a test</example></root>";
	if (xml_obj.load_from_string(str))
	{
		do_something();
	}
	</example>
</method>
 * -JJSDOC
 */

function xml_load_from_string (str, root_node_name)
{
	// Adapted from http://www.w3schools.com/Xml/tryit.asp?filename=tryxml_parsertest2
	var parser 	= false;
	var xo		= false;
	var t_obj	= false;
	var bool	= true;

	// Make sure the string is valid XML by wrapping the contents in a "parent node"
	if (root_node_name)
		str = "<" + root_node_name + ">" + str + "</" + root_node_name + ">";

	try 
	{
		// Internet Explorer
		if (this.debug_level > 1)
			alert ('xml_load_from_string: IE path');

		xo = new ActiveXObject ("Microsoft.XMLDOM");
		xo.async = "false";
		xo.loadXML (str);

		// IE special case mapping...
		this.ie_xml_obj = xo;
	}
	catch (e)
	{
		// Firefox, Mozilla, Opera, etc.
		try 
		{
			if (this.debug_level > 1)
				alert ('xml_load_from_string: Non-IE path');

			parser 		= new DOMParser();
			xo	= parser.parseFromString(str, "text/xml");

			// XML parsing failure doesn't appear to trigger a critical error, though it is logged in the 
			// audit trail.  Instead, the information is returned as a node in the object returned
			// We therefore need a manual check
			t_obj = xo.getElementsByTagName('parsererror');
			if (t_obj.length > 0)
			{
				alert ('Parse failure when converting string to XML object');
				this.debug_values();
				bool = false;
			}
		}
		catch (e)
		{
			alert (e.message);
			bool = false;
		}
	}

	// In all "create from string" scenarios, the XML object is actually stored under a "#document" node
	// This "virtual" node doesn't appear to support node insertions
	// We therefore map "this.xml_obj" to t_xml_obj.documentElement instead of t_xml_obj
	this.parent_xml_obj	= xo;
	this.xml_obj 		= xo.firstChild;
	
	this.initialise();

	return bool;
}

/* +JJSDOC
<method parent='xml_manager' visibility='private'>
	<name>xml_load_from_object</name>
	<class_name>load_from_object</class_name>
	<params>
		<param name='t_xml_obj' type='Object' optional='no' default=''>
		A "raw" XML node which needs to be wrapped
		</param>
		<param name='parent_xml_obj' type='Object' optional='yes' default='false'>
		Specific to XML fragments: the parent "raw" XML node
		</param>
		<param name='ie_xml_obj' type='Object' optional='yes' default='false'>
		Internet Explorer specific; XML fragment specific: the parent "raw" XML node
		</param>
		<param name='node_position' type='Integer' optional='yes' default='-1'>
		Specific to XML fragments: the position of this node relative to the other nodes at the current level 
		in the XML structure
		</param>
	</params>

	<returns></returns>

	<comment><![CDATA[
	Essentially an internal method: used to wrap XML fragments
	]]></comment>

	<example>
	var review_xml = new xml_manager ('review.xml');
	var child_node = review_xml.xml_obj.childNodes[0];

	var child_xml = new xml_manager();
	child_xml.load_from_object (child_xml, review_xml.xml_obj, false, 0);
	</example>
</method>
 * -JJSDOC
 */

function xml_load_from_object(t_xml_obj, parent_xml_obj, ie_xml_obj, node_position)
{
	// Used when adding/replacing nodes on a child node
	// The original parent will be cascaded down, so there should always be a single step from (g*)child to parent
	// regardless of how many levels are inbetween (e.g. parent.child.grandchild.greatgrandchild)
	this.xml_obj 		= t_xml_obj;
	this.parent_xml_obj	= (typeof (parent_xml_obj) != 'undefined' 	? parent_xml_obj	: false);
	this.ie_xml_obj		= (typeof (ie_xml_obj) != 'undefined' 		? ie_xml_obj		: false);
	this.position		= (typeof (node_position) != 'undefined' 	? node_position		: -1);

	this.initialise();
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_get_elements_by_tag_name</name>
	<class_name>xml_get_elements_by_tag_name</class_name>
	<params>
		<param name='tag_name' type='String' optional='no' default=''>
		The name of the node which is to be searched for
		</param>
		<param name='max_depth' type='Integer' optional='yes' default='-1'>
		How many levels of the XML structure to traverse before returning
		</param>
	</params>

	<returns>An array of nodes with the given tag name</returns>

	<comment><![CDATA[
	Equivalent to the standard getElementsByTagName() function call.

	<p>
	Matching is recursive - i.e. all nodes at all levels within the XML structure will be checked and all matches
	will be returned as a flat array (i.e. depth information is <b>not</b> retained).  The max_depth hint can be used to 
	restrict the search to the first N levels, reducing the time needed to complete the search
	</p>
	]]></comment>

	<example>
	<![CDATA[
	// XML:
	//	<test>
	//		<level_one>
	//			<example_node>Test</example_node>
	//		</level_one>
	//
	//		<example_node>Test</example_node>
	//	</test>
	//	
	var xml_obj = new xml_manager ('example.xml');

	// Returns two nodes:
	// xml_list[0] = test.level_one.example_node
	// xml_list[1] = test.example_node
	var xml_list = xml_obj.get_elements_by_tag_name ('example_node');

	// Returns one node:
	// xml_list[0] = test.example_node
	var xml_list = xml_obj.get_elements_by_tag_name ('example_node', 1);
	]]>
	</example>
</method>
 * -JJSDOC
 */
function xml_get_elements_by_tag_name (tag_name, max_depth)
{
	var ret_list	= new Array();
	var tmp_list	= new Array();
	var i		= 0;
	var t_node	= false;

	max_depth = (typeof(max_depth) != 'undefined' ? max_depth : -1);
	if (this.xml_obj)
	{
		/*
		 * Deal directly with the wrappers - should be quicker...
		 * (600% speedup in Firefox; 20% speedup in IE.  C'est la vie...)
		 * Note that we need to recurse through all child nodes, as per getElementsByTagName...
		 *
			var xo		= false;
			result_list = this.xml_obj.getElementsByTagName(tag_name);

			for (i = 0; i < result_list.length; i++)
			{
				xo = new xml_manager ();
				xo.load_from_object (result_list[i], this.parent_xml_obj, this.ie_xml_obj);
				ret_list.push (xo);
			}
		 */

		// Speedup hack/hint: a "depth" can be set: the function will stop recursing once this depth is reached
		max_depth--;

		for (i = 0; i < this.child_nodes.length; i++)
		{
			t_node = this.child_nodes[i];

			if (t_node.tag_name == tag_name)
				ret_list.push (t_node);

			if (t_node.child_nodes.length > 0 && max_depth != 0)
			{
				tmp_list = t_node.get_elements_by_tag_name (tag_name, max_depth);

				if (tmp_list.length > 0)
					ret_list = ret_list.concat (tmp_list);
			}
		}
	}
	else
	{
		alert('XML object has not yet been initialised: no actions possible');
	}

	return ret_list;
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_delete_node</name>
	<class_name>delete_node</class_name>
	<params>
		<param name='node' type='Object' optional='no' default=''>
		A "wrapped" child node which is to be deleted from the current node
		</param>
	</params>

	<returns></returns>

	<comment><![CDATA[
	Removes the specified child-node from the parent.
	]]></comment>

	<example>
	<![CDATA[
	// XML:
	//	<test>
	//		<example_node>Test</example_node>
	//	</test>
	//	
	var xml_obj = new xml_manager ('example.xml');

	var child_node	= xml_obj.child_nodes[0];
	xml_obj.delete_node (child_node);
	]]>	
	</example>
</method>
 * -JJSDOC
 */
function xml_delete_node (node)
{
	// Some extra sanity checking needed here...
	if (node.position != -1)
		this.delete_node_by_id (node.position);
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_delete_nodes_by_tag_name</name>
	<class_name>delete_nodes_by_tag_name</class_name>
	<params>
		<param name='node_name' type='String' optional='no' default=''>
		The name of the node(s) to be deleted
		</param>

		<param name='max_count' type='Integer' optional='yes' default='-1'>
		The number of nodes to be deleted
		</param>
	</params>

	<returns>Integer: the number of nodes deleted</returns>

	<comment><![CDATA[
	Deletes all nodes with the given tag name at the current level.  This activity is not recursive.
	]]></comment>

	<example>
	<![CDATA[
	// XML:
	//	<test>
	//		<example_node>Test One</example_node>
	//		<example_node>Test Two</example_node>
	//	</test>
	//	
	var xml_obj = new xml_manager ('example.xml');

	// "Test Two" will still be present
	xml_obj.delete_nodes_by_tag_name ('example_node', 1);

	// Both "Test One" and "Test Two" will be removed
	xml_obj.delete_nodes_by_tag_name ('example_node');
	]]>	
	</example>
</method>
 * -JJSDOC
 */

function xml_delete_nodes_by_tag_name (node_name, max_count)
{
	var i 		= 0;
	var tmp_node	= false;
	var found 	= 0;

	max_count = (typeof(max_count) != 'undefined' ? max_count : -1);

	for (i = 0; i < this.child_nodes.length; i++)
	{
		tmp_node = this.child_nodes[i];
		if (tmp_node.node_name == node_name)
		{
			// This is where things get a bit tricky: we need to work on the actual XML object, rather
			// than the wrappers - and we need to refresh the wrappers afterwards
			// We can modify the existing node in situ, but to keep things simple, we simply delete the
			// node and recreate it

			// Bear in mind that we may find multiple matches: we therefore postpone child node rebuilding
			// until the search is complete
			this.delete_node_by_id (i);
			found++;
	
			if (found == max_count)
				break;
		}
	}

	return found;
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_delete_nodes_by_attribute</name>
	<class_name>delete_nodes_by_attribute</class_name>
	<params>
		<param name='attr_name' type='String' optional='no' default=''>
		The name of the attribute which is to be used in the search
		</param>

		<param name='attr_value' type='String' optional='yes' default='false'>
		If specified, indicates that the value of the attribute also needs to be matched
		</param>

		<param name='tag_name' type='String' optional='yes' default='false'>
		If specified, indicates that the tag name also needs to be matched
		</param>

		<param name='max_count' type='Integer' optional='yes' default='-1'>
		The number of nodes to be deleted
		</param>
	</params>

	<returns>Integer: the number of nodes deleted</returns>

	<comment><![CDATA[
	Deletes all nodes at the current level with the given attribute name/value defined: the node name can
	also be specified to further restrict the search.

	<p>
	This method is <b>not</b> recursive.
	</p>
	]]></comment>

	<example>
	<![CDATA[
	// XML:
	//	<test>
	//		<example_node link='here'>Test One</example_node>
	//		<example_node link='there'>Test Two</example_node>
	//		<demo_node link='there'>Test Three</example_node>
	//	</test>
	//	
	var xml_obj = new xml_manager ('example.xml');

	// Deletes "Test One" only
	xml_obj.delete_nodes_by_attribute ('link', 'here');

	// Both "Test One" and "Test Two" will be removed
	xml_obj.delete_nodes_by_attribute ('link', false, 'example_node');

	// All three child nodes will be removed
	xml_obj.delete_nodes_by_attribute ('link');
	]]>	
	</example>
</method>
 * -JJSDOC
 */
function xml_delete_nodes_by_attribute (attr_name, attr_value, tag_name, max_count)
{
	var i 		= 0;
	var str		= '';
	var found	= 0;

	for (i = 0; i < this.child_nodes.length; i++)
	{
		tmp_node = this.child_nodes[i];

		if (!tmp_node.is_text_node() && (!tag_name || tag_name == tmp_node.tag_name))
		{
			// Random string to try and avoid clashes...
			str = tmp_node.get_attribute (attr_name, '#*T*V11YYz!%');

			// TODO: fix up so empty strings aren't matched
			if (str && (!attr_value || str == attr_value))
			{
				this.delete_node_by_id (i);
				found++;

				if (found == max_count)
					break;
			}
		}
	}

	return found;
}

/* +JJSDOC
<method parent='xml_manager' visibility='private'>
	<name>xml_delete_nodes_by_id</name>
	<class_name>delete_nodes_by_id</class_name>
	<params>
		<param name='node_id' type='Integer' optional='no' default=''>
		The numeric ID of the node to be deleted
		</param>
	</params>

	<returns></returns>

	<comment><![CDATA[
	Deletes the node at the specified position within the childNode/child_node array
	]]></comment>

	<example>
	<![CDATA[
	// XML:
	//	<test>
	//		<example_node>Test One</example_node>
	//		<example_node>Test Two</example_node>
	//	</test>
	//	
	var xml_obj = new xml_manager ('example.xml');

	// "Test Two" will still be present
	xml_obj.delete_node_by_id (0);
	]]>	
	</example>
</method>
 * -JJSDOC
 */

function xml_delete_node_by_id (node_id)
{
	var err_obj 	= false;
	var tn		= false;

	try
	{
		// We have to delete both the wrapper (this.child_nodes[id]) and the XML node (this.xml_obj.childNodes[id])
		this.child_nodes.splice (node_id, 1);
		tn = this.xml_obj.childNodes[node_id];
		this.xml_obj.removeChild(tn);

		if (this.debug_level > 4)
			alert ('xml_delete_node_by_id: Removed child ' + node_id);
	}
	catch (err_obj)
	{
		alert ('xml_delete_node_by_id: failure for ' + node_id + ': ' + err_obj.description);
		throw (err_obj);
	}
}


/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_get_nodes_by_attribute</name>
	<class_name>get_nodes_by_attribute</class_name>
	<params>
		<param name='attr_name' type='String' optional='no' default=''>
		The name of the attribute which is to be used in the search
		</param>

		<param name='attr_value' type='String' optional='yes' default='false'>
		If specified, indicates that the value of the attribute also needs to be matched
		</param>

		<param name='tag_name' type='String' optional='yes' default='false'>
		If specified, indicates that the tag name also needs to be matched
		</param>
	</params>

	<returns>Array: a list of nodes which match the given criteria</returns>

	<comment><![CDATA[
	Returns all nodes at the current level with the given attribute name/value defined: the node name can
	also be specified to further restrict the search.

	<p>
	This method is <b>not</b> recursive.
	</p>
	]]></comment>

	<example>
	<![CDATA[
	// XML:
	//	<test>
	//		<example_node link='here'>Test One</example_node>
	//		<example_node link='there'>Test Two</example_node>
	//		<demo_node link='there'>Test Three</example_node>
	//	</test>
	//	
	var xml_obj = new xml_manager ('example.xml');

	// Returns "Test One" only
	var ret_list = xml_obj.get_nodes_by_attribute ('link', 'here');

	// Both "Test One" and "Test Two" will be returned
	var ret_list = xml_obj.delete_nodes_by_attribute ('link', false, 'example_node');

	// All three child nodes will be returned
	var ret_list = xml_obj.delete_nodes_by_attribute ('link');
	]]>	
	</example>
</method>
 * -JJSDOC
 */
function xml_get_nodes_by_attribute (attr_name, attr_value, tag_name)
{
	var i 		= 0;
	var str		= '';
	var retlist	= new Array();

	for (i = 0; i < this.child_nodes.length; i++)
	{
		tmp_node = this.child_nodes[i];

		if (tmp_node.tag_name != '#text' && (!tag_name || tag_name == tmp_node.tag_name))
		{
			str = tmp_node.get_attribute (attr_name, false);

			if (str && (!attr_value || str == attr_value))
				retlist.push(tmp_node);
		}
	}

	return retlist;
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_update_node</name>
	<class_name>update_node</class_name>
	<params>
		<param name='node_name' type='String' optional='no' default=''>
		The name of the node to be updated
		</param>

		<param name='node_value' type='String' optional='no' default=''>
		The value that should be associated with the node
		</param>

		<param name='node_attributes' type='Object' optional='no' default=''>
		A set of key/value pairs which should be loaded onto the node as attributes
		</param>
	</params>

	<returns>Object: the node which has been created/updated</returns>

	<comment><![CDATA[
	Simple wrapper to insert_node(): the <b>first</b> node discovered which matches the given node_name will be deleted
	prior to the new node being created and inserted.  See insert_node() for further details on attribute management.

	<p>
	Returns a pointer to the new node: this has been xml_manager wrapped and can be actioned as required.
	</p>
	]]></comment>

	<example>
	<![CDATA[
	// XML:
	//	<test>
	//		<example_node>Test One</example_node>
	//		<example_node>Test Two</example_node>
	//	</test>
	//	
	var xml_obj = new xml_manager ('example.xml');

	// XML will now contain "Test Two" and "Test Three"
	var new_node = xml_obj.update_node ('example_node', 'Test Three');

	// All standard operations can be carried out on the node which has been returned
	new_node.render_with_xsl('test.xsl');
	]]>	
	</example>
</method>
 * -JJSDOC
 */
function xml_update_node (node_name, node_value, node_attributes)
{
	this.delete_nodes_by_tag_name (node_name, 1)
	return this.insert_node (node_name, node_value, node_attributes);
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_insert_node</name>
	<class_name>insert_node</class_name>
	<params>
		<param name='node_name' type='String' optional='no' default=''>
		The name of the node to be created
		</param>

		<param name='node_value' type='String' optional='no' default=''>
		The value that should be associated with the node
		</param>

		<param name='node_attributes' type='Object' optional='no' default=''>
		A set of key/value pairs which should be loaded onto the node as attributes
		</param>
	</params>

	<returns>Object: the node which has been created/updated</returns>

	<comment><![CDATA[
	Creates a new XML node with the given value/attributes and adds it as a child to the currently
	selected XML node.  The new node will always be added to the end of the child_node list.

	<p>
	Attributes can be added to the new node: these must be specified within an Object: all the properties of
	this object will be iterated through and added to the node.
	</p>

	<p>
	Returns a pointer to the new node: this has been xml_manager wrapped and can be actioned as required.
	</p>
	]]></comment>

	<example>
	<![CDATA[
	// XML:
	//	<test>
	//		<example_node>Test One</example_node>
	//		<example_node>Test Two</example_node>
	//	</test>
	//	
	var xml_obj = new xml_manager ('example.xml');

	var attr_list 		= new Object();
	attr_list['status'] 	= "test";
	attr_list['next_step'] 	= "debug";

	// XML will now contain "Test One", "Test Two" and "Test Three"
	// "Test Three" will have two attributes: 'status' and 'next_step'
	var new_node = xml_obj.insert_node ('example_node', 'Test Three', attr_list);

	// All standard operations can be carried out on the node which has been returned
	new_node.render_with_xsl('test.xsl');
	]]>	
	</example>
</method>
 * -JJSDOC
 */
function xml_insert_node (node_name, node_value, node_attributes)
{
	/*
	 * Adding nodes involves up to five steps:
	 * 	1: Create a new node
	 *	2: (if required) create a text-node and link to the new node
	 *	3: (if required) Add the attributes to the child node
	 *	4: Add the new child node to the parent node
	 *
	 * These steps are made trickier by cross-compatibility and namespace issues - see
	 * 	http://www.faqts.com/knowledge_base/view.phtml/aid/34741 for details
	 *
	 * We can also experience issues with the fact that the current "xml_obj" may be a child of another object
	 * To work around this, xml_manager includes a link to the parent XML object, which can be traced back
	 * and used to generate the new node.
	 * Messy, but avoids namespace issues
	 */

	var xo			= false;
	var ixo			= false;
	var tmp_node 		= false;
	var child_node 		= false;
	var text_node 		= false;
	var node_list		= false;
	var i 			= 0;
	var str			= '';
	var err_obj		= false;

	// Create the new node and the associated text-node (if any)
	// As ever, IE is a special case...
	xo = this.xml_obj;

	if (typeof(this.ie_xml_obj) != 'boolean')
	{
		try
		{
			// Need to make sure this works at lower levels...
			ixo = this.ie_xml_obj;

			// First param = type of node to create: 1 == "ELEMENT"
			tmp_node = ixo.createNode(1, node_name, ixo.namespaceURI);
			text_node = ixo.createTextNode(node_value);

		}
		catch (err_obj)
		{
			alert ('xml_insert_node (IE path): failure creating new node: ' + err_obj.description);
			throw (err_obj);
		}
	}
	else
	{
		try
		{
			xo = this.xml_obj;
			tmp_node = document.createElementNS(xo.namespaceURI, node_name);

			node_value = (typeof (node_value) != 'undefined' ? node_value : '');
			text_node = document.createTextNode(node_value.toString());
		}
		catch (err_obj)
		{
			alert ('xml_insert_node (non-IE path): failure creating new node: ' + err_obj.description + '\n' +
				i + ': ' + node_name + ': ' + this.node_value);

			throw (err_obj);
		}
	}

	// Link the text_node to the new_node
	tmp_node.appendChild(text_node);

	// Add any new attributes to the new_node
	if (node_attributes)
	{
		try
		{
			for (name in node_attributes)
				tmp_node.setAttribute(name, node_attributes[name]);
		}
		catch (err_obj)
		{
			alert ('xml_insert_node: failure parsing attributes: ' + err_obj.description);
			throw (err_obj);
		}
	}

	// Add the new_node to the xml_obj and refresh the wrappers
	try
	{
		xo.appendChild (tmp_node);

		// We also need to update the xml_manager child_nodes[] wrapper
		// The new node is always added to the end of the list
		child_node = this.initialise_child_node (tmp_node)
	}
	catch (err_obj)
	{
		alert ('xml_insert_node: failure parsing ' + node_name + ': ' + err_obj.description);
		throw (err_obj);
	}

	return child_node;
}


/* +JJSDOC
<method parent='xml_manager' visibility='private'>
	<name>xml_load_xsl_contents</name>
	<class_name>load_xsl_contents</class_name>
	<params>
		<param name='xsl_filename' type='String' optional='no' default=''>
		The name of the XSL file which needs to be loaded
		</param>

		<param name='cache_xsl' type='Boolean' optional='no' default='True'>
		If true, the XSL will be cached for future use
		</param>
	</params>

	<returns>String: the XSL source</returns>

	<comment><![CDATA[
	Loads the XSL located at the given URL - JavaScript security rules mean that the URL must be on the same 
	host as the caller.

	<p>
	As some JavaScript implementations do not support <xsl:include> statements for dynamically loaded XSL, this function
	will perform a search to find, load and insert any included XSL.  This search will recurse through the included XSL as well.
	</p>

	<p>
	<b>Please note:</b> xsl:import is <u>not</u> supported, as this requires support from the XSL interpreter.
	</p>

	<p>
	By default, all XSL will be cached for future reuse: this can be disabled on an individual basis by either setting cache_xsl 
	to false or setting the "global" variable xml_obj.xsl_cache_enabled to false.  The cache will be cleared if the page is reloaded.
	</p>
	]]></comment>

	<example>
	<![CDATA[
	var xml_obj = new xml_manager ('example.xml');

	var str = xml_obj.load_xsl_contents ('test.xsl');
	]]>	
	</example>
</method>
 * -JJSDOC
 */
function xml_load_xsl_contents (xsl_filename, cache_xsl)
{
	var retval 		= '';
	var str 		= '';
	var t_obj		= false;

	var child_xsl_filename	= '';
	var includes		= false;
	var current_wd		= './';
	var child_wd		= './';
	var re			= false;
	var t_re		= false;
	var i			= 0;

	if (typeof(cache_xsl) == 'undefined')
		cache_xsl = this.xsl_cache_enabled;

	// Since XSL parsing is deterministic, we cache previously requested items, to cut down on processing overheads
	if (typeof(this.xsl_cache[xsl_filename]) == 'undefined' || cache_xsl == false)
	{
		str = this.load_url_contents (xsl_filename);

		if (str != '')
		{
			// Check for include statements
			// This system won't work with imports, but we do support recursive checking for includes...
			//
			// The path for the "child-include" needs to be relative to the "parent-include" file's location
			// We therefore need to identify the current-working-directory and append any additional path information onto it
			if (xsl_filename.match('/') != null)
				current_wd = xsl_filename.substring(0, xsl_filename.lastIndexOf('/') + 1);

			// Javascript regexp doesn't appear to have the equivalent of s/re/ : the multi-line parameter
			// /re/m simply loops through all lines in the string and applies the RE to each in turn, rather than 
			// ignoring the newlines and treating the
			// input as a single line.
			re = /<xsl:include\s+href=['"](.*?)['"].*?>/;
			while (true)
			{
				t_obj = re.exec(str)

				if (t_obj != null)
				{
					child_xsl_filename = t_obj[1];

					// Make sure we always have a directory definition
					i = child_xsl_filename.lastIndexOf('/');
					if (i != -1)
					{
						i++;

						// We need to include the '/' on the wd and exclude it from the filename...
						child_wd = child_xsl_filename.substring(0, i);
						child_xsl_filename = child_xsl_filename.substring(i, child_xsl_filename.length);
					}
					else
					{
						child_wd = './';
					}
				
					child_xsl_filename = current_wd + child_wd + child_xsl_filename;

					if (this.debug_level > 4)
						alert ('xml_load_xsl_contents: ' + xsl_filename + 
							': recursing to load: ' + child_xsl_filename);

					t_str = this.load_xsl_contents(child_xsl_filename, true);

					// Strip the header/footer tags from the child XSL
					//  xsl:stylesheet and xsl:transform are both valid XSL container-tags
					t_re = /<xsl:(stylesheet|transform).*?>/mi;
					t_str = trim_string (t_str, t_re, true);

					t_re = /<\/xsl:(stylesheet|transform).*?>/mi;
					t_str = trim_string (t_str, t_re, false);

					// Stick the expanded XSL in situ - t_obj[0] includes the entire "<xsl:include href=''>"
					str = str.replace (t_obj[0], t_str);
				}
				else
				{
					break;
				}
			}

			// Cache the results before proceeding
			if (cache_xsl == true)
				this.xsl_cache[xsl_filename] = str;
		}
		else
		{
			alert ('xml_load_xsl_contents: unable to find content for ' + xsl_filename);
			this.xsl_cache[xsl_filename] = false;
		}

	}

	if (cache_xsl == true)
		retval = this.xsl_cache [xsl_filename];
	else
		retval = str;

	if (!retval)
		alert ('xml_load_xsl_contents: parse failure for ' + xsl_filename);
	
	return retval;
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_render_with_xsl</name>
	<class_name>render_with_xsl</class_name>
	<params>
		<param name='xsl_filename' type='String' optional='no' default=''>
		The name/URL of the XSL file which needs to be loaded
		</param>

		<param name='cache_xsl' type='Boolean' optional='no' default='True'>
		If true, the XSL will be cached for future use
		</param>

		<param name='xsl_str' type='String' optional='yes' default='False'>
		If a string containing XSL is supplied, this will override the loading of XSL from a file.
		XSL supplied in this way is not (cannot!) be cached.
		</param>

		<param name='output_xml' type='Boolean' optional='yes' default='False'>
		If set to True, the &lt;xml&gt; header will not be stripped from the content before returning
		</param>
	</params>

	<returns>String: the result of rendering the current XML object with the given XSL</returns>

	<comment><![CDATA[
	Takes the XSL located at the given URL and passes the current set of XML nodes through it 

	<p>
	This function supports <xsl:include> instructions: see load_xsl_contents() for full details
	</p>

	<p>
	<b>Please note:</b> JavaScript security rules mean that the URL must be on the same host as the caller.
	</p>

	<p>
	By default, all XSL will be cached for future reuse: this can be disabled on an individual basis by either setting cache_xsl 
	to false or setting the "global" variable xml_obj.xsl_cache_enabled to false.  The cache will be cleared if the page is reloaded.
	</p>
	]]></comment>

	<example>
	<![CDATA[
	// Render the full XML object
	var xml_obj = new xml_manager ('example.xml');
	var str = xml_obj.render_with_xsl ('test.xsl');

	// Render an XML fragment
	var child_node = xml_obj.child_nodes[0];
	var str = xml_obj.render_with_xsl ('child_test.xsl');
	]]>	
	</example>
</method>
 * -JJSDOC
 */
function xml_render_with_xsl (xsl_filename, cache_xsl, xsl_str, output_xml)
{
	// Adapted from http://johnvey.com/features/deliciousdirector/xslt-filter-sort.html
	// Returns the parsed XML as a string
	var xsl_stylesheet	= false;
	var xsl_proc		= false;
	var parser		= false;
	var fragment		= false;

	var xsl_doc		= false;
	var xsl_template	= false;

	var i			= 0;
	var str		 	= "";
	var target_div		= false;

	// Load the XSL into a local string variable
	// IE and Opera don't support XSL includes, so we need to internally parse and expand these during the load
	if (!xsl_str)
		xsl_str = this.load_xsl_contents(xsl_filename, cache_xsl);

	// Mozilla - and IE8? 
	if (xsl_str != "" && document.implementation.createDocument) 
	{
		// create a XSLT processor
		xsl_proc = new XSLTProcessor();
    
		// convert the XSL to a DOM object, so we can attach it to the XSLT processor
		// DOMParser only supports reading from local data, so we need to slurp the XSL into a string first
		parser = new DOMParser();
		xsl_stylesheet = parser.parseFromString(xsl_str, "text/xml");
    
		// attach the stylesheet and perform the transform
		// The fragment must be linked to a document; we link it to the current page
		try
		{
			xsl_proc.importStylesheet(xsl_stylesheet);
			fragment = xsl_proc.transformToFragment(this.xml_obj, document);
		}
		catch (err_obj)
		{
			alert ('XSL parse failure (Mozilla path) for ' + xsl_filename + ': ' + err_obj.description + '\n');
			throw (err_obj);
		}
    
		// create a DOM container and insert the transformed object: this then allows us to convert it 
		// into a string
		target_div = document.createElement("div");
		target_div.appendChild(fragment);
		str = target_div.innerHTML;
	} 
	else 
	{
		// IE pre-v8 (?)
		// First create a DOM document, then create an XSLT template document
		xsl_doc 	= new ActiveXObject("Msxml2.FreeThreadedDOMDocument");
		xsl_doc.async 	= false;
		xsl_template 	= new ActiveXObject("Msxml2.XSLTemplate");
    
		// load the stylesheet into the DOM document and attach said document to the XSLT template
		try
		{
			xsl_doc.loadXML (xsl_str);
			xsl_template.stylesheet = xsl_doc;

			// get the XSLT processor object and feed it the XML data
			xsl_proc 	= xsl_template.createProcessor();
			xsl_proc.input 	= this.xml_obj;
    
			// do the transform
			xsl_proc.transform();
    
			// grab the output, and insert into HTML document
			str = xsl_proc.output;
		}
		catch (err_obj)
		{
			alert ('XSL parse failure (IE path) for ' + xsl_filename + ': ' + err_obj.description + '\n');
			throw (err_obj);
		}
	}

	// Unfortunately, Mozilla doesn't support the "disable-output-escaping" flag for XLST, which means that HTML embedded
	// in a CDATA section will be sanitised (e.g. "<br>" is converted into "&lt;br&gt;".
	// We therefore need to try and catch and fix these - while not breaking any genuine usage of &lt; and &gt;...
	str = xml_fix_html_tags (str, this.debug_level);

	if (!output_xml)
	{
		// By default, we strip the "<?xml" header from the generated content.
		// This is to avoid issues with non-XML-compliant HTML being rendered
		if (str.indexOf ('<?xml') == 0)
		{
			i = str.indexOf ('?>') + 2;
			str = str.substring (i, str.length);
		}
	}
	else
	{
		// IE does something very odd: if the output is XML rather than HTML, it gives the XML an encoding type which
		// IE then can't parse!
		str = str.replace ('encoding="UTF-16"', 'encoding="ISO-8859-1"');
	}
	return str;
}

/* +JJSDOC
<method parent='xml_manager' visibility='private'>
	<name>xml_fix_html_tags</name>
	<class_name>fix_html_tags</class_name>
	<params>
		<param name='str' type='String' optional='no' default=''>
		The string to be parsed
		</param>
	</params>

	<returns>String: the HTML source with all issues corrected</returns>

	<comment><![CDATA[
	xml_manager is mainly intended for HTML processing: the HTML is stored in a CDATA section within a given XML file.

	<p>
	However, some browsers translate HTML entities when the XML is passed through an XSL stylesheet.  This function
	reviews the "parsed" HTML and corrects all issues which are identified
	]]></comment>

	<example>
	<![CDATA[
		// Output from some JavaScript engines:
		var old_str = "This is a test &amp;amp; example&lt;br />";

		// Corrected output: "This is a test &amp; example<br />"
		var str = xml_obj.fix_html_tags (old_str);
	]]>	
	</example>
</method>
 * -JJSDOC
 */
function xml_fix_html_tags (str)
{
	// It's important to ensure that we do minimal pattern matching...
	// Safari/Chrome appears to be doing something wierd and half-translating HTML tags (e.g. "&lt;br>" instead of "<br>" or "&lt;br&gt;")
	// S/C also appears to be rendering " as &quot;
	// TODO: figure out why and fix at the source!
	var regex_list 	= new Array (/&lt;[\w].*?&gt;/g, /&lt;\/[\w].*?&gt;/g, /&lt;[\w].*?>/g, /&lt;\/[\w].*?>/g);
	var i		= 0;
	var j		= 0;
	var k		= 0;
	var t_list	= false;
	var regex	= false;
	var match_list	= false;
	var match_hash	= new Object();
	var old_tag	= '';
	var new_tag	= '';

	if (str)
	{
		for (i = 0; i < regex_list.length; i++)
		{
			regex = regex_list[i];

			match_list = str.match (regex);

			if (match_list && match_list.length > 0)
			{
				for (j = 0; j < match_list.length; j++)
				{
					old_tag = match_list[j];

					// We do a search-and-replace for all instances of the tag, to speed things up
					if (typeof (match_hash[old_tag]) == 'undefined')
					{
						// substring (4, string.length -4) may be safer and quicker...
						new_tag = old_tag.replace ('&lt;', '<');
						new_tag = new_tag.replace ('&gt;', '>');

						// Another quick hack: manage & characters in embedded URLs
						new_tag = new_tag.replace ('&amp;', '&');
						match_hash[old_tag] = new_tag;

						// There may be issues with attempting to embed a tag into a RE
						// We therefore implement a quick and dirty looping mechanism

						t_list = str.split (old_tag);
						str = '';

						// Don't forget the special case of the final chunk!
						for (k = 0; k < (t_list.length); k++)
						{
							str += t_list[k]
							if (k != (t_list.length - 1))
								str += new_tag;
						}
					}
				}
			}
		}

		// We also need to deal with any remaining HTML characters
		// Mozilla at least appears to be parsing "&variable;" as "&amp;variable;"...
		//str = str.replace ('&amp;pound;', '&pound;');

		regex = /(&amp;\w+;)/g;
		match_list = str.match (regex);

		if (match_list && match_list.length > 0)
		{
			for (j = 0; j < match_list.length; j++)
			{
				old_tag = match_list[j];
				new_tag = '&' + old_tag.substring(5, old_tag.length);

				str = str.replace(old_tag, new_tag);
			}
		}

		// Slightly dodgy hacks...
		// IE's parser (and possibly others) tend to include the whitespace when dealing with conditionally generated XSL variables
		// This results in things like 
		//	<a href="&#xA;				javascript: render_comments_div (-1, false)&#xA;			">
		// We therefore run a regex to find and fix these
		// For the moment, we fix hrefs only, to minimise the risk of impacting valid white-space usage...
		str = str.replace (/(<a.*?href=["'])&#xA;\s+(.*?)&#xA;\s+?(["'].*?>)/gm, "$1$2$3");
	}

	return str;
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_get_child_node_value</name>
	<class_name>get_child_node_value</class_name>
	<params>
		<param name='node_name' type='String' optional='no' default=''>
		The name of the child node
		</param>
		<param name='default_value' type='String' optional='no' default=''>
		The value to be returned if the node is not found
		</param>
	</params>

	<returns>String: the value associated with the given child node</returns>

	<comment><![CDATA[
	Simple helper function: returns the node-value for the <b>first</b> match found in the
	child node list.

	<p>
	This function is not recursive.  get_node_value() is used to extract the child-value.
	</p>
	]]></comment>

	<example>
	<![CDATA[
	// XML:
	//	<test>
	//		<example_node>Test One</example_node>
	//		<trial_node>Test Two</trial_node>
	//		<trial_node>Test Three</trial_node>
	//	</test>
	//	
	var xml_obj = new xml_manager ('example.xml');

	// str will be set to "Test One"
	str = xml_obj.get_child_node_value('example_node');

	// str will be set to "Test Two"
	str = xml_obj.get_child_node_value('trial_node');

	// str will be set to false
	str = xml_obj.get_child_node_value('demo_node', false);
	]]>	
	</example>
</method>
 * -JJSDOC
 */
function xml_get_child_node_value (node_name, default_value)
{
	var retval = '';
	var matches = this.get_elements_by_tag_name (node_name, 1);

	if (matches.length > 0)
		retval = matches[0].get_node_value();
	else
		retval = default_value;
	
	return retval;
}

function xml_get_child_node_attribute (node_name, attr_name, default_value)
{
	var retval = '';
	var matches = this.get_elements_by_tag_name (node_name, 1);

	if (matches.length > 0)
		retval = matches[0].get_attribute (attr_name, default_value);
	else
		retval = default_value;
	
	return retval;
}

function xml_set_child_node_attribute (node_name, attr_name, new_value)
{
	var retval = '';
	var matches = this.get_elements_by_tag_name (node_name, 1);

	if (matches.length > 0)
		matches[0].set_attribute (attr_name, new_value);
	else
		alert ('xml_set_child_node:attribute: Unable to find child node ' + node_name);
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_get_node_value</name>
	<class_name>get_node_value</class_name>
	<params>
		<param name='trim_value' type='Boolean' optional='yes' default='false'>
		If true, all newlines will be removed and all instances of multi-character whitespace will be 
		truncated to a single space
		</param>
		<param name='get_all_text_nodes' type='Boolean' optional='yes' default=''>
		If true, all the text-nodes found will be concatenated into a single string
		</param>
	</params>

	<returns>String: the text- value associated with the current node</returns>

	<comment><![CDATA[
	XML text-node handling is inconsistent between browsers: large chunks of text may be split across multiple nodes
	or additional "zero-length" text-nodes may be inserted around embedded non-text nodes.  By default, this function
	concatenates and returns all text nodes under the current node as a single string: excess whitespace can also be trimmed
	if required.
	]]></comment>

	<example>
	<![CDATA[
	// XML:
	//	<test>
	//		<example_node>Test One</example_node>
	//		<trial_node>
	//		Test Two
	//			<level_two>test</level_two>
	//		Test Three
	//		</trial_node>
	//	</test>
	//	
	var xml_obj = new xml_manager ('example.xml');

	var xml_node = xml_obj.child_nodes[0];
	// Returns "Test One"
	str = xml_node.get_node_value();

	var xml_node = xml_obj.child_nodes[1];
	// Returns "Test Two Test Three"
	str = xml_node.get_node_value();
	]]>	
	</example>
</method>
 * -JJSDOC
 */
function xml_get_node_value (trim_value, get_all_text_nodes)
{
	// By default, we do *NOT* trim the return value and we concatenate all textual child nodes together
	trim_value 		= (typeof(trim_value) != 'undefined' 		? trim_value 		: false);
	get_all_text_nodes 	= (typeof(get_all_text_nodes) != 'undefined' 	? get_all_text_nodes 	: true);

	var i 		= 0;
	var cnl		= 0;
	var ni		= 0;
	var nt		= 0;
	var str		= '';
	var cn		= false;
	var retval 	= '';
	var err_obj	= false;
	var ws_re	= /^\s+$/;

	// The text associated with a given XML node is stored in 0..N nodes of type '#text'
	// There may be one or more non-text nodes inbetween these nodes, and Firefox allegedly splits data across multiple
	// nodes when a size limit is reached (4k)
	// We therefore need to handle two cases:
	// 1: Return all text nodes found; break at first non-text node
	// 2: Return all text nodes found; ignore non-text nodes
	try
	{
		cnl = this.child_nodes.length;
		for (i = 0; i < cnl; i++)
		{
			cn = this.child_nodes[i];

			if (cn.is_text_node())
			{
				// Read the value from the "wrapped" XML object
				str = cn.xml_obj.nodeValue;

				// The DOM returns the "null" object if the node has no text associated with it
				if (str == null)
					str = '';
				
				// When there is a CDATA node present, Firefox also converts pre/post whitespace into 
				// separate child nodes.  We therefore skip empty/whitespace-only nodes
				if (str != '' && str.search(ws_re) == -1)
				{
					retval += str;


					// Check for the "break when we run out of text nodes" rule
					// TODO: set up the ability to return an array of "concatenated" text nodes
					ni = i + 1;
					if (ni < cnl && !get_all_text_nodes && !this.child_nodes[ni].is_text_node())
						break;
				}
			}
		}
	}
	catch (err_obj)
	{
		// Self-contained elements (i.e. <br />) cause parsing failures on Firefox
		// We therefore trap and ignore these
		if (i == 0)
			retval = '';
		else
			alert ('xml_get_node_value: search failure: ' + err_obj.description);
	}

	// Used by xml_debug_values()
	if (trim_value)
	{
		retval = retval.replace(/\n/gm, '');
		retval = retval.replace(/\s+/gm, ' ');

		if (retval.charAt(0) == ' ')
			retval = retval.substring(1, retval.length);
	}

	return retval;
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_debug</name>
	<class_name>debug</class_name>
	<params>
		<param name='output_alert' type='Boolean' optional='yes' default='true'>
		If true, the output from the function will be displayed in an Alert popup
		</param>
		<param name='tabsize' type='Integer' optional='yes' default='0'>
		Parameter for recursive use: The current depth of the parsing
		</param>
		<param name='show_values' type='Boolean' optional='yes' default='false'>
		If set, a truncated version of value associated with the node will be displayed
		</param>
		<param name='trim_string' type='Boolean' optional='yes' default='false'>
		If set, the node-values will have whitespace trimmed prior to display
		</param>
	</params>

	<returns>String: a simple text representation of the XML object</returns>

	<comment><![CDATA[
	This function recurses through the entire XML object and generates a simple tree-view, showing all the
	child nodes and (optionally) the values associated with them.  Attributes are not displayed.

	<p>
	<b>show_values</b> will enable the display of the text associated with the node: this is truncated to 
	25 characters where necessary.
	</p>
	]]></comment>

	<example>
	<![CDATA[
	// XML:
	//	<test>
	//		<example_node>Test One</example_node>
	//		<trial_node>
	//		Test Two
	//			<level_two>test</level_two>
	//		Test Three
	//		</trial_node>
	//	</test>
	//	
	var xml_obj = new xml_manager ('example.xml');

	str = xml_obj.debug (false);

	// Output:
	// 
	// test
	// |--example_node
	// |--trial_node
	// |--|--level_two
	]]>	
	</example>
</method>
 * -JJSDOC
 */
function xml_debug (output_alert, tabsize, show_values, trim_values)
{
	output_alert 	= (typeof(output_alert)	!= 'undefined') ? output_alert	: true;
	tabsize 	= (typeof(tabsize) 	!= 'undefined') ? tabsize 	: 0;
	show_values 	= (typeof(show_values)	!= 'undefined') ? show_values	: false;
	trim_values 	= (typeof(trim_values)	!= 'undefined') ? trim_values	: false;

	var str 	= (tabsize == 0 ? 'xml_debug' : '');
	var t_str 	= '';
	var i		= 0;
	var tabs 	= '';
	var node_list	= false;
	var t_xm	= false;

	// Simple formatting mechanism
	for (i = 0; i <= tabsize; i++)
		tabs += '|--';

	if (tabsize == 0)
	{
		if (show_values)
			str = this.node_name + ': ' + this.get_node_value(trim_values) + '\n';
		else
			str = this.node_name + '\n';
	}

	if (this.child_nodes.length > 0)
	{
		for (i = 0; i < this.child_nodes.length; i++)
		{
			t_xm = this.child_nodes[i];

			// We ignore text/comment nodes.  See xml_initalise for further details on node types
			if (t_xm.is_text_node(true) == false)
			{
				if (show_values)
				{
					t_str = t_xm.get_node_value(trim_values);

					if (trim_values && t_str.length > 25)
						t_str = t_str.substring(0, 22) + '...';

					t_str = ': ' + t_str;
				}

				str += tabs + t_xm.node_name + t_str + '\n';

				// Recurse through any child nodes of the current node
				if (t_xm.child_nodes.length > 0)
					str += t_xm.debug(false, tabsize + 1, show_values, trim_values);
			}
		}
	}
	else if (output_alert)
	{
		alert ('xml_debug: No children found');
	}

	if (output_alert)
		alert (str);

	return str;
}

/* +JJSDOC
<method parent='xml_manager' visibility='public'>
	<name>xml_debug_values</name>
	<class_name>debug_values</class_name>
	<params>
		<param name='output_alert' type='Boolean' optional='yes' default='true'>
		If true, the output from the function will be displayed in an Alert popup
		</param>
		<param name='trim_string' type='Boolean' optional='yes' default='false'>
		If set, the node-values will have whitespace trimmed prior to display
		</param>
	</params>

	<returns>String: a simple text representation of the XML object</returns>

	<comment><![CDATA[
	Simple wrapper to debug() which shows the values associated with each node. 
	]]></comment>

	<example>
	<![CDATA[
	// XML:
	//	<test>
	//		<example_node>Node One</example_node>
	//		<trial_node>
	//		Node Two Top
	//			<level_two>Node Three</level_two>
	//		Node Two Bottom
	//		</trial_node>
	//	</test>
	//	
	var xml_obj = new xml_manager ('example.xml');
	str = xml_obj.debug_values (false);

	// Output:
	// 
	// test:
	// |--example_node: Node One
	// |--trial_node: Node Two Top Node Two Bottom
	// |--|--level_two: Node Three
	]]>	
	</example>
</method>
 * -JJSDOC
 */
function xml_debug_values (output_alert, trim_values)
{
	var output_alert = (typeof(output_alert) != 'undefined' ? output_alert : true);
	var trim_values = (typeof(trim_values) != 'undefined' ? trim_values : true);
	var str = '';

	return this.debug (output_alert, 0, true, trim_values);
}

function xml_inspect_internals (output_alert)
{
	var xo 		= false;
	var f_str 	= '';
	var v_str 	= '';
	var o_str 	= '';
	var u_str 	= '';
	var i		= 0;
	var err_obj	= false;

	// Stupid IE workaround hack...
	if (typeof (this.parent_xml_obj) == 'boolean')
		xo = this.xml_obj;
	else
		xo = this.parent_xml_obj;

	// We can't inspect the IE xml object, as it's a "virtual" wrapper over an ActiveX object
	// See http://www.brainbell.com/tutors/XML/XML_Book_B/XMLDOMDocument_Object.htm for details of available
	// methods and attributes

	if (typeof (this.ie_xml_obj) == 'boolean')
	{
		for (i in xo)
		{
			try
			{
				switch (typeof(xo[i]))
				{
					case 'function':
						f_str += 'Method: ' + i + '\n';
						break;
					case 'object':
						o_str += 'Object: ' + i + '\n';
						break;
					default:
						v_str += 'Attribute: ' + i + ': ' + xo[i] + '\n';
						break;
				}
			}
			catch (err_obj)
			{
				// Some of the attributes on non-IE XML objects appear to not have a type defined (???)
				// We therefore trap and flag these separately
				u_str += 'Unknown: ' + i + '\n';
			}	
		}
	
		if (output_alert)
		{
			alert (f_str);
			alert (o_str);
			alert (v_str);
			alert (u_str);
		}
		else
		{
			return f_str + "\n" + o_str + "\n" + v_str + "\n" + u_str + "\n";
		}
	}
	else
	{
		alert ('IE XML object: unable to inspect');
	}
}

function xml_inspect_wrapper (output_alert)
{
	var str = '';
	str += "xml_obj: " 		+ (this.xml_obj 	? 'Initialised' 		: 'Uninitialised') + "\n";
	str += "ie_xml_obj: " 		+ (this.ie_xml_obj 	? 'Initialised' 		: 'Uninitialised') + "\n";
	str += "parent_xml_obj: " 	+ (this.parent_xml_obj 	? 'Initialised' 		: 'Uninitialised') + "\n";
	str += "debug_level: " 		+ (this.debug_level 	? this.debug_level 		: 0) + "\n";
	str += "child_nodes: " 		+ (this.xml_obj 	? this.child_nodes.length	: 'Uninitialised') + "\n";
	str += "position: " 		+ (this.position 	? this.position 		: 0) + "\n";
	str += "tag_name: " 		+ (this.tag_name 	? this.tag_name 		: 'Undefined') + "\n";
	str += "node_name: " 		+ (this.node_name 	? this.node_name 		: 'Undefined') + "\n";
	str += "node_type: " 		+ (this.node_type 	? this.node_type 		: 'Undefined') + "\n";

	if (output_alert)
		alert (str);
	else
		return str;
}

// Copied from library.js to make xml_manager self-sufficient
function xml_load_url_contents (my_url, debug)
{
	var req_obj 	= getRequestObject();  
	var str 	= "";

	if (debug)
		alert ('load_url_contents: ' + my_url);

	// Sanity-check the request object before proceeding
	if (typeof(req_obj) == 'object') 
	{
		try
		{
			if (req_obj.readyState >= 0) 
			{
				req_obj.open('GET', my_url, false);
				req_obj.send(null);

				// Attempt to sanity check return value
				// Not sure how this will work with relative urls...
				if (my_url.indexOf('http://') == 0 || my_url.indexOf('https://') == 0)
				{
				}

				str = req_obj.responseText;
			}
			else
			{
				alert('XML HTTP Request Object not ready');
			}
		}
		catch (e)
		{
			alert ('load_url_contents: Failed to get ' + my_url);
		}
	}
	else
	{
		alert('XML HTTP Request Object unavailable');
	}

	return str;
}

