MDL-79667 lib: Upgrade OpenSpout to 4.23.0

This commit is contained in:
Mihail Geshoski 2024-03-09 22:09:47 +08:00
parent b2fa19f45d
commit 73ac16f4ad
102 changed files with 790 additions and 408 deletions

View File

@ -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);

View File

@ -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)
{

View File

@ -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;

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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)
{

View File

@ -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);
}

View File

@ -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

View File

@ -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
{

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Common\Exception;
final class EncodingConversionException extends OpenSpoutException
{
}
final class EncodingConversionException extends OpenSpoutException {}

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Common\Exception;
final class IOException extends OpenSpoutException
{
}
final class IOException extends OpenSpoutException {}

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Common\Exception;
final class InvalidArgumentException extends OpenSpoutException
{
}
final class InvalidArgumentException extends OpenSpoutException {}

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Common\Exception;
final class InvalidColorException extends OpenSpoutException
{
}
final class InvalidColorException extends OpenSpoutException {}

View File

@ -6,6 +6,4 @@ namespace OpenSpout\Common\Exception;
use Exception;
abstract class OpenSpoutException extends Exception
{
}
abstract class OpenSpoutException extends Exception {}

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Common\Exception;
final class UnsupportedTypeException extends OpenSpoutException
{
}
final class UnsupportedTypeException extends OpenSpoutException {}

View File

@ -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
{

View File

@ -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');

View File

@ -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,7 +147,7 @@ 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
* @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

View File

@ -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;
}

View File

@ -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)
{

View File

@ -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
{

View File

@ -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
}
}
}

View File

@ -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
{

View File

@ -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

View File

@ -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;

View File

@ -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,
) {}
}

View File

@ -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
{

View File

@ -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
{

View File

@ -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)
{

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Reader\Exception;
final class IteratorNotRewindableException extends ReaderException
{
}
final class IteratorNotRewindableException extends ReaderException {}

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Reader\Exception;
final class NoSheetsFoundException extends ReaderException
{
}
final class NoSheetsFoundException extends ReaderException {}

View File

@ -6,6 +6,4 @@ namespace OpenSpout\Reader\Exception;
use OpenSpout\Common\Exception\OpenSpoutException;
abstract class ReaderException extends OpenSpoutException
{
}
abstract class ReaderException extends OpenSpoutException {}

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Reader\Exception;
final class ReaderNotOpenedException extends ReaderException
{
}
final class ReaderNotOpenedException extends ReaderException {}

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Reader\Exception;
final class SharedStringNotFoundException extends ReaderException
{
}
final class SharedStringNotFoundException extends ReaderException {}

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Reader\Exception;
final class XMLProcessingException extends ReaderException
{
}
final class XMLProcessingException extends ReaderException {}

View File

@ -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">

View File

@ -43,7 +43,7 @@ final class SettingsHelper
break;
}
}
} catch (XMLProcessingException $exception) { // @codeCoverageIgnore
} catch (XMLProcessingException) { // @codeCoverageIgnore
// do nothing
}

View File

@ -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
{

View File

@ -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
{

View File

@ -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

View File

@ -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
{

View File

@ -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;

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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)
{

View File

@ -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;

View File

@ -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
{

View File

@ -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}");
}
}

View File

@ -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)
{

View File

@ -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
{

View File

@ -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 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
{

View File

@ -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...)

View File

@ -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
{

View File

@ -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
{

View File

@ -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;
}
}

View File

@ -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

View File

@ -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 = [];

View File

@ -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
) {}
}

View File

@ -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());

View File

@ -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,
) {}
}

View File

@ -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
{

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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;
@ -114,7 +115,7 @@ 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 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
{

View File

@ -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)
{

View File

@ -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
{

View File

@ -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)
{

View File

@ -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;

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Writer\Exception;
final class InvalidSheetNameException extends WriterException
{
}
final class InvalidSheetNameException extends WriterException {}

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Writer\Exception;
final class SheetNotFoundException extends WriterException
{
}
final class SheetNotFoundException extends WriterException {}

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Writer\Exception;
final class WriterAlreadyOpenedException extends WriterException
{
}
final class WriterAlreadyOpenedException extends WriterException {}

View File

@ -6,6 +6,4 @@ namespace OpenSpout\Writer\Exception;
use OpenSpout\Common\Exception\OpenSpoutException;
abstract class WriterException extends OpenSpoutException
{
}
abstract class WriterException extends OpenSpoutException {}

View File

@ -4,6 +4,4 @@ declare(strict_types=1);
namespace OpenSpout\Writer\Exception;
final class WriterNotOpenedException extends WriterException
{
}
final class WriterNotOpenedException extends WriterException {}

View File

@ -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
{

View File

@ -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)
{

View File

@ -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
{

View File

@ -6,6 +6,4 @@ namespace OpenSpout\Writer\ODS;
use OpenSpout\Writer\Common\AbstractOptions;
final class Options extends AbstractOptions
{
}
final class Options extends AbstractOptions {}

View File

@ -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)
{

View File

@ -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;

View File

@ -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';
}

View 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;
}
}

View File

@ -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
);
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));
}
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
{

View File

@ -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

View File

@ -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

View File

@ -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>';

View File

@ -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,
) {}
}

View File

@ -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;
}
}

View 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,
) {}
}

View 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
) {}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace OpenSpout\Writer\XLSX\Options;
enum PageOrientation: string
{
case PORTRAIT = 'portrait';
case LANDSCAPE = 'landscape';
}

View 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;
}
}

View 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