From a0dceef096dd5f4926efd620c654455c65a2adb1 Mon Sep 17 00:00:00 2001 From: Jon Nott Date: Thu, 10 Feb 2022 19:46:02 +0000 Subject: [PATCH] Add setCellVerticalAlignment() & allow setShouldWrapText() to be explicitly set as false MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add Common\Entity\Style\CellVerticalAlignment - duplicate get/set/hasSet/shouldApply methods for CellAlignment in Common\Entity\Style\Style for CellVerticalAlignment instead, plus corresponding properties - add setCellVerticalAlignment method to Common\Creator\Style\StyleBuilder - add vertical alignment to StyleMerger:: mergeCellProperties() - adjust wrapText logic in mergeCellProperties() to fix issue https://github.com/box/spout/issues/829 - apply vertical cell styling for both XLSX and ODS, via corresponding StyleManager classes - transform vertical alignment ‘center’ to ‘middle’ for ODS - fix logic around wrapText such that the choice whether to include wrapping styles depends on hasSetWrapText() being true, and then use shouldWrapText() thereafter to either set wrapping or no wrapping (for XLSX, wrapText=“1” or wrapText=“0”, for ODS, wrap-option=wrap or wrap-option=no-wrap). previously there was no way to set wrapping to be OFF, only to set it to be ON. - add new tests to ensure shouldWrapText(false) results in the correct negated wrapText (XLSX) / wrap-option (ODS) styles - add new tests to StyleBuilderTest for vertical alignment - add vertical alignment to documentation.md --- docs/_pages/documentation.md | 28 +++++------ .../Common/Entity/Style/CellAlignment.php | 2 +- .../Entity/Style/CellVerticalAlignment.php | 34 ++++++++++++++ src/Spout/Common/Entity/Style/Style.php | 46 +++++++++++++++++++ .../Common/Creator/Style/StyleBuilder.php | 20 ++++++++ .../Common/Manager/Style/StyleMerger.php | 7 ++- .../Writer/ODS/Manager/Style/StyleManager.php | 44 ++++++++++++++++-- .../XLSX/Manager/Style/StyleManager.php | 9 ++-- .../Common/Creator/StyleBuilderTest.php | 19 ++++++++ .../Spout/Writer/ODS/WriterWithStyleTest.php | 21 +++++++++ .../Spout/Writer/XLSX/WriterWithStyleTest.php | 20 ++++++++ 11 files changed, 226 insertions(+), 24 deletions(-) create mode 100644 src/Spout/Common/Entity/Style/CellVerticalAlignment.php diff --git a/docs/_pages/documentation.md b/docs/_pages/documentation.md index ce142cf34..30d9cf524 100755 --- a/docs/_pages/documentation.md +++ b/docs/_pages/documentation.md @@ -116,19 +116,20 @@ $reader->setShouldPreserveEmptyRows(true); For fonts and alignments, {{ site.spout_html }} does not support all the possible formatting options yet. But you can find the most important ones: -| Category | Property | API -|:---------------------|:---------------|:-------------------------------------- -| Font | Bold | `StyleBuilder::setFontBold()` -| | Italic | `StyleBuilder::setFontItalic()` -| | Underline | `StyleBuilder::setFontUnderline()` -| | Strikethrough | `StyleBuilder::setFontStrikethrough()` -| | Font name | `StyleBuilder::setFontName('Arial')` -| | Font size | `StyleBuilder::setFontSize(14)` -| | Font color | `StyleBuilder::setFontColor(Color::BLUE)`
`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))` -| Alignment | Cell alignment | `StyleBuilder::setCellAlignment(CellAlignment::CENTER)` -| | Wrap text | `StyleBuilder::setShouldWrapText(true)` -| Format _(XLSX only)_ | Number format | `StyleBuilder::setFormat('0.000')` -| | Date format | `StyleBuilder::setFormat('m/d/yy h:mm')` +| Category | Property | API +|:---------------------|:------------------------|:-------------------------------------- +| Font | Bold | `StyleBuilder::setFontBold()` +| | Italic | `StyleBuilder::setFontItalic()` +| | Underline | `StyleBuilder::setFontUnderline()` +| | Strikethrough | `StyleBuilder::setFontStrikethrough()` +| | Font name | `StyleBuilder::setFontName('Arial')` +| | Font size | `StyleBuilder::setFontSize(14)` +| | Font color | `StyleBuilder::setFontColor(Color::BLUE)`
`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))` +| Alignment | Cell alignment | `StyleBuilder::setCellAlignment(CellAlignment::CENTER)` +| | Cell vertical alignment | `StyleBuilder::setCellVerticalAlignment(CellVerticalAlignment::CENTER)` +| | Wrap text | `StyleBuilder::setShouldWrapText(true)` +| Format _(XLSX only)_ | Number format | `StyleBuilder::setFormat('0.000')` +| | Date format | `StyleBuilder::setFormat('m/d/yy h:mm')` ### Styling rows @@ -150,6 +151,7 @@ $style = (new StyleBuilder()) ->setFontColor(Color::BLUE) ->setShouldWrapText() ->setCellAlignment(CellAlignment::RIGHT) + ->setCellVerticalAlignment(CellVerticalAlignment::BOTTOM) ->setBackgroundColor(Color::YELLOW) ->build(); diff --git a/src/Spout/Common/Entity/Style/CellAlignment.php b/src/Spout/Common/Entity/Style/CellAlignment.php index 0411fb972..71dfd3c93 100644 --- a/src/Spout/Common/Entity/Style/CellAlignment.php +++ b/src/Spout/Common/Entity/Style/CellAlignment.php @@ -3,7 +3,7 @@ namespace Box\Spout\Common\Entity\Style; /** - * Class Alignment + * Class CellAlignment * This class provides constants to work with text alignment. */ abstract class CellAlignment diff --git a/src/Spout/Common/Entity/Style/CellVerticalAlignment.php b/src/Spout/Common/Entity/Style/CellVerticalAlignment.php new file mode 100644 index 000000000..dd4cccec6 --- /dev/null +++ b/src/Spout/Common/Entity/Style/CellVerticalAlignment.php @@ -0,0 +1,34 @@ + 1, + self::CENTER => 1, + self::DISTRIBUTED => 1, + self::JUSTIFY => 1, + self::TOP => 1, + ]; + + /** + * @param string $cellVerticalAlignment + * + * @return bool Whether the given cell vertical alignment is valid + */ + public static function isValid($cellVerticalAlignment) + { + return isset(self::$VALID_ALIGNMENTS[$cellVerticalAlignment]); + } +} diff --git a/src/Spout/Common/Entity/Style/Style.php b/src/Spout/Common/Entity/Style/Style.php index 0af1450ad..9ca4e2fa7 100644 --- a/src/Spout/Common/Entity/Style/Style.php +++ b/src/Spout/Common/Entity/Style/Style.php @@ -61,6 +61,13 @@ class Style /** @var bool Whether the cell alignment property was set */ private $hasSetCellAlignment = false; + /** @var bool Whether specific cell vertical alignment should be applied */ + private $shouldApplyCellVerticalAlignment = false; + /** @var string Cell vertical alignment */ + private $cellVerticalAlignment; + /** @var bool Whether the cell vertical alignment property was set */ + private $hasSetCellVerticalAlignment = false; + /** @var bool Whether the text should wrap in the cell (useful for long or multi-lines text) */ private $shouldWrapText = false; /** @var bool Whether the wrap text property was set */ @@ -354,6 +361,14 @@ public function getCellAlignment() return $this->cellAlignment; } + /** + * @return string + */ + public function getCellVerticalAlignment() + { + return $this->cellVerticalAlignment; + } + /** * @param string $cellAlignment The cell alignment * @@ -369,6 +384,21 @@ public function setCellAlignment($cellAlignment) return $this; } + /** + * @param string $cellVerticalAlignment The cell vertical alignment + * + * @return Style + */ + public function setCellVerticalAlignment($cellVerticalAlignment) + { + $this->cellVerticalAlignment = $cellVerticalAlignment; + $this->hasSetCellVerticalAlignment = true; + $this->shouldApplyCellVerticalAlignment = true; + $this->isEmpty = false; + + return $this; + } + /** * @return bool */ @@ -377,6 +407,14 @@ public function hasSetCellAlignment() return $this->hasSetCellAlignment; } + /** + * @return bool + */ + public function hasSetCellVerticalAlignment() + { + return $this->hasSetCellVerticalAlignment; + } + /** * @return bool Whether specific cell alignment should be applied */ @@ -385,6 +423,14 @@ public function shouldApplyCellAlignment() return $this->shouldApplyCellAlignment; } + /** + * @return bool Whether specific cell alignment should be applied + */ + public function shouldApplyCellVerticalAlignment() + { + return $this->shouldApplyCellVerticalAlignment; + } + /** * @return bool */ diff --git a/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php b/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php index bc2d406f3..db9fec060 100644 --- a/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php +++ b/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php @@ -4,6 +4,7 @@ use Box\Spout\Common\Entity\Style\Border; use Box\Spout\Common\Entity\Style\CellAlignment; +use Box\Spout\Common\Entity\Style\CellVerticalAlignment; use Box\Spout\Common\Entity\Style\Style; use Box\Spout\Common\Exception\InvalidArgumentException; @@ -143,6 +144,25 @@ public function setCellAlignment($cellAlignment) return $this; } + /** + * Sets the cell vertical alignment. + * + * @param string $cellVerticalAlignment The cell vertical alignment + * + * @throws InvalidArgumentException If the given cell vertical alignment is not valid + * @return StyleBuilder + */ + public function setCellVerticalAlignment($cellVerticalAlignment) + { + if (!CellVerticalAlignment::isValid($cellVerticalAlignment)) { + throw new InvalidArgumentException('Invalid cell vertical alignment value'); + } + + $this->style->setCellVerticalAlignment($cellVerticalAlignment); + + return $this; + } + /** * Set a border * diff --git a/src/Spout/Writer/Common/Manager/Style/StyleMerger.php b/src/Spout/Writer/Common/Manager/Style/StyleMerger.php index cdc45d533..575082d72 100644 --- a/src/Spout/Writer/Common/Manager/Style/StyleMerger.php +++ b/src/Spout/Writer/Common/Manager/Style/StyleMerger.php @@ -82,12 +82,15 @@ private function mergeOtherFontProperties(Style $styleToUpdate, Style $style, St */ private function mergeCellProperties(Style $styleToUpdate, Style $style, Style $baseStyle) { - if (!$style->hasSetWrapText() && $baseStyle->shouldWrapText()) { - $styleToUpdate->setShouldWrapText(); + if (!$style->hasSetWrapText() && $baseStyle->hasSetWrapText()) { + $styleToUpdate->setShouldWrapText($baseStyle->shouldWrapText()); } if (!$style->hasSetCellAlignment() && $baseStyle->shouldApplyCellAlignment()) { $styleToUpdate->setCellAlignment($baseStyle->getCellAlignment()); } + if (!$style->hasSetCellVerticalAlignment() && $baseStyle->shouldApplyCellVerticalAlignment()) { + $styleToUpdate->setCellVerticalAlignment($baseStyle->getCellVerticalAlignment()); + } if ($style->getBorder() === null && $baseStyle->shouldApplyBorder()) { $styleToUpdate->setBorder($baseStyle->getBorder()); } diff --git a/src/Spout/Writer/ODS/Manager/Style/StyleManager.php b/src/Spout/Writer/ODS/Manager/Style/StyleManager.php index 34f75c78d..657274266 100644 --- a/src/Spout/Writer/ODS/Manager/Style/StyleManager.php +++ b/src/Spout/Writer/ODS/Manager/Style/StyleManager.php @@ -4,6 +4,7 @@ use Box\Spout\Common\Entity\Style\BorderPart; use Box\Spout\Common\Entity\Style\CellAlignment; +use Box\Spout\Common\Entity\Style\CellVerticalAlignment; use Box\Spout\Writer\Common\Entity\Worksheet; use Box\Spout\Writer\ODS\Helper\BorderHelper; @@ -277,12 +278,13 @@ private function getFontSectionContent($style) */ private function getParagraphPropertiesSectionContent($style) { - if (!$style->shouldApplyCellAlignment()) { + if (!$style->shouldApplyCellAlignment() && !$style->shouldApplyCellVerticalAlignment()) { return ''; } return 'getCellAlignmentSectionContent($style) + . $this->getCellVerticalAlignmentSectionContent($style) . '/>'; } @@ -301,6 +303,21 @@ private function getCellAlignmentSectionContent($style) ); } + /** + * Returns the contents of the cell vertical alignment definition for the "" section + * + * @param \Box\Spout\Common\Entity\Style\Style $style + * + * @return string + */ + private function getCellVerticalAlignmentSectionContent($style) + { + return \sprintf( + ' fo:vertical-align="%s" ', + $this->transformCellVerticalAlignment($style->getCellVerticalAlignment()) + ); + } + /** * Even though "left" and "right" alignments are part of the spec, and interpreted * respectively as "start" and "end", using the recommended values increase compatibility @@ -319,6 +336,22 @@ private function transformCellAlignment($cellAlignment) } } + /** + * Spec uses 'middle' rather than 'center' + * http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1420236_253892949 + * + * @param string $cellAlignment + * + * @return string + */ + private function transformCellVerticalAlignment($cellVerticalAlignment) + { + switch ($cellVerticalAlignment) { + case CellVerticalAlignment::CENTER: return 'middle'; + default: return $cellVerticalAlignment; + } + } + /** * Returns the contents of the "" section, inside "" section * @@ -329,8 +362,8 @@ private function getTableCellPropertiesSectionContent($style) { $content = 'shouldWrapText()) { - $content .= $this->getWrapTextXMLContent(); + if ($style->hasSetWrapText()) { + $content .= $this->getWrapTextXMLContent($style->shouldWrapText()); } if ($style->shouldApplyBorder()) { @@ -349,11 +382,12 @@ private function getTableCellPropertiesSectionContent($style) /** * Returns the contents of the wrap text definition for the "" section * + * @param boolean $shouldWrapText * @return string */ - private function getWrapTextXMLContent() + private function getWrapTextXMLContent($shouldWrapText) { - return ' fo:wrap-option="wrap" style:vertical-align="automatic" '; + return ' fo:wrap-option="' . ($shouldWrapText ? '' : 'no-') . 'wrap" style:vertical-align="automatic" '; } /** diff --git a/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php b/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php index 4a72b2f09..e91b9a502 100644 --- a/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php +++ b/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php @@ -250,14 +250,17 @@ protected function getCellXfsSectionContent() $content .= \sprintf(' applyBorder="%d"', $style->shouldApplyBorder() ? 1 : 0); - if ($style->shouldApplyCellAlignment() || $style->shouldWrapText()) { + if ($style->shouldApplyCellAlignment() || $style->shouldApplyCellVerticalAlignment() || $style->hasSetWrapText()) { $content .= ' applyAlignment="1">'; $content .= 'shouldApplyCellAlignment()) { $content .= \sprintf(' horizontal="%s"', $style->getCellAlignment()); } - if ($style->shouldWrapText()) { - $content .= ' wrapText="1"'; + if ($style->shouldApplyCellVerticalAlignment()) { + $content .= \sprintf(' vertical="%s"', $style->getCellVerticalAlignment()); + } + if ($style->hasSetWrapText()) { + $content .= ' wrapText="' . ($style->shouldWrapText() ? '1' : '0') . '"'; } $content .= '/>'; $content .= ''; diff --git a/tests/Spout/Writer/Common/Creator/StyleBuilderTest.php b/tests/Spout/Writer/Common/Creator/StyleBuilderTest.php index e1eb00ff6..33dd47966 100644 --- a/tests/Spout/Writer/Common/Creator/StyleBuilderTest.php +++ b/tests/Spout/Writer/Common/Creator/StyleBuilderTest.php @@ -4,6 +4,7 @@ use Box\Spout\Common\Entity\Style\Border; use Box\Spout\Common\Entity\Style\CellAlignment; +use Box\Spout\Common\Entity\Style\CellVerticalAlignment; use Box\Spout\Common\Entity\Style\Color; use Box\Spout\Common\Exception\InvalidArgumentException; use Box\Spout\Writer\Common\Manager\Style\StyleMerger; @@ -53,6 +54,15 @@ public function testStyleBuilderShouldApplyCellAlignment() $this->assertTrue($style->shouldApplyCellAlignment()); } + /** + * @return void + */ + public function testStyleBuilderShouldApplyCellVerticalAlignment() + { + $style = (new StyleBuilder())->setCellVerticalAlignment(CellVerticalAlignment::CENTER)->build(); + $this->assertTrue($style->shouldApplyCellVerticalAlignment()); + } + /** * @return void */ @@ -61,4 +71,13 @@ public function testStyleBuilderShouldThrowOnInvalidCellAlignment() $this->expectException(InvalidArgumentException::class); (new StyleBuilder())->setCellAlignment('invalid_cell_alignment')->build(); } + + /** + * @return void + */ + public function testStyleBuilderShouldThrowOnInvalidCellVerticalAlignment() + { + $this->expectException(InvalidArgumentException::class); + (new StyleBuilder())->setCellVerticalAlignment('invalid_cell_alignment')->build(); + } } diff --git a/tests/Spout/Writer/ODS/WriterWithStyleTest.php b/tests/Spout/Writer/ODS/WriterWithStyleTest.php index 3c7a6fd7e..4e271afa4 100644 --- a/tests/Spout/Writer/ODS/WriterWithStyleTest.php +++ b/tests/Spout/Writer/ODS/WriterWithStyleTest.php @@ -188,6 +188,27 @@ public function testAddRowShouldAddWrapTextAlignmentInfoInStylesXmlFileIfSpecifi $this->assertFirstChildHasAttributeEquals('wrap', $customStyleElement, 'table-cell-properties', 'fo:wrap-option'); } + /** + * @return void + */ + public function testAddRowShouldAddNegatedWrapTextAlignmentInfoInStylesXmlFileIfSpecified() + { + $fileName = 'test_add_row_should_add_negated_wrap_text_alignment.ods'; + + $style = (new StyleBuilder())->setShouldWrapText(false)->build(); + $dataRows = $this->createStyledRowsFromValues([ + ['ods--11', 'ods--12'], + ], $style); + + $this->writeToODSFile($dataRows, $fileName); + + $styleElements = $this->getCellStyleElementsFromContentXmlFile($fileName); + $this->assertCount(2, $styleElements, 'There should be 2 styles (default and custom)'); + + $customStyleElement = $styleElements[1]; + $this->assertFirstChildHasAttributeEquals('no-wrap', $customStyleElement, 'table-cell-properties', 'fo:wrap-option'); + } + /** * @return void */ diff --git a/tests/Spout/Writer/XLSX/WriterWithStyleTest.php b/tests/Spout/Writer/XLSX/WriterWithStyleTest.php index 57859b67a..32f4123a4 100644 --- a/tests/Spout/Writer/XLSX/WriterWithStyleTest.php +++ b/tests/Spout/Writer/XLSX/WriterWithStyleTest.php @@ -271,6 +271,26 @@ public function testAddRowShouldAddWrapTextAlignmentInfoInStylesXmlFileIfSpecifi $this->assertFirstChildHasAttributeEquals('1', $xfElement, 'alignment', 'wrapText'); } + /** + * @return void + */ + public function testAddRowShouldAddNegatedWrapTextAlignmentInfoInStylesXmlFileIfSpecified() + { + $fileName = 'test_add_row_should_add_negated_wrap_text_alignment.xlsx'; + + $style = (new StyleBuilder())->setShouldWrapText(false)->build(); + $dataRows = $this->createStyledRowsFromValues([ + ['xlsx--11', 'xlsx--12'], + ], $style); + + $this->writeToXLSXFile($dataRows, $fileName); + + $cellXfsDomElement = $this->getXmlSectionFromStylesXmlFile($fileName, 'cellXfs'); + $xfElement = $cellXfsDomElement->getElementsByTagName('xf')->item(1); + $this->assertEquals(1, $xfElement->getAttribute('applyAlignment')); + $this->assertFirstChildHasAttributeEquals('0', $xfElement, 'alignment', 'wrapText'); + } + /** * @return void */