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.