/*
JavaScript Extension Library Library v1.1
May 2006
Shawn Bailly (www.shawn-bailly.com)

Please report bugs to www.shawn-bailly.com.
This library is released as open source, if you develop some new or better methods, please submit them to me and I may put them in the next version.
*/

//Object Extensions
/*
Object.prototype._toolkit = {
   name : "JavaScript Extension Library",
   version : "1.1",
   author : "Shawn Bailly (www.shawn-bailly.com)",
   date : "May 19, 2006",
   license : "Open",
   verbose : false
};
Object.prototype.error = function(message){
   if (this.verbose){
      alert(message);
   }
   var e = new Error(message);
   throw e;
};
Object.prototype.Inherit = function(object){
   object = (object == null ? {} : object);
   for (key in object){
      if(this[key] == null){
         this[key] = object[key];
      }
   }
   return this;
};
Object.prototype.Create = function(object){
   return object;
};
Object.prototype.Clone = function(object){
   var newObject = {};
   object = (object == null ? {} : object);
   for (key in object){
      newObject[key] = object[key];
   }
   return object;
};
Object.prototype.toHuman = function(lf){
   lf = (lf == null ? "\\n" : lf);
   var str = "";
   for (key in this){
      str += key+" = "+this[key]+lf;
   }
   return str;
};
Object.prototype.toIntrinsic = function(){
   var number = /^(\d+|\d*\.\d*)$/g.$();
   var string = /^[^\d]+$/ig.$();
   if (number.test(this)){
      return new Number(this);
   }
   if (string.test(this)){
      return new String(this);
   }
};
*/
//String Extensions
String.prototype.is = function(){
   for (var i=0; i<arguments.length; i++){
      if (arguments[i] == this){
         return true;
      }
   }
   return false;
};
String.prototype.isUpper = function(index){
   var str = (index == null ? this : this.charAt(index));
   return (str == str.toUpperCase());
};
String.prototype.isAmong = function(chars){
   if (chars.constructor != Array){
      chars = chars.split(",");
   }
   for (var i=0; i<chars.length; i++){
      if (this == chars[i]){
         return true;
      }
   }
   return false;
};
String.prototype.isBoolean = function(){
   var rv = null;
   str = this.toLowerCase();
   if (str == true || str == "true" || str == "on" || parseInt(str) > 0 || str == "yes"){
      rv = true;
   }
   if (str == false || str == "false" || str == "off" || parseInt(str) == 0 || str == "no"){
      rv = false;
   }
   return rv;
};
String.prototype.toEnum = function(inner,outer,trim){
   var rv = new Enum();
   inner = (inner == null ? "=" : inner);
   outer = (outer == null ? "&" : outer);
   trim = (trim == null ? true : trim);
   if (this.length > 0){
      var pairs = this.split(outer);
      for (var i=0; i<pairs.length; i++){
         if (pairs[i].length > 0){
            var name = pairs[i].split(inner)[0];
            var value = pairs[i].split(inner)[1];
            if (name != null && value != null && name.length > 0){
               if (trim){
                  name = name.trim();
                  value = value.trim();
               }
               rv.add(name,value);
            }
         }
      }
   };
   return rv;
};
String.prototype.lTrim = function(chr){
   chr = (chr == null ? "\s" : chr);
   var re = new RegExp("^"+chr+"+","i");
   return this.replace(re,"");
};
String.prototype.rTrim = function(chr){
   chr = (chr == null ? "\s" : chr);
   var re = new RegExp(chr+"+$","i");
   return this.replace(re,"");
};
String.prototype.trim = function(chr){
   chr = (chr == null ? " " : chr);
   var str = this;
   str = str.lTrim(chr);
   str = str.rTrim(chr);
   return str;
};
String.prototype.remove = function(index,length){
   length = (length == null ? 1 : length);
   return this.substring(0,index)+this.substr(index+length);
};
String.prototype.inject = function(str,index){
   return this.substring(0,index)+str+this.substr(index);
};
String.prototype.contains = function(chars){
   var rv = false;
   if (chars.constructor != Array){
      chars = chars.split(",");
   }
   for (var i=0; i<chars.length; i++){
      rv = (rv || (this.indexOf(chars[i]) > -1));
   }
   return rv;
};
String.prototype.toMD5 = function(){
  var str2blks_MD5 = function(str){
     nblk = ((str.length + 8) >> 6) + 1;
     blks = new Array(nblk * 16);
     for(i = 0; i < nblk * 16; i++){
      blks[i] = 0;
     }
     for(i = 0; i < str.length; i++){
       blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);
     }
     blks[i >> 2] |= 0x80 << ((i % 4) * 8);
     blks[nblk * 16 - 2] = str.length * 8;
     return blks;
  };
  var rhex = function(num){
     var hex_chr = "0123456789abcdef";
     str = "";
     for(j = 0; j <= 3; j++){
      str += hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F)+hex_chr.charAt((num >> (j * 8)) & 0x0F);
     }
     return str;
  };
  var add = function(x, y){
     var lsw = (x & 0xFFFF) + (y & 0xFFFF);
     var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
     return (msw << 16) | (lsw & 0xFFFF);
  };
  var rol = function(num, cnt){
     return (num << cnt) | (num >>> (32 - cnt));
  };
  var cmn = function(q, a, b, x, s, t){
   return add(rol(add(add(a, q), add(x, t)), s), b);
  };
  var ff = function(a, b, c, d, x, s, t){
   return cmn((b & c) | ((~b) & d), a, b, x, s, t);
  };
  var gg = function(a, b, c, d, x, s, t){
   return cmn((b & d) | (c & (~d)), a, b, x, s, t);
  };
  var hh = function(a, b, c, d, x, s, t){
   return cmn(b ^ c ^ d, a, b, x, s, t);
  };
  var ii = function(a, b, c, d, x, s, t){
   return cmn(c ^ (b | (~d)), a, b, x, s, t);
  };
  x = str2blks_MD5(this);
  a =  1732584193;
  b = -271733879;
  c = -1732584194;
  d =  271733878;

  for(i = 0; i < x.length; i += 16)
  {
    olda = a;
    oldb = b;
    oldc = c;
    oldd = d;

    a = ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = ff(c, d, a, b, x[i+10], 17, -42063);
    b = ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = ff(d, a, b, c, x[i+13], 12, -40341101);
    c = ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = ff(b, c, d, a, x[i+15], 22,  1236535329);    

    a = gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = gg(c, d, a, b, x[i+11], 14,  643717713);
    b = gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = gg(c, d, a, b, x[i+15], 14, -660478335);
    b = gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = gg(b, c, d, a, x[i+12], 20, -1926607734);
    
    a = hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = hh(b, c, d, a, x[i+14], 23, -35309556);
    a = hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = hh(d, a, b, c, x[i+12], 11, -421815835);
    c = hh(c, d, a, b, x[i+15], 16,  530742520);
    b = hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = ii(c, d, a, b, x[i+10], 15, -1051523);
    b = ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = ii(d, a, b, c, x[i+15], 10, -30611744);
    c = ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = add(a, olda);
    b = add(b, oldb);
    c = add(c, oldc);
    d = add(d, oldd);
  }
  return rhex(a) + rhex(b) + rhex(c) + rhex(d);
};
String.prototype.repeat = function(count){
   var str = "";
   for (var i=0; i<count; i++){
      str += this;
   }
   return str;
};
String.prototype.toPattern = function(){
   return this.replace(/([\.\*\+\{\}\(\)\<\>\^\$\\])/g,"\\$1");
};
String.prototype.toBase = function(base,fromBase){
   var rv = "";
   fromBase = (fromBase == null ? 10 : fromBase);
   var toNumerals = "0123456789ABCDEF";
   var fromNumerals = "0123456789ABCDEF";
   if (fromBase.constructor == String){
      fromNumerals = fromBase;
      fromBase = fromBase.length;
   }
   if (base.constructor == String){
      toNumerals = base;
      base = base.length;
   }
   if (fromBase > 1 && base > 1){
      var result = power = value = 0;
      var number = new String(this);
      var sign = (number.indexOf("-") > -1 ? -1 : 1);
      number = (number.indexOf("-") > -1 ? number.substr(1) : number);
      var integer = (number.indexOf(".") > -1 ? (number.indexOf(".") > 0 ? number.substring(0,number.indexOf(".")) : 0) : number);
      var decimal = (number.indexOf(".") > -1 ? number.substr(number.indexOf(".")+1) : 0);
      if (sign < 0)rv = "-";
      for (var i=0; i<integer.length; i++){
         power = (integer.length-i)-1;
         value = fromNumerals.indexOf(integer.charAt(i))*(Math.pow(fromBase,power));
         result += value;
      }
      if (result > 0){
         var i = j = 0;
         while (result >= Math.pow(base,(i+1))){
            i++;
         }
         while (result > 0){
            while (result >= ((j+1)*Math.pow(base,i))){
               j++;
            }
            result = result - (j)*Math.pow(base,i);
            rv += toNumerals.charAt(j);
            i--;
            j = 0;
         }
         for (j=i; j>=0; j--){
            rv += "0";
         }
      }
      result = value = 0;
      value = 0;
      for (var i=0; i<decimal.length; i++){
         power = (i+1)*-1;
         value += fromNumerals.indexOf(decimal.charAt(i))*(Math.pow(fromBase,power));
      }
      if (value > 0){
         rv += ".";
         while ((result = value*base) > 0){
            rv += toNumerals.charAt(Math.floor(result));
            value = result-Math.floor(result);
            if (rv.substr(rv.indexOf(".")).length > base){
               value = 0;
            }
         }
      }
   }
   else rv = this;
   return rv;
};

Number.prototype.toBase = String.prototype.toBase;

//Regular Expression Extensions
RegExp.prototype.$ = function(){
   /*
   Takes a regular expression and returns a compiled version of it.
   */
	var expr = this.source;
   var flags = "";
   flags += (this.ignoreCase ? "i" : "");
   flags += (this.global ? "g" : "");
   flags += (this.multiline ? "m" : "");
   this.compile(expr,flags);
	return this;
};

RegExp.prototype.escape = function(){
   var re = /\\(\(|\)|\.|\\)/g;
   return this.source.replace(re,"$1");

};

RegExp.prototype.charAt = function(index,matches){
   /*
   Return the character at specified index, if that character is NOT a regular expression match
   
   TODO: escape regular expression syntax
   */
   var str = this.escape();
   var chr = "";
   var result = null;
   var expressions = [];
   if (matches == null){
      var matches = [];
      matches[matches.length] = /(\\d)(\{\d+\})?/ig;
      matches[matches.length] = /(\\w)(\{\w+\})?/g;
   }
   for (var i=0; i<matches.length; i++){
      if (matches[i].global){
         while ((result = matches[i].exec(str)) != null){
            if (result.length >= 2){
               var q = result[2].substring(1,result[2].length-1);
            }
            else var q = 1;
            expressions[expressions.length] = {
               index : result.index,
               length : result[0].length,
               end : result.index+(result[0].length-1),
               expression : result.shift(),
               matches : result,
               strlen : q
            }
         }
      }
   }
   var offset = 0;
   for (var i=0; i<expressions.length; i++){
      if ((index >= expressions[i].index && index <= expressions[i].end) || index >= expressions[i].end){
         index += (expressions[i].length-expressions[i].strlen);
      }
   }
   chr = str.charAt(index);
   for (var i=0; i<expressions.length; i++){
      if (index >= expressions[i].index && index <= expressions[i].end){
         chr = "";
      }
      /*
      if (index >= expressions[i].index && index <= expressions[i].end){
         message.value += "\nexpression: "+expressions[i].expression;
         if (!expressions[i].expression.is("\\(","\\)","\\."))chr = "";
         else{
            chr = new RegExp(expressions[i].expression).escape();
            document.getElementById("message").value += "\nchar: "+chr;
         }
      }
      */
   }
   return chr;
};

RegExp.prototype.substr = function(start,length){
   /*
   This function is incomplete and needs further development.
   At the moment it will only recognize \d sub matches.
   It can only start the substr at 0.
   It will only recognize the qualifier: {n}
   And it is probably written very messy.
   */
   var eStr = /(\(|\)|\.)/g;
   var returnstr = "";
   var expression = this.escape();
   var chr = "";
   var startMatch = ["\\"];
   var endMatch = [];
   var result = null;
   var match = false;
   var matches = new Array();
   var flags = "";
   flags += (this.ignoreCase ? "i" : "");
   flags += (this.global ? "g" : "");
   flags += (this.multiline ? "m" : "");
   matches[matches.length] = /(\\d)(?!\{.*?\})/i;
   matches[matches.length] = /(\\d)(\{.*?\}(?:|\?)|\*|\+|\?)/i;
   if (expression.charAt(0) == "^"){
      expression = expression.substr(1);
   }
   if (expression.charAt(expression.length-1) == "$"){
      expression = expression.substr(0,expression.length-1);
   }
   if (start != null){
      length = (length == null ? (expression.length-start) : length);
      end = start+length;
      for (var i=start; i<end; i++){
         chr = expression.charAt(i);
         if (chr.isAmong(startMatch)){
            mloop:for (var z=0; z<matches.length; z++){
               if (!match){
                  result = expression.substr(i).match(matches[z]);
                  if (result != null){
                     var matchlen = result[0].length;
                     var q = RegExp.$2;
                     if (/\{.*?\}/i.test(q)){
                        var param = q.replace(/\{|\}/g,"");
                        if (param.split(",").length == 1){
                           if (end-i < param){
                              q = "{"+(end-i)+"}";
                           }
                           end += (matchlen-param);
                        }
                        else{
                           //Handle qualifiers {1,} and {1,2}
                        }
                        i += (matchlen-1);
                        returnstr += result[1]+q;
                     }
                     else{
                        //Qualifier is not {n,n}
                        i += result[0].length;
                        end += result[0].length;
                        returnstr += result[0];
                     }
                     match = true;
                     break mloop;
                  }
               }
            }
            if (!match){
               alert("bad expression");
            }
            else match = false;
         }
         else returnstr += chr;
      }
      return new RegExp(returnstr.replace(eStr,"\\$1"),flags);
   }
   else return null;
};

Array.prototype.swap = function(index,pos){
   pos = (pos == null ? this.length-1 : pos);
   var save = this[pos];
   this[pos] = this[index];
   this[index] = save;
};
Array.prototype.first = function(){
   return this[0];
};
Array.prototype.last = function(){
   return this[this.length-1];
};
Array.prototype.doUntilTrue = function(exec){
   var rv = false;
   for (var i=0; i<this.length; i++){
      rv = (rv || exec(this[i]));
   }
   return rv;
};
Array.prototype.remove = function(index){
   var save = null;
   var rv = null;
   for (var i=index; i<this.length; i++){
      if (i+1 < this.length){
         save = this[i];
         this[i] = this[i+1];
         this[i+1] = save;
      }
      else rv = this.pop();
   }
   return rv;
};

function Enum(){
   var array = [];
   this.length = function(){
      return this.keyArray().length;
   };
   this.add = function(name,value){
      if (!this.keyExists(name)){
         array[array.length] = name;
         array[name] = value;
         return true;
      }
      else return false;
   };
   this.keyExists = function(name){
      if (array[name] != null){
         return true;
      }
      else return false;
   };
   this.findKey = function(value){
      var keys = this.keyArray();
      for (var i=0; i<keys.length; i++){
         if (this.item(keys[i]) == value){
            return keys[i];
         }
      }
      return null;
   };
   this.findValue = function(name){
      return array[name];
   };
   this.findIndex = function(key){
      for (var i=0; i<array.length; i++){
         if (array[i] == key){
            return i;
         }
      }
      return -1;
   };
   this.item = function(index){
      var item = null;
      switch (typeof index){
         case "string" :
            item = this.findValue(index);
            break;
         case "number" :
            item = this.findValue(array[index]);
            break;
         default :
            Object.error("Enum.item(): parameter 1 is not a string or number.");
            break;
      }
      return item;
   };
   this.remove = function(_index){
      var key = null;
      var index = null;
      var rv = false;
      switch (typeof _index){
         case "string" :
            key = _index;
            if (this.findIndex(_index) > -1){
               index = this.findIndex(_index);
            }
            else Object.error("Enum.remove(): "+_index+" is not a valid key.");
            break;
         case "number" :
            if (_index < 0 || _index >= this.length){
               Object.error("Enum.remove(): "+_index+" is out of range.");
            }
            key = array[_index];
            index = _index;
            break;
         default :
            Object.error("Enum.remove(): parameter 1 is not a string or number.");
            break;
      }
      if (key != null && index != null){
         array.remove(index);
         array[key] = null;
         rv = true;
      };
      return rv;
   };
   this.keyArray = function(){
      var rv = [];
      for (var i=0; i<array.length; i++){
         rv[rv.length] = array[i];
      }
      return rv;
   };
   this.valueArray = function(){
      var rv = [];
      var keys = this.keyArray();
      for (var i=0; i<keys.length; i++){
         rv[rv.length] = this.findValue(keys[i]);
      }
      return rv;
   };
   this.compare = function(obj){
      var rv = false;
      try{
         var keys = obj.keyArray();
         var thiskeys = this.keyArray();
         if (keys.length == thiskeys.length){
            rv = true;
            for (var i=0; i<keys.length; i++){
               if (obj.item(keys[i]) != this.findKey(keys[i])){
                  rv = false;
               }
            }
         }
         else rv = false;
      }
      catch (e){
         rv = null;
      }
      return rv;
   };
   return this;
};

Keys = new Enum();
Keys.add("Backspace",8);
Keys.add("Tab",9);
Keys.add("Enter",13);
Keys.add("Shift",16);
Keys.add("Control",17);
Keys.add("Alt",18);
Keys.add("Pause",19);
Keys.add("Caps Lock",20);
Keys.add("Escape",27);
Keys.add("Page Up",33);
Keys.add("Page Down",34);
Keys.add("End",35);
Keys.add("Home",36);
Keys.add("Left",37);
Keys.add("Up",38);
Keys.add("Right",39);
Keys.add("Down",40);
Keys.add("Insert",45);
Keys.add("Delete",46);
Keys.add("Number 0",48);
Keys.add("Number 1",49);
Keys.add("Number 2",50);
Keys.add("Number 3",51);
Keys.add("Number 4",52);
Keys.add("Number 5",53);
Keys.add("Number 6",54);
Keys.add("Number 7",55);
Keys.add("Number 8",56);
Keys.add("Number 9",57);
Keys.add("Semi-Colon",59);
Keys.add("Equals",61);
Keys.add("A",65);
Keys.add("B",66);
Keys.add("C",67);
Keys.add("D",68);
Keys.add("E",69);
Keys.add("F",70);
Keys.add("G",71);
Keys.add("H",72);
Keys.add("I",73);
Keys.add("J",74);
Keys.add("K",75);
Keys.add("L",76);
Keys.add("M",77);
Keys.add("N",78);
Keys.add("O",79);
Keys.add("P",80);
Keys.add("Q",81);
Keys.add("R",82);
Keys.add("S",83);
Keys.add("T",84);
Keys.add("U",85);
Keys.add("V",86);
Keys.add("W",87);
Keys.add("X",88);
Keys.add("Y",89);
Keys.add("Z",90);
Keys.add("Numpad 0",96);
Keys.add("Numpad 1",97);
Keys.add("Numpad 2",98);
Keys.add("Numpad 3",99);
Keys.add("Numpad 4",100);
Keys.add("Numpad 5",101);
Keys.add("Numpad 6",102);
Keys.add("Numpad 7",103);
Keys.add("Numpad 8",104);
Keys.add("Numpad 9",105);
Keys.add("Numpad Asterick",106);
Keys.add("Numpad Plus",107);
Keys.add("Hyphen",109);
Keys.add("Numpad Period",110);
Keys.add("Numpad Forward Slash",111);
Keys.add("F1",112);
Keys.add("F2",113);
Keys.add("F3",114);
Keys.add("F4",115);
Keys.add("F5",116);
Keys.add("F6",117);
Keys.add("F7",118);
Keys.add("F8",119);
Keys.add("F9",120);
Keys.add("F10",121);
Keys.add("F11",122);
Keys.add("F12",123);
Keys.add("Num Lock",144);
Keys.add("Scroll Lock",145);
Keys.add("Comma",188);
Keys.add("Period",190);
Keys.add("Forward Slash",191);
Keys.add("Left Apostrophe",192);
Keys.add("Left Bracket",219);
Keys.add("Back Slash",220);
Keys.add("Right Bracket",221);
Keys.add("Apostrophe",222);

Key = {
   shiftKey : false,
   ctrlKey : false,
   altKey : false,
   code : 0,
   chr : function(){
      if (this.code >= 65 && this.code <= 90 && this.shiftKey){
         return Keys.findKey(this.code);
      }
      if (this.code >= 65 && this.code <= 90 && !this.shiftKey){
         return Keys.findKey(this.code).toLowerCase();
      }
      if (this.code >= 48 && this.code <= 57 && !this.shiftKey){
         return this.code-48;
      }
      if (this.code >= 48 && this.code <= 57 && this.shiftKey){
         return [")","!","@","#","$","%","^","&","*","("][this.code-48];
      }
      if (this.code >= 96 && this.code <= 105){
         return this.code-96;
      }
      if (this.code == 59 && !this.shiftKey){
         return ";";
      }
      if (this.code == 59 && this.shiftKey){
         return ":";
      }
      if (this.code == 61 && !this.shiftKey){
         return "=";
      }
      if (this.code == 61 && this.shiftKey){
         return "+";
      }
      if (this.code == 109 && !this.shiftKey){
         return "-";
      }
      if (this.code == 109 && this.shiftKey){
         return "_";
      }
      if (this.code == 188 && !this.shiftKey){
         return ",";
      }
      if (this.code == 188 && this.shiftKey){
         return "<";
      }
      if (this.code == 190 && !this.shiftKey){
         return ".";
      }
      if (this.code == 190 && this.shiftKey){
         return ">";
      }
      if (this.code == 192 && !this.shiftKey){
         return "`";
      }
      if (this.code == 192 && this.shiftKey){
         return "~";
      }
      if (this.code >= 219 && this.code <= 222 && !this.shiftKey){
         return ["[","\\","]","'"][this.code-219];
      }
      if (this.code >= 219 && this.code <= 222 && this.shiftKey){
         return ["{","|","}","\""][this.code-219];
      }
      return null;
   },
   name : function(){
      return Keys.findKey(this.code);
   }
};

/*
The block below gives Firefox a srcElement, and cancelBubble functionality like in IE.
I know it isn't standard, and probably really bad practice; but this makes not having to modify a bunch of old code much
more digestable. Eventually this package will embrace more standard practices and this may be removed.
*/
if (window.navigator.userAgent.indexOf("Firefox") > -1){
   if (window.navigator.userAgent.indexOf("Firefox/1.0.3") > -1){//Firefox 1.0.3 bug fix
      Element.prototype.__proto__.__defineGetter__("text",function(){
         return this.firstChild.nodeValue
      });
      Event.prototype.__proto__.__defineGetter__("srcElement",function(){
         var node = this.target;
         while (node.nodeType != 1)node = node.parentNode;
         return node;
      });
      Event.prototype.__proto__.__defineSetter__("cancelBubble",function(b){
         if (b)this.stopPropagation();
      });
      Event.prototype.__proto__.__defineSetter__("returnValue",function(b){
         if (!b)this.preventDefault();
      });
   }
   else{
      Event.prototype.__defineGetter__("srcElement",function(){
         var node = this.target;
         while (node.nodeType != 1)node = node.parentNode;
         return node;
      });
      Event.prototype.__defineSetter__("cancelBubble",function(b){
         if (b)this.stopPropagation();
      });
      Event.prototype.__defineSetter__("returnValue",function(b){
         if (!b)this.preventDefault();
      });
   };
};

//Static Functions
function $A(el){
   var attributes = null;
   if (el.constructor == String){
      el = document.getElementById(el);
   }
   if (el != null){
      if (arguments.length > 2){
         attributes = [];
         for (var i=1; i<arguments.length; i++){
            if (el.getAttributeNode(arguments[i]) != null){
               attributes[attributes.length] = el.getAttributeNode(arguments[i]).nodeValue;
            }
         }
      }
      else{
         if (el.getAttributeNode(arguments[1]) != null){
            attributes = el.getAttributeNode(arguments[1]).nodeValue;
         }
      }
   }
   return attributes;
};

function $Bind(el,handler,method,capture){
   el = (el.constructor == String ? document.getElementById(el) : el);
   capture = (capture == null ? false : capture);
   var hooked = false;
	if (el.addEventListener != null){
		el.addEventListener(handler,method,capture);
		hooked = true;
	};
   if (el.attachEvent != null && !hooked){
		el.attachEvent("on"+handler,method);
		hooked = true;
	};
};

function $Selection(el,start,end){
   el = (el.constructor == String ? document.getElementById(el) : el);
   start = (start == null ? -1 : start);
   end = (end == null ? start : (end == 0 ? el.value.length : end));
   if (document.selection){
      var sel = document.selection.createRange();
      var tr = el.createTextRange();
      if (tr != null && sel != null && el != null){
         if (start == -1){
            var pos = 0;
            while (sel.compareEndPoints("StartToStart",tr) > 0){
               tr.moveStart("character",1);
               pos++;
            }
            start = pos;
            end = sel.text.length+start;
         }
         else{
            tr.moveStart("character",start);
            tr.collapse();
            if (end > -1)tr.moveEnd("character",(end-start));
            tr.select();         
         }
      }
   }
   else{
      if (start == -1){
         start = el.selectionStart;
         end = el.selectionEnd;
      }
      else{
         el.selectionStart = start;
         if (end > -1)el.selectionEnd = end;
      }
   }
   return{
      start : start,
      end : end
   }
};

function $ReplaceSelection(el,str,update){
   update = (update == null ? true : update);
   el = (el.constructor == String ? document.getElementById(el) : el);
   var start = $Selection(el).start;
   var end = $Selection(el).end;
   var str = el.value.inject(str,start);
   if (update){
      el.value = str;
   }
   return str;
};
