///////////////////////////////
//       NODE TRAVERSAL      //
// ignores white space nodes //
//     *stolen from MDC*     //
///////////////////////////////
function is_all_ws(nod) {
  return !(/[^\t\n\r ]/.test(nod.data));
}

/**
 * Determine if a node should be ignored by the iterator functions.
 *
 * @param nod  An object implementing the DOM1 |Node| interface.
 * @return     true if the node is:
 *                1) A |Text| node that is all whitespace
 *                2) A |Comment| node
 *             and otherwise false.
 */

function is_ignorable(nod) {
  return (nod.nodeType == 8) || // A comment node
         ((nod.nodeType == 3) && is_all_ws(nod)); // a text node, all ws
}

/**
 * Version of |previousSibling| that skips nodes that are entirely
 * whitespace or comments.  (Normally |previousSibling| is a property
 * of all DOM nodes that gives the sibling node, the node that is
 * a child of the same parent, that occurs immediately before the
 * reference node.)
 *
 * @param sib  The reference node.
 * @return     Either:
 *               1) The closest previous sibling to |sib| that is not
 *                  ignorable according to |is_ignorable|, or
 *               2) null if no such node exists.
 */
function node_before(sib) {
  while ((sib = sib.previousSibling)) {
    if (!is_ignorable(sib)) return sib;
  }
  return null;
}

/**
 * Version of |nextSibling| that skips nodes that are entirely
 * whitespace or comments.
 *
 * @param sib  The reference node.
 * @return     Either:
 *               1) The closest next sibling to |sib| that is not
 *                  ignorable according to |is_ignorable|, or
 *               2) null if no such node exists.
 */
function node_after(sib) {
  while ((sib = sib.nextSibling)) {
    if (!is_ignorable(sib)) return sib;
  }
  return null;
}

/**
 * Version of |lastChild| that skips nodes that are entirely
 * whitespace or comments.  (Normally |lastChild| is a property
 * of all DOM nodes that gives the last of the nodes contained
 * directly in the reference node.)
 *
 * @param sib  The reference node.
 * @return     Either:
 *               1) The last child of |sib| that is not
 *                  ignorable according to |is_ignorable|, or
 *               2) null if no such node exists.
 */
function last_child(par) {
  var res = par.lastChild;
  while (res) {
    if (!is_ignorable(res)) return res;
    res = res.previousSibling;
  }
  return null;
}

/**
 * Version of |firstChild| that skips nodes that are entirely
 * whitespace and comments.
 *
 * @param sib  The reference node.
 * @return     Either:
 *               1) The first child of |sib| that is not
 *                  ignorable according to |is_ignorable|, or
 *               2) null if no such node exists.
 */
function first_child(par) {
  var res = par.firstChild;
  while (res) {
    if (!is_ignorable(res)) return res;
    res = res.nextSibling;
  }
  return null;
}

function is_first_child(sib) {
	return !node_before(sib);
}
function is_last_child(sib) {
	return !node_after(sib);
}

function num_before(sib) {
	var count = 0;
	while (sib = node_before(sib)) {
		count++;
	}
	return count;
}
function num_after(sib) {
	var count = 0;
	while (sib = node_after(sib)) {
		count++;
	}
	return count;
}

// THIS FROM QUIRKSMODE
// http://www.quirksmode.org/dom/importxml.html

function importXML(xmlURL, onloadFunc) {
	if (document.implementation && document.implementation.createDocument) {
		document.implementation.preserveWhiteSpace = false;
		var xmlDoc = document.implementation.createDocument("", "", null);
		xmlDoc.load(xmlURL);
		xmlDoc.onload = onloadFunc;
		return xmlDoc;
	} else {
		return false;
	}
}

/**
 *	For use only with the most simple of XML trees
 *	@param	par			Element		the parent item
 *	@param	childTag	String		the name of the child tags you want to get
 *	@param	posNum		int			which element you want to get (1-indexed). 0 gets them all. 1 is default.
 *	@return				mixed		if posNum is left at default or specified as
 *									any non-zero positive integer, it returns
 *									the string value of the child tag. otherwise
 *									it's an array of the values of all the child
 *									tags. Returns empty string if no tags match.
 *
 *	EG:
 *	<a>
 *		<b>first</b>
 *		<c>second</c>
 *		<b>third</b>
 *		<d />
 *	</a>
 *	var a = getElement('a');	// pseudo code!
 *	getChildValue(a, 'b');		// "first"
 *	getChildValue(a, 'c');		// "second"
 *	getChildValue(a, 'd');		// ""
 *	getChildValue(a, 'notag');	// ""
 *	getChildValue(a, 'b', 2);	// "third"
 *	getChildValue(a, 'b', 0);	// ["first", "third"]
 *
 *	@author	nickf
 *	@date	2007-09-13
 */
function getChildValue(par, childTag, posNum) {
	if (posNum == undefined) posNum = 1;
	var curr = first_child(par);
	var ret = [];
	var count = 0;
	do {
		if (curr.firstChild && curr.nodeName == childTag) {
			if (++count == posNum || posNum == 0) {
				ret.push(curr.firstChild.nodeValue);
				if (count == posNum) break;
			}
		}
	} while (curr = node_after(curr));

	return (posNum == 0 ? ret : (ret[0] == undefined ? '' : ret[0]));
}
function hasChild(par, childTag) {
	var curr = first_child(par);
	do {
		if (curr.nodeName == childTag) return true;
	} while (curr = node_after(curr));
	return false;
}