  var nodes, lastClicked, cont
  var xdom, dataDoc, dataRoot
  var rootNode=null
  var SELECTEDCOLOR="navy"
  var MOUSEOVERCOLOR="#99CCFF"
  var SHOWALLNODES=false
  var DEBUG=false
  var TREE_LOAD=false
  var GLOBAL_NODE_COUNT
  var GLOBAL_UNIQUE=0
  var GLOBAL_TREEDEF=""
  var ROOT_DELETE_ERROR="You can not delete the root node"
  var onTreeRenderNode=null
  nodes = new Array()
  
  nodeLines = new Array()
  nodeLines["i0"]="blank.gif"
  nodeLines["i1"]="vline.gif"
  nodeLines["i2"]="vnline2.gif"
  nodeLines["i3"]="veline.gif"
  nodeLines["i4"]="flm.gif"
  nodeLines["i5"]="flp.gif"
  nodeLines["i6"]="fm.gif"
  nodeLines["i7"]="fp.gif"
  
  function mapper() {
    this.xml=null
    this.html=null
    this.level=0
  }

  function treeUniqueId() {
    GLOBAL_UNIQUE++
    return "jt__"+GLOBAL_UNIQUE
  }

  function treeNode(pName,pData) {
  var dataSet, i, L, field
    this.nodeName=pName
    this.childNodes= new Array()
    this.data= new Array
    this.parentNode=null
    if (pData) {
      dataSet=pData.split(";")
      L=dataSet.length
      for (i=0;i<L;i++) {
       field=dataSet[i].split(":")
       if (field.length != 2) continue
       this.data[field[0]]=field[1]
      }
    }
    if (! this.data["id"]) {
      this.data["id"]=treeUniqueId()
    }
    this.c=createAppendChild_
    this.appendChild= appendChild_
    this.removeChild= removeChild_
    this.firstChild= firstChild_
    this.lastChild= lastChild_
    this.hasChildNodes= hasChildNodes_
    this.getAttribute= getAttribute_
    this.setAttribute= setAttribute_
    this.previousSibling= previousSibling_
    this.nextSibling= nextSibling_
  }
  
  function n(pNodeName, pData) {
  var node
    node= new treeNode(pNodeName, pData)
    return node
  }


  function createAppendChild_(pNodeName, pData) {
  var node, L
    node = n(pNodeName, pData)
    L=this.childNodes.length
    this.childNodes[L]=node
    node.parentNode=this
    return node
  }

  function appendChild_(pNode) {
  var L
    L=this.childNodes.length
    this.childNodes[L]=pNode
    pNode.parentNode=this
  }

  function indexOfChild(pParentNode,pNode) {
  var i, L, ns
    ns=pParentNode.childNodes
    L=ns.length
    if (L==0) return -1
    for (i=0;i<L;i++) {
      if (ns[i]==pNode) return i
    }
    return -1
  }

  function removeChild_(pNode) {
  var i, L, index,ns
    ns=this.childNodes
    L=ns.length
    if (L==0) return
    index=indexOfChild(this,pNode)
    if (index==-1) return
    if (index==(L-1)) {
      ns.length=L-1
      return
    }
    for (i=index;i<(L-1);i++) {
      ns[i]=ns[i+1]
    }
    ns.length=L-1
  }

  function hasChildNodes_() {
    return this.childNodes.length > 0
  }

  function firstChild_() {
  var L
    L=this.childNodes.length
    if (L==0) return null
    return this.childNodes[0]
  }

  function previousSibling_() {
  var index
    if (this.parentNode==null) return null
    index=indexOfChild(this.parentNode, this)
    if (index<1) {
      return null
    }
    else {
      return this.parentNode.childNodes[index-1]
    }
  }
  
  function nextSibling_() {
  var index, L
    if (this.parentNode==null) return null
    L=this.parentNode.childNodes.length
    index=indexOfChild(this.parentNode, this)
    if (index>(L-1)) {
      return null
    }
    else {
      return this.parentNode.childNodes[index+1]
    }
  }

  function lastChild_() {
  var L
    L=this.childNodes.length
    if (L==0) return null
    return this.childNodes[L-1]
  }

   function getAttribute_ (pName) {
     return this.data[pName]
   }

   function setAttribute_ (pName, pValue) {
     this.data[pName]=pValue
   }


  function treeParse(pRoot) {
    if (pRoot) dataRoot=pRoot
    DEBUG=false
//    if (TREE_LOAD) {
      treeParseNode(cont,dataRoot,0, true,"", null, false)
//    }
  }
  

  function treeDataLoaded() {
    if (! TREE_LOAD) return
    TREE_LOAD=false
    eval("dataRoot="+GLOBAL_TREEDEF+"()")
    treeParse()
  }


  function treeLoad(HTMLId, pTreeDef) {
  var se
    cont=document.getElementById(HTMLId)
    treeClear()
    try {
      eval("dataRoot="+pTreeDef+"()")
      treeParse()
    }
    catch (e) {
      dataRoot=null
      TREE_LOAD=true
      GLOBAL_TREEDEF=pTreeDef
      se=document.createElement("SCRIPT")
      se.language="javascript"
      se.src=pTreeDef+".js"
      document.getElementsByTagName("head")[0].appendChild(se)
    }
  }
  

  function nodeMouseOver() {
  var elem
    //elem=window.event.srcElement
    elem=this
    elem.style.backgroundColor=MOUSEOVERCOLOR
  }

  function nodeMouseOut() {
  var elem
    elem=this
    if (elem==lastClicked) {
      elem.style.backgroundColor=SELECTEDCOLOR
    }
    else {
      elem.style.backgroundColor="white"
    }
  }

  function nodeClicked() {
  var elem
    elem=this
    treeNodeClick(elem,false)
  }
  
  function treeNodeClick(pElem, pSoft) {
  var nid, xnode
    nid=pElem.getAttribute("nid")
    xnode=nodes[nid].xml
    if (lastClicked != null) {
      lastClicked.style.backgroundColor="white"
      lastClicked.style.color="black"
    }
    lastClicked=pElem
    lastClicked.style.backgroundColor=SELECTEDCOLOR
    lastClicked.style.color="white"
    if (pSoft) pElem.scrollIntoView()
    if (! pSoft) {
      try {
        onTreeNodeClicked(xnode)
      }
      catch (e) {
      }
    }
  }

  function treeSelectedNode() {
    if (lastClicked==null) return null
    return nodes[lastClicked.getAttribute("nid")].xml
  }
  
  function setState(pNode,pState, parentLevel, pInsertElem) {
  var eid, elem, display, i, childCount, cNode, img, imgState
  var map, level, lastChild, insertElem, lines, sibling
  var prevNode, cNode
    pState ? display="block" : display="none"
    eid=pNode.getAttribute("id")
    map=nodes[eid]
    elem=map.html
    if ((elem==null) && pState) {
      level=parentLevel+1
      prevNode=pNode.previousSibling()
      if (prevNode==null) {
        lines=pNode.parentNode.getAttribute("nlines")
        pNode.parentNode.nextSibling()==null ? lastChild=true : lastChild=false
        sibling=false
      }
      else {
        lines=prevNode.getAttribute("nlines")
        sibling=true
      }
      lines=lines.substr(0,lines.length-1)
      if (! sibling) {
        lastChild ? lines+="0" : lines+="1"
      }
      lastChild=(pNode.nextSibling() == null)
      DEBUG=true
      elem=treeRenderNode(cont,pNode, level,lastChild, lines, pInsertElem, "block")
      DEBUG=false
    }
    else {
      level=map.level
      if (elem != null) {
        elem.style.display=display
      }
    }
    childCount=pNode.childNodes.length
    if (childCount>0) {
      imgState=pNode.getAttribute("nstate")
      if (pState && (imgState=="closed")) return
      for (i=0;i<childCount;i++) {
        setState(pNode.childNodes[i],pState, level,pInsertElem)
      }
    }
  }

  function toggle() {
  var img,nid
  var state
    img=this
    nid=img.getAttribute("nid")
    state=nodes[nid].xml.getAttribute("nstate")
    state=="open" ? treeCloseElement(nid) : treeOpenElement(nid)
  }

  function treeOpenElement(pId) {
  var elem, imgs, img
    elem=nodes[pId].html
    imgs=elem.getElementsByTagName("IMG")
    img=imgs[imgs.length-1]
    nodes[pId].xml.setAttribute("nstate","open")
    img.src=img.src.replace(/p.gif$/,"m.gif")
    treePropagateState(pId,true)
  }

  function treeCloseElement(pId) {
  var elem, imgs, img
    elem=nodes[pId].html
    imgs=elem.getElementsByTagName("IMG")
    img=imgs[imgs.length-1]
    nodes[pId].xml.setAttribute("nstate","closed")
    img.src=img.src.replace(/m.gif$/,"p.gif")
    treePropagateState(pId,false)
  }

  function treePropagateState(pId, state) {
  var xnode, i, childCount, level, map, elem, insertElem, cNode
    map=nodes[pId]
    xnode=map.xml
    elem=map.html
    level=map.level
    childCount=xnode.childNodes.length
    insertElem=elem.nextSibling
    if (childCount>0) {
      for (i=0;i<childCount;i++) {
        setState(xnode.childNodes[i],state, level,insertElem)
      }
    }
  }

  function treeOpenPath(pNode) {
  var xnode, path, atoms, i, L, xroot, xopen, xclose, closeId, childIndex, childCount
    xnode=pNode
    path=treeGetPath(xnode)
    if (path=="") return
    atoms=path.split("|")
    L=atoms.length
    xroot=nodes[atoms[0]].xml
    for (i=0;i<L;i++) {
      switch (i) {
      case 0:
        treeOpenElement(atoms[i])
        break
      case 1:
        // close other paths
        xopen=nodes[atoms[1]].xml
        childCount=xroot.childNodes.length
        if (childCount>0) {
          for (childIndex=0;childIndex<childCount;childIndex++) {
            xclose=xroot.childNodes[childIndex]
            closeId=xclose.getAttribute("id")
            treeCloseElement(closeId)
          }
        }
        treeOpenElement(atoms[i])
        break
      default: treeOpenElement(atoms[i])
      }

    }
  }

  function checkNode(pNode, pLines, lastElem) {
  var nid, i, childCount, elem, lines, oldLines, newLines, lastChild
  var state
    nid=pNode.getAttribute("id")
    elem=nodes[nid].html
    oldLines=""+pNode.getAttribute("nlines")
    lines=pLines
    newLines=pLines
    pNode.nextSibling() ? lastChild=false : lastChild=true
    lastChild ? newLines+="0" : newLines+="1"
    if (! pNode.hasChildNodes()) {
      childCount=0
    }
    else {
      childCount=pNode.childNodes.length
    }
    if (childCount==0) {
      lastChild ? lines+="3" : lines+="2"
    }
    else {
      state=pNode.getAttribute("nstate")
      if (state==null) state=""
      if (state=="closed") {
        lastChild ? lines+="5" : lines+="7"
      }
      else {
        lastChild ? lines+="4" : lines+="6"
      }
    }
    if (oldLines != lines) treeUpdateLines(pNode, elem, lines, nid)
    if (elem==lastElem) return
    for (i=0;i<childCount;i++) {
      checkNode(pNode.childNodes[i], newLines, lastElem)
    }
  }

  function treeUpdateLines(pNode, pElem,pLines,pId) {
  var imgs, img, i, imgCount, lineCount, atom, nid
   nid=pId
   pNode.setAttribute("nlines",pLines)
   if (pLines=="") return
   lineCount=pLines.length
   imgs=pElem.getElementsByTagName("IMG")
   for (i=0;i<lineCount;i++) {
     img=imgs[i]
     atom=pLines.substr(i,1)
     img.src=nodeLines["i"+atom]
     if ("12".indexOf(atom) != -1) {
       img.parentNode.style.backgroundImage="url(vline.gif)"
     }
     else {
       img.parentNode.style.backgroundImage=""
     }
     
     if ("4567".indexOf(atom) != -1) {
       img.setAttribute("id","icon"+nid)
       img.setAttribute("nid",nid)
       if ((atom=="4") || (atom=="6")) {
         pElem.setAttribute("state","open")
       }
       else {
         pElem.setAttribute("state","closed")
       }
       img.onclick=toggle
       img.style.cursor="pointer"
     }
   }
  }

  function treeRenderNode(pCont,pNode, pLevel,lastChild, pLines, pInsertElem, pDisplay) {
  var t, label, labelspan, i, childCount, lineCount, img, table, oldTable, row, cell, last, ar
  var lines, chrome, newLines, newLine, atom, level
  var nid, objNode, map
    nid=pNode.getAttribute("id")
    map=nodes[nid]
    level=pLevel
    lines=pLines
    pNode.hasChildNodes() ? childCount=pNode.childNodes.length : childCount=0
    table=document.createElement("TABLE")
    table.style.display=pDisplay
    table.cellSpacing=0
    table.cellPadding=0
    table.border=0
    map.html=table
    table.setAttribute("id",nid)
    row=table.insertRow(-1)
    row.vAlign="top"
    lineCount=lines.length
    if (lineCount>0) {
      for (i=0;i<lineCount;i++) {
        cell=row.insertCell(-1)
        atom=lines.substr(i,1)
        img=document.createElement("IMG")
        if ("12".indexOf(atom) != -1) {
          cell.style.backgroundImage="url(vline.gif)"
          img.src="blank.gif"
        }
        else {
          img.src=nodeLines["i"+atom]
        }
        img.style.margin=0
        cell.appendChild(img)
      }
      if (lastChild) {
        cell=row.insertCell(-1)
        img=document.createElement("IMG")
        img.style.margin=0
        if (childCount>0) {
          if ((! SHOWALLNODES) && (level>=1)) {
            lines+="5"
            img.src=nodeLines["i5"]
            pNode.setAttribute("nstate","closed")
          }
          else {
            lines+="4"
            img.src=nodeLines["i4"]
            pNode.setAttribute("nstate","open")
          }
          img.setAttribute("id","icon"+nid)
          img.setAttribute("nid",nid)

          img.onclick=toggle
          img.style.cursor="pointer"
        }
        else {
          img.src=nodeLines["i3"]
          lines+="3"
        }
        cell.appendChild(img)
      }
      else {
        cell=row.insertCell(-1)
        img=document.createElement("IMG")
        if (childCount>0)  {
          if ((! SHOWALLNODES) && (level>=1)) {
            img.src=nodeLines["i7"]
            lines+="7"
            pNode.setAttribute("nstate","closed")
          }
          else {
            img.src=nodeLines["i6"]
            lines+="6"
            pNode.setAttribute("nstate","open")
          }
          img.setAttribute("id","icon"+nid)
          img.setAttribute("nid",nid)
          img.onclick=toggle
          img.style.cursor="pointer"
        }
        else {
          cell.style.backgroundImage="url(vline.gif)"
          img.src=nodeLines["i2"]
          lines+="2"
        }
        cell.appendChild(img)
      }
    }
    else {
      cell=row.insertCell(-1)
      img=document.createElement("IMG")
      lines+="4"
      img.src=nodeLines["i4"]
      img.setAttribute("id","icon"+nid)
      img.setAttribute("nid",nid)
      pNode.setAttribute("nstate","open")
      img.onclick=toggle
      img.style.cursor="hand"
      cell.appendChild(img)
    }
    // allow for custom elements
    if (onTreeRenderNode) {
      onTreeRenderNode(pNode,row)
    }
    labelspan=document.createElement("SPAN")
    labelspan.setAttribute("id","label_"+nid)
    labelspan.setAttribute("nid",nid)
    labelspan.style.cursor="pointer"
    labelspan.onclick=nodeClicked
    labelspan.onmouseover=nodeMouseOver
    labelspan.onmouseout=nodeMouseOut
    label=pNode.nodeName
    t=document.createTextNode(label)
    labelspan.appendChild(t)
    cell=row.insertCell(-1)
    cell.style.margin=0
    cell.appendChild(labelspan)
    pNode.setAttribute("nlines",lines)
    pCont.insertBefore(table,pInsertElem)
    return table
  }

  function treeParseNode(pCont,pNode, pLevel,lastChild, pLines, pInsertElem, pRecurse) {
  var t, label, labelspan, i, childCount, lineCount, img, table, oldTable, row, cell, last, ar
  var lines, chrome, newLines, newLine, atom, level, display
  var nid, objNode, map, cNode
    GLOBAL_NODE_COUNT+=1
    level=pLevel
    lines=pLines
    nid=pNode.getAttribute("id")
    map = new mapper()
    map.xml=pNode
    map.level=pLevel
    nodes[nid]=map
    lineCount=lines.length
    newLines=lines
    lastChild ? newLines+="0" : newLines+="1"
    if ((pRecurse)|| (level<=1)) {
      if (! SHOWALLNODES) {
        (level>1) ? display="none" : display="block"
      }
      else display="block"
      treeRenderNode(pCont,pNode, pLevel,lastChild, pLines, pInsertElem, display)
    }
    childCount=pNode.childNodes.length
    if (childCount>0) {
      for (i=0;i<childCount;i++) {
        last= (i==(childCount-1))
        cNode=pNode.childNodes[i]
        treeParseNode(pCont,cNode,level+1,last, newLines, pInsertElem, pRecurse)
      }
    }
  }
  

  function deleteTreeNode(pNode) {
  var i, childCount, eid, elem, prevElem, lastChild, imgs, imgCount, imgs
  var prevElem, parentNode
    childCount=pNode.childNodes.length
    if (childCount>0) {
      for (i=childCount-1;i>=0;i--) {
        deleteTreeNode(pNode.childNodes[i])
      }
    }
    eid=pNode.getAttribute("id")
    elem=nodes[eid].html
    parentNode=pNode.parentNode
    parentNode.removeChild(pNode)
    if (elem != null) {
      cont.removeChild(elem)
    }
    delete nodes[eid]
  }
  
  function treeDeleteNode() {
  var elem, nid, xnode, parentNode, parentElem, stopNode, lines, lastChild
    if (lastClicked==null) return
    nid=lastClicked.getAttribute("nid")
    xnode=nodes[nid].xml
    if (xnode==dataRoot) {
      alert(ROOT_DELETE_ERROR)
      return
    }
    elem=nodes[nid].html
    prevElement=elem.previousSibling

    parentNode=xnode.parentNode
    parentElem=nodes[parentNode.getAttribute("id")].html
    deleteTreeNode(xnode)
    lines=parentNode.getAttribute("nlines")
    lines=lines.substr(0,lines.length-1)
    checkNode(parentNode, lines, prevElement)
  }

  function treeAddChildNode(pLabel,pId) {
  var elem, nid, xnode, parentNode, parentElem, stopNode, lines, xlast
  var table, row, cell, img, label, labelspan, t, i, lineCount, newNode
  var insElem, map, lastChild
    if (lastClicked==null) return
    nid=lastClicked.getAttribute("nid")
    elem=nodes[nid].html
    xnode=nodes[nid].xml
    lastChild= (xnode.nextSibling() == null)
    lines=xnode.getAttribute("nlines")
    lines=lines.substr(0,lines.length-1)
    lastChild ? lines+="0" : lines+="1"
    lineCount=lines.length
    xlast=getLastNode(xnode)
    insElem=nodes[xlast.getAttribute("id")].html
    pLabel ? label=pLabel : label="new node"
    if (label=="") label="new node"
    pId ? nid=pId : nid=treeUniqueId()
    newNode=n(pLabel,"id:"+nid)
    xnode.appendChild(newNode)
    xnode.setAttribute("nstate","open")
    map= new mapper()
    map.xml=newNode
    nodes[nid]=map
    table=document.createElement("TABLE")
    map.html=table
    table.setAttribute("id",nid)
    table.cellSpacing=0
    table.cellPadding=0
    row=table.insertRow(-1)
    row.vAlign="top"
    for (i=0;i<lineCount;i++) {
      cell=row.insertCell(-1)
      img=document.createElement("IMG")
      atom=lines.substr(i,1)
      if ("12".indexOf(atom) != -1) {
        cell.style.backgroundImage="url(vline.gif)"
        img.src="blank.gif"
      }
      else {
        img.src=nodeLines["i"+atom]
      }
      cell.appendChild(img)
    }
    cell=row.insertCell(-1)
    img=document.createElement("IMG")
    img.src=nodeLines["i3"]
    lines+="3"
    cell.appendChild(img)
    // allow for custom elements
    if (onTreeRenderNode) {
      onTreeRenderNode(newNode,row)
    }
    labelspan=document.createElement("SPAN")
    labelspan.setAttribute("nid",nid)
    labelspan.setAttribute("id","label_"+nid)
    labelspan.style.cursor="pointer"
    labelspan.onclick=nodeClicked
    label=newNode.nodeName
    t=document.createTextNode(label)
    labelspan.appendChild(t)
    cell=row.insertCell(-1)
    cell.appendChild(labelspan)
    newNode.setAttribute("nlines",lines)
    cont.insertBefore(table, insElem.nextSibling)
    lines=xnode.getAttribute("nlines")
    lines=lines.substr(0,lines.length-1)
    checkNode(xnode, lines, table)
  }

  function getLastNode(pNode) {
    if (! pNode.hasChildNodes()) return pNode
    return getLastNode(pNode.lastChild())
  }

  function treeSelectNode(pId) {
  var elem, xnode, map
    map=nodes[pId]
    if (! map) return
    xnode=nodes[pId].xml
    treeOpenPath(xnode)
    elem=document.getElementById("label_"+pId)
    if (elem==null) return
    treeNodeClick(elem, true)
  }
  
  function treeGetPath(pNode) {
  var xnode, path
    xnode=pNode
    path=""
    while (xnode.parentNode != null) {
      xnode=xnode.parentNode
      if (path=="") {
        path=xnode.getAttribute("id")
      }
      else {
        path=xnode.getAttribute("id")+"|"+ path
      }
      if (xnode==dataRoot) break
    }
    return path
  }
  
  function treeClear() {
  var i, L, elem
    lastClicked=null
    nodes.length=0
    GLOBAL_NODE_COUNT=0
    if (cont.hasChildNodes()) {
      L=cont.childNodes.length
      for (i=L-1;i>=0;i--) {
        elem=cont.childNodes[i]
        cont.removeChild(elem)
      }
    }
  }

