From 4acd9ad08763bda23eb7debbae184517eb1596ca Mon Sep 17 00:00:00 2001 From: madflow Date: Mon, 1 May 2017 12:09:24 +0200 Subject: [PATCH] Cell value objects (#383) * first stab at cell objects, #182 * removed comment parameter, streamlined cell detection, more tests #182 * shorter constant names, missing isFormula() #182 * first batch of changes #182 * documentation #182 --- src/Spout/Writer/Common/Cell.php | 166 +++++++++++++++++++ src/Spout/Writer/ODS/Internal/Worksheet.php | 28 ++-- src/Spout/Writer/XLSX/Internal/Worksheet.php | 24 ++- tests/Spout/Writer/CSV/WriterTest.php | 14 ++ tests/Spout/Writer/ODS/WriterTest.php | 43 +++++ tests/Spout/Writer/XLSX/WriterTest.php | 55 ++++++ 6 files changed, 312 insertions(+), 18 deletions(-) create mode 100644 src/Spout/Writer/Common/Cell.php diff --git a/src/Spout/Writer/Common/Cell.php b/src/Spout/Writer/Common/Cell.php new file mode 100644 index 0000000..bbb0d91 --- /dev/null +++ b/src/Spout/Writer/Common/Cell.php @@ -0,0 +1,166 @@ +setValue($value); + } + + /** + * @param $value mixed + */ + public function setValue($value) + { + $this->value = $value; + $this->type = $this->detectType($value); + } + + /** + * @return mixed|null + */ + public function getValue() + { + return $this->value; + } + + /** + * @return int|null + */ + public function getType() + { + return $this->type; + } + + /** + * Get the current value type + * @return int + */ + protected function detectType($value) + { + if (CellHelper::isBoolean($value)) { + return self::TYPE_BOOLEAN; + } elseif (CellHelper::isEmpty($value)) { + return self::TYPE_EMPTY; + } elseif (CellHelper::isNumeric($this->getValue())) { + return self::TYPE_NUMERIC; + } elseif (CellHelper::isNonEmptyString($value)) { + return self::TYPE_STRING; + } else { + return self::TYPE_ERROR; + } + } + + /** + * @return bool + */ + public function isBoolean() + { + return $this->type === self::TYPE_BOOLEAN; + } + + /** + * @return bool + */ + public function isEmpty() + { + return $this->type === self::TYPE_EMPTY; + } + + /** + * Not used at the moment + * @return bool + */ + public function isFormula() + { + return $this->type === self::TYPE_FORMULA; + } + + /** + * @return bool + */ + public function isNumeric() + { + return $this->type === self::TYPE_NUMERIC; + } + + /** + * @return bool + */ + public function isString() + { + return $this->type === self::TYPE_STRING; + } + + /** + * @return bool + */ + public function isError() + { + return $this->type === self::TYPE_ERROR; + } + + /** + * @return string + */ + public function __toString() + { + return (string)$this->value; + } +} diff --git a/src/Spout/Writer/ODS/Internal/Worksheet.php b/src/Spout/Writer/ODS/Internal/Worksheet.php index 0920b6d..9c44798 100644 --- a/src/Spout/Writer/ODS/Internal/Worksheet.php +++ b/src/Spout/Writer/ODS/Internal/Worksheet.php @@ -5,6 +5,7 @@ namespace Box\Spout\Writer\ODS\Internal; use Box\Spout\Common\Exception\InvalidArgumentException; use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Helper\StringHelper; +use Box\Spout\Writer\Common\Cell; use Box\Spout\Writer\Common\Helper\CellHelper; use Box\Spout\Writer\Common\Internal\WorksheetInterface; @@ -191,27 +192,34 @@ class Worksheet implements WorksheetInterface $data .= ' table:number-columns-repeated="' . $numTimesValueRepeated . '"'; } - if (CellHelper::isNonEmptyString($cellValue)) { + /** @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">'; - $cellValueLines = explode("\n", $cellValue); + $cellValueLines = explode("\n", $cell->getValue()); foreach ($cellValueLines as $cellValueLine) { $data .= '' . $this->stringsEscaper->escape($cellValueLine) . ''; } $data .= ''; - } else if (CellHelper::isBoolean($cellValue)) { - $data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $cellValue . '">'; - $data .= '' . $cellValue . ''; + } else if ($cell->isBoolean()) { + $data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $cell->getValue() . '">'; + $data .= '' . $cell->getValue() . ''; $data .= ''; - } else if (CellHelper::isNumeric($cellValue)) { - $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cellValue . '">'; - $data .= '' . $cellValue . ''; + } else if ($cell->isNumeric()) { + $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">'; + $data .= '' . $cell->getValue() . ''; $data .= ''; - } else if (empty($cellValue)) { + } else if ($cell->isEmpty()) { $data .= '/>'; } else { - throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cellValue)); + throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cell->getValue())); } return $data; diff --git a/src/Spout/Writer/XLSX/Internal/Worksheet.php b/src/Spout/Writer/XLSX/Internal/Worksheet.php index b5a3dc7..7f71829 100644 --- a/src/Spout/Writer/XLSX/Internal/Worksheet.php +++ b/src/Spout/Writer/XLSX/Internal/Worksheet.php @@ -5,6 +5,7 @@ namespace Box\Spout\Writer\XLSX\Internal; use Box\Spout\Common\Exception\InvalidArgumentException; use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Helper\StringHelper; +use Box\Spout\Writer\Common\Cell; use Box\Spout\Writer\Common\Helper\CellHelper; use Box\Spout\Writer\Common\Internal\WorksheetInterface; @@ -213,13 +214,20 @@ EOD; $cellXML = 'getCellXMLFragmentForNonEmptyString($cellValue); - } else if (CellHelper::isBoolean($cellValue)) { - $cellXML .= ' t="b">' . intval($cellValue) . ''; - } else if (CellHelper::isNumeric($cellValue)) { - $cellXML .= '>' . $cellValue . ''; - } else if (empty($cellValue)) { + /** @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()) { + $cellXML .= ' t="b">' . intval($cell->getValue()) . ''; + } else if ($cell->isNumeric()) { + $cellXML .= '>' . $cell->getValue() . ''; + } else if ($cell->isEmpty()) { if ($this->styleHelper->shouldApplyStyleOnEmptyCell($styleId)) { $cellXML .= '/>'; } else { @@ -228,7 +236,7 @@ EOD; $cellXML = ''; } } else { - throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cellValue)); + throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cell->getValue())); } return $cellXML; diff --git a/tests/Spout/Writer/CSV/WriterTest.php b/tests/Spout/Writer/CSV/WriterTest.php index 9558129..c2f1d2c 100644 --- a/tests/Spout/Writer/CSV/WriterTest.php +++ b/tests/Spout/Writer/CSV/WriterTest.php @@ -5,6 +5,7 @@ namespace Box\Spout\Writer\CSV; use Box\Spout\TestUsingResource; use Box\Spout\Common\Type; use Box\Spout\Common\Helper\EncodingHelper; +use Box\Spout\Writer\Common\Cell; use Box\Spout\Writer\WriterFactory; /** @@ -177,6 +178,19 @@ class WriterTest extends \PHPUnit_Framework_TestCase $this->assertEquals('#This is, a comma#,csv--12,csv--13', $writtenContent, 'The fields should be enclosed with #'); } + /** + * @return void + */ + public function testWriteShouldAcceptCellObjects() + { + $allRows = [ + [new Cell('String Value'), new Cell(1)] + ]; + $writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_with_cell_objects.csv'); + $writtenContent = $this->trimWrittenContent($writtenContent); + $this->assertEquals('"String Value",1', $writtenContent); + } + /** * @param array $allRows * @param string $fileName diff --git a/tests/Spout/Writer/ODS/WriterTest.php b/tests/Spout/Writer/ODS/WriterTest.php index 836e679..df60af9 100644 --- a/tests/Spout/Writer/ODS/WriterTest.php +++ b/tests/Spout/Writer/ODS/WriterTest.php @@ -6,6 +6,7 @@ use Box\Spout\Common\Exception\SpoutException; use Box\Spout\Common\Type; use Box\Spout\Reader\Wrapper\XMLReader; use Box\Spout\TestUsingResource; +use Box\Spout\Writer\Common\Cell; use Box\Spout\Writer\Common\Helper\ZipHelper; use Box\Spout\Writer\WriterFactory; @@ -463,6 +464,47 @@ class WriterTest extends \PHPUnit_Framework_TestCase $this->assertEquals('application/vnd.oasis.opendocument.spreadsheet', $finfo->file($resourcePath)); } + /** + * @return void + */ + public function testWriteShouldAcceptCellObjects() + { + $fileName = 'test_writer_should_accept_cell_objects.ods'; + $dataRows = [ + [new Cell('ods--11'), new Cell('ods--12')], + [new Cell('ods--21'), new Cell('ods--22'), new Cell('ods--23')], + ]; + + $this->writeToODSFile($dataRows, $fileName); + + foreach ($dataRows as $dataRow) { + /** @var Cell $cell */ + foreach ($dataRow as $cell) { + $this->assertValueWasWritten($fileName, $cell->getValue()); + } + } + } + + /** + * @return void + */ + public function testWriteShouldAcceptCellObjectsWithDifferentValueTypes() + { + $fileName = 'test_writer_should_accept_cell_objects_with_types.ods'; + $dataRows = [ + [new Cell('i am a string'), new Cell(51465), new Cell(true), new Cell(51465.5)], + ]; + + $this->writeToODSFile($dataRows, $fileName); + + foreach ($dataRows as $dataRow) { + /** @var Cell $cell */ + foreach ($dataRow as $cell) { + $this->assertValueWasWritten($fileName, (string)$cell->getValue(), '', true); + } + } + } + /** * @param array $allRows * @param string $fileName @@ -527,6 +569,7 @@ class WriterTest extends \PHPUnit_Framework_TestCase $xmlContents = file_get_contents('zip://' . $pathToContentFile); $this->assertContains($value, $xmlContents, $message); + } /** diff --git a/tests/Spout/Writer/XLSX/WriterTest.php b/tests/Spout/Writer/XLSX/WriterTest.php index c7f4f96..fe89deb 100644 --- a/tests/Spout/Writer/XLSX/WriterTest.php +++ b/tests/Spout/Writer/XLSX/WriterTest.php @@ -5,6 +5,7 @@ namespace Box\Spout\Writer\XLSX; use Box\Spout\Common\Exception\SpoutException; use Box\Spout\Common\Type; use Box\Spout\TestUsingResource; +use Box\Spout\Writer\Common\Cell; use Box\Spout\Writer\WriterFactory; use Box\Spout\Writer\XLSX\Internal\Worksheet; @@ -507,6 +508,60 @@ class WriterTest extends \PHPUnit_Framework_TestCase $this->assertEquals('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', $finfo->file($resourcePath)); } + /** + * @return void + */ + public function testWriterShouldAcceptCellObjects() + { + $fileName = 'test_writer_should_accept_cell_objects.xlsx'; + $dataRows = [ + [new Cell('xlsx--11'), new Cell('xlsx--12')], + [new Cell('xlsx--21'), new Cell('xlsx--22'), new Cell('xlsx--23')], + ]; + + $this->writeToXLSXFile($dataRows, $fileName, $shouldUseInlineStrings = false); + + foreach ($dataRows as $dataRow) { + /** @var Cell $cell */ + foreach ($dataRow as $cell) { + $this->assertSharedStringWasWritten($fileName, $cell->getValue()); + } + } + } + + /** + * @return void + */ + public function testWriteShouldAcceptCellObjectsWithDifferentValueTypes() + { + $fileName = 'test_writer_should_accept_cell_objects_with_types.xlsx'; + + $dataRowsShared = [ + [new Cell('i am a string')], + ]; + $dataRowsInline = [ + [new Cell(51465), new Cell(true), new Cell(51465.5)] + ]; + + $dataRows = array_merge($dataRowsShared, $dataRowsInline); + + $this->writeToXLSXFile($dataRows, $fileName, $shouldUseInlineStrings = false); + + foreach ($dataRowsShared as $dataRow) { + /** @var Cell $cell */ + foreach ($dataRow as $cell) { + $this->assertSharedStringWasWritten($fileName, (string)$cell->getValue()); + } + } + + foreach ($dataRowsInline as $dataRow) { + /** @var Cell $cell */ + foreach ($dataRow as $cell) { + $this->assertInlineDataWasWrittenToSheet($fileName, 1, $cell->getValue()); + } + } + } + /** * @param array $allRows * @param string $fileName