Table.java

/*
 * Copyright 2012 The Apache Software Foundation.
 *
 * Licensed 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
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */
package org.odftoolkit.odfdom.changes;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import org.odftoolkit.odfdom.doc.table.OdfTable;
import org.odftoolkit.odfdom.doc.table.OdfTableRow;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.element.OdfStylableElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTableColumnPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTablePropertiesElement;
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.table.TableTableRowGroupElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableRowsElement;
import org.odftoolkit.odfdom.dom.style.props.OdfStyleProperty;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.type.Length;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * A MultiCoomponent uses a single XML element to represent multiple components. This container can
 * be used for spreadsheet row and cell components using repeated elements via an attribute.
 *
 * @author svante.schubertATgmail.com
 */
public class Table<T> extends Component {

  public Table(OdfElement componentElement, Component parent) {
    super(componentElement, parent);
  }

  private static final Logger LOG = Logger.getLogger(Table.class.getName());
  /** Used to indicate that the end position is not existing */
  public static final int ETERNITY = -1;
  /**
   * Used to indicate that the start position is before the table If a column is inserted in the
   * first place, there is no prior column to inherit styles from.
   */
  static final int BEFORE_START = -1;

  /**
   * The maximal number of rows being generated. Unspecified by ODF but commonly used. Counting
   * starts with 0.
   */
  public static final Integer MAX_ROW_NUMBER = 1048576;

  /**
   * The maximal number of columns being generated. Unspecified by ODF but commonly used. Counting
   * starts with 0.
   */
  public static final Integer MAX_COLUMN_NUMBER_CALC =
      1024; // 2^10 There is only a flag in Calc that was changed for having Excel Size.

  public static final Integer MAX_COLUMN_NUMBER_EXCEL = 16384; // 2^16

  /**
   * A multiple components can be represented by a single XML element
   *
   * @return the number of components the elements represents
   */
  @Override
  public int repetition() {
    return mRootElement.getRepetition();
  }

  @Override
  // TABLE ONLY -- Svante REMOVE THIS LATER AS THIS IT IS ONLY USED BY TABLES
  public List getChildren() {
    return list(this);
  }

  // Svante ToDo: After all the refactoring this looks like something to change after the release as
  // well.
  private List<Component> list(final Table tableComponent) {
    return new AbstractList<Component>() {
      @Override
      public int size() {
        return tableComponent.size();
      }

      @Override
      public Component get(int index) {
        Component c = null;
        TableTableElement tableRoot = (TableTableElement) tableComponent.getRootElement();
        OdfTable table = OdfTable.getInstance(tableRoot);
        OdfTableRow row = table.getRowByIndex(index);
        if (row != null) {
          c = row.getOdfElement().getComponent();
        }
        return c;
      }
    };
  }

  /**
   * Adds the given component to the root element
   *
   * @param c the component of the row to be added
   */
  @Override
  public void addChild(int index, Component c) {
    mRootElement.insert(c.getRootElement(), index);
    // 2DO: Svante: ARE THE ABOVE AND THE BELOW EQUIVALENT?
    //		OdfElement rootElement = c.getRootElement();
    //		if (index >= 0) {
    //			mRootElement.insertBefore(rootElement, ((OdfElement) mRootElement).receiveNode(index));
    //		} else {
    //			mRootElement.appendChild(rootElement);
    //		}
  }

  /**
   * @param index the position of the row being returned
   * @return either a text node of size 1 or an element being the root element of a component
   */
  @Override
  public Node getChildNode(int index) {
    return mRootElement.receiveNode(index);
  }

  /**
   * Removes a component from the text element container. Removes either an element representing a
   * component or text node of size 1
   *
   * @param index row position to be removed
   * @return the row being removed
   */
  @Override
  public Node remove(int index) {
    Node node = this.getChildNode(index);
    return mRootElement.removeChild(node);
  }

  /**
   * All children of the root element will be traversed. If it is a text node the size is added, if
   * it is an element and a component a size of one is added, if it is a marker, for known text
   * marker elements (text:span, text:bookmark) the children are recursive checked
   *
   * @return the number of child components
   */
  @Override
  public int size() {
    OdfTable table = OdfTable.getInstance((TableTableElement) mRootElement);
    return table.getRowCount();
  }

  /** @return a property value. */
  //	private String getPropertyValue(OdfStyleProperty prop) {
  //		String value = null;
  //
  //		OdfStylePropertiesBase properties = getPropertiesElement(prop.getPropertySet());
  //		if (properties != null) {
  //			if (properties.hasAttributeNS(prop.getName().getUri(), prop.getName().getLocalName())) {
  //				return properties.getOdfAttribute(prop.getName()).getValue();
  //			}
  //		}
  //
  //		OdfStyleBase parent = getParentStyle();
  //		if (parent != null) {
  //			return parent.getProperty(prop);
  //		}
  //
  //		return value;
  //	}
  public static String getProperty(OdfStyleProperty prop, OdfStylableElement element) {
    String value = null;
    OdfStyle style = element.getAutomaticStyle();
    if (style == null) {
      element.getOrCreateUnqiueAutomaticStyle();
      style = element.getAutomaticStyle();
      if (style == null) {
        style = element.getDocumentStyle();
      }
    }
    if (style != null) {
      value = style.getProperty(prop);
    }
    return value;
  }

  private static Length getPropertyLength(OdfStyleProperty prop, OdfStylableElement element) {
    Length length = null;
    String propValue = getProperty(prop, element);
    if (propValue != null && !propValue.isEmpty()) {
      length = new Length(propValue);
    }
    return length;
  }

  public static List<Integer> collectColumnWidths(
      TableTableElement tableElement, List<TableTableColumnElement> columns) {
    boolean hasRelColumnWidth = false;
    boolean hasAbsColumnWidth = false;
    boolean hasColumnWithoutWidth = false;
    List<Integer> columnRelWidths = new ArrayList();
    for (TableTableColumnElement column : columns) {
      if (column.hasAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "style-name")) {
        Length tableWidth = getPropertyLength(StyleTablePropertiesElement.Width, tableElement);

        int repeatedColumns = 1;
        if (column.hasAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "number-columns-repeated")) {
          repeatedColumns =
              Integer.parseInt(
                  column.getAttributeNS(
                      OdfDocumentNamespace.TABLE.getUri(), "number-columns-repeated"));
        }

        String columnRelWidth =
            getProperty(StyleTableColumnPropertiesElement.RelColumnWidth, column);

        // it is being assumed, when the columnRelWidth is once set, it is always set
        if (columnRelWidth != null && !columnRelWidth.isEmpty()) {
          hasRelColumnWidth = true;
          if (hasAbsColumnWidth) {
            LOG.warning(
                "******* BEWARE: Absolute and relative width are not supposed to be mixed!! ***********");
          }
          columnRelWidth = columnRelWidth.substring(0, columnRelWidth.indexOf('*'));
          Integer relWidth = Integer.parseInt(columnRelWidth);
          for (int i = 0; i < repeatedColumns; i++) {
            columnRelWidths.add(relWidth);
          }
        } else { // if there is no relative column width
          if (hasRelColumnWidth) {
            LOG.warning(
                "******* BEWARE: Absolute and relative width are not supposed to be mixed!! ***********");
          }

          Length columnWidth =
              getPropertyLength(StyleTableColumnPropertiesElement.ColumnWidth, column);
          // there can be only table width and ..
          if (tableWidth != null) {
            // columnwidth, with a single one missing
            if (columnWidth != null) {
              hasAbsColumnWidth = true;
              int widthFactor =
                  (int)
                      Math.round(
                          (columnWidth.getMillimeters() * 100) / tableWidth.getMillimeters());
              for (int i = 0; i < repeatedColumns; i++) {
                columnRelWidths.add(widthFactor);
              }
            } else {
              if (hasColumnWithoutWidth) {
                LOG.warning(
                    "******* BEWARE: Two columns without width and no column width are not expected!! ***********");
              }
              hasColumnWithoutWidth = true;
            }
            // if the table is not set, it will always be unset..
          } else {
            if (columnWidth != null) {
              hasAbsColumnWidth = true;
              int widthFactor = (int) Math.round((columnWidth.getMicrometer() * 10));
              for (int i = 0; i < repeatedColumns; i++) {
                columnRelWidths.add(widthFactor);
              }
            } else {
              LOG.warning(
                  "******* BEWARE: Two columns without width and no column width are not expected!! ***********");
            }
          }
        }
      }
    }
    return columnRelWidths;
  }

  static void stashColumnWidths(TableTableElement tableElement) {
    List<TableTableColumnElement> existingColumnList =
        Table.getTableColumnElements(tableElement, new LinkedList<TableTableColumnElement>());
    List<Integer> tableColumWidths = collectColumnWidths(tableElement, existingColumnList);
    tableElement.pushTableGrid(tableColumWidths);
  }

  /**
   * Returns all TableTableColumn descendants that exist within the tableElement, even within
   * groups, columns and header elements
   */
  public static List<TableTableColumnElement> getTableColumnElements(Element parent, List columns) {
    NodeList children = parent.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      Node child = children.item(i);
      if (child instanceof Element) {
        if (child instanceof TableTableColumnElement) {
          columns.add(child);
        } else if (child instanceof TableTableColumnGroupElement
            || child instanceof TableTableHeaderColumnsElement
            || child instanceof TableTableColumnsElement) {
          columns = getTableColumnElements((Element) child, columns);
        } else if (child instanceof TableTableRowGroupElement
            || child instanceof TableTableHeaderRowsElement
            || child instanceof TableTableRowElement
            || child instanceof TableTableRowsElement) {
          break;
        }
      }
    }
    return columns;
  }
}