diff --git a/ooxml/XSSF/Streaming/SXSSFCell.cs b/ooxml/XSSF/Streaming/SXSSFCell.cs index 12b9e490d..3dd85a966 100644 --- a/ooxml/XSSF/Streaming/SXSSFCell.cs +++ b/ooxml/XSSF/Streaming/SXSSFCell.cs @@ -470,7 +470,8 @@ public void SetCellValue(DateTime? value) return; } - SetCellValue(value.Value); + bool date1904 = ((SXSSFWorkbook)Sheet.Workbook).XssfWorkbook.IsDate1904(); + SetCellValue(DateUtil.GetExcelDate(value.Value, date1904)); } public void SetCellValue(double value) @@ -821,8 +822,7 @@ private String ConvertCellValueToString(CellType cellType) public void SetCellValue(DateTime value) { - bool date1904 = Sheet.Workbook.IsDate1904(); - SetCellValue(DateUtil.GetExcelDate(value, date1904)); + SetCellValue((DateTime?)value); } } } diff --git a/ooxml/XSSF/Streaming/SXSSFRow.cs b/ooxml/XSSF/Streaming/SXSSFRow.cs index 93b3455aa..04cf69782 100644 --- a/ooxml/XSSF/Streaming/SXSSFRow.cs +++ b/ooxml/XSSF/Streaming/SXSSFRow.cs @@ -362,7 +362,7 @@ object IEnumerator.Current } public void Dispose() - { + { } public IEnumerator GetEnumerator() @@ -411,9 +411,7 @@ object IEnumerator.Current } public void Dispose() - { - throw new NotImplementedException(); - } + { } public IEnumerator GetEnumerator() { diff --git a/ooxml/XSSF/Streaming/SXSSFSheet.cs b/ooxml/XSSF/Streaming/SXSSFSheet.cs index 1d89320dc..861dfd321 100644 --- a/ooxml/XSSF/Streaming/SXSSFSheet.cs +++ b/ooxml/XSSF/Streaming/SXSSFSheet.cs @@ -678,7 +678,7 @@ public IRow CreateRow(int rownum) { try { - FlushRows(_randomAccessWindowSize); + flushRows(_randomAccessWindowSize, false); } catch (IOException ioe) { @@ -1243,6 +1243,25 @@ public bool Dispose() return _writer.Dispose(); } /** + * Specifies how many rows can be accessed at most via getRow(). + * The exeeding rows (if any) are flushed to the disk while rows + * with lower index values are flushed first. + */ + private void FlushRows(int remaining, bool flushOnDisk) + { + KeyValuePair? lastRow = null; + var flushedRowsCount = 0; + while (_rows.Count > remaining) + { + flushedRowsCount++; + lastRow = flushOneRow(); + } + if (remaining == 0) + allFlushed = true; + + if (lastRow != null && flushOnDisk) + _writer.FlushRows(flushedRowsCount, lastRow.Value.Key, lastRow.Value.Value.LastCellNum); + } * Are all rows flushed to disk? */ public bool AllRowsFlushed @@ -1262,20 +1281,24 @@ public int LastFlushedRowNumber return lastFlushedRowNumber; } } - /** - * Specifies how many rows can be accessed at most via getRow(). - * The exeeding rows (if any) are flushed to the disk while rows - * with lower index values are flushed first. - */ - private void FlushRows(int remaining) + + public void FlushRows() { - while (_rows.Count > remaining) FlushOneRow(); - if (remaining == 0) allFlushed = true; + FlushRows(0, true); } - public void FlushRows() + private KeyValuePair? flushOneRow() { - FlushRows(0); + if (_rows.Count == 0) + return null; + + var firstRow = _rows.FirstOrDefault(); + // Update the best fit column widths for auto-sizing just before the rows are flushed + // _autoSizeColumnTracker.UpdateColumnWidths(row); + _writer.WriteRow(firstRow.Key, firstRow.Value); + _rows.Remove(firstRow.Key); + lastFlushedRowNumber = firstRow.Key; + return firstRow; } private void FlushOneRow() @@ -1299,7 +1322,8 @@ private void FlushOneRow() public Stream GetWorksheetXMLInputStream() { // flush all remaining data and close the temp file writer - FlushRows(0); + flushRows(0, true); + _writer.Close(); return _writer.GetWorksheetXmlInputStream(); } diff --git a/ooxml/XSSF/Streaming/SXSSFWorkbook.cs b/ooxml/XSSF/Streaming/SXSSFWorkbook.cs index 196ce4fe1..b85f4b444 100644 --- a/ooxml/XSSF/Streaming/SXSSFWorkbook.cs +++ b/ooxml/XSSF/Streaming/SXSSFWorkbook.cs @@ -467,7 +467,7 @@ private void InjectData(FileInfo zipfile, Stream outStream) } else { - CopyStream(inputStream, zos); + inputStream.CopyTo(zos); } inputStream.Close(); } @@ -483,17 +483,6 @@ private void InjectData(FileInfo zipfile, Stream outStream) } } - private static void CopyStream(Stream inputStream, Stream outputStream) - { - var chunk = new byte[1024]; - int count; - - while ((count = inputStream.Read(chunk, 0, chunk.Length)) > 0) - { - outputStream.Write(chunk, 0, count); - } - } - private static void CopyStreamAndInjectWorksheet(Stream inputStream, Stream outputStream, Stream worksheetData) { StreamReader inReader = new StreamReader(inputStream, Encoding.UTF8); @@ -604,7 +593,7 @@ private static void CopyStreamAndInjectWorksheet(Stream inputStream, Stream outp outWriter.Flush(); } //Copy the worksheet data to "out". - CopyStream(worksheetData, outputStream); + worksheetData.CopyTo(outputStream); outWriter.Write(""); outWriter.Flush(); //Copy the rest of "in" to "out". diff --git a/ooxml/XSSF/Streaming/SheetDataWriter.cs b/ooxml/XSSF/Streaming/SheetDataWriter.cs index 85e8031d6..0f8c1abb9 100644 --- a/ooxml/XSSF/Streaming/SheetDataWriter.cs +++ b/ooxml/XSSF/Streaming/SheetDataWriter.cs @@ -17,7 +17,6 @@ limitations under the License. using System; using System.Globalization; using System.IO; -using System.Linq; using System.Text; using NPOI.OpenXmlFormats.Spreadsheet; using NPOI.SS.UserModel; @@ -33,7 +32,7 @@ public class SheetDataWriter private static POILogger logger = POILogFactory.GetLogger(typeof(SheetDataWriter)); protected FileInfo TemporaryFileInfo { get; set; } - protected Stream OutputStream { get; set; } + protected Stream OutputStream { get; private set; } private int RowNum { get; set; } public int NumberOfFlushedRows { get; set; } public int LowestIndexOfFlushedRows { get; set; } // meaningful only of _numberOfFlushedRows>0 @@ -45,11 +44,13 @@ public class SheetDataWriter * If two cells contain the same string, then the cell value is the same index into SharedStringsTable */ private SharedStringsTable _sharedStringSource; + private StreamWriter _outputWriter; public SheetDataWriter() { TemporaryFileInfo = CreateTempFile(); OutputStream = CreateWriter(TemporaryFileInfo); + _outputWriter = new StreamWriter(OutputStream, Encoding.UTF8); } public SheetDataWriter(SharedStringsTable sharedStringsTable) : this() { @@ -83,10 +84,10 @@ public virtual Stream CreateWriter(FileInfo fd) { outputStream = DecorateOutputStream(fos); } - catch (Exception e) + catch (Exception) { fos.Close(); - throw e; + throw; } return outputStream; @@ -115,6 +116,7 @@ public void Close() { try { + _outputWriter.Flush(); OutputStream.Flush(); } catch (Exception) @@ -150,10 +152,10 @@ public Stream GetWorksheetXmlInputStream() { return DecorateInputStream(fis); } - catch (IOException e) + catch (IOException) { fis.Close(); - throw e; + throw; } } @@ -172,14 +174,14 @@ protected virtual Stream DecorateInputStream(Stream fis) return fis; } - //protected void Finalize() - //{ - // TemporaryFileInfo.Delete(); - // if (File.Exists(TemporaryFileInfo.FullName)) - // { - // logger.Log(POILogger.ERROR, "Can't delete temporary encryption file: " + TemporaryFileInfo); - // } - //} + protected void FinalizeWriter() + { + TemporaryFileInfo.Delete(); + if (File.Exists(TemporaryFileInfo.FullName)) + { + logger.Log(POILogger.ERROR, "Can't delete temporary encryption file: " + TemporaryFileInfo); + } + } /** * Write a row to the file @@ -189,72 +191,84 @@ protected virtual Stream DecorateInputStream(Stream fis) */ public void WriteRow(int rownum, SXSSFRow row) { - if (NumberOfFlushedRows == 0) + BeginRow(rownum, row); + using (var cells = row.GetEnumerator()) { - LowestIndexOfFlushedRows = rownum; + int columnIndex = 0; + while (cells.MoveNext()) + { + WriteCell(columnIndex++, cells.Current); + } + EndRow(); } + } - NumberLastFlushedRow = Math.Max(rownum, NumberLastFlushedRow); - NumberOfCellsOfLastFlushedRow = row.LastCellNum; - NumberOfFlushedRows++; - BeginRow(rownum, row); - var cells = row.AllCellsIterator(); - int columnIndex = 0; - while (cells.HasNext()) + public void FlushRows(int rowCount, int lastRowNum, int lastRowCellsCount) + { + if (NumberOfFlushedRows == 0) { - WriteCell(columnIndex++, cells.Next()); + LowestIndexOfFlushedRows = lastRowNum; } - EndRow(); + + NumberLastFlushedRow = Math.Max(lastRowNum, NumberLastFlushedRow); + NumberOfCellsOfLastFlushedRow = lastRowCellsCount; + NumberOfFlushedRows += rowCount; + + _outputWriter.Flush(); + OutputStream.Flush(); } private void BeginRow(int rownum, SXSSFRow row1) { - SXSSFRow row = row1 as SXSSFRow; - WriteAsBytes(OutputStream, "\n"); - + WriteAsBytes("\n"); } public void WriteCell(int columnIndex, ICell cell) @@ -263,31 +277,35 @@ public void WriteCell(int columnIndex, ICell cell) { return; } - string cellRef = new CellReference(RowNum, columnIndex).FormatAsString(); - WriteAsBytes(OutputStream, ""); + WriteAsBytes(">"); break; } case CellType.Formula: { - WriteAsBytes(OutputStream, ">"); - WriteAsBytes(OutputStream, ""); + WriteAsBytes(">"); + WriteAsBytes(""); OutputQuotedString(cell.CellFormula); - WriteAsBytes(OutputStream, ""); + WriteAsBytes(""); switch (cell.GetCachedFormulaResultTypeEnum()) { @@ -295,7 +313,9 @@ public void WriteCell(int columnIndex, ICell cell) double nval = cell.NumericCellValue; if (!Double.IsNaN(nval)) { - WriteAsBytes(OutputStream, "" + nval.ToString(CultureInfo.InvariantCulture) + ""); + WriteAsBytes(""); + WriteAsBytes(nval); + WriteAsBytes(""); } break; default: @@ -310,47 +330,55 @@ public void WriteCell(int columnIndex, ICell cell) XSSFRichTextString rt = new XSSFRichTextString(cell.StringCellValue); int sRef = _sharedStringSource.AddEntry(rt.GetCTRst()); - WriteAsBytes(OutputStream, " t=\"" + ST_CellType.s + "\">"); - WriteAsBytes(OutputStream, ""); - WriteAsBytes(OutputStream, sRef.ToString()); - WriteAsBytes(OutputStream, ""); + WriteAsBytes(" t=\""); + WriteAsBytes(nameof(ST_CellType.s)); + WriteAsBytes("\">"); + WriteAsBytes(""); + WriteAsBytes(sRef); + WriteAsBytes(""); } else { - WriteAsBytes(OutputStream, " t=\"inlineStr\">"); - WriteAsBytes(OutputStream, ""); + WriteAsBytes(""); + WriteAsBytes(">"); OutputQuotedString(cell.StringCellValue); - WriteAsBytes(OutputStream, ""); + WriteAsBytes(""); } break; } case CellType.Numeric: { - WriteAsBytes(OutputStream, " t=\"n\">"); - WriteAsBytes(OutputStream, "" + cell.NumericCellValue.ToString(CultureInfo.InvariantCulture) + ""); + WriteAsBytes(" t=\"n\">"); + WriteAsBytes(""); + WriteAsBytes(cell.NumericCellValue); + WriteAsBytes(""); break; } case CellType.Boolean: { - WriteAsBytes(OutputStream, " t=\"b\">"); - WriteAsBytes(OutputStream, "" + (cell.BooleanCellValue ? "1" : "0") + ""); + WriteAsBytes(" t=\"b\">"); + WriteAsBytes(""); + WriteAsBytes(cell.BooleanCellValue ? "1" : "0"); + WriteAsBytes(""); break; } case CellType.Error: { FormulaError error = FormulaError.ForInt(cell.ErrorCellValue); - WriteAsBytes(OutputStream, " t=\"e\">"); - WriteAsBytes(OutputStream, "" + error.String + ""); + WriteAsBytes(" t=\"e\">"); + WriteAsBytes(""); + WriteAsBytes(error.String); + WriteAsBytes(""); break; } default: @@ -358,28 +386,41 @@ public void WriteCell(int columnIndex, ICell cell) throw new InvalidOperationException("Invalid cell type: " + cell.CellType); } } - WriteAsBytes(OutputStream, ""); - OutputStream.Flush(); + WriteAsBytes(""); + } + + private void WriteAsBytes(string text) + { + _outputWriter.Write(text); + } + + private void WriteAsBytes(ArraySegment chars) + { + _outputWriter.Write(chars.Array, chars.Offset, chars.Count); } - private void WriteAsBytes(Stream outStream, string text) + private void WriteAsBytes(int value) { - var bytes = Encoding.UTF8.GetBytes(text); - outStream.Write(bytes, 0, bytes.Length); + _outputWriter.Write(value); } - private void WriteAsBytes(Stream outStream, char[] chars) + private void WriteAsBytes(float value) { - var bytes = Encoding.UTF8.GetBytes(chars); - outStream.Write(bytes, 0, bytes.Length); + _outputWriter.Write(value.ToString(CultureInfo.InvariantCulture)); } + + private void WriteAsBytes(double value) + { + _outputWriter.Write(value.ToString(CultureInfo.InvariantCulture)); + } + /** * @return whether the string has leading / trailing spaces that * need to be preserved with the xml:space=\"preserve\" attribute */ private bool HasLeadingTrailingSpaces(string str) { - if (str != null && str.Length > 0) + if (!string.IsNullOrEmpty(str)) { char firstChar = str[0]; char lastChar = str[str.Length - 1]; @@ -389,9 +430,9 @@ private bool HasLeadingTrailingSpaces(string str) } //Taken from jdk1.3/src/javax/swing/text/html/HTMLWriter.java - protected void OutputQuotedString(String s) + protected void OutputQuotedString(string s) { - if (s == null || s.Length == 0) + if (string.IsNullOrEmpty(s)) { return; } @@ -407,102 +448,99 @@ protected void OutputQuotedString(String s) case '<': if (counter > last) { - WriteAsBytes(OutputStream, GetSubArray(chars, last, counter - last)); + WriteAsBytes(GetSubArray(chars, last, counter - last)); } last = counter + 1; - WriteAsBytes(OutputStream, "<".ToCharArray()); + WriteAsBytes("<"); break; case '>': if (counter > last) { - WriteAsBytes(OutputStream, GetSubArray(chars, last, counter - last)); + WriteAsBytes(GetSubArray(chars, last, counter - last)); } last = counter + 1; - WriteAsBytes(OutputStream, ">".ToCharArray()); + WriteAsBytes(">"); break; case '&': if (counter > last) { - WriteAsBytes(OutputStream, GetSubArray(chars, last, counter - last)); + WriteAsBytes(GetSubArray(chars, last, counter - last)); } last = counter + 1; - WriteAsBytes(OutputStream, "&".ToCharArray()); + WriteAsBytes("&"); break; case '"': if (counter > last) { - WriteAsBytes(OutputStream, GetSubArray(chars, last, counter - last)); + WriteAsBytes(GetSubArray(chars, last, counter - last)); } last = counter + 1; - WriteAsBytes(OutputStream, """.ToCharArray()); + WriteAsBytes("""); break; // Special characters case '\n': case '\r': if (counter > last) { - WriteAsBytes(OutputStream, GetSubArray(chars, last, counter - last)); + WriteAsBytes(GetSubArray(chars, last, counter - last)); } - WriteAsBytes(OutputStream, " ".ToCharArray()); + WriteAsBytes(" "); last = counter + 1; break; case '\t': if (counter > last) { - WriteAsBytes(OutputStream, GetSubArray(chars, last, counter - last)); + WriteAsBytes(GetSubArray(chars, last, counter - last)); } - WriteAsBytes(OutputStream, " ".ToCharArray()); + WriteAsBytes(" "); last = counter + 1; break; case (char)0xa0: if (counter > last) { - WriteAsBytes(OutputStream, GetSubArray(chars, last, counter - last)); + WriteAsBytes(GetSubArray(chars, last, counter - last)); } - WriteAsBytes(OutputStream, " ".ToCharArray()); + WriteAsBytes(" "); last = counter + 1; break; default: // YK: XmlBeans silently replaces all ISO control characters ( < 32) with question marks. // the same rule applies to unicode surrogates and "not a character" symbols. - if (c < ' ' || Char.IsLowSurrogate(c) || Char.IsHighSurrogate(c) || - ('\uFFFE' <= c && c <= '\uFFFF')) + if (c < ' ' || Char.IsLowSurrogate(c) || Char.IsHighSurrogate(c) || '\uFFFE' <= c) { if (counter > last) { - WriteAsBytes(OutputStream, GetSubArray(chars, last, counter - last)); + WriteAsBytes(GetSubArray(chars, last, counter - last)); } - WriteAsBytes(OutputStream, "?"); + WriteAsBytes("?"); last = counter + 1; } else if (c > 127) { if (counter > last) { - WriteAsBytes(OutputStream, GetSubArray(chars, last, counter - last)); + WriteAsBytes(GetSubArray(chars, last, counter - last)); } last = counter + 1; // If the character is outside of UTF8, write the // numeric value. - WriteAsBytes(OutputStream, "&#".ToCharArray()); - WriteAsBytes(OutputStream, ((int)c).ToString()); - WriteAsBytes(OutputStream, ";"); + WriteAsBytes("&#"); + WriteAsBytes(c); + WriteAsBytes(";"); } break; } } if (last < length) { - WriteAsBytes(OutputStream, GetSubArray(chars, last, length - last)); + WriteAsBytes(GetSubArray(chars, last, length - last)); } } - private char[] GetSubArray(char[] oldArray, int skip, int take) + private static ArraySegment GetSubArray(char[] oldArray, int skip, int take) { - var sub = new char[take]; - Array.Copy(oldArray, skip, sub, 0, take); - return sub; + return new ArraySegment(oldArray, skip, take); } /**