implement cell styling and row objects

This commit is contained in:
madflow 2017-05-30 11:12:13 +02:00
parent 33c28dbf6c
commit 97cdd0c627
16 changed files with 287 additions and 212 deletions

View File

@ -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');
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
/**

View File

@ -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 = '<table:table-row table:style-name="ro1">';
@ -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 = '<table:table-cell table:style-name="ce' . $styleIndex . '"';
@ -156,13 +165,6 @@ class WorksheetManager implements WorksheetManagerInterface
$data .= ' table:number-columns-repeated="' . $numTimesValueRepeated . '"';
}
/** @TODO Remove code duplication with XLSX writer: https://github.com/box/spout/pull/383#discussion_r113292746 */
if ($cellValue instanceof Cell) {
$cell = $cellValue;
} else {
$cell = new Cell($cellValue);
}
if ($cell->isString()) {
$data .= ' office:value-type="string" calcext:value-type="string">';

View File

@ -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;
}
/**

View File

@ -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.

View File

@ -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);
}
/**

View File

@ -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 = '<row r="' . $rowIndex . '" spans="1:' . $numCells . '">';
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 = '<c r="' . $columnIndex . $rowIndex . '"';
$cellXML .= ' s="' . $styleId . '"';
/** @TODO Remove code duplication with ODS writer: https://github.com/box/spout/pull/383#discussion_r113292746 */
if ($cellValue instanceof Cell) {
$cell = $cellValue;
} else {
$cell = new Cell($cellValue);
}
if ($cell->isString()) {
$cellXML .= $this->getCellXMLFragmentForNonEmptyString($cell->getValue());
} else if ($cell->isBoolean()) {

View File

@ -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();

View File

@ -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());
}