You've already forked lazarus-ccr
fpspreadsheet: Fix insert/delete column/row issues for shared formulas. Complete shared formula test cases on insert/delete columns and rows. Duplicate these test cases for OOXML. All passed.
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3595 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
@ -501,8 +501,6 @@ type
|
|||||||
procedure ChangedCell(ARow, ACol: Cardinal);
|
procedure ChangedCell(ARow, ACol: Cardinal);
|
||||||
procedure ChangedFont(ARow, ACol: Cardinal);
|
procedure ChangedFont(ARow, ACol: Cardinal);
|
||||||
|
|
||||||
function InsertColToFormula(ACol: Cardinal; ACell: PCell): String;
|
|
||||||
|
|
||||||
procedure RemoveCell(ARow, ACol: Cardinal);
|
procedure RemoveCell(ARow, ACol: Cardinal);
|
||||||
|
|
||||||
public
|
public
|
||||||
@ -687,6 +685,7 @@ type
|
|||||||
function FindSharedFormulaBase(ACell: PCell): PCell;
|
function FindSharedFormulaBase(ACell: PCell): PCell;
|
||||||
function FindSharedFormulaRange(ACell: PCell; out ARow1, ACol1, ARow2, ACol2: Cardinal): Boolean;
|
function FindSharedFormulaRange(ACell: PCell; out ARow1, ACol1, ARow2, ACol2: Cardinal): Boolean;
|
||||||
procedure FixSharedFormulas;
|
procedure FixSharedFormulas;
|
||||||
|
procedure SplitSharedFormula(ACell: PCell);
|
||||||
function UseSharedFormula(ARow, ACol: Cardinal; ASharedFormulaBase: PCell): PCell;
|
function UseSharedFormula(ARow, ACol: Cardinal; ASharedFormulaBase: PCell): PCell;
|
||||||
|
|
||||||
{ Data manipulation methods - For Cells }
|
{ Data manipulation methods - For Cells }
|
||||||
@ -3073,6 +3072,45 @@ begin
|
|||||||
FLastRowIndex := GetLastRowIndex(true);
|
FLastRowIndex := GetLastRowIndex(true);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{@@ ----------------------------------------------------------------------------
|
||||||
|
Splits a shared formula range to which the specified cell belongs into
|
||||||
|
individual cells. Each cell gets same the formula as it had in the block.
|
||||||
|
This is required because insertion and deletion of columns/rows make shared
|
||||||
|
formulas very complicated.
|
||||||
|
-------------------------------------------------------------------------------}
|
||||||
|
procedure TsWorksheet.SplitSharedFormula(ACell: PCell);
|
||||||
|
var
|
||||||
|
r, c: Cardinal;
|
||||||
|
baseRow, baseCol: Cardinal;
|
||||||
|
lastRow, lastCol: Cardinal;
|
||||||
|
cell: PCell;
|
||||||
|
rpnFormula: TsRPNFormula;
|
||||||
|
begin
|
||||||
|
if (ACell = nil) or (ACell^.SharedFormulaBase = nil) then
|
||||||
|
exit;
|
||||||
|
lastRow := GetLastOccupiedRowIndex;
|
||||||
|
lastCol := GetLastOccupiedColIndex;
|
||||||
|
baseRow := ACell^.SharedFormulaBase^.Row;
|
||||||
|
baseCol := ACell^.SharedFormulaBase^.Col;
|
||||||
|
for r := baseRow to lastRow do
|
||||||
|
for c := baseCol to lastCol do
|
||||||
|
begin
|
||||||
|
cell := FindCell(r, c);
|
||||||
|
if (cell = nil) or (cell^.SharedFormulaBase = nil) then
|
||||||
|
continue;
|
||||||
|
if (cell^.SharedFormulaBase^.Row = baseRow) and
|
||||||
|
(cell^.SharedFormulaBase^.Col = baseCol) then
|
||||||
|
begin
|
||||||
|
// This method converts the shared formula to an rpn formula as seen from cell...
|
||||||
|
rpnFormula := BuildRPNFormula(cell);
|
||||||
|
// ... and this reconstructs the string formula, again as seen from cell.
|
||||||
|
cell^.FormulaValue := ConvertRPNFormulaToStringFormula(rpnFormula);
|
||||||
|
// Remove the SharedFormulaBase information --> cell is isolated.
|
||||||
|
cell^.SharedFormulaBase := nil;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
{@@ ----------------------------------------------------------------------------
|
{@@ ----------------------------------------------------------------------------
|
||||||
Defines a cell range sharing the "same" formula. Note that relative cell
|
Defines a cell range sharing the "same" formula. Note that relative cell
|
||||||
references are updated for each cell in the range.
|
references are updated for each cell in the range.
|
||||||
@ -5117,6 +5155,15 @@ var
|
|||||||
rLast, cLast: Cardinal;
|
rLast, cLast: Cardinal;
|
||||||
cell, nextcell, gapcell, oldbase, newbase: PCell;
|
cell, nextcell, gapcell, oldbase, newbase: PCell;
|
||||||
begin
|
begin
|
||||||
|
// Handling of shared formula references is too complicated for me...
|
||||||
|
// Splits them into isolated cell formulas
|
||||||
|
cellNode := FCells.FindLowest;
|
||||||
|
while Assigned(cellnode) do begin
|
||||||
|
cell := PCell(cellNode.Data);
|
||||||
|
SplitSharedFormula(cell);
|
||||||
|
cellNode := FCells.FindSuccessor(cellnode);
|
||||||
|
end;
|
||||||
|
|
||||||
// Update column index of cell records
|
// Update column index of cell records
|
||||||
cellnode := FCells.FindLowest;
|
cellnode := FCells.FindLowest;
|
||||||
while Assigned(cellnode) do begin
|
while Assigned(cellnode) do begin
|
||||||
@ -5133,10 +5180,9 @@ begin
|
|||||||
// Update first and last column index
|
// Update first and last column index
|
||||||
UpdateCaches;
|
UpdateCaches;
|
||||||
|
|
||||||
// Fix merged cells and shared formulas: If the inserted column runs through a
|
// Fix merged cells: If the inserted column runs through a block of
|
||||||
// block of merged cells or a shared formula the block is cut into two pieces.
|
// merged cells the block is cut into two pieces. Here we fill the gap
|
||||||
// Here we fill the gap with dummy cells and set their MergeBase / SharedFormulaBase
|
// with dummy cells and set their MergeBase correctly.
|
||||||
// correctly.
|
|
||||||
if ACol > 0 then
|
if ACol > 0 then
|
||||||
begin
|
begin
|
||||||
rLast := GetLastOccupiedRowIndex;
|
rLast := GetLastOccupiedRowIndex;
|
||||||
@ -5159,115 +5205,6 @@ begin
|
|||||||
gapcell := GetCell(r, ACol);
|
gapcell := GetCell(r, ACol);
|
||||||
gapcell^.Mergebase := cell^.MergeBase;
|
gapcell^.Mergebase := cell^.MergeBase;
|
||||||
end;
|
end;
|
||||||
end
|
|
||||||
else
|
|
||||||
// A shared formula block is found immediately to the left of the
|
|
||||||
// inserted column. If it extends to the right we have to create a new
|
|
||||||
// shared formula base in the upper left cell. Note: Excel does not
|
|
||||||
// extend the shared formula to the inserted column.
|
|
||||||
if cell^.SharedFormulaBase <> nil then begin
|
|
||||||
oldbase := cell^.SharedFormulaBase;
|
|
||||||
// Does it extend to the right of the new column?
|
|
||||||
nextcell := FindCell(r, ACol+1);
|
|
||||||
if Assigned(nextcell) and (nextcell^.SharedFormulaBase = oldbase) then
|
|
||||||
begin
|
|
||||||
// Yes.
|
|
||||||
// It next cell is in the top row of the block we make it a shared formula base
|
|
||||||
if r = oldbase^.Row then
|
|
||||||
begin
|
|
||||||
newbase := nextcell;
|
|
||||||
nextcell^.SharedFormulaBase := nextcell;
|
|
||||||
nextcell^.FormulaValue := InsertColToFormula(ACol, oldbase); // ??
|
|
||||||
// Note: the references are not correct here - we fix that later!
|
|
||||||
end;
|
|
||||||
// Use the new shared formulabase in all cells of the old block at the right
|
|
||||||
for cc := ACol+1 to cLast do
|
|
||||||
begin
|
|
||||||
cell := FindCell(r, cc);
|
|
||||||
if (cell = nil) or (cell^.SharedFormulaBase <> oldbase) then
|
|
||||||
break;
|
|
||||||
cell^.SharedFormulaBase := newbase;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
(*
|
|
||||||
else
|
|
||||||
// A shared formula block is found immediately before the inserted column
|
|
||||||
if cell^.SharedFormulaBase <> nil then begin
|
|
||||||
// Does it extend beyond the newly inserted column?
|
|
||||||
nextcell := FindCell(r, ACol+1);
|
|
||||||
if Assigned(nextcell) and (nextcell^.SharedFormulaBase = cell^.SharedFormulaBase) then
|
|
||||||
begin
|
|
||||||
// Yes - we add a cell into the gap
|
|
||||||
gapcell := GetCell(r, ACol);
|
|
||||||
// If this is the first row of the shared formula block we must define
|
|
||||||
// a new shared formula because cell references may differ by 1 on both
|
|
||||||
// sides of the inserted column!
|
|
||||||
if r = cell^.SharedFormulaBase^.Row then
|
|
||||||
begin
|
|
||||||
gapcell^.SharedFormulaBase := gapcell;
|
|
||||||
gapcell^.FormulaValue := cell^.SharedFormulaBase.FormulaValue;
|
|
||||||
gapcell^.FormulaValue := InsertColToFormula(ACol, gapcell);
|
|
||||||
newbase := gapcell;
|
|
||||||
end
|
|
||||||
else
|
|
||||||
// Link to the new base
|
|
||||||
gapcell^.SharedFormulaBase := newbase;
|
|
||||||
// Link all cells to the right of the inserted column to the new base
|
|
||||||
for ic := ACol+1 to cLast do
|
|
||||||
begin
|
|
||||||
cell := FindCell(r, ic);
|
|
||||||
if cell^.SharedFormulaBase = nextcell^.SharedFormulaBase then
|
|
||||||
cell^.SharedFormulaBase := newbase
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
*)
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Fix shared formulas:
|
|
||||||
// A shared formula block may break into two pieces if cell references Cell references to the left and to the right of
|
|
||||||
// Seek along the column immediately to the left of the inserted column
|
|
||||||
c := ACol - 1;
|
|
||||||
for r := 0 to rLast do
|
|
||||||
begin
|
|
||||||
cell := FindCell(r, c);
|
|
||||||
if not Assigned(cell) then
|
|
||||||
Continue;
|
|
||||||
// A shared formula block is found immediately to the left of the inserted column.
|
|
||||||
// If it extends beyond the new column we have to redefine the shared
|
|
||||||
// formula in the right split-off part because column offsets to the left
|
|
||||||
// part are greater by 1 now.
|
|
||||||
// Excel does not extend the shared formula into the new column, though.
|
|
||||||
if cell^.SharedFormulaBase <> nil then
|
|
||||||
begin
|
|
||||||
oldbase := cell^.SharedFormulaBase;
|
|
||||||
// Does it extend beyond the newly inserted column?
|
|
||||||
nextcell := FindCell(r, ACol+1);
|
|
||||||
if Assigned(nextcell) and (nextcell^.SharedFormulaBase = cell^.SharedFormulaBase) then
|
|
||||||
begin
|
|
||||||
// Yes. If we are at the first row of the old shared formula block we
|
|
||||||
// have to define a new base in nextcell. But the formula must be
|
|
||||||
// corrected for the inserted column!
|
|
||||||
if r = cell^.SharedFormulaBase^.Row then begin
|
|
||||||
nextcell^.SharedFormulaBase := nextcell;
|
|
||||||
nextcell^.FormulaValue := cell^.SharedFormulaBase^.FormulaValue;
|
|
||||||
nextcell^.FormulaValue := InsertColToFormula(ACol, nextcell);
|
|
||||||
newbase := nextcell;
|
|
||||||
end;
|
|
||||||
// Now link all cells to the right of the new column (which still
|
|
||||||
// use the old base) to the new base
|
|
||||||
for cc := ACol+1 to cLast do
|
|
||||||
begin
|
|
||||||
cell := FindCell(r, cc);
|
|
||||||
if (cell = nil) or (cell^.SharedFormulaBase <> oldbase) then
|
|
||||||
break;
|
|
||||||
cell^.SharedFormulaBase := newbase;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -5293,8 +5230,6 @@ begin
|
|||||||
|
|
||||||
// Update formulas
|
// Update formulas
|
||||||
if HasFormula(cell) and (cell^.FormulaValue <> '' ) then
|
if HasFormula(cell) and (cell^.FormulaValue <> '' ) then
|
||||||
cell^.FormulaValue := InsertColToFormula(col, cell);
|
|
||||||
{
|
|
||||||
begin
|
begin
|
||||||
// (1) create an rpn formula
|
// (1) create an rpn formula
|
||||||
formula := BuildRPNFormula(cell);
|
formula := BuildRPNFormula(cell);
|
||||||
@ -5314,35 +5249,6 @@ begin
|
|||||||
// (3) convert rpn formula back to string formula
|
// (3) convert rpn formula back to string formula
|
||||||
cell^.FormulaValue := ConvertRPNFormulaToStringFormula(formula);
|
cell^.FormulaValue := ConvertRPNFormulaToStringFormula(formula);
|
||||||
end;
|
end;
|
||||||
}
|
|
||||||
end;
|
|
||||||
|
|
||||||
{@@ ----------------------------------------------------------------------------
|
|
||||||
The formula of the specified cell must be modified because a column is
|
|
||||||
inserted: Cell references pointing to the left of the inserted column remain
|
|
||||||
unchanged, but cell references pointing to the inserted column or its right
|
|
||||||
must have a higher column index by 1.
|
|
||||||
Returns the modified string formula.
|
|
||||||
-------------------------------------------------------------------------------}
|
|
||||||
function TsWorksheet.InsertColToFormula(ACol: Cardinal; ACell: PCell): String;
|
|
||||||
var
|
|
||||||
rpnFormula: TsRPNFormula;
|
|
||||||
i: Integer;
|
|
||||||
begin
|
|
||||||
rpnFormula := BuildRPNFormula(ACell);
|
|
||||||
for i:=0 to Length(rpnFormula) - 1 do
|
|
||||||
begin
|
|
||||||
case rpnFormula[i].ElementKind of
|
|
||||||
fekCell, fekCellRef:
|
|
||||||
if rpnFormula[i].Col >= ACol then inc(rpnFormula[i].Col);
|
|
||||||
fekCellRange:
|
|
||||||
begin
|
|
||||||
if rpnFormula[i].Col >= ACol then inc(rpnFormula[i].Col);
|
|
||||||
if rpnFormula[i].Col2 >= ACol then inc(rpnFormula[i].Col2);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
Result := ConvertRPNFormulaToStringFormula(rpnFormula);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{@@ ----------------------------------------------------------------------------
|
{@@ ----------------------------------------------------------------------------
|
||||||
@ -5360,6 +5266,15 @@ var
|
|||||||
r, c, cc, r1, c1, r2, c2: Cardinal;
|
r, c, cc, r1, c1, r2, c2: Cardinal;
|
||||||
cell, nextcell, gapcell: PCell;
|
cell, nextcell, gapcell: PCell;
|
||||||
begin
|
begin
|
||||||
|
// Handling of shared formula references is too complicated for me...
|
||||||
|
// Splits them into isolated cell formulas
|
||||||
|
cellNode := FCells.FindLowest;
|
||||||
|
while Assigned(cellnode) do begin
|
||||||
|
cell := PCell(cellNode.Data);
|
||||||
|
SplitSharedFormula(cell);
|
||||||
|
cellNode := FCells.FindSuccessor(cellnode);
|
||||||
|
end;
|
||||||
|
|
||||||
// Update row index of cell records
|
// Update row index of cell records
|
||||||
cellnode := FCells.FindLowest;
|
cellnode := FCells.FindLowest;
|
||||||
while Assigned(cellnode) do begin
|
while Assigned(cellnode) do begin
|
||||||
@ -5376,10 +5291,9 @@ begin
|
|||||||
// Update first and last row index
|
// Update first and last row index
|
||||||
UpdateCaches;
|
UpdateCaches;
|
||||||
|
|
||||||
// Fix merged cells and shared formulas: If the inserted row runs through a
|
// Fix merged cells: If the inserted row runs through a block of merged
|
||||||
// block of merged cells or a shared formula the block is cut into two pieces.
|
// cells the block is cut into two pieces. Here we fill the gap with
|
||||||
// Here we fill the gap with dummy cells and set their MergeBase / SharedFormulaBase
|
// dummy cells and set their MergeBase correctly.
|
||||||
// correctly.
|
|
||||||
if ARow > 0 then
|
if ARow > 0 then
|
||||||
begin
|
begin
|
||||||
r := ARow - 1;
|
r := ARow - 1;
|
||||||
@ -5400,17 +5314,6 @@ begin
|
|||||||
gapcell := GetCell(ARow, c);
|
gapcell := GetCell(ARow, c);
|
||||||
gapcell^.Mergebase := cell^.MergeBase;
|
gapcell^.Mergebase := cell^.MergeBase;
|
||||||
end;
|
end;
|
||||||
end else
|
|
||||||
// A shared formula block is found
|
|
||||||
if cell^.SharedFormulaBase <> nil then begin
|
|
||||||
// Does it extend beyond the newly inserted row?
|
|
||||||
nextcell := FindCell(ARow+1, c);
|
|
||||||
if Assigned(nextcell) and (nextcell^.SharedFormulaBase = cell^.SharedFormulaBase) then
|
|
||||||
begin
|
|
||||||
// Yes - we add a cell into the gap and share the formula of the base
|
|
||||||
gapcell := GetCell(ARow, c);
|
|
||||||
gapcell^.SharedFormulaBase := cell^.SharedFormulaBase;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -48,6 +48,7 @@
|
|||||||
<Unit1>
|
<Unit1>
|
||||||
<Filename Value="datetests.pas"/>
|
<Filename Value="datetests.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
|
<UnitName Value="datetests"/>
|
||||||
</Unit1>
|
</Unit1>
|
||||||
<Unit2>
|
<Unit2>
|
||||||
<Filename Value="stringtests.pas"/>
|
<Filename Value="stringtests.pas"/>
|
||||||
@ -56,7 +57,6 @@
|
|||||||
<Unit3>
|
<Unit3>
|
||||||
<Filename Value="numberstests.pas"/>
|
<Filename Value="numberstests.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
<UnitName Value="numberstests"/>
|
|
||||||
</Unit3>
|
</Unit3>
|
||||||
<Unit4>
|
<Unit4>
|
||||||
<Filename Value="manualtests.pas"/>
|
<Filename Value="manualtests.pas"/>
|
||||||
@ -71,11 +71,11 @@
|
|||||||
<Unit6>
|
<Unit6>
|
||||||
<Filename Value="internaltests.pas"/>
|
<Filename Value="internaltests.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
|
<UnitName Value="internaltests"/>
|
||||||
</Unit6>
|
</Unit6>
|
||||||
<Unit7>
|
<Unit7>
|
||||||
<Filename Value="formattests.pas"/>
|
<Filename Value="formattests.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
<UnitName Value="formattests"/>
|
|
||||||
</Unit7>
|
</Unit7>
|
||||||
<Unit8>
|
<Unit8>
|
||||||
<Filename Value="colortests.pas"/>
|
<Filename Value="colortests.pas"/>
|
||||||
@ -96,7 +96,6 @@
|
|||||||
<Unit12>
|
<Unit12>
|
||||||
<Filename Value="rpnformulaunit.pas"/>
|
<Filename Value="rpnformulaunit.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
<UnitName Value="rpnFormulaUnit"/>
|
|
||||||
</Unit12>
|
</Unit12>
|
||||||
<Unit13>
|
<Unit13>
|
||||||
<Filename Value="formulatests.pas"/>
|
<Filename Value="formulatests.pas"/>
|
||||||
|
Reference in New Issue
Block a user