/*
 * <copyright>
 *  Copyright (c) Hyperwave GmbH 2010
 * </copyright>
 *
 * <file>
 *  Name:       InternetExplorerXMLWrapper.js
 *  Created:    Apr 27, 2007
 *  $Id: $
 * </file>
 */

// <JSClass Name="com.hyperwave.xml.InternetExplorerXMLWrapper">
//----------------------------------------------------------------------
/**
 * Internet Explorer specific implementation class for modifying an XML file.
 *
 */

defineClass ( "com.hyperwave.xml.InternetExplorerXMLWrapper",
               com.hyperwave.xml.AbstractXMLWrapper,
              function ( class$ )
{
  /**
   * Creates an instance of an XMLWrapper.
   *
   * @param theParam: string | DOM: either the URI specifying the XML file to process
   *   or the DOM representing the XML.
   */
  class$.constructor = function( theParam )
  {
    if ( typeof theParam == "string" &&
         theParam == "__proto__")
      return;

    if(typeof theParam == "string") {
      class$.super_.constructor.call ( this, theParam );

      this.xmlDoc_ = new ActiveXObject(this.static_.MSXML_DOM_DOCUMENT);

      this.xmlDoc_.async = false;
      this.xmlDoc_.resolveExternals = false;
      this.xmlDoc_.validateOnParse = false;
      this.xmlDoc_.load( this.xmlUrl_ );
      
      // don't throw an error here --> transformToString() will display an error page instead
      /*
      if (this.xmlDoc_.parseError.errorCode != 0) {
        throw new com.hyperwave.xml.exceptions.ParseException(
          this.xmlDoc_.parseError.reason
        );
      }
      */
    }
    else{
      this.xmlDoc_ = theParam;
    }

    this.tmpDoc_ = new ActiveXObject(this.static_.MSXML_DOM_DOCUMENT);

    // cache for transformed document
    this.docCache_ = [];
    
    this.errorXmlUrl_ = null;
    this.errorXslUrl_ = null;
  }

  /**
   * Instances cache
   */
  class$.static_.instances_ = {};

  /**
   * The native MS class handling XML documents/doms
   */
  class$.static_.MSXML_DOM_DOCUMENT = "MSXML2.DOMDocument.3.0";

  /**
   * All Tags which need to have a closing tag ("<.../>" is not enough).
   * Application rendering in IE changes tags without innerHTML to "<.../>"
   * instead of "<...></...>";
   * For some tags, if they don't have a closing tag, IE does not display anything after that element.
   *
   * Xinha defines the following to need a closing tag (Xinha._closingTags):
   *    a abbr acronym address applet b bdo big blockquote button caption center
   *    cite code del dfn dir div dl em fieldset font form frameset h1 h2 h3 h4 h5 h6
   *    i iframe ins kbd label legend map menu noframes noscript object ol optgroup
   *    pre q s samp script select small span strike strong style sub sup table textarea
   *    title tt u ul var
   *
   * WCM does not need all these tags. So just add those who really can occur in WCM.
   */
  class$.static_.tagsToBeClosed = ["applet", "iframe"];

  /**
   * regexp for xpathes
   */
  class$.static_.xpathExp_ = new RegExp("\\[(d+)\\]");

  class$.static_.getInstance = function( xmlURI, reload ) {
    if ( reload || !this.instances_[xmlURI] ) {
      this.instances_[ xmlURI ] = new com.hyperwave.xml.InternetExplorerXMLWrapper(xmlURI);
    }
    return this.instances_[ xmlURI ];
  }

  /**
   * Private static method to handle and cache xsl templates
   */
  class$.static_._getXSL = function( URI ) {
    if ( !this.xslCache_[ URI ] ) {
      var tmp_xsl = new ActiveXObject(this.MSXML_DOM_DOCUMENT);
      tmp_xsl.async = false;
      tmp_xsl.load(URI);

      if (tmp_xsl.parseError.errorCode != 0) {
        throw new com.hyperwave.xml.exceptions.ParseException(
          tmp_xsl.parseError.reason
        );
      } else {
        var outputElem = tmp_xsl.getElementsByTagName("xsl:output")[0];
        if ( outputElem ) {
            // overwrite or remove relevant attributes so xml can be transformed into an object
            outputElem.setAttribute("method", "xml");
            outputElem.setAttribute("omit-xml-declaration", "yes");
            outputElem.removeAttribute("doctype-system");
            outputElem.removeAttribute("doctype-public");
        } else {
            // insert an output node into the xslt with attributes set acordingly
            var xslroot = tmp_xsl.documentElement;
            var outputNode = tmp_xsl.createNode(1, "xsl:output", "http://www.w3.org/1999/XSL/Transform");
            outputNode.setAttribute("method", "xml");
            outputNode.setAttribute("omit-xml-declaration", "yes");

            xslroot.insertBefore(outputNode, xslroot.childNodes.item(1));
        }
        this.xslCache_[ URI ] = tmp_xsl;
      }
    }
    return this.xslCache_[ URI ];
  }

  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#transform
   */
  class$.transformToString = function( xslURI, useCache ){
  	try {
	    if(useCache) {
        var result = this.getTransformedDocument(xslURI, false);
      }
      else {
        var result = this.getTransformedDocument(xslURI, true);
      }
	    // and now take care to return only the contents of the
	    // FIRST "wrapper" element (used by template designer to assign CSS), so
	    // that head and unnecessary body parts are removed
	    var wrapper = result.selectNodes("//*[@id='wrapper']")[0];
	    // some tags need to have a closing tag ("<../>" is not enough)
	    var xml = wrapper.xml;
	    var tagsToBeClosed = com.hyperwave.xml.InternetExplorerXMLWrapper.static_.tagsToBeClosed;
	    for(var i=0; i<tagsToBeClosed.length; i++) {
	      var re = new RegExp("(" + tagsToBeClosed[i] + "[^>]*?)\/>", "ig");
	      var repl = "$1 ></" + tagsToBeClosed[i] + ">";
	      xml = xml.replace(re, repl);
	    }
  	}
  	catch(e) {
  		var errMsg = [];
      errMsg.push(e.name + ": " + e.number);
      errMsg.push(e.message);
      
      
      if(this.errorXmlUrl_ && this.errorXslUrl_ && xslURI != this.errorXslUrl_) {
        var wrapper = this.static_.getInstance( this.errorXmlUrl_ );
        var html = wrapper.transformToString(this.errorXslUrl_);
        html = html.replace(/%%error_details%%/, errMsg.join("<br/>"));
        return html;
      }
      else {
      	alert(errMsg.join("\n"));
        return '<div id="' + this.static_.WRAPPER_ID + '"></div>';
      }
    }

    return xml;
  }

  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#transform
   */
  class$.transform = function( xslURI ){
    var result = this.getTransformedDocument( xslURI, true );
    return new com.hyperwave.xml.InternetExplorerXMLWrapper ( result );
  }

  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#getTransformedElements
   */
  class$.getTransformedElements = function( xslURI, xPath ){
    var result = new ActiveXObject(this.static_.MSXML_DOM_DOCUMENT);
    result.async = false;
    result.validateOnParse = true;
    // Parse results into a result DOM Document.
    this.xmlDoc_.transformNodeToObject( this.static_._getXSLPrepared ( xslURI ), result );
    if (result.parseError.errorCode != 0) {
      throw new com.hyperwave.xml.exceptions.ParseException(
        result.parseError.reason
      );
    } else {
      result.setProperty("SelectionNamespaces", "xmlns:xhtml='http://www.w3.org/1999/xhtml'");
      var elems = result.selectNodes(xPath);
      var return_val = [];
      if (elems.length) {
        for (var e=0; e<elems.length; e++) {
          return_val[e] = { text: elems[e].text };
          for (var a=0; a<elems[e].attributes.length; a++) {
            return_val[e][elems[e].attributes[a].nodeName] = elems[e].attributes[a].nodeValue;
          }
        }
      }
      return return_val;
    }
  }

  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#updateNode
   */
  class$.updateNode = function( theXPath, theValue ){
    var oNode, type, insertNode;
    oNode = this.xmlDoc_.selectSingleNode(
      this.static_.getIEVersionOfXpath (theXPath)
    );
    if (oNode != null)
    {
      this.tmpDoc_.loadXML(
                            "<" + this.static_.dummyTagName + ">" +
                            theValue +
                            "</" + this.static_.dummyTagName + ">"
                          );

      if (this.tmpDoc_.parseError.errorCode != 0) {
        throw new com.hyperwave.xml.exceptions.ParseException(this.tmpDoc_.parseError.reason);
        return false;
      } else {
        insertNode = this.tmpDoc_.getElementsByTagName( this.static_.dummyTagName )[0];

        while ( oNode.childNodes.length > 0) {
          oNode.removeChild ( oNode.childNodes[0] );
        }

        while ( insertNode.childNodes.length > 0 ) {
          oNode.appendChild( insertNode.childNodes[0] );
        }
      }
      oNode = null;
    } else {
       throw new com.hyperwave.xml.exceptions.DOMException("The XPath: " + theXPath + " can not be resolved!");
       return false;
    }
    // default implementation always replaces node, so return true
    return true;
  }

  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#updateAttribute
   */
  class$.updateAttribute = function( theXPath, theValue ){
    this.updateNode(theXPath, com.hyperwave.res.ScopedMap.static_.escapeHtml(theValue));
  }

  /**
   * Retrieves the DOM Node from the given XPath.
   * @param anXPath: string: the XPath of the node to retrieve
   * @return Node: the desired DOM node.
   */
  class$.getNode = function( theXPath ) {
    var theNode;
    try {
      theNode = this.xmlDoc_.selectSingleNode(
        this.static_.getIEVersionOfXpath (theXPath)
      );
    } catch(e) {
      throw new com.hyperwave.xml.exceptions.DOMException(
        {message_:"An error occurred when trying to retrieve XML node.",
         cause_: e}
      );
    }
    return theNode;
  }
  
  class$.static_.getXMLStringFromNode = function(theNode) {
    return theNode.xml;
  }

  /**
   * Inserts a new DOM Node to the given parent with the given value as
   * the tag to insert.
   * @param theParentXPath: string: the XPath of the node to retrieve
   * @param theValue: string: the XML representation of the new node as string
   * @return String: The XPath of the isnerted node.
   */
  class$.insertNode = function( theParentXPath, theValue ) {
    var parentNode;
    parentNode = this.getNode( theParentXPath );
    if ( parentNode != null ) {
      this.tmpDoc_.loadXML(theValue);
      if (this.tmpDoc_.parseError.errorCode != 0) {
        throw new com.hyperwave.xml.exceptions.ParseException(this.tmpDoc_.parseError.reason);
      } else {
        var newNode = parentNode.appendChild(this.tmpDoc_.childNodes[0]);
        var node_index = this.getSiblingsIndex(newNode) ? "[" + this.getSiblingsIndex(newNode) + "]" : "";
        return theParentXPath + "/" + newNode.nodeName + node_index;
     }
    } else {
      throw new com.hyperwave.xml.exceptions.ParseException("Parent node '" + theParentXPath + "' can not be found!");
    }
  }

  /**
   * Replaces the DOM Node at the given XPath with the given value as
   * the tag to insert.
   * @param anXPath: string: the XPath of the node to replace
   * @param theValue: string: the XML representation of the new node as string
   */
  class$.replaceNode = function( theXPath, theValue ) {
    var oNode, newNode;
    oNode = this.getNode( theXPath );
    if ( oNode != null ) {
      this.tmpDoc_.loadXML(theValue);
      if (this.tmpDoc_.parseError.errorCode != 0) {
        throw new com.hyperwave.xml.exceptions.ParseException(this.tmpDoc_.parseError.reason);
        return false;
      } else {
        oNode.parentNode.replaceChild(this.tmpDoc_.childNodes[0], oNode);
      }
    } else {
      throw new com.hyperwave.xml.exceptions.ParseException("Parent node '" + theXPath + "' can not be found!");
      return false;
    }

  }

  /**
   * Deletes the DOM Nodes at the given XPaths.
   * @param anXPathArray: string[]: the XPath of the nodes to delete as array of strings
   */
  class$.deleteNodes = function( theXPathArray ) {
    var nodes = [];
    for(var cr=0;cr<theXPathArray.length;++cr){
      nodes.push(this.getNode(theXPathArray[cr]));
    }
    for(var cr=0;cr<nodes.length;++cr)
      nodes[cr].parentNode.removeChild(nodes[cr]);
  }

  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#getXML
   */
  class$.getXML = function() {
    return this.xmlDoc_.xml;
  }

  /**
   * Static method to change the xpath syntax, IE starts counting at 0
   * unlike the W3C recomendation...
   * @param theXPath: string: an XPath with recommended W3C syntax
   * @return string: an XPath string in which array indices start at 0 instead of 1.
   */
  class$.static_.getIEVersionOfXpath = function(theXPath) {
    var exp = new RegExp().compile(/\[(\d+)\]/g);
    function lambda_exp(all, digit) {
      var d = parseInt(digit)-1;
      return "[" + d + "]";
    };
    return theXPath.replace(exp, lambda_exp);
  }

  /**
   * @see com.hyperwave.xml.getTransformedDocument#transform
   */
  class$.getTransformedDocument = function( xslURI, force ){
    if ( !this.docCache_ [xslURI] || force ) {
      this.docCache_ [xslURI] = new ActiveXObject(this.static_.MSXML_DOM_DOCUMENT);
      this.docCache_ [xslURI].async = false;
      this.docCache_ [xslURI].validateOnParse = true;
      this.xmlDoc_.transformNodeToObject( this.static_._getXSL ( xslURI ), this.docCache_ [xslURI] );

      if (this.docCache_ [xslURI].parseError.errorCode != 0) {
        throw new com.hyperwave.xml.exceptions.XSLTransformationException(
          this.docCache_ [xslURI].parseError.reason
        );
        this.docCache_ [xslURI] = null;
      }
    }
    return this.docCache_[xslURI];
  }

  /**
   * @see com.hyperwave.xml.getTransformedScripts#transform
   */
  class$.getTransformedScripts = function( xslURI, fromWholeDocument ){
  	var return_val = [];
  	try {
	    var tmp_doc = this.getTransformedDocument (xslURI, false);
	    if(fromWholeDocument) {
	      var scripts = tmp_doc.getElementsByTagName("html")[0].getElementsByTagName("script");
	    }
	    else {
	      var scripts = tmp_doc.getElementsByTagName("html")[0].getElementsByTagName("head")[0].getElementsByTagName("script");      
	    }
	    if (scripts.length) {
	      for (var s=0; s<scripts.length; s++) {
	        return_val[s] = { text: scripts[s].text };
	        for (var a=0; a<scripts[s].attributes.length; a++) {
	          return_val[s][scripts[s].attributes[a].nodeName] = scripts[s].attributes[a].nodeValue;
	        }
	      }
	    }
  	}
  	catch(e) {
  		// silent catch: when an error occurs, an empty result will be returned
  	}
  	return return_val;
  }

});
// </JSClass>


