OdfTableCell.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.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.odftoolkit.odfdom.doc.OdfDocument;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.attribute.fo.FoTextAlignAttribute;
import org.odftoolkit.odfdom.dom.attribute.fo.FoWrapOptionAttribute;
import org.odftoolkit.odfdom.dom.attribute.office.OfficeValueTypeAttribute;
import org.odftoolkit.odfdom.dom.element.OdfStyleBase;
import org.odftoolkit.odfdom.dom.element.number.NumberCurrencySymbolElement;
import org.odftoolkit.odfdom.dom.element.number.NumberNumberElement;
import org.odftoolkit.odfdom.dom.element.number.NumberTextElement;
import org.odftoolkit.odfdom.dom.element.table.TableCoveredTableCellElement;
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.TableTableElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableHeaderRowsElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableRowElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableRowGroupElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableRowsElement;
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.OdfStylePropertiesSet;
import org.odftoolkit.odfdom.dom.style.props.OdfStyleProperty;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberCurrencyStyle;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberDateStyle;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberPercentageStyle;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberStyle;
import org.odftoolkit.odfdom.incubator.doc.number.OdfNumberTimeStyle;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeAutomaticStyles;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.incubator.doc.text.OdfTextParagraph;
import org.odftoolkit.odfdom.incubator.doc.text.OdfWhitespaceProcessor;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfName;
import org.odftoolkit.odfdom.type.Color;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

/**
 * OdfTableCell represents table cell feature in ODF document.
 *
 * <p>OdfTable provides methods to get/set/modify the cell content and cell properties.
 */
public class OdfTableCell {

  TableTableCellElementBase mOdfElement;
  int mnRepeatedColIndex;
  int mnRepeatedRowIndex;
  OdfTable mOwnerTable;
  String msFormatString;
  /** The default date format of table cell. */
  private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
  /** The default time format of table cell. */
  private static final String DEFAULT_TIME_FORMAT = "'PT'HH'H'mm'M'ss'S'";
  /** The default cell back color of table cell. */
  private static final String DEFAULT_BACKGROUND_COLOR = "#FFFFFF";
  /** The default column spanned number. */
  private static final int DEFAULT_COLUMN_SPANNED_NUMBER = 1;
  /** The default row spanned number. */
  private static final int DEFAULT_ROW_SPANNED_NUMBER = 1;
  /** The default columns repeated number. */
  private static final int DEFAULT_COLUMNS_REPEATED_NUMBER = 1;

  TableTableCellElementBase mCellElement;
  OdfDocument mDocument;

  OdfTableCell(TableTableCellElementBase odfElement, int repeatedColIndex, int repeatedRowIndex) {
    mCellElement = odfElement;
    mnRepeatedColIndex = repeatedColIndex;
    mnRepeatedRowIndex = repeatedRowIndex;
    mOwnerTable = getTable();
    mDocument = ((OdfDocument) ((OdfFileDom) mCellElement.getOwnerDocument()).getDocument());
  }

  /**
   * Get the <code>OdfTableCell</code> instance from the <code>TableTableCellElementBase</code>
   * instance.
   *
   * <p>Each <code>TableTableCellElementBase</code> instance has a one-to-one relationship to the a
   * <code>OdfTableCell</code> instance.
   *
   * @param cellElement the cell element that need to get the corresponding <code>OdfTableCell
   *     </code> instance
   * @return the <code>OdfTableCell</code> instance that represents a specified cell element
   */
  public static OdfTableCell getInstance(TableTableCellElementBase cellElement) {
    TableTableElement tableElement = null;
    Node node = cellElement.getParentNode();
    while (node != null) {
      if (node instanceof TableTableElement) {
        tableElement = (TableTableElement) node;
      }
      node = node.getParentNode();
    }
    OdfTable table = null;
    if (tableElement != null) {
      table = OdfTable.getInstance(tableElement);
    } else {
      throw new IllegalArgumentException("the cellElement is not in the table dom tree");
    }

    OdfTableCell cell = table.getCellInstance(cellElement, 0, 0);
    int colRepeatedNum = cell.getColumnsRepeatedNumber();
    int rowRepeatedNum = cell.getTableRow().getRowsRepeatedNumber();
    if (colRepeatedNum > 1 && rowRepeatedNum > 1) {
      if (colRepeatedNum > 1) {
        Logger.getLogger(OdfTableCell.class.getName())
            .log(
                Level.WARNING,
                "the cell has the repeated column number, and puzzled about get which repeated column index of the cell,");
      }
      if (rowRepeatedNum > 1) {
        Logger.getLogger(OdfTableCell.class.getName())
            .log(
                Level.WARNING,
                "the row contains the current cell has the repeated row number, and puzzled about get which repeated row index of the cell,");
      }
      Logger.getLogger(OdfTableCell.class.getName())
          .log(
              Level.WARNING,
              "here just return the first cell that the repeated column index is 0 and repeated row index is 0, too.");
    }
    return cell;
  }

  /**
   * Return the horizontal alignment setting of this cell.
   *
   * <p>The returned value can be "center", "end", "justify", "left", "right", or "start". If no
   * horizontal alignment is set, null will be returned.
   *
   * @return the horizontal alignment setting.
   */
  public String getHorizontalAlignment() {
    OdfStyleBase styleElement = getCellStyleElement();
    if (styleElement != null) {
      OdfStyleProperty property =
          OdfStyleProperty.get(
              OdfStylePropertiesSet.ParagraphProperties,
              OdfName.newName(OdfDocumentNamespace.FO, "text-align"));
      return styleElement.getProperty(property);
    }
    return null;
  }

  /**
   * Set the horizontal alignment setting of this cell.
   *
   * <p>The parameter can be "center", "end", "justify", "left", "right", or "start". Actually,
   * "left" will be interpreted as "start", while "right" will be interpreted as "end". If argument
   * is null, the explicit horizontal alignment setting is removed.
   *
   * @param horizontalAlignment the horizontal alignment setting.
   */
  public void setHorizontalAlignment(String horizontalAlignment) {
    if (FoTextAlignAttribute.Value.LEFT.toString().equalsIgnoreCase(horizontalAlignment)) {
      horizontalAlignment = FoTextAlignAttribute.Value.START.toString();
    }
    if (FoTextAlignAttribute.Value.RIGHT.toString().equalsIgnoreCase(horizontalAlignment)) {
      horizontalAlignment = FoTextAlignAttribute.Value.END.toString();
    }
    splitRepeatedCells();
    OdfStyleBase styleElement = getCellStyleElementForWrite();
    if (styleElement != null) {
      OdfStyleProperty property =
          OdfStyleProperty.get(
              OdfStylePropertiesSet.ParagraphProperties,
              OdfName.newName(OdfDocumentNamespace.FO, "text-align"));
      if (horizontalAlignment != null) {
        styleElement.setProperty(property, horizontalAlignment);
      } else {
        styleElement.removeProperty(property);
      }
    }
  }

  /**
   * Return the vertical alignment setting of this cell.
   *
   * <p>The returned value can be "auto", "automatic", "baseline", "bottom", "middle", or "top".
   *
   * @return the vertical alignment setting of this cell.
   */
  public String getVerticalAlignment() {
    OdfStyleBase styleElement = getCellStyleElement();
    if (styleElement != null) {
      OdfStyleProperty property =
          OdfStyleProperty.get(
              OdfStylePropertiesSet.TableCellProperties,
              OdfName.newName(OdfDocumentNamespace.STYLE, "vertical-align"));
      return styleElement.getProperty(property);
    }
    return null;
  }

  /**
   * Set the vertical alignment setting of this cell.
   *
   * <p>The parameter can be "auto", "automatic", "baseline", "bottom", "middle", or "top". If
   * argument is null, the explicit vertical alignment setting is removed.
   *
   * @param verticalAlignment the vertical alignment setting.
   */
  public void setVerticalAlignment(String verticalAlignment) {
    splitRepeatedCells();
    OdfStyleBase styleElement = getCellStyleElementForWrite();
    if (styleElement != null) {
      OdfStyleProperty property =
          OdfStyleProperty.get(
              OdfStylePropertiesSet.TableCellProperties,
              OdfName.newName(OdfDocumentNamespace.STYLE, "vertical-align"));
      if (verticalAlignment != null) {
        styleElement.setProperty(property, verticalAlignment);
      } else {
        styleElement.removeProperty(property);
      }
    }
  }

  /**
   * Return the wrap option of this cell.
   *
   * @return true if the cell content can be wrapped;
   *     <p>false if the cell content cannot be wrapped.
   */
  public boolean isTextWrapped() {
    OdfStyleBase styleElement = getCellStyleElement();
    if (styleElement != null) {
      OdfStyleProperty property =
          OdfStyleProperty.get(
              OdfStylePropertiesSet.TableCellProperties,
              OdfName.newName(OdfDocumentNamespace.FO, "wrap-option"));
      String wrapped = styleElement.getProperty(property);
      if ((wrapped != null) && (wrapped.equals(FoWrapOptionAttribute.Value.WRAP.toString()))) {
        return true;
      }
    }
    return false;
  }

  /**
   * Set the wrap option of this cell.
   *
   * @param isTextWrapped whether the cell content can be wrapped or not
   */
  public void setTextWrapped(boolean isTextWrapped) {
    splitRepeatedCells();
    OdfStyleBase styleElement = getCellStyleElementForWrite();
    if (styleElement != null) {
      OdfStyleProperty property =
          OdfStyleProperty.get(
              OdfStylePropertiesSet.TableCellProperties,
              OdfName.newName(OdfDocumentNamespace.FO, "wrap-option"));
      if (isTextWrapped) {
        styleElement.setProperty(property, FoWrapOptionAttribute.Value.WRAP.toString());
      } else {
        styleElement.setProperty(property, FoWrapOptionAttribute.Value.NO_WRAP.toString());
      }
    }
  }

  private TableTableRowElement findRowInTableHeaderRows(
      TableTableHeaderRowsElement headers, TableTableRowElement tr, int[] indexs) {
    int result = 0;
    for (Node m : new DomNodeList(headers.getChildNodes())) {
      if (m == tr) {
        indexs[0] = result;
        return tr;
      }
      if (m instanceof TableTableRowElement) {
        result += ((TableTableRowElement) m).getTableNumberRowsRepeatedAttribute().intValue();
      }
    }
    indexs[0] = result;
    return null;
  }

  private TableTableRowElement findRowInTableRows(
      TableTableRowsElement rows, TableTableRowElement tr, int[] indexs) {
    int result = 0;
    for (Node m : new DomNodeList(rows.getChildNodes())) {
      if (m == tr) {
        indexs[0] = result;
        return tr;
      }
      if (m instanceof TableTableRowElement) {
        result += ((TableTableRowElement) m).getTableNumberRowsRepeatedAttribute().intValue();
      }
    }
    indexs[0] = result;
    return null;
  }

  private TableTableRowElement findRowInTableRowGroup(
      OdfElement group, TableTableRowElement tr, int[] indexs) {
    int result = 0;
    int[] resultIndexs = new int[1];

    if (!(group instanceof TableTableRowGroupElement) && !(group instanceof TableTableElement)) {
      indexs[0] = 0;
      return null;
    }

    for (Node m : new DomNodeList(group.getChildNodes())) {
      if (m instanceof TableTableHeaderRowsElement) {
        TableTableHeaderRowsElement headers = (TableTableHeaderRowsElement) m;
        TableTableRowElement returnEle = findRowInTableHeaderRows(headers, tr, resultIndexs);
        result += resultIndexs[0];
        if (returnEle != null) { // find
          indexs[0] = result;
          return returnEle;
        }
      } else if (m instanceof TableTableRowGroupElement) {
        TableTableRowGroupElement aGroup = (TableTableRowGroupElement) m;
        TableTableRowElement returnEle = findRowInTableRowGroup(aGroup, tr, resultIndexs);
        result += resultIndexs[0];
        if (returnEle != null) { // find
          indexs[0] = result;
          return returnEle;
        }
      } else if (m instanceof TableTableRowsElement) {
        TableTableRowsElement rows = (TableTableRowsElement) m;
        TableTableRowElement returnEle = findRowInTableRows(rows, tr, resultIndexs);
        result += resultIndexs[0];
        if (returnEle != null) { // find
          indexs[0] = result;
          return returnEle;
        }
      } else if (m instanceof TableTableRowElement) {
        if (m == tr) { // find
          indexs[0] = result;
          return tr;
        }
        result += ((TableTableRowElement) m).getTableNumberRowsRepeatedAttribute().intValue();
      }
    }
    indexs[0] = result;
    return null;
  }

  /**
   * Get the index of the table row which contains this cell.
   *
   * @return the index of the row containing this cell
   */
  public int getRowIndex() {
    TableTableElement table = getTableElement();
    TableTableRowElement tr = getTableRowElement();
    int[] indexs = new int[1];

    TableTableRowElement returnEle = findRowInTableRowGroup(table, tr, indexs);
    if (returnEle != null) {
      return (indexs[0] + mnRepeatedRowIndex);
    } else {
      return -1;
    }
  }

  /**
   * Get an instance of table feature which contains this cell.
   *
   * @return the table containing this cell
   */
  public OdfTable getTable() {
    TableTableElement tableElement = getTableElement();
    if (tableElement != null) {
      return OdfTable.getInstance(tableElement);
    }
    return null;
  }

  /**
   * Get the index of the table column which contains this cell.
   *
   * @return the index of the column containing this cell
   */
  public int getColumnIndex() {
    TableTableRowElement tr = (TableTableRowElement) mCellElement.getParentNode();
    int result = 0;
    for (Node n : new DomNodeList(tr.getChildNodes())) {
      if (n == mCellElement) {
        return result + mnRepeatedColIndex;
      }
      if (n instanceof TableTableCellElementBase) {
        result +=
            ((TableTableCellElementBase) n).getTableNumberColumnsRepeatedAttribute().intValue();
      }
    }
    return result;
  }

  /**
   * Get the instance of table column feature which contains this cell.
   *
   * @return the instance of table column feature which contains the cell.
   */
  public OdfTableColumn getTableColumn() {
    OdfTable table = getTable();
    int index = getColumnIndex();
    return table.getColumnByIndex(index);
  }

  TableTableColumnElement getTableColumnElement() {
    // return OdfTableCellBaseImpl.getTableColumn((OdfTableCellBase) mCellElement);
    TableTableElement tableElement = getTableElement();
    int columnindex = getColumnIndex();
    OdfTable fTable = OdfTable.getInstance(tableElement);
    return fTable.getColumnElementByIndex(columnindex);
  }

  /**
   * Get the instance of table row feature which contains this cell.
   *
   * @return the instance of table row feature which contains the cell.
   */
  public OdfTableRow getTableRow() {
    OdfTable table = getTable();
    int index = getRowIndex();
    return table.getRowByIndex(index);
  }

  private TableTableRowElement getTableRowElement() {
    Node node = mCellElement.getParentNode();
    if (node instanceof TableTableRowElement) {
      return (TableTableRowElement) node;
    }
    return null;
  }

  /**
   * Get the table object who contains this cell.
   *
   * @return the table object who contains the cell.
   */
  private TableTableElement getTableElement() {
    Node node = mCellElement.getParentNode();
    while (node != null) {
      if (node instanceof TableTableElement) {
        return (TableTableElement) node;
      }
      node = node.getParentNode();
    }
    return null;
  }

  /**
   * Get the cell that covers this cell.
   *
   * <p>If the cell is a covered cell, the owner cell will be returned; if the cell is a real cell ,
   * the cell itself will be returned.
   *
   * @return the cell that covers the current cell
   */
  public OdfTableCell getOwnerTableCell() {
    OdfTable ownerTable = getTable();
    List<CellCoverInfo> coverList =
        ownerTable.getCellCoverInfos(
            0, 0, ownerTable.getColumnCount() - 1, ownerTable.getRowCount() - 1);
    return ownerTable.getOwnerCellByPosition(coverList, getColumnIndex(), getRowIndex());
  }

  /**
   * Get the instance of <code>TableTableCellElementBase</code> which represents this cell.
   *
   * @return the instance of <code>TableTableCellElementBase</code>
   */
  public TableTableCellElementBase getOdfElement() {
    return mCellElement;
  }

  /**
   * Return the currency code of this cell, for example, "USD", "EUR", "CNY", and etc.
   *
   * <p>If the value type is not "currency", an IllegalArgumentException will be thrown.
   *
   * @return the currency code
   *     <p>
   * @throws IllegalArgumentException an IllegalArgumentException will be thrown if the value type
   *     is not "currency".
   */
  public String getCurrencyCode() {
    if (mCellElement
        .getOfficeValueTypeAttribute()
        .equals(OfficeValueTypeAttribute.Value.CURRENCY.toString())) {
      return mCellElement.getOfficeCurrencyAttribute();
    } else {
      throw new IllegalArgumentException();
    }
  }

  /**
   * Set the currency code of this cell, for example, "USD", "EUR", "CNY", and etc.
   *
   * @param currency the currency code that need to be set.
   * @throws IllegalArgumentException If input <code>currency</code> is null, an
   *     IllegalArgumentException will be thrown.
   */
  public void setCurrencyCode(String currency) {
    if (currency == null) {
      throw new IllegalArgumentException("Currency code of cell should not be null.");
    }
    splitRepeatedCells();
    if (mCellElement
        .getOfficeValueTypeAttribute()
        .equals(OfficeValueTypeAttribute.Value.CURRENCY.toString())) {
      mCellElement.setOfficeCurrencyAttribute(currency);
    } else {
      throw new IllegalArgumentException();
    }
  }

  private void setTypeAttr(OfficeValueTypeAttribute.Value type) {
    mCellElement.setOfficeValueTypeAttribute(type.toString());
  }

  /**
   * Set the value type of this cell. The parameter can be "boolean", "currency", "date", "float",
   * "percentage", "string" or "time".
   *
   * <p>If the parameter <code>type</code> is not a valid cell type, an IllegalArgumentException
   * will be thrown.
   *
   * @param type the type that need to be set If input type is null, an IllegalArgumentException
   *     will be thrown.
   */
  public void setValueType(String type) {
    if (type == null) {
      throw new IllegalArgumentException("type shouldn't be null.");
    }
    String sType = type.toLowerCase();
    OfficeValueTypeAttribute.Value value = OfficeValueTypeAttribute.Value.enumValueOf(sType);
    if (value == null) {
      throw new IllegalArgumentException("the value type of cell is not valid");
    }

    mCellElement.setOfficeValueTypeAttribute(sType);
  }

  /**
   * Get the value type of this cell. The returned value can be "boolean", "currency", "date",
   * "float", "percentage", "string" or "time". If no value type is set, null will be returned.
   *
   * @return the type of the cell
   */
  public String getValueType() {
    return mCellElement.getOfficeValueTypeAttribute();
  }

  private OfficeValueTypeAttribute.Value getTypeAttr() {
    String type = mCellElement.getOfficeValueTypeAttribute();
    return OfficeValueTypeAttribute.Value.enumValueOf(type);
  }

  /**
   * Get the double value of this cell as Double object.
   *
   * <p>Throw IllegalArgumentException if the cell type is not "float".
   *
   * @return the double value of this cell as a Double object. If the cell value is empty, null will
   *     be returned.
   *     <p>An IllegalArgumentException will be thrown if the cell type is not "float".
   */
  public Double getDoubleValue() {
    if (getTypeAttr() == OfficeValueTypeAttribute.Value.FLOAT) {
      return mCellElement.getOfficeValueAttribute();
    } else {
      throw new IllegalArgumentException();
    }
  }

  /**
   * Get the currency value of this cell as Double object.
   *
   * <p>Throw IllegalArgumentException if the cell type is not "currency".
   *
   * @return the currency value of this cell as a Double object. If the cell value is empty, null
   *     will be returned.
   * @throws IllegalArgumentException an IllegalArgumentException will be thrown if the cell type is
   *     not "currency".
   */
  public Double getCurrencyValue() {
    if (getTypeAttr() == OfficeValueTypeAttribute.Value.CURRENCY) {
      return mCellElement.getOfficeValueAttribute();
    } else {
      throw new IllegalArgumentException();
    }
  }

  /**
   * Get the symbol of currency.
   *
   * @return the currency symbol
   * @throws IllegalArgumentException an IllegalArgumentException will be thrown if the value type
   *     is not "currency".
   */
  public String getCurrencySymbol() {
    if (getTypeAttr() != OfficeValueTypeAttribute.Value.CURRENCY) {
      throw new IllegalArgumentException();
    }

    OdfStyle style = getCellStyleElement();
    if (style != null) {
      String dataStyleName =
          style.getOdfAttributeValue(
              OdfName.newName(OdfDocumentNamespace.STYLE, "data-style-name"));
      OdfNumberCurrencyStyle dataStyle =
          mCellElement.getOrCreateAutomaticStyles().getCurrencyStyle(dataStyleName);
      if (dataStyle == null) {
        dataStyle = mDocument.getDocumentStyles().getCurrencyStyle(dataStyleName);
      }
      if ((dataStyle != null) && (dataStyle.getCurrencySymbolElement() != null)) {
        return dataStyle.getCurrencySymbolElement().getTextContent();
      }
    }
    return null;
  }

  /**
   * Set the value and currency of the cell, and set the value type as "currency".
   * If<code>value</value> is null, the cell value will be removed.
   *
   * @param value  the value that will be set
   * @param currency	the currency that will be set.
   * @throws IllegalArgumentException  If input currency is null, an IllegalArgumentException will be thrown.
   */
  public void setCurrencyValue(Double value, String currency) {
    if (currency == null) {
      throw new IllegalArgumentException("currency shouldn't be null.");
    }
    splitRepeatedCells();
    setTypeAttr(OfficeValueTypeAttribute.Value.CURRENCY);
    mCellElement.setOfficeValueAttribute(value);
    mCellElement.setOfficeCurrencyAttribute(currency);
  }

  /**
   * Get the cell percentage value as Double object.
   *
   * <p>Throw IllegalArgumentException if the cell type is not "percentage".
   *
   * @return the percentage value of this cell as a Double object. If the cell value is empty, null
   *     will be returned.
   * @throws IllegalArgumentException an IllegalArgumentException will be thrown if the cell type is
   *     not "percentage".
   */
  public Double getPercentageValue() {
    if (getTypeAttr() == OfficeValueTypeAttribute.Value.PERCENTAGE) {
      return mCellElement.getOfficeValueAttribute();
    } else {
      throw new IllegalArgumentException();
    }
  }

  /**
   * Set the cell value as a percentage value and set the value type as percentage too.
   * If<code>value</value> is null, the cell value will be removed.
   *
   * @param value	the value that will be set
   */
  public void setPercentageValue(Double value) {
    splitRepeatedCells();
    setTypeAttr(OfficeValueTypeAttribute.Value.PERCENTAGE);
    mCellElement.setOfficeValueAttribute(value);
  }

  /**
   * Get the text displayed in this cell.
   *
   * @return the text displayed in this cell
   */
  public String getDisplayText() {
    // TODO: This function doesn't work well if a cell contains a list.
    // Refer to testGetSetTextValue();
    OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor();
    return textProcessor.getText(mCellElement);
  }

  /**
   * Set the text displayed in this cell.
   *
   * <p>Please note the displayed text in ODF viewer might be different with the value set by this
   * method, because the displayed text in ODF viewer is calculated and set by editor.
   *
   * @param content the displayed text. If content is null, it will display the empty string
   *     instead.
   */
  public void setDisplayText(String content) {
    if (content == null) {
      content = "";
    }
    removeContent();

    OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor();
    OdfTextParagraph para = new OdfTextParagraph((OdfFileDom) mCellElement.getOwnerDocument());
    textProcessor.append(para, content);

    mCellElement.appendChild(para);
  }

  /**
   * Set the text displayed in this cell, with a specified style name.
   *
   * <p>Please note the displayed text in ODF viewer might be different with the value set by this
   * method, because the displayed text in ODF viewer are calculated and set by editor.
   *
   * @param content the displayed text. If content is null, it will display the empty string
   *     instead.
   * @param stylename the style name. If stylename is null, the content will use the default
   *     paragraph style.
   */
  public void setDisplayText(String content, String stylename) {
    if (content == null) {
      content = "";
    }
    removeContent();

    OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor();
    OdfTextParagraph para = new OdfTextParagraph((OdfFileDom) mCellElement.getOwnerDocument());
    if ((stylename != null) && (stylename.length() > 0)) {
      para.setStyleName(stylename);
    }
    textProcessor.append(para, content);

    mCellElement.appendChild(para);
  }

  /**
   * Set the cell value as a double and set the value type to be "float".
   *
   * @param value	the double value that will be set. If<code>value</value> is null, the cell value will be removed.
   */
  public void setDoubleValue(Double value) {
    splitRepeatedCells();
    setTypeAttr(OfficeValueTypeAttribute.Value.FLOAT);
    mCellElement.setOfficeValueAttribute(value);
    setDisplayText(value + "");
  }

  /**
   * Get the cell boolean value as Boolean object.
   *
   * <p>Throw IllegalArgumentException if the cell type is not "boolean".
   *
   * @return the Boolean value of cell. If the cell value is empty, null will be returned.
   * @throws IllegalArgumentException an IllegalArgumentException will be thrown if the cell type is
   *     not "boolean".
   */
  public Boolean getBooleanValue() {
    if (getTypeAttr() == OfficeValueTypeAttribute.Value.BOOLEAN) {
      return mCellElement.getOfficeBooleanValueAttribute();
    } else {
      throw new IllegalArgumentException();
    }
  }

  /**
   * Set the cell value as a boolean and set the value type to be boolean. If<code>value</value> is null, the cell value will be removed.
   *
   * @param value	the value of boolean type
   */
  public void setBooleanValue(Boolean value) {
    splitRepeatedCells();
    setTypeAttr(OfficeValueTypeAttribute.Value.BOOLEAN);
    mCellElement.setOfficeBooleanValueAttribute(value);
    setDisplayText(value + "");
  }

  /**
   * Get the cell date value as Calendar.
   *
   * <p>Throw IllegalArgumentException if the cell type is not "date".
   *
   * @return the Calendar value of cell
   * @throws IllegalArgumentException an IllegalArgumentException will be thrown, if the cell type
   *     is not "date".
   */
  public Calendar getDateValue() {
    if (getTypeAttr() == OfficeValueTypeAttribute.Value.DATE) {
      String dateStr = mCellElement.getOfficeDateValueAttribute();
      if (dateStr == null) {
        return null;
      }
      Date date = parseString(dateStr, DEFAULT_DATE_FORMAT);
      Calendar calender = Calendar.getInstance();
      calender.setTime(date);
      return calender;
    } else {
      throw new IllegalArgumentException();
    }
  }

  /**
   * Set the cell value as a date, and set the value type to be "date".
   *
   * @param date the value of {@link java.util.Calendar java.util.Calendar} type.
   */
  public void setDateValue(Calendar date) {
    if (date == null) {
      throw new IllegalArgumentException("date shouldn't be null.");
    }
    splitRepeatedCells();
    setTypeAttr(OfficeValueTypeAttribute.Value.DATE);
    SimpleDateFormat simpleFormat = new SimpleDateFormat(DEFAULT_DATE_FORMAT);
    String svalue = simpleFormat.format(date.getTime());
    mCellElement.setOfficeDateValueAttribute(svalue);
    setDisplayText(svalue);
  }

  /**
   * Set the cell value as a string, and set the value type to be string.
   *
   * @param str the value of string type. If input string is null, an empty string will be set.
   */
  public void setStringValue(String str) {
    if (str == null) {
      str = "";
    }
    splitRepeatedCells();
    setTypeAttr(OfficeValueTypeAttribute.Value.STRING);
    mCellElement.setOfficeStringValueAttribute(str);
    setDisplayText(str);
  }

  // Note: if you want to change the cell
  // splitRepeatedCells must be called first in order to
  // 1. update parent row if the row is the repeated rows.
  // 2. update the cell itself if the cell is the column repeated cells.
  void splitRepeatedCells() {
    OdfTable table = getTable();
    // 1.if the parent row is the repeated row
    // the repeated row has to be separated
    // after this the cell element and repeated index will be updated
    // according to the new parent row
    OdfTableRow row = getTableRow();
    int colIndex = getColumnIndex();
    if (row.getRowsRepeatedNumber() > 1) {
      row.splitRepeatedRows();
      OdfTableCell cell = row.getCellByIndex(colIndex);
      mCellElement = cell.getOdfElement();
      mnRepeatedColIndex = cell.mnRepeatedColIndex;
      mnRepeatedRowIndex = cell.mnRepeatedRowIndex;
    }

    // 2.if the cell is the column repeated cell
    // this repeated cell has to be separated
    int repeateNum = getColumnsRepeatedNumber();
    if (repeateNum > 1) {
      // change this repeated column to several single columns
      TableTableCellElementBase ownerCellElement = null;
      int repeatedColIndex = mnRepeatedColIndex;
      Node refElement = mCellElement;
      for (int i = repeateNum - 1; i >= 0; i--) {
        TableTableCellElementBase newCell =
            (TableTableCellElementBase) mCellElement.cloneNode(true);
        newCell.removeAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "number-columns-repeated");
        row.getOdfElement().insertBefore(newCell, refElement);
        refElement = newCell;
        if (repeatedColIndex == i) {
          ownerCellElement = newCell;
        } else {
          table.updateCellRepository(
              mCellElement, i, mnRepeatedRowIndex, newCell, 0, mnRepeatedRowIndex);
        }
      }
      // remove this column element
      row.getOdfElement().removeChild(mCellElement);

      if (ownerCellElement != null) {
        table.updateCellRepository(
            mCellElement,
            mnRepeatedColIndex,
            mnRepeatedRowIndex,
            ownerCellElement,
            0,
            mnRepeatedRowIndex);
        mCellElement = ownerCellElement;
        mnRepeatedColIndex = 0;
      }
    }
  }

  /**
   * Get the cell value as a string.
   *
   * <p>If the cell type is not string, the display text will be returned.
   *
   * @return the string value of this cell, or the display text
   */
  public String getStringValue() {
    return getDisplayText();
  }

  /**
   * Get the cell value as {@link java.util.Calendar java.util.Calendar}.
   *
   * <p>Throw exception if the cell type is not "time".
   *
   * @return the Calendar value of cell
   * @throws IllegalArgumentException an IllegalArgumentException will be thrown if the cell type is
   *     not time.
   */
  public Calendar getTimeValue() {
    if (getTypeAttr() == OfficeValueTypeAttribute.Value.TIME) {
      String timeStr = mCellElement.getOfficeTimeValueAttribute();
      Date date = parseString(timeStr, DEFAULT_TIME_FORMAT);
      Calendar calender = Calendar.getInstance();
      calender.setTime(date);
      calender.clear(Calendar.YEAR);
      calender.clear(Calendar.MONTH);
      calender.clear(Calendar.DAY_OF_MONTH);
      return calender;
    } else {
      throw new IllegalArgumentException();
    }
  }

  /**
   * Set the cell value as a time and set the value type to be "time" too.
   *
   * @param time the value of {@link java.util.Calendar java.util.Calendar} type.
   * @throws IllegalArgumentException If input time is null, an IllegalArgumentException exception
   *     will be thrown.
   */
  public void setTimeValue(Calendar time) {
    if (time == null) {
      throw new IllegalArgumentException("time shouldn't be null.");
    }
    splitRepeatedCells();
    setTypeAttr(OfficeValueTypeAttribute.Value.TIME);
    SimpleDateFormat simpleFormat = new SimpleDateFormat(DEFAULT_TIME_FORMAT);
    String svalue = simpleFormat.format(time.getTime());
    mCellElement.setOfficeTimeValueAttribute(svalue);
    setDisplayText(svalue);
  }

  private Date parseString(String value, String format) {
    SimpleDateFormat simpleFormat = new SimpleDateFormat(format);
    Date simpleDate = null;
    try {
      simpleDate = simpleFormat.parse(value);
    } catch (ParseException e) {
      Logger.getLogger(OdfTableCell.class.getName()).log(Level.SEVERE, e.getMessage(), e);
      return null;
    }
    return simpleDate;
  }

  /**
   * Get the background color of this cell.
   *
   * <p>If no background color is set, default background color "#FFFFFF" will be returned.
   *
   * @return the background color of this cell
   */
  public Color getCellBackgroundColor() {
    Color color = Color.WHITE;
    OdfStyleBase styleElement = getCellStyleElement();
    if (styleElement != null) {
      OdfStyleProperty bkColorProperty =
          OdfStyleProperty.get(
              OdfStylePropertiesSet.TableCellProperties,
              OdfName.newName(OdfDocumentNamespace.FO, "background-color"));
      String property = styleElement.getProperty(bkColorProperty);
      if (property != null) {
        try {
          color = new Color(property);
        } catch (Exception e) {
          // use default background color White
          color = Color.WHITE;
          Logger.getLogger(OdfTableCell.class.getName()).log(Level.WARNING, e.getMessage());
        }
      }
    }
    return color;
  }

  /**
   * Get the background color string of this cell.
   *
   * <p>If no background color is set, default background color "#FFFFFF" will be returned.
   *
   * @return the background color of this cell
   */
  public String getCellBackgroundColorString() {
    String color = DEFAULT_BACKGROUND_COLOR;
    OdfStyleBase styleElement = getCellStyleElement();
    if (styleElement != null) {
      OdfStyleProperty bkColorProperty =
          OdfStyleProperty.get(
              OdfStylePropertiesSet.TableCellProperties,
              OdfName.newName(OdfDocumentNamespace.FO, "background-color"));
      String property = styleElement.getProperty(bkColorProperty);
      if (Color.isValid(property)) {
        color = property;
      }
    }
    return color;
  }

  /**
   * Set the background color of this cell.
   *
   * @param cellBackgroundColor the background color that need to set. If <code>cellBackgroundColor
   *     </code> is null, default background color <code>Color.WHITE</code> will be set.
   */
  public void setCellBackgroundColor(Color cellBackgroundColor) {
    if (cellBackgroundColor == null) {
      cellBackgroundColor = Color.WHITE;
    }
    splitRepeatedCells();
    OdfStyleBase styleElement = getCellStyleElementForWrite();
    if (styleElement != null) {
      OdfStyleProperty bkColorProperty =
          OdfStyleProperty.get(
              OdfStylePropertiesSet.TableCellProperties,
              OdfName.newName(OdfDocumentNamespace.FO, "background-color"));
      styleElement.setProperty(bkColorProperty, cellBackgroundColor.toString());
    }
  }

  /**
   * Set the background color of this cell using string. The string must be a valid argument for
   * constructing {@link org.odftoolkit.odfdom.type.Color
   * <code>org.odftoolkit.odfdom.type.Color</code>}.
   *
   * @param cellBackgroundColor the background color that need to set. If cellBackgroundColor is
   *     null, default background color #FFFFFF will be set.
   * @see org.odftoolkit.odfdom.type.Color
   */
  public void setCellBackgroundColor(String cellBackgroundColor) {
    if (!Color.isValid(cellBackgroundColor)) {
      Logger.getLogger(OdfTableCell.class.getName())
          .log(
              Level.WARNING,
              "Parameter is invalid for datatype Color, default background color #FFFFFF will be set.");
      cellBackgroundColor = DEFAULT_BACKGROUND_COLOR;
    }
    splitRepeatedCells();
    OdfStyleBase styleElement = getCellStyleElementForWrite();
    if (styleElement != null) {
      OdfStyleProperty bkColorProperty =
          OdfStyleProperty.get(
              OdfStylePropertiesSet.TableCellProperties,
              OdfName.newName(OdfDocumentNamespace.FO, "background-color"));
      styleElement.setProperty(bkColorProperty, cellBackgroundColor);
    }
  }

  /**
   * Get the column spanned number of this cell.
   *
   * @return the column spanned number
   */
  int getColumnSpannedNumber() {
    if (mCellElement instanceof TableCoveredTableCellElement) {
      return 1;
    }
    Integer value = ((TableTableCellElement) mCellElement).getTableNumberColumnsSpannedAttribute();
    if (value != null) {
      return value.intValue();
    }
    return DEFAULT_COLUMN_SPANNED_NUMBER;
  }

  /**
   * Get the column repeated number of this cell.
   *
   * @return the column repeated number
   */
  int getColumnsRepeatedNumber() {
    Integer value = mCellElement.getTableNumberColumnsRepeatedAttribute();
    if (value != null) {
      return value.intValue();
    }
    return DEFAULT_COLUMNS_REPEATED_NUMBER;
  }

  /**
   * Get the row spanned number of this cell.
   *
   * @return the row spanned number
   */
  int getRowSpannedNumber() {
    if (mCellElement instanceof TableCoveredTableCellElement) {
      return 1;
    }
    Integer value = ((TableTableCellElement) mCellElement).getTableNumberRowsSpannedAttribute();
    if (value != null) {
      return value.intValue();
    }
    return DEFAULT_ROW_SPANNED_NUMBER;
  }

  /**
   * Set the column spanned number.
   *
   * @param spannedNum the column spanned number to be set. If spannedNum is less than 1, default
   *     column spanned number 1 will be set.
   */
  void setColumnSpannedNumber(int spannedNum) {
    if (spannedNum < 1) {
      spannedNum = DEFAULT_COLUMN_SPANNED_NUMBER;
    }
    splitRepeatedCells();
    if (mCellElement instanceof TableTableCellElement) {
      ((TableTableCellElement) mCellElement)
          .setTableNumberColumnsSpannedAttribute(new Integer(spannedNum));
    } else {
      throw new IllegalArgumentException();
    }
  }

  /**
   * Set the column repeated number.
   *
   * @param repeatedNum the column repeated number that need to be set. If repeatedNum is less than
   *     1, default columns repeated number 1 will be set.
   */
  void setColumnsRepeatedNumber(int repeatedNum) {
    if (repeatedNum < 1) {
      repeatedNum = DEFAULT_COLUMNS_REPEATED_NUMBER;
    }
    mCellElement.setTableNumberColumnsRepeatedAttribute(new Integer(repeatedNum));
  }

  /**
   * Set the row spanned number.
   *
   * @param spannedNum row spanned number that need to be set the row spanned number that need to be
   *     set. If spannedNum is less than 1, default row spanned number 1 will be set.
   */
  void setRowSpannedNumber(int spannedNum) {
    if (spannedNum < 1) {
      spannedNum = DEFAULT_ROW_SPANNED_NUMBER;
    }
    splitRepeatedCells();
    if (mCellElement instanceof TableTableCellElement) {
      ((TableTableCellElement) mCellElement)
          .setTableNumberRowsSpannedAttribute(new Integer(spannedNum));
    } else {
      throw new IllegalArgumentException();
    }
  }

  /**
   * Judge if the ODF DOM element of this cell is the covered cell element.
   *
   * @return true if the ODFDOM element is TableCoveredTableCellElement
   */
  boolean isCoveredElement() {
    if (mCellElement instanceof TableCoveredTableCellElement) {
      return true;
    }
    return false;
  }

  /**
   * Get the style name of this cell.
   *
   * @return the name of the style
   */
  public String getStyleName() {
    OdfStyle style = getCellStyleElement();
    if (style == null) {
      return "";
    }
    return style.getStyleNameAttribute();
  }

  /**
   * Set a formula to the cell.
   *
   * <p>Please note, the parameter <code>formula</code> will not be checked and interpreted; the
   * cell value will not be calculated. It's just simply set as a formula attribute. See
   * {@odf.attribute table:formula}
   *
   * @param formula the formula that need to be set.
   * @throws IllegalArgumentException if formula is null, an IllegalArgumentException will be
   *     thrown.
   */
  public void setFormula(String formula) {
    if (formula == null) {
      throw new IllegalArgumentException("formula shouldn't be null.");
    }
    splitRepeatedCells();
    mCellElement.setTableFormulaAttribute(formula);
  }

  /**
   * Get the formula string of the cell.
   *
   * @return the formula representation of the cell
   *     <p>If the cell does not contain a formula, null will be returned.
   */
  public String getFormula() {
    return mCellElement.getTableFormulaAttribute();
  }

  //	/**
  //	 * get the error value of the cell
  //	 * if the formula can not be calculated, an error will be set
  //	 * @return
  //	 * 			return 0 if the cell has no error
  //	 * 			return the error value of the cell if the formula result can not be calculated
  //	 * 			such as divided by 0
  //	 */
  //	public long getError()
  //	{
  //		return 0;
  //	}
  /**
   * Set the currency symbol and overall format of a currency cell.
   *
   * <p>Please note the overall format includes the symbol character, for example: $#,##0.00.
   *
   * <p>This function only works for currency.
   *
   * @param currencySymbol the currency symbol
   * @param format overall format
   * @throws IllegalArgumentException if input currencySymbol or format is null, an
   *     IllegalArgumentException will be thrown.
   */
  public void setCurrencyFormat(String currencySymbol, String format) {
    if (currencySymbol == null) {
      throw new IllegalArgumentException("currencySymbol shouldn't be null.");
    }
    if (format == null) {
      throw new IllegalArgumentException("format shouldn't be null.");
    }
    splitRepeatedCells();
    String type = mCellElement.getOfficeValueTypeAttribute();
    OfficeValueTypeAttribute.Value typeValue = null;
    msFormatString = format;
    if (type != null) {
      typeValue = OfficeValueTypeAttribute.Value.enumValueOf(type);
    }

    if (typeValue != OfficeValueTypeAttribute.Value.CURRENCY) {
      throw new IllegalArgumentException();
    }

    OdfNumberCurrencyStyle currencyStyle =
        new OdfNumberCurrencyStyle(
            (OdfFileDom) mCellElement.getOwnerDocument(),
            currencySymbol,
            format,
            getUniqueCurrencyStyleName());
    mCellElement.getOrCreateAutomaticStyles().appendChild(currencyStyle);
    setDataDisplayStyleName(currencyStyle.getStyleNameAttribute());
    Double value = getCurrencyValue();

    // set display text
    if (value != null) {
      setDisplayText(formatCurrency(currencyStyle, value.doubleValue()));
    }
  }

  // This method doesn't handle style:map element.
  private String formatCurrency(OdfNumberCurrencyStyle currencyStyle, double value) {
    String valuestr = "";
    for (Node m : new DomNodeList(currencyStyle.getChildNodes())) {
      if (m instanceof NumberCurrencySymbolElement) {
        valuestr += m.getTextContent();
      } else if (m instanceof NumberNumberElement) {
        String numberformat = currencyStyle.getNumberFormat();
        valuestr += (new DecimalFormat(numberformat)).format(value);
      } else if (m instanceof NumberTextElement) {
        String textcontent = m.getTextContent();
        if (textcontent == null || textcontent.length() == 0) {
          textcontent = " ";
        }
        valuestr += textcontent;
      }
    }
    return valuestr;
  }

  /**
   * Set the format string of the cell.
   *
   * <p>This function only works for float, date, time and percentage, otherwise an {@link
   * java.lang.IllegalArgumentException} will be thrown.
   *
   * <p>For value type float and percentage, the <code>formatStr</code> must follow the encoding
   * rule of {@link java.text.DecimalFormat <code>java.text.DecimalFormat</code>}. For value type
   * date and time, the <code>formatStr</code> must follow the encoding rule of {@link
   * java.text.SimpleDateFormat <code>java.text.SimpleDateFormat</code>}.
   *
   * <p>Refer to {@link org.odftoolkit.odfdom.doc.table.OdfTableCell#setCurrencyFormat
   * <code>setCurrencyFormat</code>} to set the format of currency.
   *
   * <p>If the cell value type is not set, the method will try to give it a value type, according to
   * common ordination. The adapt order is: percentage-> time-> date-> float.
   *
   * <blockquote>
   *
   * <table border=0 cellspacing=3 cellpadding=0 summary="Chart showing ValueType, Distinguish Symbol
   * and Distinguish Priority.">
   *     <tr bgcolor="#ccccff">
   *          <th align=left>ValueType
   *          <th align=left>Distinguish Symbol
   *          <th align=left>Distinguish Priority
   *     <tr valign=top>
   *          <td>percentage
   *          <td>%
   *          <td>1
   *     <tr valign=top>
   *          <td>time
   *          <td>H, k, m, s, S
   *          <td>2
   *     <tr valign=top>
   *          <td>date
   *          <td>y, M, w, W, D, d, F, E, K, h
   *          <td>3
   *     <tr valign=top>
   *          <td>float
   *          <td>#, 0
   *          <td>4
   * </table>
   *
   * </blockquote>
   *
   * The adapt result may be inaccurate, so you'd better set value type before call this method. If
   * adaptive failed, an {@link java.lang.UnsupportedOperationException} will be thrown.
   *
   * <p>
   *
   * @param formatStr the cell need be formatted as this specified format string.
   * @throws IllegalArgumentException if <code>formatStr</code> is null or the cell value type is
   *     supported.
   * @throws UnsupportedOperationException if the adaptive failed, when cell value type is not set.
   * @see java.text.SimpleDateFormat
   * @see java.text.DecimalFormat
   */
  public void setFormatString(String formatStr) {
    if (formatStr == null) {
      throw new IllegalArgumentException("formatStr shouldn't be null.");
    }
    String type = getValueType();
    if (type == null) {
      if (formatStr.contains("%")) {
        setValueType("percentage");
      } else if (formatStr.contains("H")
          || formatStr.contains("k")
          || formatStr.contains("m")
          || formatStr.contains("s")
          || formatStr.contains("S")) {
        setValueType("time");
      } else if (formatStr.contains("y")
          || formatStr.contains("M")
          || formatStr.contains("w")
          || formatStr.contains("W")
          || formatStr.contains("D")
          || formatStr.contains("d")
          || formatStr.contains("F")
          || formatStr.contains("E")
          || formatStr.contains("K")
          || formatStr.contains("h")) {
        setValueType("date");
      } else if (formatStr.contains("#") || formatStr.contains("0")) {
        setValueType("float");
      } else {
        throw new UnsupportedOperationException(
            "format string: " + formatStr + " can't be adapted to a possible value type.");
      }
      type = getValueType();
    }
    setCellFormatString(formatStr, type);
  }

  private void setCellFormatString(String formatStr, String type) {
    OfficeValueTypeAttribute.Value typeValue = null;
    msFormatString = formatStr;
    splitRepeatedCells();
    typeValue = OfficeValueTypeAttribute.Value.enumValueOf(type);
    if (typeValue == OfficeValueTypeAttribute.Value.FLOAT) {
      OdfNumberStyle numberStyle =
          new OdfNumberStyle(
              (OdfFileDom) mCellElement.getOwnerDocument(), formatStr, getUniqueNumberStyleName());
      mCellElement.getOrCreateAutomaticStyles().appendChild(numberStyle);
      setDataDisplayStyleName(numberStyle.getStyleNameAttribute());
      Double value = getDoubleValue();
      if (value != null) {
        setDisplayText((new DecimalFormat(formatStr)).format(value.doubleValue()));
      }
    } else if (typeValue == OfficeValueTypeAttribute.Value.DATE) {
      OdfNumberDateStyle dateStyle =
          new OdfNumberDateStyle(
              (OdfFileDom) mCellElement.getOwnerDocument(),
              formatStr,
              getUniqueDateStyleName(),
              null);
      mCellElement.getOrCreateAutomaticStyles().appendChild(dateStyle);
      setDataDisplayStyleName(dateStyle.getStyleNameAttribute());
      String dateStr = mCellElement.getOfficeDateValueAttribute();
      if (dateStr != null) {
        Calendar date = getDateValue();
        setDisplayText((new SimpleDateFormat(formatStr)).format(date.getTime()));
      }
    } else if (typeValue == OfficeValueTypeAttribute.Value.TIME) {
      OdfNumberTimeStyle timeStyle =
          new OdfNumberTimeStyle(
              (OdfFileDom) mCellElement.getOwnerDocument(), formatStr, getUniqueDateStyleName());
      mCellElement.getOrCreateAutomaticStyles().appendChild(timeStyle);
      setDataDisplayStyleName(timeStyle.getStyleNameAttribute());
      String timeStr = mCellElement.getOfficeTimeValueAttribute();
      if (timeStr != null) {
        Calendar time = getTimeValue();
        setDisplayText((new SimpleDateFormat(formatStr)).format(time.getTime()));
      }
    } else if (typeValue == OfficeValueTypeAttribute.Value.PERCENTAGE) {
      OdfNumberPercentageStyle dateStyle =
          new OdfNumberPercentageStyle(
              (OdfFileDom) mCellElement.getOwnerDocument(),
              formatStr,
              getUniquePercentageStyleName());
      mCellElement.getOrCreateAutomaticStyles().appendChild(dateStyle);
      setDataDisplayStyleName(dateStyle.getStyleNameAttribute());
      Double value = getPercentageValue();
      if (value != null) {
        setDisplayText((new DecimalFormat(formatStr)).format(value.doubleValue()));
      }
    } else {
      throw new IllegalArgumentException("This function doesn't support " + typeValue + " cell.");
    }
  }

  private void setDataDisplayStyleName(String name) {
    OdfStyleBase styleElement = getCellStyleElementForWrite();
    if (styleElement != null) {
      styleElement.setOdfAttributeValue(
          OdfName.newName(OdfDocumentNamespace.STYLE, "data-style-name"), name);
    }
  }

  protected OdfStyle getCellStyleElement() {
    String styleName = mCellElement.getStyleName();
    if (styleName == null || (styleName.equals(""))) { // search in row
      OdfTableRow aRow = getTableRow();
      styleName = aRow.getOdfElement().getTableDefaultCellStyleNameAttribute();
    }
    if (styleName == null || (styleName.equals(""))) { // search in column
      OdfTableColumn aColumn = getTableColumn();
      styleName = aColumn.getOdfElement().getTableDefaultCellStyleNameAttribute();
    }
    if (styleName == null || (styleName.equals(""))) {
      return null;
    }

    OdfStyle styleElement =
        mCellElement
            .getOrCreateAutomaticStyles()
            .getStyle(styleName, mCellElement.getStyleFamily());

    if (styleElement == null) {
      styleElement = mDocument.getDocumentStyles().getStyle(styleName, OdfStyleFamily.TableCell);
    }

    if (styleElement == null) {
      styleElement = mCellElement.getDocumentStyle();
    }

    if (styleElement == null) {
      OdfStyle newStyle =
          mCellElement.getOrCreateAutomaticStyles().newStyle(OdfStyleFamily.TableCell);
      String newname = newStyle.getStyleNameAttribute();
      mCellElement.setStyleName(newname);
      newStyle.addStyleUser(mCellElement);
      return newStyle;
    }

    return styleElement;
  }

  protected OdfStyle getCellStyleElementForWrite() {
    boolean copy = false;
    String styleName = mCellElement.getStyleName();
    if (styleName == null || (styleName.equals(""))) { // search in row
      OdfTableRow aRow = getTableRow();
      styleName = aRow.getOdfElement().getTableDefaultCellStyleNameAttribute();
      copy = true;
    }
    if (styleName == null || (styleName.equals(""))) { // search in column
      OdfTableColumn aColumn = getTableColumn();
      styleName = aColumn.getOdfElement().getTableDefaultCellStyleNameAttribute();
      copy = true;
    }
    if (styleName == null || (styleName.equals(""))) {
      return null;
    }

    OdfOfficeAutomaticStyles styles = mCellElement.getOrCreateAutomaticStyles();
    OdfStyle styleElement = styles.getStyle(styleName, mCellElement.getStyleFamily());

    if (styleElement == null) {
      styleElement = mDocument.getDocumentStyles().getStyle(styleName, OdfStyleFamily.TableCell);
    }

    if (styleElement == null) {
      styleElement = mCellElement.getDocumentStyle();
    }

    if (styleElement.getStyleUserCount() > 1 || copy) // if this style are used by many users,
    // should create a new one.
    {
      OdfStyle newStyle =
          mCellElement.getOrCreateAutomaticStyles().newStyle(OdfStyleFamily.TableCell);
      newStyle.setProperties(styleElement.getStylePropertiesDeep());
      // copy attributes
      NamedNodeMap attributes = styleElement.getAttributes();
      for (int i = 0; i < attributes.getLength(); i++) {
        Node attr = attributes.item(i);
        if (!attr.getNodeName().equals("style:name")) {
          newStyle.setAttributeNS(attr.getNamespaceURI(), attr.getNodeName(), attr.getNodeValue());
        }
      } // end of copying attributes
      // mCellElement.getOrCreateAutomaticStyles().appendChild(newStyle);
      String newname = newStyle.getStyleNameAttribute();
      mCellElement.setStyleName(newname);
      return newStyle;
    }
    return styleElement;
  }

  private String getDataDisplayStyleName() {
    String datadisplayStylename = null;
    OdfStyleBase styleElement = getCellStyleElement();
    if (styleElement != null) {
      datadisplayStylename =
          styleElement.getOdfAttributeValue(
              OdfName.newName(OdfDocumentNamespace.STYLE, "data-style-name"));
    }

    return datadisplayStylename;
  }

  private String getUniqueNumberStyleName() {
    String unique_name;
    OdfOfficeAutomaticStyles styles = mCellElement.getOrCreateAutomaticStyles();
    do {
      unique_name = String.format("n%06x", (int) (Math.random() * 0xffffff));
    } while (styles.getNumberStyle(unique_name) != null);
    return unique_name;
  }

  private String getUniqueDateStyleName() {
    String unique_name;
    OdfOfficeAutomaticStyles styles = mCellElement.getOrCreateAutomaticStyles();
    do {
      unique_name = String.format("d%06x", (int) (Math.random() * 0xffffff));
    } while (styles.getDateStyle(unique_name) != null);
    return unique_name;
  }

  private String getUniquePercentageStyleName() {
    String unique_name;
    OdfOfficeAutomaticStyles styles = mCellElement.getOrCreateAutomaticStyles();
    do {
      unique_name = String.format("p%06x", (int) (Math.random() * 0xffffff));
    } while (styles.getPercentageStyle(unique_name) != null);
    return unique_name;
  }

  //    private String getUniqueCellStyleName() {
  //    	String unique_name;
  //		OdfOfficeAutomaticStyles styles = mCellElement.getOrCreateAutomaticStyles();
  //	    do {
  //			unique_name = String.format("a%06x", (int) (Math.random() * 0xffffff));
  //		} while (styles.getStyle(unique_name, OdfStyleFamily.TableCell) != null);
  //    	return unique_name;
  //    }
  private String getUniqueCurrencyStyleName() {
    String unique_name;
    OdfOfficeAutomaticStyles styles = mCellElement.getOrCreateAutomaticStyles();
    do {
      unique_name = String.format("c%06x", (int) (Math.random() * 0xffffff));
    } while (styles.getCurrencyStyle(unique_name) != null);
    return unique_name;
  }

  /**
   * Get the format string of the cell.
   *
   * @return the format string of the cell
   */
  public String getFormatString() {
    String type = mCellElement.getOfficeValueTypeAttribute();
    OfficeValueTypeAttribute.Value typeValue = null;
    if (type != null) {
      typeValue = OfficeValueTypeAttribute.Value.enumValueOf(type);
    }

    if (typeValue == OfficeValueTypeAttribute.Value.FLOAT) {
      String name = getDataDisplayStyleName();
      OdfNumberStyle style = mCellElement.getOrCreateAutomaticStyles().getNumberStyle(name);
      if (style == null) {
        style = mDocument.getDocumentStyles().getNumberStyle(name);
      }
      if (style != null) {
        return style.getFormat(true);
      }
    } else if (typeValue == OfficeValueTypeAttribute.Value.DATE) {
      String name = getDataDisplayStyleName();
      OdfNumberDateStyle style = mCellElement.getOrCreateAutomaticStyles().getDateStyle(name);
      if (style == null) {
        style = mDocument.getDocumentStyles().getDateStyle(name);
      }
      if (style != null) {
        return style.getFormat(true);
      }
    } else if (typeValue == OfficeValueTypeAttribute.Value.CURRENCY) {
      String name = getCurrencyDisplayStyleName();
      OdfNumberCurrencyStyle dataStyle =
          mCellElement.getOrCreateAutomaticStyles().getCurrencyStyle(name);
      if (dataStyle == null) {
        dataStyle = mDocument.getDocumentStyles().getCurrencyStyle(name);
      }
      if (dataStyle != null) {
        return dataStyle.getFormat(true);
      }
    } else if (typeValue == OfficeValueTypeAttribute.Value.PERCENTAGE) {
      String name = getDataDisplayStyleName();
      OdfNumberPercentageStyle style =
          mCellElement.getOrCreateAutomaticStyles().getPercentageStyle(name);
      if (style == null) {
        style = mDocument.getDocumentStyles().getPercentageStyle(name);
      }
      if (style != null) {
        return style.getFormat(true);
      }
    }
    return null;
  }

  private String getCurrencyDisplayStyleName() {
    String name = getDataDisplayStyleName();
    OdfNumberCurrencyStyle dataStyle =
        mCellElement.getOrCreateAutomaticStyles().getCurrencyStyle(name);
    if (dataStyle == null) {
      dataStyle = mDocument.getDocumentStyles().getCurrencyStyle(name);
    }

    if (dataStyle != null) {
      return dataStyle.getConditionStyleName(getCurrencyValue());
    }
    return null;
  }

  /** Remove all the text content of cell. */
  public void removeTextContent() {
    splitRepeatedCells();
    // delete text:p child element
    Node node = mCellElement.getFirstChild();
    while (node != null) {
      Node nextNode = node.getNextSibling();
      if (node instanceof TextPElement
          || node instanceof TextHElement
          || node instanceof TextListElement) {
        mCellElement.removeChild(node);
      }
      node = nextNode;
    }
  }

  /** Remove all the content of the cell. */
  public void removeContent() {
    splitRepeatedCells();
    Node node = mCellElement.getFirstChild();
    while (node != null) {
      Node nextNode = node.getNextSibling();
      mCellElement.removeChild(node);
      node = nextNode;
    }
  }

  /**
   * Append the content of another cell.
   *
   * @param fromCell another cell whose content will be appended to this cell
   */
  void appendContentFrom(OdfTableCell fromCell) {
    splitRepeatedCells();
    OdfWhitespaceProcessor textProcess = new OdfWhitespaceProcessor();
    TableTableCellElementBase cell = fromCell.getOdfElement();
    Node node = cell.getFirstChild();
    while (node != null) {
      if (node instanceof OdfTextParagraph) {
        if (!textProcess.getText(node).equals("")) {
          mCellElement.appendChild(node.cloneNode(true));
        }
      } else {
        mCellElement.appendChild(node.cloneNode(true));
      }
      node = node.getNextSibling();
    }
  }

  /**
   * *************************************** Moved from OdfTable
   *
   * <p>*****************************************
   */
  /**
   * This method is invoked by insertCellBefore and insertRowBefore When it is needed to clone a
   * cell and the cell is a cover cell, for some instance, we need to find the cell who covers this
   * cell. So this method is to get the cell who covers this cell
   */
  OdfTableCell getCoverCell() {
    int startRowi = getRowIndex();
    int startColumni = getColumnIndex();

    for (int i = startRowi; i >= 0; i--) {
      OdfTableRow aRow = mOwnerTable.getRowByIndex(i);
      for (int j = startColumni; j >= 0; j--) {
        if (i == startRowi && j == startColumni) {
          continue;
        }
        OdfTableCell cell = aRow.getCellByIndex(j);
        if (cell.getOdfElement() instanceof TableTableCellElement) {
          TableTableCellElement cellEle = (TableTableCellElement) cell.getOdfElement();
          if ((cellEle.getTableNumberColumnsSpannedAttribute() + j > startColumni)
              && (cellEle.getTableNumberRowsSpannedAttribute() + i > startRowi)) {
            return mOwnerTable.getCellInstance(cellEle, 0, 0);
          }
        }
      }
    }
    return null;
  }

  /**
   * This method is invoked by getCoverCell. It's to get the cell in a same row who covers this
   * cell.
   *
   * @return the cell in a same row who covers this cell
   *     <p>Null if there is no cell who covers this cell
   */
  OdfTableCell getCoverCellInSameRow() {
    int startRowi = getRowIndex();
    int startColumni = getColumnIndex();

    for (int j = startColumni - 1; j >= 0; j--) {
      OdfTableCell cell = mOwnerTable.getCellByPosition(j, startRowi);
      if (cell.getOdfElement() instanceof TableCoveredTableCellElement) {
        continue;
      }

      int oldSpanN = cell.getColumnSpannedNumber();
      if (oldSpanN + j > startColumni) {
        // cell.setColumnSpannedNumber(oldSpanN-1);
        return cell;
      }
      return null;
    }
    return null;
  }

  /** This method is invoked by getCoverCell */
  OdfTableCell getCoverCellInSameColumn() {
    int startRowi = getRowIndex();
    int startColumni = getColumnIndex();

    for (int i = startRowi - 1; i >= 0; i--) {
      OdfTableCell cell = mOwnerTable.getCellByPosition(startColumni, i);
      if (cell.getOdfElement() instanceof TableCoveredTableCellElement) {
        continue;
      }

      int oldSpanN = cell.getRowSpannedNumber();
      if (oldSpanN + i > startRowi) {
        // cell.setRowSpannedNumber(oldSpanN-1);
        return cell;
      }
      return null;
    }
    return null;
  }
}