diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas
index 8a2cf7a99..ddcf092c1 100755
--- a/components/fpspreadsheet/fpspreadsheet.pas
+++ b/components/fpspreadsheet/fpspreadsheet.pas
@@ -908,7 +908,8 @@ type
{@@ 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;
{@@ 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. }
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. }
@@ -5290,8 +5291,8 @@ end;
@param ACell Pointer to the cell containing the formula and being written
to the stream
}
-procedure TsCustomSpreadWriter.WriteRPNFormula(AStream: TStream; const ARow,
- ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
+procedure TsCustomSpreadWriter.WriteRPNFormula(AStream: TStream;
+ const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
begin
Unused(AStream, ARow, ACol);
Unused(AFormula, ACell);
diff --git a/components/fpspreadsheet/tests/formulatests.pas b/components/fpspreadsheet/tests/formulatests.pas
index e48500d4f..3c6960639 100644
--- a/components/fpspreadsheet/tests/formulatests.pas
+++ b/components/fpspreadsheet/tests/formulatests.pas
@@ -46,6 +46,10 @@ type
procedure TestWriteRead_BIFF8_FormulaStrings;
// Writes out and calculates formulas, read back
+ { BIFF2 Tests }
+ procedure TestWriteRead_BIFF2_CalcRPNFormula;
+ { BIFF5 Tests }
+ procedure TestWriteRead_BIFF5_CalcRPNFormula;
{ BIFF8 Tests }
procedure TestWriteRead_BIFF8_CalcRPNFormula;
end;
@@ -223,6 +227,15 @@ begin
DeleteFile(TempFile);
end;
+procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF2_CalcRPNFormula;
+begin
+ TestCalcRPNFormulas(sfExcel2);
+end;
+
+procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF5_CalcRPNFormula;
+begin
+ TestCalcRPNFormulas(sfExcel5);
+end;
procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF8_CalcRPNFormula;
begin
diff --git a/components/fpspreadsheet/tests/spreadtestgui.lpi b/components/fpspreadsheet/tests/spreadtestgui.lpi
index b35028661..86230b550 100644
--- a/components/fpspreadsheet/tests/spreadtestgui.lpi
+++ b/components/fpspreadsheet/tests/spreadtestgui.lpi
@@ -47,9 +47,6 @@
-
-
-
@@ -157,9 +154,6 @@
-
-
-
diff --git a/components/fpspreadsheet/tests/testcases_calcrpnformula.inc b/components/fpspreadsheet/tests/testcases_calcrpnformula.inc
index 04883ba45..eff6a6c9f 100644
--- a/components/fpspreadsheet/tests/testcases_calcrpnformula.inc
+++ b/components/fpspreadsheet/tests/testcases_calcrpnformula.inc
@@ -1,6 +1,9 @@
{ include file for "formulatests.pas", containing the test cases for the
calcrpnformula test. }
+{------------------------------------------------------------------------------}
+{ Basic operations }
+{------------------------------------------------------------------------------}
// Addition
Row := 0;
MyWorksheet.WriteUTF8Text(Row, 0, '=1+1');
@@ -327,7 +330,7 @@
{ Math }
{------------------------------------------------------------------------------}
- // ABS
+// ABS
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=abs(-1)');
MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas
index 39a51d640..8d269701c 100755
--- a/components/fpspreadsheet/xlsbiff2.pas
+++ b/components/fpspreadsheet/xlsbiff2.pas
@@ -112,7 +112,11 @@ type
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; const AValue: double; ACell: PCell); override;
procedure WriteRow(AStream: TStream; ASheet: TsWorksheet;
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 WriteWindow2(AStream: TStream; ASheet: TsWorksheet);
public
@@ -1283,8 +1287,8 @@ end;
MyFormula[1].Row := 0;
MyFormula[2].TokenID := INT_EXCEL_TOKEN_TADD; +
}
-procedure TsSpreadBIFF2Writer.WriteRPNFormula(AStream: TStream; const ARow,
- ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
+procedure TsSpreadBIFF2Writer.WriteRPNFormula(AStream: TStream;
+ const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
var
FormulaResult: double;
i: Integer;
@@ -1315,14 +1319,18 @@ begin
{ BIFF2 Attributes }
WriteCellFormatting(AStream, ACell, xf);
- { Result of the formula in IEEE 754 floating-point value }
- AStream.WriteBuffer(FormulaResult, 8);
+ { Encoded result of RPN formula }
+ WriteRPNResult(AStream, ACell);
{ 0 = Do not recalculate
1 = Always recalculate }
- AStream.WriteByte($1);
+ AStream.WriteByte(1);
- { Formula }
+ { Formula data (RPN token array) }
+ WriteRPNTokenArray(AStream, AFormula, RPNLength);
+
+(*
+{ Formula }
{ The size of the token array is written later,
because it's necessary to calculate if first,
@@ -1420,8 +1428,58 @@ begin
AStream.Position := RecordSizePos;
AStream.WriteWord(WordToLE(17 + RPNLength));
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;
+{ 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 ()
*
diff --git a/components/fpspreadsheet/xlsbiff5.pas b/components/fpspreadsheet/xlsbiff5.pas
index 229443663..71c05f5ab 100755
--- a/components/fpspreadsheet/xlsbiff5.pas
+++ b/components/fpspreadsheet/xlsbiff5.pas
@@ -120,8 +120,11 @@ type
procedure WriteIndex(AStream: TStream);
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: string; ACell: PCell); override;
+{
procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal;
const AFormula: TsRPNFormula; ACell: PCell); override;
+ }
+ procedure WriteStringRecord(AStream: TStream; AString: String); override;
procedure WriteStyle(AStream: TStream);
procedure WriteWindow2(AStream: TStream; ASheet: TsWorksheet);
procedure WriteXF(AStream: TStream; AFontIndex: Word;
@@ -677,7 +680,7 @@ begin
AStream.WriteByte(len); // AnsiString, char count in 1 byte
AStream.WriteBuffer(s[1], len * SizeOf(AnsiChar)); // String data
end;
-
+ (*
{*******************************************************************
* TsSpreadBIFF5Writer.WriteRPNFormula ()
*
@@ -688,8 +691,8 @@ end;
* AFormula array
*
*******************************************************************}
-procedure TsSpreadBIFF5Writer.WriteRPNFormula(AStream: TStream; const ARow,
- ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
+procedure TsSpreadBIFF5Writer.WriteRPNFormula(AStream: TStream;
+ const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
var
FormulaResult: double;
i: Integer;
@@ -835,6 +838,7 @@ begin
AStream.WriteWord(WordToLE(22 + RPNLength));
AStream.position := FinalPos;
end;
+ *)
{*******************************************************************
* TsSpreadBIFF5Writer.WriteIndex ()
@@ -943,6 +947,28 @@ begin
}
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 ()
*
diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas
index f5781c101..8ba1499d3 100755
--- a/components/fpspreadsheet/xlsbiff8.pas
+++ b/components/fpspreadsheet/xlsbiff8.pas
@@ -123,10 +123,16 @@ type
procedure WriteIndex(AStream: TStream);
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
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;
const AFormula: TsRPNFormula; ACell: PCell); 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 WriteWindow2(AStream: TStream; ASheet: TsWorksheet);
procedure WriteXF(AStream: TStream; AFontIndex: Word;
@@ -225,9 +231,9 @@ const
INT_EXCEL_ID_FORCEFULLCALCULATION = $08A3;
{ Cell Addresses constants }
- MASK_EXCEL_COL_BITS_BIFF8=$00FF;
- MASK_EXCEL_RELATIVE_COL = $4000; // This is according to Microsoft documentation,
- MASK_EXCEL_RELATIVE_ROW = $8000; // but opposite to OpenOffice documentation!
+ MASK_EXCEL_COL_BITS_BIFF8 = $00FF;
+ MASK_EXCEL_RELATIVE_COL_BIFF8 = $4000; // This is according to Microsoft documentation,
+ MASK_EXCEL_RELATIVE_ROW_BIFF8 = $8000; // but opposite to OpenOffice documentation!
{ BOF record constants }
INT_BOF_BIFF8_VER = $0600;
@@ -240,11 +246,6 @@ const
INT_BOF_BUILD_ID = $1FD2;
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 }
MASK_STYLE_BUILT_IN = $8000;
@@ -796,6 +797,54 @@ begin
AStream.position := FinalPos;*)
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,
ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
var
@@ -869,11 +918,12 @@ begin
{ Formula }
- { The size of the token array is written later,
- because it's necessary to calculate if first,
- and this is done at the same time it is written }
+ { The size of the token array is written later, because it's necessary to
+ calculate if first, and this is done at the same time it is written }
TokenArraySizePos := AStream.Position;
- AStream.WriteWord(RPNLength);
+ WriteRPNTokenArraySize(AStream, RPNLength);
+
+ WriteRPNTokenArray(AStream, AFormula, RPNLength);
{ Formula data (RPN token array) }
for i := 0 to Length(AFormula) - 1 do
@@ -988,12 +1038,13 @@ begin
AStream.WriteByte(RPNLength);
AStream.Position := RecordSizePos;
AStream.WriteWord(WordToLE(22 + RPNLength));
- AStream.position := FinalPos;
+ 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;
+*)
{ Helper function for writing a string with 8-bit length. Overridden version
for BIFF8. Called for writing rpn formula string tokens.
diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas
index 8343bb41d..26bc2917a 100644
--- a/components/fpspreadsheet/xlscommon.pas
+++ b/components/fpspreadsheet/xlscommon.pas
@@ -320,6 +320,11 @@ const
{ Note: The assignment of the RELATIVE_COL and _ROW masks is according to
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 }
ERR_INTERSECTION_EMPTY = $00; // #NULL!
ERR_DIVIDE_BY_ZERO = $07; // #DIV/0!
@@ -478,6 +483,17 @@ type
// Writes out a PANE record
procedure WritePane(AStream: TStream; ASheet: TsWorksheet; IsBiff58: Boolean;
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
procedure WriteRow(AStream: TStream; ASheet: TsWorksheet;
ARowIndex, AFirstColIndex, ALastColIndex: Cardinal; ARow: PRow); virtual;
@@ -487,6 +503,7 @@ type
procedure WriteSelection(AStream: TStream; ASheet: TsWorksheet; APane: Byte);
procedure WriteSelections(AStream: TStream; ASheet: TsWorksheet);
procedure WriteSheetPR(AStream: TStream);
+ procedure WriteStringRecord(AStream: TStream; AString: String); virtual;
// Writes out a WINDOW1 record
procedure WriteWindow1(AStream: TStream); virtual;
// Writes the index of the XF record used in the given cell
@@ -1537,8 +1554,8 @@ begin
end;
{ 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
- where it has to overridden. }
+ version for ansistrings since it is valid for all BIFF versions except for
+ BIFF8 where it has to be overridden. }
function TsSpreadBIFFReader.ReadString_8bitLen(AStream: TStream): String;
var
len: Byte;
@@ -1968,6 +1985,272 @@ begin
{ Not used (BIFF5-BIFF8 only, not written in BIFF2-BIFF4 }
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
Valid for BIFF3-BIFF8 }
procedure TsSpreadBIFFWriter.WriteRow(AStream: TStream; ASheet: TsWorksheet;
@@ -2153,13 +2436,22 @@ var
len: Byte;
s: ansistring;
begin
- s := AString;
+ s := UTF8ToAnsi(AString);
len := Length(s);
AStream.WriteByte(len);
AStream.WriteBuffer(s[1], len);
Result := 1 + len;
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
This record contains general settings for the document window and
global workbook settings.