/**
   * 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 the <i>table:table-row</i> element {@code Node}.
   *
   * @param node A <i>table:table-row</i> {@code Node}.
   * @throws IOException If any I/O error occurs.
   */
  protected void traverseTableRow(Node node) throws IOException {

    // Get the attributes of the row
    NamedNodeMap cellAtt = node.getAttributes();

    if (cellAtt != null) {

      Node rowStyle = cellAtt.getNamedItem(ATTRIBUTE_TABLE_STYLE_NAME);

      Node tableNumRowRepeatingNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_NUM_ROWS_REPEATED);
      int repeatedRows = 1;

      if (tableNumRowRepeatingNode != null) {
        String repeatStr = tableNumRowRepeatingNode.getNodeValue();
        Debug.log(Debug.TRACE, "traverseTableRow() repeated-rows : " + repeatStr);
        repeatedRows = Integer.parseInt(repeatStr);
      }

      String styleName = "";

      if (rowStyle != null) {
        styleName = rowStyle.getNodeValue();
      }
      if (styleName.equalsIgnoreCase("Default") || styleName.length() == 0) {

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

      } else {

        RowStyle rStyle =
            (RowStyle)
                styleCat.lookup(styleName, SxcConstants.ROW_STYLE_FAMILY, null, RowStyle.class);

        int rowHeight = rStyle != null ? rStyle.getRowHeight() : 0;

        Debug.log(Debug.TRACE, "traverseTableRow() Row Height : " + rowHeight);
        ColumnRowInfo ri =
            new ColumnRowInfo(rowHeight, repeatedRows, ColumnRowInfo.ROW, rowHeight != 0);
        ColumnRowList.add(ri);
      }

      // Get the attribute representing the number of rows repeated
      Node rowsRepeatedNode = cellAtt.getNamedItem(ATTRIBUTE_TABLE_NUM_ROWS_REPEATED);

      // There is a number of rows repeated attribute:
      if (rowsRepeatedNode != null) {
        // Get the number of times the row is repeated
        String rowsRepeatedString = rowsRepeatedNode.getNodeValue();
        rowsRepeated = Integer.parseInt(rowsRepeatedString);
      } else {
        // The row is not repeated
        rowsRepeated = 1;
      }
    }

    Debug.log(Debug.TRACE, "<TR>");

    if (node.hasChildNodes()) {

      NodeList nodeList = node.getChildNodes();
      int len = nodeList.getLength();

      for (int i = 0; i < len; i++) {
        Node child = nodeList.item(i);

        if (child.getNodeType() == Node.ELEMENT_NODE) {
          String nodeName = child.getNodeName();

          if (nodeName.equals(TAG_TABLE_CELL)) {

            traverseCell(child);

          } else {

            Debug.log(Debug.TRACE, "<OTHERS " + XmlUtil.getNodeInfo(child) + " />");
          }
        }
      }
    }

    // Increase the row counter by the number of rows which are repeated
    rowID += rowsRepeated;

    // Re-initialize number of rows repeated before processing the next
    // row data.
    rowsRepeated = 1;

    // When starting a new row, set the column counter back to the
    // first column.
    colID = 1;

    // Re-initialize number of columns repeated before processing
    // the next row data.
    colsRepeated = 1;

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