fpspreadsheet: Unified writing code for rpn formulas. Calculation results of rpn formulas are now stored correctly for all biff versions.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3265 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-07-01 22:47:10 +00:00
parent b76ff60882
commit 9dc234ae22
8 changed files with 475 additions and 37 deletions

View File

@ -908,7 +908,8 @@ type
{@@ abstract method for a formula to a cell. Must be overridden by descendent classes. } {@@ abstract method for a formula to a cell. Must be overridden by descendent classes. }
procedure WriteFormula(AStream: TStream; const ARow, ACol: Cardinal; const AFormula: TsFormula; ACell: PCell); virtual; procedure WriteFormula(AStream: TStream; const ARow, ACol: Cardinal; const AFormula: TsFormula; ACell: PCell); virtual;
{@@ abstract method for am RPN formula to a cell. Must be overridden by descendent classes. } {@@ abstract method for am RPN formula to a cell. Must be overridden by descendent classes. }
procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell); virtual; procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal;
const AFormula: TsRPNFormula; ACell: PCell); virtual;
{@@ abstract method for a string to a cell. Must be overridden by descendent classes. } {@@ abstract method for a string to a cell. Must be overridden by descendent classes. }
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; const AValue: string; ACell: PCell); virtual; abstract; procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; const AValue: string; ACell: PCell); virtual; abstract;
{@@ abstract method for a number value to a cell. Must be overridden by descendent classes. } {@@ abstract method for a number value to a cell. Must be overridden by descendent classes. }
@ -5290,8 +5291,8 @@ end;
@param ACell Pointer to the cell containing the formula and being written @param ACell Pointer to the cell containing the formula and being written
to the stream to the stream
} }
procedure TsCustomSpreadWriter.WriteRPNFormula(AStream: TStream; const ARow, procedure TsCustomSpreadWriter.WriteRPNFormula(AStream: TStream;
ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell); const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
begin begin
Unused(AStream, ARow, ACol); Unused(AStream, ARow, ACol);
Unused(AFormula, ACell); Unused(AFormula, ACell);

View File

@ -46,6 +46,10 @@ type
procedure TestWriteRead_BIFF8_FormulaStrings; procedure TestWriteRead_BIFF8_FormulaStrings;
// Writes out and calculates formulas, read back // Writes out and calculates formulas, read back
{ BIFF2 Tests }
procedure TestWriteRead_BIFF2_CalcRPNFormula;
{ BIFF5 Tests }
procedure TestWriteRead_BIFF5_CalcRPNFormula;
{ BIFF8 Tests } { BIFF8 Tests }
procedure TestWriteRead_BIFF8_CalcRPNFormula; procedure TestWriteRead_BIFF8_CalcRPNFormula;
end; end;
@ -223,6 +227,15 @@ begin
DeleteFile(TempFile); DeleteFile(TempFile);
end; end;
procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF2_CalcRPNFormula;
begin
TestCalcRPNFormulas(sfExcel2);
end;
procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF5_CalcRPNFormula;
begin
TestCalcRPNFormulas(sfExcel5);
end;
procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF8_CalcRPNFormula; procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF8_CalcRPNFormula;
begin begin

View File

@ -47,9 +47,6 @@
<UseExternalDbgSyms Value="True"/> <UseExternalDbgSyms Value="True"/>
</Debugging> </Debugging>
</Linking> </Linking>
<Other>
<CompilerPath Value="$(CompPath)"/>
</Other>
</CompilerOptions> </CompilerOptions>
</Item2> </Item2>
</BuildModes> </BuildModes>
@ -157,9 +154,6 @@
<OptimizationLevel Value="0"/> <OptimizationLevel Value="0"/>
</Optimizations> </Optimizations>
</CodeGeneration> </CodeGeneration>
<Other>
<CompilerPath Value="$(CompPath)"/>
</Other>
</CompilerOptions> </CompilerOptions>
<Debugging> <Debugging>
<Exceptions Count="6"> <Exceptions Count="6">

View File

@ -1,6 +1,9 @@
{ include file for "formulatests.pas", containing the test cases for the { include file for "formulatests.pas", containing the test cases for the
calcrpnformula test. } calcrpnformula test. }
{------------------------------------------------------------------------------}
{ Basic operations }
{------------------------------------------------------------------------------}
// Addition // Addition
Row := 0; Row := 0;
MyWorksheet.WriteUTF8Text(Row, 0, '=1+1'); MyWorksheet.WriteUTF8Text(Row, 0, '=1+1');

View File

@ -112,7 +112,11 @@ type
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; const AValue: double; ACell: PCell); override; procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; const AValue: double; ACell: PCell); override;
procedure WriteRow(AStream: TStream; ASheet: TsWorksheet; procedure WriteRow(AStream: TStream; ASheet: TsWorksheet;
ARowIndex, AFirstColIndex, ALastColIndex: Cardinal; ARow: PRow); override; ARowIndex, AFirstColIndex, ALastColIndex: Cardinal; ARow: PRow); override;
procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell); override; procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal;
const AFormula: TsRPNFormula; ACell: PCell); override;
function WriteRPNFunc(AStream: TStream; AIdentifier: Word): Word; override;
procedure WriteRPNTokenArraySize(AStream: TStream; ASize: Word); override;
procedure WriteStringRecord(AStream: TStream; AString: String); override;
procedure WriteWindow1(AStream: TStream); override; procedure WriteWindow1(AStream: TStream); override;
procedure WriteWindow2(AStream: TStream; ASheet: TsWorksheet); procedure WriteWindow2(AStream: TStream; ASheet: TsWorksheet);
public public
@ -1283,8 +1287,8 @@ end;
MyFormula[1].Row := 0; MyFormula[1].Row := 0;
MyFormula[2].TokenID := INT_EXCEL_TOKEN_TADD; + MyFormula[2].TokenID := INT_EXCEL_TOKEN_TADD; +
} }
procedure TsSpreadBIFF2Writer.WriteRPNFormula(AStream: TStream; const ARow, procedure TsSpreadBIFF2Writer.WriteRPNFormula(AStream: TStream;
ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell); const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
var var
FormulaResult: double; FormulaResult: double;
i: Integer; i: Integer;
@ -1315,13 +1319,17 @@ begin
{ BIFF2 Attributes } { BIFF2 Attributes }
WriteCellFormatting(AStream, ACell, xf); WriteCellFormatting(AStream, ACell, xf);
{ Result of the formula in IEEE 754 floating-point value } { Encoded result of RPN formula }
AStream.WriteBuffer(FormulaResult, 8); WriteRPNResult(AStream, ACell);
{ 0 = Do not recalculate { 0 = Do not recalculate
1 = Always recalculate } 1 = Always recalculate }
AStream.WriteByte($1); AStream.WriteByte(1);
{ Formula data (RPN token array) }
WriteRPNTokenArray(AStream, AFormula, RPNLength);
(*
{ Formula } { Formula }
{ The size of the token array is written later, { The size of the token array is written later,
@ -1420,8 +1428,58 @@ begin
AStream.Position := RecordSizePos; AStream.Position := RecordSizePos;
AStream.WriteWord(WordToLE(17 + RPNLength)); AStream.WriteWord(WordToLE(17 + RPNLength));
AStream.position := FinalPos; AStream.position := FinalPos;
*)
{ Write sizes in the end, after we known them }
FinalPos := AStream.Position;
AStream.Position := RecordSizePos;
AStream.WriteWord(WordToLE(17 + RPNLength));
AStream.Position := FinalPos;
{ Write following STRING record if formula result is a non-empty string }
if (ACell^.ContentType = cctUTF8String) and (ACell^.UTF8StringValue <> '') then
WriteStringRecord(AStream, ACell^.UTF8StringValue);
end; end;
{ Writes the identifier for an RPN function with fixed argument count and
returns the number of bytes written. }
function TsSpreadBIFF2Writer.WriteRPNFunc(AStream: TStream; AIdentifier: Word): Word;
begin
AStream.WriteByte(Lo(AIdentifier));
Result := 1;
end;
{ Writes the size of the RPN token array. Called from WriteRPNFormula.
Overrides xlscommon. }
procedure TsSpreadBIFF2Writer.WriteRPNTokenArraySize(AStream: TStream;
ASize: Word);
begin
AStream.WriteByte(Lo(ASize));
end;
{ Writes an Excel 2 STRING record which immediately follows a FORMULA record
when the formula result is a string. }
procedure TsSpreadBIFF2Writer.WriteStringRecord(AStream: TStream;
AString: String);
var
s: ansistring;
len: Integer;
begin
s := AString;
len := Length(s);
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_STRING));
AStream.WriteWord(WordToLE(1 + len*SizeOf(Char)));
{ Write string length }
AStream.WriteByte(len);
{ Write characters }
AStream.WriteBuffer(s[1], len * SizeOf(Char));
end;
{******************************************************************* {*******************************************************************
* TsSpreadBIFF2Writer.WriteBlank () * TsSpreadBIFF2Writer.WriteBlank ()
* *

View File

@ -120,8 +120,11 @@ type
procedure WriteIndex(AStream: TStream); procedure WriteIndex(AStream: TStream);
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: string; ACell: PCell); override; const AValue: string; ACell: PCell); override;
{
procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal;
const AFormula: TsRPNFormula; ACell: PCell); override; const AFormula: TsRPNFormula; ACell: PCell); override;
}
procedure WriteStringRecord(AStream: TStream; AString: String); override;
procedure WriteStyle(AStream: TStream); procedure WriteStyle(AStream: TStream);
procedure WriteWindow2(AStream: TStream; ASheet: TsWorksheet); procedure WriteWindow2(AStream: TStream; ASheet: TsWorksheet);
procedure WriteXF(AStream: TStream; AFontIndex: Word; procedure WriteXF(AStream: TStream; AFontIndex: Word;
@ -677,7 +680,7 @@ begin
AStream.WriteByte(len); // AnsiString, char count in 1 byte AStream.WriteByte(len); // AnsiString, char count in 1 byte
AStream.WriteBuffer(s[1], len * SizeOf(AnsiChar)); // String data AStream.WriteBuffer(s[1], len * SizeOf(AnsiChar)); // String data
end; end;
(*
{******************************************************************* {*******************************************************************
* TsSpreadBIFF5Writer.WriteRPNFormula () * TsSpreadBIFF5Writer.WriteRPNFormula ()
* *
@ -688,8 +691,8 @@ end;
* AFormula array * AFormula array
* *
*******************************************************************} *******************************************************************}
procedure TsSpreadBIFF5Writer.WriteRPNFormula(AStream: TStream; const ARow, procedure TsSpreadBIFF5Writer.WriteRPNFormula(AStream: TStream;
ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell); const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
var var
FormulaResult: double; FormulaResult: double;
i: Integer; i: Integer;
@ -835,6 +838,7 @@ begin
AStream.WriteWord(WordToLE(22 + RPNLength)); AStream.WriteWord(WordToLE(22 + RPNLength));
AStream.position := FinalPos; AStream.position := FinalPos;
end; end;
*)
{******************************************************************* {*******************************************************************
* TsSpreadBIFF5Writer.WriteIndex () * TsSpreadBIFF5Writer.WriteIndex ()
@ -943,6 +947,28 @@ begin
} }
end; end;
{ Writes an Excel 5 STRING record which immediately follows a FORMULA record
when the formula result is a string.
BIFF5 writes a byte-string, but uses a 16-bit length here! }
procedure TsSpreadBIFF5Writer.WriteStringRecord(AStream: TStream;
AString: String);
var
s: ansistring;
len: Integer;
begin
s := AString;
len := Length(s);
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_STRING));
AStream.WriteWord(WordToLE(2 + len*SizeOf(Char)));
{ Write string length }
AStream.WriteWord(WordToLE(len));
{ Write characters }
AStream.WriteBuffer(s[1], len * SizeOf(Char));
end;
{******************************************************************* {*******************************************************************
* TsSpreadBIFF5Writer.WriteStyle () * TsSpreadBIFF5Writer.WriteStyle ()
* *

View File

@ -123,10 +123,16 @@ type
procedure WriteIndex(AStream: TStream); procedure WriteIndex(AStream: TStream);
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: string; ACell: PCell); override; const AValue: string; ACell: PCell); override;
function WriteRPNCellAddress(AStream: TStream; ARow, ACol: Cardinal;
AFlags: TsRelFlags): word; override;
function WriteRPNCellRangeAddress(AStream: TStream; ARow1, ACol1, ARow2, ACol2: Cardinal;
AFlags: TsRelFlags): Word; override;
{
procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal;
const AFormula: TsRPNFormula; ACell: PCell); override; const AFormula: TsRPNFormula; ACell: PCell); override;
}
function WriteString_8bitLen(AStream: TStream; AString: String): Integer; override; function WriteString_8bitLen(AStream: TStream; AString: String): Integer; override;
procedure WriteStringRecord(AStream: TStream; AString: string); procedure WriteStringRecord(AStream: TStream; AString: string); override;
procedure WriteStyle(AStream: TStream); procedure WriteStyle(AStream: TStream);
procedure WriteWindow2(AStream: TStream; ASheet: TsWorksheet); procedure WriteWindow2(AStream: TStream; ASheet: TsWorksheet);
procedure WriteXF(AStream: TStream; AFontIndex: Word; procedure WriteXF(AStream: TStream; AFontIndex: Word;
@ -226,8 +232,8 @@ const
{ Cell Addresses constants } { Cell Addresses constants }
MASK_EXCEL_COL_BITS_BIFF8 = $00FF; MASK_EXCEL_COL_BITS_BIFF8 = $00FF;
MASK_EXCEL_RELATIVE_COL = $4000; // This is according to Microsoft documentation, MASK_EXCEL_RELATIVE_COL_BIFF8 = $4000; // This is according to Microsoft documentation,
MASK_EXCEL_RELATIVE_ROW = $8000; // but opposite to OpenOffice documentation! MASK_EXCEL_RELATIVE_ROW_BIFF8 = $8000; // but opposite to OpenOffice documentation!
{ BOF record constants } { BOF record constants }
INT_BOF_BIFF8_VER = $0600; INT_BOF_BIFF8_VER = $0600;
@ -240,11 +246,6 @@ const
INT_BOF_BUILD_ID = $1FD2; INT_BOF_BUILD_ID = $1FD2;
INT_BOF_BUILD_YEAR = $07CD; INT_BOF_BUILD_YEAR = $07CD;
{ FORMULA record constants }
MASK_FORMULA_RECALCULATE_ALWAYS = $0001;
MASK_FORMULA_RECALCULATE_ON_OPEN = $0002;
MASK_FORMULA_SHARED_FORMULA = $0008;
{ STYLE record constants } { STYLE record constants }
MASK_STYLE_BUILT_IN = $8000; MASK_STYLE_BUILT_IN = $8000;
@ -796,6 +797,54 @@ begin
AStream.position := FinalPos;*) AStream.position := FinalPos;*)
end; end;
{ Writes the address of a cell as used in an RPN formula and returns the
number of bytes written.
Valid for BIFF8. }
function TsSpreadBIFF8Writer.WriteRPNCellAddress(AStream: TStream;
ARow, ACol: Cardinal; AFlags: TsRelFlags): Word;
var
c: Cardinal; // column index with encoded relative/absolute address info
begin
AStream.WriteWord(WordToLE(ARow));
c := ACol and MASK_EXCEL_COL_BITS_BIFF8;
if (rfRelRow in AFlags) then c := c or MASK_EXCEL_RELATIVE_ROW_BIFF8;
if (rfRelCol in AFlags) then c := c or MASK_EXCEL_RELATIVE_COL_BIFF8;
AStream.WriteWord(WordToLE(c));
Result := 4;
end;
{ Writes the address of a cell range as used in an RPN formula and returns the
count of bytes written.
Valid for BIFF2-BIFF5. }
function TsSpreadBIFF8Writer.WriteRPNCellRangeAddress(AStream: TStream;
ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags): Word;
{ Cell range address, BIFF8:
Offset Size Contents
0 2 Index to first row (0…65535) or offset of first row (method [B], -32768…32767)
2 2 Index to last row (0…65535) or offset of last row (method [B], -32768…32767)
4 2 Index to first column or offset of first column, with relative flags (see table above)
6 2 Index to last column or offset of last column, with relative flags (see table above)
}
var
c: Cardinal; // column index with encoded relative/absolute address info
begin
AStream.WriteWord(WordToLE(ARow1));
AStream.WriteWord(WordToLE(ARow2));
c := ACol1;
if (rfRelCol in AFlags) then c := c or MASK_EXCEL_RELATIVE_COL;
if (rfRelRow in AFlags) then c := c or MASK_EXCEL_RELATIVE_ROW;
AStream.WriteWord(WordToLE(c));
c := ACol2;
if (rfRelCol2 in AFlags) then c := c or MASK_EXCEL_RELATIVE_COL;
if (rfRelRow2 in AFlags) then c := c or MASK_EXCEL_RELATIVE_ROW;
AStream.WriteWord(WordToLE(c));
Result := 8;
end;
(*
procedure TsSpreadBIFF8Writer.WriteRPNFormula(AStream: TStream; const ARow, procedure TsSpreadBIFF8Writer.WriteRPNFormula(AStream: TStream; const ARow,
ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell); ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
var var
@ -869,11 +918,12 @@ begin
{ Formula } { Formula }
{ The size of the token array is written later, { The size of the token array is written later, because it's necessary to
because it's necessary to calculate if first, calculate if first, and this is done at the same time it is written }
and this is done at the same time it is written }
TokenArraySizePos := AStream.Position; TokenArraySizePos := AStream.Position;
AStream.WriteWord(RPNLength); WriteRPNTokenArraySize(AStream, RPNLength);
WriteRPNTokenArray(AStream, AFormula, RPNLength);
{ Formula data (RPN token array) } { Formula data (RPN token array) }
for i := 0 to Length(AFormula) - 1 do for i := 0 to Length(AFormula) - 1 do
@ -988,12 +1038,13 @@ begin
AStream.WriteByte(RPNLength); AStream.WriteByte(RPNLength);
AStream.Position := RecordSizePos; AStream.Position := RecordSizePos;
AStream.WriteWord(WordToLE(22 + RPNLength)); AStream.WriteWord(WordToLE(22 + RPNLength));
AStream.position := FinalPos; AStream.Position := FinalPos;
{ Write following STRING record if formula result is a non-empty string } { Write following STRING record if formula result is a non-empty string }
if (ACell^.ContentType = cctUTF8String) and (ACell^.UTF8StringValue <> '') then if (ACell^.ContentType = cctUTF8String) and (ACell^.UTF8StringValue <> '') then
WriteStringRecord(AStream, ACell^.UTF8StringValue); WriteStringRecord(AStream, ACell^.UTF8StringValue);
end; end;
*)
{ Helper function for writing a string with 8-bit length. Overridden version { Helper function for writing a string with 8-bit length. Overridden version
for BIFF8. Called for writing rpn formula string tokens. for BIFF8. Called for writing rpn formula string tokens.

View File

@ -320,6 +320,11 @@ const
{ Note: The assignment of the RELATIVE_COL and _ROW masks is according to { Note: The assignment of the RELATIVE_COL and _ROW masks is according to
Microsoft's documentation, but opposite to the OpenOffice documentation. } Microsoft's documentation, but opposite to the OpenOffice documentation. }
{ FORMULA record constants }
MASK_FORMULA_RECALCULATE_ALWAYS = $0001;
MASK_FORMULA_RECALCULATE_ON_OPEN = $0002;
MASK_FORMULA_SHARED_FORMULA = $0008;
{ Error codes } { Error codes }
ERR_INTERSECTION_EMPTY = $00; // #NULL! ERR_INTERSECTION_EMPTY = $00; // #NULL!
ERR_DIVIDE_BY_ZERO = $07; // #DIV/0! ERR_DIVIDE_BY_ZERO = $07; // #DIV/0!
@ -478,6 +483,17 @@ type
// Writes out a PANE record // Writes out a PANE record
procedure WritePane(AStream: TStream; ASheet: TsWorksheet; IsBiff58: Boolean; procedure WritePane(AStream: TStream; ASheet: TsWorksheet; IsBiff58: Boolean;
out ActivePane: Byte); out ActivePane: Byte);
function WriteRPNCellAddress(AStream: TStream; ARow, ACol: Cardinal;
AFlags: TsRelFlags): Word; virtual;
function WriteRPNCellRangeAddress(AStream: TStream; ARow1, ACol1, ARow2, ACol2: Cardinal;
AFlags: TsRelFlags): Word; virtual;
procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal;
const AFormula: TsRPNFormula; ACell: PCell); override;
function WriteRPNFunc(AStream: TStream; AIdentifier: Word): Word; virtual;
procedure WriteRPNResult(AStream: TStream; ACell: PCell);
procedure WriteRPNTokenArray(AStream: TStream; const AFormula: TsRPNFormula;
var RPNLength: Word);
procedure WriteRPNTokenArraySize(AStream: TStream; ASize: Word); virtual;
// Writes out a ROW record // Writes out a ROW record
procedure WriteRow(AStream: TStream; ASheet: TsWorksheet; procedure WriteRow(AStream: TStream; ASheet: TsWorksheet;
ARowIndex, AFirstColIndex, ALastColIndex: Cardinal; ARow: PRow); virtual; ARowIndex, AFirstColIndex, ALastColIndex: Cardinal; ARow: PRow); virtual;
@ -487,6 +503,7 @@ type
procedure WriteSelection(AStream: TStream; ASheet: TsWorksheet; APane: Byte); procedure WriteSelection(AStream: TStream; ASheet: TsWorksheet; APane: Byte);
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;
// 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
@ -1537,8 +1554,8 @@ begin
end; end;
{ Helper function for reading a string with 8-bit length. Here, we implement the { Helper function for reading a string with 8-bit length. Here, we implement the
version for ansistrings since it is valid for all BIFF versions except BIFF8 version for ansistrings since it is valid for all BIFF versions except for
where it has to overridden. } BIFF8 where it has to be overridden. }
function TsSpreadBIFFReader.ReadString_8bitLen(AStream: TStream): String; function TsSpreadBIFFReader.ReadString_8bitLen(AStream: TStream): String;
var var
len: Byte; len: Byte;
@ -1968,6 +1985,272 @@ begin
{ Not used (BIFF5-BIFF8 only, not written in BIFF2-BIFF4 } { Not used (BIFF5-BIFF8 only, not written in BIFF2-BIFF4 }
end; end;
{ Writes the address of a cell as used in an RPN formula and returns the
count of bytes written.
Valid for BIFF2-BIFF5. }
function TsSpreadBIFFWriter.WriteRPNCellAddress(AStream: TStream;
ARow, ACol: Cardinal; AFlags: TsRelFlags): Word;
var
r: Cardinal; // row index containing the relativ/absolute address info
begin
r := ARow and MASK_EXCEL_ROW;
if (rfRelRow in AFlags) then r := r or MASK_EXCEL_RELATIVE_ROW;
if (rfRelCol in AFlags) then r := r or MASK_EXCEL_RELATIVE_COL;
AStream.WriteWord(r);
AStream.WriteByte(ACol);
Result := 3;
end;
{ Writes the address of a cell range as used in an RPN formula and returns the
count of bytes written.
Valid for BIFF2-BIFF5. }
function TsSpreadBIFFWriter.WriteRPNCellRangeAddress(AStream: TStream;
ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags): Word;
var
r: Cardinal;
begin
r := ARow1 and MASK_EXCEL_ROW;
if (rfRelRow in AFlags) then r := r or MASK_EXCEL_RELATIVE_ROW;
if (rfRelCol in AFlags) then r := r or MASK_EXCEL_RELATIVE_COL;
AStream.WriteWord(WordToLE(r));
r := ARow2 and MASK_EXCEL_ROW;
if (rfRelRow2 in AFlags) then r := r or MASK_EXCEL_RELATIVE_ROW;
if (rfRelCol2 in AFlags) then r := r or MASK_EXCEL_RELATIVE_COL;
AStream.WriteWord(WordToLE(r));
AStream.WriteByte(ACol1);
AStream.WriteByte(ACol2);
Result := 6;
end;
{ Writes an Excel FORMULA record
The formula needs to be converted from usual user-readable string
to an RPN array
Valid for BIFF5-BIFF8.
}
procedure TsSpreadBIFFWriter.WriteRPNFormula(AStream: TStream;
const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
var
i: Integer;
len: Integer;
RPNLength: Word;
RecordSizePos, FinalPos: Int64;
// TokenID: Word;
// lSecondaryID: Word;
// c: Cardinal;
wideStr: WideString;
begin
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_FORMULA));
RecordSizePos := AStream.Position;
AStream.WriteWord(0); // This is the record size which is not yet known here
{ BIFF Record data }
AStream.WriteWord(WordToLE(ARow));
AStream.WriteWord(WordToLE(ACol));
{ Index to XF record, according to formatting }
//AStream.WriteWord(0);
WriteXFIndex(AStream, ACell);
{ Encoded result of RPN formula }
WriteRPNResult(AStream, ACell);
{ Options flags }
AStream.WriteWord(WordToLE(MASK_FORMULA_RECALCULATE_ALWAYS));
{ Not used }
AStream.WriteDWord(0);
{ Formula data (RPN token array) }
WriteRPNTokenArray(AStream, AFormula, RPNLength);
{ Write sizes in the end, after we known them }
FinalPos := AStream.Position;
AStream.Position := RecordSizePos;
AStream.WriteWord(WordToLE(22 + RPNLength));
AStream.Position := FinalPos;
{ Write following STRING record if formula result is a non-empty string }
if (ACell^.ContentType = cctUTF8String) and (ACell^.UTF8StringValue <> '') then
WriteStringRecord(AStream, ACell^.UTF8StringValue);
end;
{ Writes the identifier for an RPN function with fixed argument count and
returns the number of bytes written.
Valid for BIFF4-BIFF8. Override in BIFF2-BIFF3 }
function TsSpreadBIFFWriter.WriteRPNFunc(AStream: TStream; AIdentifier: Word): Word;
begin
AStream.WriteWord(WordToLE(AIdentifier));
Result := 2;
end;
{ Writes the result of an RPN formula. }
procedure TsSpreadBIFFWriter.WriteRPNResult(AStream: TStream; ACell: PCell);
var
FormulaResult: double;
FormulaResultWords: array[0..3] of word absolute FormulaResult;
begin
{ Determine encoded result bytes }
FormulaResult := 0.0;
case ACell^.ContentType of
cctNumber:
FormulaResult := ACell^.NumberValue;
cctDateTime:
FormulaResult := ACell^.DateTimeValue;
cctUTF8String:
begin
if ACell^.UTF8StringValue = '' then
FormulaResultWords[0] := 3;
FormulaResultWords[3] := $FFFF;
end;
cctBool:
begin
FormulaResultWords[0] := 1;
FormulaResultWords[1] := ord(ACell^.BoolValue);
FormulaResultWords[3] := $FFFF;
end;
cctError:
begin
FormulaResultWords[0] := 2;
case ACell^.ErrorValue of
errEmptyIntersection: FormulaResultWords[1] := ERR_INTERSECTION_EMPTY;// #NULL!
errDivideByZero : FormulaResultWords[1] := ERR_DIVIDE_BY_ZERO; // #DIV/0!
errWrongType : FormulaResultWords[1] := ERR_WRONG_TYPE_OF_OPERAND; // #VALUE!
errIllegalRef : FormulaResultWords[1] := ERR_ILLEGAL_REFERENCE; // #REF!
errWrongName : FormulaResultWords[1] := ERR_WRONG_NAME; // #NAME?
errOverflow : FormulaResultWords[1] := ERR_OVERFLOW; // #NUM!
errArgError : FormulaResultWords[1] := ERR_ARG_ERROR; // #N/A;
end;
FormulaResultWords[3] := $FFFF;
end;
end;
{ Write result of the formula, encoded above }
AStream.WriteBuffer(FormulaResult, 8);
end;
{ Writes the token array of the given RPN formula and returns its size }
procedure TsSpreadBIFFWriter.WriteRPNTokenArray(AStream: TStream;
const AFormula: TsRPNFormula; var RPNLength: Word);
var
i: Integer;
tokenID, secondaryID: Word;
n: Word;
TokenArraySizePos: Int64;
FinalPos: Int64;
begin
RPNLength := 0;
{ The size of the token array is written later, because it's necessary to
calculate it first, and this is done at the same time it is written }
TokenArraySizePos := AStream.Position;
WriteRPNTokenArraySize(AStream, 0);
{ Formula data (RPN token array) }
for i := 0 to Length(AFormula) - 1 do begin
{ Token identifier }
tokenID := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind, secondaryID);
AStream.WriteByte(tokenID);
inc(RPNLength);
{ Token data }
case tokenID of
{ Operand Tokens }
INT_EXCEL_TOKEN_TREFR,
INT_EXCEL_TOKEN_TREFV,
INT_EXCEL_TOKEN_TREFA: { fekCell }
begin
n := WriteRPNCellAddress(
AStream,
AFormula[i].Row, AFormula[i].Col,
AFormula[i].RelFlags
);
inc(RPNLength, n);
end;
INT_EXCEL_TOKEN_TAREA_R: { fekCellRange }
begin
n := WriteRPNCellRangeAddress(
AStream,
AFormula[i].Row, AFormula[i].Col,
AFormula[i].Row2, AFormula[i].Col2,
AFormula[i].RelFlags
);
inc(RPNLength, n);
end;
INT_EXCEL_TOKEN_TNUM: { fekNum }
begin
AStream.WriteBuffer(AFormula[i].DoubleValue, 8);
inc(RPNLength, 8);
end;
INT_EXCEL_TOKEN_TSTR: { fekString }
{ string constant is stored as widestring in BIFF8, otherwise as ansistring
Writing is done by the virtual method WriteString_8bitLen. }
begin
inc(RPNLength, WriteString_8bitLen(AStream, AFormula[i].StringValue));
end;
INT_EXCEL_TOKEN_TBOOL: { fekBool }
begin
AStream.WriteByte(ord(AFormula[i].DoubleValue <> 0.0));
inc(RPNLength, 1);
end;
{ binary operation tokens }
INT_EXCEL_TOKEN_TADD, INT_EXCEL_TOKEN_TSUB, INT_EXCEL_TOKEN_TMUL,
INT_EXCEL_TOKEN_TDIV, INT_EXCEL_TOKEN_TPOWER:
begin
end;
{ Other operations }
INT_EXCEL_TOKEN_TATTR: { fekOpSUM }
{ 3.10, page 71: e.g. =SUM(1) is represented by token array tInt(1),tAttrRum }
begin
// Unary SUM Operation
AStream.WriteByte($10); //tAttrSum token (SUM with one parameter)
AStream.WriteByte(0); // not used
AStream.WriteByte(0); // not used
inc(RPNLength, 3);
end;
// Functions with fixed parameter count
INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A:
begin
n := WriteRPNFunc(AStream, secondaryID);
inc(RPNLength, n);
end;
// Functions with variable parameter count
INT_EXCEL_TOKEN_FUNCVAR_V:
begin
AStream.WriteByte(AFormula[i].ParamsNum);
n := WriteRPNFunc(AStream, secondaryID);
inc(RPNLength, 1 + n);
end;
end; // case
end; // for
// Now update the size of the token array.
finalPos := AStream.Position;
AStream.Position := TokenArraySizePos;
AStream.WriteByte(RPNLength);
AStream.Position := finalPos;
end;
{ Writes the size of the RPN token array. Called from WriteRPNFormula.
Valid for BIFF3-BIFF8. Override in BIFF2. }
procedure TsSPREADBIFFWriter.WriteRPNTokenArraySize(AStream: TStream; ASize: Word);
begin
AStream.WriteWord(WordToLE(ASize));
end;
{ Writes an Excel 3-8 ROW record { Writes an Excel 3-8 ROW record
Valid for BIFF3-BIFF8 } Valid for BIFF3-BIFF8 }
procedure TsSpreadBIFFWriter.WriteRow(AStream: TStream; ASheet: TsWorksheet; procedure TsSpreadBIFFWriter.WriteRow(AStream: TStream; ASheet: TsWorksheet;
@ -2153,13 +2436,22 @@ var
len: Byte; len: Byte;
s: ansistring; s: ansistring;
begin begin
s := AString; s := UTF8ToAnsi(AString);
len := Length(s); len := Length(s);
AStream.WriteByte(len); AStream.WriteByte(len);
AStream.WriteBuffer(s[1], len); AStream.WriteBuffer(s[1], len);
Result := 1 + len; Result := 1 + len;
end; end;
{ Write STRING record following immediately after RPN formula if formula result
is a non-empty string.
Must be overridden because depends of BIFF version }
procedure TsSpreadBIFFWriter.WriteStringRecord(AStream: TStream;
AString: String);
begin
Unused(AStream, AString);
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.