| /* ==================================================================== |
| 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.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; |
| |
| import java.io.IOException; |
| 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; |
| |
| import org.apache.poi.ooxml.POIXMLDocumentPart; |
| import org.apache.poi.openxml4j.opc.PackagePart; |
| import org.apache.poi.ss.SpreadsheetVersion; |
| 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.StringUtil; |
| import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr; |
| import org.apache.xmlbeans.XmlException; |
| import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable; |
| import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn; |
| import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumns; |
| import org.openxmlformats.schemas.spreadsheetml.x2006.main.TableDocument; |
| |
| /** |
| * |
| * This class implements the Table Part (Open Office XML Part 4: chapter 3.5.1) |
| * |
| * 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. |
| */ |
| public class XSSFTable extends POIXMLDocumentPart implements Table { |
| |
| private CTTable ctTable; |
| private transient List<XSSFXmlColumnPr> xmlColumnPrs; |
| private transient List<XSSFTableColumn> tableColumns; |
| private transient HashMap<String, Integer> columnMap; |
| private transient CellReference startCellReference; |
| private transient CellReference endCellReference; |
| private transient String commonXPath; |
| private transient String name; |
| private transient String styleName; |
| |
| /** |
| * empty implementation, not attached to a workbook/worksheet yet |
| */ |
| public XSSFTable() { |
| super(); |
| ctTable = CTTable.Factory.newInstance(); |
| } |
| |
| /** |
| * @param part The part used to initialize the table |
| * @throws IOException If reading data from the part fails. |
| * @since POI 3.14-Beta1 |
| */ |
| public XSSFTable(PackagePart part) throws IOException { |
| super(part); |
| readFrom(part.getInputStream()); |
| } |
| |
| /** |
| * Read table XML from an {@link InputStream} |
| * @param is The stream which provides the XML data for the table. |
| * @throws IOException If reading from the stream fails |
| */ |
| public void readFrom(InputStream is) throws IOException { |
| try { |
| TableDocument doc = TableDocument.Factory.parse(is, DEFAULT_XML_OPTIONS); |
| ctTable = doc.getTable(); |
| } catch (XmlException e) { |
| throw new IOException(e.getLocalizedMessage()); |
| } |
| } |
| |
| /** |
| * @return owning sheet |
| */ |
| public XSSFSheet getXSSFSheet(){ |
| return (XSSFSheet) getParent(); |
| } |
| |
| /** |
| * write table XML to an {@link OutputStream} |
| * @param out The stream to write the XML data to |
| * @throws IOException If writing to the stream fails. |
| */ |
| public void writeTo(OutputStream out) throws IOException { |
| updateHeaders(); |
| |
| TableDocument doc = TableDocument.Factory.newInstance(); |
| doc.setTable(ctTable); |
| doc.save(out, DEFAULT_XML_OPTIONS); |
| } |
| |
| @Override |
| protected void commit() throws IOException { |
| PackagePart part = getPackagePart(); |
| OutputStream out = part.getOutputStream(); |
| writeTo(out); |
| out.close(); |
| } |
| |
| /** |
| * get the underlying CTTable XML bean |
| * @return underlying OOXML object |
| */ |
| @Internal(since="POI 3.15 beta 3") |
| public CTTable getCTTable() { |
| return ctTable; |
| } |
| |
| /** |
| * Checks if this Table element contains even a single mapping to the map identified by id |
| * @param id the XSSFMap ID |
| * @return true if the Table element contain mappings |
| */ |
| public boolean mapsTo(long id){ |
| List<XSSFXmlColumnPr> pointers = getXmlColumnPrs(); |
| |
| for (XSSFXmlColumnPr pointer: pointers) { |
| if (pointer.getMapId()==id) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * |
| * Calculates the xpath of the root element for the table. This will be the common part |
| * of all the mapping's xpaths |
| * Note: this function caches the result for performance. To flush the cache {@link #updateHeaders()} must be called. |
| * |
| * @return the xpath of the table's root element |
| */ |
| public String getCommonXpath() { |
| if (commonXPath == null) { |
| String[] commonTokens = {}; |
| for (XSSFTableColumn column : getColumns()) { |
| if (column.getXmlColumnPr()!=null) { |
| String xpath = column.getXmlColumnPr().getXPath(); |
| String[] tokens = xpath.split("/"); |
| if (commonTokens.length==0) { |
| commonTokens = tokens; |
| |
| } else { |
| final int maxLength = Math.min(commonTokens.length, tokens.length); |
| |
| for (int i =0; i<maxLength; i++) { |
| if (!commonTokens[i].equals(tokens[i])) { |
| List<String> subCommonTokens = Arrays.asList(commonTokens).subList(0, i); |
| |
| String[] container = {}; |
| |
| commonTokens = subCommonTokens.toArray(container); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| commonTokens[0] = ""; |
| commonXPath = StringUtil.join(commonTokens, "/"); |
| } |
| |
| 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; |
| } |
| |
| /** |
| * Use {@link XSSFTableColumn#getXmlColumnPr()} instead. |
| */ |
| private List<XSSFXmlColumnPr> getXmlColumnPrs() { |
| if (xmlColumnPrs == null) { |
| xmlColumnPrs = new ArrayList<>(); |
| for (XSSFTableColumn column: getColumns()) { |
| XSSFXmlColumnPr xmlColumnPr = column.getXmlColumnPr(); |
| if (xmlColumnPr != null) { |
| xmlColumnPrs.add(xmlColumnPr); |
| } |
| } |
| } |
| return xmlColumnPrs; |
| } |
| |
| /** |
| * Add a new column to the right end of the table. |
| * |
| * @param columnName |
| * the unique name of the column, must not be {@code null} |
| * @return the created table column |
| * @since 4.0.0 |
| */ |
| 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(); |
| } |
| |
| // check if name is unique and calculate unique column id |
| long nextColumnId = 0; |
| 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()); |
| } |
| // Bug #62740, the logic was just re-using the existing max ID, not incrementing beyond it. |
| nextColumnId++; |
| |
| // 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(); |
| } |
| |
| /** |
| * @return the name of the Table, if set |
| */ |
| @Override |
| public String getName() { |
| if (name == null && ctTable.getName() != null) { |
| setName(ctTable.getName()); |
| } |
| return name; |
| } |
| |
| /** |
| * Changes the name of the Table |
| * @param newName The name of the table. |
| */ |
| public void setName(String newName) { |
| if (newName == null) { |
| ctTable.unsetName(); |
| name = null; |
| return; |
| } |
| ctTable.setName(newName); |
| name = newName; |
| } |
| |
| /** |
| * @return the table style name, if set |
| * @since 3.17 beta 1 |
| */ |
| @Override |
| public String getStyleName() { |
| if (styleName == null && ctTable.isSetTableStyleInfo()) { |
| setStyleName(ctTable.getTableStyleInfo().getName()); |
| } |
| return styleName; |
| } |
| |
| /** |
| * Changes the name of the Table |
| * @param newStyleName The name of the style. |
| * @since 3.17 beta 1 |
| */ |
| public void setStyleName(String newStyleName) { |
| if (newStyleName == null) { |
| if (ctTable.isSetTableStyleInfo()) { |
| ctTable.getTableStyleInfo().unsetName(); |
| } |
| styleName = null; |
| return; |
| } |
| if (! ctTable.isSetTableStyleInfo()) { |
| ctTable.addNewTableStyleInfo(); |
| } |
| ctTable.getTableStyleInfo().setName(newStyleName); |
| styleName = newStyleName; |
| } |
| |
| /** |
| * @return the display name of the Table, if set |
| */ |
| public String getDisplayName() { |
| return ctTable.getDisplayName(); |
| } |
| |
| /** |
| * Changes the display name of the Table |
| * @param name to use |
| */ |
| public void setDisplayName(String name) { |
| if (name == null || name.isEmpty()) { |
| throw new IllegalArgumentException("Display name must not be null or empty"); |
| } |
| ctTable.setDisplayName(name); |
| } |
| |
| /** |
| * 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()}. |
| * |
| * @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() { |
| return new AreaReference( |
| getStartCellReference(), |
| getEndCellReference(), |
| SpreadsheetVersion.EXCEL2007 |
| ); |
| } |
| |
| /** |
| * 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. |
| * |
| * @see "Open Office XML Part 4: chapter 3.5.1.2, attribute ref" |
| * @since 3.17 beta 1 |
| */ |
| public void setCellReferences(AreaReference refs) { |
| 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); |
| } |
| |
| // Update |
| ctTable.setRef(ref); |
| if (ctTable.isSetAutoFilter()) { |
| String filterRef; |
| int totalsRowCount = getTotalsRowCount(); |
| if (totalsRowCount == 0) { |
| filterRef = ref; |
| } else { |
| final CellReference start = new CellReference(refs.getFirstCell().getRow(), refs.getFirstCell().getCol()); |
| // account for footer row(s) in auto-filter range, which doesn't include footers |
| final CellReference end = new CellReference(refs.getLastCell().getRow() - totalsRowCount, refs.getLastCell().getCol()); |
| // this won't have sheet references because we built the cell references without them |
| filterRef = new AreaReference(start, end, SpreadsheetVersion.EXCEL2007).formatAsString(); |
| } |
| ctTable.getAutoFilter().setRef(filterRef); |
| } |
| |
| // Have everything recomputed |
| updateReferences(); |
| 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 tableArea |
| * 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) |
| * |
| * Does not track updates to underlying changes to CTTable |
| * To synchronize with changes to the underlying CTTable, |
| * call {@link #updateReferences()}. |
| */ |
| public CellReference getStartCellReference() { |
| if (startCellReference==null) { |
| setCellReferences(); |
| } |
| return startCellReference; |
| } |
| |
| /** |
| * @return The reference for the cell in the bottom-right part of the table |
| * (see Open Office XML Part 4: chapter 3.5.1.2, attribute ref) |
| * |
| * Does not track updates to underlying changes to CTTable |
| * To synchronize with changes to the underlying CTTable, |
| * call {@link #updateReferences()}. |
| */ |
| public CellReference getEndCellReference() { |
| if (endCellReference==null) { |
| setCellReferences(); |
| } |
| return endCellReference; |
| } |
| |
| /** |
| * @since POI 3.15 beta 3 |
| */ |
| private void setCellReferences() { |
| String ref = ctTable.getRef(); |
| if (ref != null) { |
| String[] boundaries = ref.split(":", 2); |
| String from = boundaries[0]; |
| String to = boundaries.length == 2 ? boundaries[1] : boundaries[0]; |
| startCellReference = new CellReference(from); |
| endCellReference = new CellReference(to); |
| } |
| } |
| |
| |
| /** |
| * Clears the cached values set by {@link #getStartCellReference()} |
| * and {@link #getEndCellReference()}. |
| * The next call to {@link #getStartCellReference()} and |
| * {@link #getEndCellReference()} will synchronize the |
| * cell references with the underlying {@code CTTable}. |
| * Thus this method is inexpensive. |
| * |
| * @since POI 3.15 beta 3 |
| */ |
| public void updateReferences() { |
| startCellReference = null; |
| endCellReference = null; |
| } |
| |
| |
| /** |
| * 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} 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 total number of rows |
| */ |
| public int getRowCount() { |
| CellReference from = getStartCellReference(); |
| CellReference to = getEndCellReference(); |
| |
| int rowCount = 0; |
| if (from!=null && to!=null) { |
| rowCount = to.getRow() - from.getRow() + 1; |
| } |
| return rowCount; |
| } |
| |
| /** |
| * Get the number of data rows in this table. This does not include any |
| * header rows or totals rows. |
| * |
| * Returns {@code 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.setBlank(); |
| 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 |
| * "Found unreadable content" message on startup. |
| * |
| * If calling both {@link #updateReferences()} and |
| * this method, {@link #updateReferences()} |
| * should be called first. |
| * |
| * Note that a Table <em>must</em> have a header. To reproduce |
| * the equivalent of inserting a table in Excel without Headers, |
| * manually add cells with values of "Column1", "Column2" etc first. |
| */ |
| public void updateHeaders() { |
| XSSFSheet sheet = (XSSFSheet)getParent(); |
| CellReference ref = getStartCellReference(); |
| if (ref == null) return; |
| |
| int headerRow = ref.getRow(); |
| int firstHeaderColumn = ref.getCol(); |
| XSSFRow row = sheet.getRow(headerRow); |
| DataFormatter formatter = new DataFormatter(); |
| |
| if (row != null && row.getCTRow().validate()) { |
| int cellnum = firstHeaderColumn; |
| 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++; |
| } |
| } |
| } |
| tableColumns = null; |
| columnMap = null; |
| xmlColumnPrs = null; |
| commonXPath = null; |
| } |
| |
| private static String caseInsensitive(String s) { |
| return s.toUpperCase(Locale.ROOT); |
| } |
| |
| /** |
| * Gets the relative column index of a column in this table having the header name {@code column}. |
| * The column index is relative to the left-most column in the table, 0-indexed. |
| * Returns {@code -1} if {@code column} is not a header name in table. |
| * |
| * Column Header names are case-insensitive |
| * |
| * Note: this function caches column names for performance. To flush the cache (because columns |
| * have been moved or column headers have been changed), {@link #updateHeaders()} must be called. |
| * |
| * @since 3.15 beta 2 |
| */ |
| @Override |
| public int findColumnIndex(String columnHeader) { |
| if (columnHeader == null) return -1; |
| if (columnMap == null) { |
| // FIXME: replace with org.apache.commons.collections.map.CaseInsensitiveMap |
| final int count = getColumnCount(); |
| columnMap = new HashMap<>(count * 3 / 2); |
| |
| int i = 0; |
| for (XSSFTableColumn column : getColumns()) { |
| String columnName = column.getName(); |
| columnMap.put(caseInsensitive(columnName), i); |
| i++; |
| } |
| } |
| // Table column names with special characters need a single quote escape |
| // but the escape is not present in the column definition |
| Integer idx = columnMap.get(caseInsensitive(columnHeader.replace("'", ""))); |
| return idx == null ? -1 : idx; |
| } |
| |
| /** |
| * @since 3.15 beta 2 |
| */ |
| @Override |
| public String getSheetName() { |
| return getXSSFSheet().getSheetName(); |
| } |
| |
| /** |
| * Note: This is misleading. The Spec indicates this is true if the totals row |
| * has <b><i>ever</i></b> been shown, not whether or not it is currently displayed. |
| * Use {@link #getTotalsRowCount()} > 0 to decide whether or not the totals row is visible. |
| * @since 3.15 beta 2 |
| * @see #getTotalsRowCount() |
| */ |
| @Override |
| public boolean isHasTotalsRow() { |
| return ctTable.getTotalsRowShown(); |
| } |
| |
| /** |
| * @return 0 for no totals rows, 1 for totals row shown. |
| * Values > 1 are not currently used by Excel up through 2016, and the OOXML spec |
| * doesn't define how they would be implemented. |
| * @since 3.17 beta 1 |
| */ |
| @Override |
| public int getTotalsRowCount() { |
| return (int) ctTable.getTotalsRowCount(); |
| } |
| |
| /** |
| * @return 0 for no header rows, 1 for table headers shown. |
| * Values > 1 might be used by Excel for pivot tables? |
| * @since 3.17 beta 1 |
| */ |
| @Override |
| public int getHeaderRowCount() { |
| return (int) ctTable.getHeaderRowCount(); |
| } |
| |
| /** |
| * @since 3.15 beta 2 |
| */ |
| @Override |
| public int getStartColIndex() { |
| return getStartCellReference().getCol(); |
| } |
| |
| /** |
| * @since 3.15 beta 2 |
| */ |
| @Override |
| public int getStartRowIndex() { |
| return getStartCellReference().getRow(); |
| } |
| |
| /** |
| * @since 3.15 beta 2 |
| */ |
| @Override |
| public int getEndColIndex() { |
| return getEndCellReference().getCol(); |
| } |
| |
| /** |
| * @since 3.15 beta 2 |
| */ |
| @Override |
| public int getEndRowIndex() { |
| return getEndCellReference().getRow(); |
| } |
| |
| /** |
| * @since 3.17 beta 1 |
| */ |
| @Override |
| public TableStyleInfo getStyle() { |
| if (! ctTable.isSetTableStyleInfo()) return null; |
| return new XSSFTableStyleInfo(((XSSFSheet) getParent()).getWorkbook().getStylesSource(), ctTable.getTableStyleInfo()); |
| } |
| |
| /** |
| * @see org.apache.poi.ss.usermodel.Table#contains(org.apache.poi.ss.usermodel.Cell) |
| * @since 3.17 beta 1 |
| */ |
| @Override |
| public boolean contains(CellReference cell) { |
| if (cell == null) return false; |
| // check if cell is on the same sheet as the table |
| if ( ! getSheetName().equals(cell.getSheetName())) return false; |
| // check if the cell is inside the table |
| return cell.getRow() >= getStartRowIndex() |
| && cell.getRow() <= getEndRowIndex() |
| && cell.getCol() >= getStartColIndex() |
| && cell.getCol() <= getEndColIndex(); |
| } |
| |
| /** |
| * Remove relations |
| */ |
| protected void onTableDelete() { |
| for (RelationPart part : getRelationParts()) { |
| removeRelation(part.getDocumentPart(), true); |
| } |
| } |
| } |