fpspreadsheet: Add unit fpsstreams containing a buffered stream for speed-up of writing in virtual mode.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3331 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz 2014-07-18 22:48:38 +00:00
parent 883a18e10a
commit 7f6277ca08
8 changed files with 381 additions and 88 deletions

View File

@ -21,6 +21,7 @@ var
worksheet: TsWorksheet; worksheet: TsWorksheet;
dataprovider: TDataProvider; dataprovider: TDataProvider;
headerTemplate: PCell; headerTemplate: PCell;
t: TTime;
procedure TDataProvider.NeedCellData(Sender: TObject; ARow, ACol: Cardinal; procedure TDataProvider.NeedCellData(Sender: TObject; ARow, ACol: Cardinal;
var AData: variant; var AStyleCell: PCell); var AData: variant; var AStyleCell: PCell);
@ -33,7 +34,6 @@ var
} }
var var
s: String; s: String;
n: Double;
begin begin
if ARow = 0 then begin if ARow = 0 then begin
AData := Format('Column %d', [ACol + 1]); AData := Format('Column %d', [ACol + 1]);
@ -65,8 +65,8 @@ begin
{ These are the essential commands to activate virtual mode: } { These are the essential commands to activate virtual mode: }
workbook.WritingOptions := [woVirtualMode, woSaveMemory]; // workbook.WritingOptions := [woVirtualMode, woSaveMemory];
// workbook.WritingOptions := [woVirtualMode]; workbook.WritingOptions := [woVirtualMode];
{ woSaveMemory can be omitted, but is essential for large files: it causes { woSaveMemory can be omitted, but is essential for large files: it causes
writing temporaray data to a file stream instead of a memory stream. writing temporaray data to a file stream instead of a memory stream.
woSaveMemory, however, considerably slows down writing of biff files. } woSaveMemory, however, considerably slows down writing of biff files. }
@ -74,7 +74,7 @@ begin
{ Next two numbers define the size of virtual spreadsheet. { Next two numbers define the size of virtual spreadsheet.
In case of a database, VirtualRowCount is the RecordCount, VirtualColCount In case of a database, VirtualRowCount is the RecordCount, VirtualColCount
the number of fields to be written to the spreadsheet file } the number of fields to be written to the spreadsheet file }
workbook.VirtualRowCount := 10000; workbook.VirtualRowCount := 60000;
workbook.VirtualColCount := 100; workbook.VirtualColCount := 100;
{ The event handler for OnNeedCellData links the workbook to the method { The event handler for OnNeedCellData links the workbook to the method
@ -93,13 +93,17 @@ begin
worksheet.WriteRowHeight(0, 3); worksheet.WriteRowHeight(0, 3);
worksheet.WriteColWidth(0, 30); worksheet.WriteColWidth(0, 30);
{ In case of a database, you would open the dataset before calling this: } { In case of a database, you would open the dataset before calling this: }
//workbook.WriteToFile('test_virtual.xlsx', sfOOXML, true);
workbook.WriteToFile('test_virtual.xls', sfExcel8, true); t := Now;
workbook.WriteToFile('test_virtual.xlsx', sfOOXML, true);
//workbook.WriteToFile('test_virtual.xls', sfExcel8, true);
t := Now - t;
finally finally
workbook.Free; workbook.Free;
end; end;
WriteLn(Format('Execution time: %.3f sec', [t*24*60*60]));
WriteLn('Press [ENTER] to quit...'); WriteLn('Press [ENTER] to quit...');
ReadLn; ReadLn;
finally finally

View File

@ -0,0 +1,199 @@
unit fpsStreams;
interface
uses
SysUtils, Classes;
const
DEFAULT_STREAM_BUFFER_SIZE = 1024; // * 1024;
type
{ A buffered stream }
TBufStream = class(TStream)
private
FFileStream: TFileStream;
FMemoryStream: TMemoryStream;
FBufWritten: Boolean;
FBufSize: Int64;
FKeepTmpFile: Boolean;
FFileName: String;
protected
procedure CreateFileStream;
function GetPosition: Int64; override;
function GetSize: Int64; override;
public
constructor Create(ATempFile: String; AKeepFile: Boolean = false;
ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); overload;
constructor Create(ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); overload;
destructor Destroy; override;
procedure FlushBuffer;
function Read(var Buffer; Count: Longint): Longint; override;
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
function Write(const ABuffer; ACount: Longint): Longint; override;
end;
procedure ResetStream(var AStream: TStream);
implementation
uses
Math;
{ Resets the stream position to the beginning of the stream. }
procedure ResetStream(var AStream: TStream);
begin
AStream.Position := 0;
end;
constructor TBufStream.Create(ATempFile: String; AKeepFile: Boolean = false;
ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE);
begin
if ATempFile = '' then
ATempFile := ChangeFileExt(GetTempFileName, '.~abc');
// Change extension because of naming conflict if the name of the main file
// is determined by GetTempFileName also. Happens in internaltests suite.
FFileName := ATempFile;
FKeepTmpFile := AKeepFile;
FMemoryStream := TMemoryStream.Create;
// The file stream is only created when needed because of possible conflicts
// of random file names.
FBufSize := ABufSize;
end;
constructor TBufStream.Create(ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE);
begin
Create('', false, ABufSize);
end;
destructor TBufStream.Destroy;
begin
// Write current buffer content to file
FlushBuffer;
// Free streams and delete temporary file, if requested
FreeAndNil(FMemoryStream);
FreeAndNil(FFileStream);
if not FKeepTmpFile and (FFileName <> '') then DeleteFile(FFileName);
inherited Destroy;
end;
{ Creation of the file stream is delayed because of naming conflicts of other
streams are needed with random file names as well (the files do not yet exist
when the streams are created and therefore get the same name by GetTempFileName! }
procedure TBufStream.CreateFileStream;
begin
if FFileStream = nil then begin
if FFileName = '' then FFileName := ChangeFileExt(GetTempFileName, '.~abc');
FFileStream := TFileStream.Create(FFileName, fmCreate + fmOpenRead);
end;
end;
{ Flushes the contents of the memory stream to file }
procedure TBufStream.FlushBuffer;
begin
if (FMemoryStream.Size > 0) and not FBufWritten then begin
FMemoryStream.Position := 0;
CreateFileStream;
FFileStream.CopyFrom(FMemoryStream, FMemoryStream.Size);
FMemoryStream.Clear;
FBufWritten := true;
end;
end;
{ Returns the buffer position. This is the buffer position of the bytes written
to file, plus the current position in the memory buffer }
function TBufStream.GetPosition: Int64;
begin
if FFileStream = nil then
Result := FMemoryStream.Position
else
Result := FFileStream.Position + FMemoryStream.Position;
end;
function TBufStream.GetSize: Int64;
var
n: Int64;
begin
if FFileStream <> nil then
n := FFileStream.Size
else
n := 0;
if n = 0 then n := FMemoryStream.Size;
Result := Max(n, GetPosition);
end;
function TBufStream.Read(var Buffer; Count: Longint): Longint;
begin
// Case 1: All "Count" bytes are contained in memory stream
if FMemoryStream.Position + Count <= FMemoryStream.Size then begin
Result := FMemoryStream.Read(Buffer, Count);
exit;
end;
// Case 2: Memory stream is empty
if FMemoryStream.Size = 0 then begin
CreateFileStream;
Result := FFileStream.Read(Buffer, Count);
exit;
end;
// Case 3: Memory stream is not empty but contains only part of the bytes requested
FlushBuffer;
Result := FFileStream.Read(Buffer, Count);
end;
function TBufStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
var
oldPos: Int64;
newPos: Int64;
begin
oldPos := GetPosition;
case Origin of
soBeginning : newPos := Offset;
soCurrent : newPos := oldPos + Offset;
soEnd : newPos := GetSize - Offset;
end;
// case #1: New position is within buffer, no file stream yet
if (FFileStream = nil) and (newPos < FMemoryStream.Size) then begin
FMemoryStream.Position := newPos;
exit;
end;
CreateFileStream;
// case #2: New position is within buffer, file stream exists
if (newPos >= FFileStream.Position) and (newPos < FFileStream.Position + FMemoryStream.Size)
then begin
FMemoryStream.Position := newPos - FFileStream.Position;
exit;
end;
// case #3: New position is outside buffer
FlushBuffer;
FFileStream.Position := newPos;
end;
function TBufStream.Write(const ABuffer; ACount: LongInt): LongInt;
var
savedPos: Int64;
begin
// Case #1: Bytes fit into buffer
if FMemoryStream.Position + ACount < FBufSize then begin
Result := FMemoryStream.Write(ABuffer, ACount);
FBufWritten := false;
exit;
end;
// Case #2: Buffer would overflow
savedPos := GetPosition;
FlushBuffer;
FFileStream.Position := savedPos;
Result := FFileStream.Write(ABuffer, ACount);
end;
end.

View File

@ -18,7 +18,6 @@
</Parsing> </Parsing>
<Other> <Other>
<CustomOptions Value="$(IDEBuildOptions)"/> <CustomOptions Value="$(IDEBuildOptions)"/>
<CompilerPath Value="$(CompPath)"/>
</Other> </Other>
</CompilerOptions> </CompilerOptions>
<Description Value="laz_fpspreadsheet is a non-visual component that allows you to use the fpspreadsheet package to read/write spreadsheet files in .xls (BIFF/Excel), .ods OpenDocument (LibreOffice/OpenOffice) and .xlsx Open XML (Excel) formats. <Description Value="laz_fpspreadsheet is a non-visual component that allows you to use the fpspreadsheet package to read/write spreadsheet files in .xls (BIFF/Excel), .ods OpenDocument (LibreOffice/OpenOffice) and .xlsx Open XML (Excel) formats.
@ -26,7 +25,7 @@
This package is all you need if you don't want graphical components (like grids and charts)."/> This package is all you need if you don't want graphical components (like grids and charts)."/>
<License Value="LGPL with static linking exception. This is the same license as is used in the LCL (Lazarus Component Library)."/> <License Value="LGPL with static linking exception. This is the same license as is used in the LCL (Lazarus Component Library)."/>
<Version Major="1" Minor="2"/> <Version Major="1" Minor="2"/>
<Files Count="21"> <Files Count="22">
<Item1> <Item1>
<Filename Value="fpolestorage.pas"/> <Filename Value="fpolestorage.pas"/>
<UnitName Value="fpolestorage"/> <UnitName Value="fpolestorage"/>
@ -111,6 +110,10 @@ This package is all you need if you don't want graphical components (like grids
<Filename Value="fpsfunc.pas"/> <Filename Value="fpsfunc.pas"/>
<UnitName Value="fpsfunc"/> <UnitName Value="fpsfunc"/>
</Item21> </Item21>
<Item22>
<Filename Value="fpsstreams.pas"/>
<UnitName Value="fpsStreams"/>
</Item22>
</Files> </Files>
<RequiredPkgs Count="2"> <RequiredPkgs Count="2">
<Item1> <Item1>

View File

@ -11,7 +11,7 @@ uses
xlsbiff5, xlsbiff8, xlsxooxml, fpsutils, fpszipper, uvirtuallayer_types, xlsbiff5, xlsbiff8, xlsxooxml, fpsutils, fpszipper, uvirtuallayer_types,
uvirtuallayer, uvirtuallayer_ole, uvirtuallayer_ole_helpers, uvirtuallayer, uvirtuallayer_ole, uvirtuallayer_ole_helpers,
uvirtuallayer_ole_types, uvirtuallayer_stream, fpolebasic, xlscommon, uvirtuallayer_ole_types, uvirtuallayer_stream, fpolebasic, xlscommon,
wikitable, fpsNumFormatParser, fpsfunc; wikitable, fpsNumFormatParser, fpsfunc, fpsStreams;
implementation implementation

View File

@ -17,7 +17,7 @@ uses
// Instead, add .. to unit search path // Instead, add .. to unit search path
Classes, SysUtils, fpcunit, testutils, testregistry, Classes, SysUtils, fpcunit, testutils, testregistry,
fpsallformats, fpspreadsheet, xlsbiff8 {and a project requirement for lclbase for utf8 handling}, fpsallformats, fpspreadsheet, xlsbiff8 {and a project requirement for lclbase for utf8 handling},
fpsutils, testsutility, md5; fpsutils, fpsstreams, testsutility, md5;
type type
{ TSpreadReadInternalTests } { TSpreadReadInternalTests }
@ -35,6 +35,7 @@ type
procedure SetUp; override; procedure SetUp; override;
procedure TearDown; override; procedure TearDown; override;
procedure TestVirtualMode(AFormat: TsSpreadsheetFormat; SaveMemoryMode: Boolean); procedure TestVirtualMode(AFormat: TsSpreadsheetFormat; SaveMemoryMode: Boolean);
published published
// Tests getting Excel style A1 cell locations from row/column based locations. // Tests getting Excel style A1 cell locations from row/column based locations.
// Bug 26447 // Bug 26447
@ -49,6 +50,8 @@ type
procedure OverwriteExistingFile; procedure OverwriteExistingFile;
// Write out date cell and try to read as UTF8; verify if contents the same // Write out date cell and try to read as UTF8; verify if contents the same
procedure ReadDateAsUTF8; procedure ReadDateAsUTF8;
// Test buffered stream
procedure TestBufStream;
// Virtual mode tests for all file formats // Virtual mode tests for all file formats
procedure TestVirtualMode_BIFF2; procedure TestVirtualMode_BIFF2;
@ -172,6 +175,87 @@ begin
MyWorkbook.Free; MyWorkbook.Free;
end; end;
procedure TSpreadInternalTests.TestBufStream;
const
BUFSIZE = 1024;
var
stream: TBufStream;
readBuf, writeBuf1, writeBuf2: array of byte;
nRead, nWrite1, nWrite2: Integer;
i: Integer;
begin
stream := TBufStream.Create(BUFSIZE);
try
// Write 100 random bytes. They fit into the BUFSIZE of the memory buffer
nWrite1 := 100;
SetLength(writeBuf1, nWrite1);
for i:=0 to nWrite1-1 do writeBuf1[i] := Random(255);
stream.WriteBuffer(writeBuf1[0], nWrite1);
// Check stream size - must be equal to nWrite
CheckEquals(nWrite1, stream.Size, 'Stream size mismatch (#1)');
// Check stream position must be equal to nWrite
CheckEquals(nWrite1, stream.Position, 'Stream position mismatch (#2)');
// Bring stream pointer back to start
stream.Position := 0;
CheckEquals(0, stream.Position, 'Stream position mismatch (#3)');
// Read the first 10 bytes just written and compare
nRead := 10;
SetLength(readBuf, nRead);
nRead := stream.Read(readBuf[0], nRead);
CheckEquals(10, nRead, 'Read/write size mismatch (#4)');
for i:=0 to 9 do
CheckEquals(writeBuf1[i], readBuf[i], Format('Read/write mismatch at position %d (#5)', [i]));
// Back to start, and read the entire stream
stream.Position := 0;
nRead := stream.Size;
Setlength(readBuf, nRead);
nRead := stream.Read(readBuf[0], stream.Size);
CheckEquals(nWrite1, nRead, 'Stream read size mismatch (#6)');
for i:=0 to nWrite1-1 do
CheckEquals(writeBuf1[i], readBuf[i], Format('Read/write mismatch at position %d (#7)', [i]));
// Now put stream pointer to end and write another 2000 bytes. This crosses
// the size of the memory buffer, and the stream must swap to file.
stream.Seek(0, soFromEnd);
CheckEquals(stream.Size, stream.Position, 'Stream position not at end (#8)');
nWrite2 := 2000;
SetLength(writeBuf2, nWrite2);
for i:=0 to nWrite2-1 do writeBuf2[i] := Random(255);
stream.WriteBuffer(writeBuf2[0], nWrite2);
// The stream pointer must be at 100+2000, same for the size
CheckEquals(nWrite1+nWrite2, stream.Position, 'Stream position mismatch (#9)');
CheckEquals(nWrite1+nWrite2, stream.Size, 'Stream size mismatch (#10)');
// Read the last 10 bytes and compare
Stream.Seek(10, soFromEnd);
SetLength(readBuf, 10);
Stream.ReadBuffer(readBuf[0], 10);
for i:=0 to 9 do
CheckEquals(writeBuf2[nWrite2-10+i], readBuf[i], Format('Read/write mismatch at position %d from end (#11)', [i]));
// Now read all from beginning
Stream.Position := 0;
SetLength(readBuf, stream.Size);
nRead := Stream.Read(readBuf[0], stream.Size);
CheckEquals(nWrite1+nWrite2, nRead, 'Read/write size mismatch (#4)');
for i:=0 to nRead-1 do
if i < nWrite1 then
CheckEquals(writeBuf1[i], readBuf[i], Format('Read/write mismatch at position %d (#11)', [i]))
else
CheckEquals(writeBuf2[i-nWrite1], readBuf[i], Format('Read/write mismatch at position %d (#11)', [i]));
finally
stream.Free;
end;
end;
procedure TSpreadInternalTests.TestCellString; procedure TSpreadInternalTests.TestCellString;
var var
r,c: Cardinal; r,c: Cardinal;

View File

@ -220,6 +220,9 @@ var
implementation implementation
uses
fpsStreams;
const const
{ Excel record IDs } { Excel record IDs }
INT_EXCEL_ID_SST = $00FC; //BIFF8 only INT_EXCEL_ID_SST = $00FC; //BIFF8 only
@ -361,12 +364,9 @@ var
Stream: TStream; Stream: TStream;
OutputStorage: TOLEStorage; OutputStorage: TOLEStorage;
OLEDocument: TOLEDocument; OLEDocument: TOLEDocument;
fn: String;
begin begin
if (woSaveMemory in Workbook.WritingOptions) then begin if (woSaveMemory in Workbook.WritingOptions) then begin
fn := GetTempFileName('', 'fpsB8'); Stream := TBufStream.Create
if FileExists(fn) then DeleteFile(fn);
Stream := TFileStream.Create(fn, fmCreate + fmOpenRead)
end else end else
Stream := TMemoryStream.Create; Stream := TMemoryStream.Create;
@ -379,8 +379,6 @@ begin
OutputStorage.WriteOLEFile(AFileName, OLEDocument, AOverwriteExisting, 'Workbook'); OutputStorage.WriteOLEFile(AFileName, OLEDocument, AOverwriteExisting, 'Workbook');
finally finally
if (woSaveMemory in Workbook.WritingOptions) then
DeleteFile(fn);
Stream.Free; Stream.Free;
OutputStorage.Free; OutputStorage.Free;
end; end;

View File

@ -505,12 +505,12 @@ type
procedure WriteSelections(AStream: TStream; ASheet: TsWorksheet); procedure WriteSelections(AStream: TStream; ASheet: TsWorksheet);
procedure WriteSheetPR(AStream: TStream); procedure WriteSheetPR(AStream: TStream);
procedure WriteStringRecord(AStream: TStream; AString: String); virtual; procedure WriteStringRecord(AStream: TStream; AString: String); virtual;
// Writes cell content received by workbook in OnNeedCellData event
procedure WriteVirtualCells(AStream: TStream);
// Writes out a WINDOW1 record // Writes out a WINDOW1 record
procedure WriteWindow1(AStream: TStream); virtual; procedure WriteWindow1(AStream: TStream); virtual;
// Writes the index of the XF record used in the given cell // Writes the index of the XF record used in the given cell
procedure WriteXFIndex(AStream: TStream; ACell: PCell); procedure WriteXFIndex(AStream: TStream; ACell: PCell);
// Writes cell content received by workbook in OnNeedCellData event
procedure WriteVirtualCells(AStream: TStream);
public public
constructor Create(AWorkbook: TsWorkbook); override; constructor Create(AWorkbook: TsWorkbook); override;
@ -2568,6 +2568,49 @@ begin
Unused(AStream, AString); Unused(AStream, AString);
end; end;
procedure TsSpreadBIFFWriter.WriteVirtualCells(AStream: TStream);
var
r,c: Cardinal;
lCell: TCell;
value: variant;
styleCell: PCell;
begin
for r := 0 to Workbook.VirtualRowCount-1 do begin
for c := 0 to Workbook.VirtualColCount-1 do begin
FillChar(lCell, SizeOf(lCell), 0);
value := varNull;
styleCell := nil;
Workbook.OnNeedCellData(Workbook, r, c, value, styleCell);
if styleCell <> nil then lCell := styleCell^;
lCell.Row := r;
lCell.Col := c;
if VarIsNull(value) then
lCell.ContentType := cctEmpty
else
if VarIsNumeric(value) then begin
lCell.ContentType := cctNumber;
lCell.NumberValue := value;
end else
{
if VarIsDateTime(value) then begin
lCell.ContentType := cctNumber;
lCell.DateTimeValue := value;
end else
}
if VarIsStr(value) then begin
lCell.ContentType := cctUTF8String;
lCell.UTF8StringValue := VarToStrDef(value, '');
end else
if VarIsBool(value) then begin
lCell.ContentType := cctBool;
lCell.BoolValue := value <> 0;
end else
lCell.ContentType := cctEmpty;
WriteCellCallback(@lCell, AStream);
end;
end;
end;
{ Writes an Excel 5/8 WINDOW1 record { Writes an Excel 5/8 WINDOW1 record
This record contains general settings for the document window and This record contains general settings for the document window and
global workbook settings. global workbook settings.
@ -2642,48 +2685,5 @@ begin
AStream.WriteWord(WordToLE(lXFIndex)); AStream.WriteWord(WordToLE(lXFIndex));
end; end;
procedure TsSpreadBIFFWriter.WriteVirtualCells(AStream: TStream);
var
r,c: Cardinal;
lCell: TCell;
value: variant;
styleCell: PCell;
begin
for r := 0 to Workbook.VirtualRowCount-1 do begin
for c := 0 to Workbook.VirtualColCount-1 do begin
FillChar(lCell, SizeOf(lCell), 0);
value := varNull;
styleCell := nil;
Workbook.OnNeedCellData(Workbook, r, c, value, styleCell);
if styleCell <> nil then lCell := styleCell^;
lCell.Row := r;
lCell.Col := c;
if VarIsNull(value) then
lCell.ContentType := cctEmpty
else
if VarIsNumeric(value) then begin
lCell.ContentType := cctNumber;
lCell.NumberValue := value;
end else
{
if VarIsDateTime(value) then begin
lCell.ContentType := cctNumber;
lCell.DateTimeValue := value;
end else
}
if VarIsStr(value) then begin
lCell.ContentType := cctUTF8String;
lCell.UTF8StringValue := VarToStrDef(value, '');
end else
if VarIsBool(value) then begin
lCell.ContentType := cctBool;
lCell.BoolValue := value <> 0;
end else
lCell.ContentType := cctEmpty;
WriteCellCallback(@lCell, AStream);
end;
end;
end;
end. end.

View File

@ -115,7 +115,7 @@ type
implementation implementation
uses uses
variants, fpsNumFormatParser, xlscommon; variants, fpsStreams, fpsNumFormatParser, xlscommon;
const const
{ OOXML general XML constants } { OOXML general XML constants }
@ -737,16 +737,16 @@ begin
// Style records // Style records
AppendToStream(FSStyles, AppendToStream(FSStyles,
'<cellStyleXfs count="1">', '<cellStyleXfs count="1">' +
'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" />', '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" />' +
'</cellStyleXfs>' '</cellStyleXfs>'
); );
WriteStyleList(FSStyles, 'cellXfs'); WriteStyleList(FSStyles, 'cellXfs');
// Cell style records // Cell style records
AppendToStream(FSStyles, AppendToStream(FSStyles,
'<cellStyles count="1">', '<cellStyles count="1">' +
'<cellStyle name="Normal" xfId="0" builtinId="0" />', '<cellStyle name="Normal" xfId="0" builtinId="0" />' +
'</cellStyles>'); '</cellStyles>');
// Misc // Misc
@ -779,7 +779,7 @@ begin
'<Relationship Type="%s" Target="worksheets/sheet%d.xml" Id="rId%d" />', '<Relationship Type="%s" Target="worksheets/sheet%d.xml" Id="rId%d" />',
[SCHEMAS_WORKSHEET, i, i+2])); [SCHEMAS_WORKSHEET, i, i+2]));
AppendToStream(FSWOrkbookRels, AppendToStream(FSWorkbookRels,
'</Relationships>'); '</Relationships>');
{ --- Workbook --- } { --- Workbook --- }
@ -820,11 +820,10 @@ begin
XML_HEADER, Format( XML_HEADER, Format(
'<sst xmlns="%s" count="%d" uniqueCount="%d">', [SCHEMAS_SPREADML, FSharedStringsCount, FSharedStringsCount] '<sst xmlns="%s" count="%d" uniqueCount="%d">', [SCHEMAS_SPREADML, FSharedStringsCount, FSharedStringsCount]
)); ));
FSSharedStrings.Position := 0; ResetStream(FSSharedStrings);
FSSharedStrings_complete.CopyFrom(FSSharedStrings, FSSharedStrings.Size); FSSharedStrings_complete.CopyFrom(FSSharedStrings, FSSharedStrings.Size);
AppendToStream(FSSharedStrings_complete, AppendToStream(FSSharedStrings_complete,
'</sst>'); '</sst>');
FSSharedStrings_complete.Position := 0;
end; end;
{ {
@ -875,7 +874,6 @@ var
CellPosText: string; CellPosText: string;
value: Variant; value: Variant;
styleCell: PCell; styleCell: PCell;
fn: String;
row: PRow; row: PRow;
rh: String; rh: String;
h0: Single; h0: Single;
@ -885,11 +883,9 @@ begin
h0 := Workbook.GetDefaultFontSize; // Point size of default font h0 := Workbook.GetDefaultFontSize; // Point size of default font
// Create the stream // Create the stream
if (woSaveMemory in Workbook.WritingOptions) then begin if (woSaveMemory in Workbook.WritingOptions) then
fn := IncludeTrailingPathDelimiter(GetTempDir); FSSheets[FCurSheetNum] := TBufStream.Create(GetTempFileName('', Format('fpsSH%d', [FCurSheetNum])))
fn := GetTempFileName(fn, Format('fpsSH%d-', [FCurSheetNum+1])); else
FSSheets[FCurSheetNum] := TFileStream.Create(fn, fmCreate);
end else
FSSheets[FCurSheetNum] := TMemoryStream.Create; FSSheets[FCurSheetNum] := TMemoryStream.Create;
// Header // Header
@ -1016,18 +1012,15 @@ end;
{ Creates the streams for the individual data files. Will be zipped into a { Creates the streams for the individual data files. Will be zipped into a
single xlsx file. } single xlsx file. }
procedure TsSpreadOOXMLWriter.CreateStreams; procedure TsSpreadOOXMLWriter.CreateStreams;
var
dir: String;
begin begin
if (woSaveMemory in Workbook.WritingOptions) then begin if (woSaveMemory in Workbook.WritingOptions) then begin
dir := IncludeTrailingPathDelimiter(GetTempDir); FSContentTypes := TBufStream.Create(GetTempFileName('', 'fpsCT'));
FSContentTypes := TFileStream.Create(GetTempFileName(dir, 'fpsCT'), fmCreate+fmOpenRead); FSRelsRels := TBufStream.Create(GetTempFileName('', 'fpsRR'));
FSRelsRels := TFileStream.Create(GetTempFileName(dir, 'fpsRR'), fmCreate+fmOpenRead); FSWorkbookRels := TBufStream.Create(GetTempFileName('', 'fpsWBR'));
FSWorkbookRels := TFileStream.Create(GetTempFileName(dir, 'fpsWBR'), fmCreate+fmOpenRead); FSWorkbook := TBufStream.Create(GetTempFileName('', 'fpsWB'));
FSWorkbook := TFileStream.Create(GetTempFileName(dir, 'fpsWB'), fmCreate+fmOpenRead); FSStyles := TBufStream.Create(GetTempFileName('', 'fpsSTY'));
FSStyles := TFileStream.Create(GetTempFileName(dir, 'fpsSTY'), fmCreate+fmOpenRead); FSSharedStrings := TBufStream.Create(GetTempFileName('', 'fpsSS'));
FSSharedStrings := TFileStream.Create(GetTempFileName(dir, 'fpsSST'), fmCreate+fmOpenRead); FSSharedStrings_complete := TBufStream.Create(GetTempFileName('', 'fpsSSC'));
FSSharedStrings_complete := TFileStream.Create(GetTempFileName(dir, 'fpsSSTc'), fmCreate+fmOpenRead);
end else begin; end else begin;
FSContentTypes := TMemoryStream.Create; FSContentTypes := TMemoryStream.Create;
FSRelsRels := TMemoryStream.Create; FSRelsRels := TMemoryStream.Create;
@ -1072,7 +1065,17 @@ end;
procedure TsSpreadOOXMLWriter.ResetStreams; procedure TsSpreadOOXMLWriter.ResetStreams;
var var
stream: TStream; stream: TStream;
i: Integer;
begin begin
ResetStream(FSContentTypes);
ResetStream(FSRelsRels);
ResetStream(FSWorkbookRels);
ResetStream(FSWorkbook);
ResetStream(FSStyles);
ResetStream(FSSharedStrings_complete);
for i := 0 to High(FSSheets) do
ResetStream(FSSheets[i]);
{
FSContentTypes.Position := 0; FSContentTypes.Position := 0;
FSRelsRels.Position := 0; FSRelsRels.Position := 0;
FSWorkbookRels.Position := 0; FSWorkbookRels.Position := 0;
@ -1080,6 +1083,7 @@ begin
FSStyles.Position := 0; FSStyles.Position := 0;
FSSharedStrings_complete.Position := 0; FSSharedStrings_complete.Position := 0;
for stream in FSSheets do stream.Position := 0; for stream in FSSheets do stream.Position := 0;
}
end; end;
{ {
@ -1135,9 +1139,13 @@ begin
WriteGlobalFiles; WriteGlobalFiles;
WriteContent; WriteContent;
// Stream position must be at beginning, it was moved to end during adding of xml strings.
ResetStreams;
{ Now compress the files } { Now compress the files }
FZip := TZipper.Create; FZip := TZipper.Create;
try try
FZip.FileName := '__temp__.tmp';
FZip.Entries.AddFileEntry(FSContentTypes, OOXML_PATH_TYPES); FZip.Entries.AddFileEntry(FSContentTypes, OOXML_PATH_TYPES);
FZip.Entries.AddFileEntry(FSRelsRels, OOXML_PATH_RELS_RELS); FZip.Entries.AddFileEntry(FSRelsRels, OOXML_PATH_RELS_RELS);
FZip.Entries.AddFileEntry(FSWorkbookRels, OOXML_PATH_XL_RELS_RELS); FZip.Entries.AddFileEntry(FSWorkbookRels, OOXML_PATH_XL_RELS_RELS);
@ -1150,9 +1158,6 @@ begin
FZip.Entries.AddFileEntry(FSSheets[i], OOXML_PATH_XL_WORKSHEETS + 'sheet' + IntToStr(i + 1) + '.xml'); FZip.Entries.AddFileEntry(FSSheets[i], OOXML_PATH_XL_WORKSHEETS + 'sheet' + IntToStr(i + 1) + '.xml');
end; end;
// Stream position must be at beginning, it was moved to end during adding of xml strings.
ResetStreams;
FZip.SaveToStream(AStream); FZip.SaveToStream(AStream);
finally finally