Sunday, November 21, 2010

XMLHttpRequest.responseXML returns null

Issue: [JavaScript] Loading XSLT via XMLHttpRequest.responseXML always results in null.

Reason: It is due to incorrect MIME type "application/octet-stream" returned by the HTTP response found in "Content-Type" response header.

   var responseMIMEType = xhttp.getResponseHeader('Content-Type'); // xhttp is XMLHttpRequest object.

According to the XMLHttpRequest specification 3.7.5, XMLHttpRequest.responseXML will return null if the HTTP response header 'content-type' is not XML.

Resolution: If resultant responseXML is null, we could force to override the MIME type or convert XML text to XML DOM.

  • For the latest modern browsers like Mozilla (both SeaMonkey 2.0.10 and Firefox 3.5), Safari 5.0 and Opera 10.51, we could simply instruct the browser to override the MIME Type when it receives the document. Do this before XMLHttpRequest.send.
        // Override MIME type.  Set this before send. 
        if (xhttp.overrideMimeType)   xhttp.overrideMimeType('text/xml');
  • For others except IE, we could set up another defend mechanism by converting XML string to DOM with DOMParser.
        // Create XML DOM from string
        xmlDoc = (new DOMParser()).parseFromString(xhttp.responseText, 'text/xml');
  • IE doesn't support MIME overridden. And you may not be able to use DOMParser to parse XML string either. Otherwise, you may step into another error.

    The stylesheet does not contain a document element. The stylesheet may be empty, or it may not be a well-formed XML document.

    If it is the case, for IE, do this:

     xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
     xmlDoc.async = false;
     xmlDoc.loadXML(xhttp.responseText);
    

    The code for loading XML and XSLT may look like the following:

      var xhttp;
      function loadXMLDoc(url) {
        if (window.XMLHttpRequest) {
          xhttp = new XMLHttpRequest();
        }
        else {
          xhttp = new ActiveXObject('Microsoft.XMLHTTP');
        }
        
        xhttp.open('GET', url, false);  
        
        if (xhttp.overrideMimeType) 
          xhttp.overrideMimeType('text/xml');       
    
        xhttp.send(null);
     
    var xmlDoc = xhttp.responseXML; if (!xmlDoc) { if (isIE) { xmlDoc = new ActiveXObject('Microsoft.XMLDOM'); xmlDoc.async = false; xmlDoc.loadXML(xhttp.responseText); } else xmlDoc = (new DOMParser()).parseFromString(xhttp.responseText, 'text/xml'); }
    return xmlDoc; }
    One more thing, if you run or test this code under a file-system-based environment such as Visual Studio instead of being served by a Web server, the above stylesheet error on IE may still occur. To resolve this, both XML and XSLT must be loaded by the same method like this:
        var xmlDoc;
        if (isIE) {
          xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
          xmlDoc.async = false;
          xmlDoc.loadXML(xhttp.responseText);
        }
        else {
          xmlDoc = xhttp.responseXML;
          if (!xmlDoc) {
            xmlDoc = (new DOMParser()).parseFromString(xhttp.responseText, 'text/xml');
          }
        }

After the XML and the XSLT have successfully been loaded, we can transform the XML using the loaded XSLT.

  // Transform XML using XSLT
  // Here both xml and xslt are xml dom objects
  function getTransformedXML(xml, xslt) {
    if (window.ActiveXObject)  { // for IE
       return xml.transformNode(xslt);      
    }
    // for Mozilla (SeaMonkey and FireFox), Safari, Opera, etc.
    else if (document.implementation && document.implementation.createDocument) {
      var xsltProcessor = new XSLTProcessor();
      xsltProcessor.importStylesheet(xslt);
      return xsltProcessor.transformToFragment(xml, document);
    }
     
    return null;
  }

The complete code

Here the code is revised a bit to locate the MS latest XML parser for use instead of using the default old version, Microsoft.XMLDOM.
  var xhttp;
  function loadXMLDoc(url) {
    if (window.XMLHttpRequest) {
      xhttp = new XMLHttpRequest();
    }
    else {
      xhttp = new ActiveXObject('Microsoft.XMLHTTP');
    }
    
    xhttp.open('GET', url, false);  

    if (xhttp.overrideMimeType) 
      xhttp.overrideMimeType('text/xml');       

    xhttp.send(null);

    if (isIE) {
      xmlDoc = getMSXmlParser();
      xmlDoc.async = false;
      xmlDoc.loadXML(xhttp.responseText);
    }
    else {
      xmlDoc = xhttp.responseXML;
      if (!xmlDoc) {
        xmlDoc = (new DOMParser()).parseFromString(xhttp.responseText, 'text/xml');
      }
    }
    return xmlDoc;
  }

  // Transform XML using XSLT
  // Here both xml and xslt are xml dom objects
  function getTransformedXML(xml, xslt) {
    if (window.ActiveXObject)  { // for IE
       return xml.transformNode(xslt);      
    }
    // for Mozilla (SeaMonkey and FireFox), Safari, Opera, etc.
    else if (document.implementation && document.implementation.createDocument) {
      var xsltProcessor = new XSLTProcessor();
      xsltProcessor.importStylesheet(xslt);
      return xsltProcessor.transformToFragment(xml, document);
    }
     
    return null;
  }
  
function getMSXmlParser() { var parser = ['Msxml2.DOMDocument.6.0', 'Msxml2.DOMDocument.5.0', 'Msxml2.DOMDocument.4.0', 'Msxml2.DOMDocument.3.0', 'MSXML2.DOMDocument', 'Microsoft.XMLDOM']; // the same as MSXML.DOMDocument for (var i in parser) { try { var xParser = new ActiveXObject(parser[i]); if (xParser) { return xParser; } } catch (e) {} } return null; }