function QBuilder(qxml, qxmlobj, qxmlForm, xmlField, colsField, rowsField, qtypexml, qtypeobj, qtypeForm, typeField, itemsField, fgField, bgField) {
   this.qxml = qxml;
   this.qxmlobj = qxmlobj;
   this.qxmlForm = qxmlForm;
   this.xmlField = xmlField;
   this.colsField = colsField;
   this.rowsField = rowsField;
   this.qtypexml = qtypexml;
   this.qtypeobj = qtypeobj;
   this.qtypeForm = qtypeForm;
   this.typeField = typeField;
   this.itemsField = itemsField;
   this.fgField = fgField;
   this.bgField = bgField;
   this.id = '';
   this.col = -1;
   this.row = -1;
   this.rowNodes = new Array();
   this.colNodes = new Array();
   this.itemNodes = new Array();
   this.save = new Array();
   this.init = QBuilderInit;
   this.setCurrent = QBuilderSetCurrent;
   this.cellType = QBuilderCellType;
   this.changeCellType = QBuilderChangeCellType;
   this.resetCellType = QBuilderResetCellType;
   this.saveCellType = QBuilderSaveCellType;
   this.transformCellType = QBuilderTransformCellType;
   this.appendCol = QBuilderAppendCol;
   this.insertCol = QBuilderInsertCol;
   this.deleteCol = QBuilderDeleteCol;
   this.appendRow = QBuilderAppendRow;
   this.insertRow = QBuilderInsertRow;
   this.deleteRow = QBuilderDeleteRow;
   this.appendItem = QBuilderAppendItem;
   this.insertItem = QBuilderInsertItem;
   this.deleteItem = QBuilderDeleteItem;
   this.changeItem = QBuilderChangeItem;
   this.newNode = QBuilderNewNode;
   this.newCol = QBuilderNewCol;
   this.newRow = QBuilderNewRow;
   this.newItem = QBuilderNewItem;
   this.cloneCol = QBuilderCloneCol;
   this.cloneRow = QBuilderCloneRow;
   this.init();
}

function QBuilderInit() {
   // get table node
   this.tableNode = this.qxml.doc.getElementsByTagName('table').item(0);

   // get row nodes
   this.rowNodes.length = 0;
   var rows = this.qxml.doc.getElementsByTagName('row');
   for (var i=0;i < rows.length;i++) {
      this.rowNodes[rows[i].getAttribute('id')] = rows[i];
   }

   // get col nodes
   this.colNodes.length = 0;
   var cols = this.qxml.doc.getElementsByTagName('col');
   for (var i=0;i < cols.length;i++) {
      var id = cols[i].getAttribute('id');
      var a = id.split('_');
      if (!this.colNodes[a[0]]) {
         this.colNodes[a[0]] = new Array();
      }
      this.colNodes[a[0]][a[1]] = cols[i];
   }

   // get item nodes
   this.itemNodes.length = 0;
   var items = this.qxml.doc.getElementsByTagName('item');
   for (var i=0;i < items.length;i++) {
      var id = items[i].getAttribute('id');
      var a = id.split('_');
      if (!this.itemNodes[a[0]]) {
         this.itemNodes[a[0]] = new Array();
      }
      if (!this.itemNodes[a[0]][a[1]]) {
         this.itemNodes[a[0]][a[1]] = new Array();
      }
      this.itemNodes[a[0]][a[1]][a[2]] = items[i];
   }

   // set form fields
   if (this.qxmlForm) {
      var f = document.forms[this.qxmlForm];
      var xmlField = eval('f.'+this.xmlField);
      var colsField = eval('f.'+this.colsField);
      var rowsField = eval('f.'+this.rowsField);
      if (xmlField) {
         xmlField.value = this.qxml.doc.xml;
      }
      if (colsField) {
         colsField.value = this.tableNode.getAttribute('cols');
         if (colsField.value > 0) {
            colsField.disabled = true;
         }
         else {
            colsField.disabled = false;
         }
      }
      if (rowsField) {
         rowsField.value = this.tableNode.getAttribute('rows');
         if (rowsField.value > 0) {
            rowsField.disabled = true;
         }
         else {
            rowsField.disabled = false;
         }
      }
   }
}

function QBuilderSetCurrent(id) {
   this.id = id;
   var a = this.id.split('_');
   this.row = a[0];
   this.col = a[1];
}

function QBuilderCellType() {
   var currentNode = this.colNodes[this.row][this.col];
   var type = currentNode.getAttribute('type') || '';
   var items = parseInt(currentNode.getAttribute('items')) || 0;
   var fg = currentNode.getAttribute('fg') || '#000000';
   var bg = currentNode.getAttribute('bg') || '#ffffff';
   var f = document.forms[this.qtypeForm];

   // update type
   var typeField = eval('f.'+this.typeField);
   for (var i=0;i < typeField.options.length;i++) {
      if (typeField.options[i].value == type) {
         typeField.options[i].selected = true;
         this.changeCellType('type', type);
      }
   }

   // update items
   var itemsField = eval('f.'+this.itemsField);
   itemsField.value = items;
   this.changeCellType('items', items);
   if (currentNode.getAttribute('itemSeq') > 0) {
      itemsField.disabled = true;
   }
   else {
      itemsField.disabled = false;
   }

   // update fg
   var fgField = eval('f.'+this.fgField);
   fgField.value = fg;
   this.changeCellType('fg', fg);

   // update bg
   var bgField = eval('f.'+this.bgField);
   bgField.value = bg;
   this.changeCellType('bg', bg);
   this.transformCellType();
}

function QBuilderChangeCellType(name, value, transform) {
   var currentNode = this.colNodes[this.row][this.col];
   this.save[name] = currentNode.getAttribute(name) || '';

   // add initial item nodes
   if (name == 'items') {
      if ((currentNode.getAttribute('type') != '') &&
          (currentNode.getAttribute('items') != value)) {
         var count = parseInt(value);
         for (var i=0;i < count;i++) {
            this.appendItem();
         }
      }
   }
   else {
      currentNode.setAttribute(name, value);
   }

   if (transform) {
      this.transformCellType();
   }
}

function QBuilderResetCellType() {
   var currentNode = this.colNodes[this.row][this.col];
   currentNode.setAttribute('type', this.save['type']);
   currentNode.setAttribute('items', this.save['items']);
   currentNode.setAttribute('fg', this.save['fg']);
   currentNode.setAttribute('bg', this.save['bg']);
   this.qxml.transform(this.qxmlobj);
   this.init();
}

function QBuilderSaveCellType() {
   this.qxml.transform(this.qxmlobj);
   this.init();
}

function QBuilderTransformCellType() {
   var currentNode = this.colNodes[this.row][this.col];
   this.qtypexml.setXML('<?xml version="1.0"?>'+currentNode.xml);
   this.qtypexml.transform(this.qtypeobj);
}

function QBuilderAppendCol() {
   // update col count
   var count = parseInt(this.tableNode.getAttribute('cols'))+1;
   this.tableNode.setAttribute('cols', count);

   // update row seq count
   var colid = parseInt(this.tableNode.getAttribute('colSeq'))+1;
   this.tableNode.setAttribute('colSeq', colid);

   // append col to rows
   for (var i in this.rowNodes) {
      // create new col
      var newNode = this.newCol(i, colid);
      var rowNode = this.rowNodes[i];
      var currentNode = this.colNodes[i][this.col];
      if (currentNode.nextSibling) {
         rowNode.insertBefore(newNode, currentNode.nextSibling);
      }
      else {
         rowNode.appendChild(newNode);
      }
   }

   // retransform
   this.qxml.transform(this.qxmlobj);
   this.init();
}

function QBuilderInsertCol() {
   // update col count
   var count = parseInt(this.tableNode.getAttribute('cols'))+1;
   this.tableNode.setAttribute('cols', count);

   // update row seq count
   var colid = parseInt(this.tableNode.getAttribute('colSeq'))+1;
   this.tableNode.setAttribute('colSeq', colid);

   // append col to rows
   for (var i in this.rowNodes) {
      // create new col
      var newNode = this.newCol(i, colid);
      var rowNode = this.rowNodes[i];
      var currentNode = this.colNodes[i][this.col];
      rowNode.insertBefore(newNode, currentNode);
   }

   // retransform
   this.qxml.transform(this.qxmlobj);
   this.init();
}

function QBuilderDeleteCol() {
   // update col count
   var count = parseInt(this.tableNode.getAttribute('cols'))-1;
   this.tableNode.setAttribute('cols', count);
   if (count == 0) {
      this.tableNode.setAttribute('rows', 0);
   }

   // delete col from rows
   for (var i in this.rowNodes) {
      var rowNode = this.rowNodes[i];
      var currentNode = this.colNodes[i][this.col];
      rowNode.removeChild(currentNode);
   }

   // retransform
   this.qxml.transform(this.qxmlobj);
   this.init();
}

function QBuilderAppendRow() {
   // create new row
   var newNode = this.newRow();

   // append row
   var currentNode = this.rowNodes[this.row];
   if (currentNode.nextSibling) {
      this.tableNode.insertBefore(newNode, currentNode.nextSibling);
   }
   else {
      this.tableNode.appendChild(newNode);
   }

   // retransform
   this.qxml.transform(this.qxmlobj);
   this.init();
}

function QBuilderInsertRow() {
   // create new row
   var newNode = this.newRow();

   // insert row
   var currentNode = this.rowNodes[this.row];
   this.tableNode.insertBefore(newNode, currentNode);

   // retransform
   this.qxml.transform(this.qxmlobj);
   this.init();
}

function QBuilderDeleteRow() {
   // update row count
   var count = parseInt(this.tableNode.getAttribute('rows'))-1;
   this.tableNode.setAttribute('rows', count);
   if (count == 0) {
      this.tableNode.setAttribute('cols', 0);
   }

   // delete row
   var currentNode = this.rowNodes[this.row];
   this.tableNode.removeChild(currentNode);

   // retransform
   this.qxml.transform(this.qxmlobj);
   this.init();
}

function QBuilderAppendItem(itemid) {
   // create new item
   var newNode = this.newItem();

   // append item
   var currentCol = this.colNodes[this.row][this.col];
   if (itemid) {
      var a = itemid.split('_');
      var currentNode = this.itemNodes[this.row][this.col][a[2]];
      if (currentNode.nextSibling) {
         currentCol.insertBefore(newNode, currentNode.nextSibling);
      }
      else {
         currentCol.appendChild(newNode);
      }
   }
   else {
      currentCol.appendChild(newNode);
   }

   // retransform
   this.transformCellType();
   this.init();
}

function QBuilderInsertItem(itemid) {
   // create new item
   var newNode = this.newItem();

   // insert item
   var a = itemid.split('_');
   var currentCol = this.colNodes[this.row][this.col];
   var currentNode = this.itemNodes[this.row][this.col][a[2]];
   currentCol.insertBefore(newNode, currentNode);

   // retransform
   this.transformCellType();
   this.init();
}

function QBuilderDeleteItem(itemid) {
   // update item count
   var currentCol = this.colNodes[this.row][this.col];
   var count = parseInt(currentCol.getAttribute('items'))-1;
   if (count < 0) {
      count = 0;
   }
   currentCol.setAttribute('items', count);

   // enable # items field?
   var f = document.forms[this.qtypeForm];
   var itemsField = eval('f.'+this.itemsField);
   itemsField.value = count;
   if (count == 0) {
      itemsField.disabled = false;
   }

   // delete item
   var a = itemid.split('_');
   var currentNode = this.itemNodes[this.row][this.col][a[2]];
   currentCol.removeChild(currentNode);

   // retransform
   this.transformCellType();
   this.init();
}

function QBuilderChangeItem(itemid, name, value) {
   var a = itemid.split('_');
   var currentNode = this.itemNodes[this.row][this.col][a[2]];
   if (name) {
      currentNode.setAttribute(name, value);
   }
   else {
      if (!currentNode.firstChild) {
         currentNode.appendChild(this.qxml.doc.createTextNode(''));
      }
      currentNode.firstChild.nodeValue = value;
   }
}

function QBuilderNewNode() {
   var node = this.qxml.doc.createElement(QBuilderNewNode.arguments[0]);
   for (var i=1;i < QBuilderNewNode.arguments.length;i+=2) {
      node.setAttribute(QBuilderNewNode.arguments[i],
                        QBuilderNewNode.arguments[i+1]);
   }
   return(node);
}

function QBuilderNewCol(rowid, colid) {
   // create new col node
   var newNode = this.newNode('col',
                              'id',
                              rowid+'_'+colid,
                              'itemSeq',
                              0);

   // return col node
   return(newNode);
}

function QBuilderNewRow() {
   // update row count
   var count = parseInt(this.tableNode.getAttribute('rows'))+1;
   this.tableNode.setAttribute('rows', count);

   // update row seq count
   var rowid = parseInt(this.tableNode.getAttribute('rowSeq'))+1;
   this.tableNode.setAttribute('rowSeq', rowid);

   // create new row node
   var newNode = this.newNode('row', 'id', rowid);

   // add cols to new row node
   var j=0;
   var cols = this.colNodes[this.row];
   for (var i in cols) {
      var colNode = this.newNode('col',
                                 'id',
                                 rowid+'_'+i,
                                 'itemSeq',
                                 0);
      newNode.appendChild(colNode);
   }

   // return row node
   return(newNode);
}

function QBuilderNewItem() {
   // get current node
   var currentCol = this.colNodes[this.row][this.col];

   // update row count
   var count = parseInt(currentCol.getAttribute('items'))+1;
   currentCol.setAttribute('items', count);

   // update item seq count
   var itemid = parseInt(currentCol.getAttribute('itemSeq'))+1;
   currentCol.setAttribute('itemSeq', itemid);

   // disable # items field?
   var f = document.forms[this.qtypeForm];
   var itemsField = eval('f.'+this.itemsField);
   if (!itemsField.disabled) {
      itemsField.disabled = true;
   }

   // create new item node
   var newNode = this.newNode('item', 'id', this.row+'_'+this.col+'_'+itemid);
   newNode.appendChild(this.qxml.doc.createTextNode(''));

   // return row node
   return(newNode);
}

function QBuilderCloneCol() {
   var currentNode = this.colNodes[this.row][this.col];
   var type = currentNode.getAttribute('type') || '';
   var items = parseInt(currentNode.getAttribute('items')) || 0;
   var itemSeq = parseInt(currentNode.getAttribute('itemSeq')) || 0;
   var fg = currentNode.getAttribute('fg') || '#000000';
   var bg = currentNode.getAttribute('bg') || '#ffffff';
   for (var i in this.rowNodes) {
      // create new col
      var colNode = this.colNodes[i][this.col];
      if (colNode.childNodes.length == 0) {
         colNode.setAttribute('type', type);
         colNode.setAttribute('items', items);
         colNode.setAttribute('itemSeq', itemSeq);
         colNode.setAttribute('fg', fg);
         colNode.setAttribute('bg', bg);
         for (var j in this.itemNodes[this.row][this.col]) {
            var n = this.itemNodes[this.row][this.col][j].cloneNode(true)
            n.setAttribute('id', i+'_'+this.col+'_'+j);
            colNode.appendChild(n);
         }
      }
   }

   // retransform
   this.qxml.transform(this.qxmlobj);
   this.init();
}

function QBuilderCloneRow() {
   var currentNode = this.colNodes[this.row][this.col];
   var type = currentNode.getAttribute('type') || '';
   var items = parseInt(currentNode.getAttribute('items')) || 0;
   var itemSeq = parseInt(currentNode.getAttribute('itemSeq')) || 0;
   var fg = currentNode.getAttribute('fg') || '#000000';
   var bg = currentNode.getAttribute('bg') || '#ffffff';
   for (var i in this.colNodes[this.row]) {
      // create new col
      var colNode = this.colNodes[this.row][i];
      if (colNode.childNodes.length == 0) {
         colNode.setAttribute('type', type);
         colNode.setAttribute('items', items);
         colNode.setAttribute('itemSeq', itemSeq);
         colNode.setAttribute('fg', fg);
         colNode.setAttribute('bg', bg);
         for (var j in this.itemNodes[this.row][this.col]) {
            var n = this.itemNodes[this.row][this.col][j].cloneNode(true)
            n.setAttribute('id', this.row+'_'+i+'_'+j);
            colNode.appendChild(n);
         }
      }
   }

   // retransform
   this.qxml.transform(this.qxmlobj);
   this.init();
}
