| /* ==================================================================== |
| 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.streaming; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| |
| import org.apache.commons.compress.archivers.ArchiveOutputStream; |
| import org.apache.commons.compress.archivers.zip.Zip64Mode; |
| import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; |
| import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.poi.openxml4j.opc.OPCPackage; |
| import org.apache.poi.openxml4j.util.ZipArchiveThresholdInputStream; |
| import org.apache.poi.openxml4j.util.ZipEntrySource; |
| import org.apache.poi.openxml4j.util.ZipFileZipEntrySource; |
| import org.apache.poi.openxml4j.util.ZipSecureFile; |
| import org.apache.poi.ss.SpreadsheetVersion; |
| import org.apache.poi.ss.formula.EvaluationWorkbook; |
| import org.apache.poi.ss.formula.udf.UDFFinder; |
| import org.apache.poi.ss.usermodel.CellStyle; |
| import org.apache.poi.ss.usermodel.CreationHelper; |
| import org.apache.poi.ss.usermodel.DataFormat; |
| import org.apache.poi.ss.usermodel.Font; |
| import org.apache.poi.ss.usermodel.Name; |
| import org.apache.poi.ss.usermodel.PictureData; |
| import org.apache.poi.ss.usermodel.Row; |
| import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; |
| import org.apache.poi.ss.usermodel.Sheet; |
| import org.apache.poi.ss.usermodel.SheetVisibility; |
| import org.apache.poi.ss.usermodel.Workbook; |
| import org.apache.poi.util.Beta; |
| import org.apache.poi.util.IOUtils; |
| import org.apache.poi.util.Internal; |
| import org.apache.poi.util.NotImplemented; |
| import org.apache.poi.util.Removal; |
| import org.apache.poi.util.TempFile; |
| import org.apache.poi.xssf.model.SharedStringsTable; |
| import org.apache.poi.xssf.usermodel.XSSFChartSheet; |
| import org.apache.poi.xssf.usermodel.XSSFSheet; |
| import org.apache.poi.xssf.usermodel.XSSFWorkbook; |
| |
| /** |
| * Streaming version of XSSFWorkbook implementing the "BigGridDemo" strategy. |
| * |
| * This allows to write very large files without running out of memory as only |
| * a configurable portion of the rows are kept in memory at any one time. |
| * |
| * You can provide a template workbook which is used as basis for the written |
| * data. |
| * |
| * See https://poi.apache.org/spreadsheet/how-to.html#sxssf for details. |
| * |
| * Please note that there are still things that still may consume a large |
| * amount of memory based on which features you are using, e.g. merged regions, |
| * comments, ... are still only stored in memory and thus may require a lot of |
| * memory if used extensively. |
| * |
| * SXSSFWorkbook defaults to using inline strings instead of a shared strings |
| * table. This is very efficient, since no document content needs to be kept in |
| * memory, but is also known to produce documents that are incompatible with |
| * some clients. With shared strings enabled all unique strings in the document |
| * has to be kept in memory. Depending on your document content this could use |
| * a lot more resources than with shared strings disabled. |
| * |
| * Carefully review your memory budget and compatibility needs before deciding |
| * whether to enable shared strings or not. |
| */ |
| public class SXSSFWorkbook implements Workbook { |
| /** |
| * Specifies how many rows can be accessed at most via {@link SXSSFSheet#getRow}. |
| * When a new node is created via {@link SXSSFSheet#createRow} and the total number |
| * of unflushed records would exceed the specified value, then the |
| * row with the lowest index value is flushed and cannot be accessed |
| * via {@link SXSSFSheet#getRow} anymore. |
| */ |
| public static final int DEFAULT_WINDOW_SIZE = 100; |
| private static final Logger LOG = LogManager.getLogger(SXSSFWorkbook.class); |
| |
| protected final XSSFWorkbook _wb; |
| |
| private final Map<SXSSFSheet,XSSFSheet> _sxFromXHash = new HashMap<>(); |
| private final Map<XSSFSheet,SXSSFSheet> _xFromSxHash = new HashMap<>(); |
| |
| private int _randomAccessWindowSize = DEFAULT_WINDOW_SIZE; |
| |
| protected interface ISheetInjector { |
| void writeSheetData(OutputStream out) throws IOException; |
| } |
| |
| /** |
| * whether temp files should be compressed. |
| */ |
| private boolean _compressTmpFiles; |
| |
| /** |
| * shared string table - a cache of strings in this workbook |
| */ |
| protected final SharedStringsTable _sharedStringSource; |
| |
| /** |
| * controls whether Zip64 mode is used - Always became the default in POI 5.0.0 |
| */ |
| protected Zip64Mode zip64Mode = Zip64Mode.Always; |
| |
| /** |
| * Construct a new workbook with default row window size |
| */ |
| public SXSSFWorkbook(){ |
| this(null /*workbook*/); |
| } |
| |
| /** |
| * <p>Construct a workbook from a template.</p> |
| * |
| * There are three use-cases to use SXSSFWorkbook(XSSFWorkbook) : |
| * <ol> |
| * <li> |
| * Append new sheets to existing workbooks. You can open existing |
| * workbook from a file or create on the fly with XSSF. |
| * </li> |
| * <li> |
| * Append rows to existing sheets. The row number MUST be greater |
| * than {@code max(rownum)} in the template sheet. |
| * </li> |
| * <li> |
| * Use existing workbook as a template and re-use global objects such |
| * as cell styles, formats, images, etc. |
| * </li> |
| * </ol> |
| * All three use cases can work in a combination. |
| * |
| * What is not supported: |
| * <ul> |
| * <li> |
| * Access initial cells and rows in the template. After constructing |
| * all internal windows are empty and {@link SXSSFSheet#getRow} and |
| * {@link SXSSFRow#getCell} return <code>null</code>. |
| * </li> |
| * <li> |
| * Override existing cells and rows. The API silently allows that but |
| * the output file is invalid and Excel cannot read it. |
| * </li> |
| * </ul> |
| * |
| * @param workbook the template workbook |
| */ |
| public SXSSFWorkbook(XSSFWorkbook workbook){ |
| this(workbook, DEFAULT_WINDOW_SIZE); |
| } |
| |
| |
| /** |
| * Constructs an workbook from an existing workbook. |
| * <p> |
| * When a new node is created via {@link SXSSFSheet#createRow} and the total number |
| * of unflushed records would exceed the specified value, then the |
| * row with the lowest index value is flushed and cannot be accessed |
| * via {@link SXSSFSheet#getRow} anymore. |
| * </p> |
| * <p> |
| * A value of <code>-1</code> indicates unlimited access. In this case all |
| * records that have not been flushed by a call to <code>flush()</code> are available |
| * for random access. |
| * </p> |
| * <p> |
| * A value of <code>0</code> is not allowed because it would flush any newly created row |
| * without having a chance to specify any cells. |
| * </p> |
| * |
| * @param rowAccessWindowSize the number of rows that are kept in memory until flushed out, see above. |
| */ |
| public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize){ |
| this(workbook,rowAccessWindowSize, false); |
| } |
| |
| /** |
| * Constructs an workbook from an existing workbook. |
| * <p> |
| * When a new node is created via {@link SXSSFSheet#createRow} and the total number |
| * of unflushed records would exceed the specified value, then the |
| * row with the lowest index value is flushed and cannot be accessed |
| * via {@link SXSSFSheet#getRow} anymore. |
| * </p> |
| * <p> |
| * A value of <code>-1</code> indicates unlimited access. In this case all |
| * records that have not been flushed by a call to <code>flush()</code> are available |
| * for random access. |
| * </p> |
| * <p> |
| * A value of <code>0</code> is not allowed because it would flush any newly created row |
| * without having a chance to specify any cells. |
| * </p> |
| * |
| * @param rowAccessWindowSize the number of rows that are kept in memory until flushed out, see above. |
| * @param compressTmpFiles whether to use gzip compression for temporary files |
| */ |
| public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean compressTmpFiles) { |
| this(workbook,rowAccessWindowSize, compressTmpFiles, false); |
| } |
| |
| /** |
| * Constructs an workbook from an existing workbook. |
| * <p> |
| * When a new node is created via {@link SXSSFSheet#createRow} and the total number |
| * of unflushed records would exceed the specified value, then the |
| * row with the lowest index value is flushed and cannot be accessed |
| * via {@link SXSSFSheet#getRow} anymore. |
| * </p> |
| * <p> |
| * A value of <code>-1</code> indicates unlimited access. In this case all |
| * records that have not been flushed by a call to <code>flush()</code> are available |
| * for random access. |
| * </p> |
| * <p> |
| * A value of <code>0</code> is not allowed because it would flush any newly created row |
| * without having a chance to specify any cells. |
| * </p> |
| * |
| * @param workbook the template workbook |
| * @param rowAccessWindowSize the number of rows that are kept in memory until flushed out, see above. |
| * @param compressTmpFiles whether to use gzip compression for temporary files |
| * @param useSharedStringsTable whether to use a shared strings table |
| */ |
| public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean compressTmpFiles, boolean useSharedStringsTable) { |
| setRandomAccessWindowSize(rowAccessWindowSize); |
| setCompressTempFiles(compressTmpFiles); |
| if (workbook == null) { |
| _wb = new XSSFWorkbook(); |
| _sharedStringSource = useSharedStringsTable ? _wb.getSharedStringSource() : null; |
| } else { |
| _wb=workbook; |
| _sharedStringSource = useSharedStringsTable ? _wb.getSharedStringSource() : null; |
| for ( Sheet sheet : _wb ) { |
| createAndRegisterSXSSFSheet( (XSSFSheet)sheet ); |
| } |
| } |
| } |
| |
| /** |
| * Construct an empty workbook and specify the window for row access. |
| * <p> |
| * When a new node is created via {@link SXSSFSheet#createRow} and the total number |
| * of unflushed records would exceed the specified value, then the |
| * row with the lowest index value is flushed and cannot be accessed |
| * via {@link SXSSFSheet#getRow} anymore. |
| * </p> |
| * <p> |
| * A value of <code>-1</code> indicates unlimited access. In this case all |
| * records that have not been flushed by a call to <code>flush()</code> are available |
| * for random access. |
| * </p> |
| * <p> |
| * A value of <code>0</code> is not allowed because it would flush any newly created row |
| * without having a chance to specify any cells. |
| * </p> |
| * |
| * @param rowAccessWindowSize the number of rows that are kept in memory until flushed out, see above. |
| */ |
| public SXSSFWorkbook(int rowAccessWindowSize){ |
| this(null /*workbook*/, rowAccessWindowSize); |
| } |
| |
| /** |
| * See the constructors for a more detailed description of the sliding window of rows. |
| * |
| * @return The number of rows that are kept in memory at once before flushing them out. |
| */ |
| public int getRandomAccessWindowSize() { |
| return _randomAccessWindowSize; |
| } |
| |
| protected void setRandomAccessWindowSize(int rowAccessWindowSize) { |
| if(rowAccessWindowSize == 0 || rowAccessWindowSize < -1) { |
| throw new IllegalArgumentException("rowAccessWindowSize must be greater than 0 or -1"); |
| } |
| _randomAccessWindowSize = rowAccessWindowSize; |
| } |
| |
| /** |
| * @param zip64Mode {@link Zip64Mode} |
| * |
| * @since 4.1.0 |
| */ |
| @Beta |
| public void setZip64Mode(Zip64Mode zip64Mode) { |
| this.zip64Mode = zip64Mode; |
| } |
| |
| /** |
| * Get whether temp files should be compressed. |
| * |
| * @return whether to compress temp files |
| */ |
| public boolean isCompressTempFiles() { |
| return _compressTmpFiles; |
| } |
| |
| /** |
| * Set whether temp files should be compressed. |
| * <p> |
| * SXSSF writes sheet data in temporary files (a temp file per-sheet) |
| * and the size of these temp files can grow to to a very large size, |
| * e.g. for a 20 MB csv data the size of the temp xml file become few GB large. |
| * If the "compress" flag is set to <code>true</code> then the temporary XML is gzipped. |
| * </p> |
| * <p> |
| * Please note the the "compress" option may cause performance penalty. |
| * </p> |
| * <p> |
| * Setting this option only affects compression for subsequent <code>createSheet()</code> |
| * calls. |
| * </p> |
| * @param compress whether to compress temp files |
| */ |
| public void setCompressTempFiles(boolean compress) { |
| _compressTmpFiles = compress; |
| } |
| |
| @Internal |
| protected SharedStringsTable getSharedStringSource() { |
| return _sharedStringSource; |
| } |
| |
| protected SheetDataWriter createSheetDataWriter() throws IOException { |
| if(_compressTmpFiles) { |
| return new GZIPSheetDataWriter(_sharedStringSource); |
| } |
| |
| return new SheetDataWriter(_sharedStringSource); |
| } |
| |
| XSSFSheet getXSSFSheet(SXSSFSheet sheet) |
| { |
| return _sxFromXHash.get(sheet); |
| } |
| |
| SXSSFSheet getSXSSFSheet(XSSFSheet sheet) |
| { |
| return _xFromSxHash.get(sheet); |
| } |
| |
| void registerSheetMapping(SXSSFSheet sxSheet,XSSFSheet xSheet) |
| { |
| _sxFromXHash.put(sxSheet,xSheet); |
| _xFromSxHash.put(xSheet,sxSheet); |
| } |
| |
| void deregisterSheetMapping(XSSFSheet xSheet) |
| { |
| SXSSFSheet sxSheet = getSXSSFSheet(xSheet); |
| if (sxSheet != null) { |
| // ensure that the writer is closed in all cases to not have lingering writers |
| IOUtils.closeQuietly(sxSheet.getSheetDataWriter()); |
| _sxFromXHash.remove(sxSheet); |
| _xFromSxHash.remove(xSheet); |
| } |
| } |
| |
| protected XSSFSheet getSheetFromZipEntryName(String sheetRef) |
| { |
| for(XSSFSheet sheet : _sxFromXHash.values()) |
| { |
| if(sheetRef.equals(sheet.getPackagePart().getPartName().getName().substring(1))) { |
| return sheet; |
| } |
| } |
| return null; |
| } |
| |
| protected void injectData(ZipEntrySource zipEntrySource, OutputStream out) throws IOException { |
| ArchiveOutputStream zos = createArchiveOutputStream(out); |
| try { |
| Enumeration<? extends ZipArchiveEntry> en = zipEntrySource.getEntries(); |
| while (en.hasMoreElements()) { |
| ZipArchiveEntry ze = en.nextElement(); |
| ZipArchiveEntry zeOut = new ZipArchiveEntry(ze.getName()); |
| zeOut.setSize(ze.getSize()); |
| zeOut.setTime(ze.getTime()); |
| zos.putArchiveEntry(zeOut); |
| try (final InputStream is = zipEntrySource.getInputStream(ze)) { |
| if (is instanceof ZipArchiveThresholdInputStream) { |
| // #59743 - disable Threshold handling for SXSSF copy |
| // as users tend to put too much repetitive data in when using SXSSF :) |
| ((ZipArchiveThresholdInputStream)is).setGuardState(false); |
| } |
| XSSFSheet xSheet = getSheetFromZipEntryName(ze.getName()); |
| // See bug 56557, we should not inject data into the special ChartSheets |
| if (xSheet != null && !(xSheet instanceof XSSFChartSheet)) { |
| SXSSFSheet sxSheet = getSXSSFSheet(xSheet); |
| copyStreamAndInjectWorksheet(is, zos, createSheetInjector(sxSheet)); |
| } else { |
| IOUtils.copy(is, zos); |
| } |
| } finally { |
| zos.closeArchiveEntry(); |
| } |
| } |
| } finally { |
| zos.finish(); |
| zipEntrySource.close(); |
| } |
| } |
| |
| protected ZipArchiveOutputStream createArchiveOutputStream(OutputStream out) { |
| if (Zip64Mode.Always.equals(zip64Mode)) { |
| return new OpcZipArchiveOutputStream(out); |
| } else { |
| ZipArchiveOutputStream zos = new ZipArchiveOutputStream(out); |
| zos.setUseZip64(zip64Mode); |
| return zos; |
| } |
| } |
| |
| protected ISheetInjector createSheetInjector(SXSSFSheet sxSheet) throws IOException { |
| return (output) -> { |
| try (InputStream xis = sxSheet.getWorksheetXMLInputStream()) { |
| // Copy the worksheet data to "output". |
| IOUtils.copy(xis, output); |
| } |
| }; |
| } |
| |
| // private static void copyStreamAndInjectWorksheet(InputStream in, OutputStream out, InputStream worksheetData) throws IOException { |
| private static void copyStreamAndInjectWorksheet(InputStream in, OutputStream out, ISheetInjector sheetInjector) throws IOException { |
| InputStreamReader inReader = new InputStreamReader(in, StandardCharsets.UTF_8); |
| OutputStreamWriter outWriter = new OutputStreamWriter(out, StandardCharsets.UTF_8); |
| boolean needsStartTag = true; |
| int c; |
| int pos=0; |
| String s="<sheetData"; |
| int n=s.length(); |
| //Copy from "in" to "out" up to the string "<sheetData/>" or "</sheetData>" (excluding). |
| while(((c=inReader.read())!=-1)) |
| { |
| if(c==s.charAt(pos)) |
| { |
| pos++; |
| if(pos==n) |
| { |
| if ("<sheetData".equals(s)) |
| { |
| c = inReader.read(); |
| if (c == -1) |
| { |
| outWriter.write(s); |
| break; |
| } |
| if (c == '>') |
| { |
| // Found <sheetData> |
| outWriter.write(s); |
| outWriter.write(c); |
| s = "</sheetData>"; |
| n = s.length(); |
| pos = 0; |
| needsStartTag = false; |
| continue; |
| } |
| if (c == '/') |
| { |
| // Found <sheetData/ |
| c = inReader.read(); |
| if (c == -1) |
| { |
| outWriter.write(s); |
| break; |
| } |
| if (c == '>') |
| { |
| // Found <sheetData/> |
| break; |
| } |
| |
| outWriter.write(s); |
| outWriter.write('/'); |
| outWriter.write(c); |
| pos = 0; |
| continue; |
| } |
| |
| outWriter.write(s); |
| outWriter.write('/'); |
| outWriter.write(c); |
| pos = 0; |
| continue; |
| } |
| else |
| { |
| // Found </sheetData> |
| break; |
| } |
| } |
| } |
| else |
| { |
| if(pos>0) { |
| outWriter.write(s,0,pos); |
| } |
| if(c==s.charAt(0)) |
| { |
| pos=1; |
| } |
| else |
| { |
| outWriter.write(c); |
| pos=0; |
| } |
| } |
| } |
| outWriter.flush(); |
| if (needsStartTag) |
| { |
| outWriter.write("<sheetData>\n"); |
| outWriter.flush(); |
| } |
| sheetInjector.writeSheetData(out); |
| outWriter.write("</sheetData>"); |
| outWriter.flush(); |
| //Copy the rest of "in" to "out". |
| while(((c=inReader.read())!=-1)) { |
| outWriter.write(c); |
| } |
| outWriter.flush(); |
| } |
| |
| public XSSFWorkbook getXSSFWorkbook() |
| { |
| return _wb; |
| } |
| |
| //start of interface implementation |
| |
| /** |
| * Convenience method to get the active sheet. The active sheet is is the sheet |
| * which is currently displayed when the workbook is viewed in Excel. |
| * 'Selected' sheet(s) is a distinct concept. |
| * |
| * @return the index of the active sheet (0-based) |
| */ |
| @Override |
| public int getActiveSheetIndex() |
| { |
| return _wb.getActiveSheetIndex(); |
| } |
| |
| /** |
| * Convenience method to set the active sheet. The active sheet is is the sheet |
| * which is currently displayed when the workbook is viewed in Excel. |
| * 'Selected' sheet(s) is a distinct concept. |
| * |
| * @param sheetIndex index of the active sheet (0-based) |
| */ |
| @Override |
| public void setActiveSheet(int sheetIndex) |
| { |
| _wb.setActiveSheet(sheetIndex); |
| } |
| |
| /** |
| * Gets the first tab that is displayed in the list of tabs in excel. |
| * |
| * @return the first tab that to display in the list of tabs (0-based). |
| */ |
| @Override |
| public int getFirstVisibleTab() |
| { |
| return _wb.getFirstVisibleTab(); |
| } |
| |
| /** |
| * Sets the first tab that is displayed in the list of tabs in excel. |
| * |
| * @param sheetIndex the first tab that to display in the list of tabs (0-based) |
| */ |
| @Override |
| public void setFirstVisibleTab(int sheetIndex) |
| { |
| _wb.setFirstVisibleTab(sheetIndex); |
| } |
| |
| /** |
| * Sets the order of appearance for a given sheet. |
| * |
| * @param sheetname the name of the sheet to reorder |
| * @param pos the position that we want to insert the sheet into (0 based) |
| */ |
| @Override |
| public void setSheetOrder(String sheetname, int pos) |
| { |
| _wb.setSheetOrder(sheetname,pos); |
| } |
| |
| /** |
| * Sets the tab whose data is actually seen when the sheet is opened. |
| * This may be different from the "selected sheet" since excel seems to |
| * allow you to show the data of one sheet when another is seen "selected" |
| * in the tabs (at the bottom). |
| * |
| * @see Sheet#setSelected(boolean) |
| * @param index the index of the sheet to select (0 based) |
| */ |
| @Override |
| public void setSelectedTab(int index) |
| { |
| _wb.setSelectedTab(index); |
| } |
| |
| /** |
| * Set the sheet name. |
| * |
| * @param sheet number (0 based) |
| * @throws IllegalArgumentException if the name is greater than 31 chars or contains <code>/\?*[]</code> |
| */ |
| @Override |
| public void setSheetName(int sheet, String name) |
| { |
| _wb.setSheetName(sheet,name); |
| } |
| |
| /** |
| * Set the sheet name |
| * |
| * @param sheet sheet number (0 based) |
| * @return Sheet name |
| */ |
| @Override |
| public String getSheetName(int sheet) |
| { |
| return _wb.getSheetName(sheet); |
| } |
| |
| /** |
| * Returns the index of the sheet by his name |
| * |
| * @param name the sheet name |
| * @return index of the sheet (0 based) |
| */ |
| @Override |
| public int getSheetIndex(String name) |
| { |
| return _wb.getSheetIndex(name); |
| } |
| |
| /** |
| * Returns the index of the given sheet |
| * |
| * @param sheet the sheet to look up |
| * @return index of the sheet (0 based) |
| */ |
| @Override |
| public int getSheetIndex(Sheet sheet) |
| { |
| return _wb.getSheetIndex(getXSSFSheet((SXSSFSheet)sheet)); |
| } |
| |
| /** |
| * Create a Sheet for this Workbook, adds it to the sheets and returns |
| * the high level representation. Use this to create new sheets. |
| * |
| * @return Sheet representing the new sheet. |
| */ |
| @Override |
| public SXSSFSheet createSheet() |
| { |
| return createAndRegisterSXSSFSheet(_wb.createSheet()); |
| } |
| |
| SXSSFSheet createAndRegisterSXSSFSheet(XSSFSheet xSheet) |
| { |
| final SXSSFSheet sxSheet; |
| try |
| { |
| sxSheet=new SXSSFSheet(this,xSheet); |
| } |
| catch (IOException ioe) |
| { |
| throw new RuntimeException(ioe); |
| } |
| registerSheetMapping(sxSheet,xSheet); |
| return sxSheet; |
| } |
| |
| /** |
| * Create a Sheet for this Workbook, adds it to the sheets and returns |
| * the high level representation. Use this to create new sheets. |
| * |
| * @param sheetname sheetname to set for the sheet. |
| * @return Sheet representing the new sheet. |
| * @throws IllegalArgumentException if the name is greater than 31 chars or contains <code>/\?*[]</code> |
| */ |
| @Override |
| public SXSSFSheet createSheet(String sheetname) |
| { |
| return createAndRegisterSXSSFSheet(_wb.createSheet(sheetname)); |
| } |
| |
| /** |
| * <i>Not implemented for SXSSFWorkbook</i> |
| * |
| * Create a Sheet from an existing sheet in the Workbook. |
| * |
| * @return Sheet representing the cloned sheet. |
| */ |
| @Override |
| @NotImplemented |
| public Sheet cloneSheet(int sheetNum) { |
| throw new RuntimeException("Not Implemented"); |
| } |
| |
| |
| /** |
| * Get the number of spreadsheets in the workbook |
| * |
| * @return the number of sheets |
| */ |
| @Override |
| public int getNumberOfSheets() |
| { |
| return _wb.getNumberOfSheets(); |
| } |
| |
| /** |
| * Returns an iterator of the sheets in the workbook |
| * in sheet order. Includes hidden and very hidden sheets. |
| * |
| * @return an iterator of the sheets. |
| */ |
| @Override |
| public Iterator<Sheet> sheetIterator() { |
| return new SheetIterator<>(); |
| } |
| |
| protected final class SheetIterator<T extends Sheet> implements Iterator<T> { |
| final private Iterator<XSSFSheet> it; |
| @SuppressWarnings("unchecked") |
| public SheetIterator() { |
| it = (Iterator<XSSFSheet>)(Iterator<? extends Sheet>) _wb.iterator(); |
| } |
| @Override |
| public boolean hasNext() { |
| return it.hasNext(); |
| } |
| @Override |
| @SuppressWarnings("unchecked") |
| public T next() throws NoSuchElementException { |
| final XSSFSheet xssfSheet = it.next(); |
| return (T) getSXSSFSheet(xssfSheet); |
| } |
| /** |
| * Unexpected behavior may occur if sheets are reordered after iterator |
| * has been created. Support for the remove method may be added in the future |
| * if someone can figure out a reliable implementation. |
| */ |
| @Override |
| public void remove() throws IllegalStateException { |
| throw new UnsupportedOperationException("remove method not supported on XSSFWorkbook.iterator(). "+ |
| "Use Sheet.removeSheetAt(int) instead."); |
| } |
| } |
| |
| /** |
| * Alias for {@link #sheetIterator()} to allow |
| * foreach loops |
| */ |
| @Override |
| public Iterator<Sheet> iterator() { |
| return sheetIterator(); |
| } |
| |
| /** |
| * Get the Sheet object at the given index. |
| * |
| * @param index of the sheet number (0-based physical and logical) |
| * @return Sheet at the provided index |
| */ |
| @Override |
| public SXSSFSheet getSheetAt(int index) |
| { |
| return getSXSSFSheet(_wb.getSheetAt(index)); |
| } |
| |
| /** |
| * Get sheet with the given name |
| * |
| * @param name of the sheet |
| * @return Sheet with the name provided or <code>null</code> if it does not exist |
| */ |
| @Override |
| public SXSSFSheet getSheet(String name) |
| { |
| return getSXSSFSheet(_wb.getSheet(name)); |
| } |
| |
| /** |
| * Removes sheet at the given index |
| * |
| * @param index of the sheet to remove (0-based) |
| */ |
| @Override |
| public void removeSheetAt(int index) |
| { |
| // Get the sheet to be removed |
| XSSFSheet xSheet = _wb.getSheetAt(index); |
| SXSSFSheet sxSheet = getSXSSFSheet(xSheet); |
| |
| // De-register it |
| _wb.removeSheetAt(index); |
| deregisterSheetMapping(xSheet); |
| |
| // Clean up temporary resources |
| try { |
| sxSheet.dispose(); |
| } catch (IOException e) { |
| LOG.atWarn().withThrowable(e).log("Failed to dispose old sheet"); |
| } |
| } |
| |
| /** |
| * Create a new Font and add it to the workbook's font table |
| * |
| * @return new font object |
| */ |
| @Override |
| public Font createFont() |
| { |
| return _wb.createFont(); |
| } |
| |
| /** |
| * Finds a font that matches the one with the supplied attributes |
| * |
| * @return the font with the matched attributes or <code>null</code> |
| */ |
| @Override |
| public Font findFont(boolean bold, short color, short fontHeight, String name, boolean italic, boolean strikeout, short typeOffset, byte underline) |
| { |
| return _wb.findFont(bold, color, fontHeight, name, italic, strikeout, typeOffset, underline); |
| } |
| |
| @Override |
| public int getNumberOfFonts() { |
| return _wb.getNumberOfFonts(); |
| } |
| |
| @Override |
| @Deprecated |
| @Removal(version = "6.0.0") |
| public int getNumberOfFontsAsInt() |
| { |
| return getNumberOfFonts(); |
| } |
| |
| @Override |
| public Font getFontAt(int idx) |
| { |
| return _wb.getFontAt(idx); |
| } |
| |
| /** |
| * Create a new Cell style and add it to the workbook's style table |
| * |
| * @return the new Cell Style object |
| */ |
| @Override |
| public CellStyle createCellStyle() |
| { |
| return _wb.createCellStyle(); |
| } |
| |
| /** |
| * Get the number of styles the workbook contains |
| * |
| * @return count of cell styles |
| */ |
| @Override |
| public int getNumCellStyles() |
| { |
| return _wb.getNumCellStyles(); |
| } |
| |
| /** |
| * Get the cell style object at the given index |
| * |
| * @param idx index within the set of styles (0-based) |
| * @return CellStyle object at the index |
| */ |
| @Override |
| public CellStyle getCellStyleAt(int idx) |
| { |
| return _wb.getCellStyleAt(idx); |
| } |
| |
| /** |
| * Closes the underlying {@link XSSFWorkbook} and {@link OPCPackage} |
| * on which this Workbook is based, if any. |
| * |
| * <p>Once this has been called, no further |
| * operations, updates or reads should be performed on the |
| * Workbook. |
| */ |
| @Override |
| public void close() throws IOException { |
| // ensure that any lingering writer is closed |
| for (SXSSFSheet sheet : _xFromSxHash.values()) |
| { |
| try { |
| SheetDataWriter _writer = sheet.getSheetDataWriter(); |
| if (_writer != null) _writer.close(); |
| } catch (IOException e) { |
| LOG.atWarn().withThrowable(e).log("An exception occurred while closing sheet data writer for sheet {}.", sheet.getSheetName()); |
| } |
| } |
| |
| |
| // Tell the base workbook to close, does nothing if |
| // it's a newly created one |
| _wb.close(); |
| } |
| |
| /** |
| * Write out this workbook to an OutputStream. |
| * |
| * @param stream - the java OutputStream you wish to write to |
| * @exception IOException if anything can't be written. |
| */ |
| @Override |
| public void write(OutputStream stream) throws IOException { |
| flushSheets(); |
| |
| //Save the template |
| File tmplFile = TempFile.createTempFile("poi-sxssf-template", ".xlsx"); |
| boolean deleted; |
| try { |
| try (FileOutputStream os = new FileOutputStream(tmplFile)) { |
| _wb.write(os); |
| } |
| |
| //Substitute the template entries with the generated sheet data files |
| try (ZipSecureFile zf = new ZipSecureFile(tmplFile); |
| ZipFileZipEntrySource source = new ZipFileZipEntrySource(zf)) { |
| injectData(source, stream); |
| } |
| } finally { |
| deleted = tmplFile.delete(); |
| } |
| if(!deleted) { |
| throw new IOException("Could not delete temporary file after processing: " + tmplFile); |
| } |
| } |
| |
| protected void flushSheets() throws IOException { |
| for (SXSSFSheet sheet : _xFromSxHash.values()) |
| { |
| sheet.flushRows(); |
| } |
| } |
| |
| /** |
| * Dispose of temporary files backing this workbook on disk. |
| * Calling this method will render the workbook unusable. |
| * @return true if all temporary files were deleted successfully. |
| */ |
| public boolean dispose() |
| { |
| boolean success = true; |
| for (SXSSFSheet sheet : _sxFromXHash.keySet()) |
| { |
| try { |
| success = sheet.dispose() && success; |
| } catch (IOException e) { |
| LOG.atWarn().withThrowable(e).log("Failed to dispose sheet"); |
| success = false; |
| } |
| } |
| return success; |
| } |
| |
| /** |
| * @return the total number of defined names in this workbook |
| */ |
| @Override |
| public int getNumberOfNames() |
| { |
| return _wb.getNumberOfNames(); |
| } |
| |
| /** |
| * @param name the name of the defined name |
| * @return the defined name with the specified name. <code>null</code> if not found. |
| */ |
| @Override |
| public Name getName(String name) |
| { |
| return _wb.getName(name); |
| } |
| |
| /** |
| * Returns all defined names with the given name. |
| * |
| * @param name the name of the defined name |
| * @return a list of the defined names with the specified name. An empty list is returned if none is found. |
| */ |
| @Override |
| public List<? extends Name> getNames(String name) { |
| return _wb.getNames(name); |
| } |
| |
| /** |
| * Returns all defined names |
| * |
| * @return all defined names |
| */ |
| @Override |
| public List<? extends Name> getAllNames() |
| { |
| return _wb.getAllNames(); |
| } |
| |
| /** |
| * Creates a new (uninitialised) defined name in this workbook |
| * |
| * @return new defined name object |
| */ |
| @Override |
| public Name createName() |
| { |
| return _wb.createName(); |
| } |
| |
| /** |
| * Remove the given defined name |
| * |
| * @param name the name to remove |
| */ |
| @Override |
| public void removeName(Name name) |
| { |
| _wb.removeName(name); |
| } |
| |
| /** |
| * Sets the printarea for the sheet provided |
| * <p> |
| * i.e. Reference = $A$1:$B$2 |
| * @param sheetIndex Zero-based sheet index (0 Represents the first sheet to keep consistent with java) |
| * @param reference Valid name Reference for the Print Area |
| */ |
| @Override |
| public void setPrintArea(int sheetIndex, String reference) |
| { |
| _wb.setPrintArea(sheetIndex,reference); |
| } |
| |
| /** |
| * For the Convenience of Java Programmers maintaining pointers. |
| * @see #setPrintArea(int, String) |
| * @param sheetIndex Zero-based sheet index (0 = First Sheet) |
| * @param startColumn Column to begin printarea |
| * @param endColumn Column to end the printarea |
| * @param startRow Row to begin the printarea |
| * @param endRow Row to end the printarea |
| */ |
| @Override |
| public void setPrintArea(int sheetIndex, int startColumn, int endColumn, int startRow, int endRow) |
| { |
| _wb.setPrintArea(sheetIndex, startColumn, endColumn, startRow, endRow); |
| } |
| |
| /** |
| * Retrieves the reference for the printarea of the specified sheet, |
| * the sheet name is appended to the reference even if it was not specified. |
| * |
| * @param sheetIndex Zero-based sheet index (0 Represents the first sheet to keep consistent with java) |
| * @return String Null if no print area has been defined |
| */ |
| @Override |
| public String getPrintArea(int sheetIndex) |
| { |
| return _wb.getPrintArea(sheetIndex); |
| } |
| |
| /** |
| * Delete the printarea for the sheet specified |
| * |
| * @param sheetIndex Zero-based sheet index (0 = First Sheet) |
| */ |
| @Override |
| public void removePrintArea(int sheetIndex) |
| { |
| _wb.removePrintArea(sheetIndex); |
| } |
| |
| /** |
| * Retrieves the current policy on what to do when |
| * getting missing or blank cells from a row. |
| * <p> |
| * The default is to return blank and null cells. |
| * {@link MissingCellPolicy} |
| * </p> |
| */ |
| @Override |
| public MissingCellPolicy getMissingCellPolicy() |
| { |
| return _wb.getMissingCellPolicy(); |
| } |
| |
| /** |
| * Sets the policy on what to do when |
| * getting missing or blank cells from a row. |
| * |
| * This will then apply to all calls to |
| * {@link Row#getCell(int)}. See |
| * {@link MissingCellPolicy} |
| */ |
| @Override |
| public void setMissingCellPolicy(MissingCellPolicy missingCellPolicy) |
| { |
| _wb.setMissingCellPolicy(missingCellPolicy); |
| } |
| |
| /** |
| * Returns the instance of DataFormat for this workbook. |
| * |
| * @return the DataFormat object |
| */ |
| @Override |
| public DataFormat createDataFormat() |
| { |
| return _wb.createDataFormat(); |
| } |
| |
| /** |
| * Adds a picture to the workbook. |
| * |
| * @param pictureData The bytes of the picture |
| * @param format The format of the picture. |
| * |
| * @return the index to this picture (1 based). |
| * @see #PICTURE_TYPE_EMF |
| * @see #PICTURE_TYPE_WMF |
| * @see #PICTURE_TYPE_PICT |
| * @see #PICTURE_TYPE_JPEG |
| * @see #PICTURE_TYPE_PNG |
| * @see #PICTURE_TYPE_DIB |
| */ |
| @Override |
| public int addPicture(byte[] pictureData, int format) |
| { |
| return _wb.addPicture(pictureData,format); |
| } |
| |
| /** |
| * Gets all pictures from the Workbook. |
| * |
| * @return the list of pictures (a list of {@link PictureData} objects.) |
| */ |
| @Override |
| public List<? extends PictureData> getAllPictures() |
| { |
| return _wb.getAllPictures(); |
| } |
| |
| /** |
| * Returns an object that handles instantiating concrete |
| * classes of the various instances one needs for HSSF, XSSF |
| * and SXSSF. |
| */ |
| @Override |
| public CreationHelper getCreationHelper() { |
| return new SXSSFCreationHelper(this); |
| } |
| |
| protected boolean isDate1904() { |
| return _wb.isDate1904(); |
| } |
| |
| @Override |
| @NotImplemented("XSSFWorkbook#isHidden is not implemented") |
| public boolean isHidden() |
| { |
| return _wb.isHidden(); |
| } |
| |
| @Override |
| @NotImplemented("XSSFWorkbook#setHidden is not implemented") |
| public void setHidden(boolean hiddenFlag) |
| { |
| _wb.setHidden(hiddenFlag); |
| } |
| |
| @Override |
| public boolean isSheetHidden(int sheetIx) |
| { |
| return _wb.isSheetHidden(sheetIx); |
| } |
| |
| @Override |
| public boolean isSheetVeryHidden(int sheetIx) |
| { |
| return _wb.isSheetVeryHidden(sheetIx); |
| } |
| |
| @Override |
| public SheetVisibility getSheetVisibility(int sheetIx) { |
| return _wb.getSheetVisibility(sheetIx); |
| } |
| |
| @Override |
| public void setSheetHidden(int sheetIx, boolean hidden) |
| { |
| _wb.setSheetHidden(sheetIx,hidden); |
| } |
| |
| @Override |
| public void setSheetVisibility(int sheetIx, SheetVisibility visibility) { |
| _wb.setSheetVisibility(sheetIx, visibility); |
| } |
| |
| /** |
| * <i>Not implemented for SXSSFWorkbook</i> |
| * |
| * Adds the LinkTable records required to allow formulas referencing |
| * the specified external workbook to be added to this one. Allows |
| * formulas such as "[MyOtherWorkbook]Sheet3!$A$5" to be added to the |
| * file, for workbooks not already referenced. |
| * |
| * Note: this is not implemented and thus currently throws an Exception stating this. |
| * |
| * @param name The name the workbook will be referenced as in formulas |
| * @param workbook The open workbook to fetch the link required information from |
| * |
| * @throws RuntimeException stating that this method is not implemented yet. |
| */ |
| @Override |
| @NotImplemented |
| public int linkExternalWorkbook(String name, Workbook workbook) { |
| throw new RuntimeException("Not Implemented"); |
| } |
| |
| /** |
| * Register a new toolpack in this workbook. |
| * |
| * @param toopack the toolpack to register |
| */ |
| @Override |
| public void addToolPack(UDFFinder toopack) |
| { |
| _wb.addToolPack(toopack); |
| } |
| |
| /** |
| * Whether the application shall perform a full recalculation when the workbook is opened. |
| * <p> |
| * Typically you want to force formula recalculation when you modify cell formulas or values |
| * of a workbook previously created by Excel. When set to 0, this flag will tell Excel |
| * that it needs to recalculate all formulas in the workbook the next time the file is opened. |
| * </p> |
| * |
| * @param value true if the application will perform a full recalculation of |
| * workbook values when the workbook is opened |
| * @since 3.8 |
| */ |
| @Override |
| public void setForceFormulaRecalculation(boolean value){ |
| _wb.setForceFormulaRecalculation(value); |
| } |
| |
| /** |
| * Whether Excel will be asked to recalculate all formulas when the workbook is opened. |
| */ |
| @Override |
| public boolean getForceFormulaRecalculation(){ |
| return _wb.getForceFormulaRecalculation(); |
| } |
| |
| /** |
| * Returns the spreadsheet version (EXCLE2007) of this workbook |
| * |
| * @return EXCEL2007 SpreadsheetVersion enum |
| * @since 3.14 beta 2 |
| */ |
| @Override |
| public SpreadsheetVersion getSpreadsheetVersion() { |
| return SpreadsheetVersion.EXCEL2007; |
| } |
| |
| @Override |
| public int addOlePackage(byte[] oleData, String label, String fileName, String command) throws IOException { |
| return _wb.addOlePackage(oleData, label, fileName, command); |
| } |
| |
| |
| @Override |
| public EvaluationWorkbook createEvaluationWorkbook() { |
| return SXSSFEvaluationWorkbook.create(this); |
| } |
| } |