[bug-62055] Fix XSSFImportFromXML table resize. Thanks to Leonard Kappe. This closes #99

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1825315 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
PJ Fanning 2018-02-25 17:06:19 +00:00
parent 02719ac4ba
commit 4ad4d76241
11 changed files with 1053 additions and 302 deletions

View File

@ -75,9 +75,9 @@ public class CreateTable {
}
}
// Create the columns
table.addColumn();
table.addColumn();
table.addColumn();
table.createColumn("Column 1");
table.createColumn("Column 2");
table.createColumn("Column 3");
// Set which area the table should be placed in
AreaReference reference = wb.getCreationHelper().createAreaReference(

View File

@ -50,9 +50,9 @@ import org.apache.poi.xssf.usermodel.XSSFMap;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFTable;
import org.apache.poi.xssf.usermodel.XSSFTableColumn;
import org.apache.poi.xssf.usermodel.helpers.XSSFSingleXmlCell;
import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
@ -175,14 +175,11 @@ public class XSSFExportToXml implements Comparator<String>{
// Exports elements and attributes mapped with tables
if (table!=null) {
List<CTTableColumn> tableColumns = table.getCTTable().getTableColumns().getTableColumnList();
List<XSSFTableColumn> tableColumns = table.getColumns();
XSSFSheet sheet = table.getXSSFSheet();
int startRow = table.getStartCellReference().getRow();
// In mappings created with Microsoft Excel the first row contains the table header and must be skipped
startRow +=1;
int startRow = table.getStartCellReference().getRow() + table.getHeaderRowCount();
int endRow = table.getEndCellReference().getRow();
for(int i = startRow; i<= endRow; i++) {
@ -191,16 +188,12 @@ public class XSSFExportToXml implements Comparator<String>{
Node tableRootNode = getNodeByXPath(table.getCommonXpath(), doc.getFirstChild(), doc, true);
short startColumnIndex = table.getStartCellReference().getCol();
for (int j = startColumnIndex; j <= table.getEndCellReference().getCol(); j++) {
XSSFCell cell = row.getCell(j);
for (XSSFTableColumn tableColumn : tableColumns) {
XSSFCell cell = row.getCell(startColumnIndex + tableColumn.getColumnIndex());
if (cell != null) {
int tableColumnIndex = j - startColumnIndex;
if (tableColumnIndex < tableColumns.size()) {
CTTableColumn ctTableColumn = tableColumns.get(tableColumnIndex);
if (ctTableColumn.getXmlColumnPr() != null) {
XSSFXmlColumnPr pointer = new XSSFXmlColumnPr(table, ctTableColumn,
ctTableColumn.getXmlColumnPr());
String localXPath = pointer.getLocalXPath();
XSSFXmlColumnPr xmlColumnPr = tableColumn.getXmlColumnPr();
if (xmlColumnPr != null) {
String localXPath = xmlColumnPr.getLocalXPath();
Node currentNode = getNodeByXPath(localXPath,tableRootNode,doc,false);
mapCellOnNode(cell, currentNode);
}
@ -208,7 +201,6 @@ public class XSSFExportToXml implements Comparator<String>{
}
}
}
}
} /*else {
// TODO: implement filtering management in xpath
}*/

View File

@ -47,6 +47,7 @@ import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFMap;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFTable;
import org.apache.poi.xssf.usermodel.XSSFTableColumn;
import org.apache.poi.xssf.usermodel.helpers.XSSFSingleXmlCell;
import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STXmlDataType;
@ -124,8 +125,10 @@ public class XSSFImportFromXML {
String commonXPath = table.getCommonXpath();
NodeList result = (NodeList) xpath.evaluate(commonXPath, doc, XPathConstants.NODESET);
int rowOffset = table.getStartCellReference().getRow() + 1;// the first row contains the table header
int columnOffset = table.getStartCellReference().getCol() - 1;
int rowOffset = table.getStartCellReference().getRow() + table.getHeaderRowCount();
int columnOffset = table.getStartCellReference().getCol();
table.setDataRowCount(result.getLength());
for (int i = 0; i < result.getLength(); i++) {
@ -133,13 +136,18 @@ public class XSSFImportFromXML {
// OpenOffice part 4: chapter 3.5.1.7)
Node singleNode = result.item(i).cloneNode(true);
for (XSSFXmlColumnPr xmlColumnPr : table.getXmlColumnPrs()) {
int localColumnId = (int) xmlColumnPr.getId();
for (XSSFTableColumn tableColum : table.getColumns()) {
XSSFXmlColumnPr xmlColumnPr = tableColum.getXmlColumnPr();
if(xmlColumnPr == null) {
continue;
}
int rowId = rowOffset + i;
int columnId = columnOffset + localColumnId;
int columnId = columnOffset + tableColum.getColumnIndex();
String localXPath = xmlColumnPr.getLocalXPath();
localXPath = localXPath.substring(localXPath.substring(1).indexOf('/') + 2);
localXPath = localXPath.substring(localXPath.indexOf('/', 1) + 1);
// TODO: convert the data to the cell format
String value = (String) xpath.evaluate(localXPath, singleNode, XPathConstants.STRING);
@ -161,6 +169,8 @@ public class XSSFImportFromXML {
}
}
private static enum DataType {
BOOLEAN(STXmlDataType.BOOLEAN), //
DOUBLE(STXmlDataType.DOUBLE), //

View File

@ -80,6 +80,7 @@ import org.apache.poi.util.Beta;
import org.apache.poi.util.Internal;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.Removal;
import org.apache.poi.util.Units;
import org.apache.poi.xssf.model.CommentsTable;
import org.apache.poi.xssf.usermodel.XSSFPivotTable.PivotTableReferenceConfigurator;
@ -4057,9 +4058,27 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
}
/**
* Creates a new Table, and associates it with this Sheet
* Creates a new Table, and associates it with this Sheet. The table does
* not yet have an area defined and needs to be initialized by calling
* {@link XSSFTable#setArea(AreaReference)}.
*
* @deprecated Use {@link #createTable(AreaReference))} instead
*/
@Deprecated
@Removal(version = "4.2.0")
public XSSFTable createTable() {
return createTable(null);
}
/**
* Creates a new Table, and associates it with this Sheet.
*
* @param tableArea
* the area that the table should cover, should not be {@null}
* @return the created table
* @since 4.0.0
*/
public XSSFTable createTable(AreaReference tableArea) {
if (!worksheet.isSetTableParts()) {
worksheet.addNewTableParts();
}
@ -4093,6 +4112,10 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
tables.put(tbl.getId(), table);
if(tableArea != null) {
table.setArea(tableArea);
}
return table;
}

View File

@ -24,6 +24,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -32,12 +33,14 @@ import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.usermodel.TableStyleInfo;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.Internal;
import org.apache.poi.util.Removal;
import org.apache.poi.util.StringUtil;
import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr;
import org.apache.xmlbeans.XmlException;
@ -48,12 +51,12 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.TableDocument;
/**
*
* This class implements the Table Part (Open Office XML Part 4:
* chapter 3.5.1)
* This class implements the Table Part (Open Office XML Part 4: chapter 3.5.1)
*
* This implementation works under the assumption that a table contains mappings to a subtree of an XML.
* The root element of this subtree an occur multiple times (one for each row of the table). The child nodes
* of the root element can be only attributes or element with maxOccurs=1 property set
* Columns of this table may contains mappings to a subtree of an XML. The root
* element of this subtree can occur multiple times (one for each row of the
* table). The child nodes of the root element can be only attributes or
* elements with maxOccurs=1 property set.
*
*
* @author Roberto Manicardi
@ -61,8 +64,8 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.TableDocument;
public class XSSFTable extends POIXMLDocumentPart implements Table {
private CTTable ctTable;
private transient List<XSSFXmlColumnPr> xmlColumnPr;
private transient CTTableColumn[] ctColumns;
private transient List<XSSFXmlColumnPr> xmlColumnPrs;
private transient List<XSSFTableColumn> tableColumns;
private transient HashMap<String, Integer> columnMap;
private transient CellReference startCellReference;
private transient CellReference endCellReference;
@ -156,18 +159,6 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
return false;
}
/**
* caches table columns for performance.
* Updated via updateHeaders
* @since 3.15 beta 2
*/
private CTTableColumn[] getTableColumns() {
if (ctColumns == null) {
ctColumns = ctTable.getTableColumns().getTableColumnArray();
}
return ctColumns;
}
/**
*
* Calculates the xpath of the root element for the table. This will be the common part
@ -179,9 +170,9 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
public String getCommonXpath() {
if (commonXPath == null) {
String[] commonTokens = {};
for (CTTableColumn column : getTableColumns()) {
for (XSSFTableColumn column : getColumns()) {
if (column.getXmlColumnPr()!=null) {
String xpath = column.getXmlColumnPr().getXpath();
String xpath = column.getXmlColumnPr().getXPath();
String[] tokens = xpath.split("/");
if (commonTokens.length==0) {
commonTokens = tokens;
@ -210,45 +201,166 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
return commonXPath;
}
/**
* Note this list is static - once read, it does not notice later changes to the underlying column structures
* To clear the cache, call {@link #updateHeaders}
* @return List of XSSFTableColumn
* @since 4.0.0
*/
public List<XSSFTableColumn> getColumns() {
if (tableColumns == null) {
List<XSSFTableColumn> columns = new ArrayList<>();
CTTableColumns ctTableColumns = ctTable.getTableColumns();
if (ctTableColumns != null) {
for (CTTableColumn column : ctTableColumns.getTableColumnList()) {
XSSFTableColumn tableColumn = new XSSFTableColumn(this, column);
columns.add(tableColumn);
}
}
tableColumns = Collections.unmodifiableList(columns);
}
return tableColumns;
}
/**
* Note this list is static - once read, it does not notice later changes to the underlying column structures
* To clear the cache, call {@link #updateHeaders}
*
* @deprecated Use {@link XSSFTableColumn#getXmlColumnPr()} instead.
*
* @return List of XSSFXmlColumnPr
*/
@Deprecated
@Removal(version="4.2.0")
public List<XSSFXmlColumnPr> getXmlColumnPrs() {
if (xmlColumnPr==null) {
xmlColumnPr = new ArrayList<>();
for (CTTableColumn column: getTableColumns()) {
if (column.getXmlColumnPr()!=null) {
XSSFXmlColumnPr columnPr = new XSSFXmlColumnPr(this,column,column.getXmlColumnPr());
xmlColumnPr.add(columnPr);
if (xmlColumnPrs == null) {
xmlColumnPrs = new ArrayList<>();
for (XSSFTableColumn column: getColumns()) {
XSSFXmlColumnPr xmlColumnPr = column.getXmlColumnPr();
if (xmlColumnPr != null) {
xmlColumnPrs.add(xmlColumnPr);
}
}
}
return xmlColumnPr;
return xmlColumnPrs;
}
/**
* Adds another column to the table.
* Add a new column to the right end of the table.
*
* Warning - Return type likely to change!
* @param columnName
* the unique name of the column, must not be {@code null}
* @return the created table column
* @since 4.0.0
*/
@Internal("Return type likely to change")
public void addColumn() {
public XSSFTableColumn createColumn(String columnName) {
return createColumn(columnName, getColumnCount());
}
/**
* Adds a new column to the table.
*
* @param columnName
* the unique name of the column, or {@code null} for a generated name
* @param columnIndex
* the 0-based position of the column in the table
* @return the created table column
* @throws IllegalArgumentException
* if the column name is not unique or missing or if the column
* can't be created at the given index
* @since 4.0.0
*/
public XSSFTableColumn createColumn(String columnName, int columnIndex) {
int columnCount = getColumnCount();
if(columnIndex < 0 || columnIndex > columnCount) {
throw new IllegalArgumentException("Column index out of bounds");
}
// Ensure we have Table Columns
CTTableColumns columns = ctTable.getTableColumns();
if (columns == null) {
columns = ctTable.addNewTableColumns();
}
// Add another Column, and give it a sensible ID
CTTableColumn column = columns.addNewTableColumn();
int num = columns.sizeOfTableColumnArray();
columns.setCount(num);
column.setId(num);
// check if name is unique and calculate unique column id
long nextColumnId = 1;
for (XSSFTableColumn tableColumn : getColumns()) {
if (columnName != null && columnName.equalsIgnoreCase(tableColumn.getName())) {
throw new IllegalArgumentException("Column '" + columnName
+ "' already exists. Column names must be unique per table.");
}
nextColumnId = Math.max(nextColumnId, tableColumn.getId());
}
// Have the Headers updated if possible
// Add the new Column
CTTableColumn column = columns.insertNewTableColumn(columnIndex);
columns.setCount(columns.sizeOfTableColumnArray());
column.setId(nextColumnId);
if(columnName != null) {
column.setName(columnName);
} else {
column.setName("Column " + nextColumnId);
}
if (ctTable.getRef() != null) {
// calculate new area
int newColumnCount = columnCount + 1;
CellReference tableStart = getStartCellReference();
CellReference tableEnd = getEndCellReference();
SpreadsheetVersion version = getXSSFSheet().getWorkbook().getSpreadsheetVersion();
CellReference newTableEnd = new CellReference(tableEnd.getRow(),
tableStart.getCol() + newColumnCount - 1);
AreaReference newTableArea = new AreaReference(tableStart, newTableEnd, version);
setCellRef(newTableArea);
}
updateHeaders();
return getColumns().get(columnIndex);
}
/**
* Remove a column from the table.
*
* @param column
* the column to remove
* @since 4.0.0
*/
public void removeColumn(XSSFTableColumn column) {
int columnIndex = getColumns().indexOf(column);
if (columnIndex >= 0) {
ctTable.getTableColumns().removeTableColumn(columnIndex);
updateReferences();
updateHeaders();
}
}
/**
* Remove a column from the table.
*
* @param columnIndex
* the 0-based position of the column in the table
* @throws IllegalArgumentException
* if no column at the index exists or if the table has only a
* single column
* @since 4.0.0
*/
public void removeColumn(int columnIndex) {
if (columnIndex < 0 || columnIndex > getColumnCount() - 1) {
throw new IllegalArgumentException("Column index out of bounds");
}
if(getColumnCount() == 1) {
throw new IllegalArgumentException("Table must have at least one column");
}
CTTableColumns tableColumns = ctTable.getTableColumns();
tableColumns.removeTableColumn(columnIndex);
tableColumns.setCount(tableColumns.getTableColumnList().size());
updateReferences();
updateHeaders();
}
@ -323,20 +435,25 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
}
/**
* @deprecated Use {@link #getColumnCount()} instead.
*
* @return the number of mapped table columns (see Open Office XML Part 4: chapter 3.5.1.4)
*/
@Deprecated
@Removal(version = "4.2.0")
public long getNumberOfMappedColumns() {
return ctTable.getTableColumns().getCount();
}
/**
* @return The reference for the cells of the table
* (see Open Office XML Part 4: chapter 3.5.1.2, attribute ref)
* Get the area reference for the cells which this table covers. The area
* includes header rows and totals rows.
*
* Does not track updates to underlying changes to CTTable
* To synchronize with changes to the underlying CTTable,
* call {@link #updateReferences()}.
* Does not track updates to underlying changes to CTTable To synchronize
* with changes to the underlying CTTable, call {@link #updateReferences()}.
*
* @return the area of the table
* @see "Open Office XML Part 4: chapter 3.5.1.2, attribute ref"
* @since 3.17 beta 1
*/
public AreaReference getCellReferences() {
@ -346,16 +463,35 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
SpreadsheetVersion.EXCEL2007
);
}
/**
* Updates the reference for the cells of the table
* (see Open Office XML Part 4: chapter 3.5.1.2, attribute ref)
* and synchronizes any changes
* @param refs table range
* Set the area reference for the cells which this table covers. The area
* includes includes header rows and totals rows. Automatically synchronizes
* any changes by calling {@link #updateHeaders()}.
*
* Note: The area's width should be identical to the amount of columns in
* the table or the table may be invalid. All header rows, totals rows and
* at least one data row must fit inside the area. Updating the area with
* this method does not create or remove any columns and does not change any
* cell values.
*
* @deprecated Use {@link #setTableArea} instead, which will ensure that the
* the amount of columns always matches table area always width.
*
* @see "Open Office XML Part 4: chapter 3.5.1.2, attribute ref"
* @since 3.17 beta 1
*/
@Deprecated
@Removal(version="4.2.0")
public void setCellReferences(AreaReference refs) {
// Strip the Sheet name
setCellRef(refs);
}
@Internal
protected void setCellRef(AreaReference refs) {
// Strip the sheet name,
// CTWorksheet.getTableParts defines in which sheet the table is
String ref = refs.formatAsString();
if (ref.indexOf('!') != -1) {
ref = ref.substring(ref.indexOf('!')+1);
@ -383,6 +519,87 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
updateHeaders();
}
/**
* Set the area reference for the cells which this table covers. The area
* includes includes header rows and totals rows.
*
* Updating the area with this method will create new column as necessary to
* the right side of the table but will not modify any cell values.
*
* @param refs
* the new area of the table
* @throws IllegalArgumentException
* if the area is {@code null} or not
* @since 4.0.0
*/
public void setArea(AreaReference tableArea) {
if (tableArea == null) {
throw new IllegalArgumentException("AreaReference must not be null");
}
String areaSheetName = tableArea.getFirstCell().getSheetName();
if (areaSheetName != null && !areaSheetName.equals(getXSSFSheet().getSheetName())) {
// TODO to move a table from one sheet to another
// CTWorksheet.getTableParts needs to be updated on both sheets
throw new IllegalArgumentException(
"The AreaReference must not reference a different sheet");
}
int rowCount = (tableArea.getLastCell().getRow() - tableArea.getFirstCell().getRow()) + 1;
int minimumRowCount = 1 + getHeaderRowCount() + getTotalsRowCount();
if (rowCount < minimumRowCount) {
throw new IllegalArgumentException("AreaReference needs at least " + minimumRowCount
+ " rows, to cover at least one data row and all header rows and totals rows");
}
// Strip the sheet name,
// CTWorksheet.getTableParts defines in which sheet the table is
String ref = tableArea.formatAsString();
if (ref.indexOf('!') != -1) {
ref = ref.substring(ref.indexOf('!') + 1);
}
// Update
ctTable.setRef(ref);
if (ctTable.isSetAutoFilter()) {
ctTable.getAutoFilter().setRef(ref);
}
updateReferences();
// add or remove columns on the right side of the table
int columnCount = getColumnCount();
int newColumnCount = (tableArea.getLastCell().getCol() - tableArea.getFirstCell().getCol()) + 1;
if (newColumnCount > columnCount) {
for (int i = columnCount; i < newColumnCount; i++) {
createColumn(null, i);
}
} else if (newColumnCount < columnCount) {
for (int i = columnCount; i > newColumnCount; i--) {
removeColumn(i -1);
}
}
updateHeaders();
}
/**
* Get the area that this table covers.
*
* @return the table's area or {@code null} if the area has not been
* initialized
* @since 4.0.0
*/
public AreaReference getArea() {
String ref = ctTable.getRef();
if (ref != null) {
SpreadsheetVersion version = getXSSFSheet().getWorkbook().getSpreadsheetVersion();
return new AreaReference(ctTable.getRef(), version);
} else {
return null;
}
}
/**
* @return The reference for the cell in the top-left part of the table
* (see Open Office XML Part 4: chapter 3.5.1.2, attribute ref)
@ -445,12 +662,17 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
/**
* @return the total number of rows in the selection. (Note: in this version autofiltering is ignored)
* Get the total number of rows in this table, including all
* {@linkplain #getHeaderRowCount() header rows} and all
* {@linkplain #getTotalsRowCount() totals rows}. (Note: in this version
* autofiltering is ignored)
*
* Returns <code>0</code> if the start or end cell references are not set.
*
* Does not track updates to underlying changes to CTTable
* To synchronize with changes to the underlying CTTable,
* call {@link #updateReferences()}.
* Does not track updates to underlying changes to CTTable To synchronize
* with changes to the underlying CTTable, call {@link #updateReferences()}.
*
* @return the total number of rows
*/
public int getRowCount() {
CellReference from = getStartCellReference();
@ -463,6 +685,117 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
return rowCount;
}
/**
* Get the number of data rows in this table. This does not include any
* header rows or totals rows.
*
* Returns <code>0</code> if the start or end cell references are not set.
*
* Does not track updates to underlying changes to CTTable To synchronize
* with changes to the underlying CTTable, call {@link #updateReferences()}.
*
* @return the number of data rows
* @since 4.0.0
*/
public int getDataRowCount() {
CellReference from = getStartCellReference();
CellReference to = getEndCellReference();
int rowCount = 0;
if (from != null && to != null) {
rowCount = (to.getRow() - from.getRow() + 1) - getHeaderRowCount()
- getTotalsRowCount();
}
return rowCount;
}
/**
* Set the number of rows in the data area of the table. This does not
* affect any header rows or totals rows.
*
* If the new row count is less than the current row count, superfluous rows
* will be cleared. If the new row count is greater than the current row
* count, cells below the table will be overwritten by the table.
*
* To resize the table without overwriting cells, use
* {@link #setArea(AreaReference)} instead.
*
* @param newDataRowCount
* new row count for the table
* @throws IllegalArgumentException
* if the row count is less than 1
* @since 4.0.0
*/
public void setDataRowCount(int newDataRowCount) {
if (newDataRowCount < 1) {
throw new IllegalArgumentException("Table must have at least one data row");
}
updateReferences();
int dataRowCount = getDataRowCount();
if (dataRowCount == newDataRowCount) {
return;
}
CellReference tableStart = getStartCellReference();
CellReference tableEnd = getEndCellReference();
SpreadsheetVersion version = getXSSFSheet().getWorkbook().getSpreadsheetVersion();
// calculate new area
int newTotalRowCount = getHeaderRowCount() + newDataRowCount + getTotalsRowCount();
CellReference newTableEnd = new CellReference(tableStart.getRow() + newTotalRowCount - 1,
tableEnd.getCol());
AreaReference newTableArea = new AreaReference(tableStart, newTableEnd, version);
// clear cells
CellReference clearAreaStart;
CellReference clearAreaEnd;
if (newDataRowCount < dataRowCount) {
// table size reduced -
// clear all table cells that are outside of the new area
clearAreaStart = new CellReference(newTableArea.getLastCell().getRow() + 1,
newTableArea.getFirstCell().getCol());
clearAreaEnd = tableEnd;
} else {
// table size increased -
// clear all cells below the table that are inside the new area
clearAreaStart = new CellReference(tableEnd.getRow() + 1,
newTableArea.getFirstCell().getCol());
clearAreaEnd = newTableEnd;
}
AreaReference areaToClear = new AreaReference(clearAreaStart, clearAreaEnd, version);
for (CellReference cellRef : areaToClear.getAllReferencedCells()) {
XSSFRow row = getXSSFSheet().getRow(cellRef.getRow());
if (row != null) {
XSSFCell cell = row.getCell(cellRef.getCol());
if (cell != null) {
cell.setCellType(CellType.BLANK);
cell.setCellStyle(null);
}
}
}
// update table area
setCellRef(newTableArea);
}
/**
* Get the total number of columns in this table.
*
* @return the column count
* @since 4.0.0
*/
public int getColumnCount() {
CTTableColumns tableColumns = ctTable.getTableColumns();
if(tableColumns == null) {
return 0;
}
// Casting to int should be safe here - tables larger than the
// sheet (which holds the actual data of the table) can't exists.
return (int) tableColumns.getCount();
}
/**
* Synchronize table headers with cell values in the parent sheet.
* Headers <em>must</em> be in sync, otherwise Excel will display a
@ -488,19 +821,22 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
if (row != null && row.getCTRow().validate()) {
int cellnum = firstHeaderColumn;
for (CTTableColumn col : getCTTable().getTableColumns().getTableColumnList()) {
CTTableColumns ctTableColumns = getCTTable().getTableColumns();
if(ctTableColumns != null) {
for (CTTableColumn col : ctTableColumns.getTableColumnList()) {
XSSFCell cell = row.getCell(cellnum);
if (cell != null) {
col.setName(formatter.formatCellValue(cell));
}
cellnum++;
}
ctColumns = null;
columnMap = null;
xmlColumnPr = null;
commonXPath = null;
}
}
tableColumns = null;
columnMap = null;
xmlColumnPrs = null;
commonXPath = null;
}
private static String caseInsensitive(String s) {
return s.toUpperCase(Locale.ROOT);
@ -522,11 +858,11 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
if (columnHeader == null) return -1;
if (columnMap == null) {
// FIXME: replace with org.apache.commons.collections.map.CaseInsensitiveMap
final int count = getTableColumns().length;
final int count = getColumnCount();
columnMap = new HashMap<>(count * 3 / 2);
int i = 0;
for (CTTableColumn column : getTableColumns()) {
for (XSSFTableColumn column : getColumns()) {
String columnName = column.getName();
columnMap.put(caseInsensitive(columnName), i);
i++;

View File

@ -0,0 +1,134 @@
/* ====================================================================
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.apache.poi.xssf.usermodel;
import org.apache.poi.util.Internal;
import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXmlColumnPr;
/**
* A table column of an {@link XSSFTable}. Use {@link XSSFTable#createColumn} to
* create new table columns.
*
* @author Leonard Kappe
* @since 4.0.0
*/
public class XSSFTableColumn {
private final XSSFTable table;
private final CTTableColumn ctTableColumn;
private XSSFXmlColumnPr xmlColumnPr;
/**
* Create a new table column.
*
* @param table
* the table which contains the column
* @param ctTableColumn
* the table column xmlbean to wrap
* @since 4.0.0
*/
@Internal
protected XSSFTableColumn(XSSFTable table, CTTableColumn ctTableColumn) {
this.table = table;
this.ctTableColumn = ctTableColumn;
}
/**
* Get the table which contains this column
*
* @return the table containing this column
* @since 4.0.0
*/
public XSSFTable getTable() {
return table;
}
/**
* Get the identifier of this column, which is is unique per table.
*
* @return the column id
* @since 4.0.0
*/
public long getId() {
return ctTableColumn.getId();
}
/**
* Set the identifier of this column, which must be unique per table.
*
* It is up to the caller to enforce the uniqueness of the id.
*
* @return the column id
* @since 4.0.0
*/
public void setId(long columnId) {
ctTableColumn.setId(columnId);
}
/**
* Get the name of the column, which is is unique per table.
*
* @return the column name
* @since 4.0.0
*/
public String getName() {
return ctTableColumn.getName();
}
/**
* Get the name of the column, which is is unique per table.
*
* @return the column name
* @since 4.0.0
*/
public void setName(String columnName) {
ctTableColumn.setName(columnName);
}
/**
* Get the XmlColumnPr (XML column properties) if this column has an XML
* mapping.
*
* @return the XmlColumnPr or <code>null</code> if this column has no XML
* mapping
* @since 4.0.0
*/
public XSSFXmlColumnPr getXmlColumnPr() {
if (xmlColumnPr == null) {
CTXmlColumnPr ctXmlColumnPr = ctTableColumn.getXmlColumnPr();
if (ctXmlColumnPr != null) {
xmlColumnPr = new XSSFXmlColumnPr(this, ctXmlColumnPr);
}
}
return xmlColumnPr;
}
/**
* Get the column's position in its table, staring with zero from left to
* right.
*
* @return the column index
* @since 4.0.0
*/
public int getColumnIndex() {
return table.findColumnIndex(getName());
}
}

View File

@ -17,12 +17,14 @@
package org.apache.poi.xssf.usermodel.helpers;
import org.apache.poi.util.Internal;
import org.apache.poi.util.Removal;
import org.apache.poi.xssf.usermodel.XSSFTable;
import org.apache.poi.xssf.usermodel.XSSFTableColumn;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXmlColumnPr;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STXmlDataType.Enum;
/**
*
* This class is a wrapper around the CTXmlColumnPr (Open Office XML Part 4:
@ -34,15 +36,43 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.STXmlDataType.Enum;
public class XSSFXmlColumnPr {
private XSSFTable table;
private CTTableColumn ctTableColumn;
private XSSFTableColumn tableColumn;
private CTXmlColumnPr ctXmlColumnPr;
/**
* Create a new XSSFXmlColumnPr (XML column properties) wrapper around a
* CTXmlColumnPr.
*
* @param tableColumn
* table column for which the XML column properties are set
* @param ctXmlColumnPr
* the XML column properties xmlbean to wrap
*/
@Internal
public XSSFXmlColumnPr(XSSFTableColumn tableColumn, CTXmlColumnPr ctXmlColumnPr) {
this.table = tableColumn.getTable();
this.tableColumn = tableColumn;
this.ctXmlColumnPr = ctXmlColumnPr;
}
@Deprecated
@Removal(version="4.2")
public XSSFXmlColumnPr(XSSFTable table, CTTableColumn ctTableColum, CTXmlColumnPr ctXmlColumnPr) {
this.table = table;
this.ctTableColumn = ctTableColum;
this.tableColumn = table.getColumns().get(table.findColumnIndex(ctTableColum.getName()));
this.ctXmlColumnPr = ctXmlColumnPr;
}
/**
* Get the column for which these XML column properties are set.
*
* @return the table column
* @since 4.0.0
*/
public XSSFTableColumn getTableColumn() {
return tableColumn;
}
public long getMapId() {
return ctXmlColumnPr.getMapId();
}
@ -50,15 +80,20 @@ public class XSSFXmlColumnPr {
public String getXPath() {
return ctXmlColumnPr.getXpath();
}
/**
* (see Open Office XML Part 4: chapter 3.5.1.3)
*
* @deprecated Use {@link XSSFTableColumn#getId()} instead.
*
* @return An integer representing the unique identifier of this column.
*/
@Deprecated
@Removal(version="4.2")
public long getId() {
return ctTableColumn.getId();
return tableColumn.getId();
}
/**
* If the XPath is, for example, /Node1/Node2/Node3 and /Node1/Node2 is the common XPath for the table, the local XPath is /Node3
*
@ -80,8 +115,4 @@ public class XSSFXmlColumnPr {
return ctXmlColumnPr.getXmlDataType();
}
}

View File

@ -32,6 +32,7 @@ import java.util.Locale;
import javax.xml.xpath.XPathExpressionException;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xssf.usermodel.XSSFMap;
import org.apache.poi.xssf.usermodel.XSSFRow;
@ -99,8 +100,9 @@ public class TestXSSFImportFromXML {
"<ns1:Schema ID=\"" + cellC8 + "\" SchemaRef=\"c\" />" +
"<ns1:Schema ID=\"" + cellC9 + "\" SchemaRef=\"d\" />");
for (int i = 10; i < 10010; i++) {
testXML.append("<ns1:Schema ID=\"c").append(i).append("\" SchemaRef=\"d\" />");
int cellOffset = 10; // cell C10
for (int i = 0; i < 10000; i++) {
testXML.append("<ns1:Schema ID=\"c").append(i + cellOffset).append("\" SchemaRef=\"d\" />");
}
testXML.append("<ns1:Map ID=\"1\" Name=\"\" RootElement=\"\" SchemaID=\"\" ShowImportExportValidationErrors=\"\" AutoFit=\"\" Append=\"\" PreserveSortAFLayout=\"\" PreserveFormat=\"\">" + "<ns1:DataBinding DataBindingLoadMode=\"\" />" + "</ns1:Map>" + "<ns1:Map ID=\"2\" Name=\"\" RootElement=\"\" SchemaID=\"\" ShowImportExportValidationErrors=\"\" AutoFit=\"\" Append=\"\" PreserveSortAFLayout=\"\" PreserveFormat=\"\">" + "<ns1:DataBinding DataBindingLoadMode=\"\" />" + "</ns1:Map>" + "<ns1:Map ID=\"3\" Name=\"\" RootElement=\"\" SchemaID=\"\" ShowImportExportValidationErrors=\"\" AutoFit=\"\" Append=\"\" PreserveSortAFLayout=\"\" PreserveFormat=\"\">" + "<ns1:DataBinding DataBindingLoadMode=\"\" />" + "</ns1:Map>" + "</ns1:MapInfo>\u0000");
@ -114,6 +116,17 @@ public class TestXSSFImportFromXML {
//Check for Schema element
XSSFSheet sheet = wb.getSheetAt(1);
// check table size (+1 for the header row)
assertEquals(3 + 1, wb.getTable("Tabella1").getRowCount());
assertEquals(10004 + 1, wb.getTable("Tabella2").getRowCount());
// table1 size was reduced, check that former table cells have been cleared
assertEquals(CellType.BLANK, wb.getSheetAt(0).getRow(8).getCell(5).getCellType());
// table2 size was increased, check that new table cells have been cleared
assertEquals(CellType.BLANK, sheet.getRow(10).getCell(3).getCellType());
assertEquals(cellC6, sheet.getRow(5).getCell(2).getStringCellValue());
assertEquals(cellC7, sheet.getRow(6).getCell(2).getStringCellValue());
assertEquals(cellC8, sheet.getRow(7).getCell(2).getStringCellValue());

View File

@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.TempFile;
@ -223,6 +224,14 @@ public final class TestXSSFTable {
wb.close();
}
@Test
public void getColumnCount() throws IOException {
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("StructuredReferences.xlsx");
XSSFTable table = wb.getTable("\\_Prime.1");
assertEquals(3, table.getColumnCount());
wb.close();
}
@Test
public void getAndSetDisplayName() throws IOException {
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("StructuredReferences.xlsx");
@ -292,6 +301,130 @@ public final class TestXSSFTable {
IOUtils.closeQuietly(wb);
}
@Test
public void testGetDataRowCount() {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFSheet sh = wb.createSheet();
AreaReference tableArea = new AreaReference("B2:B6", wb.getSpreadsheetVersion());
XSSFTable table = sh.createTable(tableArea);
assertEquals(5, table.getRowCount()); // includes column header
assertEquals(4, table.getDataRowCount());
table.setArea(new AreaReference("B2:B7", wb.getSpreadsheetVersion()));
assertEquals(6, table.getRowCount());
assertEquals(5, table.getDataRowCount());
IOUtils.closeQuietly(wb);
}
@Test
public void testSetDataRowCount() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFSheet sh = wb.createSheet();
// 1 header row + 1 data row
AreaReference tableArea = new AreaReference("C10:C11", wb.getSpreadsheetVersion());
XSSFTable table = sh.createTable(tableArea);
assertEquals(2, table.getRowCount()); // includes all data and header/footer rows
assertEquals(1, table.getHeaderRowCount());
assertEquals(1, table.getDataRowCount());
assertEquals(0, table.getTotalsRowCount());
table.setDataRowCount(5);
assertEquals(6, table.getRowCount());
assertEquals(1, table.getHeaderRowCount());
assertEquals(5, table.getDataRowCount());
assertEquals(0, table.getTotalsRowCount());
assertEquals("C10:C15", table.getArea().formatAsString());
IOUtils.closeQuietly(wb);
}
@Test
public void testSetArea() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFSheet sh = wb.createSheet();
AreaReference tableArea = new AreaReference("B10:D12", wb.getSpreadsheetVersion());
XSSFTable table = sh.createTable(tableArea);
assertEquals(3, table.getColumnCount());
assertEquals(3, table.getRowCount());
// move table without resizing, shouldn't change row or column count
AreaReference tableArea2 = new AreaReference("B11:D13", wb.getSpreadsheetVersion());
table.setArea(tableArea2);
assertEquals(3, table.getColumnCount());
assertEquals(3, table.getRowCount());
// increase size by 1 row and 1 column
AreaReference tableArea3 = new AreaReference("B11:E14", wb.getSpreadsheetVersion());
table.setArea(tableArea3);
assertEquals(4, table.getColumnCount());
assertEquals(4, table.getRowCount());
// reduce size by 2 rows and 2 columns
AreaReference tableArea4 = new AreaReference("C12:D13", wb.getSpreadsheetVersion());
table.setArea(tableArea4);
assertEquals(2, table.getColumnCount());
assertEquals(2, table.getRowCount());
IOUtils.closeQuietly(wb);
}
@Test
public void testCreateColumn() {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFSheet sh = wb.createSheet();
AreaReference tableArea = new AreaReference("A2:A3", wb.getSpreadsheetVersion());
XSSFTable table = sh.createTable(tableArea);
assertEquals(1, table.getColumnCount());
assertEquals(2, table.getRowCount());
// add columns
table.createColumn("Column B");
table.createColumn("Column D");
table.createColumn("Column C", 2); // add between B and D
table.updateReferences();
table.updateHeaders();
assertEquals(4, table.getColumnCount());
assertEquals(2, table.getRowCount());
assertEquals("Column 1", table.getColumns().get(0).getName()); // generated name
assertEquals("Column B", table.getColumns().get(1).getName());
assertEquals("Column C", table.getColumns().get(2).getName());
assertEquals("Column D", table.getColumns().get(3).getName());
IOUtils.closeQuietly(wb);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateColumnInvalidIndex() throws IOException {
try (XSSFWorkbook wb = new XSSFWorkbook();) {
XSSFSheet sh = wb.createSheet();
AreaReference tableArea = new AreaReference("D2:D3", wb.getSpreadsheetVersion());
XSSFTable table = sh.createTable(tableArea);
// add columns
table.createColumn("Column 2", 1);
table.createColumn("Column 3", 3); // out of bounds
}
}
@Test
public void testDifferentHeaderTypes() throws IOException {
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("TablesWithDifferentHeaders.xlsx");
@ -345,13 +478,13 @@ public final class TestXSSFTable {
c5.setCellValue("CD");
c6.setCellValue("EF");
// Setting up the CTTable
XSSFTable t = s.createTable();
// Setting up the table
XSSFTable t = s.createTable(new AreaReference("A1:C3", wb.getSpreadsheetVersion()));
t.setName("TableTest");
t.setDisplayName("CT_Table_Test");
t.addColumn();
t.addColumn();
t.addColumn();
t.createColumn("Column 1");
t.createColumn("Column 2");
t.createColumn("Column 3");
t.setCellReferences(wb.getCreationHelper().createAreaReference(
new CellReference(c1), new CellReference(c6)
));

View File

@ -0,0 +1,79 @@
/* ====================================================================
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.apache.poi.xssf.usermodel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.io.IOException;
import java.util.List;
import org.apache.poi.xssf.XSSFTestDataSamples;
import org.junit.Test;
public final class TestXSSFTableColumn {
@Test
public void testGetColumnName() throws IOException {
try (XSSFWorkbook wb = XSSFTestDataSamples
.openSampleWorkbook("CustomXMLMappings-complex-type.xlsx")) {
XSSFTable table = wb.getTable("Tabella2");
List<XSSFTableColumn> tableColumns = table.getColumns();
assertEquals("ID", tableColumns.get(0).getName());
assertEquals("Unmapped Column", tableColumns.get(1).getName());
assertEquals("SchemaRef", tableColumns.get(2).getName());
assertEquals("Namespace", tableColumns.get(3).getName());
}
}
@Test
public void testGetColumnIndex() throws IOException {
try (XSSFWorkbook wb = XSSFTestDataSamples
.openSampleWorkbook("CustomXMLMappings-complex-type.xlsx")) {
XSSFTable table = wb.getTable("Tabella2");
List<XSSFTableColumn> tableColumns = table.getColumns();
assertEquals(0, tableColumns.get(0).getColumnIndex());
assertEquals(1, tableColumns.get(1).getColumnIndex());
assertEquals(2, tableColumns.get(2).getColumnIndex());
assertEquals(3, tableColumns.get(3).getColumnIndex());
}
}
@Test
public void testGetXmlColumnPrs() throws IOException {
try (XSSFWorkbook wb = XSSFTestDataSamples
.openSampleWorkbook("CustomXMLMappings-complex-type.xlsx")) {
XSSFTable table = wb.getTable("Tabella2");
List<XSSFTableColumn> tableColumns = table.getColumns();
assertNotNull(tableColumns.get(0).getXmlColumnPr());
assertNull(tableColumns.get(1).getXmlColumnPr()); // unmapped column
assertNotNull(tableColumns.get(2).getColumnIndex());
assertNotNull(tableColumns.get(3).getColumnIndex());
}
}
}