diff --git a/src/Spout/Writer/AbstractWriter.php b/src/Spout/Writer/AbstractWriter.php
index e17e16a..00adce7 100644
--- a/src/Spout/Writer/AbstractWriter.php
+++ b/src/Spout/Writer/AbstractWriter.php
@@ -5,6 +5,7 @@ namespace Box\Spout\Writer;
use Box\Spout\Common\Exception\IOException;
use Box\Spout\Common\Exception\InvalidArgumentException;
use Box\Spout\Writer\Exception\WriterNotOpenedException;
+use Box\Spout\Writer\Style\StyleBuilder;
/**
* Class AbstractWriter
@@ -26,6 +27,12 @@ abstract class AbstractWriter implements WriterInterface
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
protected $globalFunctionsHelper;
+ /** @var Style\Style Style to be applied to the next written row(s) */
+ protected $rowStyle;
+
+ /** @var Style\Style Default row style. Each writer can have its own default style */
+ protected $defaultRowStyle;
+
/** @var string Content-Type value for the header - to be defined by child class */
protected static $headerContentType;
@@ -39,13 +46,13 @@ abstract class AbstractWriter implements WriterInterface
/**
* Adds data to the currently openned writer.
- * The data must be UTF-8 encoded.
*
* @param array $dataRow Array containing data to be streamed.
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
+ * @param Style\Style $style Style to be applied to the written row
* @return void
*/
- abstract protected function addRowToWriter(array $dataRow);
+ abstract protected function addRowToWriter(array $dataRow, $style);
/**
* Closes the streamer, preventing any additional writing.
@@ -55,7 +62,16 @@ abstract class AbstractWriter implements WriterInterface
abstract protected function closeWriter();
/**
- * @param $globalFunctionsHelper
+ *
+ */
+ public function __construct()
+ {
+ $this->defaultRowStyle = $this->getDefaultRowStyle();
+ $this->resetRowStyleToDefault();
+ }
+
+ /**
+ * @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
* @return AbstractWriter
*/
public function setGlobalFunctionsHelper($globalFunctionsHelper)
@@ -138,7 +154,6 @@ abstract class AbstractWriter implements WriterInterface
/**
* Write given data to the output. New data will be appended to end of stream.
- * The data must be UTF-8 encoded.
*
* @param array $dataRow Array containing data to be streamed.
* If empty, no data is added (i.e. not even as a blank row)
@@ -153,7 +168,7 @@ abstract class AbstractWriter implements WriterInterface
if ($this->isWriterOpened) {
// empty $dataRow should not add an empty line
if (!empty($dataRow)) {
- $this->addRowToWriter($dataRow);
+ $this->addRowToWriter($dataRow, $this->rowStyle);
}
} else {
throw new WriterNotOpenedException('The writer needs to be opened before adding row.');
@@ -162,9 +177,32 @@ abstract class AbstractWriter implements WriterInterface
return $this;
}
+ /**
+ * Write given data to the output and apply the given style.
+ * @see addRow
+ *
+ * @param array $dataRow Array of array containing data to be streamed.
+ * @param Style\Style $style Style to be applied to the row.
+ * @return AbstractWriter
+ * @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
+ * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
+ * @throws \Box\Spout\Common\Exception\IOException If unable to write data
+ */
+ public function addRowWithStyle(array $dataRow, $style)
+ {
+ if (!$style instanceof Style\Style) {
+ throw new InvalidArgumentException('The "$style" argument must be a Style instance and cannot be NULL.');
+ }
+
+ $this->setRowStyle($style);
+ $this->addRow($dataRow);
+ $this->resetRowStyleToDefault();
+
+ return $this;
+ }
+
/**
* Write given data to the output. New data will be appended to end of stream.
- * The data must be UTF-8 encoded.
*
* @param array $dataRows Array of array containing data to be streamed.
* If a row is empty, it won't be added (i.e. not even as a blank row)
@@ -193,6 +231,64 @@ abstract class AbstractWriter implements WriterInterface
return $this;
}
+ /**
+ * Write given data to the output and apply the given style.
+ * @see addRows
+ *
+ * @param array $dataRows Array of array containing data to be streamed.
+ * @param Style\Style $style Style to be applied to the rows.
+ * @return AbstractWriter
+ * @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
+ * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
+ * @throws \Box\Spout\Common\Exception\IOException If unable to write data
+ */
+ public function addRowsWithStyle(array $dataRows, $style)
+ {
+ if (!$style instanceof Style\Style) {
+ throw new InvalidArgumentException('The "$style" argument must be a Style instance and cannot be NULL.');
+ }
+
+ $this->setRowStyle($style);
+ $this->addRows($dataRows);
+ $this->resetRowStyleToDefault();
+
+ return $this;
+ }
+
+ /**
+ * Returns the default style to be applied to rows.
+ * Can be overriden by children to have a custom style.
+ *
+ * @return Style\Style
+ */
+ protected function getDefaultRowStyle()
+ {
+ return (new StyleBuilder())->build();
+ }
+
+ /**
+ * Sets the style to be applied to the next written rows
+ * until it is changed or reset.
+ *
+ * @param Style\Style $style
+ * @return void
+ */
+ private function setRowStyle($style)
+ {
+ // Merge given style with the default one to inherit custom properties
+ $this->rowStyle = $style->mergeWith($this->defaultRowStyle);
+ }
+
+ /**
+ * Resets the style to be applied to the next written rows.
+ *
+ * @return void
+ */
+ private function resetRowStyleToDefault()
+ {
+ $this->rowStyle = $this->defaultRowStyle;
+ }
+
/**
* Closes the writer. This will close the streamer as well, preventing new data
* to be written to the file.
diff --git a/src/Spout/Writer/CSV/Writer.php b/src/Spout/Writer/CSV/Writer.php
index d008096..0ce4f39 100644
--- a/src/Spout/Writer/CSV/Writer.php
+++ b/src/Spout/Writer/CSV/Writer.php
@@ -69,10 +69,11 @@ class Writer extends AbstractWriter
*
* @param array $dataRow Array containing data to be written.
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
+ * @param \Box\Spout\Writer\Style\Style $style Ignored here since CSV does not support styling.
* @return void
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
*/
- protected function addRowToWriter(array $dataRow)
+ protected function addRowToWriter(array $dataRow, $style)
{
$wasWriteSuccessful = $this->globalFunctionsHelper->fputcsv($this->filePointer, $dataRow, $this->fieldDelimiter, $this->fieldEnclosure);
if ($wasWriteSuccessful === false) {
diff --git a/src/Spout/Writer/Style/Style.php b/src/Spout/Writer/Style/Style.php
new file mode 100644
index 0000000..6aafee6
--- /dev/null
+++ b/src/Spout/Writer/Style/Style.php
@@ -0,0 +1,277 @@
+id;
+ }
+
+ /**
+ * @param int $id
+ * @return Style
+ */
+ public function setId($id)
+ {
+ $this->id = $id;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isFontBold()
+ {
+ return $this->fontBold;
+ }
+
+ /**
+ * @return Style
+ */
+ public function setFontBold()
+ {
+ $this->fontBold = true;
+ $this->hasSetFontBold = true;
+ $this->shouldApplyFont = true;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isFontItalic()
+ {
+ return $this->fontItalic;
+ }
+
+ /**
+ * @return Style
+ */
+ public function setFontItalic()
+ {
+ $this->fontItalic = true;
+ $this->hasSetFontItalic = true;
+ $this->shouldApplyFont = true;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isFontUnderline()
+ {
+ return $this->fontUnderline;
+ }
+
+ /**
+ * @return Style
+ */
+ public function setFontUnderline()
+ {
+ $this->fontUnderline = true;
+ $this->hasSetFontUnderline = true;
+ $this->shouldApplyFont = true;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isFontStrikeThrough()
+ {
+ return $this->fontStrikeThrough;
+ }
+
+ /**
+ * @return Style
+ */
+ public function setFontStrikeThrough()
+ {
+ $this->fontStrikeThrough = true;
+ $this->hasSetFontStrikeThrough = true;
+ $this->shouldApplyFont = true;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getFontSize()
+ {
+ return $this->fontSize;
+ }
+
+ /**
+ * @param int $fontSize Font size, in pixels
+ * @return Style
+ */
+ public function setFontSize($fontSize)
+ {
+ $this->fontSize = $fontSize;
+ $this->hasSetFontSize = true;
+ $this->shouldApplyFont = true;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFontName()
+ {
+ return $this->fontName;
+ }
+
+ /**
+ * @param string $fontName Name of the font to use
+ * @return Style
+ */
+ public function setFontName($fontName)
+ {
+ $this->fontName = $fontName;
+ $this->hasSetFontName = true;
+ $this->shouldApplyFont = true;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function shouldWrapText()
+ {
+ return $this->shouldWrapText;
+ }
+
+ /**
+ * @return Style
+ */
+ public function setShouldWrapText()
+ {
+ $this->shouldWrapText = true;
+ $this->hasSetWrapText = true;
+ return $this;
+ }
+
+ /**
+ * @return bool Whether specific font properties should be applied
+ */
+ public function shouldApplyFont()
+ {
+ return $this->shouldApplyFont;
+ }
+
+ /**
+ * Serializes the style for future comparison with other styles.
+ * The ID is excluded from the comparison, as we only care about
+ * actual style properties.
+ *
+ * @return string The serialized style
+ */
+ public function serialize()
+ {
+ // In order to be able to properly compare style, set static ID value
+ $currentId = $this->id;
+ $this->setId(0);
+
+ $serializedStyle = serialize($this);
+
+ $this->setId($currentId);
+
+ return $serializedStyle;
+ }
+
+ /**
+ * Merges the current style with the given style, using the given style as a base. This means that:
+ * - if current style and base style both have property A set, use current style property's value
+ * - if current style has property A set but base style does not, use current style property's value
+ * - if base style has property A set but current style does not, use base style property's value
+ *
+ * @NOTE: This function returns a new style.
+ *
+ * @param Style $baseStyle
+ * @return Style New style corresponding to the merge of the 2 styles
+ */
+ public function mergeWith($baseStyle)
+ {
+ $mergedStyle = clone $this;
+
+ if (!$this->hasSetFontBold && $baseStyle->isFontBold()) {
+ $mergedStyle->setFontBold();
+ }
+ if (!$this->hasSetFontItalic && $baseStyle->isFontItalic()) {
+ $mergedStyle->setFontItalic();
+ }
+ if (!$this->hasSetFontUnderline && $baseStyle->isFontUnderline()) {
+ $mergedStyle->setFontUnderline();
+ }
+ if (!$this->hasSetFontStrikeThrough && $baseStyle->isFontStrikeThrough()) {
+ $mergedStyle->setFontStrikeThrough();
+ }
+ if (!$this->hasSetFontSize && $baseStyle->getFontSize() !== self::DEFAULT_FONT_SIZE) {
+ $mergedStyle->setFontSize($baseStyle->getFontSize());
+ }
+ if (!$this->hasSetFontName && $baseStyle->getFontName() !== self::DEFAULT_FONT_NAME) {
+ $mergedStyle->setFontName($baseStyle->getFontName());
+ }
+ if (!$this->hasSetWrapText && $baseStyle->shouldWrapText()) {
+ $mergedStyle->setShouldWrapText();
+ }
+
+ return $mergedStyle;
+ }
+}
diff --git a/src/Spout/Writer/Style/StyleBuilder.php b/src/Spout/Writer/Style/StyleBuilder.php
new file mode 100644
index 0000000..b82198b
--- /dev/null
+++ b/src/Spout/Writer/Style/StyleBuilder.php
@@ -0,0 +1,113 @@
+style = new Style();
+ }
+
+ /**
+ * Makes the font bold.
+ *
+ * @return StyleBuilder
+ */
+ public function setFontBold()
+ {
+ $this->style->setFontBold();
+ return $this;
+ }
+
+ /**
+ * Makes the font italic.
+ *
+ * @return StyleBuilder
+ */
+ public function setFontItalic()
+ {
+ $this->style->setFontItalic();
+ return $this;
+ }
+
+ /**
+ * Makes the font underlined.
+ *
+ * @return StyleBuilder
+ */
+ public function setFontUnderline()
+ {
+ $this->style->setFontUnderline();
+ return $this;
+ }
+
+ /**
+ * Makes the font struck through.
+ *
+ * @return StyleBuilder
+ */
+ public function setFontStrikeThrough()
+ {
+ $this->style->setFontStrikeThrough();
+ return $this;
+ }
+
+ /**
+ * Sets the font size.
+ *
+ * @param int $fontSize Font size, in pixels
+ * @return StyleBuilder
+ */
+ public function setFontSize($fontSize)
+ {
+ $this->style->setFontSize($fontSize);
+ return $this;
+ }
+
+ /**
+ * Sets the font name.
+ *
+ * @param string $fontName Name of the font to use
+ * @return StyleBuilder
+ */
+ public function setFontName($fontName)
+ {
+ $this->style->setFontName($fontName);
+ return $this;
+ }
+
+ /**
+ * Makes the text wrap in the cell if it's too long or
+ * on multiple lines.
+ *
+ * @return StyleBuilder
+ */
+ public function setShouldWrapText()
+ {
+ $this->style->setShouldWrapText();
+ return $this;
+ }
+
+ /**
+ * Returns the configured style. The style is cached and can be reused.
+ *
+ * @return Style
+ */
+ public function build()
+ {
+ return $this->style;
+ }
+}
diff --git a/src/Spout/Writer/WriterInterface.php b/src/Spout/Writer/WriterInterface.php
index 5bee201..e2d9f8d 100644
--- a/src/Spout/Writer/WriterInterface.php
+++ b/src/Spout/Writer/WriterInterface.php
@@ -40,6 +40,19 @@ interface WriterInterface
*/
public function addRow(array $dataRow);
+ /**
+ * Write given data to the output and apply the given style.
+ * @see addRow
+ *
+ * @param array $dataRow Array of array containing data to be streamed.
+ * @param Style\Style $style Style to be applied to the row.
+ * @return WriterInterface
+ * @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
+ * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
+ * @throws \Box\Spout\Common\Exception\IOException If unable to write data
+ */
+ public function addRowWithStyle(array $dataRow, $style);
+
/**
* Write given data to the output. New data will be appended to end of stream.
*
@@ -55,6 +68,19 @@ interface WriterInterface
*/
public function addRows(array $dataRows);
+ /**
+ * Write given data to the output and apply the given style.
+ * @see addRows
+ *
+ * @param array $dataRows Array of array containing data to be streamed.
+ * @param Style\Style $style Style to be applied to the rows.
+ * @return WriterInterface
+ * @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
+ * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
+ * @throws \Box\Spout\Common\Exception\IOException If unable to write data
+ */
+ public function addRowsWithStyle(array $dataRows, $style);
+
/**
* Closes the writer. This will close the streamer as well, preventing new data
* to be written to the file.
diff --git a/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php b/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php
index f49f8ee..afe337e 100644
--- a/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php
+++ b/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php
@@ -26,6 +26,7 @@ class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper
const CONTENT_TYPES_XML_FILE_NAME = '[Content_Types].xml';
const WORKBOOK_XML_FILE_NAME = 'workbook.xml';
const WORKBOOK_RELS_XML_FILE_NAME = 'workbook.xml.rels';
+ const STYLES_XML_FILE_NAME = 'styles.xml';
/** @var string Path to the root folder inside the temp folder where the files to create the XLSX will be stored */
protected $rootFolder;
@@ -256,6 +257,7 @@ EOD;
}
$contentTypesXmlFileContents .= <<
@@ -312,6 +314,7 @@ EOD;
$workbookRelsXmlFileContents = <<
+
EOD;
@@ -329,6 +332,20 @@ EOD;
return $this;
}
+ /**
+ * Creates the "styles.xml" file under the "xl" folder
+ *
+ * @param StyleHelper $styleHelper
+ * @return FileSystemHelper
+ */
+ public function createStylesFile($styleHelper)
+ {
+ $stylesXmlFileContents = $styleHelper->getStylesXMLFileContent();
+ $this->createFileWithContents($this->xlFolder, self::STYLES_XML_FILE_NAME, $stylesXmlFileContents);
+
+ return $this;
+ }
+
/**
* Zips the root folder and streams the contents of the zip into the given stream
*
diff --git a/src/Spout/Writer/XLSX/Helper/StyleHelper.php b/src/Spout/Writer/XLSX/Helper/StyleHelper.php
new file mode 100644
index 0000000..55af351
--- /dev/null
+++ b/src/Spout/Writer/XLSX/Helper/StyleHelper.php
@@ -0,0 +1,230 @@
+ [STYLE_ID] mapping table, keeping track of the registered styles */
+ protected $serializedStyleToStyleIdMappingTable = [];
+
+ /** @var array [STYLE_ID] => [STYLE] mapping table, keeping track of the registered styles */
+ protected $styleIdToStyleMappingTable = [];
+
+ /**
+ * @param \Box\Spout\Writer\Style\Style $defaultStyle
+ */
+ public function __construct($defaultStyle)
+ {
+ // This ensures that the default style is the first one to be registered
+ $this->registerStyle($defaultStyle);
+ }
+
+ /**
+ * Registers the given style as a used style.
+ * Duplicate styles won't be registered more than once.
+ *
+ * @param \Box\Spout\Writer\Style\Style $style The style to be registered
+ * @return \Box\Spout\Writer\Style\Style The registered style, updated with an internal ID.
+ */
+ public function registerStyle($style)
+ {
+ $serializedStyle = $style->serialize();
+
+ if (!$this->hasStyleAlreadyBeenRegistered($style)) {
+ $nextStyleId = count($this->serializedStyleToStyleIdMappingTable);
+ $style->setId($nextStyleId);
+
+ $this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId;
+ $this->styleIdToStyleMappingTable[$nextStyleId] = $style;
+ }
+
+ return $this->getStyleFromSerializedStyle($serializedStyle);
+ }
+
+ /**
+ * Returns whether the given style has already been registered.
+ *
+ * @param \Box\Spout\Writer\Style\Style $style
+ * @return bool
+ */
+ protected function hasStyleAlreadyBeenRegistered($style)
+ {
+ $serializedStyle = $style->serialize();
+ return array_key_exists($serializedStyle, $this->serializedStyleToStyleIdMappingTable);
+ }
+
+ /**
+ * Returns the registered style associated to the given serialization.
+ *
+ * @param string $serializedStyle The serialized style from which the actual style should be fetched from
+ * @return \Box\Spout\Writer\Style\Style
+ */
+ protected function getStyleFromSerializedStyle($serializedStyle)
+ {
+ $styleId = $this->serializedStyleToStyleIdMappingTable[$serializedStyle];
+ return $this->styleIdToStyleMappingTable[$styleId];
+ }
+
+ /**
+ * Returns the content of the "styles.xml" file, given a list of styles.
+ * @return string
+ */
+ public function getStylesXMLFileContent()
+ {
+ $content = <<
+
+
+EOD;
+
+ $content .= $this->getFontsSectionContent();
+ $content .= $this->getFillsSectionContent();
+ $content .= $this->getBordersSectionContent();
+ $content .= $this->getCellStyleXfsSectionContent();
+ $content .= $this->getCellXfsSectionContent();
+ $content .= $this->getCellStylesSectionContent();
+
+ $content .= <<
+EOD;
+
+ return $content;
+ }
+
+ /**
+ * Returns the content of the "" section.
+ * @return string
+ */
+ protected function getFontsSectionContent()
+ {
+ $content = ' ' . PHP_EOL;
+
+ foreach ($this->styleIdToStyleMappingTable as $style) {
+ $content .= ' ' . PHP_EOL;
+
+ if ($style->isFontBold()) {
+ $content .= ' ' . PHP_EOL;
+ }
+ if ($style->isFontItalic()) {
+ $content .= ' ' . PHP_EOL;
+ }
+ if ($style->isFontUnderline()) {
+ $content .= ' ' . PHP_EOL;
+ }
+ if ($style->isFontStrikeThrough()) {
+ $content .= ' ' . PHP_EOL;
+ }
+
+ $content .= ' ' . PHP_EOL;
+ $content .= ' ' . PHP_EOL;
+ $content .= ' ' . PHP_EOL;
+ }
+
+ $content .= ' ' . PHP_EOL;
+
+ return $content;
+ }
+
+ /**
+ * Returns the content of the "" section.
+ *
+ * @return string
+ */
+ protected function getFillsSectionContent()
+ {
+ return <<
+
+
+
+
+
+EOD;
+ }
+
+ /**
+ * Returns the content of the "" section.
+ *
+ * @return string
+ */
+ protected function getBordersSectionContent()
+ {
+ return <<
+
+
+
+
+
+
+
+
+
+EOD;
+ }
+
+ /**
+ * Returns the content of the "" section.
+ *
+ * @return string
+ */
+ protected function getCellStyleXfsSectionContent()
+ {
+ return <<
+
+
+
+EOD;
+ }
+
+ /**
+ * Returns the content of the "" section.
+ * @return string
+ */
+ protected function getCellXfsSectionContent()
+ {
+ $content = ' ' . PHP_EOL;
+
+ foreach ($this->styleIdToStyleMappingTable as $styleId => $style) {
+ $content .= ' shouldApplyFont()) {
+ $content .= ' applyFont="1"';
+ }
+
+ if ($style->shouldWrapText()) {
+ $content .= ' applyAlignment="1">' . PHP_EOL;
+ $content .= ' ' . PHP_EOL;
+ $content .= ' ' . PHP_EOL;
+ } else {
+ $content .= '/>' . PHP_EOL;
+ }
+ }
+
+ $content .= ' ' . PHP_EOL;
+
+ return $content;
+ }
+
+ /**
+ * Returns the content of the "" section.
+ *
+ * @return string
+ */
+ protected function getCellStylesSectionContent()
+ {
+ return <<
+
+
+
+EOD;
+ }
+}
diff --git a/src/Spout/Writer/XLSX/Internal/Workbook.php b/src/Spout/Writer/XLSX/Internal/Workbook.php
index 59544b1..4cd7c3d 100644
--- a/src/Spout/Writer/XLSX/Internal/Workbook.php
+++ b/src/Spout/Writer/XLSX/Internal/Workbook.php
@@ -5,6 +5,7 @@ namespace Box\Spout\Writer\XLSX\Internal;
use Box\Spout\Writer\Exception\SheetNotFoundException;
use Box\Spout\Writer\XLSX\Helper\FileSystemHelper;
use Box\Spout\Writer\XLSX\Helper\SharedStringsHelper;
+use Box\Spout\Writer\XLSX\Helper\StyleHelper;
use Box\Spout\Writer\XLSX\Sheet;
/**
@@ -34,19 +35,28 @@ class Workbook
/** @var \Box\Spout\Writer\XLSX\Helper\SharedStringsHelper Helper to write shared strings */
protected $sharedStringsHelper;
+ /** @var \Box\Spout\Writer\XLSX\Helper\StyleHelper Helper to apply styles */
+ protected $styleHelper;
+
/** @var Worksheet[] Array containing the workbook's sheets */
protected $worksheets = [];
/** @var Worksheet The worksheet where data will be written to */
protected $currentWorksheet;
+ protected $styles = [];
+
+
+
+
/**
* @param string $tempFolder
* @param bool $shouldUseInlineStrings
* @param bool $shouldCreateNewSheetsAutomatically
+ * @param \Box\Spout\Writer\Style\Style $defaultRowStyle
* @throws \Box\Spout\Common\Exception\IOException If unable to create at least one of the base folders
*/
- public function __construct($tempFolder, $shouldUseInlineStrings, $shouldCreateNewSheetsAutomatically)
+ public function __construct($tempFolder, $shouldUseInlineStrings, $shouldCreateNewSheetsAutomatically, $defaultRowStyle)
{
$this->shouldUseInlineStrings = $shouldUseInlineStrings;
$this->shouldCreateNewSheetsAutomatically = $shouldCreateNewSheetsAutomatically;
@@ -54,6 +64,8 @@ class Workbook
$this->fileSystemHelper = new FileSystemHelper($tempFolder);
$this->fileSystemHelper->createBaseFilesAndFolders();
+ $this->styleHelper = new StyleHelper($defaultRowStyle);
+
// This helper will be shared by all sheets
$xlFolder = $this->fileSystemHelper->getXlFolder();
$this->sharedStringsHelper = new SharedStringsHelper($xlFolder);
@@ -164,11 +176,12 @@ class Workbook
*
* @param array $dataRow Array containing data to be written.
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
+ * @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row.
* @return void
* @throws \Box\Spout\Common\Exception\IOException If trying to create a new sheet and unable to open the sheet for writing
* @throws \Box\Spout\Writer\Exception\WriterException If unable to write data
*/
- public function addRowToCurrentWorksheet($dataRow)
+ public function addRowToCurrentWorksheet($dataRow, $style)
{
$currentWorksheet = $this->getCurrentWorksheet();
$hasReachedMaxRows = $this->hasCurrentWorkseetReachedMaxRows();
@@ -178,12 +191,15 @@ class Workbook
// ... continue writing in a new sheet if option set
if ($this->shouldCreateNewSheetsAutomatically) {
$currentWorksheet = $this->addNewSheetAndMakeItCurrent();
- $currentWorksheet->addRow($dataRow);
+
+ $registeredStyle = $this->styleHelper->registerStyle($style);
+ $currentWorksheet->addRow($dataRow, $registeredStyle);
} else {
// otherwise, do nothing as the data won't be read anyways
}
} else {
- $currentWorksheet->addRow($dataRow);
+ $registeredStyle = $this->styleHelper->registerStyle($style);
+ $currentWorksheet->addRow($dataRow, $registeredStyle);
}
}
@@ -217,6 +233,7 @@ class Workbook
->createContentTypesFile($this->worksheets)
->createWorkbookFile($this->worksheets)
->createWorkbookRelsFile($this->worksheets)
+ ->createStylesFile($this->styleHelper)
->zipRootFolderAndCopyToStream($finalFilePointer);
$this->cleanupTempFolder();
diff --git a/src/Spout/Writer/XLSX/Internal/Worksheet.php b/src/Spout/Writer/XLSX/Internal/Worksheet.php
index ef41ec2..04996bc 100644
--- a/src/Spout/Writer/XLSX/Internal/Worksheet.php
+++ b/src/Spout/Writer/XLSX/Internal/Worksheet.php
@@ -38,7 +38,7 @@ EOD;
/** @var Resource Pointer to the sheet data file (e.g. xl/worksheets/sheet1.xml) */
protected $sheetFilePointer;
- /** @var int */
+ /** @var int Index of the last written row */
protected $lastWrittenRowIndex = 0;
/**
@@ -118,11 +118,12 @@ EOD;
*
* @param array $dataRow Array containing data to be written.
* 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
* @throws \Box\Spout\Common\Exception\IOException If the data cannot be written
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
*/
- public function addRow($dataRow)
+ public function addRow($dataRow, $style)
{
$cellNumber = 0;
$rowIndex = $this->lastWrittenRowIndex + 1;
@@ -133,6 +134,7 @@ EOD;
foreach($dataRow as $cellValue) {
$columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber);
$data .= ' 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 40f9ae4..c92b99b 100644
--- a/src/Spout/Writer/XLSX/Writer.php
+++ b/src/Spout/Writer/XLSX/Writer.php
@@ -4,6 +4,7 @@ namespace Box\Spout\Writer\XLSX;
use Box\Spout\Writer\AbstractWriter;
use Box\Spout\Writer\Exception\WriterNotOpenedException;
+use Box\Spout\Writer\Style\StyleBuilder;
use Box\Spout\Writer\XLSX\Internal\Workbook;
/**
@@ -14,6 +15,10 @@ use Box\Spout\Writer\XLSX\Internal\Workbook;
*/
class Writer extends AbstractWriter
{
+ /** Default style font values */
+ const DEFAULT_FONT_SIZE = 12;
+ const DEFAULT_FONT_NAME = 'Calibri';
+
/** @var string Content-Type value for the header */
protected static $headerContentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
@@ -74,7 +79,7 @@ class Writer extends AbstractWriter
{
if (!$this->book) {
$tempFolder = ($this->tempFolder) ? : sys_get_temp_dir();
- $this->book = new Workbook($tempFolder, $this->shouldUseInlineStrings, $this->shouldCreateNewSheetsAutomatically);
+ $this->book = new Workbook($tempFolder, $this->shouldUseInlineStrings, $this->shouldCreateNewSheetsAutomatically, $this->defaultRowStyle);
$this->book->addNewSheetAndMakeItCurrent();
}
}
@@ -161,14 +166,28 @@ class Writer extends AbstractWriter
*
* @param array $dataRow Array containing data to be written.
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
+ * @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row.
* @return void
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the book is not created yet
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
*/
- protected function addRowToWriter(array $dataRow)
+ protected function addRowToWriter(array $dataRow, $style)
{
$this->throwIfBookIsNotAvailable();
- $this->book->addRowToCurrentWorksheet($dataRow);
+ $this->book->addRowToCurrentWorksheet($dataRow, $style);
+ }
+
+ /**
+ * Returns the default style to be applied to rows.
+ *
+ * @return \Box\Spout\Writer\Style\Style
+ */
+ protected function getDefaultRowStyle()
+ {
+ return (new StyleBuilder())
+ ->setFontSize(self::DEFAULT_FONT_SIZE)
+ ->setFontName(self::DEFAULT_FONT_NAME)
+ ->build();
}
/**
diff --git a/tests/Spout/Writer/Style/StyleTest.php b/tests/Spout/Writer/Style/StyleTest.php
new file mode 100644
index 0000000..5e991ec
--- /dev/null
+++ b/tests/Spout/Writer/Style/StyleTest.php
@@ -0,0 +1,132 @@
+setFontBold()->build();
+ $style1->setId(1);
+
+ $style2 = (new StyleBuilder())->setFontBold()->build();
+ $style2->setId(2);
+
+ $this->assertEquals($style1->serialize(), $style2->serialize());
+ }
+
+ /**
+ * @return void
+ */
+ public function testMergeWithShouldReturnACopy()
+ {
+ $baseStyle = (new StyleBuilder())->build();
+ $currentStyle = (new StyleBuilder())->build();
+ $mergedStyle = $currentStyle->mergeWith($baseStyle);
+
+ $this->assertNotSame($mergedStyle, $currentStyle);
+ }
+
+ /**
+ * @return void
+ */
+ public function testMergeWithShouldMergeSetProperties()
+ {
+ $baseStyle = (new StyleBuilder())->setFontSize(99)->setFontBold()->build();
+ $currentStyle = (new StyleBuilder())->setFontName('Font')->setFontUnderline()->build();
+ $mergedStyle = $currentStyle->mergeWith($baseStyle);
+
+ $this->assertNotEquals(99, $currentStyle->getFontSize());
+ $this->assertFalse($currentStyle->isFontBold());
+
+ $this->assertEquals(99, $mergedStyle->getFontSize());
+ $this->assertTrue($mergedStyle->isFontBold());
+ $this->assertEquals('Font', $mergedStyle->getFontName());
+ $this->assertTrue($mergedStyle->isFontUnderline());
+ }
+
+ /**
+ * @return void
+ */
+ public function testMergeWithShouldPreferCurrentStylePropertyIfSetOnCurrentAndOnBase()
+ {
+ $baseStyle = (new StyleBuilder())->setFontSize(10)->build();
+ $currentStyle = (new StyleBuilder())->setFontSize(99)->build();
+ $mergedStyle = $currentStyle->mergeWith($baseStyle);
+
+ $this->assertEquals(99, $mergedStyle->getFontSize());
+ }
+
+ /**
+ * @return void
+ */
+ public function testMergeWithShouldPreferCurrentStylePropertyIfSetOnCurrentButNotOnBase()
+ {
+ $baseStyle = (new StyleBuilder())->build();
+ $currentStyle = (new StyleBuilder())->setFontItalic()->setFontStrikeThrough()->build();
+ $mergedStyle = $currentStyle->mergeWith($baseStyle);
+
+ $this->assertFalse($baseStyle->isFontItalic());
+ $this->assertFalse($baseStyle->isFontStrikeThrough());
+
+ $this->assertTrue($mergedStyle->isFontItalic());
+ $this->assertTrue($mergedStyle->isFontStrikeThrough());
+ }
+
+ /**
+ * @return void
+ */
+ public function testMergeWithShouldPreferBaseStylePropertyIfSetOnBaseButNotOnCurrent()
+ {
+ $baseStyle = (new StyleBuilder())
+ ->setFontItalic()
+ ->setFontUnderline()
+ ->setFontStrikeThrough()
+ ->setShouldWrapText()
+ ->build();
+ $currentStyle = (new StyleBuilder())->build();
+ $mergedStyle = $currentStyle->mergeWith($baseStyle);
+
+ $this->assertFalse($currentStyle->isFontUnderline());
+ $this->assertTrue($mergedStyle->isFontUnderline());
+
+ $this->assertFalse($currentStyle->shouldWrapText());
+ $this->assertTrue($mergedStyle->shouldWrapText());
+ }
+
+ /**
+ * @return void
+ */
+ public function testMergeWithShouldDoNothingIfStylePropertyNotSetOnBaseNorCurrent()
+ {
+ $baseStyle = (new StyleBuilder())->build();
+ $currentStyle = (new StyleBuilder())->build();
+ $mergedStyle = $currentStyle->mergeWith($baseStyle);
+
+ $this->assertTrue($baseStyle->serialize() === $currentStyle->serialize());
+ $this->assertTrue($currentStyle->serialize() === $mergedStyle->serialize());
+ }
+
+ /**
+ * @return void
+ */
+ public function testMergeWithShouldDoNothingIfStylePropertyNotSetOnCurrentAndIsDefaultValueOnBase()
+ {
+ $baseStyle = (new StyleBuilder())
+ ->setFontName(Style::DEFAULT_FONT_NAME)
+ ->setFontSize(Style::DEFAULT_FONT_SIZE)
+ ->build();
+ $currentStyle = (new StyleBuilder())->build();
+ $mergedStyle = $currentStyle->mergeWith($baseStyle);
+
+ $this->assertTrue($currentStyle->serialize() === $mergedStyle->serialize());
+ }
+}
diff --git a/tests/Spout/Writer/XLSX/Helper/StyleHelperTest.php b/tests/Spout/Writer/XLSX/Helper/StyleHelperTest.php
new file mode 100644
index 0000000..13d1575
--- /dev/null
+++ b/tests/Spout/Writer/XLSX/Helper/StyleHelperTest.php
@@ -0,0 +1,59 @@
+defaultStyle = (new StyleBuilder())->build();
+ }
+
+ /**
+ * @return void
+ */
+ public function testRegisterStyleShouldUpdateId()
+ {
+ $style1 = (new StyleBuilder())->setFontBold()->build();
+ $style2 = (new StyleBuilder())->setFontUnderline()->build();
+
+ $this->assertEquals(0, $this->defaultStyle->getId(), 'Default style ID should be 0');
+ $this->assertNull($style1->getId());
+ $this->assertNull($style2->getId());
+
+ $styleHelper = new StyleHelper($this->defaultStyle);
+ $registeredStyle1 = $styleHelper->registerStyle($style1);
+ $registeredStyle2 = $styleHelper->registerStyle($style2);
+
+ $this->assertEquals(1, $registeredStyle1->getId());
+ $this->assertEquals(2, $registeredStyle2->getId());
+ }
+
+ /**
+ * @return void
+ */
+ public function testRegisterStyleShouldReuseAlreadyRegisteredStyles()
+ {
+ $style = (new StyleBuilder())->setFontBold()->build();
+
+ $styleHelper = new StyleHelper($this->defaultStyle);
+ $registeredStyle1 = $styleHelper->registerStyle($style);
+ $registeredStyle2 = $styleHelper->registerStyle($style);
+
+ $this->assertEquals(1, $registeredStyle1->getId());
+ $this->assertEquals(1, $registeredStyle2->getId());
+ }
+}
diff --git a/tests/Spout/Writer/XLSX/WriterTest.php b/tests/Spout/Writer/XLSX/WriterTest.php
index 7c6f5ea..7924248 100644
--- a/tests/Spout/Writer/XLSX/WriterTest.php
+++ b/tests/Spout/Writer/XLSX/WriterTest.php
@@ -9,7 +9,7 @@ use Box\Spout\Writer\WriterFactory;
/**
* Class XLSXTest
*
- * @package Box\Spout\Writer
+ * @package Box\Spout\Writer\XLSX
*/
class WriterTest extends \PHPUnit_Framework_TestCase
{
@@ -26,8 +26,6 @@ class WriterTest extends \PHPUnit_Framework_TestCase
$writer = WriterFactory::create(Type::XLSX);
@$writer->openToFile($filePath);
- $writer->addRow(['xlsx--11', 'xlsx--12']);
- $writer->close();
}
/**
@@ -37,17 +35,15 @@ class WriterTest extends \PHPUnit_Framework_TestCase
{
$writer = WriterFactory::create(Type::XLSX);
$writer->addRow(['xlsx--11', 'xlsx--12']);
- $writer->close();
}
/**
* @expectedException \Box\Spout\Writer\Exception\WriterNotOpenedException
*/
- public function testAddRowShouldThrowExceptionIfCallAddRowsBeforeOpeningWriter()
+ public function testAddRowShouldThrowExceptionIfCalledBeforeOpeningWriter()
{
$writer = WriterFactory::create(Type::XLSX);
$writer->addRows([['xlsx--11', 'xlsx--12']]);
- $writer->close();
}
/**
diff --git a/tests/Spout/Writer/XLSX/WriterWithStyleTest.php b/tests/Spout/Writer/XLSX/WriterWithStyleTest.php
new file mode 100644
index 0000000..882e6d6
--- /dev/null
+++ b/tests/Spout/Writer/XLSX/WriterWithStyleTest.php
@@ -0,0 +1,337 @@
+defaultStyle = (new StyleBuilder())->build();
+ }
+
+ /**
+ * @expectedException \Box\Spout\Writer\Exception\WriterNotOpenedException
+ */
+ public function testAddRowWithStyleShouldThrowExceptionIfCallAddRowBeforeOpeningWriter()
+ {
+ $writer = WriterFactory::create(Type::XLSX);
+ $writer->addRowWithStyle(['xlsx--11', 'xlsx--12'], $this->defaultStyle);
+ }
+
+ /**
+ * @expectedException \Box\Spout\Writer\Exception\WriterNotOpenedException
+ */
+ public function testAddRowWithStyleShouldThrowExceptionIfCalledBeforeOpeningWriter()
+ {
+ $writer = WriterFactory::create(Type::XLSX);
+ $writer->addRowWithStyle(['xlsx--11', 'xlsx--12'], $this->defaultStyle);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderForInvalidStyle()
+ {
+ return [
+ ['style'],
+ [new \stdClass()],
+ [null],
+ ];
+ }
+
+ /**
+ * @dataProvider dataProviderForInvalidStyle
+ * @expectedException \Box\Spout\Common\Exception\InvalidArgumentException
+ *
+ * @param \Box\Spout\Writer\Style\Style $style
+ */
+ public function testAddRowWithStyleShouldThrowExceptionIfInvalidStyleGiven($style)
+ {
+ $fileName = 'test_add_row_with_style_should_throw_exception.xlsx';
+ $this->createGeneratedFolderIfNeeded($fileName);
+ $resourcePath = $this->getGeneratedResourcePath($fileName);
+
+ $writer = WriterFactory::create(Type::XLSX);
+ $writer->openToFile($resourcePath);
+ $writer->addRowWithStyle(['xlsx--11', 'xlsx--12'], $style);
+ }
+
+ /**
+ * @dataProvider dataProviderForInvalidStyle
+ * @expectedException \Box\Spout\Common\Exception\InvalidArgumentException
+ *
+ * @param \Box\Spout\Writer\Style\Style $style
+ */
+ public function testAddRowsWithStyleShouldThrowExceptionIfInvalidStyleGiven($style)
+ {
+ $fileName = 'test_add_row_with_style_should_throw_exception.xlsx';
+ $this->createGeneratedFolderIfNeeded($fileName);
+ $resourcePath = $this->getGeneratedResourcePath($fileName);
+
+ $writer = WriterFactory::create(Type::XLSX);
+ $writer->openToFile($resourcePath);
+ $writer->addRowsWithStyle([['xlsx--11', 'xlsx--12']], $style);
+ }
+
+ /**
+ * @return void
+ */
+ public function testAddRowWithStyleShouldListAllUsedFontsInCreateStylesXmlFile()
+ {
+ $fileName = 'test_add_row_with_style_should_list_all_used_fonts.xlsx';
+ $dataRows = [
+ ['xlsx--11', 'xlsx--12'],
+ ['xlsx--21', 'xlsx--22'],
+ ];
+
+ $style = (new StyleBuilder())
+ ->setFontBold()
+ ->setFontItalic()
+ ->setFontUnderline()
+ ->setFontStrikeThrough()
+ ->build();
+ $style2 = (new StyleBuilder())
+ ->setFontSize(15)
+ ->setFontName('Arial')
+ ->build();
+
+ $this->writeToXLSXFileWithMultipleStyles($dataRows, $fileName, [$style, $style2]);
+
+ $fontsDomElement = $this->getXmlSectionFromStylesXmlFile($fileName, 'fonts');
+ $this->assertEquals(3, $fontsDomElement->getAttribute('count'), 'There should be 3 fonts, including the default one.');
+
+ $fontElements = $fontsDomElement->getElementsByTagName('font');
+ $this->assertEquals(3, $fontElements->length, 'There should be 3 associated "font" elements, including the default one.');
+
+ // First font should be the default one
+ $defaultFontElement = $fontElements->item(0);
+ $this->assertChildrenNumEquals(2, $defaultFontElement, 'The default font should only have 2 properties.');
+ $this->assertFirstChildHasAttributeEquals((string) Writer::DEFAULT_FONT_SIZE, $defaultFontElement, 'sz', 'val');
+ $this->assertFirstChildHasAttributeEquals(Writer::DEFAULT_FONT_NAME, $defaultFontElement, 'name', 'val');
+
+ // Second font should contain data from the first created style
+ $secondFontElement = $fontElements->item(1);
+ $this->assertChildrenNumEquals(6, $secondFontElement, 'The font should only have 6 properties (4 custom styles + 2 default styles).');
+ $this->assertChildExists($secondFontElement, 'b');
+ $this->assertChildExists($secondFontElement, 'i');
+ $this->assertChildExists($secondFontElement, 'u');
+ $this->assertChildExists($secondFontElement, 'strike');
+ $this->assertFirstChildHasAttributeEquals((string) Writer::DEFAULT_FONT_SIZE, $secondFontElement, 'sz', 'val');
+ $this->assertFirstChildHasAttributeEquals(Writer::DEFAULT_FONT_NAME, $secondFontElement, 'name', 'val');
+
+ // Third font should contain data from the second created style
+ $thirdFontElement = $fontElements->item(2);
+ $this->assertChildrenNumEquals(2, $thirdFontElement, 'The font should only have 2 properties.');
+ $this->assertFirstChildHasAttributeEquals('15', $thirdFontElement, 'sz', 'val');
+ $this->assertFirstChildHasAttributeEquals('Arial', $thirdFontElement, 'name', 'val');
+ }
+
+ /**
+ * @return void
+ */
+ public function testAddRowWithStyleShouldAddWrapTextAlignmentInfoInStylesXmlFileIfSpecified()
+ {
+ $fileName = 'test_add_row_with_style_should_add_wrap_text_alignment.xlsx';
+ $dataRows = [
+ ['xlsx--11', 'xlsx--12'],
+ ];
+ $style = (new StyleBuilder())->setShouldWrapText()->build();
+
+ $this->writeToXLSXFile($dataRows, $fileName,$style);
+
+ $cellXfsDomElement = $this->getXmlSectionFromStylesXmlFile($fileName, 'cellXfs');
+ $xfElement = $cellXfsDomElement->getElementsByTagName('xf')->item(1);
+ $this->assertEquals(1, $xfElement->getAttribute('applyAlignment'));
+ $this->assertFirstChildHasAttributeEquals('1', $xfElement, 'alignment', 'wrapText');
+ }
+
+ /**
+ * @return void
+ */
+ public function testAddRowWithStyleShouldApplyStyleToCells()
+ {
+ $fileName = 'test_add_row_with_style_should_apply_style_to_cells.xlsx';
+ $dataRows = [
+ ['xlsx--11'],
+ ['xlsx--21'],
+ ['xlsx--31'],
+ ];
+ $style = (new StyleBuilder())->setFontBold()->build();
+ $style2 = (new StyleBuilder())->setFontSize(15)->build();
+
+ $this->writeToXLSXFileWithMultipleStyles($dataRows, $fileName, [$style, $style2, null]);
+
+ $cellDomElements = $this->getCellElementsFromSheetXmlFile($fileName);
+ $this->assertEquals(3, count($cellDomElements), 'There should be 3 cells.');
+
+ $this->assertEquals('1', $cellDomElements[0]->getAttribute('s'));
+ $this->assertEquals('2', $cellDomElements[1]->getAttribute('s'));
+ $this->assertEquals('0', $cellDomElements[2]->getAttribute('s'));
+ }
+
+ /**
+ * @return void
+ */
+ public function testAddRowWithStyleShouldReuseDuplicateStyles()
+ {
+ $fileName = 'test_add_row_with_style_should_reuse_duplicate_styles.xlsx';
+ $dataRows = [
+ ['xlsx--11'],
+ ['xlsx--21'],
+ ];
+ $style = (new StyleBuilder())->setFontBold()->build();
+
+ $this->writeToXLSXFile($dataRows, $fileName, $style);
+
+ $cellDomElements = $this->getCellElementsFromSheetXmlFile($fileName);
+ $this->assertEquals('1', $cellDomElements[0]->getAttribute('s'));
+ $this->assertEquals('1', $cellDomElements[1]->getAttribute('s'));
+ }
+
+ /**
+ * @param array $allRows
+ * @param string $fileName
+ * @param \Box\Spout\Writer\Style\Style $style
+ * @return Writer
+ */
+ private function writeToXLSXFile($allRows, $fileName, $style)
+ {
+ $this->createGeneratedFolderIfNeeded($fileName);
+ $resourcePath = $this->getGeneratedResourcePath($fileName);
+
+ /** @var \Box\Spout\Writer\XLSX\Writer $writer */
+ $writer = WriterFactory::create(Type::XLSX);
+ $writer->setShouldUseInlineStrings(true);
+
+ $writer->openToFile($resourcePath);
+ $writer->addRowsWithStyle($allRows, $style);
+ $writer->close();
+
+ return $writer;
+ }
+
+ /**
+ * @param array $allRows
+ * @param string $fileName
+ * @param \Box\Spout\Writer\Style\Style|null[] $styles
+ * @return Writer
+ */
+ private function writeToXLSXFileWithMultipleStyles($allRows, $fileName, $styles)
+ {
+ // there should be as many rows as there are styles passed in
+ $this->assertEquals(count($allRows), count($styles));
+
+ $this->createGeneratedFolderIfNeeded($fileName);
+ $resourcePath = $this->getGeneratedResourcePath($fileName);
+
+ /** @var \Box\Spout\Writer\XLSX\Writer $writer */
+ $writer = WriterFactory::create(Type::XLSX);
+ $writer->setShouldUseInlineStrings(true);
+
+ $writer->openToFile($resourcePath);
+ for ($i = 0; $i < count($allRows); $i++) {
+ if ($styles[$i] === null) {
+ $writer->addRow($allRows[$i]);
+ } else {
+ $writer->addRowWithStyle($allRows[$i], $styles[$i]);
+ }
+ }
+ $writer->close();
+
+ return $writer;
+ }
+
+ /**
+ * @param string $fileName
+ * @param string $section
+ * @return \DomElement
+ */
+ private function getXmlSectionFromStylesXmlFile($fileName, $section)
+ {
+ $resourcePath = $this->getGeneratedResourcePath($fileName);
+ $pathToStylesXmlFile = $resourcePath . '#xl/styles.xml';
+
+ $xmlReader = new \XMLReader();
+ $xmlReader->open('zip://' . $pathToStylesXmlFile);
+
+ while ($xmlReader->read() && ($xmlReader->nodeType !== \XMLReader::ELEMENT || $xmlReader->name !== $section)) {
+ // do nothing
+ }
+
+ return $xmlReader->expand();
+ }
+
+ /**
+ * @param string $fileName
+ * @return \DOMNode[]
+ */
+ private function getCellElementsFromSheetXmlFile($fileName)
+ {
+ $cellElements = [];
+
+ $resourcePath = $this->getGeneratedResourcePath($fileName);
+ $pathToStylesXmlFile = $resourcePath . '#xl/worksheets/sheet1.xml';
+
+ $xmlReader = new \XMLReader();
+ $xmlReader->open('zip://' . $pathToStylesXmlFile);
+
+ while ($xmlReader->read()) {
+ if ($xmlReader->nodeType === \XMLReader::ELEMENT && $xmlReader->name === 'c') {
+ $cellElements[] = $xmlReader->expand();
+ }
+ }
+
+ return $cellElements;
+ }
+
+ /**
+ * @param string $expectedValue
+ * @param \DOMNode $parentElement
+ * @param string $childTagName
+ * @param string $attributeName
+ * @return void
+ */
+ private function assertFirstChildHasAttributeEquals($expectedValue, $parentElement, $childTagName, $attributeName)
+ {
+ $this->assertEquals($expectedValue, $parentElement->getElementsByTagName($childTagName)->item(0)->getAttribute($attributeName));
+ }
+
+ /**
+ * @param int $expectedNumber
+ * @param \DOMNode $parentElement
+ * @param string $message
+ * @return void
+ */
+ private function assertChildrenNumEquals($expectedNumber, $parentElement, $message)
+ {
+ $this->assertEquals($expectedNumber, $parentElement->getElementsByTagName('*')->length, $message);
+ }
+
+ /**
+ * @param \DOMNode $parentElement
+ * @param string $childTagName
+ * @return void
+ */
+ private function assertChildExists($parentElement, $childTagName)
+ {
+ $this->assertEquals(1, $parentElement->getElementsByTagName($childTagName)->length);
+ }
+}