Skip to content

Commit

Permalink
Add setCellVerticalAlignment() & allow setShouldWrapText() to be expl…
Browse files Browse the repository at this point in the history
…icitly set as false

- 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 box#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
  • Loading branch information
jonnott committed Feb 11, 2022
1 parent cc42c1d commit a0dceef
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 24 deletions.
28 changes: 15 additions & 13 deletions docs/_pages/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)`<br>`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)`<br>`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

Expand All @@ -150,6 +151,7 @@ $style = (new StyleBuilder())
->setFontColor(Color::BLUE)
->setShouldWrapText()
->setCellAlignment(CellAlignment::RIGHT)
->setCellVerticalAlignment(CellVerticalAlignment::BOTTOM)
->setBackgroundColor(Color::YELLOW)
->build();

Expand Down
2 changes: 1 addition & 1 deletion src/Spout/Common/Entity/Style/CellAlignment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions src/Spout/Common/Entity/Style/CellVerticalAlignment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Box\Spout\Common\Entity\Style;

/**
* Class CellVerticalAlignment
* This class provides constants to work with text vertical alignment.
*/
abstract class CellVerticalAlignment
{
public const BOTTOM = 'bottom';
public const CENTER = 'center';
public const DISTRIBUTED = 'distributed';
public const JUSTIFY = 'justify';
public const TOP = 'top';

private static $VALID_ALIGNMENTS = [
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]);
}
}
46 changes: 46 additions & 0 deletions src/Spout/Common/Entity/Style/Style.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -354,6 +361,14 @@ public function getCellAlignment()
return $this->cellAlignment;
}

/**
* @return string
*/
public function getCellVerticalAlignment()
{
return $this->cellVerticalAlignment;
}

/**
* @param string $cellAlignment The cell alignment
*
Expand All @@ -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
*/
Expand All @@ -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
*/
Expand All @@ -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
*/
Expand Down
20 changes: 20 additions & 0 deletions src/Spout/Writer/Common/Creator/Style/StyleBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
*
Expand Down
7 changes: 5 additions & 2 deletions src/Spout/Writer/Common/Manager/Style/StyleMerger.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
44 changes: 39 additions & 5 deletions src/Spout/Writer/ODS/Manager/Style/StyleManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -277,12 +278,13 @@ private function getFontSectionContent($style)
*/
private function getParagraphPropertiesSectionContent($style)
{
if (!$style->shouldApplyCellAlignment()) {
if (!$style->shouldApplyCellAlignment() && !$style->shouldApplyCellVerticalAlignment()) {
return '';
}

return '<style:paragraph-properties '
. $this->getCellAlignmentSectionContent($style)
. $this->getCellVerticalAlignmentSectionContent($style)
. '/>';
}

Expand All @@ -301,6 +303,21 @@ private function getCellAlignmentSectionContent($style)
);
}

/**
* Returns the contents of the cell vertical alignment definition for the "<style:paragraph-properties>" 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
Expand All @@ -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 "<style:table-cell-properties>" section, inside "<style:style>" section
*
Expand All @@ -329,8 +362,8 @@ private function getTableCellPropertiesSectionContent($style)
{
$content = '<style:table-cell-properties ';

if ($style->shouldWrapText()) {
$content .= $this->getWrapTextXMLContent();
if ($style->hasSetWrapText()) {
$content .= $this->getWrapTextXMLContent($style->shouldWrapText());
}

if ($style->shouldApplyBorder()) {
Expand All @@ -349,11 +382,12 @@ private function getTableCellPropertiesSectionContent($style)
/**
* Returns the contents of the wrap text definition for the "<style:table-cell-properties>" 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" ';
}

/**
Expand Down
9 changes: 6 additions & 3 deletions src/Spout/Writer/XLSX/Manager/Style/StyleManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 .= '<alignment';
if ($style->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 .= '</xf>';
Expand Down
19 changes: 19 additions & 0 deletions tests/Spout/Writer/Common/Creator/StyleBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
*/
Expand All @@ -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();
}
}
21 changes: 21 additions & 0 deletions tests/Spout/Writer/ODS/WriterWithStyleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Loading

0 comments on commit a0dceef

Please sign in to comment.