diff --git a/lib/spout/src/Spout/Autoloader/Psr4Autoloader.php b/lib/spout/src/Spout/Autoloader/Psr4Autoloader.php index 1699293426d..37b6d4ac109 100644 --- a/lib/spout/src/Spout/Autoloader/Psr4Autoloader.php +++ b/lib/spout/src/Spout/Autoloader/Psr4Autoloader.php @@ -23,7 +23,7 @@ class Psr4Autoloader */ 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) { // normalize namespace prefix - $prefix = trim($prefix, '\\') . '\\'; + $prefix = \trim($prefix, '\\') . '\\'; // normalize the base directory with a trailing separator - $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/'; + $baseDir = \rtrim($baseDir, DIRECTORY_SEPARATOR) . '/'; // initialize the namespace prefix array if (isset($this->prefixes[$prefix]) === false) { @@ -52,9 +52,9 @@ class Psr4Autoloader // retain the base directory for the namespace prefix if ($prepend) { - array_unshift($this->prefixes[$prefix], $baseDir); + \array_unshift($this->prefixes[$prefix], $baseDir); } 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 // 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 - $prefix = substr($class, 0, $pos + 1); + $prefix = \substr($class, 0, $pos + 1); // 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 $mappedFile = $this->loadMappedFile($prefix, $relativeClass); @@ -87,7 +87,7 @@ class Psr4Autoloader // remove the trailing namespace separator for the next iteration // of strrpos() - $prefix = rtrim($prefix, '\\'); + $prefix = \rtrim($prefix, '\\'); } // never found a mapped file @@ -115,7 +115,7 @@ class Psr4Autoloader // replace namespace separators with directory separators // in the relative class name, append with .php $file = $baseDir - . str_replace('\\', '/', $relativeClass) + . \str_replace('\\', '/', $relativeClass) . '.php'; // if the mapped file exists, require it @@ -137,7 +137,7 @@ class Psr4Autoloader */ protected function requireFile($file) { - if (file_exists($file)) { + if (\file_exists($file)) { require $file; return true; diff --git a/lib/spout/src/Spout/Autoloader/autoload.php b/lib/spout/src/Spout/Autoloader/autoload.php index e08ee344bd8..44468a72815 100644 --- a/lib/spout/src/Spout/Autoloader/autoload.php +++ b/lib/spout/src/Spout/Autoloader/autoload.php @@ -8,7 +8,7 @@ require_once 'Psr4Autoloader.php'; * @var string * 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->register(); diff --git a/lib/spout/src/Spout/Common/Entity/Cell.php b/lib/spout/src/Spout/Common/Entity/Cell.php index bd7a7303b07..c1de38950b5 100644 --- a/lib/spout/src/Spout/Common/Entity/Cell.php +++ b/lib/spout/src/Spout/Common/Entity/Cell.php @@ -91,6 +91,14 @@ class Cell return !$this->isError() ? $this->value : null; } + /** + * @return mixed + */ + public function getValueEvenIfError() + { + return $this->value; + } + /** * @param Style|null $style */ diff --git a/lib/spout/src/Spout/Common/Entity/Row.php b/lib/spout/src/Spout/Common/Entity/Row.php index ccb6bf584e0..0cc49c7309b 100644 --- a/lib/spout/src/Spout/Common/Entity/Row.php +++ b/lib/spout/src/Spout/Common/Entity/Row.php @@ -70,7 +70,7 @@ class Row */ 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() { - 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() { - return array_map(function (Cell $cell) { + return \array_map(function (Cell $cell) { return $cell->getValue(); }, $this->cells); } diff --git a/lib/spout/src/Spout/Common/Entity/Style/BorderPart.php b/lib/spout/src/Spout/Common/Entity/Style/BorderPart.php index 4dcfd8257cc..c0874847e62 100644 --- a/lib/spout/src/Spout/Common/Entity/Style/BorderPart.php +++ b/lib/spout/src/Spout/Common/Entity/Style/BorderPart.php @@ -93,7 +93,7 @@ class BorderPart */ public function setName($name) { - if (!in_array($name, self::$allowedNames)) { + if (!\in_array($name, self::$allowedNames)) { throw new InvalidNameException($name); } $this->name = $name; @@ -114,7 +114,7 @@ class BorderPart */ public function setStyle($style) { - if (!in_array($style, self::$allowedStyles)) { + if (!\in_array($style, self::$allowedStyles)) { throw new InvalidStyleException($style); } $this->style = $style; @@ -152,7 +152,7 @@ class BorderPart */ public function setWidth($width) { - if (!in_array($width, self::$allowedWidths)) { + if (!\in_array($width, self::$allowedWidths)) { throw new InvalidWidthException($width); } $this->width = $width; diff --git a/lib/spout/src/Spout/Common/Entity/Style/CellAlignment.php b/lib/spout/src/Spout/Common/Entity/Style/CellAlignment.php new file mode 100644 index 00000000000..60fe83309fb --- /dev/null +++ b/lib/spout/src/Spout/Common/Entity/Style/CellAlignment.php @@ -0,0 +1,32 @@ + 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]); + } +} diff --git a/lib/spout/src/Spout/Common/Entity/Style/Color.php b/lib/spout/src/Spout/Common/Entity/Style/Color.php index 36b3d2635d8..f2a4731a0cd 100644 --- a/lib/spout/src/Spout/Common/Entity/Style/Color.php +++ b/lib/spout/src/Spout/Common/Entity/Style/Color.php @@ -8,7 +8,7 @@ use Box\Spout\Common\Exception\InvalidColorException; * Class Color * This class provides constants and functions to work with colors */ -class Color +abstract class Color { /** Standard colors - based on Office Online */ const BLACK = '000000'; @@ -38,7 +38,7 @@ class Color self::throwIfInvalidColorComponentValue($green); self::throwIfInvalidColorComponentValue($blue); - return strtoupper( + return \strtoupper( self::convertColorComponentToHex($red) . self::convertColorComponentToHex($green) . self::convertColorComponentToHex($blue) @@ -54,7 +54,7 @@ class Color */ 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"); } } @@ -67,7 +67,7 @@ class Color */ 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); } /** diff --git a/lib/spout/src/Spout/Common/Entity/Style/Style.php b/lib/spout/src/Spout/Common/Entity/Style/Style.php index b34e0368dcb..7e989a46d22 100644 --- a/lib/spout/src/Spout/Common/Entity/Style/Style.php +++ b/lib/spout/src/Spout/Common/Entity/Style/Style.php @@ -8,7 +8,7 @@ namespace Box\Spout\Common\Entity\Style; */ class Style { - /** Default font values */ + /** Default values */ const DEFAULT_FONT_SIZE = 11; const DEFAULT_FONT_COLOR = Color::BLACK; const DEFAULT_FONT_NAME = 'Arial'; @@ -54,6 +54,13 @@ class Style /** @var bool Whether specific font properties should be applied */ 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) */ private $shouldWrapText = false; /** @var bool Whether the wrap text property was set */ @@ -71,6 +78,12 @@ class Style /** @var bool */ private $hasSetBackgroundColor = false; + /** @var string Format */ + private $format; + + /** @var bool */ + private $hasSetFormat = false; + /** * @return int|null */ @@ -319,6 +332,44 @@ class Style 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 */ @@ -383,4 +434,33 @@ class Style { 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; + } } diff --git a/lib/spout/src/Spout/Common/Helper/CellTypeHelper.php b/lib/spout/src/Spout/Common/Helper/CellTypeHelper.php index 03d26a34275..97d2e8ddae3 100644 --- a/lib/spout/src/Spout/Common/Helper/CellTypeHelper.php +++ b/lib/spout/src/Spout/Common/Helper/CellTypeHelper.php @@ -23,7 +23,7 @@ class CellTypeHelper */ 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) { - $valueType = gettype($value); + $valueType = \gettype($value); return ($valueType === 'integer' || $valueType === 'double'); } @@ -49,7 +49,7 @@ class CellTypeHelper */ public static function isBoolean($value) { - return gettype($value) === 'boolean'; + return \gettype($value) === 'boolean'; } /** diff --git a/lib/spout/src/Spout/Common/Helper/EncodingHelper.php b/lib/spout/src/Spout/Common/Helper/EncodingHelper.php index 90642743a3b..62ddf60bd56 100644 --- a/lib/spout/src/Spout/Common/Helper/EncodingHelper.php +++ b/lib/spout/src/Spout/Common/Helper/EncodingHelper.php @@ -61,7 +61,7 @@ class EncodingHelper $bomUsed = $this->supportedEncodingsWithBom[$encoding]; // we skip the N first bytes - $byteOffsetToSkipBom = strlen($bomUsed); + $byteOffsetToSkipBom = \strlen($bomUsed); } return $byteOffsetToSkipBom; @@ -80,9 +80,9 @@ class EncodingHelper $this->globalFunctionsHelper->rewind($filePointer); - if (array_key_exists($encoding, $this->supportedEncodingsWithBom)) { + if (\array_key_exists($encoding, $this->supportedEncodingsWithBom)) { $potentialBom = $this->supportedEncodingsWithBom[$encoding]; - $numBytesInBom = strlen($potentialBom); + $numBytesInBom = \strlen($potentialBom); $hasBOM = ($this->globalFunctionsHelper->fgets($filePointer, $numBytesInBom + 1) === $potentialBom); } diff --git a/lib/spout/src/Spout/Common/Helper/Escaper/ODS.php b/lib/spout/src/Spout/Common/Helper/Escaper/ODS.php index 54208367e91..b7e6eb17afd 100644 --- a/lib/spout/src/Spout/Common/Helper/Escaper/ODS.php +++ b/lib/spout/src/Spout/Common/Helper/Escaper/ODS.php @@ -18,14 +18,14 @@ class ODS implements EscaperInterface { // @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as // 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. // 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 - $replacedString = htmlspecialchars($string, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8'); + $replacedString = \htmlspecialchars($string, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8'); } else { // 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 // some characters should not be escaped though: "\t", "\r" and "\n". @@ -34,7 +34,7 @@ class ODS implements EscaperInterface '\x0B-\x0C' . // skipping "\r" (0xD) '\x0E-\x1F]'; - $replacedString = preg_replace("/$regexPattern/", '�', $escapedString); + $replacedString = \preg_replace("/$regexPattern/", '�', $escapedString); } return $replacedString; diff --git a/lib/spout/src/Spout/Common/Helper/Escaper/XLSX.php b/lib/spout/src/Spout/Common/Helper/Escaper/XLSX.php index d72caa8904f..daef46e4dd2 100644 --- a/lib/spout/src/Spout/Common/Helper/Escaper/XLSX.php +++ b/lib/spout/src/Spout/Common/Helper/Escaper/XLSX.php @@ -28,7 +28,7 @@ class XLSX implements EscaperInterface if (!$this->isAlreadyInitialized) { $this->escapableControlCharactersPattern = $this->getEscapableControlCharactersPattern(); $this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap(); - $this->controlCharactersEscapingReverseMap = array_flip($this->controlCharactersEscapingMap); + $this->controlCharactersEscapingReverseMap = \array_flip($this->controlCharactersEscapingMap); $this->isAlreadyInitialized = true; } @@ -47,7 +47,7 @@ class XLSX implements EscaperInterface $escapedString = $this->escapeControlCharacters($string); // @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as // 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; } @@ -103,10 +103,10 @@ class XLSX implements EscaperInterface // control characters values are from 0 to 1F (hex values) in the ASCII table for ($charValue = 0x00; $charValue <= 0x1F; $charValue++) { - $character = chr($charValue); - if (preg_match("/{$this->escapableControlCharactersPattern}/", $character)) { - $charHexValue = dechex($charValue); - $escapedChar = '_x' . sprintf('%04s', strtoupper($charHexValue)) . '_'; + $character = \chr($charValue); + if (\preg_match("/{$this->escapableControlCharactersPattern}/", $character)) { + $charHexValue = \dechex($charValue); + $escapedChar = '_x' . \sprintf('%04s', \strtoupper($charHexValue)) . '_'; $controlCharactersEscapingMap[$escapedChar] = $character; } } @@ -132,11 +132,11 @@ class XLSX implements EscaperInterface $escapedString = $this->escapeEscapeCharacter($string); // if no control characters - if (!preg_match("/{$this->escapableControlCharactersPattern}/", $escapedString)) { + if (!\preg_match("/{$this->escapableControlCharactersPattern}/", $escapedString)) { return $escapedString; } - return preg_replace_callback("/({$this->escapableControlCharactersPattern})/", function ($matches) { + return \preg_replace_callback("/({$this->escapableControlCharactersPattern})/", function ($matches) { return $this->controlCharactersEscapingReverseMap[$matches[0]]; }, $escapedString); } @@ -149,7 +149,7 @@ class XLSX implements EscaperInterface */ 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) { // only unescape characters that don't contain the escaped escape character for now - $unescapedString = preg_replace("/(?unescapeEscapeCharacter($unescapedString); @@ -185,6 +185,6 @@ class XLSX implements EscaperInterface */ protected function unescapeEscapeCharacter($string) { - return preg_replace('/_x005F(_x[\dA-F]{4}_)/', '$1', $string); + return \preg_replace('/_x005F(_x[\dA-F]{4}_)/', '$1', $string); } } diff --git a/lib/spout/src/Spout/Common/Helper/FileSystemHelper.php b/lib/spout/src/Spout/Common/Helper/FileSystemHelper.php index b1bff8fc85c..4d5d223dc70 100644 --- a/lib/spout/src/Spout/Common/Helper/FileSystemHelper.php +++ b/lib/spout/src/Spout/Common/Helper/FileSystemHelper.php @@ -19,7 +19,7 @@ class FileSystemHelper implements FileSystemHelperInterface */ public function __construct($baseFolderPath) { - $this->baseFolderRealPath = realpath($baseFolderPath); + $this->baseFolderRealPath = \realpath($baseFolderPath); } /** @@ -36,7 +36,7 @@ class FileSystemHelper implements FileSystemHelperInterface $folderPath = $parentFolderPath . '/' . $folderName; - $wasCreationSuccessful = mkdir($folderPath, 0777, true); + $wasCreationSuccessful = \mkdir($folderPath, 0777, true); if (!$wasCreationSuccessful) { throw new IOException("Unable to create folder: $folderPath"); } @@ -60,7 +60,7 @@ class FileSystemHelper implements FileSystemHelperInterface $filePath = $parentFolderPath . '/' . $fileName; - $wasCreationSuccessful = file_put_contents($filePath, $fileContents); + $wasCreationSuccessful = \file_put_contents($filePath, $fileContents); if ($wasCreationSuccessful === false) { throw new IOException("Unable to create file: $filePath"); } @@ -79,8 +79,8 @@ class FileSystemHelper implements FileSystemHelperInterface { $this->throwIfOperationNotInBaseFolder($filePath); - if (file_exists($filePath) && is_file($filePath)) { - unlink($filePath); + if (\file_exists($filePath) && \is_file($filePath)) { + \unlink($filePath); } } @@ -102,13 +102,13 @@ class FileSystemHelper implements FileSystemHelperInterface foreach ($itemIterator as $item) { if ($item->isDir()) { - rmdir($item->getPathname()); + \rmdir($item->getPathname()); } else { - unlink($item->getPathname()); + \unlink($item->getPathname()); } } - rmdir($folderPath); + \rmdir($folderPath); } /** @@ -122,8 +122,8 @@ class FileSystemHelper implements FileSystemHelperInterface */ protected function throwIfOperationNotInBaseFolder($operationFolderPath) { - $operationFolderRealPath = realpath($operationFolderPath); - $isInBaseFolder = (strpos($operationFolderRealPath, $this->baseFolderRealPath) === 0); + $operationFolderRealPath = \realpath($operationFolderPath); + $isInBaseFolder = (\strpos($operationFolderRealPath, $this->baseFolderRealPath) === 0); if (!$isInBaseFolder) { throw new IOException("Cannot perform I/O operation outside of the base folder: {$this->baseFolderRealPath}"); } diff --git a/lib/spout/src/Spout/Common/Helper/GlobalFunctionsHelper.php b/lib/spout/src/Spout/Common/Helper/GlobalFunctionsHelper.php index 0b5f6f18421..5deb8d93f26 100644 --- a/lib/spout/src/Spout/Common/Helper/GlobalFunctionsHelper.php +++ b/lib/spout/src/Spout/Common/Helper/GlobalFunctionsHelper.php @@ -20,7 +20,7 @@ class GlobalFunctionsHelper */ 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) { - return fgets($handle, $length); + return \fgets($handle, $length); } /** @@ -46,7 +46,7 @@ class GlobalFunctionsHelper */ public function fputs($handle, $string) { - return fputs($handle, $string); + return \fputs($handle, $string); } /** @@ -58,7 +58,7 @@ class GlobalFunctionsHelper */ public function fflush($handle) { - return fflush($handle); + return \fflush($handle); } /** @@ -71,7 +71,7 @@ class GlobalFunctionsHelper */ 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. // @see https://bugs.php.net/bug.php?id=43225 // @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. // @see https://bugs.php.net/bug.php?id=43225 // @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) { - return fwrite($handle, $string); + return \fwrite($handle, $string); } /** @@ -138,7 +138,7 @@ class GlobalFunctionsHelper */ public function fclose($handle) { - return fclose($handle); + return \fclose($handle); } /** @@ -150,7 +150,7 @@ class GlobalFunctionsHelper */ public function rewind($handle) { - return rewind($handle); + return \rewind($handle); } /** @@ -162,7 +162,7 @@ class GlobalFunctionsHelper */ public function file_exists($fileName) { - return file_exists($fileName); + return \file_exists($fileName); } /** @@ -176,7 +176,7 @@ class GlobalFunctionsHelper { $realFilePath = $this->convertToUseRealPath($filePath); - return file_get_contents($realFilePath); + return \file_get_contents($realFilePath); } /** @@ -191,13 +191,13 @@ class GlobalFunctionsHelper $realFilePath = $filePath; if ($this->isZipStream($filePath)) { - if (preg_match('/zip:\/\/(.*)#(.*)/', $filePath, $matches)) { + if (\preg_match('/zip:\/\/(.*)#(.*)/', $filePath, $matches)) { $documentPath = $matches[1]; $documentInsideZipPath = $matches[2]; - $realFilePath = 'zip://' . realpath($documentPath) . '#' . $documentInsideZipPath; + $realFilePath = 'zip://' . \realpath($documentPath) . '#' . $documentInsideZipPath; } } else { - $realFilePath = realpath($filePath); + $realFilePath = \realpath($filePath); } return $realFilePath; @@ -211,7 +211,7 @@ class GlobalFunctionsHelper */ protected function isZipStream($path) { - return (strpos($path, 'zip://') === 0); + return (\strpos($path, 'zip://') === 0); } /** @@ -223,7 +223,7 @@ class GlobalFunctionsHelper */ public function feof($handle) { - return feof($handle); + return \feof($handle); } /** @@ -235,7 +235,7 @@ class GlobalFunctionsHelper */ 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) { - return basename($path, $suffix); + return \basename($path, $suffix); } /** @@ -260,7 +260,7 @@ class GlobalFunctionsHelper */ public function header($string) { - header($string); + \header($string); } /** @@ -271,8 +271,8 @@ class GlobalFunctionsHelper */ public function ob_end_clean() { - if (ob_get_length() > 0) { - ob_end_clean(); + if (\ob_get_length() > 0) { + \ob_end_clean(); } } @@ -287,7 +287,7 @@ class GlobalFunctionsHelper */ 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) { - 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() { - return stream_get_wrappers(); + return \stream_get_wrappers(); } /** @@ -324,6 +324,6 @@ class GlobalFunctionsHelper */ public function function_exists($functionName) { - return function_exists($functionName); + return \function_exists($functionName); } } diff --git a/lib/spout/src/Spout/Common/Helper/StringHelper.php b/lib/spout/src/Spout/Common/Helper/StringHelper.php index c698815f081..09061329d82 100644 --- a/lib/spout/src/Spout/Common/Helper/StringHelper.php +++ b/lib/spout/src/Spout/Common/Helper/StringHelper.php @@ -18,7 +18,7 @@ class StringHelper */ public function __construct() { - $this->hasMbstringSupport = extension_loaded('mbstring'); + $this->hasMbstringSupport = \extension_loaded('mbstring'); } /** @@ -32,7 +32,7 @@ class StringHelper */ 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) { - $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; } @@ -64,7 +64,7 @@ class StringHelper */ 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; } diff --git a/lib/spout/src/Spout/Common/Manager/OptionsManagerAbstract.php b/lib/spout/src/Spout/Common/Manager/OptionsManagerAbstract.php index 20eb14ece76..5670b95be56 100644 --- a/lib/spout/src/Spout/Common/Manager/OptionsManagerAbstract.php +++ b/lib/spout/src/Spout/Common/Manager/OptionsManagerAbstract.php @@ -46,7 +46,7 @@ abstract class OptionsManagerAbstract implements OptionsManagerInterface */ public function setOption($optionName, $optionValue) { - if (in_array($optionName, $this->supportedOptions)) { + if (\in_array($optionName, $this->supportedOptions)) { $this->options[$optionName] = $optionValue; } } diff --git a/lib/spout/src/Spout/Reader/CSV/Creator/InternalEntityFactory.php b/lib/spout/src/Spout/Reader/CSV/Creator/InternalEntityFactory.php index cdb4197fc0c..65cf3eb7ee0 100644 --- a/lib/spout/src/Spout/Reader/CSV/Creator/InternalEntityFactory.php +++ b/lib/spout/src/Spout/Reader/CSV/Creator/InternalEntityFactory.php @@ -89,7 +89,7 @@ class InternalEntityFactory implements InternalEntityFactoryInterface */ public function createRowFromArray(array $cellValues = []) { - $cells = array_map(function ($cellValue) { + $cells = \array_map(function ($cellValue) { return $this->createCell($cellValue); }, $cellValues); diff --git a/lib/spout/src/Spout/Reader/CSV/Reader.php b/lib/spout/src/Spout/Reader/CSV/Reader.php index 77249c16221..ca61ef2e2f1 100644 --- a/lib/spout/src/Spout/Reader/CSV/Reader.php +++ b/lib/spout/src/Spout/Reader/CSV/Reader.php @@ -84,8 +84,8 @@ class Reader extends ReaderAbstract */ protected function openReader($filePath) { - $this->originalAutoDetectLineEndings = ini_get('auto_detect_line_endings'); - ini_set('auto_detect_line_endings', '1'); + $this->originalAutoDetectLineEndings = \ini_get('auto_detect_line_endings'); + \ini_set('auto_detect_line_endings', '1'); $this->filePointer = $this->globalFunctionsHelper->fopen($filePath, 'r'); if (!$this->filePointer) { @@ -123,6 +123,6 @@ class Reader extends ReaderAbstract $this->globalFunctionsHelper->fclose($this->filePointer); } - ini_set('auto_detect_line_endings', $this->originalAutoDetectLineEndings); + \ini_set('auto_detect_line_endings', $this->originalAutoDetectLineEndings); } } diff --git a/lib/spout/src/Spout/Reader/CSV/RowIterator.php b/lib/spout/src/Spout/Reader/CSV/RowIterator.php index bec30723b81..78e66501320 100644 --- a/lib/spout/src/Spout/Reader/CSV/RowIterator.php +++ b/lib/spout/src/Spout/Reader/CSV/RowIterator.php @@ -147,7 +147,7 @@ class RowIterator implements IteratorInterface if ($rowData !== false) { // 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->numReadRows++; } else { @@ -193,13 +193,13 @@ class RowIterator implements IteratorInterface case EncodingHelper::ENCODING_UTF16_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 - $cellValue = ltrim($cellValue); + $cellValue = \ltrim($cellValue); break; case EncodingHelper::ENCODING_UTF16_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 - $cellValue = rtrim($cellValue); + $cellValue = \rtrim($cellValue); break; } @@ -215,7 +215,7 @@ class RowIterator implements IteratorInterface */ protected function isEmptyLine($lineData) { - return (is_array($lineData) && count($lineData) === 1 && $lineData[0] === null); + return (\is_array($lineData) && \count($lineData) === 1 && $lineData[0] === null); } /** diff --git a/lib/spout/src/Spout/Reader/Common/Creator/ReaderFactory.php b/lib/spout/src/Spout/Reader/Common/Creator/ReaderFactory.php index 245a42a1d46..6b101759bf9 100644 --- a/lib/spout/src/Spout/Reader/Common/Creator/ReaderFactory.php +++ b/lib/spout/src/Spout/Reader/Common/Creator/ReaderFactory.php @@ -37,7 +37,7 @@ class ReaderFactory */ public static function createFromFile(string $path) { - $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + $extension = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); return self::createFromType($extension); } diff --git a/lib/spout/src/Spout/Reader/Common/Manager/RowManager.php b/lib/spout/src/Spout/Reader/Common/Manager/RowManager.php index ea76255c4a8..623c8d8b5c1 100644 --- a/lib/spout/src/Spout/Reader/Common/Manager/RowManager.php +++ b/lib/spout/src/Spout/Reader/Common/Manager/RowManager.php @@ -47,12 +47,14 @@ class RowManager */ public function fillMissingIndexesWithEmptyCells(Row $row) { - if ($row->getNumCells() === 0) { + $numCells = $row->getNumCells(); + + if ($numCells === 0) { return $row; } $rowCells = $row->getCells(); - $maxCellIndex = max(array_keys($rowCells)); + $maxCellIndex = $numCells; for ($cellIndex = 0; $cellIndex < $maxCellIndex; $cellIndex++) { if (!isset($rowCells[$cellIndex])) { diff --git a/lib/spout/src/Spout/Reader/Common/XMLProcessor.php b/lib/spout/src/Spout/Reader/Common/XMLProcessor.php index 3b2d8481821..05ee970b57c 100644 --- a/lib/spout/src/Spout/Reader/Common/XMLProcessor.php +++ b/lib/spout/src/Spout/Reader/Common/XMLProcessor.php @@ -73,7 +73,7 @@ class XMLProcessor { $callbackObject = $callback[0]; $callbackMethodName = $callback[1]; - $reflectionMethod = new \ReflectionMethod(get_class($callbackObject), $callbackMethodName); + $reflectionMethod = new \ReflectionMethod(\get_class($callbackObject), $callbackMethodName); $reflectionMethod->setAccessible(true); return [ diff --git a/lib/spout/src/Spout/Reader/ODS/Helper/CellValueFormatter.php b/lib/spout/src/Spout/Reader/ODS/Helper/CellValueFormatter.php index 50209ec2f77..168770f105b 100644 --- a/lib/spout/src/Spout/Reader/ODS/Helper/CellValueFormatter.php +++ b/lib/spout/src/Spout/Reader/ODS/Helper/CellValueFormatter.php @@ -22,9 +22,11 @@ class CellValueFormatter /** Definition of XML nodes names used to parse data */ const XML_NODE_P = 'p'; - const XML_NODE_S = 'text:s'; - const XML_NODE_A = 'text:a'; - const XML_NODE_SPAN = 'text:span'; + const XML_NODE_TEXT_A = 'text:a'; + const XML_NODE_TEXT_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 */ 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 */ 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 \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); foreach ($pNodes as $pNode) { - $currentPValue = ''; - - 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; + $pNodeValues[] = $this->extractTextValueFromNode($pNode); } - $escapedCellValue = implode("\n", $pNodeValues); + $escapedCellValue = \implode("\n", $pNodeValues); $cellValue = $this->escaper->unescape($escapedCellValue); 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: + * - + * - + * - + * + * @param string $nodeName + * @return bool + */ + private function isWhitespaceNode($nodeName) + { + return isset(self::$WHITESPACE_XML_NODES[$nodeName]); + } + + /** + * The "" 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 => + * - tab => + * - 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 "" + $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. * diff --git a/lib/spout/src/Spout/Reader/ReaderAbstract.php b/lib/spout/src/Spout/Reader/ReaderAbstract.php index dea234e7d73..39b333d4c4e 100644 --- a/lib/spout/src/Spout/Reader/ReaderAbstract.php +++ b/lib/spout/src/Spout/Reader/ReaderAbstract.php @@ -145,7 +145,7 @@ abstract class ReaderAbstract implements ReaderInterface } // 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) { $streamScheme = null; - if (preg_match('/^(\w+):\/\//', $filePath, $matches)) { + if (\preg_match('/^(\w+):\/\//', $filePath, $matches)) { $streamScheme = $matches[1]; } @@ -190,7 +190,7 @@ abstract class ReaderAbstract implements ReaderInterface $streamScheme = $this->getStreamWrapperScheme($filePath); return ($streamScheme !== null) ? - in_array($streamScheme, $this->globalFunctionsHelper->stream_get_wrappers()) : + \in_array($streamScheme, $this->globalFunctionsHelper->stream_get_wrappers()) : true; } diff --git a/lib/spout/src/Spout/Reader/Wrapper/XMLInternalErrorsHelper.php b/lib/spout/src/Spout/Reader/Wrapper/XMLInternalErrorsHelper.php index 7047a9475cb..ecf0dbd0684 100644 --- a/lib/spout/src/Spout/Reader/Wrapper/XMLInternalErrorsHelper.php +++ b/lib/spout/src/Spout/Reader/Wrapper/XMLInternalErrorsHelper.php @@ -20,8 +20,8 @@ trait XMLInternalErrorsHelper */ protected function useXMLInternalErrors() { - libxml_clear_errors(); - $this->initialUseInternalErrorsValue = libxml_use_internal_errors(true); + \libxml_clear_errors(); + $this->initialUseInternalErrorsValue = \libxml_use_internal_errors(true); } /** @@ -48,7 +48,7 @@ trait XMLInternalErrorsHelper */ private function hasXMLErrorOccured() { - return (libxml_get_last_error() !== false); + return (\libxml_get_last_error() !== false); } /** @@ -60,10 +60,10 @@ trait XMLInternalErrorsHelper private function getLastXMLErrorMessage() { $errorMessage = null; - $error = libxml_get_last_error(); + $error = \libxml_get_last_error(); if ($error !== false) { - $errorMessage = trim($error->message); + $errorMessage = \trim($error->message); } return $errorMessage; @@ -74,6 +74,6 @@ trait XMLInternalErrorsHelper */ protected function resetXMLInternalErrorsSetting() { - libxml_use_internal_errors($this->initialUseInternalErrorsValue); + \libxml_use_internal_errors($this->initialUseInternalErrorsValue); } } diff --git a/lib/spout/src/Spout/Reader/Wrapper/XMLReader.php b/lib/spout/src/Spout/Reader/Wrapper/XMLReader.php index bdbaf4d1764..6f85f320c8f 100644 --- a/lib/spout/src/Spout/Reader/Wrapper/XMLReader.php +++ b/lib/spout/src/Spout/Reader/Wrapper/XMLReader.php @@ -46,9 +46,9 @@ class XMLReader extends \XMLReader public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath) { // 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; $pattern = '/zip:\/\/([^#]+)#(.*)/'; - if (preg_match($pattern, $zipStreamURI, $matches)) { + if (\preg_match($pattern, $zipStreamURI, $matches)) { $zipFilePath = $matches[1]; $innerFilePath = $matches[2]; @@ -158,7 +158,7 @@ class XMLReader extends \XMLReader // In some cases, the node has a prefix (for instance, "" can also be ""). // 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 - $hasPrefix = (strpos($nodeName, ':') !== false); + $hasPrefix = (\strpos($nodeName, ':') !== false); $currentNodeName = ($hasPrefix) ? $this->name : $this->localName; return ($this->nodeType === $nodeType && $currentNodeName === $nodeName); diff --git a/lib/spout/src/Spout/Reader/XLSX/Helper/CellHelper.php b/lib/spout/src/Spout/Reader/XLSX/Helper/CellHelper.php index 27b5abfa289..d970189e653 100644 --- a/lib/spout/src/Spout/Reader/XLSX/Helper/CellHelper.php +++ b/lib/spout/src/Spout/Reader/XLSX/Helper/CellHelper.php @@ -37,7 +37,7 @@ class CellHelper $columnIndex = 0; // 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, // since we checked before that there are between 1 and 3 letters. @@ -75,6 +75,6 @@ class CellHelper */ 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); } } diff --git a/lib/spout/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php b/lib/spout/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php index fba36b13ec0..169c395be14 100644 --- a/lib/spout/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php +++ b/lib/spout/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php @@ -163,7 +163,7 @@ class CellValueFormatter */ protected function formatStrCellValue($nodeValue) { - $escapedCellValue = trim($nodeValue); + $escapedCellValue = \trim($nodeValue); $cellValue = $this->escaper->unescape($escapedCellValue); return $cellValue; @@ -248,8 +248,8 @@ class CellValueFormatter $baseDate = $this->shouldUse1904Dates ? '1904-01-01' : '1899-12-30'; $daysSinceBaseDate = (int) $nodeValue; - $timeRemainder = fmod($nodeValue, 1); - $secondsRemainder = round($timeRemainder * self::NUM_SECONDS_IN_ONE_DAY, 0); + $timeRemainder = \fmod($nodeValue, 1); + $secondsRemainder = \round($timeRemainder * self::NUM_SECONDS_IN_ONE_DAY, 0); $dateObj = \DateTime::createFromFormat('|Y-m-d', $baseDate); $dateObj->modify('+' . $daysSinceBaseDate . 'days'); diff --git a/lib/spout/src/Spout/Reader/XLSX/Helper/DateFormatHelper.php b/lib/spout/src/Spout/Reader/XLSX/Helper/DateFormatHelper.php index 1618af76145..44357881941 100644 --- a/lib/spout/src/Spout/Reader/XLSX/Helper/DateFormatHelper.php +++ b/lib/spout/src/Spout/Reader/XLSX/Helper/DateFormatHelper.php @@ -62,13 +62,13 @@ class DateFormatHelper // Remove brackets potentially present at the beginning of the format string // and text portion of the format at the end of it (starting with ";") // 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. // 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 // 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) { // 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 - $transformedPart = strtolower($dateFormatPart); + $transformedPart = \strtolower($dateFormatPart); // Remove escapes related to non-format characters - $transformedPart = str_replace('\\', '', $transformedPart); + $transformedPart = \str_replace('\\', '', $transformedPart); // 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 if (self::has12HourFormatMarker($dateFormatPart)) { - $transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_12]); + $transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_12]); } 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 @@ -97,16 +97,16 @@ class DateFormatHelper } // 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 // all characters that are inside double quotes (and double quotes must be removed). // 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]; - $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); return $phpDateFormat; @@ -118,6 +118,6 @@ class DateFormatHelper */ private static function has12HourFormatMarker($excelDateFormat) { - return (stripos($excelDateFormat, 'am/pm') !== false); + return (\stripos($excelDateFormat, 'am/pm') !== false); } } diff --git a/lib/spout/src/Spout/Reader/XLSX/Manager/OptionsManager.php b/lib/spout/src/Spout/Reader/XLSX/Manager/OptionsManager.php index 253e009295f..25fcc5cbb02 100644 --- a/lib/spout/src/Spout/Reader/XLSX/Manager/OptionsManager.php +++ b/lib/spout/src/Spout/Reader/XLSX/Manager/OptionsManager.php @@ -29,7 +29,7 @@ class OptionsManager extends OptionsManagerAbstract */ 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_PRESERVE_EMPTY_ROWS, false); $this->setOption(Options::SHOULD_USE_1904_DATES, false); diff --git a/lib/spout/src/Spout/Reader/XLSX/Manager/SharedStringsCaching/CachingStrategyFactory.php b/lib/spout/src/Spout/Reader/XLSX/Manager/SharedStringsCaching/CachingStrategyFactory.php index bc7ee9ceede..1c00a52d6c3 100644 --- a/lib/spout/src/Spout/Reader/XLSX/Manager/SharedStringsCaching/CachingStrategyFactory.php +++ b/lib/spout/src/Spout/Reader/XLSX/Manager/SharedStringsCaching/CachingStrategyFactory.php @@ -103,14 +103,14 @@ class CachingStrategyFactory protected function getMemoryLimitInKB() { $memoryLimitFormatted = $this->getMemoryLimitFromIni(); - $memoryLimitFormatted = strtolower(trim($memoryLimitFormatted)); + $memoryLimitFormatted = \strtolower(\trim($memoryLimitFormatted)); // No memory limit if ($memoryLimitFormatted === '-1') { return -1; } - if (preg_match('/(\d+)([bkmgt])b?/', $memoryLimitFormatted, $matches)) { + if (\preg_match('/(\d+)([bkmgt])b?/', $memoryLimitFormatted, $matches)) { $amount = (int) ($matches[1]); $unit = $matches[2]; @@ -133,6 +133,6 @@ class CachingStrategyFactory */ protected function getMemoryLimitFromIni() { - return ini_get('memory_limit'); + return \ini_get('memory_limit'); } } diff --git a/lib/spout/src/Spout/Reader/XLSX/Manager/SharedStringsCaching/FileBasedStrategy.php b/lib/spout/src/Spout/Reader/XLSX/Manager/SharedStringsCaching/FileBasedStrategy.php index c8ded706821..0e743b492bc 100644 --- a/lib/spout/src/Spout/Reader/XLSX/Manager/SharedStringsCaching/FileBasedStrategy.php +++ b/lib/spout/src/Spout/Reader/XLSX/Manager/SharedStringsCaching/FileBasedStrategy.php @@ -55,7 +55,7 @@ class FileBasedStrategy implements CachingStrategyInterface public function __construct($tempFolder, $maxNumStringsPerTempFile, $helperFactory) { $this->fileSystemHelper = $helperFactory->createFileSystemHelper($tempFolder); - $this->tempFolder = $this->fileSystemHelper->createFolder($tempFolder, uniqid('sharedstrings')); + $this->tempFolder = $this->fileSystemHelper->createFolder($tempFolder, \uniqid('sharedstrings')); $this->maxNumStringsPerTempFile = $maxNumStringsPerTempFile; @@ -135,7 +135,7 @@ class FileBasedStrategy implements CachingStrategyInterface // free memory 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; } @@ -151,7 +151,7 @@ class FileBasedStrategy implements CachingStrategyInterface 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) { - 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) { - return str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString); + return \str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString); } /** diff --git a/lib/spout/src/Spout/Reader/XLSX/Manager/SharedStringsManager.php b/lib/spout/src/Spout/Reader/XLSX/Manager/SharedStringsManager.php index 18d46811776..caaeed767ac 100644 --- a/lib/spout/src/Spout/Reader/XLSX/Manager/SharedStringsManager.php +++ b/lib/spout/src/Spout/Reader/XLSX/Manager/SharedStringsManager.php @@ -190,7 +190,7 @@ class SharedStringsManager $textNodeValue = $textNode->nodeValue; $shouldPreserveWhitespace = $this->shouldPreserveWhitespace($textNode); - $sharedStringValue .= ($shouldPreserveWhitespace) ? $textNodeValue : trim($textNodeValue); + $sharedStringValue .= ($shouldPreserveWhitespace) ? $textNodeValue : \trim($textNodeValue); } } diff --git a/lib/spout/src/Spout/Reader/XLSX/Manager/SheetManager.php b/lib/spout/src/Spout/Reader/XLSX/Manager/SheetManager.php index 42f287d1829..e7b41e0105f 100644 --- a/lib/spout/src/Spout/Reader/XLSX/Manager/SheetManager.php +++ b/lib/spout/src/Spout/Reader/XLSX/Manager/SheetManager.php @@ -116,7 +116,7 @@ class SheetManager { // 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... - $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); return XMLProcessor::PROCESSING_CONTINUE; @@ -211,7 +211,7 @@ class SheetManager $sheetDataXMLFilePath = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TARGET); // sometimes, the sheet data file path already contains "/xl/"... - if (strpos($sheetDataXMLFilePath, '/xl/') !== 0) { + if (\strpos($sheetDataXMLFilePath, '/xl/') !== 0) { $sheetDataXMLFilePath = '/xl/' . $sheetDataXMLFilePath; break; } diff --git a/lib/spout/src/Spout/Reader/XLSX/Manager/StyleManager.php b/lib/spout/src/Spout/Reader/XLSX/Manager/StyleManager.php index 9232ef1e84f..d5db0d588cc 100644 --- a/lib/spout/src/Spout/Reader/XLSX/Manager/StyleManager.php +++ b/lib/spout/src/Spout/Reader/XLSX/Manager/StyleManager.php @@ -48,7 +48,10 @@ class StyleManager /** @var string Path of the XLSX file being read */ 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; /** @var InternalEntityFactory Factory to create entities */ @@ -75,8 +78,11 @@ class StyleManager { $this->filePath = $filePath; $this->entityFactory = $entityFactory; - $this->builtinNumFmtIdIndicatingDates = array_keys(self::$builtinNumFmtIdToNumFormatMapping); - $this->stylesXMLFilePath = $workbookRelationshipsManager->getStylesXMLFilePath(); + $this->builtinNumFmtIdIndicatingDates = \array_keys(self::$builtinNumFmtIdToNumFormatMapping); + $this->hasStylesXMLFile = $workbookRelationshipsManager->hasStylesXMLFile(); + if ($this->hasStylesXMLFile) { + $this->stylesXMLFilePath = $workbookRelationshipsManager->getStylesXMLFilePath(); + } } /** @@ -88,6 +94,10 @@ class StyleManager */ public function shouldFormatNumericValueAsDate($styleId) { + if (!$this->hasStylesXMLFile) { + return false; + } + $stylesAttributes = $this->getStylesAttributes(); // Default style (0) does not format numeric values as timestamps. Only custom styles do. @@ -263,7 +273,7 @@ class StyleManager */ 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) { // 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; } @@ -288,7 +298,7 @@ class StyleManager { // Remove extra formatting (what's between [ ], the brackets should not be preceded by a "\") $pattern = '((?getWorkbookRelationships(); + + return isset($workbookRelationships[self::RELATIONSHIP_TYPE_STYLES]); + } + + /** + * @return string The path of the styles XML file */ public function getStylesXMLFilePath() { @@ -83,7 +93,7 @@ class WorkbookRelationshipsManager $stylesXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES]; // 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) { // make sure we return a full path $stylesXMLFilePath = self::BASE_PATH . $stylesXMLFilePath; diff --git a/lib/spout/src/Spout/Reader/XLSX/RowIterator.php b/lib/spout/src/Spout/Reader/XLSX/RowIterator.php index f1614258473..4af4530d936 100644 --- a/lib/spout/src/Spout/Reader/XLSX/RowIterator.php +++ b/lib/spout/src/Spout/Reader/XLSX/RowIterator.php @@ -127,7 +127,7 @@ class RowIterator implements IteratorInterface */ protected function normalizeSheetDataXMLFilePath($sheetDataXMLFilePath) { - return ltrim($sheetDataXMLFilePath, '/'); + return \ltrim($sheetDataXMLFilePath, '/'); } /** @@ -234,7 +234,7 @@ class RowIterator implements IteratorInterface { // Read dimensions of the 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; } @@ -257,11 +257,11 @@ class RowIterator implements IteratorInterface $numberOfColumnsForRow = $this->numColumns; $spans = $xmlReader->getAttribute(self::XML_ATTRIBUTE_SPANS); // returns '1:5' for instance if ($spans) { - list(, $numberOfColumnsForRow) = explode(':', $spans); + list(, $numberOfColumnsForRow) = \explode(':', $spans); $numberOfColumnsForRow = (int) $numberOfColumnsForRow; } - $cells = array_fill(0, $numberOfColumnsForRow, $this->entityFactory->createCell('')); + $cells = \array_fill(0, $numberOfColumnsForRow, $this->entityFactory->createCell('')); $this->currentlyProcessedRow->setCells($cells); return XMLProcessor::PROCESSING_CONTINUE; diff --git a/lib/spout/src/Spout/Reader/XLSX/SheetIterator.php b/lib/spout/src/Spout/Reader/XLSX/SheetIterator.php index d9ed17c79d0..81f481c3cdc 100644 --- a/lib/spout/src/Spout/Reader/XLSX/SheetIterator.php +++ b/lib/spout/src/Spout/Reader/XLSX/SheetIterator.php @@ -27,7 +27,7 @@ class SheetIterator implements IteratorInterface // Fetch all available sheets $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.'); } } @@ -51,7 +51,7 @@ class SheetIterator implements IteratorInterface */ public function valid() { - return ($this->currentSheetIndex < count($this->sheets)); + return ($this->currentSheetIndex < \count($this->sheets)); } /** diff --git a/lib/spout/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php b/lib/spout/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php index a35a2806a3e..bc2d406f3f0 100644 --- a/lib/spout/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php +++ b/lib/spout/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php @@ -3,7 +3,9 @@ namespace Box\Spout\Writer\Common\Creator\Style; 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\Exception\InvalidArgumentException; /** * Class StyleBuilder @@ -122,6 +124,25 @@ class StyleBuilder 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 * @@ -148,6 +169,20 @@ class StyleBuilder 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. * diff --git a/lib/spout/src/Spout/Writer/Common/Creator/WriterEntityFactory.php b/lib/spout/src/Spout/Writer/Common/Creator/WriterEntityFactory.php index 3acbed9a266..8e43c31e388 100644 --- a/lib/spout/src/Spout/Writer/Common/Creator/WriterEntityFactory.php +++ b/lib/spout/src/Spout/Writer/Common/Creator/WriterEntityFactory.php @@ -98,7 +98,7 @@ class WriterEntityFactory */ public static function createRowFromArray(array $cellValues = [], Style $rowStyle = null) { - $cells = array_map(function ($cellValue) { + $cells = \array_map(function ($cellValue) { return new Cell($cellValue); }, $cellValues); diff --git a/lib/spout/src/Spout/Writer/Common/Creator/WriterFactory.php b/lib/spout/src/Spout/Writer/Common/Creator/WriterFactory.php index e5045879c3a..96a8eb0382d 100644 --- a/lib/spout/src/Spout/Writer/Common/Creator/WriterFactory.php +++ b/lib/spout/src/Spout/Writer/Common/Creator/WriterFactory.php @@ -35,7 +35,7 @@ class WriterFactory */ public static function createFromFile(string $path) { - $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + $extension = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); return self::createFromType($extension); } diff --git a/lib/spout/src/Spout/Writer/Common/Entity/Workbook.php b/lib/spout/src/Spout/Writer/Common/Entity/Workbook.php index 782310b8527..dd182199da2 100644 --- a/lib/spout/src/Spout/Writer/Common/Entity/Workbook.php +++ b/lib/spout/src/Spout/Writer/Common/Entity/Workbook.php @@ -19,7 +19,7 @@ class Workbook */ public function __construct() { - $this->internalId = uniqid(); + $this->internalId = \uniqid(); } /** diff --git a/lib/spout/src/Spout/Writer/Common/Helper/CellHelper.php b/lib/spout/src/Spout/Writer/Common/Helper/CellHelper.php index 472d310fa9f..afe3c712609 100644 --- a/lib/spout/src/Spout/Writer/Common/Helper/CellHelper.php +++ b/lib/spout/src/Spout/Writer/Common/Helper/CellHelper.php @@ -8,38 +8,39 @@ namespace Box\Spout\Writer\Common\Helper; */ class CellHelper { - /** @var array Cache containing the mapping column index => cell index */ - private static $columnIndexToCellIndexCache = []; + /** @var array Cache containing the mapping column index => column letters */ + 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, * 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. * - * @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', ...) */ - 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... - if (!isset(self::$columnIndexToCellIndexCache[$originalColumnIndex])) { - $cellIndex = ''; - $capitalAAsciiValue = ord('A'); + if (!isset(self::$columnIndexToColumnLettersCache[$originalColumnIndex])) { + $columnLetters = ''; + $capitalAAsciiValue = \ord('A'); do { - $modulus = $columnIndex % 26; - $cellIndex = chr($capitalAAsciiValue + $modulus) . $cellIndex; + $modulus = $columnIndexZeroBased % 26; + $columnLetters = \chr($capitalAAsciiValue + $modulus) . $columnLetters; // substracting 1 because it's zero-based - $columnIndex = (int) ($columnIndex / 26) - 1; - } while ($columnIndex >= 0); + $columnIndexZeroBased = (int) ($columnIndexZeroBased / 26) - 1; + } while ($columnIndexZeroBased >= 0); - self::$columnIndexToCellIndexCache[$originalColumnIndex] = $cellIndex; + self::$columnIndexToColumnLettersCache[$originalColumnIndex] = $columnLetters; } - return self::$columnIndexToCellIndexCache[$originalColumnIndex]; + return self::$columnIndexToColumnLettersCache[$originalColumnIndex]; } } diff --git a/lib/spout/src/Spout/Writer/Common/Helper/ZipHelper.php b/lib/spout/src/Spout/Writer/Common/Helper/ZipHelper.php index b8fcd6c1c5e..7a250c86a0b 100644 --- a/lib/spout/src/Spout/Writer/Common/Helper/ZipHelper.php +++ b/lib/spout/src/Spout/Writer/Common/Helper/ZipHelper.php @@ -135,7 +135,7 @@ class ZipHelper public static function canChooseCompressionMethod() { // 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) { $itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname()); - $itemLocalPath = str_replace($folderRealPath, '', $itemRealPath); + $itemLocalPath = \str_replace($folderRealPath, '', $itemRealPath); if ($itemInfo->isFile() && !$this->shouldSkipFile($zip, $itemLocalPath, $existingFileMode)) { $zip->addFile($itemRealPath, $itemLocalPath); @@ -181,9 +181,9 @@ class ZipHelper */ 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) { - $zipFilePointer = fopen($zipFilePath, 'r'); - stream_copy_to_stream($zipFilePointer, $pointer); - fclose($zipFilePointer); + $zipFilePointer = \fopen($zipFilePath, 'r'); + \stream_copy_to_stream($zipFilePointer, $pointer); + \fclose($zipFilePointer); } } diff --git a/lib/spout/src/Spout/Writer/Common/Manager/SheetManager.php b/lib/spout/src/Spout/Writer/Common/Manager/SheetManager.php index 708137c70ac..d1d4e6a0447 100644 --- a/lib/spout/src/Spout/Writer/Common/Manager/SheetManager.php +++ b/lib/spout/src/Spout/Writer/Common/Manager/SheetManager.php @@ -45,8 +45,8 @@ class SheetManager */ public function throwIfNameIsInvalid($name, Sheet $sheet) { - if (!is_string($name)) { - $actualType = gettype($name); + if (!\is_string($name)) { + $actualType = \gettype($name); $errorMessage = "The sheet's name is invalid. It must be a string ($actualType given)."; 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 .= implode("\n - ", $failedRequirements); + $errorMessage .= \implode("\n - ", $failedRequirements); throw new InvalidSheetNameException($errorMessage); } } @@ -90,7 +90,7 @@ class SheetManager */ 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); } /** diff --git a/lib/spout/src/Spout/Writer/Common/Manager/Style/StyleManager.php b/lib/spout/src/Spout/Writer/Common/Manager/Style/StyleManager.php index 36abdc38297..77eec73fb53 100644 --- a/lib/spout/src/Spout/Writer/Common/Manager/Style/StyleManager.php +++ b/lib/spout/src/Spout/Writer/Common/Manager/Style/StyleManager.php @@ -80,7 +80,7 @@ class StyleManager implements StyleManagerInterface return $cellStyle; } - if ($cell->isString() && strpos($cell->getValue(), "\n") !== false) { + if ($cell->isString() && \strpos($cell->getValue(), "\n") !== false) { $cellStyle->setShouldWrapText(); } diff --git a/lib/spout/src/Spout/Writer/Common/Manager/Style/StyleMerger.php b/lib/spout/src/Spout/Writer/Common/Manager/Style/StyleMerger.php index d3446d28704..806c8d55587 100644 --- a/lib/spout/src/Spout/Writer/Common/Manager/Style/StyleMerger.php +++ b/lib/spout/src/Spout/Writer/Common/Manager/Style/StyleMerger.php @@ -85,9 +85,15 @@ class StyleMerger if (!$style->hasSetWrapText() && $baseStyle->shouldWrapText()) { $styleToUpdate->setShouldWrapText(); } + if (!$style->hasSetCellAlignment() && $baseStyle->shouldApplyCellAlignment()) { + $styleToUpdate->setCellAlignment($baseStyle->getCellAlignment()); + } if (!$style->getBorder() && $baseStyle->shouldApplyBorder()) { $styleToUpdate->setBorder($baseStyle->getBorder()); } + if (!$style->getFormat() && $baseStyle->shouldApplyFormat()) { + $styleToUpdate->setFormat($baseStyle->getFormat()); + } if (!$style->shouldApplyBackgroundColor() && $baseStyle->shouldApplyBackgroundColor()) { $styleToUpdate->setBackgroundColor($baseStyle->getBackgroundColor()); } diff --git a/lib/spout/src/Spout/Writer/Common/Manager/Style/StyleRegistry.php b/lib/spout/src/Spout/Writer/Common/Manager/Style/StyleRegistry.php index fc978e73c8c..d9e315ff262 100644 --- a/lib/spout/src/Spout/Writer/Common/Manager/Style/StyleRegistry.php +++ b/lib/spout/src/Spout/Writer/Common/Manager/Style/StyleRegistry.php @@ -37,7 +37,7 @@ class StyleRegistry $serializedStyle = $this->serialize($style); if (!$this->hasStyleAlreadyBeenRegistered($style)) { - $nextStyleId = count($this->serializedStyleToStyleIdMappingTable); + $nextStyleId = \count($this->serializedStyleToStyleIdMappingTable); $style->setId($nextStyleId); $this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId; @@ -79,7 +79,7 @@ class StyleRegistry */ public function getRegisteredStyles() { - return array_values($this->styleIdToStyleMappingTable); + return \array_values($this->styleIdToStyleMappingTable); } /** @@ -105,7 +105,7 @@ class StyleRegistry $currentId = $style->getId(); $style->setId(0); - $serializedStyle = serialize($style); + $serializedStyle = \serialize($style); $style->setId($currentId); diff --git a/lib/spout/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php b/lib/spout/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php index 7be5c6e1f67..b51355551f9 100644 --- a/lib/spout/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php +++ b/lib/spout/src/Spout/Writer/Common/Manager/WorkbookManagerAbstract.php @@ -124,7 +124,7 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface { $worksheets = $this->getWorksheets(); - $newSheetIndex = count($worksheets); + $newSheetIndex = \count($worksheets); $sheetManager = $this->managerFactory->createSheetManager(); $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 $currentMaxNumColumns = $worksheet->getMaxNumColumns(); $cellsCount = $row->getNumCells(); - $worksheet->setMaxNumColumns(max($currentMaxNumColumns, $cellsCount)); + $worksheet->setMaxNumColumns(\max($currentMaxNumColumns, $cellsCount)); } /** diff --git a/lib/spout/src/Spout/Writer/Exception/Border/InvalidNameException.php b/lib/spout/src/Spout/Writer/Exception/Border/InvalidNameException.php index 8bedc3854b6..c975d4fc483 100644 --- a/lib/spout/src/Spout/Writer/Exception/Border/InvalidNameException.php +++ b/lib/spout/src/Spout/Writer/Exception/Border/InvalidNameException.php @@ -11,6 +11,6 @@ class InvalidNameException extends WriterException { $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()))); } } diff --git a/lib/spout/src/Spout/Writer/Exception/Border/InvalidStyleException.php b/lib/spout/src/Spout/Writer/Exception/Border/InvalidStyleException.php index e4bb145f61a..2d1d78c86d6 100644 --- a/lib/spout/src/Spout/Writer/Exception/Border/InvalidStyleException.php +++ b/lib/spout/src/Spout/Writer/Exception/Border/InvalidStyleException.php @@ -11,6 +11,6 @@ class InvalidStyleException extends WriterException { $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()))); } } diff --git a/lib/spout/src/Spout/Writer/Exception/Border/InvalidWidthException.php b/lib/spout/src/Spout/Writer/Exception/Border/InvalidWidthException.php index f38d8980ea1..790ddc2cb51 100644 --- a/lib/spout/src/Spout/Writer/Exception/Border/InvalidWidthException.php +++ b/lib/spout/src/Spout/Writer/Exception/Border/InvalidWidthException.php @@ -11,6 +11,6 @@ class InvalidWidthException extends WriterException { $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()))); } } diff --git a/lib/spout/src/Spout/Writer/ODS/Helper/BorderHelper.php b/lib/spout/src/Spout/Writer/ODS/Helper/BorderHelper.php index a53f63c4aef..34886acf411 100644 --- a/lib/spout/src/Spout/Writer/ODS/Helper/BorderHelper.php +++ b/lib/spout/src/Spout/Writer/ODS/Helper/BorderHelper.php @@ -53,14 +53,14 @@ class BorderHelper $definition = 'fo:border-%s="%s"'; if ($borderPart->getStyle() === Border::STYLE_NONE) { - $borderPartDefinition = sprintf($definition, $borderPart->getName(), 'none'); + $borderPartDefinition = \sprintf($definition, $borderPart->getName(), 'none'); } else { $attributes = [ self::$widthMap[$borderPart->getWidth()], self::$styleMap[$borderPart->getStyle()], '#' . $borderPart->getColor(), ]; - $borderPartDefinition = sprintf($definition, $borderPart->getName(), implode(' ', $attributes)); + $borderPartDefinition = \sprintf($definition, $borderPart->getName(), \implode(' ', $attributes)); } return $borderPartDefinition; diff --git a/lib/spout/src/Spout/Writer/ODS/Helper/FileSystemHelper.php b/lib/spout/src/Spout/Writer/ODS/Helper/FileSystemHelper.php index d0e2e0507e4..5598bb4a55c 100644 --- a/lib/spout/src/Spout/Writer/ODS/Helper/FileSystemHelper.php +++ b/lib/spout/src/Spout/Writer/ODS/Helper/FileSystemHelper.php @@ -89,7 +89,7 @@ class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper impleme */ protected function createRootFolder() { - $this->rootFolder = $this->createFolder($this->baseFolderRealPath, uniqid('ods')); + $this->rootFolder = $this->createFolder($this->baseFolderRealPath, \uniqid('ods')); return $this; } @@ -210,22 +210,22 @@ EOD; // Append sheets content to "content.xml" $contentXmlFilePath = $this->rootFolder . '/' . self::CONTENT_XML_FILE_NAME; - $contentXmlHandle = fopen($contentXmlFilePath, 'a'); + $contentXmlHandle = \fopen($contentXmlFilePath, 'a'); foreach ($worksheets as $worksheet) { // write the "" node, with the final sheet's name - fwrite($contentXmlHandle, $worksheetManager->getTableElementStartAsString($worksheet)); + \fwrite($contentXmlHandle, $worksheetManager->getTableElementStartAsString($worksheet)); $worksheetFilePath = $worksheet->getFilePath(); $this->copyFileContentsToTarget($worksheetFilePath, $contentXmlHandle); - fwrite($contentXmlHandle, ''); + \fwrite($contentXmlHandle, ''); } $contentXmlFileContents = ''; - fwrite($contentXmlHandle, $contentXmlFileContents); - fclose($contentXmlHandle); + \fwrite($contentXmlHandle, $contentXmlFileContents); + \fclose($contentXmlHandle); return $this; } @@ -241,9 +241,9 @@ EOD; */ protected function copyFileContentsToTarget($sourceFilePath, $targetResource) { - $sourceHandle = fopen($sourceFilePath, 'r'); - stream_copy_to_stream($sourceHandle, $targetResource); - fclose($sourceHandle); + $sourceHandle = \fopen($sourceFilePath, 'r'); + \stream_copy_to_stream($sourceHandle, $targetResource); + \fclose($sourceHandle); } /** diff --git a/lib/spout/src/Spout/Writer/ODS/Manager/OptionsManager.php b/lib/spout/src/Spout/Writer/ODS/Manager/OptionsManager.php index 4d29928cde0..a6fb564ef79 100644 --- a/lib/spout/src/Spout/Writer/ODS/Manager/OptionsManager.php +++ b/lib/spout/src/Spout/Writer/ODS/Manager/OptionsManager.php @@ -42,7 +42,7 @@ class OptionsManager extends OptionsManagerAbstract */ 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::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true); } diff --git a/lib/spout/src/Spout/Writer/ODS/Manager/Style/StyleManager.php b/lib/spout/src/Spout/Writer/ODS/Manager/Style/StyleManager.php index 5b3bb0d5ade..34f75c78d4d 100644 --- a/lib/spout/src/Spout/Writer/ODS/Manager/Style/StyleManager.php +++ b/lib/spout/src/Spout/Writer/ODS/Manager/Style/StyleManager.php @@ -3,6 +3,7 @@ namespace Box\Spout\Writer\ODS\Manager\Style; 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\ODS\Helper\BorderHelper; @@ -199,6 +200,7 @@ EOD; $content = ''; $content .= $this->getTextPropertiesSectionContent($style); + $content .= $this->getParagraphPropertiesSectionContent($style); $content .= $this->getTableCellPropertiesSectionContent($style); $content .= ''; @@ -214,26 +216,26 @@ EOD; */ private function getTextPropertiesSectionContent($style) { - $content = ''; - - if ($style->shouldApplyFont()) { - $content .= $this->getFontSectionContent($style); + if (!$style->shouldApplyFont()) { + return ''; } - return $content; + return 'getFontSectionContent($style) + . '/>'; } /** - * Returns the contents of the "" section, inside "" section + * Returns the contents of the fonts definition section, inside "" section * * @param \Box\Spout\Common\Entity\Style\Style $style + * * @return string */ private function getFontSectionContent($style) { $defaultStyle = $this->getDefaultStyle(); - - $content = 'getFontColor(); if ($fontColor !== $defaultStyle->getFontColor()) { @@ -263,11 +265,60 @@ EOD; $content .= ' style:text-line-through-style="solid"'; } - $content .= '/>'; - return $content; } + /** + * Returns the contents of the "" section, inside "" section + * + * @param \Box\Spout\Common\Entity\Style\Style $style + * + * @return string + */ + private function getParagraphPropertiesSectionContent($style) + { + if (!$style->shouldApplyCellAlignment()) { + return ''; + } + + return 'getCellAlignmentSectionContent($style) + . '/>'; + } + + /** + * Returns the contents of the cell alignment definition for the "" 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 "" section, inside "" section * @@ -276,7 +327,7 @@ EOD; */ private function getTableCellPropertiesSectionContent($style) { - $content = ''; + $content = 'shouldWrapText()) { $content .= $this->getWrapTextXMLContent(); @@ -290,6 +341,8 @@ EOD; $content .= $this->getBackgroundColorXMLContent($style); } + $content .= '/>'; + return $content; } @@ -300,7 +353,7 @@ EOD; */ private function getWrapTextXMLContent() { - return ''; + return ' fo:wrap-option="wrap" style:vertical-align="automatic" '; } /** @@ -311,13 +364,11 @@ EOD; */ private function getBorderXMLContent($style) { - $borderProperty = ''; - - $borders = array_map(function (BorderPart $borderPart) { + $borders = \array_map(function (BorderPart $borderPart) { return BorderHelper::serializeBorderPart($borderPart); }, $style->getBorder()->getParts()); - return sprintf($borderProperty, implode(' ', $borders)); + return \sprintf(' %s ', \implode(' ', $borders)); } /** @@ -328,9 +379,6 @@ EOD; */ private function getBackgroundColorXMLContent($style) { - return sprintf( - '', - $style->getBackgroundColor() - ); + return \sprintf(' fo:background-color="#%s" ', $style->getBackgroundColor()); } } diff --git a/lib/spout/src/Spout/Writer/ODS/Manager/Style/StyleRegistry.php b/lib/spout/src/Spout/Writer/ODS/Manager/Style/StyleRegistry.php index bc9dcccd7ab..6c580d49c7a 100644 --- a/lib/spout/src/Spout/Writer/ODS/Manager/Style/StyleRegistry.php +++ b/lib/spout/src/Spout/Writer/ODS/Manager/Style/StyleRegistry.php @@ -33,6 +33,6 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry */ public function getUsedFonts() { - return array_keys($this->usedFontsSet); + return \array_keys($this->usedFontsSet); } } diff --git a/lib/spout/src/Spout/Writer/ODS/Manager/WorkbookManager.php b/lib/spout/src/Spout/Writer/ODS/Manager/WorkbookManager.php index 367c281656f..77c5f90373b 100644 --- a/lib/spout/src/Spout/Writer/ODS/Manager/WorkbookManager.php +++ b/lib/spout/src/Spout/Writer/ODS/Manager/WorkbookManager.php @@ -56,7 +56,7 @@ class WorkbookManager extends WorkbookManagerAbstract protected function writeAllFilesToDiskAndZipThem($finalFilePointer) { $worksheets = $this->getWorksheets(); - $numWorksheets = count($worksheets); + $numWorksheets = \count($worksheets); $this->fileSystemHelper ->createContentFile($this->worksheetManager, $this->styleManager, $worksheets) diff --git a/lib/spout/src/Spout/Writer/ODS/Manager/WorksheetManager.php b/lib/spout/src/Spout/Writer/ODS/Manager/WorksheetManager.php index 6d76fa0b8dd..4dfe9c8361f 100644 --- a/lib/spout/src/Spout/Writer/ODS/Manager/WorksheetManager.php +++ b/lib/spout/src/Spout/Writer/ODS/Manager/WorksheetManager.php @@ -61,7 +61,7 @@ class WorksheetManager implements WorksheetManagerInterface */ public function startSheet(Worksheet $worksheet) { - $sheetFilePointer = fopen($worksheet->getFilePath(), 'w'); + $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w'); $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer); $worksheet->setFilePointer($sheetFilePointer); @@ -134,7 +134,7 @@ class WorksheetManager implements WorksheetManagerInterface $data .= ''; - $wasWriteSuccessful = fwrite($worksheet->getFilePointer(), $data); + $wasWriteSuccessful = \fwrite($worksheet->getFilePointer(), $data); if ($wasWriteSuccessful === false) { throw new IOException("Unable to write data in {$worksheet->getFilePath()}"); } @@ -190,7 +190,7 @@ class WorksheetManager implements WorksheetManagerInterface if ($cell->isString()) { $data .= ' office:value-type="string" calcext:value-type="string">'; - $cellValueLines = explode("\n", $cell->getValue()); + $cellValueLines = \explode("\n", $cell->getValue()); foreach ($cellValueLines as $cellValueLine) { $data .= '' . $this->stringsEscaper->escape($cellValueLine) . ''; } @@ -204,10 +204,15 @@ class WorksheetManager implements WorksheetManagerInterface $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">'; $data .= '' . $cell->getValue() . ''; $data .= ''; + } 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 .= '' . $cell->getValueEvenIfError() . ''; + $data .= ''; } elseif ($cell->isEmpty()) { $data .= '/>'; } 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; @@ -223,10 +228,10 @@ class WorksheetManager implements WorksheetManagerInterface { $worksheetFilePointer = $worksheet->getFilePointer(); - if (!is_resource($worksheetFilePointer)) { + if (!\is_resource($worksheetFilePointer)) { return; } - fclose($worksheetFilePointer); + \fclose($worksheetFilePointer); } } diff --git a/lib/spout/src/Spout/Writer/WriterAbstract.php b/lib/spout/src/Spout/Writer/WriterAbstract.php index 4bcc407d5d2..d96a6280fab 100644 --- a/lib/spout/src/Spout/Writer/WriterAbstract.php +++ b/lib/spout/src/Spout/Writer/WriterAbstract.php @@ -223,7 +223,7 @@ abstract class WriterAbstract implements WriterInterface $this->closeWriter(); - if (is_resource($this->filePointer)) { + if (\is_resource($this->filePointer)) { $this->globalFunctionsHelper->fclose($this->filePointer); } @@ -243,7 +243,7 @@ abstract class WriterAbstract implements WriterInterface // remove output file if it was created if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) { - $outputFolderPath = dirname($this->outputFilePath); + $outputFolderPath = \dirname($this->outputFilePath); $fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath); $fileSystemHelper->deleteFile($this->outputFilePath); } diff --git a/lib/spout/src/Spout/Writer/XLSX/Helper/BorderHelper.php b/lib/spout/src/Spout/Writer/XLSX/Helper/BorderHelper.php index 1c773fde34e..ed202cb63c3 100644 --- a/lib/spout/src/Spout/Writer/XLSX/Helper/BorderHelper.php +++ b/lib/spout/src/Spout/Writer/XLSX/Helper/BorderHelper.php @@ -43,8 +43,8 @@ class BorderHelper { $borderStyle = self::getBorderStyle($borderPart); - $colorEl = $borderPart->getColor() ? sprintf('', $borderPart->getColor()) : ''; - $partEl = sprintf( + $colorEl = $borderPart->getColor() ? \sprintf('', $borderPart->getColor()) : ''; + $partEl = \sprintf( '<%s style="%s">%s', $borderPart->getName(), $borderStyle, diff --git a/lib/spout/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php b/lib/spout/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php index 8997b98cc2d..0a19d9ed842 100644 --- a/lib/spout/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php +++ b/lib/spout/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php @@ -112,7 +112,7 @@ class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper impleme */ private function createRootFolder() { - $this->rootFolder = $this->createFolder($this->baseFolderRealPath, uniqid('xlsx', true)); + $this->rootFolder = $this->createFolder($this->baseFolderRealPath, \uniqid('xlsx', true)); return $this; } diff --git a/lib/spout/src/Spout/Writer/XLSX/Manager/OptionsManager.php b/lib/spout/src/Spout/Writer/XLSX/Manager/OptionsManager.php index 53718bcff19..d3b5cd42356 100644 --- a/lib/spout/src/Spout/Writer/XLSX/Manager/OptionsManager.php +++ b/lib/spout/src/Spout/Writer/XLSX/Manager/OptionsManager.php @@ -52,7 +52,7 @@ class OptionsManager extends OptionsManagerAbstract ->setFontName(self::DEFAULT_FONT_NAME) ->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::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true); $this->setOption(Options::SHOULD_USE_INLINE_STRINGS, true); diff --git a/lib/spout/src/Spout/Writer/XLSX/Manager/SharedStringsManager.php b/lib/spout/src/Spout/Writer/XLSX/Manager/SharedStringsManager.php index 2a60c3cf3aa..b0a5b48d6c9 100644 --- a/lib/spout/src/Spout/Writer/XLSX/Manager/SharedStringsManager.php +++ b/lib/spout/src/Spout/Writer/XLSX/Manager/SharedStringsManager.php @@ -40,13 +40,13 @@ EOD; public function __construct($xlFolder, $stringsEscaper) { $sharedStringsFilePath = $xlFolder . '/' . self::SHARED_STRINGS_FILE_NAME; - $this->sharedStringsFilePointer = fopen($sharedStringsFilePath, 'w'); + $this->sharedStringsFilePointer = \fopen($sharedStringsFilePath, 'w'); $this->throwIfSharedStringsFilePointerIsNotAvailable(); // 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 . '>'; - fwrite($this->sharedStringsFilePointer, $header); + \fwrite($this->sharedStringsFilePointer, $header); $this->stringsEscaper = $stringsEscaper; } @@ -73,7 +73,7 @@ EOD; */ public function writeString($string) { - fwrite($this->sharedStringsFilePointer, '' . $this->stringsEscaper->escape($string) . ''); + \fwrite($this->sharedStringsFilePointer, '' . $this->stringsEscaper->escape($string) . ''); $this->numSharedStrings++; // Shared string ID is zero-based @@ -87,20 +87,20 @@ EOD; */ public function close() { - if (!is_resource($this->sharedStringsFilePointer)) { + if (!\is_resource($this->sharedStringsFilePointer)) { return; } - fwrite($this->sharedStringsFilePointer, ''); + \fwrite($this->sharedStringsFilePointer, ''); // 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); - $defaultStringsCountPartLength = strlen(self::DEFAULT_STRINGS_COUNT_PART); + $firstPartHeaderLength = \strlen(self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER); + $defaultStringsCountPartLength = \strlen(self::DEFAULT_STRINGS_COUNT_PART); // Adding 1 to take into account the space between the last xml attribute and "count" - fseek($this->sharedStringsFilePointer, $firstPartHeaderLength + 1); - fwrite($this->sharedStringsFilePointer, sprintf("%-{$defaultStringsCountPartLength}s", 'count="' . $this->numSharedStrings . '" uniqueCount="' . $this->numSharedStrings . '"')); + \fseek($this->sharedStringsFilePointer, $firstPartHeaderLength + 1); + \fwrite($this->sharedStringsFilePointer, \sprintf("%-{$defaultStringsCountPartLength}s", 'count="' . $this->numSharedStrings . '" uniqueCount="' . $this->numSharedStrings . '"')); - fclose($this->sharedStringsFilePointer); + \fclose($this->sharedStringsFilePointer); } } diff --git a/lib/spout/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php b/lib/spout/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php index b98307afa98..f0ca9d9e61c 100644 --- a/lib/spout/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php +++ b/lib/spout/src/Spout/Writer/XLSX/Manager/Style/StyleManager.php @@ -33,7 +33,10 @@ class StyleManager extends \Box\Spout\Writer\Common\Manager\Style\StyleManager $associatedBorderId = $this->styleRegistry->getBorderIdForStyleId($styleId); $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 EOD; + $content .= $this->getFormatsSectionContent(); $content .= $this->getFontsSectionContent(); $content .= $this->getFillsSectionContent(); $content .= $this->getBordersSectionContent(); @@ -62,6 +66,35 @@ EOD; return $content; } + /** + * Returns the content of the "" 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[] = ''; + } + $content = ''; + $content .= \implode('', $tags); + $content .= ''; + + return $content; + } + /** * Returns the content of the "" section. * @@ -71,7 +104,7 @@ EOD; { $registeredStyles = $this->styleRegistry->getRegisteredStyles(); - $content = ''; + $content = ''; /** @var Style $style */ foreach ($registeredStyles as $style) { @@ -112,8 +145,8 @@ EOD; $registeredFills = $this->styleRegistry->getRegisteredFills(); // Excel reserves two default fills - $fillsCount = count($registeredFills) + 2; - $content = sprintf('', $fillsCount); + $fillsCount = \count($registeredFills) + 2; + $content = \sprintf('', $fillsCount); $content .= ''; $content .= ''; @@ -124,7 +157,7 @@ EOD; $style = $this->styleRegistry->getStyleFromStyleId($styleId); $backgroundColor = $style->getBackgroundColor(); - $content .= sprintf( + $content .= \sprintf( '', $backgroundColor ); @@ -145,7 +178,7 @@ EOD; $registeredBorders = $this->styleRegistry->getRegisteredBorders(); // There is one default border with index 0 - $borderCount = count($registeredBorders) + 1; + $borderCount = \count($registeredBorders) + 1; $content = ''; @@ -200,24 +233,32 @@ EOD; { $registeredStyles = $this->styleRegistry->getRegisteredStyles(); - $content = ''; + $content = ''; foreach ($registeredStyles as $style) { $styleId = $style->getId(); $fillId = $this->getFillIdForStyleId($styleId); $borderId = $this->getBorderIdForStyleId($styleId); + $numFmtId = $this->getFormatIdForStyleId($styleId); - $content .= 'shouldApplyFont()) { $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 .= ''; + $content .= 'shouldApplyCellAlignment()) { + $content .= \sprintf(' horizontal="%s"', $style->getCellAlignment()); + } + if ($style->shouldWrapText()) { + $content .= ' wrapText="1"'; + } + $content .= '/>'; $content .= ''; } else { $content .= '/>'; @@ -261,6 +302,22 @@ EOD; 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 "" section. * diff --git a/lib/spout/src/Spout/Writer/XLSX/Manager/Style/StyleRegistry.php b/lib/spout/src/Spout/Writer/XLSX/Manager/Style/StyleRegistry.php index abd89f1e489..ace607ca5d2 100644 --- a/lib/spout/src/Spout/Writer/XLSX/Manager/Style/StyleRegistry.php +++ b/lib/spout/src/Spout/Writer/XLSX/Manager/Style/StyleRegistry.php @@ -10,6 +10,79 @@ use Box\Spout\Common\Entity\Style\Style; */ 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 */ @@ -48,11 +121,52 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry { $registeredStyle = parent::registerStyle($style); $this->registerFill($registeredStyle); + $this->registerFormat($registeredStyle); $this->registerBorder($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 * @@ -107,7 +221,7 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry if ($style->shouldApplyBorder()) { $border = $style->getBorder(); - $serializedBorder = serialize($border); + $serializedBorder = \serialize($border); $isBorderAlreadyRegistered = isset($this->registeredBorders[$serializedBorder]); @@ -117,7 +231,7 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry $this->styleIdToBorderMappingTable[$styleId] = $registeredBorderId; } else { $this->registeredBorders[$serializedBorder] = $styleId; - $this->styleIdToBorderMappingTable[$styleId] = count($this->registeredBorders); + $this->styleIdToBorderMappingTable[$styleId] = \count($this->registeredBorders); } } else { // 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 array + */ + public function getRegisteredFormats() + { + return $this->registeredFormats; + } } diff --git a/lib/spout/src/Spout/Writer/XLSX/Manager/WorkbookManager.php b/lib/spout/src/Spout/Writer/XLSX/Manager/WorkbookManager.php index 944516b3c6e..708208bd515 100644 --- a/lib/spout/src/Spout/Writer/XLSX/Manager/WorkbookManager.php +++ b/lib/spout/src/Spout/Writer/XLSX/Manager/WorkbookManager.php @@ -44,7 +44,7 @@ class WorkbookManager extends WorkbookManagerAbstract { $worksheetFilesFolder = $this->fileSystemHelper->getXlWorksheetsFolder(); - return $worksheetFilesFolder . '/' . strtolower($sheet->getName()) . '.xml'; + return $worksheetFilesFolder . '/' . \strtolower($sheet->getName()) . '.xml'; } /** diff --git a/lib/spout/src/Spout/Writer/XLSX/Manager/WorksheetManager.php b/lib/spout/src/Spout/Writer/XLSX/Manager/WorksheetManager.php index b0e458f0da3..741d0aa829f 100644 --- a/lib/spout/src/Spout/Writer/XLSX/Manager/WorksheetManager.php +++ b/lib/spout/src/Spout/Writer/XLSX/Manager/WorksheetManager.php @@ -107,13 +107,13 @@ EOD; */ public function startSheet(Worksheet $worksheet) { - $sheetFilePointer = fopen($worksheet->getFilePath(), 'w'); + $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w'); $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer); $worksheet->setFilePointer($sheetFilePointer); - fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER); - fwrite($sheetFilePointer, ''); + \fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER); + \fwrite($sheetFilePointer, ''); } /** @@ -153,21 +153,19 @@ EOD; */ private function addNonEmptyRow(Worksheet $worksheet, Row $row) { - $cellIndex = 0; $rowStyle = $row->getStyle(); - $rowIndex = $worksheet->getLastWrittenRowIndex() + 1; + $rowIndexOneBased = $worksheet->getLastWrittenRowIndex() + 1; $numCells = $row->getNumCells(); - $rowXML = ''; + $rowXML = ''; - foreach ($row->getCells() as $cell) { - $rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndex, $cellIndex); - $cellIndex++; + foreach ($row->getCells() as $columnIndexZeroBased => $cell) { + $rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndexOneBased, $columnIndexZeroBased); } $rowXML .= ''; - $wasWriteSuccessful = fwrite($worksheet->getFilePointer(), $rowXML); + $wasWriteSuccessful = \fwrite($worksheet->getFilePointer(), $rowXML); if ($wasWriteSuccessful === false) { 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 * Then builds and returns xml for the cell. * - * @param Cell $cell + * @param Cell $cell * @param Style $rowStyle - * @param int $rowIndex - * @param int $cellIndex + * @param int $rowIndexOneBased + * @param int $columnIndexZeroBased + * * @throws InvalidArgumentException If the given value cannot be processed * @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 $mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle); @@ -193,23 +192,24 @@ EOD; $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. * - * @param int $rowIndex - * @param int $cellNumber + * @param int $rowIndexOneBased + * @param int $columnIndexZeroBased * @param Cell $cell - * @param int $styleId + * @param int $styleId + * * @throws InvalidArgumentException If the given value cannot be processed * @return string */ - private function getCellXML($rowIndex, $cellNumber, Cell $cell, $styleId) + private function getCellXML($rowIndexOneBased, $columnIndexZeroBased, Cell $cell, $styleId) { - $columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber); - $cellXML = 'isString()) { @@ -218,6 +218,9 @@ EOD; $cellXML .= ' t="b">' . (int) ($cell->getValue()) . ''; } elseif ($cell->isNumeric()) { $cellXML .= '>' . $cell->getValue() . ''; + } elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) { + // only writes the error value if it's a string + $cellXML .= ' t="e">' . $cell->getValueEvenIfError() . ''; } elseif ($cell->isEmpty()) { if ($this->styleManager->shouldApplyStyleOnEmptyCell($styleId)) { $cellXML .= '/>'; @@ -227,7 +230,7 @@ EOD; $cellXML = ''; } } 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; @@ -263,12 +266,12 @@ EOD; { $worksheetFilePointer = $worksheet->getFilePointer(); - if (!is_resource($worksheetFilePointer)) { + if (!\is_resource($worksheetFilePointer)) { return; } - fwrite($worksheetFilePointer, ''); - fwrite($worksheetFilePointer, ''); - fclose($worksheetFilePointer); + \fwrite($worksheetFilePointer, ''); + \fwrite($worksheetFilePointer, ''); + \fclose($worksheetFilePointer); } }