From 9ca9d741fecd8545c39a0c3b3dfaaa00432855c5 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sun, 13 Mar 2022 03:04:37 +0100 Subject: [PATCH] Resolve saving cell references, string literals and formula as values for conditional formatting rules in the Xls file save The code is ugly as sin; but it works... I'll do some refactoring and cleaning later (once I've had some sleep, because I'm stupidly still working on this at 3am) The main remaining issue is formulae that can't be parsed in BIFF8 files; e.g. formulae that use functions that aren't available. In this case, all CF in that worksheet is corrupted, and the file errors when opened, so it is a serious issue. The ISODD()/ISEVEN example in 07_Expression_Comparisons.php uses these, so I've temporarily commented out setting that CF range until I've solved that problem. There are other limitations listed in the BIFF documentation; but they're harder to detect. I've also left a couple of debug statements in the code to display these formula errors: I'll remove them once I've resolved the issue. --- .../07_Expression_Comparisons.php | 7 +- .../Wizard/WizardAbstract.php | 2 +- src/PhpSpreadsheet/Writer/Xls/Worksheet.php | 69 +++++++++++++++---- 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/samples/ConditionalFormatting/07_Expression_Comparisons.php b/samples/ConditionalFormatting/07_Expression_Comparisons.php index 88d79b23..43d6fe56 100644 --- a/samples/ConditionalFormatting/07_Expression_Comparisons.php +++ b/samples/ConditionalFormatting/07_Expression_Comparisons.php @@ -28,6 +28,7 @@ $helper->log('Add data'); $spreadsheet->setActiveSheetIndex(0); $spreadsheet->getActiveSheet() ->setCellValue('A1', 'Odd/Even Expression Comparison') + ->setCellValue('A4', 'Note that these functions are not available for Xls files') ->setCellValue('A15', 'Sales Grid Expression Comparison') ->setCellValue('A25', 'Sales Grid Multiple Expression Comparison'); @@ -101,9 +102,9 @@ $expressionWizard->expression('ISEVEN(A1)') ->setStyle($yellowStyle); $conditionalStyles[] = $expressionWizard->getConditional(); -$spreadsheet->getActiveSheet() - ->getStyle($expressionWizard->getCellRange()) - ->setConditionalStyles($conditionalStyles); +//$spreadsheet->getActiveSheet() +// ->getStyle($expressionWizard->getCellRange()) +// ->setConditionalStyles($conditionalStyles); // Set rules for Sales Grid Row match against Country Comparison $cellRange = 'A17:D22'; diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php index 75d6856e..df9daab3 100644 --- a/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php +++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php @@ -122,7 +122,7 @@ abstract class WizardAbstract return "{$worksheet}{$column}{$row}"; } - protected static function reverseAdjustCellRef(string $condition, string $cellRange): string + public static function reverseAdjustCellRef(string $condition, string $cellRange): string { $conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($cellRange))); [$referenceCell] = $conditionalRange[0]; diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index c02e3420..ecfc1a6a 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -13,6 +13,7 @@ use PhpOffice\PhpSpreadsheet\Shared\Xls; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Color; use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard; use PhpOffice\PhpSpreadsheet\Style\Protection; use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; use PhpOffice\PhpSpreadsheet\Worksheet\SheetView; @@ -569,7 +570,7 @@ class Worksheet extends BIFFwriter $arrConditional[$conditional->getHashCode()] = true; // Write CFRULE record - $this->writeCFRule($conditional); + $this->writeCFRule($conditional, $cellCoordinate); } } } @@ -2779,7 +2780,7 @@ class Worksheet extends BIFFwriter /** * Write CFRule Record. */ - private function writeCFRule(Conditional $conditional): void + private function writeCFRule(Conditional $conditional, string $cellRange): void { $record = 0x01B1; // Record identifier $type = null; // Type of the CF @@ -2832,21 +2833,59 @@ class Worksheet extends BIFFwriter // $szValue2 : size of the formula data for second value or formula $arrConditions = $conditional->getConditions(); $numConditions = count($arrConditions); + + $szValue1 = 0x0000; + $szValue2 = 0x0000; + $operand1 = null; + $operand2 = null; + if ($numConditions == 1) { - $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000); - $szValue2 = 0x0000; - $operand1 = pack('Cv', 0x1E, $arrConditions[0]); - $operand2 = null; + if (is_int($arrConditions[0]) || is_float($arrConditions[0])) { + $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000); + $operand1 = pack('Cv', 0x1E, $arrConditions[0]); + } else { + try { + $formula1 = Wizard\WizardAbstract::reverseAdjustCellRef((string) $arrConditions[0], $cellRange); + $this->parser->parse($formula1); + $formula1 = $this->parser->toReversePolish(); + $szValue1 = strlen($formula1); + } catch (PhpSpreadsheetException $e) { + var_dump("PARSER EXCEPTION: {$e->getMessage()}"); + $formula1 = null; + } + $operand1 = $formula1; + } } elseif ($numConditions == 2 && ($conditional->getOperatorType() == Conditional::OPERATOR_BETWEEN)) { - $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000); - $szValue2 = ($arrConditions[1] <= 65535 ? 3 : 0x0000); - $operand1 = pack('Cv', 0x1E, $arrConditions[0]); - $operand2 = pack('Cv', 0x1E, $arrConditions[1]); - } else { - $szValue1 = 0x0000; - $szValue2 = 0x0000; - $operand1 = null; - $operand2 = null; + if (is_int($arrConditions[0]) || is_float($arrConditions[0])) { + $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000); + $operand1 = pack('Cv', 0x1E, $arrConditions[0]); + } else { + try { + $formula1 = Wizard\WizardAbstract::reverseAdjustCellRef((string) $arrConditions[0], $cellRange); + $this->parser->parse($formula1); + $formula1 = $this->parser->toReversePolish(); + $szValue1 = strlen($formula1); + } catch (PhpSpreadsheetException $e) { + var_dump("PARSER EXCEPTION: {$e->getMessage()}"); + $formula1 = null; + } + $operand1 = $formula1; + } + if (is_int($arrConditions[1]) || is_float($arrConditions[1])) { + $szValue2 = ($arrConditions[1] <= 65535 ? 3 : 0x0000); + $operand2 = pack('Cv', 0x1E, $arrConditions[1]); + } else { + try { + $formula2 = Wizard\WizardAbstract::reverseAdjustCellRef((string) $arrConditions[1], $cellRange); + $this->parser->parse($formula2); + $formula2 = $this->parser->toReversePolish(); + $szValue2 = strlen($formula2); + } catch (PhpSpreadsheetException $e) { + var_dump("PARSER EXCEPTION: {$e->getMessage()}"); + $formula2 = null; + } + $operand2 = $formula2; + } } // $flags : Option flags