diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index ca662551f..030cd81ab 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -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. diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas index e52ad7301..5693ba9c5 100755 --- a/components/fpspreadsheet/xlsbiff2.pas +++ b/components/fpspreadsheet/xlsbiff2.pas @@ -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; diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index 5bc66092b..f067245f2 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -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. diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index a81dfa606..5b803c51f 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -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. }