fpspreadsheet: Extend TBufStream for reading. Rename workbook's WritingOptions to Options, and the option flags from woXXXX to boXXXX. Use boBufStream (former woBufStream) to activate TBufStream for reading of xls and ods. Speed up reading of biff2 by factor 3 (rel to commit before prev one). Fix pile-up of temporary files when saving ods.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3357 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-07-22 15:59:29 +00:00
parent 38bb8b4756
commit 064dd6aba2
10 changed files with 343 additions and 127 deletions

View File

@@ -45,10 +45,8 @@ type
var AValue: Variant; var AStyleCell: PCell); var AValue: Variant; var AStyleCell: PCell);
procedure ReadFromIni; procedure ReadFromIni;
procedure WriteToIni; procedure WriteToIni;
procedure RunReadTest(Idx: Integer; Log: String; procedure RunReadTest(Idx: Integer; Log: String; Options: TsWorkbookOptions);
Options: TsWorkbookWritingOptions); procedure RunWriteTest(Idx: integer; Rows: integer; Log: string; Options: TsWorkbookOptions);
procedure RunWriteTest(Idx: integer; Rows: integer; Log: string;
Options: TsWorkbookWritingOptions);
procedure StatusMsg(const AMsg: String); procedure StatusMsg(const AMsg: String);
public public
{ public declarations } { public declarations }
@@ -80,6 +78,8 @@ const
rc100k = 6; rc100k = 6;
CONTENT_PREFIX: array[0..2] of Char = ('S', 'N', 'M'); CONTENT_PREFIX: array[0..2] of Char = ('S', 'N', 'M');
CONTENT_TEXT: array[0..2] of string = ('strings only', 'numbers only', '50% strings and 50% numbers');
FORMAT_EXT: array[0..4] of String = ('.ods', '.xlsx', '.xls', '_b5.xls', '_b2.xls'); FORMAT_EXT: array[0..4] of String = ('.ods', '.xlsx', '.xls', '_b5.xls', '_b2.xls');
SPREAD_FORMAT: array[0..4] of TsSpreadsheetFormat = (sfOpenDocument, sfOOXML, sfExcel8, sfExcel5, sfExcel2); SPREAD_FORMAT: array[0..4] of TsSpreadsheetFormat = (sfOpenDocument, sfOOXML, sfExcel8, sfExcel5, sfExcel2);
@@ -116,7 +116,7 @@ begin
end; end;
procedure TForm1.RunReadTest(Idx: Integer; Log: String; procedure TForm1.RunReadTest(Idx: Integer; Log: String;
Options: TsWorkbookWritingOptions); Options: TsWorkbookOptions);
var var
MyWorkbook: TsWorkbook; MyWorkbook: TsWorkbook;
MyWorksheet: TsWorksheet; MyWorksheet: TsWorksheet;
@@ -127,9 +127,7 @@ var
ok: Boolean; ok: Boolean;
begin begin
s := Trim(Log); s := Trim(Log);
Log := Log + ' '; Log := Log + ' ';
try try
for i := 0 to CgFormats.Items.Count-1 do begin for i := 0 to CgFormats.Items.Count-1 do begin
if FEscape then begin if FEscape then begin
@@ -162,6 +160,7 @@ begin
MyWorkbook := TsWorkbook.Create; MyWorkbook := TsWorkbook.Create;
try try
Application.ProcessMessages; Application.ProcessMessages;
MyWorkbook.Options := Options;
Tm := GetTickCount; Tm := GetTickCount;
try try
MyWorkbook.ReadFromFile(fname, SPREAD_FORMAT[i]); MyWorkbook.ReadFromFile(fname, SPREAD_FORMAT[i]);
@@ -184,7 +183,7 @@ begin
end; end;
procedure TForm1.RunWriteTest(Idx: integer; Rows: integer; Log: string; procedure TForm1.RunWriteTest(Idx: integer; Rows: integer; Log: string;
Options: TsWorkbookWritingOptions); Options: TsWorkbookOptions);
var var
MyWorkbook: TsWorkbook; MyWorkbook: TsWorkbook;
MyWorksheet: TsWorksheet; MyWorksheet: TsWorksheet;
@@ -201,13 +200,13 @@ begin
end; end;
MyWorksheet := MyWorkbook.AddWorksheet('Sheet1'); MyWorksheet := MyWorkbook.AddWorksheet('Sheet1');
MyWorkbook.WritingOptions := Options; MyWorkbook.Options := Options;
Application.ProcessMessages; Application.ProcessMessages;
Tm := GetTickCount; Tm := GetTickCount;
try try
if woVirtualMode in Options then if boVirtualMode in Options then
begin begin
MyWorkbook.VirtualRowCount := Rows; MyWorkbook.VirtualRowCount := Rows;
MyWorkbook.VirtualColCount := COLCOUNT; MyWorkbook.VirtualColCount := COLCOUNT;
@@ -222,7 +221,7 @@ begin
for ARow := 0 to Rows - 1 do for ARow := 0 to Rows - 1 do
begin begin
if ARow mod 1000 = 0 then begin if ARow mod 1000 = 0 then begin
StatusMsg(Format('Populating row %d', [ARow])); StatusMsg(Format('Building row %d...', [ARow]));
if FEscape then begin if FEscape then begin
Log := 'Test aborted'; Log := 'Test aborted';
exit; exit;
@@ -236,7 +235,8 @@ begin
1: for ACol := 0 to COLCOUNT-1 do 1: for ACol := 0 to COLCOUNT-1 do
MyWorksheet.WriteNumber(ARow, ACol, 1E5*ARow + ACol); MyWorksheet.WriteNumber(ARow, ACol, 1E5*ARow + ACol);
2: for ACol := 0 to COLCOUNT-1 do 2: for ACol := 0 to COLCOUNT-1 do
if (odd(ARow) and odd(ACol)) or odd(ARow+ACol) then begin if (odd(ARow) and odd(ACol)) or odd(ARow+ACol) then
begin
S := 'Xy' + IntToStr(ARow) + 'x' + IntToStr(ACol); S := 'Xy' + IntToStr(ARow) + 'x' + IntToStr(ACol);
MyWorksheet.WriteUTF8Text(ARow, ACol, S); MyWorksheet.WriteUTF8Text(ARow, ACol, S);
end else end else
@@ -255,7 +255,8 @@ begin
Log := Log + ' ' + format('%5.1f ', [(GetTickCount - Tm) / 1000]); Log := Log + ' ' + format('%5.1f ', [(GetTickCount - Tm) / 1000]);
for k := 0 to CgFormats.Items.Count-1 do begin for k := 0 to CgFormats.Items.Count-1 do
begin
if FEscape then begin if FEscape then begin
Log := 'Test aborted'; Log := 'Test aborted';
exit; exit;
@@ -313,16 +314,11 @@ begin
FEscape := false; FEscape := false;
EnableControls(false); EnableControls(false);
try
Memo.Append ('Running: Reading TsWorkbook from various file formats'); Memo.Append ('Running: Reading TsWorkbook from various file formats');
case RgContent.ItemIndex of Memo.Append (' Worksheet contains ' + CONTENT_TEXT[RgContent.ItemIndex]);
0: Memo.Append(' Worksheet contains strings only');
1: Memo.Append(' Worksheet contains numbers only');
2: Memo.Append(' Worksheet contains 50% strings and 50% numbers');
end;
Memo.Append (' (Times in seconds)'); Memo.Append (' (Times in seconds)');
//'----------- .ods .xlsx biff8 biff5 biff2'); //'----------- .ods .xlsx biff8 biff5 biff2');
//'Rows x Cols W.Options Build Write Write Write Write Write' //'Rows x Cols Options Build Write Write Write Write Write'
s := '-------------------------------- '; s := '-------------------------------- ';
if CgFormats.Checked[fmtODS] then s := s + ' .ods '; if CgFormats.Checked[fmtODS] then s := s + ' .ods ';
if CgFormats.Checked[fmtXLSX] then s := s + '.xlsx '; if CgFormats.Checked[fmtXLSX] then s := s + '.xlsx ';
@@ -330,7 +326,7 @@ begin
if CgFormats.Checked[fmtXLS5] then s := s + 'biff5 '; if CgFormats.Checked[fmtXLS5] then s := s + 'biff5 ';
if CgFormats.Checked[fmtXLS2] then s := s + 'biff2'; if CgFormats.Checked[fmtXLS2] then s := s + 'biff2';
Memo.Append(TrimRight(s)); Memo.Append(TrimRight(s));
s := 'Rows x Cols W.Options '; s := 'Rows x Cols Options ';
if CgFormats.Checked[fmtODS] then s := s + ' Read '; if CgFormats.Checked[fmtODS] then s := s + ' Read ';
if CgFormats.Checked[fmtXLSX] then s := s + ' Read '; if CgFormats.Checked[fmtXLSX] then s := s + ' Read ';
if CgFormats.Checked[fmtXLS8] then s := s + ' Read '; if CgFormats.Checked[fmtXLS8] then s := s + ' Read ';
@@ -341,6 +337,7 @@ begin
len := Length(s); len := Length(s);
Memo.Append(DupeString('-', len)); Memo.Append(DupeString('-', len));
try
for i:=0 to CgRowCount.Items.Count-1 do begin for i:=0 to CgRowCount.Items.Count-1 do begin
if FEscape then if FEscape then
exit; exit;
@@ -351,12 +348,16 @@ begin
rows := GetRowCount(i); rows := GetRowCount(i);
s := Format('%7.0nx%d', [1.0*rows, COLCOUNT]); s := Format('%7.0nx%d', [1.0*rows, COLCOUNT]);
if CbVirtualModeOnly.Checked then begin
//RunReadTest(2, s + ' [boVM ]', [boVirtualMode]);
//RunReadTest(4, s + ' [boVM, boBS]', [boVirtualMode, boBufStream]);
end else begin
RunReadTest(1, s + ' [ ]', []); RunReadTest(1, s + ' [ ]', []);
(* //RunReadTest(2, s + ' [boVM ]', [boVirtualMode]);
RunReadTest(2, s + ' [woVM ]', [woVirtualMode]); RunReadTest(3, s + ' [ boBS]', [boBufStream]);
RunReadTest(3, s + ' [ woBS]', [woBufStream]); //RunReadTest(4, s + ' [boVM, boBS]', [boVirtualMode, boBufStream]);
RunReadTest(4, s + ' [woVM, woBS]', [woVirtualMode, woBufStream]); end;
*)
Memo.Append(DupeString('-', len)); Memo.Append(DupeString('-', len));
end; end;
Memo.Append('Ready'); Memo.Append('Ready');
@@ -378,14 +379,10 @@ begin
EnableControls(false); EnableControls(false);
Memo.Append ('Running: Building TsWorkbook and writing to different file formats'); Memo.Append ('Running: Building TsWorkbook and writing to different file formats');
case RgContent.ItemIndex of Memo.Append (' Worksheet contains ' + CONTENT_TEXT[RgContent.ItemIndex]);
0: Memo.Append(' Worksheet contains strings only');
1: Memo.Append(' Worksheet contains numbers only');
2: Memo.Append(' Worksheet contains 50% strings and 50% numbers');
end;
Memo.Append (' (Times in seconds)'); Memo.Append (' (Times in seconds)');
//'----------- .ods .xlsx biff8 biff5 biff2'); //'----------- .ods .xlsx biff8 biff5 biff2');
//'Rows x Cols W.Options Build Write Write Write Write Write' //'Rows x Cols Options Build Write Write Write Write Write'
s := '-------------------------------- '; s := '-------------------------------- ';
if CgFormats.Checked[fmtODS] then s := s + ' .ods '; if CgFormats.Checked[fmtODS] then s := s + ' .ods ';
if CgFormats.Checked[fmtXLSX] then s := s + '.xlsx '; if CgFormats.Checked[fmtXLSX] then s := s + '.xlsx ';
@@ -393,7 +390,7 @@ begin
if CgFormats.Checked[fmtXLS5] then s := s + 'biff5 '; if CgFormats.Checked[fmtXLS5] then s := s + 'biff5 ';
if CgFormats.Checked[fmtXLS2] then s := s + 'biff2'; if CgFormats.Checked[fmtXLS2] then s := s + 'biff2';
Memo.Append(TrimRight(s)); Memo.Append(TrimRight(s));
s := 'Rows x Cols W.Options Build '; s := 'Rows x Cols Options Build ';
if CgFormats.Checked[fmtODS] then s := s + 'Write '; if CgFormats.Checked[fmtODS] then s := s + 'Write ';
if CgFormats.Checked[fmtXLSX] then s := s + 'Write '; if CgFormats.Checked[fmtXLSX] then s := s + 'Write ';
if CgFormats.Checked[fmtXLS8] then s := s + 'Write '; if CgFormats.Checked[fmtXLS8] then s := s + 'Write ';
@@ -414,13 +411,13 @@ begin
Rows := GetRowCount(i); Rows := GetRowCount(i);
s := Format('%7.0nx%d', [1.0*Rows, COLCOUNT]); s := Format('%7.0nx%d', [1.0*Rows, COLCOUNT]);
if CbVirtualModeOnly.Checked then begin if CbVirtualModeOnly.Checked then begin
RunWriteTest(2, Rows, s + ' [woVM ]', [woVirtualMode]); RunWriteTest(2, Rows, s + ' [boVM ]', [boVirtualMode]);
RunWriteTest(4, Rows, s + ' [woVM, woBS]', [woVirtualMode, woBufStream]); RunWriteTest(4, Rows, s + ' [boVM, boBS]', [boVirtualMode, boBufStream]);
end else begin end else begin
RunWriteTest(1, Rows, s + ' [ ]', []); RunWriteTest(1, Rows, s + ' [ ]', []);
RunWriteTest(2, Rows, s + ' [woVM ]', [woVirtualMode]); RunWriteTest(2, Rows, s + ' [boVM ]', [boVirtualMode]);
RunWriteTest(3, Rows, s + ' [ woBS]', [woBufStream]); RunWriteTest(3, Rows, s + ' [ boBS]', [boBufStream]);
RunWriteTest(4, Rows, s + ' [woVM, woBS]', [woVirtualMode, woBufStream]); RunWriteTest(4, Rows, s + ' [boVM, boBS]', [boVirtualMode, boBufStream]);
end; end;
Memo.Append(DupeString('-', len)); Memo.Append(DupeString('-', len));
end; end;
@@ -450,6 +447,7 @@ begin
CgRowCount.Enabled := AEnable; CgRowCount.Enabled := AEnable;
LblCancel.Visible := not AEnable; LblCancel.Visible := not AEnable;
StatusMsg(''); StatusMsg('');
Application.ProcessMessages;
end; end;
procedure TForm1.FormCreate(Sender: TObject); procedure TForm1.FormCreate(Sender: TObject);

View File

@@ -64,17 +64,17 @@ begin
{ These are the essential commands to activate virtual mode: } { These are the essential commands to activate virtual mode: }
// workbook.WritingOptions := [woVirtualMode, woBufStream]; workbook.Options := [boVirtualMode, boBufStream];
workbook.WritingOptions := [woVirtualMode]; // workbook.Options := [boVirtualMode];
{ woBufStream can be omitted, but is important for large files: it causes { boBufStream can be omitted, but is important for large files: it causes
writing temporary data to a buffered file stream instead of a pure writing temporary data to a buffered file stream instead of a pure
memory stream which can overflow memory. The option can slow down the memory stream which can overflow memory. In cases, the option can slow
writing process a bit. } down the writing process a bit. }
{ 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 := 20000; workbook.VirtualRowCount := 5000;
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
@@ -95,8 +95,10 @@ begin
{ 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: }
t := Now; t := Now;
//workbook.WriteToFile('test_virtual.xlsx', sfOOXML, true); workbook.WriteToFile('test_virtual.xlsx', sfOOXML, true);
workbook.WriteToFile('test_virtual.xls', sfExcel8, true); //workbook.WriteToFile('test_virtual.xls', sfExcel8, true);
//workbook.WriteToFile('test_virtual.xls', sfExcel5, true);
//workbook.WriteToFile('test_virtual.xls', sfExcel2, true);
t := Now - t; t := Now - t;
finally finally

View File

@@ -193,7 +193,7 @@ type
implementation implementation
uses uses
StrUtils; StrUtils, fpsStreams;
const const
{ OpenDocument general XML constants } { OpenDocument general XML constants }
@@ -1191,8 +1191,23 @@ var
parser: TDOMParser; parser: TDOMParser;
src: TXMLInputSource; src: TXMLInputSource;
stream: TStream; stream: TStream;
// fstream: TStream;
begin begin
{
if (boBufStream in Workbook.Options) then begin
fstream := TFileStream.Create(AFilename, fmOpenRead + fmShareDenyWrite);
stream := TMemorystream.Create;
stream.CopyFrom(fstream, fstream.Size);
stream.Position := 0;
fstream.free;
end
}
if (boBufStream in Workbook.Options) then
stream := TBufStream.Create(AFileName, fmOpenRead + fmShareDenyWrite)
else
stream := TFileStream.Create(AFileName, fmOpenRead + fmShareDenyWrite); stream := TFileStream.Create(AFileName, fmOpenRead + fmShareDenyWrite);
try try
parser := TDOMParser.Create; parser := TDOMParser.Create;
try try
@@ -2239,7 +2254,14 @@ procedure TsSpreadOpenDocWriter.CreateStreams;
var var
dir: String; dir: String;
begin begin
if (woBufStream in Workbook.WritingOptions) then begin if (boBufStream in Workbook.Options) then begin
FSMeta := TBufStream.Create(GetTempFileName('', 'fpsM'));
FSSettings := TBufStream.Create(GetTempFileName('', 'fpsS'));
FSStyles := TBufStream.Create(GetTempFileName('', 'fpsSTY'));
FSContent := TBufStream.Create(GetTempFileName('', 'fpsC'));
FSMimeType := TBufStream.Create(GetTempFileName('', 'fpsMT'));
FSMetaInfManifest := TBufStream.Create(GetTempFileName('', 'fpsMIM'));
{
dir := IncludeTrailingPathDelimiter(GetTempDir); dir := IncludeTrailingPathDelimiter(GetTempDir);
FSMeta := TFileStream.Create(GetTempFileName(dir, 'fpsM'), fmCreate+fmOpenRead); FSMeta := TFileStream.Create(GetTempFileName(dir, 'fpsM'), fmCreate+fmOpenRead);
FSSettings := TFileStream.Create(GetTempFileName(dir, 'fpsS'), fmCreate+fmOpenRead); FSSettings := TFileStream.Create(GetTempFileName(dir, 'fpsS'), fmCreate+fmOpenRead);
@@ -2247,6 +2269,7 @@ begin
FSContent := TFileStream.Create(GetTempFileName(dir, 'fpsC'), fmCreate+fmOpenRead); FSContent := TFileStream.Create(GetTempFileName(dir, 'fpsC'), fmCreate+fmOpenRead);
FSMimeType := TFileStream.Create(GetTempFileName(dir, 'fpsMT'), fmCreate+fmOpenRead); FSMimeType := TFileStream.Create(GetTempFileName(dir, 'fpsMT'), fmCreate+fmOpenRead);
FSMetaInfManifest := TFileStream.Create(GetTempFileName(dir, 'fpsMIM'), fmCreate+fmOpenRead); FSMetaInfManifest := TFileStream.Create(GetTempFileName(dir, 'fpsMIM'), fmCreate+fmOpenRead);
}
end else begin; end else begin;
FSMeta := TMemoryStream.Create; FSMeta := TMemoryStream.Create;
FSSettings := TMemoryStream.Create; FSSettings := TMemoryStream.Create;

View File

@@ -699,22 +699,33 @@ type
end; end;
{@@ {@@
Options considered when writing a workbook Option flags for the workbook
@param woVirtualMode If in virtual mode date are not taken from cells @param boVirtualMode If in virtual mode date are not taken from cells
when a spreadsheet is written to file, but are when a spreadsheet is written to file, but are
provided by means of the event OnNeedCellData. provided by means of the event OnNeedCellData.
@param woBufStream When this option is set a buffered stream is used @param boBufStream When this option is set a buffered stream is used
for writing (a memory stream swapping to disk) } for writing (a memory stream swapping to disk) or
TsWorkbookWritingOption = (woVirtualMode, woBufStream); reading (a file stream pre-reading chunks of data
to memory) }
TsWorkbookOption = (boVirtualMode, boBufStream);
{@@ {@@
Options considered when writing a workbook } Set of options flags for the workbook }
TsWorkbookWritingOptions = set of TsWorkbookWritingOption; TsWorkbookOptions = set of TsWorkbookOption;
{@@
Event fired when writing a file in virtual mode. The event handler has to
pass data ("AValue") and formatting ("AStyleCell") to the writer }
TsWorkbookNeedCellDataEvent = procedure(Sender: TObject; ARow, ACol: Cardinal; TsWorkbookNeedCellDataEvent = procedure(Sender: TObject; ARow, ACol: Cardinal;
var AValue: variant; var AStyleCell: PCell) of object; var AValue: variant; var AStyleCell: PCell) of object;
{@@
Event fired when reading a file in virtual mode. The event handler has to
process the data provided by the read in the "ADataCell". }
TsWorkbookHaveCellDataEvent = procedure(Sender: TObject; ARow, ACol: Cardinal;
const ADataCell: PCell) of object;
{@@ {@@
The workbook contains the worksheets and provides methods for reading from The workbook contains the worksheets and provides methods for reading from
and writing to file. and writing to file.
@@ -734,8 +745,9 @@ type
FVirtualColCount: Cardinal; FVirtualColCount: Cardinal;
FVirtualRowCount: Cardinal; FVirtualRowCount: Cardinal;
FWriting: Boolean; FWriting: Boolean;
FWritingOptions: TsWorkbookWritingOptions; FOptions: TsWorkbookOptions;
FOnNeedCellData: TsWorkbookNeedCellDataEvent; FOnNeedCellData: TsWorkbookNeedCellDataEvent;
FOnHaveCellData: TsWorkbookHaveCellDataEvent;
FFileName: String; FFileName: String;
{ Setter/Getter } { Setter/Getter }
@@ -824,11 +836,15 @@ type
property ReadFormulas: Boolean read FReadFormulas write FReadFormulas; property ReadFormulas: Boolean read FReadFormulas write FReadFormulas;
property VirtualColCount: cardinal read FVirtualColCount write SetVirtualColCount; property VirtualColCount: cardinal read FVirtualColCount write SetVirtualColCount;
property VirtualRowCount: cardinal read FVirtualRowCount write SetVirtualRowCount; property VirtualRowCount: cardinal read FVirtualRowCount write SetVirtualRowCount;
property WritingOptions: TsWorkbookWritingOptions read FWritingOptions write FWritingOptions; property Options: TsWorkbookOptions read FOptions write FOptions;
{@@ This event allows to provide external cell data for writing to file, {@@ This event allows to provide external cell data for writing to file,
standard cells are ignored. Intended for converting large database files standard cells are ignored. Intended for converting large database files
to s spreadsheet format. Requires WritingOption woVirtualMode to be set. } to a spreadsheet format. Requires Option boVirtualMode to be set. }
property OnNeedCellData: TsWorkbookNeedCellDataEvent read FOnNeedCellData write FOnNeedCellData; property OnNeedCellData: TsWorkbookNeedCellDataEvent read FOnNeedCellData write FOnNeedCellData;
{@@ This event accepts cell data while reading a spreadsheet file. Data are
not encorporated in a spreadsheet, they are just passed through to the
event handler for processing. Requires Optio boVirtualMode to be set. }
property OnHaveCellData: TsWorkbookHaveCellDataEvent read FOnHaveCellData write FOnHaveCellData;
end; end;
{@@ Contents of a number format record } {@@ Contents of a number format record }
@@ -4188,7 +4204,7 @@ var
sheet: TsWorksheet; sheet: TsWorksheet;
r1,r2, c1,c2: Cardinal; r1,r2, c1,c2: Cardinal;
begin begin
if (woVirtualMode in WritingOptions) then begin if (boVirtualMode in Options) then begin
ALastRow := FVirtualRowCount - 1; ALastRow := FVirtualRowCount - 1;
ALastCol := FVirtualColCount - 1; ALastCol := FVirtualColCount - 1;
end else begin end else begin
@@ -5313,9 +5329,28 @@ end;
@see TsWorkbook @see TsWorkbook
} }
procedure TsCustomSpreadReader.ReadFromFile(AFileName: string; AData: TsWorkbook); procedure TsCustomSpreadReader.ReadFromFile(AFileName: string; AData: TsWorkbook);
{
var var
InputFile: TFileStream; fs, ms: TStream;
begin begin
fs := TFileStream.Create(AFileName, fmOpenRead);
ms := TMemoryStream.Create;
try
ms.CopyFrom(fs, fs.Size);
ms.Position := 0;
ReadFromStream(ms, AData);
finally
ms.Free;
fs.Free;
end;
end;
}
var
InputFile: TStream;
begin
if (boBufStream in Workbook.Options) then
InputFile := TBufStream.Create(AFileName, fmOpenRead)
else
InputFile := TFileStream.Create(AFileName, fmOpenRead); InputFile := TFileStream.Create(AFileName, fmOpenRead);
try try
ReadFromStream(InputFile, AData); ReadFromStream(InputFile, AData);
@@ -5495,7 +5530,7 @@ procedure TsCustomSpreadWriter.GetSheetDimensions(AWorksheet: TsWorksheet;
begin begin
AFirstRow := 0; AFirstRow := 0;
AFirstCol := 0; AFirstCol := 0;
if (woVirtualMode in AWorksheet.Workbook.WritingOptions) then begin if (boVirtualMode in AWorksheet.Workbook.Options) then begin
ALastRow := AWorksheet.Workbook.VirtualRowCount-1; ALastRow := AWorksheet.Workbook.VirtualRowCount-1;
ALastCol := AWorksheet.Workbook.VirtualColCount-1; ALastCol := AWorksheet.Workbook.VirtualColCount-1;
end else begin end else begin
@@ -5751,7 +5786,7 @@ begin
if AOverwriteExisting then lMode := fmCreate or fmOpenWrite if AOverwriteExisting then lMode := fmCreate or fmOpenWrite
else lMode := fmCreate; else lMode := fmCreate;
if (woBufStream in Workbook.WritingOptions) then if (boBufStream in Workbook.Options) then
OutputFile := TBufStream.Create(AFileName, lMode) OutputFile := TBufStream.Create(AFileName, lMode)
else else
OutputFile := TFileStream.Create(AFileName, lMode); OutputFile := TFileStream.Create(AFileName, lMode);

View File

@@ -5,8 +5,8 @@ interface
uses uses
SysUtils, Classes; SysUtils, Classes;
const var
DEFAULT_STREAM_BUFFER_SIZE = 1024 * 1024; DEFAULT_STREAM_BUFFER_SIZE: Integer = 1024 * 1024; // 1 MB
type type
{ A buffered stream } { A buffered stream }
@@ -23,13 +23,15 @@ type
procedure CreateFileStream; procedure CreateFileStream;
function GetPosition: Int64; override; function GetPosition: Int64; override;
function GetSize: Int64; override; function GetSize: Int64; override;
function IsWritingMode: Boolean;
public public
constructor Create(AFileName: String; AMode: Word; constructor Create(AFileName: String; AMode: Word;
ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); overload; ABufSize: Cardinal = Cardinal(-1)); overload;
constructor Create(ATempFile: String; AKeepFile: Boolean = false; constructor Create(ATempFile: String; AKeepFile: Boolean = false;
ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); overload; ABufSize: Cardinal = Cardinal(-1)); overload;
constructor Create(ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); overload; constructor Create(ABufSize: Cardinal = Cardinal(-1)); overload;
destructor Destroy; override; destructor Destroy; override;
procedure FillBuffer;
procedure FlushBuffer; procedure FlushBuffer;
function Read(var Buffer; Count: Longint): Longint; override; function Read(var Buffer; Count: Longint): Longint; override;
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override; function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
@@ -51,18 +53,19 @@ end;
{@@ {@@
Constructor of the TBufStream. Creates a memory stream and prepares everything Constructor of the TBufStream. Creates a memory stream and prepares everything
to create also a file stream if the streamsize exceeds ABufSize bytes. to create also a file stream if the stream size exceeds ABufSize bytes.
@param ATempFile File name for the file stream. If an empty string is @param ATempFile File name for the file stream. If an empty string is
used a temporary file name is created by calling GetTempFileName. used a temporary file name is created by calling GetTempFileName.
@param AKeepFile If true the stream is flushed to file when the stream is @param AKeepFile If true and the stream is in WritingMode the stream is
flushed to file when the stream is
destroyed. If false the file is deleted when the stream destroyed. If false the file is deleted when the stream
is destroyed. is destroyed.
@param ABufSize Maximum size of the memory stream before swapping to file @param ABufSize Maximum size of the memory stream before swapping to file
starts. Value is given in bytes. starts. Value is given in bytes.
} }
constructor TBufStream.Create(ATempFile: String; AKeepFile: Boolean = false; constructor TBufStream.Create(ATempFile: String; AKeepFile: Boolean = false;
ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); ABufSize: Cardinal = Cardinal(-1));
begin begin
if ATempFile = '' then if ATempFile = '' then
ATempFile := ChangeFileExt(GetTempFileName, '.~abc'); ATempFile := ChangeFileExt(GetTempFileName, '.~abc');
@@ -73,6 +76,9 @@ begin
FMemoryStream := TMemoryStream.Create; FMemoryStream := TMemoryStream.Create;
// The file stream is only created when needed because of possible conflicts // The file stream is only created when needed because of possible conflicts
// of random file names. // of random file names.
if ABufSize = Cardinal(-1) then
FBufSize := DEFAULT_STREAM_BUFFER_SIZE
else
FBufSize := ABufSize; FBufSize := ABufSize;
FFileMode := fmCreate + fmOpenRead; FFileMode := fmCreate + fmOpenRead;
end; end;
@@ -86,7 +92,7 @@ end;
@param ABufSize Maximum size of the memory stream before swapping to file @param ABufSize Maximum size of the memory stream before swapping to file
starts. Value is given in bytes. starts. Value is given in bytes.
} }
constructor TBufStream.Create(ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); constructor TBufStream.Create(ABufSize: Cardinal = Cardinal(-1));
begin begin
Create('', false, ABufSize); Create('', false, ABufSize);
end; end;
@@ -103,9 +109,12 @@ end;
starts. Value is given in bytes. starts. Value is given in bytes.
} }
constructor TBufStream.Create(AFileName: String; AMode: Word; constructor TBufStream.Create(AFileName: String; AMode: Word;
ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); ABufSize: Cardinal = Cardinal(-1));
var
keep: Boolean;
begin begin
Create(AFileName, true, ABufSize); keep := AMode and (fmCreate + fmOpenWrite) <> 0;
Create(AFileName, keep, ABufSize);
FFileMode := AMode; FFileMode := AMode;
end; end;
@@ -117,7 +126,8 @@ begin
// Free streams and delete temporary file, if requested // Free streams and delete temporary file, if requested
FreeAndNil(FMemoryStream); FreeAndNil(FMemoryStream);
FreeAndNil(FFileStream); FreeAndNil(FFileStream);
if not FKeepTmpFile and (FFileName <> '') then DeleteFile(FFileName); if not FKeepTmpFile and (FFileName <> '') and IsWritingMode then
DeleteFile(FFileName);
inherited Destroy; inherited Destroy;
end; end;
@@ -133,10 +143,25 @@ begin
end; end;
end; end;
{ Reads FBufSize bytes from the stream into the buffer }
procedure TBufStream.FillBuffer;
var
p, n: Int64;
begin
p := GetPosition;
FMemoryStream.Clear;
FMemoryStream.Position := 0;
FFileStream.Position := p;
n := Min(FBufSize, FFileStream.Size - p);
FMemoryStream.CopyFrom(FFileStream, n);
FMemoryStream.Position := 0;
FFileStream.Position := p;
end;
{ Flushes the contents of the memory stream to file } { Flushes the contents of the memory stream to file }
procedure TBufStream.FlushBuffer; procedure TBufStream.FlushBuffer;
begin begin
if (FMemoryStream.Size > 0) and not FBufWritten then begin if (FMemoryStream.Size > 0) and not FBufWritten and IsWritingMode then begin
FMemoryStream.Position := 0; FMemoryStream.Position := 0;
CreateFileStream; CreateFileStream;
FFileStream.CopyFrom(FMemoryStream, FMemoryStream.Size); FFileStream.CopyFrom(FMemoryStream, FMemoryStream.Size);
@@ -161,12 +186,32 @@ function TBufStream.GetSize: Int64;
var var
n: Int64; n: Int64;
begin begin
if IsWritingMode then begin
if FFileStream <> nil then if FFileStream <> nil then
n := FFileStream.Size n := FFileStream.Size
else else
n := 0; n := 0;
if n = 0 then n := FMemoryStream.Size; if n = 0 then n := FMemoryStream.Size;
Result := Max(n, GetPosition); Result := Max(n, GetPosition);
end else begin
CreateFileStream;
Result := FFileStream.Size;
end;
end;
{@@
Returns true if the stream is in WritingMode.
"WritingMode" means that the stream is primarily used for writing. The
memory stream is initially empty but fills during writing, it is written to
disk when it is full.
The (unnamend) opposite of "WritingMode" indicates that the stream is used
for reading. The memory stream is initially full, but the stream pointer is at
it start. When data are read the stream pointer advances towards the end.
When the requested data are not contained in the memory stream another
ABufSize of bytes are read into the memory stream. }
function TBufStream.IsWritingMode: Boolean;
begin
Result := FFileMode and (fmCreate + fmOpenWrite) <> 0;
end; end;
{@@ {@@
@@ -180,28 +225,39 @@ end;
@return Number of bytes that were read from the stream.} @return Number of bytes that were read from the stream.}
function TBufStream.Read(var Buffer; Count: Longint): Longint; function TBufStream.Read(var Buffer; Count: Longint): Longint;
begin begin
// Case 1: All "Count" bytes are contained in memory stream // Case 1: Memory stream is empty
if FMemoryStream.Size = 0 then begin
CreateFileStream;
if IsWritingMode then begin
Result := FFileStream.Read(Buffer, Count);
end else begin
FillBuffer;
Result := FMemoryStream.Read(Buffer, Count);
end;
exit;
end;
// Case 2: All "Count" bytes are contained in memory stream
if FMemoryStream.Position + Count <= FMemoryStream.Size then begin if FMemoryStream.Position + Count <= FMemoryStream.Size then begin
Result := FMemoryStream.Read(Buffer, Count); Result := FMemoryStream.Read(Buffer, Count);
exit; exit;
end; 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 // Case 3: Memory stream is not empty but contains only part of the bytes requested
if IsWritingMode then begin
FlushBuffer; FlushBuffer;
Result := FFileStream.Read(Buffer, Count); Result := FFileStream.Read(Buffer, Count);
end else begin
FillBuffer;
Result := FMemoryStream.Read(Buffer, Count);
end;
end; end;
function TBufStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; function TBufStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
var var
oldPos: Int64; oldPos: Int64;
newPos: Int64; newPos: Int64;
n: Int64;
begin begin
oldPos := GetPosition; oldPos := GetPosition;
case Origin of case Origin of
@@ -226,8 +282,20 @@ begin
end; end;
// case #3: New position is outside buffer // case #3: New position is outside buffer
if IsWritingMode then
FlushBuffer; FlushBuffer;
FFileStream.Position := newPos; FFileStream.Position := newPos;
FMemoryStream.Position := 0;
if not IsWritingMode then begin
FillBuffer;
{
FMemoryStream.Position := 0;
n := Min(FBufSize, FFileStream.Size - newPos);
FMemoryStream.CopyFrom(FFileStream, n);
FFileStream.Position := newPos;
FMemoryStream.Position := 0;
}
end;
end; end;
function TBufStream.Write(const ABuffer; ACount: LongInt): LongInt; function TBufStream.Write(const ABuffer; ACount: LongInt): LongInt;

View File

@@ -51,6 +51,7 @@ type
// 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 // Test buffered stream
procedure TestReadBufStream;
procedure TestBufStream; procedure TestBufStream;
// Virtual mode tests for all file formats // Virtual mode tests for all file formats
@@ -252,6 +253,95 @@ begin
end; end;
end; end;
procedure TSpreadInternalTests.TestReadBufStream;
const
BUF_SIZE = 1024;
FILE_SIZE = 2000;
var
tempFileName: String;
stream: TStream;
writedata: array of Byte;
readdata: array of Byte;
i, n, nread: Integer;
begin
RandSeed := 0;
// Create a test file
tempFileName := GetTempFileName;
stream := TFileStream.Create(tempFileName, fmCreate);
try
SetLength(writedata, FILE_SIZE);
for i:=0 to High(writedata) do
writedata[i] := random(256);
stream.WriteBuffer(writedata[0], Length(writedata));
finally
stream.Free;
end;
// Use a TBufStream to read parts of the file back
stream := TBufStream.Create(tempFilename, fmOpenRead, BUF_SIZE);
try
// Check stream size
CheckEquals(FILE_SIZE, stream.Size, 'Size mismatch');
// Read first 100 bytes and compare with data
nread := 100;
SetLength(readdata, nread);
n := stream.Read(readdata[0], nread);
CheckEquals(nread, n, 'Bytes count mismatch');
for i:=0 to nread-1 do
CheckEquals(writedata[i], readdata[i], Format('Read mismatch at position %d', [i]));
// Check stream size
CheckEquals(FILE_SIZE, stream.Size, 'Size mismatch');
// Read next 100 bytes and compare
stream.ReadBuffer(readdata[0], nread);
for i:=0 to nread-1 do
CheckEquals(writedata[i+nread], readdata[i], Format('Read mismatch at position %d', [i+nread]));
// Go to position 1000, this is 24 bytes to the end of the buffer, and read
// 100 bytes again - this process will require to refresh the buffer
stream.Position := 1000;
stream.ReadBuffer(readdata[0], nread);
for i:=0 to nread-1 do
CheckEquals(writedata[i+1000], readdata[i], Format('Read mismatch at position %d', [i+1000]));
// Check stream size
CheckEquals(FILE_SIZE, stream.Size, 'Size mismatch');
// Read next 100 bytes
stream.ReadBuffer(readdata[0], nread);
for i:=0 to nread-1 do
CheckEquals(writedata[i+1000+nread], readdata[i], Format('Read mismatch at position %d', [i+1000+nread]));
// Go back to start and fill the memory stream again with bytes 0..1023
stream.Position := 0;
stream.ReadBuffer(readdata[0], nread);
// Now read 100 bytes which are not in the buffer
stream.Position := 1500; // this is past the buffered range
stream.ReadBuffer(readdata[0], 100);
for i:=0 to nread-1 do
CheckEquals(writedata[i+1500], readdata[i], Format('Read mismatch at position %d', [i+1500]));
// Go back to start and fill the memory stream again with bytes 0..1023
stream.Position := 0;
stream.ReadBuffer(readdata[0], 100);
// Read last 100 bytes
stream.Seek(nread, soFromEnd);
stream.ReadBuffer(readdata[0], nread);
for i:=0 to nread-1 do
CheckEquals(writedata[i+FILE_SIZE-nread], readdata[i],
Format('Read mismatch at position %d', [i+FILE_SIZE-nread]));
finally
stream.Free;
DeleteFile(tempFileName);
end;
end;
procedure TSpreadInternalTests.TestCellString; procedure TSpreadInternalTests.TestCellString;
var var
r,c: Cardinal; r,c: Cardinal;
@@ -307,9 +397,9 @@ begin
workbook := TsWorkbook.Create; workbook := TsWorkbook.Create;
try try
worksheet := workbook.AddWorksheet('VirtualMode'); worksheet := workbook.AddWorksheet('VirtualMode');
workbook.WritingOptions := workbook.WritingOptions + [woVirtualMode]; workbook.Options := workbook.Options + [boVirtualMode];
if ABufStreamMode then if ABufStreamMode then
workbook.WritingOptions := workbook.WritingOptions + [woBufStream]; workbook.Options := workbook.Options + [boBufStream];
workbook.VirtualColCount := 1; workbook.VirtualColCount := 1;
workbook.VirtualRowCount := Length(SollNumbers) + 4; workbook.VirtualRowCount := Length(SollNumbers) + 4;
// We'll use only the first 4 SollStrings, the others cause trouble due to utf8 and formatting. // We'll use only the first 4 SollStrings, the others cause trouble due to utf8 and formatting.

View File

@@ -1084,7 +1084,7 @@ begin
WriteColWidths(AStream); WriteColWidths(AStream);
WriteRows(AStream, sheet); WriteRows(AStream, sheet);
if (woVirtualMode in Workbook.WritingOptions) then if (boVirtualMode in Workbook.Options) then
WriteVirtualCells(AStream) WriteVirtualCells(AStream)
else begin else begin
WriteRows(AStream, sheet); WriteRows(AStream, sheet);

View File

@@ -347,7 +347,7 @@ var
OutputStorage: TOLEStorage; OutputStorage: TOLEStorage;
OLEDocument: TOLEDocument; OLEDocument: TOLEDocument;
begin begin
if (woBufStream in Workbook.WritingOptions) then begin if (boBufStream in Workbook.Options) then begin
Stream := TBufStream.Create Stream := TBufStream.Create
end else end else
Stream := TMemoryStream.Create; Stream := TMemoryStream.Create;
@@ -434,7 +434,7 @@ begin
WriteSelection(AStream, sheet, pane); WriteSelection(AStream, sheet, pane);
WriteRows(AStream, sheet); WriteRows(AStream, sheet);
if (woVirtualMode in Workbook.WritingOptions) then if (boVirtualMode in Workbook.Options) then
WriteVirtualCells(AStream) WriteVirtualCells(AStream)
else begin else begin
WriteRows(AStream, sheet); WriteRows(AStream, sheet);

View File

@@ -365,7 +365,7 @@ var
OutputStorage: TOLEStorage; OutputStorage: TOLEStorage;
OLEDocument: TOLEDocument; OLEDocument: TOLEDocument;
begin begin
if (woBufStream in Workbook.WritingOptions) then begin if (boBufStream in Workbook.Options) then begin
Stream := TBufStream.Create Stream := TBufStream.Create
end else end else
Stream := TMemoryStream.Create; Stream := TMemoryStream.Create;
@@ -446,7 +446,7 @@ begin
WriteDimensions(AStream, sheet); WriteDimensions(AStream, sheet);
//WriteRowAndCellBlock(AStream, sheet); //WriteRowAndCellBlock(AStream, sheet);
if (woVirtualMode in Workbook.WritingOptions) then if (boVirtualMode in Workbook.Options) then
WriteVirtualCells(AStream) WriteVirtualCells(AStream)
else begin else begin
WriteRows(AStream, sheet); WriteRows(AStream, sheet);

View File

@@ -880,7 +880,7 @@ begin
h0 := Workbook.GetDefaultFontSize; // Point size of default font h0 := Workbook.GetDefaultFontSize; // Point size of default font
// Create the stream // Create the stream
if (woBufStream in Workbook.WritingOptions) then if (boBufStream in Workbook.Options) then
FSSheets[FCurSheetNum] := TBufStream.Create(GetTempFileName('', Format('fpsSH%d', [FCurSheetNum]))) FSSheets[FCurSheetNum] := TBufStream.Create(GetTempFileName('', Format('fpsSH%d', [FCurSheetNum])))
else else
FSSheets[FCurSheetNum] := TMemoryStream.Create; FSSheets[FCurSheetNum] := TMemoryStream.Create;
@@ -902,7 +902,7 @@ begin
AppendToStream(FSSheets[FCurSheetNum], AppendToStream(FSSheets[FCurSheetNum],
'<sheetData>'); '<sheetData>');
if (woVirtualMode in Workbook.WritingOptions) and Assigned(Workbook.OnNeedCellData) if (boVirtualMode in Workbook.Options) and Assigned(Workbook.OnNeedCellData)
then begin then begin
for r := 0 to Workbook.VirtualRowCount-1 do begin for r := 0 to Workbook.VirtualRowCount-1 do begin
row := CurSheet.FindRow(r); row := CurSheet.FindRow(r);
@@ -1012,7 +1012,7 @@ end;
single xlsx file. } single xlsx file. }
procedure TsSpreadOOXMLWriter.CreateStreams; procedure TsSpreadOOXMLWriter.CreateStreams;
begin begin
if (woBufStream in Workbook.WritingOptions) then begin if (boBufStream in Workbook.Options) then begin
FSContentTypes := TBufStream.Create(GetTempFileName('', 'fpsCT')); FSContentTypes := TBufStream.Create(GetTempFileName('', 'fpsCT'));
FSRelsRels := TBufStream.Create(GetTempFileName('', 'fpsRR')); FSRelsRels := TBufStream.Create(GetTempFileName('', 'fpsRR'));
FSWorkbookRels := TBufStream.Create(GetTempFileName('', 'fpsWBR')); FSWorkbookRels := TBufStream.Create(GetTempFileName('', 'fpsWBR'));
@@ -1111,7 +1111,7 @@ begin
then lMode := fmCreate or fmOpenWrite then lMode := fmCreate or fmOpenWrite
else lMode := fmCreate; else lMode := fmCreate;
if (woBufStream in Workbook.WritingOptions) then if (boBufStream in Workbook.Options) then
lStream := TBufStream.Create(AFileName, lMode) lStream := TBufStream.Create(AFileName, lMode)
else else
lStream := TFileStream.Create(AFileName, lMode); lStream := TFileStream.Create(AFileName, lMode);