OdfTable.java

/**
 * **********************************************************************
 *
 * <p>Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * <p>**********************************************************************
 */
package org.odftoolkit.odfdom.doc.table;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.xerces.dom.ParentNode;
import org.odftoolkit.odfdom.doc.OdfDocument;
import org.odftoolkit.odfdom.doc.OdfDocument.OdfMediaType;
import org.odftoolkit.odfdom.doc.OdfSpreadsheetDocument;
import org.odftoolkit.odfdom.dom.OdfContentDom;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.OdfSchemaDocument;
import org.odftoolkit.odfdom.dom.OdfStylesDom;
import org.odftoolkit.odfdom.dom.attribute.table.TableAlignAttribute;
import org.odftoolkit.odfdom.dom.element.style.StyleTableCellPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTableColumnPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTablePropertiesElement;
import org.odftoolkit.odfdom.dom.element.table.TableCoveredTableCellElement;
import org.odftoolkit.odfdom.dom.element.table.TableNamedRangeElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableCellElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableCellElementBase;
import org.odftoolkit.odfdom.dom.element.table.TableTableColumnElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableColumnGroupElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableColumnsElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableHeaderColumnsElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableHeaderRowsElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableRowElement;
import org.odftoolkit.odfdom.dom.element.text.TextHElement;
import org.odftoolkit.odfdom.dom.element.text.TextListElement;
import org.odftoolkit.odfdom.dom.element.text.TextPElement;
import org.odftoolkit.odfdom.dom.style.OdfStyleFamily;
import org.odftoolkit.odfdom.dom.style.props.OdfTableProperties;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeAutomaticStyles;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfName;
import org.odftoolkit.odfdom.pkg.OdfPackageDocument;
import org.odftoolkit.odfdom.pkg.OdfXMLFactory;
import org.odftoolkit.odfdom.type.Length.Unit;
import org.odftoolkit.odfdom.type.PositiveLength;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * OdfTable represents the table feature in ODF spreadsheet and text documents.
 *
 * <p>OdfTable provides methods to get/add/delete/modify table column/row/cell.
 */
public class OdfTable {

  TableTableElement mTableElement;
  protected OdfDocument mDocument;
  protected boolean mIsSpreadsheet;
  protected boolean mIsCellStyleInheritance = true;
  private static final int DEFAULT_ROW_COUNT = 2;
  private static final int DEFAULT_COLUMN_COUNT = 5;
  private static final double DEFAULT_TABLE_WIDTH = 6;
  private static final int DEFAULT_REL_TABLE_WIDTH = 65535;
  private static final String DEFAULT_TABLE_ALIGN = "margins";
  // TODO: should save seperately for different dom tree
  static IdentityHashMap<TableTableElement, OdfTable> mTableRepository =
      new IdentityHashMap<TableTableElement, OdfTable>();
  IdentityHashMap<TableTableCellElementBase, Vector<OdfTableCell>> mCellRepository =
      new IdentityHashMap<TableTableCellElementBase, Vector<OdfTableCell>>();
  IdentityHashMap<TableTableRowElement, Vector<OdfTableRow>> mRowRepository =
      new IdentityHashMap<TableTableRowElement, Vector<OdfTableRow>>();
  IdentityHashMap<TableTableColumnElement, Vector<OdfTableColumn>> mColumnRepository =
      new IdentityHashMap<TableTableColumnElement, Vector<OdfTableColumn>>();

  private OdfTable(TableTableElement table) {
    mTableElement = table;
    mDocument = (OdfDocument) ((OdfFileDom) (table.getOwnerDocument())).getDocument();
    if (mDocument instanceof OdfSpreadsheetDocument) {
      mIsSpreadsheet = true;
    } else {
      mIsSpreadsheet = false;
    }
  }

  /**
   * Get a table feature instance by an instance of <code>TableTableElement</code>.
   *
   * @param odfElement an instance of <code>TableTableElement</code>
   * @return an instance of <code>OdfTable</code> that can represent <code>odfElement</code>
   */
  public static synchronized OdfTable getInstance(TableTableElement odfElement) {
    if (mTableRepository.containsKey(odfElement)) {
      return mTableRepository.get(odfElement);
    } else {
      OdfTable newTable = new OdfTable(odfElement);
      mTableRepository.put(odfElement, newTable);
      return newTable;
    }
  }

  OdfTableCell getCellInstance(
      TableTableCellElementBase cell, int repeatedColIndex, int repeatedRowIndex) {
    if (mCellRepository.containsKey(cell)) {
      Vector<OdfTableCell> list = mCellRepository.get(cell);
      OdfTableCell fCell = null;
      for (int i = 0; i < list.size(); i++) {
        if (list.get(i).getOdfElement() == cell
            && list.get(i).mnRepeatedColIndex == repeatedColIndex
            && list.get(i).mnRepeatedRowIndex == repeatedRowIndex) {
          fCell = list.get(i);
          break;
        }
      }
      if (fCell == null) {
        fCell = new OdfTableCell(cell, repeatedColIndex, repeatedRowIndex);
        list.add(fCell);
      }
      return fCell;
    } else {
      OdfTableCell newCell = new OdfTableCell(cell, repeatedColIndex, repeatedRowIndex);
      Vector<OdfTableCell> list = new Vector<OdfTableCell>();
      list.add(newCell);
      mCellRepository.put(cell, list);
      return newCell;
    }
  }

  OdfTableRow getRowInstance(TableTableRowElement row, int repeatedRowIndex) {
    if (mRowRepository.containsKey(row)) {
      Vector<OdfTableRow> list = mRowRepository.get(row);
      if (list.size() <= repeatedRowIndex) {
        list.setSize(repeatedRowIndex + 1);
      }
      OdfTableRow fCell = list.get(repeatedRowIndex);
      if (fCell == null) {
        fCell = new OdfTableRow(row, repeatedRowIndex);
        list.set(repeatedRowIndex, fCell);
      }
      return fCell;
    } else {
      OdfTableRow newCell = new OdfTableRow(row, repeatedRowIndex);
      int size = (repeatedRowIndex > 7) ? (repeatedRowIndex + 1) : 8;
      Vector<OdfTableRow> list = new Vector<OdfTableRow>(size);
      list.setSize(repeatedRowIndex + 1);
      list.set(repeatedRowIndex, newCell);
      mRowRepository.put(row, list);
      return newCell;
    }
  }

  OdfTableColumn getColumnInstance(TableTableColumnElement col, int repeatedColIndex) {
    if (mColumnRepository.containsKey(col)) {
      Vector<OdfTableColumn> list = mColumnRepository.get(col);
      if (list.size() <= repeatedColIndex) {
        list.setSize(repeatedColIndex + 1);
      }
      OdfTableColumn fClm = list.get(repeatedColIndex);
      if (fClm == null) {
        fClm = new OdfTableColumn(col, repeatedColIndex);
        list.set(repeatedColIndex, fClm);
      }
      return fClm;
    } else {
      OdfTableColumn newCell = new OdfTableColumn(col, repeatedColIndex);
      int size = (repeatedColIndex > 7) ? (repeatedColIndex + 1) : 8;
      Vector<OdfTableColumn> list = new Vector<OdfTableColumn>(size);
      list.setSize(repeatedColIndex + 1);
      list.set(repeatedColIndex, newCell);
      mColumnRepository.put(col, list);
      return newCell;
    }
  }

  TableTableColumnElement getColumnElementByIndex(int colIndex) {
    int result = 0;
    TableTableColumnElement columnEle = null;
    for (Node n : new DomNodeList(mTableElement.getChildNodes())) {
      if (n instanceof TableTableHeaderColumnsElement
          || n instanceof TableTableColumnGroupElement) {
        ParentNode headers = (ParentNode) n;
        for (Node m : new DomNodeList(headers.getChildNodes())) {
          if (m instanceof TableTableColumnElement) {
            columnEle = (TableTableColumnElement) m;
            if (columnEle.getTableNumberColumnsRepeatedAttribute() == null) {
              result += 1;
            } else {
              result += columnEle.getTableNumberColumnsRepeatedAttribute();
            }
          }
          if (result > colIndex) {
            break;
          }
        }
      }
      if (n instanceof TableTableColumnElement) {
        columnEle = (TableTableColumnElement) n;
        if (columnEle.getTableNumberColumnsRepeatedAttribute() == null) {
          result += 1;
        } else {
          result += columnEle.getTableNumberColumnsRepeatedAttribute();
        }
      }
      if (result > colIndex) {
        break;
      }
    }
    return columnEle;
  }

  TableTableRowElement getRowElementByIndex(int rowIndex) {
    int result = 0;
    TableTableRowElement rowEle = null;
    for (Node n : new DomNodeList(mTableElement.getChildNodes())) {
      if (n instanceof TableTableHeaderRowsElement) {
        TableTableHeaderRowsElement headers = (TableTableHeaderRowsElement) n;
        for (Node m : new DomNodeList(headers.getChildNodes())) {
          if (m instanceof TableTableRowElement) {
            rowEle = (TableTableRowElement) m;
            result += rowEle.getTableNumberRowsRepeatedAttribute();
          }
          if (result > rowIndex) {
            break;
          }
        }
      }
      if (n instanceof TableTableRowElement) {
        rowEle = (TableTableRowElement) n;
        result += ((TableTableRowElement) n).getTableNumberRowsRepeatedAttribute();
      }
      if (result > rowIndex) {
        break;
      }
    }
    return rowEle;
  }

  /**
   * Get the width of the table (in Millimeter).
   *
   * <p>Throw an UnsupportedOperationException if the table is one sheet of a spreadsheet document.
   * because the sheet doesn't have an attribute of table width.
   *
   * @return the width of the current table (in Millimeter).
   *     <p>An UnsupportedOperationException will be thrown if the table is in the spreadsheet
   *     document.
   */
  public long getWidth() {
    if (!mIsSpreadsheet) {
      String sWidth = mTableElement.getProperty(OdfTableProperties.Width);
      if (sWidth == null) {
        int colCount = getColumnCount();
        int tableWidth = 0;
        for (int i = 0; i < colCount; i++) {
          OdfTableColumn col = getColumnByIndex(i);
          tableWidth += col.getWidth();
        }
        return tableWidth;
      } else {
        return PositiveLength.parseLong(sWidth, Unit.MILLIMETER);
      }
    } else {
      throw new UnsupportedOperationException();
    }
  }

  /**
   * Set the width of the table (in Millimeter).
   *
   * <p>Throw an UnsupportedOperationException if the table is part of a spreadsheet document that
   * does not allow to change the table size, because spreadsheet is not allow user to set the table
   * size.
   *
   * @param width the width that need to set (in Millimeter).
   *     <p>An UnsupportedOperationException will be thrown if the table is in the spreadsheet
   *     document.
   */
  public void setWidth(long width) {
    if (!mIsSpreadsheet) {
      String sWidthMM = String.valueOf(width) + Unit.MILLIMETER.abbr();
      String sWidthIN = PositiveLength.mapToUnit(sWidthMM, Unit.INCH);
      mTableElement.setProperty(OdfTableProperties.Width, sWidthIN);
      // if the width is changed, we should also change the table:align properties if it is
      // "margins"
      // otherwise the width seems not changed
      String alineStyle = mTableElement.getProperty(StyleTablePropertiesElement.Align);
      if (TableAlignAttribute.Value.MARGINS.toString().equals(alineStyle)) {
        mTableElement.setProperty(
            StyleTablePropertiesElement.Align, TableAlignAttribute.Value.LEFT.toString());
      }
    } else {
      throw new UnsupportedOperationException();
    }
  }

  static void setLeftTopBorderStyleProperties(OdfStyle style) {
    style.setProperty(StyleTableCellPropertiesElement.Padding, "0.0382in");
    style.setProperty(StyleTableCellPropertiesElement.BorderLeft, "0.0007in solid #000000");
    style.setProperty(StyleTableCellPropertiesElement.BorderRight, "none");
    style.setProperty(StyleTableCellPropertiesElement.BorderTop, "0.0007in solid #000000");
    style.setProperty(StyleTableCellPropertiesElement.BorderBottom, "0.0007in solid #000000");
  }

  static void setRightTopBorderStyleProperties(OdfStyle style) {
    style.setProperty(StyleTableCellPropertiesElement.Padding, "0.0382in");
    style.setProperty(StyleTableCellPropertiesElement.Border, "0.0007in solid #000000");
  }

  static void setLeftBottomBorderStylesProperties(OdfStyle style) {
    style.setProperty(StyleTableCellPropertiesElement.Padding, "0.0382in");
    style.setProperty(StyleTableCellPropertiesElement.BorderLeft, "0.0007in solid #000000");
    style.setProperty(StyleTableCellPropertiesElement.BorderRight, "none");
    style.setProperty(StyleTableCellPropertiesElement.BorderTop, "none");
    style.setProperty(StyleTableCellPropertiesElement.BorderBottom, "0.0007in solid #000000");
  }

  static void setRightBottomBorderStylesProperties(OdfStyle style) {
    style.setProperty(StyleTableCellPropertiesElement.Padding, "0.0382in");
    style.setProperty(StyleTableCellPropertiesElement.Border, "0.0007in solid #000000");
    style.setProperty(StyleTableCellPropertiesElement.BorderTop, "none");
    style.setProperty(StyleTableCellPropertiesElement.BorderBottom, "0.0007in solid #000000");
  }

  private static TableTableElement createTable(
      OdfElement parent, int numRows, int numCols, int headerRowNumber, int headerColumnNumber)
      throws Exception {

    // check arguments
    if (numRows < 1
        || numCols < 1
        || headerRowNumber < 0
        || headerColumnNumber < 0
        || headerRowNumber > numRows
        || headerColumnNumber > numCols) {
      throw new IllegalArgumentException(
          "Can not create table with the given parameters:\n"
              + "Rows "
              + numRows
              + ", Columns "
              + numCols
              + ", HeaderRows "
              + headerRowNumber
              + ", HeaderColumns "
              + headerColumnNumber);
    }
    OdfFileDom dom = (OdfFileDom) parent.getOwnerDocument();
    OdfOfficeAutomaticStyles styles = null;
    if (dom instanceof OdfContentDom) {
      styles = ((OdfContentDom) dom).getAutomaticStyles();
    } else if (dom instanceof OdfStylesDom) {
      styles = ((OdfStylesDom) dom).getAutomaticStyles();
    }

    // 1. create table element
    TableTableElement newTEle =
        (TableTableElement)
            OdfXMLFactory.newOdfElement(dom, OdfName.newName(OdfDocumentNamespace.TABLE, "table"));
    String tablename = getUniqueTableName(parent);
    newTEle.setTableNameAttribute(tablename);
    // create style
    OdfStyle tableStyle = styles.newStyle(OdfStyleFamily.Table);
    String stylename = tableStyle.getStyleNameAttribute();
    tableStyle.setProperty(StyleTablePropertiesElement.Width, DEFAULT_TABLE_WIDTH + "in");
    tableStyle.setProperty(StyleTablePropertiesElement.Align, DEFAULT_TABLE_ALIGN);
    newTEle.setStyleName(stylename);

    // 2. create column elements
    // 2.0 create column style
    OdfStyle columnStyle = styles.newStyle(OdfStyleFamily.TableColumn);
    String columnStylename = columnStyle.getStyleNameAttribute();
    columnStyle.setProperty(
        StyleTableColumnPropertiesElement.ColumnWidth,
        new DecimalFormat("000.0000").format(DEFAULT_TABLE_WIDTH / numCols) + "in");
    columnStyle.setProperty(
        StyleTableColumnPropertiesElement.RelColumnWidth,
        Math.round(DEFAULT_REL_TABLE_WIDTH / numCols) + "*");
    // 2.1 create header column elements
    if (headerColumnNumber > 0) {
      TableTableHeaderColumnsElement headercolumns =
          (TableTableHeaderColumnsElement)
              OdfXMLFactory.newOdfElement(
                  dom, OdfName.newName(OdfDocumentNamespace.TABLE, "table-header-columns"));
      TableTableColumnElement headercolumn =
          (TableTableColumnElement)
              OdfXMLFactory.newOdfElement(
                  dom, OdfName.newName(OdfDocumentNamespace.TABLE, "table-column"));
      headercolumn.setTableNumberColumnsRepeatedAttribute(headerColumnNumber);
      headercolumns.appendChild(headercolumn);
      newTEle.appendChild(headercolumns);
      headercolumn.setStyleName(columnStylename);
    }
    // 2.2 create common column elements
    TableTableColumnElement columns =
        (TableTableColumnElement)
            OdfXMLFactory.newOdfElement(
                dom, OdfName.newName(OdfDocumentNamespace.TABLE, "table-column"));
    columns.setTableNumberColumnsRepeatedAttribute(numCols - headerColumnNumber);
    columns.setStyleName(columnStylename);
    newTEle.appendChild(columns);

    // 3. create row elements
    // 3.0 create 4 kinds of styles
    OdfStyle lefttopStyle = null,
        leftbottomStyle = null,
        righttopStyle = null,
        rightbottomStyle = null;

    OdfPackageDocument document = dom.getDocument();
    if (!document.getMediaTypeString().equals(OdfMediaType.SPREADSHEET.getMediaTypeString())) {
      lefttopStyle = styles.newStyle(OdfStyleFamily.TableCell);
      setLeftTopBorderStyleProperties(lefttopStyle);

      leftbottomStyle = styles.newStyle(OdfStyleFamily.TableCell);
      setLeftBottomBorderStylesProperties(leftbottomStyle);

      righttopStyle = styles.newStyle(OdfStyleFamily.TableCell);
      setRightTopBorderStyleProperties(righttopStyle);

      rightbottomStyle = styles.newStyle(OdfStyleFamily.TableCell);
      setRightBottomBorderStylesProperties(rightbottomStyle);
    }

    // 3.1 create header row elements
    if (headerRowNumber > 0) {
      TableTableHeaderRowsElement headerrows =
          (TableTableHeaderRowsElement)
              OdfXMLFactory.newOdfElement(
                  dom, OdfName.newName(OdfDocumentNamespace.TABLE, "table-header-rows"));
      for (int i = 0; i < headerRowNumber; i++) {
        TableTableRowElement aRow =
            (TableTableRowElement)
                OdfXMLFactory.newOdfElement(
                    dom, OdfName.newName(OdfDocumentNamespace.TABLE, "table-row"));
        for (int j = 0; j < numCols; j++) {
          TableTableCellElement aCell =
              (TableTableCellElement)
                  OdfXMLFactory.newOdfElement(
                      dom, OdfName.newName(OdfDocumentNamespace.TABLE, "table-cell"));
          TextPElement aParagraph =
              (TextPElement)
                  OdfXMLFactory.newOdfElement(dom, OdfName.newName(OdfDocumentNamespace.TEXT, "p"));
          aCell.appendChild(aParagraph);
          if (!(document instanceof OdfSpreadsheetDocument)) {
            if ((j + 1 == numCols) && (i == 0)) {
              aCell.setStyleName(righttopStyle.getStyleNameAttribute());
            } else if (i == 0) {
              aCell.setStyleName(lefttopStyle.getStyleNameAttribute());
            } else if ((j + 1 == numCols) && (i > 0)) {
              aCell.setStyleName(rightbottomStyle.getStyleNameAttribute());
            } else {
              aCell.setStyleName(leftbottomStyle.getStyleNameAttribute());
            }
          }
          aRow.appendChild(aCell);
        }
        headerrows.appendChild(aRow);
      }
      newTEle.appendChild(headerrows);
    }

    // 3.2 create common row elements
    for (int i = headerRowNumber; i < numRows; i++) {
      TableTableRowElement aRow =
          (TableTableRowElement)
              OdfXMLFactory.newOdfElement(
                  dom, OdfName.newName(OdfDocumentNamespace.TABLE, "table-row"));
      for (int j = 0; j < numCols; j++) {
        TableTableCellElement aCell =
            (TableTableCellElement)
                OdfXMLFactory.newOdfElement(
                    dom, OdfName.newName(OdfDocumentNamespace.TABLE, "table-cell"));
        TextPElement aParagraph =
            (TextPElement)
                OdfXMLFactory.newOdfElement(dom, OdfName.newName(OdfDocumentNamespace.TEXT, "p"));
        aCell.appendChild(aParagraph);
        if (!(document instanceof OdfSpreadsheetDocument)) {
          if ((j + 1 == numCols) && (i == 0)) {
            aCell.setStyleName(righttopStyle.getStyleNameAttribute());
          } else if (i == 0) {
            aCell.setStyleName(lefttopStyle.getStyleNameAttribute());
          } else if ((j + 1 == numCols) && (i > 0)) {
            aCell.setStyleName(rightbottomStyle.getStyleNameAttribute());
          } else {
            aCell.setStyleName(leftbottomStyle.getStyleNameAttribute());
          }
        }
        aRow.appendChild(aCell);
      }
      newTEle.appendChild(aRow);
    }

    return newTEle;
  }

  //	/**
  //	 * The given table will be appended at the end of the given document.
  //	 *
  //	 * @param document	the ODF document that contains this feature
  //	 * @return the created <code>OdfTable</code> feature instance
  //	 */
  //	public static void appendTable(TableTableElement table, OdfDocument document) {
  //		try {
  //			OdfElement contentRoot = document.getContentRoot();
  //			contentRoot.appendChild(table);
  //		} catch (DOMException e) {
  //			Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, e.getMessage(), e);
  //		} catch (Exception e) {
  //			Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, e.getMessage(), e);
  //		}
  //	}
  /**
   * Construct the <code>OdfTable</code> feature. The default column count is 5. The default row
   * count is 2.
   *
   * <p>The table will be inserted at the end of the document the parent element belongs to. An
   * unique table name will be given, you may set a custom table name using the <code>setTableName
   * </code> method.
   *
   * <p>If the document is a text document, cell borders will be created by default.
   *
   * @param tableParent the ODF element the new table will be appended to
   * @return the created <code>OdfTable</code> feature instance
   */
  public static OdfTable newTable(OdfElement tableParent) {
    try {
      TableTableElement newTEle =
          createTable(tableParent, DEFAULT_ROW_COUNT, DEFAULT_COLUMN_COUNT, 0, 0);
      tableParent.appendChild(newTEle);
      return OdfTable.getInstance(newTEle);

    } catch (DOMException e) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, e.getMessage(), e);
    } catch (Exception e) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, e.getMessage(), e);
    }

    return null;
  }

  /**
   * Construct the <code>OdfTable</code> feature. The default column count is 5. The default row
   * count is 2.
   *
   * <p>The table will be inserted at the end of the given document. An unique table name will be
   * given, you may set a custom table name using the <code>setTableName</code> method.
   *
   * <p>If the document is a text document, cell borders will be created by default.
   *
   * @param document the ODF document that contains this feature
   * @return the created <code>OdfTable</code> feature instance
   */
  public static OdfTable newTable(OdfDocument document) {
    OdfTable table = null;
    try {
      table = newTable(document.getContentRoot());
    } catch (Exception ex) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, null, ex);
    }
    return table;
  }

  private static String getUniqueTableName(OdfElement tableParent) {
    OdfFileDom dom = (OdfFileDom) tableParent.getOwnerDocument();
    List<TableTableElement> tableList = ((OdfSchemaDocument) dom.getDocument()).getTables();
    boolean notUnique = true;

    String tablename = "Table" + (tableList.size() + 1);

    while (notUnique) {
      notUnique = false;
      for (int i = 0; i < tableList.size(); i++) {
        if (tableList.get(i).getTableNameAttribute().equalsIgnoreCase(tablename)) {
          notUnique = true;
          break;
        }
      }
      if (notUnique) {
        tablename = tablename + Math.round(Math.random() * 10);
      }
    }

    return tablename;
  }

  /**
   * Construct the <code>OdfTable</code> feature with a specified row number and column number.
   *
   * <p>The table will be inserted at the end of the document. An unique table name will be given,
   * you may set a custom table name using the <code>setTableName</code> method.
   *
   * <p>If the document is a text document, cell borders will be created by default.
   *
   * @param tableParent the ODF element the new table will be appended to
   * @param numRows the row number
   * @param numCols the column number
   * @return a new instance of <code>OdfTable</code>
   */
  public static OdfTable newTable(OdfElement tableParent, int numRows, int numCols) {
    try {
      TableTableElement newTEle = createTable(tableParent, numRows, numCols, 0, 0);
      tableParent.appendChild(newTEle);

      return OdfTable.getInstance(newTEle);

    } catch (Exception e) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, e.getMessage(), e);
    }

    return null;
  }

  /**
   * Construct the <code>OdfTable</code> feature with a specified row number and column number.
   *
   * <p>The table will be inserted at the end of the document. An unique table name will be given,
   * you may set a custom table name using the <code>setTableName</code> method.
   *
   * <p>If the document is a text document, cell borders will be created by default.
   *
   * @param document the ODF document that contains this feature
   * @param numRows the row number
   * @param numCols the column number
   * @return a new instance of <code>OdfTable</code>
   */
  public static OdfTable newTable(OdfDocument document, int numRows, int numCols) {
    OdfTable table = null;
    try {
      table = newTable(document.getContentRoot(), numRows, numCols);
    } catch (Exception ex) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, null, ex);
    }
    return table;
  }

  /**
   * Construct the <code>OdfTable</code> feature with a specified row number, column number, header
   * row number, header column number.
   *
   * <p>An unique table name will be given, you may set a custom table name using the <code>
   * setTableName</code> method.
   *
   * <p>If the document is a text document, cell borders will be created by default.
   *
   * @param tableParent the ODF element the new table will be appended to
   * @param numRows the row number
   * @param numCols the column number
   * @param headerRowNumber the header row number
   * @param headerColumnNumber the header column number
   * @return a new instance of <code>OdfTable</code>
   */
  public static OdfTable newTable(
      OdfElement tableParent,
      int numRows,
      int numCols,
      int headerRowNumber,
      int headerColumnNumber) {
    try {
      TableTableElement newTEle =
          createTable(tableParent, numRows, numCols, headerRowNumber, headerColumnNumber);
      tableParent.appendChild(newTEle);

      return OdfTable.getInstance(newTEle);

    } catch (DOMException e) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, e.getMessage(), e);
    } catch (Exception e) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, e.getMessage(), e);
    }

    return null;
  }

  /**
   * Construct the OdfTable feature with a specified 2 dimension array as the data of this table.
   * The value type of each cell is float.
   *
   * <p>The table will be inserted at the end of the document. An unique table name will be given,
   * you may set a custom table name using the <code>setTableName</code> method.
   *
   * <p>If the document is a text document, cell borders will be created by default.
   *
   * @param document the ODF document that contains this feature
   * @param numRows the row number
   * @param numCols the column number
   * @param headerRowNumber the header row number
   * @param headerColumnNumber the header column number
   * @return a new instance of <code>OdfTable</code>
   */
  public static OdfTable newTable(
      OdfDocument document, int numRows, int numCols, int headerRowNumber, int headerColumnNumber) {
    OdfTable table = null;
    try {
      table =
          newTable(
              document.getContentRoot(), numRows, numCols, headerRowNumber, headerColumnNumber);
    } catch (Exception ex) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, null, ex);
    }
    return table;
  }

  /**
   * Construct the OdfTable feature with a specified 2 dimension array as the data of this table.
   * The value type of each cell is float.
   *
   * <p>The table will be inserted at the end of the document. An unique table name will be given,
   * you may set a custom table name using the <code>setTableName</code> method.
   *
   * <p>If the document is a text document, cell borders will be created by default.
   *
   * @param tableParent the ODF document that contains this feature
   * @param rowLabel set as the header row, it can be null if no header row needed
   * @param columnLabel set as the header column, it can be null if no header column needed
   * @param data the two dimension array of double as the data of this table
   * @return a new instance of <code>OdfTable</code>
   */
  public static OdfTable newTable(
      OdfElement tableParent, String[] rowLabel, String[] columnLabel, double[][] data) {
    int rowNumber = DEFAULT_ROW_COUNT;
    int columnNumber = DEFAULT_COLUMN_COUNT;
    if (data != null) {
      rowNumber = data.length;
      columnNumber = data[0].length;
    }
    int rowHeaders = 0, columnHeaders = 0;

    if (rowLabel != null) {
      rowHeaders = 1;
    }
    if (columnLabel != null) {
      columnHeaders = 1;
    }

    try {
      TableTableElement newTEle =
          createTable(
              tableParent,
              rowNumber + rowHeaders,
              columnNumber + columnHeaders,
              rowHeaders,
              columnHeaders);
      tableParent.appendChild(newTEle);

      OdfTable table = OdfTable.getInstance(newTEle);
      List<OdfTableRow> rowList = table.getRowList();
      for (int i = 0; i < rowNumber + rowHeaders; i++) {
        OdfTableRow row = rowList.get(i);
        for (int j = 0; j < columnNumber + columnHeaders; j++) {
          if ((i == 0) && (j == 0)) {
            continue;
          }
          OdfTableCell cell = row.getCellByIndex(j);
          if (i == 0 && columnLabel != null) // first row, should fill column labels
          {
            if (j <= columnLabel.length) {
              cell.setStringValue(columnLabel[j - 1]);
            } else {
              cell.setStringValue("");
            }
          } else if (j == 0 && rowLabel != null) // first column, should fill row labels
          {
            if (i <= rowLabel.length) {
              cell.setStringValue(rowLabel[i - 1]);
            } else {
              cell.setStringValue("");
            }
          } else { // data
            if ((data != null) && (i >= rowHeaders) && (j >= columnHeaders)) {
              cell.setDoubleValue(data[i - rowHeaders][j - columnHeaders]);
            }
          }
        }
      }
      return table;
    } catch (DOMException e) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, e.getMessage(), e);
    } catch (Exception e) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, e.getMessage(), e);
    }
    return null;
  }

  /**
   * Construct the OdfTable feature with a specified 2 dimension array as the data of this table.
   * The value type of each cell is float.
   *
   * <p>The table will be inserted at the end of the document. An unique table name will be given,
   * you may set a custom table name using the <code>setTableName</code> method.
   *
   * <p>If the document is a text document, cell borders will be created by default.
   *
   * @param document the ODF document that contains this feature
   * @param rowLabel set as the header row, it can be null if no header row needed
   * @param columnLabel set as the header column, it can be null if no header column needed
   * @param data the two dimension array of double as the data of this table
   * @return a new instance of <code>OdfTable</code>
   */
  public static OdfTable newTable(
      OdfDocument document, String[] rowLabel, String[] columnLabel, double[][] data) {
    OdfTable table = null;
    try {
      table = newTable(document.getContentRoot(), rowLabel, columnLabel, data);
    } catch (Exception ex) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, null, ex);
    }
    return table;
  }

  /**
   * Construct the OdfTable feature with a specified 2 dimension array as the data of this table.
   * The value type of each cell is string.
   *
   * <p>The table will be inserted at the end of the document. An unique table name will be given,
   * you may set a custom table name using the <code>setTableName</code> method.
   *
   * <p>If the document is a text document, cell borders will be created by default.
   *
   * @param tableParent the ODF element the new table will be appended to
   * @param rowLabel set as the header row, it can be null if no header row needed
   * @param columnLabel set as the header column, it can be null if no header column needed
   * @param data the two dimension array of string as the data of this table
   * @return a new instance of <code>OdfTable</code>
   */
  public static OdfTable newTable(
      OdfElement tableParent, String[] rowLabel, String[] columnLabel, String[][] data) {
    int rowNumber = DEFAULT_ROW_COUNT;
    int columnNumber = DEFAULT_COLUMN_COUNT;
    if (data != null) {
      rowNumber = data.length;
      columnNumber = data[0].length;
    }
    int rowHeaders = 0, columnHeaders = 0;

    if (rowLabel != null) {
      rowHeaders = 1;
    }
    if (columnLabel != null) {
      columnHeaders = 1;
    }

    try {
      TableTableElement newTEle =
          createTable(
              tableParent,
              rowNumber + rowHeaders,
              columnNumber + columnHeaders,
              rowHeaders,
              columnHeaders);
      tableParent.appendChild(newTEle);

      OdfTable table = OdfTable.getInstance(newTEle);
      List<OdfTableRow> rowList = table.getRowList();
      for (int i = 0; i < rowNumber + rowHeaders; i++) {
        OdfTableRow row = rowList.get(i);
        for (int j = 0; j < columnNumber + columnHeaders; j++) {
          if ((i == 0) && (j == 0)) {
            continue;
          }
          OdfTableCell cell = row.getCellByIndex(j);
          if (i == 0 && columnLabel != null) // first row, should fill column labels
          {
            if (j <= columnLabel.length) {
              cell.setStringValue(columnLabel[j - 1]);
            } else {
              cell.setStringValue("");
            }
          } else if (j == 0 && rowLabel != null) // first column, should fill row labels
          {
            if (i <= rowLabel.length) {
              cell.setStringValue(rowLabel[i - 1]);
            } else {
              cell.setStringValue("");
            }
          } else {
            if ((data != null) && (i >= rowHeaders) && (j >= columnHeaders)) {
              cell.setStringValue(data[i - rowHeaders][j - columnHeaders]);
            }
          }
        }
      }

      return table;

    } catch (DOMException e) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, e.getMessage(), e);
    } catch (Exception e) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, e.getMessage(), e);
    }

    return null;
  }

  /**
   * Construct the OdfTable feature with a specified 2 dimension array as the data of this table.
   * The value type of each cell is string.
   *
   * <p>The table will be inserted at the end of the document. An unique table name will be given,
   * you may set a custom table name using the <code>setTableName</code> method.
   *
   * <p>If the document is a text document, cell borders will be created by default.
   *
   * @param document the ODF document that contains this feature
   * @param rowLabel set as the header row, it can be null if no header row needed
   * @param columnLabel set as the header column, it can be null if no header column needed
   * @param data the two dimension array of string as the data of this table
   * @return a new instance of <code>OdfTable</code>
   */
  public static OdfTable newTable(
      OdfDocument document, String[] rowLabel, String[] columnLabel, String[][] data) {

    OdfTable table = null;
    try {
      table = newTable(document.getContentRoot(), rowLabel, columnLabel, data);
    } catch (Exception ex) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, null, ex);
    }
    return table;
  }

  /**
   * Get the row count of this table.
   *
   * @return total count of rows
   */
  public int getRowCount() {
    int result = 0;

    for (Node n : new DomNodeList(mTableElement.getChildNodes())) {
      if (n instanceof TableTableHeaderRowsElement) {
        result += getHeaderRowCount((TableTableHeaderRowsElement) n);
      }
      if (n instanceof TableTableRowElement) {
        result += ((TableTableRowElement) n).getTableNumberRowsRepeatedAttribute();
      }
    }
    return result;
  }

  /**
   * Get the column count of this table.
   *
   * @return total count of columns
   */
  public int getColumnCount() {
    int result = 0;

    // ToDo There can be groups (only in calc) and header rows and it should end with rows
    for (Node n : new DomNodeList(mTableElement.getChildNodes())) {
      if (n instanceof TableTableColumnElement) {
        result += getColumnInstance((TableTableColumnElement) n, 0).getColumnsRepeatedNumber();
      } else if (n instanceof Element) {
        result += getColumnsCount((Element) n);
      }
      if (n instanceof TableTableRowElement) {
        break;
      }
    }
    return result;
  }

  /**
   * This method is invoked by appendRow. When a table has no row, the first row is a default row.
   */
  private TableTableRowElement createDefaultRow(int columnCount) {
    OdfFileDom dom = (OdfFileDom) mTableElement.getOwnerDocument();
    // 3. create row elements
    // 3.0 create 4 kinds of styles
    OdfStyle lefttopStyle = null, righttopStyle = null;

    if (!mIsSpreadsheet) {
      lefttopStyle = mTableElement.getOrCreateAutomaticStyles().newStyle(OdfStyleFamily.TableCell);
      setLeftTopBorderStyleProperties(lefttopStyle);

      righttopStyle = mTableElement.getOrCreateAutomaticStyles().newStyle(OdfStyleFamily.TableCell);
      setRightTopBorderStyleProperties(righttopStyle);
    }

    // 3.1 create header row elements
    TableTableRowElement aRow =
        (TableTableRowElement)
            OdfXMLFactory.newOdfElement(
                dom, OdfName.newName(OdfDocumentNamespace.TABLE, "table-row"));
    for (int j = 0; j < columnCount; j++) {
      TableTableCellElement aCell =
          (TableTableCellElement)
              OdfXMLFactory.newOdfElement(
                  dom, OdfName.newName(OdfDocumentNamespace.TABLE, "table-cell"));
      TextPElement aParagraph =
          (TextPElement)
              OdfXMLFactory.newOdfElement(dom, OdfName.newName(OdfDocumentNamespace.TEXT, "p"));
      aCell.appendChild(aParagraph);
      if (!mIsSpreadsheet) {
        if (j + 1 == columnCount) {
          aCell.setStyleName(righttopStyle.getStyleNameAttribute());
        } else {
          aCell.setStyleName(lefttopStyle.getStyleNameAttribute());
        }
      }
      aRow.appendChild(aCell);
    }

    return aRow;
  }

  /**
   * Append a row to the end of the table. The style of new row is same with the last row in the
   * table.
   *
   * <p>Since ODFDOM 8.5 automatic table expansion is supported. Whenever a cell outside the current
   * table is addressed the table is instantly expanded. Method <code>getCellByPosition</code> can
   * randomly access any cell, no matter it in or out of the table original range.
   *
   * @return a new appended row
   * @see #appendRows(int)
   * @see #getRowByIndex(int)
   * @see #getCellByPosition(int, int)
   * @see #getCellByPosition(String)
   */
  public OdfTableRow appendRow() {
    int columnCount = getColumnCount();
    List<OdfTableRow> rowList = getRowList();

    if (rowList.size() == 0) // no row, create a default row
    {
      TableTableRowElement newRow = createDefaultRow(columnCount);
      mTableElement.appendChild(newRow);

      return getRowInstance(newRow, 0);
    } else {
      OdfTableRow refRow = rowList.get(rowList.size() - 1);
      OdfTableRow newRow = insertRowBefore(refRow, null);
      return newRow;
    }
  }

  /**
   * Append a specific number of rows to the end of the table. The style of new rows are same with
   * the last row in the table.
   *
   * <p>Since ODFDOM 8.5 automatic table expansion is supported. Whenever a cell outside the current
   * table is addressed the table is instantly expanded. Method <code>getCellByPosition</code> can
   * randomly access any cell, no matter it in or out of the table original range.
   *
   * @param rowCount is the number of rows to be appended.
   * @return a list of new appended rows
   * @see #appendRow()
   * @see #getRowByIndex(int)
   * @see #getCellByPosition(int, int)
   * @see #getCellByPosition(String)
   */
  public List<OdfTableRow> appendRows(int rowCount) {
    return appendRows(rowCount, false);
  }

  List<OdfTableRow> appendRows(int rowCount, boolean isCleanStyle) {
    List<OdfTableRow> resultList = new ArrayList<OdfTableRow>();
    if (rowCount <= 0) {
      return resultList;
    }
    OdfTableRow firstRow = appendRow();
    resultList.add(firstRow);
    if (rowCount == 1) {
      return resultList;
    }
    List<OdfTableRow> list = insertRowsBefore((getRowCount() - 1), (rowCount - 1));
    resultList.addAll(list);
    if (isCleanStyle) {
      // clean style name
      for (OdfTableRow row : resultList) {
        for (int i = 0; i < row.getCellCount(); i++) {
          TableTableCellElement cellElement =
              (TableTableCellElement) (row.getCellByIndex(i).mCellElement);
          cellElement.removeAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "style-name");
        }
      }
    }
    return resultList;
  }

  /**
   * Append a column at the end of the table. The style of new column is same with the last column
   * in the table.
   *
   * <p>Since ODFDOM 8.5 automatic table expansion is supported. Whenever a cell outside the current
   * table is addressed the table is instantly expanded. Method <code>getCellByPosition</code> can
   * randomly access any cell, no matter it in or out of the table original range.
   *
   * @return a new appended column
   * @see #appendColumns(int)
   * @see #getColumnByIndex(int)
   * @see #getCellByPosition(int, int)
   * @see #getCellByPosition(String)
   */
  public OdfTableColumn appendColumn() {
    List<OdfTableColumn> columnList = getColumnList();
    int columnCount = columnList.size();

    TableTableColumnElement newColumn;
    OdfElement positonElement = getRowElementByIndex(0);
    if (positonElement.getParentNode() instanceof TableTableHeaderRowsElement) {
      positonElement = (OdfElement) positonElement.getParentNode();
    }

    // Moved before column elements inserted
    // insert cells firstly
    // Or else, wrong column number will be gotten in updateCellRepository, which will cause a NPE.
    // insertCellBefore()->splitRepeatedRows()->updateRowRepository()->updateCellRepository()
    List<OdfTableRow> rowList = getRowList();
    for (int i = 0; i < rowList.size(); ) {
      OdfTableRow row1 = rowList.get(i);
      row1.insertCellBefore(row1.getCellByIndex(columnCount - 1), null);
      i = i + row1.getRowsRepeatedNumber();
    }

    // insert columns secondly
    if (columnList.size() == 0) // no column, create a new column
    {
      OdfStyle columnStyle =
          mTableElement.getOrCreateAutomaticStyles().newStyle(OdfStyleFamily.TableColumn);
      String columnStylename = columnStyle.getStyleNameAttribute();
      columnStyle.setProperty(
          StyleTableColumnPropertiesElement.ColumnWidth, DEFAULT_TABLE_WIDTH + "in");
      columnStyle.setProperty(
          StyleTableColumnPropertiesElement.RelColumnWidth, DEFAULT_REL_TABLE_WIDTH + "*");

      newColumn =
          (TableTableColumnElement)
              OdfXMLFactory.newOdfElement(
                  (OdfFileDom) mTableElement.getOwnerDocument(),
                  OdfName.newName(OdfDocumentNamespace.TABLE, "table-column"));
      newColumn.setStyleName(columnStylename);
      mTableElement.insertBefore(newColumn, positonElement);
    } else { // has column, append a same column as the last one.
      TableTableColumnElement refColumn = columnList.get(columnList.size() - 1).getOdfElement();
      newColumn = (TableTableColumnElement) refColumn.cloneNode(true);
      newColumn.setTableNumberColumnsRepeatedAttribute(1); // chagne to remove attribute
      mTableElement.insertBefore(newColumn, positonElement);
    }

    return getColumnInstance(newColumn, 0);
  }

  /**
   * Append a specific number of columns to the right of the table. The style of new columns are
   * same with the rightmost column in the table.
   *
   * <p>Since ODFDOM 8.5 automatic table expansion is supported. Whenever a cell outside the current
   * table is addressed the table is instantly expanded. Method <code>getCellByPosition</code> can
   * randomly access any cell, no matter it in or out of the table original range.
   *
   * @param columnCount is the number of columns to be appended.
   * @return a list of new appended columns
   * @see #appendColumn()
   * @see #getColumnByIndex(int)
   * @see #getCellByPosition(int, int)
   * @see #getCellByPosition(String)
   */
  public List<OdfTableColumn> appendColumns(int columnCount) {
    return appendColumns(columnCount, false);
  }

  List<OdfTableColumn> appendColumns(int columnCount, boolean isCleanStyle) {
    List<OdfTableColumn> resultList = new ArrayList<OdfTableColumn>();
    if (columnCount <= 0) {
      return resultList;
    }
    OdfTableColumn firstClm = appendColumn();
    resultList.add(firstClm);
    if (columnCount == 1) {
      return resultList;
    }
    List<OdfTableColumn> list = insertColumnsBefore((getColumnCount() - 1), (columnCount - 1));
    resultList.addAll(list);
    // clean style name
    if (isCleanStyle) {
      for (OdfTableColumn column : resultList) {
        for (int i = 0; i < column.getCellCount(); i++) {
          TableTableCellElement cellElement =
              (TableTableCellElement) (column.getCellByIndex(i).mCellElement);
          cellElement.removeAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "style-name");
        }
      }
    }
    return resultList;
  }

  /** This method is to insert a numbers of row */
  private List<OdfTableRow> insertMultipleRowBefore(
      OdfTableRow refRow, OdfTableRow positionRow, int count) {
    List<OdfTableRow> resultList = new ArrayList<OdfTableRow>();
    int j = 1;

    if (count <= 0) {
      return resultList;
    }

    OdfTableRow firstRow = insertRowBefore(refRow, positionRow);
    resultList.add(firstRow);

    if (count == 1) {
      return resultList;
    }

    TableTableRowElement rowEle = firstRow.getOdfElement();
    for (int i = 0; i < getColumnCount(); ) {
      OdfTableCell refCell = refRow.getCellByIndex(i);
      if (!refCell.isCoveredElement()) {
        int coveredHeigth = refCell.getRowSpannedNumber();
        if (coveredHeigth > 1) {
          refCell.setRowSpannedNumber(coveredHeigth + 1);
        }
      }
      i += refCell.getColumnsRepeatedNumber();
    }
    firstRow.setRowsRepeatedNumber(count);
    while (j < count) {
      resultList.add(getRowInstance(rowEle, j));
      j++;
    }
    return resultList;
  }

  private OdfTableRow insertRowBefore(
      OdfTableRow refRow, OdfTableRow positionRow) // only insert one Row
      {
    int columnCount = getColumnCount();
    TableTableRowElement aRow =
        (TableTableRowElement)
            OdfXMLFactory.newOdfElement(
                (OdfFileDom) mTableElement.getOwnerDocument(),
                OdfName.newName(OdfDocumentNamespace.TABLE, "table-row"));
    int coveredLength = 0, coveredHeigth = 0;
    for (int i = 0; i < columnCount; ) {
      OdfTableCell refCell = refRow.getCellByIndex(i);
      if (!refCell.isCoveredElement()) // not cover element
      {
        TableTableCellElement aCellEle = (TableTableCellElement) refCell.getOdfElement();
        coveredHeigth = aCellEle.getTableNumberRowsSpannedAttribute();
        if (coveredHeigth == 1) {
          TableTableCellElement newCellEle = (TableTableCellElement) aCellEle.cloneNode(true);
          cleanCell(newCellEle);
          aRow.appendChild(newCellEle);
        } else { // cover more rows
          aCellEle.setTableNumberRowsSpannedAttribute(coveredHeigth + 1);
          TableCoveredTableCellElement newCellEle =
              (TableCoveredTableCellElement)
                  OdfXMLFactory.newOdfElement(
                      (OdfFileDom) mTableElement.getOwnerDocument(),
                      OdfName.newName(OdfDocumentNamespace.TABLE, "covered-table-cell"));
          newCellEle.setTableNumberColumnsRepeatedAttribute(refCell.getColumnsRepeatedNumber());
          aRow.appendChild(newCellEle);
        }

        coveredLength =
            aCellEle.getTableNumberColumnsSpannedAttribute() - refCell.getColumnsRepeatedNumber();
        i = i + refCell.getColumnsRepeatedNumber();
      } else {
        TableCoveredTableCellElement aCellEle =
            (TableCoveredTableCellElement) refCell.getOdfElement();
        if (coveredLength >= 1) {
          TableCoveredTableCellElement newCellEle =
              (TableCoveredTableCellElement) aCellEle.cloneNode(true);
          aRow.appendChild(newCellEle);
          coveredLength -= newCellEle.getTableNumberColumnsRepeatedAttribute();
        } else {
          TableTableCellElement coveredCell =
              (TableTableCellElement) refCell.getCoverCell().getOdfElement();
          TableTableCellElement newCellEle = (TableTableCellElement) coveredCell.cloneNode(true);
          cleanCell(newCellEle);
          newCellEle.removeAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "number-rows-spanned");
          aRow.appendChild(newCellEle);

          coveredLength =
              coveredCell.getTableNumberColumnsSpannedAttribute()
                  - refCell.getColumnsRepeatedNumber();
        }
        i = i + refCell.getColumnsRepeatedNumber();
      }
    }
    if (positionRow == null) {
      mTableElement.appendChild(aRow);
    } else {
      mTableElement.insertBefore(aRow, positionRow.getOdfElement());
    }

    return getRowInstance(aRow, 0);
  }

  void cleanCell(TableTableCellElement newCellEle) {
    newCellEle.removeAttributeNS(OdfDocumentNamespace.OFFICE.getUri(), "value");
    newCellEle.removeAttributeNS(OdfDocumentNamespace.OFFICE.getUri(), "date-value");
    newCellEle.removeAttributeNS(OdfDocumentNamespace.OFFICE.getUri(), "time-value");
    newCellEle.removeAttributeNS(OdfDocumentNamespace.OFFICE.getUri(), "boolean-value");
    newCellEle.removeAttributeNS(OdfDocumentNamespace.OFFICE.getUri(), "string-value");
    newCellEle.removeAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "formula");
    newCellEle.removeAttributeNS(OdfDocumentNamespace.OFFICE.getUri(), "value-type");
    if (!isCellStyleInheritance()) {
      newCellEle.removeAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "style-name");
    }
    Node n = newCellEle.getFirstChild();
    while (n != null) {
      Node m = n.getNextSibling();
      if (n instanceof TextPElement || n instanceof TextHElement || n instanceof TextListElement) {
        newCellEle.removeChild(n);
      }
      n = m;
    }
  }

  /**
   * Return an instance of <code>TableTableElement</code> which represents this feature.
   *
   * @return an instance of <code>TableTableElement</code>
   */
  public TableTableElement getOdfElement() {
    return mTableElement;
  }

  /**
   * Insert a specific number of columns before the column whose index is <code>index</code>.
   *
   * @param index is the index of the column to insert before.
   * @param columnCount is the number of columns to insert.
   * @return a list of new inserted columns
   */
  public List<OdfTableColumn> insertColumnsBefore(int index, int columnCount) {
    OdfTableColumn refColumn, positionCol;
    ArrayList<OdfTableColumn> list = new ArrayList<OdfTableColumn>();
    int columncount = getColumnCount();

    if (index >= columncount) {
      throw new IndexOutOfBoundsException();
    }

    if (index == 0) {
      int iRowCount = getRowCount();
      for (int i = 0; i < iRowCount; i++) {
        OdfTableRow row = getRowByIndex(i);
        row.insertCellByIndex(index, columnCount);
      }

      refColumn = getColumnByIndex(index);
      positionCol = refColumn;

      TableTableColumnElement newColumnEle =
          (TableTableColumnElement) refColumn.getOdfElement().cloneNode(true);
      newColumnEle.setTableNumberColumnsRepeatedAttribute(new Integer(columnCount));
      mTableElement.insertBefore(newColumnEle, positionCol.getOdfElement());

      for (int i = 0; i < columnCount; i++) {
        list.add(getColumnInstance(newColumnEle, i));
      }
      return list;
    }

    // 1. insert the cell
    int iRowCount = getRowCount();
    for (int i = iRowCount - 1; i >= 0; ) {
      OdfTableRow row = getRowByIndex(i);
      OdfTableCell refCell = row.getCellByIndex(index - 1);
      OdfTableCell positionCell = null;
      positionCell = row.getCellByIndex(index);
      row.insertCellBefore(refCell, positionCell, columnCount);
      i = i - row.getRowsRepeatedNumber();
    }

    refColumn = getColumnByIndex(index - 1);
    positionCol = getColumnByIndex(index);
    // 2. insert a <table:table-column>
    if (refColumn.getOdfElement() == positionCol.getOdfElement()) {
      TableTableColumnElement column = refColumn.getOdfElement();
      int repeatedCount = getColumnInstance(column, 0).getColumnsRepeatedNumber();
      getColumnInstance(column, 0).setColumnsRepeatedNumber((repeatedCount + columnCount));
      TableTableColumnElement columnEle = positionCol.getOdfElement();
      OdfTableColumn startCol = getColumnInstance(positionCol.getOdfElement(), 0);
      for (int i = repeatedCount + columnCount - 1;
          i >= columnCount + (index - startCol.getColumnIndex());
          i--) {
        updateColumnRepository(columnEle, i - columnCount, columnEle, i);
      }
      for (int i = 0; i < columnCount; i++) {
        list.add(getColumnInstance(column, refColumn.mnRepeatedIndex + 1 + i));
      }
    } else {
      TableTableColumnElement newColumnEle =
          (TableTableColumnElement) refColumn.getOdfElement().cloneNode(true);
      newColumnEle.setTableNumberColumnsRepeatedAttribute(new Integer(columnCount));
      mTableElement.insertBefore(newColumnEle, positionCol.getOdfElement());

      for (int i = 0; i < columnCount; i++) {
        list.add(getColumnInstance(newColumnEle, i));
      }
    }

    return list;
  }

  /**
   * Remove a specific number of columns, starting from the column at <code>index</code>.
   *
   * @param startIndex is the index of the first column to delete.
   * @param deleteColCount is the number of columns to delete.
   */
  public void removeColumnsByIndex(int startIndex, int deleteColCount) {
    removeColumnsByIndex(startIndex, deleteColCount, Boolean.FALSE);
  }

  /**
   * Remove a specific number of columns, starting from the column at <code>index</code>.
   *
   * @param startIndex is the index of the first column to delete.
   * @param deleteColCount is the number of columns to delete.
   */
  public void removeColumnsByIndex(int startIndex, int deleteColCount, boolean columnsOnly) {
    // 0. verify the index
    if (deleteColCount <= 0) {
      return;
    }
    if (startIndex < 0) {
      throw new IllegalArgumentException(
          "startIndex of the deleted columns should not be negative");
    }
    int colCount = getColumnCount();
    if (startIndex >= colCount) {
      throw new IndexOutOfBoundsException("Start column index is out of bound");
    }
    if (startIndex + deleteColCount >= colCount) {
      deleteColCount = colCount - startIndex;
    }

    // 1. remove cell
    if (!columnsOnly) {
      for (int i = 0; i < getRowCount(); i++) {
        OdfTableRow aRow = getRowByIndex(i);
        aRow.removeCellByIndex(startIndex, deleteColCount);
      }
    }

    // 2. remove column
    OdfTableColumn firstColumn;
    for (int i = 0; i < deleteColCount; i++) {
      firstColumn = getColumnByIndex(startIndex);
      int repeatedAttr = firstColumn.getColumnsRepeatedNumber();
      if (repeatedAttr == 1) {
        TableTableColumnElement columnEle =
            OdfElement.findNextChildNode(
                TableTableColumnElement.class, firstColumn.getOdfElement());
        Element columnParent = (Element) firstColumn.getOdfElement().getParentNode();
        columnParent.removeChild(firstColumn.getOdfElement());
        if (i < (deleteColCount - 1)) {
          firstColumn = this.getColumnInstance(columnEle, 0);
        }
      } else {
        if (repeatedAttr > firstColumn.mnRepeatedIndex) {
          firstColumn.setColumnsRepeatedNumber(repeatedAttr - 1);
          OdfTableColumn startCol = this.getColumnInstance(firstColumn.getOdfElement(), 0);
          updateColumnRepository(
              firstColumn.getOdfElement(), startIndex - startCol.getColumnIndex(), null, 0);
        }
      }
    }
  }

  private void reviseStyleFromTopRowToMediumRow(OdfTableRow oldTopRow) {
    if (mIsSpreadsheet) {
      return;
    }
    int length = getColumnCount();

    for (int i = 0; i < length; ) {
      OdfTableCell cell = oldTopRow.getCellByIndex(i);
      if (cell.isCoveredElement()) {
        i = i + cell.getColumnsRepeatedNumber();
        continue;
      }
      OdfStyle styleEle = cell.getCellStyleElementForWrite();
      if (i < length - 1) {
        setLeftBottomBorderStylesProperties(styleEle);
      } else {
        setRightBottomBorderStylesProperties(styleEle);
      }
      i = i + cell.getColumnsRepeatedNumber();
    }
  }

  private void reviseStyleFromMediumRowToTopRow(OdfTableRow newTopRow) {
    if (mIsSpreadsheet) {
      return;
    }
    int length = getColumnCount();

    for (int i = 0; i < length; ) {
      OdfTableCell cell = newTopRow.getCellByIndex(i);
      if (cell.isCoveredElement()) {
        i = i + cell.getColumnsRepeatedNumber();
        continue;
      }
      OdfStyle styleEle = cell.getCellStyleElementForWrite();
      if (i < length - 1) {
        setLeftTopBorderStyleProperties(styleEle);
      } else {
        setRightTopBorderStyleProperties(styleEle);
      }
      i = i + cell.getColumnsRepeatedNumber();
    }
  }

  /**
   * Insert a specific number of rows before the row at <code>index</code>.
   *
   * @param index is the index of the row to insert before.
   * @param rowCount is the number of rows to insert.
   * @return a list of new inserted rows
   */
  public List<OdfTableRow> insertRowsBefore(int index, int rowCount) {
    if (index >= getRowCount()) {
      throw new IndexOutOfBoundsException();
    }

    ArrayList<OdfTableRow> list = new ArrayList<OdfTableRow>();

    if (index == 0) {
      OdfTableRow refRow = getRowByIndex(index);
      OdfTableRow positionRow = refRow;
      // add first row
      OdfTableRow newFirstRow = insertRowBefore(refRow, positionRow);
      reviseStyleFromTopRowToMediumRow(refRow);
      list.add(newFirstRow);
      List<OdfTableRow> rowList = insertMultipleRowBefore(refRow, refRow, rowCount - 1);
      for (int i = 0; i < rowList.size(); i++) {
        list.add(rowList.get(i));
      }
      return list;
    }

    OdfTableRow refRow = getRowByIndex(index - 1);
    OdfTableRow positionRow = getRowByIndex(index);
    // 1. insert a <table:table-column>
    if (refRow.getOdfElement() == positionRow.getOdfElement()) {
      TableTableRowElement row = refRow.getOdfElement();
      int repeatedCount = refRow.getRowsRepeatedNumber();
      refRow.setRowsRepeatedNumber(repeatedCount + rowCount);
      TableTableRowElement rowEle = positionRow.getOdfElement();
      OdfTableRow startRow = getRowInstance(positionRow.getOdfElement(), 0);
      for (int i = repeatedCount + rowCount - 1;
          i >= rowCount + (index - startRow.getRowIndex());
          i--) {
        updateRowRepository(rowEle, i - rowCount, rowEle, i);
      }
      for (int i = 0; i < rowCount; i++) {
        list.add(getRowInstance(row, refRow.mnRepeatedIndex + 1 + i));
      }
    } else {
      List<OdfTableRow> newRowList = insertMultipleRowBefore(refRow, positionRow, rowCount);
      if (index - 1 == 0) {
        // correct styles
        reviseStyleFromTopRowToMediumRow(newRowList.get(0));
      }
      for (int i = 0; i < newRowList.size(); i++) {
        list.add(newRowList.get(i));
      }
    }

    return list;
  }

  /**
   * Return a list of columns in the current table.
   *
   * @return a list of table columns
   */
  public List<OdfTableColumn> getColumnList() {
    ArrayList<OdfTableColumn> list = new ArrayList<OdfTableColumn>();
    TableTableColumnElement colEle = null;
    for (Node n : new DomNodeList(mTableElement.getChildNodes())) {
      if (n instanceof TableTableHeaderColumnsElement) {
        TableTableHeaderColumnsElement headers = (TableTableHeaderColumnsElement) n;
        for (Node m : new DomNodeList(headers.getChildNodes())) {
          if (m instanceof TableTableColumnElement) {
            colEle = (TableTableColumnElement) m;
            for (int i = 0; i < getColumnInstance(colEle, 0).getColumnsRepeatedNumber(); i++) {
              list.add(getColumnInstance(colEle, i));
            }
          }
        }
      }
      if (n instanceof TableTableColumnElement) {
        colEle = (TableTableColumnElement) n;
        for (int i = 0; i < getColumnInstance(colEle, 0).getColumnsRepeatedNumber(); i++) {
          list.add(getColumnInstance(colEle, i));
        }
      }
    }
    return list;
  }

  /**
   * Return a list of table rows in the current table.
   *
   * @return a list of table rows
   */
  public List<OdfTableRow> getRowList() {
    ArrayList<OdfTableRow> list = new ArrayList<OdfTableRow>();
    TableTableRowElement rowEle = null;
    for (Node n : new DomNodeList(mTableElement.getChildNodes())) {
      if (n instanceof TableTableHeaderRowsElement) {
        TableTableHeaderRowsElement headers = (TableTableHeaderRowsElement) n;
        for (Node m : new DomNodeList(headers.getChildNodes())) {
          if (m instanceof TableTableRowElement) {
            rowEle = (TableTableRowElement) m;
            for (int i = 0; i < rowEle.getTableNumberRowsRepeatedAttribute(); i++) {
              list.add(getRowInstance(rowEle, i));
            }
          }
        }
      }
      if (n instanceof TableTableRowElement) {
        rowEle = (TableTableRowElement) n;
        for (int i = 0; i < rowEle.getTableNumberRowsRepeatedAttribute(); i++) {
          list.add(getRowInstance(rowEle, i));
        }
      }
    }
    return list;
  }

  /**
   * Return a list of table rows in the current table.
   *
   * @return a list of table rows
   */
  public List<TableTableRowElement> getRowElementList() {
    ArrayList<TableTableRowElement> list = new ArrayList<TableTableRowElement>();
    TableTableRowElement rowEle = null;
    for (Node n : new DomNodeList(mTableElement.getChildNodes())) {
      if (n instanceof TableTableHeaderRowsElement) {
        TableTableHeaderRowsElement headers = (TableTableHeaderRowsElement) n;
        for (Node m : new DomNodeList(headers.getChildNodes())) {
          if (m instanceof TableTableRowElement) {
            rowEle = (TableTableRowElement) m;
            list.add(rowEle);
          }
        }
      }
      if (n instanceof TableTableRowElement) {
        rowEle = (TableTableRowElement) n;
        list.add(rowEle);
      }
    }
    return list;
  }

  /**
   * Get the column at the specified index. The table will be automatically expanded, when the given
   * index is outside of the original table.
   *
   * @param index the zero-based index of the column.
   * @return the column at the specified index
   */
  public OdfTableColumn getColumnByIndex(int index) {
    int result = 0;
    if (index < 0) {
      throw new IllegalArgumentException("index should be nonnegative integer.");
    }
    // expand column as needed.
    int lastIndex = getColumnCount() - 1;
    if (index > lastIndex) {
      appendColumns(index - lastIndex);
    }

    OdfTableColumn col = null;
    // TableTableColumnElement colEle=null;
    for (Node n : new DomNodeList(mTableElement.getChildNodes())) {
      if (n instanceof TableTableHeaderColumnsElement || n instanceof TableTableColumnsElement) {
        col = getColumnByIndex((OdfElement) n, index);
        if (col != null) {
          return col;
        }
        result += getColumnContainerCount((OdfElement) n);
      } else if (n instanceof TableTableColumnElement) {
        col = getColumnInstance((TableTableColumnElement) n, 0);
        result += col.getColumnsRepeatedNumber();
      }
      // || n instanceof TableTableColumnsElement
      if ((result > index) && (col != null)) {
        return getColumnInstance(
            col.getOdfElement(), index - (result - col.getColumnsRepeatedNumber()));
      }
      if (n instanceof TableTableRowElement) {
        break;
      }
    }
    return null;
  }

  private OdfTableRow getHeaderRowByIndex(TableTableHeaderRowsElement headers, int nIndex) {
    int result = 0;
    OdfTableRow row = null;
    for (Node n : new DomNodeList(headers.getChildNodes())) {
      if (n instanceof TableTableRowElement) {
        row = getRowInstance((TableTableRowElement) n, 0);
        result += row.getRowsRepeatedNumber();
      }
      if ((result > nIndex) && (row != null)) {
        return getRowInstance(row.getOdfElement(), nIndex - (result - row.getRowsRepeatedNumber()));
      }
    }
    return null;
  }

  private OdfTableColumn getColumnByIndex(OdfElement columnContainer, int nIndex) {
    int result = 0;
    OdfTableColumn col = null;
    for (Node n : new DomNodeList(columnContainer.getChildNodes())) {
      if (n instanceof TableTableColumnElement) {
        col = getColumnInstance((TableTableColumnElement) n, 0);
        result += col.getColumnsRepeatedNumber();
      }
      if (result > nIndex) {
        return getColumnInstance(
            col.getOdfElement(), nIndex - (result - col.getColumnsRepeatedNumber()));
      }
    }
    return null;
  }

  /**
   * Get the row at the specified index. The table will be automatically expanded, when the given
   * index is outside of the original table.
   *
   * @param index the zero-based index of the row.
   * @return the row at the specified index
   */
  public OdfTableRow getRowByIndex(int index) {
    if (index < 0) {
      throw new IllegalArgumentException("index should be nonnegative integer.");
    }
    // expand row as needed.
    int lastIndex = getRowCount() - 1;
    if (index > lastIndex) {
      appendRows(index - lastIndex);
    }
    int result = 0;
    OdfTableRow row = null;
    for (Node n : new DomNodeList(mTableElement.getChildNodes())) {
      if (n instanceof TableTableHeaderRowsElement) {
        row = getHeaderRowByIndex((TableTableHeaderRowsElement) n, index);
        if (row != null) {
          return row;
        }
        result += getHeaderRowCount((TableTableHeaderRowsElement) n);
      }
      if (n instanceof TableTableRowElement) {
        row = getRowInstance((TableTableRowElement) n, 0);
        result += row.getRowsRepeatedNumber();
      }
      if (result > index) {
        return getRowInstance(row.getOdfElement(), index - (result - row.getRowsRepeatedNumber()));
      }
    }
    return null;
  }

  /**
   * Remove the specific number of rows, starting from the row at <code>index</code>.
   *
   * @param startIndex is the zero-based index of the first row to delete.
   * @param deleteRowCount is the number of rows to delete.
   */
  public void removeRowsByIndex(int startIndex, int deleteRowCount) {
    boolean deleted = false;
    // 0. verify the index
    if (deleteRowCount <= 0) {
      return;
    }
    if (startIndex < 0) {
      throw new IllegalArgumentException("startIndex of the deleted rows should not be negative");
    }
    int rowCount = getRowCount();
    if (startIndex >= rowCount) {
      throw new IndexOutOfBoundsException("Start index out of bound");
    }
    if (startIndex + deleteRowCount >= rowCount) {
      deleteRowCount = rowCount - startIndex;
    }

    // 1. remove row
    OdfTableRow firstRow = getRowByIndex(startIndex);
    for (int i = startIndex; i < startIndex + deleteRowCount; i++) {
      int repeatedAttr = firstRow.getRowsRepeatedNumber();
      if (repeatedAttr == 1) {
        TableTableRowElement rowEle =
            OdfElement.findNextChildNode(TableTableRowElement.class, firstRow.getOdfElement());
        firstRow.removeAllCellsRelationship();
        firstRow.getOdfElement().getParentNode().removeChild(firstRow.getOdfElement());
        updateRowRepository(firstRow.getOdfElement(), firstRow.mnRepeatedIndex, null, 0);
        if (i < (startIndex + deleteRowCount - 1)) {
          firstRow = this.getRowInstance(rowEle, 0);
        }
        deleted = true;
      } else {
        if (repeatedAttr > firstRow.mnRepeatedIndex) {
          firstRow.setRowsRepeatedNumber(repeatedAttr - 1);
          OdfTableRow startRow = this.getRowInstance(firstRow.getOdfElement(), 0);
          updateRowRepository(firstRow.getOdfElement(), i - startRow.getRowIndex(), null, 0);
        }
      }
    }
    // 2. if mediumRow becomes as top row, revise style
    if (deleted && startIndex == 0) {
      OdfTableRow aRow = getRowByIndex(0);
      reviseStyleFromMediumRowToTopRow(aRow);
    }
  }

  /** Remove this table from the document */
  public void remove() {
    mTableElement.getParentNode().removeChild(mTableElement);
  }

  private int getHeaderRowCount(TableTableHeaderRowsElement headers) {
    int result = 0;
    if (headers != null) {
      for (Node n : new DomNodeList(headers.getChildNodes())) {
        if (n instanceof TableTableRowElement) {
          result += ((TableTableRowElement) n).getTableNumberRowsRepeatedAttribute();
        }
      }
    }
    return result;
  }

  /**
   * Return the number of header rows in this table.
   *
   * @return the number of header rows.
   */
  public int getHeaderRowCount() {

    TableTableHeaderRowsElement headers =
        OdfElement.findFirstChildNode(TableTableHeaderRowsElement.class, mTableElement);
    return getHeaderRowCount(headers);
  }

  private int getColumnContainerCount(OdfElement columnContainer) {
    int result = 0;
    if (columnContainer != null) {
      for (Node n : new DomNodeList(columnContainer.getChildNodes())) {
        if (n instanceof TableTableColumnElement) {
          result += getColumnInstance(((TableTableColumnElement) n), 0).getColumnsRepeatedNumber();
        }
      }
    }
    return result;
  }

  private int getColumnsCount(Element columnContainer) {
    int result = 0;
    if (columnContainer != null) {
      for (Node n : new DomNodeList(columnContainer.getChildNodes())) {
        if (n instanceof TableTableColumnElement) {
          result += getColumnInstance(((TableTableColumnElement) n), 0).getColumnsRepeatedNumber();
        } else if (n instanceof Element) {
          result += getColumnsCount((Element) n);
        }
      }
    }
    return result;
  }

  /**
   * Return the number of header columns in the table.
   *
   * @return the number of header columns.
   */
  public int getHeaderColumnCount() {
    TableTableHeaderColumnsElement headers =
        OdfElement.findFirstChildNode(TableTableHeaderColumnsElement.class, mTableElement);
    return getColumnContainerCount(headers);
  }

  /**
   * Return the table name.
   *
   * @return the table name
   */
  public String getTableName() {
    return mTableElement.getTableNameAttribute();
  }

  /**
   * Set the table name.
   *
   * @param tableName the table name
   * @throws IllegalArgumentException if the tableName is duplicate with one of tables in the
   *     current document
   */
  public void setTableName(String tableName) {
    // check if the table name is already exist
    boolean isSpreadsheet = mDocument instanceof OdfSpreadsheetDocument;
    List<OdfTable> tableList = mDocument.getTableList(!isSpreadsheet);
    for (int i = 0; i < tableList.size(); i++) {
      OdfTable table = tableList.get(i);
      if (tableName.equals(table.getTableName())) {
        if (table != this) {
          throw new IllegalArgumentException(
              "The table name is duplicate with one of tables in the current document.");
        }
      }
    }
    mTableElement.setTableNameAttribute(tableName);
  }

  /**
   * Return true if the table is protected.
   *
   * @return true if the table is protected
   */
  public boolean isProtected() {
    if (mTableElement.getTableProtectedAttribute() != null) {
      return mTableElement.getTableProtectedAttribute().booleanValue();
    } else {
      return false;
    }
  }

  /**
   * Set if the table is protected.
   *
   * @param isProtected the protected attribute of the table to be set
   */
  public void setProtected(boolean isProtected) {
    mTableElement.setTableProtectedAttribute(isProtected);
  }

  /**
   * Return true if cell style is inherited when a new cell is added to the table.
   *
   * <p>The default setting is inherited. In this condition, the style of new column is same with
   * the previous column before the inserted position, while the style of new row is same with the
   * last row before the inserted position.
   *
   * <p>This feature setting will influence <code>appendRow()</code>, <code>appendColumn()</code>,
   * <code>appendRows()</code>, <code>appendColumns()</code>, <code>insertRowsBefore()</code> and
   * <code>insertColumnsBefore()</code>.
   *
   * <p>For <code>getCellByPosition()</code>, <code>getCellRangeByPosition()</code>, <code>
   * getCellRangeByName()</code>, <code>getRowByIndex()</code> and <code>getColumnByIndex()</code>,
   * if need automatically expand cells, it will return empty cell(s) without any style settings. So
   * inheritance setting have no effect on them.
   *
   * @return true if cell style is inherited when a new cell is added to the table.
   * @see #setCellStyleInheritance(boolean)
   * @see #appendColumn()
   * @see #appendColumns(int)
   * @see #appendRow()
   * @see #appendRows(int)
   * @see #insertColumnsBefore(int, int)
   * @see #insertRowsBefore(int, int)
   * @see #getCellByPosition(int, int)
   * @see #getCellByPosition(String)
   * @see #getCellRangeByPosition(int, int, int, int)
   * @see #getCellRangeByPosition(String, String)
   * @see #getCellRangeByName(String)
   * @see #getColumnByIndex(int)
   * @see #getRowByIndex(int)
   */
  protected boolean isCellStyleInheritance() {
    return mIsCellStyleInheritance;
  }

  /**
   * This method allows users to set whether cell style is inherited or not when a new cell is added
   * to the table. Of course, the default setting is inherited. In this condition, the style of new
   * column is same with the previous column before the inserted position, while the style of new
   * row is same with the last row before the inserted position.
   *
   * <p>This feature setting will influence <code>appendRow()</code>, <code>appendColumn()</code>,
   * <code>appendRows()</code>, <code>appendColumns()</code>, <code>insertRowsBefore()</code> and
   * <code>insertColumnsBefore()</code>.
   *
   * <p>For <code>getCellByPosition()</code>, <code>getCellRangeByPosition()</code>, <code>
   * getCellRangeByName()</code>, <code>getRowByIndex()</code> and <code>getColumnByIndex()</code>,
   * if need automatically expand cells, it will return empty cell(s) without any style settings. So
   * inheritance setting have no effect on them.
   *
   * @param isEnabled if<code>isEnabled</code> is true, cell style will be inherited by new cell.
   * @see #isCellStyleInheritance()
   * @see #appendColumn()
   * @see #appendColumns(int)
   * @see #appendRow()
   * @see #appendRows(int)
   * @see #insertColumnsBefore(int, int)
   * @see #insertRowsBefore(int, int)
   * @see #getCellByPosition(int, int)
   * @see #getCellByPosition(String)
   * @see #getCellRangeByPosition(int, int, int, int)
   * @see #getCellRangeByPosition(String, String)
   * @see #getCellRangeByName(String)
   * @see #getColumnByIndex(int)
   * @see #getRowByIndex(int)
   */
  protected void setCellStyleInheritance(boolean isEnabled) {
    mIsCellStyleInheritance = isEnabled;
  }
  ////////////////////////////////////////////////////////////////////////////////////////////

  /**
   * Return a range of cells within the specified range. The table will be automatically expanded as
   * need.
   *
   * @param startCol the column index of the first cell inside the range.
   * @param startRow the row index of the first cell inside the range.
   * @param endCol the column index of the last cell inside the range.
   * @param endRow the row index of the last cell inside the range.
   * @return the specified cell range.
   */
  public OdfTableCellRange getCellRangeByPosition(
      int startCol, int startRow, int endCol, int endRow) {
    // test whether cell position is out of table range and expand table
    // automatically.
    getCellByPosition(startCol, startRow);
    getCellByPosition(endCol, endRow);
    return new OdfTableCellRange(this, startCol, startRow, endCol, endRow);
  }

  /**
   * Return a range of cells within the specified range. The range is specified by the cell address
   * of the first cell and the cell address of the last cell. The table will be automatically
   * expanded as need.
   *
   * <p>The cell address is constructed with a table name, a dot (.), an alphabetic value
   * representing the column, and a numeric value representing the row. The table name can be
   * omitted. For example: "$Sheet1.A1", "Sheet1.A1" and "A1" are all valid cell address.
   *
   * @param startAddress the cell address of the first cell inside the range.
   * @param endAddress the cell address of the last cell inside the range.
   * @return the specified cell range.
   */
  public OdfTableCellRange getCellRangeByPosition(String startAddress, String endAddress) {
    return getCellRangeByPosition(
        getColIndexFromCellAddress(startAddress),
        getRowIndexFromCellAddress(startAddress),
        getColIndexFromCellAddress(endAddress),
        getRowIndexFromCellAddress(endAddress));
  }

  /**
   * Return a range of cells by a specified name.
   * <p>
   * After you get a cell range with <code>getCellRangeByPosition</code>, you
   * can assign a name to this cell range with the method      <code>setCellRangeName<code> in class
   * <code>OdfTableCellRange</code>. Then you will get a <b>named range</b>
   * which can be represented by name. This method can be used to get a named
   * range.
   *
   * @param name	the name of the specified named range
   * @return	the specified cell range.
   */
  public OdfTableCellRange getCellRangeByName(String name) {
    NodeList nameRanges;
    try {
      nameRanges =
          mTableElement
              .getOwnerDocument()
              .getElementsByTagNameNS(OdfDocumentNamespace.TABLE.getUri(), "named-range");
      for (int i = 0; i < nameRanges.getLength(); i++) {
        TableNamedRangeElement nameRange = (TableNamedRangeElement) nameRanges.item(i);
        if (nameRange.getTableNameAttribute().equals(name)) {
          String cellRange = nameRange.getTableCellRangeAddressAttribute();
          String[] addresses = cellRange.split(":");
          return getCellRangeByPosition(addresses[0], addresses[1]);
        }
      }
    } catch (Exception e) {
      Logger.getLogger(OdfTable.class.getName()).log(Level.SEVERE, e.getMessage(), e);
    }
    return null;
  }

  /**
   * Return a single cell that is positioned at the specified column and row. The table will be
   * automatically expanded as need.
   *
   * @param colIndex the column index of the cell.
   * @param rowIndex the row index of the cell.
   * @return the cell at the specified position
   */
  public OdfTableCell getCellByPosition(int colIndex, int rowIndex) {
    if (colIndex < 0 || rowIndex < 0) {
      throw new IllegalArgumentException("colIndex and rowIndex should be nonnegative integer.");
    }
    // expand row as needed.
    int lastRowIndex = getRowCount() - 1;
    if (rowIndex > lastRowIndex) {
      // need clean cell style.
      appendRows((rowIndex - lastRowIndex), true);
    }
    // expand column as needed.
    int lastColumnIndex = getColumnCount() - 1;
    if (colIndex > lastColumnIndex) {
      // need clean cell style.
      appendColumns((colIndex - lastColumnIndex), true);
    }
    OdfTableRow row = getRowByIndex(rowIndex);
    return row.getCellByIndex(colIndex);
  }

  // return array of string contain 3 member
  // 1. sheet table name
  // 2. alphabetic represent the column
  // 3. string represent the row number
  String[] splitCellAddress(String cellAddress) {
    String[] returnArray = new String[3];
    // seperate column and row from cell range
    StringTokenizer stDot = new StringTokenizer(cellAddress, ".");
    // get sheet table name and the cell address
    String cell = "";
    if (stDot.countTokens() >= 2) {
      StringTokenizer stDollar = new StringTokenizer(stDot.nextToken(), "$");
      returnArray[0] = stDollar.nextToken();
      cell = stDot.nextToken();
    } else {
      returnArray[0] = getTableName();
      cell = stDot.nextToken();
    }

    // get the column/row number from the cell address
    StringTokenizer stDollar = new StringTokenizer(cell, "$");
    if (stDollar.countTokens() >= 2) {
      returnArray[1] = stDollar.nextToken();
      returnArray[2] = stDollar.nextToken();
    } else {
      cell = stDollar.nextToken();
      for (int i = 0; i < cell.length(); i++) {
        if (!Character.isLetter(cell.charAt(i))) {
          returnArray[1] = cell.substring(0, i);
          returnArray[2] = cell.substring(i);
          break;
        }
      }
    }
    return returnArray;
  }

  /**
   * Return a single cell that is positioned at the specified cell address. The table can be
   * automatically expanded as need.
   *
   * <p>The cell address is constructed with a table name, a dot (.), an alphabetic value
   * representing the column, and a numeric value representing the row. The table name can be
   * omitted. For example: "$Sheet1.A1", "Sheet1.A1" and "A1" are all valid cell address.
   *
   * @param address the cell address of the cell.
   * @return the cell at the specified position.
   */
  public OdfTableCell getCellByPosition(String address) {
    return getCellByPosition(
        getColIndexFromCellAddress(address), getRowIndexFromCellAddress(address));
  }

  // TODO: can put these two method to type.CellAddress
  int getColIndexFromCellAddress(String cellAddress) {
    String[] returnArray = splitCellAddress(cellAddress);
    String colNum = returnArray[1];
    int colIndex = 0;
    for (int i = 0; i < colNum.length(); i++) {
      colIndex = 26 * colIndex;
      colIndex += (colNum.charAt(i) - 'A') + 1;
    }

    return (colIndex - 1);
  }

  int getRowIndexFromCellAddress(String cellAddress) {
    String[] returnArray = splitCellAddress(cellAddress);
    return Integer.parseInt(returnArray[2]) - 1;
  }

  String getAbsoluteCellAddress(int colIndex, int rowIndex) {
    int remainder = 0;
    int multiple = colIndex;
    String cellRange = "";
    while (multiple != 0) {
      multiple = colIndex / 26;
      remainder = colIndex % 26;
      char c;
      if (multiple == 0) {
        c = (char) ('A' + remainder);
      } else {
        c = (char) ('A' + multiple - 1);
      }
      cellRange = cellRange + String.valueOf(c);
      colIndex = remainder;
    }
    cellRange = "$" + cellRange + "$" + (rowIndex + 1);
    return cellRange;
  }
  // the parameter is the column/row index in the ownerTable,rather than in the cell range
  // if the position is a covered cell, then get the owner cell for it

  OdfTableCell getOwnerCellByPosition(List<CellCoverInfo> coverList, int nCol, int nRow) {
    CellCoverInfo info;
    if (!isCoveredCellInOwnerTable(coverList, nCol, nRow)) {
      OdfTableCell cell = getCellByPosition(nCol, nRow);
      return cell;
    } else {
      for (int m = 0; m < coverList.size(); m++) {
        info = coverList.get(m);
        if (((nCol > info.nStartCol)
                && (nCol <= info.nEndCol)
                && (nRow == info.nStartRow)
                && (nRow == info.nEndRow))
            || ((nCol == info.nStartCol)
                && (nCol == info.nEndCol)
                && (nRow > info.nStartRow)
                && (nRow <= info.nEndRow))
            || ((nCol > info.nStartCol)
                && (nCol <= info.nEndCol)
                && (nRow > info.nStartRow)
                && (nRow <= info.nEndRow))) {
          OdfTableCell cell = getCellByPosition(info.nStartCol, info.nStartRow);
          return cell;
        }
      }
    }
    return null;
  }

  // the parameter is the column/row index in the ownerTable,rather than in the cell range
  boolean isCoveredCellInOwnerTable(List<CellCoverInfo> coverList, int nCol, int nRow) {
    CellCoverInfo info;
    for (int m = 0; m < coverList.size(); m++) {
      info = coverList.get(m);
      if (((nCol > info.nStartCol)
              && (nCol <= info.nEndCol)
              && (nRow == info.nStartRow)
              && (nRow == info.nEndRow))
          || ((nCol == info.nStartCol)
              && (nCol == info.nEndCol)
              && (nRow > info.nStartRow)
              && (nRow <= info.nEndRow))
          || ((nCol > info.nStartCol)
              && (nCol <= info.nEndCol)
              && (nRow > info.nStartRow)
              && (nRow <= info.nEndRow))) // covered cell
      {
        return true;
      }
    }
    return false;
  }

  List<CellCoverInfo> getCellCoverInfos(int nStartCol, int nStartRow, int nEndCol, int nEndRow) {
    List<CellCoverInfo> coverList = new ArrayList<CellCoverInfo>();
    int nColSpan, nRowSpan;
    for (int i = nStartCol; i < nEndCol + 1; i++) {
      for (int j = nStartRow; j < nEndRow + 1; j++) {
        OdfTableCell cell = getCellByPosition(i, j);
        if (cell != null) {
          nColSpan = cell.getColumnSpannedNumber();
          nRowSpan = cell.getRowSpannedNumber();
          if ((nColSpan > 1) || (nRowSpan > 1)) {
            coverList.add(new CellCoverInfo(i, j, nColSpan, nRowSpan));
          }
        }
      }
    }
    return coverList;
  }

  // the odfelement of the FTableColumn changed, so we should update the repository here
  void updateColumnRepository(
      TableTableColumnElement oldElement,
      int oldRepeatIndex,
      TableTableColumnElement newElement,
      int newRepeatIndex) {
    if (mColumnRepository.containsKey(oldElement)) {
      Vector<OdfTableColumn> oldList = mColumnRepository.get(oldElement);
      if (oldRepeatIndex < oldList.size()) {
        if (oldElement != newElement) {
          // the new column replace the old column
          OdfTableColumn oldColumn = oldList.get(oldRepeatIndex);
          if (oldColumn != null) {
            // update the mnRepeateIndex of the column which locate after the removed column
            for (int i = oldRepeatIndex + 1; i < oldList.size(); i++) {
              OdfTableColumn column = oldList.get(i);
              if (column != null) {
                column.mnRepeatedIndex = i - 1;
              }
            }
            oldList.remove(oldColumn);
            // oldList.add(oldRepeatIndex, null);
            if (newElement != null) {
              oldColumn.maColumnElement = newElement;
              oldColumn.mnRepeatedIndex = newRepeatIndex;
              int size = (newRepeatIndex > 7) ? (newRepeatIndex + 1) : 8;
              Vector<OdfTableColumn> list = new Vector<OdfTableColumn>(size);
              list.setSize(newRepeatIndex + 1);
              list.set(newRepeatIndex, oldColumn);
              mColumnRepository.put(newElement, list);
            } else {
              oldColumn.maColumnElement = null;
            }
          }
        } else {
          // the new column element is equal to the old column element, just change the repeatIndex
          OdfTableColumn oldColumn = oldList.get(oldRepeatIndex);
          if (oldColumn != null) {
            oldList.remove(oldColumn);
            oldList.add(oldRepeatIndex, null);
            oldColumn.mnRepeatedIndex = newRepeatIndex;
            if (newRepeatIndex >= oldList.size()) {
              oldList.setSize(newRepeatIndex + 1);
            }
            oldList.set(newRepeatIndex, oldColumn);
          } else {
            getColumnInstance(newElement, newRepeatIndex);
          }
        }
      }
    }
  }

  // the odfelement of the FTableRow changed, so we should update the repository here
  void updateRowRepository(
      TableTableRowElement oldElement,
      int oldRepeatIndex,
      TableTableRowElement newElement,
      int newRepeatIndex) {
    if (mRowRepository.containsKey(oldElement)) {
      Vector<OdfTableRow> oldList = mRowRepository.get(oldElement);
      if (oldRepeatIndex < oldList.size()) {
        if (oldElement != newElement) {
          // the new row replace the old row
          OdfTableRow oldRow = oldList.get(oldRepeatIndex);
          Vector<OdfTableCell> updateCellList = new Vector<OdfTableCell>();
          if (oldRow != null) {
            // update the mnRepeateIndex of the row which locate after the removed row
            for (int i = oldRepeatIndex + 1; i < oldList.size(); i++) {
              OdfTableRow row = oldList.get(i);
              if (row != null) {
                // update the cell in this row,
                int colNum = getColumnCount();
                for (int j = 0; j < colNum; j++) {
                  OdfTableCell cell = row.getCellByIndex(j);
                  updateCellList.add(cell);
                }
                row.mnRepeatedIndex = i - 1;
              }
            }
            oldList.remove(oldRow);
            if (newElement != null) {
              // update the cell in this row
              int colNum = getColumnCount();
              OdfTableCell[] oldCells = new OdfTableCell[colNum];
              for (int j = 0; j < colNum; j++) {
                oldCells[j] = oldRow.getCellByIndex(j);
              }
              ///
              oldRow.maRowElement = newElement;
              oldRow.mnRepeatedIndex = newRepeatIndex;
              int size = (newRepeatIndex > 7) ? (newRepeatIndex + 1) : 8;
              Vector<OdfTableRow> list = new Vector<OdfTableRow>(size);
              list.setSize(newRepeatIndex + 1);
              list.set(newRepeatIndex, oldRow);
              mRowRepository.put(newElement, list);
              // update the cell in this row
              OdfTableCell[] newCells = new OdfTableCell[colNum];
              for (int j = 0; j < colNum; j++) {
                newCells[j] = oldRow.getCellByIndex(j);
              }
              for (int j = 0; j < colNum; j++) {
                this.updateCellRepository(
                    oldCells[j].getOdfElement(),
                    oldCells[j].mnRepeatedColIndex,
                    oldCells[j].mnRepeatedRowIndex,
                    newCells[j].getOdfElement(),
                    newCells[j].mnRepeatedColIndex,
                    newCells[j].mnRepeatedRowIndex);
              }

              // update the mnRepeatedRowIndex of the cell which locate after the removed row
              for (int j = 0; j < updateCellList.size(); j++) {
                OdfTableCell cell = updateCellList.get(j);
                if (cell.mnRepeatedRowIndex > oldRepeatIndex) {
                  cell.mnRepeatedRowIndex--;
                }
              }
            } else {
              oldRow.maRowElement = null;
            }
          }
        } else {
          // the new row element is equal to the old row element, just change the repeatIndex
          OdfTableRow oldRow = oldList.get(oldRepeatIndex);
          if (oldRow != null) {
            oldList.remove(oldRow);
            oldList.add(oldRepeatIndex, null);
            oldRow.mnRepeatedIndex = newRepeatIndex;
            if (newRepeatIndex >= oldList.size()) {
              oldList.setSize(newRepeatIndex + 1);
            }
            oldList.set(newRepeatIndex, oldRow);
          } else {
            getRowInstance(newElement, newRepeatIndex);
          }
        }
      }
    }
  }

  // the odfelement of the FTableCell changed, so we should update the repository here
  void updateCellRepository(
      TableTableCellElementBase oldElement,
      int oldRepeatColIndex,
      int oldRepeatRowIndex,
      TableTableCellElementBase newElement,
      int newRepeatColIndex,
      int newRepeatRowIndex) {
    if (mCellRepository.containsKey(oldElement)) {
      OdfTableCell oldCell = null;
      Vector<OdfTableCell> oldList = mCellRepository.get(oldElement);
      for (int i = 0; i < oldList.size(); i++) {
        if (oldList.get(i).getOdfElement() == oldElement
            && oldList.get(i).mnRepeatedColIndex == oldRepeatColIndex
            && oldList.get(i).mnRepeatedRowIndex == oldRepeatRowIndex) {
          oldCell = oldList.get(i);
          break;
        }
      }
      if (oldElement != newElement) {
        // the new cell replace the old cell
        if (oldCell != null) {
          // update the mnRepeateRowIndex &  mnRepeateColIndex of the cell which locate after the
          // removed cell
          for (int i = 0; i < oldList.size(); i++) {
            OdfTableCell cell = oldList.get(i);
            if (cell != null && (cell.getOdfElement() == oldElement)) {
              if ((cell.mnRepeatedRowIndex == oldRepeatRowIndex)
                  && (cell.mnRepeatedColIndex > oldRepeatColIndex)) {
                cell.mnRepeatedColIndex--;
              }
            }
          }
          oldList.remove(oldCell);
          if (oldList.size() == 0) {
            mCellRepository.remove(oldElement);
          }
          if (newElement != null) {
            oldCell.mCellElement = newElement;
            oldCell.mnRepeatedColIndex = newRepeatColIndex;
            oldCell.mnRepeatedRowIndex = newRepeatRowIndex;
            Vector<OdfTableCell> list;
            if (mCellRepository.containsKey(newElement)) {
              list = mCellRepository.get(newElement);
              boolean bReplaced = false;
              for (int i = 0; i < list.size(); i++) {
                OdfTableCell cell = list.get(i);
                if (cell != null && (cell.getOdfElement() == newElement)) {
                  if ((cell.mnRepeatedColIndex == newRepeatColIndex)
                      && (cell.mnRepeatedRowIndex == newRepeatRowIndex)) {
                    list.remove(i);
                    list.add(i, oldCell);
                    bReplaced = true;
                    break;
                  }
                }
              }
              if (!bReplaced) {
                list.add(oldCell);
              }
            } else {
              list = new Vector<OdfTableCell>();
              list.add(oldCell);
              mCellRepository.put(newElement, list);
            }
          } else {
            oldCell.mCellElement = null;
            oldCell.mnRepeatedColIndex = 0;
            oldCell.mnRepeatedRowIndex = 0;
          }
        }
      } else {
        // the new cell element is equal to the old cell element, just change the repeatIndex
        if (oldCell != null) {
          oldCell.mnRepeatedColIndex = newRepeatColIndex;
          oldCell.mnRepeatedRowIndex = newRepeatRowIndex;
        } else {
          getCellInstance(newElement, newRepeatColIndex, newRepeatRowIndex);
        }
      }
    }
  }

  void updateRepositoryWhenCellElementChanged(
      int startRow, int endRow, int startClm, int endClm, TableTableCellElement newCellEle) {
    for (int i = startRow; i < endRow; i++) {
      for (int j = startClm; j < endClm; j++) {
        OdfTableCell cell = getCellByPosition(j, i);
        updateCellRepository(
            cell.getOdfElement(),
            cell.mnRepeatedColIndex,
            cell.mnRepeatedRowIndex,
            newCellEle,
            cell.mnRepeatedColIndex,
            cell.mnRepeatedRowIndex);
      }
    }
  }
}

/**
 * Record the Cell Cover Info in this cell range.
 * <p>
 * Sometimes the covered cell is not tagged as <table:covered-table-cell>
 * element.
 *
 */
class CellCoverInfo {

  int nStartCol;
  int nStartRow;
  int nEndCol;
  int nEndRow;

  CellCoverInfo(int nSC, int nSR, int nColumnSpan, int nRowSpan) {
    nStartCol = nSC;
    nStartRow = nSR;
    nEndCol = nSC + nColumnSpan - 1;
    nEndRow = nSR + nRowSpan - 1;
  }
}