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:
parent
883a18e10a
commit
7f6277ca08
@ -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
|
||||||
|
199
components/fpspreadsheet/fpsstreams.pas
Normal file
199
components/fpspreadsheet/fpsstreams.pas
Normal 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.
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user