MDL-70302 core: Upgrade spout to 3.1.0

This commit is contained in:
Peter Dias 2021-01-13 10:50:45 +08:00
parent fee3970787
commit 23df19225a
68 changed files with 787 additions and 311 deletions

View file

@ -23,7 +23,7 @@ class Psr4Autoloader
*/ */
public function register() public function register()
{ {
spl_autoload_register([$this, 'loadClass']); \spl_autoload_register([$this, 'loadClass']);
} }
/** /**
@ -40,10 +40,10 @@ class Psr4Autoloader
public function addNamespace($prefix, $baseDir, $prepend = false) public function addNamespace($prefix, $baseDir, $prepend = false)
{ {
// normalize namespace prefix // normalize namespace prefix
$prefix = trim($prefix, '\\') . '\\'; $prefix = \trim($prefix, '\\') . '\\';
// normalize the base directory with a trailing separator // normalize the base directory with a trailing separator
$baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/'; $baseDir = \rtrim($baseDir, DIRECTORY_SEPARATOR) . '/';
// initialize the namespace prefix array // initialize the namespace prefix array
if (isset($this->prefixes[$prefix]) === false) { if (isset($this->prefixes[$prefix]) === false) {
@ -52,9 +52,9 @@ class Psr4Autoloader
// retain the base directory for the namespace prefix // retain the base directory for the namespace prefix
if ($prepend) { if ($prepend) {
array_unshift($this->prefixes[$prefix], $baseDir); \array_unshift($this->prefixes[$prefix], $baseDir);
} else { } else {
array_push($this->prefixes[$prefix], $baseDir); \array_push($this->prefixes[$prefix], $baseDir);
} }
} }
@ -72,12 +72,12 @@ class Psr4Autoloader
// work backwards through the namespace names of the fully-qualified // work backwards through the namespace names of the fully-qualified
// class name to find a mapped file name // class name to find a mapped file name
while (($pos = strrpos($prefix, '\\')) !== false) { while (($pos = \strrpos($prefix, '\\')) !== false) {
// retain the trailing namespace separator in the prefix // retain the trailing namespace separator in the prefix
$prefix = substr($class, 0, $pos + 1); $prefix = \substr($class, 0, $pos + 1);
// the rest is the relative class name // the rest is the relative class name
$relativeClass = substr($class, $pos + 1); $relativeClass = \substr($class, $pos + 1);
// try to load a mapped file for the prefix and relative class // try to load a mapped file for the prefix and relative class
$mappedFile = $this->loadMappedFile($prefix, $relativeClass); $mappedFile = $this->loadMappedFile($prefix, $relativeClass);
@ -87,7 +87,7 @@ class Psr4Autoloader
// remove the trailing namespace separator for the next iteration // remove the trailing namespace separator for the next iteration
// of strrpos() // of strrpos()
$prefix = rtrim($prefix, '\\'); $prefix = \rtrim($prefix, '\\');
} }
// never found a mapped file // never found a mapped file
@ -115,7 +115,7 @@ class Psr4Autoloader
// replace namespace separators with directory separators // replace namespace separators with directory separators
// in the relative class name, append with .php // in the relative class name, append with .php
$file = $baseDir $file = $baseDir
. str_replace('\\', '/', $relativeClass) . \str_replace('\\', '/', $relativeClass)
. '.php'; . '.php';
// if the mapped file exists, require it // if the mapped file exists, require it
@ -137,7 +137,7 @@ class Psr4Autoloader
*/ */
protected function requireFile($file) protected function requireFile($file)
{ {
if (file_exists($file)) { if (\file_exists($file)) {
require $file; require $file;
return true; return true;

View file

@ -8,7 +8,7 @@ require_once 'Psr4Autoloader.php';
* @var string * @var string
* Full path to "src/Spout" which is what we want "Box\Spout" to map to. * Full path to "src/Spout" which is what we want "Box\Spout" to map to.
*/ */
$srcBaseDirectory = dirname(dirname(__FILE__)); $srcBaseDirectory = \dirname(\dirname(__FILE__));
$loader = new Psr4Autoloader(); $loader = new Psr4Autoloader();
$loader->register(); $loader->register();

View file

@ -91,6 +91,14 @@ class Cell
return !$this->isError() ? $this->value : null; return !$this->isError() ? $this->value : null;
} }
/**
* @return mixed
*/
public function getValueEvenIfError()
{
return $this->value;
}
/** /**
* @param Style|null $style * @param Style|null $style
*/ */

View file

@ -70,7 +70,7 @@ class Row
*/ */
public function getCellAtIndex($cellIndex) public function getCellAtIndex($cellIndex)
{ {
return isset($this->cells[$cellIndex]) ? $this->cells[$cellIndex] : null; return $this->cells[$cellIndex] ?? null;
} }
/** /**
@ -89,7 +89,13 @@ class Row
*/ */
public function getNumCells() public function getNumCells()
{ {
return count($this->cells); // When using "setCellAtIndex", it's possible to
// have "$this->cells" contain holes.
if (empty($this->cells)) {
return 0;
}
return \max(\array_keys($this->cells)) + 1;
} }
/** /**
@ -116,7 +122,7 @@ class Row
*/ */
public function toArray() public function toArray()
{ {
return array_map(function (Cell $cell) { return \array_map(function (Cell $cell) {
return $cell->getValue(); return $cell->getValue();
}, $this->cells); }, $this->cells);
} }

View file

@ -93,7 +93,7 @@ class BorderPart
*/ */
public function setName($name) public function setName($name)
{ {
if (!in_array($name, self::$allowedNames)) { if (!\in_array($name, self::$allowedNames)) {
throw new InvalidNameException($name); throw new InvalidNameException($name);
} }
$this->name = $name; $this->name = $name;
@ -114,7 +114,7 @@ class BorderPart
*/ */
public function setStyle($style) public function setStyle($style)
{ {
if (!in_array($style, self::$allowedStyles)) { if (!\in_array($style, self::$allowedStyles)) {
throw new InvalidStyleException($style); throw new InvalidStyleException($style);
} }
$this->style = $style; $this->style = $style;
@ -152,7 +152,7 @@ class BorderPart
*/ */
public function setWidth($width) public function setWidth($width)
{ {
if (!in_array($width, self::$allowedWidths)) { if (!\in_array($width, self::$allowedWidths)) {
throw new InvalidWidthException($width); throw new InvalidWidthException($width);
} }
$this->width = $width; $this->width = $width;

View file

@ -0,0 +1,32 @@
<?php
namespace Box\Spout\Common\Entity\Style;
/**
* Class Alignment
* This class provides constants to work with text alignment.
*/
abstract class CellAlignment
{
const LEFT = 'left';
const RIGHT = 'right';
const CENTER = 'center';
const JUSTIFY = 'justify';
private static $VALID_ALIGNMENTS = [
self::LEFT => 1,
self::RIGHT => 1,
self::CENTER => 1,
self::JUSTIFY => 1,
];
/**
* @param string $cellAlignment
*
* @return bool Whether the given cell alignment is valid
*/
public static function isValid($cellAlignment)
{
return isset(self::$VALID_ALIGNMENTS[$cellAlignment]);
}
}

View file

@ -8,7 +8,7 @@ use Box\Spout\Common\Exception\InvalidColorException;
* Class Color * Class Color
* This class provides constants and functions to work with colors * This class provides constants and functions to work with colors
*/ */
class Color abstract class Color
{ {
/** Standard colors - based on Office Online */ /** Standard colors - based on Office Online */
const BLACK = '000000'; const BLACK = '000000';
@ -38,7 +38,7 @@ class Color
self::throwIfInvalidColorComponentValue($green); self::throwIfInvalidColorComponentValue($green);
self::throwIfInvalidColorComponentValue($blue); self::throwIfInvalidColorComponentValue($blue);
return strtoupper( return \strtoupper(
self::convertColorComponentToHex($red) . self::convertColorComponentToHex($red) .
self::convertColorComponentToHex($green) . self::convertColorComponentToHex($green) .
self::convertColorComponentToHex($blue) self::convertColorComponentToHex($blue)
@ -54,7 +54,7 @@ class Color
*/ */
protected static function throwIfInvalidColorComponentValue($colorComponent) protected static function throwIfInvalidColorComponentValue($colorComponent)
{ {
if (!is_int($colorComponent) || $colorComponent < 0 || $colorComponent > 255) { if (!\is_int($colorComponent) || $colorComponent < 0 || $colorComponent > 255) {
throw new InvalidColorException("The RGB components must be between 0 and 255. Received: $colorComponent"); throw new InvalidColorException("The RGB components must be between 0 and 255. Received: $colorComponent");
} }
} }
@ -67,7 +67,7 @@ class Color
*/ */
protected static function convertColorComponentToHex($colorComponent) protected static function convertColorComponentToHex($colorComponent)
{ {
return str_pad(dechex($colorComponent), 2, '0', STR_PAD_LEFT); return \str_pad(\dechex($colorComponent), 2, '0', STR_PAD_LEFT);
} }
/** /**

View file

@ -8,7 +8,7 @@ namespace Box\Spout\Common\Entity\Style;
*/ */
class Style class Style
{ {
/** Default font values */ /** Default values */
const DEFAULT_FONT_SIZE = 11; const DEFAULT_FONT_SIZE = 11;
const DEFAULT_FONT_COLOR = Color::BLACK; const DEFAULT_FONT_COLOR = Color::BLACK;
const DEFAULT_FONT_NAME = 'Arial'; const DEFAULT_FONT_NAME = 'Arial';
@ -54,6 +54,13 @@ class Style
/** @var bool Whether specific font properties should be applied */ /** @var bool Whether specific font properties should be applied */
private $shouldApplyFont = false; private $shouldApplyFont = false;
/** @var bool Whether specific cell alignment should be applied */
private $shouldApplyCellAlignment = false;
/** @var string Cell alignment */
private $cellAlignment;
/** @var bool Whether the cell alignment property was set */
private $hasSetCellAlignment = false;
/** @var bool Whether the text should wrap in the cell (useful for long or multi-lines text) */ /** @var bool Whether the text should wrap in the cell (useful for long or multi-lines text) */
private $shouldWrapText = false; private $shouldWrapText = false;
/** @var bool Whether the wrap text property was set */ /** @var bool Whether the wrap text property was set */
@ -71,6 +78,12 @@ class Style
/** @var bool */ /** @var bool */
private $hasSetBackgroundColor = false; private $hasSetBackgroundColor = false;
/** @var string Format */
private $format;
/** @var bool */
private $hasSetFormat = false;
/** /**
* @return int|null * @return int|null
*/ */
@ -319,6 +332,44 @@ class Style
return $this->hasSetFontName; return $this->hasSetFontName;
} }
/**
* @return string
*/
public function getCellAlignment()
{
return $this->cellAlignment;
}
/**
* @param string $cellAlignment The cell alignment
*
* @return Style
*/
public function setCellAlignment($cellAlignment)
{
$this->cellAlignment = $cellAlignment;
$this->hasSetCellAlignment = true;
$this->shouldApplyCellAlignment = true;
return $this;
}
/**
* @return bool
*/
public function hasSetCellAlignment()
{
return $this->hasSetCellAlignment;
}
/**
* @return bool Whether specific cell alignment should be applied
*/
public function shouldApplyCellAlignment()
{
return $this->shouldApplyCellAlignment;
}
/** /**
* @return bool * @return bool
*/ */
@ -383,4 +434,33 @@ class Style
{ {
return $this->hasSetBackgroundColor; return $this->hasSetBackgroundColor;
} }
/**
* Sets format
* @param string $format
* @return Style
*/
public function setFormat($format)
{
$this->hasSetFormat = true;
$this->format = $format;
return $this;
}
/**
* @return string
*/
public function getFormat()
{
return $this->format;
}
/**
* @return bool Whether format should be applied
*/
public function shouldApplyFormat()
{
return $this->hasSetFormat;
}
} }

View file

@ -23,7 +23,7 @@ class CellTypeHelper
*/ */
public static function isNonEmptyString($value) public static function isNonEmptyString($value)
{ {
return (gettype($value) === 'string' && $value !== ''); return (\gettype($value) === 'string' && $value !== '');
} }
/** /**
@ -35,7 +35,7 @@ class CellTypeHelper
*/ */
public static function isNumeric($value) public static function isNumeric($value)
{ {
$valueType = gettype($value); $valueType = \gettype($value);
return ($valueType === 'integer' || $valueType === 'double'); return ($valueType === 'integer' || $valueType === 'double');
} }
@ -49,7 +49,7 @@ class CellTypeHelper
*/ */
public static function isBoolean($value) public static function isBoolean($value)
{ {
return gettype($value) === 'boolean'; return \gettype($value) === 'boolean';
} }
/** /**

View file

@ -61,7 +61,7 @@ class EncodingHelper
$bomUsed = $this->supportedEncodingsWithBom[$encoding]; $bomUsed = $this->supportedEncodingsWithBom[$encoding];
// we skip the N first bytes // we skip the N first bytes
$byteOffsetToSkipBom = strlen($bomUsed); $byteOffsetToSkipBom = \strlen($bomUsed);
} }
return $byteOffsetToSkipBom; return $byteOffsetToSkipBom;
@ -80,9 +80,9 @@ class EncodingHelper
$this->globalFunctionsHelper->rewind($filePointer); $this->globalFunctionsHelper->rewind($filePointer);
if (array_key_exists($encoding, $this->supportedEncodingsWithBom)) { if (\array_key_exists($encoding, $this->supportedEncodingsWithBom)) {
$potentialBom = $this->supportedEncodingsWithBom[$encoding]; $potentialBom = $this->supportedEncodingsWithBom[$encoding];
$numBytesInBom = strlen($potentialBom); $numBytesInBom = \strlen($potentialBom);
$hasBOM = ($this->globalFunctionsHelper->fgets($filePointer, $numBytesInBom + 1) === $potentialBom); $hasBOM = ($this->globalFunctionsHelper->fgets($filePointer, $numBytesInBom + 1) === $potentialBom);
} }

View file

@ -18,14 +18,14 @@ class ODS implements EscaperInterface
{ {
// @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as // @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as
// single/double quotes (for XML attributes) need to be encoded. // single/double quotes (for XML attributes) need to be encoded.
if (defined('ENT_DISALLOWED')) { if (\defined('ENT_DISALLOWED')) {
// 'ENT_DISALLOWED' ensures that invalid characters in the given document type are replaced. // 'ENT_DISALLOWED' ensures that invalid characters in the given document type are replaced.
// Otherwise control characters like a vertical tab "\v" will make the XML document unreadable by the XML processor // Otherwise control characters like a vertical tab "\v" will make the XML document unreadable by the XML processor
// @link https://github.com/box/spout/issues/329 // @link https://github.com/box/spout/issues/329
$replacedString = htmlspecialchars($string, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8'); $replacedString = \htmlspecialchars($string, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8');
} else { } else {
// We are on hhvm or any other engine that does not support ENT_DISALLOWED. // We are on hhvm or any other engine that does not support ENT_DISALLOWED.
$escapedString = htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); $escapedString = \htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
// control characters values are from 0 to 1F (hex values) in the ASCII table // control characters values are from 0 to 1F (hex values) in the ASCII table
// some characters should not be escaped though: "\t", "\r" and "\n". // some characters should not be escaped though: "\t", "\r" and "\n".
@ -34,7 +34,7 @@ class ODS implements EscaperInterface
'\x0B-\x0C' . '\x0B-\x0C' .
// skipping "\r" (0xD) // skipping "\r" (0xD)
'\x0E-\x1F]'; '\x0E-\x1F]';
$replacedString = preg_replace("/$regexPattern/", '<27>', $escapedString); $replacedString = \preg_replace("/$regexPattern/", '<27>', $escapedString);
} }
return $replacedString; return $replacedString;

View file

@ -28,7 +28,7 @@ class XLSX implements EscaperInterface
if (!$this->isAlreadyInitialized) { if (!$this->isAlreadyInitialized) {
$this->escapableControlCharactersPattern = $this->getEscapableControlCharactersPattern(); $this->escapableControlCharactersPattern = $this->getEscapableControlCharactersPattern();
$this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap(); $this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap();
$this->controlCharactersEscapingReverseMap = array_flip($this->controlCharactersEscapingMap); $this->controlCharactersEscapingReverseMap = \array_flip($this->controlCharactersEscapingMap);
$this->isAlreadyInitialized = true; $this->isAlreadyInitialized = true;
} }
@ -47,7 +47,7 @@ class XLSX implements EscaperInterface
$escapedString = $this->escapeControlCharacters($string); $escapedString = $this->escapeControlCharacters($string);
// @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as // @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as
// single/double quotes (for XML attributes) need to be encoded. // single/double quotes (for XML attributes) need to be encoded.
$escapedString = htmlspecialchars($escapedString, ENT_QUOTES, 'UTF-8'); $escapedString = \htmlspecialchars($escapedString, ENT_QUOTES, 'UTF-8');
return $escapedString; return $escapedString;
} }
@ -103,10 +103,10 @@ class XLSX implements EscaperInterface
// control characters values are from 0 to 1F (hex values) in the ASCII table // control characters values are from 0 to 1F (hex values) in the ASCII table
for ($charValue = 0x00; $charValue <= 0x1F; $charValue++) { for ($charValue = 0x00; $charValue <= 0x1F; $charValue++) {
$character = chr($charValue); $character = \chr($charValue);
if (preg_match("/{$this->escapableControlCharactersPattern}/", $character)) { if (\preg_match("/{$this->escapableControlCharactersPattern}/", $character)) {
$charHexValue = dechex($charValue); $charHexValue = \dechex($charValue);
$escapedChar = '_x' . sprintf('%04s', strtoupper($charHexValue)) . '_'; $escapedChar = '_x' . \sprintf('%04s', \strtoupper($charHexValue)) . '_';
$controlCharactersEscapingMap[$escapedChar] = $character; $controlCharactersEscapingMap[$escapedChar] = $character;
} }
} }
@ -132,11 +132,11 @@ class XLSX implements EscaperInterface
$escapedString = $this->escapeEscapeCharacter($string); $escapedString = $this->escapeEscapeCharacter($string);
// if no control characters // if no control characters
if (!preg_match("/{$this->escapableControlCharactersPattern}/", $escapedString)) { if (!\preg_match("/{$this->escapableControlCharactersPattern}/", $escapedString)) {
return $escapedString; return $escapedString;
} }
return preg_replace_callback("/({$this->escapableControlCharactersPattern})/", function ($matches) { return \preg_replace_callback("/({$this->escapableControlCharactersPattern})/", function ($matches) {
return $this->controlCharactersEscapingReverseMap[$matches[0]]; return $this->controlCharactersEscapingReverseMap[$matches[0]];
}, $escapedString); }, $escapedString);
} }
@ -149,7 +149,7 @@ class XLSX implements EscaperInterface
*/ */
protected function escapeEscapeCharacter($string) protected function escapeEscapeCharacter($string)
{ {
return preg_replace('/_(x[\dA-F]{4})_/', '_x005F_$1_', $string); return \preg_replace('/_(x[\dA-F]{4})_/', '_x005F_$1_', $string);
} }
/** /**
@ -171,7 +171,7 @@ class XLSX implements EscaperInterface
foreach ($this->controlCharactersEscapingMap as $escapedCharValue => $charValue) { foreach ($this->controlCharactersEscapingMap as $escapedCharValue => $charValue) {
// only unescape characters that don't contain the escaped escape character for now // only unescape characters that don't contain the escaped escape character for now
$unescapedString = preg_replace("/(?<!_x005F)($escapedCharValue)/", $charValue, $unescapedString); $unescapedString = \preg_replace("/(?<!_x005F)($escapedCharValue)/", $charValue, $unescapedString);
} }
return $this->unescapeEscapeCharacter($unescapedString); return $this->unescapeEscapeCharacter($unescapedString);
@ -185,6 +185,6 @@ class XLSX implements EscaperInterface
*/ */
protected function unescapeEscapeCharacter($string) protected function unescapeEscapeCharacter($string)
{ {
return preg_replace('/_x005F(_x[\dA-F]{4}_)/', '$1', $string); return \preg_replace('/_x005F(_x[\dA-F]{4}_)/', '$1', $string);
} }
} }

View file

@ -19,7 +19,7 @@ class FileSystemHelper implements FileSystemHelperInterface
*/ */
public function __construct($baseFolderPath) public function __construct($baseFolderPath)
{ {
$this->baseFolderRealPath = realpath($baseFolderPath); $this->baseFolderRealPath = \realpath($baseFolderPath);
} }
/** /**
@ -36,7 +36,7 @@ class FileSystemHelper implements FileSystemHelperInterface
$folderPath = $parentFolderPath . '/' . $folderName; $folderPath = $parentFolderPath . '/' . $folderName;
$wasCreationSuccessful = mkdir($folderPath, 0777, true); $wasCreationSuccessful = \mkdir($folderPath, 0777, true);
if (!$wasCreationSuccessful) { if (!$wasCreationSuccessful) {
throw new IOException("Unable to create folder: $folderPath"); throw new IOException("Unable to create folder: $folderPath");
} }
@ -60,7 +60,7 @@ class FileSystemHelper implements FileSystemHelperInterface
$filePath = $parentFolderPath . '/' . $fileName; $filePath = $parentFolderPath . '/' . $fileName;
$wasCreationSuccessful = file_put_contents($filePath, $fileContents); $wasCreationSuccessful = \file_put_contents($filePath, $fileContents);
if ($wasCreationSuccessful === false) { if ($wasCreationSuccessful === false) {
throw new IOException("Unable to create file: $filePath"); throw new IOException("Unable to create file: $filePath");
} }
@ -79,8 +79,8 @@ class FileSystemHelper implements FileSystemHelperInterface
{ {
$this->throwIfOperationNotInBaseFolder($filePath); $this->throwIfOperationNotInBaseFolder($filePath);
if (file_exists($filePath) && is_file($filePath)) { if (\file_exists($filePath) && \is_file($filePath)) {
unlink($filePath); \unlink($filePath);
} }
} }
@ -102,13 +102,13 @@ class FileSystemHelper implements FileSystemHelperInterface
foreach ($itemIterator as $item) { foreach ($itemIterator as $item) {
if ($item->isDir()) { if ($item->isDir()) {
rmdir($item->getPathname()); \rmdir($item->getPathname());
} else { } else {
unlink($item->getPathname()); \unlink($item->getPathname());
} }
} }
rmdir($folderPath); \rmdir($folderPath);
} }
/** /**
@ -122,8 +122,8 @@ class FileSystemHelper implements FileSystemHelperInterface
*/ */
protected function throwIfOperationNotInBaseFolder($operationFolderPath) protected function throwIfOperationNotInBaseFolder($operationFolderPath)
{ {
$operationFolderRealPath = realpath($operationFolderPath); $operationFolderRealPath = \realpath($operationFolderPath);
$isInBaseFolder = (strpos($operationFolderRealPath, $this->baseFolderRealPath) === 0); $isInBaseFolder = (\strpos($operationFolderRealPath, $this->baseFolderRealPath) === 0);
if (!$isInBaseFolder) { if (!$isInBaseFolder) {
throw new IOException("Cannot perform I/O operation outside of the base folder: {$this->baseFolderRealPath}"); throw new IOException("Cannot perform I/O operation outside of the base folder: {$this->baseFolderRealPath}");
} }

View file

@ -20,7 +20,7 @@ class GlobalFunctionsHelper
*/ */
public function fopen($fileName, $mode) public function fopen($fileName, $mode)
{ {
return fopen($fileName, $mode); return \fopen($fileName, $mode);
} }
/** /**
@ -33,7 +33,7 @@ class GlobalFunctionsHelper
*/ */
public function fgets($handle, $length = null) public function fgets($handle, $length = null)
{ {
return fgets($handle, $length); return \fgets($handle, $length);
} }
/** /**
@ -46,7 +46,7 @@ class GlobalFunctionsHelper
*/ */
public function fputs($handle, $string) public function fputs($handle, $string)
{ {
return fputs($handle, $string); return \fputs($handle, $string);
} }
/** /**
@ -58,7 +58,7 @@ class GlobalFunctionsHelper
*/ */
public function fflush($handle) public function fflush($handle)
{ {
return fflush($handle); return \fflush($handle);
} }
/** /**
@ -71,7 +71,7 @@ class GlobalFunctionsHelper
*/ */
public function fseek($handle, $offset) public function fseek($handle, $offset)
{ {
return fseek($handle, $offset); return \fseek($handle, $offset);
} }
/** /**
@ -90,9 +90,9 @@ class GlobalFunctionsHelper
// To fix that, simply disable the escape character. // To fix that, simply disable the escape character.
// @see https://bugs.php.net/bug.php?id=43225 // @see https://bugs.php.net/bug.php?id=43225
// @see http://tools.ietf.org/html/rfc4180 // @see http://tools.ietf.org/html/rfc4180
$escapeCharacter = "\0"; $escapeCharacter = PHP_VERSION_ID >= 70400 ? '' : "\0";
return fgetcsv($handle, $length, $delimiter, $enclosure, $escapeCharacter); return \fgetcsv($handle, $length, $delimiter, $enclosure, $escapeCharacter);
} }
/** /**
@ -111,9 +111,9 @@ class GlobalFunctionsHelper
// To fix that, simply disable the escape character. // To fix that, simply disable the escape character.
// @see https://bugs.php.net/bug.php?id=43225 // @see https://bugs.php.net/bug.php?id=43225
// @see http://tools.ietf.org/html/rfc4180 // @see http://tools.ietf.org/html/rfc4180
$escapeCharacter = "\0"; $escapeCharacter = PHP_VERSION_ID >= 70400 ? '' : "\0";
return fputcsv($handle, $fields, $delimiter, $enclosure, $escapeCharacter); return \fputcsv($handle, $fields, $delimiter, $enclosure, $escapeCharacter);
} }
/** /**
@ -126,7 +126,7 @@ class GlobalFunctionsHelper
*/ */
public function fwrite($handle, $string) public function fwrite($handle, $string)
{ {
return fwrite($handle, $string); return \fwrite($handle, $string);
} }
/** /**
@ -138,7 +138,7 @@ class GlobalFunctionsHelper
*/ */
public function fclose($handle) public function fclose($handle)
{ {
return fclose($handle); return \fclose($handle);
} }
/** /**
@ -150,7 +150,7 @@ class GlobalFunctionsHelper
*/ */
public function rewind($handle) public function rewind($handle)
{ {
return rewind($handle); return \rewind($handle);
} }
/** /**
@ -162,7 +162,7 @@ class GlobalFunctionsHelper
*/ */
public function file_exists($fileName) public function file_exists($fileName)
{ {
return file_exists($fileName); return \file_exists($fileName);
} }
/** /**
@ -176,7 +176,7 @@ class GlobalFunctionsHelper
{ {
$realFilePath = $this->convertToUseRealPath($filePath); $realFilePath = $this->convertToUseRealPath($filePath);
return file_get_contents($realFilePath); return \file_get_contents($realFilePath);
} }
/** /**
@ -191,13 +191,13 @@ class GlobalFunctionsHelper
$realFilePath = $filePath; $realFilePath = $filePath;
if ($this->isZipStream($filePath)) { if ($this->isZipStream($filePath)) {
if (preg_match('/zip:\/\/(.*)#(.*)/', $filePath, $matches)) { if (\preg_match('/zip:\/\/(.*)#(.*)/', $filePath, $matches)) {
$documentPath = $matches[1]; $documentPath = $matches[1];
$documentInsideZipPath = $matches[2]; $documentInsideZipPath = $matches[2];
$realFilePath = 'zip://' . realpath($documentPath) . '#' . $documentInsideZipPath; $realFilePath = 'zip://' . \realpath($documentPath) . '#' . $documentInsideZipPath;
} }
} else { } else {
$realFilePath = realpath($filePath); $realFilePath = \realpath($filePath);
} }
return $realFilePath; return $realFilePath;
@ -211,7 +211,7 @@ class GlobalFunctionsHelper
*/ */
protected function isZipStream($path) protected function isZipStream($path)
{ {
return (strpos($path, 'zip://') === 0); return (\strpos($path, 'zip://') === 0);
} }
/** /**
@ -223,7 +223,7 @@ class GlobalFunctionsHelper
*/ */
public function feof($handle) public function feof($handle)
{ {
return feof($handle); return \feof($handle);
} }
/** /**
@ -235,7 +235,7 @@ class GlobalFunctionsHelper
*/ */
public function is_readable($fileName) public function is_readable($fileName)
{ {
return is_readable($fileName); return \is_readable($fileName);
} }
/** /**
@ -248,7 +248,7 @@ class GlobalFunctionsHelper
*/ */
public function basename($path, $suffix = null) public function basename($path, $suffix = null)
{ {
return basename($path, $suffix); return \basename($path, $suffix);
} }
/** /**
@ -260,7 +260,7 @@ class GlobalFunctionsHelper
*/ */
public function header($string) public function header($string)
{ {
header($string); \header($string);
} }
/** /**
@ -271,8 +271,8 @@ class GlobalFunctionsHelper
*/ */
public function ob_end_clean() public function ob_end_clean()
{ {
if (ob_get_length() > 0) { if (\ob_get_length() > 0) {
ob_end_clean(); \ob_end_clean();
} }
} }
@ -287,7 +287,7 @@ class GlobalFunctionsHelper
*/ */
public function iconv($string, $sourceEncoding, $targetEncoding) public function iconv($string, $sourceEncoding, $targetEncoding)
{ {
return iconv($sourceEncoding, $targetEncoding, $string); return \iconv($sourceEncoding, $targetEncoding, $string);
} }
/** /**
@ -301,7 +301,7 @@ class GlobalFunctionsHelper
*/ */
public function mb_convert_encoding($string, $sourceEncoding, $targetEncoding) public function mb_convert_encoding($string, $sourceEncoding, $targetEncoding)
{ {
return mb_convert_encoding($string, $targetEncoding, $sourceEncoding); return \mb_convert_encoding($string, $targetEncoding, $sourceEncoding);
} }
/** /**
@ -312,7 +312,7 @@ class GlobalFunctionsHelper
*/ */
public function stream_get_wrappers() public function stream_get_wrappers()
{ {
return stream_get_wrappers(); return \stream_get_wrappers();
} }
/** /**
@ -324,6 +324,6 @@ class GlobalFunctionsHelper
*/ */
public function function_exists($functionName) public function function_exists($functionName)
{ {
return function_exists($functionName); return \function_exists($functionName);
} }
} }

View file

@ -18,7 +18,7 @@ class StringHelper
*/ */
public function __construct() public function __construct()
{ {
$this->hasMbstringSupport = extension_loaded('mbstring'); $this->hasMbstringSupport = \extension_loaded('mbstring');
} }
/** /**
@ -32,7 +32,7 @@ class StringHelper
*/ */
public function getStringLength($string) public function getStringLength($string)
{ {
return $this->hasMbstringSupport ? mb_strlen($string) : strlen($string); return $this->hasMbstringSupport ? \mb_strlen($string) : \strlen($string);
} }
/** /**
@ -47,7 +47,7 @@ class StringHelper
*/ */
public function getCharFirstOccurrencePosition($char, $string) public function getCharFirstOccurrencePosition($char, $string)
{ {
$position = $this->hasMbstringSupport ? mb_strpos($string, $char) : strpos($string, $char); $position = $this->hasMbstringSupport ? \mb_strpos($string, $char) : \strpos($string, $char);
return ($position !== false) ? $position : -1; return ($position !== false) ? $position : -1;
} }
@ -64,7 +64,7 @@ class StringHelper
*/ */
public function getCharLastOccurrencePosition($char, $string) public function getCharLastOccurrencePosition($char, $string)
{ {
$position = $this->hasMbstringSupport ? mb_strrpos($string, $char) : strrpos($string, $char); $position = $this->hasMbstringSupport ? \mb_strrpos($string, $char) : \strrpos($string, $char);
return ($position !== false) ? $position : -1; return ($position !== false) ? $position : -1;
} }

View file

@ -46,7 +46,7 @@ abstract class OptionsManagerAbstract implements OptionsManagerInterface
*/ */
public function setOption($optionName, $optionValue) public function setOption($optionName, $optionValue)
{ {
if (in_array($optionName, $this->supportedOptions)) { if (\in_array($optionName, $this->supportedOptions)) {
$this->options[$optionName] = $optionValue; $this->options[$optionName] = $optionValue;
} }
} }

View file

@ -89,7 +89,7 @@ class InternalEntityFactory implements InternalEntityFactoryInterface
*/ */
public function createRowFromArray(array $cellValues = []) public function createRowFromArray(array $cellValues = [])
{ {
$cells = array_map(function ($cellValue) { $cells = \array_map(function ($cellValue) {
return $this->createCell($cellValue); return $this->createCell($cellValue);
}, $cellValues); }, $cellValues);

View file

@ -84,8 +84,8 @@ class Reader extends ReaderAbstract
*/ */
protected function openReader($filePath) protected function openReader($filePath)
{ {
$this->originalAutoDetectLineEndings = ini_get('auto_detect_line_endings'); $this->originalAutoDetectLineEndings = \ini_get('auto_detect_line_endings');
ini_set('auto_detect_line_endings', '1'); \ini_set('auto_detect_line_endings', '1');
$this->filePointer = $this->globalFunctionsHelper->fopen($filePath, 'r'); $this->filePointer = $this->globalFunctionsHelper->fopen($filePath, 'r');
if (!$this->filePointer) { if (!$this->filePointer) {
@ -123,6 +123,6 @@ class Reader extends ReaderAbstract
$this->globalFunctionsHelper->fclose($this->filePointer); $this->globalFunctionsHelper->fclose($this->filePointer);
} }
ini_set('auto_detect_line_endings', $this->originalAutoDetectLineEndings); \ini_set('auto_detect_line_endings', $this->originalAutoDetectLineEndings);
} }
} }

View file

@ -147,7 +147,7 @@ class RowIterator implements IteratorInterface
if ($rowData !== false) { if ($rowData !== false) {
// str_replace will replace NULL values by empty strings // str_replace will replace NULL values by empty strings
$rowDataBufferAsArray = str_replace(null, null, $rowData); $rowDataBufferAsArray = \str_replace(null, null, $rowData);
$this->rowBuffer = $this->entityFactory->createRowFromArray($rowDataBufferAsArray); $this->rowBuffer = $this->entityFactory->createRowFromArray($rowDataBufferAsArray);
$this->numReadRows++; $this->numReadRows++;
} else { } else {
@ -193,13 +193,13 @@ class RowIterator implements IteratorInterface
case EncodingHelper::ENCODING_UTF16_LE: case EncodingHelper::ENCODING_UTF16_LE:
case EncodingHelper::ENCODING_UTF32_LE: case EncodingHelper::ENCODING_UTF32_LE:
// remove whitespace from the beginning of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data // remove whitespace from the beginning of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data
$cellValue = ltrim($cellValue); $cellValue = \ltrim($cellValue);
break; break;
case EncodingHelper::ENCODING_UTF16_BE: case EncodingHelper::ENCODING_UTF16_BE:
case EncodingHelper::ENCODING_UTF32_BE: case EncodingHelper::ENCODING_UTF32_BE:
// remove whitespace from the end of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data // remove whitespace from the end of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data
$cellValue = rtrim($cellValue); $cellValue = \rtrim($cellValue);
break; break;
} }
@ -215,7 +215,7 @@ class RowIterator implements IteratorInterface
*/ */
protected function isEmptyLine($lineData) protected function isEmptyLine($lineData)
{ {
return (is_array($lineData) && count($lineData) === 1 && $lineData[0] === null); return (\is_array($lineData) && \count($lineData) === 1 && $lineData[0] === null);
} }
/** /**

View file

@ -37,7 +37,7 @@ class ReaderFactory
*/ */
public static function createFromFile(string $path) public static function createFromFile(string $path)
{ {
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); $extension = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
return self::createFromType($extension); return self::createFromType($extension);
} }

View file

@ -47,12 +47,14 @@ class RowManager
*/ */
public function fillMissingIndexesWithEmptyCells(Row $row) public function fillMissingIndexesWithEmptyCells(Row $row)
{ {
if ($row->getNumCells() === 0) { $numCells = $row->getNumCells();
if ($numCells === 0) {
return $row; return $row;
} }
$rowCells = $row->getCells(); $rowCells = $row->getCells();
$maxCellIndex = max(array_keys($rowCells)); $maxCellIndex = $numCells;
for ($cellIndex = 0; $cellIndex < $maxCellIndex; $cellIndex++) { for ($cellIndex = 0; $cellIndex < $maxCellIndex; $cellIndex++) {
if (!isset($rowCells[$cellIndex])) { if (!isset($rowCells[$cellIndex])) {

View file

@ -73,7 +73,7 @@ class XMLProcessor
{ {
$callbackObject = $callback[0]; $callbackObject = $callback[0];
$callbackMethodName = $callback[1]; $callbackMethodName = $callback[1];
$reflectionMethod = new \ReflectionMethod(get_class($callbackObject), $callbackMethodName); $reflectionMethod = new \ReflectionMethod(\get_class($callbackObject), $callbackMethodName);
$reflectionMethod->setAccessible(true); $reflectionMethod->setAccessible(true);
return [ return [

View file

@ -22,9 +22,11 @@ class CellValueFormatter
/** Definition of XML nodes names used to parse data */ /** Definition of XML nodes names used to parse data */
const XML_NODE_P = 'p'; const XML_NODE_P = 'p';
const XML_NODE_S = 'text:s'; const XML_NODE_TEXT_A = 'text:a';
const XML_NODE_A = 'text:a'; const XML_NODE_TEXT_SPAN = 'text:span';
const XML_NODE_SPAN = 'text:span'; const XML_NODE_TEXT_S = 'text:s';
const XML_NODE_TEXT_TAB = 'text:tab';
const XML_NODE_TEXT_LINE_BREAK = 'text:line-break';
/** Definition of XML attributes used to parse data */ /** Definition of XML attributes used to parse data */
const XML_ATTRIBUTE_TYPE = 'office:value-type'; const XML_ATTRIBUTE_TYPE = 'office:value-type';
@ -41,6 +43,13 @@ class CellValueFormatter
/** @var \Box\Spout\Common\Helper\Escaper\ODS Used to unescape XML data */ /** @var \Box\Spout\Common\Helper\Escaper\ODS Used to unescape XML data */
protected $escaper; protected $escaper;
/** @var array List of XML nodes representing whitespaces and their corresponding value */
private static $WHITESPACE_XML_NODES = [
self::XML_NODE_TEXT_S => ' ',
self::XML_NODE_TEXT_TAB => "\t",
self::XML_NODE_TEXT_LINE_BREAK => "\n",
];
/** /**
* @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings * @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
* @param \Box\Spout\Common\Helper\Escaper\ODS $escaper Used to unescape XML data * @param \Box\Spout\Common\Helper\Escaper\ODS $escaper Used to unescape XML data
@ -96,29 +105,71 @@ class CellValueFormatter
$pNodes = $node->getElementsByTagName(self::XML_NODE_P); $pNodes = $node->getElementsByTagName(self::XML_NODE_P);
foreach ($pNodes as $pNode) { foreach ($pNodes as $pNode) {
$currentPValue = ''; $pNodeValues[] = $this->extractTextValueFromNode($pNode);
foreach ($pNode->childNodes as $childNode) {
if ($childNode instanceof \DOMText) {
$currentPValue .= $childNode->nodeValue;
} elseif ($childNode->nodeName === self::XML_NODE_S) {
$spaceAttribute = $childNode->getAttribute(self::XML_ATTRIBUTE_C);
$numSpaces = (!empty($spaceAttribute)) ? (int) $spaceAttribute : 1;
$currentPValue .= str_repeat(' ', $numSpaces);
} elseif ($childNode->nodeName === self::XML_NODE_A || $childNode->nodeName === self::XML_NODE_SPAN) {
$currentPValue .= $childNode->nodeValue;
}
}
$pNodeValues[] = $currentPValue;
} }
$escapedCellValue = implode("\n", $pNodeValues); $escapedCellValue = \implode("\n", $pNodeValues);
$cellValue = $this->escaper->unescape($escapedCellValue); $cellValue = $this->escaper->unescape($escapedCellValue);
return $cellValue; return $cellValue;
} }
/**
* @param $pNode
* @return string
*/
private function extractTextValueFromNode($pNode)
{
$textValue = '';
foreach ($pNode->childNodes as $childNode) {
if ($childNode instanceof \DOMText) {
$textValue .= $childNode->nodeValue;
} elseif ($this->isWhitespaceNode($childNode->nodeName)) {
$textValue .= $this->transformWhitespaceNode($childNode);
} elseif ($childNode->nodeName === self::XML_NODE_TEXT_A || $childNode->nodeName === self::XML_NODE_TEXT_SPAN) {
$textValue .= $this->extractTextValueFromNode($childNode);
}
}
return $textValue;
}
/**
* Returns whether the given node is a whitespace node. It must be one of these:
* - <text:s />
* - <text:tab />
* - <text:line-break />
*
* @param string $nodeName
* @return bool
*/
private function isWhitespaceNode($nodeName)
{
return isset(self::$WHITESPACE_XML_NODES[$nodeName]);
}
/**
* The "<text:p>" node can contain the string value directly
* or contain child elements. In this case, whitespaces contain in
* the child elements should be replaced by their XML equivalent:
* - space => <text:s />
* - tab => <text:tab />
* - line break => <text:line-break />
*
* @see https://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1415200_253892949
*
* @param \DOMNode $node The XML node representing a whitespace
* @return string The corresponding whitespace value
*/
private function transformWhitespaceNode($node)
{
$countAttribute = $node->getAttribute(self::XML_ATTRIBUTE_C); // only defined for "<text:s>"
$numWhitespaces = (!empty($countAttribute)) ? (int) $countAttribute : 1;
return \str_repeat(self::$WHITESPACE_XML_NODES[$node->nodeName], $numWhitespaces);
}
/** /**
* Returns the cell Numeric value from the given node. * Returns the cell Numeric value from the given node.
* *

View file

@ -145,7 +145,7 @@ abstract class ReaderAbstract implements ReaderInterface
} }
// Need to use realpath to fix "Can't open file" on some Windows setup // Need to use realpath to fix "Can't open file" on some Windows setup
return realpath($filePath); return \realpath($filePath);
} }
/** /**
@ -158,7 +158,7 @@ abstract class ReaderAbstract implements ReaderInterface
protected function getStreamWrapperScheme($filePath) protected function getStreamWrapperScheme($filePath)
{ {
$streamScheme = null; $streamScheme = null;
if (preg_match('/^(\w+):\/\//', $filePath, $matches)) { if (\preg_match('/^(\w+):\/\//', $filePath, $matches)) {
$streamScheme = $matches[1]; $streamScheme = $matches[1];
} }
@ -190,7 +190,7 @@ abstract class ReaderAbstract implements ReaderInterface
$streamScheme = $this->getStreamWrapperScheme($filePath); $streamScheme = $this->getStreamWrapperScheme($filePath);
return ($streamScheme !== null) ? return ($streamScheme !== null) ?
in_array($streamScheme, $this->globalFunctionsHelper->stream_get_wrappers()) : \in_array($streamScheme, $this->globalFunctionsHelper->stream_get_wrappers()) :
true; true;
} }

View file

@ -20,8 +20,8 @@ trait XMLInternalErrorsHelper
*/ */
protected function useXMLInternalErrors() protected function useXMLInternalErrors()
{ {
libxml_clear_errors(); \libxml_clear_errors();
$this->initialUseInternalErrorsValue = libxml_use_internal_errors(true); $this->initialUseInternalErrorsValue = \libxml_use_internal_errors(true);
} }
/** /**
@ -48,7 +48,7 @@ trait XMLInternalErrorsHelper
*/ */
private function hasXMLErrorOccured() private function hasXMLErrorOccured()
{ {
return (libxml_get_last_error() !== false); return (\libxml_get_last_error() !== false);
} }
/** /**
@ -60,10 +60,10 @@ trait XMLInternalErrorsHelper
private function getLastXMLErrorMessage() private function getLastXMLErrorMessage()
{ {
$errorMessage = null; $errorMessage = null;
$error = libxml_get_last_error(); $error = \libxml_get_last_error();
if ($error !== false) { if ($error !== false) {
$errorMessage = trim($error->message); $errorMessage = \trim($error->message);
} }
return $errorMessage; return $errorMessage;
@ -74,6 +74,6 @@ trait XMLInternalErrorsHelper
*/ */
protected function resetXMLInternalErrorsSetting() protected function resetXMLInternalErrorsSetting()
{ {
libxml_use_internal_errors($this->initialUseInternalErrorsValue); \libxml_use_internal_errors($this->initialUseInternalErrorsValue);
} }
} }

View file

@ -46,9 +46,9 @@ class XMLReader extends \XMLReader
public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath) public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath)
{ {
// The file path should not start with a '/', otherwise it won't be found // The file path should not start with a '/', otherwise it won't be found
$fileInsideZipPathWithoutLeadingSlash = ltrim($fileInsideZipPath, '/'); $fileInsideZipPathWithoutLeadingSlash = \ltrim($fileInsideZipPath, '/');
return (self::ZIP_WRAPPER . realpath($zipFilePath) . '#' . $fileInsideZipPathWithoutLeadingSlash); return (self::ZIP_WRAPPER . \realpath($zipFilePath) . '#' . $fileInsideZipPathWithoutLeadingSlash);
} }
/** /**
@ -62,7 +62,7 @@ class XMLReader extends \XMLReader
$doesFileExists = false; $doesFileExists = false;
$pattern = '/zip:\/\/([^#]+)#(.*)/'; $pattern = '/zip:\/\/([^#]+)#(.*)/';
if (preg_match($pattern, $zipStreamURI, $matches)) { if (\preg_match($pattern, $zipStreamURI, $matches)) {
$zipFilePath = $matches[1]; $zipFilePath = $matches[1];
$innerFilePath = $matches[2]; $innerFilePath = $matches[2];
@ -158,7 +158,7 @@ class XMLReader extends \XMLReader
// In some cases, the node has a prefix (for instance, "<sheet>" can also be "<x:sheet>"). // In some cases, the node has a prefix (for instance, "<sheet>" can also be "<x:sheet>").
// So if the given node name does not have a prefix, we need to look at the unprefixed name ("localName"). // So if the given node name does not have a prefix, we need to look at the unprefixed name ("localName").
// @see https://github.com/box/spout/issues/233 // @see https://github.com/box/spout/issues/233
$hasPrefix = (strpos($nodeName, ':') !== false); $hasPrefix = (\strpos($nodeName, ':') !== false);
$currentNodeName = ($hasPrefix) ? $this->name : $this->localName; $currentNodeName = ($hasPrefix) ? $this->name : $this->localName;
return ($this->nodeType === $nodeType && $currentNodeName === $nodeName); return ($this->nodeType === $nodeType && $currentNodeName === $nodeName);

View file

@ -37,7 +37,7 @@ class CellHelper
$columnIndex = 0; $columnIndex = 0;
// Remove row information // Remove row information
$columnLetters = preg_replace('/\d/', '', $cellIndex); $columnLetters = \preg_replace('/\d/', '', $cellIndex);
// strlen() is super slow too... Using isset() is way faster and not too unreadable, // strlen() is super slow too... Using isset() is way faster and not too unreadable,
// since we checked before that there are between 1 and 3 letters. // since we checked before that there are between 1 and 3 letters.
@ -75,6 +75,6 @@ class CellHelper
*/ */
protected static function isValidCellIndex($cellIndex) protected static function isValidCellIndex($cellIndex)
{ {
return (preg_match('/^[A-Z]{1,3}\d+$/', $cellIndex) === 1); return (\preg_match('/^[A-Z]{1,3}\d+$/', $cellIndex) === 1);
} }
} }

View file

@ -163,7 +163,7 @@ class CellValueFormatter
*/ */
protected function formatStrCellValue($nodeValue) protected function formatStrCellValue($nodeValue)
{ {
$escapedCellValue = trim($nodeValue); $escapedCellValue = \trim($nodeValue);
$cellValue = $this->escaper->unescape($escapedCellValue); $cellValue = $this->escaper->unescape($escapedCellValue);
return $cellValue; return $cellValue;
@ -248,8 +248,8 @@ class CellValueFormatter
$baseDate = $this->shouldUse1904Dates ? '1904-01-01' : '1899-12-30'; $baseDate = $this->shouldUse1904Dates ? '1904-01-01' : '1899-12-30';
$daysSinceBaseDate = (int) $nodeValue; $daysSinceBaseDate = (int) $nodeValue;
$timeRemainder = fmod($nodeValue, 1); $timeRemainder = \fmod($nodeValue, 1);
$secondsRemainder = round($timeRemainder * self::NUM_SECONDS_IN_ONE_DAY, 0); $secondsRemainder = \round($timeRemainder * self::NUM_SECONDS_IN_ONE_DAY, 0);
$dateObj = \DateTime::createFromFormat('|Y-m-d', $baseDate); $dateObj = \DateTime::createFromFormat('|Y-m-d', $baseDate);
$dateObj->modify('+' . $daysSinceBaseDate . 'days'); $dateObj->modify('+' . $daysSinceBaseDate . 'days');

View file

@ -62,13 +62,13 @@ class DateFormatHelper
// Remove brackets potentially present at the beginning of the format string // Remove brackets potentially present at the beginning of the format string
// and text portion of the format at the end of it (starting with ";") // and text portion of the format at the end of it (starting with ";")
// See §18.8.31 of ECMA-376 for more detail. // See §18.8.31 of ECMA-376 for more detail.
$dateFormat = preg_replace('/^(?:\[\$[^\]]+?\])?([^;]*).*/', '$1', $excelDateFormat); $dateFormat = \preg_replace('/^(?:\[\$[^\]]+?\])?([^;]*).*/', '$1', $excelDateFormat);
// Double quotes are used to escape characters that must not be interpreted. // Double quotes are used to escape characters that must not be interpreted.
// For instance, ["Day " dd] should result in "Day 13" and we should not try to interpret "D", "a", "y" // For instance, ["Day " dd] should result in "Day 13" and we should not try to interpret "D", "a", "y"
// By exploding the format string using double quote as a delimiter, we can get all parts // By exploding the format string using double quote as a delimiter, we can get all parts
// that must be transformed (even indexes) and all parts that must not be (odd indexes). // that must be transformed (even indexes) and all parts that must not be (odd indexes).
$dateFormatParts = explode('"', $dateFormat); $dateFormatParts = \explode('"', $dateFormat);
foreach ($dateFormatParts as $partIndex => $dateFormatPart) { foreach ($dateFormatParts as $partIndex => $dateFormatPart) {
// do not look at odd indexes // do not look at odd indexes
@ -77,19 +77,19 @@ class DateFormatHelper
} }
// Make sure all characters are lowercase, as the mapping table is using lowercase characters // Make sure all characters are lowercase, as the mapping table is using lowercase characters
$transformedPart = strtolower($dateFormatPart); $transformedPart = \strtolower($dateFormatPart);
// Remove escapes related to non-format characters // Remove escapes related to non-format characters
$transformedPart = str_replace('\\', '', $transformedPart); $transformedPart = \str_replace('\\', '', $transformedPart);
// Apply general transformation first... // Apply general transformation first...
$transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_GENERAL]); $transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_GENERAL]);
// ... then apply hour transformation, for 12-hour or 24-hour format // ... then apply hour transformation, for 12-hour or 24-hour format
if (self::has12HourFormatMarker($dateFormatPart)) { if (self::has12HourFormatMarker($dateFormatPart)) {
$transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_12]); $transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_12]);
} else { } else {
$transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_24]); $transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_24]);
} }
// overwrite the parts array with the new transformed part // overwrite the parts array with the new transformed part
@ -97,16 +97,16 @@ class DateFormatHelper
} }
// Merge all transformed parts back together // Merge all transformed parts back together
$phpDateFormat = implode('"', $dateFormatParts); $phpDateFormat = \implode('"', $dateFormatParts);
// Finally, to have the date format compatible with the DateTime::format() function, we need to escape // Finally, to have the date format compatible with the DateTime::format() function, we need to escape
// all characters that are inside double quotes (and double quotes must be removed). // all characters that are inside double quotes (and double quotes must be removed).
// For instance, ["Day " dd] should become [\D\a\y\ dd] // For instance, ["Day " dd] should become [\D\a\y\ dd]
$phpDateFormat = preg_replace_callback('/"(.+?)"/', function ($matches) { $phpDateFormat = \preg_replace_callback('/"(.+?)"/', function ($matches) {
$stringToEscape = $matches[1]; $stringToEscape = $matches[1];
$letters = preg_split('//u', $stringToEscape, -1, PREG_SPLIT_NO_EMPTY); $letters = \preg_split('//u', $stringToEscape, -1, PREG_SPLIT_NO_EMPTY);
return '\\' . implode('\\', $letters); return '\\' . \implode('\\', $letters);
}, $phpDateFormat); }, $phpDateFormat);
return $phpDateFormat; return $phpDateFormat;
@ -118,6 +118,6 @@ class DateFormatHelper
*/ */
private static function has12HourFormatMarker($excelDateFormat) private static function has12HourFormatMarker($excelDateFormat)
{ {
return (stripos($excelDateFormat, 'am/pm') !== false); return (\stripos($excelDateFormat, 'am/pm') !== false);
} }
} }

View file

@ -29,7 +29,7 @@ class OptionsManager extends OptionsManagerAbstract
*/ */
protected function setDefaultOptions() protected function setDefaultOptions()
{ {
$this->setOption(Options::TEMP_FOLDER, sys_get_temp_dir()); $this->setOption(Options::TEMP_FOLDER, \sys_get_temp_dir());
$this->setOption(Options::SHOULD_FORMAT_DATES, false); $this->setOption(Options::SHOULD_FORMAT_DATES, false);
$this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false); $this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false);
$this->setOption(Options::SHOULD_USE_1904_DATES, false); $this->setOption(Options::SHOULD_USE_1904_DATES, false);

View file

@ -103,14 +103,14 @@ class CachingStrategyFactory
protected function getMemoryLimitInKB() protected function getMemoryLimitInKB()
{ {
$memoryLimitFormatted = $this->getMemoryLimitFromIni(); $memoryLimitFormatted = $this->getMemoryLimitFromIni();
$memoryLimitFormatted = strtolower(trim($memoryLimitFormatted)); $memoryLimitFormatted = \strtolower(\trim($memoryLimitFormatted));
// No memory limit // No memory limit
if ($memoryLimitFormatted === '-1') { if ($memoryLimitFormatted === '-1') {
return -1; return -1;
} }
if (preg_match('/(\d+)([bkmgt])b?/', $memoryLimitFormatted, $matches)) { if (\preg_match('/(\d+)([bkmgt])b?/', $memoryLimitFormatted, $matches)) {
$amount = (int) ($matches[1]); $amount = (int) ($matches[1]);
$unit = $matches[2]; $unit = $matches[2];
@ -133,6 +133,6 @@ class CachingStrategyFactory
*/ */
protected function getMemoryLimitFromIni() protected function getMemoryLimitFromIni()
{ {
return ini_get('memory_limit'); return \ini_get('memory_limit');
} }
} }

View file

@ -55,7 +55,7 @@ class FileBasedStrategy implements CachingStrategyInterface
public function __construct($tempFolder, $maxNumStringsPerTempFile, $helperFactory) public function __construct($tempFolder, $maxNumStringsPerTempFile, $helperFactory)
{ {
$this->fileSystemHelper = $helperFactory->createFileSystemHelper($tempFolder); $this->fileSystemHelper = $helperFactory->createFileSystemHelper($tempFolder);
$this->tempFolder = $this->fileSystemHelper->createFolder($tempFolder, uniqid('sharedstrings')); $this->tempFolder = $this->fileSystemHelper->createFolder($tempFolder, \uniqid('sharedstrings'));
$this->maxNumStringsPerTempFile = $maxNumStringsPerTempFile; $this->maxNumStringsPerTempFile = $maxNumStringsPerTempFile;
@ -135,7 +135,7 @@ class FileBasedStrategy implements CachingStrategyInterface
// free memory // free memory
unset($this->inMemoryTempFileContents); unset($this->inMemoryTempFileContents);
$this->inMemoryTempFileContents = explode(PHP_EOL, $this->globalFunctionsHelper->file_get_contents($tempFilePath)); $this->inMemoryTempFileContents = \explode(PHP_EOL, $this->globalFunctionsHelper->file_get_contents($tempFilePath));
$this->inMemoryTempFilePath = $tempFilePath; $this->inMemoryTempFilePath = $tempFilePath;
} }
@ -151,7 +151,7 @@ class FileBasedStrategy implements CachingStrategyInterface
throw new SharedStringNotFoundException("Shared string not found for index: $sharedStringIndex"); throw new SharedStringNotFoundException("Shared string not found for index: $sharedStringIndex");
} }
return rtrim($sharedString, PHP_EOL); return \rtrim($sharedString, PHP_EOL);
} }
/** /**
@ -162,7 +162,7 @@ class FileBasedStrategy implements CachingStrategyInterface
*/ */
private function escapeLineFeed($unescapedString) private function escapeLineFeed($unescapedString)
{ {
return str_replace("\n", self::ESCAPED_LINE_FEED_CHARACTER, $unescapedString); return \str_replace("\n", self::ESCAPED_LINE_FEED_CHARACTER, $unescapedString);
} }
/** /**
@ -173,7 +173,7 @@ class FileBasedStrategy implements CachingStrategyInterface
*/ */
private function unescapeLineFeed($escapedString) private function unescapeLineFeed($escapedString)
{ {
return str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString); return \str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString);
} }
/** /**

View file

@ -190,7 +190,7 @@ class SharedStringsManager
$textNodeValue = $textNode->nodeValue; $textNodeValue = $textNode->nodeValue;
$shouldPreserveWhitespace = $this->shouldPreserveWhitespace($textNode); $shouldPreserveWhitespace = $this->shouldPreserveWhitespace($textNode);
$sharedStringValue .= ($shouldPreserveWhitespace) ? $textNodeValue : trim($textNodeValue); $sharedStringValue .= ($shouldPreserveWhitespace) ? $textNodeValue : \trim($textNodeValue);
} }
} }

View file

@ -116,7 +116,7 @@ class SheetManager
{ {
// Using "filter_var($x, FILTER_VALIDATE_BOOLEAN)" here because the value of the "date1904" attribute // Using "filter_var($x, FILTER_VALIDATE_BOOLEAN)" here because the value of the "date1904" attribute
// may be the string "false", that is not mapped to the boolean "false" by default... // may be the string "false", that is not mapped to the boolean "false" by default...
$shouldUse1904Dates = filter_var($xmlReader->getAttribute(self::XML_ATTRIBUTE_DATE_1904), FILTER_VALIDATE_BOOLEAN); $shouldUse1904Dates = \filter_var($xmlReader->getAttribute(self::XML_ATTRIBUTE_DATE_1904), FILTER_VALIDATE_BOOLEAN);
$this->optionsManager->setOption(Options::SHOULD_USE_1904_DATES, $shouldUse1904Dates); $this->optionsManager->setOption(Options::SHOULD_USE_1904_DATES, $shouldUse1904Dates);
return XMLProcessor::PROCESSING_CONTINUE; return XMLProcessor::PROCESSING_CONTINUE;
@ -211,7 +211,7 @@ class SheetManager
$sheetDataXMLFilePath = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TARGET); $sheetDataXMLFilePath = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TARGET);
// sometimes, the sheet data file path already contains "/xl/"... // sometimes, the sheet data file path already contains "/xl/"...
if (strpos($sheetDataXMLFilePath, '/xl/') !== 0) { if (\strpos($sheetDataXMLFilePath, '/xl/') !== 0) {
$sheetDataXMLFilePath = '/xl/' . $sheetDataXMLFilePath; $sheetDataXMLFilePath = '/xl/' . $sheetDataXMLFilePath;
break; break;
} }

View file

@ -48,7 +48,10 @@ class StyleManager
/** @var string Path of the XLSX file being read */ /** @var string Path of the XLSX file being read */
protected $filePath; protected $filePath;
/** @var string Path of the styles XML file */ /** @var bool Whether the XLSX file contains a styles XML file */
protected $hasStylesXMLFile;
/** @var string|null Path of the styles XML file */
protected $stylesXMLFilePath; protected $stylesXMLFilePath;
/** @var InternalEntityFactory Factory to create entities */ /** @var InternalEntityFactory Factory to create entities */
@ -75,8 +78,11 @@ class StyleManager
{ {
$this->filePath = $filePath; $this->filePath = $filePath;
$this->entityFactory = $entityFactory; $this->entityFactory = $entityFactory;
$this->builtinNumFmtIdIndicatingDates = array_keys(self::$builtinNumFmtIdToNumFormatMapping); $this->builtinNumFmtIdIndicatingDates = \array_keys(self::$builtinNumFmtIdToNumFormatMapping);
$this->stylesXMLFilePath = $workbookRelationshipsManager->getStylesXMLFilePath(); $this->hasStylesXMLFile = $workbookRelationshipsManager->hasStylesXMLFile();
if ($this->hasStylesXMLFile) {
$this->stylesXMLFilePath = $workbookRelationshipsManager->getStylesXMLFilePath();
}
} }
/** /**
@ -88,6 +94,10 @@ class StyleManager
*/ */
public function shouldFormatNumericValueAsDate($styleId) public function shouldFormatNumericValueAsDate($styleId)
{ {
if (!$this->hasStylesXMLFile) {
return false;
}
$stylesAttributes = $this->getStylesAttributes(); $stylesAttributes = $this->getStylesAttributes();
// Default style (0) does not format numeric values as timestamps. Only custom styles do. // Default style (0) does not format numeric values as timestamps. Only custom styles do.
@ -263,7 +273,7 @@ class StyleManager
*/ */
protected function isNumFmtIdBuiltInDateFormat($numFmtId) protected function isNumFmtIdBuiltInDateFormat($numFmtId)
{ {
return in_array($numFmtId, $this->builtinNumFmtIdIndicatingDates); return \in_array($numFmtId, $this->builtinNumFmtIdIndicatingDates);
} }
/** /**
@ -273,7 +283,7 @@ class StyleManager
protected function isFormatCodeCustomDateFormat($formatCode) protected function isFormatCodeCustomDateFormat($formatCode)
{ {
// if no associated format code or if using the default "General" format // if no associated format code or if using the default "General" format
if ($formatCode === null || strcasecmp($formatCode, self::NUMBER_FORMAT_GENERAL) === 0) { if ($formatCode === null || \strcasecmp($formatCode, self::NUMBER_FORMAT_GENERAL) === 0) {
return false; return false;
} }
@ -288,7 +298,7 @@ class StyleManager
{ {
// Remove extra formatting (what's between [ ], the brackets should not be preceded by a "\") // Remove extra formatting (what's between [ ], the brackets should not be preceded by a "\")
$pattern = '((?<!\\\)\[.+?(?<!\\\)\])'; $pattern = '((?<!\\\)\[.+?(?<!\\\)\])';
$formatCode = preg_replace($pattern, '', $formatCode); $formatCode = \preg_replace($pattern, '', $formatCode);
// custom date formats contain specific characters to represent the date: // custom date formats contain specific characters to represent the date:
// e - yy - m - d - h - s // e - yy - m - d - h - s
@ -300,7 +310,7 @@ class StyleManager
// character not preceded by "\" (case insensitive) // character not preceded by "\" (case insensitive)
$pattern = '/(?<!\\\)' . $dateFormatCharacter . '/i'; $pattern = '/(?<!\\\)' . $dateFormatCharacter . '/i';
if (preg_match($pattern, $formatCode)) { if (\preg_match($pattern, $formatCode)) {
$hasFoundDateFormatCharacter = true; $hasFoundDateFormatCharacter = true;
break; break;
} }

View file

@ -55,7 +55,7 @@ class WorkbookRelationshipsManager
$sharedStringsXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS]; $sharedStringsXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS];
// the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml") // the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
$doesContainBasePath = (strpos($sharedStringsXMLFilePath, self::BASE_PATH) !== false); $doesContainBasePath = (\strpos($sharedStringsXMLFilePath, self::BASE_PATH) !== false);
if (!$doesContainBasePath) { if (!$doesContainBasePath) {
// make sure we return an absolute file path // make sure we return an absolute file path
$sharedStringsXMLFilePath = self::BASE_PATH . $sharedStringsXMLFilePath; $sharedStringsXMLFilePath = self::BASE_PATH . $sharedStringsXMLFilePath;
@ -75,7 +75,17 @@ class WorkbookRelationshipsManager
} }
/** /**
* @return string|null The path of the styles XML file * @return bool Whether the XLSX file contains a styles XML file
*/
public function hasStylesXMLFile()
{
$workbookRelationships = $this->getWorkbookRelationships();
return isset($workbookRelationships[self::RELATIONSHIP_TYPE_STYLES]);
}
/**
* @return string The path of the styles XML file
*/ */
public function getStylesXMLFilePath() public function getStylesXMLFilePath()
{ {
@ -83,7 +93,7 @@ class WorkbookRelationshipsManager
$stylesXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES]; $stylesXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES];
// the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml") // the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
$doesContainBasePath = (strpos($stylesXMLFilePath, self::BASE_PATH) !== false); $doesContainBasePath = (\strpos($stylesXMLFilePath, self::BASE_PATH) !== false);
if (!$doesContainBasePath) { if (!$doesContainBasePath) {
// make sure we return a full path // make sure we return a full path
$stylesXMLFilePath = self::BASE_PATH . $stylesXMLFilePath; $stylesXMLFilePath = self::BASE_PATH . $stylesXMLFilePath;

View file

@ -127,7 +127,7 @@ class RowIterator implements IteratorInterface
*/ */
protected function normalizeSheetDataXMLFilePath($sheetDataXMLFilePath) protected function normalizeSheetDataXMLFilePath($sheetDataXMLFilePath)
{ {
return ltrim($sheetDataXMLFilePath, '/'); return \ltrim($sheetDataXMLFilePath, '/');
} }
/** /**
@ -234,7 +234,7 @@ class RowIterator implements IteratorInterface
{ {
// Read dimensions of the sheet // Read dimensions of the sheet
$dimensionRef = $xmlReader->getAttribute(self::XML_ATTRIBUTE_REF); // returns 'A1:M13' for instance (or 'A1' for empty sheet) $dimensionRef = $xmlReader->getAttribute(self::XML_ATTRIBUTE_REF); // returns 'A1:M13' for instance (or 'A1' for empty sheet)
if (preg_match('/[A-Z]+\d+:([A-Z]+\d+)/', $dimensionRef, $matches)) { if (\preg_match('/[A-Z]+\d+:([A-Z]+\d+)/', $dimensionRef, $matches)) {
$this->numColumns = CellHelper::getColumnIndexFromCellIndex($matches[1]) + 1; $this->numColumns = CellHelper::getColumnIndexFromCellIndex($matches[1]) + 1;
} }
@ -257,11 +257,11 @@ class RowIterator implements IteratorInterface
$numberOfColumnsForRow = $this->numColumns; $numberOfColumnsForRow = $this->numColumns;
$spans = $xmlReader->getAttribute(self::XML_ATTRIBUTE_SPANS); // returns '1:5' for instance $spans = $xmlReader->getAttribute(self::XML_ATTRIBUTE_SPANS); // returns '1:5' for instance
if ($spans) { if ($spans) {
list(, $numberOfColumnsForRow) = explode(':', $spans); list(, $numberOfColumnsForRow) = \explode(':', $spans);
$numberOfColumnsForRow = (int) $numberOfColumnsForRow; $numberOfColumnsForRow = (int) $numberOfColumnsForRow;
} }
$cells = array_fill(0, $numberOfColumnsForRow, $this->entityFactory->createCell('')); $cells = \array_fill(0, $numberOfColumnsForRow, $this->entityFactory->createCell(''));
$this->currentlyProcessedRow->setCells($cells); $this->currentlyProcessedRow->setCells($cells);
return XMLProcessor::PROCESSING_CONTINUE; return XMLProcessor::PROCESSING_CONTINUE;

View file

@ -27,7 +27,7 @@ class SheetIterator implements IteratorInterface
// Fetch all available sheets // Fetch all available sheets
$this->sheets = $sheetManager->getSheets(); $this->sheets = $sheetManager->getSheets();
if (count($this->sheets) === 0) { if (\count($this->sheets) === 0) {
throw new NoSheetsFoundException('The file must contain at least one sheet.'); throw new NoSheetsFoundException('The file must contain at least one sheet.');
} }
} }
@ -51,7 +51,7 @@ class SheetIterator implements IteratorInterface
*/ */
public function valid() public function valid()
{ {
return ($this->currentSheetIndex < count($this->sheets)); return ($this->currentSheetIndex < \count($this->sheets));
} }
/** /**

View file

@ -3,7 +3,9 @@
namespace Box\Spout\Writer\Common\Creator\Style; namespace Box\Spout\Writer\Common\Creator\Style;
use Box\Spout\Common\Entity\Style\Border; use Box\Spout\Common\Entity\Style\Border;
use Box\Spout\Common\Entity\Style\CellAlignment;
use Box\Spout\Common\Entity\Style\Style; use Box\Spout\Common\Entity\Style\Style;
use Box\Spout\Common\Exception\InvalidArgumentException;
/** /**
* Class StyleBuilder * Class StyleBuilder
@ -122,6 +124,25 @@ class StyleBuilder
return $this; return $this;
} }
/**
* Sets the cell alignment.
*
* @param string $cellAlignment The cell alignment
*
* @throws InvalidArgumentException If the given cell alignment is not valid
* @return StyleBuilder
*/
public function setCellAlignment($cellAlignment)
{
if (!CellAlignment::isValid($cellAlignment)) {
throw new InvalidArgumentException('Invalid cell alignment value');
}
$this->style->setCellAlignment($cellAlignment);
return $this;
}
/** /**
* Set a border * Set a border
* *
@ -148,6 +169,20 @@ class StyleBuilder
return $this; return $this;
} }
/**
* Sets a format
*
* @param string $format Format
* @return StyleBuilder
* @api
*/
public function setFormat($format)
{
$this->style->setFormat($format);
return $this;
}
/** /**
* Returns the configured style. The style is cached and can be reused. * Returns the configured style. The style is cached and can be reused.
* *

View file

@ -98,7 +98,7 @@ class WriterEntityFactory
*/ */
public static function createRowFromArray(array $cellValues = [], Style $rowStyle = null) public static function createRowFromArray(array $cellValues = [], Style $rowStyle = null)
{ {
$cells = array_map(function ($cellValue) { $cells = \array_map(function ($cellValue) {
return new Cell($cellValue); return new Cell($cellValue);
}, $cellValues); }, $cellValues);

View file

@ -35,7 +35,7 @@ class WriterFactory
*/ */
public static function createFromFile(string $path) public static function createFromFile(string $path)
{ {
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); $extension = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
return self::createFromType($extension); return self::createFromType($extension);
} }

View file

@ -19,7 +19,7 @@ class Workbook
*/ */
public function __construct() public function __construct()
{ {
$this->internalId = uniqid(); $this->internalId = \uniqid();
} }
/** /**

View file

@ -8,38 +8,39 @@ namespace Box\Spout\Writer\Common\Helper;
*/ */
class CellHelper class CellHelper
{ {
/** @var array Cache containing the mapping column index => cell index */ /** @var array Cache containing the mapping column index => column letters */
private static $columnIndexToCellIndexCache = []; private static $columnIndexToColumnLettersCache = [];
/** /**
* Returns the cell index (base 26) associated to the base 10 column index. * Returns the column letters (base 26) associated to the base 10 column index.
* Excel uses A to Z letters for column indexing, where A is the 1st column, * Excel uses A to Z letters for column indexing, where A is the 1st column,
* Z is the 26th and AA is the 27th. * Z is the 26th and AA is the 27th.
* The mapping is zero based, so that 0 maps to A, B maps to 1, Z to 25 and AA to 26. * The mapping is zero based, so that 0 maps to A, B maps to 1, Z to 25 and AA to 26.
* *
* @param int $columnIndex The Excel column index (0, 42, ...) * @param int $columnIndexZeroBased The Excel column index (0, 42, ...)
*
* @return string The associated cell index ('A', 'BC', ...) * @return string The associated cell index ('A', 'BC', ...)
*/ */
public static function getCellIndexFromColumnIndex($columnIndex) public static function getColumnLettersFromColumnIndex($columnIndexZeroBased)
{ {
$originalColumnIndex = $columnIndex; $originalColumnIndex = $columnIndexZeroBased;
// Using isset here because it is way faster than array_key_exists... // Using isset here because it is way faster than array_key_exists...
if (!isset(self::$columnIndexToCellIndexCache[$originalColumnIndex])) { if (!isset(self::$columnIndexToColumnLettersCache[$originalColumnIndex])) {
$cellIndex = ''; $columnLetters = '';
$capitalAAsciiValue = ord('A'); $capitalAAsciiValue = \ord('A');
do { do {
$modulus = $columnIndex % 26; $modulus = $columnIndexZeroBased % 26;
$cellIndex = chr($capitalAAsciiValue + $modulus) . $cellIndex; $columnLetters = \chr($capitalAAsciiValue + $modulus) . $columnLetters;
// substracting 1 because it's zero-based // substracting 1 because it's zero-based
$columnIndex = (int) ($columnIndex / 26) - 1; $columnIndexZeroBased = (int) ($columnIndexZeroBased / 26) - 1;
} while ($columnIndex >= 0); } while ($columnIndexZeroBased >= 0);
self::$columnIndexToCellIndexCache[$originalColumnIndex] = $cellIndex; self::$columnIndexToColumnLettersCache[$originalColumnIndex] = $columnLetters;
} }
return self::$columnIndexToCellIndexCache[$originalColumnIndex]; return self::$columnIndexToColumnLettersCache[$originalColumnIndex];
} }
} }

View file

@ -135,7 +135,7 @@ class ZipHelper
public static function canChooseCompressionMethod() public static function canChooseCompressionMethod()
{ {
// setCompressionName() is a PHP7+ method... // setCompressionName() is a PHP7+ method...
return (method_exists(new \ZipArchive(), 'setCompressionName')); return (\method_exists(new \ZipArchive(), 'setCompressionName'));
} }
/** /**
@ -151,7 +151,7 @@ class ZipHelper
foreach ($itemIterator as $itemInfo) { foreach ($itemIterator as $itemInfo) {
$itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname()); $itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname());
$itemLocalPath = str_replace($folderRealPath, '', $itemRealPath); $itemLocalPath = \str_replace($folderRealPath, '', $itemRealPath);
if ($itemInfo->isFile() && !$this->shouldSkipFile($zip, $itemLocalPath, $existingFileMode)) { if ($itemInfo->isFile() && !$this->shouldSkipFile($zip, $itemLocalPath, $existingFileMode)) {
$zip->addFile($itemRealPath, $itemLocalPath); $zip->addFile($itemRealPath, $itemLocalPath);
@ -181,9 +181,9 @@ class ZipHelper
*/ */
protected function getNormalizedRealPath($path) protected function getNormalizedRealPath($path)
{ {
$realPath = realpath($path); $realPath = \realpath($path);
return str_replace(DIRECTORY_SEPARATOR, '/', $realPath); return \str_replace(DIRECTORY_SEPARATOR, '/', $realPath);
} }
/** /**
@ -210,8 +210,8 @@ class ZipHelper
*/ */
protected function copyZipToStream($zipFilePath, $pointer) protected function copyZipToStream($zipFilePath, $pointer)
{ {
$zipFilePointer = fopen($zipFilePath, 'r'); $zipFilePointer = \fopen($zipFilePath, 'r');
stream_copy_to_stream($zipFilePointer, $pointer); \stream_copy_to_stream($zipFilePointer, $pointer);
fclose($zipFilePointer); \fclose($zipFilePointer);
} }
} }

View file

@ -45,8 +45,8 @@ class SheetManager
*/ */
public function throwIfNameIsInvalid($name, Sheet $sheet) public function throwIfNameIsInvalid($name, Sheet $sheet)
{ {
if (!is_string($name)) { if (!\is_string($name)) {
$actualType = gettype($name); $actualType = \gettype($name);
$errorMessage = "The sheet's name is invalid. It must be a string ($actualType given)."; $errorMessage = "The sheet's name is invalid. It must be a string ($actualType given).";
throw new InvalidSheetNameException($errorMessage); throw new InvalidSheetNameException($errorMessage);
} }
@ -74,9 +74,9 @@ class SheetManager
} }
} }
if (count($failedRequirements) !== 0) { if (\count($failedRequirements) !== 0) {
$errorMessage = "The sheet's name (\"$name\") is invalid. It did not respect these rules:\n - "; $errorMessage = "The sheet's name (\"$name\") is invalid. It did not respect these rules:\n - ";
$errorMessage .= implode("\n - ", $failedRequirements); $errorMessage .= \implode("\n - ", $failedRequirements);
throw new InvalidSheetNameException($errorMessage); throw new InvalidSheetNameException($errorMessage);
} }
} }
@ -90,7 +90,7 @@ class SheetManager
*/ */
private function doesContainInvalidCharacters($name) private function doesContainInvalidCharacters($name)
{ {
return (str_replace(self::$INVALID_CHARACTERS_IN_SHEET_NAME, '', $name) !== $name); return (\str_replace(self::$INVALID_CHARACTERS_IN_SHEET_NAME, '', $name) !== $name);
} }
/** /**

View file

@ -80,7 +80,7 @@ class StyleManager implements StyleManagerInterface
return $cellStyle; return $cellStyle;
} }
if ($cell->isString() && strpos($cell->getValue(), "\n") !== false) { if ($cell->isString() && \strpos($cell->getValue(), "\n") !== false) {
$cellStyle->setShouldWrapText(); $cellStyle->setShouldWrapText();
} }

View file

@ -85,9 +85,15 @@ class StyleMerger
if (!$style->hasSetWrapText() && $baseStyle->shouldWrapText()) { if (!$style->hasSetWrapText() && $baseStyle->shouldWrapText()) {
$styleToUpdate->setShouldWrapText(); $styleToUpdate->setShouldWrapText();
} }
if (!$style->hasSetCellAlignment() && $baseStyle->shouldApplyCellAlignment()) {
$styleToUpdate->setCellAlignment($baseStyle->getCellAlignment());
}
if (!$style->getBorder() && $baseStyle->shouldApplyBorder()) { if (!$style->getBorder() && $baseStyle->shouldApplyBorder()) {
$styleToUpdate->setBorder($baseStyle->getBorder()); $styleToUpdate->setBorder($baseStyle->getBorder());
} }
if (!$style->getFormat() && $baseStyle->shouldApplyFormat()) {
$styleToUpdate->setFormat($baseStyle->getFormat());
}
if (!$style->shouldApplyBackgroundColor() && $baseStyle->shouldApplyBackgroundColor()) { if (!$style->shouldApplyBackgroundColor() && $baseStyle->shouldApplyBackgroundColor()) {
$styleToUpdate->setBackgroundColor($baseStyle->getBackgroundColor()); $styleToUpdate->setBackgroundColor($baseStyle->getBackgroundColor());
} }

View file

@ -37,7 +37,7 @@ class StyleRegistry
$serializedStyle = $this->serialize($style); $serializedStyle = $this->serialize($style);
if (!$this->hasStyleAlreadyBeenRegistered($style)) { if (!$this->hasStyleAlreadyBeenRegistered($style)) {
$nextStyleId = count($this->serializedStyleToStyleIdMappingTable); $nextStyleId = \count($this->serializedStyleToStyleIdMappingTable);
$style->setId($nextStyleId); $style->setId($nextStyleId);
$this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId; $this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId;
@ -79,7 +79,7 @@ class StyleRegistry
*/ */
public function getRegisteredStyles() public function getRegisteredStyles()
{ {
return array_values($this->styleIdToStyleMappingTable); return \array_values($this->styleIdToStyleMappingTable);
} }
/** /**
@ -105,7 +105,7 @@ class StyleRegistry
$currentId = $style->getId(); $currentId = $style->getId();
$style->setId(0); $style->setId(0);
$serializedStyle = serialize($style); $serializedStyle = \serialize($style);
$style->setId($currentId); $style->setId($currentId);

View file

@ -124,7 +124,7 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
{ {
$worksheets = $this->getWorksheets(); $worksheets = $this->getWorksheets();
$newSheetIndex = count($worksheets); $newSheetIndex = \count($worksheets);
$sheetManager = $this->managerFactory->createSheetManager(); $sheetManager = $this->managerFactory->createSheetManager();
$sheet = $this->entityFactory->createSheet($newSheetIndex, $this->workbook->getInternalId(), $sheetManager); $sheet = $this->entityFactory->createSheet($newSheetIndex, $this->workbook->getInternalId(), $sheetManager);
@ -260,7 +260,7 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
// update max num columns for the worksheet // update max num columns for the worksheet
$currentMaxNumColumns = $worksheet->getMaxNumColumns(); $currentMaxNumColumns = $worksheet->getMaxNumColumns();
$cellsCount = $row->getNumCells(); $cellsCount = $row->getNumCells();
$worksheet->setMaxNumColumns(max($currentMaxNumColumns, $cellsCount)); $worksheet->setMaxNumColumns(\max($currentMaxNumColumns, $cellsCount));
} }
/** /**

View file

@ -11,6 +11,6 @@ class InvalidNameException extends WriterException
{ {
$msg = '%s is not a valid name identifier for a border. Valid identifiers are: %s.'; $msg = '%s is not a valid name identifier for a border. Valid identifiers are: %s.';
parent::__construct(sprintf($msg, $name, implode(',', BorderPart::getAllowedNames()))); parent::__construct(\sprintf($msg, $name, \implode(',', BorderPart::getAllowedNames())));
} }
} }

View file

@ -11,6 +11,6 @@ class InvalidStyleException extends WriterException
{ {
$msg = '%s is not a valid style identifier for a border. Valid identifiers are: %s.'; $msg = '%s is not a valid style identifier for a border. Valid identifiers are: %s.';
parent::__construct(sprintf($msg, $name, implode(',', BorderPart::getAllowedStyles()))); parent::__construct(\sprintf($msg, $name, \implode(',', BorderPart::getAllowedStyles())));
} }
} }

View file

@ -11,6 +11,6 @@ class InvalidWidthException extends WriterException
{ {
$msg = '%s is not a valid width identifier for a border. Valid identifiers are: %s.'; $msg = '%s is not a valid width identifier for a border. Valid identifiers are: %s.';
parent::__construct(sprintf($msg, $name, implode(',', BorderPart::getAllowedWidths()))); parent::__construct(\sprintf($msg, $name, \implode(',', BorderPart::getAllowedWidths())));
} }
} }

View file

@ -53,14 +53,14 @@ class BorderHelper
$definition = 'fo:border-%s="%s"'; $definition = 'fo:border-%s="%s"';
if ($borderPart->getStyle() === Border::STYLE_NONE) { if ($borderPart->getStyle() === Border::STYLE_NONE) {
$borderPartDefinition = sprintf($definition, $borderPart->getName(), 'none'); $borderPartDefinition = \sprintf($definition, $borderPart->getName(), 'none');
} else { } else {
$attributes = [ $attributes = [
self::$widthMap[$borderPart->getWidth()], self::$widthMap[$borderPart->getWidth()],
self::$styleMap[$borderPart->getStyle()], self::$styleMap[$borderPart->getStyle()],
'#' . $borderPart->getColor(), '#' . $borderPart->getColor(),
]; ];
$borderPartDefinition = sprintf($definition, $borderPart->getName(), implode(' ', $attributes)); $borderPartDefinition = \sprintf($definition, $borderPart->getName(), \implode(' ', $attributes));
} }
return $borderPartDefinition; return $borderPartDefinition;

View file

@ -89,7 +89,7 @@ class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper impleme
*/ */
protected function createRootFolder() protected function createRootFolder()
{ {
$this->rootFolder = $this->createFolder($this->baseFolderRealPath, uniqid('ods')); $this->rootFolder = $this->createFolder($this->baseFolderRealPath, \uniqid('ods'));
return $this; return $this;
} }
@ -210,22 +210,22 @@ EOD;
// Append sheets content to "content.xml" // Append sheets content to "content.xml"
$contentXmlFilePath = $this->rootFolder . '/' . self::CONTENT_XML_FILE_NAME; $contentXmlFilePath = $this->rootFolder . '/' . self::CONTENT_XML_FILE_NAME;
$contentXmlHandle = fopen($contentXmlFilePath, 'a'); $contentXmlHandle = \fopen($contentXmlFilePath, 'a');
foreach ($worksheets as $worksheet) { foreach ($worksheets as $worksheet) {
// write the "<table:table>" node, with the final sheet's name // write the "<table:table>" node, with the final sheet's name
fwrite($contentXmlHandle, $worksheetManager->getTableElementStartAsString($worksheet)); \fwrite($contentXmlHandle, $worksheetManager->getTableElementStartAsString($worksheet));
$worksheetFilePath = $worksheet->getFilePath(); $worksheetFilePath = $worksheet->getFilePath();
$this->copyFileContentsToTarget($worksheetFilePath, $contentXmlHandle); $this->copyFileContentsToTarget($worksheetFilePath, $contentXmlHandle);
fwrite($contentXmlHandle, '</table:table>'); \fwrite($contentXmlHandle, '</table:table>');
} }
$contentXmlFileContents = '</office:spreadsheet></office:body></office:document-content>'; $contentXmlFileContents = '</office:spreadsheet></office:body></office:document-content>';
fwrite($contentXmlHandle, $contentXmlFileContents); \fwrite($contentXmlHandle, $contentXmlFileContents);
fclose($contentXmlHandle); \fclose($contentXmlHandle);
return $this; return $this;
} }
@ -241,9 +241,9 @@ EOD;
*/ */
protected function copyFileContentsToTarget($sourceFilePath, $targetResource) protected function copyFileContentsToTarget($sourceFilePath, $targetResource)
{ {
$sourceHandle = fopen($sourceFilePath, 'r'); $sourceHandle = \fopen($sourceFilePath, 'r');
stream_copy_to_stream($sourceHandle, $targetResource); \stream_copy_to_stream($sourceHandle, $targetResource);
fclose($sourceHandle); \fclose($sourceHandle);
} }
/** /**

View file

@ -42,7 +42,7 @@ class OptionsManager extends OptionsManagerAbstract
*/ */
protected function setDefaultOptions() protected function setDefaultOptions()
{ {
$this->setOption(Options::TEMP_FOLDER, sys_get_temp_dir()); $this->setOption(Options::TEMP_FOLDER, \sys_get_temp_dir());
$this->setOption(Options::DEFAULT_ROW_STYLE, $this->styleBuilder->build()); $this->setOption(Options::DEFAULT_ROW_STYLE, $this->styleBuilder->build());
$this->setOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true); $this->setOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true);
} }

View file

@ -3,6 +3,7 @@
namespace Box\Spout\Writer\ODS\Manager\Style; namespace Box\Spout\Writer\ODS\Manager\Style;
use Box\Spout\Common\Entity\Style\BorderPart; use Box\Spout\Common\Entity\Style\BorderPart;
use Box\Spout\Common\Entity\Style\CellAlignment;
use Box\Spout\Writer\Common\Entity\Worksheet; use Box\Spout\Writer\Common\Entity\Worksheet;
use Box\Spout\Writer\ODS\Helper\BorderHelper; use Box\Spout\Writer\ODS\Helper\BorderHelper;
@ -199,6 +200,7 @@ EOD;
$content = '<style:style style:data-style-name="N0" style:family="table-cell" style:name="ce' . $styleIndex . '" style:parent-style-name="Default">'; $content = '<style:style style:data-style-name="N0" style:family="table-cell" style:name="ce' . $styleIndex . '" style:parent-style-name="Default">';
$content .= $this->getTextPropertiesSectionContent($style); $content .= $this->getTextPropertiesSectionContent($style);
$content .= $this->getParagraphPropertiesSectionContent($style);
$content .= $this->getTableCellPropertiesSectionContent($style); $content .= $this->getTableCellPropertiesSectionContent($style);
$content .= '</style:style>'; $content .= '</style:style>';
@ -214,26 +216,26 @@ EOD;
*/ */
private function getTextPropertiesSectionContent($style) private function getTextPropertiesSectionContent($style)
{ {
$content = ''; if (!$style->shouldApplyFont()) {
return '';
if ($style->shouldApplyFont()) {
$content .= $this->getFontSectionContent($style);
} }
return $content; return '<style:text-properties '
. $this->getFontSectionContent($style)
. '/>';
} }
/** /**
* Returns the contents of the "<style:text-properties>" section, inside "<style:style>" section * Returns the contents of the fonts definition section, inside "<style:text-properties>" section
* *
* @param \Box\Spout\Common\Entity\Style\Style $style * @param \Box\Spout\Common\Entity\Style\Style $style
*
* @return string * @return string
*/ */
private function getFontSectionContent($style) private function getFontSectionContent($style)
{ {
$defaultStyle = $this->getDefaultStyle(); $defaultStyle = $this->getDefaultStyle();
$content = '';
$content = '<style:text-properties';
$fontColor = $style->getFontColor(); $fontColor = $style->getFontColor();
if ($fontColor !== $defaultStyle->getFontColor()) { if ($fontColor !== $defaultStyle->getFontColor()) {
@ -263,11 +265,60 @@ EOD;
$content .= ' style:text-line-through-style="solid"'; $content .= ' style:text-line-through-style="solid"';
} }
$content .= '/>';
return $content; return $content;
} }
/**
* Returns the contents of the "<style:paragraph-properties>" section, inside "<style:style>" section
*
* @param \Box\Spout\Common\Entity\Style\Style $style
*
* @return string
*/
private function getParagraphPropertiesSectionContent($style)
{
if (!$style->shouldApplyCellAlignment()) {
return '';
}
return '<style:paragraph-properties '
. $this->getCellAlignmentSectionContent($style)
. '/>';
}
/**
* Returns the contents of the cell alignment definition for the "<style:paragraph-properties>" section
*
* @param \Box\Spout\Common\Entity\Style\Style $style
*
* @return string
*/
private function getCellAlignmentSectionContent($style)
{
return \sprintf(
' fo:text-align="%s" ',
$this->transformCellAlignment($style->getCellAlignment())
);
}
/**
* Even though "left" and "right" alignments are part of the spec, and interpreted
* respectively as "start" and "end", using the recommended values increase compatibility
* with software that will read the created ODS file.
*
* @param string $cellAlignment
*
* @return string
*/
private function transformCellAlignment($cellAlignment)
{
switch ($cellAlignment) {
case CellAlignment::LEFT: return 'start';
case CellAlignment::RIGHT: return 'end';
default: return $cellAlignment;
}
}
/** /**
* Returns the contents of the "<style:table-cell-properties>" section, inside "<style:style>" section * Returns the contents of the "<style:table-cell-properties>" section, inside "<style:style>" section
* *
@ -276,7 +327,7 @@ EOD;
*/ */
private function getTableCellPropertiesSectionContent($style) private function getTableCellPropertiesSectionContent($style)
{ {
$content = ''; $content = '<style:table-cell-properties ';
if ($style->shouldWrapText()) { if ($style->shouldWrapText()) {
$content .= $this->getWrapTextXMLContent(); $content .= $this->getWrapTextXMLContent();
@ -290,6 +341,8 @@ EOD;
$content .= $this->getBackgroundColorXMLContent($style); $content .= $this->getBackgroundColorXMLContent($style);
} }
$content .= '/>';
return $content; return $content;
} }
@ -300,7 +353,7 @@ EOD;
*/ */
private function getWrapTextXMLContent() private function getWrapTextXMLContent()
{ {
return '<style:table-cell-properties fo:wrap-option="wrap" style:vertical-align="automatic"/>'; return ' fo:wrap-option="wrap" style:vertical-align="automatic" ';
} }
/** /**
@ -311,13 +364,11 @@ EOD;
*/ */
private function getBorderXMLContent($style) private function getBorderXMLContent($style)
{ {
$borderProperty = '<style:table-cell-properties %s />'; $borders = \array_map(function (BorderPart $borderPart) {
$borders = array_map(function (BorderPart $borderPart) {
return BorderHelper::serializeBorderPart($borderPart); return BorderHelper::serializeBorderPart($borderPart);
}, $style->getBorder()->getParts()); }, $style->getBorder()->getParts());
return sprintf($borderProperty, implode(' ', $borders)); return \sprintf(' %s ', \implode(' ', $borders));
} }
/** /**
@ -328,9 +379,6 @@ EOD;
*/ */
private function getBackgroundColorXMLContent($style) private function getBackgroundColorXMLContent($style)
{ {
return sprintf( return \sprintf(' fo:background-color="#%s" ', $style->getBackgroundColor());
'<style:table-cell-properties fo:background-color="#%s"/>',
$style->getBackgroundColor()
);
} }
} }

View file

@ -33,6 +33,6 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
*/ */
public function getUsedFonts() public function getUsedFonts()
{ {
return array_keys($this->usedFontsSet); return \array_keys($this->usedFontsSet);
} }
} }

View file

@ -56,7 +56,7 @@ class WorkbookManager extends WorkbookManagerAbstract
protected function writeAllFilesToDiskAndZipThem($finalFilePointer) protected function writeAllFilesToDiskAndZipThem($finalFilePointer)
{ {
$worksheets = $this->getWorksheets(); $worksheets = $this->getWorksheets();
$numWorksheets = count($worksheets); $numWorksheets = \count($worksheets);
$this->fileSystemHelper $this->fileSystemHelper
->createContentFile($this->worksheetManager, $this->styleManager, $worksheets) ->createContentFile($this->worksheetManager, $this->styleManager, $worksheets)

View file

@ -61,7 +61,7 @@ class WorksheetManager implements WorksheetManagerInterface
*/ */
public function startSheet(Worksheet $worksheet) public function startSheet(Worksheet $worksheet)
{ {
$sheetFilePointer = fopen($worksheet->getFilePath(), 'w'); $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
$this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer); $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
$worksheet->setFilePointer($sheetFilePointer); $worksheet->setFilePointer($sheetFilePointer);
@ -134,7 +134,7 @@ class WorksheetManager implements WorksheetManagerInterface
$data .= '</table:table-row>'; $data .= '</table:table-row>';
$wasWriteSuccessful = fwrite($worksheet->getFilePointer(), $data); $wasWriteSuccessful = \fwrite($worksheet->getFilePointer(), $data);
if ($wasWriteSuccessful === false) { if ($wasWriteSuccessful === false) {
throw new IOException("Unable to write data in {$worksheet->getFilePath()}"); throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
} }
@ -190,7 +190,7 @@ class WorksheetManager implements WorksheetManagerInterface
if ($cell->isString()) { if ($cell->isString()) {
$data .= ' office:value-type="string" calcext:value-type="string">'; $data .= ' office:value-type="string" calcext:value-type="string">';
$cellValueLines = explode("\n", $cell->getValue()); $cellValueLines = \explode("\n", $cell->getValue());
foreach ($cellValueLines as $cellValueLine) { foreach ($cellValueLines as $cellValueLine) {
$data .= '<text:p>' . $this->stringsEscaper->escape($cellValueLine) . '</text:p>'; $data .= '<text:p>' . $this->stringsEscaper->escape($cellValueLine) . '</text:p>';
} }
@ -204,10 +204,15 @@ class WorksheetManager implements WorksheetManagerInterface
$data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">'; $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">';
$data .= '<text:p>' . $cell->getValue() . '</text:p>'; $data .= '<text:p>' . $cell->getValue() . '</text:p>';
$data .= '</table:table-cell>'; $data .= '</table:table-cell>';
} elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
// only writes the error value if it's a string
$data .= ' office:value-type="string" calcext:value-type="error" office:value="">';
$data .= '<text:p>' . $cell->getValueEvenIfError() . '</text:p>';
$data .= '</table:table-cell>';
} elseif ($cell->isEmpty()) { } elseif ($cell->isEmpty()) {
$data .= '/>'; $data .= '/>';
} else { } else {
throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cell->getValue())); throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . \gettype($cell->getValue()));
} }
return $data; return $data;
@ -223,10 +228,10 @@ class WorksheetManager implements WorksheetManagerInterface
{ {
$worksheetFilePointer = $worksheet->getFilePointer(); $worksheetFilePointer = $worksheet->getFilePointer();
if (!is_resource($worksheetFilePointer)) { if (!\is_resource($worksheetFilePointer)) {
return; return;
} }
fclose($worksheetFilePointer); \fclose($worksheetFilePointer);
} }
} }

View file

@ -223,7 +223,7 @@ abstract class WriterAbstract implements WriterInterface
$this->closeWriter(); $this->closeWriter();
if (is_resource($this->filePointer)) { if (\is_resource($this->filePointer)) {
$this->globalFunctionsHelper->fclose($this->filePointer); $this->globalFunctionsHelper->fclose($this->filePointer);
} }
@ -243,7 +243,7 @@ abstract class WriterAbstract implements WriterInterface
// remove output file if it was created // remove output file if it was created
if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) { if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
$outputFolderPath = dirname($this->outputFilePath); $outputFolderPath = \dirname($this->outputFilePath);
$fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath); $fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath);
$fileSystemHelper->deleteFile($this->outputFilePath); $fileSystemHelper->deleteFile($this->outputFilePath);
} }

View file

@ -43,8 +43,8 @@ class BorderHelper
{ {
$borderStyle = self::getBorderStyle($borderPart); $borderStyle = self::getBorderStyle($borderPart);
$colorEl = $borderPart->getColor() ? sprintf('<color rgb="%s"/>', $borderPart->getColor()) : ''; $colorEl = $borderPart->getColor() ? \sprintf('<color rgb="%s"/>', $borderPart->getColor()) : '';
$partEl = sprintf( $partEl = \sprintf(
'<%s style="%s">%s</%s>', '<%s style="%s">%s</%s>',
$borderPart->getName(), $borderPart->getName(),
$borderStyle, $borderStyle,

View file

@ -112,7 +112,7 @@ class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper impleme
*/ */
private function createRootFolder() private function createRootFolder()
{ {
$this->rootFolder = $this->createFolder($this->baseFolderRealPath, uniqid('xlsx', true)); $this->rootFolder = $this->createFolder($this->baseFolderRealPath, \uniqid('xlsx', true));
return $this; return $this;
} }

View file

@ -52,7 +52,7 @@ class OptionsManager extends OptionsManagerAbstract
->setFontName(self::DEFAULT_FONT_NAME) ->setFontName(self::DEFAULT_FONT_NAME)
->build(); ->build();
$this->setOption(Options::TEMP_FOLDER, sys_get_temp_dir()); $this->setOption(Options::TEMP_FOLDER, \sys_get_temp_dir());
$this->setOption(Options::DEFAULT_ROW_STYLE, $defaultRowStyle); $this->setOption(Options::DEFAULT_ROW_STYLE, $defaultRowStyle);
$this->setOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true); $this->setOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true);
$this->setOption(Options::SHOULD_USE_INLINE_STRINGS, true); $this->setOption(Options::SHOULD_USE_INLINE_STRINGS, true);

View file

@ -40,13 +40,13 @@ EOD;
public function __construct($xlFolder, $stringsEscaper) public function __construct($xlFolder, $stringsEscaper)
{ {
$sharedStringsFilePath = $xlFolder . '/' . self::SHARED_STRINGS_FILE_NAME; $sharedStringsFilePath = $xlFolder . '/' . self::SHARED_STRINGS_FILE_NAME;
$this->sharedStringsFilePointer = fopen($sharedStringsFilePath, 'w'); $this->sharedStringsFilePointer = \fopen($sharedStringsFilePath, 'w');
$this->throwIfSharedStringsFilePointerIsNotAvailable(); $this->throwIfSharedStringsFilePointerIsNotAvailable();
// the headers is split into different parts so that we can fseek and put in the correct count and uniqueCount later // the headers is split into different parts so that we can fseek and put in the correct count and uniqueCount later
$header = self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER . ' ' . self::DEFAULT_STRINGS_COUNT_PART . '>'; $header = self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER . ' ' . self::DEFAULT_STRINGS_COUNT_PART . '>';
fwrite($this->sharedStringsFilePointer, $header); \fwrite($this->sharedStringsFilePointer, $header);
$this->stringsEscaper = $stringsEscaper; $this->stringsEscaper = $stringsEscaper;
} }
@ -73,7 +73,7 @@ EOD;
*/ */
public function writeString($string) public function writeString($string)
{ {
fwrite($this->sharedStringsFilePointer, '<si><t xml:space="preserve">' . $this->stringsEscaper->escape($string) . '</t></si>'); \fwrite($this->sharedStringsFilePointer, '<si><t xml:space="preserve">' . $this->stringsEscaper->escape($string) . '</t></si>');
$this->numSharedStrings++; $this->numSharedStrings++;
// Shared string ID is zero-based // Shared string ID is zero-based
@ -87,20 +87,20 @@ EOD;
*/ */
public function close() public function close()
{ {
if (!is_resource($this->sharedStringsFilePointer)) { if (!\is_resource($this->sharedStringsFilePointer)) {
return; return;
} }
fwrite($this->sharedStringsFilePointer, '</sst>'); \fwrite($this->sharedStringsFilePointer, '</sst>');
// Replace the default strings count with the actual number of shared strings in the file header // Replace the default strings count with the actual number of shared strings in the file header
$firstPartHeaderLength = strlen(self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER); $firstPartHeaderLength = \strlen(self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER);
$defaultStringsCountPartLength = strlen(self::DEFAULT_STRINGS_COUNT_PART); $defaultStringsCountPartLength = \strlen(self::DEFAULT_STRINGS_COUNT_PART);
// Adding 1 to take into account the space between the last xml attribute and "count" // Adding 1 to take into account the space between the last xml attribute and "count"
fseek($this->sharedStringsFilePointer, $firstPartHeaderLength + 1); \fseek($this->sharedStringsFilePointer, $firstPartHeaderLength + 1);
fwrite($this->sharedStringsFilePointer, sprintf("%-{$defaultStringsCountPartLength}s", 'count="' . $this->numSharedStrings . '" uniqueCount="' . $this->numSharedStrings . '"')); \fwrite($this->sharedStringsFilePointer, \sprintf("%-{$defaultStringsCountPartLength}s", 'count="' . $this->numSharedStrings . '" uniqueCount="' . $this->numSharedStrings . '"'));
fclose($this->sharedStringsFilePointer); \fclose($this->sharedStringsFilePointer);
} }
} }

View file

@ -33,7 +33,10 @@ class StyleManager extends \Box\Spout\Writer\Common\Manager\Style\StyleManager
$associatedBorderId = $this->styleRegistry->getBorderIdForStyleId($styleId); $associatedBorderId = $this->styleRegistry->getBorderIdForStyleId($styleId);
$hasStyleCustomBorders = ($associatedBorderId !== null && $associatedBorderId !== 0); $hasStyleCustomBorders = ($associatedBorderId !== null && $associatedBorderId !== 0);
return ($hasStyleCustomFill || $hasStyleCustomBorders); $associatedFormatId = $this->styleRegistry->getFormatIdForStyleId($styleId);
$hasStyleCustomFormats = ($associatedFormatId !== null && $associatedFormatId !== 0);
return ($hasStyleCustomFill || $hasStyleCustomBorders || $hasStyleCustomFormats);
} }
/** /**
@ -48,6 +51,7 @@ class StyleManager extends \Box\Spout\Writer\Common\Manager\Style\StyleManager
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"> <styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
EOD; EOD;
$content .= $this->getFormatsSectionContent();
$content .= $this->getFontsSectionContent(); $content .= $this->getFontsSectionContent();
$content .= $this->getFillsSectionContent(); $content .= $this->getFillsSectionContent();
$content .= $this->getBordersSectionContent(); $content .= $this->getBordersSectionContent();
@ -62,6 +66,35 @@ EOD;
return $content; return $content;
} }
/**
* Returns the content of the "<numFmts>" section.
*
* @return string
*/
protected function getFormatsSectionContent()
{
$tags = [];
$registeredFormats = $this->styleRegistry->getRegisteredFormats();
foreach ($registeredFormats as $styleId) {
$numFmtId = $this->styleRegistry->getFormatIdForStyleId($styleId);
//Built-in formats do not need to be declared, skip them
if ($numFmtId < 164) {
continue;
}
/** @var Style $style */
$style = $this->styleRegistry->getStyleFromStyleId($styleId);
$format = $style->getFormat();
$tags[] = '<numFmt numFmtId="' . $numFmtId . '" formatCode="' . $format . '"/>';
}
$content = '<numFmts count="' . \count($tags) . '">';
$content .= \implode('', $tags);
$content .= '</numFmts>';
return $content;
}
/** /**
* Returns the content of the "<fonts>" section. * Returns the content of the "<fonts>" section.
* *
@ -71,7 +104,7 @@ EOD;
{ {
$registeredStyles = $this->styleRegistry->getRegisteredStyles(); $registeredStyles = $this->styleRegistry->getRegisteredStyles();
$content = '<fonts count="' . count($registeredStyles) . '">'; $content = '<fonts count="' . \count($registeredStyles) . '">';
/** @var Style $style */ /** @var Style $style */
foreach ($registeredStyles as $style) { foreach ($registeredStyles as $style) {
@ -112,8 +145,8 @@ EOD;
$registeredFills = $this->styleRegistry->getRegisteredFills(); $registeredFills = $this->styleRegistry->getRegisteredFills();
// Excel reserves two default fills // Excel reserves two default fills
$fillsCount = count($registeredFills) + 2; $fillsCount = \count($registeredFills) + 2;
$content = sprintf('<fills count="%d">', $fillsCount); $content = \sprintf('<fills count="%d">', $fillsCount);
$content .= '<fill><patternFill patternType="none"/></fill>'; $content .= '<fill><patternFill patternType="none"/></fill>';
$content .= '<fill><patternFill patternType="gray125"/></fill>'; $content .= '<fill><patternFill patternType="gray125"/></fill>';
@ -124,7 +157,7 @@ EOD;
$style = $this->styleRegistry->getStyleFromStyleId($styleId); $style = $this->styleRegistry->getStyleFromStyleId($styleId);
$backgroundColor = $style->getBackgroundColor(); $backgroundColor = $style->getBackgroundColor();
$content .= sprintf( $content .= \sprintf(
'<fill><patternFill patternType="solid"><fgColor rgb="%s"/></patternFill></fill>', '<fill><patternFill patternType="solid"><fgColor rgb="%s"/></patternFill></fill>',
$backgroundColor $backgroundColor
); );
@ -145,7 +178,7 @@ EOD;
$registeredBorders = $this->styleRegistry->getRegisteredBorders(); $registeredBorders = $this->styleRegistry->getRegisteredBorders();
// There is one default border with index 0 // There is one default border with index 0
$borderCount = count($registeredBorders) + 1; $borderCount = \count($registeredBorders) + 1;
$content = '<borders count="' . $borderCount . '">'; $content = '<borders count="' . $borderCount . '">';
@ -200,24 +233,32 @@ EOD;
{ {
$registeredStyles = $this->styleRegistry->getRegisteredStyles(); $registeredStyles = $this->styleRegistry->getRegisteredStyles();
$content = '<cellXfs count="' . count($registeredStyles) . '">'; $content = '<cellXfs count="' . \count($registeredStyles) . '">';
foreach ($registeredStyles as $style) { foreach ($registeredStyles as $style) {
$styleId = $style->getId(); $styleId = $style->getId();
$fillId = $this->getFillIdForStyleId($styleId); $fillId = $this->getFillIdForStyleId($styleId);
$borderId = $this->getBorderIdForStyleId($styleId); $borderId = $this->getBorderIdForStyleId($styleId);
$numFmtId = $this->getFormatIdForStyleId($styleId);
$content .= '<xf numFmtId="0" fontId="' . $styleId . '" fillId="' . $fillId . '" borderId="' . $borderId . '" xfId="0"'; $content .= '<xf numFmtId="' . $numFmtId . '" fontId="' . $styleId . '" fillId="' . $fillId . '" borderId="' . $borderId . '" xfId="0"';
if ($style->shouldApplyFont()) { if ($style->shouldApplyFont()) {
$content .= ' applyFont="1"'; $content .= ' applyFont="1"';
} }
$content .= sprintf(' applyBorder="%d"', $style->shouldApplyBorder() ? 1 : 0); $content .= \sprintf(' applyBorder="%d"', $style->shouldApplyBorder() ? 1 : 0);
if ($style->shouldWrapText()) { if ($style->shouldApplyCellAlignment() || $style->shouldWrapText()) {
$content .= ' applyAlignment="1">'; $content .= ' applyAlignment="1">';
$content .= '<alignment wrapText="1"/>'; $content .= '<alignment';
if ($style->shouldApplyCellAlignment()) {
$content .= \sprintf(' horizontal="%s"', $style->getCellAlignment());
}
if ($style->shouldWrapText()) {
$content .= ' wrapText="1"';
}
$content .= '/>';
$content .= '</xf>'; $content .= '</xf>';
} else { } else {
$content .= '/>'; $content .= '/>';
@ -261,6 +302,22 @@ EOD;
return $isDefaultStyle ? 0 : ($this->styleRegistry->getBorderIdForStyleId($styleId) ?: 0); return $isDefaultStyle ? 0 : ($this->styleRegistry->getBorderIdForStyleId($styleId) ?: 0);
} }
/**
* Returns the format ID associated to the given style ID.
* For the default style use general format.
*
* @param int $styleId
* @return int
*/
private function getFormatIdForStyleId($styleId)
{
// For the default style (ID = 0), we don't want to override the format.
// Otherwise all cells of the spreadsheet will have a format.
$isDefaultStyle = ($styleId === 0);
return $isDefaultStyle ? 0 : ($this->styleRegistry->getFormatIdForStyleId($styleId) ?: 0);
}
/** /**
* Returns the content of the "<cellStyles>" section. * Returns the content of the "<cellStyles>" section.
* *

View file

@ -10,6 +10,79 @@ use Box\Spout\Common\Entity\Style\Style;
*/ */
class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
{ {
/**
* @see https://msdn.microsoft.com/en-us/library/ff529597(v=office.12).aspx
* @var array Mapping between built-in format and the associated numFmtId
*/
protected static $builtinNumFormatToIdMapping = [
'General' => 0,
'0' => 1,
'0.00' => 2,
'#,##0' => 3,
'#,##0.00' => 4,
'$#,##0,\-$#,##0' => 5,
'$#,##0,[Red]\-$#,##0' => 6,
'$#,##0.00,\-$#,##0.00' => 7,
'$#,##0.00,[Red]\-$#,##0.00' => 8,
'0%' => 9,
'0.00%' => 10,
'0.00E+00' => 11,
'# ?/?' => 12,
'# ??/??' => 13,
'mm-dd-yy' => 14,
'd-mmm-yy' => 15,
'd-mmm' => 16,
'mmm-yy' => 17,
'h:mm AM/PM' => 18,
'h:mm:ss AM/PM' => 19,
'h:mm' => 20,
'h:mm:ss' => 21,
'm/d/yy h:mm' => 22,
'#,##0 ,(#,##0)' => 37,
'#,##0 ,[Red](#,##0)' => 38,
'#,##0.00,(#,##0.00)' => 39,
'#,##0.00,[Red](#,##0.00)' => 40,
'_("$"* #,##0.00_),_("$"* \(#,##0.00\),_("$"* "-"??_),_(@_)' => 44,
'mm:ss' => 45,
'[h]:mm:ss' => 46,
'mm:ss.0' => 47,
'##0.0E+0' => 48,
'@' => 49,
'[$-404]e/m/d' => 27,
'm/d/yy' => 30,
't0' => 59,
't0.00' => 60,
't#,##0' => 61,
't#,##0.00' => 62,
't0%' => 67,
't0.00%' => 68,
't# ?/?' => 69,
't# ??/??' => 70,
];
/**
* @var array
*/
protected $registeredFormats = [];
/**
* @var array [STYLE_ID] => [FORMAT_ID] maps a style to a format declaration
*/
protected $styleIdToFormatsMappingTable = [];
/**
* If the numFmtId is lower than 0xA4 (164 in decimal)
* then it's a built-in number format.
* Since Excel is the dominant vendor - we play along here
*
* @var int The fill index counter for custom fills.
*/
protected $formatIndex = 164;
/** /**
* @var array * @var array
*/ */
@ -48,11 +121,52 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
{ {
$registeredStyle = parent::registerStyle($style); $registeredStyle = parent::registerStyle($style);
$this->registerFill($registeredStyle); $this->registerFill($registeredStyle);
$this->registerFormat($registeredStyle);
$this->registerBorder($registeredStyle); $this->registerBorder($registeredStyle);
return $registeredStyle; return $registeredStyle;
} }
/**
* Register a format definition
*
* @param Style $style
*/
protected function registerFormat(Style $style)
{
$styleId = $style->getId();
$format = $style->getFormat();
if ($format) {
$isFormatRegistered = isset($this->registeredFormats[$format]);
// We need to track the already registered format definitions
if ($isFormatRegistered) {
$registeredStyleId = $this->registeredFormats[$format];
$registeredFormatId = $this->styleIdToFormatsMappingTable[$registeredStyleId];
$this->styleIdToFormatsMappingTable[$styleId] = $registeredFormatId;
} else {
$this->registeredFormats[$format] = $styleId;
$id = self::$builtinNumFormatToIdMapping[$format] ?? $this->formatIndex++;
$this->styleIdToFormatsMappingTable[$styleId] = $id;
}
} else {
// The formatId maps a style to a format declaration
// When there is no format definition - we default to 0 ( General )
$this->styleIdToFormatsMappingTable[$styleId] = 0;
}
}
/**
* @param int $styleId
* @return int|null Format ID associated to the given style ID
*/
public function getFormatIdForStyleId($styleId)
{
return $this->styleIdToFormatsMappingTable[$styleId] ?? null;
}
/** /**
* Register a fill definition * Register a fill definition
* *
@ -107,7 +221,7 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
if ($style->shouldApplyBorder()) { if ($style->shouldApplyBorder()) {
$border = $style->getBorder(); $border = $style->getBorder();
$serializedBorder = serialize($border); $serializedBorder = \serialize($border);
$isBorderAlreadyRegistered = isset($this->registeredBorders[$serializedBorder]); $isBorderAlreadyRegistered = isset($this->registeredBorders[$serializedBorder]);
@ -117,7 +231,7 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
$this->styleIdToBorderMappingTable[$styleId] = $registeredBorderId; $this->styleIdToBorderMappingTable[$styleId] = $registeredBorderId;
} else { } else {
$this->registeredBorders[$serializedBorder] = $styleId; $this->registeredBorders[$serializedBorder] = $styleId;
$this->styleIdToBorderMappingTable[$styleId] = count($this->registeredBorders); $this->styleIdToBorderMappingTable[$styleId] = \count($this->registeredBorders);
} }
} else { } else {
// If no border should be applied - the mapping is the default border: 0 // If no border should be applied - the mapping is the default border: 0
@ -151,4 +265,12 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
{ {
return $this->registeredBorders; return $this->registeredBorders;
} }
/**
* @return array
*/
public function getRegisteredFormats()
{
return $this->registeredFormats;
}
} }

View file

@ -44,7 +44,7 @@ class WorkbookManager extends WorkbookManagerAbstract
{ {
$worksheetFilesFolder = $this->fileSystemHelper->getXlWorksheetsFolder(); $worksheetFilesFolder = $this->fileSystemHelper->getXlWorksheetsFolder();
return $worksheetFilesFolder . '/' . strtolower($sheet->getName()) . '.xml'; return $worksheetFilesFolder . '/' . \strtolower($sheet->getName()) . '.xml';
} }
/** /**

View file

@ -107,13 +107,13 @@ EOD;
*/ */
public function startSheet(Worksheet $worksheet) public function startSheet(Worksheet $worksheet)
{ {
$sheetFilePointer = fopen($worksheet->getFilePath(), 'w'); $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
$this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer); $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
$worksheet->setFilePointer($sheetFilePointer); $worksheet->setFilePointer($sheetFilePointer);
fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER); \fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER);
fwrite($sheetFilePointer, '<sheetData>'); \fwrite($sheetFilePointer, '<sheetData>');
} }
/** /**
@ -153,21 +153,19 @@ EOD;
*/ */
private function addNonEmptyRow(Worksheet $worksheet, Row $row) private function addNonEmptyRow(Worksheet $worksheet, Row $row)
{ {
$cellIndex = 0;
$rowStyle = $row->getStyle(); $rowStyle = $row->getStyle();
$rowIndex = $worksheet->getLastWrittenRowIndex() + 1; $rowIndexOneBased = $worksheet->getLastWrittenRowIndex() + 1;
$numCells = $row->getNumCells(); $numCells = $row->getNumCells();
$rowXML = '<row r="' . $rowIndex . '" spans="1:' . $numCells . '">'; $rowXML = '<row r="' . $rowIndexOneBased . '" spans="1:' . $numCells . '">';
foreach ($row->getCells() as $cell) { foreach ($row->getCells() as $columnIndexZeroBased => $cell) {
$rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndex, $cellIndex); $rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndexOneBased, $columnIndexZeroBased);
$cellIndex++;
} }
$rowXML .= '</row>'; $rowXML .= '</row>';
$wasWriteSuccessful = fwrite($worksheet->getFilePointer(), $rowXML); $wasWriteSuccessful = \fwrite($worksheet->getFilePointer(), $rowXML);
if ($wasWriteSuccessful === false) { if ($wasWriteSuccessful === false) {
throw new IOException("Unable to write data in {$worksheet->getFilePath()}"); throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
} }
@ -177,14 +175,15 @@ EOD;
* Applies styles to the given style, merging the cell's style with its row's style * Applies styles to the given style, merging the cell's style with its row's style
* Then builds and returns xml for the cell. * Then builds and returns xml for the cell.
* *
* @param Cell $cell * @param Cell $cell
* @param Style $rowStyle * @param Style $rowStyle
* @param int $rowIndex * @param int $rowIndexOneBased
* @param int $cellIndex * @param int $columnIndexZeroBased
*
* @throws InvalidArgumentException If the given value cannot be processed * @throws InvalidArgumentException If the given value cannot be processed
* @return string * @return string
*/ */
private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $rowIndex, $cellIndex) private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $rowIndexOneBased, $columnIndexZeroBased)
{ {
// Apply row and extra styles // Apply row and extra styles
$mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle); $mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
@ -193,23 +192,24 @@ EOD;
$registeredStyle = $this->styleManager->registerStyle($newCellStyle); $registeredStyle = $this->styleManager->registerStyle($newCellStyle);
return $this->getCellXML($rowIndex, $cellIndex, $cell, $registeredStyle->getId()); return $this->getCellXML($rowIndexOneBased, $columnIndexZeroBased, $cell, $registeredStyle->getId());
} }
/** /**
* Builds and returns xml for a single cell. * Builds and returns xml for a single cell.
* *
* @param int $rowIndex * @param int $rowIndexOneBased
* @param int $cellNumber * @param int $columnIndexZeroBased
* @param Cell $cell * @param Cell $cell
* @param int $styleId * @param int $styleId
*
* @throws InvalidArgumentException If the given value cannot be processed * @throws InvalidArgumentException If the given value cannot be processed
* @return string * @return string
*/ */
private function getCellXML($rowIndex, $cellNumber, Cell $cell, $styleId) private function getCellXML($rowIndexOneBased, $columnIndexZeroBased, Cell $cell, $styleId)
{ {
$columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber); $columnLetters = CellHelper::getColumnLettersFromColumnIndex($columnIndexZeroBased);
$cellXML = '<c r="' . $columnIndex . $rowIndex . '"'; $cellXML = '<c r="' . $columnLetters . $rowIndexOneBased . '"';
$cellXML .= ' s="' . $styleId . '"'; $cellXML .= ' s="' . $styleId . '"';
if ($cell->isString()) { if ($cell->isString()) {
@ -218,6 +218,9 @@ EOD;
$cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>'; $cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>';
} elseif ($cell->isNumeric()) { } elseif ($cell->isNumeric()) {
$cellXML .= '><v>' . $cell->getValue() . '</v></c>'; $cellXML .= '><v>' . $cell->getValue() . '</v></c>';
} elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
// only writes the error value if it's a string
$cellXML .= ' t="e"><v>' . $cell->getValueEvenIfError() . '</v></c>';
} elseif ($cell->isEmpty()) { } elseif ($cell->isEmpty()) {
if ($this->styleManager->shouldApplyStyleOnEmptyCell($styleId)) { if ($this->styleManager->shouldApplyStyleOnEmptyCell($styleId)) {
$cellXML .= '/>'; $cellXML .= '/>';
@ -227,7 +230,7 @@ EOD;
$cellXML = ''; $cellXML = '';
} }
} else { } else {
throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cell->getValue())); throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . \gettype($cell->getValue()));
} }
return $cellXML; return $cellXML;
@ -263,12 +266,12 @@ EOD;
{ {
$worksheetFilePointer = $worksheet->getFilePointer(); $worksheetFilePointer = $worksheet->getFilePointer();
if (!is_resource($worksheetFilePointer)) { if (!\is_resource($worksheetFilePointer)) {
return; return;
} }
fwrite($worksheetFilePointer, '</sheetData>'); \fwrite($worksheetFilePointer, '</sheetData>');
fwrite($worksheetFilePointer, '</worksheet>'); \fwrite($worksheetFilePointer, '</worksheet>');
fclose($worksheetFilePointer); \fclose($worksheetFilePointer);
} }
} }