//
// ******************************************************************************************************
// *                                                                                                    *
// *                                  Javascript Classes and Functions                                  *
// *                                                                                                    *
// *  Class                                           Base Class                                        *
// *                                                                                                    *
// *    ShowPage_AJAX                                   (n/a)                                           *
// *    ShowPage_AJAX_JS                                ShowPage_AJAX                                   *
// *    ShowPage_AJAX_Query                             ShowPage_AJAX                                   *
// *    ShowPage_AJAX_XSL                               ShowPage_AJAX                                   *
// *                                                                                                    *
// ******************************************************************************************************
//

//
// ******************************************************************************************************
// *                                                                                                    *
// * Class ShowPage_AJAX                                                                                *
// *                                                                                                    *
// * Fetch and display query results, one query after another, asynchronously (AJAX).                   *
// * Query results are fetched from the server by the VCSH_Global.query() function, using XML-RPC.      *
// * The query and its results are formatted and placed together inside a container <x id="id"> element.*
// * The results of subsequent queries replace the contents of this container.                          *
// *                                                                                                    *
// * This class can handle any data that are served either:                                             *
// *   (1) as native Javascript data types, or                                                          *
// *   (2) as Javascript objects, with a constructor and a toString() member function.                  *
// *                                                                                                    *
// ******************************************************************************************************
//
// constructor
//
function ShowPage_AJAX(server, query, id, interval, count, proto) {
  //
  // server    : (string) web server for queries
  // query     : (Array of queries) each query is an Array of strings and query arguments
  // id        : (string) id attribute of the container Element created/used to contain the results
  // interval  : (int) time interval between updates, in milliseconds; if <= 0, re-queue after -interval
  // count     : (int) maximum number of fetches; the last query may be repeated
  // proto     : (function) constructor of the object to be fetched and displayed
  //
  if (arguments.length == 0 || server === null) {
  } else if (typeof server != "object") {
    this.init.apply(this, arguments);
  } else if (server instanceof ShowPage_AJAX) {
    this.init(server.server, server.query, server.id, server.interval, server.count, server.proto);
  } else {
    this.init(server["server"], server["query"], server["id"], server["interval"], server["count"],
      server["proto"]);
  }
}
//
// protected (initializer)
//
ShowPage_AJAX.prototype.init = function(server, query, id, interval, count, proto) {
  this.server    = server    !== undefined ? server    : HOST;              // string
  this.query     = query instanceof Array  ? query     : [];                // Array of Arrays
  this.id        = id        !== undefined ? id        : "_";               // string
  this.interval  = interval  !== undefined ? interval  : 1000;              // int (milliseconds)
  this.count     = count     !== undefined ? count     : this.query.length; // int (number of fetches)
  this.proto     = proto     !== undefined ? proto     : null;              // constuctor
  this.index     = 0;
  this.rep       = 0;
  this.x         = null;
  this.timeout   = null;
};
//
// public
//
ShowPage_AJAX.prototype.sprint   = function() { return "ShowPage_AJAX {" + this.toString() + "\n}"; };
ShowPage_AJAX.prototype.toString = function() {
  var s = "";
  if (this.server    !== undefined) s += "\n  [server]    => "    + this.server;
  if (this.query instanceof Array) {
    s += "\n  [query]     => [";
    for (var i=0; i<this.query.length; i++) s+=  " [" + this.query[i].join("; ") + "]";
    s += " ]";
  }
  if (this.id        !== undefined) s += "\n  [id]        => " + this.id;
  if (this.interval  !== undefined) s += "\n  [interval]  => " + this.interval;
  if (this.count     !== undefined) s += "\n  [count]     => " + this.count;
  if (this.proto     !== undefined) s += "\n  [proto]     => " +
                    (this.proto === null ? "null" : "(" + typeof this.proto + ")");
  return s;
};
ShowPage_AJAX.prototype.start = function(sync) {
  //
  // Start the asynchronous sequence of Fetch and Display actions.
  // If sync is defined and non-zero, the first action will be synchronous.
  //
  if (this.reset()) return; // return on error
  if (sync !== undefined && sync) this.next(); // make the first pass synchronous
  else with ({ x : this }) this.timeout = setTimeout(function() { x.next(); }, 0);
};
//
// protected
//
ShowPage_AJAX.prototype.reset = function() {
  while (this.timeout) { clearTimeout(this.timeout); this.timeout = null; }
  this.index = 0;
  this.rep   = this.count;
  this.x     = window.document.getElementById(this.id);
  return !this.x;
};
ShowPage_AJAX.prototype.next = function() {
  //
  // Fetch data and display the result.
  // Note:  This base class has no output function.  Use a derived class instead.
  //
  this.x.innerHTML = '<span class="no">Note:  Class ShowPage_AJAX has no output function.</span>';
};

//
// ******************************************************************************************************
// *                                                                                                    *
// * Class ShowPage_AJAX_JS                                                                             *
// *                                                                                                    *
// * Fetch and display query results, one query after another, asynchronously (AJAX).                   *
// * Query results are fetched from the server by the VCSH_Global.query() function, using XML-RPC.      *
// * These are formatted and placed together inside a container <x id="id"> element.                    *
// * The results of subsequent queries replace the contents of this container.                          *
// *                                                                                                    *
// * This class can handle any data that are served either:                                             *
// *   (1) as native Javascript data types, or                                                          *
// *   (2) as Javascript objects, with a constructor and a toString() member function.                  *
// *                                                                                                    *
// ******************************************************************************************************
//
ShowPage_AJAX_JS.prototype = new ShowPage_AJAX();
ShowPage_AJAX_JS.prototype.constructor = ShowPage_AJAX_JS;
ShowPage_AJAX_JS.superclass = ShowPage_AJAX.prototype;
//
// constructor
//
function ShowPage_AJAX_JS(server, query, id, interval, count, proto, transform, params) {
  //
  // transform : (function) transformation to convert objects to strings for display
  // params    : (object) associative array of parameters to pass to the transformation function
  //
  if (arguments.length == 0 || server === null) {
  } else if (typeof server != "object") {
    this.init.apply(this, arguments);
  } else if (server instanceof ShowPage_AJAX_JS) {
    this.init(server.server, server.query, server.id, server.interval,
      server.count, server.proto, server.transform, server.params);
  } else {
    this.init(server["server"], server["query"], server["id"], server["interval"],
      server["count"], server["proto"], server["transform"], server["params"]);
  }
}
//
// protected (initializer)
//
ShowPage_AJAX_JS.prototype.init = function(server, query, id, interval, count, proto, transform, params) {
  ShowPage_AJAX_JS.superclass.init.apply(this, arguments);
  this.transform = transform !== undefined ? transform : ShowPage.sprint; // function (optional)
  this.params    = params    !== undefined ? params    : {};              // object (optional)
};
//
// public
//
ShowPage_AJAX_JS.prototype.sprint = function() { return "ShowPage_AJAX_JS {" + this.toString() + "\n}"; };
ShowPage_AJAX_JS.prototype.toString = function() {
  var s = ShowPage_AJAX_JS.superclass.toString.call(this);
  if (this.transform !== undefined) s += "\n  [transform] => (" + typeof this.transform + ")";
  if (typeof this.params == "object") {
    s += "\n  [params]    => [";
    var and = " ";
    for (var i in this.params) { s+= and + i + " => '" + this.params[i] + "'"; and = ", "; }
    s += " ]";
  }
  return s;
};
ShowPage_AJAX_JS.prototype.next = function(q) {
  //
  // Fetch data and display the result.  Then schedule the next action.
  //
  for (;;) {
    // if (false) {
    //   //
    //   // Post a synchronous query and wait for the response.
    //   //
    //   if (--this.rep < 0) return;
    //   var q = VCSH_Global.query.apply(null, [HOST].concat(this.query[this.index]));
    // } else
    if (q === undefined) {
      //
      // Post an asynchronous query.
      //
      if (--this.rep < 0) return;
      if (this.query[this.index].length) {
        with ({ t : this })
          VCSH_Global.query.apply(null, [HOST].concat([function(x) { t.next(x); }], this.query[this.index]));
        return;
      }
      else q = ""; // empty string in response to an empty query
    } else if (typeof q == "object" && q instanceof xmlrpcresp) {
      //
      // Process the response to an asynchronous query.
      //
      var q = VCSH_Global.response(q);
    } else {
      var q = null;
    }
    if (q === null && this.index >= this.query.length - 1) return; // give up
    this.index = Math.min(this.index + 1, this.query.length - 1);
    if (q === null) continue; // try another query
    if (this.proto !== null && this.proto.prototype !== undefined &&
      typeof this.proto.prototype.toString == "function") {
      //
      // this.proto looks like a constructor, and it provides a toString() member function.
      // Use it to construct an object, hoping it will accept whatever the query gave us.
      //
//    if (q instanceof Array && typeof this.proto.prototype.merge == "function") {
//      for (var i=0, qq=null; i<q.length; i++)
//        qq = i ? qq.merge(new this.proto(q[i])) : new this.proto(q[i]);
//      if (qq === null) qq = new this.proto("");
//      q = qq.toXML();
//    } else
      if (q instanceof Array) for (var i=0; i<q.length; i++) q[i] = new this.proto(q[i]);
      else                                                   q    = new this.proto(q);
    }
    if (typeof this.transform == "function") {
      //
      // If q is an Array, then this.transform must be able to act on the entire Array.
      //
      q = this.transform(q, this.params);
//    if (q instanceof Array) for (var i=0; i<q.length; i++) q[i] = this.transform(q[i], this.params) + "\n";
//    else                                                   q    = this.transform(q,    this.params);
    } else {
      if (q instanceof Array) for (var i=0; i<q.length; i++) q[i] = String(q[i]) + "\n";
      else                                                   q    = String(q);
    }
    if (q instanceof Array) q = q.join("");
    this.x.innerHTML = q;
    //
    // Try to free up some memory for garbage collection.
    //
    q = null;
    break;
  }
  with ({ x : this }) x.timeout =
    x.interval > 0 ? setTimeout(function() { x.next(); }, x.interval) :
    x.interval < 0 ? setTimeout(function() { ShowPage_Load.pushUpdate('function', x.id, function(id) { x.next(); }); }, - x.interval) :
                     ShowPage_Load.pushUpdate('function', x.id, function(id) { x.next(); });
;
};

//
// ******************************************************************************************************
// *                                                                                                    *
// * Class ShowPage_AJAX_Query                                                                          *
// *                                                                                                    *
// * Fetch and display query results, one query after another, asynchronously (AJAX).                   *
// * Query results are fetched from the server by the VCSH_Global.query() function, using XML-RPC.      *
// * The query and its results are formatted and placed together inside a container <x id="id"> element.*
// * Subsequent queries and their results replace the contents of this container.                       *
// *                                                                                                    *
// * This class can handle any data that are served either:                                             *
// *   (1) as native Javascript data types, or                                                          *
// *   (2) as Javascript objects, with a constructor and a toString() member function.                  *
// *                                                                                                    *
// ******************************************************************************************************
//
ShowPage_AJAX_Query.prototype = new ShowPage_AJAX();
ShowPage_AJAX_Query.prototype.constructor = ShowPage_AJAX_Query;
ShowPage_AJAX_Query.superclass = ShowPage_AJAX.prototype;
//
// constructor
//
function ShowPage_AJAX_Query(server, query, id, interval, count, proto, transform, params) {
  //
  // transform : (function) transformation to convert objects to strings for display
  // params    : (object) associative array of parameters to pass to the transformation function
  //
  if (arguments.length == 0 || server === null) {
  } else if (typeof server != "object") {
    this.init.apply(this, arguments);
  } else if (server instanceof ShowPage_AJAX_Query) {
    this.init(server.server, server.query, server.id, server.interval,
      server.count, server.proto, server.transform, server.params);
  } else {
    this.init(server["server"], server["query"], server["id"], server["interval"],
      server["count"], server["proto"], server["transform"], server["params"]);
  }
}
//
// protected (initializer)
//
ShowPage_AJAX_Query.prototype.init = function(server, query, id, interval, count, proto, transform, params) {
  ShowPage_AJAX_Query.superclass.init.apply(this, arguments);
  this.transform = transform !== undefined ? transform : ShowPage.sprint; // function (optional)
  this.params    = params    !== undefined ? params    : {};              // object (optional)
};
//
// public
//
ShowPage_AJAX_Query.prototype.sprint = function() { return "ShowPage_AJAX_Query {" + this.toString() + "\n}"; };
ShowPage_AJAX_Query.prototype.toString = function() {
  var s = ShowPage_AJAX_Query.superclass.toString.call(this);
  if (this.transform !== undefined) s += "\n  [transform] => (" + typeof this.transform + ")";
  if (typeof this.params == "object") {
    s += "\n  [params]    => [";
    var and = " ";
    for (var i in this.params) { s+= and + i + " => '" + this.params[i] + "'"; and = ", "; }
    s += " ]";
  }
  return s;
};
ShowPage_AJAX_Query.prototype.next = function(q) {
  //
  // Fetch data and display the result.  Then schedule the next action.
  //
  for (;;) {
    // if (false) {
    //   //
    //   // Post a synchronous query and wait for the response.
    //   //
    //   if (--this.rep < 0) return;
    //   var q = VCSH_Global.query.apply(null, [HOST].concat(this.query[this.index]));
    // } else
    if (q === undefined) {
      //
      // Post an asynchronous query.
      //
      if (--this.rep < 0) return;
      if (this.query[this.index].length) {
        with ({ t : this })
          VCSH_Global.query.apply(null, [HOST].concat([function(x) { t.next(x); }], this.query[this.index]));
        return;
      }
      else q = ""; // empty string in response to an empty query
    } else if (typeof q == "object" && q instanceof xmlrpcresp) {
      //
      // Process the response to an asynchronous query.
      //
      var q = VCSH_Global.response(q);
    } else {
      var q = null;
    }
    var message = this.query[this.index][0];
    if (this.query[this.index].length > 1) {
      message += " (" + this.query[this.index][1];
      for (var i=2; i<this.query[this.index].length; i++) message += " " + this.query[this.index][i];
      message += ")";
    }
    message += ": ";
    if (q === null && this.index >= this.query.length - 1) return; // give up
    this.index = Math.min(this.index + 1, this.query.length - 1);
    if (q === null) continue; // try another query
    if (this.proto !== null && this.proto.prototype !== undefined &&
      typeof this.proto.prototype.toString == "function") {
      //
      // this.proto looks like a constructor, and it provides a toString() member function.
      // Use it to construct an object, hoping it will accept whatever the query gave us.
      //
//    if (q instanceof Array && typeof this.proto.prototype.merge == "function") {
//      for (var i=0, qq=null; i<q.length; i++)
//        qq = qq ? qq.merge(new this.proto(q[i])) : new this.proto(q[i]);
//      if (qq === null) qq = new this.proto("");
//      q = qq.toXML();
//    } else
      if (q instanceof Array) for (var i=0; i<q.length; i++) q[i] = new this.proto(q[i]);
      else                                                   q    = new this.proto(q);
    }
    if (typeof this.transform == "function") {
      //
      // If q is an Array, then this.transform must be able to act on the entire Array.
      //
      q = this.transform(q, this.params);
//    if (q instanceof Array) for (var i=0; i<q.length; i++) q[i] = this.transform(q[i], this.params);
//    else                                                   q    = this.transform(q,    this.params);
    } else {
      if (q instanceof Array) for (var i=0; i<q.length; i++) q[i] = String(q[i]);
      else                                                   q    = String(q);
    }
    if (q instanceof Array) q = q.join(", ");
    this.x.innerHTML = '<span class="subheading">' + message + '</span>' + q;
    //
    // Try to free up some memory for garbage collection.
    //
    q = null;
    break;
  }
  with ({ x : this }) x.timeout =
    x.interval > 0 ? setTimeout(function() { x.next(); }, x.interval) :
    x.interval < 0 ? setTimeout(function() { ShowPage_Load.pushUpdate('function', x.id, function(id) { x.next(); }); }, - x.interval) :
                     ShowPage_Load.pushUpdate('function', x.id, function(id) { x.next(); });
};

//
// ******************************************************************************************************
// *                                                                                                    *
// * Class ShowPage_AJAX_XSL                                                                            *
// *                                                                                                    *
// * Fetch and display query results, one query after another, asynchronously (AJAX).                   *
// * Objects are fetched from the server by the VCSH_Global.query() function, using XML-RPC.            *
// * These are passed through an XSL transformation and placed together inside a container <x id="id">. *
// * The results of subsequent queries replace the contents of this container.                          *
// *                                                                                                    *
// * This class can handle any objects that are served either:                                          *
// *   (1) as well-formed XML documents, or                                                             *
// *   (2) as Javascript objects, with a constructor and a toXML() member function.                     *
// *                                                                                                    *
// ******************************************************************************************************
//
ShowPage_AJAX_XSL.prototype = new ShowPage_AJAX();
ShowPage_AJAX_XSL.prototype.constructor = ShowPage_AJAX_XSL;
ShowPage_AJAX_XSL.superclass = ShowPage_AJAX.prototype;
//
// constructor
//
function ShowPage_AJAX_XSL(server, query, id, interval, count, proto, transform, params) {
  //
  // transform : (string) name of the XSL stylesheet, to transform XML into HTML for display
  // params    : (object) associative array of top-level parameters to pass to the XSL stylesheet
  //
  if (arguments.length == 0 || server === null) {
  } else if (typeof server != "object") {
    this.init.apply(this, arguments);
  } else if (server instanceof ShowPage_AJAX_XSL) {
    this.init(server.server, server.query, server.id, server.interval,
      server.count, server.proto, server.transform, server.params);
  } else {
    this.init(server["server"], server["query"], server["id"], server["interval"],
      server["count"], server["proto"], server["transform"], server["params"]);
  }
}
//
// protected (initializer)
//
ShowPage_AJAX_XSL.prototype.init = function(server, query, id, interval, count, proto, transform, params) {
  ShowPage_AJAX_XSL.superclass.init.apply(this, arguments);
  this.transform = transform !== undefined ? transform : "ccd";                          // string (optional)
  this.params    = { FRAGMENT : true, TRANSPORT : TRANSPORT, HOST : HOST, HOME : HOME }; // object (default)
  if (params !== undefined) for (i in params) this.params[i] = params[i];                // object (optional)
  this.xsldoc    = null;
  this.xslproc   = null;
};
//
// public
//
ShowPage_AJAX_XSL.prototype.sprint = function() { return "ShowPage_AJAX_XSL {" + this.toString() + "\n}"; };
ShowPage_AJAX_XSL.prototype.toString = function() {
  var s = ShowPage_AJAX_XSL.superclass.toString.call(this);
  if (this.transform !== undefined) s += "\n  [transform] => " + this.transform;
  if (typeof this.params == "object") {
    s += "\n  [params]    => [";
    var and = " ";
    for (var i in this.params) { s+= and + i + " => '" + this.params[i] + "'"; and = ", "; }
    s += " ]";
  }
  return s;
};
ShowPage_AJAX_XSL.prototype.reset = function() {
  if (ShowPage_AJAX_XSL.superclass.reset.call(this)) return true; // error
  if (navigator.appName.search(/Microsoft Internet Explorer/) != -1) {
    //
    // Load a new XSL stylesheet every time, because it seems we can set its parameters only once.
    //
  } else if (VCSH_Global.cache.xsldoc[this.transform] !== undefined) {
    this.xsldoc = VCSH_Global.cache.xsldoc[this.transform];
  } else if (this.xsldoc !== null) {
    VCSH_Global.cache.xsldoc[this.transform] = this.xsldoc;
  } else if (this.xsldoc === null && typeof this.transform == "string") {
    this.xsldoc = VCSH_Global.query(HOST, "Show XSL stylesheet", this.transform);
    if (this.xsldoc === null) return true; // error
    VCSH_Global.cache.xsldoc[this.transform] = this.xsldoc;
  }
  var opera = navigator.userAgent.match(/^Opera\/([0-9]+\.[0-9]+)/i);
  if (navigator.appName.search(/Microsoft Internet Explorer/) != -1) {
    //
    // this.xsldoc may be shared by several ShowPage_AJAX_XSL objects, so we postpone setting its parameters.
    //
  } else if (opera && parseFloat(opera[1]) < 9.50) {
    //
    // Opera used to have a bug that forces us to build a new XSLTProcessor every time (see below).
    //
  } else if (this.xslproc === null) {
    this.xslproc = new XSLTProcessor();
    this.xslproc.importStylesheet(this.xsldoc.documentElement);
    for (var i in this.params)
      this.xslproc.setParameter(null, i, ShowPage.numeric_html_entity_decode(this.params[i], 'all'));
  }
  return false;
};
ShowPage_AJAX_XSL.prototype.next = function(q) {
  //
  // Fetch data and display the result.  Then schedule the next action.
  //
  for (;;) {
    // if (false) {
    //   //
    //   // Post a synchronous query and wait for the response.
    //   //
    //   if (--this.rep < 0) return;
    //   var q = VCSH_Global.query.apply(null, [HOST].concat(this.query[this.index]));
    // } else
    if (q === undefined) {
      //
      // Post an asynchronous query.
      //
      if (--this.rep < 0) return;
      if (this.query[this.index].length) {
        with ({ t : this })
          VCSH_Global.query.apply(null, [HOST].concat([function(x) { t.next(x); }], this.query[this.index]));
        return;
      }
      else q = ""; // empty string in response to an empty query
    } else if (typeof q == "object" && q instanceof xmlrpcresp) {
      //
      // Process the response to an asynchronous query.
      //
      var q = VCSH_Global.response(q);
    } else {
      var q = null;
    }
    if (q === null) {
      // punt
    } else if (q.nodeType === 9 || q.nodeType === 11) {
      //
      // The object is already an XML DOM Document or DocumentFragment.
      //
    } else if (this.proto !== null && this.proto.prototype !== undefined &&
      typeof this.proto.prototype.toXML == "function") {
      //
      // this.proto looks like a constructor, and it provides a toXML() member function.
      // Use it to construct an object, hoping it will accept whatever the query gave us.
      // Then convert the object into an XML DOM Document, using its toXML() member function.
      //
      if (q instanceof Array && typeof this.proto.prototype.merge == "function") {
        for (var i=0, qq=null; i<q.length; i++) qq = i ? qq.merge(new this.proto(q[i])) : new this.proto(q[i]);
        if (qq === null) qq = new VCSH_CCData("");
        // alert(qq.toXML("xml")); // for debugging
        q = qq.toXML();
      }
      else if (q instanceof Array) for (var i=0; i<q.length; i++) q[i] = (new this.proto(q[i])).toXML();
      else                                                        q    = (new this.proto(q)).toXML();
    } else {
      q = null; // punt
    }
    if (q === null && this.index >= this.query.length - 1) return; // give up
    this.index = Math.min(this.index + 1, this.query.length - 1);
    if (q === null) continue; // try another query
    if (!(q instanceof Array)) q = [q];
    //
    // Use XSL to transform the XML DOM Document(s) into HTML fragment(s).
    // Replace the contents of "<x id="id"> with this/these.
    //
    while (this.x.firstChild) this.x.removeChild(this.x.firstChild);
    var opera = navigator.userAgent.match(/^Opera\/([0-9]+\.[0-9]+)/i);
    if (navigator.appName.search(/Microsoft Internet Explorer/) != -1) {
      //
      // Load a new XSL stylesheet every time, because it seems we can set its parameters only once.
      //
      this.xsldoc = VCSH_Global.query(HOST, "Show XSL stylesheet", this.transform);
      //
      // Reset parameters in this.xsldoc.
      //
      for (var i in this.params) ShowPage_AJAX_XSL.setParameter(this.xsldoc, "xsl", i,
        ShowPage.numeric_html_entity_decode(this.params[i],'all'));
      //
      // Transform the query results into HTML.
      //
      for (var i=0; i<q.length; i++) q[i] = q[i].transformNode(this.xsldoc)
        .replace(/^<!DOCTYPE[^>]*>/,                      "")
        .replace(/\s+/g,                                  " ")
        .replace(/\s+xmlns:\w+\s*=\s*('[^']*'|"[^"]*")/g, "");
//      q[i] = q[i].transformNode(this.xsldoc).replace(/^<!DOCTYPE[^>]*>/,"").replace(/\s+/g," ");
      //
      // Append the HTML fragment to this.x.
      // MSIE provides no innerHTML for elements that reject text-node children.
      // We must parse and insert the children of these elements one at a time.
      //
      if (this.x.tagName.search(ShowPage_AJAX_XSL.noInnerHTML) != -1)
        for (var i=0; i<q.length; i++) ShowPage_AJAX_XSL.parseElements(this.x, q[i]);
      else this.x.innerHTML = q.join("");
    } else if (opera && parseFloat(opera[1]) < 9.50) {
      //
      // Opera used to crash if it re-uses an XSLTProcessor that calls the generate-id() function.
      // Workaround:  build a new XSLTProcessor every time.
      //
      this.xslproc = new XSLTProcessor();
      this.xslproc.importStylesheet(this.xsldoc.documentElement);
      for (var i in this.params)
        this.xslproc.setParameter(null, i, ShowPage.numeric_html_entity_decode(this.params[i], 'all'));
      for (var i=0; i<q.length; i++) this.x.appendChild(this.xslproc.transformToFragment(q[i], window.document));
      this.xslproc = null;
    } else {
      //
      // Transform the query results into HTML, and append the HTML fragment to this.x.
      //
      for (var i=0; i<q.length; i++) this.x.appendChild(this.xslproc.transformToFragment(q[i], window.document));
    }
    //
    // Try to free up some memory for garbage collection.
    //
    q = null;
    break;
  }
  if (this.x.tagName.search(/^(th|td)$/i) != -1) {
    //
    // If thix.x is a table cell, then show its table row if and only if the row has at least one non-empty cell.
    // Note:  This is a bit of a hack.  We can find a better way to do this later.
    //
    var y = this.x.parentNode;
    if (y && y.nodeType === 1 && y.tagName.search(/^tr$/i) != -1) {
      var c = y.cells, show = false;
      for (var i=0; i<c.length; i++) {
        for (var z=c[i].firstChild; z; z=z.nextSibling)
          if ((z.nodeType != 3 && z.nodeType != 4) || z.data.search(/\S/) != -1) {
          //
          // Found a non-whitespace node.
          //
          show = true;
          break;
        }
        if (show) break;
      }
      y.style.display = show ? "" : "none"; // should not work on MSIE, but it does
    }
  } else if (this.x.style.display == "" || this.x.style.display.search(/^none$/i) != -1) {
    //
    // If x.style.display is either "" or "none," then show it if and only if it is non-empty.
    // Note:  This is a bit of a hack.  We can find a better way to do this later.
    //
    var show = false;
    for (var z=this.x.firstChild; z; z=z.nextSibling)
      if ((z.nodeType != 3 && z.nodeType != 4) || z.data.search(/\S/) != -1) {
      //
      // Found a non-whitespace node.
      //
      show = true;
      break;
    }
    this.x.style.display = show ? "" : "none"; // should not work on MSIE, but it does
  }
  with ({ x : this }) x.timeout =
    x.interval > 0 ? setTimeout(function() { x.next(); }, x.interval) :
    x.interval < 0 ? setTimeout(function() { ShowPage_Load.pushUpdate('function', x.id, function(id) { x.next(); }); }, - x.interval) :
                     ShowPage_Load.pushUpdate('function', x.id, function(id) { x.next(); });
};
//
// Regular expression to match HTML elements that reject text-node children.  MSIE provides no innerHTML for these.
//
ShowPage_AJAX_XSL.noInnerHTML = /^(col|colgroup|frameset|html|optgroup|select|style|table|tbody|tfoot|thead|title|tr)$/i;
//
// Parse CDATA sections, text and elements from HTML in e.
// Note:  This parser is painfully slow!
// To do: Parse other kinds of nodes too, as needed (comments, processing instructions, etc.).
//
ShowPage_AJAX_XSL.parseElements = function(x, e) {
  var c = false, i = 0, j = 0, r,
    y = [{ i : i, t : x.tagName, p : x.tagName.search(ShowPage_AJAX_XSL.noInnerHTML) == -1, x : x }];
  while (i < e.length) if (c) {
    //
    // End CDATA section.
    // If the present node accepts text, then it has an innerHTML parser that will take care of this.
    //
    c = false;
    r = e.indexOf("]]"+">",i);
    i = r == -1 ? e.length : r + 3;
  } else if ((r = e.indexOf("<",i)) == i && e.indexOf("<"+"![[CDATA",i) == i) {
    //
    // Start CDATA section.
    //
    c = true;
    i += 9;
  } else if (r > i || r == -1) {
    //
    // Parsed text.
    // If the present node accepts text, then it has an innerHTML parser that will take care of this.
    //
    i = r == -1 ? e.length : r;
  } else if (r != i) {
    //
    // This can't happen, but we test just in case.
    //
    break;
  } else if (r = e.substr(i).match(/^<\/([^\s>]+)>/)) {
    //
    // End-tag.
    // Note:  End-tags are optional for some elements; close any such elements also.
    //
    while (j > 0) {
      //
      // If the present node has an innerHTML parser but none of its ancestors do, then use its innerHTML.
      //
      if (y[j].p && !y[j-1].p) y[j].x.innerHTML = e.substring(y[j].i,i);
      if (j--, y.pop().t === r[1]) break;
    }
    i += r[0].length;
  } else if (r = e.substr(i).match(/^<([^\s>]+)([^>]*)\/>/)) {
    //
    // Start-tag; empty element (with no matching end-tag).
    // If none of the ancestors has an innerHTML parser, then append this element.
    //
    if (!y[j].p) {
      var z = window.document.createElement(r[1]);
      ShowPage_AJAX_XSL.parseAttributes(z, r[2]);
      y[j].x.appendChild[z];
    }
    i += r[0].length;
  } else if (r = e.substr(i).match(/^<([^\s>]+)([^>]*)>/)) {
    //
    // Start-tag; non-empty element (with possible matching end-tag).
    // If none of the ancestors has an innerHTML parser, then append this element.
    //
    var z = null;
    if (!y[j].p) {
      z = window.document.createElement(r[1]);
      ShowPage_AJAX_XSL.parseAttributes(z, r[2]);
      y[j].x.appendChild(z);
    }
    i += r[0].length;
    //
    // Push only those elements which may have end-tags.
    // Note whether this element or any of its ancestors has an innerHTML parser.
    //
    if (r[1].search(/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i) == -1)
      y.push({ i : i, t : r[1], p : y[j++].p || r[1].search(ShowPage_AJAX_XSL.noInnerHTML) == -1, x : z });
  } else {
    //
    // Parse failed; expected "<tag ... >", "<tag ... />" or "</tag>".
    //
    break;
  }
  x.normalize();
};
//
// Parse attributes of x from a.
//
ShowPage_AJAX_XSL.parseAttributes = function(x, a) {
  var a1 = a, a2 = "", r1, r2, r3;
  while (r1 = a1.match(/(\S+)(\s*)(.*)/)) {
    //
    // Append the next non-blank substring, hoping it completes either name="value" or name='value'.
    //
    a2 += r1[1];
    if ((r2 = a2.match(/^([a-zA-Z_][a-zA-Z0-9_:]*)\s*\=\s*('[^']*'|"[^"]*")$/)) &&
      !((r3 = r2[2].match(/(\\+)['"]$/)) && r3[1].length % 2)) {
      //
      // a2 has the form name="value" or name='value' and the final quote is not backslash-escaped.
      //
      x.setAttribute(r2[1], r2[2].substr(1,r2[2].length-2));
      a2 = ""
    } else {
      //
      // More tokens are needed to complete a2 as name="value" or name='value'.
      //
      a2 += r1[2];
    }
    a1 = r1[3];
  }
};
//
// Emulate W3C capabilites for MSIE, as needed above.
//
ShowPage_AJAX_XSL.setParameter = function(x, namespace, name, value) {
  //
  // Edit the XSL document x to emulate XSLTProcessor.setParameter().
  // Set the value of every instance of <xsl:param> name to value.
  // In this instance, namespace refers to the prefix, and is used only if needed.
  // Question:  Should we set every instance, or only those found in the global context?
  //
  var p = x.getElementsByTagName("param");
  if (namespace && p.length == 0) p = x.getElementsByTagName(namespace + ":" + "param");
  for (var j=0; j<p.length; j++)
    if (p.item(j).nodeType === 1 && p.item(j).getAttribute("name") == name) {
//  if (p.item(j).hasAttribute("select")) //***** Why does MSIE object to this query?
      p.item(j).removeAttribute("select");
    if (p.item(j).firstChild && p.item(j).firstChild.data) p.item(j).firstChild.data = value;
    else p.item(j).appendChild(x.createTextNode(value));
  }
};
