OdfTableColumn.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.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import org.odftoolkit.odfdom.doc.OdfDocument;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
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.style.OdfStyleFamily;
import org.odftoolkit.odfdom.dom.style.props.OdfTableColumnProperties;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfName;
import org.odftoolkit.odfdom.pkg.OdfXMLFactory;
import org.odftoolkit.odfdom.type.Length.Unit;
import org.odftoolkit.odfdom.type.PositiveLength;
import org.w3c.dom.Node;
/**
* OdfTableColumn represents table column feature in ODF document.
*
* <p>OdfTableColumn provides methods to get table cells that belong to this table column.
*/
public class OdfTableColumn {
TableTableColumnElement maColumnElement;
int mnRepeatedIndex;
private static final String DEFAULT_WIDTH = "0in";
private OdfDocument mDocument;
/**
* Construct the <code>OdfTableColumn</code> feature.
*
* @param odfElement the element that can construct this table column
* @param repeatedIndex the index in the repeated columns
*/
OdfTableColumn(TableTableColumnElement colElement, int repeatedIndex) {
maColumnElement = colElement;
mnRepeatedIndex = repeatedIndex;
mDocument = (OdfDocument) ((OdfFileDom) maColumnElement.getOwnerDocument()).getDocument();
}
/**
* Get the <code>OdfTableColumn</code> instance from the <code>TableTableColumnElement</code>
* instance.
*
* <p>Each <code>TableTableColumnElement</code> instance has a one-to-one relationship to the a
* <code>OdfTableColumn</code> instance.
*
* @param colElement the column element that need to get the corresponding <code>OdfTableColumn
* </code> instance
* @return the <code>OdfTableColumn</code> instance represent the specified column element
*/
public static OdfTableColumn getInstance(TableTableColumnElement colElement) {
TableTableElement tableElement = null;
Node node = colElement.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 colElement is not in the table dom tree");
}
OdfTableColumn column = table.getColumnInstance(colElement, 0);
if (column.getColumnsRepeatedNumber() > 1) {
Logger.getLogger(OdfTableColumn.class.getName())
.log(
Level.WARNING,
"the column has the repeated column number, and puzzled about get which repeated index of the column,"
+ "here just return the first column of the repeated columns.");
}
return column;
}
/**
* Get the <code>TableTableElement</code> who contains this cell.
*
* @return the table that contains the cell.
*/
private TableTableElement getTableElement() {
Node node = maColumnElement.getParentNode();
while (node != null) {
if (node instanceof TableTableElement) {
return (TableTableElement) node;
}
node = node.getParentNode();
}
return null;
}
/**
* Get owner table of the current column.
*
* @return the parent table of this column
*/
public OdfTable getTable() {
TableTableElement tableElement = getTableElement();
if (tableElement != null) {
return OdfTable.getInstance(tableElement);
}
return null;
}
/**
* Get the width of the column (in Millimeter).
*
* @return the width of the current column (in Millimeter).
*/
public long getWidth() {
String sWidth = maColumnElement.getProperty(OdfTableColumnProperties.ColumnWidth);
if (sWidth == null) {
sWidth = DEFAULT_WIDTH;
}
return PositiveLength.parseLong(sWidth, Unit.MILLIMETER);
}
/**
* Set the width of the column (in Millimeter).
*
* @param width the width that will be set to the column (in Millimeter).
*/
public void setWidth(long width) {
String sWidthMM = String.valueOf(width) + Unit.MILLIMETER.abbr();
String sWidthIN = PositiveLength.mapToUnit(sWidthMM, Unit.INCH);
splitRepeatedColumns();
maColumnElement.setProperty(OdfTableColumnProperties.ColumnWidth, sWidthIN);
// check if need set relative width
int index = getColumnIndex();
if (index >= 1) {
index = index - 1;
} else {
index = index + 1;
}
OdfTableColumn column = null;
if (index < getTable().getColumnCount()) {
column = getTable().getColumnByIndex(index);
}
if (column != null) {
long prevColumnRelWidth = column.getRelativeWidth();
if (prevColumnRelWidth != 0) {
long prevColumnWidth = column.getWidth();
setRelativeWidth(prevColumnRelWidth / prevColumnWidth * width);
}
}
}
// if one of the repeated column want to change something
// then this repeated column have to split to repeated number columns
// the maColumnElement should also be updated according to the original index in the repeated
// column
void splitRepeatedColumns() {
OdfTable table = getTable();
TableTableElement tableEle = table.getOdfElement();
int repeateNum = getColumnsRepeatedNumber();
if (repeateNum > 1) {
// change this repeated column to several single columns
TableTableColumnElement ownerColumnElement = null;
int repeatedColumnIndex = mnRepeatedIndex;
Node refElement = maColumnElement;
maColumnElement.removeAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-columns-repeated");
String originalWidth = maColumnElement.getProperty(OdfTableColumnProperties.ColumnWidth);
String originalRelWidth =
maColumnElement.getProperty(OdfTableColumnProperties.RelColumnWidth);
for (int i = repeateNum - 1; i >= 0; i--) {
TableTableColumnElement newColumn =
(TableTableColumnElement)
OdfXMLFactory.newOdfElement(
(OdfFileDom) maColumnElement.getOwnerDocument(),
OdfName.newName(OdfDocumentNamespace.TABLE, "table-column"));
if (originalWidth != null && originalWidth.length() > 0) {
newColumn.setProperty(OdfTableColumnProperties.ColumnWidth, originalWidth);
}
if (originalRelWidth != null && originalRelWidth.length() > 0) {
newColumn.setProperty(OdfTableColumnProperties.RelColumnWidth, originalRelWidth);
}
tableEle.insertBefore(newColumn, refElement);
refElement = newColumn;
if (repeatedColumnIndex == i) {
ownerColumnElement = newColumn;
} else {
table.updateColumnRepository(maColumnElement, i, newColumn, 0);
}
}
// remove this column element
tableEle.removeChild(maColumnElement);
if (ownerColumnElement != null) {
table.updateColumnRepository(maColumnElement, mnRepeatedIndex, ownerColumnElement, 0);
}
}
}
private long getRelativeWidth() {
String sRelWidth = maColumnElement.getProperty(OdfTableColumnProperties.RelColumnWidth);
if (sRelWidth != null) {
if (sRelWidth.contains("*")) {
Long value = Long.valueOf(sRelWidth.substring(0, sRelWidth.indexOf("*")));
return value.longValue();
}
}
return 0;
}
private void setRelativeWidth(long relWidth) {
maColumnElement.setProperty(
OdfTableColumnProperties.RelColumnWidth, String.valueOf(relWidth) + "*");
}
/**
* Returns if the column always keeps its optimal width.
*
* @return true if the column always keeps its optimal width; vice versa
*/
public boolean isOptimalWidth() {
return Boolean.parseBoolean(
maColumnElement.getProperty(OdfTableColumnProperties.UseOptimalColumnWidth));
}
/**
* Set if the column always keeps its optimal width.
*
* @param isUseOptimalWidth the flag that indicate column should keep its optimal width or not
*/
public void setUseOptimalWidth(boolean isUseOptimalWidth) {
maColumnElement.setProperty(
OdfTableColumnProperties.UseOptimalColumnWidth, String.valueOf(isUseOptimalWidth));
}
/**
* Return an instance of <code>TableTableColumnElement</code> which represents this feature.
*
* @return an instance of <code>TableTableColumnElement</code>
*/
public TableTableColumnElement getOdfElement() {
return maColumnElement;
}
/**
* Get the count of cells in this column.
*
* @return the cells count in the current column
*/
public int getCellCount() {
return getTable().getRowCount();
}
/**
* Get a cell with a specific index. The table will be automatically expanded, when the given
* index is outside of the original table.
*
* @param index the cell index in this column
* @return the cell object in the given cell index
*/
public OdfTableCell getCellByIndex(int index) {
return getTable().getCellByPosition(getColumnIndex(), index);
}
/**
* Get the previous column of the current column.
*
* @return the previous column before this column in the owner table
*/
public OdfTableColumn getPreviousColumn() {
OdfTable table = getTable();
// the column has repeated column number > 1
if (maColumnElement.getTableNumberColumnsRepeatedAttribute().intValue() > 1) {
if (mnRepeatedIndex > 0) {
return table.getColumnInstance(maColumnElement, mnRepeatedIndex - 1);
}
}
// the column has repeated column number > 1 && the index is 0
// or the column has repeated column num = 1
Node aPrevNode = maColumnElement.getPreviousSibling();
Node aCurNode = maColumnElement;
while (true) {
if (aPrevNode == null) {
// does not have previous sibling, then get the parent
// because aCurNode might be the child element of table-header-columns, table-columns,
// table-column-group
Node parentNode = aCurNode.getParentNode();
// if the parent is table, then it means that this column is the first column in this table
// it has no previous column
if (parentNode instanceof TableTableElement) {
return null;
}
aPrevNode = parentNode.getPreviousSibling();
}
// else the parent node might be table-header-columns, table-columns, table-column-group
if (aPrevNode != null) {
try {
if (aPrevNode instanceof TableTableColumnElement) {
return table.getColumnInstance(
(TableTableColumnElement) aPrevNode,
((TableTableColumnElement) aPrevNode)
.getTableNumberColumnsRepeatedAttribute()
.intValue()
- 1);
} else if (aPrevNode instanceof TableTableColumnsElement
|| aPrevNode instanceof TableTableHeaderColumnsElement
|| aPrevNode instanceof TableTableColumnGroupElement) {
XPath xpath = ((OdfFileDom) maColumnElement.getOwnerDocument()).getXPath();
TableTableColumnElement lastCol =
(TableTableColumnElement)
xpath.evaluate("//table:table-column[last()]", aPrevNode, XPathConstants.NODE);
if (lastCol != null) {
return table.getColumnInstance(
lastCol, lastCol.getTableNumberColumnsRepeatedAttribute().intValue() - 1);
}
} else {
aCurNode = aPrevNode;
aPrevNode = aPrevNode.getPreviousSibling();
}
} catch (XPathExpressionException e) {
Logger.getLogger(OdfTableColumn.class.getName()).log(Level.SEVERE, e.getMessage(), e);
}
}
}
}
/**
* Get the next column of the current column.
*
* @return the next column after this column in the owner table
*/
public OdfTableColumn getNextColumn() {
OdfTable table = getTable();
// the column has repeated column number > 1
if (getColumnsRepeatedNumber() > 1) {
if (mnRepeatedIndex < (getColumnsRepeatedNumber() - 1)) {
return table.getColumnInstance(maColumnElement, mnRepeatedIndex + 1);
}
}
Node aNextNode = maColumnElement.getNextSibling();
Node aCurNode = maColumnElement;
while (true) {
if (aNextNode == null) {
// does not have next sibling, then get the parent
// because aCurNode might be the child element of table-header-columns, table-columns,
// table-column-group
Node parentNode = aCurNode.getParentNode();
// if the parent is table, then it means that this column is the last column in this table
// it has no next column
if (parentNode instanceof TableTableElement) {
return null;
}
aNextNode = parentNode.getNextSibling();
}
// else the parent node might be table-header-columns, table-columns, table-column-group
if (aNextNode != null) {
try {
if (aNextNode instanceof TableTableColumnElement) {
return table.getColumnInstance((TableTableColumnElement) aNextNode, 0);
} else if (aNextNode instanceof TableTableColumnsElement
|| aNextNode instanceof TableTableHeaderColumnsElement
|| aNextNode instanceof TableTableColumnGroupElement) {
XPath xpath = ((OdfFileDom) maColumnElement.getOwnerDocument()).getXPath();
TableTableColumnElement firstCol =
(TableTableColumnElement)
xpath.evaluate("//table:table-column[first()]", aNextNode, XPathConstants.NODE);
if (firstCol != null) {
return table.getColumnInstance(firstCol, 0);
}
} else {
aCurNode = aNextNode;
aNextNode = aNextNode.getNextSibling();
}
} catch (XPathExpressionException e) {
Logger.getLogger(OdfTableColumn.class.getName()).log(Level.SEVERE, e.getMessage(), e);
}
}
}
}
/**
* Get the index of this column in the owner table.
*
* @return the index of the column
*/
public int getColumnIndex() {
int result = 0;
OdfTable table = getTable();
TableTableColumnElement columnEle;
TableTableElement mTableElement = table.getOdfElement();
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) {
columnEle = (TableTableColumnElement) m;
if (columnEle == getOdfElement()) {
return result + mnRepeatedIndex;
}
if (columnEle.getTableNumberColumnsRepeatedAttribute() == null) {
result += 1;
} else {
result += columnEle.getTableNumberColumnsRepeatedAttribute();
}
}
}
}
if (n instanceof TableTableColumnElement) {
columnEle = (TableTableColumnElement) n;
if (columnEle == getOdfElement()) {
break;
}
if (columnEle.getTableNumberColumnsRepeatedAttribute() == null) {
result += 1;
} else {
result += columnEle.getTableNumberColumnsRepeatedAttribute();
}
}
}
return result + mnRepeatedIndex;
}
/**
* Set the default cell style to this column.
*
* <p>The style should already exist in this document.
*
* <p>This method is not recommended for text document cases. These is a style assigned to each
* cell in tables under text documents. So setting the default cell style to a column may not
* work.
*
* @param style the cell style of the document
*/
public void setDefaultCellStyle(OdfStyle style) {
splitRepeatedColumns();
OdfStyle defaultStyle = getDefaultCellStyle();
if (defaultStyle != null) {
defaultStyle.removeStyleUser(maColumnElement);
}
if (style != null) {
style.addStyleUser(maColumnElement);
maColumnElement.setTableDefaultCellStyleNameAttribute(style.getStyleNameAttribute());
}
}
/**
* Get the default cell style of this column.
*
* @return the default cell style of this column
*/
public OdfStyle getDefaultCellStyle() {
String styleName = maColumnElement.getTableDefaultCellStyleNameAttribute();
OdfStyle style =
maColumnElement.getOrCreateAutomaticStyles().getStyle(styleName, OdfStyleFamily.TableCell);
if (style == null) {
style = mDocument.getDocumentStyles().getStyle(styleName, OdfStyleFamily.TableCell);
}
return style;
}
// note: we have to use this method to modify the column repeated number
// in order to update mnRepeatedIndex of the each column
void setColumnsRepeatedNumber(int num) {
// update the mnRepeatedIndex for the ever repeated column
maColumnElement.setTableNumberColumnsRepeatedAttribute(Integer.valueOf(num));
}
int getColumnsRepeatedNumber() {
Integer count = maColumnElement.getTableNumberColumnsRepeatedAttribute();
if (count == null) {
return 1;
} else {
return count.intValue();
}
}
}