function dom() {
  var jsonStack = new Array();
  var unreserved = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.~";
  var reserved = "!*'();:@&=+$,/?%#[]";
  var allowed = unreserved + reserved;
  var hexchars = "0123456789ABCDEFabcdef";
  var docReadyProcId;
  var docReadyEvent;

  this.isReady = false;

  this.tagName = function(o) {
    if (!o.tagName) return;
    return this.removens(o.tagName.toUpperCase());
  }

  this.removens = function(s) {
    if (s.indexOf(':') == 1) return s;
    return s.substring(s.indexOf(':') + 1);
  }

  this.cancelBubbling = function(e) {
    if (ivy.browser.isIE) {
      e.returnValue = false;
      e.cancelBubble = true;
    } else {
      try {
        e.preventDefault();
        e.stopPropagation();
      } catch (ex) { }
    }
  }
  
  this.apply = function(o, p) {
    for (var i in p) o[i] = p[i];
  }

  this.attachEvent = function(o, e, f) {
    if (window.addEventListener) {
      switch (e) {
        case 'propertychange':
          e = 'DOMAttrModified';
          break;
      }
      o.addEventListener(e, f, false);
    } else {
      o.attachEvent('on' + e, f);
    }
  }

  this.detachEvent = function(o, e, f) {
    if (window.removeEventListener) {
      o.removeEventListener(e, f, false);
    } else {
      o.detachEvent('on' + e, f);
    }
  }

  this.gethex = function(d) {
    return "%" + hexchars.charAt(d >> 4) + hexchars.charAt(d & 0xF);
  }

  this.encode = function(decoded, charset) {
    var encoded = "";

    if (charset == "ascii") {
      var notascii = "";
      for (var i = 0; i < decoded.length; i++) {
        var ch = decoded.charAt(i);
        if (unreserved.indexOf(ch) != -1) {
          encoded = encoded + ch;
        } else {
          var charcode = decoded.charCodeAt(i);
          if (charcode < 128) {
            encoded = encoded + gethex(charcode);
          } else {
            encoded = encoded + ch;
            notascii = notascii + ch + " ";
          }
        }
      }
      return encoded;
    }

    if (charset == "utf8") {
      for (var i = 0; i < decoded.length; i++) {
        var ch = decoded.charAt(i);
        if (unreserved.indexOf(ch) != -1) {
          encoded = encoded + ch;
        } else {
          var charcode = decoded.charCodeAt(i);
          if (charcode < 128) encoded = encoded + gethex(charcode);
          if (charcode > 127 && charcode < 2048) {
            encoded = encoded + gethex((charcode >> 6) | 0xC0);
            encoded = encoded + gethex((charcode & 0x3F) | 0x80);
          }
          if (charcode > 2047 && charcode < 65536) {
            encoded = encoded + gethex((charcode >> 12) | 0xE0);
            encoded = encoded + gethex(((charcode >> 6) & 0x3F) | 0x80);
            encoded = encoded + gethex((charcode & 0x3F) | 0x80);
          }

          if (charcode > 65535) {
            encoded = encoded + gethex((charcode >> 18) | 0xF0);
            encoded = encoded + gethex(((charcode >> 12) & 0x3F) | 0x80);
            encoded = encoded + gethex(((charcode >> 6) & 0x3F) | 0x80);
            encoded = encoded + gethex((charcode & 0x3F) | 0x80);
          }
        }
      }
      return encoded;
    }
  }

  this.copyToClipboard = function(s) {
    if (window.clipboardData && clipboardData.setData) {
      clipboardData.setData('Text', s);
    } else {
      netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
      var clip = Components.classes['@mozilla.org/widget/clipboard;1'].createInstance(Components.interfaces.nsIClipboard); if (!clip) return;
      var trans = Components.classes['@mozilla.org/widget/transferable;1'].createInstance(Components.interfaces.nsITransferable); if (!trans) return;
      trans.addDataFlavor('text/unicode');
      var str = new Object();
      var len = new Object();
      var str = Components.classes['@mozilla.org/supports-string;1'].createInstance(Components.interfaces.nsISupportsString);
      var copytext = s;
      str.data = copytext;
      trans.setTransferData('text/unicode', str, copytext.length * 2);
      var clipid = Components.interfaces.nsIClipboard;
      if (!clip) return false;
      clip.setData(trans, null, clipid.kGlobalClipboard);
    }
  }

  this.rollOver = function(o) {
    var o = this.getObject(o);
    if (!o) return;
    var dim = ivy.box.createRolloverDiv(o, ivy.box.defaultRollOverProperties);
    alert(this.json(dim));
  }

  this.setFlag = function(o) {
    jsonStack.push(o);
    o.stackFlag = true;
  }

  this.whiteSpace = function(level, c) {
    var s = '';
    for (j = 0; j < (level - 1) * 2; j++) s += ' ';
    return s + (c || '');
  }

  this.json = function(o, depth, level) {
    var o = this.getObject(o);
    if (!o) return;
    if (o.stackFlag) return '<Recursive Pointer>';
    level = (level || 0) + 1;
    this.setFlag(o);
    var s = this.whiteSpace(level, '{') + '\n';
    var first = true;
    for (var i in o) {
      if (i != 'stackFlag') {
        var val = this.objectValue(o[i], depth, level);
        if (val == '') val = null;
        if (!first) s += ',\n';
        s += this.whiteSpace(level + 1);
        s += '"' + i + '": ' + val;
        first = false;
      }
    }
    if (level == 1) {
      do {
        var x = jsonStack.pop();
        delete x.stackFlag;
      } while (jsonStack.length != 0);
    }
    if (s.charAt(s.length - 1) != '\n') s += '\n';
    return s + this.whiteSpace(level) + '}';
  }

  this.objectValue = function(o, depth, level) {
    var t = this.getObjectClass(o);
    var s = '';
    switch (t) {
      case 'Array':
        s += '[';
        for (var i = 0; i < o.length; i++) {
          if (i != 0) s += ', ';
          s += this.objectValue(o[i], depth, level + 1);
        }
        if (s.charAt(s.length - 1) == '}') {
          s += '\n' + this.whiteSpace(level + 1, ']');
        } else {
          s += ']';
        }
        break;
      case 'String':
        s += '"' + o + '"';
        break;
      default:
        if (typeof o == 'object' && (!depth || level < depth)) {
          s += '\n' + this.json(o, depth, level);
        } else {
          s += o;
        }
        break;
    }
    return s;
  }

  this.getObjectClass = function(obj) {
    if (obj && obj.constructor && obj.constructor.toString) {
      var arr = obj.constructor.toString().match(/function\s*(\w+)/);
      if (arr && arr.length == 2) return arr[1];
    }
  }
  
  this.style = function(o, name) {
    var value = "";
    if (document.defaultView && document.defaultView.getComputedStyle) {
      return document.defaultView.getComputedStyle(o, "").getPropertyValue(name);
    }
    if (o && o.currentStyle) {
      name = name.replace('/\-(\w)/g', function(strMatch, p1) { return p1.toUpperCase(); });
      return o.currentStyle[name];
    }
  }

  this.getObject = function(arg) {
    if (typeof arg == 'object') return arg;
    if (typeof arg == 'string') return document.getElementById(arg);
  }

  this.getParentByTagName = function(o, tagName) {
    if (o == null) return;
    do {
      o = o.parentNode;
    } while (o != null && o.tagName != tagName);
    return o;
  }

  this.getParentByClassName = function(o, className) {
    if (o == null) return;
    do {
      o = o.parentNode;
    } while (o != null && o.className != className);
    return o;
  }

  this.nextSibling = function(o) {
    var x = o.nextSibling;
    while (x != null && (!x.tagName || x.tagName.charAt(0) == '/')) x = x.nextSibling;
    return x;
  }

  this.previousSibling = function(o) {
    var x = o.previousSibling;
    while (x != null && (!x.tagName || x.tagName.charAt(0) == '/')) x = x.previousSibling;
    return x;
  }

  this.firstChild = function(o) {
    var x = o.firstChild;
    if (x == null) return;
    if (!x.tagName) x = x.nextSibling;
    return x;
  }

  this.childNodes = function(o) {
    if (ivy.browser.isIE) return o.children;
    var x = new Array();
    var j = 0;
    for (var i = 0; i < o.childNodes.length; i++) {
      if (o.childNodes[i].tagName) {
        x[j] = o.childNodes[i];
        j++;
      }
    }
    return x;
  }
  
  /* ONREADY */

  this.initDocReady = function() {
    docReadyEvent = new Array();
    if (this.isReady) return;

    if (ivy.browser.isGecko || ivy.browser.isOpera) {
      document.addEventListener('DOMContentLoaded', this.fireDocReady, false);
      return;
    }

    if (ivy.browser.isIE) {
      document.onreadystatechange = function() {
        if (document.readyState == 'complete') {
          document.onreadystatechange = null;
          ivy.dom.fireDocReady();
        }
      };
      return;
    }

    if (ivy.browser.isSafari) {
      docReadyProcId = setInterval(function() {
        if (document.readyState == 'complete') {
          ivy.dom.fireDocReady();
        }
      }, 10);
    }
  };

  this.fireDocReady = function() {
    this.isReady = true;
    if (ivy.browser.isGecko || ivy.browser.isOpera) {
      document.removeEventListener("DOMContentLoaded", this.fireDocReady, false);
    }
    if (docReadyProcId) {
      clearInterval(docReadyProcId);
      docReadyProcId = null;
    }

    if (docReadyEvent) {
      for (var i = 0; i < docReadyEvent.length; i++) {
        docReadyEvent[i].call();
      }
    }
  };

  this.onready = function(f) {
    docReadyEvent[docReadyEvent.length] = f;
  }
}

ivy.dom = new dom();
ivy.dom.initDocReady();

String.prototype.px = function() {
  var s = this;
  if (s == 'auto') return 0;
  s = s.replace('px', '');
  s = s / 1;
  return (s != 'NaN' ? s : 0);
}