From 9c2e469e1d8fc104c64255068f2d068cc5b0413f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 25 Oct 2023 16:35:14 -0700 Subject: [PATCH] Slk Shared Formulas Fix #2267. The Slk format has a way to express a "shared formula", but the Slk reader does not yet understand it. Thanks to @SheetJSDev for documenting the problem and pointing the way towards a solution. It has taken a long time to get there. Part of the problem is that I have not been successful in getting Excel to use this type of construction when saving a Slk file. So I have resorted to saving a Slk file where shared formulas *could* be used, and then editing it by hand to actually use them. It would not surprise me in the least to have neglected one or more possible ways to specify a shared formula; but, at least the issue as documented is resolved, and if new issues arise, we'll probably be in better shape to deal with them. --- src/PhpSpreadsheet/Reader/Slk.php | 32 ++++++- .../Reader/Slk/SlkSharedFormulasTest.php | 38 ++++++++ tests/data/Reader/Slk/issue.2267c.slk | 89 +++++++++++++++++++ 3 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Reader/Slk/SlkSharedFormulasTest.php create mode 100644 tests/data/Reader/Slk/issue.2267c.slk diff --git a/src/PhpSpreadsheet/Reader/Slk.php b/src/PhpSpreadsheet/Reader/Slk.php index 3b026fe277..275d0971b9 100644 --- a/src/PhpSpreadsheet/Reader/Slk.php +++ b/src/PhpSpreadsheet/Reader/Slk.php @@ -5,6 +5,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; +use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Border; @@ -269,14 +270,14 @@ private function processCRecord(array $rowData, Spreadsheet &$spreadsheet, strin $hasCalculatedValue = false; $tryNumeric = false; $cellDataFormula = $cellData = ''; + $sharedColumn = $sharedRow = -1; + $sharedFormula = false; foreach ($rowData as $rowDatum) { switch ($rowDatum[0]) { - case 'C': case 'X': $column = substr($rowDatum, 1); break; - case 'R': case 'Y': $row = substr($rowDatum, 1); @@ -298,9 +299,36 @@ private function processCRecord(array $rowData, Spreadsheet &$spreadsheet, strin ->getText() ->createText($comment); + break; + case 'C': + $sharedColumn = (int) substr($rowDatum, 1); + + break; + case 'R': + $sharedRow = (int) substr($rowDatum, 1); + + break; + case 'S': + $sharedFormula = true; + break; } } + if ($sharedFormula === true && $sharedRow >= 0 && $sharedColumn >= 0) { + $thisCoordinate = Coordinate::stringFromColumnIndex((int) $column) . $row; + $sharedCoordinate = Coordinate::stringFromColumnIndex($sharedColumn) . $sharedRow; + $formula = $spreadsheet->getActiveSheet()->getCell($sharedCoordinate)->getValue(); + $spreadsheet->getActiveSheet()->getCell($thisCoordinate)->setValue($formula); + $referenceHelper = ReferenceHelper::getInstance(); + $newFormula = $referenceHelper->updateFormulaReferences($formula, 'A1', (int) $column - $sharedColumn, (int) $row - $sharedRow, '', true, false); + $spreadsheet->getActiveSheet()->getCell($thisCoordinate)->setValue($newFormula); + //$calc = $spreadsheet->getActiveSheet()->getCell($thisCoordinate)->getCalculatedValue(); + //$spreadsheet->getActiveSheet()->getCell($thisCoordinate)->setCalculatedValue($calc); + $cellData = Calculation::unwrapResult($cellData); + $spreadsheet->getActiveSheet()->getCell($thisCoordinate)->setCalculatedValue($cellData, $tryNumeric); + + return; + } $columnLetter = Coordinate::stringFromColumnIndex((int) $column); $cellData = Calculation::unwrapResult($cellData); diff --git a/tests/PhpSpreadsheetTests/Reader/Slk/SlkSharedFormulasTest.php b/tests/PhpSpreadsheetTests/Reader/Slk/SlkSharedFormulasTest.php new file mode 100644 index 0000000000..15af9f8db8 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Slk/SlkSharedFormulasTest.php @@ -0,0 +1,38 @@ +load($testbook); + $sheet = $spreadsheet->getActiveSheet(); + $range = 'A1:' . $sheet->getHighestDataColumn() . $sheet->getHighestDataRow(); + $values = $sheet->RangeToArray($range, null, false, false, false, false); // just get values, don't calculate + $expected = [ + [1, 10, 100, 101, 102], + ['=A1+1', '=B1+1', '=C1+1', '=D1+1', '=E1+1'], + ['=A2+1', '=B2+1', '=C2+1', '=D2+1', '=E2+1'], + ['=A3+1', '=B3+1', '=C3+1', '=D3+1', '=E3+1'], + ['=A4+1', '=B4+1', '=C4+1', '=D4+1', '=E4+1'], + ]; + self::assertSame($expected, $values); + $calcValues = $sheet->RangeToArray($range, null, true, false, false, false); // get calculated values + $expectedCalc = [ + [1, 10, 100, 101, 102], + [2, 11, 101, 102, 103], + [3, 12, 102, 103, 104], + [4, 13, 103, 104, 105], + [5, 14, 104, 105, 106], + ]; + self::assertSame($expectedCalc, $calcValues); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Reader/Slk/issue.2267c.slk b/tests/data/Reader/Slk/issue.2267c.slk new file mode 100644 index 0000000000..e069d6c06e --- /dev/null +++ b/tests/data/Reader/Slk/issue.2267c.slk @@ -0,0 +1,89 @@ +ID;PWXL;N;E +P;PGeneral +P;P0 +P;P0.00 +P;P#,##0 +P;P#,##0.00 +P;P#,##0_);;\(#,##0\) +P;P#,##0_);;[Red]\(#,##0\) +P;P#,##0.00_);;\(#,##0.00\) +P;P#,##0.00_);;[Red]\(#,##0.00\) +P;P"$"#,##0_);;\("$"#,##0\) +P;P"$"#,##0_);;[Red]\("$"#,##0\) +P;P"$"#,##0.00_);;\("$"#,##0.00\) +P;P"$"#,##0.00_);;[Red]\("$"#,##0.00\) +P;P0% +P;P0.00% +P;P0.00E+00 +P;P##0.0E+0 +P;P#\ ?/? +P;P#\ ??/?? +P;Pyyyy/mm/dd +P;Pdd/mmm/yy +P;Pdd/mmm +P;Pmmm/yy +P;Ph:mm\ AM/PM +P;Ph:mm:ss\ AM/PM +P;Ph:mm +P;Ph:mm:ss +P;Pyyyy/mm/dd\ h:mm +P;Pmm:ss +P;Pmm:ss.0 +P;P@ +P;P[h]:mm:ss +P;P_("$"* #,##0_);;_("$"* \(#,##0\);;_("$"* "-"_);;_(@_) +P;P_(* #,##0_);;_(* \(#,##0\);;_(* "-"_);;_(@_) +P;P_("$"* #,##0.00_);;_("$"* \(#,##0.00\);;_("$"* "-"??_);;_(@_) +P;P_(* #,##0.00_);;_(* \(#,##0.00\);;_(* "-"??_);;_(@_) +P;FCalibri;M220;L9 +P;FCalibri;M220;L9 +P;FCalibri;M220;L9 +P;FCalibri;M220;L9 +P;ECalibri;M220;L9 +P;ECalibri Light;M360;L55 +P;ECalibri;M300;SB;L55 +P;ECalibri;M260;SB;L55 +P;ECalibri;M220;SB;L55 +P;ECalibri;M220;L18 +P;ECalibri;M220;L21 +P;ECalibri;M220;L61 +P;ECalibri;M220;L63 +P;ECalibri;M220;SB;L64 +P;ECalibri;M220;SB;L53 +P;ECalibri;M220;L53 +P;ECalibri;M220;SB;L10 +P;ECalibri;M220;L11 +P;ECalibri;M220;SI;L24 +P;ECalibri;M220;SB;L9 +P;ECalibri;M220;L10 +P;ESegoe UI;M200;L9 +F;P0;DG0G8;M320 +B;Y6;X5;D0 0 5 4 +O;D;V0;K47;G100 0.001 +F;W1 16384 10 +C;Y1;X1;K1 +C;X2;K10 +C;X3;K100 +C;X4;K101 +C;X5;K102 +C;Y2;X1;K2;ER[-1]C+1 +C;X2;K11;ER[-1]C+1 +C;X3;K101;S;R2;C1 +C;X4;K102;S;R2;C1 +C;X5;K103;S;R2;C1 +C;Y3;X1;K3;S;R2;C1 +C;X2;K12;ER[-1]C+1 +C;X3;K102;S;R2;C1 +C;X4;K103;S;R2;C1 +C;X5;K104;S;R2;C1 +C;Y4;X1;K4;S;R2;C1 +C;X2;K13;ER[-1]C+1 +C;X3;K103;S;R2;C1 +C;X4;K104;S;R2;C1 +C;X5;K105;S;R2;C1 +C;Y5;X1;K5;S;R2;C1 +C;X2;K14;ER[-1]C+1 +C;X3;K104;S;R2;C1 +C;X4;K105;S;R2;C1 +C;X5;K106;S;R2;C1 +E