Cross browser XML

by Jan Verhoeven, July 31, 2008

When Internet Explorer 5 came out, I did a lot of experiments with XML in the browser. Unfortunately my code dit not work in other browsers.

We are now 8 years later and all modern browsers support XML and XPath. Although some of them still do not support XSLT.

Two years ago I started working on a browser based XML editor because it seemed we needed one at work. Before I could finish the editor the Altova Authentic ActiveX control was used. So, I parked my personal project. As usual, when someone comes up with an IE only solution (as is the Authentic solution), it is just a matter of time before clients are not satisfied with being tied to IE for their XML editing. Therefore I proceeded with my work on the browser based XML editor. And now I am in the finishing stage.

Writing an XML editor is one thing. Writing an XML editor that works in IE, FF, Opera and Safari is a different piece of cake. But I got it working. I will not describe the XML editor itself here, but only the cross-browser bits.

I made a list of XML things that I needed.

  1. The ability to load an XML document from an xml string, as I wanted to editor to be completely ignorent of the outside world.
  2. The ability to serialize the XML document to an xml string that I could send back to the server using an Ajax-call.
  3. Using selectSingleNode and selectNodes with XPath expressions.

I searched the web for the required routines and embedded those into my own javascript classes.

Getting and setting xml

For all browsers except IE I added the following prototype functions.

Document.prototype.loadXML = function (s) {

   // parse the string to a new doc
   var doc2 = (new DOMParser()).parseFromString(s, "text/xml");

   // remove all initial children
   while (this.hasChildNodes())
	  this.removeChild(this.lastChild);

   // insert and import nodes
   for (var i = 0; i < doc2.childNodes.length; i++) {
	  this.appendChild(this.importNode(doc2.childNodes[i], true));
   }
};


Document.prototype.__defineGetter__("xml", function () {
   return (new XMLSerializer()).serializeToString(this);
});

Selecting nodes

Again for all non IE browsers I added 2 functions to Node.prototype.

Node.prototype.selectNodes = function (sXPath)
{
	var xpe = new XPathEvaluator();
	var nsResolver = xpe.createNSResolver(this.ownerDocument == null ?
		this.documentElement : this.ownerDocument.documentElement);

	var oResult = xpe.evaluate(sXPath, this, nsResolver, 
	XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);    
  
	var aNodes = new Array;

	if (oResult != null) {
		var oElement = oResult.iterateNext();
		while(oElement) {
			aNodes.push(oElement);
			oElement = oResult.iterateNext();
		}
	}

	return aNodes;
};

Node.prototype.selectSingleNode = function (sXPath)
{
	var xpe = new XPathEvaluator();
	var nsResolver = xpe.createNSResolver(this.ownerDocument == null ?
		this.documentElement : this.ownerDocument.documentElement);


	var oResult = xpe.evaluate(sXPath, this, nsResolver, 
	XPathResult.FIRST_ORDERED_NODE_TYPE, null);
	if (oResult != null)
	{
		return oResult.singleNodeValue;
	}
	else
	{
		return null;
	}              
};

Getting and setting node text

Node.prototype.__defineGetter__("text", function () {
	return(this.textContent);
}); // text

Node.prototype.__defineSetter__("text", function (txt) {
	this.textContent = txt;
}); 


Almost there

Now that I could access the XML documents in a uniform way I though I was ready with the cross browser stuff.

Getting and setting node attribute values is something that is done all the time in a XML editor. Because my XML editor is schema based and I wanted to add meta data to the schema using a namespace, I discovered a difference between Opera and the other browsers.

In my schema I have attributes like meta:label-nl. In all browsers except opera I can retrieve the value of that attribute with a simple node.getAttribute("meta:label-nl"). No so in opera.

In Opera you have to use the namespaceURI in combination with the local name of the attribute.

if (window.opera)
{
	var value = node.getAttribute("http://jansfreeware.com/meta", "label-nl");
}

And of course I have the corresponding xmlns:meta="http://jansfreeware.com/meta" declaration in my schema document element. You must use the full URI of the namespace and not the meta alias.