OdfTableCellRange.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.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.odftoolkit.odfdom.doc.OdfDocument;
import org.odftoolkit.odfdom.doc.OdfSpreadsheetDocument;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.attribute.office.OfficeValueTypeAttribute;
import org.odftoolkit.odfdom.dom.element.table.TableCoveredTableCellElement;
import org.odftoolkit.odfdom.dom.element.table.TableNamedExpressionsElement;
import org.odftoolkit.odfdom.dom.element.table.TableNamedRangeElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableCellElement;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfName;
import org.odftoolkit.odfdom.pkg.OdfXMLFactory;
/**
* OdfTableCellRange represent a rang of cells that are adjacent with each other
*
* <p>OdfTableCellRange provides methods to get/set/modify the properties of cell range.
*/
public class OdfTableCellRange {
private int mnStartRow;
private int mnStartColumn;
private int mnEndRow;
private int mnEndColumn;
private String msCellRangeName;
private OdfTable maOwnerTable;
private boolean mbSpreadsheet;
/**
* Construct the instance of OdfTableCellRange.
*
* @param table is the container table of this cell range.
* @param startColumn is the column index of the first cell in this cell range.
* @param startRow is the row index of the first cell in this cell range.
* @param endColumn is the column index of the last cell in this cell range.
* @param endRow is the row index of the last cell in this cell range.
*/
OdfTableCellRange(OdfTable table, int startColumn, int startRow, int endColumn, int endRow) {
maOwnerTable = table;
OdfDocument doc =
(OdfDocument) ((OdfFileDom) maOwnerTable.getOdfElement().getOwnerDocument()).getDocument();
if (doc instanceof OdfSpreadsheetDocument) {
mbSpreadsheet = true;
}
// the first cell is the covered cell, then the cell range should be enlarged
// so that it can contains the complete cell
// get the cell cover info
mnStartColumn = startColumn;
mnStartRow = startRow;
mnEndColumn = endColumn;
mnEndRow = endRow;
List<CellCoverInfo> coverList =
maOwnerTable.getCellCoverInfos(
0, 0, maOwnerTable.getColumnCount() - 1, maOwnerTable.getRowCount() - 1);
OdfTableCell cell; // = maOwnerTable.getOwnerCellByPosition(coverList, nStartColumn, nStartRow);
for (int i = startColumn; i <= endColumn; i++) {
cell = maOwnerTable.getOwnerCellByPosition(coverList, i, startRow);
int rowIndex = cell.getRowIndex();
int colIndex = cell.getColumnIndex();
mnStartColumn = Math.min(mnStartColumn, colIndex);
mnStartRow = Math.min(mnStartRow, rowIndex);
mnEndColumn = Math.max(mnEndColumn, colIndex + cell.getColumnSpannedNumber() - 1);
mnEndRow = Math.max(mnEndRow, rowIndex + cell.getRowSpannedNumber() - 1);
}
for (int i = startColumn; i <= endColumn; i++) {
cell = maOwnerTable.getOwnerCellByPosition(coverList, i, endRow);
int rowIndex = cell.getRowIndex();
int colIndex = cell.getColumnIndex();
mnStartColumn = Math.min(mnStartColumn, colIndex);
mnStartRow = Math.min(mnStartRow, rowIndex);
mnEndColumn = Math.max(mnEndColumn, colIndex + cell.getColumnSpannedNumber() - 1);
mnEndRow = Math.max(mnEndRow, rowIndex + cell.getRowSpannedNumber() - 1);
}
for (int i = startRow + 1; i < endRow; i++) {
cell = maOwnerTable.getOwnerCellByPosition(coverList, startColumn, i);
int rowIndex = cell.getRowIndex();
int colIndex = cell.getColumnIndex();
mnStartColumn = Math.min(mnStartColumn, colIndex);
mnStartRow = Math.min(mnStartRow, rowIndex);
mnEndColumn = Math.max(mnEndColumn, colIndex + cell.getColumnSpannedNumber() - 1);
mnEndRow = Math.max(mnEndRow, rowIndex + cell.getRowSpannedNumber() - 1);
}
for (int i = startRow + 1; i < endRow; i++) {
cell = maOwnerTable.getOwnerCellByPosition(coverList, endColumn, i);
int rowIndex = cell.getRowIndex();
int colIndex = cell.getColumnIndex();
mnStartColumn = Math.min(mnStartColumn, colIndex);
mnStartRow = Math.min(mnStartRow, rowIndex);
mnEndColumn = Math.max(mnEndColumn, colIndex + cell.getColumnSpannedNumber() - 1);
mnEndRow = Math.max(mnEndRow, rowIndex + cell.getRowSpannedNumber() - 1);
}
}
/** construct the empty cellRange */
OdfTableCellRange() {}
/** Merge the current cell range to one cell */
public void merge() {
OdfTableCell firstCell = maOwnerTable.getCellByPosition(mnStartColumn, mnStartRow);
// note: after merge, the cell row/column count might be changed
int rowCount = maOwnerTable.getRowCount();
int colCount = maOwnerTable.getColumnCount();
// if the cell range is the whole table, then merge it to a big cell
// as to the spreadsheet document, it should still keep the original cell count,
// rather than merge to a big cell.
if (rowCount == (mnEndRow - mnStartRow + 1)
&& colCount == (mnEndColumn - mnStartColumn + 1)
&& !mbSpreadsheet) {
if (firstCell.getOdfElement() instanceof TableTableCellElement) {
TableTableCellElement firstCellElement =
(TableTableCellElement) (firstCell.getOdfElement());
firstCellElement.removeAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-columns-spanned");
firstCellElement.removeAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-rows-spanned");
firstCellElement.setOfficeValueTypeAttribute(
OfficeValueTypeAttribute.Value.STRING.toString());
}
// just copy the text of the other cells to this first cell
for (int i = mnStartRow; i < mnEndRow + 1; i++) {
for (int j = mnStartColumn; j < mnEndColumn + 1; j++) {
OdfTableCell cellBase = maOwnerTable.getCellByPosition(j, i);
if (j != mnStartColumn || i != mnStartRow) {
// copy the content of this cell to the first cell
firstCell.appendContentFrom(cellBase);
}
}
}
maOwnerTable.removeRowsByIndex(1, maOwnerTable.getRowCount() - 1);
maOwnerTable.removeColumnsByIndex(1, maOwnerTable.getColumnCount() - 1);
OdfTableColumn firstColumn = maOwnerTable.getColumnByIndex(0);
firstColumn.setWidth(maOwnerTable.getWidth());
mnEndRow = mnStartRow;
mnEndColumn = mnStartColumn;
return;
} // if the cell range covered all the table row, and the merged column > 1
// the merged column can be removed
else if (rowCount == (mnEndRow - mnStartRow + 1)
&& colCount > (mnEndColumn - mnStartColumn + 1)
&& (mnEndColumn - mnStartColumn) > 0) {
// the first cell, set the span attribute
if (firstCell.getOdfElement() instanceof TableTableCellElement) {
TableTableCellElement firstCellElement =
(TableTableCellElement) (firstCell.getOdfElement());
firstCellElement.removeAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-columns-spanned");
firstCellElement.setTableNumberRowsSpannedAttribute(
Integer.valueOf(mnEndRow - mnStartRow + 1));
firstCellElement.setOfficeValueTypeAttribute(
OfficeValueTypeAttribute.Value.STRING.toString());
}
// the other cell, copy the content to first cell
// if it is also in the first column of the cell range, set to the covered cell
// other cell not in the first column will be removed when remove the column
for (int i = mnStartRow; i < mnEndRow + 1; i++) {
for (int j = mnStartColumn; j < mnEndColumn + 1; j++) {
OdfTableCell cellBase = maOwnerTable.getCellByPosition(j, i);
if (j != mnStartColumn || i != mnStartRow) {
// append content to first cell
firstCell.appendContentFrom(cellBase);
// change the cell in the first column of cell range to covered cell
if ((j == mnStartColumn)
&& (cellBase.getOdfElement() instanceof TableTableCellElement)) {
// change the normal cell to be the covered cell
TableTableCellElement firstColumnCell =
(TableTableCellElement) cellBase.getOdfElement();
TableCoveredTableCellElement coveredCell =
(TableCoveredTableCellElement)
OdfXMLFactory.newOdfElement(
(OdfFileDom) firstColumnCell.getOwnerDocument(),
OdfName.newName(OdfDocumentNamespace.TABLE, "covered-table-cell"));
OdfTableRow parentRow = cellBase.getTableRow();
parentRow.getOdfElement().insertBefore(coveredCell, firstColumnCell);
parentRow.getOdfElement().removeChild(firstColumnCell);
}
}
}
}
List<Long> widthList = getCellRangeWidthList();
long nCellRangeWidth =
widthList.get(widthList.size() - 1).longValue() - widthList.get(0).longValue();
maOwnerTable.removeColumnsByIndex(mnStartColumn + 1, mnEndColumn - mnStartColumn);
OdfTableColumn firstColumn = maOwnerTable.getColumnByIndex(mnStartColumn);
firstColumn.setWidth(nCellRangeWidth);
mnEndColumn = mnStartColumn;
return;
} // if the cell range covered all the table column, the merged row can be removed
else if (rowCount > (mnEndRow - mnStartRow + 1)
&& colCount == (mnEndColumn - mnStartColumn + 1)
&& (mnEndRow - mnStartRow) > 0) {
// the first cell, set the span attribute
if (firstCell.getOdfElement() instanceof TableTableCellElement) {
TableTableCellElement firstCellElement =
(TableTableCellElement) (firstCell.getOdfElement());
firstCellElement.removeAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-rows-spanned");
firstCellElement.setTableNumberColumnsSpannedAttribute(
Integer.valueOf(mnEndColumn - mnStartColumn + 1));
firstCellElement.setOfficeValueTypeAttribute(
OfficeValueTypeAttribute.Value.STRING.toString());
}
// the other cell, copy the content to first cell
// if it is also in the first row of the cell range, set to the covered cell
// other cell not in the first row will be removed when remove the row
for (int i = mnStartRow; i < mnEndRow + 1; i++) {
for (int j = mnStartColumn; j < mnEndColumn + 1; j++) {
OdfTableCell cellBase = maOwnerTable.getCellByPosition(j, i);
if (j != mnStartColumn || i != mnStartRow) {
// append content to first cell
firstCell.appendContentFrom(cellBase);
// change the cell in the first row of cell range to covered cell
if ((i == mnStartRow) && (cellBase.getOdfElement() instanceof TableTableCellElement)) {
// change the normal cell to be the covered cell
TableTableCellElement firstRowCell = (TableTableCellElement) cellBase.getOdfElement();
TableCoveredTableCellElement coveredCell =
(TableCoveredTableCellElement)
OdfXMLFactory.newOdfElement(
(OdfFileDom) firstRowCell.getOwnerDocument(),
OdfName.newName(OdfDocumentNamespace.TABLE, "covered-table-cell"));
OdfTableRow parentRow = cellBase.getTableRow();
parentRow.getOdfElement().insertBefore(coveredCell, firstRowCell);
parentRow.getOdfElement().removeChild(firstRowCell);
}
}
}
}
maOwnerTable.removeRowsByIndex(mnStartRow + 1, mnEndRow - mnStartRow);
mnEndRow = mnStartRow;
return;
} // don't remove any row/column
else {
// first keep the column and row count in this cell range
// the first cell, set the span attribute
if (firstCell.getOdfElement() instanceof TableTableCellElement) {
TableTableCellElement firstCellElement =
(TableTableCellElement) (firstCell.getOdfElement());
firstCellElement.setTableNumberColumnsSpannedAttribute(
Integer.valueOf(mnEndColumn - mnStartColumn + 1));
firstCellElement.setTableNumberRowsSpannedAttribute(
Integer.valueOf(mnEndRow - mnStartRow + 1));
firstCellElement.setOfficeValueTypeAttribute(
OfficeValueTypeAttribute.Value.STRING.toString());
}
// the other cell, set to the covered cell
for (int i = mnStartRow; i < mnEndRow + 1; i++) {
for (int j = mnStartColumn; j < mnEndColumn + 1; j++) {
OdfTableCell cellBase = maOwnerTable.getCellByPosition(j, i);
if (j != mnStartColumn || i != mnStartRow) {
if (cellBase.getOdfElement() instanceof TableTableCellElement) {
// change the normal cell to be the covered cell
TableTableCellElement cell = (TableTableCellElement) cellBase.getOdfElement();
TableCoveredTableCellElement coveredCell =
(TableCoveredTableCellElement)
OdfXMLFactory.newOdfElement(
(OdfFileDom) cell.getOwnerDocument(),
OdfName.newName(OdfDocumentNamespace.TABLE, "covered-table-cell"));
OdfTableRow parentRow = cellBase.getTableRow();
parentRow.getOdfElement().insertBefore(coveredCell, cell);
// copy the content of this cell to the first cell
firstCell.appendContentFrom(cellBase);
cellBase.removeContent();
// set the table column repeated attribute
int repeatedNum = cell.getTableNumberColumnsRepeatedAttribute().intValue();
int num = (mnEndColumn - j + 1) - repeatedNum;
if (num >= 0) {
coveredCell.setTableNumberColumnsRepeatedAttribute(Integer.valueOf(repeatedNum));
parentRow.getOdfElement().removeChild(cell);
} else {
coveredCell.setTableNumberColumnsRepeatedAttribute(
new Integer(mnEndColumn - j + 1));
cell.setTableNumberColumnsRepeatedAttribute(Integer.valueOf(-num));
}
} else if (cellBase.getOdfElement() instanceof TableCoveredTableCellElement) {
try {
// copy the content of this cell to the first cell
firstCell.appendContentFrom(cellBase);
cellBase.removeContent();
} catch (Exception e) {
Logger.getLogger(OdfTableCellRange.class.getName())
.log(Level.SEVERE, e.getMessage(), e);
}
}
}
}
}
}
}
// vector store the x coordinate of each column which reference to the left start point of owner
// table
// the returned value is all measured with "mm" unit
private List<Long> getCellRangeWidthList() {
List<Long> list = new ArrayList<Long>();
Long length = Long.valueOf(0);
for (int i = 0; i < maOwnerTable.getColumnCount() - 1; i++) {
OdfTableColumn col = maOwnerTable.getColumnByIndex(i);
int repeateNum = col.getColumnsRepeatedNumber();
if (repeateNum == 1) {
if (isColumnInCellRange(i)) {
list.add(length);
}
length = Long.valueOf(length.longValue() + col.getWidth());
} else {
for (int j = 0; j < repeateNum; j++) {
if (isColumnInCellRange(i + j)) {
list.add(length);
length = Long.valueOf(length.longValue() + col.getWidth());
}
}
i += repeateNum - 1;
}
}
// x coordinate of last column right point
list.add(length);
return list;
}
// vector store the x coordinate of each will split column start point
List<Long> getVeticalSplitCellRangeWidthList(int splitNum) {
// get each cell in the cell range(the cell here means the real cell, not the covered cell)
List<CellCoverInfo> coverList =
maOwnerTable.getCellCoverInfos(mnStartColumn, mnStartRow, mnEndColumn, mnEndRow);
// then get the real(uncovered) cell x coordinate
List<Long> tmpList = new ArrayList<Long>();
List<Long> widthList = getCellRangeWidthList();
for (int i = mnStartColumn; i < mnEndColumn + 1; i++) {
for (int j = mnStartRow; j < mnEndRow + 1; j++) {
if (maOwnerTable.isCoveredCellInOwnerTable(coverList, i, j)) {
continue;
} else {
// the real cell, record the x coordinate of the left point
Long width = widthList.get(i - mnStartColumn);
if (!tmpList.contains(width)) {
tmpList.add(width);
}
}
}
}
// last, reorder the tmpVector and split it to splitNum between each item
Long[] widthArray = (Long[]) tmpList.toArray();
Arrays.sort(widthArray);
List<Long> rtnValues = new ArrayList<Long>();
Long colWidth;
long unitWidth;
rtnValues.add(widthArray[0]);
for (int i = 1; i < widthArray.length; i++) {
colWidth = Long.valueOf(widthArray[i].longValue() - widthArray[i - 1].longValue());
unitWidth = colWidth.longValue() / splitNum;
for (int j = 1; j < splitNum; j++) {
long eachWidth = unitWidth * j + widthArray[i - 1].longValue();
rtnValues.add(Long.valueOf(eachWidth));
}
rtnValues.add(widthArray[i]);
}
return rtnValues;
}
/**
* Get the name of the named cell range.
*
* @return the name of the cell range
*/
public String getCellRangeName() {
return msCellRangeName;
}
/**
* Set the name of the current cell range.
*
* @param cellRangeName the name that need to set
*/
public void setCellRangeName(String cellRangeName) {
try {
OdfElement contentRoot = maOwnerTable.mDocument.getContentRoot();
// create name range element
OdfFileDom contentDom = ((OdfFileDom) maOwnerTable.getOdfElement().getOwnerDocument());
TableNamedExpressionsElement nameExpress =
(TableNamedExpressionsElement)
OdfXMLFactory.newOdfElement(
contentDom, OdfName.newName(OdfDocumentNamespace.TABLE, "named-expressions"));
String startCellRange =
"$"
+ maOwnerTable.getTableName()
+ "."
+ maOwnerTable.getAbsoluteCellAddress(mnStartColumn, mnStartRow);
String endCellRange =
"$"
+ maOwnerTable.getTableName()
+ "."
+ maOwnerTable.getAbsoluteCellAddress(mnEndColumn, mnEndRow);
TableNamedRangeElement nameRange =
(TableNamedRangeElement)
nameExpress.newTableNamedRangeElement(
startCellRange + ":" + endCellRange, cellRangeName);
nameRange.setTableBaseCellAddressAttribute(endCellRange);
contentRoot.appendChild(nameExpress);
msCellRangeName = cellRangeName;
} catch (Exception ex) {
Logger.getLogger(OdfTableCellRange.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* Get the <code>OdfTable</code> instance who contains this cell range.
*
* @return the table that contains the cell range.
*/
public OdfTable getTable() {
return maOwnerTable;
}
/**
* Get the number of rows in this cell range.
*
* @return rows number in the cell range
*/
public int getRowNumber() {
return (mnEndRow - mnStartRow + 1);
}
/**
* Get the number of columns in this cell range.
*
* @return columns number in the cell range
*/
public int getColumnNumber() {
return (mnEndColumn - mnStartColumn + 1);
}
/**
* Returns a single cell that is positioned at specified column and row.
*
* @param clmIndex the column index of the cell inside the range.
* @param rowIndex the row index of the cell inside the range.
* @return the cell at the specified position relative to the start position of the cell range
* @throws IndexOutOfBoundsException if the column/row index is bigger than the column/row count
*/
public OdfTableCell getCellByPosition(int clmIndex, int rowIndex)
throws IndexOutOfBoundsException {
return maOwnerTable.getCellByPosition(mnStartColumn + clmIndex, mnStartRow + rowIndex);
}
/**
* Check if the given column in is this cell range.
*
* @param colIndex the given column index
* @return true if the given column index is in the current cell range
*/
private boolean isColumnInCellRange(int colIndex) {
if (colIndex < mnStartColumn || colIndex > mnEndColumn) {
return false;
} else {
return true;
}
}
/**
* Returns a single cell that is positioned at specified cell address.
*
* @param address the cell address of the cell inside the range.
* @return the cell at the specified cell address relative to the start position of the cell range
*/
public OdfTableCell getCellByPosition(String address) {
// if the address also contain the table name, but the table is not the maOwnerTable
// what should do? get the table then getcellByPosition?
return getCellByPosition(
maOwnerTable.getColIndexFromCellAddress(address),
maOwnerTable.getRowIndexFromCellAddress(address));
}
}