Skip to content
This repository was archived by the owner on May 26, 2022. It is now read-only.

Commit 3e0afd8

Browse files
authored
Apply custom style to empty cells if needed (#307)
Fixes #295 If a row should be written with a custom style, the handling of empty cells should change. Instead of being skipped entirely, empty cells will be applied the custom style, if this style has custom background color or borders. If not, then the cell definition can still be skipped.
1 parent d4e57b1 commit 3e0afd8

File tree

6 files changed

+110
-15
lines changed

6 files changed

+110
-15
lines changed

src/Spout/Writer/Style/Style.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ public function shouldApplyFont()
288288

289289
/**
290290
* Sets the background color
291-
* @param $color ARGB color (@see Color)
291+
* @param string $color ARGB color (@see Color)
292292
* @return Style
293293
*/
294294
public function setBackgroundColor($color)

src/Spout/Writer/XLSX/Helper/StyleHelper.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ protected function registerFill($style)
7171

7272
if ($backgroundColor) {
7373
$isBackgroundColorRegistered = isset($this->registeredFills[$backgroundColor]);
74-
74+
7575
// We need to track the already registered background definitions
7676
if ($isBackgroundColorRegistered) {
7777
$registeredStyleId = $this->registeredFills[$backgroundColor];
@@ -113,13 +113,32 @@ protected function registerBorder($style)
113113
$this->styleIdToBorderMappingTable[$styleId] = count($this->registeredBorders);
114114
}
115115

116-
} else {
116+
} else {
117117
// If no border should be applied - the mapping is the default border: 0
118118
$this->styleIdToBorderMappingTable[$styleId] = 0;
119119
}
120120
}
121121

122122

123+
/**
124+
* For empty cells, we can specify a style or not. If no style are specified,
125+
* then the software default will be applied. But sometimes, it may be useful
126+
* to override this default style, for instance if the cell should have a
127+
* background color different than the default one or some borders
128+
* (fonts property don't really matter here).
129+
*
130+
* @param int $styleId
131+
* @return bool Whether the cell should define a custom style
132+
*/
133+
public function shouldApplyStyleOnEmptyCell($styleId)
134+
{
135+
$hasStyleCustomFill = (isset($this->styleIdToFillMappingTable[$styleId]) && $this->styleIdToFillMappingTable[$styleId] !== 0);
136+
$hasStyleCustomBorders = (isset($this->styleIdToBorderMappingTable[$styleId]) && $this->styleIdToBorderMappingTable[$styleId] !== 0);
137+
138+
return ($hasStyleCustomFill || $hasStyleCustomBorders);
139+
}
140+
141+
123142
/**
124143
* Returns the content of the "styles.xml" file, given a list of styles.
125144
*

src/Spout/Writer/XLSX/Internal/Workbook.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public function addNewSheet()
8686
$sheet = new Sheet($newSheetIndex);
8787

8888
$worksheetFilesFolder = $this->fileSystemHelper->getXlWorksheetsFolder();
89-
$worksheet = new Worksheet($sheet, $worksheetFilesFolder, $this->sharedStringsHelper, $this->shouldUseInlineStrings);
89+
$worksheet = new Worksheet($sheet, $worksheetFilesFolder, $this->sharedStringsHelper, $this->styleHelper, $this->shouldUseInlineStrings);
9090
$this->worksheets[] = $worksheet;
9191

9292
return $worksheet;

src/Spout/Writer/XLSX/Internal/Worksheet.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class Worksheet implements WorksheetInterface
3030
/** @var \Box\Spout\Writer\XLSX\Helper\SharedStringsHelper Helper to write shared strings */
3131
protected $sharedStringsHelper;
3232

33+
/** @var \Box\Spout\Writer\XLSX\Helper\StyleHelper Helper to work with styles */
34+
protected $styleHelper;
35+
3336
/** @var bool Whether inline or shared strings should be used */
3437
protected $shouldUseInlineStrings;
3538

@@ -46,13 +49,15 @@ class Worksheet implements WorksheetInterface
4649
* @param \Box\Spout\Writer\Common\Sheet $externalSheet The associated "external" sheet
4750
* @param string $worksheetFilesFolder Temporary folder where the files to create the XLSX will be stored
4851
* @param \Box\Spout\Writer\XLSX\Helper\SharedStringsHelper $sharedStringsHelper Helper for shared strings
52+
* @param \Box\Spout\Writer\XLSX\Helper\StyleHelper Helper to work with styles
4953
* @param bool $shouldUseInlineStrings Whether inline or shared strings should be used
5054
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
5155
*/
52-
public function __construct($externalSheet, $worksheetFilesFolder, $sharedStringsHelper, $shouldUseInlineStrings)
56+
public function __construct($externalSheet, $worksheetFilesFolder, $sharedStringsHelper, $styleHelper, $shouldUseInlineStrings)
5357
{
5458
$this->externalSheet = $externalSheet;
5559
$this->sharedStringsHelper = $sharedStringsHelper;
60+
$this->styleHelper = $styleHelper;
5661
$this->shouldUseInlineStrings = $shouldUseInlineStrings;
5762

5863
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
@@ -177,8 +182,13 @@ private function getCellXML($rowIndex, $cellNumber, $cellValue, $styleId)
177182
} else if (CellHelper::isNumeric($cellValue)) {
178183
$cellXML .= '><v>' . $cellValue . '</v></c>';
179184
} else if (empty($cellValue)) {
180-
// don't write empty cells (not appending to $cellXML is the right behavior!)
181-
$cellXML = '';
185+
if ($this->styleHelper->shouldApplyStyleOnEmptyCell($styleId)) {
186+
$cellXML .= '/>';
187+
} else {
188+
// don't write empty cells that do no need styling
189+
// NOTE: not appending to $cellXML is the right behavior!!
190+
$cellXML = '';
191+
}
182192
} else {
183193
throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cellValue));
184194
}

tests/Spout/Writer/XLSX/Helper/StyleHelperTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Box\Spout\Writer\XLSX\Helper;
44

5+
use Box\Spout\Writer\Style\Border;
6+
use Box\Spout\Writer\Style\BorderBuilder;
7+
use Box\Spout\Writer\Style\Color;
58
use Box\Spout\Writer\Style\StyleBuilder;
69

710
/**
@@ -57,6 +60,27 @@ public function testRegisterStyleShouldReuseAlreadyRegisteredStyles()
5760
$this->assertEquals(1, $registeredStyle2->getId());
5861
}
5962

63+
/**
64+
* @return void
65+
*/
66+
public function testShouldApplyStyleOnEmptyCell()
67+
{
68+
$styleWithFont = (new StyleBuilder())->setFontBold()->build();
69+
$styleWithBackground = (new StyleBuilder())->setBackgroundColor(Color::BLUE)->build();
70+
71+
$border = (new BorderBuilder())->setBorderBottom(Color::GREEN)->build();
72+
$styleWithBorder = (new StyleBuilder())->setBorder($border)->build();
73+
74+
$styleHelper = new StyleHelper($this->defaultStyle);
75+
$styleHelper->registerStyle($styleWithFont);
76+
$styleHelper->registerStyle($styleWithBackground);
77+
$styleHelper->registerStyle($styleWithBorder);
78+
79+
$this->assertFalse($styleHelper->shouldApplyStyleOnEmptyCell($styleWithFont->getId()));
80+
$this->assertTrue($styleHelper->shouldApplyStyleOnEmptyCell($styleWithBackground->getId()));
81+
$this->assertTrue($styleHelper->shouldApplyStyleOnEmptyCell($styleWithBorder->getId()));
82+
}
83+
6084
/**
6185
* @return void
6286
*/

tests/Spout/Writer/XLSX/WriterWithStyleTest.php

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,53 @@ public function testAddRowWithStyleShouldApplyStyleToCells()
176176
$this->assertEquals('0', $cellDomElements[2]->getAttribute('s'));
177177
}
178178

179+
/**
180+
* @return void
181+
*/
182+
public function testAddRowWithStyleShouldApplyStyleToEmptyCellsIfNeeded()
183+
{
184+
$fileName = 'test_add_row_with_style_should_apply_style_to_empty_cells_if_needed.xlsx';
185+
$dataRows = [
186+
['xlsx--11', '', 'xlsx--13'],
187+
['xlsx--21', '', 'xlsx--23'],
188+
['xlsx--31', '', 'xlsx--33'],
189+
['xlsx--41', '', 'xlsx--43'],
190+
];
191+
192+
$styleWithFont = (new StyleBuilder())->setFontBold()->build();
193+
$styleWithBackground = (new StyleBuilder())->setBackgroundColor(Color::BLUE)->build();
194+
195+
$border = (new BorderBuilder())->setBorderBottom(Color::GREEN)->build();
196+
$styleWithBorder = (new StyleBuilder())->setBorder($border)->build();
197+
198+
$this->writeToXLSXFileWithMultipleStyles($dataRows, $fileName, [null, $styleWithFont, $styleWithBackground, $styleWithBorder]);
199+
200+
$cellDomElements = $this->getCellElementsFromSheetXmlFile($fileName);
201+
202+
// The first and second rows should not have a reference to the empty cell
203+
// The other rows should have the reference because style should be applied to them
204+
// So that's: 2 + 2 + 3 + 3 = 10 cells
205+
$this->assertEquals(10, count($cellDomElements));
206+
207+
// First row has 2 styled cells
208+
$this->assertEquals('0', $cellDomElements[0]->getAttribute('s'));
209+
$this->assertEquals('0', $cellDomElements[1]->getAttribute('s'));
210+
211+
// Second row has 2 styled cells
212+
$this->assertEquals('1', $cellDomElements[2]->getAttribute('s'));
213+
$this->assertEquals('1', $cellDomElements[3]->getAttribute('s'));
214+
215+
// Third row has 3 styled cells
216+
$this->assertEquals('2', $cellDomElements[4]->getAttribute('s'));
217+
$this->assertEquals('2', $cellDomElements[5]->getAttribute('s'));
218+
$this->assertEquals('2', $cellDomElements[6]->getAttribute('s'));
219+
220+
// Third row has 3 styled cells
221+
$this->assertEquals('3', $cellDomElements[7]->getAttribute('s'));
222+
$this->assertEquals('3', $cellDomElements[8]->getAttribute('s'));
223+
$this->assertEquals('3', $cellDomElements[9]->getAttribute('s'));
224+
}
225+
179226
/**
180227
* @return void
181228
*/
@@ -403,17 +450,12 @@ public function testSetDefaultRowStyle()
403450
*/
404451
public function testReUseBorders()
405452
{
406-
407453
$fileName = 'test_reuse_borders.xlsx';
408454

409-
$borderLeft = (new BorderBuilder())
410-
->setBorderLeft()
411-
->build();
455+
$borderLeft = (new BorderBuilder())->setBorderLeft()->build();
412456
$borderLeftStyle = (new StyleBuilder())->setBorder($borderLeft)->build();
413457

414-
$borderRight = (new BorderBuilder())
415-
->setBorderRight(Color::RED, Border::WIDTH_THICK)
416-
->build();
458+
$borderRight = (new BorderBuilder())->setBorderRight(Color::RED, Border::WIDTH_THICK)->build();
417459
$borderRightStyle = (new StyleBuilder())->setBorder($borderRight)->build();
418460

419461
$fontStyle = (new StyleBuilder())->setFontBold()->build();
@@ -436,7 +478,7 @@ public function testReUseBorders()
436478
$borderRightStyle,
437479
$borderRightFontBoldStyle
438480
];
439-
481+
440482
$this->writeToXLSXFileWithMultipleStyles($dataRows, $fileName, $styles);
441483
$borderElements = $this->getXmlSectionFromStylesXmlFile($fileName, 'borders');
442484

0 commit comments

Comments
 (0)