/**
   * This method process the cells in a <code>Document</code> and generates a portion of the <code>
   * Document</code>.
   *
   * <p>This method assumes that records are sorted by row and then column.
   *
   * @param root The <code>Node</code> of the <code>Document</code> we are building that we will
   *     append our cell <code>Node</code> objects. This <code>Node</code> should be a TAG_TABLE
   *     tag.
   * @throws IOException If any I/O error occurs.
   */
  protected void processColumns(Node root) throws IOException {

    for (Iterator<ColumnRowInfo> e = decoder.getColumnRowInfos(); e.hasNext(); ) {

      ColumnRowInfo ci = e.next();
      if (ci.isColumn()) {
        ColumnStyle cStyle =
            new ColumnStyle(
                "Default",
                SxcConstants.COLUMN_STYLE_FAMILY,
                SxcConstants.DEFAULT_STYLE,
                ci.getSize(),
                null);

        Style result[] = styleCat.getMatching(cStyle);
        String styleName;
        if (result.length == 0) {

          cStyle.setName("co" + colStyles++);
          styleName = cStyle.getName();
          Debug.log(Debug.TRACE, "No existing style found, adding " + styleName);
          styleCat.add(cStyle);
        } else {
          ColumnStyle existingStyle = (ColumnStyle) result[0];
          styleName = existingStyle.getName();
          Debug.log(Debug.TRACE, "Existing style found : " + styleName);
        }

        // Create an element node for the new row
        Element colElement = doc.createElement(TAG_TABLE_COLUMN);
        colElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, styleName);
        if (ci.getRepeated() != 1) {
          String repeatStr = String.valueOf(ci.getRepeated());
          colElement.setAttribute(ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED, repeatStr);
        }
        root.appendChild(colElement);
      }
    }
  }
  /**
   * This method process the cells in a <code>Document</code> and generates a portion of the <code>
   * Document</code>.
   *
   * <p>This method assumes that records are sorted by row and then column.
   *
   * @param root The <code>Node</code> of the <code>Document</code> we are building that we will
   *     append our cell <code>Node</code> objects. This <code>Node</code> should be a TAG_TABLE
   *     tag.
   * @throws IOException If any I/O error occurs.
   */
  protected void processCells(Node root) throws IOException {

    // The current row element
    Element rowElement = null;

    // The current cell element
    Element cellElement = null;

    // The row number - we may not have any rows (empty sheet)
    // so set to zero.
    int row = 0;

    // The column number - This is the expected column number of
    // the next cell we are reading.
    int col = 1;

    // The number of columns in the spreadsheet
    int lastColumn = decoder.getNumberOfColumns();

    Node autoStylesNode = null;

    // Loop over all cells in the spreadsheet
    while (decoder.goToNextCell()) {

      // Get the row number
      int newRow = decoder.getRowNumber();

      // Is the cell in a new row, or part of the current row?
      if (newRow != row) {

        // Make sure that all the cells in the previous row
        // have been entered.
        if (col <= lastColumn && rowElement != null) {
          int numSkippedCells = lastColumn - col + 1;
          addEmptyCells(numSkippedCells, rowElement);
        }

        // log an end row - if we already have a row
        if (row != 0) {
          Debug.log(Debug.TRACE, "</tr>");
        }

        // How far is the new row from the last row?
        int deltaRows = newRow - row;

        // Check if we have skipped any rows
        if (deltaRows > 1) {
          // Add in empty rows
          addEmptyRows(deltaRows - 1, root, lastColumn);
        }

        // Re-initialize column (since we are in a new row)
        col = 1;

        // Create an element node for the new row
        rowElement = doc.createElement(TAG_TABLE_ROW);

        for (Iterator<ColumnRowInfo> e = decoder.getColumnRowInfos(); e.hasNext(); ) {
          ColumnRowInfo cri = e.next();
          if (cri.isRow() && cri.getRepeated() == newRow - 1) {
            // We have the correct Row BIFFRecord for this row
            RowStyle rStyle =
                new RowStyle(
                    "Default",
                    SxcConstants.ROW_STYLE_FAMILY,
                    SxcConstants.DEFAULT_STYLE,
                    cri.getSize(),
                    null);

            Style result[] = styleCat.getMatching(rStyle);
            String styleName;
            if (result.length == 0) {

              rStyle.setName("ro" + rowStyles++);
              styleName = rStyle.getName();
              Debug.log(Debug.TRACE, "No existing style found, adding " + styleName);
              styleCat.add(rStyle);
            } else {
              RowStyle existingStyle = (RowStyle) result[0];
              styleName = existingStyle.getName();
              Debug.log(Debug.TRACE, "Existing style found : " + styleName);
            }
            rowElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, styleName);
            // For now we will not use the repeat column attribute
          }
        }

        // Append the row element to the root node
        root.appendChild(rowElement);

        // Update row number
        row = newRow;

        Debug.log(Debug.TRACE, "<tr>");
      }

      // Get the column number of the current cell
      int newCol = decoder.getColNumber();

      // Check to see if some columns were skipped
      if (newCol != col) {

        // How many columns have we skipped?
        int numColsSkipped = newCol - col;

        addEmptyCells(numColsSkipped, rowElement);

        // Update the column number to account for the
        // skipped cells
        col = newCol;
      }

      // Lets start dealing with the cell data
      Debug.log(Debug.TRACE, "<td>");

      // Get the cell's contents
      String cellContents = decoder.getCellContents();

      // Get the type of the data in the cell
      String cellType = decoder.getCellDataType();

      // Get the cell format
      Format fmt = decoder.getCellFormat();

      // Create an element node for the cell
      cellElement = doc.createElement(TAG_TABLE_CELL);

      Node bodyNode = doc.getElementsByTagName(TAG_OFFICE_BODY).item(0);

      // Not every document has an automatic style tag
      autoStylesNode = doc.getElementsByTagName(TAG_OFFICE_AUTOMATIC_STYLES).item(0);

      if (autoStylesNode == null) {
        autoStylesNode = doc.createElement(TAG_OFFICE_AUTOMATIC_STYLES);
        doc.insertBefore(autoStylesNode, bodyNode);
      }

      CellStyle tStyle =
          new CellStyle(
              "Default",
              SxcConstants.TABLE_CELL_STYLE_FAMILY,
              SxcConstants.DEFAULT_STYLE,
              fmt,
              null);
      String styleName;
      Style result[] = styleCat.getMatching(tStyle);
      if (result.length == 0) {

        tStyle.setName("ce" + textStyles++);
        styleName = tStyle.getName();
        Debug.log(Debug.TRACE, "No existing style found, adding " + styleName);
        styleCat.add(tStyle);
      } else {
        CellStyle existingStyle = (CellStyle) result[0];
        styleName = existingStyle.getName();
        Debug.log(Debug.TRACE, "Existing style found : " + styleName);
      }

      cellElement.setAttribute(ATTRIBUTE_TABLE_STYLE_NAME, styleName);

      // Store the cell data into the appropriate attributes
      processCellData(cellElement, cellType, cellContents);

      // Append the cell element to the row node
      rowElement.appendChild(cellElement);

      // Append the cellContents as a text node
      Element textElement = doc.createElement(TAG_PARAGRAPH);
      cellElement.appendChild(textElement);
      textElement.appendChild(doc.createTextNode(cellContents));

      Debug.log(Debug.TRACE, cellContents);
      Debug.log(Debug.TRACE, "</td>");

      // Increment to the column number of the next expected cell
      col++;
    }

    // Make sure that the last row is padded correctly
    if (col <= lastColumn && rowElement != null) {
      int numSkippedCells = lastColumn - col + 1;
      addEmptyCells(numSkippedCells, rowElement);
    }

    // Now write the style catalog to the document
    if (autoStylesNode != null) {
      Debug.log(Debug.TRACE, "Well the autostyle node was found!!!");
      NodeList nl = styleCat.writeNode(doc, "dummy").getChildNodes();
      int nlLen = nl.getLength(); // nl.item reduces the length
      for (int i = 0; i < nlLen; i++) {
        autoStylesNode.appendChild(nl.item(0));
      }
    }

    if (row != 0) {

      // The sheet does have rows, so write out a /tr
      Debug.log(Debug.TRACE, "</tr>");
    }
  }
  /**
   * This method traverses a <i>table:table-cell</i> element {@code Node}.
   *
   * @param node a <i>table:table-cell</i> {@code Node}.
   * @throws IOException if any I/O error occurs.
   */
  protected void traverseCell(Node node) throws IOException {

    NamedNodeMap cellAtt = node.getAttributes();

    fmt.clearFormatting();

    // Get the type of data in the cell
    Node tableValueTypeNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_VALUE_TYPE);

    // Get the number of columns this cell is repeated
    Node colsRepeatedNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED);

    // Get the style type
    Node tableStyleNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_STYLE_NAME);

    String styleName = "";

    if (tableStyleNode != null) {
      styleName = tableStyleNode.getNodeValue();
    }

    CellStyle cStyle = null;

    if (styleName.equalsIgnoreCase("Default")) {

      Debug.log(Debug.TRACE, "No defined Style Attribute was found");

    } else if (styleName.length() != 0) {

      cStyle =
          (CellStyle)
              styleCat.lookup(
                  styleName, SxcConstants.TABLE_CELL_STYLE_FAMILY, null, CellStyle.class);
    }

    if (cStyle != null) {
      Format definedFormat = cStyle.getFormat();
      fmt = new Format(definedFormat);
    }

    // There is a number of cols repeated attribute
    if (colsRepeatedNode != null) {
      // Get the number of times the cell is repeated
      String colsRepeatedString = colsRepeatedNode.getNodeValue();
      colsRepeated = Integer.parseInt(colsRepeatedString);
    } else {
      // The cell is not repeated
      colsRepeated = 1;
    }

    // if there is no style we need to check to see if there is a default
    // cell style defined in the table-column's

    if (fmt.isDefault() && styleName.length() == 0) {
      int index = 1;
      for (Iterator<ColumnRowInfo> e = ColumnRowList.iterator(); e.hasNext(); ) {
        ColumnRowInfo cri = e.next();
        if (cri.isColumn()) {
          if (colID >= index && colID < (index + cri.getRepeated())) {
            fmt = new Format(cri.getFormat());
          }
          index += cri.getRepeated();
        }
      }
    }

    if (tableValueTypeNode != null) {

      String cellType = tableValueTypeNode.getNodeValue();

      if (cellType.equalsIgnoreCase(CELLTYPE_STRING)) {

        // has text:p tag
        fmt.setCategory(CELLTYPE_STRING);
        Node tableStringValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_STRING_VALUE);
        Debug.log(Debug.TRACE, "Cell Type String :  " + tableStringValueNode);
        if (tableStringValueNode != null) {
          fmt.setValue(tableStringValueNode.getNodeValue());
        }

      } else if (cellType.equalsIgnoreCase(CELLTYPE_FLOAT)) {

        // has table:value attribute
        // has text:p tag

        // Determine the number of decimal places StarCalc
        // is displaying for this floating point output.
        fmt.setCategory(CELLTYPE_FLOAT);
        fmt.setDecimalPlaces(getDecimalPlaces(node));
        Node tableValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_VALUE);
        fmt.setValue(tableValueNode.getNodeValue());

      } else if (cellType.equalsIgnoreCase(CELLTYPE_TIME)) {

        // has table:time-value attribute
        // has text:p tag - which is the value we convert

        fmt.setCategory(CELLTYPE_TIME);
        Node tableTimeNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_TIME_VALUE);
        fmt.setValue(tableTimeNode.getNodeValue());

      } else if (cellType.equalsIgnoreCase(CELLTYPE_DATE)) {

        // has table:date-value attribute
        // has text:p tag - which is the value we convert

        fmt.setCategory(CELLTYPE_DATE);
        Node tableDateNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_DATE_VALUE);
        fmt.setValue(tableDateNode.getNodeValue());

      } else if (cellType.equalsIgnoreCase(CELLTYPE_CURRENCY)) {

        // has table:currency
        // has table:value attribute
        // has text:p tag

        fmt.setCategory(CELLTYPE_CURRENCY);
        fmt.setDecimalPlaces(getDecimalPlaces(node));
        Node tableValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_VALUE);
        fmt.setValue(tableValueNode.getNodeValue());

      } else if (cellType.equalsIgnoreCase(CELLTYPE_BOOLEAN)) {

        // has table:boolean-value attribute
        // has text:p tag - which is the value we convert

        fmt.setCategory(CELLTYPE_BOOLEAN);
        Node tableBooleanNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_BOOLEAN_VALUE);
        fmt.setValue(tableBooleanNode.getNodeValue());

      } else if (cellType.equalsIgnoreCase(CELLTYPE_PERCENT)) {

        // has table:value attribute
        // has text:p tag

        fmt.setCategory(CELLTYPE_PERCENT);
        fmt.setDecimalPlaces(getDecimalPlaces(node));
        Node tableValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_VALUE);
        fmt.setValue(tableValueNode.getNodeValue());

      } else {

        Debug.log(Debug.TRACE, "No defined value type" + cellType);
        // Should never get here

      }
    }

    Node tableFormulaNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_FORMULA);

    if (tableFormulaNode != null) {
      if (tableValueTypeNode
          == null) { // If there is no value-type Node we must assume string-value
        fmt.setCategory(CELLTYPE_STRING);
        Node tableStringValueNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_STRING_VALUE);
        fmt.setValue(tableStringValueNode.getNodeValue());
      }
      String cellFormula = tableFormulaNode.getNodeValue();
      addCell(cellFormula);
    } else {

      // Text node, Date node, or Time node

      Debug.log(Debug.INFO, "TextNode, DateNode, TimeNode or BooleanNode\n");
      // This handles the case where we have style information but no content
      if (node.hasChildNodes()) {
        NodeList childList = node.getChildNodes();
        int len = childList.getLength();

        for (int i = 0; i < len; i++) {
          Node child = childList.item(i);
          if (child.getNodeType() == Node.ELEMENT_NODE) {
            String childName = child.getNodeName();
            if (childName.equals(TAG_PARAGRAPH)) {
              traverseParagraph(child);
            }
          }
        }
      } else if (!fmt.isDefault()) {
        addCell("");
      }
    }

    // Increase the column counter by the number of times the
    // last cell was repeated.
    colID += colsRepeated;

    // Re-initialize the number of columns repeated before processing
    // the next cell data.
    colsRepeated = 1;
  }
  /**
   * This method traverses the <i>table:table-column</i> {@code Node}.
   *
   * <p>Not yet implemented.
   *
   * @param node A <i>table:table-column</i> {@code Node}.
   * @throws IOException If any I/O error occurs.
   */
  protected void traverseTableColumn(Node node) throws IOException {

    Debug.log(Debug.TRACE, "traverseColumn() : ");
    NamedNodeMap cellAtt = node.getAttributes();
    Node tableStyleNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_STYLE_NAME);
    Node tableNumColRepeatingNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED);
    Node tableDefaultCellStyle = cellAtt.getNamedItem(ATTRIBUTE_DEFAULT_CELL_STYLE);

    int repeatedColumns = 1;
    int columnWidth = 0;
    ColumnRowInfo col = new ColumnRowInfo(ColumnRowInfo.COLUMN);

    if (tableNumColRepeatingNode != null) {
      Debug.log(
          Debug.TRACE,
          "traverseColumn() repeated-cols : " + tableNumColRepeatingNode.getNodeValue());
      repeatedColumns = Integer.parseInt(tableNumColRepeatingNode.getNodeValue());
      col.setRepeated(repeatedColumns);
    }

    String cellStyleName = "";

    if (tableDefaultCellStyle != null) {
      cellStyleName = tableDefaultCellStyle.getNodeValue();

      Debug.log(Debug.TRACE, "traverseColumn() default-cell-style : " + cellStyleName);
    }

    CellStyle cellStyle = null;

    if (cellStyleName.equalsIgnoreCase("Default") || cellStyleName.length() == 0) {

      Debug.log(Debug.TRACE, "No default cell Style Attribute was found");

    } else {

      cellStyle =
          (CellStyle)
              styleCat.lookup(
                  cellStyleName, SxcConstants.TABLE_CELL_STYLE_FAMILY, null, CellStyle.class);
    }

    if (cellStyle != null) {
      Format defaultFmt = new Format(cellStyle.getFormat());
      col.setFormat(defaultFmt);
    }

    String styleName = "";

    if (tableStyleNode != null) {
      styleName = tableStyleNode.getNodeValue();
    }

    if (styleName.equalsIgnoreCase("Default") || styleName.length() == 0) {

      Debug.log(Debug.TRACE, "No defined Style Attribute was found");

    } else {

      ColumnStyle cStyle =
          (ColumnStyle)
              styleCat.lookup(styleName, SxcConstants.COLUMN_STYLE_FAMILY, null, ColumnStyle.class);

      columnWidth = cStyle != null ? cStyle.getColWidth() : 0;
      col.setSize(columnWidth);
      Debug.log(Debug.TRACE, "traverseColumn() Column Width : " + columnWidth);
    }
    ColumnRowList.add(col);
  }