/* ==================================================================== 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 static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.setPassword; import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.validatePassword; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.*; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import org.apache.logging.log4j.Logger; import org.apache.poi.logging.PoiLogManager; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.ooxml.POIXMLException; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.PartAlreadyExistsException; import org.apache.poi.openxml4j.opc.*; import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.formula.FormulaShifter; import org.apache.poi.ss.formula.SheetNameFormatter; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.AreaReference; import org.apache.poi.ss.util.CellAddress; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.PaneInformation; import org.apache.poi.ss.util.SSCellRange; import org.apache.poi.ss.util.SheetUtil; import org.apache.poi.util.Beta; import org.apache.poi.util.Internal; import org.apache.poi.util.Removal; import org.apache.poi.util.Units; import org.apache.poi.xssf.model.Comments; import org.apache.poi.xssf.usermodel.XSSFPivotTable.PivotTableReferenceConfigurator; import org.apache.poi.xssf.usermodel.helpers.ColumnHelper; import org.apache.poi.xssf.usermodel.helpers.XSSFColumnShifter; import org.apache.poi.xssf.usermodel.helpers.XSSFIgnoredErrorHelper; import org.apache.poi.xssf.usermodel.helpers.XSSFRowShifter; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlOptions; import org.openxmlformats.schemas.spreadsheetml.x2006.main.*; /** * High level representation of a SpreadsheetML worksheet. * *
* Sheets are the central structures within a workbook, and are where a user does most of his spreadsheet work. * The most common type of sheet is the worksheet, which is represented as a grid of cells. Worksheet cells can * contain text, numbers, dates, and formulas. Cells can also be formatted. *
*/ public class XSSFSheet extends POIXMLDocumentPart implements Sheet, OoxmlSheetExtensions { private static final Logger LOG = PoiLogManager.getLogger(XSSFSheet.class); private static final double DEFAULT_ROW_HEIGHT = 15.0; private static final double DEFAULT_MARGIN_HEADER = 0.3; private static final double DEFAULT_MARGIN_FOOTER = 0.3; private static final double DEFAULT_MARGIN_TOP = 0.75; private static final double DEFAULT_MARGIN_BOTTOM = 0.75; private static final double DEFAULT_MARGIN_LEFT = 0.7; private static final double DEFAULT_MARGIN_RIGHT = 0.7; //TODO make the two variable below private! protected CTSheet sheet; protected CTWorksheet worksheet; private final SortedMap* This process can be relatively slow on large sheets, so this should * normally only be called once per column, at the end of your * processing. *
* You can specify whether the content of merged cells should be considered or ignored. * Default is to ignore merged cells. * * @param column the column index * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column */ @Override public void autoSizeColumn(int column, boolean useMergedCells) { double width = SheetUtil.getColumnWidth(this, column, useMergedCells); if (width != -1) { width *= 256; width += arbitraryExtraWidth; int maxColumnWidth = 255*256; // The maximum column width for an individual cell is 255 characters if (width > maxColumnWidth) { width = maxColumnWidth; } setColumnWidth(column, Math.toIntExact(Math.round(width))); columnHelper.setColBestFit(column, true); } } /** * Set the extra width added to the best-fit column width (default 0.0). ** Only applied to auto-sized columns. *
* @param arbitraryExtraWidth the extra width added to the best-fit column width * @since 5.4.1 */ public void setArbitraryExtraWidth(final double arbitraryExtraWidth) { this.arbitraryExtraWidth = arbitraryExtraWidth; } /** * Get the extra width added to the best-fit column width. ** Only applied to auto-sized columns. *
* @return the extra width added to the best-fit column width * @since 5.4.0 */ public double getArbitraryExtraWidth() { return arbitraryExtraWidth; } /** * Return the sheet's existing drawing, or null if there isn't yet one. * * Use {@link #createDrawingPatriarch()} to get or create * * @return a SpreadsheetML drawing */ @Override public XSSFDrawing getDrawingPatriarch() { CTDrawing ctDrawing = getCTDrawing(); if (ctDrawing != null) { // Search the referenced drawing in the list of the sheet's relations for (RelationPart rp : getRelationParts()){ POIXMLDocumentPart p = rp.getDocumentPart(); if (p instanceof XSSFDrawing) { XSSFDrawing dr = (XSSFDrawing)p; String drId = rp.getRelationship().getId(); if (drId.equals(ctDrawing.getId())){ return dr; } } } LOG.atError().log("Can't find drawing with id={} in the list of the sheet's relationships", ctDrawing.getId()); } return null; } /** * Create a new SpreadsheetML drawing. If this sheet already contains a drawing - return that. * * @return a SpreadsheetML drawing */ @Override public XSSFDrawing createDrawingPatriarch() { XSSFDrawing existingDrawing = getDrawingPatriarch(); if (existingDrawing != null) { return existingDrawing; } // Default drawingNumber = #drawings.size() + 1 int drawingNumber = getPackagePart().getPackage().getPartsByContentType(XSSFRelation.DRAWINGS.getContentType()).size() + 1; drawingNumber = getNextPartNumber(XSSFRelation.DRAWINGS, drawingNumber); RelationPart rp = createRelationship(XSSFRelation.DRAWINGS, getWorkbook().getXssfFactory(), drawingNumber, false); XSSFDrawing drawing = rp.getDocumentPart(); String relId = rp.getRelationship().getId(); //add CT_Drawing element which indicates that this sheet contains drawing components built on the drawingML platform. //The relationship Id references the part containing the drawingML definitions. CTDrawing ctDrawing = worksheet.addNewDrawing(); ctDrawing.setId(relId); // Return the newly created drawing return drawing; } /** * Get VML drawing for this sheet (aka 'legacy' drawing). * * @param autoCreate if true, then a new VML drawing part is created * * @return the VML drawing of {@code null} if the drawing was not found and autoCreate=false */ @Override public XSSFVMLDrawing getVMLDrawing(boolean autoCreate) { if (xssfvmlDrawing == null) { XSSFVMLDrawing drawing = null; CTLegacyDrawing ctDrawing = getCTLegacyDrawing(); if(ctDrawing == null) { if(autoCreate) { int drawingNumber = getNextPartNumber(XSSFRelation.VML_DRAWINGS, getPackagePart().getPackage().getPartsByContentType(XSSFRelation.VML_DRAWINGS.getContentType()).size()); RelationPart rp = createRelationship(XSSFRelation.VML_DRAWINGS, getWorkbook().getXssfFactory(), drawingNumber, false); drawing = rp.getDocumentPart(); String relId = rp.getRelationship().getId(); //add CTLegacyDrawing element which indicates that this sheet contains drawing components built on the drawingML platform. //The relationship Id references the part containing the drawing definitions. ctDrawing = worksheet.addNewLegacyDrawing(); ctDrawing.setId(relId); } } else { //search the referenced drawing in the list of the sheet's relations final String id = ctDrawing.getId(); for (RelationPart rp : getRelationParts()){ POIXMLDocumentPart p = rp.getDocumentPart(); if(p instanceof XSSFVMLDrawing) { XSSFVMLDrawing dr = (XSSFVMLDrawing)p; String drId = rp.getRelationship().getId(); if (drId.equals(id)) { drawing = dr; break; } // do not break here since drawing has not been found yet (see bug 52425) } } if(drawing == null){ LOG.atError().log("Can't find VML drawing with id={} in the list of the sheet's relationships", id); } } xssfvmlDrawing = drawing; } return xssfvmlDrawing; } protected CTDrawing getCTDrawing() { return worksheet.getDrawing(); } protected CTLegacyDrawing getCTLegacyDrawing() { return worksheet.getLegacyDrawing(); } /** * Creates a split (freezepane). Any existing freezepane or split pane is overwritten. * @param colSplit Horizontal position of split. * @param rowSplit Vertical position of split. */ @Override public void createFreezePane(int colSplit, int rowSplit) { createFreezePane( colSplit, rowSplit, colSplit, rowSplit ); } /** * Creates a split (freezepane). Any existing freezepane or split pane is overwritten. * ** If both colSplit and rowSplit are zero then the existing freeze pane is removed *
* * @param colSplit Horizontal position of split. * @param rowSplit Vertical position of split. * @param leftmostColumn Left column visible in right pane. * @param topRow Top row visible in bottom pane */ @Override public void createFreezePane(int colSplit, int rowSplit, int leftmostColumn, int topRow) { final boolean removeSplit = colSplit == 0 && rowSplit == 0; final CTSheetView ctView = getDefaultSheetView(!removeSplit); if (ctView != null) { ctView.setSelectionArray(null); } // If both colSplit and rowSplit are zero then the existing freeze pane is removed if (removeSplit) { if (ctView != null && ctView.isSetPane()) { ctView.unsetPane(); } return; } assert(ctView != null); final CTPane pane = (ctView.isSetPane()) ? ctView.getPane() : ctView.addNewPane(); assert(pane != null); if (colSplit > 0) { pane.setXSplit(colSplit); } else if (pane.isSetXSplit()) { pane.unsetXSplit(); } if (rowSplit > 0) { pane.setYSplit(rowSplit); } else if(pane.isSetYSplit()) { pane.unsetYSplit(); } STPane.Enum activePane = STPane.BOTTOM_RIGHT; int pRow = topRow, pCol = leftmostColumn; if (rowSplit == 0) { pRow = 0; activePane = STPane.TOP_RIGHT; } else if (colSplit == 0) { pCol = 0; activePane = STPane.BOTTOM_LEFT; } pane.setState(STPaneState.FROZEN); pane.setTopLeftCell(new CellReference(pRow, pCol).formatAsString()); pane.setActivePane(activePane); ctView.addNewSelection().setPane(activePane); } /** * Create a new row within the sheet and return the high level representation * * Note: If a row already exists at this position, it is removed/overwritten and * any existing cell is removed! * * @param rownum row number * @return High level {@link XSSFRow} object representing a row in the sheet * @see #removeRow(Row) */ @Override public XSSFRow createRow(int rownum) { // Performance optimization: explicit boxing is slightly faster than auto-unboxing, though may use more memory //noinspection UnnecessaryBoxing final Integer rownumI = Integer.valueOf(rownum); // NOSONAR CTRow ctRow; XSSFRow prev = _rows.get(rownumI); if(prev != null){ // the Cells in an existing row are invalidated on-purpose, in order to clean up correctly, we // need to call the remove, so things like ArrayFormulas and CalculationChain updates are done // correctly. // We remove the cell this way as the internal cell-list is changed by the remove call and // thus would cause ConcurrentModificationException otherwise while(prev.getFirstCellNum() != -1) { prev.removeCell(prev.getCell(prev.getFirstCellNum())); } ctRow = prev.getCTRow(); ctRow.set(CTRow.Factory.newInstance()); } else { if(_rows.isEmpty() || rownum > _rows.lastKey()) { // we can append the new row at the end ctRow = worksheet.getSheetData().addNewRow(); } else { // get number of rows where row index < rownum // --> this tells us where our row should go int idx = _rows.headMap(rownumI).size(); ctRow = worksheet.getSheetData().insertNewRow(idx); } } XSSFRow r = new XSSFRow(ctRow, this); r.setRowNum(rownum); _rows.put(rownumI, r); return r; } /** * Creates a split pane. Any existing freezepane or split pane is overwritten. * @param xSplitPos Horizontal position of split (in 1/20th of a point). * @param ySplitPos Vertical position of split (in 1/20th of a point). * @param topRow Top row visible in bottom pane * @param leftmostColumn Left column visible in right pane. * @param activePane Active pane. One of: PANE_LOWER_RIGHT, * PANE_UPPER_RIGHT, PANE_LOWER_LEFT, PANE_UPPER_LEFT (but there is a * bug, so add 1) * @see #PANE_LOWER_LEFT * @see #PANE_LOWER_RIGHT * @see #PANE_UPPER_LEFT * @see #PANE_UPPER_RIGHT * @deprecated use {@link #createSplitPane(int, int, int, int, PaneType)} */ @Override @Deprecated @Removal(version = "7.0.0") public void createSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, int activePane) { createFreezePane(xSplitPos, ySplitPos, leftmostColumn, topRow); if (xSplitPos > 0 || ySplitPos > 0) { final CTPane pane = getPane(true); pane.setState(STPaneState.SPLIT); pane.setActivePane(STPane.Enum.forInt(activePane)); } } /** * Creates a split pane. Any existing freezepane or split pane is overwritten. * @param xSplitPos Horizontal position of split (in 1/20th of a point). * @param ySplitPos Vertical position of split (in 1/20th of a point). * @param topRow Top row visible in bottom pane * @param leftmostColumn Left column visible in right pane. * @param activePane Active pane. * @see PaneType * @since POI 5.2.3 */ @Override public void createSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, PaneType activePane) { createFreezePane(xSplitPos, ySplitPos, leftmostColumn, topRow); if (xSplitPos > 0 || ySplitPos > 0) { final CTPane pane = getPane(true); pane.setState(STPaneState.SPLIT); STPane.Enum stPaneEnum; switch (activePane) { case LOWER_RIGHT: stPaneEnum = STPane.BOTTOM_RIGHT; break; case UPPER_RIGHT: stPaneEnum = STPane.TOP_RIGHT; break; case LOWER_LEFT: stPaneEnum = STPane.BOTTOM_LEFT; break; case UPPER_LEFT: default: stPaneEnum = STPane.TOP_LEFT; break; } pane.setActivePane(stPaneEnum); } } /** * Return cell comment at row, column, if one exists. Otherwise returns null. * * @param address the location of the cell comment * @return the cell comment, if one exists. Otherwise, return null. */ @Override public XSSFComment getCellComment(CellAddress address) { if (sheetComments == null) { return null; } return sheetComments.findCellComment(address); } /** * Returns all cell comments on this sheet. * @return A map of each Comment in the sheet, keyed on the cell address where * the comment is located. */ @Override public Map* Note, the returned value is always greater that {@link #getDefaultColumnWidth()} because the latter does not include margins. * Actual column width measured as the number of characters of the maximum digit width of the * numbers 0, 1, 2, ..., 9 as rendered in the normal style's font. There are 4 pixels of margin * padding (two on each side), plus 1 pixel padding for the gridlines. *
* * @param columnIndex - the column to set (0-based) * @return width - the width in units of 1/256th of a character width */ @Override public int getColumnWidth(int columnIndex) { CTCol col = columnHelper.getColumn(columnIndex, false); double width = col == null || !col.isSetWidth() ? getDefaultColumnWidth() : col.getWidth(); return Math.toIntExact(Math.round(width*256)); } /** * Get the actual column width in pixels * ** Please note, that this method works correctly only for workbooks * with the default font size (Calibri 11pt for .xlsx). *
*/ @Override public float getColumnWidthInPixels(int columnIndex) { float widthIn256 = getColumnWidth(columnIndex); return (float)(widthIn256/256.0*Units.DEFAULT_CHARACTER_WIDTH); } /** * Get the default column width for the sheet (if the columns do not define their own width) in * characters. ** Note, this value is different from {@link #getColumnWidth(int)}. The latter is always greater and includes * 4 pixels of margin padding (two on each side), plus 1 pixel padding for the gridlines. *
* @return column width, default value is 8 */ @Override public int getDefaultColumnWidth() { CTSheetFormatPr pr = worksheet.getSheetFormatPr(); return pr == null ? 8 : Math.toIntExact(pr.getBaseColWidth()); } /** * Get the default row height for the sheet (if the rows do not define their own height) in * twips (1/20 of a point) * * @return default row height */ @Override public short getDefaultRowHeight() { return (short)(getDefaultRowHeightInPoints() * Font.TWIPS_PER_POINT); } /** * Get the default row height for the sheet measured in point size (if the rows do not define their own height). * * @return default row height in points */ @Override public float getDefaultRowHeightInPoints() { CTSheetFormatPr pr = worksheet.getSheetFormatPr(); return (float)(pr == null ? 0 : pr.getDefaultRowHeight()); } private CTSheetFormatPr getSheetTypeSheetFormatPr(final boolean createIfNotExists) { if (worksheet.isSetSheetFormatPr()) { return worksheet.getSheetFormatPr(); } return createIfNotExists ? worksheet.addNewSheetFormatPr() : null; } /** * Returns the CellStyle that applies to the given * (0 based) column, or null if no style has been * set for that column */ @Override public CellStyle getColumnStyle(int column) { int idx = columnHelper.getColDefaultStyle(column); return getWorkbook().getCellStyleAt(idx == -1 ? 0 : idx); } /** * Sets whether the worksheet is displayed from right to left instead of from left to right. * * @param value true for right to left, false otherwise. */ @Override public void setRightToLeft(boolean value) { final CTSheetView dsv = getDefaultSheetView(true); assert(dsv != null); dsv.setRightToLeft(value); } /** * Whether the text is displayed in right-to-left mode in the window * * @return whether the text is displayed in right-to-left mode in the window */ @Override public boolean isRightToLeft() { final CTSheetView dsv = getDefaultSheetView(false); return (dsv != null && dsv.getRightToLeft()); } /** * Get whether to display the guts or not, * default value is true * * @return boolean - guts or no guts */ @Override public boolean getDisplayGuts() { CTSheetPr sheetPr = getSheetTypeSheetPr(); CTOutlinePr outlinePr = sheetPr.getOutlinePr() == null ? CTOutlinePr.Factory.newInstance() : sheetPr.getOutlinePr(); return outlinePr.getShowOutlineSymbols(); } /** * Set whether to display the guts or not * * @param value - guts or no guts */ @Override public void setDisplayGuts(boolean value) { CTSheetPr sheetPr = getSheetTypeSheetPr(); CTOutlinePr outlinePr = sheetPr.getOutlinePr() == null ? sheetPr.addNewOutlinePr() : sheetPr.getOutlinePr(); outlinePr.setShowOutlineSymbols(value); } /** * Gets the flag indicating whether the window should show 0 (zero) in cells containing zero value. * When false, cells with zero value appear blank instead of showing the number zero. * * @return whether all zero values on the worksheet are displayed (defaults to true) */ @Override public boolean isDisplayZeros(){ final CTSheetView dsv = getDefaultSheetView(false); return (dsv == null) || dsv.getShowZeros(); } /** * Set whether the window should show 0 (zero) in cells containing zero value. * When false, cells with zero value appear blank instead of showing the number zero. * * @param value whether to display or hide all zero values on the worksheet */ @Override public void setDisplayZeros(boolean value){ final CTSheetView view = getDefaultSheetView(true); assert(view != null); view.setShowZeros(value); } /** * Gets the first row on the sheet * * @return the number of the first logical row on the sheet, zero based */ @Override public int getFirstRowNum() { return _rows.isEmpty() ? -1 : _rows.firstKey(); } /** * Flag indicating whether the Fit to Page print option is enabled. * * @return {@code true} */ @Override public boolean getFitToPage() { CTSheetPr sheetPr = getSheetTypeSheetPr(); CTPageSetUpPr psSetup = (sheetPr == null || !sheetPr.isSetPageSetUpPr()) ? CTPageSetUpPr.Factory.newInstance() : sheetPr.getPageSetUpPr(); return psSetup.getFitToPage(); } private CTSheetPr getSheetTypeSheetPr() { if (worksheet.getSheetPr() == null) { worksheet.setSheetPr(CTSheetPr.Factory.newInstance()); } return worksheet.getSheetPr(); } private CTHeaderFooter getSheetTypeHeaderFooter() { if (worksheet.getHeaderFooter() == null) { worksheet.setHeaderFooter(CTHeaderFooter.Factory.newInstance()); } return worksheet.getHeaderFooter(); } /** * Returns the default footer for the sheet, * creating one as needed. * You may also want to look at * {@link #getFirstFooter()}, * {@link #getOddFooter()} and * {@link #getEvenFooter()} */ @Override public Footer getFooter() { // The default footer is an odd footer return getOddFooter(); } /** * Returns the default header for the sheet, * creating one as needed. * You may also want to look at * {@link #getFirstHeader()}, * {@link #getOddHeader()} and * {@link #getEvenHeader()} */ @Override public Header getHeader() { // The default header is an odd header return getOddHeader(); } /** * Returns the odd footer. Used on all pages unless * other footers also present, when used on only * odd pages. */ public Footer getOddFooter() { return new XSSFOddFooter(getSheetTypeHeaderFooter()); } /** * Returns the even footer. Not there by default, but * when set, used on even pages. */ public Footer getEvenFooter() { return new XSSFEvenFooter(getSheetTypeHeaderFooter()); } /** * Returns the first page footer. Not there by * default, but when set, used on the first page. */ public Footer getFirstFooter() { return new XSSFFirstFooter(getSheetTypeHeaderFooter()); } /** * Returns the odd header. Used on all pages unless * other headers also present, when used on only * odd pages. */ public Header getOddHeader() { return new XSSFOddHeader(getSheetTypeHeaderFooter()); } /** * Returns the even header. Not there by default, but * when set, used on even pages. */ public Header getEvenHeader() { return new XSSFEvenHeader(getSheetTypeHeaderFooter()); } /** * Returns the first page header. Not there by * default, but when set, used on the first page. */ public Header getFirstHeader() { return new XSSFFirstHeader(getSheetTypeHeaderFooter()); } /** * Determine whether printed output for this sheet will be horizontally centered. */ @Override public boolean getHorizontallyCenter() { CTPrintOptions opts = worksheet.getPrintOptions(); return opts != null && opts.getHorizontalCentered(); } @Override public int getLastRowNum() { // _rows.getLastKey() (O(logN)) or caching last row (O(1))? // A test with 1_000_000 rows shows that querying getLastRowNum with lastKey() implementation takes ~40 ms, // and ~1.2 ms with cached implementation. 40 ms is negligible compared to the time of evaluation a million // cells, and the lastKey implementation is much more elegant and less error prone than caching. return _rows.isEmpty() ? -1 : _rows.lastKey(); } @Override public short getLeftCol() { String cellRef = worksheet.getSheetViews().getSheetViewArray(0).getTopLeftCell(); if(cellRef == null) { return 0; } CellReference cellReference = new CellReference(cellRef); return cellReference.getCol(); } /** * Gets the size of the margin in inches. * * @param margin which margin to get * @return the size of the margin * @see Sheet#LeftMargin * @see Sheet#RightMargin * @see Sheet#TopMargin * @see Sheet#BottomMargin * @see Sheet#HeaderMargin * @see Sheet#FooterMargin * @deprecated use {@link #getMargin(PageMargin)} */ @Override @Deprecated @Removal(version = "7.0.0") public double getMargin(short margin) { return getMargin(PageMargin.getByShortValue(margin)); } /** * Gets the size of the margin in inches. * * @param margin which margin to get * @return the size of the margin * @since POI 5.2.3 */ @Override public double getMargin(PageMargin margin) { if (!worksheet.isSetPageMargins()) { return 0; } CTPageMargins pageMargins = worksheet.getPageMargins(); switch (margin) { case LEFT: return pageMargins.getLeft(); case RIGHT: return pageMargins.getRight(); case TOP: return pageMargins.getTop(); case BOTTOM: return pageMargins.getBottom(); case HEADER: return pageMargins.getHeader(); case FOOTER: return pageMargins.getFooter(); default : throw new IllegalArgumentException("Unknown margin constant: " + margin); } } /** * Sets the size of the margin in inches. * * @param margin which margin to set * @param size the size of the margin * @see Sheet#LeftMargin * @see Sheet#RightMargin * @see Sheet#TopMargin * @see Sheet#BottomMargin * @see Sheet#HeaderMargin * @see Sheet#FooterMargin * @deprecated use {@link #setMargin(PageMargin, double)} instead */ @Override @Deprecated @Removal(version = "7.0.0") public void setMargin(short margin, double size) { final PageMargin pageMargin = PageMargin.getByShortValue(margin); if (pageMargin == null) { throw new IllegalArgumentException( "Unknown margin constant: " + margin ); } setMargin(pageMargin, size); } /** * Sets the size of the margin in inches. * * @param margin which margin to set * @param size the size of the margin * @since POI 5.2.3 */ @Override public void setMargin(PageMargin margin, double size) { CTPageMargins pageMargins = worksheet.isSetPageMargins() ? worksheet.getPageMargins() : worksheet.addNewPageMargins(); switch (margin) { case LEFT: pageMargins.setLeft(size); break; case RIGHT: pageMargins.setRight(size); break; case TOP: pageMargins.setTop(size); break; case BOTTOM: pageMargins.setBottom(size); break; case HEADER: pageMargins.setHeader(size); break; case FOOTER: pageMargins.setFooter(size); break; default: throw new IllegalArgumentException( "Unknown margin constant: " + margin ); } } /** * Returns the merged region at the specified index. If you want multiple * regions, it is faster to call {@link #getMergedRegions()} than to call * this each time. * * @return the merged region at the specified index */ @Override public CellRangeAddress getMergedRegion(int index) { CTMergeCells ctMergeCells = worksheet.getMergeCells(); if(ctMergeCells == null) { throw new IllegalStateException("This worksheet does not contain merged regions"); } CTMergeCell ctMergeCell = ctMergeCells.getMergeCellArray(index); String ref = ctMergeCell.getRef(); return CellRangeAddress.valueOf(ref); } /** * Returns the list of merged regions. If you want multiple regions, this is * faster than calling {@link #getMergedRegion(int)} each time. * * @return the list of merged regions */ @Override public List* When true a summary row is inserted below the detailed data being summarized and a * new outline level is established on that row. *
** When false a summary row is inserted above the detailed data being summarized and a new outline level * is established on that row. *
* @return {@code true} if row summaries appear below detail in the outline */ @Override public boolean getRowSumsBelow() { CTSheetPr sheetPr = worksheet.getSheetPr(); CTOutlinePr outlinePr = (sheetPr != null && sheetPr.isSetOutlinePr()) ? sheetPr.getOutlinePr() : null; return outlinePr == null || outlinePr.getSummaryBelow(); } /** * Flag indicating whether summary rows appear below detail in an outline, when applying an outline. * ** When true a summary row is inserted below the detailed data being summarized and a * new outline level is established on that row. *
** When false a summary row is inserted above the detailed data being summarized and a new outline level * is established on that row. *
* @param value {@code true} if row summaries appear below detail in the outline */ @Override public void setRowSumsBelow(boolean value) { ensureOutlinePr().setSummaryBelow(value); } /** * Flag indicating whether summary columns appear to the right of detail in an outline, when applying an outline. * ** When true a summary column is inserted to the right of the detailed data being summarized * and a new outline level is established on that column. *
** When false a summary column is inserted to the left of the detailed data being * summarized and a new outline level is established on that column. *
* @return {@code true} if col summaries appear right of the detail in the outline */ @Override public boolean getRowSumsRight() { CTSheetPr sheetPr = worksheet.getSheetPr(); CTOutlinePr outlinePr = (sheetPr != null && sheetPr.isSetOutlinePr()) ? sheetPr.getOutlinePr() : CTOutlinePr.Factory.newInstance(); return outlinePr.getSummaryRight(); } /** * Flag indicating whether summary columns appear to the right of detail in an outline, when applying an outline. * ** When true a summary column is inserted to the right of the detailed data being summarized * and a new outline level is established on that column. *
** When false a summary column is inserted to the left of the detailed data being * summarized and a new outline level is established on that column. *
* @param value {@code true} if col summaries appear right of the detail in the outline */ @Override public void setRowSumsRight(boolean value) { ensureOutlinePr().setSummaryRight(value); } /** * Ensure CTWorksheet.CTSheetPr.CTOutlinePr */ private CTOutlinePr ensureOutlinePr(){ CTSheetPr sheetPr = worksheet.isSetSheetPr() ? worksheet.getSheetPr() : worksheet.addNewSheetPr(); return sheetPr.isSetOutlinePr() ? sheetPr.getOutlinePr() : sheetPr.addNewOutlinePr(); } /** * A flag indicating whether scenarios are locked when the sheet is protected. * * @return true => protection enabled; false => protection disabled */ @Override public boolean getScenarioProtect() { return worksheet.isSetSheetProtection() && worksheet.getSheetProtection().getScenarios(); } /** * The top row in the visible view when the sheet is * first viewed after opening it in a viewer * * @return integer indicating the rownum (0 based) of the top row */ @Override public short getTopRow() { final CTSheetView dsv = getDefaultSheetView(false); final String cellRef = (dsv == null) ? null : dsv.getTopLeftCell(); if(cellRef == null) { return 0; } return (short) new CellReference(cellRef).getRow(); } /** * Determine whether printed output for this sheet will be vertically centered. * * @return whether printed output for this sheet will be vertically centered. */ @Override public boolean getVerticallyCenter() { CTPrintOptions opts = worksheet.getPrintOptions(); return opts != null && opts.getVerticalCentered(); } /** * Group between (0 based) columns */ @Override public void groupColumn(int fromColumn, int toColumn) { groupColumn1Based(fromColumn+1, toColumn+1); } private void groupColumn1Based(int fromColumn, int toColumn) { CTCols ctCols=worksheet.getColsArray(0); CTCol ctCol=CTCol.Factory.newInstance(); // copy attributes, as they might be removed by merging with the new column // TODO: check if this fix is really necessary or if the sweeping algorithm // in addCleanColIntoCols needs to be adapted ... CTCol fixCol_before = this.columnHelper.getColumn1Based(toColumn, false); if (fixCol_before != null) { fixCol_before = (CTCol)fixCol_before.copy(); } ctCol.setMin(fromColumn); ctCol.setMax(toColumn); this.columnHelper.addCleanColIntoCols(ctCols, ctCol); CTCol fixCol_after = this.columnHelper.getColumn1Based(toColumn, false); if (fixCol_before != null && fixCol_after != null) { this.columnHelper.setColumnAttributes(fixCol_before, fixCol_after); } int maxLevelCol = -1; for(int index = fromColumn; index <= toColumn; index++){ CTCol col = columnHelper.getColumn1Based(index, false); //col must exist final short outlineLevel = col.getOutlineLevel(); final int newOutlineLevel = outlineLevel + 1; col.setOutlineLevel((short) newOutlineLevel); maxLevelCol = Math.max(maxLevelCol, newOutlineLevel); index = Math.toIntExact(col.getMax()); } worksheet.setColsArray(0, ctCols); increaseSheetFormatPrOutlineLevelColIfNecessary((short) Math.min(Short.MAX_VALUE, maxLevelCol)); } /** * Do not leave the width attribute undefined (see #52186). */ private void setColWidthAttribute(CTCols ctCols) { for (CTCol col : ctCols.getColArray()) { if (!col.isSetWidth()) { col.setWidth(getDefaultColumnWidth()); col.setCustomWidth(false); } } } /** * Tie a range of cell together so that they can be collapsed or expanded * * @param fromRow start row (0-based) * @param toRow end row (0-based) */ @Override public void groupRow(int fromRow, int toRow) { int maxOutlineLevel = -1; for (int i = fromRow; i <= toRow; i++) { XSSFRow xrow = getRow(i); if (xrow == null) { xrow = createRow(i); } CTRow ctrow = xrow.getCTRow(); final short outlineLevel = ctrow.getOutlineLevel(); final int newOutlineLevel = outlineLevel + 1; maxOutlineLevel = Math.max(maxOutlineLevel, newOutlineLevel); ctrow.setOutlineLevel((short) newOutlineLevel); } increaseSheetFormatPrOutlineLevelRowIfNecessary((short) Math.min(Short.MAX_VALUE, maxOutlineLevel)); } private short getMaxOutlineLevelRows(){ int outlineLevel = 0; for (XSSFRow xrow : _rows.values()) { outlineLevel = Math.max(outlineLevel, xrow.getCTRow().getOutlineLevel()); } return (short) outlineLevel; } private short getMaxOutlineLevelCols() { CTCols ctCols = worksheet.getColsArray(0); int outlineLevel = 0; for (CTCol col : ctCols.getColArray()) { outlineLevel = Math.max(outlineLevel, col.getOutlineLevel()); } return (short) outlineLevel; } /** * Determines if there is a page break at the indicated column */ @Override public boolean isColumnBroken(int column) { for (int colBreak : getColumnBreaks()) { if (colBreak == column) { return true; } } return false; } /** * Get the hidden state for a given column. * * @param columnIndex - the column to set (0-based) * @return hidden - {@code false} if the column is visible */ @Override public boolean isColumnHidden(int columnIndex) { CTCol col = columnHelper.getColumn(columnIndex, false); return col != null && col.getHidden(); } /** * Gets the flag indicating whether this sheet should display formulas. * * @return {@code true} if this sheet should display formulas. */ @Override public boolean isDisplayFormulas() { final CTSheetView dsv = getDefaultSheetView(false); return dsv != null && dsv.getShowFormulas(); } /** * Gets the flag indicating whether this sheet displays the lines * between rows and columns to make editing and reading easier. * * @return {@code true} (default) if this sheet displays gridlines. * @see #isPrintGridlines() to check if printing of gridlines is turned on or off */ @Override public boolean isDisplayGridlines() { final CTSheetView dsv = getDefaultSheetView(false); return (dsv == null) || dsv.getShowGridLines(); } /** * Sets the flag indicating whether this sheet should display the lines * between rows and columns to make editing and reading easier. * To turn printing of gridlines use {@link #setPrintGridlines(boolean)} * * * @param show {@code true} if this sheet should display gridlines. * @see #setPrintGridlines(boolean) */ @Override public void setDisplayGridlines(boolean show) { final CTSheetView dsv = getDefaultSheetView(true); assert(dsv != null); dsv.setShowGridLines(show); } /** * Gets the flag indicating whether this sheet should display row and column headings. ** Row heading are the row numbers to the side of the sheet *
** Column heading are the letters or numbers that appear above the columns of the sheet *
* * @return {@code true} (default) if this sheet should display row and column headings. */ @Override public boolean isDisplayRowColHeadings() { final CTSheetView dsv = getDefaultSheetView(false); return (dsv == null) || dsv.getShowRowColHeaders(); } /** * Sets the flag indicating whether this sheet should display row and column headings. ** Row heading are the row numbers to the side of the sheet *
** Column heading are the letters or numbers that appear above the columns of the sheet *
* * @param show {@code true} if this sheet should display row and column headings. */ @Override public void setDisplayRowColHeadings(boolean show) { final CTSheetView dsv = getDefaultSheetView(true); assert(dsv != null); dsv.setShowRowColHeaders(show); } /** * Returns whether gridlines are printed. * * @return whether gridlines are printed */ @Override public boolean isPrintGridlines() { CTPrintOptions opts = worksheet.getPrintOptions(); return opts != null && opts.getGridLines(); } /** * Turns on or off the printing of gridlines. * * @param value boolean to turn on or off the printing of gridlines */ @Override public void setPrintGridlines(boolean value) { CTPrintOptions opts = worksheet.isSetPrintOptions() ? worksheet.getPrintOptions() : worksheet.addNewPrintOptions(); opts.setGridLines(value); } /** * Returns whether row and column headings are printed. * * @return whether row and column headings are printed */ @Override public boolean isPrintRowAndColumnHeadings() { CTPrintOptions opts = worksheet.getPrintOptions(); return opts != null && opts.getHeadings(); } /** * Turns on or off the printing of row and column headings. * * @param value boolean to turn on or off the printing of row and column headings */ @Override public void setPrintRowAndColumnHeadings(boolean value) { CTPrintOptions opts = worksheet.isSetPrintOptions() ? worksheet.getPrintOptions() : worksheet.addNewPrintOptions(); opts.setHeadings(value); } /** * Tests if there is a page break at the indicated row * * @param row index of the row to test * @return {@code true} if there is a page break at the indicated row */ @Override public boolean isRowBroken(int row) { for (int rowBreak : getRowBreaks()) { if (rowBreak == row) { return true; } } return false; } private void setBreak(int id, CTPageBreak ctPgBreak, int lastIndex) { CTBreak brk = ctPgBreak.addNewBrk(); // this is id of the element which is 1-based:* Calculating the formula values with {@link FormulaEvaluator} is the * recommended solution, but this may be used for certain cases where * evaluation in POI is not possible. *
* ** It is recommended to force recalcuation of formulas on workbook level using * {@link Workbook#setForceFormulaRecalculation(boolean)} * to ensure that all cross-worksheet formuals and external dependencies are updated. *
* @param value true if the application will perform a full recalculation of * this worksheet values when the workbook is opened * * @see Workbook#setForceFormulaRecalculation(boolean) */ @Override public void setForceFormulaRecalculation(boolean value) { CTCalcPr calcPr = getWorkbook().getCTWorkbook().getCalcPr(); if(worksheet.isSetSheetCalcPr()) { // Change the current setting CTSheetCalcPr calc = worksheet.getSheetCalcPr(); calc.setFullCalcOnLoad(value); } else if(value) { // Add the Calc block and set it CTSheetCalcPr calc = worksheet.addNewSheetCalcPr(); calc.setFullCalcOnLoad(value); } if(value && calcPr != null && calcPr.getCalcMode() == STCalcMode.MANUAL) { calcPr.setCalcMode(STCalcMode.AUTO); } } /** * Whether Excel will be asked to recalculate all formulas of this sheet * when the workbook is opened. * * Note: This just returns if the sheet has the recalculate flag set and * will still return false even if recalculation is enabled on workbook-level. * * @return true if the Sheet has the recalculate-flag set. */ @Override public boolean getForceFormulaRecalculation() { if(worksheet.isSetSheetCalcPr()) { CTSheetCalcPr calc = worksheet.getSheetCalcPr(); return calc.getFullCalcOnLoad(); } return false; } /** * @return an iterator of the PHYSICAL rows. Meaning the 3rd element may not * be the third row if say for instance the second row is undefined. * Call getRowNum() on each row if you care which one it is. */ @Override @SuppressWarnings("unchecked") public Iterator* The maximum column width for an individual cell is 255 characters. * This value represents the number of characters that can be displayed * in a cell that is formatted with the standard font (first font in the workbook). *
* *
* Character width is defined as the maximum digit width
* of the numbers {@code 0, 1, 2, ... 9} as rendered
* using the default font (first font in the workbook).
*
* Unless you are using a very special font, the default character is '0' (zero),
* this is true for Arial (default font in HSSF) and Calibri (default font in XSSF)
*
* Please note, that the width set by this method includes 4 pixels of margin padding (two on each side), * plus 1 pixel padding for the gridlines (Section 3.3.1.12 of the OOXML spec). * This results is a slightly less value of visible characters than passed to this method (approx. 1/2 of a character). *
** To compute the actual number of visible characters, * Excel uses the following formula (Section 3.3.1.12 of the OOXML spec): *
* {@code * width = Truncate([{Number of Visible Characters} * * {Maximum Digit Width} + {5 pixel padding}]/{Maximum Digit Width}*256)/256 * } *Using the Calibri font as an example, the maximum digit width of 11 point font size is 7 pixels (at 96 dpi).
* If you set a column width to be eight characters wide, e.g. {@code setColumnWidth(columnIndex, 8*256)},
* then the actual value of visible characters (the value shown in Excel) is derived from the following equation:
* {@code
Truncate([numChars*7+5]/7*256)/256 = 8;
* }
*
* which gives {@code 7.29}.
*
* @param columnIndex - the column to set (0-based)
* @param width - the width in units of 1/256th of a character width
* @throws IllegalArgumentException if width > 255*256 (the maximum column width in Excel is 255 characters)
*/
@Override
public void setColumnWidth(int columnIndex, int width) {
if(width > 255*256) {
throw new IllegalArgumentException("The maximum column width for an individual cell is 255 characters.");
}
columnHelper.setColWidth(columnIndex, (double)width/256);
columnHelper.setCustomWidth(columnIndex, true);
}
@Override
public void setDefaultColumnStyle(int column, CellStyle style) {
columnHelper.setColDefaultStyle(column, style);
}
/**
* Specifies the number of characters of the maximum digit width of the normal style's font.
* This value does not include margin padding or extra padding for gridlines. It is only the
* number of characters.
*
* @param width the number of characters. Default value is {@code 8}.
*/
@Override
public void setDefaultColumnWidth(int width) {
getSheetTypeSheetFormatPr(true).setBaseColWidth(width);
}
/**
* Set the default row height for the sheet (if the rows do not define their own height) in
* twips (1/20 of a point)
*
* @param height default row height in twips (1/20 of a point)
*/
@Override
public void setDefaultRowHeight(short height) {
setDefaultRowHeightInPoints((float)height / Font.TWIPS_PER_POINT);
}
/**
* Sets default row height measured in point size.
*
* @param height default row height measured in point size.
*/
@Override
public void setDefaultRowHeightInPoints(float height) {
CTSheetFormatPr pr = getSheetTypeSheetFormatPr(true);
pr.setDefaultRowHeight(height);
pr.setCustomHeight(true);
}
/**
* Sets the flag indicating whether this sheet should display formulas.
*
* @param show {@code true} if this sheet should display formulas.
*/
@Override
public void setDisplayFormulas(boolean show) {
final CTSheetView dsv = getDefaultSheetView(true);
assert(dsv != null);
dsv.setShowFormulas(show);
}
/**
* Flag indicating whether the Fit to Page print option is enabled.
*
* @param b {@code true} if the Fit to Page print option is enabled.
*/
@Override
public void setFitToPage(boolean b) {
getSheetTypePageSetUpPr().setFitToPage(b);
}
/**
* Center on page horizontally when printing.
*
* @param value whether to center on page horizontally when printing.
*/
@Override
public void setHorizontallyCenter(boolean value) {
CTPrintOptions opts = worksheet.isSetPrintOptions() ?
worksheet.getPrintOptions() : worksheet.addNewPrintOptions();
opts.setHorizontalCentered(value);
}
/**
* Whether the output is vertically centered on the page.
*
* @param value true to vertically center, false otherwise.
*/
@Override
public void setVerticallyCenter(boolean value) {
CTPrintOptions opts = worksheet.isSetPrintOptions() ?
worksheet.getPrintOptions() : worksheet.addNewPrintOptions();
opts.setVerticalCentered(value);
}
/**
* group the row It is possible for collapsed to be false and yet still have
* the rows in question hidden. This can be achieved by having a lower
* outline level collapsed, thus hiding all the child rows. Note that in
* this case, if the lowest level were expanded, the middle level would
* remain collapsed.
*
* @param rowIndex -
* the row involved, 0 based
* @param collapse -
* boolean value for collapse
*/
@Override
public void setRowGroupCollapsed(int rowIndex, boolean collapse) {
if (collapse) {
collapseRow(rowIndex);
} else {
expandRow(rowIndex);
}
}
/**
* @param rowIndex the zero based row index to collapse
*/
private void collapseRow(int rowIndex) {
XSSFRow row = getRow(rowIndex);
if (row != null) {
int startRow = findStartOfRowOutlineGroup(rowIndex);
// Hide all the columns until the end of the group
int lastRow = writeHidden(row, startRow, true);
if (getRow(lastRow) != null) {
getRow(lastRow).getCTRow().setCollapsed(true);
} else {
XSSFRow newRow = createRow(lastRow);
newRow.getCTRow().setCollapsed(true);
}
}
}
/**
* @param rowIndex the zero based row index to find from
*/
private int findStartOfRowOutlineGroup(int rowIndex) {
// Find the start of the group.
short level = getRow(rowIndex).getCTRow().getOutlineLevel();
int currentRow = rowIndex;
while (getRow(currentRow) != null) {
if (getRow(currentRow).getCTRow().getOutlineLevel() < level) {
return currentRow + 1;
}
currentRow--;
}
return currentRow;
}
private int writeHidden(XSSFRow xRow, int rowIndex, boolean hidden) {
short level = xRow.getCTRow().getOutlineLevel();
for (Iterator
* Additionally, shifts merged regions that are completely defined in these
* rows (i.e. merged 2 cells on a row to be shifted).
* @param startRow the row to start shifting
* @param endRow the row to end shifting
* @param n the number of rows to shift
*/
@Override
public void shiftRows(int startRow, int endRow, int n) {
shiftRows(startRow, endRow, n, false, false);
}
/**
* Shifts rows between startRow and endRow n number of rows.
* If you use a negative number, it will shift rows up.
* Code ensures that rows don't wrap around
*
*
* Additionally, shifts merged regions that are completely defined in these
* rows (i.e. merged 2 cells on a row to be shifted). All merged regions that are
* completely overlaid by shifting will be deleted.
*
* @param startRow the row to start shifting
* @param endRow the row to end shifting
* @param n the number of rows to shift
* @param copyRowHeight whether to copy the row height during the shift
* @param resetOriginalRowHeight whether to set the original row's height to the default
*/
@Override
public void shiftRows(int startRow, int endRow, final int n, boolean copyRowHeight, boolean resetOriginalRowHeight) {
List
* When only 1 sheet is selected and active, this value should be in synch with the activeTab value.
* In case of a conflict, the Start Part setting wins and sets the active sheet tab.
*
* When only 1 sheet is selected and active, this value should be in synch with the activeTab value.
* In case of a conflict, the Start Part setting wins and sets the active sheet tab.
*
* The table is assigned a default display name (since 4.1.1) which can be overridden
* by calling {@code setDisplayName}. The default display name is guaranteed to not conflict
* with the names of any {@code XSSFName} or {@code XSSFTable} in the workbook.
*
* @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();
}
CTTableParts tblParts = worksheet.getTableParts();
CTTablePart tbl = tblParts.addNewTablePart();
// Table numbers need to be unique in the file, not just
// unique within the sheet. Find the next one
int tableNumber = getPackagePart().getPackage().getPartsByContentType(XSSFRelation.TABLE.getContentType()).size() + 1;
// the id could already be taken after insertion/deletion of different tables
boolean loop = true;
while(loop) {
loop = false;
for (PackagePart packagePart : getPackagePart().getPackage().getPartsByContentType(XSSFRelation.TABLE.getContentType())) {
String fileName = XSSFRelation.TABLE.getFileName(tableNumber);
if(fileName.equals(packagePart.getPartName().getName())) {
// duplicate found, increase the number and start iterating again
tableNumber++;
loop = true;
}
}
}
RelationPart rp = createRelationship(XSSFRelation.TABLE, getWorkbook().getXssfFactory(), tableNumber, false);
XSSFTable table = rp.getDocumentPart();
tbl.setId(rp.getRelationship().getId());
table.getCTTable().setId(tableNumber);
tables.put(tbl.getId(), table);
if(tableArea != null && table.supportsAreaReference(tableArea)) {
table.setArea(tableArea);
}
// Set the default name of the table. This must not conflict with any defined names.
while(tableNumber
* 10 - 10%
* 20 - 20%
* ...
* 100 - 100%
* ...
* 400 - 400%
*
*
* Current view can be Normal, Page Layout, or Page Break Preview.
*
* @param scale window zoom magnification
* @throws IllegalArgumentException if scale is invalid
*/
@Override
public void setZoom(int scale) {
if (scale < 10 || scale > 400) {
throw new IllegalArgumentException("Valid scale values range from 10 to 400");
}
final CTSheetView dsv = getDefaultSheetView(true);
assert(dsv != null);
dsv.setZoomScale(scale);
}
/**
* copyRows rows from srcRows to this sheet starting at destStartRow
*
* Additionally copies merged regions that are completely defined in these
* rows (ie. merged 2 cells on a row to be shifted).
* @param srcRows the rows to copy. Formulas will be offset by the difference
* in the row number of the first row in srcRows and destStartRow (even if srcRows
* are from a different sheet).
* @param destStartRow the row in this sheet to paste the first row of srcRows
* the remainder of srcRows will be pasted below destStartRow per the cell copy policy
* @param policy is the cell copy policy, which can be used to merge the source and destination
* when the source is blank, copy styles only, paste as value, etc
*/
@Beta
public void copyRows(List extends Row> srcRows, int destStartRow, CellCopyPolicy policy) {
copyRows(srcRows, destStartRow, policy, new CellCopyContext());
}
/**
* copyRows rows from srcRows to this sheet starting at destStartRow
*
* Additionally copies merged regions that are completely defined in these
* rows (ie. merged 2 cells on a row to be shifted).
* @param srcRows the rows to copy. Formulas will be offset by the difference
* in the row number of the first row in srcRows and destStartRow (even if srcRows
* are from a different sheet).
* @param destStartRow the row in this sheet to paste the first row of srcRows
* the remainder of srcRows will be pasted below destStartRow per the cell copy policy
* @param policy is the cell copy policy, which can be used to merge the source and destination
* when the source is blank, copy styles only, paste as value, etc
* @param context the context - see {@link CellCopyContext}
* @since POI 5.4.1
*/
@Beta
public void copyRows(List extends Row> srcRows, int destStartRow,
CellCopyPolicy policy, CellCopyContext context) {
if (srcRows == null || srcRows.isEmpty()) {
throw new IllegalArgumentException("No rows to copy");
}
final Row srcStartRow = srcRows.get(0);
final Row srcEndRow = srcRows.get(srcRows.size() - 1);
if (srcStartRow == null) {
throw new IllegalArgumentException("copyRows: First row cannot be null");
}
final int srcStartRowNum = srcStartRow.getRowNum();
final int srcEndRowNum = srcEndRow.getRowNum();
// check row numbers to make sure they are continuous and increasing (monotonic)
// and srcRows does not contain null rows
final int size = srcRows.size();
for (int index=1; index < size; index++) {
final Row curRow = srcRows.get(index);
if (curRow == null) {
throw new IllegalArgumentException("srcRows may not contain null rows. Found null row at index " + index + ".");
//} else if (curRow.getRowNum() != prevRow.getRowNum() + 1) {
// throw new IllegalArgumentException("srcRows must contain continuously increasing row numbers. " +
// "Got srcRows[" + (index-1) + "]=Row " + prevRow.getRowNum() + ", srcRows[" + index + "]=Row " + curRow.getRowNum() + ".");
// FIXME: assumes row objects belong to non-null sheets and sheets belong to non-null workbooks.
} else if (srcStartRow.getSheet().getWorkbook() != curRow.getSheet().getWorkbook()) {
throw new IllegalArgumentException("All rows in srcRows must belong to the same sheet in the same workbook. " +
"Expected all rows from same workbook (" + srcStartRow.getSheet().getWorkbook() + "). " +
"Got srcRows[" + index + "] from different workbook (" + curRow.getSheet().getWorkbook() + ").");
} else if (srcStartRow.getSheet() != curRow.getSheet()) {
throw new IllegalArgumentException("All rows in srcRows must belong to the same sheet. " +
"Expected all rows from " + srcStartRow.getSheet().getSheetName() + ". " +
"Got srcRows[" + index + "] from " + curRow.getSheet().getSheetName());
}
}
// FIXME: is special behavior needed if srcRows and destRows belong to the same sheets and the regions overlap?
final CellCopyPolicy options = new CellCopyPolicy(policy);
// avoid O(N^2) performance scanning through all regions for each row
// merged regions will be copied after all the rows have been copied
options.setCopyMergedRegions(false);
// FIXME: if srcRows contains gaps or null values, clear out those rows that will be overwritten
// how will this work with merging (copy just values, leave cell styles in place?)
int r = destStartRow;
for (Row srcRow : srcRows) {
int destRowNum;
if (policy.isCondenseRows()) {
destRowNum = r++;
} else {
final int shift = (srcRow.getRowNum() - srcStartRowNum);
destRowNum = destStartRow + shift;
}
//removeRow(destRowNum); //this probably clears all external formula references to destRow, causing unwanted #REF! errors
final XSSFRow destRow = createRow(destRowNum);
destRow.copyRowFrom(srcRow, options, context);
}
// ======================
// Only do additional copy operations here that cannot be done with Row.copyFromRow(Row, options)
// reasons: operation needs to interact with multiple rows or sheets
// Copy merged regions that are contained within the copy region
if (policy.isCopyMergedRegions()) {
// FIXME: is this something that rowShifter could be doing?
final int shift = destStartRow - srcStartRowNum;
for (CellRangeAddress srcRegion : srcStartRow.getSheet().getMergedRegions()) {
if (srcStartRowNum <= srcRegion.getFirstRow() && srcRegion.getLastRow() <= srcEndRowNum) {
// srcRegion is fully inside the copied rows
final CellRangeAddress destRegion = srcRegion.copy();
destRegion.setFirstRow(destRegion.getFirstRow() + shift);
destRegion.setLastRow(destRegion.getLastRow() + shift);
addMergedRegion(destRegion);
}
}
}
}
/**
* Copies rows between srcStartRow and srcEndRow to the same sheet, starting at destStartRow
* Convenience function for {@link #copyRows(List, int, CellCopyPolicy)}
*
* Equivalent to copyRows(getRows(srcStartRow, srcEndRow, false), destStartRow, cellCopyPolicy)
*
* @param srcStartRow the index of the first row to copy the cells from in this sheet
* @param srcEndRow the index of the last row to copy the cells from in this sheet
* @param destStartRow the index of the first row to copy the cells to in this sheet
* @param cellCopyPolicy the policy to use to determine how cells are copied
*/
@Beta
public void copyRows(int srcStartRow, int srcEndRow, int destStartRow, CellCopyPolicy cellCopyPolicy) {
copyRows(srcStartRow, srcEndRow, destStartRow, cellCopyPolicy, new CellCopyContext());
}
/**
* Copies rows between srcStartRow and srcEndRow to the same sheet, starting at destStartRow
* Convenience function for {@link #copyRows(List, int, CellCopyPolicy, CellCopyContext)}
*
* Equivalent to copyRows(getRows(srcStartRow, srcEndRow, false), destStartRow, cellCopyPolicy)
*
* @param srcStartRow the index of the first row to copy the cells from in this sheet
* @param srcEndRow the index of the last row to copy the cells from in this sheet
* @param destStartRow the index of the first row to copy the cells to in this sheet
* @param cellCopyPolicy the policy to use to determine how cells are copied
* @param context the context - see {@link CellCopyContext}
* @since POI 5.4.1
*/
@Beta
public void copyRows(int srcStartRow, int srcEndRow, int destStartRow,
CellCopyPolicy cellCopyPolicy, CellCopyContext context) {
final Listnull
removes the existing override
* @since POI 5.2.3
*/
@Beta
public void setDimensionOverride(CellRangeAddress dimension) {
this.dimensionOverride = dimension;
}
static void cloneTables(XSSFSheet sheet) {
for (XSSFTable table : sheet.getTables()) {
// clone table; XSSFTable.setArea fails and throws exception for too small tables
XSSFTable clonedTable = null;
if (table.supportsAreaReference(table.getArea())) {
clonedTable = sheet.createTable(table.getArea());
}
if (clonedTable != null) {
clonedTable.updateHeaders();
// clone style
clonedTable.setStyleName(table.getStyleName());
XSSFTableStyleInfo style = (XSSFTableStyleInfo)table.getStyle();
XSSFTableStyleInfo clonedStyle = (XSSFTableStyleInfo)clonedTable.getStyle();
if (style != null && clonedStyle != null) {
clonedStyle.setShowColumnStripes(style.isShowColumnStripes());
clonedStyle.setShowRowStripes(style.isShowRowStripes());
clonedStyle.setFirstColumn(style.isShowFirstColumn());
clonedStyle.setLastColumn(style.isShowLastColumn());
}
//clone autofilter
clonedTable.getCTTable().setAutoFilter(table.getCTTable().getAutoFilter());
//clone totalsrow
int totalsRowCount = table.getTotalsRowCount();
if (totalsRowCount == 1) { // never seen more than one totals row
XSSFRow totalsRow = sheet.getRow(clonedTable.getEndCellReference().getRow());
if (clonedTable.getCTTable().getTableColumns() != null
&& !clonedTable.getCTTable().getTableColumns().getTableColumnList().isEmpty()) {
clonedTable.getCTTable().setTotalsRowCount(totalsRowCount);
for (int i = 0; i < clonedTable.getCTTable().getTableColumns().getTableColumnList().size(); i++) {
CTTableColumn tableCol = table.getCTTable().getTableColumns().getTableColumnList().get(i);
CTTableColumn clonedTableCol = clonedTable.getCTTable().getTableColumns().getTableColumnList().get(i);
clonedTableCol.setTotalsRowFunction(tableCol.getTotalsRowFunction());
int intTotalsRowFunction = clonedTableCol.getTotalsRowFunction().intValue();
sheet.getWorkbook().setCellFormulaValidation(false);
if (intTotalsRowFunction == 10) { //custom
CTTableFormula totalsRowFormula = tableCol.getTotalsRowFormula();
clonedTableCol.setTotalsRowFormula(totalsRowFormula);
totalsRow.getCell(clonedTable.getStartCellReference().getCol()+i).setCellFormula(totalsRowFormula.getStringValue());
} else if (intTotalsRowFunction == 1) { //none
//totalsRow.getCell(clonedTable.getStartCellReference().getCol()+i).setBlank();
} else {
String subtotalFormulaStart = getSubtotalFormulaStartFromTotalsRowFunction(intTotalsRowFunction);
if (subtotalFormulaStart != null)
totalsRow.getCell(clonedTable.getStartCellReference().getCol()+i).setCellFormula(subtotalFormulaStart + "," + clonedTable.getName() +"[" + clonedTableCol.getName()+ "])");
}
}
}
}
// clone calculated column formulas
if (clonedTable.getCTTable().getTableColumns() != null
&& !clonedTable.getCTTable().getTableColumns().getTableColumnList().isEmpty()) {
clonedTable.getCTTable().setTotalsRowCount(totalsRowCount);
for (int i = 0; i < clonedTable.getCTTable().getTableColumns().getTableColumnList().size(); i++) {
CTTableColumn tableCol = table.getCTTable().getTableColumns().getTableColumnList().get(i);
CTTableColumn clonedTableCol = clonedTable.getCTTable().getTableColumns().getTableColumnList().get(i);
if (tableCol.getCalculatedColumnFormula() != null) {
clonedTableCol.setCalculatedColumnFormula(tableCol.getCalculatedColumnFormula());
CTTableFormula calculatedColumnFormula = clonedTableCol.getCalculatedColumnFormula();
String formula = tableCol.getCalculatedColumnFormula().getStringValue();
String clonedFormula = formula.replace(table.getName(), clonedTable.getName());
calculatedColumnFormula.setStringValue(clonedFormula);
int rFirst = clonedTable.getStartCellReference().getRow() + clonedTable.getHeaderRowCount();
int rLast = clonedTable.getEndCellReference().getRow() - clonedTable.getTotalsRowCount();
int c = clonedTable.getStartCellReference().getCol() + i;
sheet.getWorkbook().setCellFormulaValidation(false);
for (int r = rFirst; r <= rLast; r++) {
XSSFRow row = sheet.getRow(r);
if (row == null) row = sheet.createRow(r);
XSSFCell cell = row.getCell(c, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
cell.setCellFormula(clonedFormula);
}
}
}
}
}
// remove old table
sheet.removeTable(table);
}
}
private static String getSubtotalFormulaStartFromTotalsRowFunction(int intTotalsRowFunction) {
final int INT_NONE = 1;
final int INT_SUM = 2;
final int INT_MIN = 3;
final int INT_MAX = 4;
final int INT_AVERAGE = 5;
final int INT_COUNT = 6;
final int INT_COUNT_NUMS = 7;
final int INT_STD_DEV = 8;
final int INT_VAR = 9;
final int INT_CUSTOM = 10;
String subtotalFormulaStart = null;
switch (intTotalsRowFunction) {
case INT_NONE:
subtotalFormulaStart = null;
break;
case INT_SUM:
subtotalFormulaStart = "SUBTOTAL(109";
break;
case INT_MIN:
subtotalFormulaStart = "SUBTOTAL(105";
break;
case INT_MAX:
subtotalFormulaStart = "SUBTOTAL(104";
break;
case INT_AVERAGE:
subtotalFormulaStart = "SUBTOTAL(101";
break;
case INT_COUNT:
subtotalFormulaStart = "SUBTOTAL(103";
break;
case INT_COUNT_NUMS:
subtotalFormulaStart = "SUBTOTAL(102";
break;
case INT_STD_DEV:
subtotalFormulaStart = "SUBTOTAL(107";
break;
case INT_VAR:
subtotalFormulaStart = "SUBTOTAL(110";
break;
case INT_CUSTOM:
subtotalFormulaStart = null;
break;
default:
subtotalFormulaStart = null;
}
return subtotalFormulaStart;
}
}