diff --git a/src/Spout/Writer/CSV/Writer.php b/src/Spout/Writer/CSV/Writer.php index 8bf3c0f..a17f5e3 100644 --- a/src/Spout/Writer/CSV/Writer.php +++ b/src/Spout/Writer/CSV/Writer.php @@ -6,6 +6,7 @@ use Box\Spout\Writer\WriterAbstract; use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Helper\EncodingHelper; use Box\Spout\Writer\Common\Entity\Options; +use Box\Spout\Writer\Common\Entity\Row; /** * Class Writer @@ -77,20 +78,20 @@ class Writer extends WriterAbstract } /** - * Adds data to the currently opened writer. + * Adds a row to the currently opened writer. + * + * @param Row $row The row containing cells and styles * - * @param array $dataRow Array containing data to be written. - * Example $dataRow = ['data1', 1234, null, '', 'data5']; - * @param \Box\Spout\Writer\Common\Entity\Style\Style $style Ignored here since CSV does not support styling. * @return void - * @throws \Box\Spout\Common\Exception\IOException If unable to write data + * @throws IOException If unable to write data + * @internal param \Box\Spout\Writer\Common\Entity\Style\Style $style Ignored here since CSV does not support styling. */ - protected function addRowToWriter(array $dataRow, $style) + protected function addRowToWriter(Row $row) { $fieldDelimiter = $this->optionsManager->getOption(Options::FIELD_DELIMITER); $fieldEnclosure = $this->optionsManager->getOption(Options::FIELD_ENCLOSURE); - $wasWriteSuccessful = $this->globalFunctionsHelper->fputcsv($this->filePointer, $dataRow, $fieldDelimiter, $fieldEnclosure); + $wasWriteSuccessful = $this->globalFunctionsHelper->fputcsv($this->filePointer, $row->getCells(), $fieldDelimiter, $fieldEnclosure); if ($wasWriteSuccessful === false) { throw new IOException('Unable to write data'); } diff --git a/src/Spout/Writer/Common/Entity/Cell.php b/src/Spout/Writer/Common/Entity/Cell.php index fc8112f..587de27 100644 --- a/src/Spout/Writer/Common/Entity/Cell.php +++ b/src/Spout/Writer/Common/Entity/Cell.php @@ -2,8 +2,9 @@ namespace Box\Spout\Writer\Common\Entity; -use Box\Spout\Writer\Common\Helper\CellHelper; use Box\Spout\Writer\Common\Entity\Style\Style; +use Box\Spout\Writer\Common\Helper\CellHelper; +use Box\Spout\Writer\Common\Manager\Style\StyleMerger; /** * Class Cell @@ -61,6 +62,11 @@ class Cell */ protected $style = null; + /** + * @var StyleMerger + */ + protected $styleMerger; + /** * Cell constructor. * @param $value mixed @@ -72,6 +78,8 @@ class Cell if ($style) { $this->setStyle($style); } + + $this->styleMerger = new StyleMerger(); } /** @@ -100,10 +108,13 @@ class Cell } /** - * @return Style|null + * @return Style */ public function getStyle() { + if (!isset($this->style)) { + $this->setStyle(new Style()); + } return $this->style; } @@ -191,4 +202,18 @@ class Cell { return (string)$this->value; } + + /** + * @param Style $style|null + * @return $this + */ + public function applyStyle(Style $style = null) + { + if ($style === null) { + return $this; + } + $merged = $this->styleMerger->merge($this->getStyle(), $style); + $this->setStyle($merged); + return $this; + } } diff --git a/src/Spout/Writer/Common/Entity/Row.php b/src/Spout/Writer/Common/Entity/Row.php index 5aa1c0c..56e7ab2 100644 --- a/src/Spout/Writer/Common/Entity/Row.php +++ b/src/Spout/Writer/Common/Entity/Row.php @@ -3,6 +3,7 @@ namespace Box\Spout\Writer\Common\Entity; use Box\Spout\Writer\Common\Entity\Style\Style; +use Box\Spout\Writer\Common\Manager\Style\StyleMerger; class Row { @@ -18,6 +19,11 @@ class Row */ protected $style = null; + /** + * @var StyleMerger + */ + protected $styleMerger; + /** * Row constructor. * @param array $cells @@ -28,6 +34,8 @@ class Row $this ->setCells($cells) ->setStyle($style); + + $this->styleMerger = new StyleMerger(); } /** @@ -56,6 +64,9 @@ class Row */ public function getStyle() { + if (!isset($this->style)) { + $this->setStyle(new Style()); + } return $this->style; } @@ -69,12 +80,38 @@ class Row return $this; } + /** + * @param Style $style|null + * @return $this + */ + public function applyStyle(Style $style = null) + { + if ($style === null) { + return $this; + } + $merged = $this->styleMerger->merge($this->getStyle(), $style); + $this->setStyle($merged); + return $this; + } + /** * @param Cell $cell + * @return Row */ public function addCell(Cell $cell) { $this->cells[] = $cell; return $this; } + + /** + * Detect whether this row is considered empty. + * An empty row has either no cells at all - or only empty cells + * + * @return bool + */ + public function isEmpty() + { + return count($this->cells) === 0 || (count($this->cells) === 1 && $this->cells[0]->isEmpty()); + } } diff --git a/src/Spout/Writer/Common/Manager/Style/StyleManager.php b/src/Spout/Writer/Common/Manager/Style/StyleManager.php index 1f64166..ea6561e 100644 --- a/src/Spout/Writer/Common/Manager/Style/StyleManager.php +++ b/src/Spout/Writer/Common/Manager/Style/StyleManager.php @@ -2,6 +2,7 @@ namespace Box\Spout\Writer\Common\Manager\Style; +use Box\Spout\Writer\Common\Entity\Cell; use Box\Spout\Writer\Common\Entity\Style\Style; /** @@ -46,17 +47,17 @@ class StyleManager implements StyleManagerInterface return $this->styleRegistry->registerStyle($style); } + /** * Apply additional styles if the given row needs it. * Typically, set "wrap text" if a cell contains a new line. * - * @param Style $style The original style - * @param array $dataRow The row the style will be applied to - * @return Style The updated style + * @param Cell $cell + * @return Style */ - public function applyExtraStylesIfNeeded($style, $dataRow) + public function applyExtraStylesIfNeeded(Cell $cell) { - $updatedStyle = $this->applyWrapTextIfCellContainsNewLine($style, $dataRow); + $updatedStyle = $this->applyWrapTextIfCellContainsNewLine($cell); return $updatedStyle; } @@ -69,24 +70,18 @@ class StyleManager implements StyleManagerInterface * A workaround would be to encode "\n" as "_x000D_" but it does not work * on the Windows version of Excel... * - * @param Style $style The original style - * @param array $dataRow The row the style will be applied to - * @return Style The eventually updated style + * @param Cell $cell The cell the style should be applied to + * @return \Box\Spout\Writer\Common\Entity\Style\Style The eventually updated style */ - protected function applyWrapTextIfCellContainsNewLine($style, $dataRow) + protected function applyWrapTextIfCellContainsNewLine(Cell $cell) { // if the "wrap text" option is already set, no-op - if ($style->hasSetWrapText()) { - return $style; + if ($cell->getStyle()->hasSetWrapText()) { + return $cell->getStyle(); } - - foreach ($dataRow as $cell) { - if (is_string($cell) && strpos($cell, "\n") !== false) { - $style->setShouldWrapText(); - break; - } + if ($cell->isString() && strpos($cell->getValue(), "\n") !== false) { + $cell->getStyle()->setShouldWrapText(); } - - return $style; + return $cell->getStyle(); } } diff --git a/src/Spout/Writer/Common/Manager/Style/StyleManagerInterface.php b/src/Spout/Writer/Common/Manager/Style/StyleManagerInterface.php index f9f7349..d75506e 100644 --- a/src/Spout/Writer/Common/Manager/Style/StyleManagerInterface.php +++ b/src/Spout/Writer/Common/Manager/Style/StyleManagerInterface.php @@ -2,8 +2,10 @@ namespace Box\Spout\Writer\Common\Manager\Style; +use Box\Spout\Writer\Common\Entity\Cell; use Box\Spout\Writer\Common\Entity\Style\Style; + /** * Interface StyleHManagernterface * @@ -24,9 +26,8 @@ interface StyleManagerInterface * Apply additional styles if the given row needs it. * Typically, set "wrap text" if a cell contains a new line. * - * @param Style $style The original style - * @param array $dataRow The row the style will be applied to - * @return Style The updated style + * @param Cell $cell + * @return \Box\Spout\Writer\Common\Entity\Style\Style The updated style */ - public function applyExtraStylesIfNeeded($style, $dataRow); + public function applyExtraStylesIfNeeded(Cell $cell); } diff --git a/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php b/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php index c10af47..4732d04 100644 --- a/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php +++ b/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php @@ -12,7 +12,6 @@ use Box\Spout\Writer\Common\Entity\Worksheet; use Box\Spout\Writer\Exception\SheetNotFoundException; use Box\Spout\Writer\Exception\WriterException; use Box\Spout\Writer\Common\Creator\EntityFactory; -use Box\Spout\Writer\Common\Entity\Style\Style; /** * Class WorkbookManagerAbstract @@ -192,18 +191,16 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface } /** - * Adds data to the current sheet. + * Adds a row to the current sheet. * If shouldCreateNewSheetsAutomatically option is set to true, it will handle pagination * with the creation of new worksheets if one worksheet has reached its maximum capicity. * - * @param array $dataRow Array containing data to be written. Cannot be empty. - * Example $dataRow = ['data1', 1234, null, '', 'data5']; - * @param Style $style Style to be applied to the row. + * @param Row $row The row to added * @return void * @throws IOException If trying to create a new sheet and unable to open the sheet for writing * @throws WriterException If unable to write data */ - public function addRowToCurrentWorksheet($dataRow, Style $style) + public function addRowToCurrentWorksheet(Row $row) { $currentWorksheet = $this->getCurrentWorksheet(); $hasReachedMaxRows = $this->hasCurrentWorkseetReachedMaxRows(); @@ -214,12 +211,12 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface if ($this->optionManager->getOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY)) { $currentWorksheet = $this->addNewSheetAndMakeItCurrent(); - $this->addRowWithStyleToWorksheet($currentWorksheet, $dataRow, $style); + $this->addRowWithStyleToWorksheet($currentWorksheet, $row); } else { // otherwise, do nothing as the data won't be written anyways } } else { - $this->addRowWithStyleToWorksheet($currentWorksheet, $dataRow, $style); + $this->addRowWithStyleToWorksheet($currentWorksheet, $row); } } @@ -238,19 +235,16 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface * @param Worksheet $worksheet Worksheet to write the row to * @param array $dataRow Array containing data to be written. Cannot be empty. * Example $dataRow = ['data1', 1234, null, '', 'data5']; - * @param Style $style Style to be applied to the row. * @return void * @throws WriterException If unable to write data */ - private function addRowWithStyleToWorksheet(Worksheet $worksheet, $dataRow, Style $style) + private function addRowWithStyleToWorksheet(Worksheet $worksheet, Row $row) { - $updatedStyle = $this->styleManager->applyExtraStylesIfNeeded($style, $dataRow); - $registeredStyle = $this->styleManager->registerStyle($updatedStyle); - $this->worksheetManager->addRow($worksheet, $dataRow, $registeredStyle); + $this->worksheetManager->addRow($worksheet, $row); // update max num columns for the worksheet $currentMaxNumColumns = $worksheet->getMaxNumColumns(); - $cellsCount = count($dataRow); + $cellsCount = count($row->getCells()); $worksheet->setMaxNumColumns(max($currentMaxNumColumns, $cellsCount)); } @@ -312,4 +306,4 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface $rootFolder = $this->fileSystemHelper->getRootFolder(); $this->fileSystemHelper->deleteFolderRecursively($rootFolder); } -} \ No newline at end of file +} diff --git a/src/Spout/Writer/Common/Manager/WorkbookManagerInterface.php b/src/Spout/Writer/Common/Manager/WorkbookManagerInterface.php index dfaf263..0c23f7f 100644 --- a/src/Spout/Writer/Common/Manager/WorkbookManagerInterface.php +++ b/src/Spout/Writer/Common/Manager/WorkbookManagerInterface.php @@ -4,11 +4,12 @@ namespace Box\Spout\Writer\Common\Manager; use Box\Spout\Common\Exception\IOException; use Box\Spout\Writer\Common\Entity\Sheet; +use Box\Spout\Writer\Common\Entity\Row; use Box\Spout\Writer\Common\Entity\Workbook; use Box\Spout\Writer\Common\Entity\Worksheet; +use Box\Spout\Writer\Common\Sheet; use Box\Spout\Writer\Exception\SheetNotFoundException; use Box\Spout\Writer\Exception\WriterException; -use Box\Spout\Writer\Common\Entity\Style\Style; /** * Interface WorkbookManagerInterface @@ -55,19 +56,16 @@ interface WorkbookManagerInterface public function setCurrentSheet(Sheet $sheet); /** - * Adds data to the current sheet. + * Adds a row to the current sheet. * If shouldCreateNewSheetsAutomatically option is set to true, it will handle pagination * with the creation of new worksheets if one worksheet has reached its maximum capicity. * - * @param array $dataRow Array containing data to be written. Cannot be empty. - * Example $dataRow = ['data1', 1234, null, '', 'data5']; - * @param Style $style Style to be applied to the row. + * @param Row $row The row to added * @return void * @throws IOException If trying to create a new sheet and unable to open the sheet for writing * @throws WriterException If unable to write data */ - public function addRowToCurrentWorksheet($dataRow, Style $style); - + public function addRowToCurrentWorksheet(Row $row); /** * Closes the workbook and all its associated sheets. * All the necessary files are written to disk and zipped together to create the final file. @@ -77,4 +75,4 @@ interface WorkbookManagerInterface * @return void */ public function close($finalFilePointer); -} \ No newline at end of file +} diff --git a/src/Spout/Writer/Common/Manager/WorksheetManagerInterface.php b/src/Spout/Writer/Common/Manager/WorksheetManagerInterface.php index 8f41559..3382b6d 100644 --- a/src/Spout/Writer/Common/Manager/WorksheetManagerInterface.php +++ b/src/Spout/Writer/Common/Manager/WorksheetManagerInterface.php @@ -2,8 +2,8 @@ namespace Box\Spout\Writer\Common\Manager; +use Box\Spout\Writer\Common\Entity\Row; use Box\Spout\Writer\Common\Entity\Worksheet; -use Box\Spout\Writer\Common\Entity\Style\Style; /** * Interface WorksheetManagerInterface @@ -14,17 +14,15 @@ use Box\Spout\Writer\Common\Entity\Style\Style; interface WorksheetManagerInterface { /** - * Adds data to the worksheet. + * Adds a row to the worksheet. * * @param Worksheet $worksheet The worksheet to add the row to - * @param array $dataRow Array containing data to be written. Cannot be empty. - * Example $dataRow = ['data1', 1234, null, '', 'data5']; - * @param Style $rowStyle Style to be applied to the row. NULL means use default style. + * @param Row $row The row to be added * @return void * @throws \Box\Spout\Common\Exception\IOException If the data cannot be written * @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported */ - public function addRow(Worksheet $worksheet, $dataRow, $rowStyle); + public function addRow(Worksheet $worksheet, Row $row); /** * Prepares the worksheet to accept data @@ -42,4 +40,4 @@ interface WorksheetManagerInterface * @return void */ public function close(Worksheet $worksheet); -} \ No newline at end of file +} diff --git a/src/Spout/Writer/ODS/Creator/InternalFactory.php b/src/Spout/Writer/ODS/Creator/InternalFactory.php index 885bff3..24fc0d5 100644 --- a/src/Spout/Writer/ODS/Creator/InternalFactory.php +++ b/src/Spout/Writer/ODS/Creator/InternalFactory.php @@ -48,20 +48,22 @@ class InternalFactory implements InternalFactoryInterface $fileSystemHelper->createBaseFilesAndFolders(); $styleManager = $this->createStyleManager($optionsManager); - $worksheetManager = $this->createWorksheetManager(); + $worksheetManager = $this->createWorksheetManager($styleManager); return new WorkbookManager($workbook, $optionsManager, $worksheetManager, $styleManager, $fileSystemHelper, $this->entityFactory); } /** + * @param StyleManager $styleManager * @return WorksheetManager */ - private function createWorksheetManager() + private function createWorksheetManager(StyleManager $styleManager) { $stringsEscaper = $this->createStringsEscaper(); $stringsHelper = $this->createStringHelper(); - return new WorksheetManager($stringsEscaper, $stringsHelper); + + return new WorksheetManager($styleManager, $stringsEscaper, $stringsHelper); } /** diff --git a/src/Spout/Writer/ODS/Manager/WorksheetManager.php b/src/Spout/Writer/ODS/Manager/WorksheetManager.php index 189ac08..5a46254 100644 --- a/src/Spout/Writer/ODS/Manager/WorksheetManager.php +++ b/src/Spout/Writer/ODS/Manager/WorksheetManager.php @@ -6,9 +6,9 @@ use Box\Spout\Common\Exception\InvalidArgumentException; use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Helper\StringHelper; use Box\Spout\Writer\Common\Entity\Cell; +use Box\Spout\Writer\Common\Entity\Row; use Box\Spout\Writer\Common\Entity\Worksheet; use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface; -use Box\Spout\Writer\Common\Entity\Style\Style; use Box\Spout\Writer\ODS\Manager\Style\StyleManager; /** @@ -25,6 +25,9 @@ class WorksheetManager implements WorksheetManagerInterface /** @var StringHelper String helper */ private $stringHelper; + /** @var StyleManager Manages styles */ + private $styleManager; + /** * WorksheetManager constructor. * @@ -32,11 +35,13 @@ class WorksheetManager implements WorksheetManagerInterface * @param StringHelper $stringHelper */ public function __construct( + StyleManager $styleManager, \Box\Spout\Common\Escaper\ODS $stringsEscaper, StringHelper $stringHelper) { $this->stringsEscaper = $stringsEscaper; $this->stringHelper = $stringHelper; + $this->styleManager = $styleManager; } /** @@ -87,24 +92,21 @@ class WorksheetManager implements WorksheetManagerInterface } /** - * Adds data to the given worksheet. + /** + * Adds a row to the worksheet. * * @param Worksheet $worksheet The worksheet to add the row to - * @param array $dataRow Array containing data to be written. Cannot be empty. - * Example $dataRow = ['data1', 1234, null, '', 'data5']; - * @param Style $rowStyle Style to be applied to the row. NULL means use default style. + * @param Row $row The row to be added * @return void + * * @throws IOException If the data cannot be written * @throws InvalidArgumentException If a cell value's type is not supported */ - public function addRow(Worksheet $worksheet, $dataRow, $rowStyle) + public function addRow(Worksheet $worksheet, Row $row) { - // $dataRow can be an associative array. We need to transform - // it into a regular array, as we'll use the numeric indexes. - $dataRowWithNumericIndexes = array_values($dataRow); - $styleIndex = ($rowStyle->getId() + 1); // 1-based - $cellsCount = count($dataRow); + $cells = $row->getCells(); + $cellsCount = count($cells); $data = ''; @@ -112,15 +114,22 @@ class WorksheetManager implements WorksheetManagerInterface $nextCellIndex = 1; for ($i = 0; $i < $cellsCount; $i++) { - $currentCellValue = $dataRowWithNumericIndexes[$currentCellIndex]; - // Using isset here because it is way faster than array_key_exists... - if (!isset($dataRowWithNumericIndexes[$nextCellIndex]) || - $currentCellValue !== $dataRowWithNumericIndexes[$nextCellIndex]) { + /** @var Cell $cell */ + $cell = $row->getCells()[$currentCellIndex]; + /** @var Cell|null $nextCell */ + $nextCell = isset($cells[$nextCellIndex]) ? $cells[$nextCellIndex] : null; + + if (null === $nextCell || $cell->getValue() !== $nextCell->getValue()) { + + // Apply styles - the row style is merged at this point + $cell->applyStyle($row->getStyle()); + $this->styleManager->applyExtraStylesIfNeeded($cell); + $registeredStyle = $this->styleManager->registerStyle($cell->getStyle()); + $styleIndex = $registeredStyle->getId() + 1; // 1-based $numTimesValueRepeated = ($nextCellIndex - $currentCellIndex); - $data .= $this->getCellXML($currentCellValue, $styleIndex, $numTimesValueRepeated); - + $data .= $this->getCellXML($cell, $styleIndex, $numTimesValueRepeated); $currentCellIndex = $nextCellIndex; } @@ -142,13 +151,13 @@ class WorksheetManager implements WorksheetManagerInterface /** * Returns the cell XML content, given its value. * - * @param mixed $cellValue The value to be written + * @param Cell $cell The cell to be written * @param int $styleIndex Index of the used style * @param int $numTimesValueRepeated Number of times the value is consecutively repeated * @return string The cell XML content * @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported */ - private function getCellXML($cellValue, $styleIndex, $numTimesValueRepeated) + protected function getCellXML(Cell $cell, $styleIndex, $numTimesValueRepeated) { $data = 'isString()) { $data .= ' office:value-type="string" calcext:value-type="string">'; diff --git a/src/Spout/Writer/WriterAbstract.php b/src/Spout/Writer/WriterAbstract.php index 7569ff2..ecba0db 100644 --- a/src/Spout/Writer/WriterAbstract.php +++ b/src/Spout/Writer/WriterAbstract.php @@ -7,7 +7,9 @@ use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Exception\SpoutException; use Box\Spout\Common\Helper\FileSystemHelper; use Box\Spout\Common\Helper\GlobalFunctionsHelper; +use Box\Spout\Writer\Common\Entity\Cell; use Box\Spout\Writer\Common\Entity\Options; +use Box\Spout\Writer\Common\Entity\Row; use Box\Spout\Writer\Common\Entity\Style\Style; use Box\Spout\Writer\Common\Manager\OptionsManagerInterface; use Box\Spout\Writer\Common\Manager\Style\StyleMerger; @@ -40,9 +42,6 @@ abstract class WriterAbstract implements WriterInterface /** @var StyleMerger Helps merge styles together */ protected $styleMerger; - /** @var Style Style to be applied to the next written row(s) */ - protected $rowStyle; - /** @var string Content-Type value for the header - to be defined by child class */ protected static $headerContentType; @@ -59,8 +58,6 @@ abstract class WriterAbstract implements WriterInterface $this->optionsManager = $optionsManager; $this->styleMerger = $styleMerger; $this->globalFunctionsHelper = $globalFunctionsHelper; - - $this->resetRowStyleToDefault(); } /** @@ -72,14 +69,12 @@ abstract class WriterAbstract implements WriterInterface abstract protected function openWriter(); /** - * Adds data to the currently openned writer. + * Adds a row to the currently opened writer. * - * @param array $dataRow Array containing data to be streamed. - * Example $dataRow = ['data1', 1234, null, '', 'data5']; - * @param Style $style Style to be applied to the written row + * @param Row $row The row containing cells and styles * @return void */ - abstract protected function addRowToWriter(array $dataRow, $style); + abstract protected function addRowToWriter(Row $row); /** * Closes the streamer, preventing any additional writing. @@ -99,7 +94,6 @@ abstract class WriterAbstract implements WriterInterface public function setDefaultRowStyle($defaultStyle) { $this->optionsManager->setOption(Options::DEFAULT_ROW_STYLE, $defaultStyle); - $this->resetRowStyleToDefault(); return $this; } @@ -199,27 +193,37 @@ abstract class WriterAbstract implements WriterInterface /** * Write given data to the output. New data will be appended to end of stream. * - * @param array $dataRow Array containing data to be streamed. - * If empty, no data is added (i.e. not even as a blank row) - * Example: $dataRow = ['data1', 1234, null, '', 'data5', false]; + * @param array|\Box\Spout\Writer\Common\Entity\Row $row The row to be appended to the stream + * @return WriterInterface + * @internal param array $row Array containing data to be streamed. + * Example $row= ['data1', 1234, null, '', 'data5']; + * @internal param \Box\Spout\Writer\Common\Entity\Row $row A Row object with cells and styles + * Example $row = (new Row())->addCell('data1'); + * + * @throws SpoutException If anything else goes wrong while writing data + * @throws WriterNotOpenedException If this function is called before opening the writer + * * @api - * @return WriterAbstract - * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer - * @throws \Box\Spout\Common\Exception\IOException If unable to write data - * @throws \Box\Spout\Common\Exception\SpoutException If anything else goes wrong while writing data */ - public function addRow(array $dataRow) + public function addRow($row) { + if (!is_array($row) && !$row instanceof Row) { + throw new InvalidArgumentException('addRow accepts an array with scalar values or a Row object'); + } + + if (is_array($row) && !empty($row)) { + $row = $this->createRowFromArray($row, null); + } + if ($this->isWriterOpened) { - // empty $dataRow should not add an empty line - if (!empty($dataRow)) { + if (!empty($row)) { try { - $this->addRowToWriter($dataRow, $this->rowStyle); + $this->applyDefaultRowStyle($row); + $this->addRowToWriter($row); } catch (SpoutException $e) { // if an exception occurs while writing data, // close the writer and remove all files created so far. $this->closeAndAttemptToCleanupAllFiles(); - // re-throw the exception to alert developers of the error throw $e; } @@ -227,35 +231,72 @@ abstract class WriterAbstract implements WriterInterface } else { throw new WriterNotOpenedException('The writer needs to be opened before adding row.'); } - return $this; } + /** + * @inheritdoc + * + * @api + */ + public function withRow(\Closure $callback) + { + return $this->addRow($callback(new Row())); + } + /** * Write given data to the output and apply the given style. * @see addRow * - * @api - * @param array $dataRow Array of array containing data to be streamed. + * @param array|\Box\Spout\Writer\Common\Entity\Row $row The row to be appended to the stream * @param Style $style Style to be applied to the row. - * @return WriterAbstract - * @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid - * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer - * @throws \Box\Spout\Common\Exception\IOException If unable to write data + * @return WriterInterface + * @internal param array $row Array containing data to be streamed. + * Example $row= ['data1', 1234, null, '', 'data5']; + * @internal param \Box\Spout\Writer\Common\Entity\Row $row A Row object with cells and styles + * Example $row = (new Row())->addCell('data1'); + * @api + * @throws InvalidArgumentException If the input param is not valid */ - public function addRowWithStyle(array $dataRow, $style) + public function addRowWithStyle($row, $style) { + if (!is_array($row) && !$row instanceof Row) { + throw new InvalidArgumentException('addRowWithStyle accepts an array with scalar values or a Row object'); + } + if (!$style instanceof Style) { throw new InvalidArgumentException('The "$style" argument must be a Style instance and cannot be NULL.'); } - $this->setRowStyle($style); - $this->addRow($dataRow); - $this->resetRowStyleToDefault(); + if (is_array($row)) { + $row = $this->createRowFromArray($row, $style); + } + $this->addRow($row); return $this; } + /** + * @param array $dataRows + * @param Style|null $style + * @return Row + */ + protected function createRowFromArray(array $dataRows, Style $style = null) + { + $row = (new Row())->setCells(array_map(function ($value) { + if ($value instanceof Cell) { + return $value; + } + return new Cell($value); + }, $dataRows)); + + if ($style !== null) { + $row->setStyle($style); + } + + return $row; + } + /** * Write given data to the output. New data will be appended to end of stream. * @@ -275,15 +316,13 @@ abstract class WriterAbstract implements WriterInterface { if (!empty($dataRows)) { $firstRow = reset($dataRows); - if (!is_array($firstRow)) { - throw new InvalidArgumentException('The input should be an array of arrays'); + if (!is_array($firstRow) && !$firstRow instanceof Row) { + throw new InvalidArgumentException('The input should be an array of arrays or row objects'); } - foreach ($dataRows as $dataRow) { $this->addRow($dataRow); } } - return $this; } @@ -305,35 +344,32 @@ abstract class WriterAbstract implements WriterInterface throw new InvalidArgumentException('The "$style" argument must be a Style instance and cannot be NULL.'); } - $this->setRowStyle($style); - $this->addRows($dataRows); - $this->resetRowStyleToDefault(); + $this->addRows(array_map(function ($row) use ($style) { + if (is_array($row)) { + return $this->createRowFromArray($row, $style); + } elseif ($row instanceof Row) { + return $row; + } else { + throw new InvalidArgumentException(); + } + }, $dataRows)); return $this; } /** - * Sets the style to be applied to the next written rows - * until it is changed or reset. - * - * @param Style $style - * @return void + * @param Row $row + * @return $this */ - private function setRowStyle($style) + private function applyDefaultRowStyle(Row $row) { - // Merge given style with the default one to inherit custom properties $defaultRowStyle = $this->optionsManager->getOption(Options::DEFAULT_ROW_STYLE); - $this->rowStyle = $this->styleMerger->merge($style, $defaultRowStyle); - } - - /** - * Resets the style to be applied to the next written rows. - * - * @return void - */ - private function resetRowStyleToDefault() - { - $this->rowStyle = $this->optionsManager->getOption(Options::DEFAULT_ROW_STYLE); + if (null === $defaultRowStyle) { + return $this; + } + $merged = $this->styleMerger->merge($row->getStyle(), $defaultRowStyle); + $row->setStyle($merged); + return $this; } /** diff --git a/src/Spout/Writer/WriterInterface.php b/src/Spout/Writer/WriterInterface.php index d9dee14..4b8c6c6 100644 --- a/src/Spout/Writer/WriterInterface.php +++ b/src/Spout/Writer/WriterInterface.php @@ -34,26 +34,38 @@ interface WriterInterface /** * Write given data to the output. New data will be appended to end of stream. * - * @param array $dataRow Array containing data to be streamed. - * Example $dataRow = ['data1', 1234, null, '', 'data5']; + * @param array|\Box\Spout\Writer\Common\Entity\Row $row The row to be appended to the stream * @return WriterInterface - * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yetthe writer - * @throws \Box\Spout\Common\Exception\IOException If unable to write data + * @internal param array $row Array containing data to be streamed. + * Example $row= ['data1', 1234, null, '', 'data5']; + * @internal param \Box\Spout\Writer\Common\Entity\Row $row A Row object with cells and styles + * Example $row = (new Row())->addCell('data1'); */ - public function addRow(array $dataRow); + public function addRow($row); /** * Write given data to the output and apply the given style. * @see addRow * - * @param array $dataRow Array of array containing data to be streamed. + * @param array|\Box\Spout\Writer\Common\Entity\Row $row The row to be appended to the stream * @param Style $style Style to be applied to the row. * @return WriterInterface - * @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid - * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer - * @throws \Box\Spout\Common\Exception\IOException If unable to write data + * @internal param array $row Array containing data to be streamed. + * Example $row= ['data1', 1234, null, '', 'data5']; + * @internal param \Box\Spout\Writer\Common\Entity\Row $row A Row object with cells and styles + * Example $row = (new Row())->addCell('data1'); */ - public function addRowWithStyle(array $dataRow, $style); + public function addRowWithStyle($row, $style); + + /** + * Write given data to the output with a closure funtion. New data will be appended to end of stream. + * + * @param \Closure $callback A callback returning a Row object. A new Row object is injected into the callback. + * @return WriterInterface + * @internal param \Closure $callback + * Example withRow(function(Row $row) { return $row->addCell('data1'); }) + */ + public function withRow(\Closure $callback); /** * Write given data to the output. New data will be appended to end of stream. diff --git a/src/Spout/Writer/WriterMultiSheetsAbstract.php b/src/Spout/Writer/WriterMultiSheetsAbstract.php index f76a150..67917db 100644 --- a/src/Spout/Writer/WriterMultiSheetsAbstract.php +++ b/src/Spout/Writer/WriterMultiSheetsAbstract.php @@ -6,6 +6,7 @@ use Box\Spout\Common\Helper\GlobalFunctionsHelper; use Box\Spout\Writer\Common\Entity\Sheet; use Box\Spout\Writer\Common\Manager\OptionsManagerInterface; use Box\Spout\Writer\Common\Entity\Options; +use Box\Spout\Writer\Common\Entity\Row; use Box\Spout\Writer\Common\Entity\Worksheet; use Box\Spout\Writer\Common\Manager\Style\StyleMerger; use Box\Spout\Writer\Exception\SheetNotFoundException; @@ -159,17 +160,15 @@ abstract class WriterMultiSheetsAbstract extends WriterAbstract * If shouldCreateNewSheetsAutomatically option is set to true, it will handle pagination * with the creation of new worksheets if one worksheet has reached its maximum capicity. * - * @param array $dataRow Array containing data to be written. - * Example $dataRow = ['data1', 1234, null, '', 'data5']; - * @param \Box\Spout\Writer\Common\Entity\Style\Style $style Style to be applied to the row. + * @param Row $row * @return void * @throws WriterNotOpenedException If the book is not created yet * @throws \Box\Spout\Common\Exception\IOException If unable to write data */ - protected function addRowToWriter(array $dataRow, $style) + protected function addRowToWriter(Row $row) { $this->throwIfWorkbookIsNotAvailable(); - $this->workbookManager->addRowToCurrentWorksheet($dataRow, $style); + $this->workbookManager->addRowToCurrentWorksheet($row); } /** diff --git a/src/Spout/Writer/XLSX/Manager/WorksheetManager.php b/src/Spout/Writer/XLSX/Manager/WorksheetManager.php index 6a03dad..73f908f 100644 --- a/src/Spout/Writer/XLSX/Manager/WorksheetManager.php +++ b/src/Spout/Writer/XLSX/Manager/WorksheetManager.php @@ -9,9 +9,9 @@ use Box\Spout\Writer\Common\Helper\CellHelper; use Box\Spout\Writer\Common\Manager\OptionsManagerInterface; use Box\Spout\Writer\Common\Entity\Options; use Box\Spout\Writer\Common\Entity\Cell; +use Box\Spout\Writer\Common\Entity\Row; use Box\Spout\Writer\Common\Entity\Worksheet; use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface; -use Box\Spout\Writer\Common\Entity\Style\Style; use Box\Spout\Writer\XLSX\Manager\Style\StyleManager; /** @@ -115,60 +115,47 @@ EOD; } /** - * Adds data to the given worksheet. + * Adds a row to the worksheet. * * @param Worksheet $worksheet The worksheet to add the row to - * @param array $dataRow Array containing data to be written. Cannot be empty. - * Example $dataRow = ['data1', 1234, null, '', 'data5']; - * @param Style $rowStyle Style to be applied to the row. NULL means use default style. + * @param Row $row The row to be added * @return void * @throws IOException If the data cannot be written * @throws InvalidArgumentException If a cell value's type is not supported */ - public function addRow(Worksheet $worksheet, $dataRow, $rowStyle) + public function addRow(Worksheet $worksheet, Row $row) { - if (!$this->isEmptyRow($dataRow)) { - $this->addNonEmptyRow($worksheet, $dataRow, $rowStyle); + if (!$row->isEmpty()) { + $this->addNonEmptyRow($worksheet, $row); } $worksheet->setLastWrittenRowIndex($worksheet->getLastWrittenRowIndex() + 1); } - /** - * Returns whether the given row is empty - * - * @param array $dataRow Array containing data to be written. Cannot be empty. - * Example $dataRow = ['data1', 1234, null, '', 'data5']; - * @return bool Whether the given row is empty - */ - private function isEmptyRow($dataRow) - { - $numCells = count($dataRow); - // using "reset()" instead of "$dataRow[0]" because $dataRow can be an associative array - return ($numCells === 1 && CellHelper::isEmpty(reset($dataRow))); - } - /** * Adds non empty row to the worksheet. * - * @param Worksheet $worksheet The worksheet to add the row to - * @param array $dataRow Array containing data to be written. Cannot be empty. - * Example $dataRow = ['data1', 1234, null, '', 'data5']; - * @param \Box\Spout\Writer\Common\Entity\Style\Style $style Style to be applied to the row. NULL means use default style. + * @param Row $row The row to be written * @return void + * * @throws \Box\Spout\Common\Exception\IOException If the data cannot be written * @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported */ - private function addNonEmptyRow(Worksheet $worksheet, $dataRow, $style) + private function addNonEmptyRow(Worksheet $worksheet, Row $row) { $cellNumber = 0; $rowIndex = $worksheet->getLastWrittenRowIndex() + 1; - $numCells = count($dataRow); + $numCells = count($row->getCells()); $rowXML = ''; - foreach($dataRow as $cellValue) { - $rowXML .= $this->getCellXML($rowIndex, $cellNumber, $cellValue, $style->getId()); + /** @var Cell $cell */ + foreach($row->getCells() as $cell) { + // Apply styles - the row style is merged at this point + $cell->applyStyle($row->getStyle()); + $this->styleManager->applyExtraStylesIfNeeded($cell); + $registeredStyle = $this->styleManager->registerStyle($cell->getStyle()); + $rowXML .= $this->getCellXML($rowIndex, $cellNumber, $cell, $registeredStyle->getId()); $cellNumber++; } @@ -185,24 +172,17 @@ EOD; * * @param int $rowIndex * @param int $cellNumber - * @param mixed $cellValue + * @param Cell $cell * @param int $styleId * @return string * @throws InvalidArgumentException If the given value cannot be processed */ - private function getCellXML($rowIndex, $cellNumber, $cellValue, $styleId) + private function getCellXML($rowIndex, $cellNumber, Cell $cell, $styleId) { $columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber); $cellXML = 'isString()) { $cellXML .= $this->getCellXMLFragmentForNonEmptyString($cell->getValue()); } else if ($cell->isBoolean()) { diff --git a/tests/Spout/Writer/Common/Entity/RowTest.php b/tests/Spout/Writer/Common/Entity/RowTest.php index 3f6304d..6018191 100644 --- a/tests/Spout/Writer/Common/Entity/RowTest.php +++ b/tests/Spout/Writer/Common/Entity/RowTest.php @@ -29,12 +29,6 @@ class RowTest extends TestCase $this->assertInstanceOf('Box\Spout\Writer\Common\Entity\Row', new Row([$this->cellMock()->getMock()])); } - public function testInvalidInstanceCellType() - { - $this->expectException('TypeError'); - $this->assertInstanceOf('Box\Spout\Writer\Common\Entity\Row', new Row(['string'])); - } - public function testSetCells() { $o = new Row(); diff --git a/tests/Spout/Writer/Common/Manager/Style/StyleManagerTest.php b/tests/Spout/Writer/Common/Manager/Style/StyleManagerTest.php index ea2b1f6..2978087 100644 --- a/tests/Spout/Writer/Common/Manager/Style/StyleManagerTest.php +++ b/tests/Spout/Writer/Common/Manager/Style/StyleManagerTest.php @@ -3,6 +3,7 @@ namespace Box\Spout\Writer\Common\Manager\Style; use Box\Spout\Writer\Common\Creator\Style\StyleBuilder; +use Box\Spout\Writer\Common\Entity\Cell; /** * Class StyleManagerTest @@ -31,7 +32,7 @@ class StyleManagerTest extends \PHPUnit_Framework_TestCase $this->assertFalse($style->shouldWrapText()); $styleManager = $this->getStyleManager(); - $updatedStyle = $styleManager->applyExtraStylesIfNeeded($style, [12, 'single line', "multi\nlines", null]); + $updatedStyle = $styleManager->applyExtraStylesIfNeeded(new Cell("multi\nlines", $style)); $this->assertTrue($updatedStyle->shouldWrapText()); } @@ -45,7 +46,7 @@ class StyleManagerTest extends \PHPUnit_Framework_TestCase $this->assertTrue($style->shouldWrapText()); $styleManager = $this->getStyleManager(); - $updatedStyle = $styleManager->applyExtraStylesIfNeeded($style, ["multi\nlines"]); + $updatedStyle = $styleManager->applyExtraStylesIfNeeded(new Cell("multi\nlines")); $this->assertTrue($updatedStyle->shouldWrapText()); }