fpspreadsheet: Implement virtual reading mode for biff8 and biff5 (activated by workbook option boVirtualMode when reading). Add demo_virtualmode_read. Update speed test (factor 2 faster than standard mode, main advantage: no significant memory usage)

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3372 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-07-24 11:51:34 +00:00
parent 6606f62e79
commit 5a65855a48
11 changed files with 498 additions and 184 deletions

View File

@ -10,7 +10,7 @@ object Form1: TForm1
OnCloseQuery = FormCloseQuery OnCloseQuery = FormCloseQuery
OnCreate = FormCreate OnCreate = FormCreate
OnKeyPress = FormKeyPress OnKeyPress = FormKeyPress
LCLVersion = '1.3' LCLVersion = '1.2.4.0'
object StatusBar: TStatusBar object StatusBar: TStatusBar
Left = 0 Left = 0
Height = 23 Height = 23

View File

@ -37,6 +37,8 @@ type
FCurFormat: TsSpreadsheetFormat; FCurFormat: TsSpreadsheetFormat;
procedure EnableControls(AEnable: Boolean); procedure EnableControls(AEnable: Boolean);
function GetRowCount(AIndex: Integer): Integer; function GetRowCount(AIndex: Integer): Integer;
procedure ReadCellDataHandler(Sender: TObject; ARow, ACol: Cardinal;
const ADataCell: PCell);
procedure WriteCellStringHandler(Sender: TObject; ARow, ACol: Cardinal; procedure WriteCellStringHandler(Sender: TObject; ARow, ACol: Cardinal;
var AValue: Variant; var AStyleCell: PCell); var AValue: Variant; var AStyleCell: PCell);
procedure WriteCellNumberHandler(Sender: TObject; ARow, ACol: Cardinal; procedure WriteCellNumberHandler(Sender: TObject; ARow, ACol: Cardinal;
@ -87,6 +89,12 @@ const
{ TForm1 } { TForm1 }
procedure TForm1.ReadCellDataHandler(Sender: TObject; ARow, ACol: Cardinal;
const ADataCell: PCell);
begin
// nothing to do here.
end;
procedure TForm1.WriteCellStringHandler(Sender: TObject; ARow, ACol: cardinal; procedure TForm1.WriteCellStringHandler(Sender: TObject; ARow, ACol: cardinal;
var AValue: variant; var AStyleCell: PCell); var AValue: variant; var AStyleCell: PCell);
var var
@ -161,6 +169,8 @@ begin
try try
Application.ProcessMessages; Application.ProcessMessages;
MyWorkbook.Options := Options; MyWorkbook.Options := Options;
if boVirtualMode in Options then
MyWorkbook.OnReadCellData := @ReadCellDataHandler;
Tm := GetTickCount; Tm := GetTickCount;
try try
MyWorkbook.ReadFromFile(fname, SPREAD_FORMAT[i]); MyWorkbook.ReadFromFile(fname, SPREAD_FORMAT[i]);
@ -349,13 +359,13 @@ begin
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
//RunReadTest(2, s + ' [boVM ]', [boVirtualMode]); RunReadTest(2, s + ' [boVM ]', [boVirtualMode]);
//RunReadTest(4, s + ' [boVM, boBS]', [boVirtualMode, boBufStream]); RunReadTest(4, s + ' [boVM, boBS]', [boVirtualMode, boBufStream]);
end else begin end else begin
RunReadTest(1, s + ' [ ]', []); RunReadTest(1, s + ' [ ]', []);
//RunReadTest(2, s + ' [boVM ]', [boVirtualMode]); RunReadTest(2, s + ' [boVM ]', [boVirtualMode]);
RunReadTest(3, s + ' [ boBS]', [boBufStream]); RunReadTest(3, s + ' [ boBS]', [boBufStream]);
//RunReadTest(4, s + ' [boVM, boBS]', [boVirtualMode, boBufStream]); RunReadTest(4, s + ' [boVM, boBS]', [boVirtualMode, boBufStream]);
end; end;
Memo.Append(DupeString('-', len)); Memo.Append(DupeString('-', len));

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="9"/>
<PathDelim Value="\"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<Title Value="demo_virtualmode_read"/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
</General>
<i18n>
<EnableI18N LFM="False"/>
</i18n>
<VersionInfo>
<StringTable ProductVersion=""/>
</VersionInfo>
<BuildModes Count="2">
<Item1 Name="Debug" Default="True"/>
<Item2 Name="Release">
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<Target>
<Filename Value="demo_virtualmode_read"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<OtherUnitFiles Value="..\.."/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
<CodeGeneration>
<SmartLinkUnit Value="True"/>
</CodeGeneration>
<Linking>
<Debugging>
<GenerateDebugInfo Value="False"/>
<StripSymbols Value="True"/>
</Debugging>
<LinkSmart Value="True"/>
<Options>
<Win32>
<GraphicApplication Value="True"/>
</Win32>
</Options>
</Linking>
<Other>
<CompilerPath Value="$(CompPath)"/>
</Other>
</CompilerOptions>
</Item2>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
</PublishOptions>
<RunParams>
<local>
<FormatVersion Value="1"/>
</local>
</RunParams>
<RequiredPackages Count="1">
<Item1>
<PackageName Value="LazUtils"/>
</Item1>
</RequiredPackages>
<Units Count="1">
<Unit0>
<Filename Value="demo_virtualmode_read.lpr"/>
<IsPartOfProject Value="True"/>
</Unit0>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<Target>
<Filename Value="demo_virtualmode_read"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<OtherUnitFiles Value="..\.."/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
<CodeGeneration>
<Optimizations>
<OptimizationLevel Value="0"/>
</Optimizations>
</CodeGeneration>
<Linking>
<Debugging>
<UseExternalDbgSyms Value="True"/>
</Debugging>
</Linking>
<Other>
<CompilerPath Value="$(CompPath)"/>
</Other>
</CompilerOptions>
<Debugging>
<Exceptions Count="3">
<Item1>
<Name Value="EAbort"/>
</Item1>
<Item2>
<Name Value="ECodetoolError"/>
</Item2>
<Item3>
<Name Value="EFOpenError"/>
</Item3>
</Exceptions>
</Debugging>
</CONFIG>

View File

@ -0,0 +1,93 @@
program demo_virtualmode_read;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}
{$IFDEF UseCThreads}
cthreads,
{$ENDIF}
{$ENDIF}
Classes, SysUtils,
lazutf8,
variants, fpspreadsheet, xlsbiff2, xlsbiff5, xlsbiff8, xlsxooxml;
type
TDataAnalyzer = class
NumberCellCount: integer;
LabelCellCount: Integer;
procedure ReadCellDataHandler(Sender: TObject; ARow,ACol: Cardinal;
const ADataCell: PCell);
end;
const
TestFileName = 'test_virtual.xls';
var
workbook: TsWorkbook;
worksheet: TsWorksheet;
dataAnalyzer: TDataAnalyzer;
t: TTime;
procedure TDataAnalyzer.ReadCellDataHandler(Sender: TObject;
ARow, ACol: Cardinal; const ADataCell: PCell);
{ This is just a sample stupidly counting the number and label cells.
A more serious example could write the cell data to a database. }
var
s: String;
begin
if ADataCell^.ContentType = cctNumber then
inc(NumberCellCount);
if ADataCell^.ContentType = cctUTF8String then
inc(LabelCellCount);
// you can use the event handler also to provide feedback on how the process
// progresses:
if (ACol = 0) and (ARow mod 1000 = 0) then
WriteLn('Reading row ', ARow, '...');
end;
begin
if not FileExists(TestFileName) then begin
WriteLn('The test file does not exist. Please run demo_virtual_write first.');
Halt;
end;
dataAnalyzer := TDataAnalyzer.Create;
try
workbook := TsWorkbook.Create;
try
{ These are the essential commands to activate virtual mode: }
workbook.Options := [boVirtualMode];
// workbook.Options := [boVirtualMode, buBufStream];
{ boBufStream can be omitted, but is important for large files: it reads
large pieces of the file to a memory stream from which the data are
analyzed faster. }
{ The event handler for OnReadCellData links the workbook to the method
from which analyzes the data. }
workbook.OnReadCellData := @dataAnalyzer.ReadCellDataHandler;
t := Now;
workbook.ReadFromFile(TestFileName);
t := Now - t;
WriteLn(Format('The workbook containes %d number and %d label cells, total %d.', [
dataAnalyzer.NumberCellCount,
dataAnalyzer.LabelCellCount,
dataAnalyzer.NumberCellCount + dataAnalyzer.LabelCellCount]));
WriteLn(Format('Execution time: %.3f sec', [t*24*60*60]));
finally
workbook.Free;
end;
finally
dataAnalyzer.Free;
end;
WriteLn('Press [ENTER] to quit...');
ReadLn;
end.

View File

@ -49,7 +49,7 @@ var
end else end else
AData := 10000*ARow + ACol; AData := 10000*ARow + ACol;
// you can use the OnNeedData also to provide feedback on how the process // you can use the event handler also to provide feedback on how the process
// progresses: // progresses:
if (ACol = 0) and (ARow mod 1000 = 0) then if (ACol = 0) and (ARow mod 1000 = 0) then
WriteLn('Writing row ', ARow, '...'); WriteLn('Writing row ', ARow, '...');
@ -76,10 +76,10 @@ 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 := 5000; workbook.VirtualRowCount := 20000;
workbook.VirtualColCount := 100; workbook.VirtualColCount := 100;
{ The event handler for OnNeedCellData links the workbook to the method { The event handler for OnWriteCellData links the workbook to the method
from which it gets the data to be written. } from which it gets the data to be written. }
workbook.OnWriteCellData := @dataprovider.WriteCellDataHandler; workbook.OnWriteCellData := @dataprovider.WriteCellDataHandler;
@ -97,8 +97,8 @@ 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', sfExcel5, true);
//workbook.WriteToFile('test_virtual.xls', sfExcel2, true); //workbook.WriteToFile('test_virtual.xls', sfExcel2, true);
t := Now - t; t := Now - t;

View File

@ -12,6 +12,10 @@ This folder contains various demo applications:
- demo_virtualmode_writing: demonstrates how the virtual mode of the workbook - demo_virtualmode_writing: demonstrates how the virtual mode of the workbook
can be used to create huge spreadsheet files. can be used to create huge spreadsheet files.
- demo_virtualmode_reading: demonstrates how the virtual mode of the workbook
can be used to read huge spreadsheet files. Requires the file written by
demo_virtualmode_writing.
- demo_write_formatting: shows some simple cell formatting - demo_write_formatting: shows some simple cell formatting
- demo_write_formula: shows some rpn formulas - demo_write_formula: shows some rpn formulas

View File

@ -941,6 +941,10 @@ type
FWorksheet: TsWorksheet; FWorksheet: TsWorksheet;
{@@ List of number formats found in the file } {@@ List of number formats found in the file }
FNumFormatList: TsCustomNumFormatList; FNumFormatList: TsCustomNumFormatList;
{@@ Temporary cell for virtual mode}
FVirtualCell: TCell;
{@@ Stores if the reader is in virtual mode }
FIsVirtualMode: Boolean;
procedure CreateNumFormatList; virtual; procedure CreateNumFormatList; virtual;
{ Record reading methods } { Record reading methods }
{@@ Abstract method for reading a blank cell. Must be overridden by descendent classes. } {@@ Abstract method for reading a blank cell. Must be overridden by descendent classes. }
@ -1094,7 +1098,8 @@ function GetFileFormatName(AFormat: TsSpreadsheetFormat): String;
procedure MakeLEPalette(APalette: PsPalette; APaletteSize: Integer); procedure MakeLEPalette(APalette: PsPalette; APaletteSize: Integer);
function SameCellBorders(ACell1, ACell2: PCell): Boolean; function SameCellBorders(ACell1, ACell2: PCell): Boolean;
procedure InitCell(var ACell: TCell); procedure InitCell(var ACell: TCell); overload;
procedure InitCell(ARow, ACol: Cardinal; var ACell: TCell); overload;
implementation implementation
@ -1496,34 +1501,13 @@ begin
ACell.NumberFormatStr := ''; ACell.NumberFormatStr := '';
FillChar(ACell, SizeOf(ACell), 0); FillChar(ACell, SizeOf(ACell), 0);
end; end;
(*
Col: Cardinal; // zero-based procedure InitCell(ARow, ACol: Cardinal; var ACell: TCell);
Row: Cardinal; // zero-based begin
ContentType: TCellContentType; InitCell(ACell);
{ Possible values for the cells } ACell.Row := ARow;
FormulaValue: TsFormula; ACell.Col := ACol;
RPNFormulaValue: TsRPNFormula; end;
NumberValue: double;
UTF8StringValue: ansistring;
DateTimeValue: TDateTime;
BoolValue: Boolean;
ErrorValue: TsErrorValue;
{ Formatting fields }
{ When adding/deleting formatting fields don't forget to update CopyFormat! }
UsedFormattingFields: TsUsedFormattingFields;
FontIndex: Integer;
TextRotation: TsTextRotation;
HorAlignment: TsHorAlignment;
VertAlignment: TsVertAlignment;
Border: TsCellBorders;
BorderStyles: TsCelLBorderStyles;
BackgroundColor: TsColor;
NumberFormat: TsNumberFormat;
NumberFormatStr: String;
RGBBackgroundColor: TFPColor; // only valid if BackgroundColor=scRGBCOLOR
{ Status flags }
CalcState: TsCalcState;
*)
{ TsWorksheet } { TsWorksheet }
@ -5305,6 +5289,8 @@ constructor TsCustomSpreadReader.Create(AWorkbook: TsWorkbook);
begin begin
inherited Create; inherited Create;
FWorkbook := AWorkbook; FWorkbook := AWorkbook;
FIsVirtualMode := (boVirtualMode in FWorkbook.Options) and
Assigned(FWorkbook.OnReadCellData);
CreateNumFormatList; CreateNumFormatList;
end; end;

View File

@ -85,7 +85,6 @@
<Unit2> <Unit2>
<Filename Value="stringtests.pas"/> <Filename Value="stringtests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="stringtests"/>
</Unit2> </Unit2>
<Unit3> <Unit3>
<Filename Value="numberstests.pas"/> <Filename Value="numberstests.pas"/>
@ -94,7 +93,6 @@
<Unit4> <Unit4>
<Filename Value="manualtests.pas"/> <Filename Value="manualtests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="manualtests"/>
</Unit4> </Unit4>
<Unit5> <Unit5>
<Filename Value="testsutility.pas"/> <Filename Value="testsutility.pas"/>
@ -128,12 +126,10 @@
<Unit12> <Unit12>
<Filename Value="rpnformulaunit.pas"/> <Filename Value="rpnformulaunit.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="rpnFormulaUnit"/>
</Unit12> </Unit12>
<Unit13> <Unit13>
<Filename Value="formulatests.pas"/> <Filename Value="formulatests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="formulatests"/>
</Unit13> </Unit13>
<Unit14> <Unit14>
<Filename Value="emptycelltests.pas"/> <Filename Value="emptycelltests.pas"/>

View File

@ -82,7 +82,6 @@ type
FCurrentWorksheet: Integer; FCurrentWorksheet: Integer;
protected protected
{ Record writing methods } { Record writing methods }
procedure ReadBlank(AStream: TStream); override;
procedure ReadFont(const AStream: TStream); procedure ReadFont(const AStream: TStream);
procedure ReadFormat(AStream: TStream); override; procedure ReadFormat(AStream: TStream); override;
procedure ReadLabel(AStream: TStream); override; procedure ReadLabel(AStream: TStream); override;
@ -1450,6 +1449,7 @@ var
ARow, ACol: Cardinal; ARow, ACol: Cardinal;
XF: Word; XF: Word;
AStrValue: ansistring; AStrValue: ansistring;
cell: PCell;
begin begin
ReadRowColXF(AStream, ARow, ACol, XF); ReadRowColXF(AStream, ARow, ACol, XF);
@ -1458,8 +1458,15 @@ begin
SetLength(AStrValue,L); SetLength(AStrValue,L);
AStream.ReadBuffer(AStrValue[1], L); AStream.ReadBuffer(AStrValue[1], L);
{ Create cell }
if FIsVirtualMode then begin
InitCell(ARow, ACol, FVirtualCell);
cell := @FVirtualCell;
end else
cell := FWorksheet.GetCell(ARow, ACol);
{ Save the data } { Save the data }
FWorksheet.WriteUTF8Text(ARow, ACol, ISO_8859_1ToUTF8(AStrValue)); FWorksheet.WriteUTF8Text(cell, ISO_8859_1ToUTF8(AStrValue));
//Read formatting runs (not supported) //Read formatting runs (not supported)
B:=AStream.ReadByte; B:=AStream.ReadByte;
for L := 0 to B-1 do begin for L := 0 to B-1 do begin
@ -1468,7 +1475,10 @@ begin
end; end;
{ Add attributes to cell } { Add attributes to cell }
ApplyCellFormatting(ARow, ACol, XF); ApplyCellFormatting(cell, XF);
if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
end; end;
{ Reads a STRING record which contains the result of string formula. } { Reads a STRING record which contains the result of string formula. }
@ -1485,6 +1495,8 @@ begin
if (FIncompleteCell <> nil) and (s <> '') then begin if (FIncompleteCell <> nil) and (s <> '') then begin
FIncompleteCell^.UTF8StringValue := AnsiToUTF8(s); FIncompleteCell^.UTF8StringValue := AnsiToUTF8(s);
FIncompleteCell^.ContentType := cctUTF8String; FIncompleteCell^.ContentType := cctUTF8String;
if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, FIncompleteCell^.Row, FIncompleteCell^.Col, FIncompleteCell);
end; end;
end; end;
FIncompleteCell := nil; FIncompleteCell := nil;
@ -1657,17 +1669,6 @@ begin
FWorksheetNames.Free; FWorksheetNames.Free;
end; end;
procedure TsSpreadBIFF5Reader.ReadBlank(AStream: TStream);
var
ARow, ACol: Cardinal;
XF: Word;
begin
{ Read row, column, and XF index from BIFF file }
ReadRowColXF(AStream, ARow, ACol, XF);
{ Add attributes to cell}
ApplyCellFormatting(ARow, ACol, XF);
end;
procedure TsSpreadBIFF5Reader.ReadFont(const AStream: TStream); procedure TsSpreadBIFF5Reader.ReadFont(const AStream: TStream);
var var
lCodePage: Word; lCodePage: Word;
@ -1761,7 +1762,7 @@ var
L: Word; L: Word;
ARow, ACol: Cardinal; ARow, ACol: Cardinal;
XF: WORD; XF: WORD;
// AValue: array[0..255] of Char; cell: PCell;
AValue: ansistring; AValue: ansistring;
AStrValue: ansistring; AStrValue: ansistring;
begin begin
@ -1776,23 +1777,21 @@ begin
SetLength(AValue, L); SetLength(AValue, L);
AStream.ReadBuffer(AValue[1], L); AStream.ReadBuffer(AValue[1], L);
{ Save the data } { Create cell }
FWorksheet.WriteUTF8Text(ARow, ACol, ISO_8859_1ToUTF8(AValue)); if FIsVirtualMode then begin
InitCell(ARow, ACol, FVirtualCell);
(* cell := @FVirtualCell;
ReadRowColXF(AStream, ARow, ACol, XF); end else
cell := FWorksheet.GetCell(ARow, ACol);
{ Byte String with 16-bit size }
L := AStream.ReadWord();
AStream.ReadBuffer(AValue, L);
AValue[L] := #0;
AStrValue := AValue;
{ Save the data } { Save the data }
FWorksheet.WriteUTF8Text(ARow, ACol, ISO_8859_1ToUTF8(AStrValue)); FWorksheet.WriteUTF8Text(cell, ISO_8859_1ToUTF8(AValue));
*)
{ Add attributes } { Add attributes }
ApplyCellFormatting(ARow, ACol, XF); ApplyCellFormatting(cell, XF);
if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
end; end;

View File

@ -78,12 +78,10 @@ type
procedure ReadBoundsheet(AStream: TStream); procedure ReadBoundsheet(AStream: TStream);
function ReadString(const AStream: TStream; const ALength: WORD): UTF8String; function ReadString(const AStream: TStream; const ALength: WORD): UTF8String;
protected protected
procedure ReadBlank(AStream: TStream); override;
procedure ReadFont(const AStream: TStream); procedure ReadFont(const AStream: TStream);
procedure ReadFormat(AStream: TStream); override; procedure ReadFormat(AStream: TStream); override;
procedure ReadLabel(AStream: TStream); override; procedure ReadLabel(AStream: TStream); override;
procedure ReadLabelSST(const AStream: TStream); procedure ReadLabelSST(const AStream: TStream);
// procedure ReadNumber() --> xlscommon
procedure ReadRichString(const AStream: TStream); procedure ReadRichString(const AStream: TStream);
procedure ReadRPNCellAddress(AStream: TStream; out ARow, ACol: Cardinal; procedure ReadRPNCellAddress(AStream: TStream; out ARow, ACol: Cardinal;
out AFlags: TsRelFlags); override; out AFlags: TsRelFlags); override;
@ -110,8 +108,6 @@ type
{ Record writing methods } { Record writing methods }
procedure WriteBOF(AStream: TStream; ADataType: Word); procedure WriteBOF(AStream: TStream; ADataType: Word);
function WriteBoundsheet(AStream: TStream; ASheetName: string): Int64; function WriteBoundsheet(AStream: TStream; ASheetName: string): Int64;
// procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
// const AValue: TDateTime; ACell: PCell); override;
procedure WriteDimensions(AStream: TStream; AWorksheet: TsWorksheet); procedure WriteDimensions(AStream: TStream; AWorksheet: TsWorksheet);
procedure WriteEOF(AStream: TStream); procedure WriteEOF(AStream: TStream);
procedure WriteFont(AStream: TStream; AFont: TsFont); procedure WriteFont(AStream: TStream; AFont: TsFont);
@ -278,6 +274,26 @@ const
XF_ROTATION_STACKED XF_ROTATION_STACKED
); );
type
TBIFF8LabelRecord = packed record
RecordID: Word;
RecordSize: Word;
Row: Word;
Col: Word;
XFIndex: Word;
TextLen: Word;
TextFlags: Byte;
end;
TBIFF8LabelSSTRecord = packed record
RecordID: Word;
RecordSize: Word;
Row: Word;
Col: Word;
XFIndex: Word;
SSTIndex: DWord;
end;
{ TsSpreadBIFF8Writer } { TsSpreadBIFF8Writer }
@ -960,16 +976,6 @@ end;
*******************************************************************} *******************************************************************}
procedure TsSpreadBIFF8Writer.WriteLabel(AStream: TStream; const ARow, procedure TsSpreadBIFF8Writer.WriteLabel(AStream: TStream; const ARow,
ACol: Cardinal; const AValue: string; ACell: PCell); ACol: Cardinal; const AValue: string; ACell: PCell);
type
TLabelRecord = packed record
RecordID: Word;
RecordSize: Word;
Row: Word;
Col: Word;
XFIndex: Word;
TextLen: Word;
TextFlags: Byte;
end;
const const
//limit for this format: 32767 bytes - header (see reclen below): //limit for this format: 32767 bytes - header (see reclen below):
//37267-8-1=32758 //37267-8-1=32758
@ -978,7 +984,7 @@ var
L, RecLen: Word; L, RecLen: Word;
TextTooLong: boolean=false; TextTooLong: boolean=false;
WideValue: WideString; WideValue: WideString;
rec: TLabelRecord; rec: TBIFF8LabelRecord;
buf: array of byte; buf: array of byte;
begin begin
WideValue := UTF8Decode(AValue); //to UTF16 WideValue := UTF8Decode(AValue); //to UTF16
@ -1027,33 +1033,13 @@ begin
{ Clean up } { Clean up }
SetLength(buf, 0); SetLength(buf, 0);
(*
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_LABEL));
RecLen := 8 + 1 + L * SizeOf(WideChar);
AStream.WriteWord(WordToLE(RecLen));
{ BIFF Record data }
AStream.WriteWord(WordToLE(ARow));
AStream.WriteWord(WordToLE(ACol));
{ Index to XF record, according to formatting }
WriteXFIndex(AStream, ACell);
{ Byte String with 16-bit size }
AStream.WriteWord(WordToLE(L));
{ Byte flags. 1 means regular Unicode LE encoding}
AStream.WriteByte(1);
AStream.WriteBuffer(WideStringToLE(WideValue)[1], L * Sizeof(WideChar));
{ {
//todo: keep a log of errors and show with an exception after writing file or something. //todo: keep a log of errors and show with an exception after writing file or something.
We can't just do the following We can't just do the following
if TextTooLong then if TextTooLong then
Raise Exception.CreateFmt('Text value exceeds %d character limit in cell [%d,%d]. Text has been truncated.',[MaxBytes,ARow,ACol]); Raise Exception.CreateFmt('Text value exceeds %d character limit in cell [%d,%d]. Text has been truncated.',[MaxBytes,ARow,ACol]);
because the file wouldn't be written. because the file wouldn't be written.
} *) }
end; end;
{******************************************************************* {*******************************************************************
@ -1539,7 +1525,8 @@ begin
try try
// Only one stream is necessary for any number of worksheets // Only one stream is necessary for any number of worksheets
OLEDocument.Stream := MemStream; OLEDocument.Stream := MemStream;
OLEStorage.ReadOLEFile(AFileName, OLEDocument,'Workbook'); OLEStorage.ReadOLEFile(AFileName, OLEDocument, 'Workbook');
// Can't be shared with BIFF5 because of the parameter "Workbook" !!!)
// Check if the operation succeded // Check if the operation succeded
if MemStream.Size = 0 then raise Exception.Create('FPSpreadsheet: Reading the OLE document failed'); if MemStream.Size = 0 then raise Exception.Create('FPSpreadsheet: Reading the OLE document failed');
@ -1599,23 +1586,13 @@ begin
end; end;
procedure TsSpreadBIFF8Reader.ReadBlank(AStream: TStream);
var
ARow, ACol: Cardinal;
XF: Word;
begin
{ Read row, column, and XF index from BIFF file }
ReadRowColXF(AStream, ARow, ACol, XF);
{ Add attributes to cell}
ApplyCellFormatting(ARow, ACol, XF);
end;
procedure TsSpreadBIFF8Reader.ReadLabel(AStream: TStream); procedure TsSpreadBIFF8Reader.ReadLabel(AStream: TStream);
var var
L: Word; L: Word;
ARow, ACol: Cardinal; ARow, ACol: Cardinal;
XF: Word; XF: Word;
WideStrValue: WideString; WideStrValue: WideString;
cell: PCell;
begin begin
{ BIFF Record data: Row, Column, XF Index } { BIFF Record data: Row, Column, XF Index }
ReadRowColXF(AStream, ARow, ACol, XF); ReadRowColXF(AStream, ARow, ACol, XF);
@ -1627,10 +1604,19 @@ begin
WideStrValue:=ReadWideString(AStream,L); WideStrValue:=ReadWideString(AStream,L);
{ Save the data } { Save the data }
FWorksheet.WriteUTF8Text(ARow, ACol, UTF16ToUTF8(WideStrValue)); if FIsVirtualMode then begin
InitCell(ARow, ACol, FVirtualCell); // "virtual" cell
cell := @FVirtualCell;
end else
cell := FWorksheet.GetCell(ARow, ACol); // "real" cell
FWorksheet.WriteUTF8Text(cell, UTF16ToUTF8(WideStrValue));
{Add attributes} {Add attributes}
ApplyCellFormatting(ARow, ACol, XF); ApplyCellFormatting(cell, XF);
if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
end; end;
procedure TsSpreadBIFF8Reader.ReadRichString(const AStream: TStream); procedure TsSpreadBIFF8Reader.ReadRichString(const AStream: TStream);
@ -1640,15 +1626,23 @@ var
ARow, ACol: Cardinal; ARow, ACol: Cardinal;
XF: Word; XF: Word;
AStrValue: ansistring; AStrValue: ansistring;
cell: PCell;
begin begin
ReadRowColXF(AStream, ARow, ACol, XF); ReadRowColXF(AStream, ARow, ACol, XF);
{ Byte String with 16-bit size } { Byte String with 16-bit size }
L := WordLEtoN(AStream.ReadWord()); L := WordLEtoN(AStream.ReadWord());
AStrValue:=ReadString(AStream,L); AStrValue:=ReadString(AStream,L); // ???? shouldn't this be a unicode string ????
{ Create cell }
if FIsVirtualMode then begin
InitCell(ARow, ACol, FVirtualCell);
cell := @FVirtualCell;
end else
cell := FWorksheet.GetCell(ARow, ACol);
{ Save the data } { Save the data }
FWorksheet.WriteUTF8Text(ARow, ACol, AStrValue); FWorksheet.WriteUTF8Text(cell, AStrValue);
//Read formatting runs (not supported) //Read formatting runs (not supported)
B:=WordLEtoN(AStream.ReadWord); B:=WordLEtoN(AStream.ReadWord);
for L := 0 to B-1 do begin for L := 0 to B-1 do begin
@ -1657,7 +1651,10 @@ begin
end; end;
{Add attributes} {Add attributes}
ApplyCellFormatting(ARow, ACol, XF); ApplyCellFormatting(cell, XF);
if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
end; end;
{ Reads the cell address used in an RPN formula element. Evaluates the corresponding { Reads the cell address used in an RPN formula element. Evaluates the corresponding
@ -1779,16 +1776,34 @@ var
ACol,ARow: Cardinal; ACol,ARow: Cardinal;
XF: WORD; XF: WORD;
SSTIndex: DWORD; SSTIndex: DWORD;
rec: TBIFF8LabelSSTRecord;
cell: PCell;
begin begin
ReadRowColXF(AStream, ARow, ACol, XF); { Read entire record, starting at Row }
SSTIndex := DWordLEtoN(AStream.ReadDWord); AStream.ReadBuffer(rec.Row, SizeOf(TBIFF8LabelSSTRecord) - 2*SizeOf(Word));
ARow := WordLEToN(rec.Row);
ACol := WordLEToN(rec.Col);
XF := WordLEToN(rec.XFIndex);
SSTIndex := DWordLEToN(rec.SSTIndex);
if SizeInt(SSTIndex) >= FSharedStringTable.Count then begin if SizeInt(SSTIndex) >= FSharedStringTable.Count then begin
Raise Exception.CreateFmt('Index %d in SST out of range (0-%d)',[Integer(SSTIndex),FSharedStringTable.Count-1]); Raise Exception.CreateFmt('Index %d in SST out of range (0-%d)',[Integer(SSTIndex),FSharedStringTable.Count-1]);
end; end;
FWorksheet.WriteUTF8Text(ARow, ACol, FSharedStringTable[SSTIndex]);
{ Create cell }
if FIsVirtualMode then begin
InitCell(ARow, ACol, FVirtualCell);
cell := @FVirtualCell;
end else
cell := FWorksheet.GetCell(ARow, ACol);
FWorksheet.WriteUTF8Text(cell, FSharedStringTable[SSTIndex]);
{Add attributes} {Add attributes}
ApplyCellFormatting(ARow, ACol, XF); ApplyCellFormatting(cell, XF);
if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
end; end;
{ Helper function for reading a string with 8-bit length. } { Helper function for reading a string with 8-bit length. }
@ -1808,6 +1823,8 @@ begin
if (FIncompleteCell <> nil) and (s <> '') then begin if (FIncompleteCell <> nil) and (s <> '') then begin
FIncompleteCell^.UTF8StringValue := UTF8Encode(s); FIncompleteCell^.UTF8StringValue := UTF8Encode(s);
FIncompleteCell^.ContentType := cctUTF8String; FIncompleteCell^.ContentType := cctUTF8String;
if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, FIncompleteCell^.Row, FIncompleteCell^.Col, FIncompleteCell);
end; end;
FIncompleteCell := nil; FIncompleteCell := nil;
end; end;

View File

@ -11,8 +11,12 @@ interface
uses uses
Classes, SysUtils, DateUtils, Classes, SysUtils, DateUtils,
fpspreadsheet, {$ifdef USE_NEW_OLE}
fpsutils, lconvencoding; fpolebasic,
{$else}
fpolestorage,
{$endif}
fpspreadsheet, fpsutils, lconvencoding;
const const
{ RECORD IDs which didn't change across versions 2-8 } { RECORD IDs which didn't change across versions 2-8 }
@ -378,7 +382,8 @@ type
FPaletteFound: Boolean; FPaletteFound: Boolean;
FXFList: TFPList; // of TXFListData FXFList: TFPList; // of TXFListData
FIncompleteCell: PCell; FIncompleteCell: PCell;
procedure ApplyCellFormatting(ARow, ACol: Cardinal; XFIndex: Word); virtual; procedure ApplyCellFormatting(ARow, ACol: Cardinal; XFIndex: Word); virtual; overload;
procedure ApplyCellFormatting(ACell: PCell; XFIndex: Word); virtual; overload;
procedure CreateNumFormatList; override; procedure CreateNumFormatList; override;
// Extracts a number out of an RK value // Extracts a number out of an RK value
function DecodeRKValue(const ARK: DWORD): Double; function DecodeRKValue(const ARK: DWORD): Double;
@ -394,6 +399,8 @@ type
function IsDateTime(Number: Double; ANumberFormat: TsNumberFormat; function IsDateTime(Number: Double; ANumberFormat: TsNumberFormat;
ANumberFormatStr: String; out ADateTime: TDateTime): Boolean; ANumberFormatStr: String; out ADateTime: TDateTime): Boolean;
// Here we can add reading of records which didn't change across BIFF5-8 versions // Here we can add reading of records which didn't change across BIFF5-8 versions
// Read a blank cell
procedure ReadBlank(AStream: TStream); virtual;
procedure ReadCodePage(AStream: TStream); procedure ReadCodePage(AStream: TStream);
// Read column info // Read column info
procedure ReadColInfo(const AStream: TStream); procedure ReadColInfo(const AStream: TStream);
@ -434,6 +441,7 @@ type
procedure ReadStringRecord(AStream: TStream); virtual; procedure ReadStringRecord(AStream: TStream); virtual;
// Read WINDOW2 record (gridlines, sheet headers) // Read WINDOW2 record (gridlines, sheet headers)
procedure ReadWindow2(AStream: TStream); virtual; procedure ReadWindow2(AStream: TStream); virtual;
public public
constructor Create(AWorkbook: TsWorkbook); override; constructor Create(AWorkbook: TsWorkbook); override;
destructor Destroy; override; destructor Destroy; override;
@ -678,6 +686,14 @@ const
); );
type type
TBIFF58BlankRecord = packed record
RecordID: Word;
RecordSize: Word;
Row: Word;
Col: Word;
XFIndex: Word;
end;
TBIFF58NumberRecord = packed record TBIFF58NumberRecord = packed record
RecordID: Word; RecordID: Word;
RecordSize: Word; RecordSize: Word;
@ -836,51 +852,58 @@ procedure TsSpreadBIFFReader.ApplyCellFormatting(ARow, ACol: Cardinal;
XFIndex: Word); XFIndex: Word);
var var
lCell: PCell; lCell: PCell;
XFData: TXFListData;
begin begin
lCell := FWorksheet.GetCell(ARow, ACol); lCell := FWorksheet.GetCell(ARow, ACol);
if Assigned(lCell) then begin ApplyCellFormatting(lCell, XFIndex);
end;
{ Applies the XF formatting referred to by XFIndex to the specified cell }
procedure TsSpreadBIFFReader.ApplyCellFormatting(ACell: PCell; XFIndex: Word);
var
XFData: TXFListData;
begin
if Assigned(ACell) then begin
XFData := TXFListData(FXFList.Items[XFIndex]); XFData := TXFListData(FXFList.Items[XFIndex]);
// Font // Font
if XFData.FontIndex = 1 then if XFData.FontIndex = 1 then
Include(lCell^.UsedFormattingFields, uffBold) Include(ACell^.UsedFormattingFields, uffBold)
else else
if XFData.FontIndex > 1 then if XFData.FontIndex > 1 then
Include(lCell^.UsedFormattingFields, uffFont); Include(ACell^.UsedFormattingFields, uffFont);
lCell^.FontIndex := XFData.FontIndex; ACell^.FontIndex := XFData.FontIndex;
// Alignment // Alignment
lCell^.HorAlignment := XFData.HorAlignment; ACell^.HorAlignment := XFData.HorAlignment;
lCell^.VertAlignment := XFData.VertAlignment; ACell^.VertAlignment := XFData.VertAlignment;
// Word wrap // Word wrap
if XFData.WordWrap then if XFData.WordWrap then
Include(lCell^.UsedFormattingFields, uffWordWrap) Include(ACell^.UsedFormattingFields, uffWordWrap)
else else
Exclude(lCell^.UsedFormattingFields, uffWordWrap); Exclude(ACell^.UsedFormattingFields, uffWordWrap);
// Text rotation // Text rotation
if XFData.TextRotation > trHorizontal then if XFData.TextRotation > trHorizontal then
Include(lCell^.UsedFormattingFields, uffTextRotation) Include(ACell^.UsedFormattingFields, uffTextRotation)
else else
Exclude(lCell^.UsedFormattingFields, uffTextRotation); Exclude(ACell^.UsedFormattingFields, uffTextRotation);
lCell^.TextRotation := XFData.TextRotation; ACell^.TextRotation := XFData.TextRotation;
// Borders // Borders
lCell^.BorderStyles := XFData.BorderStyles; ACell^.BorderStyles := XFData.BorderStyles;
if XFData.Borders <> [] then begin if XFData.Borders <> [] then begin
Include(lCell^.UsedFormattingFields, uffBorder); Include(ACell^.UsedFormattingFields, uffBorder);
lCell^.Border := XFData.Borders; ACell^.Border := XFData.Borders;
end else end else
Exclude(lCell^.UsedFormattingFields, uffBorder); Exclude(ACell^.UsedFormattingFields, uffBorder);
// Background color // Background color
if XFData.BackgroundColor <> scTransparent then begin if XFData.BackgroundColor <> scTransparent then begin
Include(lCell^.UsedFormattingFields, uffBackgroundColor); Include(ACell^.UsedFormattingFields, uffBackgroundColor);
lCell^.BackgroundColor := XFData.BackgroundColor; ACell^.BackgroundColor := XFData.BackgroundColor;
end else end else
Exclude(lCell^.UsedFormattingFields, uffBackgroundColor); Exclude(ACell^.UsedFormattingFields, uffBackgroundColor);
end; end;
end; end;
@ -986,6 +1009,34 @@ begin
end; end;
end; end;
// Reads a blank cell
procedure TsSpreadBIFFReader.ReadBlank(AStream: TStream);
var
ARow, ACol: Cardinal;
XF: Word;
rec: TBIFF58BlankRecord;
cell: PCell;
begin
AStream.ReadBuffer(rec.Row, SizeOf(TBIFF58BlankRecord) - 2*SizeOf(Word));
ARow := WordLEToN(rec.Row);
ACol := WordLEToN(rec.Col);
XF := WordLEToN(rec.XFIndex);
if FIsVirtualMode then begin
InitCell(ARow, ACol, FVirtualCell);
cell := @FVirtualCell;
end else
cell := FWorksheet.GetCell(ARow, ACol);
FWorksheet.WriteBlank(cell);
{ Add attributes to cell}
ApplyCellFormatting(cell, XF);
if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
end;
// In BIFF8 it seams to always use the UTF-16 codepage // In BIFF8 it seams to always use the UTF-16 codepage
procedure TsSpreadBIFFReader.ReadCodePage(AStream: TStream); procedure TsSpreadBIFFReader.ReadCodePage(AStream: TStream);
var var
@ -1123,14 +1174,21 @@ begin
{ Not used } { Not used }
AStream.ReadDWord; AStream.ReadDWord;
{ Create cell }
if FIsVirtualMode then begin // "Virtual" cell
InitCell(ARow, ACol, FVirtualCell);
cell := @FVirtualCell;
end else
cell := FWorksheet.GetCell(ARow, ACol); // "Real" cell
// Now determine the type of the formula result // Now determine the type of the formula result
if (Data[6] = $FF) and (Data[7] = $FF) then if (Data[6] = $FF) and (Data[7] = $FF) then
case Data[0] of case Data[0] of
0: // String -> Value is found in next record (STRING) 0: // String -> Value is found in next record (STRING)
FIncompleteCell := FWorksheet.GetCell(ARow, ACol); FIncompleteCell := cell;
1: // Boolean value 1: // Boolean value
FWorksheet.WriteBoolValue(ARow, ACol, Data[2] = 1); FWorksheet.WriteBoolValue(cell, Data[2] = 1);
2: begin // Error value 2: begin // Error value
case Data[2] of case Data[2] of
@ -1142,9 +1200,10 @@ begin
ERR_OVERFLOW : err := errOverflow; ERR_OVERFLOW : err := errOverflow;
ERR_ARG_ERROR : err := errArgError; ERR_ARG_ERROR : err := errArgError;
end; end;
FWorksheet.WriteErrorValue(ARow, ACol, err); FWorksheet.WriteErrorValue(cell, err);
end; end;
3: FWorksheet.WriteBlank(ARow, ACol);
3: FWorksheet.WriteBlank(cell);
end end
else begin else begin
if SizeOf(Double) <> 8 then if SizeOf(Double) <> 8 then
@ -1156,20 +1215,22 @@ begin
{Find out what cell type, set content type and value} {Find out what cell type, set content type and value}
ExtractNumberFormat(XF, nf, nfs); ExtractNumberFormat(XF, nf, nfs);
if IsDateTime(ResultFormula, nf, nfs, dt) then if IsDateTime(ResultFormula, nf, nfs, dt) then
FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs) FWorksheet.WriteDateTime(cell, dt, nf, nfs)
else else
FWorksheet.WriteNumber(ARow, ACol, ResultFormula, nf, nfs); //, nd, ncs); FWorksheet.WriteNumber(cell, ResultFormula, nf, nfs); //, nd, ncs);
end; end;
{ Formula token array } { Formula token array }
if FWorkbook.ReadFormulas then begin if FWorkbook.ReadFormulas then begin
cell := FWorksheet.FindCell(ARow, ACol);
ok := ReadRPNTokenArray(AStream, cell^.RPNFormulaValue); ok := ReadRPNTokenArray(AStream, cell^.RPNFormulaValue);
if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported); if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported);
end; end;
{Add attributes} {Add attributes}
ApplyCellFormatting(ARow, ACol, XF); ApplyCellFormatting(cell, XF);
if FIsVirtualMode and (cell <> FIncompleteCell) then
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
end; end;
// Reads multiple blank cell records // Reads multiple blank cell records
@ -1178,14 +1239,25 @@ procedure TsSpreadBIFFReader.ReadMulBlank(AStream: TStream);
var var
ARow, fc, lc, XF: Word; ARow, fc, lc, XF: Word;
pending: integer; pending: integer;
cell: PCell;
begin begin
ARow := WordLEtoN(AStream.ReadWord); ARow := WordLEtoN(AStream.ReadWord);
fc := WordLEtoN(AStream.ReadWord); fc := WordLEtoN(AStream.ReadWord);
pending := RecordSize - Sizeof(fc) - Sizeof(ARow); pending := RecordSize - Sizeof(fc) - Sizeof(ARow);
if FIsVirtualMode then begin
InitCell(ARow, 0, FVirtualCell);
cell := @FVirtualCell;
end;
while pending > SizeOf(XF) do begin while pending > SizeOf(XF) do begin
XF := AStream.ReadWord; //XF record (not used) XF := AStream.ReadWord; //XF record (not used)
FWorksheet.WriteBlank(ARow, fc); if FIsVirtualMode then
ApplyCellFormatting(ARow, fc, XF); cell^.Col := fc
else
cell := FWorksheet.GetCell(ARow, fc);
FWorksheet.WriteBlank(cell);
ApplyCellFormatting(cell, XF);
if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, ARow, fc, cell);
inc(fc); inc(fc);
dec(pending, SizeOf(XF)); dec(pending, SizeOf(XF));
end; end;
@ -1209,20 +1281,32 @@ var
RK: DWORD; RK: DWORD;
nf: TsNumberFormat; nf: TsNumberFormat;
nfs: String; nfs: String;
cell: PCell;
begin begin
ARow := WordLEtoN(AStream.ReadWord); ARow := WordLEtoN(AStream.ReadWord);
fc := WordLEtoN(AStream.ReadWord); fc := WordLEtoN(AStream.ReadWord);
pending := RecordSize - SizeOf(fc) - SizeOf(ARow); pending := RecordSize - SizeOf(fc) - SizeOf(ARow);
if FIsVirtualMode then begin
InitCell(ARow, fc, FVirtualCell);
cell := @FVirtualCell;
end;
while pending > SizeOf(XF) + SizeOf(RK) do begin while pending > SizeOf(XF) + SizeOf(RK) do begin
XF := AStream.ReadWord; //XF record (used for date checking) XF := AStream.ReadWord; //XF record (used for date checking)
if FIsVirtualMode then
cell^.Col := fc
else
cell := FWorksheet.GetCell(ARow, fc);
RK := DWordLEtoN(AStream.ReadDWord); RK := DWordLEtoN(AStream.ReadDWord);
lNumber := DecodeRKValue(RK); lNumber := DecodeRKValue(RK);
{Find out what cell type, set contenttype and value} {Find out what cell type, set contenttype and value}
ExtractNumberFormat(XF, nf, nfs); ExtractNumberFormat(XF, nf, nfs);
if IsDateTime(lNumber, nf, nfs, lDateTime) then if IsDateTime(lNumber, nf, nfs, lDateTime) then
FWorksheet.WriteDateTime(ARow, fc, lDateTime, nf, nfs) FWorksheet.WriteDateTime(cell, lDateTime, nf, nfs)
else else
FWorksheet.WriteNumber(ARow, fc, lNumber, nf, nfs); FWorksheet.WriteNumber(cell, lNumber, nf, nfs);
ApplyCellFormatting(cell, XF);
if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, ARow, fc, cell);
inc(fc); inc(fc);
dec(pending, SizeOf(XF) + SizeOf(RK)); dec(pending, SizeOf(XF) + SizeOf(RK));
end; end;
@ -1246,6 +1330,7 @@ var
dt: TDateTime; dt: TDateTime;
nf: TsNumberFormat; nf: TsNumberFormat;
nfs: String; nfs: String;
cell: PCell;
begin begin
{ Read entire record, starting at Row } { Read entire record, starting at Row }
AStream.ReadBuffer(rec.Row, SizeOf(TBIFF58NumberRecord) - 2*SizeOf(Word)); AStream.ReadBuffer(rec.Row, SizeOf(TBIFF58NumberRecord) - 2*SizeOf(Word));
@ -1253,22 +1338,27 @@ begin
ACol := WordLEToN(rec.Col); ACol := WordLEToN(rec.Col);
XF := WordLEToN(rec.XFIndex); XF := WordLEToN(rec.XFIndex);
value := rec.Value; value := rec.Value;
(*
ReadRowColXF(AStream, ARow, ACol, XF);
{ IEE 754 floating-point value }
AStream.ReadBuffer(value, 8);
*)
{Find out what cell type, set content type and value} {Find out what cell type, set content type and value}
ExtractNumberFormat(XF, nf, nfs); ExtractNumberFormat(XF, nf, nfs);
{ Create cell }
if FIsVirtualMode then begin // "virtual" cell
InitCell(ARow, ACol, FVirtualCell);
cell := @FVirtualCell;
end else
cell := FWorksheet.GetCell(ARow, ACol); // "real" cell
if IsDateTime(value, nf, nfs, dt) then if IsDateTime(value, nf, nfs, dt) then
FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs) FWorksheet.WriteDateTime(cell, dt, nf, nfs)
else else
FWorksheet.WriteNumber(ARow, ACol, value, nf, nfs); FWorksheet.WriteNumber(cell, value, nf, nfs);
{ Add attributes to cell } { Add attributes to cell }
ApplyCellFormatting(ARow, ACol, XF); ApplyCellFormatting(cell, XF);
if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
end; end;
// Read the palette // Read the palette
@ -1334,6 +1424,7 @@ var
XF: Word; XF: Word;
lDateTime: TDateTime; lDateTime: TDateTime;
Number: Double; Number: Double;
cell: PCell;
nf: TsNumberFormat; // Number format nf: TsNumberFormat; // Number format
nfs: String; // Number format string nfs: String; // Number format string
begin begin
@ -1346,15 +1437,25 @@ begin
{Check RK codes} {Check RK codes}
Number := DecodeRKValue(RK); Number := DecodeRKValue(RK);
{Create cell}
if FIsVirtualMode then begin
InitCell(ARow, ACol, FVirtualCell);
cell := @FVirtualCell;
end else
cell := FWorksheet.GetCell(ARow, ACol);
{Find out what cell type, set contenttype and value} {Find out what cell type, set contenttype and value}
ExtractNumberFormat(XF, nf, nfs); ExtractNumberFormat(XF, nf, nfs);
if IsDateTime(Number, nf, nfs, lDateTime) then if IsDateTime(Number, nf, nfs, lDateTime) then
FWorksheet.WriteDateTime(ARow, ACol, lDateTime, nf, nfs) FWorksheet.WriteDateTime(cell, lDateTime, nf, nfs)
else else
FWorksheet.WriteNumber(ARow, ACol, Number, nf, nfs); FWorksheet.WriteNumber(cell, Number, nf, nfs);
{Add attributes} {Add attributes}
ApplyCellFormatting(ARow, ACol, XF); ApplyCellFormatting(cell, XF);
if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
end; end;
// Read the part of the ROW record that is common to BIFF3-8 versions // Read the part of the ROW record that is common to BIFF3-8 versions
@ -1748,16 +1849,8 @@ end;
different record structure. } different record structure. }
procedure TsSpreadBIFFWriter.WriteBlank(AStream: TStream; procedure TsSpreadBIFFWriter.WriteBlank(AStream: TStream;
const ARow, ACol: Cardinal; ACell: PCell); const ARow, ACol: Cardinal; ACell: PCell);
type
TBlankRecord = packed record
RecordID: Word;
RecordSize: Word;
Row: Word;
Col: Word;
XFIndex: Word;
end;
var var
rec: TBlankRecord; rec: TBIFF58BlankRecord;
begin begin
{ BIFF record header } { BIFF record header }
rec.RecordID := WordToLE(INT_EXCEL_ID_BLANK); rec.RecordID := WordToLE(INT_EXCEL_ID_BLANK);