function dom() {
  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) {
      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.debug = function(o) {
    var s = '{';
    for (var i in o) {
      var val = o[i] + '';
      if (val.indexOf('function') == -1) {
        if (s != '{') s += ', \n';
        s += '"' + i + '": ' + debugvalue(o[i]);
      }
    }
    return s + '}';
  }

  this.debugvalue = function(o) {
    var t = getObjectClass(o);
    var s = '';
    switch (t) {
      case 'Array':
        s += '[';
        for (var i = 0; i < o.length; i++) {
          if (i != 0) s += ', ';
          s += debugvalue(o[i]);
        }
        s += ']';
        break;
      case 'String':
        s += '"' + o + '"';
        break;
      default:
        if (typeof o == 'object') {
          s += debug(o);
        } 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];
    }
    return undefined;
  }

  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;
  }

  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();