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 = ( TFEKind = (
{ Basic operands } { Basic operands }
fekCell, fekCellRef, fekCellRange, fekNum, fekInteger, fekString, fekBool, fekCell, fekCellRef, fekCellRange, fekCellOffset, fekNum, fekInteger,
fekErr, fekMissingArg, fekString, fekBool, fekErr, fekMissingArg,
{ Basic operations } { Basic operations }
fekAdd, fekSub, fekMul, fekDiv, fekPercent, fekPower, fekUMinus, fekUPlus, fekAdd, fekSub, fekMul, fekDiv, fekPercent, fekPower, fekUMinus, fekUPlus,
fekConcat, // string concatenation fekConcat, // string concatenation
@ -137,11 +137,13 @@ type
or relative. It is a set consisting of TsRelFlag elements. } or relative. It is a set consisting of TsRelFlag elements. }
TsRelFlags = set of TsRelFlag; 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 TsFormulaElement = record
ElementKind: TFEKind; ElementKind: TFEKind;
Row, Row2: Word; // zero-based Row, Row2: Cardinal; // zero-based
Col, Col2: Word; // zero-based Col, Col2: Cardinal; // zero-based
Param1, Param2: Word; // Extra parameters Param1, Param2: Word; // Extra parameters
DoubleValue: double; DoubleValue: double;
IntValue: Word; IntValue: Word;
@ -381,6 +383,9 @@ type
{@@ State flags while calculating formulas } {@@ State flags while calculating formulas }
TsCalcState = (csNotCalculated, csCalculating, csCalculated); TsCalcState = (csNotCalculated, csCalculating, csCalculated);
{@@ Pointer to a TCell record }
PCell = ^TCell;
{@@ Cell structure for TsWorksheet {@@ Cell structure for TsWorksheet
The cell record contains information on the location of the cell (row and The cell record contains information on the location of the cell (row and
column index), on the value contained (number, date, text, ...), and on column index), on the value contained (number, date, text, ...), and on
@ -403,6 +408,7 @@ type
DateTimeValue: TDateTime; DateTimeValue: TDateTime;
BoolValue: Boolean; BoolValue: Boolean;
ErrorValue: TsErrorValue; ErrorValue: TsErrorValue;
SharedFormulaBase: PCell; // Cell containing the shared formula
{ Formatting fields } { Formatting fields }
{ When adding/deleting formatting fields don't forget to update CopyFormat! } { When adding/deleting formatting fields don't forget to update CopyFormat! }
UsedFormattingFields: TsUsedFormattingFields; UsedFormattingFields: TsUsedFormattingFields;
@ -420,9 +426,6 @@ type
CalcState: TsCalcState; CalcState: TsCalcState;
end; end;
{@@ Pointer to a TCell record }
PCell = ^TCell;
const const
// Takes account of effect of cell margins on row height by adding this // 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. // 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 ReadAsDateTime(ACell: PCell; out AResult: TDateTime): Boolean; overload;
function ReadFormulaAsString(ACell: PCell): String; function ReadFormulaAsString(ACell: PCell): String;
function ReadNumericValue(ACell: PCell; out AValue: Double): Boolean; function ReadNumericValue(ACell: PCell; out AValue: Double): Boolean;
function ReadRPNFormulaAsString(ACell: PCell): String;
{ Reading of cell attributes } { Reading of cell attributes }
function GetNumberFormatAttributes(ACell: PCell; out ADecimals: Byte; function GetNumberFormatAttributes(ACell: PCell; out ADecimals: Byte;
@ -675,6 +677,8 @@ type
{ Formulas } { Formulas }
procedure CalcFormulas; procedure CalcFormulas;
function HasFormula(ACell: PCell): Boolean;
function ReadRPNFormulaAsString(ACell: PCell): String;
{ Data manipulation methods - For Cells } { Data manipulation methods - For Cells }
procedure CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal; AFromWorksheet: TsWorksheet); procedure CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal; AFromWorksheet: TsWorksheet);
@ -1141,6 +1145,7 @@ type
ANext: PRPNItem): PRPNItem; overload; ANext: PRPNItem): PRPNItem; overload;
function RPNCellRange(ARow, ACol, ARow2, ACol2: Integer; AFlags: TsRelFlags; function RPNCellRange(ARow, ACol, ARow2, ACol2: Integer; AFlags: TsRelFlags;
ANext: PRPNItem): PRPNItem; overload; ANext: PRPNItem): PRPNItem; overload;
function RPNCellOffset(ARowOffset, AColOffset: Integer; ANext: PRPNItem): PRPNItem;
function RPNErr(AErrCode: Byte; ANext: PRPNItem): PRPNItem; function RPNErr(AErrCode: Byte; ANext: PRPNItem): PRPNItem;
function RPNInteger(AValue: Word; ANext: PRPNItem): PRPNItem; function RPNInteger(AValue: Word; ANext: PRPNItem): PRPNItem;
function RPNMissingArg(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), // fekCell
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellRef (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), // 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), // fekCellNum
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellInteger (Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellInteger
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellString (Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellString
@ -1767,6 +1773,7 @@ var
fe: TsFormulaElement; fe: TsFormulaElement;
cell: PCell; cell: PCell;
r,c: Cardinal; r,c: Cardinal;
formula: TsRPNFormula;
begin begin
if (Length(ACell^.RPNFormulaValue) = 0) or if (Length(ACell^.RPNFormulaValue) = 0) or
(ACell^.ContentType = cctError) (ACell^.ContentType = cctError)
@ -1777,8 +1784,14 @@ begin
args := TsArgumentStack.Create; args := TsArgumentStack.Create;
try try
for i := 0 to Length(ACell^.RPNFormulaValue) - 1 do begin // Take care of shared formulas
fe := ACell^.RPNFormulaValue[i]; // "fe" means "formula element" 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 case fe.ElementKind of
fekCell, fekCellRef: fekCell, fekCellRef:
begin begin
@ -1803,6 +1816,16 @@ begin
end; end;
args.PushCellRange(fe.Row, fe.Col, fe.Row2, fe.Col2, self); args.PushCellRange(fe.Row, fe.Col, fe.Row2, fe.Col2, self);
end; 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: fekNum:
args.PushNumber(fe.DoubleValue, self); args.PushNumber(fe.DoubleValue, self);
fekInteger: fekInteger:
@ -1956,17 +1979,6 @@ begin
lDestCell^.Col := AToCol; lDestCell^.Col := AToCol;
ChangedCell(AToRow, AToCol); ChangedCell(AToRow, AToCol);
ChangedFont(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; end;
{@@ {@@
@ -2059,7 +2071,6 @@ begin
if (Result = nil) then if (Result = nil) then
begin begin
New(Result); New(Result);
// Result := GetMem(SizeOf(TCell));
FillChar(Result^, SizeOf(TCell), #0); FillChar(Result^, SizeOf(TCell), #0);
Result^.Row := ARow; Result^.Row := ARow;
@ -2140,16 +2151,19 @@ var
nf: TsNumberFormat; nf: TsNumberFormat;
begin begin
Result := false; Result := false;
if ACell <> nil then begin if ACell <> nil then
begin
parser := TsNumFormatParser.Create(FWorkbook, ACell^.NumberFormatStr); parser := TsNumFormatParser.Create(FWorkbook, ACell^.NumberFormatStr);
try try
if parser.Status = psOK then begin if parser.Status = psOK then begin
nf := parser.NumFormat; nf := parser.NumFormat;
if (nf = nfGeneral) or IsDateTimeFormat(nf) then begin if (nf = nfGeneral) or IsDateTimeFormat(nf) then
begin
ADecimals := 2; ADecimals := 2;
ACurrencySymbol := '?'; ACurrencySymbol := '?';
end end
else begin else
begin
ADecimals := parser.Decimals; ADecimals := parser.Decimals;
ACurrencySymbol := parser.CurrencySymbol; ACurrencySymbol := parser.CurrencySymbol;
end; end;
@ -2223,12 +2237,14 @@ var
AVLNode: TAVLTreeNode; AVLNode: TAVLTreeNode;
i: Integer; i: Integer;
begin begin
if AForceCalculation then begin if AForceCalculation then
begin
Result := $FFFFFFFF; Result := $FFFFFFFF;
// Traverse the tree from lowest to highest. // Traverse the tree from lowest to highest.
// Since tree primary sort order is on row lowest col could exist anywhere. // Since tree primary sort order is on row lowest col could exist anywhere.
AVLNode := FCells.FindLowest; AVLNode := FCells.FindLowest;
While Assigned(AVLNode) do begin while Assigned(AVLNode) do
begin
Result := Math.Min(Result, PCell(AVLNode.Data)^.Col); Result := Math.Min(Result, PCell(AVLNode.Data)^.Col);
AVLNode := FCells.FindSuccessor(AVLNode); AVLNode := FCells.FindSuccessor(AVLNode);
end; end;
@ -2240,7 +2256,8 @@ begin
// Store the result // Store the result
FFirstColIndex := Result; FFirstColIndex := Result;
end end
else begin else
begin
Result := FFirstColIndex; Result := FFirstColIndex;
if Result = $FFFFFFFF then if Result = $FFFFFFFF then
Result := GetFirstColIndex(true); Result := GetFirstColIndex(true);
@ -2266,7 +2283,8 @@ var
AVLNode: TAVLTreeNode; AVLNode: TAVLTreeNode;
i: Integer; i: Integer;
begin begin
if AForceCalculation then begin if AForceCalculation then
begin
Result := 0; Result := 0;
// Traverse the tree from lowest to highest. // Traverse the tree from lowest to highest.
// Since tree primary sort order is on Row // Since tree primary sort order is on Row
@ -2312,7 +2330,8 @@ begin
n := GetLastColIndex; n := GetLastColIndex;
c := 0; c := 0;
Result := FindCell(ARow, c); Result := FindCell(ARow, c);
while (result = nil) and (c < n) do begin while (result = nil) and (c < n) do
begin
inc(c); inc(c);
result := FindCell(ARow, c); result := FindCell(ARow, c);
end; end;
@ -2331,7 +2350,8 @@ begin
n := GetLastColIndex; n := GetLastColIndex;
c := n; c := n;
Result := FindCell(ARow, c); Result := FindCell(ARow, c);
while (Result = nil) and (c > 0) do begin while (Result = nil) and (c > 0) do
begin
dec(c); dec(c);
Result := FindCell(ARow, c); Result := FindCell(ARow, c);
end; end;
@ -2356,7 +2376,8 @@ var
AVLNode: TAVLTreeNode; AVLNode: TAVLTreeNode;
i: Integer; i: Integer;
begin begin
if AForceCalculation then begin if AForceCalculation then
begin
Result := $FFFFFFFF; Result := $FFFFFFFF;
AVLNode := FCells.FindLowest; AVLNode := FCells.FindLowest;
if Assigned(AVLNode) then if Assigned(AVLNode) then
@ -2368,7 +2389,8 @@ begin
// Store result // Store result
FFirstRowIndex := Result; FFirstRowIndex := Result;
end end
else begin else
begin
Result := FFirstRowIndex; Result := FFirstRowIndex;
if Result = $FFFFFFFF then if Result = $FFFFFFFF then
Result := GetFirstRowIndex(true); Result := GetFirstRowIndex(true);
@ -2394,7 +2416,8 @@ var
AVLNode: TAVLTreeNode; AVLNode: TAVLTreeNode;
i: Integer; i: Integer;
begin begin
if AForceCalculation then begin if AForceCalculation then
begin
Result := 0; Result := 0;
AVLNode := FCells.FindHighest; AVLNode := FCells.FindHighest;
if Assigned(AVLNode) then if Assigned(AVLNode) then
@ -2420,6 +2443,18 @@ begin
Result := GetLastRowIndex; Result := GetLastRowIndex;
end; 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 Reads the contents of a cell and returns an user readable text
representing the contents of the cell. representing the contents of the cell.
@ -2470,7 +2505,8 @@ function TsWorksheet.ReadAsUTF8Text(ACell: PCell): ansistring;
fmtp, fmtn, fmt0: String; fmtp, fmtn, fmt0: String;
begin begin
Result := ''; Result := '';
if not IsNaN(Value) then begin if not IsNaN(Value) then
begin
if ANumberFormatStr = '' then if ANumberFormatStr = '' then
ANumberFormatStr := BuildDateTimeFormatString(ANumberFormat, ANumberFormatStr := BuildDateTimeFormatString(ANumberFormat,
Workbook.FormatSettings, ANumberFormatStr); Workbook.FormatSettings, ANumberFormatStr);
@ -2613,11 +2649,12 @@ begin
Result := ''; Result := '';
if ACell = nil then if ACell = nil then
exit; exit;
if Length(ACell^.RPNFormulaValue) > 0 then if HasFormula(ACell) then begin
Result := ReadRPNFormulaAsString(ACell) Result := ReadRPNFormulaAsString(ACell);
else if Result = '' then
Result := ACell^.FormulaValue.FormulaStr; Result := ACell^.FormulaValue.FormulaStr;
end; end;
end;
{@@ {@@
Returns to numeric equivalent of the cell contents. This is the NumberValue Returns to numeric equivalent of the cell contents. This is the NumberValue
@ -2668,6 +2705,7 @@ var
s: String; s: String;
ptr: Pointer; ptr: Pointer;
fek: TFEKind; fek: TFEKind;
formula: TsRPNFormula;
begin begin
Result := ''; Result := '';
if ACell = nil then if ACell = nil then
@ -2676,13 +2714,19 @@ begin
fs := Workbook.FormatSettings; fs := Workbook.FormatSettings;
L := TStringList.Create; L := TStringList.Create;
try 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 // 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. // 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 // 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 // items will subsequently be deleted, and this is much easier when going
// in reverse direction. // in reverse direction.
for i := Length(ACell^.RPNFormulaValue)-1 downto 0 do begin for i := Length(formula)-1 downto 0 do begin
elem := ACell^.RPNFormulaValue[i]; elem := formula[i];
ptr := Pointer(elem.ElementKind); ptr := Pointer(elem.ElementKind);
case elem.ElementKind of case elem.ElementKind of
fekNum: fekNum:
@ -2698,6 +2742,8 @@ begin
L.AddObject(GetCellString(elem.Row, elem.Col, elem.RelFlags), ptr); L.AddObject(GetCellString(elem.Row, elem.Col, elem.RelFlags), ptr);
fekCellRange: fekCellRange:
L.AddObject(GetCellRangeString(elem.Row, elem.Col, elem.Row2, elem.Col2, elem.RelFlags), ptr); 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: // Operations:
else else
L.AddObject(FEProps[elem.ElementKind].Symbol, ptr); L.AddObject(FEProps[elem.ElementKind].Symbol, ptr);
@ -2752,7 +2798,7 @@ begin
end; end;
else else
if fek >= fekAdd then begin if fek >= fekAdd then begin
elem := ACell^.RPNFormulaValue[Length(ACell^.RPNFormulaValue) - 1 - i]; elem := formula[Length(formula) - 1 - i];
s := ''; s := '';
for j:= i+elem.ParamsNum downto i+1 do begin for j:= i+elem.ParamsNum downto i+1 do begin
if j < L.Count then 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 Creates a pointer to a new RPN item. This represents an element in the array
of token of an RPN formula. of token of an RPN formula.
@return Pointer the the RPN item @return Pointer to the RPN item
} }
function NewRPNItem: PRPNItem; function NewRPNItem: PRPNItem;
begin begin
New(Result); New(Result);
// Result := GetMem(SizeOf(TRPNItem));
FillChar(Result^.FE, SizeOf(Result^.FE), 0); FillChar(Result^.FE, SizeOf(Result^.FE), 0);
Result^.FE.StringValue := ''; Result^.FE.StringValue := '';
end; end;
@ -6979,14 +7024,9 @@ end;
} }
procedure DisposeRPNItem(AItem: PRPNItem); procedure DisposeRPNItem(AItem: PRPNItem);
begin begin
if AItem <> nil then begin if AItem <> nil then
{
AItem.FE.StringValue := '';;
FreeMem(AItem, SizeOf(TRPNItem));
}
Dispose(AItem); Dispose(AItem);
end; end;
end;
{@@ {@@
Creates a boolean value entry in the RPN array. Creates a boolean value entry in the RPN array.
@ -7125,6 +7165,25 @@ begin
Result^.Next := ANext; Result^.Next := ANext;
end; 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. 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 ReadRowColXF(AStream: TStream; out ARow, ACol: Cardinal; out AXF: Word); override;
procedure ReadRowInfo(AStream: TStream); override; procedure ReadRowInfo(AStream: TStream); override;
function ReadRPNFunc(AStream: TStream): Word; override; function ReadRPNFunc(AStream: TStream): Word; override;
procedure ReadRPNSharedFormulaBase(AStream: TStream; out ARow, ACol: Cardinal);
function ReadRPNTokenArraySize(AStream: TStream): Word; override; function ReadRPNTokenArraySize(AStream: TStream): Word; override;
procedure ReadStringRecord(AStream: TStream); override; procedure ReadStringRecord(AStream: TStream); override;
procedure ReadWindow2(AStream: TStream); override; procedure ReadWindow2(AStream: TStream); override;
@ -601,7 +602,7 @@ begin
{ Formula token array } { Formula token array }
if FWorkbook.ReadFormulas then begin if FWorkbook.ReadFormulas then begin
ok := ReadRPNTokenArray(AStream, cell^.RPNFormulaValue); ok := ReadRPNTokenArray(AStream, cell);
if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported); if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported);
end; end;
@ -784,6 +785,19 @@ begin
Result := b; Result := b;
end; 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. { Helper funtion for reading of the size of the token array of an RPN formula.
Is overridden because BIFF2 uses 1 byte only. } Is overridden because BIFF2 uses 1 byte only. }
function TsSpreadBIFF2Reader.ReadRPNTokenArraySize(AStream: TStream): Word; function TsSpreadBIFF2Reader.ReadRPNTokenArraySize(AStream: TStream): Word;

View File

@ -87,6 +87,8 @@ type
procedure ReadRichString(const AStream: TStream); procedure ReadRichString(const AStream: TStream);
procedure ReadRPNCellAddress(AStream: TStream; out ARow, ACol: Cardinal; procedure ReadRPNCellAddress(AStream: TStream; out ARow, ACol: Cardinal;
out AFlags: TsRelFlags); override; out AFlags: TsRelFlags); override;
procedure ReadRPNCellAddressOffset(AStream: TStream;
out ARowOffset, AColOffset: Integer); override;
procedure ReadRPNCellRangeAddress(AStream: TStream; procedure ReadRPNCellRangeAddress(AStream: TStream;
out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); override; out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); override;
procedure ReadSST(const AStream: TStream); procedure ReadSST(const AStream: TStream);
@ -1479,6 +1481,7 @@ begin
INT_EXCEL_ID_NUMBER : ReadNumber(AStream); INT_EXCEL_ID_NUMBER : ReadNumber(AStream);
INT_EXCEL_ID_LABEL : ReadLabel(AStream); INT_EXCEL_ID_LABEL : ReadLabel(AStream);
INT_EXCEL_ID_FORMULA : ReadFormula(AStream); INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
INT_EXCEL_ID_SHAREDFMLA: ReadSharedFormula(AStream);
INT_EXCEL_ID_STRING : ReadStringRecord(AStream); INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
//(RSTRING) This record stores a formatted text cell (Rich-Text). //(RSTRING) This record stores a formatted text cell (Rich-Text).
// In BIFF8 it is usually replaced by the LABELSST record. Excel still // In BIFF8 it is usually replaced by the LABELSST record. Excel still
@ -1703,6 +1706,24 @@ begin
if (c and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow); if (c and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow);
end; 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. { Reads a cell range address used in an RPN formula element.
Evaluates the corresponding bits to distinguish between absolute and Evaluates the corresponding bits to distinguish between absolute and
relative addresses. relative addresses.

View File

@ -60,6 +60,7 @@ const
INT_EXCEL_ID_MULBLANK = $00BE; // does not exist before BIFF5 INT_EXCEL_ID_MULBLANK = $00BE; // does not exist before BIFF5
INT_EXCEL_ID_XF = $00E0; // BIFF2:$0043, BIFF3:$0243, BIFF4:$0443 INT_EXCEL_ID_XF = $00E0; // BIFF2:$0043, BIFF3:$0243, BIFF4:$0443
INT_EXCEL_ID_RSTRING = $00D6; // does not exist before BIFF5 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 INT_EXCEL_ID_BOF = $0809; // BIFF2:$0009, BIFF3:$0209; BIFF4:$0409
{ FONT record constants } { FONT record constants }
@ -105,6 +106,9 @@ const
INT_EXCEL_TOKEN_TAREA_R = $25; INT_EXCEL_TOKEN_TAREA_R = $25;
INT_EXCEL_TOKEN_TAREA_V = $45; INT_EXCEL_TOKEN_TAREA_V = $45;
INT_EXCEL_TOKEN_TAREA_A = $65; 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 } { Function Tokens }
// _R: reference; _V: value; _A: array // _R: reference; _V: value; _A: array
@ -118,6 +122,9 @@ const
INT_EXCEL_TOKEN_FUNCVAR_V = $42; INT_EXCEL_TOKEN_FUNCVAR_V = $42;
INT_EXCEL_TOKEN_FUNCVAR_A = $62; INT_EXCEL_TOKEN_FUNCVAR_A = $62;
{ Special tokens }
INT_EXCEL_TOKEN_TEXP = $01; // cell belongs to shared formula
{ Built-in/worksheet functions } { Built-in/worksheet functions }
INT_EXCEL_SHEET_FUNC_COUNT = 0; INT_EXCEL_SHEET_FUNC_COUNT = 0;
INT_EXCEL_SHEET_FUNC_IF = 1; INT_EXCEL_SHEET_FUNC_IF = 1;
@ -431,11 +438,15 @@ type
// Read the array of RPN tokens of a formula // Read the array of RPN tokens of a formula
procedure ReadRPNCellAddress(AStream: TStream; out ARow, ACol: Cardinal; procedure ReadRPNCellAddress(AStream: TStream; out ARow, ACol: Cardinal;
out AFlags: TsRelFlags); virtual; out AFlags: TsRelFlags); virtual;
procedure ReadRPNCellAddressOffset(AStream: TStream;
out ARowOffset, AColOffset: Integer); virtual;
procedure ReadRPNCellRangeAddress(AStream: TStream; procedure ReadRPNCellRangeAddress(AStream: TStream;
out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); virtual; out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); virtual;
function ReadRPNFunc(AStream: TStream): Word; 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; function ReadRPNTokenArraySize(AStream: TStream): word; virtual;
procedure ReadSharedFormula(AStream: TStream);
// Helper function for reading a string with 8-bit length // Helper function for reading a string with 8-bit length
function ReadString_8bitLen(AStream: TStream): String; virtual; function ReadString_8bitLen(AStream: TStream): String; virtual;
@ -542,6 +553,7 @@ const
INT_EXCEL_TOKEN_TREFV, {fekCell} INT_EXCEL_TOKEN_TREFV, {fekCell}
INT_EXCEL_TOKEN_TREFR, {fekCellRef} INT_EXCEL_TOKEN_TREFR, {fekCellRef}
INT_EXCEL_TOKEN_TAREA_R, {fekCellRange} INT_EXCEL_TOKEN_TAREA_R, {fekCellRange}
INT_EXCEL_TOKEN_TREFN_V, {fekCellOffset}
INT_EXCEL_TOKEN_TNUM, {fekNum} INT_EXCEL_TOKEN_TNUM, {fekNum}
INT_EXCEL_TOKEN_TINT, {fekInteger} INT_EXCEL_TOKEN_TINT, {fekInteger}
INT_EXCEL_TOKEN_TSTR, {fekString} INT_EXCEL_TOKEN_TSTR, {fekString}
@ -1166,8 +1178,6 @@ var
cell: PCell; cell: PCell;
begin begin
{ BIFF Record header }
{ BIFF Record data }
{ Index to XF Record } { Index to XF Record }
ReadRowColXF(AStream, ARow, ACol, XF); ReadRowColXF(AStream, ARow, ACol, XF);
@ -1230,8 +1240,9 @@ begin
{ Formula token array } { Formula token array }
if FWorkbook.ReadFormulas then begin if FWorkbook.ReadFormulas then begin
ok := ReadRPNTokenArray(AStream, cell^.RPNFormulaValue); ok := ReadRPNTokenArray(AStream, cell);
if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported); if not ok then
FWorksheet.WriteErrorValue(cell, errFormulaNotSupported);
end; end;
{Add attributes} {Add attributes}
@ -1533,6 +1544,24 @@ begin
if (r and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow); if (r and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow);
end; 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 { Reads the cell address used in an RPN formula element. Evaluates the corresponding
bits to distinguish between absolute and relative addresses. bits to distinguish between absolute and relative addresses.
Implemented here for BIFF2-BIFF5. BIFF8 must be overridden. } Implemented here for BIFF2-BIFF5. BIFF8 must be overridden. }
@ -1565,8 +1594,22 @@ begin
Result := WordLEToN(AStream.ReadWord); Result := WordLEToN(AStream.ReadWord);
end; 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; function TsSpreadBIFFReader.ReadRPNTokenArray(AStream: TStream;
var AFormula: TsRPNFormula): Boolean; ACell: PCell): Boolean;
var var
n: Word; n: Word;
p0: Int64; p0: Int64;
@ -1576,6 +1619,7 @@ var
dblVal: Double = 0.0; // IEEE 8 byte floating point number dblVal: Double = 0.0; // IEEE 8 byte floating point number
flags: TsRelFlags; flags: TsRelFlags;
r, c, r2, c2: Cardinal; r, c, r2, c2: Cardinal;
dr, dc: Integer;
fek: TFEKind; fek: TFEKind;
func: Word; func: Word;
b: Byte; b: Byte;
@ -1603,6 +1647,15 @@ begin
ReadRPNCellRangeAddress(AStream, r, c, r2, c2, flags); ReadRPNCellRangeAddress(AStream, r, c, r2, c2, flags);
rpnItem := RPNCellRange(r, c, r2, c2, flags, rpnItem); rpnItem := RPNCellRange(r, c, r2, c2, flags, rpnItem);
end; 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: INT_EXCEL_TOKEN_TMISSARG:
rpnItem := RPNMissingArg(rpnItem); rpnItem := RPNMissingArg(rpnItem);
INT_EXCEL_TOKEN_TSTR: INT_EXCEL_TOKEN_TSTR:
@ -1655,7 +1708,15 @@ begin
end; end;
if not found then if not found then
supported := false; 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 else
found := false; found := false;
@ -1671,11 +1732,11 @@ begin
end; end;
if not supported then begin if not supported then begin
DestroyRPNFormula(rpnItem); DestroyRPNFormula(rpnItem);
SetLength(AFormula, 0); SetLength(ACell^.RPNFormulaValue, 0);
Result := false; Result := false;
end end
else begin 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; Result := true;
end; end;
end; end;
@ -1688,6 +1749,40 @@ begin
Result := WordLEToN(AStream.ReadWord); Result := WordLEToN(AStream.ReadWord);
end; 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 { 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 version for ansistrings since it is valid for all BIFF versions except for
BIFF8 where it has to be overridden. } BIFF8 where it has to be overridden. }