mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 13:38:32 +01:00
MDL-79667 lib: Upgrade OpenSpout to 4.23.0
This commit is contained in:
parent
b2fa19f45d
commit
73ac16f4ad
@ -27,7 +27,7 @@ abstract class Cell
|
||||
$this->setStyle($style);
|
||||
}
|
||||
|
||||
abstract public function getValue(): null|bool|string|int|float|DateTimeInterface|DateInterval;
|
||||
abstract public function getValue(): null|bool|DateInterval|DateTimeInterface|float|int|string;
|
||||
|
||||
final public function setStyle(?Style $style): void
|
||||
{
|
||||
@ -39,7 +39,7 @@ abstract class Cell
|
||||
return $this->style;
|
||||
}
|
||||
|
||||
final public static function fromValue(null|bool|string|int|float|DateTimeInterface|DateInterval $value, ?Style $style = null): self
|
||||
final public static function fromValue(null|bool|DateInterval|DateTimeInterface|float|int|string $value, ?Style $style = null): self
|
||||
{
|
||||
if (\is_bool($value)) {
|
||||
return new BooleanCell($value, $style);
|
||||
@ -57,7 +57,7 @@ abstract class Cell
|
||||
return new DateIntervalCell($value, $style);
|
||||
}
|
||||
if (isset($value[0]) && '=' === $value[0]) {
|
||||
return new FormulaCell($value, $style);
|
||||
return new FormulaCell($value, $style, null);
|
||||
}
|
||||
|
||||
return new StringCell($value, $style);
|
||||
|
@ -9,7 +9,7 @@ use OpenSpout\Common\Entity\Style\Style;
|
||||
|
||||
final class BooleanCell extends Cell
|
||||
{
|
||||
private bool $value;
|
||||
private readonly bool $value;
|
||||
|
||||
public function __construct(bool $value, ?Style $style)
|
||||
{
|
||||
|
@ -10,8 +10,14 @@ use OpenSpout\Common\Entity\Style\Style;
|
||||
|
||||
final class DateIntervalCell extends Cell
|
||||
{
|
||||
private DateInterval $value;
|
||||
private readonly DateInterval $value;
|
||||
|
||||
/**
|
||||
* For Excel make sure to set a format onto the style (Style::setFormat()) with the left most unit enclosed with
|
||||
* brackets: '[h]:mm', '[hh]:mm:ss', '[m]:ss', '[s]', etc.
|
||||
* This makes sure excel knows what to do with the remaining time that exceeds this unit. Without brackets Excel
|
||||
* will interpret the value as date time and not duration if it is greater or equal 1.
|
||||
*/
|
||||
public function __construct(DateInterval $value, ?Style $style)
|
||||
{
|
||||
$this->value = $value;
|
||||
|
@ -10,7 +10,7 @@ use OpenSpout\Common\Entity\Style\Style;
|
||||
|
||||
final class DateTimeCell extends Cell
|
||||
{
|
||||
private DateTimeInterface $value;
|
||||
private readonly DateTimeInterface $value;
|
||||
|
||||
public function __construct(DateTimeInterface $value, ?Style $style)
|
||||
{
|
||||
|
@ -9,7 +9,7 @@ use OpenSpout\Common\Entity\Style\Style;
|
||||
|
||||
final class EmptyCell extends Cell
|
||||
{
|
||||
private ?string $value;
|
||||
private readonly ?string $value;
|
||||
|
||||
public function __construct(?string $value, ?Style $style)
|
||||
{
|
||||
|
@ -9,7 +9,7 @@ use OpenSpout\Common\Entity\Style\Style;
|
||||
|
||||
final class ErrorCell extends Cell
|
||||
{
|
||||
private string $value;
|
||||
private readonly string $value;
|
||||
|
||||
public function __construct(string $value, ?Style $style)
|
||||
{
|
||||
|
@ -4,16 +4,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Common\Entity\Cell;
|
||||
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use OpenSpout\Common\Entity\Cell;
|
||||
use OpenSpout\Common\Entity\Style\Style;
|
||||
|
||||
final class FormulaCell extends Cell
|
||||
{
|
||||
private string $value;
|
||||
|
||||
public function __construct(string $value, ?Style $style)
|
||||
{
|
||||
$this->value = $value;
|
||||
public function __construct(
|
||||
private readonly string $value,
|
||||
?Style $style,
|
||||
private readonly null|DateInterval|DateTimeImmutable|float|int|string $computedValue = null,
|
||||
) {
|
||||
parent::__construct($style);
|
||||
}
|
||||
|
||||
@ -21,4 +23,9 @@ final class FormulaCell extends Cell
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getComputedValue(): null|DateInterval|DateTimeImmutable|float|int|string
|
||||
{
|
||||
return $this->computedValue;
|
||||
}
|
||||
}
|
||||
|
@ -9,15 +9,15 @@ use OpenSpout\Common\Entity\Style\Style;
|
||||
|
||||
final class NumericCell extends Cell
|
||||
{
|
||||
private int|float $value;
|
||||
private readonly float|int $value;
|
||||
|
||||
public function __construct(int|float $value, ?Style $style)
|
||||
public function __construct(float|int $value, ?Style $style)
|
||||
{
|
||||
$this->value = $value;
|
||||
parent::__construct($style);
|
||||
}
|
||||
|
||||
public function getValue(): int|float
|
||||
public function getValue(): float|int
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use OpenSpout\Common\Entity\Style\Style;
|
||||
|
||||
final class StringCell extends Cell
|
||||
{
|
||||
private string $value;
|
||||
private readonly string $value;
|
||||
|
||||
public function __construct(string $value, ?Style $style)
|
||||
{
|
||||
|
@ -41,7 +41,7 @@ final class Row
|
||||
*/
|
||||
public static function fromValues(array $cellValues = [], ?Style $rowStyle = null): self
|
||||
{
|
||||
$cells = array_map(static function (null|bool|string|int|float|DateTimeInterface|DateInterval $cellValue): Cell {
|
||||
$cells = array_map(static function (null|bool|DateInterval|DateTimeInterface|float|int|string $cellValue): Cell {
|
||||
return Cell::fromValue($cellValue);
|
||||
}, $cellValues);
|
||||
|
||||
@ -134,7 +134,7 @@ final class Row
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return array_map(static function (Cell $cell): null|bool|string|int|float|DateTimeInterface|DateInterval {
|
||||
return array_map(static function (Cell $cell): null|bool|DateInterval|DateTimeInterface|float|int|string {
|
||||
return $cell->getValue();
|
||||
}, $this->cells);
|
||||
}
|
||||
|
@ -31,10 +31,10 @@ final class BorderPart
|
||||
Border::WIDTH_THICK,
|
||||
];
|
||||
|
||||
private string $style;
|
||||
private string $name;
|
||||
private string $color;
|
||||
private string $width;
|
||||
private readonly string $style;
|
||||
private readonly string $name;
|
||||
private readonly string $color;
|
||||
private readonly string $width;
|
||||
|
||||
/**
|
||||
* @param string $name @see BorderPart::allowedNames
|
||||
|
@ -65,7 +65,7 @@ final class Color
|
||||
/**
|
||||
* Throws an exception is the color component value is outside of bounds (0 - 255).
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\InvalidColorException
|
||||
* @throws InvalidColorException
|
||||
*/
|
||||
private static function throwIfInvalidColorComponentValue(int $colorComponent): void
|
||||
{
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Common\Exception;
|
||||
|
||||
final class EncodingConversionException extends OpenSpoutException
|
||||
{
|
||||
}
|
||||
final class EncodingConversionException extends OpenSpoutException {}
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Common\Exception;
|
||||
|
||||
final class IOException extends OpenSpoutException
|
||||
{
|
||||
}
|
||||
final class IOException extends OpenSpoutException {}
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Common\Exception;
|
||||
|
||||
final class InvalidArgumentException extends OpenSpoutException
|
||||
{
|
||||
}
|
||||
final class InvalidArgumentException extends OpenSpoutException {}
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Common\Exception;
|
||||
|
||||
final class InvalidColorException extends OpenSpoutException
|
||||
{
|
||||
}
|
||||
final class InvalidColorException extends OpenSpoutException {}
|
||||
|
@ -6,6 +6,4 @@ namespace OpenSpout\Common\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
abstract class OpenSpoutException extends Exception
|
||||
{
|
||||
}
|
||||
abstract class OpenSpoutException extends Exception {}
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Common\Exception;
|
||||
|
||||
final class UnsupportedTypeException extends OpenSpoutException
|
||||
{
|
||||
}
|
||||
final class UnsupportedTypeException extends OpenSpoutException {}
|
||||
|
@ -33,9 +33,9 @@ final class EncodingHelper
|
||||
/** @var array<string, string> Map representing the encodings supporting BOMs (key) and their associated BOM (value) */
|
||||
private array $supportedEncodingsWithBom;
|
||||
|
||||
private bool $canUseIconv;
|
||||
private readonly bool $canUseIconv;
|
||||
|
||||
private bool $canUseMbString;
|
||||
private readonly bool $canUseMbString;
|
||||
|
||||
public function __construct(bool $canUseIconv, bool $canUseMbString)
|
||||
{
|
||||
@ -89,7 +89,7 @@ final class EncodingHelper
|
||||
*
|
||||
* @return string The converted, UTF-8 string
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed
|
||||
* @throws EncodingConversionException If conversion is not supported or if the conversion failed
|
||||
*/
|
||||
public function attemptConversionToUTF8(?string $string, string $sourceEncoding): ?string
|
||||
{
|
||||
@ -104,7 +104,7 @@ final class EncodingHelper
|
||||
*
|
||||
* @return string The converted string, encoded with the given encoding
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed
|
||||
* @throws EncodingConversionException If conversion is not supported or if the conversion failed
|
||||
*/
|
||||
public function attemptConversionFromUTF8(?string $string, string $targetEncoding): ?string
|
||||
{
|
||||
@ -145,7 +145,7 @@ final class EncodingHelper
|
||||
*
|
||||
* @return string The converted string, encoded with the given encoding
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed
|
||||
* @throws EncodingConversionException If conversion is not supported or if the conversion failed
|
||||
*/
|
||||
private function attemptConversion(?string $string, string $sourceEncoding, string $targetEncoding): ?string
|
||||
{
|
||||
|
@ -33,6 +33,7 @@ final class XLSX implements EscaperInterface
|
||||
$this->initIfNeeded();
|
||||
|
||||
$escapedString = $this->escapeControlCharacters($string);
|
||||
|
||||
// @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as
|
||||
// single/double quotes (for XML attributes) need to be encoded.
|
||||
return htmlspecialchars($escapedString, ENT_QUOTES, 'UTF-8');
|
||||
|
@ -14,7 +14,7 @@ use RecursiveIteratorIterator;
|
||||
final class FileSystemHelper implements FileSystemHelperInterface
|
||||
{
|
||||
/** @var string Real path of the base folder where all the I/O can occur */
|
||||
private string $baseFolderRealPath;
|
||||
private readonly string $baseFolderRealPath;
|
||||
|
||||
/**
|
||||
* @param string $baseFolderPath The path of the base folder where all the I/O can occur
|
||||
@ -39,7 +39,7 @@ final class FileSystemHelper implements FileSystemHelperInterface
|
||||
*
|
||||
* @return string Path of the created folder
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the folder or if the folder path is not inside of the base folder
|
||||
* @throws IOException If unable to create the folder or if the folder path is not inside of the base folder
|
||||
*/
|
||||
public function createFolder(string $parentFolderPath, string $folderName): string
|
||||
{
|
||||
@ -73,7 +73,7 @@ final class FileSystemHelper implements FileSystemHelperInterface
|
||||
*
|
||||
* @return string Path of the created file
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the file or if the file path is not inside of the base folder
|
||||
* @throws IOException If unable to create the file or if the file path is not inside of the base folder
|
||||
*/
|
||||
public function createFileWithContents(string $parentFolderPath, string $fileName, string $fileContents): string
|
||||
{
|
||||
@ -102,7 +102,7 @@ final class FileSystemHelper implements FileSystemHelperInterface
|
||||
*
|
||||
* @param string $filePath Path of the file to delete
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the file path is not inside of the base folder
|
||||
* @throws IOException If the file path is not inside of the base folder
|
||||
*/
|
||||
public function deleteFile(string $filePath): void
|
||||
{
|
||||
@ -118,7 +118,7 @@ final class FileSystemHelper implements FileSystemHelperInterface
|
||||
*
|
||||
* @param string $folderPath Path of the folder to delete
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the folder path is not inside of the base folder
|
||||
* @throws IOException If the folder path is not inside of the base folder
|
||||
*/
|
||||
public function deleteFolderRecursively(string $folderPath): void
|
||||
{
|
||||
@ -147,8 +147,8 @@ final class FileSystemHelper implements FileSystemHelperInterface
|
||||
*
|
||||
* @param string $operationFolderPath The path of the folder where the I/O operation should occur
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the folder where the I/O operation should occur
|
||||
* is not inside the base folder or the base folder does not exist
|
||||
* @throws IOException If the folder where the I/O operation should occur
|
||||
* is not inside the base folder or the base folder does not exist
|
||||
*/
|
||||
private function throwIfOperationNotInBaseFolder(string $operationFolderPath): void
|
||||
{
|
||||
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Common\Helper;
|
||||
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -17,7 +19,7 @@ interface FileSystemHelperInterface
|
||||
*
|
||||
* @return string Path of the created folder
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the folder or if the folder path is not inside of the base folder
|
||||
* @throws IOException If unable to create the folder or if the folder path is not inside of the base folder
|
||||
*/
|
||||
public function createFolder(string $parentFolderPath, string $folderName): string;
|
||||
|
||||
@ -31,7 +33,7 @@ interface FileSystemHelperInterface
|
||||
*
|
||||
* @return string Path of the created file
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the file or if the file path is not inside of the base folder
|
||||
* @throws IOException If unable to create the file or if the file path is not inside of the base folder
|
||||
*/
|
||||
public function createFileWithContents(string $parentFolderPath, string $fileName, string $fileContents): string;
|
||||
|
||||
@ -40,7 +42,7 @@ interface FileSystemHelperInterface
|
||||
*
|
||||
* @param string $filePath Path of the file to delete
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the file path is not inside of the base folder
|
||||
* @throws IOException If the file path is not inside of the base folder
|
||||
*/
|
||||
public function deleteFile(string $filePath): void;
|
||||
|
||||
@ -49,7 +51,7 @@ interface FileSystemHelperInterface
|
||||
*
|
||||
* @param string $folderPath Path of the folder to delete
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the folder path is not inside of the base folder
|
||||
* @throws IOException If the folder path is not inside of the base folder
|
||||
*/
|
||||
public function deleteFolderRecursively(string $folderPath): void;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ namespace OpenSpout\Common\Helper;
|
||||
final class StringHelper
|
||||
{
|
||||
/** @var bool Whether the mbstring extension is loaded */
|
||||
private bool $hasMbstringSupport;
|
||||
private readonly bool $hasMbstringSupport;
|
||||
|
||||
public function __construct(bool $hasMbstringSupport)
|
||||
{
|
||||
|
@ -24,7 +24,7 @@ abstract class AbstractReader implements ReaderInterface
|
||||
*
|
||||
* @param string $filePath Path of the file to be read
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the file at the given path does not exist, is not readable or is corrupted
|
||||
* @throws IOException If the file at the given path does not exist, is not readable or is corrupted
|
||||
*/
|
||||
public function open(string $filePath): void
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Reader\CSV;
|
||||
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
use OpenSpout\Common\Helper\EncodingHelper;
|
||||
use OpenSpout\Reader\AbstractReader;
|
||||
|
||||
@ -18,14 +19,8 @@ final class Reader extends AbstractReader
|
||||
/** @var SheetIterator To iterator over the CSV unique "sheet" */
|
||||
private SheetIterator $sheetIterator;
|
||||
|
||||
/** @var string Original value for the "auto_detect_line_endings" INI value */
|
||||
private string $originalAutoDetectLineEndings;
|
||||
|
||||
/** @var bool Whether the code is running with PHP >= 8.1 */
|
||||
private bool $isRunningAtLeastPhp81;
|
||||
|
||||
private Options $options;
|
||||
private EncodingHelper $encodingHelper;
|
||||
private readonly Options $options;
|
||||
private readonly EncodingHelper $encodingHelper;
|
||||
|
||||
public function __construct(
|
||||
?Options $options = null,
|
||||
@ -33,7 +28,6 @@ final class Reader extends AbstractReader
|
||||
) {
|
||||
$this->options = $options ?? new Options();
|
||||
$this->encodingHelper = $encodingHelper ?? EncodingHelper::factory();
|
||||
$this->isRunningAtLeastPhp81 = \PHP_VERSION_ID >= 80100;
|
||||
}
|
||||
|
||||
public function getSheetIterator(): SheetIterator
|
||||
@ -57,20 +51,10 @@ final class Reader extends AbstractReader
|
||||
*
|
||||
* @param string $filePath Path of the CSV file to be read
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function openReader(string $filePath): void
|
||||
{
|
||||
// "auto_detect_line_endings" is deprecated in PHP 8.1
|
||||
if (!$this->isRunningAtLeastPhp81) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$originalAutoDetectLineEndings = \ini_get('auto_detect_line_endings');
|
||||
\assert(false !== $originalAutoDetectLineEndings);
|
||||
$this->originalAutoDetectLineEndings = $originalAutoDetectLineEndings;
|
||||
ini_set('auto_detect_line_endings', '1');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$resource = fopen($filePath, 'r');
|
||||
\assert(false !== $resource);
|
||||
$this->filePointer = $resource;
|
||||
@ -92,12 +76,5 @@ final class Reader extends AbstractReader
|
||||
protected function closeReader(): void
|
||||
{
|
||||
fclose($this->filePointer);
|
||||
|
||||
// "auto_detect_line_endings" is deprecated in PHP 8.1
|
||||
if (!$this->isRunningAtLeastPhp81) {
|
||||
// @codeCoverageIgnoreStart
|
||||
ini_set('auto_detect_line_endings', $this->originalAutoDetectLineEndings);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ namespace OpenSpout\Reader\CSV;
|
||||
|
||||
use OpenSpout\Common\Entity\Cell;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Common\Exception\EncodingConversionException;
|
||||
use OpenSpout\Common\Helper\EncodingHelper;
|
||||
use OpenSpout\Reader\RowIteratorInterface;
|
||||
|
||||
@ -26,15 +27,15 @@ final class RowIterator implements RowIteratorInterface
|
||||
private int $numReadRows = 0;
|
||||
|
||||
/** @var null|Row Buffer used to store the current row, while checking if there are more rows to read */
|
||||
private ?Row $rowBuffer;
|
||||
private ?Row $rowBuffer = null;
|
||||
|
||||
/** @var bool Indicates whether all rows have been read */
|
||||
private bool $hasReachedEndOfFile = false;
|
||||
|
||||
private Options $options;
|
||||
private readonly Options $options;
|
||||
|
||||
/** @var EncodingHelper Helper to work with different encodings */
|
||||
private EncodingHelper $encodingHelper;
|
||||
private readonly EncodingHelper $encodingHelper;
|
||||
|
||||
/**
|
||||
* @param resource $filePointer Pointer to the CSV file to read
|
||||
@ -79,7 +80,7 @@ final class RowIterator implements RowIteratorInterface
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.next.php
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\EncodingConversionException If unable to convert data to UTF-8
|
||||
* @throws EncodingConversionException If unable to convert data to UTF-8
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
@ -123,7 +124,7 @@ final class RowIterator implements RowIteratorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \OpenSpout\Common\Exception\EncodingConversionException If unable to convert data to UTF-8
|
||||
* @throws EncodingConversionException If unable to convert data to UTF-8
|
||||
*/
|
||||
private function readDataForNextRow(): void
|
||||
{
|
||||
@ -168,7 +169,7 @@ final class RowIterator implements RowIteratorInterface
|
||||
*
|
||||
* @return array<int, null|string>|false The row for the current file pointer, encoded in UTF-8 or FALSE if nothing to read
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\EncodingConversionException If unable to convert data to UTF-8
|
||||
* @throws EncodingConversionException If unable to convert data to UTF-8
|
||||
*/
|
||||
private function getNextUTF8EncodedRow(): array|false
|
||||
{
|
||||
|
@ -12,7 +12,7 @@ use OpenSpout\Reader\SheetInterface;
|
||||
final class Sheet implements SheetInterface
|
||||
{
|
||||
/** @var RowIterator To iterate over the CSV's rows */
|
||||
private RowIterator $rowIterator;
|
||||
private readonly RowIterator $rowIterator;
|
||||
|
||||
/**
|
||||
* @param RowIterator $rowIterator Corresponding row iterator
|
||||
|
@ -12,7 +12,7 @@ use OpenSpout\Reader\SheetIteratorInterface;
|
||||
final class SheetIterator implements SheetIteratorInterface
|
||||
{
|
||||
/** @var Sheet The CSV unique "sheet" */
|
||||
private Sheet $sheet;
|
||||
private readonly Sheet $sheet;
|
||||
|
||||
/** @var bool Whether the unique "sheet" has already been read */
|
||||
private bool $hasReadUniqueSheet = false;
|
||||
|
@ -14,9 +14,8 @@ final class ColumnWidth
|
||||
* @param positive-int $end
|
||||
*/
|
||||
public function __construct(
|
||||
public int $start,
|
||||
public int $end,
|
||||
public float $width,
|
||||
) {
|
||||
}
|
||||
public readonly int $start,
|
||||
public readonly int $end,
|
||||
public readonly float $width,
|
||||
) {}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ use OpenSpout\Reader\XLSX\Reader as XLSXReader;
|
||||
/**
|
||||
* This factory is used to create readers, based on the type of the file to be read.
|
||||
* It supports CSV, XLSX and ODS formats.
|
||||
*
|
||||
* @deprecated Guessing mechanisms are brittle by nature and won't be provided by this library anymore
|
||||
*/
|
||||
final class ReaderFactory
|
||||
{
|
||||
@ -22,7 +24,7 @@ final class ReaderFactory
|
||||
*
|
||||
* @param string $path The path to the spreadsheet file. Supported extensions are .csv,.ods and .xlsx
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\UnsupportedTypeException
|
||||
* @throws UnsupportedTypeException
|
||||
*/
|
||||
public static function createFromFile(string $path): ReaderInterface
|
||||
{
|
||||
@ -41,8 +43,8 @@ final class ReaderFactory
|
||||
*
|
||||
* @param string $path the path to the spreadsheet file
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\UnsupportedTypeException
|
||||
* @throws \OpenSpout\Common\Exception\IOException
|
||||
* @throws UnsupportedTypeException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static function createFromFileByMimeType(string $path): ReaderInterface
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Reader\Common;
|
||||
|
||||
use OpenSpout\Reader\Exception\XMLProcessingException;
|
||||
use OpenSpout\Reader\Wrapper\XMLReader;
|
||||
use ReflectionMethod;
|
||||
|
||||
@ -25,7 +26,7 @@ final class XMLProcessor
|
||||
public const PROCESSING_STOP = 2;
|
||||
|
||||
/** @var XMLReader The XMLReader object that will help read sheet's XML data */
|
||||
private XMLReader $xmlReader;
|
||||
private readonly XMLReader $xmlReader;
|
||||
|
||||
/** @var array<string, array{reflectionMethod: ReflectionMethod, reflectionObject: object}> Registered callbacks */
|
||||
private array $callbacks = [];
|
||||
@ -55,7 +56,7 @@ final class XMLProcessor
|
||||
* Resumes the reading of the XML file where it was left off.
|
||||
* Stops whenever a callback indicates that reading should stop or at the end of the file.
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\XMLProcessingException
|
||||
* @throws XMLProcessingException
|
||||
*/
|
||||
public function readUntilStopped(): void
|
||||
{
|
||||
|
@ -8,7 +8,7 @@ use Throwable;
|
||||
|
||||
final class InvalidValueException extends ReaderException
|
||||
{
|
||||
private string $invalidValue;
|
||||
private readonly string $invalidValue;
|
||||
|
||||
public function __construct(string $invalidValue, string $message = '', int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Reader\Exception;
|
||||
|
||||
final class IteratorNotRewindableException extends ReaderException
|
||||
{
|
||||
}
|
||||
final class IteratorNotRewindableException extends ReaderException {}
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Reader\Exception;
|
||||
|
||||
final class NoSheetsFoundException extends ReaderException
|
||||
{
|
||||
}
|
||||
final class NoSheetsFoundException extends ReaderException {}
|
||||
|
@ -6,6 +6,4 @@ namespace OpenSpout\Reader\Exception;
|
||||
|
||||
use OpenSpout\Common\Exception\OpenSpoutException;
|
||||
|
||||
abstract class ReaderException extends OpenSpoutException
|
||||
{
|
||||
}
|
||||
abstract class ReaderException extends OpenSpoutException {}
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Reader\Exception;
|
||||
|
||||
final class ReaderNotOpenedException extends ReaderException
|
||||
{
|
||||
}
|
||||
final class ReaderNotOpenedException extends ReaderException {}
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Reader\Exception;
|
||||
|
||||
final class SharedStringNotFoundException extends ReaderException
|
||||
{
|
||||
}
|
||||
final class SharedStringNotFoundException extends ReaderException {}
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Reader\Exception;
|
||||
|
||||
final class XMLProcessingException extends ReaderException
|
||||
{
|
||||
}
|
||||
final class XMLProcessingException extends ReaderException {}
|
||||
|
@ -61,10 +61,10 @@ final class CellValueFormatter
|
||||
];
|
||||
|
||||
/** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
|
||||
private bool $shouldFormatDates;
|
||||
private readonly bool $shouldFormatDates;
|
||||
|
||||
/** @var ODS Used to unescape XML data */
|
||||
private ODS $escaper;
|
||||
private readonly ODS $escaper;
|
||||
|
||||
/**
|
||||
* @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
|
||||
@ -150,7 +150,7 @@ final class CellValueFormatter
|
||||
*
|
||||
* @throws InvalidValueException If the value is not a valid date
|
||||
*/
|
||||
private function formatDateCellValue(DOMElement $node): string|DateTimeImmutable
|
||||
private function formatDateCellValue(DOMElement $node): DateTimeImmutable|string
|
||||
{
|
||||
// The XML node looks like this:
|
||||
// <table:table-cell calcext:value-type="date" office:date-value="2016-05-19T16:39:00" office:value-type="date">
|
||||
|
@ -43,7 +43,7 @@ final class SettingsHelper
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (XMLProcessingException $exception) { // @codeCoverageIgnore
|
||||
} catch (XMLProcessingException) { // @codeCoverageIgnore
|
||||
// do nothing
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ namespace OpenSpout\Reader\ODS;
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
use OpenSpout\Common\Helper\Escaper\ODS;
|
||||
use OpenSpout\Reader\AbstractReader;
|
||||
use OpenSpout\Reader\Exception\NoSheetsFoundException;
|
||||
use OpenSpout\Reader\ODS\Helper\SettingsHelper;
|
||||
use ZipArchive;
|
||||
|
||||
@ -17,7 +18,7 @@ final class Reader extends AbstractReader
|
||||
{
|
||||
private ZipArchive $zip;
|
||||
|
||||
private Options $options;
|
||||
private readonly Options $options;
|
||||
|
||||
/** @var SheetIterator To iterator over the ODS sheets */
|
||||
private SheetIterator $sheetIterator;
|
||||
@ -47,8 +48,8 @@ final class Reader extends AbstractReader
|
||||
*
|
||||
* @param string $filePath Path of the file to be read
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the file at the given path or its content cannot be read
|
||||
* @throws \OpenSpout\Reader\Exception\NoSheetsFoundException If there are no sheets in the file
|
||||
* @throws IOException If the file at the given path or its content cannot be read
|
||||
* @throws NoSheetsFoundException If there are no sheets in the file
|
||||
*/
|
||||
protected function openReader(string $filePath): void
|
||||
{
|
||||
|
@ -7,9 +7,11 @@ namespace OpenSpout\Reader\ODS;
|
||||
use DOMElement;
|
||||
use OpenSpout\Common\Entity\Cell;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
use OpenSpout\Reader\Common\XMLProcessor;
|
||||
use OpenSpout\Reader\Exception\InvalidValueException;
|
||||
use OpenSpout\Reader\Exception\IteratorNotRewindableException;
|
||||
use OpenSpout\Reader\Exception\SharedStringNotFoundException;
|
||||
use OpenSpout\Reader\ODS\Helper\CellValueFormatter;
|
||||
use OpenSpout\Reader\RowIteratorInterface;
|
||||
use OpenSpout\Reader\Wrapper\XMLReader;
|
||||
@ -30,13 +32,13 @@ final class RowIterator implements RowIteratorInterface
|
||||
public const XML_ATTRIBUTE_NUM_ROWS_REPEATED = 'table:number-rows-repeated';
|
||||
public const XML_ATTRIBUTE_NUM_COLUMNS_REPEATED = 'table:number-columns-repeated';
|
||||
|
||||
private Options $options;
|
||||
private readonly Options $options;
|
||||
|
||||
/** @var XMLProcessor Helper Object to process XML nodes */
|
||||
private XMLProcessor $xmlProcessor;
|
||||
private readonly XMLProcessor $xmlProcessor;
|
||||
|
||||
/** @var Helper\CellValueFormatter Helper to format cell values */
|
||||
private Helper\CellValueFormatter $cellValueFormatter;
|
||||
private readonly Helper\CellValueFormatter $cellValueFormatter;
|
||||
|
||||
/** @var bool Whether the iterator has already been rewound once */
|
||||
private bool $hasAlreadyBeenRewound = false;
|
||||
@ -45,7 +47,7 @@ final class RowIterator implements RowIteratorInterface
|
||||
private Row $currentlyProcessedRow;
|
||||
|
||||
/** @var null|Row Buffer used to store the current row, while checking if there are more rows to read */
|
||||
private ?Row $rowBuffer;
|
||||
private ?Row $rowBuffer = null;
|
||||
|
||||
/** @var bool Indicates whether all rows have been read */
|
||||
private bool $hasReachedEndOfFile = false;
|
||||
@ -57,7 +59,7 @@ final class RowIterator implements RowIteratorInterface
|
||||
private int $nextRowIndexToBeProcessed = 1;
|
||||
|
||||
/** @var null|Cell Last processed cell (because when reading cell at column N+1, cell N is processed) */
|
||||
private ?Cell $lastProcessedCell;
|
||||
private ?Cell $lastProcessedCell = null;
|
||||
|
||||
/** @var int Number of times the last processed row should be repeated */
|
||||
private int $numRowsRepeated = 1;
|
||||
@ -90,7 +92,7 @@ final class RowIterator implements RowIteratorInterface
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.rewind.php
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\IteratorNotRewindableException If the iterator is rewound more than once
|
||||
* @throws IteratorNotRewindableException If the iterator is rewound more than once
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
@ -125,8 +127,8 @@ final class RowIterator implements RowIteratorInterface
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.next.php
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\SharedStringNotFoundException If a shared string was not found
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to read the sheet data XML
|
||||
* @throws SharedStringNotFoundException If a shared string was not found
|
||||
* @throws IOException If unable to read the sheet data XML
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
@ -176,8 +178,8 @@ final class RowIterator implements RowIteratorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \OpenSpout\Reader\Exception\SharedStringNotFoundException If a shared string was not found
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to read the sheet data XML
|
||||
* @throws SharedStringNotFoundException If a shared string was not found
|
||||
* @throws IOException If unable to read the sheet data XML
|
||||
*/
|
||||
private function readDataForNextRow(): void
|
||||
{
|
||||
|
@ -12,19 +12,19 @@ use OpenSpout\Reader\SheetWithVisibilityInterface;
|
||||
final class Sheet implements SheetWithVisibilityInterface
|
||||
{
|
||||
/** @var RowIterator To iterate over sheet's rows */
|
||||
private RowIterator $rowIterator;
|
||||
private readonly RowIterator $rowIterator;
|
||||
|
||||
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
|
||||
private int $index;
|
||||
private readonly int $index;
|
||||
|
||||
/** @var string Name of the sheet */
|
||||
private string $name;
|
||||
private readonly string $name;
|
||||
|
||||
/** @var bool Whether the sheet was the active one */
|
||||
private bool $isActive;
|
||||
private readonly bool $isActive;
|
||||
|
||||
/** @var bool Whether the sheet is visible */
|
||||
private bool $isVisible;
|
||||
private readonly bool $isVisible;
|
||||
|
||||
/**
|
||||
* @param RowIterator $rowIterator The corresponding row iterator
|
||||
|
@ -35,15 +35,15 @@ final class SheetIterator implements SheetIteratorInterface
|
||||
public const XML_ATTRIBUTE_TABLE_DISPLAY = 'table:display';
|
||||
|
||||
/** @var string Path of the file to be read */
|
||||
private string $filePath;
|
||||
private readonly string $filePath;
|
||||
|
||||
private Options $options;
|
||||
private readonly Options $options;
|
||||
|
||||
/** @var XMLReader The XMLReader object that will help read sheet's XML data */
|
||||
private XMLReader $xmlReader;
|
||||
private readonly XMLReader $xmlReader;
|
||||
|
||||
/** @var ODS Used to unescape XML data */
|
||||
private ODS $escaper;
|
||||
private readonly ODS $escaper;
|
||||
|
||||
/** @var bool Whether there are still at least a sheet to be read */
|
||||
private bool $hasFoundSheet;
|
||||
@ -52,7 +52,7 @@ final class SheetIterator implements SheetIteratorInterface
|
||||
private int $currentSheetIndex;
|
||||
|
||||
/** @var string The name of the sheet that was defined as active */
|
||||
private ?string $activeSheetName;
|
||||
private readonly ?string $activeSheetName;
|
||||
|
||||
/** @var array<string, bool> Associative array [STYLE_NAME] => [IS_SHEET_VISIBLE] */
|
||||
private array $sheetsVisibility;
|
||||
@ -75,7 +75,7 @@ final class SheetIterator implements SheetIteratorInterface
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.rewind.php
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to open the XML file containing sheets' data
|
||||
* @throws IOException If unable to open the XML file containing sheets' data
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Reader;
|
||||
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
|
||||
/**
|
||||
* @template T of SheetIteratorInterface
|
||||
*/
|
||||
@ -15,7 +17,7 @@ interface ReaderInterface
|
||||
*
|
||||
* @param string $filePath Path of the file to be read
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException
|
||||
* @throws IOException
|
||||
*/
|
||||
public function open(string $filePath): void;
|
||||
|
||||
@ -24,7 +26,7 @@ interface ReaderInterface
|
||||
*
|
||||
* @return T
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\ReaderNotOpenedException If called before opening the reader
|
||||
* @throws Exception\ReaderNotOpenedException If called before opening the reader
|
||||
*/
|
||||
public function getSheetIterator(): SheetIteratorInterface;
|
||||
|
||||
|
@ -28,7 +28,7 @@ trait XMLInternalErrorsHelper
|
||||
* Throws an XMLProcessingException if an error occured.
|
||||
* It also always resets the "libxml_use_internal_errors" setting back to its initial value.
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\XMLProcessingException
|
||||
* @throws XMLProcessingException
|
||||
*/
|
||||
private function resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured(): void
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Reader\Wrapper;
|
||||
|
||||
use OpenSpout\Reader\Exception\XMLProcessingException;
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
@ -59,7 +60,7 @@ final class XMLReader extends \XMLReader
|
||||
*
|
||||
* @see \XMLReader::read
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\XMLProcessingException If an error/warning occurred
|
||||
* @throws XMLProcessingException If an error/warning occurred
|
||||
*/
|
||||
public function read(): bool
|
||||
{
|
||||
@ -79,7 +80,7 @@ final class XMLReader extends \XMLReader
|
||||
*
|
||||
* @return bool TRUE on success or FALSE on failure
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\XMLProcessingException If an error/warning occurred
|
||||
* @throws XMLProcessingException If an error/warning occurred
|
||||
*/
|
||||
public function readUntilNodeFound(string $nodeName): bool
|
||||
{
|
||||
@ -98,7 +99,7 @@ final class XMLReader extends \XMLReader
|
||||
*
|
||||
* @param null|string $localName The name of the next node to move to
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\XMLProcessingException If an error/warning occurred
|
||||
* @throws XMLProcessingException If an error/warning occurred
|
||||
*/
|
||||
public function next($localName = null): bool
|
||||
{
|
||||
|
@ -27,7 +27,7 @@ final class CellHelper
|
||||
*
|
||||
* @param string $cellIndex The Excel cell index ('A1', 'BC13', ...)
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\InvalidArgumentException When the given cell index is invalid
|
||||
* @throws InvalidArgumentException When the given cell index is invalid
|
||||
*/
|
||||
public static function getColumnIndexFromCellIndex(string $cellIndex): int
|
||||
{
|
||||
|
@ -4,9 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Reader\XLSX\Helper;
|
||||
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use DOMElement;
|
||||
use Exception;
|
||||
use OpenSpout\Common\Entity\Cell;
|
||||
use OpenSpout\Common\Helper\Escaper\XLSX;
|
||||
use OpenSpout\Reader\Exception\InvalidValueException;
|
||||
use OpenSpout\Reader\XLSX\Manager\SharedStringsManager;
|
||||
@ -33,6 +35,7 @@ final class CellValueFormatter
|
||||
*/
|
||||
public const XML_NODE_VALUE = 'v';
|
||||
public const XML_NODE_INLINE_STRING_VALUE = 't';
|
||||
public const XML_NODE_FORMULA = 'f';
|
||||
|
||||
/**
|
||||
* Definition of XML attributes used to parse data.
|
||||
@ -46,19 +49,19 @@ final class CellValueFormatter
|
||||
public const NUM_SECONDS_IN_ONE_DAY = 86400;
|
||||
|
||||
/** @var SharedStringsManager Manages shared strings */
|
||||
private SharedStringsManager $sharedStringsManager;
|
||||
private readonly SharedStringsManager $sharedStringsManager;
|
||||
|
||||
/** @var StyleManagerInterface Manages styles */
|
||||
private StyleManagerInterface $styleManager;
|
||||
private readonly StyleManagerInterface $styleManager;
|
||||
|
||||
/** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
|
||||
private bool $shouldFormatDates;
|
||||
private readonly bool $shouldFormatDates;
|
||||
|
||||
/** @var bool Whether date/time values should use a calendar starting in 1904 instead of 1900 */
|
||||
private bool $shouldUse1904Dates;
|
||||
private readonly bool $shouldUse1904Dates;
|
||||
|
||||
/** @var XLSX Used to unescape XML data */
|
||||
private XLSX $escaper;
|
||||
private readonly XLSX $escaper;
|
||||
|
||||
/**
|
||||
* @param SharedStringsManager $sharedStringsManager Manages shared strings
|
||||
@ -83,31 +86,44 @@ final class CellValueFormatter
|
||||
|
||||
/**
|
||||
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
|
||||
*
|
||||
* @return bool|DateTimeImmutable|float|int|string The value associated with the cell
|
||||
*
|
||||
* @throws InvalidValueException If the value is not valid
|
||||
*/
|
||||
public function extractAndFormatNodeValue(DOMElement $node): bool|DateTimeImmutable|float|int|string
|
||||
public function extractAndFormatNodeValue(DOMElement $node): Cell
|
||||
{
|
||||
// Default cell type is "n"
|
||||
$cellType = $node->getAttribute(self::XML_ATTRIBUTE_TYPE) ?: self::CELL_TYPE_NUMERIC;
|
||||
$cellStyleId = (int) $node->getAttribute(self::XML_ATTRIBUTE_STYLE_ID);
|
||||
$cellType = $node->getAttribute(self::XML_ATTRIBUTE_TYPE);
|
||||
if ('' === $cellType) {
|
||||
$cellType = self::CELL_TYPE_NUMERIC;
|
||||
}
|
||||
$vNodeValue = $this->getVNodeValue($node);
|
||||
|
||||
if (('' === $vNodeValue) && (self::CELL_TYPE_INLINE_STRING !== $cellType)) {
|
||||
return $vNodeValue;
|
||||
if (self::CELL_TYPE_NUMERIC === $cellType) {
|
||||
$fNodeValue = $node->getElementsByTagName(self::XML_NODE_FORMULA)->item(0)?->nodeValue;
|
||||
if (null !== $fNodeValue) {
|
||||
$computedValue = $this->formatNumericCellValue($vNodeValue, (int) $node->getAttribute(self::XML_ATTRIBUTE_STYLE_ID));
|
||||
|
||||
return new Cell\FormulaCell('='.$fNodeValue, null, $computedValue);
|
||||
}
|
||||
}
|
||||
|
||||
return match ($cellType) {
|
||||
if ('' === $vNodeValue && self::CELL_TYPE_INLINE_STRING !== $cellType) {
|
||||
return Cell::fromValue($vNodeValue);
|
||||
}
|
||||
|
||||
$rawValue = match ($cellType) {
|
||||
self::CELL_TYPE_INLINE_STRING => $this->formatInlineStringCellValue($node),
|
||||
self::CELL_TYPE_SHARED_STRING => $this->formatSharedStringCellValue($vNodeValue),
|
||||
self::CELL_TYPE_STR => $this->formatStrCellValue($vNodeValue),
|
||||
self::CELL_TYPE_BOOLEAN => $this->formatBooleanCellValue($vNodeValue),
|
||||
self::CELL_TYPE_NUMERIC => $this->formatNumericCellValue($vNodeValue, $cellStyleId),
|
||||
self::CELL_TYPE_NUMERIC => $this->formatNumericCellValue($vNodeValue, (int) $node->getAttribute(self::XML_ATTRIBUTE_STYLE_ID)),
|
||||
self::CELL_TYPE_DATE => $this->formatDateCellValue($vNodeValue),
|
||||
default => throw new InvalidValueException($vNodeValue),
|
||||
default => new Cell\ErrorCell($vNodeValue, null),
|
||||
};
|
||||
|
||||
if ($rawValue instanceof Cell) {
|
||||
return $rawValue;
|
||||
}
|
||||
|
||||
return Cell::fromValue($rawValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -178,13 +194,15 @@ final class CellValueFormatter
|
||||
*
|
||||
* @param int $cellStyleId 0 being the default style
|
||||
*/
|
||||
private function formatNumericCellValue(int|float|string $nodeValue, int $cellStyleId): DateTimeImmutable|float|int|string
|
||||
private function formatNumericCellValue(float|int|string $nodeValue, int $cellStyleId): DateInterval|DateTimeImmutable|float|int|string
|
||||
{
|
||||
// Numeric values can represent numbers as well as timestamps.
|
||||
// We need to look at the style of the cell to determine whether it is one or the other.
|
||||
$shouldFormatAsDate = $this->styleManager->shouldFormatNumericValueAsDate($cellStyleId);
|
||||
$formatCode = $this->styleManager->getNumberFormatCode($cellStyleId);
|
||||
|
||||
if ($shouldFormatAsDate) {
|
||||
if (DateIntervalFormatHelper::isDurationFormat($formatCode)) {
|
||||
$cellValue = $this->formatExcelDateIntervalValue((float) $nodeValue, $formatCode);
|
||||
} elseif ($this->styleManager->shouldFormatNumericValueAsDate($cellStyleId)) {
|
||||
$cellValue = $this->formatExcelTimestampValue((float) $nodeValue, $cellStyleId);
|
||||
} else {
|
||||
$nodeIntValue = (int) $nodeValue;
|
||||
@ -195,6 +213,16 @@ final class CellValueFormatter
|
||||
return $cellValue;
|
||||
}
|
||||
|
||||
private function formatExcelDateIntervalValue(float $nodeValue, string $excelFormat): DateInterval|string
|
||||
{
|
||||
$dateInterval = DateIntervalFormatHelper::createDateIntervalFromHours($nodeValue);
|
||||
if ($this->shouldFormatDates) {
|
||||
return DateIntervalFormatHelper::formatDateInterval($dateInterval, $excelFormat);
|
||||
}
|
||||
|
||||
return $dateInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cell's PHP Date value, associated to the given timestamp.
|
||||
* NOTE: The timestamp is a float representing the number of days since the base Excel date:
|
||||
@ -207,7 +235,7 @@ final class CellValueFormatter
|
||||
*
|
||||
* @see ECMA-376 Part 1 - §18.17.4
|
||||
*/
|
||||
private function formatExcelTimestampValue(float $nodeValue, int $cellStyleId): string|DateTimeImmutable
|
||||
private function formatExcelTimestampValue(float $nodeValue, int $cellStyleId): DateTimeImmutable|string
|
||||
{
|
||||
if (!$this->isValidTimestampValue($nodeValue)) {
|
||||
throw new InvalidValueException((string) $nodeValue);
|
||||
@ -236,7 +264,7 @@ final class CellValueFormatter
|
||||
*
|
||||
* @param int $cellStyleId 0 being the default style
|
||||
*/
|
||||
private function formatExcelTimestampValueAsDateTimeValue(float $nodeValue, int $cellStyleId): string|DateTimeImmutable
|
||||
private function formatExcelTimestampValueAsDateTimeValue(float $nodeValue, int $cellStyleId): DateTimeImmutable|string
|
||||
{
|
||||
$baseDate = $this->shouldUse1904Dates ? '1904-01-01' : '1899-12-30';
|
||||
|
||||
@ -288,16 +316,14 @@ final class CellValueFormatter
|
||||
* @see ECMA-376 Part 1 - §18.17.4
|
||||
*
|
||||
* @param string $nodeValue ISO 8601 Date string
|
||||
*
|
||||
* @throws InvalidValueException If the value is not a valid date
|
||||
*/
|
||||
private function formatDateCellValue(string $nodeValue): string|DateTimeImmutable
|
||||
private function formatDateCellValue(string $nodeValue): Cell\ErrorCell|DateTimeImmutable|string
|
||||
{
|
||||
// Mitigate thrown Exception on invalid date-time format (http://php.net/manual/en/datetime.construct.php)
|
||||
try {
|
||||
$cellValue = ($this->shouldFormatDates) ? $nodeValue : new DateTimeImmutable($nodeValue);
|
||||
} catch (Exception $exception) {
|
||||
throw new InvalidValueException($nodeValue, '', 0, $exception);
|
||||
} catch (Exception) {
|
||||
return new Cell\ErrorCell($nodeValue, null);
|
||||
}
|
||||
|
||||
return $cellValue;
|
||||
|
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Reader\XLSX\Helper;
|
||||
|
||||
use DateInterval;
|
||||
|
||||
final class DateIntervalFormatHelper
|
||||
{
|
||||
/**
|
||||
* @see https://www.php.net/manual/en/dateinterval.format.php.
|
||||
*/
|
||||
private const dateIntervalFormats = [
|
||||
'hh' => '%H',
|
||||
'h' => '%h',
|
||||
'mm' => '%I',
|
||||
'm' => '%i',
|
||||
'ss' => '%S',
|
||||
's' => '%s',
|
||||
];
|
||||
|
||||
/**
|
||||
* Excel stores durations as fractions of days (24h = 1).
|
||||
*
|
||||
* Only fills hours/minutes/seconds because those are the only values that we can format back out again.
|
||||
* Excel can also only handle those units as duration.
|
||||
* PHP's DateInterval is also quite limited - it will not automatically convert unit overflow
|
||||
* (60 seconds are not converted to 1 minute).
|
||||
*/
|
||||
public static function createDateIntervalFromHours(float $dayFractions): DateInterval
|
||||
{
|
||||
$time = abs($dayFractions) * 24; // convert to hours
|
||||
$hours = floor($time);
|
||||
$time = ($time - $hours) * 60;
|
||||
$minutes = (int) floor($time); // must cast to int for type strict compare below
|
||||
$time = ($time - $minutes) * 60;
|
||||
$seconds = (int) round($time); // must cast to int for type strict compare below
|
||||
|
||||
// Bubble up rounding gain if we ended up with 60 seconds - disadvantage of using fraction of days for small durations:
|
||||
if (60 === $seconds) {
|
||||
$seconds = 0;
|
||||
++$minutes;
|
||||
}
|
||||
if (60 === $minutes) {
|
||||
$minutes = 0;
|
||||
++$hours;
|
||||
}
|
||||
|
||||
$interval = new DateInterval("P0DT{$hours}H{$minutes}M{$seconds}S");
|
||||
if ($dayFractions < 0) {
|
||||
$interval->invert = 1;
|
||||
}
|
||||
|
||||
return $interval;
|
||||
}
|
||||
|
||||
public static function isDurationFormat(string $excelFormat): bool
|
||||
{
|
||||
// Only consider formats with leading brackets as valid duration formats (e.g. "[hh]:mm", "[mm]:ss", etc.):
|
||||
return 1 === preg_match('/^(\[hh?](:mm(:ss)?)?|\[mm?](:ss)?|\[ss?])$/', $excelFormat);
|
||||
}
|
||||
|
||||
public static function toPHPDateIntervalFormat(string $excelDateFormat, ?string &$startUnit = null): string
|
||||
{
|
||||
$startUnit = null;
|
||||
$phpFormatParts = [];
|
||||
$formatParts = explode(':', str_replace(['[', ']'], '', $excelDateFormat));
|
||||
foreach ($formatParts as $formatPart) {
|
||||
$startUnit ??= $formatPart;
|
||||
$phpFormatParts[] = self::dateIntervalFormats[$formatPart];
|
||||
}
|
||||
|
||||
// Add the minus sign for potential negative durations:
|
||||
return '%r'.implode(':', $phpFormatParts);
|
||||
}
|
||||
|
||||
public static function formatDateInterval(DateInterval $dateInterval, string $excelDateFormat): string
|
||||
{
|
||||
$phpFormat = self::toPHPDateIntervalFormat($excelDateFormat, $startUnit);
|
||||
|
||||
// We have to move the hours to minutes or hours+minutes to seconds if the format in Excel did the same:
|
||||
$startUnit = $startUnit[0]; // only take the first char
|
||||
$dateIntervalClone = clone $dateInterval;
|
||||
if ('m' === $startUnit) {
|
||||
$dateIntervalClone->i = $dateIntervalClone->i + $dateIntervalClone->h * 60;
|
||||
$dateIntervalClone->h = 0;
|
||||
} elseif ('s' === $startUnit) {
|
||||
$dateIntervalClone->s = $dateIntervalClone->s + $dateIntervalClone->i * 60 + $dateIntervalClone->h * 3600;
|
||||
$dateIntervalClone->i = 0;
|
||||
$dateIntervalClone->h = 0;
|
||||
}
|
||||
|
||||
return $dateIntervalClone->format($phpFormat);
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ final class CachingStrategyFactory implements CachingStrategyFactoryInterface
|
||||
*/
|
||||
public const MAX_NUM_STRINGS_PER_TEMP_FILE = 10000;
|
||||
|
||||
private MemoryLimit $memoryLimit;
|
||||
private readonly MemoryLimit $memoryLimit;
|
||||
|
||||
public function __construct(MemoryLimit $memoryLimit)
|
||||
{
|
||||
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Reader\XLSX\Manager\SharedStringsCaching;
|
||||
|
||||
use OpenSpout\Reader\Exception\SharedStringNotFoundException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -30,7 +32,7 @@ interface CachingStrategyInterface
|
||||
*
|
||||
* @return string The shared string at the given index
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\SharedStringNotFoundException If no shared string found for the given index
|
||||
* @throws SharedStringNotFoundException If no shared string found for the given index
|
||||
*/
|
||||
public function getStringAtIndex(int $sharedStringIndex): string;
|
||||
|
||||
|
@ -22,17 +22,17 @@ final class FileBasedStrategy implements CachingStrategyInterface
|
||||
public const ESCAPED_LINE_FEED_CHARACTER = '_x000A_';
|
||||
|
||||
/** @var FileSystemHelper Helper to perform file system operations */
|
||||
private FileSystemHelper $fileSystemHelper;
|
||||
private readonly FileSystemHelper $fileSystemHelper;
|
||||
|
||||
/** @var string Temporary folder where the temporary files will be created */
|
||||
private string $tempFolder;
|
||||
private readonly string $tempFolder;
|
||||
|
||||
/**
|
||||
* @var int Maximum number of strings that can be stored in one temp file
|
||||
*
|
||||
* @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE
|
||||
*/
|
||||
private int $maxNumStringsPerTempFile;
|
||||
private readonly int $maxNumStringsPerTempFile;
|
||||
|
||||
/** @var null|resource Pointer to the last temp file a shared string was written to */
|
||||
private $tempFilePointer;
|
||||
@ -61,8 +61,6 @@ final class FileBasedStrategy implements CachingStrategyInterface
|
||||
$this->tempFolder = $this->fileSystemHelper->createFolder($tempFolder, uniqid('sharedstrings'));
|
||||
|
||||
$this->maxNumStringsPerTempFile = $maxNumStringsPerTempFile;
|
||||
|
||||
$this->tempFilePointer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -110,7 +108,7 @@ final class FileBasedStrategy implements CachingStrategyInterface
|
||||
*
|
||||
* @return string The shared string at the given index
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\SharedStringNotFoundException If no shared string found for the given index
|
||||
* @throws SharedStringNotFoundException If no shared string found for the given index
|
||||
*/
|
||||
public function getStringAtIndex(int $sharedStringIndex): string
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ final class InMemoryStrategy implements CachingStrategyInterface
|
||||
private SplFixedArray $inMemoryCache;
|
||||
|
||||
/** @var bool Whether the cache has been closed */
|
||||
private bool $isCacheClosed;
|
||||
private bool $isCacheClosed = false;
|
||||
|
||||
/**
|
||||
* @param int $sharedStringsUniqueCount Number of unique shared strings
|
||||
@ -28,7 +28,6 @@ final class InMemoryStrategy implements CachingStrategyInterface
|
||||
public function __construct(int $sharedStringsUniqueCount)
|
||||
{
|
||||
$this->inMemoryCache = new SplFixedArray($sharedStringsUniqueCount);
|
||||
$this->isCacheClosed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,13 +59,13 @@ final class InMemoryStrategy implements CachingStrategyInterface
|
||||
*
|
||||
* @return string The shared string at the given index
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\SharedStringNotFoundException If no shared string found for the given index
|
||||
* @throws SharedStringNotFoundException If no shared string found for the given index
|
||||
*/
|
||||
public function getStringAtIndex(int $sharedStringIndex): string
|
||||
{
|
||||
try {
|
||||
return $this->inMemoryCache->offsetGet($sharedStringIndex);
|
||||
} catch (RuntimeException $e) {
|
||||
} catch (RuntimeException) {
|
||||
throw new SharedStringNotFoundException("Shared string not found for index: {$sharedStringIndex}");
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ namespace OpenSpout\Reader\XLSX\Manager\SharedStringsCaching;
|
||||
*/
|
||||
final class MemoryLimit
|
||||
{
|
||||
private string $memoryLimit;
|
||||
private readonly string $memoryLimit;
|
||||
|
||||
public function __construct(string $memoryLimit)
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ namespace OpenSpout\Reader\XLSX\Manager;
|
||||
|
||||
use DOMElement;
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
use OpenSpout\Reader\Exception\SharedStringNotFoundException;
|
||||
use OpenSpout\Reader\Exception\XMLProcessingException;
|
||||
use OpenSpout\Reader\Wrapper\XMLReader;
|
||||
use OpenSpout\Reader\XLSX\Manager\SharedStringsCaching\CachingStrategyFactoryInterface;
|
||||
@ -34,15 +35,15 @@ final class SharedStringsManager
|
||||
public const XML_ATTRIBUTE_VALUE_PRESERVE = 'preserve';
|
||||
|
||||
/** @var string Path of the XLSX file being read */
|
||||
private string $filePath;
|
||||
private readonly string $filePath;
|
||||
|
||||
private Options $options;
|
||||
private readonly Options $options;
|
||||
|
||||
/** @var WorkbookRelationshipsManager Helps retrieving workbook relationships */
|
||||
private WorkbookRelationshipsManager $workbookRelationshipsManager;
|
||||
private readonly WorkbookRelationshipsManager $workbookRelationshipsManager;
|
||||
|
||||
/** @var CachingStrategyFactoryInterface Factory to create shared strings caching strategies */
|
||||
private CachingStrategyFactoryInterface $cachingStrategyFactory;
|
||||
private readonly CachingStrategyFactoryInterface $cachingStrategyFactory;
|
||||
|
||||
/** @var CachingStrategyInterface The best caching strategy for storing shared strings */
|
||||
private CachingStrategyInterface $cachingStrategy;
|
||||
@ -77,7 +78,7 @@ final class SharedStringsManager
|
||||
* The XML file can be really big with sheets containing a lot of data. That is why
|
||||
* we need to use a XML reader that provides streaming like the XMLReader library.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If shared strings XML file can't be read
|
||||
* @throws IOException If shared strings XML file can't be read
|
||||
*/
|
||||
public function extractSharedStrings(): void
|
||||
{
|
||||
@ -118,7 +119,7 @@ final class SharedStringsManager
|
||||
*
|
||||
* @return string The shared string at the given index
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\SharedStringNotFoundException If no shared string found for the given index
|
||||
* @throws SharedStringNotFoundException If no shared string found for the given index
|
||||
*/
|
||||
public function getStringAtIndex(int $sharedStringIndex): string
|
||||
{
|
||||
@ -142,7 +143,7 @@ final class SharedStringsManager
|
||||
*
|
||||
* @return null|int Number of unique shared strings in the sharedStrings.xml file
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If sharedStrings.xml is invalid and can't be read
|
||||
* @throws IOException If sharedStrings.xml is invalid and can't be read
|
||||
*/
|
||||
private function getSharedStringsUniqueCount(XMLReader $xmlReader): ?int
|
||||
{
|
||||
|
@ -51,15 +51,15 @@ final class SheetManager
|
||||
public const SHEET_STATE_HIDDEN = 'hidden';
|
||||
|
||||
/** @var string Path of the XLSX file being read */
|
||||
private string $filePath;
|
||||
private readonly string $filePath;
|
||||
|
||||
private Options $options;
|
||||
private readonly Options $options;
|
||||
|
||||
/** @var SharedStringsManager Manages shared strings */
|
||||
private SharedStringsManager $sharedStringsManager;
|
||||
private readonly SharedStringsManager $sharedStringsManager;
|
||||
|
||||
/** @var XLSX Used to unescape XML data */
|
||||
private XLSX $escaper;
|
||||
private readonly XLSX $escaper;
|
||||
|
||||
/** @var Sheet[] List of sheets */
|
||||
private array $sheets;
|
||||
@ -111,7 +111,7 @@ final class SheetManager
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \OpenSpout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<workbookPr>" starting node
|
||||
* @param XMLReader $xmlReader XMLReader object, positioned on a "<workbookPr>" starting node
|
||||
*
|
||||
* @return int A return code that indicates what action should the processor take next
|
||||
*/
|
||||
@ -126,7 +126,7 @@ final class SheetManager
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \OpenSpout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<workbookView>" starting node
|
||||
* @param XMLReader $xmlReader XMLReader object, positioned on a "<workbookView>" starting node
|
||||
*
|
||||
* @return int A return code that indicates what action should the processor take next
|
||||
*/
|
||||
@ -140,7 +140,7 @@ final class SheetManager
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \OpenSpout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<sheet>" starting node
|
||||
* @param XMLReader $xmlReader XMLReader object, positioned on a "<sheet>" starting node
|
||||
*
|
||||
* @return int A return code that indicates what action should the processor take next
|
||||
*/
|
||||
@ -166,11 +166,11 @@ final class SheetManager
|
||||
* We can find the XML file path describing the sheet inside "workbook.xml.res", by mapping with the sheet ID
|
||||
* ("r:id" in "workbook.xml", "Id" in "workbook.xml.res").
|
||||
*
|
||||
* @param \OpenSpout\Reader\Wrapper\XMLReader $xmlReaderOnSheetNode XML Reader instance, pointing on the node describing the sheet, as defined in "workbook.xml"
|
||||
* @param int $sheetIndexZeroBased Index of the sheet, based on order of appearance in the workbook (zero-based)
|
||||
* @param bool $isSheetActive Whether this sheet was defined as active
|
||||
* @param XMLReader $xmlReaderOnSheetNode XML Reader instance, pointing on the node describing the sheet, as defined in "workbook.xml"
|
||||
* @param int $sheetIndexZeroBased Index of the sheet, based on order of appearance in the workbook (zero-based)
|
||||
* @param bool $isSheetActive Whether this sheet was defined as active
|
||||
*
|
||||
* @return \OpenSpout\Reader\XLSX\Sheet Sheet instance
|
||||
* @return Sheet Sheet instance
|
||||
*/
|
||||
private function getSheetFromSheetXMLNode(XMLReader $xmlReaderOnSheetNode, int $sheetIndexZeroBased, bool $isSheetActive): Sheet
|
||||
{
|
||||
|
@ -11,25 +11,25 @@ class StyleManager implements StyleManagerInterface
|
||||
/**
|
||||
* Nodes used to find relevant information in the styles XML file.
|
||||
*/
|
||||
public const XML_NODE_NUM_FMTS = 'numFmts';
|
||||
public const XML_NODE_NUM_FMT = 'numFmt';
|
||||
public const XML_NODE_CELL_XFS = 'cellXfs';
|
||||
public const XML_NODE_XF = 'xf';
|
||||
final public const XML_NODE_NUM_FMTS = 'numFmts';
|
||||
final public const XML_NODE_NUM_FMT = 'numFmt';
|
||||
final public const XML_NODE_CELL_XFS = 'cellXfs';
|
||||
final public const XML_NODE_XF = 'xf';
|
||||
|
||||
/**
|
||||
* Attributes used to find relevant information in the styles XML file.
|
||||
*/
|
||||
public const XML_ATTRIBUTE_NUM_FMT_ID = 'numFmtId';
|
||||
public const XML_ATTRIBUTE_FORMAT_CODE = 'formatCode';
|
||||
public const XML_ATTRIBUTE_APPLY_NUMBER_FORMAT = 'applyNumberFormat';
|
||||
public const XML_ATTRIBUTE_COUNT = 'count';
|
||||
final public const XML_ATTRIBUTE_NUM_FMT_ID = 'numFmtId';
|
||||
final public const XML_ATTRIBUTE_FORMAT_CODE = 'formatCode';
|
||||
final public const XML_ATTRIBUTE_APPLY_NUMBER_FORMAT = 'applyNumberFormat';
|
||||
final public const XML_ATTRIBUTE_COUNT = 'count';
|
||||
|
||||
/**
|
||||
* By convention, default style ID is 0.
|
||||
*/
|
||||
public const DEFAULT_STYLE_ID = 0;
|
||||
final public const DEFAULT_STYLE_ID = 0;
|
||||
|
||||
public const NUMBER_FORMAT_GENERAL = 'General';
|
||||
final public const NUMBER_FORMAT_GENERAL = 'General';
|
||||
|
||||
/**
|
||||
* Mapping between built-in numFmtId and the associated format - for dates only.
|
||||
@ -52,10 +52,10 @@ class StyleManager implements StyleManagerInterface
|
||||
];
|
||||
|
||||
/** @var string Path of the XLSX file being read */
|
||||
private string $filePath;
|
||||
private readonly string $filePath;
|
||||
|
||||
/** @var null|string Path of the styles XML file */
|
||||
private ?string $stylesXMLFilePath;
|
||||
private readonly ?string $stylesXMLFilePath;
|
||||
|
||||
/** @var array<int, string> Array containing a mapping NUM_FMT_ID => FORMAT_CODE */
|
||||
private array $customNumberFormats;
|
||||
@ -107,7 +107,7 @@ class StyleManager implements StyleManagerInterface
|
||||
$numberFormatCode = self::builtinNumFmtIdToNumFormatMapping[$numFmtId];
|
||||
} else {
|
||||
$customNumberFormats = $this->getCustomNumberFormats();
|
||||
$numberFormatCode = $customNumberFormats[$numFmtId];
|
||||
$numberFormatCode = $customNumberFormats[$numFmtId] ?? '';
|
||||
}
|
||||
|
||||
return $numberFormatCode;
|
||||
@ -166,7 +166,7 @@ class StyleManager implements StyleManagerInterface
|
||||
* For simplicity, the styles attributes are kept in memory. This is possible thanks
|
||||
* to the reuse of formats. So 1 million cells should not use 1 million formats.
|
||||
*
|
||||
* @param \OpenSpout\Reader\Wrapper\XMLReader $xmlReader XML Reader positioned on the "numFmts" node
|
||||
* @param XMLReader $xmlReader XML Reader positioned on the "numFmts" node
|
||||
*/
|
||||
private function extractNumberFormats(XMLReader $xmlReader): void
|
||||
{
|
||||
@ -188,7 +188,7 @@ class StyleManager implements StyleManagerInterface
|
||||
* For simplicity, the styles attributes are kept in memory. This is possible thanks
|
||||
* to the reuse of styles. So 1 million cells should not use 1 million styles.
|
||||
*
|
||||
* @param \OpenSpout\Reader\Wrapper\XMLReader $xmlReader XML Reader positioned on the "cellXfs" node
|
||||
* @param XMLReader $xmlReader XML Reader positioned on the "cellXfs" node
|
||||
*/
|
||||
private function extractStyleAttributes(XMLReader $xmlReader): void
|
||||
{
|
||||
@ -262,7 +262,7 @@ class StyleManager implements StyleManagerInterface
|
||||
$customNumberFormats = $this->getCustomNumberFormats();
|
||||
|
||||
// Using isset here because it is way faster than array_key_exists...
|
||||
return (isset($customNumberFormats[$numFmtId])) ? $customNumberFormats[$numFmtId] : null;
|
||||
return $customNumberFormats[$numFmtId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -296,6 +296,10 @@ class StyleManager implements StyleManagerInterface
|
||||
$formatCode = preg_replace($pattern, '', $formatCode);
|
||||
\assert(null !== $formatCode);
|
||||
|
||||
// Remove strings in double quotes, as they won't be interpreted as date format characters
|
||||
$formatCode = preg_replace('/"[^"]+"/', '', $formatCode);
|
||||
\assert(null !== $formatCode);
|
||||
|
||||
// custom date formats contain specific characters to represent the date:
|
||||
// e - yy - m - d - h - s
|
||||
// and all of their variants (yyyy - mm - dd...)
|
||||
|
@ -35,7 +35,7 @@ final class WorkbookRelationshipsManager
|
||||
public const XML_ATTRIBUTE_TARGET = 'Target';
|
||||
|
||||
/** @var string Path of the XLSX file being read */
|
||||
private string $filePath;
|
||||
private readonly string $filePath;
|
||||
|
||||
/** @var array<string, string> Cache of the already read workbook relationships: [TYPE] => [FILE_NAME] */
|
||||
private array $cachedWorkbookRelationships;
|
||||
@ -114,7 +114,7 @@ final class WorkbookRelationshipsManager
|
||||
*
|
||||
* @return array<string, string>
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If workbook.xml.rels can't be read
|
||||
* @throws IOException If workbook.xml.rels can't be read
|
||||
*/
|
||||
private function getWorkbookRelationships(): array
|
||||
{
|
||||
|
@ -7,6 +7,7 @@ namespace OpenSpout\Reader\XLSX;
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
use OpenSpout\Common\Helper\Escaper\XLSX;
|
||||
use OpenSpout\Reader\AbstractReader;
|
||||
use OpenSpout\Reader\Exception\NoSheetsFoundException;
|
||||
use OpenSpout\Reader\XLSX\Manager\SharedStringsCaching\CachingStrategyFactory;
|
||||
use OpenSpout\Reader\XLSX\Manager\SharedStringsCaching\CachingStrategyFactoryInterface;
|
||||
use OpenSpout\Reader\XLSX\Manager\SharedStringsCaching\MemoryLimit;
|
||||
@ -28,8 +29,8 @@ final class Reader extends AbstractReader
|
||||
/** @var SheetIterator To iterator over the XLSX sheets */
|
||||
private SheetIterator $sheetIterator;
|
||||
|
||||
private Options $options;
|
||||
private CachingStrategyFactoryInterface $cachingStrategyFactory;
|
||||
private readonly Options $options;
|
||||
private readonly CachingStrategyFactoryInterface $cachingStrategyFactory;
|
||||
|
||||
public function __construct(
|
||||
?Options $options = null,
|
||||
@ -66,8 +67,8 @@ final class Reader extends AbstractReader
|
||||
*
|
||||
* @param string $filePath Path of the file to be read
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the file at the given path or its content cannot be read
|
||||
* @throws \OpenSpout\Reader\Exception\NoSheetsFoundException If there are no sheets in the file
|
||||
* @throws IOException If the file at the given path or its content cannot be read
|
||||
* @throws NoSheetsFoundException If there are no sheets in the file
|
||||
*/
|
||||
protected function openReader(string $filePath): void
|
||||
{
|
||||
|
@ -7,10 +7,11 @@ namespace OpenSpout\Reader\XLSX;
|
||||
use DOMElement;
|
||||
use OpenSpout\Common\Entity\Cell;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Common\Exception\InvalidArgumentException;
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
use OpenSpout\Reader\Common\Manager\RowManager;
|
||||
use OpenSpout\Reader\Common\XMLProcessor;
|
||||
use OpenSpout\Reader\Exception\InvalidValueException;
|
||||
use OpenSpout\Reader\Exception\SharedStringNotFoundException;
|
||||
use OpenSpout\Reader\RowIteratorInterface;
|
||||
use OpenSpout\Reader\Wrapper\XMLReader;
|
||||
use OpenSpout\Reader\XLSX\Helper\CellHelper;
|
||||
@ -35,22 +36,22 @@ final class RowIterator implements RowIteratorInterface
|
||||
public const XML_ATTRIBUTE_CELL_INDEX = 'r';
|
||||
|
||||
/** @var string Path of the XLSX file being read */
|
||||
private string $filePath;
|
||||
private readonly string $filePath;
|
||||
|
||||
/** @var string Path of the sheet data XML file as in [Content_Types].xml */
|
||||
private string $sheetDataXMLFilePath;
|
||||
private readonly string $sheetDataXMLFilePath;
|
||||
|
||||
/** @var XMLReader The XMLReader object that will help read sheet's XML data */
|
||||
private XMLReader $xmlReader;
|
||||
private readonly XMLReader $xmlReader;
|
||||
|
||||
/** @var XMLProcessor Helper Object to process XML nodes */
|
||||
private XMLProcessor $xmlProcessor;
|
||||
private readonly XMLProcessor $xmlProcessor;
|
||||
|
||||
/** @var Helper\CellValueFormatter Helper to format cell values */
|
||||
private Helper\CellValueFormatter $cellValueFormatter;
|
||||
private readonly Helper\CellValueFormatter $cellValueFormatter;
|
||||
|
||||
/** @var RowManager Manages rows */
|
||||
private RowManager $rowManager;
|
||||
private readonly RowManager $rowManager;
|
||||
|
||||
/**
|
||||
* TODO: This variable can be deleted when row indices get preserved.
|
||||
@ -63,7 +64,7 @@ final class RowIterator implements RowIteratorInterface
|
||||
private Row $currentlyProcessedRow;
|
||||
|
||||
/** @var null|Row Buffer used to store the current row, while checking if there are more rows to read */
|
||||
private ?Row $rowBuffer;
|
||||
private ?Row $rowBuffer = null;
|
||||
|
||||
/** @var bool Indicates whether all rows have been read */
|
||||
private bool $hasReachedEndOfFile = false;
|
||||
@ -72,7 +73,7 @@ final class RowIterator implements RowIteratorInterface
|
||||
private int $numColumns = 0;
|
||||
|
||||
/** @var bool Whether empty rows should be returned or skipped */
|
||||
private bool $shouldPreserveEmptyRows;
|
||||
private readonly bool $shouldPreserveEmptyRows;
|
||||
|
||||
/** @var int Last row index processed (one-based) */
|
||||
private int $lastRowIndexProcessed = 0;
|
||||
@ -124,7 +125,7 @@ final class RowIterator implements RowIteratorInterface
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.rewind.php
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the sheet data XML cannot be read
|
||||
* @throws IOException If the sheet data XML cannot be read
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
@ -164,8 +165,8 @@ final class RowIterator implements RowIteratorInterface
|
||||
*
|
||||
* @see http://php.net/manual/en/iterator.next.php
|
||||
*
|
||||
* @throws \OpenSpout\Reader\Exception\SharedStringNotFoundException If a shared string was not found
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to read the sheet data XML
|
||||
* @throws SharedStringNotFoundException If a shared string was not found
|
||||
* @throws IOException If unable to read the sheet data XML
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
@ -251,8 +252,8 @@ final class RowIterator implements RowIteratorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \OpenSpout\Reader\Exception\SharedStringNotFoundException If a shared string was not found
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to read the sheet data XML
|
||||
* @throws SharedStringNotFoundException If a shared string was not found
|
||||
* @throws IOException If unable to read the sheet data XML
|
||||
*/
|
||||
private function readDataForNextRow(): void
|
||||
{
|
||||
@ -317,9 +318,9 @@ final class RowIterator implements RowIteratorInterface
|
||||
$currentColumnIndex = $this->getColumnIndex($xmlReader);
|
||||
|
||||
// NOTE: expand() will automatically decode all XML entities of the child nodes
|
||||
/** @var DOMElement $node */
|
||||
$node = $xmlReader->expand();
|
||||
$cell = $this->getCell($node);
|
||||
\assert($node instanceof DOMElement);
|
||||
$cell = $this->cellValueFormatter->extractAndFormatNodeValue($node);
|
||||
|
||||
$this->currentlyProcessedRow->setCellAtIndex($cell, $currentColumnIndex);
|
||||
$this->lastColumnIndexProcessed = $currentColumnIndex;
|
||||
@ -366,7 +367,7 @@ final class RowIterator implements RowIteratorInterface
|
||||
*
|
||||
* @return int Row index
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\InvalidArgumentException When the given cell index is invalid
|
||||
* @throws InvalidArgumentException When the given cell index is invalid
|
||||
*/
|
||||
private function getRowIndex(XMLReader $xmlReader): int
|
||||
{
|
||||
@ -383,7 +384,7 @@ final class RowIterator implements RowIteratorInterface
|
||||
*
|
||||
* @return int Column index
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\InvalidArgumentException When the given cell index is invalid
|
||||
* @throws InvalidArgumentException When the given cell index is invalid
|
||||
*/
|
||||
private function getColumnIndex(XMLReader $xmlReader): int
|
||||
{
|
||||
@ -394,21 +395,4 @@ final class RowIterator implements RowIteratorInterface
|
||||
CellHelper::getColumnIndexFromCellIndex($currentCellIndex) :
|
||||
$this->lastColumnIndexProcessed + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cell with (unescaped) correctly marshalled, cell value associated to the given XML node.
|
||||
*
|
||||
* @return Cell The cell set with the associated with the cell
|
||||
*/
|
||||
private function getCell(DOMElement $node): Cell
|
||||
{
|
||||
try {
|
||||
$cellValue = $this->cellValueFormatter->extractAndFormatNodeValue($node);
|
||||
$cell = Cell::fromValue($cellValue);
|
||||
} catch (InvalidValueException $exception) {
|
||||
$cell = new Cell\ErrorCell($exception->getInvalidValue(), null);
|
||||
}
|
||||
|
||||
return $cell;
|
||||
}
|
||||
}
|
||||
|
@ -13,22 +13,22 @@ use OpenSpout\Reader\SheetWithVisibilityInterface;
|
||||
final class Sheet implements SheetWithVisibilityInterface
|
||||
{
|
||||
/** @var RowIterator To iterate over sheet's rows */
|
||||
private RowIterator $rowIterator;
|
||||
private readonly RowIterator $rowIterator;
|
||||
|
||||
/** @var SheetHeaderReader To read the header of the sheet, containing for instance the col widths */
|
||||
private SheetHeaderReader $headerReader;
|
||||
private readonly SheetHeaderReader $headerReader;
|
||||
|
||||
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
|
||||
private int $index;
|
||||
private readonly int $index;
|
||||
|
||||
/** @var string Name of the sheet */
|
||||
private string $name;
|
||||
private readonly string $name;
|
||||
|
||||
/** @var bool Whether the sheet was the active one */
|
||||
private bool $isActive;
|
||||
private readonly bool $isActive;
|
||||
|
||||
/** @var bool Whether the sheet is visible */
|
||||
private bool $isVisible;
|
||||
private readonly bool $isVisible;
|
||||
|
||||
/**
|
||||
* @param RowIterator $rowIterator The corresponding row iterator
|
||||
|
@ -18,16 +18,16 @@ final class SheetHeaderReader
|
||||
public const XML_ATTRIBUTE_WIDTH = 'width';
|
||||
|
||||
/** @var string Path of the XLSX file being read */
|
||||
private string $filePath;
|
||||
private readonly string $filePath;
|
||||
|
||||
/** @var string Path of the sheet data XML file as in [Content_Types].xml */
|
||||
private string $sheetDataXMLFilePath;
|
||||
private readonly string $sheetDataXMLFilePath;
|
||||
|
||||
/** @var XMLReader The XMLReader object that will help read sheet's XML data */
|
||||
private XMLReader $xmlReader;
|
||||
private readonly XMLReader $xmlReader;
|
||||
|
||||
/** @var XMLProcessor Helper Object to process XML nodes */
|
||||
private XMLProcessor $xmlProcessor;
|
||||
private readonly XMLProcessor $xmlProcessor;
|
||||
|
||||
/** @var ColumnWidth[] The widths of the columns in the sheet, if specified */
|
||||
private array $columnWidths = [];
|
||||
|
@ -16,10 +16,9 @@ final class AutoFilter
|
||||
* @param positive-int $toRow
|
||||
*/
|
||||
public function __construct(
|
||||
public int $fromColumnIndex,
|
||||
public int $fromRow,
|
||||
public int $toColumnIndex,
|
||||
public int $toRow
|
||||
) {
|
||||
}
|
||||
public readonly int $fromColumnIndex,
|
||||
public readonly int $fromRow,
|
||||
public readonly int $toColumnIndex,
|
||||
public readonly int $toRow
|
||||
) {}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ final class Writer extends AbstractWriter
|
||||
/** @var string Content-Type value for the header */
|
||||
protected static string $headerContentType = 'text/csv; charset=UTF-8';
|
||||
|
||||
private Options $options;
|
||||
private readonly Options $options;
|
||||
|
||||
private int $lastWrittenRowIndex = 0;
|
||||
|
||||
@ -49,10 +49,16 @@ final class Writer extends AbstractWriter
|
||||
*/
|
||||
protected function addRowToWriter(Row $row): void
|
||||
{
|
||||
$cells = array_map(static function (Cell\BooleanCell|Cell\EmptyCell|Cell\NumericCell|Cell\StringCell|Cell\FormulaCell $value): string {
|
||||
$cells = array_map(static function (Cell\BooleanCell|Cell\DateIntervalCell|Cell\DateTimeCell|Cell\EmptyCell|Cell\FormulaCell|Cell\NumericCell|Cell\StringCell $value): string {
|
||||
if ($value instanceof Cell\BooleanCell) {
|
||||
return (string) (int) $value->getValue();
|
||||
}
|
||||
if ($value instanceof Cell\DateTimeCell) {
|
||||
return $value->getValue()->format(DATE_ATOM);
|
||||
}
|
||||
if ($value instanceof Cell\DateIntervalCell) {
|
||||
return $value->getValue()->format('P%yY%mM%dDT%hH%iM%sS%fF');
|
||||
}
|
||||
|
||||
return (string) $value->getValue();
|
||||
}, $row->getCells());
|
||||
|
@ -14,9 +14,8 @@ final class ColumnWidth
|
||||
* @param positive-int $end
|
||||
*/
|
||||
public function __construct(
|
||||
public int $start,
|
||||
public int $end,
|
||||
public float $width,
|
||||
) {
|
||||
}
|
||||
public readonly int $start,
|
||||
public readonly int $end,
|
||||
public readonly float $width,
|
||||
) {}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ use OpenSpout\Writer\XLSX\Writer as XLSXWriter;
|
||||
/**
|
||||
* This factory is used to create writers, based on the type of the file to be read.
|
||||
* It supports CSV, XLSX and ODS formats.
|
||||
*
|
||||
* @deprecated Guessing mechanisms are brittle by nature and won't be provided by this library anymore
|
||||
*/
|
||||
final class WriterFactory
|
||||
{
|
||||
@ -21,7 +23,7 @@ final class WriterFactory
|
||||
*
|
||||
* @param string $path The path to the spreadsheet file. Supported extensions are .csv,.ods and .xlsx
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\UnsupportedTypeException
|
||||
* @throws UnsupportedTypeException
|
||||
*/
|
||||
public static function createFromFile(string $path): WriterInterface
|
||||
{
|
||||
|
@ -7,6 +7,7 @@ namespace OpenSpout\Writer\Common\Entity;
|
||||
use OpenSpout\Writer\AutoFilter;
|
||||
use OpenSpout\Writer\Common\ColumnWidth;
|
||||
use OpenSpout\Writer\Common\Manager\SheetManager;
|
||||
use OpenSpout\Writer\Exception\InvalidSheetNameException;
|
||||
use OpenSpout\Writer\XLSX\Entity\SheetView;
|
||||
|
||||
/**
|
||||
@ -17,10 +18,10 @@ final class Sheet
|
||||
public const DEFAULT_SHEET_NAME_PREFIX = 'Sheet';
|
||||
|
||||
/** @var 0|positive-int Index of the sheet, based on order in the workbook (zero-based) */
|
||||
private int $index;
|
||||
private readonly int $index;
|
||||
|
||||
/** @var string ID of the sheet's associated workbook. Used to restrict sheet name uniqueness enforcement to a single workbook */
|
||||
private string $associatedWorkbookId;
|
||||
private readonly string $associatedWorkbookId;
|
||||
|
||||
/** @var string Name of the sheet */
|
||||
private string $name;
|
||||
@ -29,7 +30,7 @@ final class Sheet
|
||||
private bool $isVisible;
|
||||
|
||||
/** @var SheetManager Sheet manager */
|
||||
private SheetManager $sheetManager;
|
||||
private readonly SheetManager $sheetManager;
|
||||
|
||||
private ?SheetView $sheetView = null;
|
||||
|
||||
@ -41,6 +42,9 @@ final class Sheet
|
||||
/** @var ColumnWidth[] Array of min-max-width arrays */
|
||||
private array $COLUMN_WIDTHS = [];
|
||||
|
||||
/** @var string rows to repeat at top */
|
||||
private ?string $printTitleRows = null;
|
||||
|
||||
/**
|
||||
* @param 0|positive-int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
|
||||
* @param string $associatedWorkbookId ID of the sheet's associated workbook
|
||||
@ -88,7 +92,7 @@ final class Sheet
|
||||
*
|
||||
* @param string $name Name of the sheet
|
||||
*
|
||||
* @throws \OpenSpout\Writer\Exception\InvalidSheetNameException if the sheet's name is invalid
|
||||
* @throws InvalidSheetNameException if the sheet's name is invalid
|
||||
*/
|
||||
public function setName(string $name): self
|
||||
{
|
||||
@ -205,4 +209,14 @@ final class Sheet
|
||||
{
|
||||
return $this->COLUMN_WIDTHS;
|
||||
}
|
||||
|
||||
public function getPrintTitleRows(): ?string
|
||||
{
|
||||
return $this->printTitleRows;
|
||||
}
|
||||
|
||||
public function setPrintTitleRows(string $printTitleRows): void
|
||||
{
|
||||
$this->printTitleRows = $printTitleRows;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ final class Workbook
|
||||
private array $worksheets = [];
|
||||
|
||||
/** @var string Timestamp based unique ID identifying the workbook */
|
||||
private string $internalId;
|
||||
private readonly string $internalId;
|
||||
|
||||
/**
|
||||
* Workbook constructor.
|
||||
|
@ -10,19 +10,19 @@ namespace OpenSpout\Writer\Common\Entity;
|
||||
final class Worksheet
|
||||
{
|
||||
/** @var string Path to the XML file that will contain the sheet data */
|
||||
private string $filePath;
|
||||
private readonly string $filePath;
|
||||
|
||||
/** @var null|resource Pointer to the sheet data file (e.g. xl/worksheets/sheet1.xml) */
|
||||
private $filePointer;
|
||||
|
||||
/** @var Sheet The "external" sheet */
|
||||
private Sheet $externalSheet;
|
||||
private readonly Sheet $externalSheet;
|
||||
|
||||
/** @var int Maximum number of columns among all the written rows */
|
||||
private int $maxNumColumns;
|
||||
private int $maxNumColumns = 0;
|
||||
|
||||
/** @var int Index of the last written row */
|
||||
private int $lastWrittenRowIndex;
|
||||
private int $lastWrittenRowIndex = 0;
|
||||
|
||||
/**
|
||||
* Worksheet constructor.
|
||||
@ -30,10 +30,7 @@ final class Worksheet
|
||||
public function __construct(string $worksheetFilePath, Sheet $externalSheet)
|
||||
{
|
||||
$this->filePath = $worksheetFilePath;
|
||||
$this->filePointer = null;
|
||||
$this->externalSheet = $externalSheet;
|
||||
$this->maxNumColumns = 0;
|
||||
$this->lastWrittenRowIndex = 0;
|
||||
}
|
||||
|
||||
public function getFilePath(): string
|
||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Writer\Common\Helper;
|
||||
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
use OpenSpout\Common\Helper\FileSystemHelperInterface;
|
||||
|
||||
/**
|
||||
@ -14,7 +15,7 @@ interface FileSystemWithRootFolderHelperInterface extends FileSystemHelperInterf
|
||||
/**
|
||||
* Creates all the folders needed to create a spreadsheet, as well as the files that won't change.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create at least one of the base folders
|
||||
* @throws IOException If unable to create at least one of the base folders
|
||||
*/
|
||||
public function createBaseFilesAndFolders(): void;
|
||||
|
||||
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace OpenSpout\Writer\Common\Manager;
|
||||
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Common\Exception\InvalidArgumentException;
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
use OpenSpout\Common\Helper\StringHelper;
|
||||
use OpenSpout\Writer\Common\AbstractOptions;
|
||||
@ -32,10 +33,10 @@ abstract class AbstractWorkbookManager implements WorkbookManagerInterface
|
||||
protected AbstractOptions $options;
|
||||
|
||||
/** @var Workbook The workbook to manage */
|
||||
private Workbook $workbook;
|
||||
private readonly Workbook $workbook;
|
||||
|
||||
/** @var StyleMerger Helper to merge styles */
|
||||
private StyleMerger $styleMerger;
|
||||
private readonly StyleMerger $styleMerger;
|
||||
|
||||
/** @var Worksheet The worksheet where data will be written to */
|
||||
private Worksheet $currentWorksheet;
|
||||
@ -113,8 +114,8 @@ abstract class AbstractWorkbookManager implements WorkbookManagerInterface
|
||||
*
|
||||
* @param Row $row The row to be added
|
||||
*
|
||||
* @throws IOException If trying to create a new sheet and unable to open the sheet for writing
|
||||
* @throws \OpenSpout\Common\Exception\InvalidArgumentException
|
||||
* @throws IOException If trying to create a new sheet and unable to open the sheet for writing
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
final public function addRowToCurrentWorksheet(Row $row): void
|
||||
{
|
||||
@ -190,7 +191,7 @@ abstract class AbstractWorkbookManager implements WorkbookManagerInterface
|
||||
*
|
||||
* @return Worksheet The created sheet
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to open the sheet for writing
|
||||
* @throws IOException If unable to open the sheet for writing
|
||||
*/
|
||||
private function addNewSheet(): Worksheet
|
||||
{
|
||||
@ -253,7 +254,7 @@ abstract class AbstractWorkbookManager implements WorkbookManagerInterface
|
||||
* @param Row $row The row to be added
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws \OpenSpout\Common\Exception\InvalidArgumentException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function addRowToWorksheet(Worksheet $worksheet, Row $row): void
|
||||
{
|
||||
|
@ -13,9 +13,9 @@ use OpenSpout\Common\Entity\Style\Style;
|
||||
*/
|
||||
final class RegisteredStyle
|
||||
{
|
||||
private Style $style;
|
||||
private readonly Style $style;
|
||||
|
||||
private bool $isMatchingRowStyle;
|
||||
private readonly bool $isMatchingRowStyle;
|
||||
|
||||
public function __construct(Style $style, bool $isMatchingRowStyle)
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ final class SheetManager
|
||||
/** @var array<string, array<int, string>> Associative array [WORKBOOK_ID] => [[SHEET_INDEX] => [SHEET_NAME]] keeping track of sheets' name to enforce uniqueness per workbook */
|
||||
private static array $SHEETS_NAME_USED = [];
|
||||
|
||||
private StringHelper $stringHelper;
|
||||
private readonly StringHelper $stringHelper;
|
||||
|
||||
/**
|
||||
* SheetManager constructor.
|
||||
@ -43,7 +43,7 @@ final class SheetManager
|
||||
*
|
||||
* @param Sheet $sheet The sheet whose future name is checked
|
||||
*
|
||||
* @throws \OpenSpout\Writer\Exception\InvalidSheetNameException if the sheet's name is invalid
|
||||
* @throws InvalidSheetNameException if the sheet's name is invalid
|
||||
*/
|
||||
public function throwIfNameIsInvalid(string $name, Sheet $sheet): void
|
||||
{
|
||||
|
@ -11,8 +11,8 @@ use OpenSpout\Common\Entity\Style\Style;
|
||||
*/
|
||||
final class PossiblyUpdatedStyle
|
||||
{
|
||||
private Style $style;
|
||||
private bool $isUpdated;
|
||||
private readonly Style $style;
|
||||
private readonly bool $isUpdated;
|
||||
|
||||
public function __construct(Style $style, bool $isUpdated)
|
||||
{
|
||||
|
@ -5,6 +5,8 @@ declare(strict_types=1);
|
||||
namespace OpenSpout\Writer\Common\Manager;
|
||||
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Common\Exception\InvalidArgumentException;
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
use OpenSpout\Writer\Common\Entity\Worksheet;
|
||||
|
||||
/**
|
||||
@ -18,8 +20,8 @@ interface WorksheetManagerInterface
|
||||
* @param Worksheet $worksheet The worksheet to add the row to
|
||||
* @param Row $row The row to be added
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the data cannot be written
|
||||
* @throws \OpenSpout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
|
||||
* @throws IOException If the data cannot be written
|
||||
* @throws InvalidArgumentException If a cell value's type is not supported
|
||||
*/
|
||||
public function addRow(Worksheet $worksheet, Row $row): void;
|
||||
|
||||
@ -28,7 +30,7 @@ interface WorksheetManagerInterface
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet to start
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the sheet data file cannot be opened for writing
|
||||
* @throws IOException If the sheet data file cannot be opened for writing
|
||||
*/
|
||||
public function startSheet(Worksheet $worksheet): void;
|
||||
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Writer\Exception;
|
||||
|
||||
final class InvalidSheetNameException extends WriterException
|
||||
{
|
||||
}
|
||||
final class InvalidSheetNameException extends WriterException {}
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Writer\Exception;
|
||||
|
||||
final class SheetNotFoundException extends WriterException
|
||||
{
|
||||
}
|
||||
final class SheetNotFoundException extends WriterException {}
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Writer\Exception;
|
||||
|
||||
final class WriterAlreadyOpenedException extends WriterException
|
||||
{
|
||||
}
|
||||
final class WriterAlreadyOpenedException extends WriterException {}
|
||||
|
@ -6,6 +6,4 @@ namespace OpenSpout\Writer\Exception;
|
||||
|
||||
use OpenSpout\Common\Exception\OpenSpoutException;
|
||||
|
||||
abstract class WriterException extends OpenSpoutException
|
||||
{
|
||||
}
|
||||
abstract class WriterException extends OpenSpoutException {}
|
||||
|
@ -4,6 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Writer\Exception;
|
||||
|
||||
final class WriterNotOpenedException extends WriterException
|
||||
{
|
||||
}
|
||||
final class WriterNotOpenedException extends WriterException {}
|
||||
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace OpenSpout\Writer\ODS\Helper;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
use OpenSpout\Common\Helper\FileSystemHelper as CommonFileSystemHelper;
|
||||
use OpenSpout\Writer\Common\Entity\Worksheet;
|
||||
use OpenSpout\Writer\Common\Helper\FileSystemWithRootFolderHelperInterface;
|
||||
@ -27,11 +28,11 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
public const MIMETYPE_FILE_NAME = 'mimetype';
|
||||
public const STYLES_XML_FILE_NAME = 'styles.xml';
|
||||
|
||||
private string $baseFolderRealPath;
|
||||
private readonly string $baseFolderRealPath;
|
||||
|
||||
/** @var string document creator */
|
||||
private string $creator;
|
||||
private CommonFileSystemHelper $baseFileSystemHelper;
|
||||
private readonly string $creator;
|
||||
private readonly CommonFileSystemHelper $baseFileSystemHelper;
|
||||
|
||||
/** @var string Path to the root folder inside the temp folder where the files to create the ODS will be stored */
|
||||
private string $rootFolder;
|
||||
@ -43,7 +44,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
private string $sheetsContentTempFolder;
|
||||
|
||||
/** @var ZipHelper Helper to perform tasks with Zip archive */
|
||||
private ZipHelper $zipHelper;
|
||||
private readonly ZipHelper $zipHelper;
|
||||
|
||||
/**
|
||||
* @param string $baseFolderPath The path of the base folder where all the I/O can occur
|
||||
@ -91,7 +92,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates all the folders needed to create a ODS file, as well as the files that won't change.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create at least one of the base folders
|
||||
* @throws IOException If unable to create at least one of the base folders
|
||||
*/
|
||||
public function createBaseFilesAndFolders(): void
|
||||
{
|
||||
@ -214,7 +215,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the folder that will be used as root.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the folder
|
||||
* @throws IOException If unable to create the folder
|
||||
*/
|
||||
private function createRootFolder(): self
|
||||
{
|
||||
@ -226,7 +227,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the "META-INF" folder under the root folder as well as the "manifest.xml" file in it.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the folder or the "manifest.xml" file
|
||||
* @throws IOException If unable to create the folder or the "manifest.xml" file
|
||||
*/
|
||||
private function createMetaInfoFolderAndFile(): self
|
||||
{
|
||||
@ -240,7 +241,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the "manifest.xml" file under the "META-INF" folder (under root).
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the file
|
||||
* @throws IOException If unable to create the file
|
||||
*/
|
||||
private function createManifestFile(): self
|
||||
{
|
||||
@ -263,7 +264,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
* Creates the temp folder where specific sheets content will be written to.
|
||||
* This folder is not part of the final ODS file and is only used to be able to jump between sheets.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the folder
|
||||
* @throws IOException If unable to create the folder
|
||||
*/
|
||||
private function createSheetsContentTempFolder(): self
|
||||
{
|
||||
@ -275,7 +276,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the "meta.xml" file under the root folder.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the file
|
||||
* @throws IOException If unable to create the file
|
||||
*/
|
||||
private function createMetaFile(): self
|
||||
{
|
||||
@ -300,7 +301,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the "mimetype" file under the root folder.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the file
|
||||
* @throws IOException If unable to create the file
|
||||
*/
|
||||
private function createMimetypeFile(): self
|
||||
{
|
||||
|
@ -22,7 +22,7 @@ use OpenSpout\Writer\ODS\Helper\BorderHelper;
|
||||
*/
|
||||
final class StyleManager extends CommonStyleManager
|
||||
{
|
||||
private AbstractOptions $options;
|
||||
private readonly AbstractOptions $options;
|
||||
|
||||
public function __construct(StyleRegistry $styleRegistry, AbstractOptions $options)
|
||||
{
|
||||
|
@ -25,13 +25,13 @@ use OpenSpout\Writer\ODS\Manager\Style\StyleManager;
|
||||
final class WorksheetManager implements WorksheetManagerInterface
|
||||
{
|
||||
/** @var ODSEscaper Strings escaper */
|
||||
private ODSEscaper $stringsEscaper;
|
||||
private readonly ODSEscaper $stringsEscaper;
|
||||
|
||||
/** @var StyleManager Manages styles */
|
||||
private StyleManager $styleManager;
|
||||
private readonly StyleManager $styleManager;
|
||||
|
||||
/** @var StyleMerger Helper to merge styles together */
|
||||
private StyleMerger $styleMerger;
|
||||
private readonly StyleMerger $styleMerger;
|
||||
|
||||
/**
|
||||
* WorksheetManager constructor.
|
||||
@ -51,7 +51,7 @@ final class WorksheetManager implements WorksheetManagerInterface
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet to start
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the sheet data file cannot be opened for writing
|
||||
* @throws IOException If the sheet data file cannot be opened for writing
|
||||
*/
|
||||
public function startSheet(Worksheet $worksheet): void
|
||||
{
|
||||
|
@ -6,6 +6,4 @@ namespace OpenSpout\Writer\ODS;
|
||||
|
||||
use OpenSpout\Writer\Common\AbstractOptions;
|
||||
|
||||
final class Options extends AbstractOptions
|
||||
{
|
||||
}
|
||||
final class Options extends AbstractOptions {}
|
||||
|
@ -19,7 +19,7 @@ final class Writer extends AbstractWriterMultiSheets
|
||||
{
|
||||
/** @var string Content-Type value for the header */
|
||||
protected static string $headerContentType = 'application/vnd.oasis.opendocument.spreadsheet';
|
||||
private Options $options;
|
||||
private readonly Options $options;
|
||||
|
||||
public function __construct(?Options $options = null)
|
||||
{
|
||||
|
@ -5,6 +5,8 @@ declare(strict_types=1);
|
||||
namespace OpenSpout\Writer;
|
||||
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Common\Exception\InvalidArgumentException;
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
|
||||
interface WriterInterface
|
||||
{
|
||||
@ -14,7 +16,7 @@ interface WriterInterface
|
||||
*
|
||||
* @param string $outputFilePath Path of the output file that will contain the data
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the writer cannot be opened or if the given path is not writable
|
||||
* @throws IOException If the writer cannot be opened or if the given path is not writable
|
||||
*/
|
||||
public function openToFile(string $outputFilePath): void;
|
||||
|
||||
@ -24,7 +26,7 @@ interface WriterInterface
|
||||
*
|
||||
* @param string $outputFileName Name of the output file that will contain the data. If a path is passed in, only the file name will be kept
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If the writer cannot be opened
|
||||
* @throws IOException If the writer cannot be opened
|
||||
*/
|
||||
public function openToBrowser(string $outputFileName): void;
|
||||
|
||||
@ -33,8 +35,8 @@ interface WriterInterface
|
||||
*
|
||||
* @param Row $row The row to be appended to the stream
|
||||
*
|
||||
* @throws \OpenSpout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to write data
|
||||
* @throws Exception\WriterNotOpenedException If the writer has not been opened yet
|
||||
* @throws IOException If unable to write data
|
||||
*/
|
||||
public function addRow(Row $row): void;
|
||||
|
||||
@ -43,9 +45,9 @@ interface WriterInterface
|
||||
*
|
||||
* @param Row[] $rows The rows to be appended to the stream
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\InvalidArgumentException If the input param is not valid
|
||||
* @throws \OpenSpout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to write data
|
||||
* @throws InvalidArgumentException If the input param is not valid
|
||||
* @throws Exception\WriterNotOpenedException If the writer has not been opened yet
|
||||
* @throws IOException If unable to write data
|
||||
*/
|
||||
public function addRows(array $rows): void;
|
||||
|
||||
|
@ -256,7 +256,7 @@ final class SheetView
|
||||
private function generateAttributes(array $data): string
|
||||
{
|
||||
// Create attribute for each key
|
||||
$attributes = array_map(static function (string $key, bool|string|int $value): string {
|
||||
$attributes = array_map(static function (string $key, bool|int|string $value): string {
|
||||
if (\is_bool($value)) {
|
||||
$value = $value ? 'true' : 'false';
|
||||
}
|
||||
|
41
lib/openspout/src/Writer/XLSX/Helper/DateIntervalHelper.php
Normal file
41
lib/openspout/src/Writer/XLSX/Helper/DateIntervalHelper.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Writer\XLSX\Helper;
|
||||
|
||||
use DateInterval;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class DateIntervalHelper
|
||||
{
|
||||
/**
|
||||
* Excel stores time durations as fractions of days:
|
||||
* A value of 1 equals 24 hours, a value of 0.5 equals 12 hours, etc.
|
||||
*
|
||||
* Note: Excel can only display durations up to hours and it will only auto-detect this value as an actual duration
|
||||
* if the value is less than 1, even if you specify a custom format such als "hh:mm:ss".
|
||||
* To force the display into a duration format, you have to use the brackets around the left most unit
|
||||
* of the format, e.g. "[h]:mm" or "[mm]:ss", which tells Excel to use up all the remaining time exceeding
|
||||
* this unit and put it in this last unit.
|
||||
*/
|
||||
public static function toExcel(DateInterval $interval): float
|
||||
{
|
||||
// For years and months we can only use the respective average of days here - this won't be accurate, but the
|
||||
// DateInterval doesn't give us more details on those:
|
||||
$days = $interval->y * 365.25
|
||||
+ $interval->m * 30.437
|
||||
+ $interval->d
|
||||
+ $interval->h / 24
|
||||
+ $interval->i / 24 / 60
|
||||
+ $interval->s / 24 / 60 / 60;
|
||||
|
||||
if (1 === $interval->invert) {
|
||||
$days *= -1;
|
||||
}
|
||||
|
||||
return $days;
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace OpenSpout\Writer\XLSX\Helper;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
use OpenSpout\Common\Helper\Escaper\XLSX;
|
||||
use OpenSpout\Common\Helper\FileSystemHelper as CommonFileSystemHelper;
|
||||
use OpenSpout\Writer\Common\Entity\Sheet;
|
||||
@ -40,17 +41,17 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
||||
EOD;
|
||||
|
||||
private string $baseFolderRealPath;
|
||||
private CommonFileSystemHelper $baseFileSystemHelper;
|
||||
private readonly string $baseFolderRealPath;
|
||||
private readonly CommonFileSystemHelper $baseFileSystemHelper;
|
||||
|
||||
/** @var ZipHelper Helper to perform tasks with Zip archive */
|
||||
private ZipHelper $zipHelper;
|
||||
private readonly ZipHelper $zipHelper;
|
||||
|
||||
/** @var string document creator */
|
||||
private string $creator;
|
||||
private readonly string $creator;
|
||||
|
||||
/** @var XLSX Used to escape XML data */
|
||||
private XLSX $escaper;
|
||||
private readonly XLSX $escaper;
|
||||
|
||||
/** @var string Path to the root folder inside the temp folder where the files to create the XLSX will be stored */
|
||||
private string $rootFolder;
|
||||
@ -131,7 +132,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates all the folders needed to create a XLSX file, as well as the files that won't change.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create at least one of the base folders
|
||||
* @throws IOException If unable to create at least one of the base folders
|
||||
*/
|
||||
public function createBaseFilesAndFolders(): void
|
||||
{
|
||||
@ -221,6 +222,9 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
);
|
||||
$definedNames .= '<definedName function="false" hidden="true" localSheetId="'.$sheet->getIndex().'" name="_xlnm._FilterDatabase" vbProcedure="false">'.$name.'</definedName>';
|
||||
}
|
||||
if (null !== $printTitleRows = $sheet->getPrintTitleRows()) {
|
||||
$definedNames .= '<definedName name="_xlnm.Print_Titles" localSheetId="'.$sheet->getIndex().'">'.$this->escaper->escape($sheet->getName()).'!'.$printTitleRows.'</definedName>';
|
||||
}
|
||||
}
|
||||
if ('' !== $definedNames) {
|
||||
$workbookXmlFileContents .= '<definedNames>'.$definedNames.'</definedNames>';
|
||||
@ -307,7 +311,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
public function createContentFiles(Options $options, array $worksheets): self
|
||||
{
|
||||
$allMergeCells = $options->getMergeCells();
|
||||
|
||||
$pageSetup = $options->getPageSetup();
|
||||
foreach ($worksheets as $worksheet) {
|
||||
$contentXmlFilePath = $this->getXlWorksheetsFolder().\DIRECTORY_SEPARATOR.basename($worksheet->getFilePath());
|
||||
$worksheetFilePointer = fopen($contentXmlFilePath, 'w');
|
||||
@ -326,10 +330,15 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
CellHelper::getColumnLettersFromColumnIndex($autofilter->toColumnIndex),
|
||||
$autofilter->toRow
|
||||
);
|
||||
fwrite($worksheetFilePointer, '<sheetPr filterMode="false"><pageSetUpPr fitToPage="false"/></sheetPr>');
|
||||
if (isset($pageSetup) && $pageSetup->fitToPage) {
|
||||
fwrite($worksheetFilePointer, '<sheetPr filterMode="false"><pageSetUpPr fitToPage="true"/></sheetPr>');
|
||||
} else {
|
||||
fwrite($worksheetFilePointer, '<sheetPr filterMode="false"><pageSetUpPr fitToPage="false"/></sheetPr>');
|
||||
}
|
||||
fwrite($worksheetFilePointer, sprintf('<dimension ref="%s"/>', $range));
|
||||
} elseif (isset($pageSetup) && $pageSetup->fitToPage) {
|
||||
fwrite($worksheetFilePointer, '<sheetPr><pageSetUpPr fitToPage="true"/></sheetPr>');
|
||||
}
|
||||
|
||||
if (null !== ($sheetView = $sheet->getSheetView())) {
|
||||
fwrite($worksheetFilePointer, '<sheetViews>'.$sheetView->getXml().'</sheetViews>');
|
||||
}
|
||||
@ -366,6 +375,12 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
fwrite($worksheetFilePointer, $mergeCellString);
|
||||
}
|
||||
|
||||
$this->getXMLFragmentForPageMargin($worksheetFilePointer, $options);
|
||||
|
||||
$this->getXMLFragmentForPageSetup($worksheetFilePointer, $options);
|
||||
|
||||
$this->getXMLFragmentForHeaderFooter($worksheetFilePointer, $options);
|
||||
|
||||
// Add the legacy drawing for comments
|
||||
fwrite($worksheetFilePointer, '<legacyDrawing r:id="rId_comments_vml1"/>');
|
||||
|
||||
@ -411,6 +426,93 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
$this->deleteFile($zipFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $targetResource
|
||||
*/
|
||||
private function getXMLFragmentForPageMargin($targetResource, Options $options): void
|
||||
{
|
||||
$pageMargin = $options->getPageMargin();
|
||||
if (null === $pageMargin) {
|
||||
return;
|
||||
}
|
||||
|
||||
fwrite($targetResource, "<pageMargins top=\"{$pageMargin->top}\" right=\"{$pageMargin->right}\" bottom=\"{$pageMargin->bottom}\" left=\"{$pageMargin->left}\" header=\"{$pageMargin->header}\" footer=\"{$pageMargin->footer}\"/>");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $targetResource
|
||||
*/
|
||||
private function getXMLFragmentForHeaderFooter($targetResource, Options $options): void
|
||||
{
|
||||
$headerFooter = $options->getHeaderFooter();
|
||||
if (null === $headerFooter) {
|
||||
return;
|
||||
}
|
||||
|
||||
$xml = '<headerFooter';
|
||||
|
||||
if ($headerFooter->differentOddEven) {
|
||||
$xml .= " differentOddEven=\"{$headerFooter->differentOddEven}\"";
|
||||
}
|
||||
|
||||
$xml .= '>';
|
||||
|
||||
if (null !== $headerFooter->oddHeader) {
|
||||
$xml .= "<oddHeader>{$headerFooter->oddHeader}</oddHeader>";
|
||||
}
|
||||
|
||||
if (null !== $headerFooter->oddFooter) {
|
||||
$xml .= "<oddFooter>{$headerFooter->oddFooter}</oddFooter>";
|
||||
}
|
||||
|
||||
if ($headerFooter->differentOddEven) {
|
||||
if (null !== $headerFooter->evenHeader) {
|
||||
$xml .= "<evenHeader>{$headerFooter->evenHeader}</evenHeader>";
|
||||
}
|
||||
|
||||
if (null !== $headerFooter->evenFooter) {
|
||||
$xml .= "<evenFooter>{$headerFooter->evenFooter}</evenFooter>";
|
||||
}
|
||||
}
|
||||
|
||||
$xml .= '</headerFooter>';
|
||||
|
||||
fwrite($targetResource, $xml);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $targetResource
|
||||
*/
|
||||
private function getXMLFragmentForPageSetup($targetResource, Options $options): void
|
||||
{
|
||||
$pageSetup = $options->getPageSetup();
|
||||
if (null === $pageSetup) {
|
||||
return;
|
||||
}
|
||||
|
||||
$xml = '<pageSetup';
|
||||
|
||||
if (null !== $pageSetup->pageOrientation) {
|
||||
$xml .= " orientation=\"{$pageSetup->pageOrientation->value}\"";
|
||||
}
|
||||
|
||||
if (null !== $pageSetup->paperSize) {
|
||||
$xml .= " paperSize=\"{$pageSetup->paperSize->value}\"";
|
||||
}
|
||||
|
||||
if (null !== $pageSetup->fitToHeight) {
|
||||
$xml .= " fitToHeight=\"{$pageSetup->fitToHeight}\"";
|
||||
}
|
||||
|
||||
if (null !== $pageSetup->fitToWidth) {
|
||||
$xml .= " fitToWidth=\"{$pageSetup->fitToWidth}\"";
|
||||
}
|
||||
|
||||
$xml .= '/>';
|
||||
|
||||
fwrite($targetResource, $xml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct column width references xml to inject into worksheet xml file.
|
||||
*/
|
||||
@ -453,7 +555,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the folder that will be used as root.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the folder
|
||||
* @throws IOException If unable to create the folder
|
||||
*/
|
||||
private function createRootFolder(): self
|
||||
{
|
||||
@ -465,7 +567,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the "_rels" folder under the root folder as well as the ".rels" file in it.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the folder or the ".rels" file
|
||||
* @throws IOException If unable to create the folder or the ".rels" file
|
||||
*/
|
||||
private function createRelsFolderAndFile(): self
|
||||
{
|
||||
@ -479,7 +581,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the ".rels" file under the "_rels" folder (under root).
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the file
|
||||
* @throws IOException If unable to create the file
|
||||
*/
|
||||
private function createRelsFile(): self
|
||||
{
|
||||
@ -500,7 +602,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the "docProps" folder under the root folder as well as the "app.xml" and "core.xml" files in it.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the folder or one of the files
|
||||
* @throws IOException If unable to create the folder or one of the files
|
||||
*/
|
||||
private function createDocPropsFolderAndFiles(): self
|
||||
{
|
||||
@ -515,7 +617,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the "app.xml" file under the "docProps" folder.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the file
|
||||
* @throws IOException If unable to create the file
|
||||
*/
|
||||
private function createAppXmlFile(): self
|
||||
{
|
||||
@ -535,7 +637,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the "core.xml" file under the "docProps" folder.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the file
|
||||
* @throws IOException If unable to create the file
|
||||
*/
|
||||
private function createCoreXmlFile(): self
|
||||
{
|
||||
@ -557,7 +659,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the "xl" folder under the root folder as well as its subfolders.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create at least one of the folders
|
||||
* @throws IOException If unable to create at least one of the folders
|
||||
*/
|
||||
private function createXlFolderAndSubFolders(): self
|
||||
{
|
||||
@ -573,7 +675,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
* Creates the temp folder where specific sheets content will be written to.
|
||||
* This folder is not part of the final ODS file and is only used to be able to jump between sheets.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the folder
|
||||
* @throws IOException If unable to create the folder
|
||||
*/
|
||||
private function createSheetsContentTempFolder(): self
|
||||
{
|
||||
@ -585,7 +687,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the "_rels" folder under the "xl" folder.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the folder
|
||||
* @throws IOException If unable to create the folder
|
||||
*/
|
||||
private function createXlRelsFolder(): self
|
||||
{
|
||||
@ -597,7 +699,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the "drawings" folder under the "xl" folder.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the folder
|
||||
* @throws IOException If unable to create the folder
|
||||
*/
|
||||
private function createDrawingsFolder(): self
|
||||
{
|
||||
@ -609,7 +711,7 @@ final class FileSystemHelper implements FileSystemWithRootFolderHelperInterface
|
||||
/**
|
||||
* Creates the "worksheets" folder under the "xl" folder.
|
||||
*
|
||||
* @throws \OpenSpout\Common\Exception\IOException If unable to create the folder
|
||||
* @throws IOException If unable to create the folder
|
||||
*/
|
||||
private function createXlWorksheetsFolder(): self
|
||||
{
|
||||
|
@ -64,11 +64,11 @@ final class CommentsManager
|
||||
*/
|
||||
private array $drawingFilePointers = [];
|
||||
|
||||
private string $xlFolder;
|
||||
private readonly string $xlFolder;
|
||||
|
||||
private int $shapeId = 1024;
|
||||
|
||||
private Escaper\XLSX $stringsEscaper;
|
||||
private readonly Escaper\XLSX $stringsEscaper;
|
||||
|
||||
/**
|
||||
* @param string $xlFolder Path to the "xl" folder
|
||||
|
@ -31,7 +31,7 @@ final class SharedStringsManager
|
||||
private int $numSharedStrings = 0;
|
||||
|
||||
/** @var Escaper\XLSX Strings escaper */
|
||||
private Escaper\XLSX $stringsEscaper;
|
||||
private readonly Escaper\XLSX $stringsEscaper;
|
||||
|
||||
/**
|
||||
* @param string $xlFolder Path to the "xl" folder
|
||||
|
@ -17,6 +17,7 @@ use OpenSpout\Writer\Common\Manager\RegisteredStyle;
|
||||
use OpenSpout\Writer\Common\Manager\Style\StyleMerger;
|
||||
use OpenSpout\Writer\Common\Manager\WorksheetManagerInterface;
|
||||
use OpenSpout\Writer\XLSX\Helper\DateHelper;
|
||||
use OpenSpout\Writer\XLSX\Helper\DateIntervalHelper;
|
||||
use OpenSpout\Writer\XLSX\Manager\Style\StyleManager;
|
||||
use OpenSpout\Writer\XLSX\Options;
|
||||
|
||||
@ -35,24 +36,24 @@ final class WorksheetManager implements WorksheetManagerInterface
|
||||
public const MAX_CHARACTERS_PER_CELL = 32767;
|
||||
|
||||
/** @var CommentsManager Manages comments */
|
||||
private CommentsManager $commentsManager;
|
||||
private readonly CommentsManager $commentsManager;
|
||||
|
||||
private Options $options;
|
||||
private readonly Options $options;
|
||||
|
||||
/** @var StyleManager Manages styles */
|
||||
private StyleManager $styleManager;
|
||||
private readonly StyleManager $styleManager;
|
||||
|
||||
/** @var StyleMerger Helper to merge styles together */
|
||||
private StyleMerger $styleMerger;
|
||||
private readonly StyleMerger $styleMerger;
|
||||
|
||||
/** @var SharedStringsManager Helper to write shared strings */
|
||||
private SharedStringsManager $sharedStringsManager;
|
||||
private readonly SharedStringsManager $sharedStringsManager;
|
||||
|
||||
/** @var XLSXEscaper Strings escaper */
|
||||
private XLSXEscaper $stringsEscaper;
|
||||
private readonly XLSXEscaper $stringsEscaper;
|
||||
|
||||
/** @var StringHelper String helper */
|
||||
private StringHelper $stringHelper;
|
||||
private readonly StringHelper $stringHelper;
|
||||
|
||||
/**
|
||||
* WorksheetManager constructor.
|
||||
@ -200,6 +201,8 @@ final class WorksheetManager implements WorksheetManagerInterface
|
||||
$cellXML .= '><f>'.substr($cell->getValue(), 1).'</f></c>';
|
||||
} elseif ($cell instanceof Cell\DateTimeCell) {
|
||||
$cellXML .= '><v>'.DateHelper::toExcel($cell->getValue()).'</v></c>';
|
||||
} elseif ($cell instanceof Cell\DateIntervalCell) {
|
||||
$cellXML .= '><v>'.DateIntervalHelper::toExcel($cell->getValue()).'</v></c>';
|
||||
} elseif ($cell instanceof Cell\ErrorCell) {
|
||||
// only writes the error value if it's a string
|
||||
$cellXML .= ' t="e"><v>'.$cell->getRawValue().'</v></c>';
|
||||
|
@ -17,11 +17,10 @@ final class MergeCell
|
||||
* @param positive-int $bottomRightRow
|
||||
*/
|
||||
public function __construct(
|
||||
public int $sheetIndex,
|
||||
public int $topLeftColumn,
|
||||
public int $topLeftRow,
|
||||
public int $bottomRightColumn,
|
||||
public int $bottomRightRow,
|
||||
) {
|
||||
}
|
||||
public readonly int $sheetIndex,
|
||||
public readonly int $topLeftColumn,
|
||||
public readonly int $topLeftRow,
|
||||
public readonly int $bottomRightColumn,
|
||||
public readonly int $bottomRightRow,
|
||||
) {}
|
||||
}
|
||||
|
@ -6,6 +6,9 @@ namespace OpenSpout\Writer\XLSX;
|
||||
|
||||
use OpenSpout\Common\Entity\Style\Style;
|
||||
use OpenSpout\Writer\Common\AbstractOptions;
|
||||
use OpenSpout\Writer\XLSX\Options\HeaderFooter;
|
||||
use OpenSpout\Writer\XLSX\Options\PageMargin;
|
||||
use OpenSpout\Writer\XLSX\Options\PageSetup;
|
||||
|
||||
final class Options extends AbstractOptions
|
||||
{
|
||||
@ -17,6 +20,12 @@ final class Options extends AbstractOptions
|
||||
/** @var MergeCell[] */
|
||||
private array $MERGE_CELLS = [];
|
||||
|
||||
private ?PageMargin $pageMargin = null;
|
||||
|
||||
private ?PageSetup $pageSetup = null;
|
||||
|
||||
private ?HeaderFooter $headerFooter = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
@ -63,4 +72,34 @@ final class Options extends AbstractOptions
|
||||
{
|
||||
return $this->MERGE_CELLS;
|
||||
}
|
||||
|
||||
public function setPageMargin(PageMargin $pageMargin): void
|
||||
{
|
||||
$this->pageMargin = $pageMargin;
|
||||
}
|
||||
|
||||
public function getPageMargin(): ?PageMargin
|
||||
{
|
||||
return $this->pageMargin;
|
||||
}
|
||||
|
||||
public function setPageSetup(PageSetup $pageSetup): void
|
||||
{
|
||||
$this->pageSetup = $pageSetup;
|
||||
}
|
||||
|
||||
public function getPageSetup(): ?PageSetup
|
||||
{
|
||||
return $this->pageSetup;
|
||||
}
|
||||
|
||||
public function setHeaderFooter(HeaderFooter $headerFooter): void
|
||||
{
|
||||
$this->headerFooter = $headerFooter;
|
||||
}
|
||||
|
||||
public function getHeaderFooter(): ?HeaderFooter
|
||||
{
|
||||
return $this->headerFooter;
|
||||
}
|
||||
}
|
||||
|
16
lib/openspout/src/Writer/XLSX/Options/HeaderFooter.php
Normal file
16
lib/openspout/src/Writer/XLSX/Options/HeaderFooter.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Writer\XLSX\Options;
|
||||
|
||||
final class HeaderFooter
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ?string $oddHeader = null,
|
||||
public readonly ?string $oddFooter = null,
|
||||
public readonly ?string $evenHeader = null,
|
||||
public readonly ?string $evenFooter = null,
|
||||
public readonly bool $differentOddEven = false,
|
||||
) {}
|
||||
}
|
17
lib/openspout/src/Writer/XLSX/Options/PageMargin.php
Normal file
17
lib/openspout/src/Writer/XLSX/Options/PageMargin.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Writer\XLSX\Options;
|
||||
|
||||
final class PageMargin
|
||||
{
|
||||
public function __construct(
|
||||
public readonly float $top = 0.75,
|
||||
public readonly float $right = 0.7,
|
||||
public readonly float $bottom = 0.75,
|
||||
public readonly float $left = 0.7,
|
||||
public readonly float $header = 0.3,
|
||||
public readonly float $footer = 0.3
|
||||
) {}
|
||||
}
|
11
lib/openspout/src/Writer/XLSX/Options/PageOrientation.php
Normal file
11
lib/openspout/src/Writer/XLSX/Options/PageOrientation.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Writer\XLSX\Options;
|
||||
|
||||
enum PageOrientation: string
|
||||
{
|
||||
case PORTRAIT = 'portrait';
|
||||
case LANDSCAPE = 'landscape';
|
||||
}
|
19
lib/openspout/src/Writer/XLSX/Options/PageSetup.php
Normal file
19
lib/openspout/src/Writer/XLSX/Options/PageSetup.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Writer\XLSX\Options;
|
||||
|
||||
final class PageSetup
|
||||
{
|
||||
public readonly bool $fitToPage;
|
||||
|
||||
public function __construct(
|
||||
public readonly ?PageOrientation $pageOrientation,
|
||||
public readonly ?PaperSize $paperSize,
|
||||
public readonly ?int $fitToHeight = null,
|
||||
public readonly ?int $fitToWidth = null,
|
||||
) {
|
||||
$this->fitToPage = null !== $fitToHeight || null !== $fitToWidth;
|
||||
}
|
||||
}
|
34
lib/openspout/src/Writer/XLSX/Options/PaperSize.php
Normal file
34
lib/openspout/src/Writer/XLSX/Options/PaperSize.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenSpout\Writer\XLSX\Options;
|
||||
|
||||
enum PaperSize: int
|
||||
{
|
||||
case Letter = 1;
|
||||
case Tabloid = 3;
|
||||
case Ledger = 4;
|
||||
case Legal = 5;
|
||||
case Statement = 6;
|
||||
case Executive = 7;
|
||||
case A3 = 8;
|
||||
case A4 = 9;
|
||||
case A5 = 11;
|
||||
case B4 = 12;
|
||||
case B5 = 13;
|
||||
case Folio = 14;
|
||||
case Quarto = 15;
|
||||
case Standard = 16;
|
||||
case Note = 18;
|
||||
case C = 24;
|
||||
case D = 25;
|
||||
case E = 26;
|
||||
case DL = 27;
|
||||
case C5 = 28;
|
||||
case C3 = 29;
|
||||
case C4 = 30;
|
||||
case C6 = 31;
|
||||
case C65 = 32;
|
||||
case B6 = 35;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user