From: PJ Fanning Date: Sun, 25 Feb 2018 17:06:19 +0000 (+0000) Subject: [bug-62055] Fix XSSFImportFromXML table resize. Thanks to Leonard Kappe. This closes #99 X-Git-Tag: REL_4_0_0_FINAL~233 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=4ad4d76241eae44386e52c3a23ca1b7ca28d1458;p=poi.git [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 --- diff --git a/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateTable.java b/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateTable.java index e1905f5be2..d1acce5f72 100644 --- a/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateTable.java +++ b/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateTable.java @@ -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( diff --git a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java index c7e211d145..f95823f423 100644 --- a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java +++ b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java @@ -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,35 +175,27 @@ public class XSSFExportToXml implements Comparator{ // Exports elements and attributes mapped with tables if (table!=null) { - List tableColumns = table.getCTTable().getTableColumns().getTableColumnList(); + List 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++) { XSSFRow row = sheet.getRow(i); - Node tableRootNode = getNodeByXPath(table.getCommonXpath(),doc.getFirstChild(),doc,true); + 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(); - Node currentNode = getNodeByXPath(localXPath,tableRootNode,doc,false); - mapCellOnNode(cell,currentNode); - } + XSSFXmlColumnPr xmlColumnPr = tableColumn.getXmlColumnPr(); + if (xmlColumnPr != null) { + String localXPath = xmlColumnPr.getLocalXPath(); + Node currentNode = getNodeByXPath(localXPath,tableRootNode,doc,false); + mapCellOnNode(cell, currentNode); } } } diff --git a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFImportFromXML.java b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFImportFromXML.java index 8ea6f1a8a4..d32dae26fc 100644 --- a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFImportFromXML.java +++ b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFImportFromXML.java @@ -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,25 +125,32 @@ 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++) { // TODO: implement support for denormalized XMLs (see // OpenOffice part 4: chapter 3.5.1.7) - Node singleNode = result.item(i).cloneNode(true); - for (XSSFXmlColumnPr xmlColumnPr : table.getXmlColumnPrs()) { + Node singleNode = result.item(i).cloneNode(true); + + for (XSSFTableColumn tableColum : table.getColumns()) { + + XSSFXmlColumnPr xmlColumnPr = tableColum.getXmlColumnPr(); + if(xmlColumnPr == null) { + continue; + } - int localColumnId = (int) xmlColumnPr.getId(); 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); + String value = (String) xpath.evaluate(localXPath, singleNode, XPathConstants.STRING); logger.log(POILogger.DEBUG, "Extracting with xpath " + localXPath + " : value is '" + value + "'"); XSSFRow row = table.getXSSFSheet().getRow(rowId); if (row == null) { @@ -161,6 +169,8 @@ public class XSSFImportFromXML { } } + + private static enum DataType { BOOLEAN(STXmlDataType.BOOLEAN), // DOUBLE(STXmlDataType.DOUBLE), // diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java index f4329abdc4..417d1e44e3 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java @@ -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,10 +4058,28 @@ 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() { - if(! worksheet.isSetTableParts()) { + 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; } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java index 5d84662405..0fbd8d333b 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java @@ -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 xmlColumnPr; - private transient CTTableColumn[] ctColumns; + private transient List xmlColumnPrs; + private transient List tableColumns; private transient HashMap columnMap; private transient CellReference startCellReference; private transient CellReference endCellReference; @@ -155,18 +158,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; - } /** * @@ -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 getColumns() { + if (tableColumns == null) { + List 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 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()); + } + + // 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); + } - // Have the Headers updated if possible + 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 0 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(); @@ -462,6 +684,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 0 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. @@ -479,7 +812,7 @@ public class XSSFTable extends POIXMLDocumentPart implements Table { public void updateHeaders() { XSSFSheet sheet = (XSSFSheet)getParent(); CellReference ref = getStartCellReference(); - if(ref == null) return; + if (ref == null) return; int headerRow = ref.getRow(); int firstHeaderColumn = ref.getCol(); @@ -488,18 +821,21 @@ public class XSSFTable extends POIXMLDocumentPart implements Table { if (row != null && row.getCTRow().validate()) { int cellnum = firstHeaderColumn; - for (CTTableColumn col : getCTTable().getTableColumns().getTableColumnList()) { - XSSFCell cell = row.getCell(cellnum); - if (cell != null) { - col.setName(formatter.formatCellValue(cell)); + 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++; } - cellnum++; } - ctColumns = null; - columnMap = null; - xmlColumnPr = null; - commonXPath = null; } + tableColumns = null; + columnMap = null; + xmlColumnPrs = null; + commonXPath = null; } private static String caseInsensitive(String s) { @@ -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++; diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTableColumn.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTableColumn.java new file mode 100644 index 0000000000..6cb483db13 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTableColumn.java @@ -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 null 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()); + } + +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFXmlColumnPr.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFXmlColumnPr.java index 574d64013a..47d34f4bb4 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFXmlColumnPr.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFXmlColumnPr.java @@ -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: @@ -32,56 +34,85 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.STXmlDataType.Enum; * @author Roberto Manicardi */ public class XSSFXmlColumnPr { - - private XSSFTable table; - private CTTableColumn ctTableColumn; - private CTXmlColumnPr ctXmlColumnPr; - - public XSSFXmlColumnPr(XSSFTable table ,CTTableColumn ctTableColum,CTXmlColumnPr ctXmlColumnPr){ - this.table = table; - this.ctTableColumn = ctTableColum; - this.ctXmlColumnPr = ctXmlColumnPr; - } - - public long getMapId(){ - return ctXmlColumnPr.getMapId(); - } - - public String getXPath(){ - return ctXmlColumnPr.getXpath(); - } - /** - * (see Open Office XML Part 4: chapter 3.5.1.3) - * @return An integer representing the unique identifier of this column. - */ - public long getId(){ - return ctTableColumn.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 - * - * @return the local XPath - */ - public String getLocalXPath(){ - StringBuilder localXPath = new StringBuilder(); - int numberOfCommonXPathAxis = table.getCommonXpath().split("/").length-1; - - String[] xPathTokens = ctXmlColumnPr.getXpath().split("/"); - for(int i=numberOfCommonXPathAxis; i" + name + "" + - "" + teacher + "" + - "" + tutor + "" + - "" + cdl + "" + - "" + duration + "" + - "" + topic + "" + - "" + project + "" + - "" + credits + "" + - "\u0000"; - - XSSFMap map = wb.getMapInfo().getXSSFMapByName("CORSO_mapping"); - assertNotNull(map); - XSSFImportFromXML importer = new XSSFImportFromXML(map); - - importer.importFromXML(testXML); - - XSSFSheet sheet = wb.getSheetAt(0); - - XSSFRow row = sheet.getRow(0); - assertTrue(row.getCell(0).getStringCellValue().equals(name)); - assertTrue(row.getCell(1).getStringCellValue().equals(teacher)); - assertTrue(row.getCell(2).getStringCellValue().equals(tutor)); - assertTrue(row.getCell(3).getStringCellValue().equals(cdl)); - assertTrue(row.getCell(4).getStringCellValue().equals(duration)); - assertTrue(row.getCell(5).getStringCellValue().equals(topic)); - assertTrue(row.getCell(6).getStringCellValue().equals(project)); - assertTrue(row.getCell(7).getStringCellValue().equals(credits)); - } - } - - @Test(timeout=60000) - public void testMultiTable() throws IOException, XPathExpressionException, SAXException{ - try (XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("CustomXMLMappings-complex-type.xlsx")) { - String cellC6 = "c6"; - String cellC7 = "c7"; - String cellC8 = "c8"; - String cellC9 = "c9"; - - StringBuilder testXML = new StringBuilder("" + - "" + - "" + - "" + - ""); - - for (int i = 10; i < 10010; i++) { - testXML.append(""); - } - - testXML.append("" + "" + "" + "" + "" + "" + "" + "" + "" + "\u0000"); - - XSSFMap map = wb.getMapInfo().getXSSFMapByName("MapInfo_mapping"); - assertNotNull(map); - XSSFImportFromXML importer = new XSSFImportFromXML(map); - - importer.importFromXML(testXML.toString()); - - //Check for Schema element - XSSFSheet sheet = wb.getSheetAt(1); - - assertEquals(cellC6, sheet.getRow(5).getCell(2).getStringCellValue()); - assertEquals(cellC7, sheet.getRow(6).getCell(2).getStringCellValue()); - assertEquals(cellC8, sheet.getRow(7).getCell(2).getStringCellValue()); - assertEquals(cellC9, sheet.getRow(8).getCell(2).getStringCellValue()); - assertEquals("c5001", sheet.getRow(5000).getCell(2).getStringCellValue()); - } - } - - - @Test - public void testSingleAttributeCellWithNamespace() throws IOException, XPathExpressionException, SAXException{ - try (XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("CustomXMLMapping-singleattributenamespace.xlsx")) { - int id = 1; - String displayName = "dispName"; - String ref = "19"; - int count = 21; - - String testXML = "" + - "" + - "" + - "\u0000"; - XSSFMap map = wb.getMapInfo().getXSSFMapByName("table_mapping"); - assertNotNull(map); - XSSFImportFromXML importer = new XSSFImportFromXML(map); - importer.importFromXML(testXML); - - //Check for Schema element - XSSFSheet sheet = wb.getSheetAt(0); - - assertEquals(new Double(id), sheet.getRow(28).getCell(1).getNumericCellValue(), 0); - assertEquals(displayName, sheet.getRow(11).getCell(5).getStringCellValue()); - assertEquals(ref, sheet.getRow(14).getCell(7).getStringCellValue()); - assertEquals(new Double(count), sheet.getRow(18).getCell(3).getNumericCellValue(), 0); - } - } - - @Test - public void testOptionalFields_Bugzilla_55864() throws IOException, XPathExpressionException, SAXException { - try (XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("55864.xlsx")) { - String testXML = "" + - "" + - "" + - "Albert" + - "Einstein" + - "1879-03-14" + - "" + - ""; - - XSSFMap map = wb.getMapInfo().getXSSFMapByName("PersonInfoRoot_Map"); - assertNotNull(map); - XSSFImportFromXML importer = new XSSFImportFromXML(map); - - importer.importFromXML(testXML); - - XSSFSheet sheet = wb.getSheetAt(0); - - XSSFRow rowHeadings = sheet.getRow(0); - XSSFRow rowData = sheet.getRow(1); - - assertEquals("FirstName", rowHeadings.getCell(0).getStringCellValue()); - assertEquals("Albert", rowData.getCell(0).getStringCellValue()); - - assertEquals("LastName", rowHeadings.getCell(1).getStringCellValue()); - assertEquals("Einstein", rowData.getCell(1).getStringCellValue()); - - assertEquals("BirthDate", rowHeadings.getCell(2).getStringCellValue()); - assertEquals("1879-03-14", rowData.getCell(2).getStringCellValue()); - - // Value for OptionalRating is declared optional (minOccurs=0) in 55864.xlsx - assertEquals("OptionalRating", rowHeadings.getCell(3).getStringCellValue()); - assertNull("", rowData.getCell(3)); - } - } - - @Test - public void testOptionalFields_Bugzilla_57890() throws IOException, ParseException, XPathExpressionException, SAXException { - XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("57890.xlsx"); - - String testXML = "" + "" - + "" + "" + Integer.MIN_VALUE + "" + "12345" - + "1.0000123" + "1991-03-14" + "" + ""; - - XSSFMap map = wb.getMapInfo().getXSSFMapByName("TestInfoRoot_Map"); - assertNotNull(map); - XSSFImportFromXML importer = new XSSFImportFromXML(map); - - importer.importFromXML(testXML); - - XSSFSheet sheet = wb.getSheetAt(0); - - XSSFRow rowHeadings = sheet.getRow(0); - XSSFRow rowData = sheet.getRow(1); - - assertEquals("Date", rowHeadings.getCell(0).getStringCellValue()); - Date date = new SimpleDateFormat("yyyy-MM-dd", DateFormatSymbols.getInstance(Locale.ROOT)).parse("1991-3-14"); - assertEquals(date, rowData.getCell(0).getDateCellValue()); - - assertEquals("Amount Int", rowHeadings.getCell(1).getStringCellValue()); - assertEquals(new Double(Integer.MIN_VALUE), rowData.getCell(1).getNumericCellValue(), 0); - - assertEquals("Amount Double", rowHeadings.getCell(2).getStringCellValue()); - assertEquals(1.0000123, rowData.getCell(2).getNumericCellValue(), 0); - - assertEquals("Amount UnsignedInt", rowHeadings.getCell(3).getStringCellValue()); - assertEquals(new Double(12345), rowData.getCell(3).getNumericCellValue(), 0); - - wb.close(); - } - - - + + @Test + public void testImportFromXML() throws IOException, XPathExpressionException, SAXException{ + try (XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("CustomXMLMappings.xlsx")) { + String name = "name"; + String teacher = "teacher"; + String tutor = "tutor"; + String cdl = "cdl"; + String duration = "duration"; + String topic = "topic"; + String project = "project"; + String credits = "credits"; + + String testXML = "" + + "" + name + "" + + "" + teacher + "" + + "" + tutor + "" + + "" + cdl + "" + + "" + duration + "" + + "" + topic + "" + + "" + project + "" + + "" + credits + "" + + "\u0000"; + + XSSFMap map = wb.getMapInfo().getXSSFMapByName("CORSO_mapping"); + assertNotNull(map); + XSSFImportFromXML importer = new XSSFImportFromXML(map); + + importer.importFromXML(testXML); + + XSSFSheet sheet = wb.getSheetAt(0); + + XSSFRow row = sheet.getRow(0); + assertTrue(row.getCell(0).getStringCellValue().equals(name)); + assertTrue(row.getCell(1).getStringCellValue().equals(teacher)); + assertTrue(row.getCell(2).getStringCellValue().equals(tutor)); + assertTrue(row.getCell(3).getStringCellValue().equals(cdl)); + assertTrue(row.getCell(4).getStringCellValue().equals(duration)); + assertTrue(row.getCell(5).getStringCellValue().equals(topic)); + assertTrue(row.getCell(6).getStringCellValue().equals(project)); + assertTrue(row.getCell(7).getStringCellValue().equals(credits)); + } + } + + @Test(timeout=60000) + public void testMultiTable() throws IOException, XPathExpressionException, SAXException{ + try (XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("CustomXMLMappings-complex-type.xlsx")) { + String cellC6 = "c6"; + String cellC7 = "c7"; + String cellC8 = "c8"; + String cellC9 = "c9"; + + StringBuilder testXML = new StringBuilder("" + + "" + + "" + + "" + + ""); + + int cellOffset = 10; // cell C10 + for (int i = 0; i < 10000; i++) { + testXML.append(""); + } + + testXML.append("" + "" + "" + "" + "" + "" + "" + "" + "" + "\u0000"); + + XSSFMap map = wb.getMapInfo().getXSSFMapByName("MapInfo_mapping"); + assertNotNull(map); + XSSFImportFromXML importer = new XSSFImportFromXML(map); + + importer.importFromXML(testXML.toString()); + + //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()); + assertEquals(cellC9, sheet.getRow(8).getCell(2).getStringCellValue()); + assertEquals("c5001", sheet.getRow(5000).getCell(2).getStringCellValue()); + } + } + + + @Test + public void testSingleAttributeCellWithNamespace() throws IOException, XPathExpressionException, SAXException{ + try (XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("CustomXMLMapping-singleattributenamespace.xlsx")) { + int id = 1; + String displayName = "dispName"; + String ref = "19"; + int count = 21; + + String testXML = "" + + "" + + "" + + "\u0000"; + XSSFMap map = wb.getMapInfo().getXSSFMapByName("table_mapping"); + assertNotNull(map); + XSSFImportFromXML importer = new XSSFImportFromXML(map); + importer.importFromXML(testXML); + + //Check for Schema element + XSSFSheet sheet = wb.getSheetAt(0); + + assertEquals(new Double(id), sheet.getRow(28).getCell(1).getNumericCellValue(), 0); + assertEquals(displayName, sheet.getRow(11).getCell(5).getStringCellValue()); + assertEquals(ref, sheet.getRow(14).getCell(7).getStringCellValue()); + assertEquals(new Double(count), sheet.getRow(18).getCell(3).getNumericCellValue(), 0); + } + } + + @Test + public void testOptionalFields_Bugzilla_55864() throws IOException, XPathExpressionException, SAXException { + try (XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("55864.xlsx")) { + String testXML = "" + + "" + + "" + + "Albert" + + "Einstein" + + "1879-03-14" + + "" + + ""; + + XSSFMap map = wb.getMapInfo().getXSSFMapByName("PersonInfoRoot_Map"); + assertNotNull(map); + XSSFImportFromXML importer = new XSSFImportFromXML(map); + + importer.importFromXML(testXML); + + XSSFSheet sheet = wb.getSheetAt(0); + + XSSFRow rowHeadings = sheet.getRow(0); + XSSFRow rowData = sheet.getRow(1); + + assertEquals("FirstName", rowHeadings.getCell(0).getStringCellValue()); + assertEquals("Albert", rowData.getCell(0).getStringCellValue()); + + assertEquals("LastName", rowHeadings.getCell(1).getStringCellValue()); + assertEquals("Einstein", rowData.getCell(1).getStringCellValue()); + + assertEquals("BirthDate", rowHeadings.getCell(2).getStringCellValue()); + assertEquals("1879-03-14", rowData.getCell(2).getStringCellValue()); + + // Value for OptionalRating is declared optional (minOccurs=0) in 55864.xlsx + assertEquals("OptionalRating", rowHeadings.getCell(3).getStringCellValue()); + assertNull("", rowData.getCell(3)); + } + } + + @Test + public void testOptionalFields_Bugzilla_57890() throws IOException, ParseException, XPathExpressionException, SAXException { + XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("57890.xlsx"); + + String testXML = "" + "" + + "" + "" + Integer.MIN_VALUE + "" + "12345" + + "1.0000123" + "1991-03-14" + "" + ""; + + XSSFMap map = wb.getMapInfo().getXSSFMapByName("TestInfoRoot_Map"); + assertNotNull(map); + XSSFImportFromXML importer = new XSSFImportFromXML(map); + + importer.importFromXML(testXML); + + XSSFSheet sheet = wb.getSheetAt(0); + + XSSFRow rowHeadings = sheet.getRow(0); + XSSFRow rowData = sheet.getRow(1); + + assertEquals("Date", rowHeadings.getCell(0).getStringCellValue()); + Date date = new SimpleDateFormat("yyyy-MM-dd", DateFormatSymbols.getInstance(Locale.ROOT)).parse("1991-3-14"); + assertEquals(date, rowData.getCell(0).getDateCellValue()); + + assertEquals("Amount Int", rowHeadings.getCell(1).getStringCellValue()); + assertEquals(new Double(Integer.MIN_VALUE), rowData.getCell(1).getNumericCellValue(), 0); + + assertEquals("Amount Double", rowHeadings.getCell(2).getStringCellValue()); + assertEquals(1.0000123, rowData.getCell(2).getNumericCellValue(), 0); + + assertEquals("Amount UnsignedInt", rowHeadings.getCell(3).getStringCellValue()); + assertEquals(new Double(12345), rowData.getCell(3).getNumericCellValue(), 0); + + wb.close(); + } + + + } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFTable.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFTable.java index 6ff8b8b007..9e2f9246dd 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFTable.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFTable.java @@ -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"); @@ -291,7 +300,131 @@ 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) )); diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFTableColumn.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFTableColumn.java new file mode 100644 index 0000000000..83cf5634a2 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFTableColumn.java @@ -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 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 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 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()); + + } + } +} diff --git a/test-data/spreadsheet/CustomXMLMappings-complex-type.xlsx b/test-data/spreadsheet/CustomXMLMappings-complex-type.xlsx index 0d6d8d0099..7cd5101479 100644 Binary files a/test-data/spreadsheet/CustomXMLMappings-complex-type.xlsx and b/test-data/spreadsheet/CustomXMLMappings-complex-type.xlsx differ