Skip to content
This repository has been archived by the owner on May 26, 2022. It is now read-only.

Initial commit to support Cell styling and number formats in styles #209

Closed
wants to merge 9 commits into from
Empty file modified src/Spout/Writer/AbstractWriter.php
100644 → 100755
Empty file.
4 changes: 4 additions & 0 deletions src/Spout/Writer/CSV/Writer.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ protected function openWriter()
$this->globalFunctionsHelper->fputs($this->filePointer, EncodingHelper::BOM_UTF8);
}

public function registerStyle($style) {
return false;
}

/**
* Adds data to the currently opened writer.
*
Expand Down
4 changes: 4 additions & 0 deletions src/Spout/Writer/ODS/Writer.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ protected function getWorkbook()
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
Expand Down
73 changes: 73 additions & 0 deletions src/Spout/Writer/Style/Style.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,27 @@ class Style
/** @var bool Whether specific font properties should be applied */
protected $shouldApplyFont = false;

protected $verticalAlignment = 'center';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 2 different changes here: alignment and number format. Can you please split these changes into 2 branches? It will reduce the scope of each commit and make the review easier/faster. Thanks!

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 */
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
*/
Expand Down Expand Up @@ -243,6 +259,63 @@ public function shouldApplyFont()
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;
}

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.
* The ID is excluded from the comparison, as we only care about
Expand Down
16 changes: 16 additions & 0 deletions src/Spout/Writer/Style/StyleBuilder.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,22 @@ public function setShouldWrapText()
return $this;
}

public function setNumberFormat($format)
{
$this->style->setNumberFormat($format);
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.
*
Expand Down
47 changes: 43 additions & 4 deletions src/Spout/Writer/XLSX/Helper/StyleHelper.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public function getStylesXMLFileContent()
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
EOD;

$content .= $this->getNumberFormatSectionContent();
$content .= $this->getFontsSectionContent();
$content .= $this->getFillsSectionContent();
$content .= $this->getBordersSectionContent();
Expand All @@ -39,6 +40,31 @@ public function getStylesXMLFileContent()
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[] = '<numFmt numFmtId="'.$style->getNumberFormatId().'" formatCode="'.$style->getNumberFormat().'"/>';
}
}

if ($numberFormatCount == 0){
return '';
}

$content = '<numFmts count="'.$numberFormatCount.'">';
$content .= implode('', $formats);
$content .= '</numFmts>';
return $content;
}

/**
* Returns the content of the "<fonts>" section.
*
Expand Down Expand Up @@ -139,16 +165,29 @@ protected function getCellXfsSectionContent()
$content = '<cellXfs count="' . count($registeredStyles) . '">';

foreach ($registeredStyles as $style) {
$content .= '<xf numFmtId="0" fontId="' . $style->getId() . '" fillId="0" borderId="0" xfId="0"';
$content .= '<xf numFmtId="'.$style->getNumberFormatId().'" fontId="' . $style->getId() . '" fillId="0" borderId="0" xfId="0"';

if ($style->shouldApplyNumberFormat()) {
$content .= ' applyNumberFormat="1"';
}

if ($style->shouldApplyFont()) {
$content .= ' applyFont="1"';
}

if ($style->shouldWrapText()) {
if ($style->shouldWrapText() || $style->shouldApplyVerticalAlignment() || $style->shouldApplyHorizontalAlignment()) {
$content .= ' applyAlignment="1">';
$content .= '<alignment wrapText="1"/>';
$content .= '</xf>';
$content .= '<alignment ';
if ($style->shouldWrapText()) {
$content .= 'wrapText="1" ';
}
if ($style->shouldApplyVerticalAlignment()) {
$content .= 'vertical="' . $style->getVerticalAlignment() . '" ';
}
if ($style->shouldApplyHorizontalAlignment()) {
$content .= 'horizontal="' . $style->getHorizontalAlignment() . '" ';
}
$content .= '/></xf>';
} else {
$content .= '/>';
}
Expand Down
4 changes: 4 additions & 0 deletions src/Spout/Writer/XLSX/Internal/Workbook.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public function __construct($tempFolder, $shouldUseInlineStrings, $shouldCreateN
$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
*/
Expand Down
12 changes: 10 additions & 2 deletions src/Spout/Writer/XLSX/Internal/Worksheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,18 @@ public function addRow($dataRow, $style)

$rowXML = '<row r="' . $rowIndex . '" spans="1:' . $numCells . '">';

foreach($dataRow as $cellValue) {
foreach($dataRow as $cell) {
if (is_array($cell)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of this, where a value can have multiple types.

Copy link
Author

@Rushleader Rushleader Jun 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why shouldn't a value be able to be generated with multiple types (I agree, would be nicer with a class instead of whats happening now). Since a value and its interaction within the sheet can be different depending on type, especially when running around with different formats.

I could also change this so you set a styling object for a specific Column Index (Which will introduce another feature). That way you won't have the multiple types for a cell

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A parameter that can be of multiple types hides the complexity of the program. If you pass an number, it will do X but if you pass an array it will do Y. While it's fine to do different things, the expectation of what a function does should be clear. In this example, there should probably be 2 functions (one taking a number, one taking an array) with explicit names.

The problem is less the difference between a number and a string but more between a single element VS a collection. Those represent really different things and should be treated carefully.

$cellValue = $cell[0];
$cellStyle = $cell[1];
} else {
$cellValue = $cell;
$cellStyle = $style;
}

$columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber);
$cellXML = '<c r="' . $columnIndex . $rowIndex . '"';
$cellXML .= ' s="' . $style->getId() . '"';
$cellXML .= ' s="' . $cellStyle->getId() . '"';

if (CellHelper::isNonEmptyString($cellValue)) {
if ($this->shouldUseInlineStrings) {
Expand Down
4 changes: 4 additions & 0 deletions src/Spout/Writer/XLSX/Writer.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ public function setShouldUseInlineStrings($shouldUseInlineStrings)
return $this;
}

public function registerStyle($style) {
$this->book->registerStyle($style);
}

/**
* Configures the write and sets the current sheet pointer to a new sheet.
*
Expand Down
113 changes: 113 additions & 0 deletions test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests should be written with PHPUnit (look at examples in the tests folder)

Copy link
Author

@Rushleader Rushleader Jun 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just a quick test file to test what the performance decrease/impact would be. Since the premise of being able to style cells would seem te be a lot heavier, never intended to use this as a unit test


include_once 'src/Spout/Autoloader/autoload.php';

use Box\Spout\Common\Type;
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Writer\Style\StyleBuilder;

# UNIX_DATE = (EXCEL_DATE - 25569) * 86400 : Excel -> 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);