+ 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
uses
Classes, SysUtils, fpimage, AVL_Tree, avglvltree, lconvencoding;
Classes, SysUtils, fpimage, AVL_Tree, avglvltree, lconvencoding, fpsutils;
type
TsSpreadsheetFormat = (sfExcel2, sfExcel3, sfExcel4, sfExcel5, sfExcel8,
@ -56,15 +56,51 @@ type
DoubleValue: double;
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 = (
{ Basic operands }
fekCell, fekCellRange, fekNum,
fekCell, fekCellRef, fekCellRange, fekNum, fekString, fekBool, fekMissingArg,
{ 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}
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 }
fekOpSUM
);
@ -72,9 +108,13 @@ type
TsFormulaElement = record
ElementKind: TFEKind;
Row, Row2: Word; // zero-based
Col, Col2: Byte; // zero-based
Col, Col2: Word; // zero-based
Param1, Param2: Word; // Extra parameters
DoubleValue: double;
IntValue: Word;
StringValue: String;
RelFlags: TsRelFlags; // store info on relative/absolute addresses
ParamsNum: Byte;
end;
TsExpandedFormula = array of TsFormulaElement;
@ -378,6 +418,54 @@ type
Format: TsSpreadsheetFormat;
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
GsSpreadFormats: array of TsSpreadFormatData;
@ -1874,6 +1962,220 @@ begin
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
SetLength(GsSpreadFormats, 0);

View File

@ -14,6 +14,9 @@ uses
type
TsSelectionDirection = (fpsVerticalSelection, fpsHorizontalSelection);
TsRelFlag = (rfRelRow, rfRelCol, rfRelRow2, rfRelCol2);
TsRelFlags = set of TsRelFlag;
const
// Date formatting string for unambiguous date/time display as strings
// 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;
var AFirstCellRow, AFirstCellCol, ACount: Integer;
var ADirection: TsSelectionDirection): Boolean;
function ParseCellRangeString(const AStr: string;
var AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Integer;
var AFlags: TsRelFlags): Boolean;
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;
var AResult: Integer): Boolean;
function ParseCellColString(const AStr: string;
@ -148,11 +156,17 @@ function ParseIntervalString(const AStr: string;
var AFirstCellRow, AFirstCellCol, ACount: Integer;
var ADirection: TsSelectionDirection): Boolean;
var
Cells: TStringList;
//Cells: TStringList;
LastCellRow, LastCellCol: Integer;
p: Integer;
s1, s2: String;
begin
Result := True;
{ Simpler:
use "pos" instead of the TStringList overhead.
And: the StringList is not free'ed here
// First get the cells
Cells := TStringList.Create;
ExtractStrings([':'],[], PChar(AStr), Cells);
@ -162,6 +176,19 @@ begin
if not Result then Exit;
Result := ParseCellString(Cells[1], LastCellRow, LastCellCol);
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
begin
@ -176,6 +203,42 @@ begin
else Exit(False);
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
@ -184,13 +247,17 @@ end;
0 - Reading Column part 1 (necesserely needs a letter)
1 - Reading Column part 2, but could be the first number as well
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
i: Integer;
state: Integer;
Col, Row: string;
lChar: Char;
isAbs: Boolean;
const
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'];
@ -201,11 +268,20 @@ begin
state := 0;
Col := '';
Row := '';
AFlags := [rfRelCol, rfRelRow];
isAbs := false;
// 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
lChar := AStr[i + 1];
lChar := AStr[i];
if lChar = '$' then begin
if isAbs then
exit(false);
isAbs := true;
continue;
end;
case state of
@ -214,6 +290,9 @@ begin
if lChar in cLetters then
begin
Col := lChar;
if isAbs then
Exclude(AFlags, rfRelCol);
isAbs := false;
state := 1;
end
else Exit(False);
@ -221,10 +300,14 @@ begin
1:
begin
if lChar in cLetters then Col := Col + lChar
if lChar in cLetters then
Col := Col + lChar
else if lChar in cDigits then
begin
Row := lChar;
if isAbs then
Exclude(AFlags, rfRelRow);
isAbs := false;
state := 2;
end
else Exit(False);
@ -244,6 +327,16 @@ begin
ParseCellColString(Col, ACellCol);
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;
begin
try
@ -267,6 +360,12 @@ begin
AResult := (Ord(AStr[1]) - Ord('A') + 1) * INT_NUM_LETTERS
+ Ord(AStr[2]) - Ord('A');
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);
Result := True;

View File

@ -50,7 +50,7 @@ type
procedure TearDown; override;
published
// Current fpspreadsheet does not yet have support for new RPN formulas
{.$DEFINE FPSPREAD_HAS_NEWRPNSUPPORT}
{$DEFINE FPSPREAD_HAS_NEWRPNSUPPORT}
{$IFDEF FPSPREAD_HAS_NEWRPNSUPPORT}
// 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.

View File

@ -57,9 +57,8 @@ type
{ TsSpreadBIFF2Writer }
TsSpreadBIFF2Writer = class(TsCustomSpreadWriter)
TsSpreadBIFF2Writer = class(TsSpreadBIFFWriter)
private
function FEKindToExcelID(AElement: TFEKind; var AParamsNum, AFuncNum: Byte): Byte;
procedure WriteCellFormatting(AStream: TStream; ACell: PCell);
public
{ General writing methods }
@ -86,8 +85,8 @@ const
{ Cell Addresses constants }
MASK_EXCEL_ROW = $3FFF;
MASK_EXCEL_RELATIVE_ROW = $4000;
MASK_EXCEL_RELATIVE_COL = $8000;
MASK_EXCEL_RELATIVE_COL = $4000; // This is according to Microsoft documentation,
MASK_EXCEL_RELATIVE_ROW = $8000; // but opposite to OpenOffice documentation!
{ BOF record constants }
INT_EXCEL_SHEET = $0010;
@ -96,47 +95,6 @@ const
{ 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);
var
BorderByte: Byte = 0;
@ -239,7 +197,10 @@ var
i: Integer;
RPNLength: Word;
TokenArraySizePos, RecordSizePos, FinalPos: Cardinal;
FormulaKind, ParamsNum, ExtraInfo: Byte;
FormulaKind, ExtraInfo: Word;
r: Cardinal;
len: Integer;
s: ansistring;
begin
RPNLength := 0;
FormulaResult := 0.0;
@ -258,7 +219,7 @@ begin
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);
{ 0 = Do not recalculate
@ -276,8 +237,9 @@ begin
{ Formula data (RPN token array) }
for i := 0 to Length(AFormula) - 1 do
begin
{ Token identifier }
FormulaKind := FEKindToExcelID(AFormula[i].ElementKind, ParamsNum, ExtraInfo);
FormulaKind := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind, ExtraInfo);
AStream.WriteByte(FormulaKind);
Inc(RPNLength);
@ -295,17 +257,60 @@ begin
Inc(RPNLength, 8);
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:
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);
Inc(RPNLength, 3);
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:
begin
AStream.WriteByte(ParamsNum);
AStream.WriteByte(ExtraInfo);
AStream.WriteByte(AFormula[i].ParamsNum);
AStream.WriteByte(Lo(ExtraInfo));
// taking only the low-bytes, the high-bytes are needed for compatibility
// with other BIFF formats...
Inc(RPNLength, 2);
end;

View File

@ -106,7 +106,6 @@ type
TsSpreadBIFF5Writer = class(TsSpreadBIFFWriter)
private
WorkBookEncoding: TsEncoding;
function FEKindToExcelID(AElement: TFEKind; var AParamsNum: Byte; var AExtra: Word): Byte;
public
// constructor Create;
// destructor Destroy; override;
@ -156,8 +155,8 @@ const
{ Cell Addresses constants }
MASK_EXCEL_ROW = $3FFF;
MASK_EXCEL_RELATIVE_ROW = $4000;
MASK_EXCEL_RELATIVE_COL = $8000;
MASK_EXCEL_RELATIVE_COL = $4000; // This is according to Microsoft documentation,
MASK_EXCEL_RELATIVE_ROW = $8000; // but opposite to OpenOffice documentation!
{ BOF record constants }
INT_BOF_BIFF5_VER = $0500;
@ -260,48 +259,6 @@ const
{ 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 ()
*
@ -647,8 +604,11 @@ var
i: Integer;
RPNLength: Word;
TokenArraySizePos, RecordSizePos, FinalPos: Int64;
FormulaKind, ParamsNum: Byte;
FormulaKind: Word;
ExtraInfo: Word;
r: Cardinal;
len: Integer;
s: ansistring;
begin
RPNLength := 0;
FormulaResult := 0.0;
@ -665,7 +625,7 @@ begin
{ Index to XF Record }
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);
{ Options flags }
@ -686,7 +646,7 @@ begin
for i := 0 to Length(AFormula) - 1 do
begin
{ Token identifier }
FormulaKind := FEKindToExcelID(AFormula[i].ElementKind, ParamsNum, ExtraInfo);
FormulaKind := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind, ExtraInfo);
AStream.WriteByte(FormulaKind);
Inc(RPNLength);
@ -704,13 +664,48 @@ begin
Inc(RPNLength, 8);
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:
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);
Inc(RPNLength, 3);
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
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
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:
begin
AStream.WriteByte(ParamsNum);
AStream.WriteByte(AFormula[i].ParamsNum);
AStream.WriteWord(WordToLE(ExtraInfo));
Inc(RPNLength, 3);
end;

View File

@ -176,6 +176,7 @@ const
{ Excel record IDs }
INT_EXCEL_ID_BOF = $0809;
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_DIMENSIONS = $0200;
INT_EXCEL_ID_FONT = $0031;
@ -196,12 +197,13 @@ const
INT_EXCEL_ID_PALETTE = $0092;
INT_EXCEL_ID_CODEPAGE = $0042;
INT_EXCEL_ID_FORMAT = $041E;
INT_EXCEL_ID_FORCEFULLCALCULATION = $08A3;
{ Cell Addresses constants }
MASK_EXCEL_ROW = $3FFF;
MASK_EXCEL_COL_BITS_BIFF8=$00FF;
MASK_EXCEL_RELATIVE_ROW = $4000;
MASK_EXCEL_RELATIVE_COL = $8000;
MASK_EXCEL_RELATIVE_COL = $4000; // This is according to Microsoft documentation,
MASK_EXCEL_RELATIVE_ROW = $8000; // but opposite to OpenOffice documentation!
{ BOF record constants }
INT_BOF_BIFF8_VER = $0600;
@ -872,6 +874,7 @@ begin
AStream.WriteBuffer(WideStringToLE(WideFontName)[1], Len * Sizeof(WideChar));
end;
{*******************************************************************
* TsSpreadBIFF8Writer.WriteFormula ()
*
@ -967,10 +970,13 @@ procedure TsSpreadBIFF8Writer.WriteRPNFormula(AStream: TStream; const ARow,
var
FormulaResult: double;
i: Integer;
len: Integer;
RPNLength: Word;
TokenArraySizePos, RecordSizePos, FinalPos: Int64;
TokenID: Byte;
TokenID: Word;
lSecondaryID: Word;
c: Cardinal;
wideStr: WideString;
begin
RPNLength := 0;
FormulaResult := 0.0;
@ -988,7 +994,7 @@ begin
//AStream.WriteWord(0);
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);
{ Options flags }
@ -1019,7 +1025,10 @@ begin
INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: { fekCell }
begin
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);
end;
@ -1035,8 +1044,14 @@ begin
}
AStream.WriteWord(WordToLE(AFormula[i].Row));
AStream.WriteWord(WordToLE(AFormula[i].Row2));
AStream.WriteWord(WordToLE(AFormula[i].Col));
AStream.WriteWord(WordToLE(AFormula[i].Col2));
c := AFormula[i].Col;
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);
end;
@ -1046,6 +1061,23 @@ begin
Inc(RPNLength, 8);
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 }
INT_EXCEL_TOKEN_TADD, INT_EXCEL_TOKEN_TSUB, INT_EXCEL_TOKEN_TMUL,
INT_EXCEL_TOKEN_TDIV, INT_EXCEL_TOKEN_TPOWER: begin end;
@ -1060,13 +1092,21 @@ begin
Inc(RPNLength, 3);
end;
// Functions
// Functions with fixed parameter count
INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A:
begin
AStream.WriteWord(lSecondaryID);
AStream.WriteWord(WordToLE(lSecondaryID));
Inc(RPNLength, 2);
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
end;
end;

View File

@ -26,19 +26,23 @@ const
INT_EXCEL_TOKEN_TSUB = $04;
INT_EXCEL_TOKEN_TMUL = $05;
INT_EXCEL_TOKEN_TDIV = $06;
INT_EXCEL_TOKEN_TPOWER = $07; // Power Exponentiation
INT_EXCEL_TOKEN_TCONCAT = $08; // Concatenation
INT_EXCEL_TOKEN_TLT = $09; // Less than
INT_EXCEL_TOKEN_TLE = $0A; // Less than or equal
INT_EXCEL_TOKEN_TEQ = $0B; // Equal
INT_EXCEL_TOKEN_TGE = $0C; // Greater than or equal
INT_EXCEL_TOKEN_TGT = $0D; // Greater than
INT_EXCEL_TOKEN_TNE = $0E; // Not equal
INT_EXCEL_TOKEN_TPOWER = $07; // Power Exponentiation ^
INT_EXCEL_TOKEN_TCONCAT = $08; // Concatenation &
INT_EXCEL_TOKEN_TLT = $09; // Less than <
INT_EXCEL_TOKEN_TLE = $0A; // Less than or equal <=
INT_EXCEL_TOKEN_TEQ = $0B; // Equal =
INT_EXCEL_TOKEN_TGE = $0C; // Greater than or equal >=
INT_EXCEL_TOKEN_TGT = $0D; // Greater than >
INT_EXCEL_TOKEN_TNE = $0E; // Not equal <>
INT_EXCEL_TOKEN_TISECT = $0F; // Cell range intersection
INT_EXCEL_TOKEN_TLIST = $10; // Cell range list
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}
INT_EXCEL_TOKEN_TMISSARG= $16; //missing operand
INT_EXCEL_TOKEN_TSTR = $17; //string
INT_EXCEL_TOKEN_TBOOL = $1D; //boolean
INT_EXCEL_TOKEN_TINT = $1E; //integer
@ -49,6 +53,9 @@ const
INT_EXCEL_TOKEN_TREFR = $24;
INT_EXCEL_TOKEN_TREFV = $44;
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 }
// _R: reference; _V: value; _A: array
@ -61,13 +68,109 @@ const
INT_EXCEL_TOKEN_FUNCVAR_R = $22;
INT_EXCEL_TOKEN_FUNCVAR_V = $42;
INT_EXCEL_TOKEN_FUNCVAR_A = $62;
INT_EXCEL_TOKEN_TAREA_R = $25;
{ 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_INT = 25;
INT_EXCEL_SHEET_FUNC_SIGN = 26;
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_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 }
// 01H tExp Matrix formula or shared formula
@ -200,7 +303,7 @@ type
function GetLastRowIndex(AWorksheet: TsWorksheet): Integer;
procedure GetLastColCallback(ACell: PCell; AStream: TStream);
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
// Workbook Globals records
// Write out used codepage for character encoding
@ -408,45 +511,174 @@ begin
end;
function TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID(
AElementKind: TFEKind; out ASecondaryID: Word): Byte;
begin
ASecondaryID := 0;
AElementKind: TFEKind; out ASecondaryID: Word): Word;
const
{ 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
{ Operand Tokens }
fekCell: Result := INT_EXCEL_TOKEN_TREFR;
fekCellRange: Result := INT_EXCEL_TOKEN_TAREA_R;
fekNum: Result := INT_EXCEL_TOKEN_TNUM;
{ Basic operations }
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 Functions}
fekABS:
begin
Result := INT_EXCEL_TOKEN_FUNC_V;
ASecondaryID := INT_EXCEL_SHEET_FUNC_ABS;
// Basic operations
(0, INT_EXCEL_TOKEN_TADD), {fekAdd, +}
(0, INT_EXCEL_TOKEN_TSUB), {fekSub, -}
(0, INT_EXCEL_TOKEN_TDIV), {fekDiv, /}
(0, INT_EXCEL_TOKEN_TMUL), {fekMul, *}
(0, INT_EXCEL_TOKEN_TPERCENT), {fekPercent, %}
(0, INT_EXCEL_TOKEN_TPOWER), {fekPower, ^}
(0, INT_EXCEL_TOKEN_TUMINUS), {fekUMinus, -}
(0, INT_EXCEL_TOKEN_TUPLUS), {fekUPlus, +}
(0, INT_EXCEL_TOKEN_TCONCAT), {fekConcat, &, for strings}
(0, INT_EXCEL_TOKEN_TEQ), {fekEqual, =}
(0, INT_EXCEL_TOKEN_TGT), {fekGreater, >}
(0, INT_EXCEL_TOKEN_TGE), {fekGreaterEqual, >=}
(0, INT_EXCEL_TOKEN_TLT), {fekLess <}
(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;
fekDATE:
begin
1: begin
Result := INT_EXCEL_TOKEN_FUNC_V;
ASecondaryID := INT_EXCEL_SHEET_FUNC_DATE;
ASecondaryID := TokenIDs[AElementKind, 1]
end;
fekROUND:
begin
Result := INT_EXCEL_TOKEN_FUNC_V;
ASecondaryID := INT_EXCEL_SHEET_FUNC_ROUND;
2: begin
Result := INT_EXCEL_TOKEN_FUNCVAR_V;
ASecondaryID := TokenIDs[AElementKind, 1]
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;