﻿
/*
This class acts as a wrapper to the IE MSXML and Mozilla DOMParser to manipulate an XML document. 
It is cross browser compliant and has been tested with IE6,IE7, IE8 and Firefox 3.0.8. 
See the directions below of how to use it.

Constructor:
var xml = new XMLWrapper("<myxml></myxml>") 
    
Functions to Return data:
* GetTextValue (xpath)  - Get the text value of the node. Returns null if it's not found
* String() - returns the string value of the XML
* GetNodeNamesOfChildren (xpathToParent) - returns an array of the names of the child nodes of the xpath parameter.
* GetArrayWithTextValues (xPathToParent) - returns an array of element values of the nodes returned from the xpath parameter.
* CountNodes = function (xpath)  - count the number of nodes returned

Functions to iterate over elements
* NodeIterator (xpath) - Sets up a node iterater of the nodes returned in the xpath parameter.
* NodeIterator_NextNode -  returns the next node from the elements setup in the function NodeIterator
* NodeIterator_Clear - when you're done, clear the iterator
  
An example of how to use this function:
    
var xml = new XMLWrapper("<toplevel><a>hello</a><b>dolly</b><toplevel>")
xml.NodeIterator("/toplevel");
var node = xml.NodeIterator_NextNode())
while (node != null) {
..do something with the node 
node = xml.NodeIterator_NextNode()
}

Functions to Manipulate the XML Docment:
* DeleteNode (xpath) - Delete a specific Node and all it's children.
* SelectFirstNode (xpath) - Select the first node in the XPATH. A node is returned.
* DeleteChildren (xpath) - Delete the children of the first node of the xpath parameter
* AppendNode = (xpath, nodename, value) - Add child node under the position of the xpath.
* SetXmlNodeAttribute(xpath, attrName, attrValue, shouldRemoveIfValueIsEmpty) - Add a new attribute
at the first node of the xpath.  
* ReplaceElementValue (xpath, newValue)  
*/

function XMLWrapper(xmlString) {
	//TODO! Parse the XML to make sure it's valid (w3c only)
	//TODO! Does IE8 support msxml2 3.0? Sniff for highest version available (ie only)

	var myDocument;
	if (window.DOMParser) {
		// This browser appears to support DOMParser (Mozilla)
		var parser = new DOMParser();
		myDocument = parser.parseFromString(xmlString, "text/xml");

		//      //check if there are errors in parsing the document (NOTE: There is a bug in Mozilla which
		//      //does not produce an error if the xml is invalid.    
		//      var roottag = doc.documentElement;
		//      if ((roottag.tagName == "parserError") || (roottag.namespaceURI == "http://www.mozilla.org/newlayout/xml/parsererror.xml")){
		//            alert("Function: GetReferenceToXmlObject.  Xml is not valid: " + xmlString);
		//      }
	} else if (window.ActiveXObject) {
		// Internet Explorer, create a new XML document using ActiveX 
		// and use loadXML as a DOM parser. 
		myDocument = new ActiveXObject("MSXML2.DOMDocument.3.0");
		myDocument.async = false;
		myDocument.loadXML(xmlString);
		myDocument.setProperty("SelectionLanguage", "XPath");
	} else {
		//Not supported
		alert("The XMLWrapper object in XMLWrapper.js does not suupport this browser.")
	}
	//The xmlDoc is used in scoped to the XMLWrapper object.   
	this.xmlDoc = myDocument
}

//Get the value of a single node.
XMLWrapper.prototype.GetTextValue = function(xpath) {
	var returnStr = ""
	var node = this.SelectFirstNode(xpath)
	if (node != null) {
		if (!window.DOMParser) {
			//The ie way
			returnStr = node.text
		} else {
			//The W3C way
			if (node.textContent) {
				returnStr = node.textContent
			}
		}
	}
	return returnStr
}

XMLWrapper.prototype.ReplaceElementValue = function(xpath, newValue) {
	var node = this.SelectFirstNode(xpath)
	if (node != null) {
		if (!window.DOMParser) {
			node.text = newValue
		} else {
			node.textContent = newValue
		}
	}
}

XMLWrapper.prototype.SelectFirstNode = function(xpath) {
	var returnNode = null
	if (window.DOMParser) {
		//The W3C way

		var xpe = new XPathEvaluator();
		var nsResolver = xpe.createNSResolver(this.xmlDoc.ownerDocument == null ? this.xmlDoc.documentElement : this.xmlDoc.ownerDocument.documentElement);
		var results = xpe.evaluate(xpath, this.xmlDoc, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
		if (results.singleNodeValue) {
			returnNode = results.singleNodeValue
		}
	}
	else {
		//The ie way
		var node
		node = this.xmlDoc.selectSingleNode(xpath)
		if (node != null) {
			returnNode = node
		}
	}
	return returnNode;
}

XMLWrapper.prototype.DeleteNode = function(xpath) {
	var node = this.SelectFirstNode(xpath)
	if (node != null) {
		//can this be used for both ie and w3c?
		node.parentNode.removeChild(node);

		//        //**** TEST THIS FOR IE!!
		//        if (node) {
		//            // w3c way
		//            node.parentNode.removeChild(node);
		//        } else {
		//            //ie way
		//            var nodeToDelete = xmlDoc.selectNodes(xPathString);
		//            nodeToDelete.removeAll;
		//        }
	}

	//This does not return anything
	return null
}
XMLWrapper.prototype.DeleteChildren = function(xpath) {
	var node = this.SelectFirstNode(xpath)
	//**** TEST THIS FOR IE!!
	if (node) {
		// w3c way
		while (node.hasChildNodes()) {
			node.removeChild(node.firstChild);
		}
	} else {
		//ie way
		var nodeToDelete = this.xmlDoc.selectNodes(xpath);
		nodeToDelete.removeAll;
	}

	//This does not return anything
	return null;
}
XMLWrapper.prototype.AppendNode = function(xpath, nodename, value) {
	var node = this.SelectFirstNode(xpath)

	//this works for IE and W3C
	var newElem = this.xmlDoc.createElement(nodename);
	if (value != null && value != '') {
		var newText = this.xmlDoc.createTextNode(value);
		newElem.appendChild(newText);
	}

	node.appendChild(newElem);

	//return the new element so it can be further modified
	return newElem;
}

XMLWrapper.prototype.CountNodes = function(xpath) {
	var nodes = this.xmlDoc.selectNodes(xpath);
	return nodes.length;
}


XMLWrapper.prototype.SetXmlNodeAttribute = function(xpath, attrName, attrValue, shouldRemoveIfValueIsEmpty) {
	var node = this.SelectFirstNode(xpath)
	// this works for ie and w3c    
	if (attrValue == "" && shouldRemoveIfValueIsEmpty == true) {
		node.removeAttribute(attrName)
	} else {
		var newAttr = this.xmlDoc.createAttribute(attrName);
		newAttr.value = attrValue;
		node.setAttributeNode(newAttr)
	}
	// nothing to return    
	return null;
}
XMLWrapper.prototype.String = function() {
	var xmlString
	if (!window.DOMParser) {
		xmlString = this.xmlDoc.xml
	} else {
		var serializer = new XMLSerializer();
		var xmlString = serializer.serializeToString(this.xmlDoc);
	}
	return xmlString;
}
//returns the names of the nodes in an array
XMLWrapper.prototype.GetNodeNamesOfChildren = function(xpath) {
	var names = new Array();
	if (!window.DOMParser) {
		//ie only       
		nodesToProcess = this.xmlDoc.selectSingleNode(xpath);
		var pos = 0
		while (pos < nodesToProcess.childNodes.length) {
			names.push(nodesToProcess.childNodes[pos].nodeName);
			pos = pos + 1;
		}
	} else {
		//w3c
		// add something to the xpath that indicates to "select every child element"
		// NOTE: This COULD BE WRITTEN MUCH NICER. I couldn't find a method to iterate to the child element.
		xpath = xpath + "/*"
		var xpe = new XPathEvaluator();
		var nsResolver = xpe.createNSResolver(this.xmlDoc.ownerDocument == null ? this.xmlDoc.documentElement : this.xmlDoc.ownerDocument.documentElement);
		var results = xpe.evaluate(xpath, this.xmlDoc, nsResolver, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
		var iter = results.iterateNext();
		while (iter) {
			names.push(iter.nodeName)
			iter = results.iterateNext();
		}
	}
	return names;
}

XMLWrapper.prototype.GetArrayWithTextValues = function(xPathToParent) {
	var returnArray = new Array
	// create a node iterator
	this.NodeIterator(xPathToParent);

	// traverse all the nodes
	var node = this.NodeIterator_NextNode()
	while (node != null) {
		if (node.text) {
			//The ie way
			returnArray.push(node.text)
		} else {
			//The W3C way
			returnArray.push(node.textContent)
		}
		node = this.NodeIterator_NextNode()
	}

	//    if (oNode.hasChildNodes) {
	//        var valueNodes = oNode.childNodes
	//        for (var i=0; i < valueNodes.length; i++) {
	//              returnArray.push(valueNodes.item(i).text)
	//        }
	//     }     
	return returnArray;

}

XMLWrapper.prototype.CreateElement = function(elementName, value, attrName, attrValues) {

	var newElem = this.xmlDoc.createElement(elementName);
	if (value != null && value != '') {
		var newText = this.xmlDoc.createTextNode(value);
		newElem.appendChild(newText);
	}

	if (attrName != null && attrValues != null) {
		if (attrName.length = attrValues.length) {
			for (var i = 0; i < attrName.length; i++) {
				var newAttr = this.xmlDoc.createAttribute(attrName[i]);
				newAttr.value = attrValues[i];
				newElem.setAttributeNode(newAttr)
			}
		}
	}

	//return the element, but it still needs to be appended somewhere on the document.
	return newElem;
}
XMLWrapper.prototype.AppendNodeToXmlDoc = function(xpathOfParent, node) {
	var parentNode = this.SelectFirstNode(xpathOfParent)
	parentNode.appendChild(node)

}

XMLWrapper.prototype.NodeIterator = function(xpath) {
	//this is scoped only through the "ReadOnlyNodeIterator" class.

	if (!window.DOMParser) {
		//ie only       
		this.nodesToProcess = this.xmlDoc.selectNodes(xpath);
		//return this.NextNode();
	} else {
		//w3c
		// add something to the xpath that indicates to "select every child element"
		// NOTE: This COULD BE WRITTEN MUCH NICER. I couldn't find a method to iterate to the child element.
		var xpe = new XPathEvaluator();
		var nsResolver = xpe.createNSResolver(this.xmlDoc.ownerDocument == null ? this.xmlDoc.documentElement : this.xmlDoc.ownerDocument.documentElement);
		this.nodesToProcess = xpe.evaluate(xpath, this.xmlDoc, nsResolver, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
	}

	// return empty string if it worked.
	return "";
}
XMLWrapper.prototype.NodeIterator_NextNode = function() {
	var node = null;
	try {
		if (!window.DOMParser) {
			node = this.nodesToProcess.nextNode;
		} else {
			node = this.nodesToProcess.iterateNext();
		}
	}
	catch (e) {
		//An attempt was made to use an object that is not, or is no longer, usable"
		// code: "11" nsresult: "0x8053000b (NS_ERROR_DOM_INVALID_STATE_ERR
		// Not sure how to fix it at this time.  FF only, IE678 are okay.
		//alert(e);
	}
	return node;
}
XMLWrapper.prototype.NodeIterator_Clear = function() {
	this.nodesToProcess = "";
	return null;
}
