From f06c45421b1904f196f6aa4c2f99989a1f3185c6 Mon Sep 17 00:00:00 2001
From: PJ Fanning
Date: Sun, 28 Jun 2020 11:57:37 +0000
Subject: [PATCH] [github-184] New EmittingSXSSFWorkbook. Thanks to mobreza.
This closes #184
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1879302 13f79535-47bb-0310-9956-ffa450edef68
---
.../xssf/streaming/EmittingSXSSFSheet.java | 69 +++
.../xssf/streaming/EmittingSXSSFWorkbook.java | 219 ++++++++
.../poi/xssf/streaming/IRowGenerator.java | 37 ++
.../xssf/streaming/SXSSFFormulaEvaluator.java | 12 +-
.../poi/xssf/streaming/SXSSFPicture.java | 4 +-
.../apache/poi/xssf/streaming/SXSSFSheet.java | 211 ++++----
.../poi/xssf/streaming/SXSSFWorkbook.java | 220 ++++----
.../poi/xssf/streaming/SheetDataWriter.java | 7 +-
.../xssf/streaming/StreamingSheetWriter.java | 84 +++
.../streaming/TestEmittingSXSSFWorkbook.java | 488 ++++++++++++++++++
10 files changed, 1138 insertions(+), 213 deletions(-)
create mode 100644 src/ooxml/java/org/apache/poi/xssf/streaming/EmittingSXSSFSheet.java
create mode 100644 src/ooxml/java/org/apache/poi/xssf/streaming/EmittingSXSSFWorkbook.java
create mode 100644 src/ooxml/java/org/apache/poi/xssf/streaming/IRowGenerator.java
create mode 100644 src/ooxml/java/org/apache/poi/xssf/streaming/StreamingSheetWriter.java
create mode 100644 src/ooxml/testcases/org/apache/poi/xssf/streaming/TestEmittingSXSSFWorkbook.java
diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/EmittingSXSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/streaming/EmittingSXSSFSheet.java
new file mode 100644
index 00000000000..c0b49d5d7a3
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xssf/streaming/EmittingSXSSFSheet.java
@@ -0,0 +1,69 @@
+/* ====================================================================
+ 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.poi.util.Beta;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+
+/**
+ * A variant of SXSSFSheet that uses IRowGenerator to create rows.
+ *
+ * This variant is experimental and APIs may change at short notice.
+ *
+ * @see EmittingSXSSFWorkbook
+ * @since 5.0.0
+ */
+@Beta
+public class EmittingSXSSFSheet extends SXSSFSheet {
+ private IRowGenerator rowGenerator;
+
+ public EmittingSXSSFSheet(EmittingSXSSFWorkbook workbook, XSSFSheet xSheet) throws IOException {
+ super(workbook, xSheet, workbook.getRandomAccessWindowSize());
+ }
+
+ @Override
+ public InputStream getWorksheetXMLInputStream() throws IOException {
+ throw new RuntimeException("Not supported by EmittingSXSSFSheet");
+ }
+
+ public void setRowGenerator(IRowGenerator rowGenerator) {
+ this.rowGenerator = rowGenerator;
+ }
+
+ public void writeRows(OutputStream out) throws IOException {
+ // delayed creation of SheetDataWriter
+ _writer = ((EmittingSXSSFWorkbook) _workbook).createSheetDataWriter(out);
+ try {
+ if (this.rowGenerator != null) {
+ this.rowGenerator.generateRows(this);
+ }
+ } catch (Exception e) {
+ throw new IOException("Error generating Excel rows", e);
+ } finally {
+ // flush buffered rows
+ flushRows(0);
+ // flush writer buffer
+ _writer.close();
+ out.flush();
+ }
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/EmittingSXSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/streaming/EmittingSXSSFWorkbook.java
new file mode 100644
index 00000000000..fb2d28a6ccd
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xssf/streaming/EmittingSXSSFWorkbook.java
@@ -0,0 +1,219 @@
+/* ====================================================================
+ 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.IOException;
+import java.io.OutputStream;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.util.Beta;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * An variant of SXSSFWorkbook that avoids generating a temporary file and writes data directly to
+ * the provided OutputStream.
+ *
+ * This variant is experimental and APIs may change at short notice.
+ *
+ * @since 5.0.0
+ */
+@Beta
+public class EmittingSXSSFWorkbook extends SXSSFWorkbook {
+ private static final POILogger logger = POILogFactory.getLogger(EmittingSXSSFWorkbook.class);
+
+ public EmittingSXSSFWorkbook() {
+ this(null);
+ }
+
+ public EmittingSXSSFWorkbook(XSSFWorkbook workbook) {
+ this(workbook, SXSSFWorkbook.DEFAULT_WINDOW_SIZE);
+ }
+
+ public EmittingSXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize) {
+ super(workbook, rowAccessWindowSize, false, false);
+ }
+
+ @Override
+ protected SheetDataWriter createSheetDataWriter() throws IOException {
+ throw new RuntimeException("Not supported by EmittingSXSSFWorkbook");
+ }
+
+ protected StreamingSheetWriter createSheetDataWriter(OutputStream out) throws IOException {
+ return new StreamingSheetWriter(out);
+ }
+
+ @Override
+ protected ISheetInjector createSheetInjector(SXSSFSheet sxSheet) throws IOException {
+ EmittingSXSSFSheet ssxSheet = (EmittingSXSSFSheet) sxSheet;
+ return (output) -> {
+ ssxSheet.writeRows(output);
+ };
+ }
+
+ @Override
+ SXSSFSheet createAndRegisterSXSSFSheet(XSSFSheet xSheet) {
+ final EmittingSXSSFSheet sxSheet;
+ try {
+ sxSheet = new EmittingSXSSFSheet(this, xSheet);
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ registerSheetMapping(sxSheet, xSheet);
+ return sxSheet;
+ }
+
+ public EmittingSXSSFSheet createSheet() {
+ return (EmittingSXSSFSheet) super.createSheet();
+ }
+
+ public EmittingSXSSFSheet createSheet(String sheetname) {
+ return (EmittingSXSSFSheet) super.createSheet(sheetname);
+ }
+
+ /**
+ * 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 sheetIterator() {
+ return new SheetIterator();
+ }
+
+ private final class SheetIterator implements Iterator {
+ final private Iterator it;
+
+ @SuppressWarnings("unchecked")
+ public SheetIterator() {
+ it = (Iterator) (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();
+ EmittingSXSSFSheet sxSheet = (EmittingSXSSFSheet) getSXSSFSheet(xssfSheet);
+ return (T) (sxSheet == null ? xssfSheet : sxSheet);
+ }
+
+ /**
+ * 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 iterator() {
+ return sheetIterator();
+ }
+
+ @Override
+ public SXSSFSheet getSheetAt(int index) {
+ throw new RuntimeException("Not supported by EmittingSXSSFWorkbook");
+ }
+
+ public XSSFSheet getXSSFSheetAt(int index) {
+ return _wb.getSheetAt(index);
+ }
+
+ /**
+ * Gets the sheet at the given index for streaming.
+ *
+ * @param index the index
+ * @return the streaming sheet at
+ */
+ public EmittingSXSSFSheet getStreamingSheetAt(int index) {
+ XSSFSheet xSheet = _wb.getSheetAt(index);
+ SXSSFSheet sxSheet = getSXSSFSheet(xSheet);
+ if (sxSheet == null && xSheet != null) {
+ return (EmittingSXSSFSheet) createAndRegisterSXSSFSheet(xSheet);
+ } else {
+ return (EmittingSXSSFSheet) sxSheet;
+ }
+ }
+
+ @Override
+ public SXSSFSheet getSheet(String name) {
+ throw new RuntimeException("Not supported by EmittingSXSSFWorkbook");
+ }
+
+ public XSSFSheet getXSSFSheet(String name) {
+ return _wb.getSheet(name);
+ }
+
+ /**
+ * Gets sheet with the given name for streaming.
+ *
+ * @param name the name
+ * @return the streaming sheet
+ */
+ public EmittingSXSSFSheet getStreamingSheet(String name) {
+ XSSFSheet xSheet = _wb.getSheet(name);
+ EmittingSXSSFSheet sxSheet = (EmittingSXSSFSheet) getSXSSFSheet(xSheet);
+ if (sxSheet == null && xSheet != null) {
+ return (EmittingSXSSFSheet) createAndRegisterSXSSFSheet(xSheet);
+ } else {
+ return sxSheet;
+ }
+ }
+
+ /**
+ * 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);
+
+ // The sheet may not be a streaming sheet and is not mapped
+ if (sxSheet != null) {
+ deregisterSheetMapping(xSheet);
+
+ // Clean up temporary resources
+ try {
+ sxSheet.dispose();
+ } catch (IOException e) {
+ logger.log(POILogger.WARN, e);
+ }
+ }
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/IRowGenerator.java b/src/ooxml/java/org/apache/poi/xssf/streaming/IRowGenerator.java
new file mode 100644
index 00000000000..c9ec4e6c5d0
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xssf/streaming/IRowGenerator.java
@@ -0,0 +1,37 @@
+/* ====================================================================
+ 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 org.apache.poi.util.Beta;
+
+/**
+ * IRowGenerator for Emitting SXSSF sheets
+ *
+ * @see EmittingSXSSFWorkbook
+ */
+@Beta
+public interface IRowGenerator {
+
+ /**
+ * Generate and add rows to the sheet
+ *
+ * @param sheet the sheet
+ * @throws Exception the exception
+ */
+ void generateRows(SXSSFSheet sheet) throws Exception;
+}
diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFFormulaEvaluator.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFFormulaEvaluator.java
index 088694054bb..69f704ef788 100644
--- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFFormulaEvaluator.java
+++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFFormulaEvaluator.java
@@ -106,11 +106,13 @@ public static void evaluateAllFormulaCells(SXSSFWorkbook wb, boolean skipOutOfWi
// Process the sheets as best we can
for (Sheet sheet : wb) {
- // Check if any rows have already been flushed out
- int lastFlushedRowNum = ((SXSSFSheet) sheet).getLastFlushedRowNum();
- if (lastFlushedRowNum > -1) {
- if (! skipOutOfWindow) throw new RowFlushedException(0);
- logger.log(POILogger.INFO, "Rows up to " + lastFlushedRowNum + " have already been flushed, skipping");
+ if (sheet instanceof SXSSFSheet) {
+ // Check if any rows have already been flushed out
+ int lastFlushedRowNum = ((SXSSFSheet) sheet).getLastFlushedRowNum();
+ if (lastFlushedRowNum > -1) {
+ if (! skipOutOfWindow) throw new RowFlushedException(0);
+ logger.log(POILogger.INFO, "Rows up to " + lastFlushedRowNum + " have already been flushed, skipping");
+ }
}
// Evaluate what we have
diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFPicture.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFPicture.java
index 1d83b7c7d63..23a43dc3425 100644
--- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFPicture.java
+++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFPicture.java
@@ -24,6 +24,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more
import org.apache.poi.ss.usermodel.Picture;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Shape;
+import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.ImageUtils;
import org.apache.poi.util.Internal;
@@ -201,7 +202,8 @@ private float getRowHeightInPixels(int rowIndex) {
// THE FOLLOWING THREE LINES ARE THE MAIN CHANGE compared to the non-streaming version: use the SXSSF sheet,
// not the XSSF sheet (which never contais rows when using SXSSF)
XSSFSheet xssfSheet = getSheet();
- SXSSFSheet sheet = _wb.getSXSSFSheet(xssfSheet);
+ SXSSFSheet sxSheet = _wb.getSXSSFSheet(xssfSheet);
+ Sheet sheet = sxSheet == null ? xssfSheet : sxSheet;
Row row = sheet.getRow(rowIndex);
float height = row != null ? row.getHeightInPoints() : sheet.getDefaultRowHeightInPoints();
return height * Units.PIXEL_DPI / Units.POINT_DPI;
diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java
index f639dc12e7b..64f24ddcc1a 100644
--- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java
+++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java
@@ -61,19 +61,26 @@ Licensed to the Apache Software Foundation (ASF) under one or more
/**
* Streaming version of XSSFSheet implementing the "BigGridDemo" strategy.
-*/
+ */
public class SXSSFSheet implements Sheet
{
/*package*/ final XSSFSheet _sh;
- private final SXSSFWorkbook _workbook;
+ protected final SXSSFWorkbook _workbook;
private final TreeMap _rows = new TreeMap<>();
- private final SheetDataWriter _writer;
+ protected SheetDataWriter _writer;
private int _randomAccessWindowSize = SXSSFWorkbook.DEFAULT_WINDOW_SIZE;
- private final AutoSizeColumnTracker _autoSizeColumnTracker;
+ protected final AutoSizeColumnTracker _autoSizeColumnTracker;
private int outlineLevelRow;
private int lastFlushedRowNumber = -1;
private boolean allFlushed;
+ protected SXSSFSheet(SXSSFWorkbook workbook, XSSFSheet xSheet, int randomAccessWindowSize) {
+ _workbook = workbook;
+ _sh = xSheet;
+ setRandomAccessWindowSize(randomAccessWindowSize);
+ _autoSizeColumnTracker = new AutoSizeColumnTracker(this);
+ }
+
public SXSSFSheet(SXSSFWorkbook workbook, XSSFSheet xSheet) throws IOException {
_workbook = workbook;
_sh = xSheet;
@@ -90,8 +97,8 @@ SheetDataWriter getSheetDataWriter(){
return _writer;
}
-/* Gets "" document fragment*/
- public InputStream getWorksheetXMLInputStream() throws IOException
+ /* Gets "" document fragment*/
+ public InputStream getWorksheetXMLInputStream() throws IOException
{
// flush all remaining data and close the temp file writer
flushRows(0);
@@ -99,7 +106,7 @@ public InputStream getWorksheetXMLInputStream() throws IOException
return _writer.getWorksheetXMLInputStream();
}
-//start of interface implementation
+ //start of interface implementation
@Override
public Iterator iterator()
{
@@ -128,7 +135,7 @@ public SXSSFRow createRow(int rownum)
if(rownum <= _writer.getLastFlushedRow() ) {
throw new IllegalArgumentException(
"Attempting to write a row["+rownum+"] " +
- "in the range [0," + _writer.getLastFlushedRow() + "] that is already written to disk.");
+ "in the range [0," + _writer.getLastFlushedRow() + "] that is already written to disk.");
}
// attempt to overwrite a existing row in the input template
@@ -143,7 +150,7 @@ public SXSSFRow createRow(int rownum)
allFlushed = false;
if(_randomAccessWindowSize >= 0 && _rows.size() > _randomAccessWindowSize) {
try {
- flushRows(_randomAccessWindowSize);
+ flushRows(_randomAccessWindowSize);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
@@ -274,7 +281,7 @@ public int getColumnWidth(int columnIndex)
/**
* 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).
@@ -283,8 +290,8 @@ public int getColumnWidth(int columnIndex)
@Override
public float getColumnWidthInPixels(int columnIndex) {
return _sh.getColumnWidthInPixels(columnIndex);
- }
-
+ }
+
/**
* Set the default column width for the sheet (if the columns do not define their own width)
* in characters
@@ -308,7 +315,7 @@ public int getDefaultColumnWidth()
{
return _sh.getDefaultColumnWidth();
}
-
+
/**
* Get the default row height for the sheet (if the rows do not define their own height) in
@@ -356,7 +363,7 @@ public void setDefaultRowHeightInPoints(float height)
{
_sh.setDefaultRowHeightInPoints(height);
}
-
+
/**
* Returns the CellStyle that applies to the given
@@ -461,7 +468,7 @@ public void removeMergedRegion(int index)
{
_sh.removeMergedRegion(index);
}
-
+
/**
* Removes a merged region of cells (hence letting them free)
*
@@ -568,7 +575,7 @@ public boolean isDisplayZeros()
{
return _sh.isDisplayZeros();
}
-
+
/**
* Sets whether the worksheet is displayed from right to left instead of from left to right.
*
@@ -577,7 +584,7 @@ public boolean isDisplayZeros()
@Override
public void setRightToLeft(boolean value)
{
- _sh.setRightToLeft(value);
+ _sh.setRightToLeft(value);
}
/**
@@ -588,7 +595,7 @@ public void setRightToLeft(boolean value)
@Override
public boolean isRightToLeft()
{
- return _sh.isRightToLeft();
+ return _sh.isRightToLeft();
}
/**
@@ -733,7 +740,7 @@ public void setPrintGridlines(boolean show)
{
_sh.setPrintGridlines(show);
}
-
+
/**
* Returns whether row and column headings are printed.
*
@@ -841,7 +848,7 @@ public boolean getProtect()
{
return _sh.getProtect();
}
-
+
/**
* Sets the protection enabled as well as the password
* @param password to set for protection. Pass null
to remove protection
@@ -851,7 +858,7 @@ public void protectSheet(String password)
{
_sh.protectSheet(password);
}
-
+
/**
* Answer whether scenario protection is enabled or disabled
*
@@ -862,7 +869,7 @@ public boolean getScenarioProtect()
{
return _sh.getScenarioProtect();
}
-
+
/**
* Window zoom magnification for current view representing percent values.
* Valid values range from 10 to 400. Horizontal and Vertical scale together.
@@ -933,7 +940,7 @@ public void showInPane(int topRow, int leftCol)
*/
@Override
public void setForceFormulaRecalculation(boolean value) {
- _sh.setForceFormulaRecalculation(value);
+ _sh.setForceFormulaRecalculation(value);
}
/**
@@ -942,12 +949,12 @@ public void setForceFormulaRecalculation(boolean value) {
*/
@Override
public boolean getForceFormulaRecalculation() {
- return _sh.getForceFormulaRecalculation();
+ return _sh.getForceFormulaRecalculation();
}
/**
* Not implemented for SXSSFSheets
- *
+ *
* 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.
@@ -969,7 +976,7 @@ public void shiftRows(int startRow, int endRow, int n) {
/**
* Not implemented for SXSSFSheets
- *
+ *
* 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
@@ -1236,7 +1243,7 @@ public void ungroupColumn(int fromColumn, int toColumn)
* Please note the rows being grouped must be in the current window,
* if the rows are already flushed then groupRow has no effect.
*
- *
+ *
* Correct code:
*
* Workbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory
@@ -1249,8 +1256,8 @@ public void ungroupColumn(int fromColumn, int toColumn)
* }
*
*
- *
- *
+ *
+ *
* Incorrect code:
*
* Workbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory
@@ -1261,7 +1268,7 @@ public void ungroupColumn(int fromColumn, int toColumn)
* sh.groupRow(100, 200); // the rows in the range [100, 200] are already flushed and groupRows has no effect
*
*
- *
+ *
*
* @param fromRow start row (0-based)
* @param toRow end row (0-based)
@@ -1280,7 +1287,7 @@ public void groupRow(int fromRow, int toRow)
setWorksheetOutlineLevelRow();
}
-
+
/**
* Set row groupings (like groupRow) in a stream-friendly manner
*
@@ -1308,8 +1315,8 @@ public void setRowOutlineLevel(int rownum, int level)
private void setWorksheetOutlineLevelRow() {
CTWorksheet ct = _sh.getCTWorksheet();
CTSheetFormatPr pr = ct.isSetSheetFormatPr() ?
- ct.getSheetFormatPr() :
- ct.addNewSheetFormatPr();
+ ct.getSheetFormatPr() :
+ ct.addNewSheetFormatPr();
if(outlineLevelRow > 0) {
pr.setOutlineLevelRow((short)outlineLevelRow);
}
@@ -1346,7 +1353,7 @@ public void setRowGroupCollapsed(int row, boolean collapse)
throw new RuntimeException("Unable to expand row: Not Implemented");
}
}
-
+
/**
* @param rowIndex the zero based row index to collapse
*/
@@ -1359,7 +1366,7 @@ private void collapseRow(int rowIndex) {
// Hide all the columns until the end of the group
int lastRow = writeHidden(row, startRow);
- SXSSFRow lastRowObj = getRow(lastRow);
+ SXSSFRow lastRowObj = getRow(lastRow);
if (lastRowObj != null) {
lastRowObj.setCollapsed(true);
} else {
@@ -1368,7 +1375,7 @@ private void collapseRow(int rowIndex) {
}
}
}
-
+
/**
* @param rowIndex the zero based row index to find from
*/
@@ -1388,7 +1395,7 @@ private int findStartOfRowOutlineGroup(int rowIndex) {
}
return currentRow + 1;
}
-
+
private int writeHidden(SXSSFRow xRow, int rowIndex) {
int level = xRow.getOutlineLevel();
SXSSFRow currRow = getRow(rowIndex);
@@ -1412,8 +1419,8 @@ public void setDefaultColumnStyle(int column, CellStyle style)
{
_sh.setDefaultColumnStyle(column, style);
}
-
-
+
+
/**
* Track a column in the sheet for auto-sizing.
* Note this has undefined behavior if a column is tracked after one or more rows are written to the sheet.
@@ -1428,7 +1435,7 @@ public void trackColumnForAutoSizing(int column)
{
_autoSizeColumnTracker.trackColumn(column);
}
-
+
/**
* Track several columns in the sheet for auto-sizing.
* Note this has undefined behavior if columns are tracked after one or more rows are written to the sheet.
@@ -1441,7 +1448,7 @@ public void trackColumnsForAutoSizing(Collection columns)
{
_autoSizeColumnTracker.trackColumns(columns);
}
-
+
/**
* Tracks all columns in the sheet for auto-sizing. If this is called, individual columns do not need to be tracked.
* Because determining the best-fit width for a cell is expensive, this may affect the performance.
@@ -1451,7 +1458,7 @@ public void trackAllColumnsForAutoSizing()
{
_autoSizeColumnTracker.trackAllColumns();
}
-
+
/**
* Removes a column that was previously marked for inclusion in auto-size column tracking.
* When a column is untracked, the best-fit width is forgotten.
@@ -1467,7 +1474,7 @@ public boolean untrackColumnForAutoSizing(int column)
{
return _autoSizeColumnTracker.untrackColumn(column);
}
-
+
/**
* Untracks several columns in the sheet for auto-sizing.
* When a column is untracked, the best-fit width is forgotten.
@@ -1481,7 +1488,7 @@ public boolean untrackColumnsForAutoSizing(Collection columns)
{
return _autoSizeColumnTracker.untrackColumns(columns);
}
-
+
/**
* Untracks all columns in the sheet for auto-sizing. Best-fit column widths are forgotten.
* If this is called, individual columns do not need to be untracked.
@@ -1491,7 +1498,7 @@ public void untrackAllColumnsForAutoSizing()
{
_autoSizeColumnTracker.untrackAllColumns();
}
-
+
/**
* Returns true if column is currently tracked for auto-sizing.
*
@@ -1503,7 +1510,7 @@ public boolean isColumnTrackedForAutoSizing(int column)
{
return _autoSizeColumnTracker.isColumnTracked(column);
}
-
+
/**
* Get the currently tracked columns for auto-sizing.
* Note if all columns are tracked, this will only return the columns that have been explicitly or implicitly tracked,
@@ -1527,7 +1534,7 @@ public Set getTrackedColumnsForAutoSizing()
*
* You can specify whether the content of merged cells should be considered or ignored.
* Default is to ignore merged cells.
- *
+ *
*
* Special note about SXSSF implementation: You must register the columns you wish to track with
* the SXSSFSheet using {@link #trackColumnForAutoSizing(int)} or {@link #trackAllColumnsForAutoSizing()}.
@@ -1554,7 +1561,7 @@ public void autoSizeColumn(int column)
*
* You can specify whether the content of merged cells should be considered or ignored.
* Default is to ignore merged cells.
- *
+ *
*
* Special note about SXSSF implementation: You must register the columns you wish to track with
* the SXSSFSheet using {@link #trackColumnForAutoSizing(int)} or {@link #trackAllColumnsForAutoSizing()}.
@@ -1590,21 +1597,21 @@ public void autoSizeColumn(int column, boolean useMergedCells)
catch (final IllegalStateException e) {
throw new IllegalStateException("Could not auto-size column. Make sure the column was tracked prior to auto-sizing the column.", e);
}
-
+
// get the best-fit width of rows currently in the random access window
final int activeWidth = (int) (256 * SheetUtil.getColumnWidth(this, column, useMergedCells));
// the best-fit width for both flushed rows and random access window rows
// flushedWidth or activeWidth may be negative if column contains only blank cells
final int bestFitWidth = Math.max(flushedWidth, activeWidth);
-
+
if (bestFitWidth > 0) {
final int maxColumnWidth = 255*256; // The maximum column width for an individual cell is 255 characters
final int width = Math.min(bestFitWidth, maxColumnWidth);
setColumnWidth(column, width);
}
}
-
+
/**
* Returns cell comment for the specified row and column
*
@@ -1615,7 +1622,7 @@ public XSSFComment getCellComment(CellAddress ref)
{
return _sh.getCellComment(ref);
}
-
+
/**
* Returns all cell comments on this sheet.
* @return A map of each Comment in the sheet, keyed on the cell address where
@@ -1625,7 +1632,7 @@ public XSSFComment getCellComment(CellAddress ref)
public Map getCellComments() {
return _sh.getCellComments();
}
-
+
/**
* Get a Hyperlink in this sheet anchored at row, column
*
@@ -1637,7 +1644,7 @@ public Map getCellComments() {
public XSSFHyperlink getHyperlink(int row, int column) {
return _sh.getHyperlink(row, column);
}
-
+
/**
* Get a Hyperlink in this sheet located in a cell specified by {code addr}
*
@@ -1649,7 +1656,7 @@ public XSSFHyperlink getHyperlink(int row, int column) {
public XSSFHyperlink getHyperlink(CellAddress addr) {
return _sh.getHyperlink(addr);
}
-
+
/**
* Get a list of Hyperlinks in this sheet
*
@@ -1659,7 +1666,7 @@ public XSSFHyperlink getHyperlink(CellAddress addr) {
public List getHyperlinkList() {
return _sh.getHyperlinkList();
}
-
+
/**
* {@inheritDoc}
*/
@@ -1744,7 +1751,7 @@ public CellRange extends Cell> removeArrayFormula(Cell cell) {
throw new RuntimeException("Not Implemented");
}
-
+
@Override
public DataValidationHelper getDataValidationHelper()
{
@@ -1769,7 +1776,7 @@ public void addValidationData(DataValidation dataValidation)
/**
* Enable filtering for a range of cells
- *
+ *
* @param range the range of cells to filter
*/
@Override
@@ -1782,30 +1789,30 @@ public AutoFilter setAutoFilter(CellRangeAddress range)
public SheetConditionalFormatting getSheetConditionalFormatting(){
return _sh.getSheetConditionalFormatting();
}
-
-
+
+
@Override
public CellRangeAddress getRepeatingRows() {
- return _sh.getRepeatingRows();
+ return _sh.getRepeatingRows();
}
-
+
@Override
public CellRangeAddress getRepeatingColumns() {
- return _sh.getRepeatingColumns();
+ return _sh.getRepeatingColumns();
}
-
+
@Override
public void setRepeatingRows(CellRangeAddress rowRangeRef) {
- _sh.setRepeatingRows(rowRangeRef);
+ _sh.setRepeatingRows(rowRangeRef);
}
-
+
@Override
public void setRepeatingColumns(CellRangeAddress columnRangeRef) {
- _sh.setRepeatingColumns(columnRangeRef);
+ _sh.setRepeatingColumns(columnRangeRef);
}
-
-
-
+
+
+
//end of interface implementation
/**
* Specifies how many rows can be accessed at most via getRow().
@@ -1821,12 +1828,12 @@ public void setRepeatingColumns(CellRangeAddress columnRangeRef) {
*/
public void setRandomAccessWindowSize(int value)
{
- if(value == 0 || value < -1) {
- throw new IllegalArgumentException("RandomAccessWindowSize must be either -1 or a positive integer");
- }
- _randomAccessWindowSize = value;
+ if(value == 0 || value < -1) {
+ throw new IllegalArgumentException("RandomAccessWindowSize must be either -1 or a positive integer");
+ }
+ _randomAccessWindowSize = value;
}
-
+
/**
* Are all rows flushed to disk?
*/
@@ -1880,7 +1887,7 @@ private void flushOneRow() throws IOException
}
public void changeRowNum(SXSSFRow row, int newRowNum)
{
-
+
removeRow(row);
_rows.put(newRowNum,row);
}
@@ -1904,7 +1911,7 @@ boolean dispose() throws IOException {
if (!allFlushed) {
flushRows();
}
- return _writer.dispose();
+ return _writer == null || _writer.dispose();
}
@Override
@@ -1935,21 +1942,21 @@ public XSSFColor getTabColor() {
public void setTabColor(XSSFColor color) {
_sh.setTabColor(color);
}
-
+
/**
* Enable sheet protection
*/
public void enableLocking() {
safeGetProtectionField().setSheet(true);
}
-
+
/**
* Disable sheet protection
*/
public void disableLocking() {
safeGetProtectionField().setSheet(false);
}
-
+
/**
* Enable or disable Autofilters locking.
* This does not modify sheet protection status.
@@ -1958,7 +1965,7 @@ public void disableLocking() {
public void lockAutoFilter(boolean enabled) {
safeGetProtectionField().setAutoFilter(enabled);
}
-
+
/**
* Enable or disable Deleting columns locking.
* This does not modify sheet protection status.
@@ -1967,7 +1974,7 @@ public void lockAutoFilter(boolean enabled) {
public void lockDeleteColumns(boolean enabled) {
safeGetProtectionField().setDeleteColumns(enabled);
}
-
+
/**
* Enable or disable Deleting rows locking.
* This does not modify sheet protection status.
@@ -1976,7 +1983,7 @@ public void lockDeleteColumns(boolean enabled) {
public void lockDeleteRows(boolean enabled) {
safeGetProtectionField().setDeleteRows(enabled);
}
-
+
/**
* Enable or disable Formatting cells locking.
* This does not modify sheet protection status.
@@ -1985,7 +1992,7 @@ public void lockDeleteRows(boolean enabled) {
public void lockFormatCells(boolean enabled) {
safeGetProtectionField().setFormatCells(enabled);
}
-
+
/**
* Enable or disable Formatting columns locking.
* This does not modify sheet protection status.
@@ -1994,7 +2001,7 @@ public void lockFormatCells(boolean enabled) {
public void lockFormatColumns(boolean enabled) {
safeGetProtectionField().setFormatColumns(enabled);
}
-
+
/**
* Enable or disable Formatting rows locking.
* This does not modify sheet protection status.
@@ -2003,7 +2010,7 @@ public void lockFormatColumns(boolean enabled) {
public void lockFormatRows(boolean enabled) {
safeGetProtectionField().setFormatRows(enabled);
}
-
+
/**
* Enable or disable Inserting columns locking.
* This does not modify sheet protection status.
@@ -2012,7 +2019,7 @@ public void lockFormatRows(boolean enabled) {
public void lockInsertColumns(boolean enabled) {
safeGetProtectionField().setInsertColumns(enabled);
}
-
+
/**
* Enable or disable Inserting hyperlinks locking.
* This does not modify sheet protection status.
@@ -2021,7 +2028,7 @@ public void lockInsertColumns(boolean enabled) {
public void lockInsertHyperlinks(boolean enabled) {
safeGetProtectionField().setInsertHyperlinks(enabled);
}
-
+
/**
* Enable or disable Inserting rows locking.
* This does not modify sheet protection status.
@@ -2030,7 +2037,7 @@ public void lockInsertHyperlinks(boolean enabled) {
public void lockInsertRows(boolean enabled) {
safeGetProtectionField().setInsertRows(enabled);
}
-
+
/**
* Enable or disable Pivot Tables locking.
* This does not modify sheet protection status.
@@ -2039,7 +2046,7 @@ public void lockInsertRows(boolean enabled) {
public void lockPivotTables(boolean enabled) {
safeGetProtectionField().setPivotTables(enabled);
}
-
+
/**
* Enable or disable Sort locking.
* This does not modify sheet protection status.
@@ -2048,7 +2055,7 @@ public void lockPivotTables(boolean enabled) {
public void lockSort(boolean enabled) {
safeGetProtectionField().setSort(enabled);
}
-
+
/**
* Enable or disable Objects locking.
* This does not modify sheet protection status.
@@ -2057,7 +2064,7 @@ public void lockSort(boolean enabled) {
public void lockObjects(boolean enabled) {
safeGetProtectionField().setObjects(enabled);
}
-
+
/**
* Enable or disable Scenarios locking.
* This does not modify sheet protection status.
@@ -2066,7 +2073,7 @@ public void lockObjects(boolean enabled) {
public void lockScenarios(boolean enabled) {
safeGetProtectionField().setScenarios(enabled);
}
-
+
/**
* Enable or disable Selection of locked cells locking.
* This does not modify sheet protection status.
@@ -2075,7 +2082,7 @@ public void lockScenarios(boolean enabled) {
public void lockSelectLockedCells(boolean enabled) {
safeGetProtectionField().setSelectLockedCells(enabled);
}
-
+
/**
* Enable or disable Selection of unlocked cells locking.
* This does not modify sheet protection status.
@@ -2085,7 +2092,7 @@ public void lockSelectUnlockedCells(boolean enabled) {
safeGetProtectionField().setSelectUnlockedCells(enabled);
}
-
+
private CTSheetProtection safeGetProtectionField() {
CTWorksheet ct = _sh.getCTWorksheet();
if (!isSheetProtectionEnabled()) {
@@ -2093,12 +2100,12 @@ private CTSheetProtection safeGetProtectionField() {
}
return ct.getSheetProtection();
}
-
+
/* package */ boolean isSheetProtectionEnabled() {
CTWorksheet ct = _sh.getCTWorksheet();
return (ct.isSetSheetProtection());
}
-
+
/**
* Set background color of the sheet tab
*
@@ -2112,10 +2119,10 @@ public void setTabColor(int colorIndex){
color.setIndexed(colorIndex);
pr.setTabColor(color);
}
-
- @NotImplemented
- @Override
- public void shiftColumns(int startColumn, int endColumn, int n){
- throw new UnsupportedOperationException("Not Implemented");
+
+ @NotImplemented
+ @Override
+ public void shiftColumns(int startColumn, int endColumn, int n){
+ throw new UnsupportedOperationException("Not Implemented");
}
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java
index 2074a70b4ac..176986d479d 100644
--- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java
+++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java
@@ -96,13 +96,17 @@ public class SXSSFWorkbook implements Workbook {
public static final int DEFAULT_WINDOW_SIZE = 100;
private static final POILogger logger = POILogFactory.getLogger(SXSSFWorkbook.class);
- private final XSSFWorkbook _wb;
+ protected final XSSFWorkbook _wb;
private final Map _sxFromXHash = new HashMap<>();
private final Map _xFromSxHash = new HashMap<>();
private int _randomAccessWindowSize = DEFAULT_WINDOW_SIZE;
+ protected static interface ISheetInjector {
+ void writeSheetData(OutputStream out) throws IOException;
+ }
+
/**
* whether temp files should be compressed.
*/
@@ -111,23 +115,23 @@ public class SXSSFWorkbook implements Workbook {
/**
* shared string table - a cache of strings in this workbook
*/
- private final SharedStringsTable _sharedStringSource;
+ protected final SharedStringsTable _sharedStringSource;
/**
* controls whether Zip64 mode is used - Always became the default in POI 5.0.0
*/
- private Zip64Mode zip64Mode = Zip64Mode.Always;
+ protected Zip64Mode zip64Mode = Zip64Mode.Always;
/**
* Construct a new workbook with default row window size
*/
public SXSSFWorkbook(){
- this(null /*workbook*/);
+ this(null /*workbook*/);
}
/**
* Construct a workbook from a template.
- *
+ *
* There are three use-cases to use SXSSFWorkbook(XSSFWorkbook) :
*
* -
@@ -144,7 +148,7 @@ public SXSSFWorkbook(){
*
*
* All three use cases can work in a combination.
- *
+ *
* What is not supported:
*
* -
@@ -161,9 +165,9 @@ public SXSSFWorkbook(){
* @param workbook the template workbook
*/
public SXSSFWorkbook(XSSFWorkbook workbook){
- this(workbook, DEFAULT_WINDOW_SIZE);
+ this(workbook, DEFAULT_WINDOW_SIZE);
}
-
+
/**
* Constructs an workbook from an existing workbook.
@@ -186,7 +190,7 @@ public SXSSFWorkbook(XSSFWorkbook workbook){
* @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);
+ this(workbook,rowAccessWindowSize, false);
}
/**
@@ -210,8 +214,8 @@ public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize){
* @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);
+ public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean compressTmpFiles) {
+ this(workbook,rowAccessWindowSize, compressTmpFiles, false);
}
/**
@@ -237,11 +241,11 @@ public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean com
* @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){
+ public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean compressTmpFiles, boolean useSharedStringsTable) {
setRandomAccessWindowSize(rowAccessWindowSize);
setCompressTempFiles(compressTmpFiles);
if (workbook == null) {
- _wb=new XSSFWorkbook();
+ _wb = new XSSFWorkbook();
_sharedStringSource = useSharedStringsTable ? _wb.getSharedStringSource() : null;
} else {
_wb=workbook;
@@ -273,7 +277,7 @@ public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean com
* @param rowAccessWindowSize the number of rows that are kept in memory until flushed out, see above.
*/
public SXSSFWorkbook(int rowAccessWindowSize){
- this(null /*workbook*/, rowAccessWindowSize);
+ this(null /*workbook*/, rowAccessWindowSize);
}
/**
@@ -282,10 +286,10 @@ public SXSSFWorkbook(int rowAccessWindowSize){
* @return The number of rows that are kept in memory at once before flushing them out.
*/
public int getRandomAccessWindowSize() {
- return _randomAccessWindowSize;
+ return _randomAccessWindowSize;
}
- private void setRandomAccessWindowSize(int rowAccessWindowSize) {
+ protected void setRandomAccessWindowSize(int rowAccessWindowSize) {
if(rowAccessWindowSize == 0 || rowAccessWindowSize < -1) {
throw new IllegalArgumentException("rowAccessWindowSize must be greater than 0 or -1");
}
@@ -323,7 +327,7 @@ public boolean isCompressTempFiles() {
* Please note the the "compress" option may cause performance penalty.
*
*
- * Setting this option only affects compression for subsequent createSheet()
+ * Setting this option only affects compression for subsequent createSheet()
* calls.
*
* @param compress whether to compress temp files
@@ -331,7 +335,7 @@ public boolean isCompressTempFiles() {
public void setCompressTempFiles(boolean compress) {
_compressTmpFiles = compress;
}
-
+
@Internal
protected SharedStringsTable getSharedStringSource() {
return _sharedStringSource;
@@ -341,7 +345,7 @@ protected SheetDataWriter createSheetDataWriter() throws IOException {
if(_compressTmpFiles) {
return new GZIPSheetDataWriter(_sharedStringSource);
}
-
+
return new SheetDataWriter(_sharedStringSource);
}
@@ -364,20 +368,20 @@ void registerSheetMapping(SXSSFSheet sxSheet,XSSFSheet xSheet)
void deregisterSheetMapping(XSSFSheet xSheet)
{
SXSSFSheet sxSheet=getSXSSFSheet(xSheet);
-
+
// ensure that the writer is closed in all cases to not have lingering writers
try {
sxSheet.getSheetDataWriter().close();
} catch (IOException e) {
// ignore exception here
}
-
+
_sxFromXHash.remove(sxSheet);
_xFromSxHash.remove(xSheet);
}
- private XSSFSheet getSheetFromZipEntryName(String sheetRef)
+ protected XSSFSheet getSheetFromZipEntryName(String sheetRef)
{
for(XSSFSheet sheet : _sxFromXHash.values())
{
@@ -408,9 +412,7 @@ protected void injectData(ZipEntrySource zipEntrySource, OutputStream out) throw
// See bug 56557, we should not inject data into the special ChartSheets
if (xSheet != null && !(xSheet instanceof XSSFChartSheet)) {
SXSSFSheet sxSheet = getSXSSFSheet(xSheet);
- try (InputStream xis = sxSheet.getWorksheetXMLInputStream()) {
- copyStreamAndInjectWorksheet(is, zos, xis);
- }
+ copyStreamAndInjectWorksheet(is, zos, createSheetInjector(sxSheet));
} else {
IOUtils.copy(is, zos);
}
@@ -434,7 +436,17 @@ protected ZipArchiveOutputStream createArchiveOutputStream(OutputStream out) {
}
}
- private static void copyStreamAndInjectWorksheet(InputStream in, OutputStream out, InputStream worksheetData) throws IOException {
+ 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;
@@ -450,58 +462,58 @@ private static void copyStreamAndInjectWorksheet(InputStream in, OutputStream ou
pos++;
if(pos==n)
{
- if ("')
- {
- // Found
- outWriter.write(s);
- outWriter.write(c);
- s = "";
- n = s.length();
- pos = 0;
- needsStartTag = false;
- continue;
- }
- if (c == '/')
- {
- // Found ')
- {
- // Found
- 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
- break;
- }
+ if ("')
+ {
+ // Found
+ outWriter.write(s);
+ outWriter.write(c);
+ s = "";
+ n = s.length();
+ pos = 0;
+ needsStartTag = false;
+ continue;
+ }
+ if (c == '/')
+ {
+ // Found ')
+ {
+ // Found
+ 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
+ break;
+ }
}
}
else
@@ -523,11 +535,10 @@ private static void copyStreamAndInjectWorksheet(InputStream in, OutputStream ou
outWriter.flush();
if (needsStartTag)
{
- outWriter.write("\n");
- outWriter.flush();
+ outWriter.write("\n");
+ outWriter.flush();
}
- //Copy the worksheet data to "out".
- IOUtils.copy(worksheetData,out);
+ sheetInjector.writeSheetData(out);
outWriter.write("");
outWriter.flush();
//Copy the rest of "in" to "out".
@@ -732,7 +743,7 @@ public int getNumberOfSheets()
{
return _wb.getNumberOfSheets();
}
-
+
/**
* Returns an iterator of the sheets in the workbook
* in sheet order. Includes hidden and very hidden sheets.
@@ -743,7 +754,7 @@ public int getNumberOfSheets()
public Iterator sheetIterator() {
return new SheetIterator<>();
}
-
+
private final class SheetIterator implements Iterator {
final private Iterator it;
@SuppressWarnings("unchecked")
@@ -771,7 +782,7 @@ public void remove() throws IllegalStateException {
"Use Sheet.removeSheetAt(int) instead.");
}
}
-
+
/**
* Alias for {@link #sheetIterator()} to allow
* foreach loops
@@ -816,11 +827,11 @@ 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();
@@ -839,7 +850,7 @@ public Font createFont()
{
return _wb.createFont();
}
-
+
/**
* Finds a font that matches the one with the supplied attributes
*
@@ -905,7 +916,7 @@ public CellStyle getCellStyleAt(int idx)
}
/**
- * Closes the underlying {@link XSSFWorkbook} and {@link OPCPackage}
+ * Closes the underlying {@link XSSFWorkbook} and {@link OPCPackage}
* on which this Workbook is based, if any.
*
* Once this has been called, no further
@@ -918,20 +929,21 @@ public void close() throws IOException {
for (SXSSFSheet sheet : _xFromSxHash.values())
{
try {
- sheet.getSheetDataWriter().close();
+ SheetDataWriter _writer = sheet.getSheetDataWriter();
+ if (_writer != null) _writer.close();
} catch (IOException e) {
logger.log(POILogger.WARN,
"An exception occurred while closing sheet data writer for sheet "
- + sheet.getSheetName() + ".", e);
+ + sheet.getSheetName() + ".", e);
}
}
-
- // Tell the base workbook to close, does nothing if
+
+ // Tell the base workbook to close, does nothing if
// it's a newly created one
_wb.close();
}
-
+
/**
* Write out this workbook to an OutputStream.
*
@@ -962,14 +974,14 @@ public void write(OutputStream stream) throws IOException {
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.
@@ -1053,7 +1065,7 @@ public void removeName(Name name)
_wb.removeName(name);
}
- /**
+ /**
* Sets the printarea for the sheet provided
*
* i.e. Reference = $A$1:$B$2
@@ -1188,7 +1200,7 @@ public CreationHelper getCreationHelper() {
protected boolean isDate1904() {
return _wb.isDate1904();
}
-
+
@Override
@NotImplemented("XSSFWorkbook#isHidden is not implemented")
public boolean isHidden()
@@ -1214,7 +1226,7 @@ public boolean isSheetVeryHidden(int sheetIx)
{
return _wb.isSheetVeryHidden(sheetIx);
}
-
+
@Override
public SheetVisibility getSheetVisibility(int sheetIx) {
return _wb.getSheetVisibility(sheetIx);
@@ -1230,13 +1242,13 @@ public void setSheetHidden(int sheetIx, boolean hidden)
public void setSheetVisibility(int sheetIx, SheetVisibility visibility) {
_wb.setSheetVisibility(sheetIx, visibility);
}
-
+
/**
* Not implemented for SXSSFWorkbook
*
* 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
+ * 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.
@@ -1251,7 +1263,7 @@ public void setSheetVisibility(int sheetIx, SheetVisibility visibility) {
public int linkExternalWorkbook(String name, Workbook workbook) {
throw new RuntimeException("Not Implemented");
}
-
+
/**
* Register a new toolpack in this workbook.
*
@@ -1290,7 +1302,7 @@ public boolean getForceFormulaRecalculation(){
/**
* Returns the spreadsheet version (EXCLE2007) of this workbook
- *
+ *
* @return EXCEL2007 SpreadsheetVersion enum
* @since 3.14 beta 2
*/
@@ -1303,6 +1315,6 @@ public SpreadsheetVersion getSpreadsheetVersion() {
public int addOlePackage(byte[] oleData, String label, String fileName, String command) throws IOException {
return _wb.addOlePackage(oleData, label, fileName, command);
}
-
+
//end of interface implementation
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SheetDataWriter.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SheetDataWriter.java
index cbac3b8822d..8cf5cbc0e2b 100644
--- a/src/ooxml/java/org/apache/poi/xssf/streaming/SheetDataWriter.java
+++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SheetDataWriter.java
@@ -55,7 +55,7 @@ public class SheetDataWriter implements Closeable {
private static final POILogger logger = POILogFactory.getLogger(SheetDataWriter.class);
private final File _fd;
- private final Writer _out;
+ protected final Writer _out;
private int _rownum;
private int _numberOfFlushedRows;
private int _lowestIndexOfFlushedRows; // meaningful only of _numberOfFlushedRows>0
@@ -72,6 +72,11 @@ public SheetDataWriter() throws IOException {
_fd = createTempFile();
_out = createWriter(_fd);
}
+
+ public SheetDataWriter(Writer writer) throws IOException {
+ _fd = null;
+ _out = writer;
+ }
public SheetDataWriter(SharedStringsTable sharedStringsTable) throws IOException {
this();
diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/StreamingSheetWriter.java b/src/ooxml/java/org/apache/poi/xssf/streaming/StreamingSheetWriter.java
new file mode 100644
index 00000000000..c4f64712198
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xssf/streaming/StreamingSheetWriter.java
@@ -0,0 +1,84 @@
+/* ====================================================================
+ 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.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+import org.apache.poi.util.Beta;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * Unlike SheetDataWriter, this writer does not create a temporary file, it writes data directly
+ * to the provided OutputStream.
+ * @since 5.0.0
+ */
+@Beta
+public class StreamingSheetWriter extends SheetDataWriter {
+ private static final POILogger logger = POILogFactory.getLogger(StreamingSheetWriter.class);
+
+ public StreamingSheetWriter() throws IOException {
+ throw new RuntimeException("StreamingSheetWriter requires OutputStream");
+ }
+
+ public StreamingSheetWriter(OutputStream out) throws IOException {
+ super(createWriter(out));
+ logger.log(POILogger.DEBUG, "Preparing SSXSSF sheet writer");
+ }
+
+ @Override
+ public File createTempFile() throws IOException {
+ throw new RuntimeException("Not supported with StreamingSheetWriter");
+ }
+
+ @Override
+ public Writer createWriter(File fd) throws IOException {
+ throw new RuntimeException("Not supported with StreamingSheetWriter");
+ }
+
+ /**
+ * Create a writer for the sheet data.
+ *
+ * @param out the output stream to write to
+ */
+ protected static Writer createWriter(OutputStream out) throws IOException {
+ return new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
+ }
+
+ @Override
+ public void close() throws IOException {
+ _out.flush();
+ }
+
+ @Override
+ public InputStream getWorksheetXMLInputStream() throws IOException {
+ throw new RuntimeException("Not supported with StreamingSheetWriter");
+ }
+
+ @Override
+ boolean dispose() throws IOException {
+ _out.close();
+ return true;
+ }
+}
diff --git a/src/ooxml/testcases/org/apache/poi/xssf/streaming/TestEmittingSXSSFWorkbook.java b/src/ooxml/testcases/org/apache/poi/xssf/streaming/TestEmittingSXSSFWorkbook.java
new file mode 100644
index 00000000000..df526b1b541
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/xssf/streaming/TestEmittingSXSSFWorkbook.java
@@ -0,0 +1,488 @@
+/*
+ * ====================================================================
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.poi.POIDataSamples;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackageAccess;
+import org.apache.poi.ss.usermodel.BaseTestXWorkbook;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.util.NullOutputStream;
+import org.apache.poi.xssf.SXSSFITestDataProvider;
+import org.apache.poi.xssf.XSSFTestDataSamples;
+import org.apache.poi.xssf.usermodel.XSSFCell;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.junit.After;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public final class TestEmittingSXSSFWorkbook extends BaseTestXWorkbook {
+
+ public TestEmittingSXSSFWorkbook() {
+ super(SXSSFITestDataProvider.instance);
+ }
+
+ @After
+ public void tearDown() {
+ ((SXSSFITestDataProvider) _testDataProvider).cleanup();
+ }
+
+ /**
+ * cloning of sheets is not supported in SXSSF
+ */
+ @Override
+ @Test
+ public void cloneSheet() throws IOException {
+ try {
+ super.cloneSheet();
+ fail("expected exception");
+ } catch (RuntimeException e) {
+ assertEquals("Not Implemented", e.getMessage());
+ }
+ }
+
+ /**
+ * cloning of sheets is not supported in SXSSF
+ */
+ @Override
+ @Test
+ public void sheetClone() throws IOException {
+ try {
+ super.sheetClone();
+ fail("expected exception");
+ } catch (RuntimeException e) {
+ assertEquals("Not Implemented", e.getMessage());
+ }
+ }
+
+ /**
+ * Skip this test, as SXSSF doesn't update formulas on sheet name changes.
+ */
+ @Override
+ @Ignore("SXSSF doesn't update formulas on sheet name changes, as most cells probably aren't in memory at the time")
+ @Test
+ public void setSheetName() {
+ }
+
+ @Test
+ public void existingWorkbook() throws IOException {
+ XSSFWorkbook xssfWb1 = new XSSFWorkbook();
+ xssfWb1.createSheet("S1");
+ EmittingSXSSFWorkbook wb1 = new EmittingSXSSFWorkbook(xssfWb1);
+ XSSFWorkbook xssfWb2 = SXSSFITestDataProvider.instance.writeOutAndReadBack(wb1);
+ assertTrue(wb1.dispose());
+
+ EmittingSXSSFWorkbook wb2 = new EmittingSXSSFWorkbook(xssfWb2);
+ assertEquals(1, wb2.getNumberOfSheets());
+ Sheet sheet = wb2.getStreamingSheetAt(0);
+ assertNotNull(sheet);
+ assertEquals("S1", sheet.getSheetName());
+ assertTrue(wb2.dispose());
+ xssfWb2.close();
+ xssfWb1.close();
+
+ wb2.close();
+ wb1.close();
+ }
+
+ @Test
+ public void useSharedStringsTable() throws Exception {
+ // not supported with EmittingSXSSF
+ }
+
+ @Test
+ public void addToExistingWorkbook() throws IOException {
+ XSSFWorkbook xssfWb1 = new XSSFWorkbook();
+ xssfWb1.createSheet("S1");
+ Sheet sheet = xssfWb1.createSheet("S2");
+ Row row = sheet.createRow(1);
+ Cell cell = row.createCell(1);
+ cell.setCellValue("value 2_1_1");
+ EmittingSXSSFWorkbook wb1 = new EmittingSXSSFWorkbook(xssfWb1);
+ XSSFWorkbook xssfWb2 = SXSSFITestDataProvider.instance.writeOutAndReadBack(wb1);
+ assertTrue(wb1.dispose());
+ xssfWb1.close();
+
+ EmittingSXSSFWorkbook wb2 = new EmittingSXSSFWorkbook(xssfWb2);
+ // Add a row to the existing empty sheet
+ EmittingSXSSFSheet ssheet1 = wb2.getStreamingSheetAt(0);
+ ssheet1.setRowGenerator((ssxSheet) -> {
+ Row row1_1 = ssxSheet.createRow(1);
+ Cell cell1_1_1 = row1_1.createCell(1);
+ cell1_1_1.setCellValue("value 1_1_1");
+ });
+
+ // Add a row to the existing non-empty sheet
+ EmittingSXSSFSheet ssheet2 = wb2.getStreamingSheetAt(1);
+ ssheet2.setRowGenerator((ssxSheet) -> {
+ Row row2_2 = ssxSheet.createRow(2);
+ Cell cell2_2_1 = row2_2.createCell(1);
+ cell2_2_1.setCellValue("value 2_2_1");
+ });
+ // Add a sheet with one row
+ EmittingSXSSFSheet ssheet3 = wb2.createSheet("S3");
+ ssheet3.setRowGenerator((ssxSheet) -> {
+ Row row3_1 = ssxSheet.createRow(1);
+ Cell cell3_1_1 = row3_1.createCell(1);
+ cell3_1_1.setCellValue("value 3_1_1");
+ });
+
+ XSSFWorkbook xssfWb3 = SXSSFITestDataProvider.instance.writeOutAndReadBack(wb2);
+ wb2.close();
+
+ assertEquals(3, xssfWb3.getNumberOfSheets());
+ // Verify sheet 1
+ XSSFSheet sheet1 = xssfWb3.getSheetAt(0);
+ assertEquals("S1", sheet1.getSheetName());
+ assertEquals(1, sheet1.getPhysicalNumberOfRows());
+ XSSFRow row1_1 = sheet1.getRow(1);
+ assertNotNull(row1_1);
+ XSSFCell cell1_1_1 = row1_1.getCell(1);
+ assertNotNull(cell1_1_1);
+ assertEquals("value 1_1_1", cell1_1_1.getStringCellValue());
+ // Verify sheet 2
+ XSSFSheet sheet2 = xssfWb3.getSheetAt(1);
+ assertEquals("S2", sheet2.getSheetName());
+ assertEquals(2, sheet2.getPhysicalNumberOfRows());
+ Row row2_1 = sheet2.getRow(1);
+ assertNotNull(row2_1);
+ Cell cell2_1_1 = row2_1.getCell(1);
+ assertNotNull(cell2_1_1);
+ assertEquals("value 2_1_1", cell2_1_1.getStringCellValue());
+ XSSFRow row2_2 = sheet2.getRow(2);
+ assertNotNull(row2_2);
+ XSSFCell cell2_2_1 = row2_2.getCell(1);
+ assertNotNull(cell2_2_1);
+ assertEquals("value 2_2_1", cell2_2_1.getStringCellValue());
+ // Verify sheet 3
+ XSSFSheet sheet3 = xssfWb3.getSheetAt(2);
+ assertEquals("S3", sheet3.getSheetName());
+ assertEquals(1, sheet3.getPhysicalNumberOfRows());
+ XSSFRow row3_1 = sheet3.getRow(1);
+ assertNotNull(row3_1);
+ XSSFCell cell3_1_1 = row3_1.getCell(1);
+ assertNotNull(cell3_1_1);
+ assertEquals("value 3_1_1", cell3_1_1.getStringCellValue());
+
+ xssfWb2.close();
+ xssfWb3.close();
+ wb1.close();
+ }
+
+ @Test
+ public void sheetdataWriter() throws IOException {
+ EmittingSXSSFWorkbook wb = new EmittingSXSSFWorkbook();
+ SXSSFSheet sh = wb.createSheet();
+ assertSame(sh.getClass(), EmittingSXSSFSheet.class);
+ SheetDataWriter wr = sh.getSheetDataWriter();
+ assertNull(wr);
+ wb.close();
+ }
+
+ @Test
+ public void gzipSheetdataWriter() throws IOException {
+ EmittingSXSSFWorkbook wb = new EmittingSXSSFWorkbook();
+ wb.setCompressTempFiles(true);
+
+ final int rowNum = 1000;
+ final int sheetNum = 5;
+ populateData(wb, 1000, 5);
+
+ XSSFWorkbook xwb = SXSSFITestDataProvider.instance.writeOutAndReadBack(wb);
+ for (int i = 0; i < sheetNum; i++) {
+ Sheet sh = xwb.getSheetAt(i);
+ assertEquals("sheet" + i, sh.getSheetName());
+ for (int j = 0; j < rowNum; j++) {
+ Row row = sh.getRow(j);
+ assertNotNull("row[" + j + "]", row);
+ Cell cell1 = row.getCell(0);
+ assertEquals(new CellReference(cell1).formatAsString(), cell1.getStringCellValue());
+
+ Cell cell2 = row.getCell(1);
+ assertEquals(i, (int) cell2.getNumericCellValue());
+
+ Cell cell3 = row.getCell(2);
+ assertEquals(j, (int) cell3.getNumericCellValue());
+ }
+ }
+
+ assertTrue(wb.dispose());
+ xwb.close();
+ wb.close();
+ }
+
+ private static void assertWorkbookDispose(EmittingSXSSFWorkbook wb) {
+ populateData(wb, 1000, 5);
+
+ for (Sheet sheet : wb) {
+ EmittingSXSSFSheet sxSheet = (EmittingSXSSFSheet) sheet;
+ assertNull(sxSheet.getSheetDataWriter());
+ }
+
+ assertTrue(wb.dispose());
+
+ for (Sheet sheet : wb) {
+ EmittingSXSSFSheet sxSheet = (EmittingSXSSFSheet) sheet;
+ assertNull(sxSheet.getSheetDataWriter());
+ }
+ }
+
+ private static void populateData(EmittingSXSSFWorkbook wb, final int rowNum, final int sheetNum) {
+ for (int i = 0; i < sheetNum; i++) {
+ EmittingSXSSFSheet sheet = wb.createSheet("sheet" + i);
+ int index = i;
+ sheet.setRowGenerator((sh) -> {
+ for (int j = 0; j < rowNum; j++) {
+ Row row = sh.createRow(j);
+ Cell cell1 = row.createCell(0);
+ cell1.setCellValue(new CellReference(cell1).formatAsString());
+
+ Cell cell2 = row.createCell(1);
+ cell2.setCellValue(index);
+
+ Cell cell3 = row.createCell(2);
+ cell3.setCellValue(j);
+ }
+ });
+ }
+ }
+
+ @Test
+ public void workbookDispose() throws IOException {
+ EmittingSXSSFWorkbook wb1 = new EmittingSXSSFWorkbook();
+ // the underlying writer is SheetDataWriter
+ assertWorkbookDispose(wb1);
+ wb1.close();
+
+ EmittingSXSSFWorkbook wb2 = new EmittingSXSSFWorkbook();
+ wb2.setCompressTempFiles(true);
+ // the underlying writer is GZIPSheetDataWriter
+ assertWorkbookDispose(wb2);
+ wb2.close();
+ }
+
+ @Ignore("currently writing the same sheet multiple times is not supported...")
+ @Test
+ public void bug53515() throws Exception {
+ Workbook wb1 = new SXSSFWorkbook(10);
+ populateWorkbook(wb1);
+ saveTwice(wb1);
+ Workbook wb2 = new XSSFWorkbook();
+ populateWorkbook(wb2);
+ saveTwice(wb2);
+ wb2.close();
+ wb1.close();
+ }
+
+ @Ignore("Crashes the JVM because of documented JVM behavior with concurrent writing/reading of zip-files, "
+ + "see http://www.oracle.com/technetwork/java/javase/documentation/overview-156328.html")
+ @Test
+ public void bug53515a() throws Exception {
+ File out = new File("Test.xlsx");
+ assertTrue(!out.exists() || out.delete());
+ for (int i = 0; i < 2; i++) {
+ final SXSSFWorkbook wb;
+ if (out.exists()) {
+ wb = new SXSSFWorkbook((XSSFWorkbook) WorkbookFactory.create(out));
+ } else {
+ wb = new SXSSFWorkbook(10);
+ }
+
+ try {
+ FileOutputStream outSteam = new FileOutputStream(out);
+ if (i == 0) {
+ populateWorkbook(wb);
+ } else {
+ System.gc();
+ System.gc();
+ System.gc();
+ }
+
+ wb.write(outSteam);
+ // assertTrue(wb.dispose());
+ outSteam.close();
+ } finally {
+ assertTrue(wb.dispose());
+ }
+ wb.close();
+ }
+ assertTrue(out.exists());
+ assertTrue(out.delete());
+ }
+
+ private static void populateWorkbook(Workbook wb) {
+ Sheet sh = wb.createSheet();
+ for (int rownum = 0; rownum < 100; rownum++) {
+ Row row = sh.createRow(rownum);
+ for (int cellnum = 0; cellnum < 10; cellnum++) {
+ Cell cell = row.createCell(cellnum);
+ String address = new CellReference(cell).formatAsString();
+ cell.setCellValue(address);
+ }
+ }
+ }
+
+ private static void saveTwice(Workbook wb) throws Exception {
+ for (int i = 0; i < 2; i++) {
+ try {
+ NullOutputStream out = new NullOutputStream();
+ wb.write(out);
+ out.close();
+ } catch (Exception e) {
+ throw new Exception("ERROR: failed on " + (i + 1) + "th time calling " + wb.getClass().getName()
+ + ".write() with exception " + e.getMessage(), e);
+ }
+ }
+ }
+
+ @Test
+ public void closeDoesNotModifyWorkbook() throws IOException {
+ final String filename = "SampleSS.xlsx";
+ final File file = POIDataSamples.getSpreadSheetInstance().getFile(filename);
+
+ // Some tests commented out because close() modifies the file
+ // See bug 58779
+
+ // String
+ // wb = new SXSSFWorkbook(new XSSFWorkbook(file.getPath()));
+ // assertCloseDoesNotModifyFile(filename, wb);
+
+ // File
+ // wb = new SXSSFWorkbook(new XSSFWorkbook(file));
+ // assertCloseDoesNotModifyFile(filename, wb);
+
+ // InputStream
+
+ try (FileInputStream fis = new FileInputStream(file);
+ XSSFWorkbook xwb = new XSSFWorkbook(fis);
+ SXSSFWorkbook wb = new SXSSFWorkbook(xwb)) {
+ assertCloseDoesNotModifyFile(filename, wb);
+ }
+
+ // OPCPackage
+ // wb = new SXSSFWorkbook(new XSSFWorkbook(OPCPackage.open(file)));
+ // assertCloseDoesNotModifyFile(filename, wb);
+ }
+
+ /**
+ * Bug #59743
+ *
+ * this is only triggered on other files apart of sheet[1,2,...].xml as those are either copied uncompressed or with
+ * the use of GZIPInputStream so we use shared strings
+ */
+ @Test
+ public void testZipBombNotTriggeredOnUselessContent() throws IOException {
+ SXSSFWorkbook swb = new SXSSFWorkbook(null, 1, true, true);
+ SXSSFSheet s = swb.createSheet();
+ char[] useless = new char[32767];
+ Arrays.fill(useless, ' ');
+
+ for (int row = 0; row < 1; row++) {
+ Row r = s.createRow(row);
+ for (int col = 0; col < 10; col++) {
+ char[] prefix = Integer.toHexString(row * 1000 + col).toCharArray();
+ Arrays.fill(useless, 0, 10, ' ');
+ System.arraycopy(prefix, 0, useless, 0, prefix.length);
+ String ul = new String(useless);
+ r.createCell(col, CellType.STRING).setCellValue(ul);
+ }
+ }
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ swb.write(bos);
+ swb.dispose();
+ swb.close();
+ }
+
+ /**
+ * To avoid accident changes to the template, you should be able to create a SXSSFWorkbook from a read-only XSSF
+ * one, then change + save that (only). See bug #60010 TODO Fix this to work!
+ */
+ @Test
+ @Ignore
+ public void createFromReadOnlyWorkbook() throws Exception {
+ String sheetName = "Test SXSSF";
+ File input = XSSFTestDataSamples.getSampleFile("sample.xlsx");
+
+ try (OPCPackage pkg = OPCPackage.open(input, PackageAccess.READ)) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try (XSSFWorkbook xssf = new XSSFWorkbook(pkg)) {
+ try (SXSSFWorkbook wb = new SXSSFWorkbook(xssf, 2)) {
+ Sheet s = wb.createSheet(sheetName);
+ for (int i = 0; i < 10; i++) {
+ Row r = s.createRow(i);
+ r.createCell(0).setCellValue(true);
+ r.createCell(1).setCellValue(2.4);
+ r.createCell(2).setCellValue("Test Row " + i);
+ }
+ assertEquals(10, s.getLastRowNum());
+
+ wb.write(bos);
+ wb.dispose();
+ }
+ }
+
+ try (XSSFWorkbook xssf = new XSSFWorkbook(new ByteArrayInputStream(bos.toByteArray()))) {
+ Sheet s = xssf.getSheet(sheetName);
+ assertEquals(10, s.getLastRowNum());
+ assertTrue(s.getRow(0).getCell(0).getBooleanCellValue());
+ assertEquals("Test Row 9", s.getRow(9).getCell(2).getStringCellValue());
+ }
+ }
+ }
+
+ @Test
+ public void test56557() throws IOException {
+ Workbook wb = XSSFTestDataSamples.openSampleWorkbook("56557.xlsx");
+
+ // Using streaming XSSFWorkbook makes the output file invalid
+ wb = new SXSSFWorkbook(((XSSFWorkbook) wb));
+
+ // Should not throw POIXMLException: java.io.IOException: Unable to parse xml bean when reading back
+ Workbook wbBack = XSSFTestDataSamples.writeOutAndReadBack(wb);
+ assertNotNull(wbBack);
+ wbBack.close();
+
+ wb.close();
+ }
+}