diff --git a/components/fpspreadsheet/fpsexprparser.pas b/components/fpspreadsheet/fpsexprparser.pas index 779e4c9ac..4eec8de5b 100644 --- a/components/fpspreadsheet/fpsexprparser.pas +++ b/components/fpspreadsheet/fpsexprparser.pas @@ -606,6 +606,8 @@ type FIsRef: Boolean; protected procedure Check; override; + function GetCol: Cardinal; + function GetRow: Cardinal; procedure GetNodeValue(var Result: TsExpressionResult); override; public constructor Create(AParser: TsExpressionParser; AWorksheet: TsWorksheet; @@ -688,6 +690,7 @@ type FDirty: Boolean; FWorksheet: TsWorksheet; FDialect: TsFormulaDialect; + FActiveCell: PCell; procedure CheckEOF; procedure CheckNodes(var ALeft, ARight: TsExprNode); function ConvertNode(Todo: TsExprNode; ToType: TsResultType): TsExprNode; @@ -738,6 +741,8 @@ type function Evaluate: TsExpressionResult; procedure EvaluateExpression(var Result: TsExpressionResult); function ResultType: TsResultType; + function SharedFormulaMode: Boolean; + property AsFloat: TsExprFloat read GetAsFloat; property AsInteger: Int64 read GetAsInteger; property AsString: String read GetAsString; @@ -750,6 +755,7 @@ type property RPNFormula: TsRPNFormula read GetRPNFormula write SetRPNFormula; property Identifiers: TsExprIdentifierDefs read FIdentifiers write SetIdentifiers; property BuiltIns: TsBuiltInExprCategories read FBuiltIns write SetBuiltIns; + property ActiveCell: PCell read FActiveCell write FActiveCell; property Worksheet: TsWorksheet read FWorksheet; property Dialect: TsFormulaDialect read FDialect write FDialect; end; @@ -1974,6 +1980,13 @@ begin if Assigned(FExprNode) then FExprNode.Check; end; +{ Signals that the parser is in SharedFormulaMode, i.e. there is an active cell + to which all relative addresses have to be adapted. } +function TsExpressionParser.SharedFormulaMode: Boolean; +begin + Result := (ActiveCell <> nil) and (ActiveCell^.SharedFormulaBase <> nil); +end; + function TsExpressionParser.TokenType: TsTokenType; begin Result := FScanner.TokenType; @@ -3775,66 +3788,90 @@ end; function TsCellExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; begin if FIsRef then - Result := RPNCellRef(FRow, FCol, FFlags, ANext) + Result := RPNCellRef(GetRow, GetCol, FFlags, ANext) else - Result := RPNCellValue(FRow, FCol, FFlags, ANext); + Result := RPNCellValue(GetRow, GetCol, FFlags, ANext); end; function TsCellExprNode.AsString: string; begin - Result := GetCellString(FRow, FCol, FFlags); + Result := GetCellString(GetRow, GetCol, FFlags); if FParser.Dialect = fdOpenDocument then Result := '[.' + Result + ']'; end; -procedure TsCellExprNode.GetNodeValue(var Result: TsExpressionResult); -begin - if (FCell <> nil) and HasFormula(FCell) then - case FCell^.CalcState of - csNotCalculated: - Worksheet.CalcFormula(FCell); - csCalculating: - raise Exception.Create(SErrCircularReference); - end; - - Result.ResultType := rtCell; - Result.ResRow := FRow; - Result.ResCol := FCol; - Result.Worksheet := FWorksheet; -end; - procedure TsCellExprNode.Check; begin // Nothing to check; end; +{ Calculates the row address of the node's cell for various cases: + (1) SharedFormula mode: + The "ActiveCell" of the parser is the cell for which the formula is + calculated. If the formula contains a relative address in the cell node + the function calculates the row address of the cell represented by the + node as seen from the active cell. + If the formula contains an absolute address the function returns the row + address of the SharedFormulaBase of the ActiveCell. + (2) Normal mode: + Returns the "true" row address of the cell assigned to the formula node. } +function TsCellExprNode.GetCol: Cardinal; +begin + if FParser.SharedFormulaMode then + begin + // A shared formula is stored in the SharedFormulaBase cell of the ActiveCell + // Since the cell data stored in the node are those used by the formula in + // the SharedFormula, the current node is relative to the SharedFormulaBase + if rfRelCol in FFlags then + Result := FCol - FParser.ActiveCell^.SharedFormulaBase^.Col + FParser.ActiveCell^.Col + else + Result := FParser.ActiveCell^.SharedFormulaBase^.Col; + end + else + // Normal mode + Result := FCol; +end; + +procedure TsCellExprNode.GetNodeValue(var Result: TsExpressionResult); +var + cell: PCell; +begin + if Parser.SharedFormulaMode then + cell := FWorksheet.FindCell(GetRow, GetCol) + else + cell := FCell; + + if (cell <> nil) and HasFormula(cell) then + case cell^.CalcState of + csNotCalculated: + Worksheet.CalcFormula(cell); + csCalculating: + raise Exception.Create(SErrCircularReference); + end; + + Result.ResultType := rtCell; + Result.ResRow := GetRow; + Result.ResCol := GetCol; + Result.Worksheet := FWorksheet; +end; + +{ See GetCol } +function TsCellExprNode.GetRow: Cardinal; +begin + if Parser.SharedFormulaMode then + begin + if rfRelRow in FFlags then + Result := FRow - FParser.ActiveCell^.SharedFormulaBase^.Row + FParser.ActiveCell^.Row + else + Result := FParser.ActiveCell^.SharedFormulaBase^.Row; + end + else + Result := FRow; +end; + function TsCellExprNode.NodeType: TsResultType; begin Result := rtCell; - { - if FIsRef then - Result := rtCell - else - begin - Result := rtEmpty; - if FCell <> nil then - case FCell^.ContentType of - cctNumber: - if frac(FCell^.NumberValue) = 0 then - Result := rtInteger - else - Result := rtFloat; - cctDateTime: - Result := rtDateTime; - cctUTF8String: - Result := rtString; - cctBool: - Result := rtBoolean; - cctError: - Result := rtError; - end; - end; - } end; diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas index 6ed34838a..55d531785 100755 --- a/components/fpspreadsheet/fpsopendocument.pas +++ b/components/fpspreadsheet/fpsopendocument.pas @@ -3621,7 +3621,11 @@ begin parser := TsSpreadsheetParser.Create(FWorksheet); try parser.Dialect := fdOpenDocument; - parser.Expression := ACell^.FormulaValue; + if ACell^.SharedFormulaBase <> nil then begin + parser.ActiveCell := ACell; + parser.Expression := ACell^.SharedFormulaBase^.FormulaValue; + end else + parser.Expression := ACell^.FormulaValue; formula := Parser.LocalizedExpression[FPointSeparatorSettings]; finally parser.Free; diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index b45a4cc0b..1314385f6 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -1760,7 +1760,11 @@ begin end; parser := TsSpreadsheetParser.Create(self); try - parser.Expression := ACell^.FormulaValue; + if (ACell^.SharedFormulaBase <> nil) then begin + parser.ActiveCell := ACell; + parser.Expression := ACell^.SharedFormulaBase^.FormulaValue; + end else + parser.Expression := ACell^.FormulaValue; Result := parser.RPNFormula; finally parser.Free; @@ -1798,6 +1802,7 @@ var parser: TsSpreadsheetParser; res: TsExpressionResult; formula: String; + cell: PCell; begin ACell^.CalcState := csCalculating; @@ -1817,6 +1822,20 @@ begin rtDateTime : WriteDateTime(ACell, res.ResDateTime); rtString : WriteUTF8Text(ACell, res.ResString); rtBoolean : WriteBoolValue(ACell, res.ResBoolean); + rtCell : begin + cell := FindCell(res.ResRow, res.ResCol); + if cell = nil then + WriteBlank(ACell) + else + case cell^.ContentType of + cctNumber : WriteNumber(ACell, cell^.NumberValue); + cctDateTime : WriteDateTime(ACell, cell^.DateTimeValue); + cctUTF8String: WriteUTF8Text(ACell, cell^.UTF8StringValue); + cctBool : WriteBoolValue(ACell, cell^.Boolvalue); + cctError : WriteErrorValue(ACell, cell^.ErrorValue); + cctEmpty : WriteBlank(ACell); + end; + end; end; finally parser.Free;