From b1cf5f8aa3771d8d692380cb633b9baa4c261d3e Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 16 Feb 2023 19:58:15 -0800 Subject: [PATCH 1/4] WIP Conditional Formatting Improvements for Xlsx Fix #3370. Conditional styles are always generated with 5 borders (right, left, top, bottom, diagonal) even though the border style is none in each case. For the spreadsheet in question, top and bottom were inappropriate and interfered with the desired formatting. A new border style, BORDER_OMIT is added which will cause the Xlsx Writer to not generate that style. All conditional borders will be initialized with that value. Any border included in the Xml will, of course, change it to the specified type. Fix #3202. User wants a condition to use "No format set" as you can in Excel. A new boolean property `$noFormatSet`, along with setter and getter, is added to Style/Conditional. It is initialized to false. User can call setter to change it. More importantly for the issue in question, if the Xlsx Reader encounters a `cfRule` tag which does not have a `dxfId` attribute (i.e. no style is associated with the rule), it will set noFormatSet to true. Similarly, the Xlsx writer will not generate a `dfxId` tag when noFormatSet is true. This change is applicable only to Xlsx. Html, Csv, and Ods do not have support for Conditional Formatting. Limited support was added to Xls with PR #2696 in April 2022 and PR #2702 about a month later. However, with the current release code, Xls equivalents of the two new test spreadsheets in this PR are too complicated to be handled correctly by PhpSpreadsheet - loading and then saving them as Xls results in Excel complaining of corruption, and the results don't meet expectations. Since I have no idea how BIFF works, and since the problems with those spreadsheets are not caused by this PR, I am not planning to address those problems at this time. --- .../Reader/Xlsx/ConditionalStyles.php | 5 +- src/PhpSpreadsheet/Reader/Xlsx/Styles.php | 22 +++++-- src/PhpSpreadsheet/Style/Border.php | 6 +- src/PhpSpreadsheet/Style/Borders.php | 22 +++---- src/PhpSpreadsheet/Style/Conditional.php | 15 +++++ src/PhpSpreadsheet/Style/Style.php | 2 +- src/PhpSpreadsheet/Writer/Xlsx/Style.php | 10 +-- src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 2 +- .../Reader/Xlsx/ConditionalBorderTest.php | 38 ++++++++++++ .../Xlsx/ConditionalNoFormatSetTest.php | 58 ++++++++++++++++++ tests/data/Reader/XLSX/issue.3202.xlsx | Bin 0 -> 10728 bytes tests/data/Reader/XLSX/issue.3370.xlsx | Bin 0 -> 10159 bytes 12 files changed, 155 insertions(+), 25 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalBorderTest.php create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalNoFormatSetTest.php create mode 100644 tests/data/Reader/XLSX/issue.3202.xlsx create mode 100644 tests/data/Reader/XLSX/issue.3370.xlsx diff --git a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php index aa6b62b26f..59bf5b84eb 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php @@ -163,7 +163,7 @@ private function readConditionalStyles(SimpleXMLElement $xmlSheet): array $conditionals = []; foreach ($xmlSheet->conditionalFormatting as $conditional) { foreach ($conditional->cfRule as $cfRule) { - if (Conditional::isValidConditionType((string) $cfRule['type']) && isset($this->dxfs[(int) ($cfRule['dxfId'])])) { + if (Conditional::isValidConditionType((string) $cfRule['type']) && (!isset($cfRule['dxfId']) || isset($this->dxfs[(int) ($cfRule['dxfId'])]))) { $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; } elseif ((string) $cfRule['type'] == Conditional::CONDITION_DATABAR) { $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; @@ -197,6 +197,7 @@ private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array $objConditional = new Conditional(); $objConditional->setConditionType((string) $cfRule['type']); $objConditional->setOperatorType((string) $cfRule['operator']); + $objConditional->setNoFormatSet(!isset($cfRule['dxfId'])); if ((string) $cfRule['text'] != '') { $objConditional->setText((string) $cfRule['text']); @@ -227,7 +228,7 @@ private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array $objConditional->setDataBar( $this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions) // @phpstan-ignore-line ); - } else { + } elseif (isset($cfRule['dxfId'])) { $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]); } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php index 5b089fa874..705b319e75 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php @@ -206,11 +206,21 @@ public function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderSt $borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH); } - $this->readBorder($borderStyle->getLeft(), $borderStyleXml->left); - $this->readBorder($borderStyle->getRight(), $borderStyleXml->right); - $this->readBorder($borderStyle->getTop(), $borderStyleXml->top); - $this->readBorder($borderStyle->getBottom(), $borderStyleXml->bottom); - $this->readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal); + if (isset($borderStyleXml->left)) { + $this->readBorder($borderStyle->getLeft(), $borderStyleXml->left); + } + if (isset($borderStyleXml->right)) { + $this->readBorder($borderStyle->getRight(), $borderStyleXml->right); + } + if (isset($borderStyleXml->top)) { + $this->readBorder($borderStyle->getTop(), $borderStyleXml->top); + } + if (isset($borderStyleXml->bottom)) { + $this->readBorder($borderStyle->getBottom(), $borderStyleXml->bottom); + } + if (isset($borderStyleXml->diagonal)) { + $this->readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal); + } } private function getAttribute(SimpleXMLElement $xml, string $attribute): string @@ -233,6 +243,8 @@ private function readBorder(Border $border, SimpleXMLElement $borderXml): void $style = $this->getAttribute($borderXml, 'style'); if ($style !== '') { $border->setBorderStyle((string) $style); + } else { + $border->setBorderStyle(Border::BORDER_NONE); } if (isset($borderXml->color)) { $border->getColor()->setARGB($this->readColor($borderXml->color)); diff --git a/src/PhpSpreadsheet/Style/Border.php b/src/PhpSpreadsheet/Style/Border.php index c6fb51f159..568e1e006a 100644 --- a/src/PhpSpreadsheet/Style/Border.php +++ b/src/PhpSpreadsheet/Style/Border.php @@ -21,6 +21,7 @@ class Border extends Supervisor const BORDER_SLANTDASHDOT = 'slantDashDot'; const BORDER_THICK = 'thick'; const BORDER_THIN = 'thin'; + const BORDER_OMIT = 'omit'; // should be used only for Conditional /** * Border style. @@ -48,7 +49,7 @@ class Border extends Supervisor * Leave this value at default unless you understand exactly what * its ramifications are */ - public function __construct($isSupervisor = false) + public function __construct($isSupervisor = false, bool $isConditional = false) { // Supervisor? parent::__construct($isSupervisor); @@ -60,6 +61,9 @@ public function __construct($isSupervisor = false) if ($isSupervisor) { $this->color->bindParent($this, 'color'); } + if ($isConditional) { + $this->borderStyle = self::BORDER_OMIT; + } } /** diff --git a/src/PhpSpreadsheet/Style/Borders.php b/src/PhpSpreadsheet/Style/Borders.php index 56a52709ed..a1247e859d 100644 --- a/src/PhpSpreadsheet/Style/Borders.php +++ b/src/PhpSpreadsheet/Style/Borders.php @@ -96,27 +96,27 @@ class Borders extends Supervisor * Leave this value at default unless you understand exactly what * its ramifications are */ - public function __construct($isSupervisor = false) + public function __construct($isSupervisor = false, bool $isConditional = false) { // Supervisor? parent::__construct($isSupervisor); // Initialise values - $this->left = new Border($isSupervisor); - $this->right = new Border($isSupervisor); - $this->top = new Border($isSupervisor); - $this->bottom = new Border($isSupervisor); - $this->diagonal = new Border($isSupervisor); + $this->left = new Border($isSupervisor, $isConditional); + $this->right = new Border($isSupervisor, $isConditional); + $this->top = new Border($isSupervisor, $isConditional); + $this->bottom = new Border($isSupervisor, $isConditional); + $this->diagonal = new Border($isSupervisor, $isConditional); $this->diagonalDirection = self::DIAGONAL_NONE; // Specially for supervisor if ($isSupervisor) { // Initialize pseudo-borders - $this->allBorders = new Border(true); - $this->outline = new Border(true); - $this->inside = new Border(true); - $this->vertical = new Border(true); - $this->horizontal = new Border(true); + $this->allBorders = new Border(true, $isConditional); + $this->outline = new Border(true, $isConditional); + $this->inside = new Border(true, $isConditional); + $this->vertical = new Border(true, $isConditional); + $this->horizontal = new Border(true, $isConditional); // bind parent if we are a supervisor $this->left->bindParent($this, 'left'); diff --git a/src/PhpSpreadsheet/Style/Conditional.php b/src/PhpSpreadsheet/Style/Conditional.php index 019c0648d6..de565d3458 100644 --- a/src/PhpSpreadsheet/Style/Conditional.php +++ b/src/PhpSpreadsheet/Style/Conditional.php @@ -115,6 +115,9 @@ class Conditional implements IComparable */ private $style; + /** @var bool */ + private $noFormatSet = false; + /** * Create a new Conditional. */ @@ -124,6 +127,18 @@ public function __construct() $this->style = new Style(false, true); } + public function getNoFormatSet(): bool + { + return $this->noFormatSet; + } + + public function setNoFormatSet(bool $noFormatSet): self + { + $this->noFormatSet = $noFormatSet; + + return $this; + } + /** * Get Condition type. * diff --git a/src/PhpSpreadsheet/Style/Style.php b/src/PhpSpreadsheet/Style/Style.php index 62f20e92f3..be70639eb2 100644 --- a/src/PhpSpreadsheet/Style/Style.php +++ b/src/PhpSpreadsheet/Style/Style.php @@ -99,7 +99,7 @@ public function __construct($isSupervisor = false, $isConditional = false) // Initialise values $this->font = new Font($isSupervisor, $isConditional); $this->fill = new Fill($isSupervisor, $isConditional); - $this->borders = new Borders($isSupervisor); + $this->borders = new Borders($isSupervisor, $isConditional); $this->alignment = new Alignment($isSupervisor, $isConditional); $this->numberFormat = new NumberFormat($isSupervisor, $isConditional); $this->protection = new Protection($isSupervisor, $isConditional); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Style.php b/src/PhpSpreadsheet/Writer/Xlsx/Style.php index b9146647b7..29f4eec957 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Style.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Style.php @@ -527,8 +527,11 @@ private function writeCellStyleDxf(XMLWriter $objWriter, \PhpOffice\PhpSpreadshe private function writeBorderPr(XMLWriter $objWriter, $name, Border $border): void { // Write BorderPr - if ($border->getBorderStyle() != Border::BORDER_NONE) { - $objWriter->startElement($name); + if ($border->getBorderStyle() === Border::BORDER_OMIT) { + return; + } + $objWriter->startElement($name); + if ($border->getBorderStyle() !== Border::BORDER_NONE) { $objWriter->writeAttribute('style', $border->getBorderStyle()); // color @@ -536,10 +539,9 @@ private function writeBorderPr(XMLWriter $objWriter, $name, Border $border): voi $objWriter->startElement('color'); $objWriter->writeAttribute('rgb', $border->getColor()->getARGB()); $objWriter->endElement(); - - $objWriter->endElement(); } } + $objWriter->endElement(); } /** diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index f4904d2557..53c4512457 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -681,7 +681,7 @@ private function writeConditionalFormatting(XMLWriter $objWriter, Phpspreadsheet $objWriter->writeAttribute('type', $conditional->getConditionType()); self::writeAttributeIf( $objWriter, - ($conditional->getConditionType() != Conditional::CONDITION_DATABAR), + ($conditional->getConditionType() !== Conditional::CONDITION_DATABAR && $conditional->getNoFormatSet() === false), 'dxfId', (string) $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) ); diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalBorderTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalBorderTest.php new file mode 100644 index 0000000000..6c0bc44080 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalBorderTest.php @@ -0,0 +1,38 @@ +'; + self::assertStringContainsString($expected, $data); + + $reader = new XlsxReader(); + $spreadsheet = $reader->load($testfile); + $outfile = File::temporaryFilename(); + $writer = new XlsxWriter($spreadsheet); + $writer->save($outfile); + $spreadsheet->disconnectWorksheets(); + + $file = 'zip://'; + $file .= $outfile; + $file .= '#xl/styles.xml'; + $data = file_get_contents($file); + unlink($outfile); + + $expected = ''; + self::assertStringContainsString($expected, $data); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalNoFormatSetTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalNoFormatSetTest.php new file mode 100644 index 0000000000..570a613ea5 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalNoFormatSetTest.php @@ -0,0 +1,58 @@ +' + . '' + . '$A1>5' + . '' + . '' + . '$A1>1' + . '' + . '' + . '$A1=3' + . '' + . ''; + self::assertStringContainsString($expected, $data); + + $reader = new XlsxReader(); + $spreadsheet = $reader->load($testfile); + $outfile = File::temporaryFilename(); + $writer = new XlsxWriter($spreadsheet); + $writer->save($outfile); + $spreadsheet->disconnectWorksheets(); + + $file = 'zip://'; + $file .= $outfile; + $file .= '#xl/worksheets/sheet1.xml'; + $data = file_get_contents($file); + unlink($outfile); + + $expected = '' + . '' + . '$A1=3' + . '' + . '' + . '$A1>5' + . '' + . '' + . '$A1>1' + . '' + . ''; + self::assertStringContainsString($expected, $data); + } +} diff --git a/tests/data/Reader/XLSX/issue.3202.xlsx b/tests/data/Reader/XLSX/issue.3202.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..93733238f03b946a04b2d436a1f98429b7949330 GIT binary patch literal 10728 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 CB?UqN literal 0 HcmV?d00001 From 09d074516aaca180d7443610736695bddcfcf8cc Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 17 Feb 2023 17:11:59 -0800 Subject: [PATCH 2/4] Update Documentation, Write Alignment and Font Less Often It doesn't cause any particular harm except for small increases in file size and run time, but Alignment tags are written even when (a) all its attributes are null for Conditional Formatting, and (b) when the xml specifically indicates that Alignment should not be applied. Similarly, Font is written even when all its attributes are null for Conditional Formatting. There are some errors in the Conditional Formatting documentation. Specifying a solid fill color in a Conditional Style requires the use of endColor, not StartColor. The discussion of Order of Evaluating is not entirely accurate. I have changed it to what I believe is an accurate explanation of how Excel works; and also added a mention that other spreadsheet programs might not work the same way, adding a couple of illustrations of the difference. The description of the multiple conditions did not quite match the diagram. 'Stop if true' was a blank paragraph; it is now described, and the new 'No format set' option is described in that paragraph since (I think) it would be used most often in conjunction with 'Stop if true'. --- docs/topics/conditional-formatting.md | 35 ++++-- .../images/11-21-CF-Rule-Order-2.pic2.png | Bin 0 -> 43342 bytes .../images/11-21-CF-Rule-Order-2.pic3.png | Bin 0 -> 5183 bytes src/PhpSpreadsheet/Writer/Xlsx/Style.php | 106 +++++++++++------- .../Reader/Xlsx/ConditionalBorderTest.php | 2 +- 5 files changed, 92 insertions(+), 51 deletions(-) create mode 100644 docs/topics/images/11-21-CF-Rule-Order-2.pic2.png create mode 100644 docs/topics/images/11-21-CF-Rule-Order-2.pic3.png diff --git a/docs/topics/conditional-formatting.md b/docs/topics/conditional-formatting.md index 086a794206..bc04c7cc04 100644 --- a/docs/topics/conditional-formatting.md +++ b/docs/topics/conditional-formatting.md @@ -43,7 +43,7 @@ $conditional->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERA $conditional->addCondition(80); $conditional->getStyle()->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_DARKGREEN); $conditional->getStyle()->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID); -$conditional->getStyle()->getFill()->getStartColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_GREEN); +$conditional->getStyle()->getFill()->getEndColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_GREEN); $conditionalStyles = $spreadsheet->getActiveSheet()->getStyle('A1:A10')->getConditionalStyles(); $conditionalStyles[] = $conditional; @@ -63,7 +63,7 @@ $wizard = $wizardFactory->newRule(\PhpOffice\PhpSpreadsheet\Style\ConditionalFor $wizard->greaterThan(80); $wizard->getStyle()->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_DARKGREEN); $wizard->getStyle()->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID); -$wizard->getStyle()->getFill()->getStartColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_GREEN); +$wizard->getStyle()->getFill()->getEndColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_GREEN); $conditional = $wizard->getConditional(); ``` @@ -84,7 +84,7 @@ $conditional2->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPER $conditional2->addCondition(10); $conditional2->getStyle()->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_DARKRED); $conditional2->getStyle()->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID); -$conditional2->getStyle()->getFill()->getStartColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_RED); +$conditional2->getStyle()->getFill()->getEndColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_RED); $conditionalStyles = $spreadsheet->getActiveSheet()->getStyle('A1:A10')->getConditionalStyles(); $conditionalStyles[] = $conditional2; @@ -98,7 +98,7 @@ $wizard = $wizardFactory->newRule(\PhpOffice\PhpSpreadsheet\Style\ConditionalFor $wizard->lessThan(10); $wizard->getStyle()->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_DARKGREEN); $wizard->getStyle()->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID); -$wizard->getStyle()->getFill()->getStartColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_GREEN); +$wizard->getStyle()->getFill()->getEndColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_GREEN); $conditional = $wizard->getConditional(); ``` @@ -106,9 +106,9 @@ $conditional = $wizard->getConditional(); ### Order of Evaluating Multiple Rules/Conditions -`$conditionalStyles` is an array, which not only represents multiple conditions that can be applied to a cell (or range of cells), but also the order in which they are checked. MS Excel will check each of those conditions in turn in the order they are defined; and will stop checking once it finds a first matching rule. This means that the order of checking conditions can be important. +`$conditionalStyles` is an array, which not only represents multiple conditions that can be applied to a cell (or range of cells), but also the order in which they are checked. Some spreadsheet programs stop processing conditions once they find a match. On the other hand, MS Excel will check each of those conditions in turn in the order they are defined. It will stop checking only if it finds a matching rule that specifies 'stop if true'; however, if it finds conflicting matches with conflicting formatting (e.g. both set a background fill color but use different choices), the first match wins. In either case, this means that the order of checking conditions can be important. -Consider the following. We have one condition that checks if a cell value is between -10 and 10, styling the cell in yellow if that condition matches; and a second condition that checks if the cell value is equal to 0, styling the cell in red if that matches. +Consider the following. We have one condition that checks if a cell value is between -2 and 2, styling the fill color of the cell in yellow if that condition matches; and a second condition that checks if the cell value is equal to 0, styling the fill color of the cell in red if that matches. - Yellow if value is between -2 and 2 - Red if value equals 0 @@ -120,12 +120,22 @@ If the rule order is reversed - Red if value equals 0 - Yellow if value is between -2 and 2 -then the cell containing the value 0 will be rendered in red, because that is the first matching condition; and the between rule will not be assessed for that cell. +then the cell containing the value 0 will be rendered in red, because that is the first matching condition; and the formatting in the other condition conflicts with this, so is discarded. ![11-21-CF-Rule-Order-2.png](./images/11-21-CF-Rule-Order-2.png) So when you have multiple conditions where the rules might "overlap", the order of these is important. +If the cell matches multiple conditions, Excel (but not most other spreadsheet programs) will apply non-conflicting styles from each match. So, for the example above, if we wanted a match of 0 to have a different *font* color rather than a different *fill* color, Excel can honor both. + +![11-21-CF-Rule-Order-2.pic2.png](./images/11-21-CF-Rule-Order-2.pic2.png) + +Here is the same spreadsheet opened in LibreOffice - cell A4 has only the first conditional style applied to it. (You would see the same if you checked 'Stop if True' in Excel.) If you want the spreadsheet to appear the same in both Excel and LibreOffice, you would need to use more complicated conditions. + +![11-21-CF-Rule-Order-2.pic2.png](./images/11-21-CF-Rule-Order-2.pic3.png) + +PhpSpreadsheet supports the setting of [Stop If True](#stop-if-true-and-no-format-set). + ### Reader/Writer Support @@ -704,8 +714,17 @@ This example can also be found in the [code samples](https://github.com/PHPOffic ## General Notes -### Stop If True +### Stop If True, and No Format Set +Normally, Excel continues to check even after it finds a match. To tell it to stop once a match is found, 'stop if true' should be specified: +```php +$conditional->setStopIfTrue(true); +``` + +Sometimes you want a matched cell to just show its unconditional format. This is most useful in conjunction with 'stop if true'. +```php +$conditional->setNoFormatSet(true); +``` ### Changing the Cell Range diff --git a/docs/topics/images/11-21-CF-Rule-Order-2.pic2.png b/docs/topics/images/11-21-CF-Rule-Order-2.pic2.png new file mode 100644 index 0000000000000000000000000000000000000000..d3596a5c5cbc556a3726b34604715a29cc93bae4 GIT binary patch literal 43342 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 UtqQF@wdn`YMChs&!YqUS2ALMePyhe` literal 0 HcmV?d00001 diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Style.php b/src/PhpSpreadsheet/Writer/Xlsx/Style.php index 29f4eec957..f62ed4f97d 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Style.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Style.php @@ -260,13 +260,21 @@ private function writePatternFill(XMLWriter $objWriter, Fill $fill): void $objWriter->endElement(); } + private function startFont(XMLWriter $objWriter, bool &$fontStarted): void + { + if (!$fontStarted) { + $fontStarted = true; + $objWriter->startElement('font'); + } + } + /** * Write Font. */ private function writeFont(XMLWriter $objWriter, Font $font): void { + $fontStarted = false; // font - $objWriter->startElement('font'); // Weird! The order of these elements actually makes a difference when opening Xlsx // files in Excel2003 with the compatibility pack. It's not documented behaviour, // and makes for a real WTF! @@ -275,6 +283,7 @@ private function writeFont(XMLWriter $objWriter, Font $font): void // for conditional formatting). Otherwise it will apparently not be picked up in conditional // formatting style dialog if ($font->getBold() !== null) { + $this->startFont($objWriter, $fontStarted); $objWriter->startElement('b'); $objWriter->writeAttribute('val', $font->getBold() ? '1' : '0'); $objWriter->endElement(); @@ -282,6 +291,7 @@ private function writeFont(XMLWriter $objWriter, Font $font): void // Italic if ($font->getItalic() !== null) { + $this->startFont($objWriter, $fontStarted); $objWriter->startElement('i'); $objWriter->writeAttribute('val', $font->getItalic() ? '1' : '0'); $objWriter->endElement(); @@ -289,6 +299,7 @@ private function writeFont(XMLWriter $objWriter, Font $font): void // Strikethrough if ($font->getStrikethrough() !== null) { + $this->startFont($objWriter, $fontStarted); $objWriter->startElement('strike'); $objWriter->writeAttribute('val', $font->getStrikethrough() ? '1' : '0'); $objWriter->endElement(); @@ -296,6 +307,7 @@ private function writeFont(XMLWriter $objWriter, Font $font): void // Underline if ($font->getUnderline() !== null) { + $this->startFont($objWriter, $fontStarted); $objWriter->startElement('u'); $objWriter->writeAttribute('val', $font->getUnderline()); $objWriter->endElement(); @@ -303,6 +315,7 @@ private function writeFont(XMLWriter $objWriter, Font $font): void // Superscript / subscript if ($font->getSuperscript() === true || $font->getSubscript() === true) { + $this->startFont($objWriter, $fontStarted); $objWriter->startElement('vertAlign'); if ($font->getSuperscript() === true) { $objWriter->writeAttribute('val', 'superscript'); @@ -314,6 +327,7 @@ private function writeFont(XMLWriter $objWriter, Font $font): void // Size if ($font->getSize() !== null) { + $this->startFont($objWriter, $fontStarted); $objWriter->startElement('sz'); $objWriter->writeAttribute('val', StringHelper::formatNumber($font->getSize())); $objWriter->endElement(); @@ -321,6 +335,7 @@ private function writeFont(XMLWriter $objWriter, Font $font): void // Foreground color if ($font->getColor()->getARGB() !== null) { + $this->startFont($objWriter, $fontStarted); $objWriter->startElement('color'); $objWriter->writeAttribute('rgb', $font->getColor()->getARGB()); $objWriter->endElement(); @@ -328,12 +343,15 @@ private function writeFont(XMLWriter $objWriter, Font $font): void // Name if ($font->getName() !== null) { + $this->startFont($objWriter, $fontStarted); $objWriter->startElement('name'); $objWriter->writeAttribute('val', $font->getName()); $objWriter->endElement(); } - $objWriter->endElement(); + if ($fontStarted) { + $objWriter->endElement(); + } } /** @@ -401,40 +419,42 @@ private function writeCellStyleXf(XMLWriter $objWriter, \PhpOffice\PhpSpreadshee $objWriter->writeAttribute('applyNumberFormat', ($spreadsheet->getDefaultStyle()->getNumberFormat()->getHashCode() != $style->getNumberFormat()->getHashCode()) ? '1' : '0'); $objWriter->writeAttribute('applyFill', ($spreadsheet->getDefaultStyle()->getFill()->getHashCode() != $style->getFill()->getHashCode()) ? '1' : '0'); $objWriter->writeAttribute('applyBorder', ($spreadsheet->getDefaultStyle()->getBorders()->getHashCode() != $style->getBorders()->getHashCode()) ? '1' : '0'); - $objWriter->writeAttribute('applyAlignment', ($spreadsheet->getDefaultStyle()->getAlignment()->getHashCode() != $style->getAlignment()->getHashCode()) ? '1' : '0'); + $applyAlignment = ($spreadsheet->getDefaultStyle()->getAlignment()->getHashCode() != $style->getAlignment()->getHashCode()) ? '1' : '0'; + $objWriter->writeAttribute('applyAlignment', $applyAlignment); if ($style->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $style->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) { $objWriter->writeAttribute('applyProtection', 'true'); } // alignment - $objWriter->startElement('alignment'); - $vertical = Alignment::VERTICAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getVertical()] ?? ''; - $horizontal = Alignment::HORIZONTAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getHorizontal()] ?? ''; - if ($horizontal !== '') { - $objWriter->writeAttribute('horizontal', $horizontal); - } - if ($vertical !== '') { - $objWriter->writeAttribute('vertical', $vertical); - } + if ($applyAlignment === '1') { + $objWriter->startElement('alignment'); + $vertical = Alignment::VERTICAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getVertical()] ?? ''; + $horizontal = Alignment::HORIZONTAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getHorizontal()] ?? ''; + if ($horizontal !== '') { + $objWriter->writeAttribute('horizontal', $horizontal); + } + if ($vertical !== '') { + $objWriter->writeAttribute('vertical', $vertical); + } - $textRotation = 0; - if ($style->getAlignment()->getTextRotation() >= 0) { - $textRotation = $style->getAlignment()->getTextRotation(); - } else { - $textRotation = 90 - $style->getAlignment()->getTextRotation(); - } - $objWriter->writeAttribute('textRotation', (string) $textRotation); + if ($style->getAlignment()->getTextRotation() >= 0) { + $textRotation = $style->getAlignment()->getTextRotation(); + } else { + $textRotation = 90 - $style->getAlignment()->getTextRotation(); + } + $objWriter->writeAttribute('textRotation', (string) $textRotation); - $objWriter->writeAttribute('wrapText', ($style->getAlignment()->getWrapText() ? 'true' : 'false')); - $objWriter->writeAttribute('shrinkToFit', ($style->getAlignment()->getShrinkToFit() ? 'true' : 'false')); + $objWriter->writeAttribute('wrapText', ($style->getAlignment()->getWrapText() ? 'true' : 'false')); + $objWriter->writeAttribute('shrinkToFit', ($style->getAlignment()->getShrinkToFit() ? 'true' : 'false')); - if ($style->getAlignment()->getIndent() > 0) { - $objWriter->writeAttribute('indent', (string) $style->getAlignment()->getIndent()); - } - if ($style->getAlignment()->getReadOrder() > 0) { - $objWriter->writeAttribute('readingOrder', (string) $style->getAlignment()->getReadOrder()); + if ($style->getAlignment()->getIndent() > 0) { + $objWriter->writeAttribute('indent', (string) $style->getAlignment()->getIndent()); + } + if ($style->getAlignment()->getReadOrder() > 0) { + $objWriter->writeAttribute('readingOrder', (string) $style->getAlignment()->getReadOrder()); + } + $objWriter->endElement(); } - $objWriter->endElement(); // protection if ($style->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $style->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) { @@ -469,26 +489,28 @@ private function writeCellStyleDxf(XMLWriter $objWriter, \PhpOffice\PhpSpreadshe $this->writeFill($objWriter, $style->getFill()); // alignment - $objWriter->startElement('alignment'); $horizontal = Alignment::HORIZONTAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getHorizontal()] ?? ''; - if ($horizontal) { - $objWriter->writeAttribute('horizontal', $horizontal); - } $vertical = Alignment::VERTICAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getVertical()] ?? ''; - if ($vertical) { - $objWriter->writeAttribute('vertical', $vertical); - } + $rotation = $style->getAlignment()->getTextRotation(); + if ($horizontal || $vertical || $rotation !== null) { + $objWriter->startElement('alignment'); + if ($horizontal) { + $objWriter->writeAttribute('horizontal', $horizontal); + } + if ($vertical) { + $objWriter->writeAttribute('vertical', $vertical); + } - if ($style->getAlignment()->getTextRotation() !== null) { - $textRotation = 0; - if ($style->getAlignment()->getTextRotation() >= 0) { - $textRotation = $style->getAlignment()->getTextRotation(); - } else { - $textRotation = 90 - $style->getAlignment()->getTextRotation(); + if ($rotation !== null) { + if ($rotation >= 0) { + $textRotation = $rotation; + } else { + $textRotation = 90 - $rotation; + } + $objWriter->writeAttribute('textRotation', (string) $textRotation); } - $objWriter->writeAttribute('textRotation', (string) $textRotation); + $objWriter->endElement(); } - $objWriter->endElement(); // border $this->writeBorder($objWriter, $style->getBorders()); diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalBorderTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalBorderTest.php index 6c0bc44080..df5ebe64ab 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalBorderTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalBorderTest.php @@ -32,7 +32,7 @@ public function testReadAndWriteConditionalBorder(): void $data = file_get_contents($file); unlink($outfile); - $expected = ''; + $expected = ''; self::assertStringContainsString($expected, $data); } } From b17d46a7558c7cfa057b6060fc5b017df2895929 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 19 Feb 2023 06:40:26 -0800 Subject: [PATCH 3/4] Xlsx Writer Allow StartColor for Conditional Solid Fill To set a solid fill in a non-conditional style, you set StartColor (xml will use that value as fgColor and a default value as bgColor). If you instead set EndColor (xml will use that value as bgColor and a default value as fgColor), the styling will not work as expected. However, for conditional styles, if you set StartColor (xml will use that value as fgColor and not specify bgColor), the styling will not work as expected. If you instead set EndColor (xml will use that value as bgColor and not specify fgColor), the styling will work as expected. Together, this means that you need to use different methods for non-conditional style fill than for conditional style fill. This isn't a big problem, but it is a bit weird. This PR changes Xlsx Writer so that if (a) fill is olid and (b) startColor is specified and (c) endColor is null, the xml will be written as bgColor without specifying fgColor. This means that you can set StartColor for both conditional and non-conditional and get the expected styling. You may, of course, continue to specify EndColor instead for conditional. --- src/PhpSpreadsheet/Writer/Xlsx/Style.php | 9 +- .../Writer/Xlsx/ConditionalFillTest.php | 90 +++++++++++++++++++ .../Writer/Xlsx/FloatsRetainedTest.php | 12 +-- 3 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Writer/Xlsx/ConditionalFillTest.php diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Style.php b/src/PhpSpreadsheet/Writer/Xlsx/Style.php index f62ed4f97d..0261f22e56 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Style.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Style.php @@ -243,8 +243,13 @@ private function writePatternFill(XMLWriter $objWriter, Fill $fill): void if (self::writePatternColors($fill)) { // fgColor if ($fill->getStartColor()->getARGB()) { - $objWriter->startElement('fgColor'); - $objWriter->writeAttribute('rgb', $fill->getStartColor()->getARGB()); + if (!$fill->getEndColor()->getARGB() && $fill->getFillType() === Fill::FILL_SOLID) { + $objWriter->startElement('bgColor'); + $objWriter->writeAttribute('rgb', $fill->getStartColor()->getARGB()); + } else { + $objWriter->startElement('fgColor'); + $objWriter->writeAttribute('rgb', $fill->getStartColor()->getARGB()); + } $objWriter->endElement(); } // bgColor diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/ConditionalFillTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/ConditionalFillTest.php new file mode 100644 index 0000000000..c3db072514 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/ConditionalFillTest.php @@ -0,0 +1,90 @@ +getActiveSheet(); + $sheet->getCell('A1')->setValue(10); + $sheet->getCell('A2')->setValue(20); + $sheet->getCell('A3')->setValue(30); + $sheet->getCell('A4')->setValue(40); + + $sheet->getStyle('A1')->getFill()->setFillType(Fill::FILL_SOLID); + $sheet->getStyle('A1')->getFill()->getStartColor()->setARGB('FFFF0000'); + $sheet->getStyle('A2')->getFill()->setFillType(Fill::FILL_SOLID); + // Need to specify StartColor for desired effect + $sheet->getStyle('A2')->getFill()->getEndColor()->setARGB('FF00FF00'); + + $conditional1 = new Conditional(); + $conditional1->setConditionType(Conditional::CONDITION_CELLIS); + $conditional1->setOperatorType(Conditional::OPERATOR_GREATERTHAN); + $conditional1->addCondition(35); + $conditional1->getStyle()->getFill()->setFillType(Fill::FILL_SOLID); + $conditional1->getStyle()->getFill()->getEndColor()->setARGB('FF0000FF'); + + $conditional2 = new Conditional(); + $conditional2->setConditionType(Conditional::CONDITION_CELLIS); + $conditional2->setOperatorType(Conditional::OPERATOR_LESSTHAN); + $conditional2->addCondition(35); + $conditional2->getStyle()->getFill()->setFillType(Fill::FILL_SOLID); + // Before issue37303202, had needed to specify EndColor for desired effect. + // This was the opposite of non-Conditional style. + $conditional2->getStyle()->getFill()->getStartColor()->setARGB('FFFFFF00'); + + $conditionalStyles = $spreadsheet->getActiveSheet()->getStyle('A3:A4')->getConditionalStyles(); + $conditionalStyles[] = $conditional1; + $conditionalStyles[] = $conditional2; + + $spreadsheet->getActiveSheet()->getStyle('A3:A4')->setConditionalStyles($conditionalStyles); + $sheet->setSelectedCells('C1'); + + $outfile = File::temporaryFilename(); + $writer = new XlsxWriter($spreadsheet); + $writer->save($outfile); + $spreadsheet->disconnectWorksheets(); + + $file = 'zip://'; + $file .= $outfile; + $file .= '#xl/styles.xml'; + $data = file_get_contents($file); + unlink($outfile); + + $expected = '' + . '' + . '' + . '' + . '' + . ''; + self::assertStringContainsString($expected, $data, 'style for A1'); + $expected = '' + . '' + . '' + . '' + . '' + . ''; + self::assertStringContainsString($expected, $data, 'style for A2'); + $expected = '' + . '' + . '' + . '' + . ''; + self::assertStringContainsString($expected, $data, 'conditional 1'); + $expected = '' + . '' + . '' + . '' + . ''; + self::assertStringContainsString($expected, $data, 'conditional 2'); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php index 6ba8316b46..34992a51fa 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php @@ -18,17 +18,19 @@ class FloatsRetainedTest extends TestCase public function testIntyFloatsRetainedByWriter($value): void { $outputFilename = File::temporaryFilename(); - $sheet = new Spreadsheet(); - $sheet->getActiveSheet()->getCell('A1')->setValue($value); + $spreadsheet = new Spreadsheet(); + $spreadsheet->getActiveSheet()->getCell('A1')->setValue($value); - $writer = new Writer($sheet); + $writer = new Writer($spreadsheet); $writer->save($outputFilename); + $spreadsheet->disconnectWorksheets(); $reader = new Reader(); - $sheet = $reader->load($outputFilename); + $spreadsheet2 = $reader->load($outputFilename); unlink($outputFilename); - self::assertSame($value, $sheet->getActiveSheet()->getCell('A1')->getValue()); + self::assertSame($value, $spreadsheet2->getActiveSheet()->getCell('A1')->getValue()); + $spreadsheet2->disconnectWorksheets(); } public function providerIntyFloatsRetainedByWriter(): array From e32bf1a4459d03782f6e726c29a5744d410cc68b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 23 Feb 2023 21:20:56 -0800 Subject: [PATCH 4/4] Fix Some (Not Many) Xls Problems I will open an issue for the (pre-existing) remainder. --- .../Writer/Xls/Style/CellBorder.php | 1 + src/PhpSpreadsheet/Writer/Xls/Worksheet.php | 16 ++++++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/PhpSpreadsheet/Writer/Xls/Style/CellBorder.php b/src/PhpSpreadsheet/Writer/Xls/Style/CellBorder.php index 8d47d6aa7c..bc14920654 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Style/CellBorder.php +++ b/src/PhpSpreadsheet/Writer/Xls/Style/CellBorder.php @@ -24,6 +24,7 @@ class CellBorder Border::BORDER_DASHDOTDOT => 0x0B, Border::BORDER_MEDIUMDASHDOTDOT => 0x0C, Border::BORDER_SLANTDASHDOT => 0x0D, + Border::BORDER_OMIT => 0x00, ]; public static function style(Border $border): int diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index 79ab874c4b..7cca70c09e 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -2891,15 +2891,11 @@ private function writeCFRule( $bFormatProt = 0; } // Border - $bBorderLeft = ($conditional->getStyle()->getBorders()->getLeft()->getColor()->getARGB() == Color::COLOR_BLACK - && $conditional->getStyle()->getBorders()->getLeft()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); - $bBorderRight = ($conditional->getStyle()->getBorders()->getRight()->getColor()->getARGB() == Color::COLOR_BLACK - && $conditional->getStyle()->getBorders()->getRight()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); - $bBorderTop = ($conditional->getStyle()->getBorders()->getTop()->getColor()->getARGB() == Color::COLOR_BLACK - && $conditional->getStyle()->getBorders()->getTop()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); - $bBorderBottom = ($conditional->getStyle()->getBorders()->getBottom()->getColor()->getARGB() == Color::COLOR_BLACK - && $conditional->getStyle()->getBorders()->getBottom()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); - if ($bBorderLeft == 0 || $bBorderRight == 0 || $bBorderTop == 0 || $bBorderBottom == 0) { + $bBorderLeft = ($conditional->getStyle()->getBorders()->getLeft()->getBorderStyle() !== Border::BORDER_OMIT) ? 1 : 0; + $bBorderRight = ($conditional->getStyle()->getBorders()->getRight()->getBorderStyle() !== Border::BORDER_OMIT) ? 1 : 0; + $bBorderTop = ($conditional->getStyle()->getBorders()->getTop()->getBorderStyle() !== Border::BORDER_OMIT) ? 1 : 0; + $bBorderBottom = ($conditional->getStyle()->getBorders()->getBottom()->getBorderStyle() !== Border::BORDER_OMIT) ? 1 : 0; + if ($bBorderLeft === 1 || $bBorderRight === 1 || $bBorderTop === 1 || $bBorderBottom === 1) { $bFormatBorder = 1; } else { $bFormatBorder = 0; @@ -2908,7 +2904,7 @@ private function writeCFRule( $bFillStyle = ($conditional->getStyle()->getFill()->getFillType() === null ? 0 : 1); $bFillColor = ($conditional->getStyle()->getFill()->getStartColor()->getARGB() === null ? 0 : 1); $bFillColorBg = ($conditional->getStyle()->getFill()->getEndColor()->getARGB() === null ? 0 : 1); - if ($bFillStyle == 0 || $bFillColor == 0 || $bFillColorBg == 0) { + if ($bFillStyle == 1 || $bFillColor == 1 || $bFillColorBg == 1) { $bFormatFill = 1; } else { $bFormatFill = 0;