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
This commit is contained in:
wp_xxyyzz
2018-05-11 23:18:10 +00:00
parent e797ae0afd
commit bc685aaeb8
8 changed files with 954 additions and 225 deletions

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;

View File

@@ -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;

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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 }