fpspreadsheet: Initial version of reading BIFF8 shared rpn formulas. Reconstruction of string formula from shared rpn formula ok.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3488 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz 2014-08-15 19:29:58 +00:00
parent 5a6d0dd650
commit 62ff8bc2d0
4 changed files with 269 additions and 80 deletions

View File

@ -81,8 +81,8 @@ type
}
TFEKind = (
{ Basic operands }
fekCell, fekCellRef, fekCellRange, fekNum, fekInteger, fekString, fekBool,
fekErr, fekMissingArg,
fekCell, fekCellRef, fekCellRange, fekCellOffset, fekNum, fekInteger,
fekString, fekBool, fekErr, fekMissingArg,
{ Basic operations }
fekAdd, fekSub, fekMul, fekDiv, fekPercent, fekPower, fekUMinus, fekUPlus,
fekConcat, // string concatenation
@ -137,11 +137,13 @@ type
or relative. It is a set consisting of TsRelFlag elements. }
TsRelFlags = set of TsRelFlag;
{@@ Elements of an expanded formula. }
{@@ Elements of an expanded formula.
Note: If ElementKind is fekCellOffset, "Row" and "Col" have to be cast
to signed integers! }
TsFormulaElement = record
ElementKind: TFEKind;
Row, Row2: Word; // zero-based
Col, Col2: Word; // zero-based
Row, Row2: Cardinal; // zero-based
Col, Col2: Cardinal; // zero-based
Param1, Param2: Word; // Extra parameters
DoubleValue: double;
IntValue: Word;
@ -381,6 +383,9 @@ type
{@@ State flags while calculating formulas }
TsCalcState = (csNotCalculated, csCalculating, csCalculated);
{@@ Pointer to a TCell record }
PCell = ^TCell;
{@@ Cell structure for TsWorksheet
The cell record contains information on the location of the cell (row and
column index), on the value contained (number, date, text, ...), and on
@ -403,6 +408,7 @@ type
DateTimeValue: TDateTime;
BoolValue: Boolean;
ErrorValue: TsErrorValue;
SharedFormulaBase: PCell; // Cell containing the shared formula
{ Formatting fields }
{ When adding/deleting formatting fields don't forget to update CopyFormat! }
UsedFormattingFields: TsUsedFormattingFields;
@ -420,9 +426,6 @@ type
CalcState: TsCalcState;
end;
{@@ Pointer to a TCell record }
PCell = ^TCell;
const
// Takes account of effect of cell margins on row height by adding this
// value to the nominal row height. Note that this is an empirical value and may be wrong.
@ -537,7 +540,6 @@ type
function ReadAsDateTime(ACell: PCell; out AResult: TDateTime): Boolean; overload;
function ReadFormulaAsString(ACell: PCell): String;
function ReadNumericValue(ACell: PCell; out AValue: Double): Boolean;
function ReadRPNFormulaAsString(ACell: PCell): String;
{ Reading of cell attributes }
function GetNumberFormatAttributes(ACell: PCell; out ADecimals: Byte;
@ -675,6 +677,8 @@ type
{ Formulas }
procedure CalcFormulas;
function HasFormula(ACell: PCell): Boolean;
function ReadRPNFormulaAsString(ACell: PCell): String;
{ Data manipulation methods - For Cells }
procedure CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal; AFromWorksheet: TsWorksheet);
@ -1141,6 +1145,7 @@ type
ANext: PRPNItem): PRPNItem; overload;
function RPNCellRange(ARow, ACol, ARow2, ACol2: Integer; AFlags: TsRelFlags;
ANext: PRPNItem): PRPNItem; overload;
function RPNCellOffset(ARowOffset, AColOffset: Integer; ANext: PRPNItem): PRPNItem;
function RPNErr(AErrCode: Byte; ANext: PRPNItem): PRPNItem;
function RPNInteger(AValue: Word; ANext: PRPNItem): PRPNItem;
function RPNMissingArg(ANext: PRPNItem): PRPNItem;
@ -1320,6 +1325,7 @@ var
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCell
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellRef
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellRange
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellOffset
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellNum
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellInteger
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellString
@ -1767,6 +1773,7 @@ var
fe: TsFormulaElement;
cell: PCell;
r,c: Cardinal;
formula: TsRPNFormula;
begin
if (Length(ACell^.RPNFormulaValue) = 0) or
(ACell^.ContentType = cctError)
@ -1777,8 +1784,14 @@ begin
args := TsArgumentStack.Create;
try
for i := 0 to Length(ACell^.RPNFormulaValue) - 1 do begin
fe := ACell^.RPNFormulaValue[i]; // "fe" means "formula element"
// Take care of shared formulas
if ACell^.SharedFormulaBase = nil then
formula := ACell^.RPNFormulaValue
else
formula := ACell^.SharedFormulaBase^.RPNFormulaValue;
for i := 0 to Length(formula) - 1 do begin
fe := formula[i]; // "fe" means "formula element"
case fe.ElementKind of
fekCell, fekCellRef:
begin
@ -1803,6 +1816,16 @@ begin
end;
args.PushCellRange(fe.Row, fe.Col, fe.Row2, fe.Col2, self);
end;
fekCellOffset:
begin
cell := FindCell(aCell^.Row + SmallInt(fe.Row), ACell^.Col + SmallInt(fe.Col));
if cell <> nil then
case cell^.CalcState of
csNotCalculated: CalcRPNFormula(cell);
csCalculating : raise Exception.Create(lpCircularReference);
end;
args.PushCell(cell, self);
end;
fekNum:
args.PushNumber(fe.DoubleValue, self);
fekInteger:
@ -1956,17 +1979,6 @@ begin
lDestCell^.Col := AToCol;
ChangedCell(AToRow, AToCol);
ChangedFont(AToRow, AToCol);
{
lCurStr := AFromWorksheet.ReadAsUTF8Text(AFromRow, AFromCol);
lCurUsedFormatting := AFromWorksheet.ReadUsedFormatting(AFromRow, AFromCol);
lCurColor := AFromWorksheet.ReadBackgroundColor(AFromRow, AFromCol);
WriteUTF8Text(AToRow, AToCol, lCurStr);
WriteUsedFormatting(AToRow, AToCol, lCurUsedFormatting);
if uffBackgroundColor in lCurUsedFormatting then
begin
WriteBackgroundColor(AToRow, AToCol, lCurColor);
end;
}
end;
{@@
@ -2059,7 +2071,6 @@ begin
if (Result = nil) then
begin
New(Result);
// Result := GetMem(SizeOf(TCell));
FillChar(Result^, SizeOf(TCell), #0);
Result^.Row := ARow;
@ -2140,16 +2151,19 @@ var
nf: TsNumberFormat;
begin
Result := false;
if ACell <> nil then begin
if ACell <> nil then
begin
parser := TsNumFormatParser.Create(FWorkbook, ACell^.NumberFormatStr);
try
if parser.Status = psOK then begin
nf := parser.NumFormat;
if (nf = nfGeneral) or IsDateTimeFormat(nf) then begin
if (nf = nfGeneral) or IsDateTimeFormat(nf) then
begin
ADecimals := 2;
ACurrencySymbol := '?';
end
else begin
else
begin
ADecimals := parser.Decimals;
ACurrencySymbol := parser.CurrencySymbol;
end;
@ -2223,12 +2237,14 @@ var
AVLNode: TAVLTreeNode;
i: Integer;
begin
if AForceCalculation then begin
if AForceCalculation then
begin
Result := $FFFFFFFF;
// Traverse the tree from lowest to highest.
// Since tree primary sort order is on row lowest col could exist anywhere.
AVLNode := FCells.FindLowest;
While Assigned(AVLNode) do begin
while Assigned(AVLNode) do
begin
Result := Math.Min(Result, PCell(AVLNode.Data)^.Col);
AVLNode := FCells.FindSuccessor(AVLNode);
end;
@ -2240,7 +2256,8 @@ begin
// Store the result
FFirstColIndex := Result;
end
else begin
else
begin
Result := FFirstColIndex;
if Result = $FFFFFFFF then
Result := GetFirstColIndex(true);
@ -2266,7 +2283,8 @@ var
AVLNode: TAVLTreeNode;
i: Integer;
begin
if AForceCalculation then begin
if AForceCalculation then
begin
Result := 0;
// Traverse the tree from lowest to highest.
// Since tree primary sort order is on Row
@ -2312,7 +2330,8 @@ begin
n := GetLastColIndex;
c := 0;
Result := FindCell(ARow, c);
while (result = nil) and (c < n) do begin
while (result = nil) and (c < n) do
begin
inc(c);
result := FindCell(ARow, c);
end;
@ -2331,7 +2350,8 @@ begin
n := GetLastColIndex;
c := n;
Result := FindCell(ARow, c);
while (Result = nil) and (c > 0) do begin
while (Result = nil) and (c > 0) do
begin
dec(c);
Result := FindCell(ARow, c);
end;
@ -2356,7 +2376,8 @@ var
AVLNode: TAVLTreeNode;
i: Integer;
begin
if AForceCalculation then begin
if AForceCalculation then
begin
Result := $FFFFFFFF;
AVLNode := FCells.FindLowest;
if Assigned(AVLNode) then
@ -2368,7 +2389,8 @@ begin
// Store result
FFirstRowIndex := Result;
end
else begin
else
begin
Result := FFirstRowIndex;
if Result = $FFFFFFFF then
Result := GetFirstRowIndex(true);
@ -2394,7 +2416,8 @@ var
AVLNode: TAVLTreeNode;
i: Integer;
begin
if AForceCalculation then begin
if AForceCalculation then
begin
Result := 0;
AVLNode := FCells.FindHighest;
if Assigned(AVLNode) then
@ -2420,6 +2443,18 @@ begin
Result := GetLastRowIndex;
end;
{@@
Returns TRUE if the cell contains a formula (direct or shared, does not matter).
}
function TsWorksheet.HasFormula(ACell: PCell): Boolean;
begin
Result := Assigned(ACell) and (
(ACell^.SharedFormulaBase <> nil) or
(Length(ACell^.RPNFormulaValue) > 0) or
(Length(ACell^.FormulaValue.FormulaStr) > 0)
);
end;
{@@
Reads the contents of a cell and returns an user readable text
representing the contents of the cell.
@ -2470,7 +2505,8 @@ function TsWorksheet.ReadAsUTF8Text(ACell: PCell): ansistring;
fmtp, fmtn, fmt0: String;
begin
Result := '';
if not IsNaN(Value) then begin
if not IsNaN(Value) then
begin
if ANumberFormatStr = '' then
ANumberFormatStr := BuildDateTimeFormatString(ANumberFormat,
Workbook.FormatSettings, ANumberFormatStr);
@ -2613,10 +2649,11 @@ begin
Result := '';
if ACell = nil then
exit;
if Length(ACell^.RPNFormulaValue) > 0 then
Result := ReadRPNFormulaAsString(ACell)
else
Result := ACell^.FormulaValue.FormulaStr;
if HasFormula(ACell) then begin
Result := ReadRPNFormulaAsString(ACell);
if Result = '' then
Result := ACell^.FormulaValue.FormulaStr;
end;
end;
{@@
@ -2668,6 +2705,7 @@ var
s: String;
ptr: Pointer;
fek: TFEKind;
formula: TsRPNFormula;
begin
Result := '';
if ACell = nil then
@ -2676,13 +2714,19 @@ begin
fs := Workbook.FormatSettings;
L := TStringList.Create;
try
// Take care of shared formulas
if ACell^.SharedFormulaBase = nil then
formula := ACell^.RPNFormulaValue
else
formula := ACell^.SharedFormulaBase^.RPNFormulaValue;
// We store the cell values and operation codes in a stringlist which serves
// as kind of stack. Therefore, we do not destroy the original formula array.
// We reverse the order of the items because in the next step stringlist
// items will subsequently be deleted, and this is much easier when going
// in reverse direction.
for i := Length(ACell^.RPNFormulaValue)-1 downto 0 do begin
elem := ACell^.RPNFormulaValue[i];
for i := Length(formula)-1 downto 0 do begin
elem := formula[i];
ptr := Pointer(elem.ElementKind);
case elem.ElementKind of
fekNum:
@ -2698,6 +2742,8 @@ begin
L.AddObject(GetCellString(elem.Row, elem.Col, elem.RelFlags), ptr);
fekCellRange:
L.AddObject(GetCellRangeString(elem.Row, elem.Col, elem.Row2, elem.Col2, elem.RelFlags), ptr);
fekCellOffset:
L.AddObject(GetCellString(ACell^.Row + SmallInt(elem.Row), ACell^.Col + SmallInt(elem.Col)), ptr);
// Operations:
else
L.AddObject(FEProps[elem.ElementKind].Symbol, ptr);
@ -2752,7 +2798,7 @@ begin
end;
else
if fek >= fekAdd then begin
elem := ACell^.RPNFormulaValue[Length(ACell^.RPNFormulaValue) - 1 - i];
elem := formula[Length(formula) - 1 - i];
s := '';
for j:= i+elem.ParamsNum downto i+1 do begin
if j < L.Count then begin
@ -6964,12 +7010,11 @@ end;
Creates a pointer to a new RPN item. This represents an element in the array
of token of an RPN formula.
@return Pointer the the RPN item
@return Pointer to the RPN item
}
function NewRPNItem: PRPNItem;
begin
New(Result);
// Result := GetMem(SizeOf(TRPNItem));
FillChar(Result^.FE, SizeOf(Result^.FE), 0);
Result^.FE.StringValue := '';
end;
@ -6979,13 +7024,8 @@ end;
}
procedure DisposeRPNItem(AItem: PRPNItem);
begin
if AItem <> nil then begin
{
AItem.FE.StringValue := '';;
FreeMem(AItem, SizeOf(TRPNItem));
}
if AItem <> nil then
Dispose(AItem);
end;
end;
{@@
@ -7125,6 +7165,25 @@ begin
Result^.Next := ANext;
end;
{@@
Creates an entry in the RPN array for a relative cell reference as used in
shared formulas. The given parameters indicate the relativ offset between
the current cell coordinates and a reference rell.
@param ARowOffset Offset between current row and the row of a reference cell
@param AColOffset Offset between current column and the column of a reference cell
@param ANext Pointer to the next RPN item in the list
}
function RPNCellOffset(ARowOffset, AColOffset: Integer; ANext: PRPNItem): PRPNItem;
begin
Result := NewRPNItem;
Result^.FE.ElementKind := fekCellOffset;
Result^.FE.Row := ARowOffset;
Result^.FE.Col := AColOffset;
Result^.FE.RelFlags := [rfRelRow, rfRelCol];
Result^.Next := ANext;
end;
{@@
Creates an entry in the RPN array with an error value.

View File

@ -76,6 +76,7 @@ type
procedure ReadRowColXF(AStream: TStream; out ARow, ACol: Cardinal; out AXF: Word); override;
procedure ReadRowInfo(AStream: TStream); override;
function ReadRPNFunc(AStream: TStream): Word; override;
procedure ReadRPNSharedFormulaBase(AStream: TStream; out ARow, ACol: Cardinal);
function ReadRPNTokenArraySize(AStream: TStream): Word; override;
procedure ReadStringRecord(AStream: TStream); override;
procedure ReadWindow2(AStream: TStream); override;
@ -601,7 +602,7 @@ begin
{ Formula token array }
if FWorkbook.ReadFormulas then begin
ok := ReadRPNTokenArray(AStream, cell^.RPNFormulaValue);
ok := ReadRPNTokenArray(AStream, cell);
if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported);
end;
@ -784,6 +785,19 @@ begin
Result := b;
end;
{ Reads the cell coordiantes of the top/left cell of a range using a shared formula.
This cell contains the rpn token sequence of the formula.
Is overridden because BIFF2 has 1 byte for column. }
procedure TsSpreadBIFF2Reader.ReadRPNSharedFormulaBase(AStream: TStream;
out ARow, ACol: Cardinal);
begin
// 2 bytes for row of first cell in shared formula
ARow := WordLEToN(AStream.ReadWord);
// 1 byte for column of first cell in shared formula
ACol := AStream.ReadByte;
end;
{ Helper funtion for reading of the size of the token array of an RPN formula.
Is overridden because BIFF2 uses 1 byte only. }
function TsSpreadBIFF2Reader.ReadRPNTokenArraySize(AStream: TStream): Word;

View File

@ -87,6 +87,8 @@ type
procedure ReadRichString(const AStream: TStream);
procedure ReadRPNCellAddress(AStream: TStream; out ARow, ACol: Cardinal;
out AFlags: TsRelFlags); override;
procedure ReadRPNCellAddressOffset(AStream: TStream;
out ARowOffset, AColOffset: Integer); override;
procedure ReadRPNCellRangeAddress(AStream: TStream;
out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); override;
procedure ReadSST(const AStream: TStream);
@ -1474,29 +1476,30 @@ begin
case RecordType of
INT_EXCEL_ID_BLANK : ReadBlank(AStream);
INT_EXCEL_ID_MULBLANK: ReadMulBlank(AStream);
INT_EXCEL_ID_NUMBER : ReadNumber(AStream);
INT_EXCEL_ID_LABEL : ReadLabel(AStream);
INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
INT_EXCEL_ID_BLANK : ReadBlank(AStream);
INT_EXCEL_ID_MULBLANK : ReadMulBlank(AStream);
INT_EXCEL_ID_NUMBER : ReadNumber(AStream);
INT_EXCEL_ID_LABEL : ReadLabel(AStream);
INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
INT_EXCEL_ID_SHAREDFMLA: ReadSharedFormula(AStream);
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
//(RSTRING) This record stores a formatted text cell (Rich-Text).
// In BIFF8 it is usually replaced by the LABELSST record. Excel still
// uses this record, if it copies formatted text cells to the clipboard.
INT_EXCEL_ID_RSTRING : ReadRichString(AStream);
INT_EXCEL_ID_RSTRING : ReadRichString(AStream);
// (RK) This record represents a cell that contains an RK value
// (encoded integer or floating-point value). If a floating-point
// value cannot be encoded to an RK value, a NUMBER record will be written.
// This record replaces the record INTEGER written in BIFF2.
INT_EXCEL_ID_RK : ReadRKValue(AStream);
INT_EXCEL_ID_MULRK : ReadMulRKValues(AStream);
INT_EXCEL_ID_LABELSST: ReadLabelSST(AStream); //BIFF8 only
INT_EXCEL_ID_COLINFO : ReadColInfo(AStream);
INT_EXCEL_ID_ROW : ReadRowInfo(AStream);
INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream);
INT_EXCEL_ID_PANE : ReadPane(AStream);
INT_EXCEL_ID_BOF : ;
INT_EXCEL_ID_EOF : SectionEOF := True;
INT_EXCEL_ID_RK : ReadRKValue(AStream);
INT_EXCEL_ID_MULRK : ReadMulRKValues(AStream);
INT_EXCEL_ID_LABELSST : ReadLabelSST(AStream); //BIFF8 only
INT_EXCEL_ID_COLINFO : ReadColInfo(AStream);
INT_EXCEL_ID_ROW : ReadRowInfo(AStream);
INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream);
INT_EXCEL_ID_PANE : ReadPane(AStream);
INT_EXCEL_ID_BOF : ;
INT_EXCEL_ID_EOF : SectionEOF := True;
else
// nothing
end;
@ -1703,6 +1706,24 @@ begin
if (c and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow);
end;
{ Read the difference between cell row and column indexed of a cell and a reference
cell.
Overriding the implementation in xlscommon. }
procedure TsSpreadBIFF8Reader.ReadRPNCellAddressOffset(AStream: TStream;
out ARowOffset, AColOffset: Integer);
var
dr: SmallInt;
dc: ShortInt;
begin
// 2 bytes for row offset
dr := WordLEToN(AStream.ReadWord);
ARowOffset := dr;
// 2 bytes for column offset
dc := Lo(WordLEToN(AStream.ReadWord));
AColOffset := dc;
end;
{ Reads a cell range address used in an RPN formula element.
Evaluates the corresponding bits to distinguish between absolute and
relative addresses.

View File

@ -60,6 +60,7 @@ const
INT_EXCEL_ID_MULBLANK = $00BE; // does not exist before BIFF5
INT_EXCEL_ID_XF = $00E0; // BIFF2:$0043, BIFF3:$0243, BIFF4:$0443
INT_EXCEL_ID_RSTRING = $00D6; // does not exist before BIFF5
INT_EXCEL_ID_SHAREDFMLA = $04BC; // does not exist before BIFF5
INT_EXCEL_ID_BOF = $0809; // BIFF2:$0009, BIFF3:$0209; BIFF4:$0409
{ FONT record constants }
@ -105,6 +106,9 @@ const
INT_EXCEL_TOKEN_TAREA_R = $25;
INT_EXCEL_TOKEN_TAREA_V = $45;
INT_EXCEL_TOKEN_TAREA_A = $65;
INT_EXCEL_TOKEN_TREFN_R = $2C;
INT_EXCEL_TOKEN_TREFN_V = $4C;
INT_EXCEL_TOKEN_TREFN_A = $6C;
{ Function Tokens }
// _R: reference; _V: value; _A: array
@ -114,9 +118,12 @@ const
INT_EXCEL_TOKEN_FUNC_A = $61;
//VAR: variable number of arguments:
INT_EXCEL_TOKEN_FUNCVAR_R = $22;
INT_EXCEL_TOKEN_FUNCVAR_V = $42;
INT_EXCEL_TOKEN_FUNCVAR_A = $62;
INT_EXCEL_TOKEN_FUNCVAR_R = $22;
INT_EXCEL_TOKEN_FUNCVAR_V = $42;
INT_EXCEL_TOKEN_FUNCVAR_A = $62;
{ Special tokens }
INT_EXCEL_TOKEN_TEXP = $01; // cell belongs to shared formula
{ Built-in/worksheet functions }
INT_EXCEL_SHEET_FUNC_COUNT = 0;
@ -431,11 +438,15 @@ type
// Read the array of RPN tokens of a formula
procedure ReadRPNCellAddress(AStream: TStream; out ARow, ACol: Cardinal;
out AFlags: TsRelFlags); virtual;
procedure ReadRPNCellAddressOffset(AStream: TStream;
out ARowOffset, AColOffset: Integer); virtual;
procedure ReadRPNCellRangeAddress(AStream: TStream;
out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); virtual;
function ReadRPNFunc(AStream: TStream): Word; virtual;
function ReadRPNTokenArray(AStream: TStream; var AFormula: TsRPNFormula): Boolean;
procedure ReadRPNSharedFormulaBase(AStream: TStream; out ARow, ACol: Cardinal); virtual;
function ReadRPNTokenArray(AStream: TStream; ACell: PCell): Boolean;
function ReadRPNTokenArraySize(AStream: TStream): word; virtual;
procedure ReadSharedFormula(AStream: TStream);
// Helper function for reading a string with 8-bit length
function ReadString_8bitLen(AStream: TStream): String; virtual;
@ -542,6 +553,7 @@ const
INT_EXCEL_TOKEN_TREFV, {fekCell}
INT_EXCEL_TOKEN_TREFR, {fekCellRef}
INT_EXCEL_TOKEN_TAREA_R, {fekCellRange}
INT_EXCEL_TOKEN_TREFN_V, {fekCellOffset}
INT_EXCEL_TOKEN_TNUM, {fekNum}
INT_EXCEL_TOKEN_TINT, {fekInteger}
INT_EXCEL_TOKEN_TSTR, {fekString}
@ -1166,8 +1178,6 @@ var
cell: PCell;
begin
{ BIFF Record header }
{ BIFF Record data }
{ Index to XF Record }
ReadRowColXF(AStream, ARow, ACol, XF);
@ -1230,8 +1240,9 @@ begin
{ Formula token array }
if FWorkbook.ReadFormulas then begin
ok := ReadRPNTokenArray(AStream, cell^.RPNFormulaValue);
if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported);
ok := ReadRPNTokenArray(AStream, cell);
if not ok then
FWorksheet.WriteErrorValue(cell, errFormulaNotSupported);
end;
{Add attributes}
@ -1533,6 +1544,24 @@ begin
if (r and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow);
end;
{ Read the difference between cell row and column indexed of a cell and a reference
cell.
Implemented here for BIFF5. BIFF8 must be overridden. Not used by BIFF2. }
procedure TsSpreadBIFFReader.ReadRPNCellAddressOffset(AStream: TStream;
out ARowOffset, AColOffset: Integer);
var
r: SmallInt;
c: ShortInt;
begin
// 2 bytes for row
r := WordLEToN(AStream.ReadWord) and $3FFF;
ARowOffset := r;
// 1 byte for column
c := AStream.ReadByte;
AColOffset := c;
end;
{ Reads the cell address used in an RPN formula element. Evaluates the corresponding
bits to distinguish between absolute and relative addresses.
Implemented here for BIFF2-BIFF5. BIFF8 must be overridden. }
@ -1565,8 +1594,22 @@ begin
Result := WordLEToN(AStream.ReadWord);
end;
{ Reads the cell coordiantes of the top/left cell of a range using a shared formula.
This cell contains the rpn token sequence of the formula.
Valid for BIFF3-BIFF8. BIFF2 needs to be overridden (has 1 byte for column). }
procedure TsSpreadBIFFReader.ReadRPNSharedFormulaBase(AStream: TStream;
out ARow, ACol: Cardinal);
begin
// 2 bytes for row of first cell in shared formula
ARow := WordLEToN(AStream.ReadWord);
// 2 bytes for column of first cell in shared formula
ACol := WordLEToN(AStream.ReadWord);
end;
{ Reads the array of rpn tokens from the current stream position, creates an
rpn formula and stores it in the cell. }
function TsSpreadBIFFReader.ReadRPNTokenArray(AStream: TStream;
var AFormula: TsRPNFormula): Boolean;
ACell: PCell): Boolean;
var
n: Word;
p0: Int64;
@ -1576,6 +1619,7 @@ var
dblVal: Double = 0.0; // IEEE 8 byte floating point number
flags: TsRelFlags;
r, c, r2, c2: Cardinal;
dr, dc: Integer;
fek: TFEKind;
func: Word;
b: Byte;
@ -1603,6 +1647,15 @@ begin
ReadRPNCellRangeAddress(AStream, r, c, r2, c2, flags);
rpnItem := RPNCellRange(r, c, r2, c2, flags, rpnItem);
end;
INT_EXCEL_TOKEN_TREFN_R, INT_EXCEL_TOKEN_TREFN_V:
begin
ReadRPNCellAddressOffset(AStream, dr, dc);
rpnItem := RPNCellOffset(dr, dc, rpnItem);
end;
INT_EXCEL_TOKEN_TREFN_A:
begin
raise Exception.Create('Cell range offset not yet implemented. Please report an issue');
end;
INT_EXCEL_TOKEN_TMISSARG:
rpnItem := RPNMissingArg(rpnItem);
INT_EXCEL_TOKEN_TSTR:
@ -1655,7 +1708,15 @@ begin
end;
if not found then
supported := false;
end
end;
INT_EXCEL_TOKEN_TEXP:
// Indicates that cell belongs to a shared or array formula. We determine
// the base cell of the shared formula and store it in the current cell.
begin
ReadRPNSharedFormulaBase(AStream, r, c);
ACell^.SharedFormulaBase := FWorksheet.FindCell(r, c);
end;
else
found := false;
@ -1671,11 +1732,11 @@ begin
end;
if not supported then begin
DestroyRPNFormula(rpnItem);
SetLength(AFormula, 0);
SetLength(ACell^.RPNFormulaValue, 0);
Result := false;
end
else begin
AFormula := CreateRPNFormula(rpnItem, true); // true --> we have to flip the order of items!
ACell^.RPNFormulaValue := CreateRPNFormula(rpnItem, true); // true --> we have to flip the order of items!
Result := true;
end;
end;
@ -1688,6 +1749,40 @@ begin
Result := WordLEToN(AStream.ReadWord);
end;
{ Reads a SHAREDFMLA record, i.e. reads cell range coordinates and a rpn
formula. The formula is applied to all cells in the range. The formula stored
only in the top/left cell of the range. }
procedure TsSpreadBIFFReader.ReadSharedFormula(AStream: TStream);
var
r1, r2, c1, c2: Cardinal;
flags: TsRelFlags;
b: Byte;
ok: Boolean;
cell: PCell;
begin
// Cell range in which the formula is valid
r1 := WordLEToN(AStream.ReadWord);
r2 := WordLEToN(AStream.ReadWord);
c1 := AStream.ReadByte; // 8 bit, even for BIFF8
c2 := AStream.ReadByte;
{ Create cell }
if FIsVirtualMode then begin // "Virtual" cell
InitCell(r1, c1, FVirtualCell);
cell := @FVirtualCell;
end else
cell := FWorksheet.GetCell(r1, c1); // "Real" cell
// Unused
AStream.ReadByte;
// Number of existing FORMULA records for this shared formula
AStream.ReadByte;
// RPN formula tokens
ok := ReadRPNTokenArray(AStream, cell);
end;
{ Helper function for reading a string with 8-bit length. Here, we implement the
version for ansistrings since it is valid for all BIFF versions except for
BIFF8 where it has to be overridden. }