/* Returns next element with the given tag name, beginning at el
 * and searching the tree upward.
 */
function getAncestorElementByTagName(/* DomNode */el, /* string */nodeName) {
	if (el.nodeName.toLowerCase() == nodeName.toLowerCase()) return el;
	else if (el.parentNode) return getAncestorElementByTagName(el.parentNode, nodeName);
	else return null;
}

/* Returns the flattened tree (Array) of the given node.
 * An optional filterFunc(Node) determines whether to return an element or not.
 * Another optional recurseFunc(Node) determines whether to recurse into an element or not.
 */
function getFlatTree(/* DomNode */node, /* function(DomNode) */filterFunc/* = null */, /* function(DomNode) */recurseFunc/* = null */) {
	var result;
	if (!filterFunc || filterFunc(node)) result = [node];
	for (var i=0; i<node.childNodes.length; i++) {
		if (!recurseFunc || recurseFunc(node.childNodes[i])) {
			var arr = getFlatTree(node.childNodes[i], filterFunc, recurseFunc, true); // allow no return value
			if (arr && result) result = result.concat(arr);
			else if (arr) result = arr;
		}
	}
	if (!arguments[3] && !result) return []; // ensure at least empty array as final return value
	else return result;
}

/* Returns all elements with the given tag name, contained by el.
 */
function getDescendantElementsByTagName(/* DomNode */el, nodeName) {
	return getFlatTree(el, function(el) { return el.nodeName.toLowerCase() == nodeName.toLowerCase(); });
}

/* Returns all elements with the given attribute, contained by el.
 */
function getDescendantElementsByAttribute(/* DomNode */el, /* string */name, /* string */value) {
	return getFlatTree(el, function(el) { return el.nodeType == 1 && el.getAttribute(name) == value; });
}

/* Returns all elements with the class name, contained by el.
 */
function getDescendantElementsByClassName(/* DomNode */el, /* string */className) {
	return getFlatTree(el, function(el) { return el.className == className });
}

/* Returns the previous element, skipping all other nodes (eg. text)
 */
function getPreviousElement(/* DomNode */el) {
	if (el.previousSibling)
		if (el.previousSibling.nodeType == 1) return el.previousSibling;
		else return getPreviousElement(el.previousSibling);
	else return null;
}

/* Returns the next element, skipping all other nodes (eg. text)
 */
function getNextElement(/* DomNode */el) {
	if (el.nextSibling)
		if (el.nextSibling.nodeType == 1) return el.nextSibling;
		else return getNextElement(el.nextSibling);
	else return null;
}

/* Clones a node into the context of another DOM Document.
 * This is needed for i.e. Opera that throws an error when appending
 * nodes from another Document.
 */
function cloneDomNode(/* DomDocument */contextDoc, /* DomNode */node) {
	if (contextDoc == node.ownerDocument) return node.cloneNode(true);
	var DOM_ELEMENT_NODE = 1;
	var DOM_TEXT_NODE = 3;
	var DOM_CDATA_SECTION_NODE = 4;
	var DOM_COMMENT_NODE = 8;
	var DOM_DOCUMENT_FRAGMENT_NODE = 11;
	var result;
	switch (node.nodeType) {
		case DOM_ELEMENT_NODE:
		    if (contextDoc.createElementNS)
				result = contextDoc.createElementNS(node.namespaceURI, node.nodeName);
			else result = contextDoc.createElement(node.nodeName); // MSXML automatically recognizes prefix
			break;
		case DOM_TEXT_NODE:
			result = contextDoc.createTextNode(node.nodeValue);
			break;
		case DOM_CDATA_SECTION_NODE:
			result = contextDoc.createCDATASection(node.nodeValue);
			break;
		case DOM_COMMENT_NODE:
			result = contextDoc.createComment(node.nodeValue);
			break;
		case DOM_DOCUMENT_FRAGMENT_NODE:
			result = contextDoc.createDocumentFragment();
			break;
		default: return;
	}
	for (var i = 0; node.attributes && i < node.attributes.length; i++) {
	    if (result.setAttributeNS)
			result.setAttributeNS(node.attributes[i].namespaceURI, node.attributes[i].name, node.attributes[i].value);
		else result.setAttribute(node.attributes[i].name, node.attributes[i].value); // MSXML automatically recognizes prefix
	}
	var child;
	for (var i = 0; i < node.childNodes.length; i++) {
		child = cloneDomNode(contextDoc, node.childNodes[i]);
		if (child) result.appendChild(child);
	}
	return result;
}



/* Recursively evaluates all JavaScript nodes.
 * This needs to be done for dynamic generated code.
 * Warning: There is a chance that the code has already been evaluated,
 * but yet no browser is known to be doing this. In that case, prepare for trouble.
 */
function evalJsNodes(/* DomNode */node) {
	var jsNodes = getFlatTree	(
								 	node, 
									function(el) 
									{ 
										return el.nodeName.toLowerCase() == 'script' && (el.getAttribute('src') == null || el.getAttribute('src') == ''); 
									}
								);



	for (var i = 0; i < jsNodes.length; i++) eval(jsNodes[i].text);
	
}

/* Recursively removes all JavaScript nodes and returns a string containing the javascript code.
 * This code can be executed with eval().
 */
function stripJS(/* DomNode */node) {
	var js = '';
	var jsNodes = getFlatTree(node, function(el) { return el.nodeName.toLowerCase() == 'script' && (el.getAttribute('src') == null || el.getAttribute('src') == ''); });
	for (var i = 0; i < jsNodes.length; i++) {
		js += jsNodes[i].text + '\n';
		jsNodes[i].parentNode.removeChild(jsNodes[i]);
	}
	return js;
}

