| /* |
| 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 |
| |
| 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.simple.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.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; |
| import org.odftoolkit.odfdom.type.PositiveLength; |
| import org.odftoolkit.odfdom.type.Length.Unit; |
| import org.odftoolkit.simple.Component; |
| import org.odftoolkit.simple.Document; |
| import org.odftoolkit.simple.SpreadsheetDocument; |
| import org.w3c.dom.Node; |
| |
| /** |
| * Column represents table column feature in ODF document. |
| * <p> |
| * Column provides methods to get table cells that belong to this table column. |
| */ |
| public class Column extends Component { |
| |
| TableTableColumnElement maColumnElement; |
| int mnRepeatedIndex; |
| private static final String DEFAULT_WIDTH = "0in"; |
| private final int DEFAULT_REL_TABLE_WIDTH = 65535; |
| private Document mDocument; |
| |
| /** |
| * Construct the <code>Column</code> feature. |
| * |
| * @param odfElement |
| * the element that can construct this table column |
| * @param repeatedIndex |
| * the index in the repeated columns |
| */ |
| Column(TableTableColumnElement colElement, int repeatedIndex) { |
| maColumnElement = colElement; |
| mnRepeatedIndex = repeatedIndex; |
| mDocument = (Document) ((OdfFileDom) maColumnElement.getOwnerDocument()).getDocument(); |
| } |
| |
| /** |
| * Get the <code>Column</code> instance from the |
| * <code>TableTableColumnElement</code> instance. |
| * <p> |
| * Each <code>TableTableColumnElement</code> instance has a one-to-one |
| * relationship to the a <code>Column</code> instance. |
| * |
| * @param colElement |
| * the column element that need to get the corresponding |
| * <code>Column</code> instance |
| * @return the <code>Column</code> instance represent the specified column |
| * element |
| */ |
| public static Column getInstance(TableTableColumnElement colElement) { |
| TableTableElement tableElement = null; |
| Node node = colElement.getParentNode(); |
| while (node != null) { |
| if (node instanceof TableTableElement) { |
| tableElement = (TableTableElement) node; |
| } |
| node = node.getParentNode(); |
| } |
| Table table = null; |
| if (tableElement != null) { |
| table = Table.getInstance(tableElement); |
| } else { |
| throw new IllegalArgumentException("the colElement is not in the table dom tree"); |
| } |
| |
| Column column = table.getColumnInstance(colElement, 0); |
| if (column.getColumnsRepeatedNumber() > 1) { |
| Logger.getLogger(Column.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 Table getTable() { |
| TableTableElement tableElement = getTableElement(); |
| if (tableElement != null) { |
| return Table.getInstance(tableElement); |
| } |
| return null; |
| } |
| |
| /** |
| * Get the width of the column (in Millimeter). |
| * |
| * @return the width of the current column (in Millimeter). |
| */ |
| public double getWidth() { |
| String sWidth = maColumnElement.getProperty(OdfTableColumnProperties.ColumnWidth); |
| if (sWidth == null) { |
| sWidth = DEFAULT_WIDTH; |
| } |
| return PositiveLength.parseDouble(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(double width) { |
| double roundingFactor = 10000.0; |
| //TODO:need refactor to PositiveLength. |
| double inValue = Math.round(roundingFactor * width / Unit.INCH.unitInMillimiter()) / roundingFactor; |
| String sWidthIN = String.valueOf(inValue) + Unit.INCH.abbr(); |
| //width before modification |
| double columnWidth = getWidth(); |
| if(columnWidth < 0) { |
| columnWidth = 0; |
| } |
| splitRepeatedColumns(); |
| maColumnElement.setProperty(OdfTableColumnProperties.ColumnWidth, sWidthIN); |
| |
| Table table = getTable(); |
| // check if need set relative width |
| if (!(table.getOwnerDocument() instanceof SpreadsheetDocument)) { |
| int index = getColumnIndex(); |
| int columnCount = table.getColumnCount(); |
| if (index == columnCount-1) { |
| //if the column to resize is the rightmost |
| index = index - 1; |
| } else { |
| index = index + 1; |
| } |
| if (index >= 0) { |
| Column column = null; |
| if (index < columnCount) { |
| column = table.getColumnByIndex(index); |
| } else if (columnCount >= 2) { |
| column = table.getColumnByIndex(columnCount - 2); |
| } |
| |
| double nextColumnWidth = 0; |
| if (column != null) { |
| nextColumnWidth = column.getWidth(); |
| setRelativeWidth((long) (DEFAULT_REL_TABLE_WIDTH / table.getWidth() * width)); |
| } |
| |
| // total width of two columns |
| double columnsWidth = nextColumnWidth + columnWidth; |
| // calculates the new width of the next / previous column |
| double newWidthNextColumn = columnsWidth - width; |
| if (newWidthNextColumn < 0) { |
| newWidthNextColumn = 0; |
| } |
| inValue = Math.round(roundingFactor * newWidthNextColumn / Unit.INCH.unitInMillimiter()) |
| / roundingFactor; |
| sWidthIN = String.valueOf(inValue) + Unit.INCH.abbr(); |
| column.getOdfElement().setProperty(OdfTableColumnProperties.ColumnWidth, sWidthIN); |
| double relWidth = (DEFAULT_REL_TABLE_WIDTH / table.getWidth()) * newWidthNextColumn; |
| column.setRelativeWidth((long)relWidth); |
| } |
| } |
| } |
| |
| // 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() { |
| Table table = getTable(); |
| TableTableElement tableEle = table.getOdfElement(); |
| int repeateNum = getColumnsRepeatedNumber(); |
| if (repeateNum > 1) { |
| // change this repeated column to several single columns |
| TableTableColumnElement ownerColumnElement = null; |
| String columnWidthStr = null; |
| long columnWidth = 0; |
| 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); |
| if (originalWidth != null) { |
| columnWidthStr = Length.mapToUnit(originalWidth, Unit.MILLIMETER); |
| columnWidth = PositiveLength.parseLong(columnWidthStr, Unit.MILLIMETER); |
| } |
| 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) { |
| long relWidth = (long) ((DEFAULT_REL_TABLE_WIDTH / table.getWidth()) * columnWidth); |
| newColumn.setProperty(OdfTableColumnProperties.RelColumnWidth, String.valueOf(relWidth) + "*"); |
| } |
| 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); |
| // update column element. |
| maColumnElement = ownerColumnElement; |
| } |
| } |
| } |
| |
| // 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) { |
| if (relWidth < 40) { |
| maColumnElement.setProperty(OdfTableColumnProperties.RelColumnWidth, String.valueOf(40) + "*"); |
| } else { |
| 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 Cell 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 Column getPreviousColumn() { |
| Table 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(Column.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 Column getNextColumn() { |
| Table table = getTable(); |
| // the column has repeated column number > 1 |
| int columnsRepeatedNumber = getColumnsRepeatedNumber(); |
| if (columnsRepeatedNumber > 1) { |
| if (mnRepeatedIndex < (columnsRepeatedNumber - 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(Column.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; |
| Table 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.getAutomaticStyles().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(); |
| } |
| } |
| } |