Add support for styling
Added top level methods on the Writer: - addRowWithStyle() - addRowsWithStyle() Added a style builder, to easily create new styles. Each writer can specify its own default style and all styles will automatically inherit from it. For now, the style properties supported are: - bold - italic - underline - strikethrough - font size - font name - wrap text (alignment)
This commit is contained in:
1 parent
0104714cbd
commit
21263a0730
14 files changed
+1344
-22
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
namespace Box\Spout\Writer\Style;
|
||||
|
||||
/**
|
||||
* Class Style
|
||||
* Represents a style to be applied to a cell
|
||||
*
|
||||
* @package Box\Spout\Writer\Style
|
||||
*/
|
||||
class Style
|
||||
{
|
||||
/** Default font values */
|
||||
const DEFAULT_FONT_SIZE = 11;
|
||||
const DEFAULT_FONT_NAME = 'Arial';
|
||||
|
||||
/** @var int|null Style ID */
|
||||
protected $id = null;
|
||||
|
||||
/** @var bool Whether the font should be bold */
|
||||
protected $fontBold = false;
|
||||
/** @var bool Whether the bold property was set */
|
||||
protected $hasSetFontBold = false;
|
||||
|
||||
/** @var bool Whether the font should be italic */
|
||||
protected $fontItalic = false;
|
||||
/** @var bool Whether the italic property was set */
|
||||
protected $hasSetFontItalic = false;
|
||||
|
||||
/** @var bool Whether the font should be underlined */
|
||||
protected $fontUnderline = false;
|
||||
/** @var bool Whether the underline property was set */
|
||||
protected $hasSetFontUnderline = false;
|
||||
|
||||
/** @var bool Whether the font should be struck through */
|
||||
protected $fontStrikeThrough = false;
|
||||
/** @var bool Whether the strikethrough property was set */
|
||||
protected $hasSetFontStrikeThrough = false;
|
||||
|
||||
/** @var int Font size */
|
||||
protected $fontSize = self::DEFAULT_FONT_SIZE;
|
||||
/** @var bool Whether the font size property was set */
|
||||
protected $hasSetFontSize = false;
|
||||
|
||||
/** @var string Font name */
|
||||
protected $fontName = self::DEFAULT_FONT_NAME;
|
||||
/** @var bool Whether the font name property was set */
|
||||
protected $hasSetFontName = false;
|
||||
|
||||
/** @var bool Whether specific font properties should be applied */
|
||||
protected $shouldApplyFont = 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;
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Box\Spout\Writer\Style;
|
||||
|
||||
/**
|
||||
* Class StyleBuilder
|
||||
* Builder to create new styles
|
||||
*
|
||||
* @package Box\Spout\Writer\Style
|
||||
*/
|
||||
class StyleBuilder
|
||||
{
|
||||
/** @var Style Style to be created */
|
||||
protected $style;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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 .= <<<EOD
|
||||
<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" PartName="/xl/styles.xml"/>
|
||||
<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" PartName="/xl/sharedStrings.xml"/>
|
||||
<Override ContentType="application/vnd.openxmlformats-package.core-properties+xml" PartName="/docProps/core.xml"/>
|
||||
<Override ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" PartName="/docProps/app.xml"/>
|
||||
@@ -312,6 +314,7 @@ EOD;
|
||||
$workbookRelsXmlFileContents = <<<EOD
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rIdStyles" Target="styles.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"/>
|
||||
<Relationship Id="rIdSharedStrings" Target="sharedStrings.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"/>
|
||||
|
||||
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
|
||||
*
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
namespace Box\Spout\Writer\XLSX\Helper;
|
||||
|
||||
/**
|
||||
* Class StyleHelper
|
||||
* This class provides helper functions to manage styles
|
||||
*
|
||||
* @package Box\Spout\Writer\XLSX\Helper
|
||||
*/
|
||||
class StyleHelper
|
||||
{
|
||||
/** @var array [SERIALIZED_STYLE] => [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
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||
|
||||
EOD;
|
||||
|
||||
$content .= $this->getFontsSectionContent();
|
||||
$content .= $this->getFillsSectionContent();
|
||||
$content .= $this->getBordersSectionContent();
|
||||
$content .= $this->getCellStyleXfsSectionContent();
|
||||
$content .= $this->getCellXfsSectionContent();
|
||||
$content .= $this->getCellStylesSectionContent();
|
||||
|
||||
$content .= <<<EOD
|
||||
</styleSheet>
|
||||
EOD;
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the "<fonts>" section.
|
||||
* @return string
|
||||
*/
|
||||
protected function getFontsSectionContent()
|
||||
{
|
||||
$content = ' <fonts count="' . count($this->styleIdToStyleMappingTable) . '">' . PHP_EOL;
|
||||
|
||||
foreach ($this->styleIdToStyleMappingTable as $style) {
|
||||
$content .= ' <font>' . PHP_EOL;
|
||||
|
||||
if ($style->isFontBold()) {
|
||||
$content .= ' <b/>' . PHP_EOL;
|
||||
}
|
||||
if ($style->isFontItalic()) {
|
||||
$content .= ' <i/>' . PHP_EOL;
|
||||
}
|
||||
if ($style->isFontUnderline()) {
|
||||
$content .= ' <u/>' . PHP_EOL;
|
||||
}
|
||||
if ($style->isFontStrikeThrough()) {
|
||||
$content .= ' <strike/>' . PHP_EOL;
|
||||
}
|
||||
|
||||
$content .= ' <sz val="' . $style->getFontSize() . '"/>' . PHP_EOL;
|
||||
$content .= ' <name val="' . $style->getFontName() . '"/>' . PHP_EOL;
|
||||
$content .= ' </font>' . PHP_EOL;
|
||||
}
|
||||
|
||||
$content .= ' </fonts>' . PHP_EOL;
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the "<fills>" section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFillsSectionContent()
|
||||
{
|
||||
return <<<EOD
|
||||
<fills count="1">
|
||||
<fill>
|
||||
<patternFill patternType="none"/>
|
||||
</fill>
|
||||
</fills>
|
||||
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the "<borders>" section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getBordersSectionContent()
|
||||
{
|
||||
return <<<EOD
|
||||
<borders count="1">
|
||||
<border>
|
||||
<left/>
|
||||
<right/>
|
||||
<top/>
|
||||
<bottom/>
|
||||
<diagonal/>
|
||||
</border>
|
||||
</borders>
|
||||
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the "<cellStyleXfs>" section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getCellStyleXfsSectionContent()
|
||||
{
|
||||
return <<<EOD
|
||||
<cellStyleXfs count="1">
|
||||
<xf borderId="0" fillId="0" fontId="0" numFmtId="0"/>
|
||||
</cellStyleXfs>
|
||||
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the "<cellXfs>" section.
|
||||
* @return string
|
||||
*/
|
||||
protected function getCellXfsSectionContent()
|
||||
{
|
||||
$content = ' <cellXfs count="' . count($this->styleIdToStyleMappingTable) . '">' . PHP_EOL;
|
||||
|
||||
foreach ($this->styleIdToStyleMappingTable as $styleId => $style) {
|
||||
$content .= ' <xf numFmtId="0" fontId="' . $styleId . '" fillId="0" borderId="0" xfId="0"';
|
||||
|
||||
if ($style->shouldApplyFont()) {
|
||||
$content .= ' applyFont="1"';
|
||||
}
|
||||
|
||||
if ($style->shouldWrapText()) {
|
||||
$content .= ' applyAlignment="1">' . PHP_EOL;
|
||||
$content .= ' <alignment wrapText="1"/>' . PHP_EOL;
|
||||
$content .= ' </xf>' . PHP_EOL;
|
||||
} else {
|
||||
$content .= '/>' . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
$content .= ' </cellXfs>' . PHP_EOL;
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the "<cellStyles>" section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getCellStylesSectionContent()
|
||||
{
|
||||
return <<<EOD
|
||||
<cellStyles count="1">
|
||||
<cellStyle builtinId="0" name="Normal" xfId="0"/>
|
||||
</cellStyles>
|
||||
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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 .= ' <c r="' . $columnIndex . $rowIndex . '"';
|
||||
$data .= ' s="' . $style->getId() . '"';
|
||||
|
||||
if (CellHelper::isNonEmptyString($cellValue)) {
|
||||
if ($this->shouldUseInlineStrings) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user