/*
 * <copyright>
 *  Copyright (c) Hyperwave GmbH 2010
 * </copyright>
 *
 * <file>
 *  Name:       MozillaXMLWrapper.js
 *  Created:    June 5, 2007
 *  $Id: $
 * </file>
 */

// <JSClass Name="com.hyperwave.xml.MozillaXMLWrapper">
//----------------------------------------------------------------------
/**
 * Firefox/Mozilla specific implementation class for modifying an XML file.
 * 
 */

defineClass ( "com.hyperwave.xml.MozillaXMLWrapper",
               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 ( theParam == "__proto__")
      return;

    if(typeof theParam == "string") {          
      class$.super_.constructor.call ( this, theParam );
    
      this.xmlDoc_ = document.implementation.createDocument("","", null);
      this.xmlDoc_.async = false;
    
      // error handling:
      // the initial plan was to use Event.observe on the document for the
      // "error" event, however, this event does not trigger!
      this.xmlDoc_.load(this.xmlUrl_);
      
      // In case the document with the given URL cannot 
      // be loaded the document simply remains empty .... 
      // note: move this into an "onload" handler in case of this.xmlDoc_.async = true.
      if(!this.xmlDoc_.documentElement){
        throw new com.hyperwave.xml.exceptions.DocumentLoadException(
          {message_:"The document with URL: " + this.xmlUrl_ + " could not be loaded!"}
        );
      }
    }
    else {
      this.xmlDoc_ = theParam;
    }
    
    this.tmpDoc_ = document.implementation.createDocument("","", null);

    // cache for transformed document
    this.docCache_ = [];
    
    this.errorXmlUrl_ = null;
    this.errorXslUrl_ = null;
  }
  /*
   * Instances cache
   */
  class$.static_.instances_ = {};
  
  /* 
   * dummy tag name for creating dynamic XML content
   */
  class$.static_.dummyTagName = "dummy";
    
  /**
   * Factory method to retrieve (a probably cached) version of an 
   * XMLWrapper representing the XML specified by the URL handed over. 
   */
  class$.static_.getInstance = function( xmlURI, reload ) {
    if ( reload || !this.instances_[xmlURI] ) {
      this.instances_[ xmlURI ] = new com.hyperwave.xml.MozillaXMLWrapper(xmlURI);
    }
    return this.instances_[ xmlURI ];
  }  
  
  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#updateNode
   */
  class$.updateNode = function( theXPath, theValue ){
    var parser = new DOMParser();
    this.tmpDoc_ = parser.parseFromString(
                         this.static_.XHTML_DOCTYPE +  
                         "<" + this.static_.dummyTagName + ">" + 
                         theValue + 
                         "</" + this.static_.dummyTagName + ">",
                         "text/xml" );
    if(this.tmpDoc_.documentElement.nodeName == "parsererror"){
      throw new com.hyperwave.xml.exceptions.DOMException(
        {message_:"Error when trying to create DOM fragment from component value: " + 
          this.tmpDoc_.documentElement.firstChild.nodeValue}           
      )      
    }                         
    var insertNode = this.tmpDoc_.getElementsByTagName( this.static_.dummyTagName )[0];                         
    
    try {
      var nsResolver = this.xmlDoc_.createNSResolver(this.xmlDoc_.documentElement);
      var result = this.xmlDoc_.evaluate(theXPath, this.xmlDoc_, nsResolver, 0, null);
      var res;
      if (res = result.iterateNext()){
        theNode = res;
        while ( theNode.childNodes.length > 0) {
          theNode.removeChild ( theNode.childNodes[0]);
        }
  
        for ( i=0; i<insertNode.childNodes.length; i++) {
          var newNode = this.xmlDoc_.importNode(insertNode.childNodes[i], true);
          theNode.appendChild( newNode );
        }          
      }
    }
    catch(e){
      throw new com.hyperwave.xml.exceptions.DOMException(
        {message_:"An error occurred when trying to update XML DOM.",
         cause_: e}
      );
    }
    // default implementation always replaces node, so return true
    return true;
  }
  
  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#updateAttribute
   */
  class$.updateAttribute = function( theXPath, theValue ){    
    try {
      var nsResolver = this.xmlDoc_.createNSResolver(this.xmlDoc_.documentElement);
      var result = this.xmlDoc_.evaluate(theXPath, this.xmlDoc_, nsResolver, 0, null);
      var res;
      if (res = result.iterateNext()){
        res.value = theValue;
      }
    }
    catch(e){
      throw new com.hyperwave.xml.exceptions.DOMException(
        {message_:"An error occurred while trying to update XML DOM.",
         cause_: e}
      );
    }
    // default implementation always replaces attribute value, so return true
    return true;
  }


  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#getNode
   */
  class$.getNode = function( theXPath ) {
    var theNode = null;
    try {
      var nsResolver = this.xmlDoc_.createNSResolver(this.xmlDoc_.documentElement);
      var result = this.xmlDoc_.evaluate(theXPath, this.xmlDoc_, nsResolver, 0, null);
      var res;
      if (res = result.iterateNext()){
        theNode = res;
      }
    }
    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) {
  	var serializer = new XMLSerializer();
    var str = serializer.serializeToString( theNode );
    return str;
  }
  
  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#deleteNodes
   */
  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#replaceNode
   */
  class$.replaceNode = function( theXPath, theValue ) {
    var parser = new DOMParser();
    this.tmpDoc_ = parser.parseFromString(
                         this.static_.XHTML_DOCTYPE +  
                         theValue.strip(),
                         "text/xml" );
    if(this.tmpDoc_.documentElement.nodeName == "parsererror"){
      throw new com.hyperwave.xml.exceptions.DOMException(
        {message_:"Error when trying to create DOM fragment from component value: " + 
          this.tmpDoc_.documentElement.firstChild.nodeValue}           
      )      
    }                         
    var insertNode = this.tmpDoc_.childNodes[1];                         
    var node = this.getNode(theXPath);
    node.parentNode.replaceChild(insertNode,node);
  }
    
  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#insertNode
   */
  class$.insertNode = function( theParentPath, theValue ) {
    var parser = new DOMParser();
    this.tmpDoc_ = parser.parseFromString(
                         this.static_.XHTML_DOCTYPE +  
                         theValue.strip(),
                         "text/xml" );
    if(this.tmpDoc_.documentElement.nodeName == "parsererror"){
      throw new com.hyperwave.xml.exceptions.DOMException(
        {message_:"Error when trying to create DOM fragment from component value: " + 
          this.tmpDoc_.documentElement.firstChild.nodeValue}           
      )      
    }                         
    var insertNode = this.tmpDoc_.childNodes[1];                         
    try {
      var nsResolver = this.xmlDoc_.createNSResolver(this.xmlDoc_.documentElement);
      var result = this.xmlDoc_.evaluate(theParentPath, this.xmlDoc_, nsResolver, 0, null);
      var res;
      if (res = result.iterateNext()){
        theNode = res;
  
        var newNode = this.xmlDoc_.importNode(insertNode, true);
        newNode = theNode.appendChild( newNode );
        var node_index = this.getSiblingsIndex(newNode) ? "[" + this.getSiblingsIndex(newNode) + "]" : "";
        return theParentPath + "/" + newNode.nodeName + node_index;
      }
    }
    catch(e){
      throw new com.hyperwave.xml.exceptions.DOMException(
        {message_:"An error occurred when trying to update XML DOM.",
         cause_: e}
      );
    }
    // default implementation always replaces node, so return true
  }
  
  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#getXML
   */
  class$.getXML = function() {
    var xmls = new XMLSerializer();        
    return xmls.serializeToString(this.xmlDoc_);;
  }
  
  /**
   * Private static method to handle and cache XSL templates
   */
  class$.static_._getXSL = function( URI ) {
    if ( !this.xslCache_[ URI ] ) {
      var processor = new XSLTProcessor();
      var xslt = document.implementation.createDocument("", "", null);

      xslt.async = false;
      xslt.load( URI );
      processor.importStylesheet(xslt);

       this.xslCache_[ URI ] = processor;
    }
    return this.xslCache_[ URI ]; 
  }
   
  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#transformToString
   */
  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 
	    // "wrapper" element (used by template designer to assign CSS), so 
	    // that head and unnecessary body parts are removed
	    var wrapper = result.getElementById( this.static_.WRAPPER_ID );
	    
	    var xmls = new XMLSerializer();        
	    return '<' + wrapper.tagName + ' id="' + this.static_.WRAPPER_ID + '">' + wrapper.innerHTML + '</' + wrapper.tagName + ">";
    }
    catch(e) {
      var errMsg = [];
      errMsg.push(e.exceptionName_);
      errMsg.push(e.message_);
      for(var i=0; i<e.stackTrace_.length;i++) {
      	errMsg.push(e.stackTrace_[i]);
      }
      
    	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>';
    	}
    }
  }

  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#transformToString
   */
  class$.getTransformedScripts = function( xslURI, fromWholeDocument ){
  	try {
	    var result = this.getTransformedDocument(xslURI, false);
	    if(fromWholeDocument) {
	      return result.getElementsByTagName("html")[0].getElementsByTagName("script");
	    } 
	    else {
	      return result.getElementsByTagName("html")[0].getElementsByTagName("head")[0].getElementsByTagName("script");
	    }    
  	}
  	catch(e) {
  		return new Element("script");
  	}
  }

  /**
   * @see com.hyperwave.xml.AbstractXMLWrapper#transform
   */
  class$.transform = function( xslURI ){

    var result = this.getTransformedDocument(xslURI, true);
    return new com.hyperwave.xml.MozillaXMLWrapper(result);    
  }

  /**
   * @see com.hyperwave.xml.getTransformedDocument#transform
   */
  class$.getTransformedDocument = function( xslURI, force ){
    if ( !this.docCache_ [xslURI] || force ) {
      try {
        this.docCache_ [xslURI] = this.static_._getXSL(xslURI).transformToDocument(this.xmlDoc_);
      } catch(e){
        throw new com.hyperwave.xml.exceptions.XSLTransformationException(
          {message_:"An error occurred when trying transform XML.",
           cause_: e}
        );
      }
    }  
    return this.docCache_[xslURI];
  }

});
// </JSClass>
