/**
 * treeview.js
 *
 * Various functions that enhance the Treeview skin for MediaWiki.  See the
 * README.treeview_javascript file for a brief description - it should be
 * located in the same directory as this file.
 *
 * Be careful with modifications - this code is trying to deal with many
 * possible browser configurations and caching issues that might not all be
 * immediately apparent; some of it's a bit hairy - it's slowly being
 * simplified as my understanding of what's possible in portable javascript
 * evolves.
 *
 * Licenced under the General Public Licence 2, without warranty.
 *
 * @licence GPL2 http://www.gnu.org/copyleft/gpl.html
 * @author http://members.dodo.com.au/~netocrat
 * @version $Date: 2007/05/06 09:50:53 $ $Revision: 1.34 $
 */

/* mnemonics: tv_hi => Treeview hidden input id
 * sync with Treeview.php template
 * These are the data-items communicated by the server in "hidden" inputs;
 * this object serves to enforce their documentation in a single place rather
 * than for any real programmatic need.
 */
var tv_hi = new Object();
tv_hi['cookieroot'] = 'cookieroot';
tv_hi['serverqrybase'] = 'serverqrybase';
tv_hi['imgdir'] = 'imgdir';
tv_hi['iconwidth'] = 'iconwidth';
tv_hi['iconheight'] = 'iconheight';
tv_hi['pagedbtitle'] = 'pagedbtitle';
tv_hi['tvstate'] = 'tvstate';
tv_hi['xmlurl'] = 'xmlurl';
tv_hi['isloggedin'] = 'isloggedin';
tv_hi['expandednodes'] = 'expandednodes';
tv_hi['customleaficon_prefix'] = 'icon.0.';
tv_hi['customcollapseicon_prefix'] = 'icon.1.';
tv_hi['customexpandicon_prefix'] = 'icon.2.';
tv_hi['tv_txtleafimg'] = 'tv_txtleafimg';
tv_hi['tv_txtcollapseimg'] = 'tv_txtcollapseimg';
tv_hi['tv_txtexpandimg'] = 'tv_txtexpandimg';
tv_hi['baseurl_prefix'] = 'baseurl.';
/* derives from the identically named define in Hierarchy.php */
tv_hi['TV_ROOT_ID'] = 'TV_ROOT_ID';

/*------------------BEGIN--------------
 * Settings that are overridable in the 'Treeview.js' message when
 * $wgUseSiteJs = true; this message is accessible as the MediaWiki:Treeview.js
 * article when $wgUseDatabaseMessages is set to true (both of these variables
 * are true by default for most - all? - MediaWiki versions).
 * ATTN TRANSLATORS: translatable strings are those within this section that
 * are double-quote-delimited: "this is translatable" + ' and this is not'.
 */

if (!tv_syncopts) var tv_syncopts = new Object();
if (!tv_syncopts['man'   ]) tv_syncopts['man'   ] = ["man.",
  "Manually synchronises the treeview's state with the cookie-persisted state"];
if (!tv_syncopts['auto'  ]) tv_syncopts['auto'  ] = ["auto",
  "On page (re)load automatically synchronises the treeview's state with " +
  "the cookie-persisted state"];
if (!tv_syncopts['links' ]) tv_syncopts['links' ] = ["links",
  "Persists the treeview's state in the query-strings of treeview links " +
  "instead of in a cookie"];
/* Keep this key in sync with its TVSYNCSERVER counterpart in State.php */
if (!tv_syncopts['server']) tv_syncopts['server'] = ["server",
  "(Unreliable) server-synchronises the treeview's state with the " +
  "cookie-persisted state (instead of synchronising in the browser)"];
if (!tv_syncopts['off'   ]) tv_syncopts['off'   ] = ["off",
  "Disables persistence of the treeview state across pages"];

if (tv_warn_missing === undefined) var tv_warn_missing = true;
if (!tv_syncoptorder) var tv_syncoptorder=['man','auto','links','server','off'];

/* Only when cookies are enabled */
if (!tv_defaultsynctype) var tv_defaultsynctype = 'auto';

if (!tv_msg) var tv_msg = new Object();
if (!tv_msg['navbar_slider_tooltip'])
	tv_msg['navbar_slider_tooltip'  ] = "toggle navbar visibility";
if (!tv_msg['actions_slider_tooltip'])
	tv_msg['actions_slider_tooltip']="toggle page action icons visibility";
if (!tv_msg['loading']) tv_msg['loading'] = "Loading...";

if (!tv_msg['missingnode'])
	tv_msg['missingnode'    ] = "Containing node no longer exists.";
if (!tv_msg['missingchildren'])
	tv_msg['missingchildren'] = "Child nodes no longer exist.";
if (!tv_msg['trytvreload']) tv_msg['trytvreload'] = "Resync treeview.";
if (!tv_msg['tvreloadlinktooltip']) tv_msg['tvreloadlinktooltip'] =
	"Reload all visible treeview nodes from the server.";

/* Diagnostics - lower priority for translation */
if (!tv_msg['missoptwarn.1']) tv_msg['missoptwarn.1'] = "Warning: '";
if (!tv_msg['missoptwarn.2']) tv_msg['missoptwarn.2'] =
  "' is missing from tv_syncoptorder array (set tv_warn_missing to false to " +
  "avoid this alert)";
if (!tv_msg['tryfullpagereload'])
	tv_msg['tryfullpagereload'    ]="Try a full page reload.";
if (!tv_msg['defaultdiaglinktext'])
	tv_msg['defaultdiaglinktext'  ]="Problem encountered.";
if (!tv_msg['reportlinktooltip'])
	tv_msg['reportlinkhint'       ]="Bug reporting and feedback page";
if (!tv_msg['diaglinktooltip'])
	tv_msg['diaglinktooltip'      ]="Pop up a diagnostics display";
if (!tv_msg['fullreloadlinktooltip'])
	tv_msg['fullreloadlinktooltip']="Expand link by reloading entire page.";
if (!tv_msg['inaccessible'])
	tv_msg['inaccessible'         ]="[inaccessible to debug code]";
if (!tv_msg['d1']) tv_msg['d1'        ]=
	"[This code is currently undergoing testing and development; ";
if (!tv_msg['d2']) tv_msg['d2'        ]="bug reports and feedback";
if (!tv_msg['d3']) tv_msg['d3'        ]=
  " would be appreciated.  First though consider the simple possibilities " +
  "that your internet connection has been (perhaps temporarily) lost or that " +
  "you've set your browser to 'work offline']";
if (!tv_msg['nolod'] )  tv_msg['nolod' ] = "Load-on-demand not supported.";
if (!tv_msg['nolod1'])  tv_msg['nolod1'] = "Your browser appears to support ";
if (!tv_msg['mozobjs']) tv_msg['mozobjs']='XMLHttpRequest'+" objects";
if (!tv_msg['msobjs'])  tv_msg['msobjs']='Microsoft.XMLHTTP'+" ActiveX objects";
if (!tv_msg['nolod2']) tv_msg['nolod2'] =
  ", however it raised an exception on an attempt to instantiate one of " +
  "those objects.  The message text of the exception, if any, is: ";
if (!tv_msg['nolod3']) tv_msg['nolod3'] =
  "Your browser appears to support neither the "+'XMLHttpRequest'+" object " +
  "nor the " + 'Microsoft.XMLHTTP' + " ActiveX object.";
if (!tv_msg['dlorproc'])
	tv_msg['dlorproc'] = "Exception during download or post-processing.";
if (!tv_msg['dlorproc1']) tv_msg['dlorproc1'] =
  "Your browser raised an exception during the partial load process.  " +
  "The message text of the exception, if any, is: ";
if (!tv_msg['setup' ]) tv_msg['setup' ] = "Load set-up error.";
if (!tv_msg['setup1']) tv_msg['setup1'] =
 "Your browser raised an exception whilst setting up load-on-demand using the ";
if (!tv_msg['mozobj']) tv_msg['mozobj'] = 'XMLHttpRequest'+" object";
if (!tv_msg['msobj' ]) tv_msg['msobj' ] = 'Microsoft.XMLHTTP'+" ActiveX object";
if (!tv_msg['setup2'])
	tv_msg['setup2'] = ".  The message text of the exception, if any, is: ";
if (!tv_msg['conn'  ]) tv_msg['conn'] = "Connection error.";
if (!tv_msg['conn1' ]) tv_msg['conn1'] =
	"The server responded with a status other than 200 or 304: ";
if (!tv_msg['conn2']) tv_msg['conn2'] =
	"The full set of headers returned by the server is: ";
if (!tv_msg['xmlproc']) tv_msg['xmlproc'] = "XML processing error.";
if (!tv_msg['xmlproc1']) tv_msg['xmlproc1'] =
  "The server returned the following data.  Your browser was either unable " +
  "to parse it as XML, or it could not find in the XML a <div> tag with a " +
  "matching closing </div> tag: ";
if (!tv_msg['unknown']) tv_msg['unknown'] = "Branch data missing.";
if (!tv_msg['unknown1']) tv_msg['unknown1'] =
  "The data for this node's branch could not be found in the data returned " +
  "by the server, although your browser seems to have been able to parse " +
  "that returned data as XML.  The complete returned data is: ";
if (!tv_msg['reporturl']) tv_msg['reporturl'] =
  'http://clc-wiki.net/wiki/Planning:Treeview_skin:Feedback';

/* No translatable strings occur beyond this point. */

if (!tv_dropdowntimeout) var tv_dropdowntimeout = 600; /* ms */

/* sync with main.css; mnemonics: tv_cd=>Treeview css cross-dependencies */
if (!tv_cd) var tv_cd = new Object();
if (!tv_cd['header_marginTop_actionsvis'])
	tv_cd['header_marginTop_actionsvis'] = '1.3em';
if (!tv_cd['header_marginTop_actionshidden'])
	tv_cd['header_marginTop_actionshidden'] = '0.6em';
if (!tv_cd['classname_selectedancestor'])
	tv_cd['classname_selectedancestor'] = 'ancestorofselected';

/* Images are located in the directory specified by the value of the hidden
 * element whose id is specified by the tv_hi['imgdir'] variable - for a
 * default install this directory is skins/treeview under the Mediawiki server
 * root. mnemonics: tv_im => Treeview images
 */
if (!tv_im) var tv_im = new Object();
if (!tv_im['left_slider']) tv_im['left_slider'] = 'slideleft.png';
if (!tv_im['right_slider']) tv_im['right_slider'] = 'slideright.png';
if (!tv_im['leaf_node']) tv_im['leaf_node'] = 'leaf.png';
if (!tv_im['expand_node']) tv_im['expand_node'] = 'plus.png';
if (!tv_im['collapse_node']) tv_im['collapse_node'] = 'minus.png';

/* Settings that are overridable in MediaWiki:Treeview.js article when
 * $wgUseSiteJs = true
 *------------------END--------------
 */

var tv_expnodes = -1;
var tv_tvstate = -1;
var tv_xmlurl = -1;
var tv_wasloggedin = -1;
var tv_synctype = -1;
var TVSTATE = 0;
var TVSTATE_UI = 1;
var tv_cookienames = new Array();
tv_cookienames[TVSTATE] = 'tvstate';
tv_cookienames[TVSTATE_UI] = 'tvstate_ui'
var tv_statearr = new Array();
tv_statearr[TVSTATE] = tv_statearr[TVSTATE_UI] = -1;
var tv_keepmenuvis = false;
var tv_timer = null;
/* currently dropped-down menu, if any - for timer use */
var tv_menu = '';

var tv_slider_arr = [
  ['i-slide-act', tv_msg['actions_slider_tooltip'], 'p-cactions', 'tidyicons',
   'v', null, tv_showhidep_act],
  ['i-slide-navbar', tv_msg['navbar_slider_tooltip'], 'navbar', 'navbar', 'v',
   tv_getfirstchildspanoranchor, tv_showhide_navbar]
];

tv_skinload();

function tv_skinload() {
	/* non-DOM browsers not supported */
	if (!(document.getElementById && document.getElementsByTagName)) return;

	var cookiesenabled = tv_arecookiesenabled();
	/* initialise the array used in tv_isnodeserverexpanded() */
	tv_initexpnodes();

	tv_create_sliders();
	if (cookiesenabled) tv_create_syncopts();
	else tv_synctype = 'links';

	var ul = document.getElementById(tv_getdata('TV_ROOT_ID'));
	if (ul) tv_setup_branch(ul);
	if (tv_synctype == 'auto') tv_sync_ui_with_cookie();
	else if (tv_synctype != 'links') {
		tv_updatelinks('', new Array(), null, cookiesenabled);
	}

	/* replace direct link to login page from greyed LED indicator with
	 * 'login' dropdown menu for non-logged-in users for consistency with
	 * logged-in semantics now that we know that the browser is js-enabled
	 */
	var iled = document.getElementById('i-led');
	if (iled) {
		iled.href = "javascript:tv_dropdown('p-personal')";
		/* the html sets onmouseover for the contained image to
		 * tv_dropdown('p-personal', true) */
		iled.onmouseout = tv_starttimer;
		iled.onmouseover = tv_canceltimer;
	}
	var ul2 = document.getElementById('p-personal');
	if (ul2) {
		lis = ul2.getElementsByTagName('LI');
		for (var i = 0; i < lis.length; i++) {
			lis.item(i).onmouseover = tv_canceltimer;
			lis.item(i).onmouseout = tv_starttimer;
		}
		ul2.onmouseover = tv_canceltimer;
		ul2.onmouseout = tv_starttimer;
	}

	if (window.location) {
		var winloc_str = '' + window.location;
		var n = winloc_str.indexOf('#');
		var cmp = 'tv.';
		if (winloc_str.substr(n + 1, cmp.length) == cmp) {
			window.location = '#globalWrapper';
		}
	}
}

function tv_create_syncopts() {
	var div = document.getElementById('tvsync');
	if (div) {
		if (tv_warn_missing)
			for (var id in tv_syncopts)
				if (!tv_in_array(tv_syncoptorder, id))
					alert(tv_msg['missoptwarn.1'] + id +
					  tv_msg['missoptwarn.2']);
		for (var i = tv_syncoptorder.length - 1; i >= 0; i--) {
			var id = tv_syncoptorder[i];
			if (!tv_syncopts[id]) continue;
			/* A perfect test is impossible without querying the
			 * server; this one could return an incorrect result
			 * e.g when paging through the browser history */
			if (id == 'server' && !Number(tv_getdata('isloggedin')))
				continue;
			var link = document.createElement('a');
			link.href = "javascript:tv_setsynctype('" + id + "');";
			link.title = tv_syncopts[id][1];
			link.id = 'tvsync_' + id;
			var txt = document.createTextNode(tv_syncopts[id][0]);
			if (txt) {
				link.appendChild(txt);
				div.insertBefore(link, div.firstChild);
			}
		}
	}
	var synctype = tv_getpersistednodestate('synctype', TVSTATE_UI);
	if (!tv_syncopts[synctype]) synctype = tv_defaultsynctype;
	tv_setsynctype(synctype, true);
}

function tv_setsynctype(synctype, skipsync) {
	var oldsynctype = tv_synctype;
	var tvstate_arr;
	tv_synctype = synctype;
	tv_persistnode('synctype', tv_synctype, '', TVSTATE_UI);
	var syncdiv = document.getElementById('tvsync');
	if (syncdiv) {
		links = syncdiv.getElementsByTagName('A');
		for (var i = 0; i < links.length; i++) {
			var a = links.item(i);
			a.className = (a.id == 'tvsync_' + tv_synctype) ?
			  'selected' : '';
		}
	}
	if (tv_synctype == 'man' && !skipsync) tv_sync_ui_with_cookie();
	else if (tv_synctype == 'off') tv_delcookie(TVSTATE);
	else if (tv_synctype == 'links' && !skipsync) {
		tvstate_arr = tv_getpseudocookietvstate();
		tv_updatelinks(tv_tvstate, tvstate_arr, null,
		  tv_arecookiesenabled());
		tv_delcookie(TVSTATE);
	}
	if (oldsynctype == 'links' && tv_synctype != 'links') {
		tv_updatelinks('', new Array(), null, tv_arecookiesenabled());
	}
	if ((oldsynctype == 'links' || oldsynctype == 'off') &&
	    (tv_synctype == 'man'   || tv_synctype == 'auto' ||
	     tv_synctype == 'server')) {
		var path = tv_getdata('cookieroot');
		if (!tvstate_arr) tvstate_arr = tv_getpseudocookietvstate();
		/* PHP encodes commas when setting cookies; be consistent here*/
		document.cookie = tv_cookienames[TVSTATE] + '=' +
		  encodeURIComponent(tvstate_arr.join(',')) + '; path=' + path;
		tv_statearr[TVSTATE] = tvstate_arr;
	}
}

function tv_delcookie(whichcookie) {
	var path = tv_getdata('cookieroot');
	document.cookie = tv_cookienames[whichcookie] + '=del; path=' + path +
	  '; expires=Thu, 01-Jan-70 00:00:01 GMT';
	tv_statearr[tv_cookienames[whichcookie]] = -1;
}

/* doesn't actually "slide" when toggling visibility */
function tv_create_sliders() {
	for (var i = 0; i < tv_slider_arr.length; i++) {
		var slider_el = document.createElement('a');
		slider_el.id = tv_slider_arr[i][0];
		var slidermsg = tv_slider_arr[i][1];
		slider_el.title = slidermsg;
		var toggleddiv = tv_slider_arr[i][2];
		var icondiv = tv_slider_arr[i][3];
		var defaultvis = tv_slider_arr[i][4];
		slider_el.href = "javascript:tv_sliderclick_handler('"+i+"')";
		var icon = document.createElement('img');
		icon.src = tv_getdata('imgdir') + '/' + tv_im['right_slider'];
		icon.alt = slidermsg;
		slider_el.appendChild(icon);
		var icondiv_el = document.getElementById(icondiv);
		var toggleddiv_el = icondiv == toggleddiv ? icondiv_el :
		  document.getElementById(toggleddiv);
		/* don't show slider button for empty targets */
		if (icondiv_el && toggleddiv_el &&
		  toggleddiv_el.childNodes.length > 0) {
			icondiv_el.insertBefore(slider_el,
			  icondiv_el.firstChild);
		}
		var stored = tv_getpersistednodestate(toggleddiv, TVSTATE_UI);
		tv_sliderclick_handler(i, (stored? stored: defaultvis) == 'v');
	}
}

/* use an array index to hide the otherwise messy function-definitions-as-
 * parameters from the displayed url of the sliders that call this function
 */
function tv_sliderclick_handler(idx, visible) {
	var toggleddiv = tv_slider_arr[idx][2];
	var iconid = tv_slider_arr[idx][0];
	var def = tv_slider_arr[idx][4];
	var visindicator_func = tv_slider_arr[idx][5];
	var showhide_func = tv_slider_arr[idx][6];

	var toggleddiv_el = document.getElementById(toggleddiv);
	if (toggleddiv_el) {
		/* containing span/anchor */
		var icon = document.getElementById(iconid);
		v_el = visindicator_func ?
		  visindicator_func(toggleddiv_el,icon) :
		  toggleddiv_el;
		if (visible === true || (visible !== false && v_el &&
		  (!v_el.style.display && (def == 'v') ||
		  v_el.style.display == 'none'))) {
			var setvis = true;
			var newimg = tv_im['right_slider'];
			if (visible !== true) { /* ui click */
				tv_persistnode(toggleddiv,'v',def,TVSTATE_UI);
			}
		} else {
			var setvis = false;
			var newimg = tv_im['left_slider'];
			if (visible !== false) { /* ui click */
				tv_persistnode(toggleddiv,'i',def,TVSTATE_UI);
			}
		}
		showhide_func(setvis, toggleddiv_el, v_el);
		/* contained img of span/anchor */
		if (icon) icon = icon.firstChild;
		if (icon && icon.nodeType == 1 &&
		  icon.tagName.toUpperCase() == 'IMG') {
			var arr = icon.src.split('/');
			arr[arr.length - 1] = newimg;
			icon.src = arr.join('/');
		}
		/* A workaround to prevent a dual image under Konqueror.  The
		 * double-image seems to be triggered by the "float: right;"
		 * rule in main.css for #navbar #i-slide-navbar but it doesn't
		 * manifest on every page.
		 */
		if (visible == null) {
			var cloned = icon.parentNode.cloneNode(true);
			var id = icon.parentNode.id;
			var parentnd = icon.parentNode.parentNode;
			cloned.id = id;
			parentnd.insertBefore(cloned, parentnd.firstChild);
			parentnd.removeChild(icon.parentNode);
		}
	}
}

function tv_getfirstchildspanoranchor(element, ignore) {
	var ret = element.firstChild;
	while (ret && (!(ret.nodeType == 1 && (ret.tagName.toUpperCase()=='A' ||
	  ret.tagName.toUpperCase() == 'SPAN')) || ret == ignore)) {
		ret = ret.nextSibling;
	}
	return ret;
}

function tv_showhide_navbar(setvis, toggleddiv_el, visindicator_el) {
	var display = setvis ? 'inline' : 'none';
	var btn = visindicator_el;
	while (btn) {
		if (btn.nodeType == 1 && (btn.tagName.toUpperCase() == 'A' ||
		  btn.tagName.toUpperCase() == 'SPAN')) {
			btn.style.display = display;
		}
		btn = btn.nextSibling;
	}
}

function tv_showhidep_act(setvis, toggleddiv_el) {
	var hdr = document.getElementById('header');
	if (setvis) {
		toggleddiv_el.style.display = 'block';
		if (hdr) hdr.style.marginTop =
		  tv_cd['header_marginTop_actionsvis'];
	} else {
		toggleddiv_el.style.display = 'none';
		if (hdr) hdr.style.marginTop =
		  tv_cd['header_marginTop_actionshidden'];
	}
}

function tv_dropdown(portlet, visible) {
	var port = document.getElementById(portlet);
	if (port) {
		if (visible === true || (visible !== false &&
		  (!port.style.display || port.style.display == 'none'))) {
			tv_canceltimer();
			tv_menu = portlet;
			/* workaround required for IE */
			var newparent =
			  document.getElementById('content-borderbox1');
			if (newparent && newparent != port.parentNode) {
				newparent.appendChild(port);
				port.style.left = 'auto';
				port.style.right = '0';
			}
			port.style.display = 'block';
		} else	port.style.display = 'none';
	}
}

function tv_canceltimer() {
	tv_keepmenuvis = true;
	clearTimeout(tv_timer);
}

function tv_starttimer() {
	tv_keepmenuvis = false;
	tv_timer = window.setTimeout(tv_timerhandler, tv_dropdowntimeout);
}

function tv_timerhandler() {
	if (!tv_keepmenuvis) tv_dropdown(tv_menu, false);
}

function tv_arecookiesenabled() {
	var path = tv_getdata('cookieroot');
	/* use variable data to avoid mistaken assumptions based on old tests */
	var ts = new Date;
	var chk = 'test=' + ts;
	var cookiestr = chk + '; path=' + path;
	document.cookie = cookiestr;
	var enabled = (document.cookie.indexOf(chk) >= 0);
	if (enabled) {
		document.cookie = cookiestr +
		  '; expires=Thu, 01-Jan-70 00:00:01 GMT';
	}
	return enabled;
}

/* If state has changed since page load, sync union of cookie and pseudo-cookie
 * (with expands overriding collapses), else reverse pseudo-cookie state
 * (except for nodes in the 'expanded' list) and then sync to cookie.
 */
function tv_sync_ui_with_cookie() {
	var cookiestate_arr = tv_getpersistedtvstate(TVSTATE);
	var urlstate_arr = tv_getpseudocookietvstate();
	var chg = (tv_getpseudocookietvstate(true) != urlstate_arr);
	var collapse_arr = new Array();
	var expand_arr = new Array();
	for (var i = 0; i < cookiestate_arr.length; i++) {
		if (!tv_in_array(urlstate_arr, cookiestate_arr[i])) {
			var n = cookiestate_arr[i].indexOf('_');
			var ns = n>-1 ? cookiestate_arr[i].substr(0, n) : '';
			var nid = n>-1 ? cookiestate_arr[i].substr(n + 1) : '';
			if (ns == 'c') {
				collapse_arr[collapse_arr.length] = nid;
			} else	expand_arr[expand_arr.length] = nid;
		}
	}
	for (var i = 0; i < urlstate_arr.length; i++) {
		if (!tv_in_array(cookiestate_arr, urlstate_arr[i])) {
			var n = urlstate_arr[i].indexOf('_');
			var ns = n > -1 ? urlstate_arr[i].substr(0, n) : '';
			var nid = n > -1 ? urlstate_arr[i].substr(n + 1) : '';
			if (chg) ns = ns == 'e' ? 'c' : 'e';
			if (ns == 'c') {
				if (!tv_in_array(collapse_arr, nid)) {
					collapse_arr[collapse_arr.length]=nid;
				}
			} else if (!tv_in_array(expand_arr, nid)) {
				expand_arr[expand_arr.length] = nid;
			}
		}
	}
	var li;
	var postponed_arr2d = new Array();
	var state_str = '';
	for (var i = 0; i < collapse_arr.length; i++) {
		var nodeid = collapse_arr[i];
		if (li = document.getElementById('tv.e_' + nodeid)) {
			tv_expcollnodes([[li, nodeid]], false);
			/* persist as early as possible in case of later load-
			 * on-demand bail-outs... */
			tv_persistnode(nodeid, 'c',
			  tv_isnodeserverexpanded(nodeid) ? 'e' : 'c');
		} else {
			/* ... but non-visible nodes can't be persisted because
			 * tv_isnodeserverexpanded() might need 'expandednodes'
			 * data that will be passed in the upcoming load. */
			postponed_arr2d[postponed_arr2d.length] = ['c', nodeid];
		}
		if (state_str) state_str += ',';
		state_str += 'c_' + nodeid;
	}
	var li_arr = new Array();
	var j = 0;
	for (var i = 0; i < expand_arr.length; i++) {
		var nodeid = expand_arr[i];
		if (li = document.getElementById('tv.c_' + expand_arr[i])) {
			li_arr[j++] = [li, nodeid, false, false];
		}
		/* persist as early as possible in case of later load-
		 * on-demand bail-outs. */
		tv_persistnode(nodeid, 'e', tv_isnodeserverexpanded(nodeid) ?
		  'e' : 'c');
		if (state_str) state_str += ',';
		state_str += 'e_' + nodeid;
	}
	if (li_arr.length>0) tv_expcollnodes(li_arr, true, false, state_str,
	  postponed_arr2d);
	else	tv_persistnodes(postponed_arr2d);
}

function tv_in_array(arr, member) {
	for (var i = 0; i < arr.length; i++) if (arr[i] == member) return true;
	return false;
}

function tv_updatelinks(state_str, state_arr, alist_in, cookiesenabled) {
	var removestate = (cookiesenabled && tv_synctype != 'links');

	/* treeview */
	if (alist_in) var alist = alist_in;
	else {
		var ul = document.getElementById(tv_getdata('TV_ROOT_ID'));
		if (ul) var alist = ul.getElementsByTagName('a');
	}
	if (alist) {
		for (var i = 0; i < alist.length; i++) {
			if (alist.item(i).fallbackhref) {
				/* This is an expand/collapse icon; insert the
				 * state component that would cause an
				 * appropriate expand/collapse on full-page
				 *  reload.
				 */
				var href = alist.item(i).fallbackhref;
				var li = alist.item(i).parentNode;
				var state = tv_getnodestatefortoggle(li);
				if (state[0]) {
					var newstr = state[0] + '_' + state[1];
					for (var j=0; j<state_arr.length; j++) {
						s = state_arr[j].substr(2);
						if (s == state[1]) continue;
						newstr += ',' + state_arr[j];
					}
					alist.item(i).fallbackhref =
					  tv_rebuildhref(href, newstr, null,
					    tv_getdata('pagedbtitle'));
				}
			} else {
				var href = alist.item(i).href;
				alist.item(i).href = removestate ?
				  tv_removestatefromhref(href, alist.item(i).
				    parentNode.parentNode.id.substr('tv.X_'.
				      length)) :
				  tv_rebuildhref(href, state_str, alist.item(i).
				    parentNode.parentNode.id.substr('tv.X_'.
				      length));
			}
		}
	}

	/* navbar and treeview '?' link */
	if (!alist_in) {
		var div = document.getElementById('navbar');
		if (div) {
			var alist = div.getElementsByTagName('a');
			for (var i = 0; i < alist.length; i++) {
				var href = alist.item(i).href;
				alist.item(i).href = removestate ?
				  tv_removestatefromhref(href,alist.item(i).id):
				  tv_rebuildhref(href, state_str,
				    alist.item(i).id);
			}
		}
		var hl = document.getElementById('tvhelplink');
		if (hl) {
			var href = hl.href;
			hl.href = removestate ?
			  tv_removestatefromhref(href, hl.id) :
			  tv_rebuildhref(href, state_str, hl.id);
		}
	}
}

function tv_splithref(href) {
	var start = href.indexOf('?');
	if (start > 0) {
		qrystr = href.substr(start + 1);
		qrystr_arr = qrystr.split('&');
		var param_arr = new Array();
		var idx = -1;
		for (var i = 0; i < qrystr_arr.length; i++) {
			/* assumes that there is only a single equals sign */
			param_arr[i] = qrystr_arr[i].split('=');
			if (param_arr[i][0] == 'tvstate') idx = i;
		}
		return [href.substr(0, start), '?', qrystr_arr, param_arr, idx];
	} else return [href, '', new Array(), -1];
}

/* Precondition: qrystr_arr.length > 0 */
function tv_joinhref(baseurl, qrystr_arr) {
	return baseurl + '?' + qrystr_arr.join('&');
}

function tv_removestatefromhref(href, baseid) {
	var href_arr = tv_splithref(href);
	if (href_arr[1]) {
		if (href_arr[4] >= 0) {
			href_arr[2].splice(href_arr[4], 1);
			href_arr[3].splice(href_arr[4], 1);
		}
		if (href_arr[3].length == 1 && href_arr[3][0][0] == 'title' ||
		  href_arr[3].length <= 0 /* paranoid */) {
			return tv_getdata('baseurl_prefix', baseid);
		} else {
			return tv_joinhref(href_arr[0], href_arr[2]);
		}
	} else return href;
	return newhref;
}

function tv_rebuildhref(href, state_str, baseid, dbttl) {
	if (!state_str) return tv_removestatefromhref(href, baseid);
	var href_arr = tv_splithref(href);
	if (href_arr[1]) {
		var idx = href_arr[4];
		if (idx >= 0) {
			href_arr[3][idx][1] = state_str;
			href_arr[2][idx] = href_arr[3][idx].join('=');
		} else {
			var len = href_arr[3].length;
			href_arr[3][len] = ['tvstate', state_str];
			href_arr[2][len] = href_arr[3][len].join('=');
		}
		for (var i = 0; i < href_arr[3].length; i++) {
			if (href_arr[3][i][0] == 'action' &&
			  href_arr[3][i][1] == 'view') {
				href_arr[3][i][1] = 'view_tv';
				href_arr[2][i] = href_arr[3][i].join('=');
			}
		}
		return tv_joinhref(href_arr[0], href_arr[2]);
	} else return tv_newhrefwithqrystr(href, state_str, baseid, dbttl);
}

function tv_newhrefwithqrystr(href, state_str, baseid, dbttl) {
	var serverqrybase = tv_getdata('serverqrybase');
	if (!dbttl) {
		var dbttlel = document.getElementById('dbttl.' + baseid);
		var dbttl = dbttlel ? dbttlel.value : '';
	}
	if (dbttl && serverqrybase && state_str) {
		var newhref = serverqrybase + '?title=' + dbttl +
		 '&action=view_tv&tvstate=' + state_str;
	} else var newhref = href;
	return newhref;
}

function tv_getpersistedtvstate(whichcookie) {
	if (!whichcookie) whichcookie = TVSTATE;
	/* cached for later calls */
	if (tv_statearr[whichcookie] == -1) {
		var tvstate = '';
		var cookiearr = document.cookie.split('; ');
		for (var i = 0; i < cookiearr.length; i++) {
			/* assumes that there is only a single equals sign */
			var crumbarr = cookiearr[i].split('=');
			if (crumbarr[0] == tv_cookienames[whichcookie]) {
				tvstate = crumbarr[1] ? crumbarr[1] : '';
				break;
			}
		}
		/* PHP encodes commas when setting cookies, so decode them */
		tv_statearr[whichcookie] = tvstate ?
		  decodeURIComponent(tvstate).split(',') : new Array();
	}
	return tv_statearr[whichcookie];
}

/* Optimisation/tidying opportunity (might avoid multiple alerts in browsers
 * that monitor cookies): set the cookie once-only rather than the multiple
 * times that result from separate calls to tv_persistnode().
 */
function tv_persistnodes(state_arr2d) {
	for (var i = 0; i < state_arr2d.length; i++) {
		tv_persistnode(state_arr2d[i][1], state_arr2d[i][0],
		  tv_isnodeserverexpanded(state_arr2d[i][1]) ? 'e' : 'c',
		  TVSTATE);
	}
}

function tv_persistnode(nodeid, newstate, def, whichcookie) {
	if (!whichcookie) whichcookie = TVSTATE;
	var cookiesenabled = tv_arecookiesenabled();
	var storeincookie = cookiesenabled && !(whichcookie == TVSTATE &&
	  (tv_synctype == 'off' || tv_synctype == 'links'));

	if (!(storeincookie || whichcookie == TVSTATE)) return;

	var tvstate_arr = whichcookie == TVSTATE ? tv_getpseudocookietvstate() :
	  tv_getpersistedtvstate(whichcookie);
	var nodestate_a = tv_getpersistednodestate_arr(nodeid, null,
	  tvstate_arr);
	var res = tv_update_tvstate(tvstate_arr, nodeid, nodestate_a[0],
	  nodestate_a[1], newstate, def);
	if (res[1]) {
		if (storeincookie) {
			var tvstate_arr = res[0];
			var path = tv_getdata('cookieroot');
			var expires = tvstate_arr.length <= 0 ?
			  '; expires=Thu, 01-Jan-70 00:00:01 GMT' : '';
			/* "cookie-cache" - probably unnecessary */
			tv_statearr[whichcookie] = tvstate_arr;
			/* PHP encodes commas when setting cookies; do so here
			 * for consistency */
			document.cookie = tv_cookienames[whichcookie] + '=' +
			  encodeURIComponent(tvstate_arr.join(',')) +
			  '; path=' + path + expires;
		}
		if (whichcookie == TVSTATE) {
			tv_tvstate = tvstate_arr.join(',');
			tv_updatelinks(tv_tvstate, tvstate_arr, null,
			  cookiesenabled);
		}
	}
}

function tv_getpseudocookietvstate(getorig) {
	if (tv_tvstate == -1 || getorig) {
		var orig = tv_getdata('tvstate');
		if (tv_tvstate == -1) tv_tvstate = orig;
		else if (getorig) return orig;
	}
	var state_arr = tv_tvstate ? tv_tvstate.split(',') : new Array();
	return state_arr;
}

function tv_update_tvstate(tvstate_arr,nodeid,state_curr,idx_curr,newstate,def){
	var changed = true;
	if (def === newstate) {
		if (idx_curr > -1) tvstate_arr.splice(idx_curr, 1);
		else changed = false;
	} else {
		var newentry = newstate + '_' + nodeid;
		if (idx_curr <= -1) {
			tvstate_arr[tvstate_arr.length] = newentry;
		} else if (newstate != state_curr) {
			tvstate_arr[idx_curr] = newentry;
		} else changed = false;
	}
	return [tvstate_arr, changed];
}

function tv_getpersistednodestate(nodeid, whichcookie) {
	var nodestate_a = tv_getpersistednodestate_arr(nodeid, whichcookie);
	return nodestate_a[0];
}

function tv_getpersistednodestate_arr(nodeid, whichcookie, tvstate) {
	var nodestate = '';
	if (!tvstate) tvstate = tv_getpersistedtvstate(whichcookie);
	for (var i = 0; i < tvstate.length; i++) {
		var n = tvstate[i].indexOf('_');
		if (n > -1) {
			var ns = tvstate[i].substr(0, n);
			var nid = tvstate[i].substr(n + 1);
			if (nid == nodeid) {
				nodestate = ns;
				break;
			}
		}
	}
	return [nodestate, i < tvstate.length ? i : -1];
}

function tv_isnodeserverexpanded(nodeid) {
	/* tv_expnodes is initialised by tv_initexpnodes() and updated from
	 * data passed in xml loads by tv_addexpnodes().*/
	for (var i = 0; i < tv_expnodes.length; i++) {
		if (tv_expnodes[i] == nodeid) return true;
	}
	return false; /* earlier return is possible */
}

function tv_initexpnodes() {
	var expnodes = tv_getdata('expandednodes');
	/* avoid a single empty element */
	tv_expnodes = expnodes ? expnodes.split(',') : new Array();
}

function tv_getnodestatefortoggle(li) {
	var ret = tv_getnodestate(li);
	if (ret[0]) ret[0] = ret[0] == 'c' ? 'e' : 'c';
	return ret;
}

function tv_getnodestate(li) {
	var prelen = 'tv.X_'.length;
	if (li.id.substr(0, prelen) == 'tv.e_') var newstate = 'e';
	else if (li.id.substr(0, prelen) == 'tv.c_') var newstate = 'c';
	else var newstate = null; /* This should never occur. Check for bugs. */
	var nodeid = li.id.substr(prelen);
	return [newstate, nodeid];
}

function tv_expcollnodehandler() {
	for (var li = this.parentNode; li; li = this.parentNode) {
		if (li.nodeType == 1 && li.tagName.toUpperCase() == 'LI') {
			var nodestate = tv_getnodestatefortoggle(li);
			break;
		}
	}
	if (nodestate) {
		/* Persist the state prior to ui changes so that the call to
		 * tv_updatelinks() in tv_xmlloadhandler() can rely on the state
		 * being up-to-date.
		 */
		tv_persistnode(nodestate[1], nodestate[0],
		  tv_isnodeserverexpanded(nodestate[1]) ? 'e' : 'c');
		/* XREF-1: This call represents logic shared with server-side
		 * code; for details refer to README.treeview_javascript under
		 * the XREF-1 heading.
		 */
		tv_persistselectedancestors(li);
		if (nodestate && nodestate[0]) {
			tv_expcollnodes([[li,nodestate[1]]],nodestate[0]=='e');
		}
	}
}

function tv_resync() {
	var ul = document.getElementById(tv_getdata('TV_ROOT_ID'));
	if (ul) {
		var pseudo_li = ul.parentNode;
		pseudo_li.removeChild(ul);
		tv_expcollnodes([[pseudo_li, tv_getdata('TV_ROOT_ID')]], true,
		  true);
	}
}

function tv_persistselectedancestors(li) {
	while (li.parentNode && li.parentNode.nodeType == 1 &&
	  li.parentNode.tagName.toUpperCase() == 'UL') {
		li = li.parentNode.parentNode;
		if (li.nodeType == 1 && li.tagName.toUpperCase() == 'LI') {
			if (li.className==tv_cd['classname_selectedancestor']) {
				var nodestate = tv_getnodestatefortoggle(li);
				nodestate[0] = nodestate[0] == 'e' ? 'c' : 'e';
				tv_persistnode(nodestate[1], nodestate[0],
				 tv_isnodeserverexpanded(nodestate[1])?'e':'c');
			}
		} else	break;
	}
}

function tv_fallbackhandler(li_arr, linkmsg, diagnosticmsg, pre_text,
  offertvresync) {
	if (li_arr.length > 0) {
		if (!linkmsg) linkmsg = tv_msg['defaultdiaglinktext'];
		function popup_diagnostic() {
			var popupdivouter = document.createElement('div');
			var popupdivmid = document.createElement('div');
			var popupdiv = document.createElement('div');
			popupdivouter.appendChild(popupdivmid);
			popupdivmid.appendChild(popupdiv);
			function closepopup() {
				document.body.removeChild(popupdivouter);
			}
			var btn = document.createElement('input');
			btn.setAttribute('type', 'button');
			btn.value = 'Close';
			btn.onclick = closepopup;
			popupdiv.appendChild(btn);
			popupdiv.appendChild(document.createElement('br'));
			popupdiv.appendChild(document.createTextNode(
			  tv_msg['d1']));
			var reportlink = document.createElement('a');
			reportlink.href = tv_msg['reporturl'];
			reportlink.title = tv_msg['reportlinktooltip'];
			var text = document.createTextNode(tv_msg['d2']);
			reportlink.appendChild(text);
			popupdiv.appendChild(reportlink);
			popupdiv.appendChild(document.createTextNode(
			  tv_msg['d3']));
			popupdiv.appendChild(document.createElement('br'));
			var txtel = document.createTextNode(diagnosticmsg);
			popupdiv.appendChild(txtel);
			if (pre_text !== null) {
				var pre = document.createElement('pre');
				pre.appendChild(document.createTextNode(
				  pre_text));
				popupdiv.appendChild(pre);
			}
			popupdivouter.className = 'popupouter';
			popupdivmid.className = 'popupmid';
			popupdiv.className = 'popup';
			document.body.insertBefore(popupdivouter,
			  document.body.firstChild);
		}
		var diagnosticlink=document.createElement(offertvresync ?
		  'span' : 'a');
		if (!offertvresync) {
			 /* diagnostic shows top-of-page */
			diagnosticlink.href = '#globalWrapper';
			diagnosticlink.title = tv_msg['diaglinktooltip'];
		}
		diagnosticlink.className = 'diagnosticlink';
		var text = document.createTextNode(linkmsg);
		diagnosticlink.appendChild(text);

		var reloadlink = document.createElement('a');
		var k = (offertvresync ? 'tv' : 'full') + 'reloadlinktooltip';
		reloadlink.title = tv_msg[k];
		k = 'try' + (offertvresync ? 'tv' : 'fullpage') + 'reload';
		var text = document.createTextNode(tv_msg[k]);
		reloadlink.appendChild(text);
		var rlink = reloadlink;
		var dlink = diagnosticlink;
		for (var i = 0; i < li_arr.length; i++) {
			if (li_arr[i][3] == true) continue;
			var iconanchor = li_arr[i][0].firstChild;
			var ulload = tv_get_ul_in_li(li_arr[i][0]);
			if (!ulload) continue;
			ulload.className = 'error';
			var liload = ulload.lastChild;
			var txtload = liload ? liload.lastChild : null;
			if (txtload && iconanchor) {
				if (i) {
					rlink = reloadlink.cloneNode(true);
					dlink = diagnosticlink.cloneNode(true);
				}
				if (offertvresync) {
					rlink.href = 'javascript:tv_resync()';
				} else {
					rlink.href = iconanchor.fallbackhref;
					/*not cloned*/
					dlink.onclick = popup_diagnostic;
				}
				liload.replaceChild(dlink, txtload);
				liload.appendChild(rlink);
			}
		}
	}
}

function tv_getdata(key, appendage) {
	/* avoid 'null' being appended */
	var el = document.getElementById(tv_hi[key] + (appendage?appendage:''));
	return el ? el.value : '';
}

function tv_getxmlurl(nodeid, tvstate_str) {
	if (tv_xmlurl == -1) tv_xmlurl = tv_getdata('xmlurl');
	/* This client-side code rebuilds the state parameter in links after
	 * receiving the branch.  Specifying skiplinkstaterebuild avoids some
	 * title object rebuilds on the server.  This slightly speeds up the
	 * PHP code.
	 */
	return tv_xmlurl + nodeid + '&skiplinkstaterebuild=1&tvstate=' +
	  tvstate_str;
}

function tv_setnodeimg(li, imgtype, nodeid) {
	if (imgtype == 'L') { /* capitalised to differentiate from numeral 1 */
		var img = li.firstChild;
		img.alt = tv_getdata('tv_txtleafimg');
	} else {
		var img = li.firstChild.firstChild;
		var expanded = (imgtype == 'e');
		img.alt = tv_getdata(expanded ?
		  'tv_txtcollapseimg' : 'tv_txtexpandimg');
		img.title = tv_getdata(expanded ?
		  'tv_tt_collapsenode' : 'tv_tt_expandnode');
	}
	if (!img.alt) img.alt = '[X]'; /* The fallback's fallback */
	if (imgtype == 'L') {
		var custimgel = document.getElementById(
		  tv_hi['customleaficon_prefix'] + nodeid);
		img.title = '';
	} else {
		var custimgel = document.getElementById(tv_hi[imgtype == 'c' ?
		 'customcollapseicon_prefix':'customexpandicon_prefix']+nodeid);
	}
	if (custimgel && custimgel.value) {
		var imgsrc = custimgel.value;
	} else {
		if (imgtype == 'L') {
			var imgsrc=tv_getdata('imgdir')+'/'+tv_im['leaf_node'];
			img.title = tv_getdata('tv_tt_leafnode');
		} else {
			var imgsrc = tv_getdata('imgdir') + '/' +
			 (expanded?tv_im['collapse_node']:tv_im['expand_node']);
		}
	}
	img.src = imgsrc;
}

function tv_get_ul_in_li(li) {
	var ul = li.firstChild;
	/* Preceded by icon anchor/span */
	while (ul = ul.nextSibling) {
		if (ul.nodeType == 1 && ul.tagName.toUpperCase() == 'UL') break;
	}
	return ul;
}

function tv_statearr2dtoarr(state_a2d) {
	var state_a = new Array();
	for (var i = 0; i < arr2d.length; i++)
		state_a[i] = state_a2d[i].join['_'];
	return state_a;
}

function tv_statearrtostr(state_a) {
	return state_a.join(',');
}

/* Expand or collapse a batch of nodes - can be a batch of one. */
function tv_expcollnodes(li_arr,expand,ispseudoli,newstate_str,postponed_arr2d){
	var nodeids = '';
	if (expand) {
		var i = 0;
		while (i < li_arr.length) {
			var ul = tv_get_ul_in_li(li_arr[i][0]);
			if (ul && ul.className == 'error') {
				/* Re-expanding a collapsed error message 
				 * invokes a retry. */
				ul.parentNode.removeChild(ul);
			} else if (ul) {
				/* Branch already exists or is loading. */
				tv_expcollnode_def(li_arr[i][0], li_arr[i][1],
				  expand, ul, ispseudoli);
				li_arr.splice(i, 1);
			} else {
				if (nodeids) nodeids += ',';
				nodeids += li_arr[i][1];
				i++;
			}
		}
	}
	if (expand && li_arr.length > 0) {
		var ulload, liload, txtload;
		if ((ulload = document.createElement('ul')) &&
		  (liload = document.createElement('li')) &&
		  (txtload = document.createTextNode(tv_msg['loading']))) {
			liload.appendChild(txtload);
			ulload.appendChild(liload);
			ulload.className = 'loading';
			li_arr[0][0].appendChild(ulload);
			li_arr[0][2] = true;
			tv_expcollnode_def(li_arr[0][0], li_arr[0][1], expand,
			  ulload, ispseudoli);
		} else li_arr[0][2] = false;
		li_arr[0][3] = false;
		for (var i = 1; i < li_arr.length; i++) {
			var cloned = li_arr[0][2] ? ulload.cloneNode(true):null;
			if (cloned) {
				li_arr[i][0].appendChild(cloned);
				li_arr[i][2] = true;
				tv_expcollnode_def(li_arr[i][0], li_arr[i][1],
				  expand, ulload, ispseudoli);
			} else	li_arr[i][2] = false;
			li_arr[i][3] = false
		}
		var cookiesenabled = tv_arecookiesenabled();
		var xmlurl = tv_getxmlurl(nodeids, newstate_str ?
		  newstate_str : tv_tvstate);
		var req = null;
		try {
			if (window.XMLHttpRequest) {
				var reqtype = 'moz';
				req = new XMLHttpRequest();
			} else if (window.ActiveXObject) {
				var reqtype = 'ie';
				req = new ActiveXObject('Microsoft.XMLHTTP');
			}
		} catch (e) {
			req = null;
			var exceptionmsg = e.message;
		}

		if (!req) {
			tv_fallbackhandler(li_arr, tv_msg['nolod'], reqtype ?
			  tv_msg['nolod1'] + tv_msg[reqtype == 'moz' ?
			    'mozobjs' : 'msobjs'] + tv_msg['nolod2'] :
			  tv_msg['nolod3'], exceptionmsg ? exceptionmsg : null);
		} else {
			try {
				req.open('GET', xmlurl, true);
				if (req.overrideMimeType) {
					req.overrideMimeType('text/xml');
				}
				req.onreadystatechange = function () {
					try {
						if (req.readyState == 4) {
							tv_xmlloadhandler(req,
							  li_arr,
							  cookiesenabled,
							  postponed_arr2d);
						}
					} catch (e) {
						tv_fallbackhandler(li_arr,
						  tv_msg['dlorproc'],
						  tv_msg['dlorproc1'],
						  e.message);
					}
				}
				req.send(null);
			} catch (e) {
				tv_fallbackhandler(li_arr, tv_msg['setup'],
				  tv_msg['setup1'] + tv_msg[reqtype == 'moz' ?
				    'mozobj' : 'msobj'] + tv_msg['setup2'],
				  e.message);
			}
		}
	}
	for (var i = 0; i < li_arr.length; i++) {
		if (!li_arr[i][2]) {
			tv_expcollnode_def(li_arr[i][0], li_arr[i][1], expand,
			  null, ispseudoli);
		}
	}
}

/* (Almost unconditionally) visibly expands or collapses a node and sets its id
 * accordingly.
 */
function tv_expcollnode_def(li, nodeid, expand, ul, skipimg) {
	if (!ul) ul = tv_get_ul_in_li(li);
	if (ul) ul.style.display = expand ? 'block' : 'none';
	li.id = 'tv.' + (expand ? 'e' : 'c') + '_' + nodeid;
	if (!skipimg) tv_setnodeimg(li, expand ? 'e' : 'c', nodeid);
}

/* Adds any new nodes that the server indicates are default-expanded to the
 * array consulted by tv_isnodeserverexpanded().
 */
function tv_addexpnodes(XML) {
	var f = XML.getElementsByTagName('form');
	if (f.length > 0) {
		var h = f.item(0).getElementsByTagName('input');
		var expStr = '';
		for (var i = 0; i < h.length; i++) {
			if (h.item(i).getAttribute('id') == 'expandednodes') {
				expStr = h.item(i).getAttribute('value');
			}
		}
		if (expStr) {
			var expArr = expStr.split(',');
			for (var i = 0; i < expArr.length; i++) {
				if (!tv_in_array(expArr[i], tv_expnodes)) {
					tv_expnodes[tv_expnodes.length] =
					  expArr[i];
				}
			}
		}
	}
}

function tv_xmlloadhandler(req, li_arr, cookiesenabled, postponed_arr2d) {
	/* Firefox tripped on this for 'Work Offline' */
	try { var status = req.status; }
	catch (e) { var status = tv_msg['inaccessible']; }
	if (status != 200 && status != 304) {
		/* Firefox tripped on this for 'Work Offline' */
		try { var statusText = req.statusText;
		} catch (e) {var statusText = tv_msg['inaccessible'];}
		/* This wasn't supported by Microsoft's object */
		try { var hdrs = req.getAllResponseHeaders();
		} catch (e) { var hdrs = tv_msg['inaccessible']; }
		tv_fallbackhandler(li_arr, tv_msg['conn'], tv_msg['conn1'] +
		  status + (statusText ? ': "'+statusText+'"' : '') + ".  " +
		  (hdrs ? tv_msg['conn2'] : ''), hdrs ? hdrs : null);
		return;
	}
	var d = req.responseXML.getElementsByTagName('div');
	if (d.length <= 0) {
		/* probable xml error */
		tv_fallbackhandler(li_arr, tv_msg['xmlproc'],
		  tv_msg['xmlproc1'], req.responseText);
		return;
	}

	/* tv_addexpnodes() retrieves 'expandednodes' data ... */
	tv_addexpnodes(req.responseXML);
	/* ...that tv_isnodeserverexpanded(), called on behalf of
	 * tv_persistnodes(), might be relying on. */
	if (postponed_arr2d) tv_persistnodes(postponed_arr2d);
	/* By this point, all persists have occurred.  The new state is
	 * definitive. */
	state_arr = tv_getpseudocookietvstate();

	for (var j = 0; j < d.length; j++) {
		for (var i = 0; i < li_arr.length; i++) {
			if (li_arr[i][1] == d.item(j).getAttribute('id')) {
				var li = li_arr[i][0];
				var uls = d.item(j).getElementsByTagName('ul');
				if (uls.length <= 0) {
					/* Probable deletion on server of node
					 * being expanded or of its child nodes.
					 */
					var h = d.item(j).getElementsByTagName(
					  'input');
					var k = 'missing' + (h.length ? 'node' :
					  'children');
					tv_fallbackhandler([li_arr[i]],
					  tv_msg[k], null, null, true);
					li_arr[i][3] = true;
				} else if (uls.item(0).childNodes.length) {
					var branch = tv_xmltohtml(uls.item(0));
					tv_setup_branch(branch);
					var ul = tv_get_ul_in_li(li);
					if (ul) {
						while (ul.firstChild) ul.
						  removeChild(ul.firstChild);
						ul.parentNode.replaceChild(
						  branch, ul);
					} else	li.appendChild(branch);
					/* Collapse might have been clicked
					 * whilst the node was loading - make
					 * sure that it is re-hidden. */
					if (tv_getnodestate(li)[0] == 'c') {
						tv_expcollnode_def(li,
						  li_arr[i][1], false);
					}
					var alist = branch.
					  getElementsByTagName('a');
					if (alist) {
						tv_updatelinks(tv_tvstate,
						  state_arr, alist,
						  cookiesenabled);
					}
					li_arr[i][3] = true;
				}
			}
		}
	}
	for (var i = 0; i < li_arr.length; i++) {
		if (!li_arr[i][3]) {
			tv_fallbackhandler([li_arr[i]], tv_msg['unknown'],
			  tv_msg['unknown1'], req.responseText);
			li_arr[i][3] = true;
		}
	}
}

/* A straight .clone(true) results in non-rendering of images on some (all?)
 * browsers - this function does a more explicit clone and seems to avoid that
 * problem.
 * NOTE: ensure that the initial element doesn't have subsequent siblings.
 */
function tv_xmltohtml(root) {
	while (root) if (root.nodeType == 1) {
		var newroot = document.createElement(root.tagName);
		for (var i = 0; i < root.attributes.length; i++) {
			/* IE doesn't respond to the 'class' property but all
			 * (tested) browsers recognise 'className'. */
			if (root.attributes.item(i).nodeName == 'class') {
				newroot.className =
				  root.attributes.item(i).nodeValue;
			} else {
				newroot.setAttribute(
				  root.attributes.item(i).nodeName,
				  root.attributes.item(i).nodeValue);
			}
		}
		for (var i = 0; i < root.childNodes.length; i++) {
			if (root.childNodes.item(i).nodeType == 1) {
				newroot.appendChild(tv_xmltohtml(root.
				  childNodes.item(i)));
			} else if (root.childNodes.item(i).nodeType == 3) {
				newroot.appendChild(
				  document.createTextNode(root.childNodes.
				    item(i).nodeValue));
			}
		}
		break;
	} else root = root.nextSibling;
	return newroot;
}

/* Dummy function for hrefs - the real function is in the onclick: this prevents
 * page scroll on click and provides a friendly url for expand/collapse icons.
 */
function tv_nodetoggle(nodeid) {}

function tv_setup_branch(ul) {
	var li = ul.firstChild;
	if (li) do {
		/* 1 == ELEMENT_NODE */
		if (li.nodeType == 1 && li.tagName.toUpperCase() == 'LI') {
			var nodeid = li.id.substr('tv.e_'.length);
			var icon = li.firstChild;
			if (icon.nodeType == 1 &&
			  icon.tagName.toUpperCase() == 'A') {
				icon.fallbackhref = icon.href;
				icon.href = "javascript:tv_nodetoggle('" +
				  nodeid + "')";
				icon.onclick = tv_expcollnodehandler;
			}
			var ul2 = li.firstChild;
			while (ul2 = ul2.nextSibling) {
				if (ul2.nodeType == 1 &&
				  ul2.tagName.toUpperCase() == 'UL') {
					tv_setup_branch(ul2);
				}
			}
		}
	} while (li = li.nextSibling);
}
