diff --git a/src/Spout/Common/Entity/Row.php b/src/Spout/Common/Entity/Row.php index 0cc49c73..eece8ace 100644 --- a/src/Spout/Common/Entity/Row.php +++ b/src/Spout/Common/Entity/Row.php @@ -18,6 +18,12 @@ class Row */ protected $style; + /** + * Row height (default is 15) + * @var string + */ + protected $height = '15'; + /** * Row constructor. * @param Cell[] $cells @@ -126,4 +132,25 @@ public function toArray() return $cell->getValue(); }, $this->cells); } + + /** + * Set row height + * @param string $height + * @return Row + */ + public function setHeight($height) + { + $this->height = $height; + + return $this; + } + + /** + * Returns row height + * @return string + */ + public function getHeight() + { + return $this->height; + } } diff --git a/src/Spout/Common/Entity/Style/Style.php b/src/Spout/Common/Entity/Style/Style.php index 7e989a46..97688e92 100644 --- a/src/Spout/Common/Entity/Style/Style.php +++ b/src/Spout/Common/Entity/Style/Style.php @@ -66,6 +66,11 @@ class Style /** @var bool Whether the wrap text property was set */ private $hasSetWrapText = false; + /** @var bool Whether the cell should shrink to fit to content */ + private $shouldShrinkToFit = false; + /** @var bool Whether the shouldShrinkToFit text property was set */ + private $hasSetShrinkToFit = false; + /** @var Border */ private $border; @@ -463,4 +468,33 @@ public function shouldApplyFormat() { return $this->hasSetFormat; } + + /** + * Sets should shrink to fit + * @param bool $shrinkToFit + * @return Style + */ + public function setShouldShrinkToFit($shrinkToFit = true) + { + $this->hasSetShrinkToFit = true; + $this->shouldShrinkToFit = $shrinkToFit; + + return $this; + } + + /** + * @return bool Whether format should be applied + */ + public function shouldShrinkToFit() + { + return $this->shouldShrinkToFit; + } + + /** + * @return bool + */ + public function hasSetShrinkToFit() + { + return $this->hasSetShrinkToFit; + } } diff --git a/src/Spout/Common/Manager/OptionsManagerAbstract.php b/src/Spout/Common/Manager/OptionsManagerAbstract.php index 5670b95b..2381c52d 100644 --- a/src/Spout/Common/Manager/OptionsManagerAbstract.php +++ b/src/Spout/Common/Manager/OptionsManagerAbstract.php @@ -51,6 +51,23 @@ public function setOption($optionName, $optionValue) } } + /** + * Add an option to the internal list of options + * Used only for mergeCells() for now + * @param mixed $optionName + * @param mixed $optionValue + * @return void + */ + public function addOption($optionName, $optionValue) + { + if (\in_array($optionName, $this->supportedOptions)) { + if (!isset($this->options[$optionName])) { + $this->options[$optionName] = []; + } + $this->options[$optionName][] = $optionValue; + } + } + /** * @param string $optionName * @return mixed|null The set option or NULL if no option with given name found diff --git a/src/Spout/Common/Manager/OptionsManagerInterface.php b/src/Spout/Common/Manager/OptionsManagerInterface.php index 21fea0c5..bccb1c39 100644 --- a/src/Spout/Common/Manager/OptionsManagerInterface.php +++ b/src/Spout/Common/Manager/OptionsManagerInterface.php @@ -19,4 +19,13 @@ public function setOption($optionName, $optionValue); * @return mixed|null The set option or NULL if no option with given name found */ public function getOption($optionName); + + /** + * Add an option to the internal list of options + * Used only for mergeCells() for now + * @param mixed $optionName + * @param mixed $optionValue + * @return void + */ + public function addOption($optionName, $optionValue); } diff --git a/src/Spout/Reader/ODS/SheetIterator.php b/src/Spout/Reader/ODS/SheetIterator.php index f35b8523..c7b8cd9d 100644 --- a/src/Spout/Reader/ODS/SheetIterator.php +++ b/src/Spout/Reader/ODS/SheetIterator.php @@ -28,13 +28,13 @@ class SheetIterator implements IteratorInterface const XML_ATTRIBUTE_TABLE_STYLE_NAME = 'table:style-name'; const XML_ATTRIBUTE_TABLE_DISPLAY = 'table:display'; - /** @var string $filePath Path of the file to be read */ + /** @var string Path of the file to be read */ protected $filePath; /** @var \Box\Spout\Common\Manager\OptionsManagerInterface Reader's options manager */ protected $optionsManager; - /** @var InternalEntityFactory $entityFactory Factory to create entities */ + /** @var InternalEntityFactory Factory to create entities */ protected $entityFactory; /** @var XMLReader The XMLReader object that will help read sheet's XML data */ diff --git a/src/Spout/Reader/XLSX/Manager/SharedStringsManager.php b/src/Spout/Reader/XLSX/Manager/SharedStringsManager.php index caaeed76..8850a69b 100644 --- a/src/Spout/Reader/XLSX/Manager/SharedStringsManager.php +++ b/src/Spout/Reader/XLSX/Manager/SharedStringsManager.php @@ -43,7 +43,7 @@ class SharedStringsManager /** @var InternalEntityFactory Factory to create entities */ protected $entityFactory; - /** @var HelperFactory $helperFactory Factory to create helpers */ + /** @var HelperFactory Factory to create helpers */ protected $helperFactory; /** @var CachingStrategyFactory Factory to create shared strings caching strategies */ diff --git a/src/Spout/Reader/XLSX/RowIterator.php b/src/Spout/Reader/XLSX/RowIterator.php index 4af4530d..a54b8b19 100644 --- a/src/Spout/Reader/XLSX/RowIterator.php +++ b/src/Spout/Reader/XLSX/RowIterator.php @@ -35,7 +35,7 @@ class RowIterator implements IteratorInterface /** @var string Path of the XLSX file being read */ protected $filePath; - /** @var string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml */ + /** @var string Path of the sheet data XML file as in [Content_Types].xml */ protected $sheetDataXMLFilePath; /** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */ diff --git a/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php b/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php index bc2d406f..72fdcf0c 100644 --- a/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php +++ b/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php @@ -183,6 +183,19 @@ public function setFormat($format) return $this; } + /** + * Set should shrink to fit + * @param bool $shrinkToFit + * @return StyleBuilder + * @api + */ + public function setShouldShrinkToFit($shrinkToFit = true) + { + $this->style->setShouldShrinkToFit($shrinkToFit); + + return $this; + } + /** * Returns the configured style. The style is cached and can be reused. * diff --git a/src/Spout/Writer/Common/Entity/Options.php b/src/Spout/Writer/Common/Entity/Options.php index d7152bb6..0baa6282 100644 --- a/src/Spout/Writer/Common/Entity/Options.php +++ b/src/Spout/Writer/Common/Entity/Options.php @@ -20,4 +20,10 @@ abstract class Options // XLSX specific options const SHOULD_USE_INLINE_STRINGS = 'shouldUseInlineStrings'; + + // XLSX column widths + const COLUMN_WIDTHS = 'columnWidths'; + + // XLSX merge cells + const MERGE_CELLS = 'mergeCells'; } diff --git a/src/Spout/Writer/Common/Entity/Sheet.php b/src/Spout/Writer/Common/Entity/Sheet.php index aabdd91f..63915716 100644 --- a/src/Spout/Writer/Common/Entity/Sheet.php +++ b/src/Spout/Writer/Common/Entity/Sheet.php @@ -27,6 +27,9 @@ class Sheet /** @var SheetManager Sheet manager */ private $sheetManager; + /** @var bool Sheet is started */ + private $isSheetStarted = false; + /** * @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based) * @param string $associatedWorkbookId ID of the sheet's associated workbook @@ -108,4 +111,23 @@ public function setIsVisible($isVisible) return $this; } + + /** + * @return bool isSheetStarted Sheet was started + */ + public function isSheetStarted() + { + return $this->isSheetStarted; + } + + /** + * @param bool $isSheetStarted Set if sheet was started + * @return Sheet + */ + public function setIsSheetStarted($isSheetStarted) + { + $this->isSheetStarted = $isSheetStarted; + + return $this; + } } diff --git a/src/Spout/Writer/Common/Manager/Style/StyleMerger.php b/src/Spout/Writer/Common/Manager/Style/StyleMerger.php index 806c8d55..1adeb185 100644 --- a/src/Spout/Writer/Common/Manager/Style/StyleMerger.php +++ b/src/Spout/Writer/Common/Manager/Style/StyleMerger.php @@ -85,6 +85,9 @@ private function mergeCellProperties(Style $styleToUpdate, Style $style, Style $ if (!$style->hasSetWrapText() && $baseStyle->shouldWrapText()) { $styleToUpdate->setShouldWrapText(); } + if (!$style->hasSetShrinkToFit() && $baseStyle->shouldShrinkToFit()) { + $styleToUpdate->setShouldShrinkToFit(); + } if (!$style->hasSetCellAlignment() && $baseStyle->shouldApplyCellAlignment()) { $styleToUpdate->setCellAlignment($baseStyle->getCellAlignment()); } diff --git a/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php b/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php index b5135555..653778c7 100644 --- a/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php +++ b/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php @@ -44,7 +44,7 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface /** @var InternalEntityFactory Factory to create entities */ protected $entityFactory; - /** @var ManagerFactoryInterface $managerFactory Factory to create managers */ + /** @var ManagerFactoryInterface Factory to create managers */ protected $managerFactory; /** @var Worksheet The worksheet where data will be written to */ diff --git a/src/Spout/Writer/ODS/Creator/ManagerFactory.php b/src/Spout/Writer/ODS/Creator/ManagerFactory.php index f38c5000..a5b77ee4 100644 --- a/src/Spout/Writer/ODS/Creator/ManagerFactory.php +++ b/src/Spout/Writer/ODS/Creator/ManagerFactory.php @@ -22,7 +22,7 @@ class ManagerFactory implements ManagerFactoryInterface /** @var InternalEntityFactory */ protected $entityFactory; - /** @var HelperFactory $helperFactory */ + /** @var HelperFactory */ protected $helperFactory; /** diff --git a/src/Spout/Writer/WriterAbstract.php b/src/Spout/Writer/WriterAbstract.php index d96a6280..bbaa735a 100644 --- a/src/Spout/Writer/WriterAbstract.php +++ b/src/Spout/Writer/WriterAbstract.php @@ -33,7 +33,7 @@ abstract class WriterAbstract implements WriterInterface /** @var GlobalFunctionsHelper Helper to work with global functions */ protected $globalFunctionsHelper; - /** @var HelperFactory $helperFactory */ + /** @var HelperFactory */ protected $helperFactory; /** @var OptionsManagerInterface Writer options manager */ diff --git a/src/Spout/Writer/WriterMultiSheetsAbstract.php b/src/Spout/Writer/WriterMultiSheetsAbstract.php index 8170b679..58f22b82 100644 --- a/src/Spout/Writer/WriterMultiSheetsAbstract.php +++ b/src/Spout/Writer/WriterMultiSheetsAbstract.php @@ -4,6 +4,7 @@ use Box\Spout\Common\Creator\HelperFactory; use Box\Spout\Common\Entity\Row; +use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Helper\GlobalFunctionsHelper; use Box\Spout\Common\Manager\OptionsManagerInterface; use Box\Spout\Writer\Common\Creator\ManagerFactoryInterface; @@ -97,6 +98,7 @@ public function getSheets() * Creates a new sheet and make it the current sheet. The data will now be written to this sheet. * * @throws WriterNotOpenedException If the writer has not been opened yet + * @throws IOException If unable to open the sheet for writing * @return Sheet The created sheet */ public function addNewSheetAndMakeItCurrent() diff --git a/src/Spout/Writer/XLSX/Creator/ManagerFactory.php b/src/Spout/Writer/XLSX/Creator/ManagerFactory.php index f27a2f2f..aa3bcd5c 100644 --- a/src/Spout/Writer/XLSX/Creator/ManagerFactory.php +++ b/src/Spout/Writer/XLSX/Creator/ManagerFactory.php @@ -24,7 +24,7 @@ class ManagerFactory implements ManagerFactoryInterface /** @var InternalEntityFactory */ protected $entityFactory; - /** @var HelperFactory $helperFactory */ + /** @var HelperFactory */ protected $helperFactory; /** diff --git a/src/Spout/Writer/XLSX/Manager/OptionsManager.php b/src/Spout/Writer/XLSX/Manager/OptionsManager.php index d3b5cd42..40e299ef 100644 --- a/src/Spout/Writer/XLSX/Manager/OptionsManager.php +++ b/src/Spout/Writer/XLSX/Manager/OptionsManager.php @@ -39,6 +39,8 @@ protected function getSupportedOptions() Options::DEFAULT_ROW_STYLE, Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, Options::SHOULD_USE_INLINE_STRINGS, + Options::COLUMN_WIDTHS, + Options::MERGE_CELLS, ]; } @@ -56,5 +58,7 @@ protected function setDefaultOptions() $this->setOption(Options::DEFAULT_ROW_STYLE, $defaultRowStyle); $this->setOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true); $this->setOption(Options::SHOULD_USE_INLINE_STRINGS, true); + $this->setOption(Options::COLUMN_WIDTHS, []); + $this->setOption(Options::MERGE_CELLS, []); } } diff --git a/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php b/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php index f0ca9d9e..437b4332 100644 --- a/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php +++ b/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php @@ -249,7 +249,7 @@ protected function getCellXfsSectionContent() $content .= \sprintf(' applyBorder="%d"', $style->shouldApplyBorder() ? 1 : 0); - if ($style->shouldApplyCellAlignment() || $style->shouldWrapText()) { + if ($style->shouldApplyCellAlignment() || $style->shouldWrapText() || $style->shouldShrinkToFit()) { $content .= ' applyAlignment="1">'; $content .= 'shouldApplyCellAlignment()) { @@ -258,6 +258,10 @@ protected function getCellXfsSectionContent() if ($style->shouldWrapText()) { $content .= ' wrapText="1"'; } + if ($style->shouldShrinkToFit()) { + $content .= ' shrinkToFit="true"'; + } + $content .= '/>'; $content .= ''; } else { diff --git a/src/Spout/Writer/XLSX/Manager/WorksheetManager.php b/src/Spout/Writer/XLSX/Manager/WorksheetManager.php index 741d0aa8..4a716035 100644 --- a/src/Spout/Writer/XLSX/Manager/WorksheetManager.php +++ b/src/Spout/Writer/XLSX/Manager/WorksheetManager.php @@ -37,6 +37,8 @@ class WorksheetManager implements WorksheetManagerInterface EOD; + /** @var OptionsManagerInterface */ + private $optionsManager; /** @var bool Whether inline or shared strings should be used */ protected $shouldUseInlineStrings; @@ -84,6 +86,7 @@ public function __construct( StringHelper $stringHelper, InternalEntityFactory $entityFactory ) { + $this->optionsManager = $optionsManager; $this->shouldUseInlineStrings = $optionsManager->getOption(Options::SHOULD_USE_INLINE_STRINGS); $this->rowManager = $rowManager; $this->styleManager = $styleManager; @@ -113,7 +116,6 @@ public function startSheet(Worksheet $worksheet) $worksheet->setFilePointer($sheetFilePointer); \fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER); - \fwrite($sheetFilePointer, ''); } /** @@ -153,11 +155,28 @@ public function addRow(Worksheet $worksheet, Row $row) */ private function addNonEmptyRow(Worksheet $worksheet, Row $row) { + if (!$worksheet->getExternalSheet()->isSheetStarted()) { + // create nodes for columns widths + if ($this->optionsManager->getOption(Options::COLUMN_WIDTHS)) { + $colsString = ''; + foreach ($this->optionsManager->getOption(Options::COLUMN_WIDTHS) as $index => $width) { + $index++; + $colsString.= ''; + } + $colsString.=''; + \fwrite($worksheet->getFilePointer(), $colsString); + } + + \fwrite($worksheet->getFilePointer(), ''); + $worksheet->getExternalSheet()->setIsSheetStarted(true); + } + $rowStyle = $row->getStyle(); $rowIndexOneBased = $worksheet->getLastWrittenRowIndex() + 1; $numCells = $row->getNumCells(); + $rowHeight = $row->getHeight(); - $rowXML = ''; + $rowXML = ''; foreach ($row->getCells() as $columnIndexZeroBased => $cell) { $rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndexOneBased, $columnIndexZeroBased); @@ -270,7 +289,26 @@ public function close(Worksheet $worksheet) return; } + if (!$worksheet->getExternalSheet()->isSheetStarted()) { + \fwrite($worksheetFilePointer, ''); + $worksheet->getExternalSheet()->setIsSheetStarted(true); + } + \fwrite($worksheetFilePointer, ''); + + // create nodes for merge cells + if ($this->optionsManager->getOption(Options::MERGE_CELLS)) { + $mergeCellString = ''; + foreach ($this->optionsManager->getOption(Options::MERGE_CELLS) as $values) { + $output = \array_map(function ($value) { + return CellHelper::getColumnLettersFromColumnIndex($value[0]) . $value[1]; + }, $values); + $mergeCellString.= ''; + } + $mergeCellString.= ''; + \fwrite($worksheet->getFilePointer(), $mergeCellString); + } + \fwrite($worksheetFilePointer, ''); \fclose($worksheetFilePointer); } diff --git a/src/Spout/Writer/XLSX/Writer.php b/src/Spout/Writer/XLSX/Writer.php index 9d928fc9..41eb03a3 100644 --- a/src/Spout/Writer/XLSX/Writer.php +++ b/src/Spout/Writer/XLSX/Writer.php @@ -47,4 +47,34 @@ public function setShouldUseInlineStrings($shouldUseInlineStrings) return $this; } + + /** + * Set columns widths as list. If value is null will set column with default width (8.43) + * @param array $columnWidths + * @return WriterMultiSheetsAbstract + */ + public function setColumnWidths(array $columnWidths) + { + $this->optionsManager->setOption(Options::COLUMN_WIDTHS, $columnWidths); + + return $this; + } + + /** + * Merge cells. + * Row coordinates are indexed from 1, columns from 0 (A = 0), + * so a merge B2:G2 looks like $writer->mergeCells([1,2], [6, 2]); + * + * You may use CellHelper::getColumnLettersFromColumnIndex() to convert from "B2" to "[1,2]" + * + * @param int[] $range1 - top left cell's coordinate [column, row] + * @param int[] $range2 - bottom right cell's coordinate [column, row] + * @return $this + */ + public function mergeCells(array $range1, array $range2) + { + $this->optionsManager->addOption(Options::MERGE_CELLS, [$range1, $range2]); + + return $this; + } } diff --git a/tests/Spout/Common/Manager/OptionsManagerTest.php b/tests/Spout/Common/Manager/OptionsManagerTest.php index e2f0c5cc..66da195a 100644 --- a/tests/Spout/Common/Manager/OptionsManagerTest.php +++ b/tests/Spout/Common/Manager/OptionsManagerTest.php @@ -73,4 +73,28 @@ public function testOptionsManagerShouldReturnNullIfNoOptionNotSupported() $optionsManager->setOption('not-supported', 'something'); $this->assertNull($optionsManager->getOption('not-supported')); } + + /** + * @return void + */ + public function testOptionManagerShouldReturnArrayIfListOptionsAdded() + { + $optionsManager = $this->optionsManager; + $optionsManager->addOption('bar', 'something'); + $optionsManager->addOption('bar', 'something-else'); + $this->assertIsArray($optionsManager->getOption('bar')); + $this->assertCount(2, $optionsManager->getOption('bar')); + $this->assertEquals('something', $optionsManager->getOption('bar')[0]); + $this->assertEquals('something-else', $optionsManager->getOption('bar')[1]); + } + + /** + * @return void + */ + public function testOptionsManagerShouldReturnNullIfListOptionNotSupported() + { + $optionsManager = $this->optionsManager; + $optionsManager->addOption('not-supported', 'something'); + $this->assertNull($optionsManager->getOption('not-supported')); + } } diff --git a/tests/Spout/Reader/CSV/SpoutTestStream.php b/tests/Spout/Reader/CSV/SpoutTestStream.php index 3bfd06e1..d66e8117 100644 --- a/tests/Spout/Reader/CSV/SpoutTestStream.php +++ b/tests/Spout/Reader/CSV/SpoutTestStream.php @@ -14,10 +14,10 @@ class SpoutTestStream const PATH_TO_CSV_RESOURCES = 'tests/resources/csv/'; const CSV_EXTENSION = '.csv'; - /** @var int $position */ + /** @var int */ private $position; - /** @var resource $fileHandle */ + /** @var resource */ private $fileHandle; /** diff --git a/tests/Spout/Writer/XLSX/WriterTest.php b/tests/Spout/Writer/XLSX/WriterTest.php index 15cc4e46..16b852cf 100644 --- a/tests/Spout/Writer/XLSX/WriterTest.php +++ b/tests/Spout/Writer/XLSX/WriterTest.php @@ -7,6 +7,7 @@ use Box\Spout\Common\Exception\InvalidArgumentException; use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Exception\SpoutException; +use Box\Spout\Reader\Wrapper\XMLReader; use Box\Spout\TestUsingResource; use Box\Spout\Writer\Common\Creator\WriterEntityFactory; use Box\Spout\Writer\Exception\WriterAlreadyOpenedException; @@ -527,6 +528,105 @@ public function testAddRowShouldEscapeControlCharacters() $this->assertInlineDataWasWrittenToSheet($fileName, 1, 'control _x0015_ character'); } + /** + * @return void + */ + public function testAddRowShouldSupportRowHeights() + { + $fileName = 'test_add_row_should_support_row_heights.xlsx'; + $dataRows = $this->createRowsFromValues([ + ['First row with default height'], + ['Second row with custom height'], + ]); + + $dataRows[1]->setHeight('23'); + + $this->writeToXLSXFile($dataRows, $fileName); + $firstRow = $this->getXmlRowFromXmlFile($fileName, 1, 1); + $secondRow = $this->getXmlRowFromXmlFile($fileName, 1, 2); + $this->assertEquals('15', $firstRow->getAttribute('ht'), '1st row does not have default height.'); + $this->assertEquals('23', $secondRow->getAttribute('ht'), '2nd row does not have custom height.'); + } + + /** + * @return void + */ + public function testAddRowShouldSupportColumnWidths() + { + $fileName = 'test_add_row_should_support_column_widths.xlsx'; + $this->createGeneratedFolderIfNeeded($fileName); + $resourcePath = $this->getGeneratedResourcePath($fileName); + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->setShouldUseInlineStrings(true); + $writer->openToFile($resourcePath); + + $columnWidths = [1, 2, 3, 4]; + $writer->setColumnWidths($columnWidths); + $writer->addRows($this->createRowsFromValues([ + ['Test cell'], + ])); + $writer->close(); + + $xmlReader = $this->getXmlReaderForSheetFromXmlFile($fileName, 1); + $xmlReader->readUntilNodeFound('cols'); + $this->assertEquals('cols', $xmlReader->getCurrentNodeName(), 'Sheet does not have cols tag'); + $this->assertEquals(count($columnWidths), $xmlReader->expand()->childNodes->length, 'Sheet does not have the specified number of column definitions'); + foreach ($columnWidths as $index => $columnWidth) { + $xmlReader->readUntilNodeFound('col'); + $this->assertEquals($index + 1, $xmlReader->expand()->getAttribute('min')); + $this->assertEquals($index + 1, $xmlReader->expand()->getAttribute('max')); + $this->assertEquals($columnWidth, $xmlReader->expand()->getAttribute('width')); + } + } + + /** + * @return void + */ + public function testCloseShouldAddMergeCellTags() + { + $fileName = 'test_add_row_should_support_column_widths.xlsx'; + $this->createGeneratedFolderIfNeeded($fileName); + $resourcePath = $this->getGeneratedResourcePath($fileName); + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->setShouldUseInlineStrings(true); + $writer->openToFile($resourcePath); + + $writer->mergeCells([0, 1], [3, 1]); + $writer->mergeCells([2, 3], [10, 3]); + $writer->close(); + + $xmlReader = $this->getXmlReaderForSheetFromXmlFile($fileName, 1); + $xmlReader->readUntilNodeFound('mergeCells'); + $this->assertEquals('mergeCells', $xmlReader->getCurrentNodeName(), 'Sheet does not have mergeCells tag'); + $this->assertEquals(2, $xmlReader->expand()->childNodes->length, 'Sheet does not have the specified number of mergeCell definitions'); + $xmlReader->readUntilNodeFound('mergeCell'); + $this->assertEquals('A1:D1', $xmlReader->expand()->getAttribute('ref'), 'Merge ref for first range is not valid.'); + $xmlReader->readUntilNodeFound('mergeCell'); + $this->assertEquals('C3:K3', $xmlReader->expand()->getAttribute('ref'), 'Merge ref for second range is not valid.'); + } + + /** + * @return void + */ + public function testGeneratedFileShouldBeValidForEmptySheets() + { + $fileName = 'test_empty_sheet.xlsx'; + $this->createGeneratedFolderIfNeeded($fileName); + $resourcePath = $this->getGeneratedResourcePath($fileName); + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->openToFile($resourcePath); + + $writer->addNewSheetAndMakeItCurrent(); + $writer->close(); + + $xmlReader = $this->getXmlReaderForSheetFromXmlFile($fileName, 1); + $xmlReader->setParserProperty(XMLReader::VALIDATE, true); + $this->assertTrue($xmlReader->isValid(), 'worksheet xml is not valid'); + $xmlReader->setParserProperty(XMLReader::VALIDATE, false); + $xmlReader->readUntilNodeFound('sheetData'); + $this->assertEquals('sheetData', $xmlReader->getCurrentNodeName(), 'worksheet xml does not have sheetData'); + } + /** * @return void */ @@ -641,4 +741,42 @@ private function assertSharedStringWasWritten($fileName, $sharedString, $message $this->assertContains($sharedString, $xmlContents, $message); } + + /** + * @param $fileName + * @param $sheetIndex - 1 based + * @return XMLReader + */ + private function getXmlReaderForSheetFromXmlFile($fileName, $sheetIndex) + { + $resourcePath = $this->getGeneratedResourcePath($fileName); + + $xmlReader = new XMLReader(); + $xmlReader->openFileInZip($resourcePath, 'xl/worksheets/sheet' . $sheetIndex . '.xml'); + + return $xmlReader; + } + + /** + * @param $fileName + * @param $sheetIndex - 1 based + * @param $rowIndex - 1 based + * @throws \Box\Spout\Reader\Exception\XMLProcessingException + * @return \DOMNode|null + */ + private function getXmlRowFromXmlFile($fileName, $sheetIndex, $rowIndex) + { + $xmlReader = $this->getXmlReaderForSheetFromXmlFile($fileName, $sheetIndex); + $xmlReader->readUntilNodeFound('sheetData'); + + for ($i = 0; $i < $rowIndex; $i++) { + $xmlReader->readUntilNodeFound('row'); + } + + $row = $xmlReader->expand(); + + $xmlReader->close(); + + return $row; + } } diff --git a/tests/Spout/Writer/XLSX/WriterWithStyleTest.php b/tests/Spout/Writer/XLSX/WriterWithStyleTest.php index da0aec29..3baa6657 100644 --- a/tests/Spout/Writer/XLSX/WriterWithStyleTest.php +++ b/tests/Spout/Writer/XLSX/WriterWithStyleTest.php @@ -306,6 +306,24 @@ public function testAddRowShouldApplyCellAlignment() $this->assertFirstChildHasAttributeEquals(CellAlignment::RIGHT, $xfElement, 'alignment', 'horizontal'); } + /** + * @return void + */ + public function testAddRowShouldApplyShrinkToFit() + { + $fileName = 'test_add_row_should_apply_shrink_to_fit.xlsx'; + + $shrinkToFitStyle = (new StyleBuilder())->setShouldShrinkToFit()->build(); + $dataRows = $this->createStyledRowsFromValues([['xlsx--11']], $shrinkToFitStyle); + + $this->writeToXLSXFile($dataRows, $fileName); + + $cellXfsDomElement = $this->getXmlSectionFromStylesXmlFile($fileName, 'cellXfs'); + $xfElement = $cellXfsDomElement->getElementsByTagName('xf')->item(1); + $this->assertEquals(1, $xfElement->getAttribute('applyAlignment')); + $this->assertFirstChildHasAttributeEquals('true', $xfElement, 'alignment', 'shrinkToFit'); + } + /** * @return void */