From d6155a42434bfde91a5502a1386b10b02e592815 Mon Sep 17 00:00:00 2001 From: Adrien Loison Date: Tue, 14 Apr 2015 19:52:56 -0700 Subject: [PATCH] Better guess the cell type based on its value --- src/Spout/Writer/Helper/XLSX/CellHelper.php | 34 +++++++++ src/Spout/Writer/Internal/XLSX/Worksheet.php | 24 +++--- .../Writer/Helper/XLSX/CellHelperTest.php | 58 +++++++++++++++ tests/Spout/Writer/XLSXTest.php | 73 +++++++++++++------ 4 files changed, 157 insertions(+), 32 deletions(-) diff --git a/src/Spout/Writer/Helper/XLSX/CellHelper.php b/src/Spout/Writer/Helper/XLSX/CellHelper.php index 855d0f7..375f953 100644 --- a/src/Spout/Writer/Helper/XLSX/CellHelper.php +++ b/src/Spout/Writer/Helper/XLSX/CellHelper.php @@ -34,4 +34,38 @@ class CellHelper return $cellIndex; } + + /** + * @param $value + * @return bool Whether the given value is a non empty string + */ + public static function isNonEmptyString($value) + { + return (gettype($value) === 'string' && $value !== ''); + } + + /** + * Returns whether the given value is numeric. + * A numeric value is from type "integer" or "double" ("float" is not returned by gettype). + * + * @param $value + * @return bool Whether the given value is numeric + */ + public static function isNumeric($value) + { + $valueType = gettype($value); + return ($valueType === 'integer' || $valueType === 'double'); + } + + /** + * Returns whether the given value is boolean. + * "true"/"false" and 0/1 are not booleans. + * + * @param $value + * @return bool Whether the given value is boolean + */ + public static function isBoolean($value) + { + return gettype($value) === 'boolean'; + } } diff --git a/src/Spout/Writer/Internal/XLSX/Worksheet.php b/src/Spout/Writer/Internal/XLSX/Worksheet.php index 4a13dea..ecea628 100644 --- a/src/Spout/Writer/Internal/XLSX/Worksheet.php +++ b/src/Spout/Writer/Internal/XLSX/Worksheet.php @@ -2,6 +2,7 @@ namespace Box\Spout\Writer\Internal\XLSX; +use Box\Spout\Common\Exception\InvalidArgumentException; use Box\Spout\Common\Exception\IOException; use Box\Spout\Writer\Helper\XLSX\CellHelper; @@ -119,6 +120,7 @@ EOD; * Example $dataRow = ['data1', 1234, null, '', 'data5']; * @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($dataRow) { @@ -132,19 +134,19 @@ EOD; $columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber); $data .= ' shouldUseInlineStrings) { + $data .= ' t="inlineStr">' . $this->stringsEscaper->escape($cellValue) . '' . PHP_EOL; + } else { + $sharedStringId = $this->sharedStringsHelper->writeString($cellValue); + $data .= ' t="s">' . $sharedStringId . '' . PHP_EOL; + } + } else if (CellHelper::isNumeric($cellValue) || CellHelper::isBoolean($cellValue)) { + $data .= '>' . $cellValue . '' . PHP_EOL; + } else if (empty($cellValue)) { $data .= '/>' . PHP_EOL; } else { - if (is_numeric($cellValue)) { - $data .= '>' . $cellValue . '' . PHP_EOL; - } else { - if ($this->shouldUseInlineStrings) { - $data .= ' t="inlineStr">' . $this->stringsEscaper->escape($cellValue) . '' . PHP_EOL; - } else { - $sharedStringId = $this->sharedStringsHelper->writeString($cellValue); - $data .= ' t="s">' . $sharedStringId . '' . PHP_EOL; - } - } + throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cellValue)); } $cellNumber++; diff --git a/tests/Spout/Writer/Helper/XLSX/CellHelperTest.php b/tests/Spout/Writer/Helper/XLSX/CellHelperTest.php index b5edf4b..a5045aa 100644 --- a/tests/Spout/Writer/Helper/XLSX/CellHelperTest.php +++ b/tests/Spout/Writer/Helper/XLSX/CellHelperTest.php @@ -34,4 +34,62 @@ class CellHelperTest extends \PHPUnit_Framework_TestCase { $this->assertEquals($expectedCellIndex, CellHelper::getCellIndexFromColumnIndex($columnIndex)); } + + /** + * @return array + */ + public function testIsNonEmptyString() + { + $this->assertTrue(CellHelper::isNonEmptyString("string")); + + $this->assertFalse(CellHelper::isNonEmptyString("")); + $this->assertFalse(CellHelper::isNonEmptyString(0)); + $this->assertFalse(CellHelper::isNonEmptyString(1)); + $this->assertFalse(CellHelper::isNonEmptyString(true)); + $this->assertFalse(CellHelper::isNonEmptyString(false)); + $this->assertFalse(CellHelper::isNonEmptyString(["string"])); + $this->assertFalse(CellHelper::isNonEmptyString(new \stdClass())); + $this->assertFalse(CellHelper::isNonEmptyString(null)); + } + + /** + * @return array + */ + public function testIsNumeric() + { + $this->assertTrue(CellHelper::isNumeric(0)); + $this->assertTrue(CellHelper::isNumeric(10)); + $this->assertTrue(CellHelper::isNumeric(10.1)); + $this->assertTrue(CellHelper::isNumeric(10.10000000000000000000001)); + $this->assertTrue(CellHelper::isNumeric(0x539)); + $this->assertTrue(CellHelper::isNumeric(02471)); + $this->assertTrue(CellHelper::isNumeric(0b10100111001)); + $this->assertTrue(CellHelper::isNumeric(1337e0)); + + $this->assertFalse(CellHelper::isNumeric("0")); + $this->assertFalse(CellHelper::isNumeric("42")); + $this->assertFalse(CellHelper::isNumeric(true)); + $this->assertFalse(CellHelper::isNumeric([2])); + $this->assertFalse(CellHelper::isNumeric(new \stdClass())); + $this->assertFalse(CellHelper::isNumeric(null)); + } + + /** + * @return array + */ + public function testIsBoolean() + { + $this->assertTrue(CellHelper::isBoolean(true)); + $this->assertTrue(CellHelper::isBoolean(false)); + + $this->assertFalse(CellHelper::isBoolean(0)); + $this->assertFalse(CellHelper::isBoolean(1)); + $this->assertFalse(CellHelper::isBoolean("0")); + $this->assertFalse(CellHelper::isBoolean("1")); + $this->assertFalse(CellHelper::isBoolean("true")); + $this->assertFalse(CellHelper::isBoolean("false")); + $this->assertFalse(CellHelper::isBoolean([true])); + $this->assertFalse(CellHelper::isBoolean(new \stdClass())); + $this->assertFalse(CellHelper::isBoolean(null)); + } } diff --git a/tests/Spout/Writer/XLSXTest.php b/tests/Spout/Writer/XLSXTest.php index b30d7de..e99ab80 100644 --- a/tests/Spout/Writer/XLSXTest.php +++ b/tests/Spout/Writer/XLSXTest.php @@ -49,6 +49,19 @@ class XLSXTest extends \PHPUnit_Framework_TestCase $writer->close(); } + /** + * @expectedException \Box\Spout\Common\Exception\InvalidArgumentException + */ + public function testAddRowShouldThrowExceptionIfUnsupportedDataTypePassedIn() + { + $fileName = 'test_add_row_should_throw_exception_if_unsupported_data_type_passed_in.xlsx'; + $dataRows = [ + [new \stdClass()], + ]; + + $this->writeToXLSXFile($dataRows, $fileName); + } + /** * @return void */ @@ -96,7 +109,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase */ public function testAddRowShouldWriteGivenDataToSheetUsingInlineStrings() { - $fileName = 'test_add_row_should_write_given_data_to_sheet.xlsx'; + $fileName = 'test_add_row_should_write_given_data_to_sheet_using_inline_strings.xlsx'; $dataRows = [ ['xlsx--11', 'xlsx--12'], ['xlsx--21', 'xlsx--22', 'xlsx--23'], @@ -106,7 +119,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase foreach ($dataRows as $dataRow) { foreach ($dataRow as $cellValue) { - $this->assertInlineStringWasWrittenToSheet($fileName, 1, $cellValue); + $this->assertInlineDataWasWrittenToSheet($fileName, 1, $cellValue); } } } @@ -116,7 +129,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase */ public function testAddRowShouldWriteGivenDataToTwoSheetUsingInlineStrings() { - $fileName = 'test_add_row_should_write_given_data_to_sheet.xlsx'; + $fileName = 'test_add_row_should_write_given_data_to_two_sheets_using_inline_strings.xlsx'; $dataRows = [ ['xlsx--11', 'xlsx--12'], ['xlsx--21', 'xlsx--22', 'xlsx--23'], @@ -128,7 +141,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase for ($i = 1; $i <= $numSheets; $i++) { foreach ($dataRows as $dataRow) { foreach ($dataRow as $cellValue) { - $this->assertInlineStringWasWrittenToSheet($fileName, $numSheets, $cellValue); + $this->assertInlineDataWasWrittenToSheet($fileName, $numSheets, $cellValue); } } } @@ -139,7 +152,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase */ public function testAddRowShouldWriteGivenDataToSheetUsingSharedStrings() { - $fileName = 'test_add_row_should_write_given_data_to_sheet.xlsx'; + $fileName = 'test_add_row_should_write_given_data_to_sheet_using_shared_strings.xlsx'; $dataRows = [ ['xlsx--11', 'xlsx--12'], ['xlsx--21', 'xlsx--22', 'xlsx--23'], @@ -159,7 +172,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase */ public function testAddRowShouldWriteGivenDataToTwoSheetUsingSharedStrings() { - $fileName = 'test_add_row_should_write_given_data_to_two_sheet_using_shared_strings.xlsx'; + $fileName = 'test_add_row_should_write_given_data_to_two_sheets_using_shared_strings.xlsx'; $dataRows = [ ['xlsx--11', 'xlsx--12'], ['xlsx--21', 'xlsx--22', 'xlsx--23'], @@ -177,6 +190,24 @@ class XLSXTest extends \PHPUnit_Framework_TestCase } } + /** + * @return void + */ + public function testAddRowShouldSupportMultipleTypesOfData() + { + $fileName = 'test_add_row_should_support_multiple_types_of_data.xlsx'; + $dataRows = [ + ['xlsx--11', true, '', 0, 10.2, null], + ]; + + $this->writeToXLSXFile($dataRows, $fileName, $shouldUseInlineStrings = false); + + $this->assertSharedStringWasWritten($fileName, 'xlsx--11'); + $this->assertInlineDataWasWrittenToSheet($fileName, 1, 1); // true is converted to 1 + $this->assertInlineDataWasWrittenToSheet($fileName, 1, 0); + $this->assertInlineDataWasWrittenToSheet($fileName, 1, 10.2); + } + /** * @return void */ @@ -219,17 +250,17 @@ class XLSXTest extends \PHPUnit_Framework_TestCase foreach ($dataRowsSheet1 as $dataRow) { foreach ($dataRow as $cellValue) { - $this->assertInlineStringWasWrittenToSheet($fileName, 1, $cellValue, 'Data should have been written in Sheet 1'); + $this->assertInlineDataWasWrittenToSheet($fileName, 1, $cellValue, 'Data should have been written in Sheet 1'); } } foreach ($dataRowsSheet2 as $dataRow) { foreach ($dataRow as $cellValue) { - $this->assertInlineStringWasWrittenToSheet($fileName, 2, $cellValue, 'Data should have been written in Sheet 2'); + $this->assertInlineDataWasWrittenToSheet($fileName, 2, $cellValue, 'Data should have been written in Sheet 2'); } } foreach ($dataRowsSheet1Again as $dataRow) { foreach ($dataRow as $cellValue) { - $this->assertInlineStringWasWrittenToSheet($fileName, 1, $cellValue, 'Data should have been written in Sheet 1'); + $this->assertInlineDataWasWrittenToSheet($fileName, 1, $cellValue, 'Data should have been written in Sheet 1'); } } } @@ -252,8 +283,8 @@ class XLSXTest extends \PHPUnit_Framework_TestCase $writer = $this->writeToXLSXFile($dataRows, $fileName, true, $shouldCreateSheetsAutomatically = true); $this->assertEquals(2, count($writer->getSheets()), '2 sheets should have been created.'); - $this->assertInlineStringWasNotWrittenToSheet($fileName, 1, 'xlsx--sheet2--11'); - $this->assertInlineStringWasWrittenToSheet($fileName, 2, 'xlsx--sheet2--11'); + $this->assertInlineDataWasNotWrittenToSheet($fileName, 1, 'xlsx--sheet2--11'); + $this->assertInlineDataWasWrittenToSheet($fileName, 2, 'xlsx--sheet2--11'); \ReflectionHelper::reset(); } @@ -276,7 +307,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase $writer = $this->writeToXLSXFile($dataRows, $fileName, true, $shouldCreateSheetsAutomatically = false); $this->assertEquals(1, count($writer->getSheets()), 'Only 1 sheet should have been created.'); - $this->assertInlineStringWasNotWrittenToSheet($fileName, 1, 'xlsx--sheet1--31'); + $this->assertInlineDataWasNotWrittenToSheet($fileName, 1, 'xlsx--sheet1--31'); \ReflectionHelper::reset(); } @@ -293,8 +324,8 @@ class XLSXTest extends \PHPUnit_Framework_TestCase $this->writeToXLSXFile($dataRows, $fileName); - $this->assertInlineStringWasWrittenToSheet($fileName, 1, 'I'm in "great" mood', 'Quotes should be escaped'); - $this->assertInlineStringWasWrittenToSheet($fileName, 1, 'This <must> be escaped & tested', '<, > and & should be escaped'); + $this->assertInlineDataWasWrittenToSheet($fileName, 1, 'I'm in "great" mood', 'Quotes should be escaped'); + $this->assertInlineDataWasWrittenToSheet($fileName, 1, 'This <must> be escaped & tested', '<, > and & should be escaped'); } /** @@ -309,7 +340,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase $this->writeToXLSXFile($dataRows, $fileName); - $this->assertInlineStringWasWrittenToSheet($fileName, 1, 'control's _x0015_ "character"'); + $this->assertInlineDataWasWrittenToSheet($fileName, 1, 'control's _x0015_ "character"'); } @@ -371,33 +402,33 @@ class XLSXTest extends \PHPUnit_Framework_TestCase /** * @param string $fileName * @param int $sheetNumber - * @param string $inlineString + * @param mixed $inlineData * @param string $message * @return void */ - private function assertInlineStringWasWrittenToSheet($fileName, $sheetNumber, $inlineString, $message = '') + private function assertInlineDataWasWrittenToSheet($fileName, $sheetNumber, $inlineData, $message = '') { $resourcePath = $this->getGeneratedResourcePath($fileName); $pathToSheetFile = $resourcePath . '#xl/worksheets/sheet' . $sheetNumber . '.xml'; $xmlContents = file_get_contents('zip://' . $pathToSheetFile); - $this->assertContains($inlineString, $xmlContents, $message); + $this->assertContains((string)$inlineData, $xmlContents, $message); } /** * @param string $fileName * @param int $sheetNumber - * @param string $inlineString + * @param mixed $inlineData * @param string $message * @return void */ - private function assertInlineStringWasNotWrittenToSheet($fileName, $sheetNumber, $inlineString, $message = '') + private function assertInlineDataWasNotWrittenToSheet($fileName, $sheetNumber, $inlineData, $message = '') { $resourcePath = $this->getGeneratedResourcePath($fileName); $pathToSheetFile = $resourcePath . '#xl/worksheets/sheet' . $sheetNumber . '.xml'; $xmlContents = file_get_contents('zip://' . $pathToSheetFile); - $this->assertNotContains($inlineString, $xmlContents, $message); + $this->assertNotContains((string)$inlineData, $xmlContents, $message); } /**