// Copyright 2004, 2005 Crispin Perdue
// cris@perdues.com
// 
// This software is released under the terms of the
// GNU General Public License version 2

// Names defined: IW_Hash, dojo, agent, isKHTML, undef, iframe, iw_libs, require, registerLibraries, sqQuotables, description, iw_depth, iw_recur, iw_limit, description1, element, ELEMENT_NODE, TEXT_NODE, isElement, isText, outerHTML, setOuterHTML, changeTag, first, realNode, tagNode, firstE, nextE, firstText, registerLibrary, iw_expanders, iw_expanderTags, Expander, normalizeBindings, expandAll, spliceTables, findBindings, check, log, bkpoint, miniEval, trim, htmlEncode, replaceAll, describeThis, objectToString, contains, dojoConnect, handlerNodes, connect, callHandlers, deregister, handleNewContent, init_expand, setupExpand, dummy1, dummy2, dummy3

function IW_Hash() {
  // Little more than an Object.
}

// Dojo toolkit root object; will be defined if any part of Dojo
// is loaded.
var dojo;

var agent = navigator.userAgent;

var isKHTML = agent.match(/KHTML/);

// An undefined value that IE 5 allows me to compare.
var undef;

function iframe(name, src) {
  document.write
    ('<iframe name="'+name+'" src="'+src+'" width=100 height=0\n'
     +' style="border-style: none; visibility: hidden; overflow: hidden;\n'
     +' position: absolute; left: 0px; top: 0px;"></iframe>\n');
}

// List of libraries for the current window
var iw_libs = []

// Request use of a library file.  Preferably these should be in the
// same directory as the main file so links refer to something in the
// source code.
//
function require(src) {
  if (!contains(iw_libs, src)) {
    iframe(src, src);
    iw_libs.push(src);
  }
}

// Register all libraries that have been loaded via "require".
// 
function registerLibraries() {
  // Register loaded libraries.
  for (var i=0; i<iw_libs.length; i++) {
    var w = frames[iw_libs[i]];
    check(w, "No frame ", iw_libs[i]);
    if (!w) continue;
    lib = w.document.getElementById("library");
    check(lib, "No library in "+iw_libs[i]);
    registerLibrary(lib);
  }
}

// Characters to quote in single-quoted regexprs
var sqQuotables = new RegExp("([\\\\'])", "g");

// Returns a string that can be evaluated as a literal to reproduce
// the given value.  This has specific smarts about null, undefined,
// primitive types, Dates, and Arrays.  Other Objects are described
// as object literals with descriptions of the values of their
// properties.
//
// Descriptions of objects as object literals includes only "own"
// properties (not prototype's properties), also ignores properties
// that have an undefined or function value.  The support for objects
// here is for using them as key-value hashes rather than an object
// persistence mechanism.  Specific classes such as Date can receive
// more sophisticated treatment.
//
// If showAll is true, this displays values that may not be possible
// to read back in as literals.
//
// In IE, many built-i objects have no prototype initially, and in IE5
// there apparently is no hasOwnProperty method to test for
// inheritance of a property (for example, from the prototype).
//
function description(v, showAll, limit) {
  iw_limit = limit==undef ? (limit) : 3;
  return description1(v, showAll);
}

// Current depth.
var iw_depth = 0;

// Set to true when calling v.toString, to sniff for infinite
// recursion.
var iw_recur = false;

// Levels of nesting of Array and object displays.
// If when >= depth, no display or array or object internals.
var iw_limit;

function description1(v, showAll) {
  // Save and later restore iw_depth;
  var depth = iw_depth;

  try {

    if (v===undef) {
      return "undefined";
    }
    if (v===null) {
      return "null";
    }
    if (typeof(v)=="boolean" || typeof(v)=="number"
        || v instanceof Boolean || v instanceof Number) {
      return v.toString();
    }
  
    if (typeof(v)=="string" || v instanceof String) {
      // Replacing by $& fails in IE 5.0
      var v1 = v.replace(sqQuotables, "\\$1"); 
      v1 = v1.replace(/\n/g, "\\n");
      v1 = v1.replace(/\r/g, "\\r");
      // Any other important special cases?
      return "'"+v1+"'";
    }
  
    if (v instanceof Date) {
      return "new Date("+d.getFullYear+","+d.getMonth()+","+d.getDate()+")";
    }
  
    var d;
    if (v instanceof Array || v.push) {
      // "push" test needed for KHTML, don't know why.

      if (depth>=iw_limit)
        return "[ ... ]";

      d = "[";
      var first = true;
      iw_depth++;
      for (var i=0; i<v.length; i++) {
        // Skip functions and undefined values
        // if (v[i]==undef || typeof(v[i])=="function")
        //   continue;
        if (first) {
          first = false;
        } else {
          d += ",";
        }
        // if (v[i]!=undef) {
          d+=description1(v[i], showAll);
        // }
      }
      return d+"]";
    }

    if (v.constructor==IW_Hash || v.constructor==Object
        || v.toString==describeThis) {
      if (depth>=iw_limit)
        return "{ ... }";

      // Instanceof Hash is good, or if we just use Objects,
      // we can say v.constructor==Object.
      // IE (5?) lacks hasOwnProperty, but perhaps objects do not always
      // have prototypes??
      if (typeof(v.hasOwnProperty)!="function" && v.prototype) {
        throw new Error("description: "+v+" not supported by script engine");
      }
      var first = true;
      d = "{";
      if (showAll && v.constructor) {
        var nm = v.constructor.name;
        if (nm /*typeof(nm)=="string"*/) {
          d += nm;
        }
      }
      iw_depth++;
      for (var key in v) {
        // Skip values that are functions or undefined.
        if (v[key]==undef || typeof(v[key])=="function")
          continue;
        if (first) {
          first = false;
          // For showAll mode:
          if (d.length>1) { d += " "; }
        } else {
          d += ", ";
        }
        kd = key;
        // If the key is not a legal identifier, use its description.
        // For strings this will quote the stirng.
        if (!kd.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
          kd = description1(key, showAll);
        }
        d += kd+": "+description1(v[key], showAll);
      }
      return d+"}";
    }

    if (showAll) {
      if (iw_recur) {
        return objectToString.apply(v, []);
      } else {
        iw_recur = true;
        return v.toString();
      }
    } else {
      // log("Description? "+v.toString()+", "+typeof(v));
      throw new Error("Unknown type: "+v);
      return "'unknown'";
    }

  } finally {
    // Always restore the global current depth.
    iw_depth = depth;
  }

}

// Get an element by ID.
//
function element(id) {
  if (isElement(id)) {
    return id;
  } else if (!id.match(/[_a-zA-Z][_a-zA-Z0-9]*/)) {
    // If it isn't an ID evaluate it.
    return window.eval(id);
  } else {
    var value = document.getElementById(id);
    if (value && value.id==id) {
      // IE will use the "name" attribute if no element with ID.
      return value;
    } else {
      return null;
    }
  }
}

var ELEMENT_NODE = 1;

var TEXT_NODE = 3;

// The object is an element node.
//
function isElement(node) {
  return node && node.nodeType==ELEMENT_NODE;
}

// The object is a text node.
//
function isText(node) {
  return node && node.nodeType==TEXT_NODE;
}

// Returns the "outer" HTML of a node, like the element
// property in IE.
//
function outerHTML(node) {
  if (isText(node))
    return node.nodeValue;

  var tag = node.tagName;
  var newtag = tag=="TD" ? "TR" : tag=="TR" ? "TBODY" : "DIV";
  var frag = document.createElement(newtag);
  frag.appendChild(node.parentNode ? node.cloneNode(true) : node);
  return frag.innerHTML;
}

// Replaces the target  with one (or more) created from the given HTML string.
// The "defer" argument is true if changes will be handled elsewhere.
//
function setOuterHTML(target, html, defer) {
  var elt = element(target);
  var p = elt.parentNode;
  var nu, old;
  // It looks like in Safari the in-place method affects some
  // surrounding HTML, and doesn't help here anyway.
  if (false && elt.outerHTML) {
    var prev = elt.previousSibling;
    // Change elt to a DEL in place.
    elt.outerHTML = "<del>"+html+"</del>";
    var div = prev ? prev.nextSibling : p.firstChild;
    old = div;
  } else {
    // Create a new DIV outside the current tree
    var div = document.createElement("div");
    div.innerHTML = html;
    old = elt;
  }
  if (!defer) {
    handleNewContent(div);
  }
  // Remove old content, which may have IDs and such.  When
  // expanding macroformats this is frequent.
  old.innerHTML = "";
  old.id = null;
  // Removes each child from the div and inserts before the
  // old location.
  while (nu = div.firstChild) {
    p.insertBefore(nu, old);
  }
  // Remove the old.
  p.removeChild(old);
  if (!isKHTML) {
    // Deletes initial text in Safari.
    p.normalize();
  }
}

// Change the tag.  This uses DOM operations to avoid
// possible inaccuracies in innerHTML. (KHTML PRE)
// 
function changeTag(old, nu) {
  var a = old.attributes;
  for (var i=0; i<a.length; i++) {
    var attr = a.item(i);
    if (attr.specified) {
      if (attr.name) {
	if (!nu.getAttribute(attr.name)) {
	  nu.setAttribute(attr.name, attr.value);
	}
      } else {
	if (!nu.getAttribute(attr.nodeName)) {
	  // Should work for all browsers including IE
	  // using nodeName and nodeValue.
	  nu.setAttribute(attr.nodeName, attr.nodeValue);
	}
      }
    }
  }
  var child;
  while (child = old.firstChild) {
    nu.appendChild(child);
  }
  old.parentNode.replaceChild(nu, old);
}

// First text or element child node of the node.
//
function first(node) {
  return isElement(node) && realNode(node.firstChild);
}

// Find the first "interesting" DOM node starting at the given one.
// Interesting nodes are elements and text nodes containing
// non-whitespace.
//
function realNode(node) {
  for (; node; node=node.nextSibling) {
    if ((isText(node))) {
      var text = node.nodeValue.match(/\S+/);
      if (text) {
        return node;
      }
    } else if(isElement(node)) {
      return node;
    }
  }
  return null;
}

// Returns the first sibling of the giving node that is an element,
// starting with the node itself.
//
function tagNode(node) {
  for (; node; node=node.nextSibling) {
    if (isElement(node)) {
      return node;
    }
  }
  return null;
}

// First element node child of a node.
//
function firstE(node) {
  return isElement(node) && tagNode(node.firstChild);
}

// Next sibling element node following a node.
//
function nextE(node) {
  return node.nodeType && tagNode(node.nextSibling);
}

// Returns the "first text" within a node. If the first text or
// element child is a text node, it is the trimmed text within that
// node, otherwise it is the empty string.
//
function firstText(node) {
  var t = first(node);
  return isText(t)
    ? trim(t.nodeValue)
    : "";
}

// Registers elements of the target element as library objects such as
// template expanders.  The target should be a UL and the definitions
// are its LI elements, each with an initial word indicating the type
// of definition.
//
// An expander is represented by "expand <name>" followed by an
// element containing the expansion.  Optionally there is another
// following element with default bindings in the same format sa a
// template invocation.
// 
function registerLibrary(target) {
  if (!target)
    target = "library";
  var elt = element(target);
  if (!elt)
    return;
  check(elt.tagName=="UL","Library should be a UL");
  for (var li=firstE(elt); li; li=nextE(li)) {
    var tx = firstText(li);
    var m = tx.match(/^(\w+)\s+(\w+)$/);
    check(m, "Bad library defn syntax: ", tx);
    var key = m[1];
    var name = m[2];
    switch (key) {
    case "expand":
      iw_expanders[name] = new Expander(name, li);
      break;
    default:
      log("Unknown definition type: "+key);
    }
  }
}

// A Hash mapping from a template name to an Expander object.
// 
var iw_expanders = new IW_Hash;

// A Hash mapping from a template name to the expected tag for
// invocations.
var iw_expanderTags = new IW_Hash;

// Class for handling template expansion.
// Provides a bit of abstraction for remembering
// default bindings and potentially the innerHTML
// of the template.
//
function Expander(name, li) {
  var self = this;
  var defaults;
  var dfn = firstE(li);
  this.name = name;

  // Finds and returns the variable bindings for an invocation of this
  // template.  Merges in default bindings.
  //
  function computeBindings(e) {
    if (!defaults) {
      // Defaults are not yet defined.
      var e2 = nextE(dfn);
      if (e2) {
        // Note that the sample tag can macroexpand.
        iw_expanderTags[name] = e2.tagName;
      }
      self.defaults = defaults = e2
        ? findBindings(e2)
        : new IW_Hash;
    }
    return findBindings(e, defaults);
  }

  // Expand the given element using this expander and return the
  // expansion, a string.  Unbound expander variables expand to the
  // empty string.
  //
  // Precede the trailing "_" with a space if it is the only content
  // of an attribute value, so IE will enclose it in quotes.
  // 
  // Unless "iw" has a binding, names of the form "_iw_xxx" expand to
  // xxx, which is useful for naming attributes within macro bodies.
  // For example "_iw_foo" expands to "foo".  Don't give a binding to
  // _iw_ or this trick will fail!
  //
  function expansion(elt) {
    var b = computeBindings(elt);
    normalizeBindings(b);
    var tpl = dfn.innerHTML;
    var newhtml = replaceAll
      (tpl, /_([a-zA-Z_]\w*)_/,
       function(all, match) {
        return b[match] || "";
       });
    // alert(name+" => "+newhtml.slice(0, 30));
    return newhtml;
  }

  // Compute bindings for the example to get defaults and identify the
  // tag.
  computeBindings(dfn);

  // Shadowing vars for debugging
  this.dfn = dfn;
  this.defaults = defaults;

  // Methods
  this.expansion = expansion;
  this.toString = describeThis;
}

// Helper for "expansion".
// 
// Converts all values in the bindings to strings of legitimate HTML.
// Elements bind to their innerHTML  and strings get HTML-escaped.
//
function normalizeBindings(bindings) {
  // Normalize all values to strings.
  for (var k in bindings) {
    var v = bindings[k];
    if (v instanceof Array) {
      var b = '';
      for (var i=0; i<v.length; i++) {
        // TODO: At this spot, support delimiters.
        b += v[i].innerHTML;
      }
      bindings[k] = b;
    } else {
      // Unknown issue w IE here if lacking .toString.
      // The v can be an object, looks like a style decl.
      bindings[k] = htmlEncode(v.toString());
    }
  }
}

// Repeatedly expands all children of a given element until there are
// no more expandable templates present.  The node defaults to
// document.body.
//
function expandAll(ancestor, tag) {
  if (!ancestor)
    ancestor = document.body;
  tag = tag || "ins";

  // NOTE: relies on liveness and ordering of the elements list.
  var i=0;
  var all = ancestor.getElementsByTagName(tag);
  while (i<all.length) {
    var elt = all[i];
    var type = elt.className;
    // alert(outerHTML(elt).slice(0, 40)+" => "+type);
    if (type) {
      check(iw_expanders[type], type, " has no expander");
      if (iw_expanders[type]) {
        setOuterHTML(elt, iw_expanders[type].expansion(elt), true);
        // The result may re-expand, so don't advance.
        continue;
      }
    }
    i++;
  }
  spliceTables(ancestor);
}

// Below the given node we are looking for table nodes with the
// "level" attribute.  The level attribute contains a tag name.  The
// children at the level with that tag name will replace the nearest
// ancestor of the table having the same tag.  And they will all live
// happily ever after.
//
function spliceTables(node) {
  var a = node.getElementsByTagName("table");
  var targets = [];
  for (var i=0; i<a.length; i++) {
    // Key invariant: this loop never changes the linear ordering of
    // table nodes in the tree.  Rows or cells move to *after* the
    // table they came from, and they stay in sequence, and tables
    // with moved children retain no table descendents.  Tables may
    // temporarily re-order while moving one group of elements to
    // their new level.
    var table = a[i];
    var level = table.getAttribute("level");
    if (!level) continue;

    // Now table is a table to splice out.
    level = level.toUpperCase();
    if (!contains(["TBODY", "TR", "TD"], level)) {
      check(false, "Table level given as "+level);
      continue;
    }
    // Find children at the right level
    var childList = level=="TBODY"
      ? table.tBodies
      : level=="TR"
      ? table.rows
      : table.rows[0].cells;
    // Find parent at the same level
    for (var target=table.parentNode;
         !(target==node || target.tagName==level);
         target=target.parentNode) {
    }
    if (target.tagName==level) {
      // We found the target node.
      // Now remember it for later.
      targets[targets.length] = target;
      // Move the child nodes.
      while (childList.length>0) {
        target.parentNode.appendChild(childList[0]);
      }
    } else {
      // In a library definition there will be no such parent, so don't alert ...
      // log("No ", level, " parent for table ", outerHTML(table));
    }
  }
  // Clean up the destination nodes.
  for (var i=0; i<targets.length; i++) {
    var e = targets[i];
    if (e.parentNode) {
      // The same element may appear in targets more than once,
      // so it may already be removed.
      e.parentNode.removeChild(e);
    }
  }
}

// Helper for computeBindings
// 
// Returns bindings for the given "inline constructor" (invocation of
// an expander) Uses all attributes, plus child elements with "iwName"
// or "class" attribute.  For child elements the value of the iwName
// attribute indicates the attribute name to use in calls.  The
// attribute value or child element contents become the binding
// values.  Child elements always result in an array of DOM elements,
// attributes always result in a string.
//
// If any defaults are supplied, this will only search the
// element attributes for those.  Does not apply to values
// supplied through children are found regardless.
//
// The element itself is bound bound by default to "body", so _body_
// can expand to the children of the main element.
//
function findBindings(elt, defaults) {
  var a = elt.attributes;
  var result = new IW_Hash;
  // Bind immediate child elements regardless of defaults.
  for (var child=firstE(elt); child; child=nextE(child)) {
    var arg = child.getAttribute("iwName") || child.className;
    if (arg) {
      // Push the child onto a node list.
      value = result[arg] || new Array;
      value[value.length] = child;
      result[arg] = value;
    }
  }
  // Default the body if not supplied explicitly.
  if (result.body==null) {
    result.body = [elt];
  }
  if (defaults) {
    // Iterate over the known keys.
    for (var k in defaults) {
      // Skip any already-given values.
      if (result[k]) continue;
      var v = elt.getAttribute(k);
      // NOTE: Only applies if attr is nonempty ...
      // Null versus empty can be tricky.
      result[k] =  v ? (v) : defaults[k];
    }
  } else {
    // Iterate over all the attributes, which can be many in IE.
    for (var i=0; i<a.length; i++) {
      var attr = a.item(i);
      if (attr.specified) {
        result[attr.nodeName] = attr.nodeValue;  // TODO: normalize?
      }
    }
  }
  return result;
}

// Input checker and logger; this implementation alerts and throws.
// Replace this for alternative behaviors.
//
function check(test, message) {
  if (test)
    return;
  var m = message;
  for (var i=2; i<arguments.length; i++) {
    m += arguments[i];
  }
  log("Check failed: ", m);
  bkpoint(m);
}

// Message logger; really takes any number of arguments.  Replace this
// for alternative behaviors.
//
function log(message) {
  // try { throw new Error(); } catch(err) {}
  var m = message;
  for (var i=1; i<arguments.length; i++) {
    m += arguments[i];
  }
  alert(m);
  /*
  if (!miniEval("Log: "+m, "")) {
    // throw new Error("User Abort");
  }
  */
}

// Place one of these as a breakpoint.  It logs a message using log
// if Venkman (Moz) is set to trap on thrown exceptions.
//
function bkpoint(message) {
  try { throw new Error(message); } catch(err) {}
}

// Puts up the prompt and message.  If you enter an expression,
// loops evaluating expressions.  Otherwise returns true unless
// the user ended the session by pressing "Cancel".
// 
function miniEval(_msg, _x) {
  // x='document.location';
  // p='Expression:';
  _x = _x || "";
  var _p = "";
  while(_x=prompt(_msg+"\n"+_p,_x)) {
    _start=new Date().getTime();
    try {
      var _v=eval(_x);
      var _type=typeof(_v);
    } catch(ex) {_v=ex; _type='';}
    var _end=new Date().getTime();
    _p=_type+' ('+(_end-_start)+'ms): '+_v;
  }
}

// Returns leading and trailing whitespace from text
//
function trim(text) {
  // Whole string is space followed by anything followed by space,
  // with the anything as small as possible.
  return isKHTML
    ? text.match(/^\s*([\x00-\xff]*?)\s*$/)[1]
    : text.match(/^\s*([\u0000-\uffff]*?)\s*$/)[1];
}

// Encodes text so it can be inserted anywhere in HTML without
// being interpreted as HTML itself.  
// Taking innerHTML loses whitespace in IE!
// So textarea values in particular would not be quite right.
//
function htmlEncode(text) {
  var v = text.replace(/&/g, "&amp;");
  v = v.replace(/</g, "&lt;");
  v = v.replace(/>/g, "&gt;");
  v = v.replace(/\"/g, "&quot;");
  // TODO: and single quote.
  return v;
}

// A version of global replace, quite a bit like
// target.replace(pattern, func), but built just using String.match.
// The function receives the matching substrings as arguments, but not
// the index and entire string.  KHTML and Safari don't have
// this built in.
//
function replaceAll(target, pattern, func) {
  var result = "";
  if (isKHTML) {
    // Make the pattern be not global.
    var flags = "";
    if (pattern.ignoreCase)
      flags += "i";
    if (pattern.multiline)
      flags += "m";
    var html = target;
    var m;
    while (m = html.match(pattern)) {
      var prefix = html.slice(0, m.index);
      result += prefix + func.apply(undef, m);
      html = html.slice(m.index+m[0].length);
    }
    result += html;
  } else {
    // For browsers with global replace functional argument.
    // Make the pattern "global".
    var flags = "g";
    if (pattern.ignoreCase)
      flags += "i";
    if (pattern.multiline)
      flags += "m";
    result = target.replace(new RegExp(pattern.source, flags), func);
  }
  return result;
}

// Function that can be added to change default printing
// of an object.
//
function describeThis() {
  return description(this, true);
}

// Save the original definitions of toString;
var objectToString = Object.prototype.toString;

// Returns true if the value is == to some element of the array,
// else false.
//
function contains(array, value) {
  for (var i=0; i<array.length; i++) {
    if (array[i]==value)
      return true;
  }
  return false;
}

var dojoConnect = dojo && dojo.event && dojo.event.connect;

// Hash that maps from event name (e.g. "click") to array of
// DOM elements with handlers set up for that event.
// This registry provides access to all handlers set up
// through "connect".  (Needed when no dojoConnect.)
var handlerNodes = new Object;

// Cross-platform event handler adder.
//
var connect = dojoConnect
  ? function(node, evname, fn) {
    return dojo.event.connect(node, evname, fn);
  }
  : function(node, evname, fn) {
    if (node==window) {
      // alert("event="+evname+", fn="+fn);
    }
    var add = function(array, x) { if (!contains(array, x)) array.push(x)}
    var oldhandler = node[evname]!=callHandlers ? node[evname] : null;
    var listname = evname+"__handlers";
    node[listname] = node[listname] || new Array;
    if (oldhandler) {
      node[listname].push(oldhandler);
    }
    add(node[listname], fn);
    node[evname] = callHandlers;
    // Register that this node handles this event.
    var h = handlerNodes[evname] = handlerNodes[evname] || new Array;
    add(h, node);
    // Remember to de-register handlers when done.
    if (node!=window) {
      connect(window, "onunload", deregister);
    }
  };

// Registered as an event handler, calls all handlers 
// registered through "connect" for its node.  The event
// needs to exist and have an appropriate "type" property.
//
function callHandlers(event) {
  if (!event)
    event = window.event;
  var wantRestore = false;
  var tgt = event.currentTarget;
  if (!tgt) {
    event.currentTarget = this;
    wantRestore = true;
  }
  event.preventDefault = function() {this.prevented = true;}
  var a = this["on"+event.type+"__handlers"];
  check(a, "No handlers for ", this, " ", event.type);
  try {
    for (var i=0; i<a.length; i++) {
      this.__handler = a[i];
      var v = this.__handler(event);
    }
  } finally {
    if (wantRestore) {
      event.currentTarget = tgt;
    }
    // Clean out the object reference.
    this.__handler = null;

    var prevented = event.prevented;
    // Special case:
    if (event.type=="mouseover")
      prevented = !prevented;
    // Return value as appropriate.
    return prevented ? false : true;
  }
}

// To be set up as a window onunload handler, this walks the handler
// registry removing all handlers set up through "connect".
// 
function deregister() {
  for (var evname in handlerNodes) {
    var a = handlerNodes[evname];
    var property = evname+"__handlers";
    for (var i=0; i<a.length; i++) {
      a[i][property] = null;
    }
  }
}

function handleNewContent() {}

// Register relevant stuff, and expand if this has libraries
// (or is in a top-level window, to help developers)

var init_expand = connect(window, "onload", setupExpand);

function setupExpand() {
  if (iw_libs.length>0 || window==top) {
    registerLibraries();
    registerLibrary();
    // Will be list of tags to expand.
    var expanders = [];
    for (var k in iw_expanderTags) {
      var tag = iw_expanderTags[k];
      if (!contains(expanders, tag)) {
	expanders.push(tag);
      }
    }
    if (isKHTML) {
      // Safari does not escape chars such as "<" to
      // innerHTML of PRE tags, which results in information
      // loss, so we immediately change to them to DIV.
      var pretags = document.getElementsByTagName("pre");
      while (pretags.length) {
	var pre = pretags[0];
	var nu = document.createElement("div");
	changeTag(pre, nu);
	nu.style.cssText += " white-space: pre;";
      }
    }
    for (var i=0; i<expanders.length; i++) {
      expandAll(element('iw_mainBody') || document.body, expanders[i]);
    }
  }
}

var dummy1 = Object.prototype.toString = describeThis;

var dummy2 = Array.prototype.toString = describeThis;

var dummy3 = IW_Hash.prototype.toString = describeThis;

