/*
Builder Library v. 1.0
Copyright (c) 2006-2008 Matt Jacobson.
All Rights Reserved.

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions 
are met:

1. Redistributions of source code must retain the above copyright 
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright 
notice, this list of conditions and the following disclaimer in the 
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote 
products derived from this software without specific prior written 
permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

var Builder =  {

  Build: function(tagname, /*Array*/ attributes, inner) {
    var theObj = document.createElement(tagname);
    for (var i in attributes) {
      if (i.toLowerCase() == "onclick") {
        theObj = this.setEvt(theObj, "click", attributes[i]);
      } else if (i.toLowerCase() == "onmouseover") {
        theObj = this.setEvt(theObj, "mouseover", attributes[i]);
      } else if (i.toLowerCase() == "onmouseout") {
        theObj = this.setEvt(theObj, "mouseout", attributes[i]);
      } else if (i.toLowerCase() == "ondblclick") {
        theObj = this.setEvt(theObj, "dblclick", attributes[i]);
      } else if (i.toLowerCase() == "onsubmit") {
        attributes[i] = attributes[i].replace(/\'/g,"\\'");
        theObj.onsubmit = eval("function() { return eval('"+attributes[i]+"'); };");
        //theObj = this.setEvt(theObj,"submit",attributes[i]);
      } else if (i.toLowerCase() == "classname" || i.toLowerCase() == "class") {
        theObj.className = attributes[i];
      } else {
        theObj.setAttribute(i, attributes[i]);
      }
    }
    if (isArray(inner)) {
      for (var a = 0; a < inner.length; a++) {
        if (typeof inner[a] != "object") {
          if (inner[a] !== "") {
            theObj.appendChild(document.createTextNode(inner[a]));
          }
        } else {
          theObj.appendChild(inner[a]);
        }
      }
    } else {
      if (typeof inner != "object") {
        if (inner !== "") {
          theObj.appendChild(document.createTextNode(inner));
        }
      } else {
        theObj.appendChild(inner);
      }
    }
    return theObj;
  }, 

  setEvt: function(theObj, evtname, callback) {
    callback = callback.replace(/\\'/g,"\\\\\\'");
    callback = callback.replace(/([^\\])'/g,"$1\\'");
    eval("var callbackfcn = function() { return eval('"+callback+"') };");
    if (window.addEventListener) {
      // Mozilla, Netscape, Firefox
      theObj.addEventListener(evtname, callbackfcn, false);
    } else {
      // IE
      theObj.attachEvent("on" + evtname, callbackfcn);
    }
    return theObj;
  }, 

  htmlToObject: function(code) {
    code = code.replace(/\n/g,"");
    code = filterHexEntities(code);
    code = filterDecimalEntities(code);
    code = filterNamedEntities(code);
    var theObj = document.createDocumentFragment();
    theObj = this.htmlInObj(code, theObj);
    if (theObj === false) {
      return document.createDocumentFragment();
    }
    return theObj;
  }, 

  htmlInObj: function(code, theObj) {
    while (code.length > 0) {
      if (code.substring(0, 1) != "<") {
        var tblock = code.indexOf("<") == -1 ? code : code.substring(0, code.indexOf("<"));
        theObj.appendChild(document.createTextNode(tblock));
        code = code.substring(tblock.length);
      } else {
        var info = this.parseFirstTag(code);
        if (info === false) {
          return false;
        }
        var tagName = info[0];
        var tagAttributes = info[1];
        var tagCode = info[2];
        var tagNoClosing = info[3];
        code = code.substring(tagCode.length);
        var nextCode = "";
        // we now need to find the closing tag (unless it's a singleton, of course)
        if (!tagNoClosing) {
          var found = false;
          var stack = []; // new Array();
          //var count = 0;
          var innerInfo, innerTagName, innerTagCode, innerTagNoClosing;
          while (!found) {
            if (code.substring(0, 1) != "<") {
              var ptext = code.indexOf("<") == -1 ? code : code.substring(0, code.indexOf("<"));
              nextCode += ptext;
              code = code.substring(ptext.length);
            } else {
              // some kind of tag
              if (code.substring(1, 2) != "/") {
                // not a closing tag
                innerInfo = this.parseFirstTag(code);
                innerTagName = innerInfo[0];
                innerTagCode = innerInfo[2];
                innerTagNoClosing = innerInfo[3];
                nextCode += innerTagCode;
                code = code.substring(innerTagCode.length);
                if (!innerTagNoClosing) {
                  stack.push(innerTagName);
                } 
              } else {
                // it's a closing tag
                var name = code.substring(2, code.indexOf(">")).toLowerCase();
                if (stack.length === 0 && name == tagName) {
                  code = code.substring(name.length + 3);
                  found = true;
                } else {
                  if (stack[stack.length - 1] != name) { // bad nesting
                    while (stack[stack.length - 1] != name && stack.length > 0) {
                      alert("HTML error: <"+stack[stack.length-1]+"> needs to close before <"+innerTagName+">.");return false;
                      // stack.pop(); // instead, just ignore the unclosed tags.
                    }
                  } else {
                    stack.pop();
                    nextCode += "</" + name + ">";
                    code = code.substring(name.length + 3);
                  }
                }
              }
            }
            //count++;
            if (!found && code.length === 0) {
              //alert("HTML error: <"+topname+"> did not have a closing tag.");return false;
              theObj = this.htmlInObj(nextCode, theObj); 
              // instead, just ignore the tag.
              return theObj;
            }
          }
        }
        if (tagName != "comment") {
          var newObj = this.Build(tagName, tagAttributes, "");
          newObj = this.htmlInObj(nextCode, newObj);
          if (newObj === false) {
            return false;
          }
          theObj.appendChild(newObj);
        }
      }
    }
    return theObj;
  }, 

  parseFirstTag: function(code) {
    var tagcode = "";
    var name = "";
    var param = "";
    var value = "";
    var gathername = false;
    var gatherparam = false;
    var gathervalue = false;
    var endfound = false;
    var quote = false;
    var count = 0;
    var noclosing = false;
    var attributes = []; // new Array();
    while (!endfound) {
      var chr = code.substring(count, count + 1);
      if (!quote || chr == "\"") {
        // if we are not quoting (unless the character is a quote!)
        if (chr == "<") {
          // we are starting the tag.  start gathering the name ..
          gathername = true;
        } else if (chr == "\"") {
          // okay, we're in a quote now.  evaluate everything literally!!
          quote = !quote;
        } else if (chr == " ") {
            // if we were gathering name, we are done that.  if we were gathering a value, we are ready for the next one.
          if (gathername) {
            // we just finihsed gathering the name.  now for values ..
            gathername = false;
            gatherparam = true;
          } else if (gatherparam) {
            // you put a space next to a param .. where is the value??
            //alert("HTML error: In <"+name+">, \""+param+"\" did not receive a value.");return false;
            param = ""; // we'll just reset the param.
          } else if (gathervalue) {
            // we just finished gathering a value.  next..
            attributes[param] = value;
            param = "";
            value = "";
            gatherparam = true;
            gathervalue = false;
          }
        } else if (chr == "=") {
          // okay, we know what the param is.  start gathering the value.
          gatherparam = false;
          gathervalue = true;
        } else if (chr == ">") {
          // a tag is ending.
          endfound = true;
          if (gathervalue) {
            // this is for the last value in a tag, in case there is no space after the last value (that is okay!)
            attributes[param] = value;
            param = "";
            value = "";
            gatherparam = true;
            gathervalue = false;
          }
        } else if ((gatherparam || gathername) && chr == "/") {
          // this denotes a "singleton" tag, like <br />
          noclosing = true;
        } else {
          // it must be part of a name, param, or value.
          if (gathername) {
            name += chr.toLowerCase();
          } else if (gatherparam) {
            param += chr.toLowerCase();
          } else if (gathervalue) {
            // this is bad HTML.  please enquote all your values.
            value += chr;
          }
        }
      } else {
        if (gathervalue) {
          // we are gathering a quoted value (thanks for quoting!)
          value += chr;
        } else {
          // you should not be quoting anything else.
          //alert("HTML error: Misplaced quotes.");return false;
        }
      }
      tagcode += chr;
      count++;
    }
    if (name == "area" || name == "base" || name == "basefont" || name == "br" ||
      name == "col" || name == "frame" || name == "hr" || name == "img" ||
      name == "input" || name == "isindex" || name == "link" || name == "meta" ||
      name == "param" || name == "!--") {
      // you should have put <br/>, but that is okay.
      noclosing = true;
    }
    var info = []; // new Array();
    info[0] = name;
    info[1] = attributes;
    info[2] = tagcode;
    info[3] = noclosing;
    return info;
  }, 

  clearElt: function(obj) {
    while (obj.firstChild) {
      obj.removeChild(obj.firstChild);
    } 
  }, 

  addText: function(obj, text) {
    obj.appendChild(document.createTextNode(text));
  }, 

  setHTML: function(obj, code) {
    this.clearElt(obj);
    this.addHTML(obj, code);
  }, 

  addHTML: function(obj, code) {
    obj.appendChild(this.htmlToObject(code));
  }

};

// global, non-namespaced functions

function isArray(a) {
  return typeof a == 'object' && a.constructor == Array;
}

var namedentities =  {
  quot: '0022', amp: '0026', lt: '003C', gt: '003E', nbsp: '00A0', iexcl:
    '00A1', cent: '00A2', pound: '00A3', curren: '00A4', yen: '00A5', brvbar:
    '00A6', sect: '00A7', uml: '00A8', copy: '00A9', ordf: '00AA', laquo:
    '00AB', not: '00AC', shy: '00AD', reg: '00AE', macr: '00AF', deg: '00B0',
    plusmn: '00B1', sup2: '00B2', sup3: '00B3', acute: '00B4', micro: '00B5',
    para: '00B6', middot: '00B7', cedil: '00B8', sup1: '00B9', ordm: '00BA',
    raquo: '00BB', frac14: '00BC', frac12: '00BD', frac34: '00BE', iquest:
    '00BF', Agrave: '00C0', Aacute: '00C1', Acirc: '00C2', Atilde: '00C3',
    Auml: '00C4', Aring: '00C5', AElig: '00C6', Ccedil: '00C7', Egrave: '00C8',
    Eacute: '00C9', Ecirc: '00CA', Euml: '00CB', Igrave: '00CC', Iacute: '00CD',
    Icirc: '00CE', Iuml: '00CF', ETH: '00D0', Ntilde: '00D1', Ograve: '00D2',
    Oacute: '00D3', Ocirc: '00D4', Otilde: '00D5', Ouml: '00D6', times: '00D7',
    Oslash: '00D8', Ugrave: '00D9', Uacute: '00DA', Ucirc: '00DB', Uuml: '00DC',
    Yacute: '00DD', THORN: '00DE', szlig: '00DF', agrave: '00E0', aacute:
    '00E1', acirc: '00E2', atilde: '00E3', auml: '00E4', aring: '00E5', aelig:
    '00E6', ccedil: '00E7', egrave: '00E8', eacute: '00E9', ecirc: '00EA', euml:
    '00EB', igrave: '00EC', iacute: '00ED', icirc: '00EE', iuml: '00EF', eth:
    '00F0', ntilde: '00F1', ograve: '00F2', oacute: '00F3', ocirc: '00F4',
    otilde: '00F5', ouml: '00F6', divide: '00F7', oslash: '00F8', ugrave:
    '00F9', uacute: '00FA', ucirc: '00FB', uuml: '00FC', yacute: '00FD', thorn:
    '00FE', yuml: '00FF', OElig: '0152', oelig: '0153', Scaron: '0160', scaron:
    '0161', Yuml: '0178', fnof: '0192', circ: '02C6', tilde: '02DC', Alpha:
    '0391', Beta: '0392', Gamma: '0393', Delta: '0394', Epsilon: '0395', Zeta:
    '0396', Eta: '0397', Theta: '0398', Iota: '0399', Kappa: '039A', Lambda:
    '039B', Mu: '039C', Nu: '039D', Xi: '039E', Omicron: '039F', Pi: '03A0',
    Rho: '03A1', Sigma: '03A3', Tau: '03A4', Upsilon: '03A5', Phi: '03A6', Chi:
    '03A7', Psi: '03A8', Omega: '03A9', alpha: '03B1', beta: '03B2', gamma:
    '03B3', delta: '03B4', epsilon: '03B5', zeta: '03B6', eta: '03B7', theta:
    '03B8', iota: '03B9', kappa: '03BA', lambda: '03BB', mu: '03BC', nu: '03BD',
    xi: '03BE', omicron: '03BF', pi: '03C0', rho: '03C1', sigmaf: '03C2', sigma:
    '03C3', tau: '03C4', upsilon: '03C5', phi: '03C6', chi: '03C7', psi:
    '03C8', omega: '03C9', thetasym: '03D1', upsih: '03D2', piv: '03D6', ensp:
    '2002', emsp: '2003', thinsp: '2009', zwnj: '200C', zwj: '200D', lrm:
    '200E', rlm: '200F', ndash: '2013', mdash: '2014', lsquo: '2018', rsquo:
    '2019', sbquo: '201A', ldquo: '201C', rdquo: '201D', bdquo: '201E', dagger:
    '2020', Dagger: '2021', bull: '2022', hellip: '2026', permil: '2030', prime:
    '2032', Prime: '2033', lsaquo: '2039', rsaquo: '203A', oline: '203E',
    frasl: '2044', euro: '20AC', image: '2111', weierp: '2118', real: '211C',
    trade: '2122', alefsym: '2135', larr: '2190', uarr: '2191', rarr: '2192',
    darr: '2193', harr: '2194', crarr: '21B5', lArr: '21D0', uArr: '21D1', rArr:
    '21D2', dArr: '21D3', hArr: '21D4', forall: '2200', part: '2202', exist:
    '2203', empty: '2205', nabla: '2207', isin: '2208', notin: '2209', ni:
    '220B', prod: '220F', sum: '2211', minus: '2212', lowast: '2217', radic:
    '221A', prop: '221D', infin: '221E', ang: '2220', and: '2227', or: '2228',
    cap: '2229', cup: '222A', 'int': '222B', there4: '2234', sim: '223C', cong:
    '2245', asymp: '2248', ne: '2260', equiv: '2261', le: '2264', ge: '2265',
    sub: '2282', sup: '2283', nsub: '2284', sube: '2286', supe: '2287', oplus:
    '2295', otimes: '2297', perp: '22A5', sdot: '22C5', lceil: '2308', rceil:
    '2309', lfloor: '230A', rfloor: '230B', lang: '2329', rang: '232a', loz:
    '25CA', spades: '2660', clubs: '2663', hearts: '2665', diams: '2666'
};

function filterNamedEntities(code) {
  while (code.match(/&[A-Za-z0-9]+;/)) {
    var m = code.match(/&[A-Za-z0-9]+;/);
    var origpos = m.index;
    var pos = origpos + 1;
    var name = "";
    while (code.substring(pos, pos + 1) != ";") {
      name += code.substring(pos, pos + 1);
      pos++;
    }
    var origlen = name.length;
    var item;
    if (namedentities[name.toLowerCase()] === undefined) {
      item = "";
    } else {
      item = eval("'\\u" + namedentities[name.toLowerCase()] + "'");
    }
    code = code.substring(0, origpos) + item + code.substring(origpos + origlen + 2);
  }
  return code;
}

function filterHexEntities(code) {
  while (code.match(/&#x[0-9A-F]{0,4};/)) {
    var m = code.match(/&#x[0-9A-F]{0,4};/);
    var origpos = m.index;
    var pos = origpos + 3;
    var num = "";
    while (code.substring(pos, pos + 1) != ";") {
      num += code.substring(pos, pos + 1);
      pos++;
    }
    var origlen = num.length;
    while (num.length < 4) {
      num = "0" + num;
    } code = code.substring(0, origpos) + eval("'\\u" + num + "'") +
      code.substring(origpos + origlen + 4);
  }
  return code;
}

function decToHex(dec) {
  var a = 0;
  var b = 0;
  while (dec > b + (15 * Math.pow(16, a))) {
    b += (15 * Math.pow(16, a));
    a++;
  }
  var hex = "";
  for (var c = a; c >= 0; c--) {
    var num = Math.floor(dec / (Math.pow(16, c)));
    switch (num) {
      case 10:
        num = "A";
        break;
      case 11:
        num = "B";
        break;
      case 12:
        num = "C";
        break;
      case 13:
        num = "D";
        break;
      case 14:
        num = "E";
        break;
      case 15:
        num = "F";
    }
    hex += num.toString();
    dec %= Math.pow(16, c);
  }
  while (hex.length < 4) {
    hex = "0" + hex;
  } return hex;
}

function filterDecimalEntities(code) {
  while (code.match(/&#[0-9]{0,4};/)) {
    var m = code.match(/&#[0-9]{0,4};/);
    var origpos = m.index;
    var pos = origpos + 2;
    var num = "";
    while (code.substring(pos, pos + 1) != ";") {
      num += code.substring(pos, pos + 1);
      pos++;
    }
    code = code.substring(0, origpos) + eval("'\\u" + decToHex(num) + "'") +
      code.substring(origpos + num.length + 3);
  }
  return code;
}

function $(what) {
  return document.getElementById(what);
}
