+ Add support for more/easier RPN formulas in XLS

- Allow relative cell references in RPN formulas
- Simplify generation of RPN formulas by using nested function calls
- Add writing of cell ranges for formulas in BIFF2/BIFF5
- Simplification of code e.g. by replacing the huge case instruction in TsSpreadBiffWriter.FormulaElementKindToExcelTokenID by a look-up table.
Fixes mantis issue #25718; patch by wp; thanks a lot!



git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@2931 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
bigchimp
2014-04-08 09:48:30 +00:00
parent b7eb5352c0
commit 27498ff548
8 changed files with 857 additions and 177 deletions

View File

@ -14,7 +14,7 @@ unit fpspreadsheet;
interface interface
uses uses
Classes, SysUtils, fpimage, AVL_Tree, avglvltree, lconvencoding; Classes, SysUtils, fpimage, AVL_Tree, avglvltree, lconvencoding, fpsutils;
type type
TsSpreadsheetFormat = (sfExcel2, sfExcel3, sfExcel4, sfExcel5, sfExcel8, TsSpreadsheetFormat = (sfExcel2, sfExcel3, sfExcel4, sfExcel5, sfExcel8,
@ -56,15 +56,51 @@ type
DoubleValue: double; DoubleValue: double;
end; end;
{@@ Expanded formula. Used by backend modules. Provides more information then the text only } {@@ Expanded formula. Used by backend modules. Provides more information than the text only
See http://www.techonthenet.com/excel/formulas/ for an explanation of
meaning and parameters of each formula
NOTE: When adding or rearranging items make sure to keep the TokenID table
in TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID, unit xlscommon,
in sync !!!
}
TFEKind = ( TFEKind = (
{ Basic operands } { Basic operands }
fekCell, fekCellRange, fekNum, fekCell, fekCellRef, fekCellRange, fekNum, fekString, fekBool, fekMissingArg,
{ Basic operations } { Basic operations }
fekAdd, fekSub, fekDiv, fekMul, fekAdd, fekSub, fekDiv, fekMul, fekPercent, fekPower, fekUMinus, fekUPlus,
fekConcat, // string concatenation
fekEqual, fekGreater, fekGreaterEqual, fekLess, fekLessEqual, fekNotEqual,
{ Built-in/Worksheet Functions} { Built-in/Worksheet Functions}
fekABS, fekDATE, fekROUND, fekTIME, // math
fekABS, fekACOS, fekACOSH, fekASIN, fekASINH, fekATAN, fekATANH,
fekCOS, fekCOSH, fekDEGREES, fekEXP, fekINT, fekLN, fekLOG,
fekLOG10, fekPI, fekRADIANS, fekRAND, fekROUND,
fekSIGN, fekSIN, fekSINH, fekSQRT,
fekTAN, fekTANH,
// date/time
fekDATE, fekDATEDIF, fekDATEVALUE, fekDAY, fekHOUR, fekMINUTE, fekMONTH,
fekNOW, fekSECOND, fekTIME, fekTIMEVALUE, fekTODAY, fekWEEKDAY, fekYEAR,
// statistical
fekAVEDEV, fekAVERAGE, fekBETADIST, fekBETAINV, fekBINOMDIST, fekCHIDIST,
fekCHIINV, fekCOUNT, fekCOUNTA, fekCOUNTBLANK, fekCOUNTIF,
fekMAX, fekMEDIAN, fekMIN, fekPERMUT, fekPOISSON, fekPRODUCT,
fekSTDEV, fekSTDEVP, fekSUM, fekSUMIF, fekSUMSQ, fekVAR, fekVARP,
// financial
fekFV, fekNPER, fekPV, fekPMT, fekRATE,
// logical
fekAND, fekFALSE, fekIF, fekNOT, fekOR, fekTRUE,
// string
fekCHAR, fekCODE, fekLEFT, fekLOWER, fekMID, fekPROPER, fekREPLACE, fekRIGHT,
fekSUBSTITUTE, fekTRIM, fekUPPER,
// lookup/reference
fekCOLUMN, fekCOLUMNS, fekROW, fekROWS,
// info
fekCELLINFO, fekINFO, fekIsBLANK, fekIsERR, fekIsERROR,
fekIsLOGICAL, fekIsNA, fekIsNONTEXT, fekIsNUMBER, fekIsRef, fekIsTEXT,
fekValue,
{ Other operations } { Other operations }
fekOpSUM fekOpSUM
); );
@ -72,9 +108,13 @@ type
TsFormulaElement = record TsFormulaElement = record
ElementKind: TFEKind; ElementKind: TFEKind;
Row, Row2: Word; // zero-based Row, Row2: Word; // zero-based
Col, Col2: Byte; // zero-based Col, Col2: Word; // zero-based
Param1, Param2: Word; // Extra parameters Param1, Param2: Word; // Extra parameters
DoubleValue: double; DoubleValue: double;
IntValue: Word;
StringValue: String;
RelFlags: TsRelFlags; // store info on relative/absolute addresses
ParamsNum: Byte;
end; end;
TsExpandedFormula = array of TsFormulaElement; TsExpandedFormula = array of TsFormulaElement;
@ -378,6 +418,54 @@ type
Format: TsSpreadsheetFormat; Format: TsSpreadsheetFormat;
end; end;
{@@ Helper for simplification of RPN formula creation }
PRPNItem = ^TRPNItem;
TRPNItem = record
FE: TsFormulaElement;
Next: PRPNItem;
end;
{@@
Simple creation an RPNFormula array to be used in fpspreadsheet.
For each formula element, use one of the RPNxxxx functions implemented here.
They are designed to be nested into each other. Terminate the chain by
using nil.
Example:
The RPN formula for the string expression "$A1+2" can be created as follows:
var
f: TsRPNFormula;
f := CreateRPNFormula(
RPNCellValue('A1',
RPNNumber(2,
RPNFunc(fekAdd,
nil))));
}
function CreateRPNFormula(AItem: PRPNItem): TsRPNFormula;
function RPNBool(AValue: Boolean;
ANext: PRPNItem): PRPNItem;
function RPNCellValue(ACellAddress: String;
ANext: PRPNItem): PRPNItem; overload;
function RPNCellValue(ARow, ACol: Integer; AFlags: TsRelFlags;
ANext: PRPNItem): PRPNItem; overload;
function RPNCellRef(ACellAddress: String;
ANext: PRPNItem): PRPNItem; overload;
function RPNCellRef(ARow, ACol: Integer; AFlags: TsRelFlags;
ANext: PRPNItem): PRPNItem; overload;
function RPNCellRange(ACellRangeAddress: String;
ANext: PRPNItem): PRPNItem; overload;
function RPNCellRange(ARow, ACol, ARow2, ACol2: Integer; AFlags: TsRelFlags;
ANext: PRPNItem): PRPNItem; overload;
function RPNMissingArg(ANext: PRPNItem): PRPNItem;
function RPNNumber(AValue: Double; ANext: PRPNItem): PRPNItem;
function RPNString(AValue: String; ANext: PRPNItem): PRPNItem;
function RPNFunc(AToken: TFEKind; ANext: PRPNItem): PRPNItem; overload;
function RPNFunc(AToken: TFEKind; ANumParams: Byte; ANext: PRPNItem): PRPNItem; overload;
var var
GsSpreadFormats: array of TsSpreadFormatData; GsSpreadFormats: array of TsSpreadFormatData;
@ -1874,6 +1962,220 @@ begin
end; end;
{ Simplified creation of RPN formulas }
function NewRPNItem: PRPNItem;
begin
Result := GetMem(SizeOf(TRPNItem));
FillChar(Result^.FE, SizeOf(Result^.FE), 0);
Result^.FE.StringValue := '';
end;
procedure DisposeRPNItem(AItem: PRPNItem);
begin
if AItem <> nil then
FreeMem(AItem, SizeOf(TRPNItem));
end;
{@@
Creates a boolean value entry in the RPN array.
}
function RPNBool(AValue: Boolean; ANext: PRPNItem): PRPNItem;
begin
Result := NewRPNItem;
Result^.FE.ElementKind := fekBool;
if AValue then Result^.FE.DoubleValue := 1.0 else Result^.FE.DoubleValue := 0.0;
Result^.Next := ANext;
end;
{@@
Creates an entry in the RPN array for a cell value, specifed by its
address, e.g. 'A1'. Takes care of absolute and relative cell addresses.
}
function RPNCellValue(ACellAddress: String; ANext: PRPNItem): PRPNItem;
var
r,c: Integer;
flags: TsRelFlags;
begin
if not ParseCellString(ACellAddress, r, c, flags) then
raise Exception.CreateFmt('"%s" is not a valid cell address.', [ACellAddress]);
Result := RPNCellValue(r,c, flags, ANext);
end;
{@@
Creates an entry in the RPN array for a cell value, specifed by its
row and column index and a flag containing information on relative addresses.
}
function RPNCellValue(ARow, ACol: Integer; AFlags: TsRelFlags;
ANext: PRPNItem): PRPNItem;
begin
Result := NewRPNItem;
Result^.FE.ElementKind := fekCell;
Result^.FE.Row := ARow;
Result^.FE.Col := ACol;
Result^.FE.RelFlags := AFlags;
Result^.Next := ANext;
end;
{@@
Creates an entry in the RPN array for a cell reference, specifed by its
address, e.g. 'A1'. Takes care of absolute and relative cell addresses.
"Cell reference" means that all properties of the cell can be handled.
Note that most Excel formulas with cells require the cell value only
(--> RPNCellValue)
}
function RPNCellRef(ACellAddress: String; ANext: PRPNItem): PRPNItem;
var
r,c: Integer;
flags: TsRelFlags;
begin
if not ParseCellString(ACellAddress, r, c, flags) then
raise Exception.CreateFmt('"%s" is not a valid cell address.', [ACellAddress]);
Result := RPNCellRef(r,c, flags, ANext);
end;
{@@
Creates an entry in the RPN array for a cell reference, specifed by its
row and column index and flags containing information on relative addresses.
"Cell reference" means that all properties of the cell can be handled.
Note that most Excel formulas with cells require the cell value only
(--> RPNCellValue)
}
function RPNCellRef(ARow, ACol: Integer; AFlags: TsRelFlags;
ANext: PRPNItem): PRPNItem;
begin
Result := NewRPNItem;
Result^.FE.ElementKind := fekCellRef;
Result^.FE.Row := ARow;
Result^.FE.Col := ACol;
Result^.FE.RelFlags := AFlags;
Result^.Next := ANext;
end;
{@@
Creates an entry in the RPN array for a range of cells, specified by an
Excel-style address, e.g. A1:G5. As in Excel, use a $ sign to indicate
absolute addresses.
}
function RPNCellRange(ACellRangeAddress: String; ANext: PRPNItem): PRPNItem;
var
r1,c1, r2,c2: Integer;
flags: TsRelFlags;
begin
if not ParseCellRangeString(ACellRangeAddress, r1,c1, r2,c2, flags) then
raise Exception.CreateFmt('"%s" is not a valid cell range address.', [ACellRangeAddress]);
Result := RPNCellRange(r1,c1, r2,c2, flags, ANext);
end;
{@@
Creates an entry in the RPN array for a range of cells, specified by the
row/column indexes of the top/left and bottom/right corners of the block.
The flags indicate relative indexes.
}
function RPNCellRange(ARow, ACol, ARow2, ACol2: Integer; AFlags: TsRelFlags;
ANext: PRPNItem): PRPNItem;
begin
Result := NewRPNItem;
Result^.FE.ElementKind := fekCellRange;
Result^.FE.Row := ARow;
Result^.FE.Col := ACol;
Result^.FE.Row2 := ARow2;
Result^.FE.Col2 := ACol2;
Result^.FE.RelFlags := AFlags;
Result^.Next := ANext;
end;
{@@
Creates an entry in the RPN array for a missing argument in of function call.
}
function RPNMissingArg(ANext: PRPNItem): PRPNItem;
begin
Result := NewRPNItem;
Result^.FE.ElementKind := fekMissingArg;
Result^.Next := ANext;
end;
{@@
Creates an entry in the RPN array for a number. Integers and floating-point
values can be used likewise.
}
function RPNNumber(AValue: Double; ANext: PRPNItem): PRPNItem;
begin
Result := NewRPNItem;
Result^.FE.ElementKind := fekNum;
Result^.FE.DoubleValue := AValue;
Result^.Next := ANext;
end;
{@@
Creates an entry in the RPN array for a string.
}
function RPNString(AValue: String; ANext: PRPNItem): PRPNItem;
begin
Result := NewRPNItem;
Result^.FE.ElementKind := fekString;
Result^.FE.StringValue := AValue;
Result^.Next := ANext;
end;
{@@
Creates an entry in the RPN array for an Excel function or operation
specified by its TokenID (--> TFEKind). Note that array elements for all
needed parameters must have been created before.
}
function RPNFunc(AToken: TFEKind; ANext: PRPNItem): PRPNItem;
begin
if ord(AToken) < ord(fekAdd) then
raise Exception.Create('No basic tokens allowed here.');
Result := NewRPNItem;
Result^.FE.ElementKind := AToken;
Result^.Next := ANext;
end;
{@@
Creates an entry in the RPN array for an Excel function or operation
specified by its TokenID (--> TFEKind). Specify the number of parameters used.
They must have been created before.
}
function RPNFunc(AToken: TFEKind; ANumParams: Byte; ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(AToken, ANext);
Result^.FE.ParamsNum := ANumParams;
end;
{@@
Creates an RPN formula by a single call using nested RPN items.
}
function CreateRPNFormula(AItem: PRPNItem): TsRPNFormula;
var
item: PRPNItem;
nextitem: PRPNItem;
n: Integer;
begin
// Determine count of RPN elements
n := 0;
item := AItem;
while item <> nil do begin
inc(n);
item := item^.Next;
end;
// Set array length of TsRPNFormula result
SetLength(Result, n);
// Copy FormulaElements to result and free temporary RPNItems
item := AItem;
n := 0;
while item <> nil do begin
nextitem := item^.Next;
Result[n] := item^.FE;
inc(n);
DisposeRPNItem(item);
item := nextitem;
end;
end;
finalization finalization
SetLength(GsSpreadFormats, 0); SetLength(GsSpreadFormats, 0);

View File

@ -14,6 +14,9 @@ uses
type type
TsSelectionDirection = (fpsVerticalSelection, fpsHorizontalSelection); TsSelectionDirection = (fpsVerticalSelection, fpsHorizontalSelection);
TsRelFlag = (rfRelRow, rfRelCol, rfRelRow2, rfRelCol2);
TsRelFlags = set of TsRelFlag;
const const
// Date formatting string for unambiguous date/time display as strings // Date formatting string for unambiguous date/time display as strings
// Can be used for text output when date/time cell support is not available // Can be used for text output when date/time cell support is not available
@ -35,8 +38,13 @@ function WideStringLEToN(const AValue: WideString): WideString;
function ParseIntervalString(const AStr: string; function ParseIntervalString(const AStr: string;
var AFirstCellRow, AFirstCellCol, ACount: Integer; var AFirstCellRow, AFirstCellCol, ACount: Integer;
var ADirection: TsSelectionDirection): Boolean; var ADirection: TsSelectionDirection): Boolean;
function ParseCellRangeString(const AStr: string;
var AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Integer;
var AFlags: TsRelFlags): Boolean;
function ParseCellString(const AStr: string; function ParseCellString(const AStr: string;
var ACellRow, ACellCol: Integer): Boolean; var ACellRow, ACellCol: Integer; var AFlags: TsRelFlags): Boolean; overload;
function ParseCellString(const AStr: string;
var ACellRow, ACellCol: Integer): Boolean; overload;
function ParseCellRowString(const AStr: string; function ParseCellRowString(const AStr: string;
var AResult: Integer): Boolean; var AResult: Integer): Boolean;
function ParseCellColString(const AStr: string; function ParseCellColString(const AStr: string;
@ -148,11 +156,17 @@ function ParseIntervalString(const AStr: string;
var AFirstCellRow, AFirstCellCol, ACount: Integer; var AFirstCellRow, AFirstCellCol, ACount: Integer;
var ADirection: TsSelectionDirection): Boolean; var ADirection: TsSelectionDirection): Boolean;
var var
Cells: TStringList; //Cells: TStringList;
LastCellRow, LastCellCol: Integer; LastCellRow, LastCellCol: Integer;
p: Integer;
s1, s2: String;
begin begin
Result := True; Result := True;
{ Simpler:
use "pos" instead of the TStringList overhead.
And: the StringList is not free'ed here
// First get the cells // First get the cells
Cells := TStringList.Create; Cells := TStringList.Create;
ExtractStrings([':'],[], PChar(AStr), Cells); ExtractStrings([':'],[], PChar(AStr), Cells);
@ -162,6 +176,19 @@ begin
if not Result then Exit; if not Result then Exit;
Result := ParseCellString(Cells[1], LastCellRow, LastCellCol); Result := ParseCellString(Cells[1], LastCellRow, LastCellCol);
if not Result then Exit; if not Result then Exit;
}
// First find the position of the colon and split into parts
p := pos(':', AStr);
if p = 0 then exit(false);
s1 := copy(AStr, 1, p-1);
s2 := copy(AStr, p+1, Length(AStr));
// Then parse each of them
Result := ParseCellString(s1, AFirstCellRow, AFirstCellCol);
if not Result then Exit;
Result := ParseCellString(s2, LastCellRow, LastCellCol);
if not Result then Exit;
if AFirstCellRow = LastCellRow then if AFirstCellRow = LastCellRow then
begin begin
@ -176,6 +203,42 @@ begin
else Exit(False); else Exit(False);
end; end;
{@@
Parses strings like A5:C10 into a range selection information.
Return also information on relative/absolute cells.
}
function ParseCellRangeString(const AStr: string;
var AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Integer;
var AFlags: TsRelFlags): Boolean;
var
p: Integer;
s: String;
begin
Result := True;
// First find the colon
p := pos(':', AStr);
if p = 0 then exit(false);
// Analyze part after the colon
s := copy(AStr, p+1, Length(AStr));
Result := ParseCellString(s, ALastCellRow, ALastCellCol, AFlags);
if not Result then exit;
if (rfRelRow in AFlags) then begin
Include(AFlags, rfRelRow2);
Exclude(AFlags, rfRelRow);
end;
if (rfRelCol in AFlags) then begin
Include(AFlags, rfRelCol2);
Exclude(AFlags, rfRelCol);
end;
// Analyze part before the colon
s := copy(AStr, 1, p-1);
Result := ParseCellString(s, AFirstCellRow, AFirstCellCol, AFlags);
end;
{@@ {@@
Parses a cell string, like 'A1' into zero-based column and row numbers Parses a cell string, like 'A1' into zero-based column and row numbers
@ -184,13 +247,17 @@ end;
0 - Reading Column part 1 (necesserely needs a letter) 0 - Reading Column part 1 (necesserely needs a letter)
1 - Reading Column part 2, but could be the first number as well 1 - Reading Column part 2, but could be the first number as well
2 - Reading Row 2 - Reading Row
'AFlags' indicates relative addresses.
} }
function ParseCellString(const AStr: string; var ACellRow, ACellCol: Integer): Boolean; function ParseCellString(const AStr: string; var ACellRow, ACellCol: Integer;
var AFlags: TsRelFlags): Boolean;
var var
i: Integer; i: Integer;
state: Integer; state: Integer;
Col, Row: string; Col, Row: string;
lChar: Char; lChar: Char;
isAbs: Boolean;
const const
cLetters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', cLetters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'W', 'X', 'Y', 'Z']; 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'W', 'X', 'Y', 'Z'];
@ -201,11 +268,20 @@ begin
state := 0; state := 0;
Col := ''; Col := '';
Row := ''; Row := '';
AFlags := [rfRelCol, rfRelRow];
isAbs := false;
// Separates the string into a row and a col // Separates the string into a row and a col
for i := 0 to Length(AStr) - 1 do for i := 1 to Length(AStr) do
begin begin
lChar := AStr[i + 1]; lChar := AStr[i];
if lChar = '$' then begin
if isAbs then
exit(false);
isAbs := true;
continue;
end;
case state of case state of
@ -214,6 +290,9 @@ begin
if lChar in cLetters then if lChar in cLetters then
begin begin
Col := lChar; Col := lChar;
if isAbs then
Exclude(AFlags, rfRelCol);
isAbs := false;
state := 1; state := 1;
end end
else Exit(False); else Exit(False);
@ -221,10 +300,14 @@ begin
1: 1:
begin begin
if lChar in cLetters then Col := Col + lChar if lChar in cLetters then
Col := Col + lChar
else if lChar in cDigits then else if lChar in cDigits then
begin begin
Row := lChar; Row := lChar;
if isAbs then
Exclude(AFlags, rfRelRow);
isAbs := false;
state := 2; state := 2;
end end
else Exit(False); else Exit(False);
@ -244,6 +327,16 @@ begin
ParseCellColString(Col, ACellCol); ParseCellColString(Col, ACellCol);
end; end;
{ for compatibility with old version which does not return flags for relative
cell addresses }
function ParseCellString(const AStr: string;
var ACellRow, ACellCol: Integer): Boolean;
var
flags: TsRelFlags;
begin
ParseCellString(AStr, ACellRow, ACellCol, flags);
end;
function ParseCellRowString(const AStr: string; var AResult: Integer): Boolean; function ParseCellRowString(const AStr: string; var AResult: Integer): Boolean;
begin begin
try try
@ -267,6 +360,12 @@ begin
AResult := (Ord(AStr[1]) - Ord('A') + 1) * INT_NUM_LETTERS AResult := (Ord(AStr[1]) - Ord('A') + 1) * INT_NUM_LETTERS
+ Ord(AStr[2]) - Ord('A'); + Ord(AStr[2]) - Ord('A');
end end
else if Length(AStr) = 3 then
begin
AResult := (Ord(AStr[1]) - Ord('A') + 1) * INT_NUM_LETTERS * INT_NUM_LETTERS
+ (Ord(AStr[2]) - Ord('A') + 1) * INT_NUM_LETTERS
+ Ord(AStr[3]) - Ord('A');
end
else Exit(False); else Exit(False);
Result := True; Result := True;

View File

@ -50,7 +50,7 @@ type
procedure TearDown; override; procedure TearDown; override;
published published
// Current fpspreadsheet does not yet have support for new RPN formulas // Current fpspreadsheet does not yet have support for new RPN formulas
{.$DEFINE FPSPREAD_HAS_NEWRPNSUPPORT} {$DEFINE FPSPREAD_HAS_NEWRPNSUPPORT}
{$IFDEF FPSPREAD_HAS_NEWRPNSUPPORT} {$IFDEF FPSPREAD_HAS_NEWRPNSUPPORT}
// As described in bug 25718: Feature request & patch: Implementation of writing more functions // As described in bug 25718: Feature request & patch: Implementation of writing more functions
// Writes all rpn formulas. Use Excel or Open/LibreOffice to check validity. // Writes all rpn formulas. Use Excel or Open/LibreOffice to check validity.

View File

@ -57,9 +57,8 @@ type
{ TsSpreadBIFF2Writer } { TsSpreadBIFF2Writer }
TsSpreadBIFF2Writer = class(TsCustomSpreadWriter) TsSpreadBIFF2Writer = class(TsSpreadBIFFWriter)
private private
function FEKindToExcelID(AElement: TFEKind; var AParamsNum, AFuncNum: Byte): Byte;
procedure WriteCellFormatting(AStream: TStream; ACell: PCell); procedure WriteCellFormatting(AStream: TStream; ACell: PCell);
public public
{ General writing methods } { General writing methods }
@ -86,8 +85,8 @@ const
{ Cell Addresses constants } { Cell Addresses constants }
MASK_EXCEL_ROW = $3FFF; MASK_EXCEL_ROW = $3FFF;
MASK_EXCEL_RELATIVE_ROW = $4000; MASK_EXCEL_RELATIVE_COL = $4000; // This is according to Microsoft documentation,
MASK_EXCEL_RELATIVE_COL = $8000; MASK_EXCEL_RELATIVE_ROW = $8000; // but opposite to OpenOffice documentation!
{ BOF record constants } { BOF record constants }
INT_EXCEL_SHEET = $0010; INT_EXCEL_SHEET = $0010;
@ -96,47 +95,6 @@ const
{ TsSpreadBIFF2Writer } { TsSpreadBIFF2Writer }
function TsSpreadBIFF2Writer.FEKindToExcelID(AElement: TFEKind; var AParamsNum, AFuncNum: Byte): Byte;
begin
AFuncNum := 0;
case AElement of
{ Operands }
fekCell: Result := INT_EXCEL_TOKEN_TREFV;
fekNum: Result := INT_EXCEL_TOKEN_TNUM;
{ Operators }
fekAdd: Result := INT_EXCEL_TOKEN_TADD;
fekSub: Result := INT_EXCEL_TOKEN_TSUB;
fekDiv: Result := INT_EXCEL_TOKEN_TDIV;
fekMul: Result := INT_EXCEL_TOKEN_TMUL;
{ Built-in/worksheet functions }
fekABS:
begin
Result := INT_EXCEL_TOKEN_FUNCVAR_V;
AParamsNum := 1;
AFuncNum := INT_EXCEL_SHEET_FUNC_ABS;
end;
fekDATE:
begin
Result := INT_EXCEL_TOKEN_FUNCVAR_V;
AParamsNum := 3;
AFuncNum := INT_EXCEL_SHEET_FUNC_DATE;
end;
fekROUND:
begin
Result := INT_EXCEL_TOKEN_FUNCVAR_V;
AParamsNum := 2;
AFuncNum := INT_EXCEL_SHEET_FUNC_ROUND;
end;
fekTIME:
begin
Result := INT_EXCEL_TOKEN_FUNCVAR_V;
AParamsNum := 3;
AFuncNum := INT_EXCEL_SHEET_FUNC_TIME;
end;
end;
end;
procedure TsSpreadBIFF2Writer.WriteCellFormatting(AStream: TStream; ACell: PCell); procedure TsSpreadBIFF2Writer.WriteCellFormatting(AStream: TStream; ACell: PCell);
var var
BorderByte: Byte = 0; BorderByte: Byte = 0;
@ -239,7 +197,10 @@ var
i: Integer; i: Integer;
RPNLength: Word; RPNLength: Word;
TokenArraySizePos, RecordSizePos, FinalPos: Cardinal; TokenArraySizePos, RecordSizePos, FinalPos: Cardinal;
FormulaKind, ParamsNum, ExtraInfo: Byte; FormulaKind, ExtraInfo: Word;
r: Cardinal;
len: Integer;
s: ansistring;
begin begin
RPNLength := 0; RPNLength := 0;
FormulaResult := 0.0; FormulaResult := 0.0;
@ -258,7 +219,7 @@ begin
AStream.WriteByte($0); AStream.WriteByte($0);
AStream.WriteByte($0); AStream.WriteByte($0);
{ Result of the formula in IEE 754 floating-point value } { Result of the formula in IEEE 754 floating-point value }
AStream.WriteBuffer(FormulaResult, 8); AStream.WriteBuffer(FormulaResult, 8);
{ 0 = Do not recalculate { 0 = Do not recalculate
@ -276,8 +237,9 @@ begin
{ 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
begin begin
{ Token identifier } { Token identifier }
FormulaKind := FEKindToExcelID(AFormula[i].ElementKind, ParamsNum, ExtraInfo); FormulaKind := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind, ExtraInfo);
AStream.WriteByte(FormulaKind); AStream.WriteByte(FormulaKind);
Inc(RPNLength); Inc(RPNLength);
@ -295,17 +257,60 @@ begin
Inc(RPNLength, 8); Inc(RPNLength, 8);
end; end;
INT_EXCEL_TOKEN_TSTR:
begin
s := ansistring(AFormula[i].StringValue);
len := Length(s);
AStream.WriteByte(len);
AStream.WriteBuffer(s[1], len);
Inc(RPNLength, len + 1);
end;
INT_EXCEL_TOKEN_TBOOL:
begin
AStream.WriteByte(ord(AFormula[i].DoubleValue <> 0.0));
inc(RPNLength, 1);
end;
INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA:
begin begin
AStream.WriteWord(AFormula[i].Row and MASK_EXCEL_ROW); r := AFormula[i].Row and MASK_EXCEL_ROW;
if (rfRelRow in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW;
if (rfRelCol in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL;
AStream.WriteWord(r);
AStream.WriteByte(AFormula[i].Col); AStream.WriteByte(AFormula[i].Col);
Inc(RPNLength, 3); Inc(RPNLength, 3);
end; end;
INT_EXCEL_TOKEN_TAREA_R: { fekCellRange }
begin
r := AFormula[i].Row and MASK_EXCEL_ROW;
if (rfRelRow in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW;
if (rfRelCol in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL;
AStream.WriteWord(WordToLE(r));
r := AFormula[i].Row2 and MASK_EXCEL_ROW;
if (rfRelRow2 in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW;
if (rfRelCol2 in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL;
AStream.WriteWord(WordToLE(r));
AStream.WriteByte(AFormula[i].Col);
AStream.WriteByte(AFormula[i].Col2);
Inc(RPNLength, 6);
end;
INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A:
begin
AStream.WriteByte(Lo(ExtraInfo));
Inc(RPNLength, 1);
end;
INT_EXCEL_TOKEN_FUNCVAR_V: INT_EXCEL_TOKEN_FUNCVAR_V:
begin begin
AStream.WriteByte(ParamsNum); AStream.WriteByte(AFormula[i].ParamsNum);
AStream.WriteByte(ExtraInfo); AStream.WriteByte(Lo(ExtraInfo));
// taking only the low-bytes, the high-bytes are needed for compatibility
// with other BIFF formats...
Inc(RPNLength, 2); Inc(RPNLength, 2);
end; end;

View File

@ -106,7 +106,6 @@ type
TsSpreadBIFF5Writer = class(TsSpreadBIFFWriter) TsSpreadBIFF5Writer = class(TsSpreadBIFFWriter)
private private
WorkBookEncoding: TsEncoding; WorkBookEncoding: TsEncoding;
function FEKindToExcelID(AElement: TFEKind; var AParamsNum: Byte; var AExtra: Word): Byte;
public public
// constructor Create; // constructor Create;
// destructor Destroy; override; // destructor Destroy; override;
@ -156,8 +155,8 @@ const
{ Cell Addresses constants } { Cell Addresses constants }
MASK_EXCEL_ROW = $3FFF; MASK_EXCEL_ROW = $3FFF;
MASK_EXCEL_RELATIVE_ROW = $4000; MASK_EXCEL_RELATIVE_COL = $4000; // This is according to Microsoft documentation,
MASK_EXCEL_RELATIVE_COL = $8000; MASK_EXCEL_RELATIVE_ROW = $8000; // but opposite to OpenOffice documentation!
{ BOF record constants } { BOF record constants }
INT_BOF_BIFF5_VER = $0500; INT_BOF_BIFF5_VER = $0500;
@ -260,48 +259,6 @@ const
{ TsSpreadBIFF5Writer } { TsSpreadBIFF5Writer }
function TsSpreadBIFF5Writer.FEKindToExcelID(AElement: TFEKind;
var AParamsNum: Byte; var AExtra: Word): Byte;
begin
AExtra := 0;
case AElement of
{ Operands }
fekCell: Result := INT_EXCEL_TOKEN_TREFV;
fekNum: Result := INT_EXCEL_TOKEN_TNUM;
{ Operators }
fekAdd: Result := INT_EXCEL_TOKEN_TADD;
fekSub: Result := INT_EXCEL_TOKEN_TSUB;
fekDiv: Result := INT_EXCEL_TOKEN_TDIV;
fekMul: Result := INT_EXCEL_TOKEN_TMUL;
{ Built-in/Worksheet Function }
fekABS:
begin
Result := INT_EXCEL_TOKEN_FUNCVAR_V;
AParamsNum := 1;
AExtra := INT_EXCEL_SHEET_FUNC_ABS;
end;
fekDATE:
begin
Result := INT_EXCEL_TOKEN_FUNCVAR_V;
AParamsNum := 3;
AExtra := INT_EXCEL_SHEET_FUNC_DATE;
end;
fekROUND:
begin
Result := INT_EXCEL_TOKEN_FUNCVAR_V;
AParamsNum := 2;
AExtra := INT_EXCEL_SHEET_FUNC_ROUND;
end;
fekTIME:
begin
Result := INT_EXCEL_TOKEN_FUNCVAR_V;
AParamsNum := 3;
AExtra := INT_EXCEL_SHEET_FUNC_TIME;
end;
end;
end;
{******************************************************************* {*******************************************************************
* TsSpreadBIFF5Writer.WriteToFile () * TsSpreadBIFF5Writer.WriteToFile ()
* *
@ -647,8 +604,11 @@ var
i: Integer; i: Integer;
RPNLength: Word; RPNLength: Word;
TokenArraySizePos, RecordSizePos, FinalPos: Int64; TokenArraySizePos, RecordSizePos, FinalPos: Int64;
FormulaKind, ParamsNum: Byte; FormulaKind: Word;
ExtraInfo: Word; ExtraInfo: Word;
r: Cardinal;
len: Integer;
s: ansistring;
begin begin
RPNLength := 0; RPNLength := 0;
FormulaResult := 0.0; FormulaResult := 0.0;
@ -665,7 +625,7 @@ begin
{ Index to XF Record } { Index to XF Record }
AStream.WriteWord($0000); AStream.WriteWord($0000);
{ Result of the formula in IEE 754 floating-point value } { Result of the formula in IEEE 754 floating-point value }
AStream.WriteBuffer(FormulaResult, 8); AStream.WriteBuffer(FormulaResult, 8);
{ Options flags } { Options flags }
@ -686,7 +646,7 @@ begin
for i := 0 to Length(AFormula) - 1 do for i := 0 to Length(AFormula) - 1 do
begin begin
{ Token identifier } { Token identifier }
FormulaKind := FEKindToExcelID(AFormula[i].ElementKind, ParamsNum, ExtraInfo); FormulaKind := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind, ExtraInfo);
AStream.WriteByte(FormulaKind); AStream.WriteByte(FormulaKind);
Inc(RPNLength); Inc(RPNLength);
@ -704,13 +664,48 @@ begin
Inc(RPNLength, 8); Inc(RPNLength, 8);
end; end;
INT_EXCEL_TOKEN_TSTR:
begin
s := ansistring(AFormula[i].StringValue);
len := Length(s);
AStream.WriteByte(len);
AStream.WriteBuffer(s[1], len);
Inc(RPNLength, len + 1);
end;
INT_EXCEL_TOKEN_TBOOL: { fekBool }
begin
AStream.WriteByte(ord(AFormula[i].DoubleValue <> 0.0));
inc(RPNLength, 1);
end;
INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA:
begin begin
AStream.WriteWord(AFormula[i].Row and MASK_EXCEL_ROW); r := AFormula[i].Row and MASK_EXCEL_ROW;
if (rfRelRow in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW;
if (rfRelCol in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL;
AStream.WriteWord(r);
AStream.WriteByte(AFormula[i].Col); AStream.WriteByte(AFormula[i].Col);
Inc(RPNLength, 3); Inc(RPNLength, 3);
end; end;
INT_EXCEL_TOKEN_TAREA_R: { fekCellRange }
begin
r := AFormula[i].Row and MASK_EXCEL_ROW;
if (rfRelRow in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW;
if (rfRelCol in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL;
AStream.WriteWord(WordToLE(r));
r := AFormula[i].Row2 and MASK_EXCEL_ROW;
if (rfRelRow2 in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW;
if (rfRelCol2 in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL;
AStream.WriteWord(WordToLE(r));
AStream.WriteByte(AFormula[i].Col);
AStream.WriteByte(AFormula[i].Col2);
Inc(RPNLength, 6);
end;
{ {
sOffset Size Contents sOffset Size Contents
0 1 22H (tFuncVarR), 42H (tFuncVarV), 62H (tFuncVarA) 0 1 22H (tFuncVarR), 42H (tFuncVarV), 62H (tFuncVarA)
@ -724,9 +719,16 @@ begin
14-0 7FFFH Index to a built-in sheet function (➜3.11) or a macro command 14-0 7FFFH Index to a built-in sheet function (➜3.11) or a macro command
15 8000H 0 = Built-in function; 1 = Macro command 15 8000H 0 = Built-in function; 1 = Macro command
} }
// Functions
INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A:
begin
AStream.WriteWord(WordToLE(ExtraInfo));
Inc(RPNLength, 2);
end;
INT_EXCEL_TOKEN_FUNCVAR_V: INT_EXCEL_TOKEN_FUNCVAR_V:
begin begin
AStream.WriteByte(ParamsNum); AStream.WriteByte(AFormula[i].ParamsNum);
AStream.WriteWord(WordToLE(ExtraInfo)); AStream.WriteWord(WordToLE(ExtraInfo));
Inc(RPNLength, 3); Inc(RPNLength, 3);
end; end;

View File

@ -176,6 +176,7 @@ const
{ Excel record IDs } { Excel record IDs }
INT_EXCEL_ID_BOF = $0809; INT_EXCEL_ID_BOF = $0809;
INT_EXCEL_ID_BOUNDSHEET = $0085; // Renamed to SHEET in the latest OpenOffice docs INT_EXCEL_ID_BOUNDSHEET = $0085; // Renamed to SHEET in the latest OpenOffice docs
INT_EXCEL_ID_COUNTRY = $008C;
INT_EXCEL_ID_EOF = $000A; INT_EXCEL_ID_EOF = $000A;
INT_EXCEL_ID_DIMENSIONS = $0200; INT_EXCEL_ID_DIMENSIONS = $0200;
INT_EXCEL_ID_FONT = $0031; INT_EXCEL_ID_FONT = $0031;
@ -196,12 +197,13 @@ const
INT_EXCEL_ID_PALETTE = $0092; INT_EXCEL_ID_PALETTE = $0092;
INT_EXCEL_ID_CODEPAGE = $0042; INT_EXCEL_ID_CODEPAGE = $0042;
INT_EXCEL_ID_FORMAT = $041E; INT_EXCEL_ID_FORMAT = $041E;
INT_EXCEL_ID_FORCEFULLCALCULATION = $08A3;
{ Cell Addresses constants } { Cell Addresses constants }
MASK_EXCEL_ROW = $3FFF; MASK_EXCEL_ROW = $3FFF;
MASK_EXCEL_COL_BITS_BIFF8=$00FF; MASK_EXCEL_COL_BITS_BIFF8=$00FF;
MASK_EXCEL_RELATIVE_ROW = $4000; MASK_EXCEL_RELATIVE_COL = $4000; // This is according to Microsoft documentation,
MASK_EXCEL_RELATIVE_COL = $8000; MASK_EXCEL_RELATIVE_ROW = $8000; // but opposite to OpenOffice documentation!
{ BOF record constants } { BOF record constants }
INT_BOF_BIFF8_VER = $0600; INT_BOF_BIFF8_VER = $0600;
@ -872,6 +874,7 @@ begin
AStream.WriteBuffer(WideStringToLE(WideFontName)[1], Len * Sizeof(WideChar)); AStream.WriteBuffer(WideStringToLE(WideFontName)[1], Len * Sizeof(WideChar));
end; end;
{******************************************************************* {*******************************************************************
* TsSpreadBIFF8Writer.WriteFormula () * TsSpreadBIFF8Writer.WriteFormula ()
* *
@ -967,10 +970,13 @@ procedure TsSpreadBIFF8Writer.WriteRPNFormula(AStream: TStream; const ARow,
var var
FormulaResult: double; FormulaResult: double;
i: Integer; i: Integer;
len: Integer;
RPNLength: Word; RPNLength: Word;
TokenArraySizePos, RecordSizePos, FinalPos: Int64; TokenArraySizePos, RecordSizePos, FinalPos: Int64;
TokenID: Byte; TokenID: Word;
lSecondaryID: Word; lSecondaryID: Word;
c: Cardinal;
wideStr: WideString;
begin begin
RPNLength := 0; RPNLength := 0;
FormulaResult := 0.0; FormulaResult := 0.0;
@ -988,7 +994,7 @@ begin
//AStream.WriteWord(0); //AStream.WriteWord(0);
WriteXFIndex(AStream, ACell); WriteXFIndex(AStream, ACell);
{ Result of the formula in IEE 754 floating-point value } { Result of the formula in IEEE 754 floating-point value }
AStream.WriteBuffer(FormulaResult, 8); AStream.WriteBuffer(FormulaResult, 8);
{ Options flags } { Options flags }
@ -1019,7 +1025,10 @@ begin
INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: { fekCell } INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: { fekCell }
begin begin
AStream.WriteWord(AFormula[i].Row); AStream.WriteWord(AFormula[i].Row);
AStream.WriteWord(AFormula[i].Col and MASK_EXCEL_COL_BITS_BIFF8); c := AFormula[i].Col and MASK_EXCEL_COL_BITS_BIFF8;
if (rfRelRow in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_ROW;
if (rfRelCol in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_COL;
AStream.WriteWord(c);
Inc(RPNLength, 4); Inc(RPNLength, 4);
end; end;
@ -1035,8 +1044,14 @@ begin
} }
AStream.WriteWord(WordToLE(AFormula[i].Row)); AStream.WriteWord(WordToLE(AFormula[i].Row));
AStream.WriteWord(WordToLE(AFormula[i].Row2)); AStream.WriteWord(WordToLE(AFormula[i].Row2));
AStream.WriteWord(WordToLE(AFormula[i].Col)); c := AFormula[i].Col;
AStream.WriteWord(WordToLE(AFormula[i].Col2)); if (rfRelCol in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_COL;
if (rfRelRow in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_ROW;
AStream.WriteWord(WordToLE(c));
c := AFormula[i].Col2;
if (rfRelCol2 in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_COL;
if (rfRelRow2 in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_ROW;
AStream.WriteWord(WordToLE(c));
Inc(RPNLength, 8); Inc(RPNLength, 8);
end; end;
@ -1046,6 +1061,23 @@ begin
Inc(RPNLength, 8); Inc(RPNLength, 8);
end; end;
INT_EXCEL_TOKEN_TSTR: { fekString }
begin
// string constant is stored as widestring in BIFF8
wideStr := AFormula[i].StringValue;
len := Length(wideStr);
AStream.WriteByte(len); // char count in 1 byte
AStream.WriteByte(1); // Widestring flags, 1=regular unicode LE string
AStream.WriteBuffer(WideStringToLE(wideStr)[1], len * Sizeof(WideChar));
Inc(RPNLength, 1 + 1 + len*SizeOf(WideChar));
end;
INT_EXCEL_TOKEN_TBOOL: { fekBool }
begin
AStream.WriteByte(ord(AFormula[i].DoubleValue <> 0.0));
inc(RPNLength, 1);
end;
{ binary operation tokens } { binary operation tokens }
INT_EXCEL_TOKEN_TADD, INT_EXCEL_TOKEN_TSUB, INT_EXCEL_TOKEN_TMUL, INT_EXCEL_TOKEN_TADD, INT_EXCEL_TOKEN_TSUB, INT_EXCEL_TOKEN_TMUL,
INT_EXCEL_TOKEN_TDIV, INT_EXCEL_TOKEN_TPOWER: begin end; INT_EXCEL_TOKEN_TDIV, INT_EXCEL_TOKEN_TPOWER: begin end;
@ -1060,13 +1092,21 @@ begin
Inc(RPNLength, 3); Inc(RPNLength, 3);
end; end;
// Functions // Functions with fixed parameter count
INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A: INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A:
begin begin
AStream.WriteWord(lSecondaryID); AStream.WriteWord(WordToLE(lSecondaryID));
Inc(RPNLength, 2); Inc(RPNLength, 2);
end; end;
// Functions with variable parameter count
INT_EXCEL_TOKEN_FUNCVAR_V:
begin
AStream.WriteByte(AFormula[i].ParamsNum);
AStream.WriteWord(WordToLE(lSecondaryID));
Inc(RPNLength, 3);
end;
else else
end; end;
end; end;

View File

@ -26,19 +26,23 @@ const
INT_EXCEL_TOKEN_TSUB = $04; INT_EXCEL_TOKEN_TSUB = $04;
INT_EXCEL_TOKEN_TMUL = $05; INT_EXCEL_TOKEN_TMUL = $05;
INT_EXCEL_TOKEN_TDIV = $06; INT_EXCEL_TOKEN_TDIV = $06;
INT_EXCEL_TOKEN_TPOWER = $07; // Power Exponentiation INT_EXCEL_TOKEN_TPOWER = $07; // Power Exponentiation ^
INT_EXCEL_TOKEN_TCONCAT = $08; // Concatenation INT_EXCEL_TOKEN_TCONCAT = $08; // Concatenation &
INT_EXCEL_TOKEN_TLT = $09; // Less than INT_EXCEL_TOKEN_TLT = $09; // Less than <
INT_EXCEL_TOKEN_TLE = $0A; // Less than or equal INT_EXCEL_TOKEN_TLE = $0A; // Less than or equal <=
INT_EXCEL_TOKEN_TEQ = $0B; // Equal INT_EXCEL_TOKEN_TEQ = $0B; // Equal =
INT_EXCEL_TOKEN_TGE = $0C; // Greater than or equal INT_EXCEL_TOKEN_TGE = $0C; // Greater than or equal >=
INT_EXCEL_TOKEN_TGT = $0D; // Greater than INT_EXCEL_TOKEN_TGT = $0D; // Greater than >
INT_EXCEL_TOKEN_TNE = $0E; // Not equal INT_EXCEL_TOKEN_TNE = $0E; // Not equal <>
INT_EXCEL_TOKEN_TISECT = $0F; // Cell range intersection INT_EXCEL_TOKEN_TISECT = $0F; // Cell range intersection
INT_EXCEL_TOKEN_TLIST = $10; // Cell range list INT_EXCEL_TOKEN_TLIST = $10; // Cell range list
INT_EXCEL_TOKEN_TRANGE = $11; // Cell range INT_EXCEL_TOKEN_TRANGE = $11; // Cell range
INT_EXCEL_TOKEN_TUPLUS = $12; // Unary plus +
INT_EXCEL_TOKEN_TUMINUS = $13; // Unary minus +
INT_EXCEL_TOKEN_TPERCENT= $14; // Percent (%, divides operand by 100)
{ Constant Operand Tokens, 3.8} { Constant Operand Tokens, 3.8}
INT_EXCEL_TOKEN_TMISSARG= $16; //missing operand
INT_EXCEL_TOKEN_TSTR = $17; //string INT_EXCEL_TOKEN_TSTR = $17; //string
INT_EXCEL_TOKEN_TBOOL = $1D; //boolean INT_EXCEL_TOKEN_TBOOL = $1D; //boolean
INT_EXCEL_TOKEN_TINT = $1E; //integer INT_EXCEL_TOKEN_TINT = $1E; //integer
@ -49,6 +53,9 @@ const
INT_EXCEL_TOKEN_TREFR = $24; INT_EXCEL_TOKEN_TREFR = $24;
INT_EXCEL_TOKEN_TREFV = $44; INT_EXCEL_TOKEN_TREFV = $44;
INT_EXCEL_TOKEN_TREFA = $64; INT_EXCEL_TOKEN_TREFA = $64;
INT_EXCEL_TOKEN_TAREA_R = $25;
INT_EXCEL_TOKEN_TAREA_V = $45;
INT_EXCEL_TOKEN_TAREA_A = $65;
{ Function Tokens } { Function Tokens }
// _R: reference; _V: value; _A: array // _R: reference; _V: value; _A: array
@ -61,13 +68,109 @@ const
INT_EXCEL_TOKEN_FUNCVAR_R = $22; INT_EXCEL_TOKEN_FUNCVAR_R = $22;
INT_EXCEL_TOKEN_FUNCVAR_V = $42; INT_EXCEL_TOKEN_FUNCVAR_V = $42;
INT_EXCEL_TOKEN_FUNCVAR_A = $62; INT_EXCEL_TOKEN_FUNCVAR_A = $62;
INT_EXCEL_TOKEN_TAREA_R = $25;
{ Built-in/worksheet functions } { Built-in/worksheet functions }
INT_EXCEL_SHEET_FUNC_COUNT = 0;
INT_EXCEL_SHEET_FUNC_IF = 1;
INT_EXCEL_SHEET_FUNC_ISNA = 2;
INT_EXCEL_SHEET_FUNC_ISERROR = 3;
INT_EXCEL_SHEET_FUNC_SUM = 4;
INT_EXCEL_SHEET_FUNC_AVERAGE = 5;
INT_EXCEL_SHEET_FUNC_MIN = 6;
INT_EXCEL_SHEET_FUNC_MAX = 7;
INT_EXCEL_SHEET_FUNC_ROW = 8;
INT_EXCEL_SHEET_FUNC_COLUMN = 9;
INT_EXCEL_SHEET_FUNC_STDEV = 12;
INT_EXCEL_SHEET_FUNC_SIN = 15;
INT_EXCEL_SHEET_FUNC_COS = 16;
INT_EXCEL_SHEET_FUNC_TAN = 17;
INT_EXCEL_SHEET_FUNC_ATAN = 18;
INT_EXCEL_SHEET_FUNC_PI = 19;
INT_EXCEL_SHEET_FUNC_SQRT = 20;
INT_EXCEL_SHEET_FUNC_EXP = 21;
INT_EXCEL_SHEET_FUNC_LN = 22;
INT_EXCEL_SHEET_FUNC_LOG10 = 23;
INT_EXCEL_SHEET_FUNC_ABS = 24; // $18 INT_EXCEL_SHEET_FUNC_ABS = 24; // $18
INT_EXCEL_SHEET_FUNC_INT = 25;
INT_EXCEL_SHEET_FUNC_SIGN = 26;
INT_EXCEL_SHEET_FUNC_ROUND = 27; // $1B INT_EXCEL_SHEET_FUNC_ROUND = 27; // $1B
INT_EXCEL_SHEET_FUNC_MID = 31;
INT_EXCEL_SHEET_FUNC_VALUE = 33;
INT_EXCEL_SHEET_FUNC_TRUE = 34;
INT_EXCEL_SHEET_FUNC_FALSE = 35;
INT_EXCEL_SHEET_FUNC_AND = 36;
INT_EXCEL_SHEET_FUNC_OR = 37;
INT_EXCEL_SHEET_FUNC_NOT = 38;
INT_EXCEL_SHEET_FUNC_VAR = 46;
INT_EXCEL_SHEET_FUNC_PV = 56;
INT_EXCEL_SHEET_FUNC_FV = 57;
INT_EXCEL_SHEET_FUNC_NPER = 58;
INT_EXCEL_SHEET_FUNC_PMT = 59;
INT_EXCEL_SHEET_FUNC_RATE = 60;
INT_EXCEL_SHEET_FUNC_RAND = 63;
INT_EXCEL_SHEET_FUNC_DATE = 65; // $41 INT_EXCEL_SHEET_FUNC_DATE = 65; // $41
INT_EXCEL_SHEET_FUNC_TIME = 66; // $42 INT_EXCEL_SHEET_FUNC_TIME = 66; // $42
INT_EXCEL_SHEET_FUNC_DAY = 67;
INT_EXCEL_SHEET_FUNC_MONTH = 68;
INT_EXCEL_SHEET_FUNC_YEAR = 69;
INT_EXCEL_SHEET_FUNC_WEEKDAY = 70;
INT_EXCEL_SHEET_FUNC_HOUR = 71;
INT_EXCEL_SHEET_FUNC_MINUTE = 72;
INT_EXCEL_SHEET_FUNC_SECOND = 73;
INT_EXCEL_SHEET_FUNC_NOW = 74;
INT_EXCEL_SHEET_FUNC_ROWS = 76;
INT_EXCEL_SHEET_FUNC_COLUMNS = 77;
INT_EXCEL_SHEET_FUNC_ASIN = 98;
INT_EXCEL_SHEET_FUNC_ACOS = 99;
INT_EXCEL_SHEET_FUNC_ISREF = 105;
INT_EXCEL_SHEET_FUNC_LOG = 109;
INT_EXCEL_SHEET_FUNC_CHAR = 111;
INT_EXCEL_SHEET_FUNC_LOWER = 112;
INT_EXCEL_SHEET_FUNC_UPPER = 113;
INT_EXCEL_SHEET_FUNC_PROPER = 114;
INT_EXCEL_SHEET_FUNC_LEFT = 115;
INT_EXCEL_SHEET_FUNC_RIGHT = 116;
INT_EXCEL_SHEET_FUNC_TRIM = 118;
INT_EXCEL_SHEET_FUNC_REPLACE = 119;
INT_EXCEL_SHEET_FUNC_SUBSTITUTE = 120;
INT_EXCEL_SHEET_FUNC_CODE = 121;
INT_EXCEL_SHEET_FUNC_CELL = 125;
INT_EXCEL_SHEET_FUNC_ISERR = 126;
INT_EXCEL_SHEET_FUNC_ISTEXT = 127;
INT_EXCEL_SHEET_FUNC_ISNUMBER = 128;
INT_EXCEL_SHEET_FUNC_ISBLANK = 129;
INT_EXCEL_SHEET_FUNC_DATEVALUE = 140;
INT_EXCEL_SHEET_FUNC_TIMEVALUE = 141;
INT_EXCEL_SHEET_FUNC_COUNTA = 169;
INT_EXCEL_SHEET_FUNC_PRODUCT = 183;
INT_EXCEL_SHEET_FUNC_ISNONTEXT = 190;
INT_EXCEL_SHEET_FUNC_STDEVP = 193;
INT_EXCEL_SHEET_FUNC_VARP = 194;
INT_EXCEL_SHEET_FUNC_ISLOGICAL = 198;
INT_EXCEL_SHEET_FUNC_TODAY = 221; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_MEDIAN = 227; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_SINH = 229; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_COSH = 230; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_TANH = 231; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_ASINH = 232; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_ACOSH = 233; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_ATANH = 234; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_INFO = 244; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_AVEDEV = 269; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_BETADIST = 270; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_BETAINV = 272; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_BINOMDIST = 273; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_CHIDIST = 274; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_CHIINV = 275; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_PERMUT = 299; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_POISSON = 300; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_SUMSQ = 321; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_RADIANS = 342; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_DEGREES = 343; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_SUMIF = 345; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_COUNTIF = 346; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_COUNTBLANK = 347; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_DATEDIF = 351; // not available in BIFF2
{ Control Tokens, Special Tokens } { Control Tokens, Special Tokens }
// 01H tExp Matrix formula or shared formula // 01H tExp Matrix formula or shared formula
@ -200,7 +303,7 @@ type
function GetLastRowIndex(AWorksheet: TsWorksheet): Integer; function GetLastRowIndex(AWorksheet: TsWorksheet): Integer;
procedure GetLastColCallback(ACell: PCell; AStream: TStream); procedure GetLastColCallback(ACell: PCell; AStream: TStream);
function GetLastColIndex(AWorksheet: TsWorksheet): Word; function GetLastColIndex(AWorksheet: TsWorksheet): Word;
function FormulaElementKindToExcelTokenID(AElementKind: TFEKind; out ASecondaryID: Word): Byte; function FormulaElementKindToExcelTokenID(AElementKind: TFEKind; out ASecondaryID: Word): Word;
// Other records which didn't change // Other records which didn't change
// Workbook Globals records // Workbook Globals records
// Write out used codepage for character encoding // Write out used codepage for character encoding
@ -408,45 +511,174 @@ begin
end; end;
function TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID( function TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID(
AElementKind: TFEKind; out ASecondaryID: Word): Byte; AElementKind: TFEKind; out ASecondaryID: Word): Word;
begin const
ASecondaryID := 0; { Explanation of first index:
0 --> primary token (basic operands and operations)
1 --> secondary token of a function with a fixed parameter count
2 --> secondary token of a function with a variable parameter count }
TokenIDs: array[fekCell..fekOpSum, 0..1] of Word = (
// Basic operands
(0, INT_EXCEL_TOKEN_TREFV), {fekCell}
(0, INT_EXCEL_TOKEN_TREFR), {fekCellRef}
(0, INT_EXCEL_TOKEN_TAREA_R), {fekCellRange}
(0, INT_EXCEL_TOKEN_TNUM), {fekNum}
(0, INT_EXCEL_TOKEN_TSTR), {fekString}
(0, INT_EXCEL_TOKEN_TBOOL), {fekBool}
(0, INT_EXCEL_TOKEN_TMISSARG), {fekMissArg, missing argument}
case AElementKind of // Basic operations
{ Operand Tokens } (0, INT_EXCEL_TOKEN_TADD), {fekAdd, +}
fekCell: Result := INT_EXCEL_TOKEN_TREFR; (0, INT_EXCEL_TOKEN_TSUB), {fekSub, -}
fekCellRange: Result := INT_EXCEL_TOKEN_TAREA_R; (0, INT_EXCEL_TOKEN_TDIV), {fekDiv, /}
fekNum: Result := INT_EXCEL_TOKEN_TNUM; (0, INT_EXCEL_TOKEN_TMUL), {fekMul, *}
{ Basic operations } (0, INT_EXCEL_TOKEN_TPERCENT), {fekPercent, %}
fekAdd: Result := INT_EXCEL_TOKEN_TADD; (0, INT_EXCEL_TOKEN_TPOWER), {fekPower, ^}
fekSub: Result := INT_EXCEL_TOKEN_TSUB; (0, INT_EXCEL_TOKEN_TUMINUS), {fekUMinus, -}
fekDiv: Result := INT_EXCEL_TOKEN_TDIV; (0, INT_EXCEL_TOKEN_TUPLUS), {fekUPlus, +}
fekMul: Result := INT_EXCEL_TOKEN_TMUL; (0, INT_EXCEL_TOKEN_TCONCAT), {fekConcat, &, for strings}
{ Built-in Functions} (0, INT_EXCEL_TOKEN_TEQ), {fekEqual, =}
fekABS: (0, INT_EXCEL_TOKEN_TGT), {fekGreater, >}
begin (0, INT_EXCEL_TOKEN_TGE), {fekGreaterEqual, >=}
Result := INT_EXCEL_TOKEN_FUNC_V; (0, INT_EXCEL_TOKEN_TLT), {fekLess <}
ASecondaryID := INT_EXCEL_SHEET_FUNC_ABS; (0, INT_EXCEL_TOKEN_TLE), {fekLessEqual, <=}
(0, INT_EXCEL_TOKEN_TNE), {fekNotEqual, <>}
// Math functions
(1, INT_EXCEL_SHEET_FUNC_ABS), {fekABS}
(1, INT_EXCEL_SHEET_FUNC_ACOS), {fekACOS}
(1, INT_EXCEL_SHEET_FUNC_ACOSH), {fekACOSH}
(1, INT_EXCEL_SHEET_FUNC_ASIN), {fekASIN}
(1, INT_EXCEL_SHEET_FUNC_ASINH), {fekASINH}
(1, INT_EXCEL_SHEET_FUNC_ATAN), {fekATAN}
(1, INT_EXCEL_SHEET_FUNC_ATANH), {fekATANH}
(1, INT_EXCEL_SHEET_FUNC_COS), {fekCOS}
(1, INT_EXCEL_SHEET_FUNC_COSH), {fekCOSH}
(1, INT_EXCEL_SHEET_FUNC_DEGREES), {fekDEGREES}
(1, INT_EXCEL_SHEET_FUNC_EXP), {fekEXP}
(1, INT_EXCEL_SHEET_FUNC_INT), {fekINT}
(1, INT_EXCEL_SHEET_FUNC_LN), {fekLN}
(1, INT_EXCEL_SHEET_FUNC_LOG), {fekLOG}
(1, INT_EXCEL_SHEET_FUNC_LOG10), {fekLOG10}
(1, INT_EXCEL_SHEET_FUNC_PI), {fekPI}
(1, INT_EXCEL_SHEET_FUNC_RADIANS), {fekRADIANS}
(1, INT_EXCEL_SHEET_FUNC_RAND), {fekRAND}
(1, INT_EXCEL_SHEET_FUNC_ROUND), {fekROUND}
(1, INT_EXCEL_SHEET_FUNC_SIGN), {fekSIGN}
(1, INT_EXCEL_SHEET_FUNC_SIN), {fekSIN}
(1, INT_EXCEL_SHEET_FUNC_SINH), {fekSINH}
(1, INT_EXCEL_SHEET_FUNC_SQRT), {fekSQRT}
(1, INT_EXCEL_SHEET_FUNC_TAN), {fekTAN}
(1, INT_EXCEL_SHEET_FUNC_TANH), {fekTANH}
// Date/time functions
(1, INT_EXCEL_SHEET_FUNC_DATE), {fekDATE}
(1, INT_EXCEL_SHEET_FUNC_DATEDIF), {fekDATEDIF}
(1, INT_EXCEL_SHEET_FUNC_DATEVALUE), {fekDATEVALUE}
(1, INT_EXCEL_SHEET_FUNC_DAY), {fekDAY}
(1, INT_EXCEL_SHEET_FUNC_HOUR), {fekHOUR}
(1, INT_EXCEL_SHEET_FUNC_MINUTE), {fekMINUTE}
(1, INT_EXCEL_SHEET_FUNC_MONTH), {fekMONTH}
(1, INT_EXCEL_SHEET_FUNC_NOW), {fekNOW}
(1, INT_EXCEL_SHEET_FUNC_SECOND), {fekSECOND}
(1, INT_EXCEL_SHEET_FUNC_TIME), {fekTIME}
(1, INT_EXCEL_SHEET_FUNC_TIMEVALUE), {fekTIMEVALUE}
(1, INT_EXCEL_SHEET_FUNC_TODAY), {fekTODAY}
(2, INT_EXCEL_SHEET_FUNC_WEEKDAY), {fekWEEKDAY}
(1, INT_EXCEL_SHEET_FUNC_YEAR), {fekYEAR}
// Statistical functions
(2, INT_EXCEL_SHEET_FUNC_AVEDEV), {fekAVEDEV}
(2, INT_EXCEL_SHEET_FUNC_AVERAGE), {fekAVERAGE}
(2, INT_EXCEL_SHEET_FUNC_BETADIST), {fekBETADIST}
(2, INT_EXCEL_SHEET_FUNC_BETAINV), {fekBETAINV}
(1, INT_EXCEL_SHEET_FUNC_BINOMDIST), {fekBINOMDIST}
(1, INT_EXCEL_SHEET_FUNC_CHIDIST), {fekCHIDIST}
(1, INT_EXCEL_SHEET_FUNC_CHIINV), {fekCHIINV}
(2, INT_EXCEL_SHEET_FUNC_COUNT), {fekCOUNT}
(2, INT_EXCEL_SHEET_FUNC_COUNTA), {fekCOUNTA}
(1, INT_EXCEL_SHEET_FUNC_COUNTBLANK),{fekCOUNTBLANK}
(2, INT_EXCEL_SHEET_FUNC_COUNTIF), {fekCOUNTIF}
(2, INT_EXCEL_SHEET_FUNC_MAX), {fekMAX}
(2, INT_EXCEL_SHEET_FUNC_MEDIAN), {fekMEDIAN}
(2, INT_EXCEL_SHEET_FUNC_MIN), {fekMIN}
(1, INT_EXCEL_SHEET_FUNC_PERMUT), {fekPERMUT}
(1, INT_EXCEL_SHEET_FUNC_POISSON), {fekPOISSON}
(2, INT_EXCEL_SHEET_FUNC_PRODUCT), {fekPRODUCT}
(2, INT_EXCEL_SHEET_FUNC_STDEV), {fekSTDEV}
(2, INT_EXCEL_SHEET_FUNC_STDEVP), {fekSTDEVP}
(2, INT_EXCEL_SHEET_FUNC_SUM), {fekSUM}
(2, INT_EXCEL_SHEET_FUNC_SUMIF), {fekSUMIF}
(2, INT_EXCEL_SHEET_FUNC_SUMSQ), {fekSUMSQ}
(2, INT_EXCEL_SHEET_FUNC_VAR), {fekVAR}
(2, INT_EXCEL_SHEET_FUNC_VARP), {fekVARP}
// Financial functions
(2, INT_EXCEL_SHEET_FUNC_FV), {fekFV}
(2, INT_EXCEL_SHEET_FUNC_NPER), {fekNPER}
(2, INT_EXCEL_SHEET_FUNC_PV), {fekPV}
(2, INT_EXCEL_SHEET_FUNC_PMT), {fekPMT}
(2, INT_EXCEL_SHEET_FUNC_RATE), {fekRATE}
// Logical functions
(2, INT_EXCEL_SHEET_FUNC_AND), {fekAND}
(1, INT_EXCEL_SHEET_FUNC_FALSE), {fekFALSE}
(2, INT_EXCEL_SHEET_FUNC_IF), {fekIF}
(1, INT_EXCEL_SHEET_FUNC_NOT), {fekNOT}
(2, INT_EXCEL_SHEET_FUNC_OR), {fekOR}
(1, INT_EXCEL_SHEET_FUNC_TRUE), {fekTRUE}
// String functions
(1, INT_EXCEL_SHEET_FUNC_CHAR), {fekCHAR}
(1, INT_EXCEL_SHEET_FUNC_CODE), {fekCODE}
(2, INT_EXCEL_SHEET_FUNC_LEFT), {fekLEFT}
(1, INT_EXCEL_SHEET_FUNC_LOWER), {fekLOWER}
(1, INT_EXCEL_SHEET_FUNC_MID), {fekMID}
(1, INT_EXCEL_SHEET_FUNC_PROPER), {fekPROPER}
(1, INT_EXCEL_SHEET_FUNC_REPLACE), {fekREPLACE}
(2, INT_EXCEL_SHEET_FUNC_RIGHT), {fekRIGHT}
(2, INT_EXCEL_SHEET_FUNC_SUBSTITUTE),{fekSUBSTITUTE}
(1, INT_EXCEL_SHEET_FUNC_TRIM), {fekTRIM}
(1, INT_EXCEL_SHEET_FUNC_UPPER), {fekUPPER}
// lookup/reference functions
(2, INT_EXCEL_SHEET_FUNC_COLUMN), {fekCOLUMN}
(1, INT_EXCEL_SHEET_FUNC_COLUMNS), {fekCOLUMNS}
(2, INT_EXCEL_SHEET_FUNC_ROW), {fekROW}
(1, INT_EXCEL_SHEET_FUNC_ROWS), {fekROWS}
// Info functions
(2, INT_EXCEL_SHEET_FUNC_CELL), {fekCELLINFO}
(1, INT_EXCEL_SHEET_FUNC_INFO), {fekINFO}
(1, INT_EXCEL_SHEET_FUNC_ISBLANK), {fekIsBLANK}
(1, INT_EXCEL_SHEET_FUNC_ISERR), {fekIsERR}
(1, INT_EXCEL_SHEET_FUNC_ISERROR), {fekIsERROR}
(1, INT_EXCEL_SHEET_FUNC_ISLOGICAL), {fekIsLOGICAL}
(1, INT_EXCEL_SHEET_FUNC_ISNA), {fekIsNA}
(1, INT_EXCEL_SHEET_FUNC_ISNONTEXT), {fekIsNONTEXT}
(1, INT_EXCEL_SHEET_FUNC_ISNUMBER), {fekIsNUMBER}
(1, INT_EXCEL_SHEET_FUNC_ISREF), {fekIsREF}
(1, INT_EXCEL_SHEET_FUNC_ISTEXT), {fekIsTEXT}
(1, INT_EXCEL_SHEET_FUNC_VALUE), {fekValue}
// Other operations
(0, INT_EXCEL_TOKEN_TATTR) {fekOpSum}
);
begin
case TokenIDs[AElementKind, 0] of
0: begin
Result := TokenIDs[AElementKind, 1];
ASecondaryID := 0;
end; end;
fekDATE: 1: begin
begin
Result := INT_EXCEL_TOKEN_FUNC_V; Result := INT_EXCEL_TOKEN_FUNC_V;
ASecondaryID := INT_EXCEL_SHEET_FUNC_DATE; ASecondaryID := TokenIDs[AElementKind, 1]
end; end;
fekROUND: 2: begin
begin Result := INT_EXCEL_TOKEN_FUNCVAR_V;
Result := INT_EXCEL_TOKEN_FUNC_V; ASecondaryID := TokenIDs[AElementKind, 1]
ASecondaryID := INT_EXCEL_SHEET_FUNC_ROUND;
end; end;
fekTIME:
begin
Result := INT_EXCEL_TOKEN_FUNC_V;
ASecondaryID := INT_EXCEL_SHEET_FUNC_TIME;
end;
{ Other operations }
fekOpSUM: Result := INT_EXCEL_TOKEN_TATTR;
else
Result := 0;
end; end;
end; end;