From bc685aaeb88756cda7ba418db9cec7f4d9f012a3 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Fri, 11 May 2018 23:18:10 +0000 Subject: [PATCH] fpspreadsheet: Write single-sheet 3d references (e.g., 'Sheet1!A1:B6') to BIFF8 (xls). Issues with reading. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6405 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../source/common/fpsexprparser.pas | 226 ++++++--- .../source/common/fpspreadsheet.pas | 34 +- .../fpspreadsheet/source/common/fpsrpn.pas | 1 + .../fpspreadsheet/source/common/fpstypes.pas | 3 +- .../fpspreadsheet/source/common/fpsutils.pas | 112 ++++- .../fpspreadsheet/source/common/xlsbiff5.pas | 185 +++++-- .../fpspreadsheet/source/common/xlsbiff8.pas | 143 +++--- .../fpspreadsheet/source/common/xlscommon.pas | 475 ++++++++++++++++-- 8 files changed, 954 insertions(+), 225 deletions(-) diff --git a/components/fpspreadsheet/source/common/fpsexprparser.pas b/components/fpspreadsheet/source/common/fpsexprparser.pas index efaa00d89..32d60b481 100644 --- a/components/fpspreadsheet/source/common/fpsexprparser.pas +++ b/components/fpspreadsheet/source/common/fpsexprparser.pas @@ -87,6 +87,7 @@ type TsExpressionResult = record Worksheet : TsWorksheet; + Worksheet2 : TsWorksheet; ResString : String; case ResultType : TsResultType of rtEmpty : (); @@ -114,6 +115,7 @@ type function AsRPNItem(ANext: PRPNItem): PRPNItem; virtual; abstract; function AsString: string; virtual; abstract; procedure Check; virtual; //abstract; + function Has3DLink: Boolean; virtual; function NodeType: TsResultType; virtual; abstract; function NodeValue: TsExpressionResult; property Parser: TsExpressionParser read FParser; @@ -131,6 +133,7 @@ type public constructor Create(AParser: TsExpressionParser; ALeft, ARight: TsExprNode); destructor Destroy; override; + function Has3DLink: Boolean; override; property Left: TsExprNode read FLeft; property Right: TsExprNode read FRight; end; @@ -551,6 +554,7 @@ type function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsString: String; override; procedure Check; override; + function Has3DLink: Boolean; override; property ArgumentNodes: TsExprArgumentArray read FArgumentNodes; property ArgumentParams: TsExprParameterArray read FArgumentParams; end; @@ -579,16 +583,6 @@ type property CallBack: TsExprFunctionEvent read FCallBack; end; - TsSheetNameExprNode = class(TsExprNode) - private - FSheetName: String; - public - constructor Create(AParser: TsExpressionParser; ASheetName: String); - function AsRPNItem(ANext: PRPNItem): PRPNItem; override; - function AsString: string; override; - property SheetName: String read FSheetName; - end; - { TsCellExprNode } TsCellExprNode = class(TsExprNode) private @@ -611,45 +605,40 @@ type function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsString: string; override; procedure Check; override; + function Has3DLink: Boolean; override; function NodeType: TsResultType; override; property Worksheet: TsWorksheet read FWorksheet; end; - (* - { TsSheetCellExprNode } - TsSheetCellExprNode = class(TsBasicCellExprNode) - protected - function GetSheetIndex: Integer; - public - constructor Create(AParser: TsExpressionParser; AWorksheet: TsWorksheet; - ACellString: String); overload; - function AsRPNItem(ANext: PRPNItem): PRPNItem; override; - function AsString: string; override; - end; *) - - { TsCellRangeExprNode } - TsCellRangeIndex = 1..2; TsCellRangeExprNode = class(TsExprNode) private FWorksheet: TsWorksheet; + FWorksheet2: TsWorksheet; FRow: array[TsCellRangeIndex] of Cardinal; FCol: array[TsCellRangeIndex] of Cardinal; FFlags: TsRelFlags; + FonOtherSheet: Boolean; protected function GetCol(AIndex: TsCellRangeIndex): Cardinal; function GetRow(AIndex: TsCellRangeIndex): Cardinal; procedure GetNodeValue(out Result: TsExpressionResult); override; + function GetSheetIndex(AIndex: TscellRangeIndex): Integer; public constructor Create(AParser: TsExpressionParser; AWorksheet: TsWorksheet; - ACellRangeString: String); overload; + ACellRangeString: String; OnOtherSheet: Boolean); overload; constructor Create(AParser: TsExpressionParser; AWorksheet: TsWorksheet; + ARow1,ACol1, ARow2,ACol2: Cardinal; AFlags: TsRelFlags; OnOtherSheet: Boolean); overload; + constructor Create(AParser: TsExpressionParser; AWorksheet1, AWorksheet2: TsWorksheet; + ACellRangeString: String); overload; + constructor Create(AParser: TsExpressionParser; AWorksheet1, AWorksheet2: TsWorksheet; ARow1,ACol1, ARow2,ACol2: Cardinal; AFlags: TsRelFlags); overload; function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsString: String; override; procedure Check; override; + function Has3DLink: Boolean; override; function NodeType: TsResultType; override; property Worksheet: TsWorksheet read FWorksheet; end; @@ -763,6 +752,7 @@ type function CopyMode: Boolean; function Evaluate: TsExpressionResult; procedure EvaluateExpression(out Result: TsExpressionResult); + function Has3DLinks: Boolean; procedure PrepareCopyMode(ASourceCell, ADestCell: PCell); function ResultType: TsResultType; @@ -1681,6 +1671,8 @@ begin Result := TsConstExprNode.CreateString(self, CurrentToken) else if (TokenType = ttCell) then Result := TsCellExprNode.Create(self, FWorksheet, CurrentToken, false) + else if (TokenType = ttCellRange) then + Result := TsCellRangeExprNode.Create(self, FWorksheet, CurrentToken, false) else if (TokenType = ttSheetName) then begin sheetName := CurrentToken; GetToken; @@ -1694,11 +1686,9 @@ begin sheet := FWorksheet.WorkBook.GetWorksheetByName(sheetName); if sheet = nil then sheet := FWorksheet.Workbook.AddWorksheet(sheetName, true); - Result := TsCellRangeExprNode.Create(self, sheet, CurrentToken); + Result := TsCellRangeExprNode.Create(self, sheet, sheet, CurrentToken); end; end - else if (TokenType = ttCellRange) then - Result := TsCellRangeExprNode.Create(self, FWorksheet, CurrentToken) else if (TokenType = ttError) then Result := TsConstExprNode.CreateError(self, CurrentToken) else if not (TokenType in [ttIdentifier]) then @@ -1821,6 +1811,11 @@ begin Result := BuildStringFormula(AFormatSettings); end; +function TsExpressionParser.Has3DLinks: Boolean; +begin + Result := FExprNode.Has3DLink; +end; + procedure TsExpressionParser.SetDialect(const AValue: TsFormulaDialect); begin if FDialect = AValue then exit; @@ -1887,7 +1882,7 @@ procedure TsExpressionParser.SetRPNFormula(const AFormula: TsRPNFormula); ID: TsExprIdentifierDef; i, n: Integer; args: TsExprArgumentArray; - sheet: TsWorksheet; + sheet, sheet2: TsWorksheet; begin if AIndex < 0 then exit; @@ -1930,7 +1925,16 @@ procedure TsExpressionParser.SetRPNFormula(const AFormula: TsRPNFormula); r2 := AFormula[AIndex].Row2; c2 := AFormula[AIndex].Col2; flags := AFormula[AIndex].RelFlags; - ANode := TsCellRangeExprNode.Create(self, FWorksheet, r, c, r2, c2, flags); + idx := AFormula[AIndex].Sheet; + if idx = -1 then + ANode := TsCellRangeExprNode.Create(self, FWorksheet, r, c, r2, c2, flags, false) + else begin + sheet := FWorksheet.Workbook.GetWorksheetByIndex(idx); + idx := AFormula[AIndex].Sheet2; + if idx = -1 then sheet2 := sheet + else sheet2 := FWorksheet.Workbook.GetWorksheetByIndex(idx); + ANode := TsCellRangeExprNode.Create(self, sheet, sheet2, r,c, r2,c2, flags); + end; dec(AIndex); end; fekNum: @@ -2588,6 +2592,11 @@ begin Result := false; end; +function TsExprNode.Has3DLink: Boolean; +begin + Result := false; +end; + function TsExprNode.NodeValue: TsExpressionResult; begin GetNodeValue(Result); @@ -2633,6 +2642,11 @@ begin inherited Destroy; end; +function TsBinaryOperationExprNode.Has3DLink: Boolean; +begin + Result := FLeft.Has3DLink or FRight.Has3DLink; +end; + function TsBinaryOperationExprNode.HasError(out AResult: TsExpressionResult): Boolean; begin Result := Left.HasError(AResult) or Right.HasError(AResult); @@ -3575,6 +3589,15 @@ begin end; end; +function TsFunctionExprNode.Has3DLink: Boolean; +var + i : Integer; +begin + for i := 0 to Length(FArgumentParams)-1 do + if FArgumentNodes[i].Has3DLink then exit(true); + Result := false; +end; + { TsFunctionCallBackExprNode } @@ -3611,7 +3634,7 @@ begin FCallBack(Result, FArgumentParams); end; - + (* { TsSheetNameExprNode } constructor TsSheetNameExprNode.Create(AParser: TsExpressionParser; ASheetName: string); @@ -3629,7 +3652,7 @@ function TsSheetnameExprNode.AsString: string; begin Result := ''; end; - + *) { TsCellExprNode } @@ -3761,6 +3784,11 @@ begin Result := book.GetWorksheetIndex(FWorksheet); end; +function TsCellExprNode.Has3DLink: Boolean; +begin + Result := FOtherSheet; +end; + function TsCellExprNode.NodeType: TsResultType; begin Result := rtCell; @@ -3836,7 +3864,7 @@ end; { TsCellRangeExprNode } constructor TsCellRangeExprNode.Create(AParser: TsExpressionParser; - AWorksheet: TsWorksheet; ACellRangeString: String); + AWorksheet: TsWorksheet; ACellRangeString: String; OnOtherSheet: Boolean); var r1, c1, r2, c2: Cardinal; flags: TsRelFlags; @@ -3846,16 +3874,17 @@ begin ParseCellString(ACellRangeString, r1, c1, flags); if rfRelRow in flags then Include(flags, rfRelRow2); if rfRelCol in flags then Include(flags, rfRelCol2); - Create(AParser, AWorksheet, r1, c1, r1, c1, flags); + Create(AParser, AWorksheet, r1, c1, r1, c1, flags, OnOtherSheet); end else begin ParseCellRangeString(ACellRangeString, r1, c1, r2, c2, flags); - Create(AParser, AWorksheet, r1, c1, r2, c2, flags); + Create(AParser, AWorksheet, r1, c1, r2, c2, flags, OnOtherSheet); end; end; constructor TsCellRangeExprNode.Create(AParser: TsExpressionParser; - AWorksheet: TsWorksheet; ARow1,ACol1,ARow2,ACol2: Cardinal; AFlags: TsRelFlags); + AWorksheet: TsWorksheet; ARow1,ACol1,ARow2,ACol2: Cardinal; + AFlags: TsRelFlags; OnOtherSheet: Boolean); begin FParser := AParser; FWorksheet := AWorksheet; @@ -3864,27 +3893,73 @@ begin FRow[2] := ARow2; FCol[2] := ACol2; FFlags := AFlags; + FOnOtherSheet := OnOtherSheet; +end; + +constructor TsCellRangeExprNode.Create(AParser: TsExpressionParser; + AWorksheet1, AWorksheet2: TsWorksheet; ACellRangeString: String); +var + r1, c1, r2, c2: Cardinal; + flags: TsRelFlags; +begin + if pos(':', ACellRangeString) = 0 then + begin + ParseCellString(ACellRangeString, r1, c1, flags); + if rfRelRow in flags then Include(flags, rfRelRow2); + if rfRelCol in flags then Include(flags, rfRelCol2); + Create(AParser, AWorksheet1, AWorksheet2, r1, c1, r1, c1, flags); + end else + begin + ParseCellRangeString(ACellRangeString, r1, c1, r2, c2, flags); + Create(AParser, AWorksheet1, AWorksheet2, r1, c1, r2, c2, flags); + end; +end; + +constructor TsCellRangeExprNode.Create(AParser: TsExpressionParser; + AWorksheet1, AWorksheet2: TsWorksheet; ARow1,ACol1,ARow2,ACol2: Cardinal; + AFlags: TsRelFlags); +begin + FParser := AParser; + FWorksheet := AWorksheet1; + FWorksheet2 := AWorksheet2; + FRow[1] := ARow1; + FCol[1] := ACol1; + FRow[2] := ARow2; + FCol[2] := ACol2; + FFlags := AFlags; + FOnOtherSheet := true; end; function TsCellRangeExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; begin - Result := RPNCellRange(GetRow(1), GetCol(1), GetRow(2), GetCol(2), FFlags, ANext); + if FOnOtherSheet then + Result := RPNCellRange3D( + GetSheetIndex(1), GetRow(1), GetCol(1), + GetSheetIndex(2), GetRow(2), GetCol(2), + FFlags, ANext + ) + else + Result := RPNCellRange(GetRow(1), GetCol(1), GetRow(2), GetCol(2), FFlags, ANext); end; function TsCellRangeExprNode.AsString: string; var - r, c: Array[TsCellRangeIndex] of Cardinal; - i: TsCellRangeIndex; + r1, c1, r2, c2: Cardinal; + s1, s2: String; begin - for i in TsCellRangeIndex do - begin - r[i] := GetRow(i); - c[i] := GetCol(i); + r1 := GetRow(1); r2 := GetRow(2); + c1 := GetCol(1); c2 := GetCol(2); + s1 := FWorksheet.Name; s2 := FWorksheet2.Name; + + case FParser.Dialect of + fdExcelA1: + Result := GetCellRangeString(s1, s2, r1, c1, r2, c2, FFlags, true); + fdExcelR1C1: + Result := GetCellRangeString_R1C1(s1, s2, r1, c1, r2, c2, FFlags, + FParser.FSourceCell^.Row, FParser.FSourceCell^.Col); + fdOpenDocument: + Result := GetCellRangeString_ODS(s1, s2, r1, c1, r2, c2, FFlags); end; - if (r[1] = r[2]) and (c[1] = c[2]) then - Result := GetCellString(r[1], r[1], FFlags) - else - Result := GetCellRangeString(r[1], c[1], r[2], c[2], FFlags); end; procedure TsCellRangeExprNode.Check; @@ -3911,28 +3986,38 @@ end; procedure TsCellRangeExprNode.GetNodeValue(out Result: TsExpressionResult); var - r, c: Array[TsCellRangeIndex] of Cardinal; - rr, cc: Cardinal; + r, c, s: Array[TsCellRangeIndex] of Integer; + rr, cc, ss: Integer; i: TsCellRangeIndex; cell: PCell; + book: TsWorkbook; + sheet: TsWorksheet; begin for i in TsCellRangeIndex do begin r[i] := GetRow(i); c[i] := GetCol(i); + s[i] := GetSheetIndex(i); + end; + if not FOnOtherSheet then s[2] := s[1]; + + book := FWorksheet.Workbook; + + for ss := s[1] to s[2] do begin + sheet := book.GetWorksheetByIndex(ss); + for rr := r[1] to r[2] do + for cc := c[1] to c[2] do + begin + cell := sheet.FindCell(rr, cc); + if HasFormula(cell) then + case sheet.GetCalcState(cell) of + csNotCalculated: + sheet.CalcFormula(cell); + csCalculating: + raise ECalcEngine.Create(rsCircularReference); + end; + end; end; - for rr := r[1] to r[2] do - for cc := c[1] to c[2] do - begin - cell := FWorksheet.FindCell(rr, cc); - if HasFormula(cell) then - case FWorksheet.GetCalcState(cell) of - csNotCalculated: - FWorksheet.CalcFormula(cell); - csCalculating: - raise ECalcEngine.Create(rsCircularReference); - end; - end; Result.ResultType := rtCellRange; Result.ResCellRange.Row1 := r[1]; @@ -3940,6 +4025,7 @@ begin Result.ResCellRange.Row2 := r[2]; Result.ResCellRange.Col2 := c[2]; Result.Worksheet := FWorksheet; + Result.Worksheet2 := FWorksheet2; end; function TsCellRangeExprNode.GetRow(AIndex: TsCellRangeIndex): Cardinal; @@ -3949,6 +4035,24 @@ begin Result := FRow[AIndex] - FParser.FSourceCell^.Row + FParser.FDestCell^.Row; end; +function TsCellRangeExprNode.GetSheetIndex(AIndex: TsCellRangeIndex): Integer; +var + book: TsWorkbook; + sheet: TsWorksheet; +begin + case AIndex of + 1: sheet := FWorksheet; + 2: sheet := FWorksheet2; + end; + book := sheet.Workbook; + Result := book.GetWorksheetIndex(sheet); +end; + +function TsCellRangeExprNode.Has3DLink: Boolean; +begin + Result := FOnOtherSheet; +end; + function TsCellRangeExprNode.NodeType: TsResultType; begin Result := rtCellRange; diff --git a/components/fpspreadsheet/source/common/fpspreadsheet.pas b/components/fpspreadsheet/source/common/fpspreadsheet.pas index 9c81c84fb..4ab10e38f 100644 --- a/components/fpspreadsheet/source/common/fpspreadsheet.pas +++ b/components/fpspreadsheet/source/common/fpspreadsheet.pas @@ -803,7 +803,8 @@ type function GetWorksheetByIndex(AIndex: Integer): TsWorksheet; function GetWorksheetByName(AName: String): TsWorksheet; function GetWorksheetCount: Integer; - function GetWorksheetIndex(AWorksheet: TsWorksheet): Integer; + function GetWorksheetIndex(AWorksheet: TsWorksheet): Integer; overload; + function GetWorksheetIndex(const AWorksheetName: String): Integer; overload; procedure RemoveAllWorksheets; procedure RemoveAllEmptyWorksheets; procedure RemoveWorksheet(AWorksheet: TsWorksheet); @@ -4180,6 +4181,7 @@ begin Result := soProtected in FOptions; end; + {@@ ---------------------------------------------------------------------------- Setter for the worksheet name property. Checks if the name is valid, and exits without any change if not. Creates an event OnChangeWorksheet. @@ -5727,15 +5729,19 @@ begin if (AFormula <> '') and (AFormula[1] = '=') then AFormula := Copy(AFormula, 2, Length(AFormula)); - // Convert "localized" formula to standard format - if ALocalized then begin - parser := TsSpreadsheetParser.Create(self); - try + parser := TsSpreadsheetParser.Create(self); + try + if ALocalized then begin + // Convert "localized" formula to standard format parser.LocalizedExpression[Workbook.FormatSettings] := AFormula; AFormula := parser.Expression; - finally - parser.Free; - end; + end else + parser.Expression := AFormula; + if parser.Has3DLinks + then ACell.Flags := ACell.Flags + [cf3dFormula] + else ACell.Flags := ACell.Flags - [cf3dFormula]; + finally + parser.Free; end; end; @@ -9001,6 +9007,18 @@ begin Result := FWorksheets.IndexOf(AWorksheet); end; +{@@ ---------------------------------------------------------------------------- + Returns the index of the worksheet having the specified name, or -1 if the + worksheet does not exist. +-------------------------------------------------------------------------------} +function TsWorkbook.GetWorksheetIndex(const AWorksheetName: String): Integer; +begin + for Result := 0 to FWorksheets.Count-1 do + if TsWorksheet(FWorksheets[Result]).Name = AWorksheetName then + exit; + Result := -1; +end; + {@@ ---------------------------------------------------------------------------- Clears the list of Worksheets and releases their memory. diff --git a/components/fpspreadsheet/source/common/fpsrpn.pas b/components/fpspreadsheet/source/common/fpsrpn.pas index 8468ef95d..a5da39096 100644 --- a/components/fpspreadsheet/source/common/fpsrpn.pas +++ b/components/fpspreadsheet/source/common/fpsrpn.pas @@ -87,6 +87,7 @@ function NewRPNItem: PRPNItem; begin New(Result); FillChar(Result^.FE, SizeOf(Result^.FE), 0); + Result^.FE.Sheet2 := -1; Result^.FE.StringValue := ''; Result^.FE.SheetNames := ''; end; diff --git a/components/fpspreadsheet/source/common/fpstypes.pas b/components/fpspreadsheet/source/common/fpstypes.pas index f72e48f0c..4abcabded 100644 --- a/components/fpspreadsheet/source/common/fpstypes.pas +++ b/components/fpspreadsheet/source/common/fpstypes.pas @@ -585,7 +585,8 @@ type TsCalcState = (csNotCalculated, csCalculating, csCalculated); {@@ Cell flag } - TsCellFlag = (cfCalculating, cfCalculated, cfHasComment, cfHyperlink, cfMerged); + TsCellFlag = (cfCalculating, cfCalculated, cfHasComment, cfHyperlink, cfMerged, + cf3dFormula); {@@ Set of cell flags } TsCellFlags = set of TsCellFlag; diff --git a/components/fpspreadsheet/source/common/fpsutils.pas b/components/fpspreadsheet/source/common/fpsutils.pas index 1fcdce9be..b906ae389 100644 --- a/components/fpspreadsheet/source/common/fpsutils.pas +++ b/components/fpspreadsheet/source/common/fpsutils.pas @@ -81,6 +81,8 @@ function ParseSheetCellString(const AStr: String; out ASheetName: String; function ParseCellRowString(const AStr: string; out ARow: Cardinal): Boolean; function ParseCellColString(const AStr: string; out ACol: Cardinal): Boolean; +function GetCellRangeString(ASheet1, ASheet2: String; ARow1, ACol1, ARow2, ACol2: Cardinal; + AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false): String; overload; function GetCellRangeString(ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false): String; overload; function GetCellRangeString(ARange: TsCellRange; @@ -99,7 +101,21 @@ function ParseCellString_R1C1(const AStr: string; ABaseRow, ABaseCol: Cardinal; out ACellRow, ACellCol: Cardinal): Boolean; overload; function GetCellString_R1C1(ARow, ACol: Cardinal; AFlags: TsRelFlags = [rfRelRow, rfRelCol]; - ARefRow: Cardinal = Cardinal(-1); ARefCol: Cardinal = Cardinal(-1)): String; + ARefRow: Cardinal = Cardinal(-1); ARefCol: Cardinal = Cardinal(-1)): String; overload; +function GetCellRangeString_R1C1(ARow1, ACol1, ARow2, ACol2: Cardinal; + AFlags: TsRelFlags = [rfRelRow, rfRelCol]; + ARefRow: Cardinal = Cardinal(-1); ARefCol: Cardinal = Cardinal(-1)): String; overload; +function GetCellRangeString_R1C1(ASheet1, ASheet2: String; + ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags = [rfRelRow, rfRelCol]; + ARefRow: Cardinal = Cardinal(-1); ARefCol: Cardinal = Cardinal(-1)): String; overload; + + // OpenDocument Syntax +function GetCellRangeString_ODS(ASheet1, ASheet2: String; ARow1, ACol1, ARow2, ACol2: Cardinal; + AFlags: TsRelFlags = rfAllRel): String; overload; +function GetCellRangeString_ODS(ARow1, ACol1, ARow2, ACol2: Cardinal; + AFlags: TsRelFlags = rfAllRel): String; overload; +function GetCellRangeString_ODS(ARange: TsCellRange; + AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false): String; overload; // Error strings @@ -985,6 +1001,36 @@ begin Result := Result + 'C' + IntToStr(LongInt(ACol)+1); end; +function GetCellRangeString_R1C1(ARow1, ACol1, ARow2, ACol2: Cardinal; + AFlags: TsRelFlags = [rfRelRow, rfRelCol]; + ARefRow: Cardinal = Cardinal(-1); ARefCol: Cardinal = Cardinal(-1)): String; +var + s1, s2: String; +begin + s1 := GetCellString_R1C1(ARow1, ACol1, AFlags, ARefRow, ARefCol); + s2 := GetCellString_R1C1(ARow2, ACol2, AFlags, ARefRow, ARefCol); + if s1 = s2 then + Result := s1 + else + Result := Format('%s:%s', [s1, s2]); +end; + +function GetCellRangeString_R1C1(ASheet1, ASheet2: String; + ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags = [rfRelRow, rfRelCol]; + ARefRow: Cardinal = Cardinal(-1); ARefCol: Cardinal = Cardinal(-1)): String; +var + s: String; +begin + s := GetCellRangeString_R1C1(ARow1, ACol1, ARow2, ACol2, AFlags, ARefRow, ARefCol); + if (ASheet1 = '') and (ASheet2 = '') then + Result := s + else if (ASheet2 = '') or (ASheet1 = ASheet2) then + Result := Format('%s!%s', [ASheet1, s]) + else + Result := Format('%s:%s!%s', [ASheet1, ASheet2, s]); +end; + + {@@ ---------------------------------------------------------------------------- Calculates a cell range address string from zero-based column and row indexes @@ -1019,6 +1065,23 @@ begin ]); end; +function GetCellRangeString(ASheet1, ASheet2: String; ARow1, ACol1, ARow2, ACol2: Cardinal; + AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false): String; +var + s: String; +begin + s := GetCellRangeString(ARow1, ACol1, ARow2, ACol2, AFlags, Compact); + if (ASheet1 = '') and (ASheet2 = '') then + Result := s + else if ASheet2 = '' then + Result := Format('%s!%s', [ASheet1, s]) + else if Compact and (ASheet1 = ASheet2) then + Result := Format('%s!%s', [ASheet1, s]) + else + Result := Format('%s:%s!%s', [ASheet1, ASheet2, s]); +end; + + {@@ ---------------------------------------------------------------------------- Calculates a cell range address string from a TsCellRange record and the relative address state flags. @@ -1041,6 +1104,53 @@ begin AFlags, Compact); end; + +{@@ ---------------------------------------------------------------------------- + Calculates a cell range string with sheet specification in OpenDocument syntax +-------------------------------------------------------------------------------} +function GetCellRangeString_ODS(ASheet1, ASheet2: String; + ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags = rfAllRel): String; +var + s1, s2: String; +begin + s1 := Format('%s%s%s%s', [ + RELCHAR[rfRelCol in AFlags], GetColString(ACol1), + RELCHAR[rfRelRow in AFlags], ARow1 + 1 + ]); + s2 := Format('%s%s%s%s', [ + RELCHAR[rfRelCol2 in AFlags], GetColString(ACol2), + RELCHAR[rfRelRow2 in AFlags], ARow2 + 1 + ]); + + if (ASheet1 = '') and (ASheet2 = '') then + begin + if s1 = s2 then + Result := s1 + else + Result := Format('%s:%s', [s1, s2]) + end else + if (ASheet2 = '') or (ASheet1 = ASheet2) then begin + if s1 = s2 then + Result := Format('%s.%s', [ASheet1, s1]) + else + Result := Format('%s.%s:.%s', [ASheet1, s1, s2]); // Sheet1.A1:.B2 + end else + Result := Format('%s.%s:%s.%s', [ASheet1, s1, ASheet2, s2]); // Sheet.A1:Sheet2.B2 +end; + +function GetCellRangeString_ODS(ARow1, ACol1, ARow2, ACol2: Cardinal; + AFlags: TsRelFlags = rfAllRel): String; +begin + Result := GetCellRangeString(ARow1, ACol1, ARow2, ACol2, AFlags, true); +end; + +function GetCellRangeString_ODS(ARange: TsCellRange; + AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false): String; +begin + Result := GetCellRangeString(ARange, AFlags, true); +end; + + {@@ ---------------------------------------------------------------------------- Returns the error value code from a string. Result is false, if the string does not match one of the predefined error strings. diff --git a/components/fpspreadsheet/source/common/xlsbiff5.pas b/components/fpspreadsheet/source/common/xlsbiff5.pas index fb88302e1..95f9c21e4 100644 --- a/components/fpspreadsheet/source/common/xlsbiff5.pas +++ b/components/fpspreadsheet/source/common/xlsbiff5.pas @@ -79,8 +79,9 @@ type procedure ReadFONT(const AStream: TStream); procedure ReadFORMAT(AStream: TStream); override; procedure ReadLABEL(AStream: TStream); override; - function ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; override; - procedure ReadRPNSheetIndex(AStream: TStream; out ASheet1, ASheet2: Integer); override; +// function ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; override; + procedure ReadRPNSheetIndex(AStream: TStream; out ADocumentURL: String; + out ASheet1, ASheet2: Integer); override; procedure ReadRSTRING(AStream: TStream); procedure ReadStandardWidth(AStream: TStream; ASheet: TsWorksheet); procedure ReadStringRecord(AStream: TStream); override; @@ -115,9 +116,13 @@ type procedure WriteIndex(AStream: TStream); procedure WriteLABEL(AStream: TStream; const ARow, ACol: Cardinal; const AValue: string; ACell: PCell); override; - procedure WriteLocalLinkTable(AStream: TStream); + procedure WriteLocalLinkTable(AStream: TStream; AWorksheet: TsWorksheet); + (* function WriteRPNCellAddress3D(AStream: TStream; ASheet, ARow, ACol: Cardinal; AFlags: TsRelFlags): Word; override; + *) + function WriteRPNSheetIndex(AStream: TStream; ADocumentURL: String; + ASheet1, ASheet2: Integer): Word; override; procedure WriteStringRecord(AStream: TStream; AString: String); override; procedure WriteStyle(AStream: TStream); procedure WriteWindow2(AStream: TStream; ASheet: TsWorksheet); @@ -608,7 +613,7 @@ begin FixCols(FWorksheet); FixRows(FWorksheet); end; - + { function TsSpreadBIFF5Reader.ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; var @@ -639,34 +644,54 @@ begin if r2 = $FFFF then r2 := Cardinal(-1); if c2 = $FF then c2 := Cardinal(-1); ARPNItem := RPNCellRange3D(sheetIndex1, r1, c1, sheetIndex2, r2, c2, flags, ARPNItem); -end; +end; } procedure TsSpreadBIFF5Reader.ReadRPNSheetIndex(AStream: TStream; - out ASheet1, ASheet2: Integer); + out ADocumentURL: String; out ASheet1, ASheet2: Integer); var idx: Int16; + s: String; begin - // One-based index to EXTERNSHEET record. Negative to indicate a 3D reference. - // Positive to indicate an external reference + ADocumentURL := ''; + ASheet1 := -1; + ASheet2 := -1; + + { One-based index to EXTERNSHEET record. Negative to indicate a 3D reference. + Positive to indicate an external reference } idx := WordLEToN(AStream.ReadWord); - // We don't support external references at the moment. - if idx > 0 then begin - ASheet1 := -1; - ASheet1 := -1; - exit; + if idx < 0 then begin + { *** Internal 3d reference *** } + + // Skip 8 unused bytes + AStream.Position := AStream.Position + 8; + + // Zero-based index to first referenced sheet (-1 = deleted sheet) + idx := Int16(WordLEToN(AStream.ReadWord)); + if idx <> -1 then begin + s := FExternSheets.Strings[idx]; + ASheet1 := FWorkbook.GetWorksheetIndex(s); + end; + + // Zero-based index to last referenced sheet (-1 = deleted sheet) + idx := WordLEToN(AStream.ReadWord); + if idx <> -1 then begin + s := FExternSheets.Strings[idx]; + ASheet2 := FWorkbook.GetWorksheetIndex(s); + end; + end + else begin + { *** External reference *** } + + // Skip 12 unused byes + AStream.Position := AStream.Position + 12; + + dec(idx); // 1-based index to 0-based index + s := FExternSheets[idx]; + ADocumentURL := s; + + // NOTE: THIS IS NOT COMPLETE !!! end; - - // Skip 8 unused bytes - AStream.Position := AStream.Position + 8; - - // Zero-based index to first referenced sheet (-1 = deleted sheet) - idx := WordLEToN(AStream.ReadWord); - ASheet1 := idx; - - // Zero-based index to last referenced sheet (-1 = deleted sheet) - idx := WordLEToN(AStream.ReadWord); - ASheet2 := idx; end; procedure TsSpreadBIFF5Reader.ReadRSTRING(AStream: TStream); @@ -1172,7 +1197,6 @@ var pane: Byte; begin { Write workbook globals } - WriteBOF(AStream, INT_BOF_WORKBOOK_GLOBALS); WriteCODEPAGE(AStream, FCodePage); @@ -1180,8 +1204,6 @@ begin WritePROTECT(AStream, bpLockStructure in Workbook.Protection); WritePASSWORD(AStream, Workbook.CryptoInfo); WriteGlobalLinkTable(AStream); -// WriteEXTERNCOUNT(AStream); -// WriteEXTERNSHEET(AStream); WriteDefinedNames(AStream); WriteWINDOW1(AStream); WriteFonts(AStream); @@ -1229,7 +1251,7 @@ begin WritePageSetup(AStream); // Local link table - WriteLocalLinkTable(AStream); + WriteLocalLinkTable(AStream, FWorksheet); // Protection if FWorksheet.IsProtected then begin @@ -1750,7 +1772,7 @@ begin WriteEXTERNCOUNT(AStream, L.Count); for i:=0 to L.Count-1 do - WriteEXTERNSHEET(AStream, L[i]); + WriteEXTERNSHEET(AStream, L[i], true); finally L.Free; end; @@ -1883,38 +1905,46 @@ end; Writes a local Link Table with EXTERNCOUNT and EXTERSHEET records for internal 3D references to other sheets -------------------------------------------------------------------------------} -procedure TsSpreadBIFF5Writer.WriteLocalLinkTable(AStream: TStream); +procedure TsSpreadBIFF5Writer.WriteLocalLinkTable(AStream: TStream; + AWorksheet: TsWorksheet); var L: TStringList; cell: PCell; found: Boolean; - i, n: Integer; + i, j, n: Integer; + sheetref: PsBIFFExternSheet; + book: TsBIFFExternBook; + sheet: TsWorksheet; begin - L := TStringList.Create; - try - // Check whether there is any cell with a formula with 3D reference - found := false; - for cell in FWorksheet.Cells do - if HasFormula(cell) and (pos('!', cell^.FormulaValue) <> 0) then begin - found := true; - break; - end; - // Write every sheet to local link table. This is too much - it would be - // enough to write only those sheets involved in a 3d reference - but it - // simplifies processing of 3d references a lot. - if found then begin - n := FWorkbook.GetWorksheetCount; - if n > 0 then begin - WriteEXTERNCOUNT(AStream, word(n)); - for i := 0 to n-1 do - WriteEXTERNSHEET(AStream, FWorkbook.GetWorksheetByIndex(i).Name); + CollectExternData(FWorksheet); + if (FExternBooks = nil) or (FExternSheets = nil) then + exit; + + // Write the count of records in the local link table + n := FExternSheets.Count; + WriteEXTERNCOUNT(AStream, word(n)); + + // Write a EXTERNSHEET record for each linked sheet + for i := 0 to n-1 do begin + sheetref := FExternSheets[i]; + book := FExternBooks[sheetref^.ExternBookIndex]; + if book.Kind = ebkInternal then + begin + for j := sheetref^.FirstSheetIndex to sheetref^.LastSheetIndex do + begin + sheet := FWorkbook.GetWorksheetByIndex(j); + if sheet = AWorksheet then + WriteEXTERNSHEET(AStream, '', true) + else + WriteEXTERNSHEET(AStream, sheet.Name, true); end; + end else + begin + // Handle external links here end; - finally - L.Free; end; end; - + (* {@@ ---------------------------------------------------------------------------- Writes a 3D cell address consisting of worksheet, row and column indexes. Needed for references to other worksheets within the same workbook. @@ -1947,6 +1977,57 @@ begin Result := AStream.Position - p; end; +*) + +{@@ ---------------------------------------------------------------------------- + Writes the sheet indexes of a 3D cell address consisting. + Needed for references to other worksheets within the same workbook. + Must be followed by WriteRPNCellAddress or WriteRPNCellAddressRange. + Returns the number of bytes written or $FFFF if the feature is not supported. +-------------------------------------------------------------------------------} +function TsSpreadBIFF5Writer.WriteRPNSheetIndex(AStream: TStream; + ADocumentURL: String; ASheet1, ASheet2: Integer): Word; +var + p: Int64; + bookidx: Integer; + book: TsBIFFExternBook; + refidx: Integer; + sheetref: PsBIFFExternSheet; +begin + if ADocumentURL <> '' then // Supporting only internal links + exit; + + p := AStream.Position; + + // Find stored information on this link + bookidx := FExternBooks.FindBook(ADocumentURL); + refidx := FExternSheets.FindSheets(ADocumentURL, ASheet1, ASheet2); + sheetref := FExternSheets[refidx]; + book := FExternBooks[sheetRef^.ExternBookIndex]; + if book.Kind = ebkExternal then + exit($FFFF); + + // One-based index of the EXTERNBOOK record to which this reference belongs. + // For internal references ("3D references") this must be written as a + // negative value. + AStream.WriteWord(WordToLE(word(-(bookidx+1)))); + + // 8 unused bytes + AStream.WriteDWord(0); + AStream.WriteDWord(0); + + // Zero-based index to first referenced sheet (FFFFH = deleted sheet) + AStream.WriteWord(WordToLE(ASheet1)); + + // Single sheet reference + if ASheet2 < 0 then ASheet2 := ASheet1; + + // Zero-based index to last referenced sheet (FFFFH = deleted sheet) + AStream.WriteWord(WordToLE(ASheet2)); + + Result := AStream.Position - p; +end; + {@@ ---------------------------------------------------------------------------- Writes an Excel 5 STRING record which immediately follows a FORMULA record diff --git a/components/fpspreadsheet/source/common/xlsbiff8.pas b/components/fpspreadsheet/source/common/xlsbiff8.pas index 45c014023..dbe9e8192 100644 --- a/components/fpspreadsheet/source/common/xlsbiff8.pas +++ b/components/fpspreadsheet/source/common/xlsbiff8.pas @@ -64,21 +64,6 @@ uses fpsutils; type - TExternBookKind = (ebkExternal, ebkInternal, ebkAddInFunc, ebkDDE_OLE); - - TBIFF8ExternBook = class - Kind: TExternBookKind; - DocumentURL: String; - SheetNames: String; - function GetSheetName(AIndex: Integer): String; - end; - - TBIFF8ExternSheet = packed record - ExternBookIndex: Word; - FirstSheetIndex: Word; - LastSheetIndex: Word; - end; - { TsSpreadBIFF8Reader } TsSpreadBIFF8Reader = class(TsSpreadBIFFReader) private @@ -89,7 +74,7 @@ type FCommentID: Integer; FCommentLen: Integer; FBiff8ExternBooks: TFPObjectList; - FBiff8ExternSheets: array of TBiff8ExternSheet; + FBiff8ExternSheets: array of TsBiffExternSheet; function ReadString(const AStream: TStream; const ALength: Word; out ARichTextParams: TsRichTextParams): String; function ReadUnformattedWideString(const AStream: TStream; @@ -104,7 +89,7 @@ type procedure ReadCONTINUE(const AStream: TStream); procedure ReadDEFINEDNAME(const AStream: TStream); procedure ReadEXTERNBOOK(const AStream: TStream); - procedure ReadEXTERNSHEET(const AStream: TStream); + procedure ReadEXTERNSHEET(const AStream: TStream); virtual; procedure ReadFONT(const AStream: TStream); procedure ReadFORMAT(AStream: TStream); override; procedure ReadHeaderFooter(AStream: TStream; AIsHeader: Boolean); override; @@ -122,11 +107,12 @@ type out ARowOffset, AColOffset: Integer; out AFlags: TsRelFlags); override; procedure ReadRPNCellRangeAddress(AStream: TStream; out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); override; - function ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; override; +// function ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; override; procedure ReadRPNCellRangeOffset(AStream: TStream; out ARow1Offset, ACol1Offset, ARow2Offset, ACol2Offset: Integer; out AFlags: TsRelFlags); override; - procedure ReadRPNSheetIndex(AStream: TStream; out ASheet1, ASheet2: Integer); override; + procedure ReadRPNSheetIndex(AStream: TStream; out ADocumentURL: String; + out ASheet1, ASheet2: Integer); override; procedure ReadRSTRING(AStream: TStream); procedure ReadSST(const AStream: TStream); function ReadString_8bitLen(AStream: TStream): String; override; @@ -203,12 +189,16 @@ type out AContinueInString: Boolean): Boolean; function WriteRPNCellAddress(AStream: TStream; ARow, ACol: Cardinal; AFlags: TsRelFlags): word; override; + (* function WriteRPNCellAddress3D(AStream: TStream; ASheet, ARow, ACol: Cardinal; AFlags: TsRelFlags): Word; override; + *) function WriteRPNCellOffset(AStream: TStream; ARowOffset, AColOffset: Integer; AFlags: TsRelFlags): Word; override; function WriteRPNCellRangeAddress(AStream: TStream; ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags): Word; override; + function WriteRPNSheetIndex(AStream: TStream; ADocumentURL: String; + ASheet1, ASheet2: Integer): Word; override; procedure WriteSST(AStream: TStream); function WriteString_8bitLen(AStream: TStream; AString: String): Integer; override; procedure WriteSTRINGRecord(AStream: TStream; AString: string); override; @@ -506,27 +496,6 @@ begin end; -{ TBiff8ExternBook } - -function TBiff8ExternBook.GetSheetName(AIndex: Integer): String; -var - L: TStrings; -begin - L := TStringList.Create; - try - L.Delimiter := #1; - L.StrictDelimiter := true; - L.DelimitedText := SheetNames; - if (AIndex >= 0) and (AIndex < L.Count) then - Result := L[AIndex] - else - Result := ''; - finally - L.Free; - end; -end; - - { TsSpreadBIFF8Reader } constructor TsSpreadBIFF8Reader.Create(AWorkbook: TsWorkbook); @@ -1295,7 +1264,7 @@ begin if (c2 and MASK_EXCEL_RELATIVE_COL <> 0) then Include(AFlags, rfRelCol2); if (c2 and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow2); end; - + { function TsSpreadBIFF8Reader.ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; var @@ -1317,7 +1286,7 @@ begin sheetIndex2, r2, c2, flags, ARPNItem ); -end; +end;} { Reads the difference between row and column corner indexes of a cell range and a reference cell. @@ -1351,26 +1320,28 @@ begin end; procedure TsSpreadBIFF8Reader.ReadRPNSheetIndex(AStream: TStream; - out ASheet1, ASheet2: Integer); + out ADocumentURL: String; out ASheet1, ASheet2: Integer); var refIndex: Word; - ref: TBiff8ExternSheet; - extbook: TBiff8ExternBook; + ref: TsBiffExternSheet; + book: TsBiffExternBook; begin // Index to REF entry in EXTERNSHEET record refIndex := WordLEToN(AStream.ReadWord); ref := FBiff8ExternSheets[refIndex]; - extBook := FBiff8ExternBooks[ref.ExternBookIndex] as TBiff8ExternBook; + book := FBiff8ExternBooks[ref.ExternBookIndex] as TsBiffExternBook; // Only links to internal sheets supported so far. - if extBook.Kind <> ebkInternal then + if book.Kind <> ebkInternal then begin + ADocumentURL := ''; ASheet1 := -1; ASheet2 := -1; exit; end; + ADocumentURL := book.DocumentURL; ASheet1 := ref.FirstSheetIndex; ASheet2 := ref.LastSheetIndex; end; @@ -1870,14 +1841,14 @@ var i, n: Integer; url: widestring; sheetnames: widestring; - externbook: TBiff8Externbook; + book: TsBiffExternbook; p: Int64; t: array[0..1] of byte = (0, 0); begin if FBiff8ExternBooks = nil then - FBiff8ExternBooks := TFPObjectList.Create(true); + FBiff8ExternBooks := TsBIFFExternBookList.Create(true); - externBook := TBiff8ExternBook.Create; + book := TsBiffExternBook.Create; // Count of sheets in book n := WordLEToN(AStream.ReadWord); @@ -1886,22 +1857,23 @@ begin p := AStream.Position; AStream.ReadBuffer(t[0], 2); if (t[0] = 1) and (t[1] = 4) then - externbook.Kind := ebkInternal + book.Kind := ebkInternal else if (t[0] = 1) and (t[1] = $3A) then - externbook.Kind := ebkAddInFunc + book.Kind := ebkAddInFunc else if n = 0 then - externbook.Kind := ebkDDE_OLE + book.Kind := ebkDDE_OLE else - externbook.Kind := ebkExternal; + book.Kind := ebkExternal; - if (externbook.Kind = ebkExternal) then + { External workbook } + if (book.Kind = ebkExternal) then begin AStream.Position := p; // Encoded URL without sheet name (Unicode string, 16bit string length) url := ReadWideString(AStream, false); - externbook.DocumentURL := UTF8Encode(url); + book.DocumentURL := UTF8Encode(url); if n = 0 then sheetnames := '' @@ -1911,10 +1883,10 @@ begin for i := 2 to n do sheetnames := sheetnames + widechar(#1) + ReadWideString(AStream, false); end; - externbook.SheetNames := UTF8Encode(sheetNames); + book.SheetNames := UTF8Encode(sheetNames); end; - FBiff8ExternBooks.Add(externbook); + FBiff8ExternBooks.Add(book); end; { Reads an EXTERNSHEET record. Needed for 3d-references, named cells and @@ -2352,6 +2324,8 @@ var i: Integer; pane: Byte; begin + CollectExternData; + { Write workbook globals } WriteBOF(AStream, INT_BOF_WORKBOOK_GLOBALS); WriteCodePage(AStream, 'ucs2le'); // = utf-16 @@ -2874,7 +2848,41 @@ end; procedure TsSpreadBIFF8Writer.WriteEXTERNSHEET(AStream: TStream); var n, i: Integer; + sheetRef: PsBIFFExternSheet; + book: TsBIFFExternBook; begin + if (FExternSheets = nil) or (FExternBooks = nil) then + exit; + + { Count the following REF structures } + { We support only internal links. Once external links are supported the + following code probably can be dropped. } + n := 0; + for i := 0 to FExternSheets.Count-1 do begin + sheetRef := FExternSheets[i]; + book := FExternBooks[sheetRef^.ExternBookIndex]; + if (book.Kind = ebkInternal) then inc(n); + end; + + { BIFF record header } + WriteBIFFHeader(AStream, INT_EXCEL_ID_EXTERNSHEET, 2 + 6*n); + + { Write the determined count of REF structures } + AStream.WriteWord(WordToLE(n)); + + for i:= 0 to FExternSheets.Count-1 do begin + sheetRef := FExternSheets[i]; + book := FExternBooks[sheetRef^.ExternBookIndex]; + if (book.Kind = ebkInternal) then + begin + AStream.WriteWord(WordToLE(sheetRef^.ExternBookIndex)); + AStream.WriteWord(WordToLE(sheetRef^.FirstSheetIndex)); + AStream.WriteWord(WordToLE(sheetRef^.LastSheetIndex)); + end; + end; +end; + + (* { Since sheet range are not supported we simply note every sheet here. } n := FWorkbook.GetWorksheetCount; @@ -2892,6 +2900,7 @@ begin AStream.WriteWord(WordToLE(i)); // Index to last sheet in EXTERNBOOK sheet list end; end; +*) (* write a record for @@ -3741,7 +3750,7 @@ begin AStream.WriteWord(WordToLE(c)); Result := 4; end; - + (* function TsSpreadBIFF8Writer.WriteRPNCellAddress3D(AStream: TStream; ASheet, ARow, ACol: Cardinal; AFlags: TsRelFlags): Word; begin @@ -3753,7 +3762,7 @@ begin // Write row/column address Result := 2 + WriteRPNCellAddress(AStream, ARow, ACol, AFlags); end; - + *) {@@ ---------------------------------------------------------------------------- Writes row and column offset needed in RPN formulas (unsigned integers!) @@ -3803,6 +3812,20 @@ begin Result := 8; end; +function TsSpreadBIFF8Writer.WriteRPNSheetIndex(AStream: TStream; + ADocumentURL: String; ASheet1, ASheet2: Integer): Word; +var + idx: Integer; +begin + idx := FExternSheets.FindSheets(ADocumentURL, ASheet1, ASheet2); + if idx = -1 then + Result := $FFFE + else begin + AStream.WriteWord(WordToLE(word(idx))); + Result := 2; + end; +end; + { Writes a buffer containing a string (with header) and its associated rich-text parameters to an EXCEL record. Since the size in an EXCEL8 record is limited to 8224 bytes diff --git a/components/fpspreadsheet/source/common/xlscommon.pas b/components/fpspreadsheet/source/common/xlscommon.pas index f44d69adb..4ac03fbce 100644 --- a/components/fpspreadsheet/source/common/xlscommon.pas +++ b/components/fpspreadsheet/source/common/xlscommon.pas @@ -10,7 +10,7 @@ OpenOffice Microsoft Excel File Format document } interface uses - Classes, SysUtils, DateUtils, lconvencoding, + Classes, SysUtils, DateUtils, lconvencoding, contnrs, fpsTypes, fpSpreadsheet, fpsUtils, fpsNumFormat, fpsPalette, fpsReaderWriter, fpsrpn; @@ -379,6 +379,53 @@ type property ValidOnSheet: Integer read FValidOnSheet; end; + { TsExternBook - Information on where out-of-sheet references are stored. } + TsBIFFExternBookKind = (ebkExternal, ebkInternal, ebkAddInFunc, ebkDDE_OLE); + TsBIFFExternBook = class + Kind: TsBIFFExternBookKind; + // The following fields are used only for external workbooks. + DocumentURL: String; + SheetNames: String; // List of worksheetnames separated by #1 + function GetWorksheetName(AIndex: Integer): String; + end; + + { TsBIFFExternBookList } + TsBIFFExternBookList = class(TFPObjectlist) + private + function GetItem(AIndex: Integer): TsBIFFExternBook; + procedure SetItem(AIndex: Integer; AValue: TsBIFFExternBook); + public + function AddBook(ABookName: String): Integer; + function AddInternal: Integer; + function FindBook(ABookName: String): Integer; + function FindInternalBook: Integer; + property Item[AIndex: Integer]: TsBIFFExternBook read GetItem write SetItem; default; + end; + + { TsExternSheet - Information on a sheets used in out-of-sheet references } + TsBIFFExternSheet = packed record + ExternBookIndex: Word; + FirstSheetIndex: Word; + LastSheetIndex: Word; + end; + PsBIFFExternSheet = ^TsBIFFExternSheet; + + { A list for sheets used in out-of-sheet references } + TsBIFFExternSheetList = class(TFPList) + private + FBookList: TsBIFFExternBookList; + function GetItem(AIndex: Integer): PsBIFFExternSheet; + procedure SetItem(AIndex: Integer; AValue: PsBIFFExternSheet); + public + constructor Create(ABookList: TsBIFFExternBookList); + destructor Destroy; override; + function AddSheets(ABookName: String; ASheetIndex1, ASheetIndex2: Integer): Integer; + procedure Clear; + function FindSheets(ABookName: String; ASheetIndex1, ASheetIndex2: Integer): Integer; + property Item[AIndex: Integer]: PsBIFFExternSheet read GetItem write SetItem; default; + end; + + { TsSpreadBIFFReader } TsSpreadBIFFReader = class(TsCustomSpreadReader) protected @@ -435,8 +482,10 @@ type procedure ReadDefColWidth(AStream: TStream); // Read the default row height procedure ReadDefRowHeight(AStream: TStream); + // Read an EXTERNCOUNT record + procedure ReadEXTERNCOUNT(AStream: TStream); // Read an EXTERNSHEET record (defined names) - procedure ReadExternSheet(AStream: TStream); + procedure ReadEXTERNSHEET(AStream: TStream); virtual; // Read FORMAT record (cell formatting) procedure ReadFormat(AStream: TStream); virtual; // Read FORMULA record @@ -478,13 +527,14 @@ type out ARowOffset, AColOffset: Integer; out AFlags: TsRelFlags); virtual; procedure ReadRPNCellRangeAddress(AStream: TStream; out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); virtual; - function ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; virtual; +// function ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; virtual; procedure ReadRPNCellRangeOffset(AStream: TStream; out ARow1Offset, ACol1Offset, ARow2Offset, ACol2Offset: Integer; out AFlags: TsRelFlags); virtual; function ReadRPNFunc(AStream: TStream): Word; virtual; procedure ReadRPNSharedFormulaBase(AStream: TStream; out ARow, ACol: Cardinal); virtual; - procedure ReadRPNSheetIndex(AStream: TStream; out ASheet1, ASheet2: Integer); virtual; + procedure ReadRPNSheetIndex(AStream: TStream; out ADocumentURL: String; + out ASheet1, ASheet2: Integer); virtual; function ReadRPNTokenArray(AStream: TStream; ACell: PCell; ASharedFormulaBase: PCell = nil): Boolean; overload; function ReadRPNTokenArray(AStream: TStream; ARpnTokenArraySize: Word; @@ -525,8 +575,11 @@ type FCodePage: String; // in a format prepared for lconvencoding.ConvertEncoding FFirstNumFormatIndexInFile: Integer; FPalette: TsPalette; + FExternBooks: TsBIFFExternBookList; + FExternSheets: TsBIFFExternSheetList; procedure AddBuiltinNumFormats; override; + procedure CollectExternData(AWorksheet: TsWorksheet = nil); function FindXFIndex(AFormatIndex: Integer): Integer; virtual; function FixLineEnding(const AText: String): String; function FormulaSupported(ARPNFormula: TsRPNFormula; out AUnsupported: String): Boolean; @@ -574,7 +627,8 @@ type // Writes out an EXTERNCOUNT record procedure WriteEXTERNCOUNT(AStream: TStream; ACount: Word); // Writes out an EXTERNSHEET record - procedure WriteEXTERNSHEET(AStream: TStream; ASheetName: String); + procedure WriteEXTERNSHEET(AStream: TStream; ASheetName: String; + IsInternalSheet: Boolean); // Writes out a FORMAT record procedure WriteFORMAT(AStream: TStream; ANumFormatStr: String; ANumFormatIndex: Integer); virtual; @@ -611,8 +665,10 @@ type function WriteRPNCellAddress(AStream: TStream; ARow, ACol: Cardinal; AFlags: TsRelFlags): Word; virtual; + (* function WriteRPNCellAddress3D(AStream: TStream; ASheet, ARow, ACol: Cardinal; {%H-}AFlags: TsRelFlags): Word; virtual; + *) function WriteRPNCellOffset(AStream: TStream; ARowOffset, AColOffset: Integer; AFlags: TsRelFlags): Word; virtual; function WriteRPNCellRangeAddress(AStream: TStream; ARow1, ACol1, ARow2, ACol2: Cardinal; @@ -621,6 +677,8 @@ type AFormula: TsRPNFormula; ACell: PCell); virtual; function WriteRPNFunc(AStream: TStream; AIdentifier: Word): Word; virtual; procedure WriteRPNResult(AStream: TStream; ACell: PCell); + function WriteRPNSheetIndex(AStream: TStream; ADocumentURL: String; + ASheet1, ASheet2: Integer): word; virtual; procedure WriteRPNTokenArray(AStream: TStream; ACell: PCell; AFormula: TsRPNFormula; UseRelAddr, IsSupported: Boolean; var RPNLength: Word); procedure WriteRPNTokenArraySize(AStream: TStream; ASize: Word); virtual; @@ -900,6 +958,196 @@ begin end; +{ -----------------------------------------------------------------------------} +{ TsBIFFExternBook } +{ -----------------------------------------------------------------------------} +function TsBIFFExternBook.GetWorksheetName(AIndex: Integer): String; +var + L: TStrings; +begin + Result := ''; + if Kind = ebkExternal then begin + L := TStringList.Create; + try + L.Delimiter := #1; + L.DelimitedText := SheetNames; + Result := L[AIndex]; + finally + L.Free; + end; + end; +end; + + +{------------------------------------------------------------------------------} +{ TsBIFFExternBookList } +{------------------------------------------------------------------------------} +function TsBIFFExternBookList.AddBook(ABookName: String): Integer; +var + book: TsBIFFExternBook; +begin + if ABookName = '' then + Result := AddInternal + else + begin + Result := FindBook(ABookName); + if Result = -1 then begin + book := TsBIFFExternBook.Create; + book.DocumentURL := ABookName; + book.Kind := ebkExternal; + Result := Add(book); + end; + end; +end; + +function TsBIFFExternBookList.AddInternal: Integer; +var + book: TsBIFFExternBook; +begin + Result := FindInternalBook; + if Result = -1 then begin + book := TsBIFFExternBook.Create; + book.Kind := ebkInternal; + Result := Add(book); + end; +end; + +function TsBIFFExternBookList.FindBook(ABookName: String): Integer; +var + book: TsBIFFExternBook; +begin + if ABookName = '' then + Result := FindInternalBook + else + begin + for Result:=0 to Count-1 do + begin + book := Item[Result]; + if (book.Kind = ebkExternal) and (book.DocumentURL = ABookName) then + exit; + end; + Result := -1; + end; +end; + +function TsBIFFExternBookList.FindInternalBook: Integer; +begin + for Result := 0 to Count-1 do + if Item[Result].Kind = ebkInternal then exit; + Result := -1; +end; + +function TsBIFFExternBookList.GetItem(AIndex: Integer): TsBIFFExternBook; +begin + Result := TsBIFFExternBook(inherited Items[AIndex]); +end; + +procedure TsBIFFExternBookList.SetItem(AIndex: Integer; + AValue: TsBIFFExternBook); +begin + inherited Items[AIndex] := AValue; +end; + + +{------------------------------------------------------------------------------} +{ TsBiffExternSheetList } +{------------------------------------------------------------------------------} +constructor TsBIFFExternSheetList.Create(ABookList: TsBIFFExternBookList); +begin + inherited Create; + FBookList := ABookList; +end; + +destructor TsBIFFExternSheetList.Destroy; +begin + Clear; + inherited; +end; + +function TsBIFFExternSheetList.AddSheets(ABookName: String; + ASheetIndex1, ASheetIndex2: Integer): Integer; +var + P: PsBIFFExternSheet; + idx: Integer; +begin + Result := FindSheets(ABookName, ASheetIndex1, ASheetIndex2); + if Result = -1 then + begin + New(P); + idx := FBookList.FindBook(ABookName); + if idx = -1 then + idx := FBookList.AddBook(ABookName); + P^.ExternBookIndex := idx; + + if ASheetIndex2 = -1 then + ASheetIndex2 := ASheetIndex1; + if ASheetIndex2 < ASheetIndex1 then + begin + P^.FirstSheetIndex := ASheetIndex2; + P^.LastSheetIndex := ASheetIndex1; + end else + begin + P^.FirstSheetIndex := ASheetIndex1; + P^.LastSheetIndex := ASheetIndex2; + end; + Result := Add(P); + end; +end; + +procedure TsBIFFExternSheetList.Clear; +var + i: Integer; + P: PsBIFFExternSheet; +begin + for i:=0 to Count-1 do begin + P := Item[i]; + Dispose(P); + end; + inherited; +end; + +function TsBIFFExternSheetList.FindSheets(ABookName: String; + ASheetIndex1, ASheetIndex2: Integer): Integer; +var + book: TsBIFFExternBook; + P: PsBIFFExternSheet; + tmp: Integer; + idx: Integer; +begin + if ASheetIndex2 = -1 then ASheetIndex2 := ASheetIndex1; + if ASheetIndex2 < ASheetIndex1 then begin + tmp := ASheetIndex1; + ASheetIndex1 := ASheetIndex2; + ASheetIndex2 := tmp; + end; + + idx := FBookList.FindBook(ABookName); + if idx = -1 then + exit(-1); + + for Result:=0 to Count-1 do begin + P := Item[Result]; + if (P^.ExternBookIndex = idx) and + (P^.FirstSheetIndex = ASheetIndex1) and + (P^.LastSheetIndex = ASheetIndex2) + then + exit; + end; + Result := -1; +end; + +function TsBIFFExternSheetList.GetItem(AIndex: Integer): PsBIFFExternSheet; +begin + Result := PsBIFFExternSheet(inherited Items[AIndex]); +end; + +procedure TsBIFFExternSheetList.SetItem(AIndex: Integer; AValue: PsBIFFExternSheet); +begin + inherited Items[AIndex] := AValue; +end; + + + {------------------------------------------------------------------------------} { TsBIFFDefinedName } {------------------------------------------------------------------------------} @@ -983,7 +1231,6 @@ begin FCellFormatList := TsCellFormatList.Create(true); // true = allow duplicates! XF indexes get out of sync if not all format records are in list - FExternSheets := TStringList.Create; FDefinedNames := TFPList.Create; // Initial base date in case it won't be read from file @@ -1610,6 +1857,14 @@ begin FWorksheet.WriteDefaultRowHeight(TwipsToPts(hw), suPoints); end; +procedure TsSpreadBIFFReader.ReadEXTERNCOUNT(AStream: TStream); +begin + AStream.ReadWord; + { We ignore the value of the record, but use the presence of the record + to create the FExternSheets list. } + FExternSheets := TStringList.Create; +end; + {@@ ---------------------------------------------------------------------------- In the file format versions up to BIFF5 (incl) this record stores the name of an external document and a sheet name inside of this document. @@ -1634,7 +1889,7 @@ begin AStream.ReadBuffer(ansistr[2], len-1); ansistr[1] := char(b); s := ConvertEncoding(ansistr, FCodePage, encodingUTF8); - FExternSheets.Add(s); + FExternSheets.AddObject(s, TObject(PtrInt(b))); end; {@@ ---------------------------------------------------------------------------- @@ -2399,13 +2654,14 @@ begin if (r2 and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow2); end; +{ function TsSpreadBIFFReader.ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; begin Unused(AStream, ARPNItem); Result := false; // "false" means: "not supported" // must be overridden -end; +end; } {@@ ---------------------------------------------------------------------------- Reads the difference between row and column corner indexes of a cell range @@ -2475,9 +2731,10 @@ end; in a formula from the current stream position. Place holder - must be overridden! } procedure TsSpreadBIFFReader.ReadRPNSheetIndex(AStream: TStream; - out ASheet1, ASheet2: Integer); + out ADocumentURL: String; out ASheet1, ASheet2: Integer); begin Unused(AStream); + ADocumentURL := ''; ASheet1 := -1; ASheet2 := -1; end; @@ -2678,6 +2935,7 @@ var b: Byte; found: Boolean; sheet1, sheet2: Integer; + url: String; begin rpnItem := nil; p0 := AStream.Position; @@ -2730,28 +2988,53 @@ begin end; INT_EXCEL_TOKEN_TREF3D_V: begin - ReadRPNSheetIndex(AStream, sheet1, sheet2); - ReadRPNCellAddress(AStream, r, c, flags); - rpnItem := RpnCellValue3D(sheet1, r, c, flags, rpnItem); + ReadRPNSheetIndex(AStream, url, sheet1, sheet2); + if url <> '' then begin + supported := false; + FWorkbook.AddErrorMsg('External links are not supported.'); + end else begin + ReadRPNCellAddress(AStream, r, c, flags); + rpnItem := RpnCellValue3D(sheet1, r, c, flags, rpnItem); + end; end; INT_EXCEL_TOKEN_TREF3D_R: //, INT_EXCEL_TOKEN_TREF3d_V: begin - ReadRPNSheetIndex(AStream, sheet1, sheet2); - ReadRPNCellAddressOffset(AStream, dr, dc, flags); - if (rfRelRow in flags) - then r := LongInt(ACell^.Row) + dr - else r := dr; - if (rfRelCol in flags) - then c := LongInt(ACell^.Col) + dc - else c := dc; - case token of - INT_EXCEL_TOKEN_TREF3D_V: rpnItem := RpnCellValue3D(sheet1, r, c, flags, rpnItem); - INT_EXCEL_TOKEN_TREF3D_R: rpnItem := RpnCellRef3D(sheet1, r, c, flags, rpnItem); + ReadRPNSheetIndex(AStream, url, sheet1, sheet2); + if url <> '' then begin + supported := false; + FWorkbook.AddErrorMsg('External links are not supported.'); + end else + begin + ReadRPNCellAddressOffset(AStream, dr, dc, flags); // Is this usage of rel addresses correct? + if (rfRelRow in flags) + then r := LongInt(ACell^.Row) + dr + else r := dr; + if (rfRelCol in flags) + then c := LongInt(ACell^.Col) + dc + else c := dc; + case token of + INT_EXCEL_TOKEN_TREF3D_V: rpnItem := RpnCellValue3D(sheet1, r, c, flags, rpnItem); + INT_EXCEL_TOKEN_TREF3D_R: rpnItem := RpnCellRef3D(sheet1, r, c, flags, rpnItem); + end; end; end; INT_EXCEL_TOKEN_TAREA3D_R: begin - if not ReadRPNCellRange3D(AStream, rpnItem) then supported := false; + ReadRPNSheetIndex(AStream, url, sheet1, sheet2); + if url <> '' then begin + supported := false; + FWorkbook.AddErrorMsg('External links are not supported.'); + end else + if (sheet1 = -1) and (sheet2 = -1) then + supported := false + else + begin + ReadRPNCellRangeAddress(AStream, r, c, r2, c2, flags); + if r2 = $FFFF then r2 := Cardinal(-1); + if c2 = $FF then c2 := Cardinal(-1); + rpnItem := RPNCellRange3D(sheet1, r, c, sheet2, r2, c2, flags, rpnItem); + end; +// was: if not ReadRPNCellRange3D(AStream, rpnItem) then supported := false; end; INT_EXCEL_TOKEN_TREFN_A: begin @@ -3155,6 +3438,8 @@ end; destructor TsSpreadBIFFWriter.Destroy; begin + FExternSheets.Free; + FExternBooks.Free; FPalette.Free; inherited Destroy; end; @@ -3186,6 +3471,70 @@ begin end; end; +{@@ ---------------------------------------------------------------------------- + Collects the data for out-of-sheet links found in the specified worksheet + (or all worksheets if the parameter is omitted). + The found data are written to the FExternBooks and FExternSheets lists. +-------------------------------------------------------------------------------} +procedure TsSpreadBIFFWriter.CollectExternData(AWorksheet: TsWorksheet = nil); + + procedure DoCollectForSheet(ASheet: TsWorksheet); + var + cell: PCell; + parser: TsExpressionParser; + rpn: TsRPNFormula; + fe: TsFormulaElement; + j: Integer; + begin + for cell in ASheet.Cells do + begin + if not HasFormula(cell) then + Continue; + if not (cf3dFormula in cell^.Flags) then + Continue; + + parser := TsSpreadsheetParser.Create(ASheet); + try + parser.Expression := cell^.FormulaValue; + rpn := parser.RPNFormula; + for j:=0 to High(rpn) do + begin + fe := rpn[j]; + if fe.ElementKind in [fekCell3d, fekCellRef3d, fekCellRange3d] then + FExternSheets.AddSheets('', fe.Sheet, fe.Sheet2); // '' --> supporting only internal 3d links so far + end; + finally + parser.Free; + rpn := nil; + end; + end; + end; + +var + sheet: TsWorksheet; + i: Integer; +begin + FExternBooks.Free; + FExternBooks := TsBIFFExternBookList.Create; + FExternSheets.Free; + FExternSheets := TsBIFFExternSheetList.Create(FExternBooks); + + if AWorksheet <> nil then + DoCollectForSheet(AWorksheet) + else + for i:=0 to FWorkbook.GetWorksheetCount-1 do + begin + sheet := FWorkbook.GetWorksheetbyIndex(i); + DoCollectForSheet(sheet); + end; + + if FExternSheets.Count = 0 then begin + FreeAndNil(FExternSheets); + FreeAndNil(FExternBooks); + end; +end; + + {@@ ---------------------------------------------------------------------------- Determines the index of the XF record, according to formatting of the given format index; this format index is taken from a cell, row, or column @@ -3752,24 +4101,38 @@ end; Valid for BIFF2-BIFF5. -------------------------------------------------------------------------------} procedure TsSpreadBIFFWriter.WriteEXTERNSHEET(AStream: TStream; - ASheetName: String); + ASheetName: String; IsInternalSheet: Boolean); var s: ansistring; + n: Byte; begin // Convert to ANSI s := ConvertEncoding(ASheetName, encodingUTF8, FCodePage); + n := Length(s); { BIFF record header } WriteBIFFHeader(AStream, INT_EXCEL_ID_EXTERNSHEET, 2 + Length(s)); - { Character count in worksheet name - don't count the following flag! } - AStream.WriteByte(Length(s)); - - { Flag for identification as own sheet } - AStream.WriteByte($03); - - { Sheet name } - AStream.WriteBuffer(s[1], Length(s)); + if IsInternalSheet then + begin + if ASheetName = '' then + begin + // The current worksheet is linked: no sheet name to be written! + AStream.WriteByte(1); // Length info + AStream.WriteByte(2); // code for current worksheet + end else + begin + // Another internal worksheet is links - code 3 followed by sheet name + // Note: Code is not cosidered in the string length! + AStream.WriteByte(n); // Length byte + AStream.WriteByte(3); // code byte + AStream.WriteBuffer(s[1], n); // Sheet name characters + end; + end else + begin + // External sheets + // Not suppored at the moment - they have code 2 + end; end; (* procedure TsSpreadBIFFWriter.WriteEXTERNSHEET(AStream: TStream); @@ -4360,7 +4723,7 @@ begin // Number of bytes written Result := 3; end; - + (* {@ ----------------------------------------------------------------------------- Writes the address of a cell as used in an RPN formula and returns the count of bytes written. The return value is $FFFF if 3D addresses are not @@ -4373,7 +4736,7 @@ begin Unused(AStream, ASheet); Unused(ARow, ACol); Result := 0; -end; +end;*) {@@ ---------------------------------------------------------------------------- Writes row and column offset (unsigned integers!) @@ -4575,6 +4938,20 @@ begin end; *) +{@@ ---------------------------------------------------------------------------- + Writew the first and last worksheet indexes of a 3D cell range. + Must be followed by WriteRPNCellAddress or WriteRPNCellAddressRange. + Returns the number of bytes written, or $FFFF if 3D references are not + supported. + Valid for BIFF2 only (which does not support 3D refences). Must be overridden + for BIFF5 and BIFF8. +-------------------------------------------------------------------------------} +function TsSpreadBIFFWriter.WriteRPNSheetIndex(AStream: TStream; + ADocumentURL: String; ASheet1, ASheet2: Integer): Word; +begin + Result := $FFFF; // --> not supported by default. +end; + {@@ ---------------------------------------------------------------------------- Writes the token array of the given RPN formula and returns its size -------------------------------------------------------------------------------} @@ -4656,16 +5033,13 @@ begin INT_EXCEL_TOKEN_TREF3D_V: { fekCell3D } begin - n := WriteRPNCellAddress3D( - AStream, - AFormula[i].Sheet, - AFormula[i].Row, AFormula[i].Col, - AFormula[i].RelFlags - ); + n := WriteRPNSheetIndex(AStream, '', AFormula[i].Sheet, AFormula[i].Sheet2); if n = $FFFF then FWorkbook.AddErrorMsg('3D cell addresses are not supported.') - else + else begin + inc(n, WriteRPNCellAddress(AStream, AFormula[i].Row, AFormula[i].Col, AFormula[i].RelFlags)); inc(RPNLength, n); + end; end; INT_EXCEL_TOKEN_TAREA_R: { fekCellRange } @@ -4679,6 +5053,23 @@ begin inc(RPNLength, n); end; + INT_EXCEL_TOKEN_TAREA3D_R: { fekCellRange3D } + begin + n := WriteRPNSheetIndex(AStream, '', AFormula[i].Sheet, AFormula[i].Sheet2); + if n = $FFFF then + FWorkbook.AddErrorMsg('3D cell address ranges are not supported.') + else if n = $FFF3 then + FWorkbook.AddErrorMsg('Sheets not found in LinkTable.') + else begin + inc(n, WriteRPNCellRangeAddress(AStream, + AFormula[i].Row, AFormula[i].Col, + AFormula[i].Row2, AFormula[i].Col2, + AFormula[i].RelFlags) + ); + inc(RPNLength, n); + end; + end; + INT_EXCEL_TOKEN_TREFN_R, INT_EXCEL_TOKEN_TREFN_V, INT_EXCEL_TOKEN_TREFN_A: { fekCellOffset }