Xml Extras (WebFX)

Licensed under the Apache Software License 2.0

This component may be obtained, modified and distributed free of charge for personal as well as commercial use. Please see the license for the complete terms and exact wording.

(2006-05-29) Changed license to Apache Software License 2.0.

## Introduction

The usage of XML is everywhere. The usage of XML inside the browser has been around for a while but not until lately has it been possible to get this to work in a satisfactory way in more than one browser. A while back Mozilla added support for creating MS compatible XML classes scriptable from JavaScript.

This script creates a common interface for Mozilla and IE and also extends the Mozilla classes a little to make them behave even more like the Microsoft interfaces.

## XML in IE

In IE there are two useful ActiveX objects that are marked as safe for scripting. These objects use the MSXML XML parser and are created like this:

    var xmlDocument = new ActiveXObject("MsXml2.XmlDom");
    var xmlHttp = new ActiveXObject("MsXml2.XmlHttp");

The first object is an XML document that allows you to add new nodes to, using standard DOM1 Core methods. There are also a few extra methods provided by this object that allows you to load (get) an XML document from a file or from a string.

The second of these objects allows you to post an XML document to the server and the reply from the server is sent back to the XML HTTP request object. This allows you to post XML data to the server without leaving the current page.

## XML in Mozilla

Mozilla uses W3C compatible technologies whenever there is one. In the case of creating an XML document there happens to be a standard way to do this that was introduced in DOM Level 2. This uses the `createDocument` method of the `DOMImplementation` interface.

    document.implementation.createDocument(sNameSpaceUri, sQualifiedName, oDoctype);
    // in the simplest case it can be used as below
    document.implementation.createDocument("", "", null);

This creates an `XmlDocument` and one can use any DOM (level 1, 2 and 3) methods supported by Mozilla. Mozilla also decided to add the method `load` that allows you to load an XML document from a file (to be compatible with MS).

Mozilla also has support for XML http request objects. In this case Mozilla tried to implement as much as possible of the MS equivalent object but since this is not part of any W3C standard a special JS contructor is provided. Below is the code to create an XML http request object.

    new XMLHttpRequest();

## Implementation

The implementation creates two factory classes, called `XmlDocument` and `XmlHttp`. Both these classes have a static method (class method, shared method) called `create` that creates an instance object of the right type.

    var xmlDoc  = XmlDocument.create();
    var xmlHttp = XmlHttp.create();

### XmlHttp

    // XmlHttp factory
    function XmlHttp() {}

    XmlHttp.create = function () {
       try {
          if (window.XMLHttpRequest) {
             var req = new XMLHttpRequest();

             // some older versions of Moz did not support the readyState property
             // and the onreadystate event so we patch it!
             if (req.readyState == null) {
                req.readyState = 1;
                req.addEventListener("load", function () {
                   req.readyState = 4;
                   if (typeof req.onreadystatechange == "function")
                      req.onreadystatechange();
                }, false);
             }

             return req;
          }
          if (window.ActiveXObject) {
             return new ActiveXObject(getControlPrefix() + ".XmlHttp");
          }
       }
       catch (ex) {}
       // fell through
       throw new Error("Your browser does not support XmlHttp objects");
    };

If one removes the extra handling of the `onreadystate` event for Mozilla the static method `create` just checks the support of the browser and uses the supported mechanism to create an instance of the desired object. If the current version of Mozilla does not support the `readyState` property we know that it does not support the `onreadystate` event so we listen to the `onload` event and once that occurs we call the custom event handler (if provided).

### XmlDocument

    // XmlDocument factory
    function XmlDocument() {}

    XmlDocument.create = function () {
       try {
          // DOM2
          if (document.implementation && document.implementation.createDocument) {
             var doc = document.implementation.createDocument("", "", null);

             // some versions of Moz do not support the readyState property
             // and the onreadystate event so we patch it!
             if (doc.readyState == null) {
                doc.readyState = 1;
                doc.addEventListener("load", function () {
                   doc.readyState = 4;
                   if (typeof doc.onreadystatechange == "function")
                      doc.onreadystatechange();
                }, false);
             }

             return doc;
          }
          if (window.ActiveXObject)
             return new ActiveXObject(getControlPrefix() + ".XmlDom");
       }
       catch (ex) {}
       throw new Error("Your browser does not support XmlDocument objects");
    };

In this case the same extra handling to support `onreadystatechange` is added but except for this the `create` method should be fairly straight forward.

### getControlPrefix

Different versions of IE and Windows has different version of the MSXML parser installed and therefore we have created a litte helper function that checks which control prefix works in the user's version of IE. This function tries to create both the `"XmlHttp"` and `"XmlDom"` ActiveX objects and once it finds a prefix that works it stores that so that it does not have to try that again. If no valid control prefix is found an exception is thrown. The version independent control prefix is "MSXML2" and that is what is tried first. After that a few other prefixes are tested.

    function getControlPrefix() {
       if (getControlPrefix.prefix)
          return getControlPrefix.prefix;

       var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
       var o, o2;
       for (var i = 0; i < prefixes.length; i++) {
          try {
             // try to create the objects
             o = new ActiveXObject(prefixes[i] + ".XmlHttp");
             o2 = new ActiveXObject(prefixes[i] + ".XmlDom");
             return getControlPrefix.prefix = prefixes[i];
          }
          catch (ex) {};
       }

       throw new Error("Could not find an installed XML parser");
    }

### Extending Mozilla

For a general article about extending the Mozilla DOM see the IE Emu articles.

We would like to be able to use the `loadXML` method on the `XmlDocument` that takes a string and parses that as XML. The Mozilla version of `XmlDocument` does not support that natively so we extend the prototype of the `Document` to support this. To parse an XML string in Mozilla one can use the built in `DOMParser` object.

To load the document with an XML string we first parse the string and after that we remove all the nodes of the original tree and inserts all of the nodes from the document we got from the parsed string.

    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));
       }
    };

There is one more important feature that the Mozilla native version of the XML document is missing and that is a a way to serialize the DOM tree to an XML string. Fortunately the Mozilla developers have realized this and provided a class that can do this easily (even if creating a serializer using JS is fairly easy). This is done by using a class called `XMLSerializer`. In IE the XML document has a property called `xml` that returns the DOM tree serialized to an XML string so we define a setter on the `Document.prototype` that returns this.

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

## Documentation

The best documentation for the XmlDocument is probably the W3C DOM level 1, 2 and 3 but MS has an easier to read documentation at MSDN. Mozilla.org has a project page covering the extra XML features it provides.

Xml Extras
Usage
Demo
Download

Author: Erik Arvidsson