diff --git a/src/Spout/Writer/Common/Internal/AbstractWorkbook.php b/src/Spout/Writer/Common/Internal/AbstractWorkbook.php index 2d65dad..c8f9f9f 100644 --- a/src/Spout/Writer/Common/Internal/AbstractWorkbook.php +++ b/src/Spout/Writer/Common/Internal/AbstractWorkbook.php @@ -135,7 +135,7 @@ abstract class AbstractWorkbook implements WorkbookInterface * 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. + * @param array $dataRow Array containing data to be written. Cannot be empty. * Example $dataRow = ['data1', 1234, null, '', 'data5']; * @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row. * @return void diff --git a/src/Spout/Writer/ODS/Internal/Worksheet.php b/src/Spout/Writer/ODS/Internal/Worksheet.php index fe55382..9390030 100644 --- a/src/Spout/Writer/ODS/Internal/Worksheet.php +++ b/src/Spout/Writer/ODS/Internal/Worksheet.php @@ -7,7 +7,6 @@ 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; -use Box\Spout\Writer\Common\Sheet; /** * Class Worksheet @@ -126,7 +125,7 @@ class Worksheet implements WorksheetInterface /** * Adds data to the worksheet. * - * @param array $dataRow Array containing data to be written. + * @param array $dataRow Array containing data to be written. Cannot be empty. * Example $dataRow = ['data1', 1234, null, '', 'data5']; * @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row. NULL means use default style. * @return void @@ -135,36 +134,26 @@ class Worksheet implements WorksheetInterface */ public function addRow($dataRow, $style) { - $this->maxNumColumns = max($this->maxNumColumns, count($dataRow)); $styleIndex = ($style->getId() + 1); // 1-based + $cellsCount = count($dataRow); + $this->maxNumColumns = max($this->maxNumColumns, $cellsCount); $data = ' ' . PHP_EOL; - foreach($dataRow as $cellValue) { - $data .= ' stringsEscaper->escape($cellValueLine) . '' . PHP_EOL; - } + if (!array_key_exists($nextCellIndex, $dataRow) || $currentCellValue !== $dataRow[$nextCellIndex]) { + $numTimesValueRepeated = ($nextCellIndex - $currentCellIndex); + $data .= $this->getCellContent($currentCellValue, $styleIndex, $numTimesValueRepeated); - $data .= ' ' . PHP_EOL; - } else if (CellHelper::isBoolean($cellValue)) { - $data .= ' office:value-type="boolean" calcext:value-type="boolean" office:value="' . $cellValue . '">' . PHP_EOL; - $data .= ' ' . $cellValue . '' . PHP_EOL; - $data .= ' ' . PHP_EOL; - } else if (CellHelper::isNumeric($cellValue)) { - $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cellValue . '">' . PHP_EOL; - $data .= ' ' . $cellValue . '' . PHP_EOL; - $data .= ' ' . PHP_EOL; - } else if (empty($cellValue)) { - $data .= '/>' . PHP_EOL; - } else { - throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cellValue)); + $currentCellIndex = $nextCellIndex; } + + $nextCellIndex++; } $data .= ' ' . PHP_EOL; @@ -178,6 +167,49 @@ class Worksheet implements WorksheetInterface $this->lastWrittenRowIndex++; } + /** + * Returns the cell XML content, given its value. + * + * @param mixed $cellValue The value 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 + */ + protected function getCellContent($cellValue, $styleIndex, $numTimesValueRepeated) + { + $data = ' stringsEscaper->escape($cellValueLine) . '' . PHP_EOL; + } + + $data .= ' ' . PHP_EOL; + } else if (CellHelper::isBoolean($cellValue)) { + $data .= ' office:value-type="boolean" calcext:value-type="boolean" office:value="' . $cellValue . '">' . PHP_EOL; + $data .= ' ' . $cellValue . '' . PHP_EOL; + $data .= ' ' . PHP_EOL; + } else if (CellHelper::isNumeric($cellValue)) { + $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cellValue . '">' . PHP_EOL; + $data .= ' ' . $cellValue . '' . PHP_EOL; + $data .= ' ' . PHP_EOL; + } else if (empty($cellValue)) { + $data .= '/>' . PHP_EOL; + } else { + throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cellValue)); + } + + return $data; + } + /** * Closes the worksheet * diff --git a/src/Spout/Writer/XLSX/Internal/Worksheet.php b/src/Spout/Writer/XLSX/Internal/Worksheet.php index 01b3912..d9346c1 100644 --- a/src/Spout/Writer/XLSX/Internal/Worksheet.php +++ b/src/Spout/Writer/XLSX/Internal/Worksheet.php @@ -118,7 +118,7 @@ EOD; /** * Adds data to the worksheet. * - * @param array $dataRow Array containing data to be written. + * @param array $dataRow Array containing data to be written. Cannot be empty. * Example $dataRow = ['data1', 1234, null, '', 'data5']; * @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row. NULL means use default style. * @return void diff --git a/tests/Spout/Writer/ODS/WriterTest.php b/tests/Spout/Writer/ODS/WriterTest.php index 15d8d09..731e873 100644 --- a/tests/Spout/Writer/ODS/WriterTest.php +++ b/tests/Spout/Writer/ODS/WriterTest.php @@ -195,6 +195,48 @@ class WriterTest extends \PHPUnit_Framework_TestCase $this->assertValueWasWrittenToSheet($fileName, 1, 10.2); } + /** + * @return array + */ + public function dataProviderForTestAddRowShouldUseNumberColumnsRepeatedForRepeatedValues() + { + return [ + [['ods--11', 'ods--11', 'ods--11'], 1, 3], + [['', ''], 1, 2], + [[true, true, true, true], 1, 4], + [[1.1, 1.1], 1, 2], + [['foo', 'bar'], 2, 0], + ]; + } + /** + * @dataProvider dataProviderForTestAddRowShouldUseNumberColumnsRepeatedForRepeatedValues + * + * @param array $dataRow + * @param int $expectedNumTableCells + * @param int $expectedNumColumnsRepeated + * @return void + */ + public function testAddRowShouldUseNumberColumnsRepeatedForRepeatedValues($dataRow, $expectedNumTableCells, $expectedNumColumnsRepeated) + { + $fileName = 'test_add_row_should_use_number_columns_repeated.ods'; + $this->writeToODSFile([$dataRow], $fileName); + + $sheetXmlNode = $this->getSheetXmlNode($fileName, 1); + $tableCellNodes = $sheetXmlNode->getElementsByTagName('table-cell'); + + $this->assertEquals($expectedNumTableCells, $tableCellNodes->length); + + if ($expectedNumTableCells === 1) { + $tableCellNode = $tableCellNodes->item(0); + $numColumnsRepeated = intval($tableCellNode->getAttribute('table:number-columns-repeated')); + $this->assertEquals($expectedNumColumnsRepeated, $numColumnsRepeated); + } else { + foreach ($tableCellNodes as $tableCellNode) { + $this->assertFalse($tableCellNode->hasAttribute('table:number-columns-repeated')); + } + } + } + /** * @return void */ @@ -423,12 +465,34 @@ class WriterTest extends \PHPUnit_Framework_TestCase $this->assertNotContains($valueAsXmlString, $sheetXmlAsString, $message); } + /** + * @param string $fileName + * @param int $sheetIndex + * @return \DOMNode + */ + private function getSheetXmlNode($fileName, $sheetIndex) + { + $xmlReader = $this->moveReaderToCorrectTableNode($fileName, $sheetIndex); + return $xmlReader->expand(); + } + /** * @param string $fileName * @param int $sheetIndex * @return string */ private function getSheetXmlNodeAsString($fileName, $sheetIndex) + { + $xmlReader = $this->moveReaderToCorrectTableNode($fileName, $sheetIndex); + return $xmlReader->readOuterXml(); + } + + /** + * @param string $fileName + * @param int $sheetIndex + * @return XMLReader + */ + private function moveReaderToCorrectTableNode($fileName, $sheetIndex) { $resourcePath = $this->getGeneratedResourcePath($fileName); $pathToSheetFile = $resourcePath . '#content.xml'; @@ -441,6 +505,6 @@ class WriterTest extends \PHPUnit_Framework_TestCase $xmlReader->readUntilNodeFound('table:table'); } - return $xmlReader->readOuterXml(); + return $xmlReader; } }