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 0000000000..c0b49d5d7a
--- /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 0000000000..fb2d28a6cc
--- /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
* Please note, that this method works correctly only for workbooks
* with the default font size (Calibri 11pt for .xlsx).
@@ -283,8 +290,8 @@ public class SXSSFSheet implements Sheet
@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 class SXSSFSheet implements Sheet
{
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 class SXSSFSheet implements Sheet
{
_sh.setDefaultRowHeightInPoints(height);
}
-
+
/**
* Returns the CellStyle that applies to the given
@@ -461,7 +468,7 @@ public class SXSSFSheet implements Sheet
{
_sh.removeMergedRegion(index);
}
-
+
/**
* Removes a merged region of cells (hence letting them free)
*
@@ -568,7 +575,7 @@ public class SXSSFSheet implements Sheet
{
return _sh.isDisplayZeros();
}
-
+
/**
* Sets whether the worksheet is displayed from right to left instead of from left to right.
*
@@ -577,7 +584,7 @@ public class SXSSFSheet implements Sheet
@Override
public void setRightToLeft(boolean value)
{
- _sh.setRightToLeft(value);
+ _sh.setRightToLeft(value);
}
/**
@@ -588,7 +595,7 @@ public class SXSSFSheet implements Sheet
@Override
public boolean isRightToLeft()
{
- return _sh.isRightToLeft();
+ return _sh.isRightToLeft();
}
/**
@@ -733,7 +740,7 @@ public class SXSSFSheet implements Sheet
{
_sh.setPrintGridlines(show);
}
-
+
/**
* Returns whether row and column headings are printed.
*
@@ -841,7 +848,7 @@ public class SXSSFSheet implements Sheet
{
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 class SXSSFSheet implements Sheet
{
_sh.protectSheet(password);
}
-
+
/**
* Answer whether scenario protection is enabled or disabled
*
@@ -862,7 +869,7 @@ public class SXSSFSheet implements Sheet
{
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 class SXSSFSheet implements Sheet
*/
@Override
public void setForceFormulaRecalculation(boolean value) {
- _sh.setForceFormulaRecalculation(value);
+ _sh.setForceFormulaRecalculation(value);
}
/**
@@ -942,12 +949,12 @@ public class SXSSFSheet implements Sheet
*/
@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 class SXSSFSheet implements Sheet
/**
* 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 class SXSSFSheet implements Sheet
* Please note the rows being grouped must be in the current window,
* if the rows are already flushed then groupRow has no effect.
*
- *
- *
+ *
+ *
* Incorrect code:
*
* Workbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory
@@ -1249,8 +1256,8 @@ public class SXSSFSheet implements Sheet
* }
*
*
- *
+ *
*
* @param fromRow start row (0-based)
* @param toRow end row (0-based)
@@ -1280,7 +1287,7 @@ public class SXSSFSheet implements Sheet
setWorksheetOutlineLevelRow();
}
-
+
/**
* Set row groupings (like groupRow) in a stream-friendly manner
*
@@ -1308,8 +1315,8 @@ public class SXSSFSheet implements Sheet
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 class SXSSFSheet implements Sheet
throw new RuntimeException("Unable to expand row: Not Implemented");
}
}
-
+
/**
* @param rowIndex the zero based row index to collapse
*/
@@ -1359,7 +1366,7 @@ public class SXSSFSheet implements Sheet
// 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 @@ public class SXSSFSheet implements Sheet
}
}
}
-
+
/**
* @param rowIndex the zero based row index to find from
*/
@@ -1388,7 +1395,7 @@ public class SXSSFSheet implements Sheet
}
return currentRow + 1;
}
-
+
private int writeHidden(SXSSFRow xRow, int rowIndex) {
int level = xRow.getOutlineLevel();
SXSSFRow currRow = getRow(rowIndex);
@@ -1412,8 +1419,8 @@ public class SXSSFSheet implements Sheet
{
_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 class SXSSFSheet implements Sheet
{
_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 class SXSSFSheet implements Sheet
{
_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 class SXSSFSheet implements Sheet
{
_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 class SXSSFSheet implements Sheet
{
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 class SXSSFSheet implements Sheet
{
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 class SXSSFSheet implements Sheet
{
_autoSizeColumnTracker.untrackAllColumns();
}
-
+
/**
* Returns true if column is currently tracked for auto-sizing.
*
@@ -1503,7 +1510,7 @@ public class SXSSFSheet implements Sheet
{
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 class SXSSFSheet implements Sheet
*
* Workbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory
@@ -1261,7 +1268,7 @@ public class SXSSFSheet implements Sheet
* sh.groupRow(100, 200); // the rows in the range [100, 200] are already flushed and groupRows has no effect
*
*
* 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 class SXSSFSheet implements Sheet *
* 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 class SXSSFSheet implements Sheet
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 class SXSSFSheet implements Sheet
{
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 class SXSSFSheet implements Sheet
public Map Construct a workbook from a template.
*
* All three use cases can work in a combination.
- *
+ *
* What is not supported:
*
*
- * Setting this option only affects compression for subsequent createSheet()
+ * Setting this option only affects compression for subsequent createSheet()
* calls.
*
Once this has been called, no further @@ -918,20 +929,21 @@ public class SXSSFWorkbook implements Workbook { 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 class SXSSFWorkbook implements Workbook { 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 class SXSSFWorkbook implements Workbook { _wb.removeName(name); } - /** + /** * Sets the printarea for the sheet provided *
* i.e. Reference = $A$1:$B$2 @@ -1188,7 +1200,7 @@ public class SXSSFWorkbook implements Workbook { protected boolean isDate1904() { return _wb.isDate1904(); } - + @Override @NotImplemented("XSSFWorkbook#isHidden is not implemented") public boolean isHidden() @@ -1214,7 +1226,7 @@ public class SXSSFWorkbook implements Workbook { { return _wb.isSheetVeryHidden(sheetIx); } - + @Override public SheetVisibility getSheetVisibility(int sheetIx) { return _wb.getSheetVisibility(sheetIx); @@ -1230,13 +1242,13 @@ public class SXSSFWorkbook implements Workbook { 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 class SXSSFWorkbook implements Workbook { public int linkExternalWorkbook(String name, Workbook workbook) { throw new RuntimeException("Not Implemented"); } - + /** * Register a new toolpack in this workbook. * @@ -1290,7 +1302,7 @@ public class SXSSFWorkbook implements Workbook { /** * Returns the spreadsheet version (EXCLE2007) of this workbook - * + * * @return EXCEL2007 SpreadsheetVersion enum * @since 3.14 beta 2 */ @@ -1303,6 +1315,6 @@ public class SXSSFWorkbook implements Workbook { 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 cbac3b8822..8cf5cbc0e2 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 class SheetDataWriter implements Closeable { _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 0000000000..c4f6471219 --- /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 0000000000..df526b1b54 --- /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(); + } +}