From 7f26167be8a448f6cb516a674dbb9dded00d9f64 Mon Sep 17 00:00:00 2001 From: Salavat Salakhutdinov Date: Fri, 30 Oct 2020 18:46:52 +0300 Subject: [PATCH] update xlsx writer --- src/Spout/Common/Entity/Cell.php | 25 ++++++++++++- .../Writer/XLSX/Manager/WorksheetManager.php | 35 +++++++++++++++---- tests/Spout/Writer/XLSX/WriterTest.php | 27 ++++++++++++++ 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/Spout/Common/Entity/Cell.php b/src/Spout/Common/Entity/Cell.php index c1de389..924f880 100644 --- a/src/Spout/Common/Entity/Cell.php +++ b/src/Spout/Common/Entity/Cell.php @@ -64,14 +64,21 @@ class Cell */ protected $style; + /** + * The cell formula + * @var string|null + */ + protected $formula; + /** * @param $value mixed * @param Style|null $style */ - public function __construct($value, Style $style = null) + public function __construct($value, ?Style $style = null, $formula = null) { $this->setValue($value); $this->setStyle($style); + $this->setFormula($formula); } /** @@ -131,6 +138,22 @@ class Cell $this->type = $type; } + /** + * @return string|null + */ + public function getFormula() + { + return $this->formula; + } + + /** + * @param string|null $formula + */ + public function setFormula(?string $formula) + { + $this->formula = $formula; + } + /** * Get the current value type * diff --git a/src/Spout/Writer/XLSX/Manager/WorksheetManager.php b/src/Spout/Writer/XLSX/Manager/WorksheetManager.php index 741d0aa..2cca1e7 100644 --- a/src/Spout/Writer/XLSX/Manager/WorksheetManager.php +++ b/src/Spout/Writer/XLSX/Manager/WorksheetManager.php @@ -211,18 +211,21 @@ EOD; $columnLetters = CellHelper::getColumnLettersFromColumnIndex($columnIndexZeroBased); $cellXML = 'getCellFormulaXMLFragment($cell); if ($cell->isString()) { - $cellXML .= $this->getCellXMLFragmentForNonEmptyString($cell->getValue()); + $cellXML .= $this->getCellXMLFragmentForNonEmptyString($cell); } elseif ($cell->isBoolean()) { - $cellXML .= ' t="b">' . (int) ($cell->getValue()) . ''; + $cellXML .= ' t="b">' . $cellFormulaXMLFragment . '' . (int) ($cell->getValue()) . ''; } elseif ($cell->isNumeric()) { - $cellXML .= '>' . $cell->getValue() . ''; + $cellXML .= '>' . $cellFormulaXMLFragment . '' . $cell->getValue() . ''; } elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) { // only writes the error value if it's a string $cellXML .= ' t="e">' . $cell->getValueEvenIfError() . ''; } elseif ($cell->isEmpty()) { - if ($this->styleManager->shouldApplyStyleOnEmptyCell($styleId)) { + if ($cellFormulaXMLFragment) { + $cellXML .= '>' . $cellFormulaXMLFragment . ''; + } else if ($this->styleManager->shouldApplyStyleOnEmptyCell($styleId)) { $cellXML .= '/>'; } else { // don't write empty cells that do no need styling @@ -237,19 +240,37 @@ EOD; } /** - * Returns the XML fragment for a cell containing a non empty string + * Returns the XML fragment for a cell formula * * @param string $cellValue The cell value * @throws InvalidArgumentException If the string exceeds the maximum number of characters allowed per cell * @return string The XML fragment representing the cell */ - private function getCellXMLFragmentForNonEmptyString($cellValue) + private function getCellFormulaXMLFragment($cell) { + $cellFormula = $cell->getFormula(); + return $cellFormula ? '' . $cellFormula . '' : ''; + } + + /** + * Returns the XML fragment for a cell containing a non empty string + * + * @param Cell $cell cell + * @throws InvalidArgumentException If the string exceeds the maximum number of characters allowed per cell + * @return string The XML fragment representing the cell + */ + private function getCellXMLFragmentForNonEmptyString($cell) + { + $cellValue = $cell->getValue(); + $cellFormulaXMLFragment = $this->getCellFormulaXMLFragment($cell); + 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) { + if ($cellFormulaXMLFragment) { + $cellXMLFragment = ' t="str">' . $cellFormulaXMLFragment . '' . $cellValue . ''; + } else if ($this->shouldUseInlineStrings) { $cellXMLFragment = ' t="inlineStr">' . $this->stringsEscaper->escape($cellValue) . ''; } else { $sharedStringId = $this->sharedStringsManager->writeString($cellValue); diff --git a/tests/Spout/Writer/XLSX/WriterTest.php b/tests/Spout/Writer/XLSX/WriterTest.php index 15cc4e4..c00ae54 100644 --- a/tests/Spout/Writer/XLSX/WriterTest.php +++ b/tests/Spout/Writer/XLSX/WriterTest.php @@ -189,6 +189,33 @@ class WriterTest extends TestCase $this->assertEquals($sheets[1], $writer->getCurrentSheet(), 'The current sheet should be the second one.'); } + /** + * @return void + */ + public function testCellFormula() + { + $fileName = 'test_cell_formula.xlsx'; + + $rowsAsArray = [ + [[1], [3], ['Github link', 'HYPERLINK("https://github.com/","Github link")']], + [[0, 'A1+B1'], [-2, 'A1-B1'], ['Google link', 'HYPERLINK("https://google.com/","Google link")'], ['test']], + ]; + $rows = array_map(function($rowAsArray) { + return WriterEntityFactory::createRow((array_map(function($cellAsArray) { + return new Cell($cellAsArray[0] ?? "", null, $cellAsArray[1] ?? ""); + }, $rowAsArray))); + }, $rowsAsArray); + + $this->writeToXLSXFile($rows, $fileName); + + foreach ($rows as $row) { + foreach ($row->getCells() as $cell) { + $this->assertInlineDataWasWrittenToSheet($fileName, 1, $cell->getValue()); + $this->assertInlineDataWasWrittenToSheet($fileName, 1, $cell->getFormula()); + } + } + } + /** * @return void */