diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas
index bbb924600..a104ee11a 100755
--- a/components/fpspreadsheet/fpspreadsheet.pas
+++ b/components/fpspreadsheet/fpspreadsheet.pas
@@ -678,7 +678,8 @@ type
{ Formulas }
procedure CalcFormulas;
function ReadRPNFormulaAsString(ACell: PCell): String;
- function UseSharedFormula(ARow, ACol: Cardinal; ASharedFormulaBase: PCell): PCell;
+ function UseSharedFormula(ARow, ACol: Cardinal; ASharedFormulaBase: PCell): PCell; overload;
+ procedure UseSharedFormula(ACellRangeStr: String; ASharedFormulaBase: PCell); overload;
{ Data manipulation methods - For Cells }
procedure CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal; AFromWorksheet: TsWorksheet);
@@ -2956,6 +2957,33 @@ begin
[GetCellString(ARow, ACol)]);
end;
+{@@
+ Uses the formula defined in cell "ASharedFormulaBase" as a shared formula in
+ all cells of the given cell range.
+
+ @param ACellRangeStr Range of cells which will use the shared formula.
+ The range is given as a string in Excel notation,
+ such as A1:B5, or A1
+ @param ASharedFormulaBase Cell containing the formula to be shared
+}
+procedure TsWorksheet.UseSharedFormula(ACellRangeStr: String; ASharedFormulaBase: PCell);
+var
+ r, c, r1, c1, r2, c2: Cardinal;
+ ok: Boolean;
+begin
+ if pos(':', ACellRangeStr) = 0 then
+ begin
+ ok := ParseCellString(ACellRangeStr, r1, c1);
+ r2 := r1;
+ c2 := c1;
+ end else
+ ok := ParseCellRangeString(ACellRangeStr, r1, c1, r2, c2);
+ if ok then
+ for r := r1 to r2 do
+ for c := c1 to c2 do
+ UseSharedFormula(r, c, ASharedFormulaBase);
+end;
+
{@@
Writes UTF-8 encoded text to a cell.
diff --git a/components/fpspreadsheet/fpsutils.pas b/components/fpspreadsheet/fpsutils.pas
index e11f63887..a1fc9f826 100644
--- a/components/fpspreadsheet/fpsutils.pas
+++ b/components/fpspreadsheet/fpsutils.pas
@@ -64,7 +64,9 @@ function ParseIntervalString(const AStr: string;
out ADirection: TsSelectionDirection): Boolean;
function ParseCellRangeString(const AStr: string;
out AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Cardinal;
- out AFlags: TsRelFlags): Boolean;
+ out AFlags: TsRelFlags): Boolean; overload;
+function ParseCellRangeString(const AStr: string;
+ out AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Cardinal): Boolean; overload;
function ParseCellString(const AStr: string;
out ACellRow, ACellCol: Cardinal; out AFlags: TsRelFlags): Boolean; overload;
function ParseCellString(const AStr: string;
@@ -76,7 +78,8 @@ function ParseCellColString(const AStr: string;
function GetColString(AColIndex: Integer): String;
function GetCellString(ARow,ACol: Cardinal; AFlags: TsRelFlags = [rfRelRow, rfRelCol]): String;
-function GetCellRangeString(ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags): String;
+function GetCellRangeString(ARow1, ACol1, ARow2, ACol2: Cardinal;
+ AFlags: TsRelFlags = [rfRelRow, rfRelCol, rfRelRow2, rfRelCol2]): String;
function GetErrorValueStr(AErrorValue: TsErrorValue): String;
@@ -427,6 +430,26 @@ begin
end;
+{@@
+ Parses strings like A5:C10 into a range selection information.
+ Information on relative/absolute cells is ignored.
+
+ @param AStr Cell range string, such as A5:C10
+ @param AFirstCellRow Row index of the top/left cell of the range (output)
+ @param AFirstCellCol Column index of the top/left cell of the range (output)
+ @param ALastCellRow Row index of the bottom/right cell of the range (output)
+ @param ALastCellCol Column index of the bottom/right cell of the range (output)
+ @return false if the string is not a valid cell range
+}
+function ParseCellRangeString(const AStr: string;
+ out AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Cardinal): Boolean;
+var
+ flags: TsRelFlags;
+begin
+ Result := ParseCellRangeString(AStr, AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol, flags);
+end;
+
+
{@@
Parses a cell string, like 'A1' into zero-based column and row numbers
Note that there can be several letters to address for more than 26 columns.
@@ -647,7 +670,8 @@ end;
@example ARow1 = 0, ACol1 = 0, ARow = 2, ACol = 1, AFlags = [rfRelRow, rfRelRow2]
--> $A1:$B3
}
-function GetCellRangeString(ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags): String;
+function GetCellRangeString(ARow1, ACol1, ARow2, ACol2: Cardinal;
+ AFlags: TsRelFlags = [rfRelRow, rfRelCol, rfRelRow2, rfRelCol2]): String;
begin
Result := Format('%s%s%s%d:%s%s%s%d', [
RELCHAR[rfRelCol in AFlags], GetColString(ACol1),
diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas
index 11fdca270..bd16f4e5b 100755
--- a/components/fpspreadsheet/xlsbiff2.pas
+++ b/components/fpspreadsheet/xlsbiff2.pas
@@ -790,7 +790,9 @@ end;
{ Reads the cell coordiantes of the top/left cell of a range using a shared formula.
This cell contains the rpn token sequence of the formula.
- Is overridden because BIFF2 has 1 byte for column. }
+ Is overridden because BIFF2 has 1 byte for column.
+ Code is not called for shared formulas (which are not supported by BIFF2), but
+ maybe for array formulas. }
procedure TsSpreadBIFF2Reader.ReadRPNSharedFormulaBase(AStream: TStream;
out ARow, ACol: Cardinal);
begin
diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas
index e35f62069..56764960c 100644
--- a/components/fpspreadsheet/xlscommon.pas
+++ b/components/fpspreadsheet/xlscommon.pas
@@ -535,7 +535,8 @@ type
procedure WriteSelections(AStream: TStream; ASheet: TsWorksheet);
// Writes out a shared formula
procedure WriteSharedFormula(AStream: TStream; ACell: PCell); virtual;
- procedure WriteSharedFormulaRange(AStream: TStream; const ARange: TRect); virtual;
+ procedure WriteSharedFormulaRange(AStream: TStream;
+ AFirstRow, AFirstCol, ALastRow, ALastCol: Cardinal); virtual;
procedure WriteSheetPR(AStream: TStream);
procedure WriteStringRecord(AStream: TStream; AString: String); virtual;
// Writes cell content received by workbook in OnNeedCellData event
@@ -2882,8 +2883,7 @@ end;
the formulas in each cell. In BIFF2 WriteSharedFormula must not do anything. }
procedure TsSpreadBIFFWriter.WriteSharedFormula(AStream: TStream; ACell: PCell);
var
- range: TRect;
- node: TAVLTreeNode;
+ r, c, r1, r2, c1, c2: Cardinal;
cell: PCell;
RPNLength: word;
recordSizePos: Int64;
@@ -2892,32 +2892,29 @@ var
i: Integer;
begin
// Determine cell range covered by the shared formula in ACell.
- range := Rect(-1, -1, -1, -1);
- node := FWorksheet.Cells.FindLowest;
- while Assigned(node) do begin
- cell := PCell(node.Data);
- if cell.SharedFormulaBase = ACell then begin
- // Nodes are ordered along rows --> the first cell met must be the left border of the range
- if range.Left = -1 then
- range.Left := cell.Col
- else
- if cell.Col < range.Left then begin
- FWorkbook.AddErrorMsg('Non-rectangular cell range covered by shared formula in cell %s',
- [GetCellString(ACell^.Row, ACell^.Col)]);
- exit;
- end;
- // The right border of the range must have the max col index
- if range.Right = -1 then
- range.Right := cell.Col
- else if cell.Col > range.Right then
- range.Right := cell.Col;
- // The first cell met must be the top border of the range
- if range.Top = -1 then
- range.Top := Cell.Row;
- // dto. with bottom border
- range.Bottom := Cell.Row;
- end;
- node := FWorksheet.Cells.FindSuccessor(node);
+ // Find range of cells using this shared formula
+ r1 := ACell^.Row; r2 := r1;
+ c1 := ACell^.Col; c2 := c1;
+ r := r1;
+ c := c1;
+ while c <= FWorksheet.GetLastColIndex do
+ begin
+ cell := FWorksheet.FindCell(r, c);
+ if (cell <> nil) and (cell^.SharedFormulaBase = ACell^.SharedFormulaBase) then
+ c2 := c
+ else
+ break;
+ inc(c);
+ end;
+ c := c1;
+ while r <= FWorksheet.GetLastRowIndex do
+ begin
+ cell := FWorksheet.FindCell(r, c);
+ if (cell <> nil) and (cell^.SharedFormulaBase <> ACell^.SharedFormulaBase) then
+ r2 := r
+ else
+ break;
+ inc(r);
end;
// Write BIFF record ID and size
@@ -2927,13 +2924,13 @@ begin
startPos := AStream.Position;
// Write borders of cell range covered by the formula
- WriteSharedFormulaRange(AStream, range);
+ WriteSharedFormulaRange(AStream, r1, c1, r2, c2);
// Not used
AStream.WriteByte(0);
// Number of existing formula records
- AStream.WriteByte((range.Right-range.Left+1)*(range.Bottom-range.Top+1));
+ AStream.WriteByte((r2-r1+1) * (c2-c1+1));
// Copy the formula (we don't want to overwrite the cell formulas)
// and adjust relative references
@@ -2956,16 +2953,20 @@ end;
Valid for BIFF5 and BIFF8 - BIFF8 writes 8-bit column index as well.
No need for BIFF2 which does not support shared formulas. }
procedure TsSpreadBIFFWriter.WriteSharedFormulaRange(AStream: TStream;
- const ARange: TRect);
+ AFirstRow, AFirstCol, ALastRow, ALastCol: Cardinal);
+var
+ c: Word;
begin
// Index to first row
- AStream.WriteWord(WordToLE(ARange.Top));
+ AStream.WriteWord(WordToLE(AFirstRow));
// Index to last row
- AStream.WriteWord(WordToLE(ARange.Bottom));
+ AStream.WriteWord(WordToLE(ALastRow));
// Index to first column
- AStream.WriteByte(Lo(ARange.Left));
+ c := Lo(AFirstCol);
+ AStream.WriteByte(Lo(c));
// Index to last rcolumn
- AStream.WriteByte(Lo(ARange.Right));
+ c := Lo(ALastCol);
+ AStream.WriteByte(Lo(c));
end;
diff --git a/components/fpspreadsheet/xlsxooxml.pas b/components/fpspreadsheet/xlsxooxml.pas
index 14028b90d..d8a7390b5 100755
--- a/components/fpspreadsheet/xlsxooxml.pas
+++ b/components/fpspreadsheet/xlsxooxml.pas
@@ -2266,6 +2266,8 @@ end;
procedure TsSpreadOOXMLWriter.WriteWorksheet(AWorksheet: TsWorksheet);
begin
+ FWorksheet := AWorksheet;
+
FCurSheetNum := Length(FSSheets);
SetLength(FSSheets, FCurSheetNum + 1);
@@ -2499,16 +2501,69 @@ procedure TsSpreadOOXMLWriter.WriteFormula(AStream: TStream;
var
cellPosText: String;
lStyleIndex: Integer;
+ r, c, r2, c2: Cardinal;
+ cell: PCell;
+ id: Cardinal;
begin
cellPosText := TsWorksheet.CellPosToText(ARow, ACol);
lStyleIndex := GetStyleIndex(ACell);
- AppendToStream(AStream, Format(
- '' +
- '%s' +
- '', [
- CellPosText, lStyleIndex,
- PrepareFormula(ACell^.FormulaValue)
+ // Cell uses a shared formula
+ if Assigned(ACell^.SharedFormulaBase) then begin
+ // Cell is base of the shared formula, i.e. contains the shared formula
+ if (ACell = ACell^.SharedFormulaBase) then
+ begin
+ // Find range of cells using this shared formula
+ r2 := ACell^.Row;
+ c2 := ACell^.Col;
+ c := c2;
+ r := r2;
+ while c <= FWorksheet.GetLastColIndex do
+ begin
+ cell := FWorksheet.FindCell(r, c);
+ if (cell <> nil) and (cell^.SharedFormulaBase = ACell^.SharedFormulaBase) then
+ c2 := c
+ else
+ break;
+ inc(c);
+ end;
+ c := ACell^.Col;
+ while r <= FWorksheet.GetLastRowIndex do
+ begin
+ cell := FWorksheet.FindCell(r, c);
+ if (cell <> nil) and (cell^.SharedFormulaBase <> ACell^.SharedFormulaBase) then
+ r2 := r
+ else
+ break;
+ inc(r);
+ end;
+
+ AppendToStream(AStream, Format(
+ '' +
+ '%s' +
+ '', [
+ CellPosText, lStyleIndex,
+ GetCellRangeString(ACell^.Row, ACell^.Col, r2, c2),
+ PtrInt(ACell), // Use the cell pointer as ID of the shared formula
+ PrepareFormula(ACell^.FormulaValue)
+ ]));
+ end else
+ // Cell uses the shared formula
+ AppendToStream(AStream, Format(
+ '' +
+ '' +
+ '', [
+ CellPosText, lStyleIndex,
+ PtrInt(ACell^.SharedFormulaBase) // ID of the shared formula
+ ]));
+ end else
+ // "normal" formula
+ AppendToStream(AStream, Format(
+ '' +
+ '%s' +
+ '', [
+ CellPosText, lStyleIndex,
+ PrepareFormula(ACell^.FormulaValue)
]));
end;