diff --git a/.gitignore b/.gitignore index 62536649b..a4bddd509 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ /.idea *.iml -/tests/resources/generated +/tests/**/resources/generated /tests/coverage /vendor /composer.lock diff --git a/src/Spout/Writer/Common/Entity/Options.php b/src/Spout/Writer/Common/Entity/Options.php index d7152bb6c..391833345 100644 --- a/src/Spout/Writer/Common/Entity/Options.php +++ b/src/Spout/Writer/Common/Entity/Options.php @@ -20,4 +20,9 @@ abstract class Options // XLSX specific options const SHOULD_USE_INLINE_STRINGS = 'shouldUseInlineStrings'; + + // Cell size options + const DEFAULT_COLUMN_WIDTH = 'defaultColumnWidth'; + const DEFAULT_ROW_HEIGHT = 'defaultRowHeight'; + const COLUMN_WIDTHS = 'columnWidthDefinition'; } diff --git a/src/Spout/Writer/Common/Entity/Worksheet.php b/src/Spout/Writer/Common/Entity/Worksheet.php index 74c4976f0..c7a098706 100644 --- a/src/Spout/Writer/Common/Entity/Worksheet.php +++ b/src/Spout/Writer/Common/Entity/Worksheet.php @@ -23,6 +23,9 @@ class Worksheet /** @var int Index of the last written row */ private $lastWrittenRowIndex; + /** @var bool has the sheet data header been written */ + private $sheetDataStarted = false; + /** * Worksheet constructor. * @@ -36,6 +39,7 @@ public function __construct($worksheetFilePath, Sheet $externalSheet) $this->externalSheet = $externalSheet; $this->maxNumColumns = 0; $this->lastWrittenRowIndex = 0; + $this->sheetDataStarted = false; } /** @@ -110,4 +114,20 @@ public function getId() // sheet index is zero-based, while ID is 1-based return $this->externalSheet->getIndex() + 1; } + + /** + * @return bool + */ + public function getSheetDataStarted() + { + return $this->sheetDataStarted; + } + + /** + * @param bool $sheetDataStarted + */ + public function setSheetDataStarted($sheetDataStarted) + { + $this->sheetDataStarted = $sheetDataStarted; + } } diff --git a/src/Spout/Writer/Common/Manager/ManagesCellSize.php b/src/Spout/Writer/Common/Manager/ManagesCellSize.php new file mode 100644 index 000000000..8a517f0d9 --- /dev/null +++ b/src/Spout/Writer/Common/Manager/ManagesCellSize.php @@ -0,0 +1,63 @@ +defaultColumnWidth = $width; + } + + /** + * @param float|null $height + */ + public function setDefaultRowHeight($height) + { + $this->defaultRowHeight = $height; + } + + /** + * @param float $width + * @param array $columns One or more columns with this width + */ + public function setColumnWidth(float $width, ...$columns) + { + // Gather sequences + $sequence = []; + foreach ($columns as $i) { + $sequenceLength = count($sequence); + if ($sequenceLength > 0) { + $previousValue = $sequence[$sequenceLength - 1]; + if ($i !== $previousValue + 1) { + $this->setColumnWidthForRange($width, $sequence[0], $previousValue); + $sequence = []; + } + } + $sequence[] = $i; + } + $this->setColumnWidthForRange($width, $sequence[0], $sequence[count($sequence) - 1]); + } + + /** + * @param float $width The width to set + * @param int $start First column index of the range + * @param int $end Last column index of the range + */ + public function setColumnWidthForRange(float $width, int $start, int $end) + { + $this->columnWidths[] = [$start, $end, $width]; + } +} diff --git a/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php b/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php index 653778c70..604de65ce 100644 --- a/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php +++ b/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php @@ -15,7 +15,6 @@ use Box\Spout\Writer\Common\Manager\Style\StyleManagerInterface; use Box\Spout\Writer\Common\Manager\Style\StyleMerger; use Box\Spout\Writer\Exception\SheetNotFoundException; -use Box\Spout\Writer\Exception\WriterException; /** * Class WorkbookManagerAbstract @@ -103,7 +102,6 @@ public function getWorkbook() * Creates a new sheet in the workbook and make it the current sheet. * The writing will resume where it stopped (i.e. data won't be truncated). * - * @throws IOException If unable to open the sheet for writing * @return Worksheet The created sheet */ public function addNewSheetAndMakeItCurrent() @@ -117,7 +115,7 @@ public function addNewSheetAndMakeItCurrent() /** * Creates a new sheet in the workbook. The current sheet remains unchanged. * - * @throws \Box\Spout\Common\Exception\IOException If unable to open the sheet for writing + * @throws IOException * @return Worksheet The created sheet */ private function addNewSheet() @@ -157,6 +155,16 @@ public function getCurrentWorksheet() return $this->currentWorksheet; } + /** + * Starts the current sheet and opens the file pointer + * + * @throws IOException + */ + public function startCurrentSheet() + { + $this->worksheetManager->startSheet($this->getCurrentWorksheet()); + } + /** * Sets the given sheet as the current one. New data will be written to this sheet. * The writing will resume where it stopped (i.e. data won't be truncated). @@ -210,8 +218,9 @@ private function getWorksheetFromExternalSheet($sheet) * with the creation of new worksheets if one worksheet has reached its maximum capicity. * * @param Row $row The row to be added + * * @throws IOException If trying to create a new sheet and unable to open the sheet for writing - * @throws WriterException If unable to write data + * @throws \Box\Spout\Common\Exception\InvalidArgumentException * @return void */ public function addRowToCurrentWorksheet(Row $row) @@ -249,7 +258,9 @@ private function hasCurrentWorksheetReachedMaxRows() * * @param Worksheet $worksheet Worksheet to write the row to * @param Row $row The row to be added - * @throws WriterException If unable to write data + * + * @throws IOException + * @throws \Box\Spout\Common\Exception\InvalidArgumentException * @return void */ private function addRowToWorksheet(Worksheet $worksheet, Row $row) @@ -276,6 +287,41 @@ private function applyDefaultRowStyle(Row $row) } } + /** + * @param float $width + */ + public function setDefaultColumnWidth(float $width) + { + $this->worksheetManager->setDefaultColumnWidth($width); + } + + /** + * @param float $height + */ + public function setDefaultRowHeight(float $height) + { + $this->worksheetManager->setDefaultRowHeight($height); + } + + /** + * @param float $width + * @param array $columns One or more columns with this width + */ + public function setColumnWidth(float $width, ...$columns) + { + $this->worksheetManager->setColumnWidth($width, ...$columns); + } + + /** + * @param float $width The width to set + * @param int $start First column index of the range + * @param int $end Last column index of the range + */ + public function setColumnWidthForRange(float $width, int $start, int $end) + { + $this->worksheetManager->setColumnWidthForRange($width, $start, $end); + } + /** * Closes the workbook and all its associated sheets. * All the necessary files are written to disk and zipped together to create the final file. diff --git a/src/Spout/Writer/Common/Manager/WorkbookManagerInterface.php b/src/Spout/Writer/Common/Manager/WorkbookManagerInterface.php index aed304a02..7bb469eaf 100644 --- a/src/Spout/Writer/Common/Manager/WorkbookManagerInterface.php +++ b/src/Spout/Writer/Common/Manager/WorkbookManagerInterface.php @@ -42,6 +42,21 @@ public function getWorksheets(); */ public function getCurrentWorksheet(); + /** + * Starts the current sheet and opens its file pointer + */ + public function startCurrentSheet(); + + /** + * @param float $width + */ + public function setDefaultColumnWidth(float $width); + + /** + * @param float $height + */ + public function setDefaultRowHeight(float $height); + /** * Sets the given sheet as the current one. New data will be written to this sheet. * The writing will resume where it stopped (i.e. data won't be truncated). diff --git a/src/Spout/Writer/Common/Manager/WorksheetManagerInterface.php b/src/Spout/Writer/Common/Manager/WorksheetManagerInterface.php index bb54ee643..bb6758a7f 100644 --- a/src/Spout/Writer/Common/Manager/WorksheetManagerInterface.php +++ b/src/Spout/Writer/Common/Manager/WorksheetManagerInterface.php @@ -11,6 +11,29 @@ */ interface WorksheetManagerInterface { + /** + * @param float|null $width + */ + public function setDefaultColumnWidth($width); + + /** + * @param float|null $height + */ + public function setDefaultRowHeight($height); + + /** + * @param float $width + * @param array $columns One or more columns with this width + */ + public function setColumnWidth(float $width, ...$columns); + + /** + * @param float $width The width to set + * @param int $start First column index of the range + * @param int $end Last column index of the range + */ + public function setColumnWidthForRange(float $width, int $start, int $end); + /** * Adds a row to the worksheet. * diff --git a/src/Spout/Writer/ODS/Creator/ManagerFactory.php b/src/Spout/Writer/ODS/Creator/ManagerFactory.php index a5b77ee42..3605a37d7 100644 --- a/src/Spout/Writer/ODS/Creator/ManagerFactory.php +++ b/src/Spout/Writer/ODS/Creator/ManagerFactory.php @@ -93,7 +93,7 @@ private function createStyleManager(OptionsManagerInterface $optionsManager) { $styleRegistry = $this->createStyleRegistry($optionsManager); - return new StyleManager($styleRegistry); + return new StyleManager($styleRegistry, $optionsManager); } /** diff --git a/src/Spout/Writer/ODS/Manager/OptionsManager.php b/src/Spout/Writer/ODS/Manager/OptionsManager.php index a6fb564ef..698108a9c 100644 --- a/src/Spout/Writer/ODS/Manager/OptionsManager.php +++ b/src/Spout/Writer/ODS/Manager/OptionsManager.php @@ -34,6 +34,9 @@ protected function getSupportedOptions() Options::TEMP_FOLDER, Options::DEFAULT_ROW_STYLE, Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, + Options::DEFAULT_COLUMN_WIDTH, + Options::DEFAULT_ROW_HEIGHT, + Options::COLUMN_WIDTHS, ]; } diff --git a/src/Spout/Writer/ODS/Manager/Style/StyleManager.php b/src/Spout/Writer/ODS/Manager/Style/StyleManager.php index 34f75c78d..93ed008ec 100644 --- a/src/Spout/Writer/ODS/Manager/Style/StyleManager.php +++ b/src/Spout/Writer/ODS/Manager/Style/StyleManager.php @@ -4,7 +4,10 @@ use Box\Spout\Common\Entity\Style\BorderPart; use Box\Spout\Common\Entity\Style\CellAlignment; +use Box\Spout\Common\Manager\OptionsManagerInterface; +use Box\Spout\Writer\Common\Entity\Options; use Box\Spout\Writer\Common\Entity\Worksheet; +use Box\Spout\Writer\Common\Manager\ManagesCellSize; use Box\Spout\Writer\ODS\Helper\BorderHelper; /** @@ -13,9 +16,22 @@ */ class StyleManager extends \Box\Spout\Writer\Common\Manager\Style\StyleManager { + use ManagesCellSize; + /** @var StyleRegistry */ protected $styleRegistry; + /** + * @param StyleRegistry $styleRegistry + */ + public function __construct(StyleRegistry $styleRegistry, OptionsManagerInterface $optionsManager) + { + parent::__construct($styleRegistry); + $this->setDefaultColumnWidth($optionsManager->getOption(Options::DEFAULT_COLUMN_WIDTH)); + $this->setDefaultRowHeight($optionsManager->getOption(Options::DEFAULT_ROW_HEIGHT)); + $this->columnWidths = $optionsManager->getOption(Options::COLUMN_WIDTHS) ?? []; + } + /** * Returns the content of the "styles.xml" file, given a list of styles. * @@ -162,12 +178,16 @@ public function getContentXmlAutomaticStylesSectionContent($worksheets) $content .= $this->getStyleSectionContent($style); } - $content .= <<<'EOD' - - + $useOptimalRowHeight = empty($this->defaultRowHeight) ? 'true' : 'false'; + $defaultRowHeight = empty($this->defaultRowHeight) ? '15pt' : "{$this->defaultRowHeight}pt"; + $defaultColumnWidth = empty($this->defaultColumnWidth) ? '' : "style:column-width=\"{$this->defaultColumnWidth}pt\""; + + $content .= << + - + EOD; @@ -182,6 +202,16 @@ public function getContentXmlAutomaticStylesSectionContent($worksheets) EOD; } + // Sort column widths since ODS cares about order + usort($this->columnWidths, function ($a, $b) { + if ($a[0] === $b[0]) { + return 0; + } + + return ($a[0] < $b[0]) ? -1 : 1; + }); + $content .= $this->getTableColumnStylesXMLContent(); + $content .= ''; return $content; @@ -313,9 +343,12 @@ private function getCellAlignmentSectionContent($style) private function transformCellAlignment($cellAlignment) { switch ($cellAlignment) { - case CellAlignment::LEFT: return 'start'; - case CellAlignment::RIGHT: return 'end'; - default: return $cellAlignment; + case CellAlignment::LEFT: + return 'start'; + case CellAlignment::RIGHT: + return 'end'; + default: + return $cellAlignment; } } @@ -381,4 +414,42 @@ private function getBackgroundColorXMLContent($style) { return \sprintf(' fo:background-color="#%s" ', $style->getBackgroundColor()); } + + public function getTableColumnStylesXMLContent() : string + { + if (empty($this->columnWidths)) { + return ''; + } + + $content = ''; + foreach ($this->columnWidths as $styleIndex => $entry) { + $content .= << + + +EOD; + } + + return $content; + } + + public function getStyledTableColumnXMLContent(int $maxNumColumns) : string + { + if (empty($this->columnWidths)) { + return ''; + } + + $content = ''; + foreach ($this->columnWidths as $styleIndex => $entry) { + $numCols = $entry[1] - $entry[0] + 1; + $content .= << +EOD; + } + // Note: This assumes the column widths are contiguous and default width is + // only applied to columns after the last custom column with a custom width + $content .= ''; + + return $content; + } } diff --git a/src/Spout/Writer/ODS/Manager/WorksheetManager.php b/src/Spout/Writer/ODS/Manager/WorksheetManager.php index 7d7cb0ebb..64f6e6e60 100644 --- a/src/Spout/Writer/ODS/Manager/WorksheetManager.php +++ b/src/Spout/Writer/ODS/Manager/WorksheetManager.php @@ -95,7 +95,7 @@ public function getTableElementStartAsString(Worksheet $worksheet) $tableStyleName = 'ta' . ($externalSheet->getIndex() + 1); $tableElement = ''; - $tableElement .= ''; + $tableElement .= $this->styleManager->getStyledTableColumnXMLContent($worksheet->getMaxNumColumns()); return $tableElement; } @@ -265,4 +265,39 @@ public function close(Worksheet $worksheet) \fclose($worksheetFilePointer); } + + /** + * @param float|null $width + */ + public function setDefaultColumnWidth($width) + { + $this->styleManager->setDefaultColumnWidth($width); + } + + /** + * @param float|null $height + */ + public function setDefaultRowHeight($height) + { + $this->styleManager->setDefaultRowHeight($height); + } + + /** + * @param float $width + * @param array $columns One or more columns with this width + */ + public function setColumnWidth(float $width, ...$columns) + { + $this->styleManager->setColumnWidth($width, ...$columns); + } + + /** + * @param float $width The width to set + * @param int $start First column index of the range + * @param int $end Last column index of the range + */ + public function setColumnWidthForRange(float $width, int $start, int $end) + { + $this->styleManager->setColumnWidthForRange($width, $start, $end); + } } diff --git a/src/Spout/Writer/WriterMultiSheetsAbstract.php b/src/Spout/Writer/WriterMultiSheetsAbstract.php index 8170b679c..0161877e0 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; @@ -56,7 +57,10 @@ public function setShouldCreateNewSheetsAutomatically($shouldCreateNewSheetsAuto { $this->throwIfWriterAlreadyOpened('Writer must be configured before opening it.'); - $this->optionsManager->setOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, $shouldCreateNewSheetsAutomatically); + $this->optionsManager->setOption( + Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, + $shouldCreateNewSheetsAutomatically + ); return $this; } @@ -96,6 +100,7 @@ public function getSheets() /** * Creates a new sheet and make it the current sheet. The data will now be written to this sheet. * + * @throws IOException * @throws WriterNotOpenedException If the writer has not been opened yet * @return Sheet The created sheet */ @@ -125,8 +130,8 @@ public function getCurrentSheet() * The writing will resume where it stopped (i.e. data won't be truncated). * * @param Sheet $sheet The sheet to set as current - * @throws WriterNotOpenedException If the writer has not been opened yet * @throws SheetNotFoundException If the given sheet does not exist in the workbook + * @throws WriterNotOpenedException If the writer has not been opened yet * @return void */ public function setCurrentSheet($sheet) @@ -135,6 +140,55 @@ public function setCurrentSheet($sheet) $this->workbookManager->setCurrentSheet($sheet); } + /** + * @param float $width + * @throws WriterAlreadyOpenedException + */ + public function setDefaultColumnWidth(float $width) + { + $this->throwIfWriterAlreadyOpened('Writer must be configured before opening it.'); + $this->optionsManager->setOption( + Options::DEFAULT_COLUMN_WIDTH, + $width + ); + } + + /** + * @param float $height + * @throws WriterAlreadyOpenedException + */ + public function setDefaultRowHeight(float $height) + { + $this->throwIfWriterAlreadyOpened('Writer must be configured before opening it.'); + $this->optionsManager->setOption( + Options::DEFAULT_ROW_HEIGHT, + $height + ); + } + + /** + * @param float|null $width + * @param array $columns One or more columns with this width + * @throws WriterNotOpenedException + */ + public function setColumnWidth($width, ...$columns) + { + $this->throwIfWorkbookIsNotAvailable(); + $this->workbookManager->setColumnWidth($width, ...$columns); + } + + /** + * @param float $width The width to set + * @param int $start First column index of the range + * @param int $end Last column index of the range + * @throws WriterNotOpenedException + */ + public function setColumnWidthForRange(float $width, int $start, int $end) + { + $this->throwIfWorkbookIsNotAvailable(); + $this->workbookManager->setColumnWidthForRange($width, $start, $end); + } + /** * Checks if the workbook has been created. Throws an exception if not created yet. * @@ -143,13 +197,15 @@ public function setCurrentSheet($sheet) */ protected function throwIfWorkbookIsNotAvailable() { - if (!$this->workbookManager->getWorkbook()) { + if (empty($this->workbookManager) || !$this->workbookManager->getWorkbook()) { throw new WriterNotOpenedException('The writer must be opened before performing this action.'); } } /** * {@inheritdoc} + * + * @throws Exception\WriterException */ protected function addRowToWriter(Row $row) { diff --git a/src/Spout/Writer/XLSX/Manager/OptionsManager.php b/src/Spout/Writer/XLSX/Manager/OptionsManager.php index d3b5cd423..e6adedb8b 100644 --- a/src/Spout/Writer/XLSX/Manager/OptionsManager.php +++ b/src/Spout/Writer/XLSX/Manager/OptionsManager.php @@ -39,6 +39,9 @@ protected function getSupportedOptions() Options::DEFAULT_ROW_STYLE, Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, Options::SHOULD_USE_INLINE_STRINGS, + Options::DEFAULT_COLUMN_WIDTH, + Options::DEFAULT_ROW_HEIGHT, + Options::COLUMN_WIDTHS, ]; } diff --git a/src/Spout/Writer/XLSX/Manager/WorksheetManager.php b/src/Spout/Writer/XLSX/Manager/WorksheetManager.php index 61b93a176..6452cccb8 100644 --- a/src/Spout/Writer/XLSX/Manager/WorksheetManager.php +++ b/src/Spout/Writer/XLSX/Manager/WorksheetManager.php @@ -15,6 +15,7 @@ use Box\Spout\Writer\Common\Entity\Worksheet; use Box\Spout\Writer\Common\Helper\CellHelper; use Box\Spout\Writer\Common\Manager\RegisteredStyle; +use Box\Spout\Writer\Common\Manager\ManagesCellSize; use Box\Spout\Writer\Common\Manager\RowManager; use Box\Spout\Writer\Common\Manager\Style\StyleMerger; use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface; @@ -26,6 +27,8 @@ */ class WorksheetManager implements WorksheetManagerInterface { + use ManagesCellSize; + /** * Maximum number of characters a cell can contain * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-16c69c74-3d6a-4aaf-ba35-e6eb276e8eaa [Excel 2007] @@ -86,6 +89,9 @@ public function __construct( InternalEntityFactory $entityFactory ) { $this->shouldUseInlineStrings = $optionsManager->getOption(Options::SHOULD_USE_INLINE_STRINGS); + $this->setDefaultColumnWidth($optionsManager->getOption(Options::DEFAULT_COLUMN_WIDTH)); + $this->setDefaultRowHeight($optionsManager->getOption(Options::DEFAULT_ROW_HEIGHT)); + $this->columnWidths = $optionsManager->getOption(Options::COLUMN_WIDTHS) ?? []; $this->rowManager = $rowManager; $this->styleManager = $styleManager; $this->styleMerger = $styleMerger; @@ -114,7 +120,23 @@ public function startSheet(Worksheet $worksheet) $worksheet->setFilePointer($sheetFilePointer); \fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER); - \fwrite($sheetFilePointer, ''); + } + + /** + * Writes the sheet data header + * + * @param Worksheet $worksheet The worksheet to add the row to + * @return void + */ + private function ensureSheetDataStated(Worksheet $worksheet) + { + if (!$worksheet->getSheetDataStarted()) { + $worksheetFilePointer = $worksheet->getFilePointer(); + \fwrite($worksheetFilePointer, $this->getXMLFragmentForDefaultCellSizing()); + \fwrite($worksheetFilePointer, $this->getXMLFragmentForColumnWidths()); + \fwrite($worksheetFilePointer, ''); + $worksheet->setSheetDataStarted(true); + } } /** @@ -148,17 +170,20 @@ public function addRow(Worksheet $worksheet, Row $row) * * @param Worksheet $worksheet The worksheet to add the row to * @param Row $row The row to be written - * @throws IOException If the data cannot be written * @throws InvalidArgumentException If a cell value's type is not supported + * @throws IOException If the data cannot be written * @return void */ private function addNonEmptyRow(Worksheet $worksheet, Row $row) { + $this->ensureSheetDataStated($worksheet); + $sheetFilePointer = $worksheet->getFilePointer(); $rowStyle = $row->getStyle(); $rowIndexOneBased = $worksheet->getLastWrittenRowIndex() + 1; $numCells = $row->getNumCells(); - $rowXML = ''; + $hasCustomHeight = $this->defaultRowHeight > 0 ? '1' : '0'; + $rowXML = ""; foreach ($row->getCells() as $columnIndexZeroBased => $cell) { $registeredStyle = $this->applyStyleAndRegister($cell, $rowStyle); @@ -171,7 +196,7 @@ private function addNonEmptyRow(Worksheet $worksheet, Row $row) $rowXML .= ''; - $wasWriteSuccessful = \fwrite($worksheet->getFilePointer(), $rowXML); + $wasWriteSuccessful = \fwrite($sheetFilePointer, $rowXML); if ($wasWriteSuccessful === false) { throw new IOException("Unable to write data in {$worksheet->getFilePath()}"); } @@ -180,7 +205,7 @@ private function addNonEmptyRow(Worksheet $worksheet, Row $row) /** * Applies styles to the given style, merging the cell's style with its row's style * - * @param Cell $cell + * @param Cell $cell * @param Style $rowStyle * * @throws InvalidArgumentException If the given value cannot be processed @@ -221,10 +246,10 @@ private function applyStyleAndRegister(Cell $cell, Style $rowStyle) : Registered /** * Builds and returns xml for a single cell. * - * @param int $rowIndexOneBased - * @param int $columnIndexZeroBased + * @param int $rowIndexOneBased + * @param int $columnIndexZeroBased * @param Cell $cell - * @param int $styleId + * @param int $styleId * * @throws InvalidArgumentException If the given value cannot be processed * @return string @@ -282,6 +307,43 @@ private function getCellXMLFragmentForNonEmptyString($cellValue) return $cellXMLFragment; } + /** + * Construct column width references xml to inject into worksheet xml file + * + * @return string + */ + public function getXMLFragmentForColumnWidths() + { + if (empty($this->columnWidths)) { + return ''; + } + $xml = ''; + foreach ($this->columnWidths as $entry) { + $xml .= ''; + } + $xml .= ''; + + return $xml; + } + + /** + * Constructs default row height and width xml to inject into worksheet xml file + * + * @return string + */ + public function getXMLFragmentForDefaultCellSizing() + { + $rowHeightXml = empty($this->defaultRowHeight) ? '' : " defaultRowHeight=\"{$this->defaultRowHeight}\""; + $colWidthXml = empty($this->defaultColumnWidth) ? '' : " defaultColWidth=\"{$this->defaultColumnWidth}\""; + if (empty($colWidthXml) && empty($rowHeightXml)) { + return ''; + } + // Ensure that the required defaultRowHeight is set + $rowHeightXml = empty($rowHeightXml) ? ' defaultRowHeight="0"' : $rowHeightXml; + + return ""; + } + /** * {@inheritdoc} */ @@ -292,7 +354,7 @@ public function close(Worksheet $worksheet) if (!\is_resource($worksheetFilePointer)) { return; } - + $this->ensureSheetDataStated($worksheet); \fwrite($worksheetFilePointer, ''); \fwrite($worksheetFilePointer, ''); \fclose($worksheetFilePointer); diff --git a/tests/Spout/Writer/ODS/SheetTest.php b/tests/Spout/Writer/ODS/SheetTest.php index 3bd219366..f94f63c5a 100644 --- a/tests/Spout/Writer/ODS/SheetTest.php +++ b/tests/Spout/Writer/ODS/SheetTest.php @@ -6,6 +6,7 @@ use Box\Spout\Writer\Common\Creator\WriterEntityFactory; use Box\Spout\Writer\Common\Entity\Sheet; use Box\Spout\Writer\Exception\InvalidSheetNameException; +use Box\Spout\Writer\Exception\WriterNotOpenedException; use Box\Spout\Writer\RowCreationHelper; use PHPUnit\Framework\TestCase; @@ -82,7 +83,7 @@ public function testSetSheetNameShouldThrowWhenNameIsAlreadyUsed() */ public function testSetSheetVisibilityShouldCreateSheetHidden() { - $fileName = 'test_set_visibility_should_create_sheet_hidden.xlsx'; + $fileName = 'test_set_visibility_should_create_sheet_hidden.ods'; $this->writeDataToHiddenSheet($fileName); $resourcePath = $this->getGeneratedResourcePath($fileName); @@ -92,12 +93,120 @@ public function testSetSheetVisibilityShouldCreateSheetHidden() $this->assertStringContainsString(' table:display="false"', $xmlContents, 'The sheet visibility should have been changed to "hidden"'); } - /** - * @param string $fileName - * @param string $sheetName - * @return Sheet - */ - private function writeDataAndReturnSheetWithCustomName($fileName, $sheetName) + public function testThrowsIfWorkbookIsNotInitialized() + { + $this->expectException(WriterNotOpenedException::class); + $writer = WriterEntityFactory::createODSWriter(); + + $writer->addRow($this->createRowFromValues([])); + } + + public function testWritesDefaultCellSizesIfSet() + { + $fileName = 'test_writes_default_cell_sizes_if_set.ods'; + + $this->createGeneratedFolderIfNeeded($fileName); + $resourcePath = $this->getGeneratedResourcePath($fileName); + + $writer = WriterEntityFactory::createODSWriter(); + $writer->setDefaultColumnWidth(100.0); + $writer->setDefaultRowHeight(20.0); + $writer->openToFile($resourcePath); + + $writer->addRow($this->createRowFromValues(['ods--11', 'ods--12'])); + $writer->close(); + + $resourcePath = $this->getGeneratedResourcePath($fileName); + $pathToWorkbookFile = $resourcePath . '#content.xml'; + $xmlContents = file_get_contents('zip://' . $pathToWorkbookFile); + + $this->assertContains(' style:column-width="100pt"', $xmlContents, 'No default col width found in sheet'); + $this->assertContains(' style:row-height="20pt"', $xmlContents, 'No default row height found in sheet'); + $this->assertContains(' style:use-optimal-row-height="false', $xmlContents, 'No optimal row height override found in sheet'); + } + + public function testWritesColumnWidths() + { + $fileName = 'test_column_widths.ods'; + $writer = $this->writerForFile($fileName); + + $writer->setColumnWidth(100.0, 1); + $writer->addRow($this->createRowFromValues(['ods--11', 'ods--12'])); + $writer->close(); + + $resourcePath = $this->getGeneratedResourcePath($fileName); + $pathToWorkbookFile = $resourcePath . '#content.xml'; + $xmlContents = file_get_contents('zip://' . $pathToWorkbookFile); + + $this->assertContains('', $xmlContents, 'No matching custom col style definition found in sheet'); + $this->assertContains('', $xmlContents, 'No matching table-column-properties found in sheet'); + $this->assertContains('table:style-name="co0"', $xmlContents, 'No matching table:style-name found in sheet'); + $this->assertContains('table:number-columns-repeated="1"', $xmlContents, 'No matching table:number-columns-repeated count found in sheet'); + } + + public function testWritesMultipleColumnWidths() + { + $fileName = 'test_multiple_column_widths.ods'; + $writer = $this->writerForFile($fileName); + + $writer->setColumnWidth(100.0, 1, 2, 3); + $writer->addRow($this->createRowFromValues(['ods--11', 'ods--12', 'ods--13'])); + $writer->close(); + + $resourcePath = $this->getGeneratedResourcePath($fileName); + $pathToWorkbookFile = $resourcePath . '#content.xml'; + $xmlContents = file_get_contents('zip://' . $pathToWorkbookFile); + + $this->assertContains('', $xmlContents, 'No matching custom col style definition found in sheet'); + $this->assertContains('', $xmlContents, 'No matching table-column-properties found in sheet'); + $this->assertContains('table:style-name="co0"', $xmlContents, 'No matching table:style-name found in sheet'); + $this->assertContains('table:number-columns-repeated="3"', $xmlContents, 'No matching table:number-columns-repeated count found in sheet'); + } + + public function testWritesMultipleColumnWidthsInRanges() + { + $fileName = 'test_multiple_column_widths_in_ranges.ods'; + $writer = $this->writerForFile($fileName); + + $writer->setColumnWidth(50.0, 1, 3, 4, 6); + $writer->setColumnWidth(100.0, 2, 5); + $writer->addRow($this->createRowFromValues(['ods--11', 'ods--12', 'ods--13', 'ods--14', 'ods--15', 'ods--16'])); + $writer->close(); + + $resourcePath = $this->getGeneratedResourcePath($fileName); + $pathToWorkbookFile = $resourcePath . '#content.xml'; + $xmlContents = file_get_contents('zip://' . $pathToWorkbookFile); + + $this->assertContains('', $xmlContents, 'No matching custom col style 0 definition found in sheet'); + $this->assertContains('', $xmlContents, 'No matching custom col style 1 definition found in sheet'); + $this->assertContains('', $xmlContents, 'No matching custom col style 2 definition found in sheet'); + $this->assertContains('', $xmlContents, 'No matching custom col style 3 definition found in sheet'); + $this->assertContains('', $xmlContents, 'No matching custom col style 4 definition found in sheet'); + $this->assertContains('', $xmlContents, 'No matching table-column-properties found in sheet'); + $this->assertContains('', $xmlContents, 'No matching table-column-properties found in sheet'); + $this->assertContains('', $xmlContents, 'No matching table:number-columns-repeated count found in sheet'); + } + + public function testCanTakeColumnWidthsAsRange() + { + $fileName = 'test_column_widths_as_ranges.ods'; + $writer = $this->writerForFile($fileName); + + $writer->setColumnWidthForRange(150.0, 1, 3); + $writer->addRow($this->createRowFromValues(['ods--11', 'ods--12', 'ods--13'])); + $writer->close(); + + $resourcePath = $this->getGeneratedResourcePath($fileName); + $pathToWorkbookFile = $resourcePath . '#content.xml'; + $xmlContents = file_get_contents('zip://' . $pathToWorkbookFile); + + $this->assertContains('', $xmlContents, 'No matching custom col style 0 definition found in sheet'); + $this->assertContains('style:column-width="150pt"/>', $xmlContents, 'No matching table-column-properties found in sheet'); + $this->assertContains('table:style-name="co0"', $xmlContents, 'No matching table:style-name found in sheet'); + $this->assertContains('table:number-columns-repeated="3"', $xmlContents, 'No matching table:number-columns-repeated count found in sheet'); + } + + private function writerForFile($fileName) { $this->createGeneratedFolderIfNeeded($fileName); $resourcePath = $this->getGeneratedResourcePath($fileName); @@ -105,6 +214,18 @@ private function writeDataAndReturnSheetWithCustomName($fileName, $sheetName) $writer = WriterEntityFactory::createODSWriter(); $writer->openToFile($resourcePath); + return $writer; + } + + /** + * @param string $fileName + * @param string $sheetName + * @return void + */ + private function writeDataAndReturnSheetWithCustomName($fileName, $sheetName) + { + $writer = $this->writerForFile($fileName); + $sheet = $writer->getCurrentSheet(); $sheet->setName($sheetName); diff --git a/tests/Spout/Writer/XLSX/SheetTest.php b/tests/Spout/Writer/XLSX/SheetTest.php index a2431619c..79033c208 100644 --- a/tests/Spout/Writer/XLSX/SheetTest.php +++ b/tests/Spout/Writer/XLSX/SheetTest.php @@ -6,6 +6,7 @@ use Box\Spout\Writer\Common\Creator\WriterEntityFactory; use Box\Spout\Writer\Common\Entity\Sheet; use Box\Spout\Writer\Exception\InvalidSheetNameException; +use Box\Spout\Writer\Exception\WriterNotOpenedException; use Box\Spout\Writer\RowCreationHelper; use PHPUnit\Framework\TestCase; @@ -92,6 +93,138 @@ public function testSetSheetVisibilityShouldCreateSheetHidden() $this->assertStringContainsString(' state="hidden"', $xmlContents, 'The sheet visibility should have been changed to "hidden"'); } + public function testThrowsIfWorkbookIsNotInitialized() + { + $this->expectException(WriterNotOpenedException::class); + $writer = WriterEntityFactory::createXLSXWriter(); + + $writer->addRow($this->createRowFromValues([])); + } + + public function testWritesDefaultCellSizesIfSet() + { + $fileName = 'test_writes_default_cell_sizes_if_set.xlsx'; + $this->createGeneratedFolderIfNeeded($fileName); + $resourcePath = $this->getGeneratedResourcePath($fileName); + + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->setDefaultColumnWidth(10.0); + $writer->setDefaultRowHeight(20.0); + $writer->openToFile($resourcePath); + $writer->addRow($this->createRowFromValues(['xlsx--11', 'xlsx--12'])); + $writer->close(); + + $pathToWorkbookFile = $resourcePath . '#xl/worksheets/sheet1.xml'; + $xmlContents = file_get_contents('zip://' . $pathToWorkbookFile); + + $this->assertContains('assertContains(' defaultColWidth="10', $xmlContents, 'No default column width found in sheet'); + $this->assertContains(' defaultRowHeight="20', $xmlContents, 'No default row height found in sheet'); + $this->assertContains(' customHeight="1"', $xmlContents, 'No row height override flag found in row'); + } + + public function testWritesDefaultRequiredRowHeightIfOmitted() + { + $fileName = 'test_writes_default_required_row_height_if_omitted.xlsx'; + $this->createGeneratedFolderIfNeeded($fileName); + $resourcePath = $this->getGeneratedResourcePath($fileName); + + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->setDefaultColumnWidth(10.0); + $writer->openToFile($resourcePath); + + $writer->addRow($this->createRowFromValues(['xlsx--11', 'xlsx--12'])); + $writer->close(); + + $pathToWorkbookFile = $resourcePath . '#xl/worksheets/sheet1.xml'; + $xmlContents = file_get_contents('zip://' . $pathToWorkbookFile); + + $this->assertContains('assertContains(' defaultColWidth="10', $xmlContents, 'No default column width found in sheet'); + $this->assertContains(' defaultRowHeight="0', $xmlContents, 'No default row height found in sheet'); + } + + public function testWritesColumnWidths() + { + $fileName = 'test_column_widths.xlsx'; + $this->createGeneratedFolderIfNeeded($fileName); + $resourcePath = $this->getGeneratedResourcePath($fileName); + + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->openToFile($resourcePath); + $writer->setColumnWidth(100.0, 1); + $writer->addRow($this->createRowFromValues(['xlsx--11', 'xlsx--12'])); + $writer->close(); + + $pathToWorkbookFile = $resourcePath . '#xl/worksheets/sheet1.xml'; + $xmlContents = file_get_contents('zip://' . $pathToWorkbookFile); + + $this->assertContains('assertContains('createGeneratedFolderIfNeeded($fileName); + $resourcePath = $this->getGeneratedResourcePath($fileName); + + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->openToFile($resourcePath); + $writer->setColumnWidth(100.0, 1, 2, 3); + $writer->addRow($this->createRowFromValues(['xlsx--11', 'xlsx--12', 'xlsx--13'])); + $writer->close(); + + $pathToWorkbookFile = $resourcePath . '#xl/worksheets/sheet1.xml'; + $xmlContents = file_get_contents('zip://' . $pathToWorkbookFile); + + $this->assertContains('assertContains('createGeneratedFolderIfNeeded($fileName); + $resourcePath = $this->getGeneratedResourcePath($fileName); + + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->openToFile($resourcePath); + $writer->setColumnWidth(50.0, 1, 3, 4, 6); + $writer->setColumnWidth(100.0, 2, 5); + $writer->addRow($this->createRowFromValues(['xlsx--11', 'xlsx--12', 'xlsx--13', 'xlsx--14', 'xlsx--15', 'xlsx--16'])); + $writer->close(); + + $pathToWorkbookFile = $resourcePath . '#xl/worksheets/sheet1.xml'; + $xmlContents = file_get_contents('zip://' . $pathToWorkbookFile); + + $this->assertContains('assertContains('assertContains('assertContains('assertContains('assertContains('createGeneratedFolderIfNeeded($fileName); + $resourcePath = $this->getGeneratedResourcePath($fileName); + + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->openToFile($resourcePath); + $writer->setColumnWidthForRange(50.0, 1, 3); + $writer->addRow($this->createRowFromValues(['xlsx--11', 'xlsx--12', 'xlsx--13'])); + $writer->close(); + + $pathToWorkbookFile = $resourcePath . '#xl/worksheets/sheet1.xml'; + $xmlContents = file_get_contents('zip://' . $pathToWorkbookFile); + + $this->assertContains('assertContains('