Add XLSX support for default column width/row height as well as custom column widths through writer or OptionsManager (and test for it)
This commit is contained in:
parent
2d297e954b
commit
82170a058b
@ -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';
|
||||
}
|
||||
|
@ -103,7 +103,6 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
|
||||
* 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,8 +116,8 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
|
||||
/**
|
||||
* 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
|
||||
* @return Worksheet The created sheet
|
||||
* @throws IOException
|
||||
*/
|
||||
private function addNewSheet()
|
||||
{
|
||||
@ -157,6 +156,16 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
|
||||
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,9 +219,10 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
|
||||
* 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
|
||||
*
|
||||
* @return void
|
||||
* @throws IOException If trying to create a new sheet and unable to open the sheet for writing
|
||||
* @throws \Box\Spout\Common\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function addRowToCurrentWorksheet(Row $row)
|
||||
{
|
||||
@ -249,8 +259,10 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
|
||||
*
|
||||
* @param Worksheet $worksheet Worksheet to write the row to
|
||||
* @param Row $row The row to be added
|
||||
* @throws WriterException If unable to write data
|
||||
*
|
||||
* @return void
|
||||
* @throws IOException
|
||||
* @throws \Box\Spout\Common\Exception\InvalidArgumentException
|
||||
*/
|
||||
private function addRowToWorksheet(Worksheet $worksheet, Row $row)
|
||||
{
|
||||
@ -276,6 +288,28 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|null $width
|
||||
*/
|
||||
public function setDefaultColumnWidth(float $width) {
|
||||
$this->worksheetManager->setDefaultColumnWidth($width);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|null $height
|
||||
*/
|
||||
public function setDefaultRowHeight(float $height) {
|
||||
$this->worksheetManager->setDefaultRowHeight($height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|null $width
|
||||
* @param array $columns One or more columns with this width
|
||||
*/
|
||||
public function setColumnWidth($width, ...$columns) {
|
||||
$this->worksheetManager->setColumnWidth($width, ...$columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the workbook and all its associated sheets.
|
||||
* All the necessary files are written to disk and zipped together to create the final file.
|
||||
|
@ -42,6 +42,21 @@ interface WorkbookManagerInterface
|
||||
*/
|
||||
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).
|
||||
|
@ -4,6 +4,7 @@ namespace Box\Spout\Writer;
|
||||
|
||||
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;
|
||||
@ -96,8 +97,9 @@ abstract class WriterMultiSheetsAbstract extends WriterAbstract
|
||||
/**
|
||||
* 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
|
||||
* @return Sheet The created sheet
|
||||
* @throws IOException
|
||||
* @throws WriterNotOpenedException If the writer has not been opened yet
|
||||
*/
|
||||
public function addNewSheetAndMakeItCurrent()
|
||||
{
|
||||
@ -135,6 +137,36 @@ abstract class WriterMultiSheetsAbstract extends WriterAbstract
|
||||
$this->workbookManager->setCurrentSheet($sheet);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $width
|
||||
* @throws WriterNotOpenedException
|
||||
*/
|
||||
public function setDefaultColumnWidth(float $width)
|
||||
{
|
||||
$this->throwIfWorkbookIsNotAvailable();
|
||||
$this->workbookManager->setDefaultColumnWidth($width);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $height
|
||||
* @throws WriterNotOpenedException
|
||||
*/
|
||||
public function setDefaultRowHeight(float $height)
|
||||
{
|
||||
$this->throwIfWorkbookIsNotAvailable();
|
||||
$this->workbookManager->setDefaultRowHeight($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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the workbook has been created. Throws an exception if not created yet.
|
||||
*
|
||||
@ -143,13 +175,15 @@ abstract class WriterMultiSheetsAbstract extends WriterAbstract
|
||||
*/
|
||||
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)
|
||||
{
|
||||
|
@ -39,6 +39,9 @@ class OptionsManager extends OptionsManagerAbstract
|
||||
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,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -62,6 +62,18 @@ EOD;
|
||||
/** @var InternalEntityFactory Factory to create entities */
|
||||
private $entityFactory;
|
||||
|
||||
/** @var float|null The default column width to use */
|
||||
private $defaultColumnWidth;
|
||||
|
||||
/** @var float|null The default row height to use */
|
||||
private $defaultRowHeight;
|
||||
|
||||
/** @var bool Whether rows have been written */
|
||||
private $hasWrittenRows = false;
|
||||
|
||||
/** @var array Array of min-max-width arrays */
|
||||
private $columnWidths;
|
||||
|
||||
/**
|
||||
* WorksheetManager constructor.
|
||||
*
|
||||
@ -85,6 +97,9 @@ EOD;
|
||||
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;
|
||||
@ -102,6 +117,39 @@ EOD;
|
||||
return $this->sharedStringsManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|null $width
|
||||
*/
|
||||
public function setDefaultColumnWidth($width) {
|
||||
$this->defaultColumnWidth = $width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|null $height
|
||||
*/
|
||||
public function setDefaultRowHeight($height) {
|
||||
$this->defaultRowHeight = $height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|null $width
|
||||
* @param array $columns One or more columns with this width
|
||||
*/
|
||||
public function setColumnWidth($width, ...$columns) {
|
||||
// Gather sequences
|
||||
$sequence = [];
|
||||
foreach ($columns as $i) {
|
||||
$sequenceLength = count($sequence);
|
||||
$previousValue = $sequence[$sequenceLength - 1];
|
||||
if ($sequenceLength > 0 && $i !== $previousValue + 1) {
|
||||
$this->columnWidths[] = [$sequence[0], $previousValue, $width];
|
||||
$sequence = [];
|
||||
}
|
||||
$sequence[] = $i;
|
||||
}
|
||||
$this->columnWidths[] = [$sequence[0], $sequence[count($sequence) - 1], $width];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -113,7 +161,6 @@ EOD;
|
||||
$worksheet->setFilePointer($sheetFilePointer);
|
||||
|
||||
fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER);
|
||||
fwrite($sheetFilePointer, '<sheetData>');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -153,6 +200,12 @@ EOD;
|
||||
*/
|
||||
private function addNonEmptyRow(Worksheet $worksheet, Row $row)
|
||||
{
|
||||
$sheetFilePointer = $worksheet->getFilePointer();
|
||||
if (!$this->hasWrittenRows) {
|
||||
fwrite($sheetFilePointer, $this->getXMLFragmentForDefaultCellSizing());
|
||||
fwrite($sheetFilePointer, $this->getXMLFragmentForColumnWidths());
|
||||
fwrite($sheetFilePointer, '<sheetData>');
|
||||
}
|
||||
$cellIndex = 0;
|
||||
$rowStyle = $row->getStyle();
|
||||
$rowIndex = $worksheet->getLastWrittenRowIndex() + 1;
|
||||
@ -167,10 +220,11 @@ EOD;
|
||||
|
||||
$rowXML .= '</row>';
|
||||
|
||||
$wasWriteSuccessful = fwrite($worksheet->getFilePointer(), $rowXML);
|
||||
$wasWriteSuccessful = fwrite($sheetFilePointer, $rowXML);
|
||||
if ($wasWriteSuccessful === false) {
|
||||
throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
|
||||
}
|
||||
$this->hasWrittenRows = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -256,6 +310,41 @@ EOD;
|
||||
return $cellXMLFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct column width references xml to inject into worksheet xml file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getXMLFragmentForColumnWidths()
|
||||
{
|
||||
if (empty($this->columnWidths)) {
|
||||
return '';
|
||||
}
|
||||
$xml = '<cols>';
|
||||
foreach ($this->columnWidths as $entry) {
|
||||
$xml .= '<col min="'.$entry[0].'" max="'.$entry[1].'" width="'.$entry[2].'" customWidth="true"/>';
|
||||
}
|
||||
$xml .= '</cols>';
|
||||
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 "<sheetFormatPr{$colWidthXml}{$rowHeightXml}/>";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -267,7 +356,9 @@ EOD;
|
||||
return;
|
||||
}
|
||||
|
||||
fwrite($worksheetFilePointer, '</sheetData>');
|
||||
if ($this->hasWrittenRows) {
|
||||
fwrite($worksheetFilePointer, '</sheetData>');
|
||||
}
|
||||
fwrite($worksheetFilePointer, '</worksheet>');
|
||||
fclose($worksheetFilePointer);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use Box\Spout\TestUsingResource;
|
||||
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,124 @@ class SheetTest extends TestCase
|
||||
$this->assertContains(' 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 testThrowsWhenTryingToSetDefaultsBeforeWorkbookLoaded()
|
||||
{
|
||||
$this->expectException(WriterNotOpenedException::class);
|
||||
$writer = WriterEntityFactory::createXLSXWriter();
|
||||
$writer->setDefaultColumnWidth(10.0);
|
||||
}
|
||||
|
||||
public function testWritesDefaultCellSizesIfSet()
|
||||
{
|
||||
$fileName = 'test_writes_default_cell_sizes_if_set.xlsx';
|
||||
$this->createGeneratedFolderIfNeeded($fileName);
|
||||
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
||||
|
||||
$writer = WriterEntityFactory::createXLSXWriter();
|
||||
$writer->openToFile($resourcePath);
|
||||
$writer->setDefaultColumnWidth(10.0);
|
||||
$writer->setDefaultRowHeight(10.0);
|
||||
$writer->addRow($this->createRowFromValues(['xlsx--11', 'xlsx--12']));
|
||||
$writer->close();
|
||||
|
||||
$pathToWorkbookFile = $resourcePath . '#xl/worksheets/sheet1.xml';
|
||||
$xmlContents = file_get_contents('zip://' . $pathToWorkbookFile);
|
||||
|
||||
$this->assertContains('<sheetFormatPr', $xmlContents, 'No sheetFormatPr tag found in sheet');
|
||||
$this->assertContains(' defaultColWidth="10', $xmlContents, 'No default column width found in sheet');
|
||||
$this->assertContains(' defaultRowHeight="10', $xmlContents, 'No default row height found in sheet');
|
||||
}
|
||||
|
||||
public function testWritesDefaultRequiredRowHeightIfOmitted()
|
||||
{
|
||||
$fileName = 'test_writes_default_required_row_height_if_omitted.xlsx';
|
||||
$this->createGeneratedFolderIfNeeded($fileName);
|
||||
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
||||
|
||||
$writer = WriterEntityFactory::createXLSXWriter();
|
||||
$writer->openToFile($resourcePath);
|
||||
$writer->setDefaultColumnWidth(10.0);
|
||||
$writer->addRow($this->createRowFromValues(['xlsx--11', 'xlsx--12']));
|
||||
$writer->close();
|
||||
|
||||
$pathToWorkbookFile = $resourcePath . '#xl/worksheets/sheet1.xml';
|
||||
$xmlContents = file_get_contents('zip://' . $pathToWorkbookFile);
|
||||
|
||||
$this->assertContains('<sheetFormatPr', $xmlContents, 'No sheetFormatPr tag found in sheet');
|
||||
$this->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('<cols', $xmlContents, 'No cols tag found in sheet');
|
||||
$this->assertContains('<col min="1" max="1" width="100" customWidth="true"', $xmlContents, 'No expected column width definition found in sheet');
|
||||
}
|
||||
|
||||
public function testWritesMultipleColumnWidths()
|
||||
{
|
||||
$fileName = 'test_multiple_column_widths.xlsx';
|
||||
$this->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('<cols', $xmlContents, 'No cols tag found in sheet');
|
||||
$this->assertContains('<col min="1" max="3" width="100" customWidth="true"', $xmlContents, 'No expected column width definition found in sheet');
|
||||
}
|
||||
|
||||
public function testWritesMultipleColumnWidthsInRanges()
|
||||
{
|
||||
$fileName = 'test_multiple_column_widths_in_ranges.xlsx';
|
||||
$this->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('<cols', $xmlContents, 'No cols tag found in sheet');
|
||||
$this->assertContains('<col min="1" max="1" width="50" customWidth="true"', $xmlContents, 'No expected column width definition found in sheet');
|
||||
$this->assertContains('<col min="3" max="4" width="50" customWidth="true"', $xmlContents, 'No expected column width definition found in sheet');
|
||||
$this->assertContains('<col min="6" max="6" width="50" customWidth="true"', $xmlContents, 'No expected column width definition found in sheet');
|
||||
$this->assertContains('<col min="2" max="2" width="100" customWidth="true"', $xmlContents, 'No expected column width definition found in sheet');
|
||||
$this->assertContains('<col min="5" max="5" width="100" customWidth="true"', $xmlContents, 'No expected column width definition found in sheet');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fileName
|
||||
* @param string $sheetName
|
||||
|
Loading…
x
Reference in New Issue
Block a user