From 887d6ef0339aaa04fc15691e3890f8234ccfb8c2 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 | 38 +++++++++++++++ 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 | 43 +++++++++++++++-- .../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, 229 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 ce142cf..30d9cf5 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 0411fb9..71dfd3c 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 0000000..d1c39bb --- /dev/null +++ b/src/Spout/Common/Entity/Style/CellVerticalAlignment.php @@ -0,0 +1,38 @@ + 1, + self::BASELINE => 1, + self::BOTTOM => 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 0af1450..9ca4e2f 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 @@ class Style return $this->cellAlignment; } + /** + * @return string + */ + public function getCellVerticalAlignment() + { + return $this->cellVerticalAlignment; + } + /** * @param string $cellAlignment The cell alignment * @@ -369,6 +384,21 @@ class Style 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 @@ class Style return $this->hasSetCellAlignment; } + /** + * @return bool + */ + public function hasSetCellVerticalAlignment() + { + return $this->hasSetCellVerticalAlignment; + } + /** * @return bool Whether specific cell alignment should be applied */ @@ -385,6 +423,14 @@ class Style 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 bc2d406..db9fec0 100644 --- a/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php +++ b/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php @@ -4,6 +4,7 @@ namespace Box\Spout\Writer\Common\Creator\Style; 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 @@ class StyleBuilder 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 cdc45d5..575082d 100644 --- a/src/Spout/Writer/Common/Manager/Style/StyleMerger.php +++ b/src/Spout/Writer/Common/Manager/Style/StyleMerger.php @@ -82,12 +82,15 @@ class StyleMerger */ 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 34f75c7..34ef3e6 100644 --- a/src/Spout/Writer/ODS/Manager/Style/StyleManager.php +++ b/src/Spout/Writer/ODS/Manager/Style/StyleManager.php @@ -4,6 +4,7 @@ namespace Box\Spout\Writer\ODS\Manager\Style; 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 @@ EOD; */ private function getParagraphPropertiesSectionContent($style) { - if (!$style->shouldApplyCellAlignment()) { + if (!$style->shouldApplyCellAlignment() && !$style->shouldApplyCellVerticalAlignment()) { return ''; } return 'getCellAlignmentSectionContent($style) + . $this->getCellVerticalAlignmentSectionContent($style) . '/>'; } @@ -301,6 +303,21 @@ EOD; ); } + /** + * 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,21 @@ EOD; } } + /** + * 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) + { + return ($cellVerticalAlignment === CellVerticalAlignment::CENTER) + ? 'middle' + : $cellVerticalAlignment; + } + /** * Returns the contents of the "" section, inside "" section * @@ -329,8 +361,8 @@ EOD; { $content = 'shouldWrapText()) { - $content .= $this->getWrapTextXMLContent(); + if ($style->hasSetWrapText()) { + $content .= $this->getWrapTextXMLContent($style->shouldWrapText()); } if ($style->shouldApplyBorder()) { @@ -349,11 +381,12 @@ EOD; /** * 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 4a72b2f..e91b9a5 100644 --- a/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php +++ b/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php @@ -250,14 +250,17 @@ EOD; $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 e1eb00f..33dd479 100644 --- a/tests/Spout/Writer/Common/Creator/StyleBuilderTest.php +++ b/tests/Spout/Writer/Common/Creator/StyleBuilderTest.php @@ -4,6 +4,7 @@ namespace Box\Spout\Writer\Common\Creator\Style; 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 @@ class StyleBuilderTest extends TestCase $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 @@ class StyleBuilderTest extends TestCase $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 3c7a6fd..4e271af 100644 --- a/tests/Spout/Writer/ODS/WriterWithStyleTest.php +++ b/tests/Spout/Writer/ODS/WriterWithStyleTest.php @@ -188,6 +188,27 @@ class WriterWithStyleTest extends TestCase $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 57859b6..32f4123 100644 --- a/tests/Spout/Writer/XLSX/WriterWithStyleTest.php +++ b/tests/Spout/Writer/XLSX/WriterWithStyleTest.php @@ -271,6 +271,26 @@ class WriterWithStyleTest extends TestCase $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 */