From 4f5218dbc968550bf584c3e96927a669d1d3a58c Mon Sep 17 00:00:00 2001 From: Gerard Krom Date: Wed, 4 May 2016 11:40:11 +0200 Subject: [PATCH 1/4] Initial commit to support Cell styling and number formats in styles --- .../Common/Internal/AbstractWorkbook.php | 5 +++ src/Spout/Writer/Style/Style.php | 36 +++++++++++++++++++ src/Spout/Writer/Style/StyleBuilder.php | 6 ++++ src/Spout/Writer/XLSX/Helper/StyleHelper.php | 32 ++++++++++++++++- src/Spout/Writer/XLSX/Internal/Workbook.php | 4 +++ src/Spout/Writer/XLSX/Internal/Worksheet.php | 12 +++++-- src/Spout/Writer/XLSX/Writer.php | 4 +++ 7 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/Spout/Writer/Common/Internal/AbstractWorkbook.php b/src/Spout/Writer/Common/Internal/AbstractWorkbook.php index c8f9f9f..ca16a29 100644 --- a/src/Spout/Writer/Common/Internal/AbstractWorkbook.php +++ b/src/Spout/Writer/Common/Internal/AbstractWorkbook.php @@ -167,6 +167,11 @@ abstract class AbstractWorkbook implements WorkbookInterface } } + public function applyStyleToCurrentWorksheet($style) { + $updatedStyle = $styleHelper->applyExtraStylesIfNeeded($style, array()); + $registeredStyle = $styleHelper->registerStyle($updatedStyle); + } + /** * @return bool Whether the current worksheet has reached the maximum number of rows per sheet. */ diff --git a/src/Spout/Writer/Style/Style.php b/src/Spout/Writer/Style/Style.php index 91e9475..a4254d7 100644 --- a/src/Spout/Writer/Style/Style.php +++ b/src/Spout/Writer/Style/Style.php @@ -61,6 +61,13 @@ class Style /** @var bool Whether the wrap text property was set */ protected $hasSetWrapText = false; + /** @var string Custom number format */ + protected $numberFormat = ''; + /** @var boolean Whether specific number format has been set */ + protected $hasSetNumberFormat = false; + /** @var integer Holds the number format id */ + protected $numberFormatId = 0; + /** * @return int|null */ @@ -243,6 +250,35 @@ class Style return $this->shouldApplyFont; } + public function setNumberFormat($format) + { + $this->numberFormat = $format; + $this->hasSetNumberFormat = true; + return $this; + } + + public function setNumberFormatId($id) + { + $this->numberFormatId = $id; + } + + + public function shouldApplyNumberFormat() + { + return $this->hasSetNumberFormat; + } + + public function getNumberFormatId() + { + return $this->numberFormatId; + } + + public function getNumberFormat() + { + return $this->numberFormat; + } + + /** * Serializes the style for future comparison with other styles. * The ID is excluded from the comparison, as we only care about diff --git a/src/Spout/Writer/Style/StyleBuilder.php b/src/Spout/Writer/Style/StyleBuilder.php index 4619f43..8521a1c 100644 --- a/src/Spout/Writer/Style/StyleBuilder.php +++ b/src/Spout/Writer/Style/StyleBuilder.php @@ -121,6 +121,12 @@ class StyleBuilder return $this; } + public function setNumberFormat($format) + { + $this->style->setNumberFormat($format); + return $this; + } + /** * Returns the configured style. The style is cached and can be reused. * diff --git a/src/Spout/Writer/XLSX/Helper/StyleHelper.php b/src/Spout/Writer/XLSX/Helper/StyleHelper.php index f3da2b5..789c72a 100644 --- a/src/Spout/Writer/XLSX/Helper/StyleHelper.php +++ b/src/Spout/Writer/XLSX/Helper/StyleHelper.php @@ -25,6 +25,7 @@ class StyleHelper extends AbstractStyleHelper EOD; + $content .= $this->getNumberFormatSectionContent(); $content .= $this->getFontsSectionContent(); $content .= $this->getFillsSectionContent(); $content .= $this->getBordersSectionContent(); @@ -39,6 +40,31 @@ EOD; return $content; } + protected function getNumberFormatSectionContent() { + $formats = array(); + $numberFormatCount = 0; + // This is the limit excel holds for the default number formats + $baseNumberFormatId = 163; + + foreach($this->getRegisteredStyles() as $style) { + /* If this evals to false we should skip it since it isnt used */ + if ($style->shouldApplyNumberFormat()) { + $numberFormatCount++; + $style->setNumberFormatId($baseNumberFormatId + $numberFormatCount); + $formats[] = ''; + } + } + + if ($numberFormatCount == 0){ + return ''; + } + + $content = ''; + $content .= implode('', $formats); + $content .= ''; + return $content; + } + /** * Returns the content of the "" section. * @@ -139,7 +165,11 @@ EOD; $content = ''; foreach ($registeredStyles as $style) { - $content .= 'getNumberFormatId().'" fontId="' . $style->getId() . '" fillId="0" borderId="0" xfId="0"'; + + if ($style->shouldApplyNumberFormat()) { + $content .= ' applyNumberFormat="1"'; + } if ($style->shouldApplyFont()) { $content .= ' applyFont="1"'; diff --git a/src/Spout/Writer/XLSX/Internal/Workbook.php b/src/Spout/Writer/XLSX/Internal/Workbook.php index 5208d4f..9b9ab24 100644 --- a/src/Spout/Writer/XLSX/Internal/Workbook.php +++ b/src/Spout/Writer/XLSX/Internal/Workbook.php @@ -58,6 +58,10 @@ class Workbook extends AbstractWorkbook $this->sharedStringsHelper = new SharedStringsHelper($xlFolder); } + public function registerStyle($style) { + $this->styleHelper->registerStyle($style); + } + /** * @return \Box\Spout\Writer\XLSX\Helper\StyleHelper Helper to apply styles to XLSX files */ diff --git a/src/Spout/Writer/XLSX/Internal/Worksheet.php b/src/Spout/Writer/XLSX/Internal/Worksheet.php index af3fbc4..ab959c6 100644 --- a/src/Spout/Writer/XLSX/Internal/Worksheet.php +++ b/src/Spout/Writer/XLSX/Internal/Worksheet.php @@ -133,10 +133,18 @@ EOD; $rowXML = ''; - foreach($dataRow as $cellValue) { + foreach($dataRow as $cell) { + if (is_array($cell)) { + $cellValue = $cell[0]; + $cellStyle = $cell[1]; + } else { + $cellValue = $cell; + $cellStyle = $style; + } + $columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber); $cellXML = 'getId() . '"'; + $cellXML .= ' s="' . $cellStyle->getId() . '"'; if (CellHelper::isNonEmptyString($cellValue)) { if ($this->shouldUseInlineStrings) { diff --git a/src/Spout/Writer/XLSX/Writer.php b/src/Spout/Writer/XLSX/Writer.php index 965955a..d61040e 100644 --- a/src/Spout/Writer/XLSX/Writer.php +++ b/src/Spout/Writer/XLSX/Writer.php @@ -64,6 +64,10 @@ class Writer extends AbstractMultiSheetsWriter return $this; } + public function registerStyle($style) { + $this->book->registerStyle($style); + } + /** * Configures the write and sets the current sheet pointer to a new sheet. * From 6705219f412118e184d0a2fb1f5023c6050abd8b Mon Sep 17 00:00:00 2001 From: Gerard Krom Date: Wed, 4 May 2016 16:22:53 +0200 Subject: [PATCH 2/4] Added a test file to make a bit easier for myself to test these changes --- test.php | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 test.php diff --git a/test.php b/test.php new file mode 100644 index 0000000..a7fbdbf --- /dev/null +++ b/test.php @@ -0,0 +1,114 @@ + unix +# EXCEL_DATE = 25569 + (UNIX_DATE / 86400) : Unix -> excel + +$test = function ($name, $sheets, $rows, $cols, $groupedRows = FALSE) { + $start = microtime(TRUE); + $writer = WriterFactory::create(Type::XLSX); // for XLSX files + $writer->openToFile('__'.$name.'.xlsx'); + + $styleDef = (new StyleBuilder())->setNumberFormat('0.00000')->build(); + $styleRed = (new StyleBuilder())->setNumberFormat('[Red]0.00000')->build(); + $styleDate = (new StyleBuilder())->setNumberFormat('d-mmm-YY HH:mm:ss')->build(); + $writer->registerStyle($styleDef); + $writer->registerStyle($styleRed); + $writer->registerStyle($styleDate); + + for ($sheetNum = 0; $sheetNum < $sheets; $sheetNum++) { + if ($sheetNum != 0) { + $sheet = $writer->addNewSheetAndMakeItCurrent(); + } else { + $sheet = $writer->getCurrentSheet(); + } + $sheet->setName('data'.$sheetNum); + + $rowsArr = array(); + for ($rowNum = 0; $rowNum < $rows; $rowNum++) { + $row = array(); + for ($colNum = 0; $colNum < $cols; $colNum++) { + switch ($colNum % 6) { + default: + $row[] = $colNum; + break; + case 1: + case 3: + $row[] = array($colNum, $styleDef); + break; + case 2: + case 4: + $row[] = array($colNum, $styleRed); + break; + case 5: + $row[] = array(25569 + (time() / 86400), $styleDate); + break; + } + } + + $rowsArr[] = $row; + if (!is_int($groupedRows)) { + $writer->addRow($row); + $rowsArr = array(); + } else if (count($rowsArr) >= $groupedRows) { + $writer->addRows($rowsArr); + $rowsArr = array(); + } + } + + if (count($rowsArr)) { + $writer->addRows($rowsArr); + } + } + + $writer->close(); + + $duration = number_format(microtime(TRUE) - $start, 2); + $str = sprintf(' %s took %s seconds to run (%d r/%d c in %d sheets)', $name, $duration, $rows, $cols, $sheets); + echo $str . PHP_EOL; +}; + +/** + * Bottleneck ATM: Disk usage + */ + +$start_mem = memory_get_usage(TRUE); +echo PHP_EOL."Memory Consumption is "; +echo round($start_mem/1048576,2).''.' MB'.PHP_EOL; + +$test('1_mini_grouped_00000', 1, 50, 25, FALSE); + +exit (0); + +$cur_mem = memory_get_usage(TRUE); +echo " Current Consumption is "; +echo round($cur_mem/1048576,2).''.' MB'.PHP_EOL; + +$test('2_small_grouped_00000', 10, 7500, 50, FALSE); + +$cur_mem = memory_get_usage(TRUE); +echo " Current Consumption is "; +echo round($cur_mem/1048576,2).''.' MB'.PHP_EOL; + +$test('3_medium_grouped_00000', 10, 7500, 500, FALSE); + +$cur_mem = memory_get_usage(TRUE); +echo " Current Consumption is "; +echo round($cur_mem/1048576,2).''.' MB'.PHP_EOL; + +$test('4_large_grouped_00000', 10, 150000, 10000, FALSE); + +$cur_mem = memory_get_usage(TRUE); +echo " Current Consumption is "; +echo round($cur_mem/1048576,2).''.' MB'.PHP_EOL; + +echo " Peak Consumption is "; +echo round(memory_get_peak_usage(TRUE)/1048576,2).''.' MB'.PHP_EOL; + +exit(0); \ No newline at end of file From e4ccef8592a4815d2489cd4e46a04eb701f46eed Mon Sep 17 00:00:00 2001 From: Gerard Krom Date: Thu, 5 May 2016 17:02:18 +0200 Subject: [PATCH 3/4] Fixed some scrutinizer issues --- src/Spout/Writer/CSV/Writer.php | 4 ++++ src/Spout/Writer/Common/Internal/AbstractWorkbook.php | 5 ----- src/Spout/Writer/ODS/Writer.php | 4 ++++ test.php | 1 - 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Spout/Writer/CSV/Writer.php b/src/Spout/Writer/CSV/Writer.php index 4327902..a7e72fe 100644 --- a/src/Spout/Writer/CSV/Writer.php +++ b/src/Spout/Writer/CSV/Writer.php @@ -66,6 +66,10 @@ class Writer extends AbstractWriter $this->globalFunctionsHelper->fputs($this->filePointer, EncodingHelper::BOM_UTF8); } + public function registerStyle($style) { + return false; + } + /** * Adds data to the currently opened writer. * diff --git a/src/Spout/Writer/Common/Internal/AbstractWorkbook.php b/src/Spout/Writer/Common/Internal/AbstractWorkbook.php index ca16a29..c8f9f9f 100644 --- a/src/Spout/Writer/Common/Internal/AbstractWorkbook.php +++ b/src/Spout/Writer/Common/Internal/AbstractWorkbook.php @@ -167,11 +167,6 @@ abstract class AbstractWorkbook implements WorkbookInterface } } - public function applyStyleToCurrentWorksheet($style) { - $updatedStyle = $styleHelper->applyExtraStylesIfNeeded($style, array()); - $registeredStyle = $styleHelper->registerStyle($updatedStyle); - } - /** * @return bool Whether the current worksheet has reached the maximum number of rows per sheet. */ diff --git a/src/Spout/Writer/ODS/Writer.php b/src/Spout/Writer/ODS/Writer.php index 4b35dfd..68e8972 100644 --- a/src/Spout/Writer/ODS/Writer.php +++ b/src/Spout/Writer/ODS/Writer.php @@ -61,6 +61,10 @@ class Writer extends AbstractMultiSheetsWriter return $this->book; } + public function registerStyle($style) { + return false; + } + /** * Adds data to the currently opened writer. * If shouldCreateNewSheetsAutomatically option is set to true, it will handle pagination diff --git a/test.php b/test.php index a7fbdbf..4652fc7 100644 --- a/test.php +++ b/test.php @@ -5,7 +5,6 @@ include_once 'src/Spout/Autoloader/autoload.php'; use Box\Spout\Common\Type; use Box\Spout\Writer\WriterFactory; use Box\Spout\Writer\Style\StyleBuilder; -use Box\Spout\Writer\Style\Color; # UNIX_DATE = (EXCEL_DATE - 25569) * 86400 : Excel -> unix # EXCEL_DATE = 25569 + (UNIX_DATE / 86400) : Unix -> excel From eda5e3bd112f35f0ee5573efb17f5664891e94fe Mon Sep 17 00:00:00 2001 From: Freek van der Veer Date: Mon, 6 Jun 2016 16:22:06 +0200 Subject: [PATCH 4/4] Added alignment to styles --- src/Spout/Writer/AbstractWriter.php | 0 src/Spout/Writer/Style/Style.php | 37 ++++++++++++++++++++ src/Spout/Writer/Style/StyleBuilder.php | 10 ++++++ src/Spout/Writer/XLSX/Helper/StyleHelper.php | 15 ++++++-- 4 files changed, 59 insertions(+), 3 deletions(-) mode change 100644 => 100755 src/Spout/Writer/AbstractWriter.php mode change 100644 => 100755 src/Spout/Writer/Style/Style.php mode change 100644 => 100755 src/Spout/Writer/Style/StyleBuilder.php mode change 100644 => 100755 src/Spout/Writer/XLSX/Helper/StyleHelper.php diff --git a/src/Spout/Writer/AbstractWriter.php b/src/Spout/Writer/AbstractWriter.php old mode 100644 new mode 100755 diff --git a/src/Spout/Writer/Style/Style.php b/src/Spout/Writer/Style/Style.php old mode 100644 new mode 100755 index a4254d7..594adde --- a/src/Spout/Writer/Style/Style.php +++ b/src/Spout/Writer/Style/Style.php @@ -56,6 +56,15 @@ class Style /** @var bool Whether specific font properties should be applied */ protected $shouldApplyFont = false; + protected $verticalAlignment = 'center'; + protected $horizontalAlignment = 'center'; + + protected $hasVerticalAlignment = false; + protected $hasHorizontalAlignment = false; + + protected $shouldApplyVerticalAlignment = false; + protected $shouldApplyHorizontalAlignment = false; + /** @var bool Whether the text should wrap in the cell (useful for long or multi-lines text) */ protected $shouldWrapText = false; /** @var bool Whether the wrap text property was set */ @@ -278,6 +287,34 @@ class Style return $this->numberFormat; } + public function setVerticalAlignment($alignment) { + $this->verticalAlignment = $alignment; + $this->hasVerticalAlignment = true; + $this->shouldApplyVerticalAlignment = true; + } + + public function setHorizontalAlignment($alignment) { + $this->horizontalAlignment = $alignment; + $this->hasHorizontalAlignment = true; + $this->shouldApplyHorizontalAlignment = true; + } + + public function getVerticalAlignment() { + return $this->verticalAlignment; + } + + public function getHorizontalAlignment() { + return $this->horizontalAlignment; + } + + public function shouldApplyVerticalAlignment() { + return $this->shouldApplyVerticalAlignment; + } + + public function shouldApplyHorizontalAlignment() { + return $this->shouldApplyHorizontalAlignment; + } + /** * Serializes the style for future comparison with other styles. diff --git a/src/Spout/Writer/Style/StyleBuilder.php b/src/Spout/Writer/Style/StyleBuilder.php old mode 100644 new mode 100755 index 8521a1c..f97009f --- a/src/Spout/Writer/Style/StyleBuilder.php +++ b/src/Spout/Writer/Style/StyleBuilder.php @@ -127,6 +127,16 @@ class StyleBuilder return $this; } + public function setVerticalAlignment($alignment) { + $this->style->setVerticalAlignment($alignment); + return $this; + } + + public function setHorizontalAlignment($alignment) { + $this->style->setHorizontalAlignment($alignment); + return $this; + } + /** * Returns the configured style. The style is cached and can be reused. * diff --git a/src/Spout/Writer/XLSX/Helper/StyleHelper.php b/src/Spout/Writer/XLSX/Helper/StyleHelper.php old mode 100644 new mode 100755 index 789c72a..c95b27a --- a/src/Spout/Writer/XLSX/Helper/StyleHelper.php +++ b/src/Spout/Writer/XLSX/Helper/StyleHelper.php @@ -175,10 +175,19 @@ EOD; $content .= ' applyFont="1"'; } - if ($style->shouldWrapText()) { + if ($style->shouldWrapText() || $style->shouldApplyVerticalAlignment() || $style->shouldApplyHorizontalAlignment()) { $content .= ' applyAlignment="1">'; - $content .= ''; - $content .= ''; + $content .= 'shouldWrapText()) { + $content .= 'wrapText="1" '; + } + if ($style->shouldApplyVerticalAlignment()) { + $content .= 'vertical="' . $style->getVerticalAlignment() . '" '; + } + if ($style->shouldApplyHorizontalAlignment()) { + $content .= 'horizontal="' . $style->getHorizontalAlignment() . '" '; + } + $content .= '/>'; } else { $content .= '/>'; }