diff --git a/src/Spout/Writer/XLSX/Internal/Worksheet.php b/src/Spout/Writer/XLSX/Internal/Worksheet.php index 1dfa0d4..b5a3dc7 100644 --- a/src/Spout/Writer/XLSX/Internal/Worksheet.php +++ b/src/Spout/Writer/XLSX/Internal/Worksheet.php @@ -4,6 +4,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\Helper\CellHelper; use Box\Spout\Writer\Common\Internal\WorksheetInterface; @@ -16,6 +17,14 @@ use Box\Spout\Writer\Common\Internal\WorksheetInterface; */ class Worksheet implements WorksheetInterface { + /** + * Maximum number of characters a cell can contain + * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-16c69c74-3d6a-4aaf-ba35-e6eb276e8eaa [Excel 2007] + * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3 [Excel 2010] + * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-ca36e2dc-1f09-4620-b726-67c00b05040f [Excel 2013/2016] + */ + const MAX_CHARACTERS_PER_CELL = 32767; + const SHEET_XML_FILE_HEADER = << @@ -39,6 +48,9 @@ EOD; /** @var \Box\Spout\Common\Escaper\XLSX Strings escaper */ protected $stringsEscaper; + /** @var \Box\Spout\Common\Helper\StringHelper String helper */ + protected $stringHelper; + /** @var Resource Pointer to the sheet data file (e.g. xl/worksheets/sheet1.xml) */ protected $sheetFilePointer; @@ -62,6 +74,7 @@ EOD; /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ $this->stringsEscaper = \Box\Spout\Common\Escaper\XLSX::getInstance(); + $this->stringHelper = new StringHelper(); $this->worksheetFilePath = $worksheetFilesFolder . '/' . strtolower($this->externalSheet->getName()) . '.xml'; $this->startSheet(); @@ -192,7 +205,7 @@ EOD; * @param mixed $cellValue * @param int $styleId * @return string - * @throws InvalidArgumentException + * @throws InvalidArgumentException If the given value cannot be processed */ private function getCellXML($rowIndex, $cellNumber, $cellValue, $styleId) { @@ -201,12 +214,7 @@ EOD; $cellXML .= ' s="' . $styleId . '"'; if (CellHelper::isNonEmptyString($cellValue)) { - if ($this->shouldUseInlineStrings) { - $cellXML .= ' t="inlineStr">' . $this->stringsEscaper->escape($cellValue) . ''; - } else { - $sharedStringId = $this->sharedStringsHelper->writeString($cellValue); - $cellXML .= ' t="s">' . $sharedStringId . ''; - } + $cellXML .= $this->getCellXMLFragmentForNonEmptyString($cellValue); } else if (CellHelper::isBoolean($cellValue)) { $cellXML .= ' t="b">' . intval($cellValue) . ''; } else if (CellHelper::isNumeric($cellValue)) { @@ -226,6 +234,29 @@ EOD; return $cellXML; } + /** + * Returns the XML fragment for a cell containing a non empty string + * + * @param string $cellValue The cell value + * @return string The XML fragment representing the cell + * @throws InvalidArgumentException If the string exceeds the maximum number of characters allowed per cell + */ + private function getCellXMLFragmentForNonEmptyString($cellValue) + { + if ($this->stringHelper->getStringLength($cellValue) > self::MAX_CHARACTERS_PER_CELL) { + throw new InvalidArgumentException('Trying to add a value that exceeds the maximum number of characters allowed in a cell (32,767)'); + } + + if ($this->shouldUseInlineStrings) { + $cellXMLFragment = ' t="inlineStr">' . $this->stringsEscaper->escape($cellValue) . ''; + } else { + $sharedStringId = $this->sharedStringsHelper->writeString($cellValue); + $cellXMLFragment = ' t="s">' . $sharedStringId . ''; + } + + return $cellXMLFragment; + } + /** * Closes the worksheet * diff --git a/tests/Spout/Writer/XLSX/WriterTest.php b/tests/Spout/Writer/XLSX/WriterTest.php index f0ca574..562db8d 100644 --- a/tests/Spout/Writer/XLSX/WriterTest.php +++ b/tests/Spout/Writer/XLSX/WriterTest.php @@ -6,6 +6,7 @@ use Box\Spout\Common\Exception\SpoutException; use Box\Spout\Common\Type; use Box\Spout\TestUsingResource; use Box\Spout\Writer\WriterFactory; +use Box\Spout\Writer\XLSX\Internal\Worksheet; /** * Class WriterTest @@ -98,6 +99,19 @@ class WriterTest extends \PHPUnit_Framework_TestCase public function testAddRowShouldThrowExceptionIfUnsupportedDataTypePassedIn() { $fileName = 'test_add_row_should_throw_exception_if_unsupported_data_type_passed_in.xlsx'; + $dataRows = [ + [str_repeat('a', Worksheet::MAX_CHARACTERS_PER_CELL + 1)], + ]; + + $this->writeToXLSXFile($dataRows, $fileName); + } + + /** + * @expectedException \Box\Spout\Common\Exception\InvalidArgumentException + */ + public function testAddRowShouldThrowExceptionIfWritingStringExceedingMaxNumberOfCharactersAllowedPerCell() + { + $fileName = 'test_add_row_should_throw_exception_if_string_exceeds_max_num_chars_allowed_per_cell.xlsx'; $dataRows = [ [new \stdClass()], ];