/*
function loadfile( filename, callback ) // callback( xml )
function obj2xml(obj, name)  // convert an object to xml text
// XML call using simplified calling methodology
  // Uses global _CGI variable to know what script to call
  // ex: var _CGI = 'xmlhttp.pl'
  function _xmlquery( callback, 'param1 -escapedparam2', [param1 value], [param2 value to be escaped] );
  
  function _newval( value )    // create an object with it's value attribute set to value
  function _newatt( value )    // create an object with value=[value] and att=1
  
  
  
  // Generate query string from parameter names and values
  function _query( 'a -b', '1', '&' ); // generates 'a=1&b=%26'
*/

function _xmlquery() {
  var t=arguments[1].split(' ').join('=?&')+'=?',i=-1,l=-1,k=1,o='',a,e,v,j;
  while((i=t.indexOf('?',i+1))>=0){a=t.substring(l+1,i);v=arguments[++k];j=0
  ;if(a.substr(0,++j)=='-'||a.substr(0,++j)=='&-'){a=(j>1?'&':'')+a.substr(j);
  //v=escape(v)}o+=a+v;l=i}o+=t.substr(l+1);
  v=encodeURIComponent(v)}o+=a+v;l=i}o+=t.substr(l+1);
  //loadfilep(_CGI.substr(0,_CGI.length-1),o,arguments[0]);
  var a = new Ajax( _CGI.substr(0,_CGI.length-1), { postBody: o, onSuccess: arguments[0], onBadXML: qfail, toOb: 1 } );
}
function qfail( ob ) {
  //alert(ob.transport.responseXML.documentElement.textContent );
}
function _query() {
  var t=arguments[0].split(' ').join('=?&')+'=?',i=-1,l=-1,k=0,o='',a,e,
  v,j;while((i=t.indexOf('?',i+1))>=0){a=t.substring(l+1,i);v=arguments[
  ++k];j=0;if(a.substr(0,++j)=='-'||a.substr(0,++j)=='&-'){a=(j>1?'&':''
  )+a.substr(j);v=escape(v)}o+=a+v;l=i}return o+=t.substr(l+1)
}


Ajax = Class.create();
Ajax.Events = [ 'Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete' ];
Ajax.prototype = {
  initialize: function( $url, $options ) {
    this.transport = this.getTransport();
    this.options   = $options;
    
    $url = $url.replace(/\/\//g,'/'); // remove / duplicates
    // do request
    this.transport.open( this.options.postBody ? 'post' : 'get', $url, true );
    this.transport.onreadystatechange = this.onStateChange.bind( this );
    this.setRequestHeaders();
    this.transport.send( $options.postBody ? $options.postBody : null );
    //this.transport.send();
  },
  
  getTransport: function() {
    return Try.these(
      function() { return new ActiveXObject( 'Msxml2.XMLHTTP'  ) },
      function() { return new ActiveXObject( 'Microsoft.XMLHTTP' ) },
      function() { return new XMLHttpRequest() }
    ) || false;
  },
  
  setRequestHeaders: function() {
    var $headers = [ 'X-Requested-With', 'XMLHttpRequest',
            'X-Prototype-Version', 'Protocut Version 0.1',
            'Content-type', 'application/x-www-form-urlencoded' ];
  
    if( this.transport.overrideMimeType ) $headers.push( 'Connection', 'close' ); // Bug fix for Mozilla
    
    while( $headers.length ) this.transport.setRequestHeader( $headers.shift(), $headers.shift() );
  },
  
  onStateChange: function() {
    var $event = Ajax.Events[ this.transport.readyState ]; // grab an event name
    
    if( $event == 'Complete' ) {
      //if( this.options.onBadXML && !this.transport.responseXML.doctype ) {
      //  this.callback( 'BadXML' );
      //}
      this.callback( this.transport.status, this.responseIsSuccess() ? 'Success' : 'Failure' );
      this.callback( $event );
      this.transport.onreadystatechange = Prototype.emptyFunction;
    }
    else this.callback( $event );
  },
  
  callback:function( $type1, $type2 ){
    var $param = this;
    if( this.options.toOb && $type1 != 'BadXML' ) {
      if( xmlerror( this.transport ) ) {
        if( this.options[ 'onFailure' ] ) {
          $type1 = 'Failure';
        }
        else {
          $param = { empty: 1 };
        }
      }
      else {
        $param = xml2obj( this.transport.responseXML ).xml;
      }
    }
    
    var $name1 = 'on' + $type1;
    var $func1 = this.options[ $name1 ];
    if( $func1 ) { $func1( $param ); return; }
    if( !$type2 ) return;
    var $name2 = 'on' + $type2;
    var $func2 = this.options[ $name2 ];
    if( $func2 ) $func2( $param );
  },

  responseIsSuccess: function() {
    var $status = this.transport.status;
    if( !$status ) return 1;
    if( $status >= 200 && $status < 300 ) return 1;
    return 0;
  }
}

function xmlerror( a ) {
  if( a.readyState !=4 ) return 0;
  if( a.responseText == '<?xml version="1.0" encoding="ISO-8859-1"?>\n' ) {
    return 0;
  }
  var ob = a.responseXML;
  if( ob == null ) return 0;
  var children = ob.childNodes;
  for( var i=0;i<children.length;i++ ) {
    var child = children[i];
    if( child && child.nodeName && child.nodeName == 'parsererror' ) {
      // err = child.childNodes[0].nodeValue;
      return 1;
    }
  }
  return 0;
}

// XML DOM Related Functions
// The following functions are meant to speed and simplify the use of XML in JavaScript
// Conceptually the notation is meant to be something like E4X. ( http://en.wikipedia.org/wiki/E4X )
// Unfortunately E4X is not very widely supported yet, and so it cannot be used effectively.
// The functionality also has Perl counterparts that mirror the functionality, since there is
// not even such a thing as E4X in Perl.

// ****** EXAMPLES ********
/*
Example 1: (Reading XML)
  contacts.xml:
    <xml>
      <multi_person/> // Indicates there are multiple person tags
      <person>
        <name>Bob</name>
        <phones>
          <home>###</home>
          <cell company="Verizon">###</cell>
        </phones>
      </person>
      <person>
        <name>Larry</name>
        <phones>
        </phones>
      </person>
    </xml>
  javascript fragment:
    loadfile('contacts.xml',doStuff);
    function doStuff( xml ) {
      for( var i in xml.person ) {
        var person = xml.person[ i ];
        alert( 'Name:' + person.name.value );
        alert( 'Height:' + person.height.val
        if( person.phones.home ) alert( 'Home Phone:' + person.phones.home.value );
        var cell = person.phones.cell;
        if( cell ) {
          alert( 'Cell Phone:' + cell.value );
          if( cell.company ) {
            alert( 'From ' + cell.company.value );
          }
        }
      }
    }
Example 2: (Outputting XML)
  javascript fragment:
    var root    = new Object;
    root.person = new Array;
    var bob     = new Object;
    bob.name    = _newval( 'Bob' );// same as bob.name = new Object; bob.name.value = 'Bob';
    bob.phones  = new Object;
    bob.phones.cell = _newval( '12345' );
    bob.phones.cell.company = _newatt( 'Cingular' );
    root.person.push( bob );
    document.write( obj2xml( root ) );
  output:
    <person>
      <name>Bob</name>
      <phones>
        <cell company='Cingular'>12345</cell>
      </phones>
    </person>
*/    
// ****** END EXAMPLES ********

var _multi = 'nodename';
//variable used to allow nodes with nodename 'nodename' to always
// appear as an array

//Converts a DOM XML node returned by Microsoft.XMLDOM or createDocument
// into a recursive javascript hash
function xml2obj($top) {
  var $output       = {};//the returned object
  var $outnodes     = {};//sets of named children
  var $outnodenames = []; //names of the sets

  if( $top == null ) return $output;
  
  //load all attributes
  if( $top.attributes ) {
    if( $top.attributes.length ) {
      for( var k = 0; k < $top.attributes.length; k++ ) {
        var ob = {};
        $output[ $top.attributes[ k ].nodeName ] = ob;
        ob.value = $top.attributes[ k ].nodeValue;
        ob.att = 1;
          //set the 'att' value to indicate this note is an attribute
          //This is used in obj2xml to correctly convert this back
          //into an attribute in xml
      }
    }
  }

  // If this is a text node, store it as such
  if( allTextChildren( $top ) ) {
    $output.value='';
    for( var i = 0; i < $top.childNodes.length; i++) {
      var $achild = $top.childNodes[ i ];
      $output.value += $achild.nodeValue;
    }
    $output.value = _decode( $output.value );
  }

  //one child with non text child or multiple children
  else if( $top.childNodes && $top.childNodes.length ) { 
    var $achild;
    var $cdata = '';

    // Store all children by name, to collect items with the same name
    for( var i = 0; i < $top.childNodes.length; i++ ) {
      $achild = $top.childNodes[ i ];
      if( $achild.nodeType == 4 ) $cdata = $achild.nodeValue;
      if( $achild.nodeType != 1 ) continue;
      if( !$outnodes[ $achild.nodeName ] ) {//create array to collect if needed
        $outnodes[ $achild.nodeName ] = new Array;
        $outnodenames.push( $achild.nodeName );
      }
      $outnodes[ $achild.nodeName ].push( $achild );
    }

    // If there is a cdata node, decode entities in it and use the value of it
    if( $cdata ) {
      $output.value = $cdata;
      $output.value = _decode( $output.value );
    }

    // Run through sets of named children
    else for( var i = 0; i < $outnodenames.length; i++ ) {
      var $name = $outnodenames[ i ];
      var $num  = $outnodes[ $name ].length;
      if( $num > 1 || $outnodes[ 'multi_' + $name ] || _multi == $name ) {//put in array if more than one or array expected
        $output[ $name ] = [];
        for( var i2 = 0; i2 < $num; i2++ ) {
          $output[ $name ].push( xml2obj( $outnodes[ $name ][ i2 ] ) );
        }
      }
      else {
        $output[ $name ] = xml2obj( $outnodes[ $name ][ 0 ] );
      }
    }
  }

  return $output;
}

//Returns true iff all children of the node are text nodes
function allTextChildren( $node ) {
  if( !$node.childNodes        ) return 0;// Possibly not normal node
  if( !$node.childNodes.length ) return 0;// No children
  for( var i = 0; i < $node.childNodes.length; i++) {
      if( $node.childNodes[ i ].nodeType != 3 ) return 0;// A non-text child
  }
  return 1;
}

// Converts a recursive javascript hash representing XML into
// actual XML
function obj2xml($obj, $name, $depth) {
  $depth = $depth ? $depth + 1 : 1;
  var $xml = '';
  var $att = '';
  if( !$obj ) return '';
  for( var i in $obj ) {
    // i is the nodename
    var $type     = typeof ( $obj[ i ] );
    var $isstring = ( $type == 'string' );
    var $isobject = ( $type == 'object' );
    var $isarray  = ( $obj[ i ].length && $isobject );
    if( $isarray ) {
      for( var j = 0; j < $obj[ i ].length; j++ ) {
        $xml += obj2xml( $obj[ i ][ j ], i, $depth-2 );
      }
      $xml += _dup(' ',$depth-2);
    }
    else if( $isstring ) {
      if( i == 'value' ) {
        var $val = $obj[ i ];
        $val = _encode( $val );
        //if( val.search( /[<>&]/ ) != -1 ) {//brackets and entities demand cdata
        //  xml += '<![CDATA[' + val + ']]>';
        //}
        $xml += $val;
      }
      else $xml += '<' + i + '>' + $obj[ i ] + '</' + i + '>';
    }
    else if( $isobject ) {
      if( $obj[ i ].att ) {
        $att += ' ' + i + '="' + $obj[ i ].value + '"';
      }
      else {
        $xml += obj2xml( $obj[ i ], i, $depth );
      }
    }
  }
  if( name ) {
    $xml = ( $depth != 2 ? "\n" : '') + _dup(' ',$depth-2) + '<' + $name + $att + '>' + $xml;
    if( $isobject ) $xml += "\n" + _dup(' ',$depth-2);
    $xml += '</' + $name + '>';
  }
  return $xml;
}

function _dup( $val, $num ) {
  var $res = '';
  for( var i = 0; i < $num; i++ ) {
    $res += val;
  }
  return $res;
}

function _newval( $val ) {
  return { value: $val };
}

function _newatt( $val ) {
  return { att: 1, value: $val };
}

// Load File
// Loads an XML file or makes an XMLHttp request
// fname    = url to load, optionally including arguments
//            A unique id is appended to the request, to ensure
//            that the request is not cached.
//            example: file.xml
//            example: xmlhttp.pl?request=names
// callback = The function to call after retrieving the XML
//            callback( xml )
//            xml = A recursive javascript hash of the XML
function loadfile( $filename, $callback, $fail ) {
  var a;
  if( $fail ) {
    a = new Ajax( $filename, { onComplete: $callback, onFailure: $fail||_nul, toOb: 1 } );
  }
  else {
    a = new Ajax( $filename, { onComplete: $callback, toOb: 1 } );
  }
}

function _nul() {
}

function newxml( $text ) {
  var ob;
  if( window.ActiveXObject ) {
    ob=new ActiveXObject("Microsoft.XMLDOM");
    ob.async="false";
    ob.loadXML($text);
  }
  else {
    var parser = new DOMParser();
    ob = parser.parseFromString($text,"text/xml");
  }
  return xml2obj( ob ).xml;
}

function _decode( $text ) {
  return $text.replace( /&([#a-z0-9]{3,6});/g, _dec );
}
function _dec( $all, $par1 ) {
  switch( $par1 ) {
    case 'deg':    return '°';
    case 'frac14': return '¼';
    case 'frac12': return '½';
    case 'frac34': return '¾';
    case 'ldquo':  return '“';
    case 'rdquo':  return '”';
    case 'lsquo':  return '‘';
    case 'rsquo':  return '’';
    default: return $all;
  }
}
function _encode( $text ) {
  return '<![CDATA['+$text.replace( /[;°¼½¾“”‘’]/g, _enc )+']]>';
  return $text.replace( /[°¼½¾“”‘’]/g, _enc );
}
function _enc( $all ) {
  switch( $all ) {
    case '°': return '&deg;';
    case '¼': return '&frac14;';
    case '½': return '&frac12;';
    case '¾': return '&frac34;';
    case '“': return '&ldquo;';
    case '”': return '&rdquo;';
    case '‘': return '&lsquo;';
    case '’': return '&rsquo;';
    default: return $all;
  }
}
function forcearray($A){
  return $A ? ( $A.length ? $A : [$A] ) : [];
}

//End javascript object to xml or html functions
