diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ecca9678..12b595933 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Added - Xlsx Reader Optionally Ignore Rows With No Cells. [Issue #3982](https://github.com/PHPOffice/PhpSpreadsheet/issues/3982) [PR #4035](https://github.com/PHPOffice/PhpSpreadsheet/pull/4035) +- Option for CSV output file to have varying numbers of columns for each row. [Issue #1415](https://github.com/PHPOffice/PhpSpreadsheet/issues/1415) [PR #4076](https://github.com/PHPOffice/PhpSpreadsheet/pull/4076) ### Changed - On read, Xlsx Reader had been breaking up union ranges into separate individual ranges. It will now try to preserve range as it was read in. [PR #4042](https://github.com/PHPOffice/PhpSpreadsheet/pull/4042) +- Xlsx/Xls spreadsheet calculation and formatting of dates will use base date of spreadsheet even when spreadsheets with different base dates are simultaneously open. [Issue #1036](https://github.com/PHPOffice/PhpSpreadsheet/issues/1036) [Issue #1635](https://github.com/PHPOffice/PhpSpreadsheet/issues/1635) [PR #4071](https://github.com/PHPOffice/PhpSpreadsheet/pull/4071) ### Deprecated @@ -32,7 +34,10 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Mpdf and Tcpdf Borders on Merged Cells. [Issue #3557](https://github.com/PHPOffice/PhpSpreadsheet/issues/3557) [PR #4047](https://github.com/PHPOffice/PhpSpreadsheet/pull/4047) - Xls Conditional Format Improvements. [PR #4030](https://github.com/PHPOffice/PhpSpreadsheet/pull/4030) [PR #4033](https://github.com/PHPOffice/PhpSpreadsheet/pull/4033) - Conditional Range Unions and Intersections [Issue #4039](https://github.com/PHPOffice/PhpSpreadsheet/issues/4039) [PR #4042](https://github.com/PHPOffice/PhpSpreadsheet/pull/4042) -- Csv Reader allow use of html mimetype. [Issue #4036](https://github.com/PHPOffice/PhpSpreadsheet/issues/4036) [PR #4049](https://github.com/PHPOffice/PhpSpreadsheet/pull/4040) +- Csv Reader allow use of html mimetype. [Issue #4036](https://github.com/PHPOffice/PhpSpreadsheet/issues/4036) [PR #4040](https://github.com/PHPOffice/PhpSpreadsheet/pull/4040) +- Problem rendering line chart with missing plot label. [PR #4074](https://github.com/PHPOffice/PhpSpreadsheet/pull/4074) +- More RTL in Xlsx/Html Comments [Issue #4004](https://github.com/PHPOffice/PhpSpreadsheet/issues/4004) [PR #4065](https://github.com/PHPOffice/PhpSpreadsheet/pull/4065) +- Empty String in sharedStrings. [Issue #4063](https://github.com/PHPOffice/PhpSpreadsheet/issues/4063) [PR #4064](https://github.com/PHPOffice/PhpSpreadsheet/pull/4064) ## 2024-05-11 - 2.1.0 diff --git a/docs/topics/Looping the Loop.md b/docs/topics/Looping the Loop.md index 0b48d0976..046db3a13 100644 --- a/docs/topics/Looping the Loop.md +++ b/docs/topics/Looping the Loop.md @@ -308,6 +308,28 @@ But a peak memory usage of 49,152KB compared with the 57,344KB used by `toArray( Like `toArray()`, `rangeToArray()` is easy to use, but it has the same limitations for flexibility. It provides the same limited control over how the data from each cell is returned in the array as `toArray()`. The same additional arguments that can be provided for the `toArray()` method can also be provided to `rangeToArray()`. + +## Using `rangeToArrayYieldRows()` + +Since v2.1.0 the worksheet method `rangeToArrayYieldRows()` is available. +It allows you to iterate over all sheet's rows with little memory consumption, +while obtaining each row as an array: + +```php +$rowGenerator = $sheet->rangeToArrayYieldRows( + 'A1:' . $sheet->getHighestDataColumn() . $sheet->getHighestDataRow(), + null, + false, + false +); +foreach ($rowGenerator as $row) { + echo $row[0] . ' | ' . $row[1] . "\n"; +} +``` + +See `samples/Reader2/23_iterateRowsYield.php`. + + ## Using Iterators You don't need to build an array from the worksheet to loop through the rows and columns and do whatever processing you need; you can loop through the rows and columns in the Worksheet directly and more efficiently using PhpSpreadsheet's built-in iterators. diff --git a/docs/topics/reading-and-writing-to-file.md b/docs/topics/reading-and-writing-to-file.md index 389917952..aa6298df9 100644 --- a/docs/topics/reading-and-writing-to-file.md +++ b/docs/topics/reading-and-writing-to-file.md @@ -679,6 +679,18 @@ $writer->setOutputEncoding('SJIS-WIN'); $writer->save("05featuredemo.csv"); ``` +#### Writing CSV files with varying numbers of columns + +A CSV file can have a different number of columns in each row. This +differs from the default behavior when saving as a .csv in Excel, but +can be enabled in PhpSpreadsheet by using the following code: + +``` php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Csv($spreadsheet); +$writer->setVariableColumns(true); +$writer->save("05featuredemo.csv"); +``` + #### Decimal and thousands separators If the worksheet you are exporting contains numbers with decimal or diff --git a/samples/Reader2/23_iterateRowsYield.php b/samples/Reader2/23_iterateRowsYield.php new file mode 100644 index 000000000..f1a7ef88f --- /dev/null +++ b/samples/Reader2/23_iterateRowsYield.php @@ -0,0 +1,25 @@ +getSheet(0); + +$rowGenerator = $sheet->rangeToArrayYieldRows( + $spreadsheet->getActiveSheet()->calculateWorksheetDataDimension(), + null, + false, + false +); +foreach ($rowGenerator as $row) { + echo '| ' . $row[0] . ' | ' . $row[1] . "|\n"; +} diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 76b1a54f6..257d1d06c 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -122,7 +122,7 @@ class Calculation */ private Logger $debugLog; - private bool $suppressFormulaErrorsNew = false; + private bool $suppressFormulaErrors = false; /** * Error message for any error that was raised/thrown by the calculation engine. @@ -5382,7 +5382,7 @@ class Calculation { $this->formulaError = $errorMessage; $this->cyclicReferenceStack->clear(); - $suppress = $this->suppressFormulaErrors ?? $this->suppressFormulaErrorsNew; + $suppress = $this->suppressFormulaErrors; if (!$suppress) { throw new Exception($errorMessage, $code, $exception); } @@ -5666,12 +5666,12 @@ class Calculation public function setSuppressFormulaErrors(bool $suppressFormulaErrors): void { - $this->suppressFormulaErrorsNew = $suppressFormulaErrors; + $this->suppressFormulaErrors = $suppressFormulaErrors; } public function getSuppressFormulaErrors(): bool { - return $this->suppressFormulaErrorsNew; + return $this->suppressFormulaErrors; } public static function boolToString(mixed $operand1): mixed diff --git a/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php b/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php index 08bb7ae67..416864529 100644 --- a/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php +++ b/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php @@ -4,7 +4,6 @@ namespace PhpOffice\PhpSpreadsheet\Cell; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Engine\FormattedNumber; -use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; @@ -30,7 +29,7 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder $dataType = parent::dataTypeForValue($value); // Style logic - strings - if ($dataType === DataType::TYPE_STRING && !$value instanceof RichText) { + if ($dataType === DataType::TYPE_STRING && is_string($value)) { // Test for booleans using locale-setting if (StringHelper::strToUpper($value) === Calculation::getTRUE()) { $cell->setValueExplicit(true, DataType::TYPE_BOOL); @@ -54,17 +53,17 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/'); // Check for percentage - if (preg_match('/^\-?\d*' . $decimalSeparator . '?\d*\s?\%$/', preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value))) { - return $this->setPercentage(preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $cell); + if (preg_match('/^\-?\d*' . $decimalSeparator . '?\d*\s?\%$/', (string) preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value))) { + return $this->setPercentage((string) preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $cell); } // Check for currency - if (preg_match(FormattedNumber::currencyMatcherRegexp(), preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $matches, PREG_UNMATCHED_AS_NULL)) { + if (preg_match(FormattedNumber::currencyMatcherRegexp(), (string) preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $matches, PREG_UNMATCHED_AS_NULL)) { // Convert value to number $sign = ($matches['PrefixedSign'] ?? $matches['PrefixedSign2'] ?? $matches['PostfixedSign']) ?? null; $currencyCode = $matches['PrefixedCurrency'] ?? $matches['PostfixedCurrency']; /** @var string */ - $temp = str_replace([$decimalSeparatorNoPreg, $currencyCode, ' ', '-'], ['.', '', '', ''], preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value)); + $temp = str_replace([$decimalSeparatorNoPreg, $currencyCode, ' ', '-'], ['.', '', '', ''], (string) preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value)); $value = (float) ($sign . trim($temp)); return $this->setCurrency($value, $cell, $currencyCode ?? ''); diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index 880cc609f..e205b73ee 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -189,14 +189,21 @@ class Cell implements Stringable */ public function getFormattedValue(): string { - return (string) NumberFormat::toFormattedString( + $currentCalendar = SharedDate::getExcelCalendar(); + SharedDate::setExcelCalendar($this->getWorksheet()->getParent()?->getExcelCalendar()); + $formattedValue = (string) NumberFormat::toFormattedString( $this->getCalculatedValue(), (string) $this->getStyle()->getNumberFormat()->getFormatCode(true) ); + SharedDate::setExcelCalendar($currentCalendar); + + return $formattedValue; } protected static function updateIfCellIsTableHeader(?Worksheet $workSheet, self $cell, mixed $oldValue, mixed $newValue): void { + $oldValue = (is_scalar($oldValue) || $oldValue instanceof Stringable) ? ((string) $oldValue) : null; + $newValue = (is_scalar($newValue) || $newValue instanceof Stringable) ? ((string) $newValue) : null; if (StringHelper::strToLower($oldValue ?? '') === StringHelper::strToLower($newValue ?? '') || $workSheet === null) { return; } @@ -264,7 +271,10 @@ class Cell implements Stringable // Synonym for string case DataType::TYPE_INLINE: // Rich text - $this->value = DataType::checkString($value); + if ($value !== null && !is_scalar($value) && !($value instanceof Stringable)) { + throw new SpreadsheetException('Invalid unstringable value for datatype Inline/String/String2'); + } + $this->value = DataType::checkString(($value instanceof RichText) ? $value : ((string) $value)); break; case DataType::TYPE_NUMERIC: @@ -385,6 +395,8 @@ class Cell implements Stringable if ($this->dataType === DataType::TYPE_FORMULA) { try { + $currentCalendar = SharedDate::getExcelCalendar(); + SharedDate::setExcelCalendar($this->getWorksheet()->getParent()?->getExcelCalendar()); $index = $this->getWorksheet()->getParentOrThrow()->getActiveSheetIndex(); $selected = $this->getWorksheet()->getSelectedCells(); $thisworksheet = $this->getWorksheet(); @@ -509,6 +521,7 @@ class Cell implements Stringable $this->dataType = $originalDataType; } } catch (SpreadsheetException $ex) { + SharedDate::setExcelCalendar($currentCalendar); if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) { return $this->calculatedValue; // Fallback for calculations referencing external files. } elseif (preg_match('/[Uu]ndefined (name|offset: 2|array key 2)/', $ex->getMessage()) === 1) { @@ -521,6 +534,7 @@ class Cell implements Stringable $ex ); } + SharedDate::setExcelCalendar($currentCalendar); if ($result === '#Not Yet Implemented') { return $this->calculatedValue; // Fallback if calculation engine does not support the formula. @@ -924,7 +938,9 @@ class Cell implements Stringable */ public function __toString(): string { - return (string) $this->getValue(); + $retVal = $this->value; + + return ($retVal === null || is_scalar($retVal) || $retVal instanceof Stringable) ? ((string) $retVal) : ''; } public function getIgnoredErrors(): IgnoredErrors diff --git a/src/PhpSpreadsheet/Cell/DataType.php b/src/PhpSpreadsheet/Cell/DataType.php index 8ae11df06..a213725c0 100644 --- a/src/PhpSpreadsheet/Cell/DataType.php +++ b/src/PhpSpreadsheet/Cell/DataType.php @@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Cell; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; +use Stringable; class DataType { @@ -78,7 +79,7 @@ class DataType */ public static function checkErrorCode(mixed $value): string { - $value = (string) $value; + $value = (is_scalar($value) || $value instanceof Stringable) ? ((string) $value) : '#NULL!'; if (!isset(self::$errorCodes[$value])) { $value = '#NULL!'; diff --git a/src/PhpSpreadsheet/Cell/DataValidator.php b/src/PhpSpreadsheet/Cell/DataValidator.php index a3524dfb1..63ef8999f 100644 --- a/src/PhpSpreadsheet/Cell/DataValidator.php +++ b/src/PhpSpreadsheet/Cell/DataValidator.php @@ -46,7 +46,7 @@ class DataValidator $returnValue = $this->numericOperator($dataValidation, (float) $cellValue); } } elseif ($type === DataValidation::TYPE_TEXTLENGTH) { - $returnValue = $this->numericOperator($dataValidation, mb_strlen((string) $cellValue)); + $returnValue = $this->numericOperator($dataValidation, mb_strlen($cell->getValueString())); } return $returnValue; @@ -86,14 +86,14 @@ class DataValidator */ private function isValueInList(Cell $cell): bool { - $cellValue = $cell->getValue(); + $cellValueString = $cell->getValueString(); $dataValidation = $cell->getDataValidation(); $formula1 = $dataValidation->getFormula1(); if (!empty($formula1)) { // inline values list if ($formula1[0] === '"') { - return in_array(strtolower($cellValue), explode(',', strtolower(trim($formula1, '"'))), true); + return in_array(strtolower($cellValueString), explode(',', strtolower(trim($formula1, '"'))), true); } elseif (strpos($formula1, ':') > 0) { // values list cells $matchFormula = '=MATCH(' . $cell->getCoordinate() . ', ' . $formula1 . ', 0)'; diff --git a/src/PhpSpreadsheet/Cell/DefaultValueBinder.php b/src/PhpSpreadsheet/Cell/DefaultValueBinder.php index 2710ead43..6f75a90a8 100644 --- a/src/PhpSpreadsheet/Cell/DefaultValueBinder.php +++ b/src/PhpSpreadsheet/Cell/DefaultValueBinder.php @@ -46,19 +46,33 @@ class DefaultValueBinder implements IValueBinder // Match the value against a few data types if ($value === null) { return DataType::TYPE_NULL; - } elseif (is_float($value) || is_int($value)) { + } + if (is_float($value) || is_int($value)) { return DataType::TYPE_NUMERIC; - } elseif (is_bool($value)) { + } + if (is_bool($value)) { return DataType::TYPE_BOOL; - } elseif ($value === '') { + } + if ($value === '') { return DataType::TYPE_STRING; - } elseif ($value instanceof RichText) { + } + if ($value instanceof RichText) { return DataType::TYPE_INLINE; - } elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=') { + } + if ($value instanceof Stringable) { + $value = (string) $value; + } + if (!is_string($value)) { + $gettype = is_object($value) ? get_class($value) : gettype($value); + + throw new SpreadsheetException("unusable type $gettype"); + } + if (strlen($value) > 1 && $value[0] === '=') { return DataType::TYPE_FORMULA; - } elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) { + } + if (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) { $tValue = ltrim($value, '+-'); - if (is_string($value) && strlen($tValue) > 1 && $tValue[0] === '0' && $tValue[1] !== '.') { + if (strlen($tValue) > 1 && $tValue[0] === '0' && $tValue[1] !== '.') { return DataType::TYPE_STRING; } elseif ((!str_contains($value, '.')) && ($value > PHP_INT_MAX)) { return DataType::TYPE_STRING; @@ -67,11 +81,10 @@ class DefaultValueBinder implements IValueBinder } return DataType::TYPE_NUMERIC; - } elseif (is_string($value)) { - $errorCodes = DataType::getErrorCodes(); - if (isset($errorCodes[$value])) { - return DataType::TYPE_ERROR; - } + } + $errorCodes = DataType::getErrorCodes(); + if (isset($errorCodes[$value])) { + return DataType::TYPE_ERROR; } return DataType::TYPE_STRING; diff --git a/src/PhpSpreadsheet/Chart/Axis.php b/src/PhpSpreadsheet/Chart/Axis.php index 9d8000e3d..dc1fca4fd 100644 --- a/src/PhpSpreadsheet/Chart/Axis.php +++ b/src/PhpSpreadsheet/Chart/Axis.php @@ -37,7 +37,7 @@ class Axis extends Properties /** * Axis Number. * - * @var mixed[] + * @var array{format: string, source_linked: int, numeric: ?bool} */ private array $axisNumber = [ 'format' => self::FORMAT_CODE_GENERAL, diff --git a/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/src/PhpSpreadsheet/Chart/DataSeriesValues.php index 3f105eeca..70f90bf78 100644 --- a/src/PhpSpreadsheet/Chart/DataSeriesValues.php +++ b/src/PhpSpreadsheet/Chart/DataSeriesValues.php @@ -451,12 +451,14 @@ class DataSeriesValues extends Properties if (($dimensions[0] == 1) || ($dimensions[1] == 1)) { $this->dataValues = Functions::flattenArray($newDataValues); } else { - $newArray = array_values(array_shift($newDataValues)); + /** @var array */ + $newDataValuesx = $newDataValues; + $newArray = array_values(array_shift($newDataValuesx) ?? []); foreach ($newArray as $i => $newDataSet) { $newArray[$i] = [$newDataSet]; } - foreach ($newDataValues as $newDataSet) { + foreach ($newDataValuesx as $newDataSet) { $i = 0; foreach ($newDataSet as $newDataVal) { array_unshift($newArray[$i++], $newDataVal); diff --git a/src/PhpSpreadsheet/Chart/Properties.php b/src/PhpSpreadsheet/Chart/Properties.php index 9b294145a..655e79c85 100644 --- a/src/PhpSpreadsheet/Chart/Properties.php +++ b/src/PhpSpreadsheet/Chart/Properties.php @@ -647,8 +647,16 @@ abstract class Properties 'alpha' => $this->shadowColor->getAlpha(), ]; } + $retVal = $this->getArrayElementsValue($this->shadowProperties, $elements); + if (is_scalar($retVal)) { + $retVal = (string) $retVal; + } elseif ($retVal !== null && !is_array($retVal)) { + // @codeCoverageIgnoreStart + throw new Exception('Unexpected value for shadowProperty'); + // @codeCoverageIgnoreEnd + } - return $this->getArrayElementsValue($this->shadowProperties, $elements); + return $retVal; } public function getShadowArray(): array @@ -825,7 +833,16 @@ abstract class Properties */ public function getLineStyleProperty(array|string $elements): ?string { - return $this->getArrayElementsValue($this->lineStyleProperties, $elements); + $retVal = $this->getArrayElementsValue($this->lineStyleProperties, $elements); + if (is_scalar($retVal)) { + $retVal = (string) $retVal; + } elseif ($retVal !== null) { + // @codeCoverageIgnoreStart + throw new Exception('Unexpected value for lineStyleProperty'); + // @codeCoverageIgnoreEnd + } + + return $retVal; } protected const ARROW_SIZES = [ diff --git a/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php b/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php index 7c21a131a..151d5b58c 100644 --- a/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php +++ b/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php @@ -281,6 +281,16 @@ abstract class JpGraphRendererBase implements IRenderer $this->renderTitle(); } + private function getDataLabel(int $groupId, int $index): mixed + { + $plotLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupId)->getPlotLabelByIndex($index); + if (!$plotLabel) { + return ''; + } + + return $plotLabel->getDataValue(); + } + private function renderPlotLine(int $groupID, bool $filled = false, bool $combination = false): void { $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); @@ -334,8 +344,8 @@ abstract class JpGraphRendererBase implements IRenderer // Set the appropriate plot marker $this->formatPointMarker($seriesPlot, $marker); } - $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($index)->getDataValue(); - $seriesPlot->SetLegend($dataLabel); + + $seriesPlot->SetLegend($this->getDataLabel($groupID, $index)); $seriesPlots[] = $seriesPlot; } @@ -408,12 +418,8 @@ abstract class JpGraphRendererBase implements IRenderer if ($dimensions == '3d') { $seriesPlot->SetShadow(); } - if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) { - $dataLabel = ''; - } else { - $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue(); - } - $seriesPlot->SetLegend($dataLabel); + + $seriesPlot->SetLegend($this->getDataLabel($groupID, $j)); $seriesPlots[] = $seriesPlot; } @@ -492,8 +498,7 @@ abstract class JpGraphRendererBase implements IRenderer $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); $this->formatPointMarker($seriesPlot, $marker); } - $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); - $seriesPlot->SetLegend($dataLabel); + $seriesPlot->SetLegend($this->getDataLabel($groupID, $i)); $this->graph->Add($seriesPlot); } @@ -524,13 +529,12 @@ abstract class JpGraphRendererBase implements IRenderer $seriesPlot = new RadarPlot(array_reverse($dataValuesX)); - $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); if ($radarStyle == 'filled') { $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]); } $this->formatPointMarker($seriesPlot, $marker); - $seriesPlot->SetLegend($dataLabel); + $seriesPlot->SetLegend($this->getDataLabel($groupID, $i)); $this->graph->Add($seriesPlot); } diff --git a/src/PhpSpreadsheet/Comment.php b/src/PhpSpreadsheet/Comment.php index 1c07b5154..2e18270c1 100644 --- a/src/PhpSpreadsheet/Comment.php +++ b/src/PhpSpreadsheet/Comment.php @@ -63,6 +63,14 @@ class Comment implements IComparable, Stringable */ private Drawing $backgroundImage; + public const TEXTBOX_DIRECTION_RTL = 'rtl'; + public const TEXTBOX_DIRECTION_LTR = 'ltr'; + // MS uses 'auto' in xml but 'context' in UI + public const TEXTBOX_DIRECTION_AUTO = 'auto'; + public const TEXTBOX_DIRECTION_CONTEXT = 'auto'; + + private string $textboxDirection = ''; + /** * Create a new Comment. */ @@ -232,9 +240,6 @@ class Comment implements IComparable, Stringable return $this->fillColor; } - /** - * Set Alignment. - */ public function setAlignment(string $alignment): self { $this->alignment = $alignment; @@ -242,14 +247,23 @@ class Comment implements IComparable, Stringable return $this; } - /** - * Get Alignment. - */ public function getAlignment(): string { return $this->alignment; } + public function setTextboxDirection(string $textboxDirection): self + { + $this->textboxDirection = $textboxDirection; + + return $this; + } + + public function getTextboxDirection(): string + { + return $this->textboxDirection; + } + /** * Get hash code. */ @@ -265,6 +279,7 @@ class Comment implements IComparable, Stringable . ($this->visible ? 1 : 0) . $this->fillColor->getHashCode() . $this->alignment + . $this->textboxDirection . ($this->hasBackgroundImage() ? $this->backgroundImage->getHashCode() : '') . __CLASS__ ); diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php index d365fbf23..cc4a4859a 100644 --- a/src/PhpSpreadsheet/Reader/Html.php +++ b/src/PhpSpreadsheet/Reader/Html.php @@ -9,6 +9,7 @@ use DOMNode; use DOMText; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\Comment; use PhpOffice\PhpSpreadsheet\Document\Properties; use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException; use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension; @@ -332,6 +333,15 @@ class Html extends BaseReader $sheet->getComment($column . $row) ->getText() ->createTextRun($child->textContent); + if (isset($attributeArray['dir']) && $attributeArray['dir'] === 'rtl') { + $sheet->getComment($column . $row)->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL); + } + if (isset($attributeArray['style'])) { + $alignStyle = $attributeArray['style']; + if (preg_match('/\\btext-align:\\s*(left|right|center|justify)\\b/', $alignStyle, $matches) === 1) { + $sheet->getComment($column . $row)->setAlignment($matches[1]); + } + } } else { $this->processDomElement($child, $sheet, $row, $column, $cellContent); } diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index 58a22835d..be9ccb268 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -1927,8 +1927,10 @@ class Xls extends BaseReader // offset: 0; size: 2; 0 = base 1900, 1 = base 1904 Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); + $this->spreadsheet->setExcelCalendar(Date::CALENDAR_WINDOWS_1900); if (ord($recordData[0]) == 1) { Date::setExcelCalendar(Date::CALENDAR_MAC_1904); + $this->spreadsheet->setExcelCalendar(Date::CALENDAR_MAC_1904); } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index c45700cc4..bcf245973 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\Hyperlink; +use PhpOffice\PhpSpreadsheet\Comment; use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\AutoFilter; @@ -703,6 +704,8 @@ class Xlsx extends BaseReader $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t); } elseif (isset($val->r)) { $sharedStrings[] = $this->parseRichText($val); + } else { + $sharedStrings[] = ''; } } } @@ -712,12 +715,14 @@ class Xlsx extends BaseReader $xmlWorkbookNS = $this->loadZip($relTarget, $mainNS); // Set base date + $excel->setExcelCalendar(Date::CALENDAR_WINDOWS_1900); if ($xmlWorkbookNS->workbookPr) { Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); $attrs1904 = self::getAttributes($xmlWorkbookNS->workbookPr); if (isset($attrs1904['date1904'])) { if (self::boolean((string) $attrs1904['date1904'])) { Date::setExcelCalendar(Date::CALENDAR_MAC_1904); + $excel->setExcelCalendar(Date::CALENDAR_MAC_1904); } } } @@ -1153,6 +1158,14 @@ class Xlsx extends BaseReader $fillImageTitle = ''; $clientData = $shape->xpath('.//x:ClientData'); + $textboxDirection = ''; + $textboxPath = $shape->xpath('.//v:textbox'); + $textbox = (string) ($textboxPath[0]['style'] ?? ''); + if (preg_match('/rtl/i', $textbox) === 1) { + $textboxDirection = Comment::TEXTBOX_DIRECTION_RTL; + } elseif (preg_match('/ltr/i', $textbox) === 1) { + $textboxDirection = Comment::TEXTBOX_DIRECTION_LTR; + } if (is_array($clientData) && !empty($clientData)) { $clientData = $clientData[0]; @@ -1168,7 +1181,7 @@ class Xlsx extends BaseReader } $temp = $clientData->xpath('.//x:TextHAlign'); if (!empty($temp)) { - $textHAlign = $temp[0]; + $textHAlign = strtolower($temp[0]); } } } @@ -1177,6 +1190,9 @@ class Xlsx extends BaseReader if (is_numeric($rowx) && is_numeric($colx) && $textHAlign !== null) { $docSheet->getComment([1 + (int) $colx, 1 + (int) $rowx], false)->setAlignment((string) $textHAlign); } + if (is_numeric($rowx) && is_numeric($colx) && $textboxDirection !== '') { + $docSheet->getComment([1 + (int) $colx, 1 + (int) $rowx], false)->setTextboxDirection($textboxDirection); + } $fillImageRelNode = $shape->xpath('.//v:fill/@o:relid'); if (is_array($fillImageRelNode) && !empty($fillImageRelNode)) { diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index de9c3f25d..c8acfd8cf 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -444,7 +444,7 @@ class ReferenceHelper if ($cell->getDataType() === DataType::TYPE_FORMULA) { // Formula should be adjusted $worksheet->getCell($newCoordinate) - ->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true)); + ->setValue($this->updateFormulaReferences($cell->getValueString(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true)); } else { // Cell value should not be adjusted $worksheet->getCell($newCoordinate)->setValueExplicit($cell->getValue(), $cell->getDataType()); @@ -457,7 +457,7 @@ class ReferenceHelper but we do still need to adjust any formulae in those cells */ if ($cell->getDataType() === DataType::TYPE_FORMULA) { // Formula should be adjusted - $cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true)); + $cell->setValue($this->updateFormulaReferences($cell->getValueString(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true)); } } } @@ -897,7 +897,7 @@ class ReferenceHelper foreach ($sheet->getCoordinates(false) as $coordinate) { $cell = $sheet->getCell($coordinate); if ($cell->getDataType() === DataType::TYPE_FORMULA) { - $formula = $cell->getValue(); + $formula = $cell->getValueString(); if (str_contains($formula, $oldName)) { $formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula); $formula = str_replace($oldName . '!', $newName . '!', $formula); diff --git a/src/PhpSpreadsheet/RichText/RichText.php b/src/PhpSpreadsheet/RichText/RichText.php index 32b3727be..1dc391a6c 100644 --- a/src/PhpSpreadsheet/RichText/RichText.php +++ b/src/PhpSpreadsheet/RichText/RichText.php @@ -27,8 +27,8 @@ class RichText implements IComparable, Stringable // Rich-Text string attached to cell? if ($cell !== null) { // Add cell text and style - if ($cell->getValue() != '') { - $objRun = new Run($cell->getValue()); + if ($cell->getValueString() !== '') { + $objRun = new Run($cell->getValueString()); $objRun->setFont(clone $cell->getWorksheet()->getStyle($cell->getCoordinate())->getFont()); $this->addText($objRun); } diff --git a/src/PhpSpreadsheet/Shared/Date.php b/src/PhpSpreadsheet/Shared/Date.php index ed19534de..b8feeb9c2 100644 --- a/src/PhpSpreadsheet/Shared/Date.php +++ b/src/PhpSpreadsheet/Shared/Date.php @@ -10,7 +10,6 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; -use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; class Date @@ -64,15 +63,15 @@ class Date /** * Set the Excel calendar (Windows 1900 or Mac 1904). * - * @param int $baseYear Excel base date (1900 or 1904) + * @param ?int $baseYear Excel base date (1900 or 1904) * * @return bool Success or failure */ - public static function setExcelCalendar(int $baseYear): bool + public static function setExcelCalendar(?int $baseYear): bool { if ( - ($baseYear == self::CALENDAR_WINDOWS_1900) - || ($baseYear == self::CALENDAR_MAC_1904) + ($baseYear === self::CALENDAR_WINDOWS_1900) + || ($baseYear === self::CALENDAR_MAC_1904) ) { self::$excelCalendar = $baseYear; @@ -173,7 +172,7 @@ class Date throw new Exception("Invalid string $value supplied for datatype Date"); } - $newValue = SharedDate::PHPToExcel($date); + $newValue = self::PHPToExcel($date); if ($newValue === false) { throw new Exception("Invalid string $value supplied for datatype Date"); } diff --git a/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php b/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php index 3abe695fc..911a9c34f 100644 --- a/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php +++ b/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php @@ -159,13 +159,15 @@ class PolynomialBestFit extends BestFit $coefficients = []; for ($i = 0; $i < $C->rows; ++$i) { $r = $C->getValue($i + 1, 1); // row and column are origin-1 - if (abs($r) <= 10 ** (-9)) { + if (!is_numeric($r) || abs($r) <= 10 ** (-9)) { $r = 0; + } else { + $r += 0; } $coefficients[] = $r; } - $this->intersect = array_shift($coefficients); + $this->intersect = (float) array_shift($coefficients); // Phpstan is correct //* @phpstan-ignore-next-line $this->slope = $coefficients; diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php index 095507d7b..e571cc4f6 100644 --- a/src/PhpSpreadsheet/Spreadsheet.php +++ b/src/PhpSpreadsheet/Spreadsheet.php @@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Document\Properties; use PhpOffice\PhpSpreadsheet\Document\Security; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; +use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Style\Style; @@ -31,6 +32,8 @@ class Spreadsheet implements JsonSerializable self::VISIBILITY_VERY_HIDDEN, ]; + protected int $excelCalendar = Date::CALENDAR_WINDOWS_1900; + /** * Unique ID. */ @@ -1553,4 +1556,26 @@ class Spreadsheet implements JsonSerializable return $table; } + + /** + * @return bool Success or failure + */ + public function setExcelCalendar(int $baseYear): bool + { + if (($baseYear === Date::CALENDAR_WINDOWS_1900) || ($baseYear === Date::CALENDAR_MAC_1904)) { + $this->excelCalendar = $baseYear; + + return true; + } + + return false; + } + + /** + * @return int Excel base date (1900 or 1904) + */ + public function getExcelCalendar(): int + { + return $this->excelCalendar; + } } diff --git a/src/PhpSpreadsheet/Writer/Csv.php b/src/PhpSpreadsheet/Writer/Csv.php index 6e0c20d25..d15bc654c 100644 --- a/src/PhpSpreadsheet/Writer/Csv.php +++ b/src/PhpSpreadsheet/Writer/Csv.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Writer; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Spreadsheet; use Stringable; @@ -54,6 +55,13 @@ class Csv extends BaseWriter */ private string $outputEncoding = ''; + /** + * Whether number of columns should be allowed to vary + * between rows, or use a fixed range based on the max + * column overall. + */ + private bool $variableColumns = false; + /** * Create a new CSV. */ @@ -104,7 +112,17 @@ class Csv extends BaseWriter $maxRow = $sheet->getHighestDataRow(); // Write rows to file + $row = 0; foreach ($sheet->rangeToArrayYieldRows("A1:$maxCol$maxRow", '', $this->preCalculateFormulas) as $cellsArray) { + ++$row; + if ($this->variableColumns) { + $column = $sheet->getHighestDataColumn($row); + if ($column === 'A' && !$sheet->cellExists("A$row")) { + $cellsArray = []; + } else { + array_splice($cellsArray, Coordinate::columnIndexFromString($column)); + } + } $this->writeLine($this->fileHandle, $cellsArray); } @@ -301,4 +319,26 @@ class Csv extends BaseWriter } fwrite($fileHandle, $line); } + + /** + * Get whether number of columns should be allowed to vary + * between rows, or use a fixed range based on the max + * column overall. + */ + public function getVariableColumns(): bool + { + return $this->variableColumns; + } + + /** + * Set whether number of columns should be allowed to vary + * between rows, or use a fixed range based on the max + * column overall. + */ + public function setVariableColumns(bool $pValue): self + { + $this->variableColumns = $pValue; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index d7a52e65b..1dfd8c0bf 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Chart\Chart; +use PhpOffice\PhpSpreadsheet\Comment; use PhpOffice\PhpSpreadsheet\Document\Properties; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\Run; @@ -1763,9 +1764,15 @@ class Html extends BaseWriter $result = ''; if (!$this->isPdf && isset($worksheet->getComments()[$coordinate])) { $sanitizedString = $this->generateRowCellDataValueRich($worksheet->getComment($coordinate)->getText()); + $dir = ($worksheet->getComment($coordinate)->getTextboxDirection() === Comment::TEXTBOX_DIRECTION_RTL) ? ' dir="rtl"' : ''; + $align = strtolower($worksheet->getComment($coordinate)->getAlignment()); + $alignment = Alignment::HORIZONTAL_ALIGNMENT_FOR_HTML[$align] ?? ''; + if ($alignment !== '') { + $alignment = " style=\"text-align:$alignment\""; + } if ($sanitizedString !== '') { $result .= ''; - $result .= '
' . $sanitizedString . '
'; + $result .= "
" . $sanitizedString . '
'; $result .= PHP_EOL; } } diff --git a/src/PhpSpreadsheet/Writer/Xls/Workbook.php b/src/PhpSpreadsheet/Writer/Xls/Workbook.php index 62d4b08d9..a1a5faf8f 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Workbook.php +++ b/src/PhpSpreadsheet/Writer/Xls/Workbook.php @@ -910,9 +910,9 @@ class Workbook extends BIFFwriter $record = 0x0022; // Record identifier $length = 0x0002; // Bytes to follow - $f1904 = (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904) - ? 1 - : 0; // Flag for 1904 date system + $f1904 = ($this->spreadsheet->getExcelCalendar() === Date::CALENDAR_MAC_1904) + ? 1 // Flag for 1904 date system + : 0; // Flag for 1900 date system $header = pack('vv', $record, $length); $data = pack('v', $f1904); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Comments.php b/src/PhpSpreadsheet/Writer/Xlsx/Comments.php index f64542197..f42c2427b 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Comments.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Comments.php @@ -209,12 +209,14 @@ class Comments extends WriterPart $objWriter->endElement(); // v:textbox + $textBoxArray = [Comment::TEXTBOX_DIRECTION_RTL => 'rtl', Comment::TEXTBOX_DIRECTION_LTR => 'ltr']; + $textboxRtl = $textBoxArray[strtolower($comment->getTextBoxDirection())] ?? 'auto'; $objWriter->startElement('v:textbox'); - $objWriter->writeAttribute('style', 'mso-direction-alt:auto'); + $objWriter->writeAttribute('style', "mso-direction-alt:$textboxRtl"); // div $objWriter->startElement('div'); - $objWriter->writeAttribute('style', 'text-align:left'); + $objWriter->writeAttribute('style', ($textboxRtl === 'rtl' ? 'text-align:right;direction:rtl' : 'text-align:left')); $objWriter->endElement(); $objWriter->endElement(); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php b/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php index 5604be544..0cdfb3cd8 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php @@ -40,7 +40,7 @@ class Workbook extends WriterPart $this->writeFileVersion($objWriter); // workbookPr - $this->writeWorkbookPr($objWriter); + $this->writeWorkbookPr($objWriter, $spreadsheet); // workbookProtection $this->writeWorkbookProtection($objWriter, $spreadsheet); @@ -81,11 +81,11 @@ class Workbook extends WriterPart /** * Write WorkbookPr. */ - private function writeWorkbookPr(XMLWriter $objWriter): void + private function writeWorkbookPr(XMLWriter $objWriter, Spreadsheet $spreadsheet): void { $objWriter->startElement('workbookPr'); - if (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904) { + if ($spreadsheet->getExcelCalendar() === Date::CALENDAR_MAC_1904) { $objWriter->writeAttribute('date1904', '1'); } diff --git a/tests/PhpSpreadsheetTests/Cell/CellTest.php b/tests/PhpSpreadsheetTests/Cell/CellTest.php index 8bd4b9f8f..0a4308b61 100644 --- a/tests/PhpSpreadsheetTests/Cell/CellTest.php +++ b/tests/PhpSpreadsheetTests/Cell/CellTest.php @@ -19,16 +19,20 @@ use PHPUnit\Framework\TestCase; class CellTest extends TestCase { + private ?Spreadsheet $spreadsheet = null; + protected function setUp(): void { - parent::setUp(); Cell::setValueBinder(new DefaultValueBinder()); } protected function tearDown(): void { - parent::tearDown(); Cell::setValueBinder(new DefaultValueBinder()); + if ($this->spreadsheet !== null) { + $this->spreadsheet->disconnectWorksheets(); + $this->spreadsheet = null; + } } public function testSetValueBinderOverride(): void @@ -92,15 +96,13 @@ class CellTest extends TestCase public function testInvalidIsoDateSetValueExplicit(): void { - $spreadsheet = new Spreadsheet(); - $cell = $spreadsheet->getActiveSheet()->getCell('A1'); + $this->spreadsheet = new Spreadsheet(); + $cell = $this->spreadsheet->getActiveSheet()->getCell('A1'); $dateValue = '2022-02-29'; // Invalid leap year $this->expectException(Exception::class); $this->expectExceptionMessage("Invalid string {$dateValue} supplied for datatype Date"); $cell->setValueExplicit($dateValue, DataType::TYPE_ISO_DATE); - - $spreadsheet->disconnectWorksheets(); } /** @@ -110,8 +112,8 @@ class CellTest extends TestCase { $this->expectException(Exception::class); - $spreadsheet = new Spreadsheet(); - $cell = $spreadsheet->getActiveSheet()->getCell('A1'); + $this->spreadsheet = new Spreadsheet(); + $cell = $this->spreadsheet->getActiveSheet()->getCell('A1'); $cell->setValueExplicit($value, $dataType); } @@ -166,8 +168,8 @@ class CellTest extends TestCase public function testDestroyCell2(): void { - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); + $this->spreadsheet = new Spreadsheet(); + $sheet = $this->spreadsheet->getActiveSheet(); $cell = $sheet->getCell('A1'); self::assertSame('A1', $cell->getCoordinate()); $this->expectException(Exception::class); @@ -245,6 +247,7 @@ class CellTest extends TestCase $greenStyle->getFill()->getStartColor()->getARGB(), $style->getFill()->getStartColor()->getARGB() ); + $spreadsheet->disconnectWorksheets(); } /** @@ -296,6 +299,7 @@ class CellTest extends TestCase if ($fillStyle === Fill::FILL_SOLID) { self::assertEquals($fillColor, $style->getFill()->getStartColor()->getARGB()); } + $spreadsheet->disconnectWorksheets(); } public static function appliedStyling(): array diff --git a/tests/PhpSpreadsheetTests/Cell/DefaultValueBinderTest.php b/tests/PhpSpreadsheetTests/Cell/DefaultValueBinderTest.php index fb9b42dbe..8cb5d8876 100644 --- a/tests/PhpSpreadsheetTests/Cell/DefaultValueBinderTest.php +++ b/tests/PhpSpreadsheetTests/Cell/DefaultValueBinderTest.php @@ -108,4 +108,27 @@ class DefaultValueBinderTest extends TestCase self::assertTrue($binder::$called); $spreadsheet->disconnectWorksheets(); } + + public function testDataTypeForValueExceptions(): void + { + try { + self::assertSame('s', DefaultValueBinder::dataTypeForValue(new SpreadsheetException())); + } catch (SpreadsheetException $e) { + self::fail('Should not have failed for stringable'); + } + + try { + DefaultValueBinder::dataTypeForValue([]); + self::fail('Should have failed for array'); + } catch (SpreadsheetException $e) { + self::assertStringContainsString('unusable type array', $e->getMessage()); + } + + try { + DefaultValueBinder::dataTypeForValue(new DateTime()); + self::fail('Should have failed for DateTime'); + } catch (SpreadsheetException $e) { + self::assertStringContainsString('unusable type DateTime', $e->getMessage()); + } + } } diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/DateReaderTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/DateReaderTest.php new file mode 100644 index 000000000..58bcbb95b --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xls/DateReaderTest.php @@ -0,0 +1,127 @@ +load($filename); + + self::assertSame(Date::CALENDAR_WINDOWS_1900, $spreadsheet->getExcelCalendar()); + + $worksheet = $spreadsheet->getActiveSheet(); + self::assertSame(44562, $worksheet->getCell('A1')->getValue()); + self::assertSame('2022-01-01', $worksheet->getCell('A1')->getFormattedValue()); + self::assertSame(44926, $worksheet->getCell('A2')->getValue()); + self::assertSame('2022-12-31', $worksheet->getCell('A2')->getFormattedValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function testReadExcel1904Spreadsheet(): void + { + $filename = 'tests/data/Reader/XLS/1904_Calendar.xls'; + $reader = new Xls(); + $spreadsheet = $reader->load($filename); + + self::assertSame(Date::CALENDAR_MAC_1904, $spreadsheet->getExcelCalendar()); + + $worksheet = $spreadsheet->getActiveSheet(); + self::assertSame(43100, $worksheet->getCell('A1')->getValue()); + self::assertSame('2022-01-01', $worksheet->getCell('A1')->getFormattedValue()); + self::assertSame(43464, $worksheet->getCell('A2')->getValue()); + self::assertSame('2022-12-31', $worksheet->getCell('A2')->getFormattedValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function testNewDateInLoadedExcel1900Spreadsheet(): void + { + $filename = 'tests/data/Reader/XLS/1900_Calendar.xls'; + $reader = new Xls(); + $spreadsheet = $reader->load($filename); + + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->getCell('A4')->setValue('=DATE(2023,1,1)'); + self::assertEquals(44927, $worksheet->getCell('A4')->getCalculatedValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function testNewDateInLoadedExcel1904Spreadsheet(): void + { + $filename = 'tests/data/Reader/XLS/1904_Calendar.xls'; + $reader = new Xls(); + $spreadsheet = $reader->load($filename); + + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->getCell('A4')->setValue('=DATE(2023,1,1)'); + self::assertEquals(43465, $worksheet->getCell('A4')->getCalculatedValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function testSwitchCalendars(): void + { + $filename1904 = 'tests/data/Reader/XLS/1904_Calendar.xls'; + $reader1904 = new Xls(); + $spreadsheet1904 = $reader1904->load($filename1904); + $worksheet1904 = $spreadsheet1904->getActiveSheet(); + $date1 = Date::convertIsoDate('2022-01-01'); + self::assertSame(43100.0, $date1); + + $filename1900 = 'tests/data/Reader/XLS/1900_Calendar.xls'; + $reader1900 = new Xls(); + $spreadsheet1900 = $reader1900->load($filename1900); + $worksheet1900 = $spreadsheet1900->getActiveSheet(); + $date2 = Date::convertIsoDate('2022-01-01'); + self::assertSame(44562.0, $date2); + + self::assertSame(44562, $worksheet1900->getCell('A1')->getValue()); + self::assertSame('2022-01-01', $worksheet1900->getCell('A1')->getFormattedValue()); + self::assertSame(44926, $worksheet1900->getCell('A2')->getValue()); + self::assertSame('2022-12-31', $worksheet1900->getCell('A2')->getFormattedValue()); + self::assertSame(44561, $worksheet1900->getCell('B1')->getCalculatedValue()); + self::assertSame('2021-12-31', $worksheet1900->getCell('B1')->getFormattedValue()); + self::assertSame(44927, $worksheet1900->getCell('B2')->getCalculatedValue()); + self::assertSame('2023-01-01', $worksheet1900->getCell('B2')->getFormattedValue()); + + self::assertSame(43100, $worksheet1904->getCell('A1')->getValue()); + self::assertSame('2022-01-01', $worksheet1904->getCell('A1')->getFormattedValue()); + self::assertSame(43464, $worksheet1904->getCell('A2')->getValue()); + self::assertSame('2022-12-31', $worksheet1904->getCell('A2')->getFormattedValue()); + self::assertSame(43099, $worksheet1904->getCell('B1')->getCalculatedValue()); + self::assertSame('2021-12-31', $worksheet1904->getCell('B1')->getFormattedValue()); + self::assertSame(43465, $worksheet1904->getCell('B2')->getCalculatedValue()); + self::assertSame('2023-01-01', $worksheet1904->getCell('B2')->getFormattedValue()); + + // Check that accessing date values from one spreadsheet doesn't break accessing correct values from another + self::assertSame(44561, $worksheet1900->getCell('B1')->getCalculatedValue()); + self::assertSame('2021-12-31', $worksheet1900->getCell('B1')->getFormattedValue()); + self::assertSame(44927, $worksheet1900->getCell('B2')->getCalculatedValue()); + self::assertSame('2023-01-01', $worksheet1900->getCell('B2')->getFormattedValue()); + self::assertSame(44562, $worksheet1900->getCell('A1')->getValue()); + self::assertSame('2022-01-01', $worksheet1900->getCell('A1')->getFormattedValue()); + self::assertSame(44926, $worksheet1900->getCell('A2')->getValue()); + self::assertSame('2022-12-31', $worksheet1900->getCell('A2')->getFormattedValue()); + + self::assertSame(43099, $worksheet1904->getCell('B1')->getCalculatedValue()); + self::assertSame('2021-12-31', $worksheet1904->getCell('B1')->getFormattedValue()); + self::assertSame(43465, $worksheet1904->getCell('B2')->getCalculatedValue()); + self::assertSame('2023-01-01', $worksheet1904->getCell('B2')->getFormattedValue()); + self::assertSame(43100, $worksheet1904->getCell('A1')->getValue()); + self::assertSame('2022-01-01', $worksheet1904->getCell('A1')->getFormattedValue()); + self::assertSame(43464, $worksheet1904->getCell('A2')->getValue()); + self::assertSame('2022-12-31', $worksheet1904->getCell('A2')->getFormattedValue()); + $spreadsheet1900->disconnectWorksheets(); + $spreadsheet1904->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/DateReaderTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/DateReaderTest.php new file mode 100644 index 000000000..7eaee0df7 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/DateReaderTest.php @@ -0,0 +1,135 @@ +load($filename); + + self::assertSame(Date::CALENDAR_WINDOWS_1900, $spreadsheet->getExcelCalendar()); + + $worksheet = $spreadsheet->getActiveSheet(); + self::assertSame(44562, $worksheet->getCell('A1')->getValue()); + self::assertSame('2022-01-01', $worksheet->getCell('A1')->getFormattedValue()); + self::assertSame(44926, $worksheet->getCell('A2')->getValue()); + self::assertSame('2022-12-31', $worksheet->getCell('A2')->getFormattedValue()); + self::assertSame(44561, $worksheet->getCell('B1')->getCalculatedValue()); + self::assertSame('2021-12-31', $worksheet->getCell('B1')->getFormattedValue()); + self::assertSame(44927, $worksheet->getCell('B2')->getCalculatedValue()); + self::assertSame('2023-01-01', $worksheet->getCell('B2')->getFormattedValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function testReadExcel1904Spreadsheet(): void + { + $filename = 'tests/data/Reader/XLSX/1904_Calendar.xlsx'; + $reader = new Xlsx(); + $spreadsheet = $reader->load($filename); + + self::assertSame(Date::CALENDAR_MAC_1904, $spreadsheet->getExcelCalendar()); + + $worksheet = $spreadsheet->getActiveSheet(); + self::assertSame(43100, $worksheet->getCell('A1')->getValue()); + self::assertSame('2022-01-01', $worksheet->getCell('A1')->getFormattedValue()); + self::assertSame(43464, $worksheet->getCell('A2')->getValue()); + self::assertSame('2022-12-31', $worksheet->getCell('A2')->getFormattedValue()); + self::assertSame(43099, $worksheet->getCell('B1')->getCalculatedValue()); + self::assertSame('2021-12-31', $worksheet->getCell('B1')->getFormattedValue()); + self::assertSame(43465, $worksheet->getCell('B2')->getCalculatedValue()); + self::assertSame('2023-01-01', $worksheet->getCell('B2')->getFormattedValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function testNewDateInLoadedExcel1900Spreadsheet(): void + { + $filename = 'tests/data/Reader/XLSX/1900_Calendar.xlsx'; + $reader = new Xlsx(); + $spreadsheet = $reader->load($filename); + + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->getCell('A4')->setValue('=DATE(2023,1,1)'); + self::assertEquals(44927, $worksheet->getCell('A4')->getCalculatedValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function testNewDateInLoadedExcel1904Spreadsheet(): void + { + $filename = 'tests/data/Reader/XLSX/1904_Calendar.xlsx'; + $reader = new Xlsx(); + $spreadsheet = $reader->load($filename); + + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->getCell('A4')->setValue('=DATE(2023,1,1)'); + self::assertEquals(43465, $worksheet->getCell('A4')->getCalculatedValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function testSwitchCalendars(): void + { + $filename1904 = 'tests/data/Reader/XLSX/1904_Calendar.xlsx'; + $reader1904 = new Xlsx(); + $spreadsheet1904 = $reader1904->load($filename1904); + $worksheet1904 = $spreadsheet1904->getActiveSheet(); + $date1 = Date::convertIsoDate('2022-01-01'); + self::assertSame(43100.0, $date1); + + $filename1900 = 'tests/data/Reader/XLSX/1900_Calendar.xlsx'; + $reader1900 = new Xlsx(); + $spreadsheet1900 = $reader1900->load($filename1900); + $worksheet1900 = $spreadsheet1900->getActiveSheet(); + $date2 = Date::convertIsoDate('2022-01-01'); + self::assertSame(44562.0, $date2); + + self::assertSame(44562, $worksheet1900->getCell('A1')->getValue()); + self::assertSame('2022-01-01', $worksheet1900->getCell('A1')->getFormattedValue()); + self::assertSame(44926, $worksheet1900->getCell('A2')->getValue()); + self::assertSame('2022-12-31', $worksheet1900->getCell('A2')->getFormattedValue()); + self::assertSame(44561, $worksheet1900->getCell('B1')->getCalculatedValue()); + self::assertSame('2021-12-31', $worksheet1900->getCell('B1')->getFormattedValue()); + self::assertSame(44927, $worksheet1900->getCell('B2')->getCalculatedValue()); + self::assertSame('2023-01-01', $worksheet1900->getCell('B2')->getFormattedValue()); + + self::assertSame(43100, $worksheet1904->getCell('A1')->getValue()); + self::assertSame('2022-01-01', $worksheet1904->getCell('A1')->getFormattedValue()); + self::assertSame(43464, $worksheet1904->getCell('A2')->getValue()); + self::assertSame('2022-12-31', $worksheet1904->getCell('A2')->getFormattedValue()); + self::assertSame(43099, $worksheet1904->getCell('B1')->getCalculatedValue()); + self::assertSame('2021-12-31', $worksheet1904->getCell('B1')->getFormattedValue()); + self::assertSame(43465, $worksheet1904->getCell('B2')->getCalculatedValue()); + self::assertSame('2023-01-01', $worksheet1904->getCell('B2')->getFormattedValue()); + + // Check that accessing date values from one spreadsheet doesn't break accessing correct values from another + self::assertSame(44561, $worksheet1900->getCell('B1')->getCalculatedValue()); + self::assertSame('2021-12-31', $worksheet1900->getCell('B1')->getFormattedValue()); + self::assertSame(44927, $worksheet1900->getCell('B2')->getCalculatedValue()); + self::assertSame('2023-01-01', $worksheet1900->getCell('B2')->getFormattedValue()); + self::assertSame(44562, $worksheet1900->getCell('A1')->getValue()); + self::assertSame('2022-01-01', $worksheet1900->getCell('A1')->getFormattedValue()); + self::assertSame(44926, $worksheet1900->getCell('A2')->getValue()); + self::assertSame('2022-12-31', $worksheet1900->getCell('A2')->getFormattedValue()); + + self::assertSame(43099, $worksheet1904->getCell('B1')->getCalculatedValue()); + self::assertSame('2021-12-31', $worksheet1904->getCell('B1')->getFormattedValue()); + self::assertSame(43465, $worksheet1904->getCell('B2')->getCalculatedValue()); + self::assertSame('2023-01-01', $worksheet1904->getCell('B2')->getFormattedValue()); + self::assertSame(43100, $worksheet1904->getCell('A1')->getValue()); + self::assertSame('2022-01-01', $worksheet1904->getCell('A1')->getFormattedValue()); + self::assertSame(43464, $worksheet1904->getCell('A2')->getValue()); + self::assertSame('2022-12-31', $worksheet1904->getCell('A2')->getFormattedValue()); + $spreadsheet1900->disconnectWorksheets(); + $spreadsheet1904->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4063Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4063Test.php new file mode 100644 index 000000000..d30503ae1 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4063Test.php @@ -0,0 +1,24 @@ +getActiveSheet(); + $data = $sheet->toArray(null, true, true, true); + $nbsp = "\u{00a0}"; + self::assertSame(['A' => '226', 'B' => '', 'C' => $nbsp], $data[17]); + self::assertSame(['A' => '38873', 'B' => 'gg', 'C' => ' '], $data[22]); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Csv/VariableColumnsTest.php b/tests/PhpSpreadsheetTests/Writer/Csv/VariableColumnsTest.php new file mode 100644 index 000000000..2d7270814 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Csv/VariableColumnsTest.php @@ -0,0 +1,81 @@ +getActiveSheet(); + $sheet->fromArray( + [ + [1, 2, 3, 4], + [1, 2], + [1, 2, 3, 4, 5], + [], + [1], + [1, 2, 3], + ] + ); + + $filename = File::temporaryFilename(); + $writer = new Csv($spreadsheet); + $writer->setVariableColumns(true); + $writer->save($filename); + + $contents = (string) file_get_contents($filename); + unlink($filename); + $spreadsheet->disconnectWorksheets(); + + $rows = explode(PHP_EOL, $contents); + + self::assertSame('"1","2","3","4"', $rows[0]); + self::assertSame('"1","2"', $rows[1]); + self::assertSame('"1","2","3","4","5"', $rows[2]); + self::assertSame('', $rows[3]); + self::assertSame('"1"', $rows[4]); + self::assertSame('"1","2","3"', $rows[5]); + } + + public function testFixedColumns(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->fromArray( + [ + [1, 2, 3, 4], + [1, 2], + [1, 2, 3, 4, 5], + [], + [1], + [1, 2, 3], + ] + ); + + $filename = File::temporaryFilename(); + $writer = new Csv($spreadsheet); + self::assertFalse($writer->getVariableColumns()); + $writer->save($filename); + + $contents = (string) file_get_contents($filename); + unlink($filename); + $spreadsheet->disconnectWorksheets(); + + $rows = explode(PHP_EOL, $contents); + + self::assertSame('"1","2","3","4",""', $rows[0]); + self::assertSame('"1","2","","",""', $rows[1]); + self::assertSame('"1","2","3","4","5"', $rows[2]); + self::assertSame('"","","","",""', $rows[3]); + self::assertSame('"1","","","",""', $rows[4]); + self::assertSame('"1","2","3","",""', $rows[5]); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Html/CommentAlignmentTest.php b/tests/PhpSpreadsheetTests/Writer/Html/CommentAlignmentTest.php new file mode 100644 index 000000000..a5f65cbfc --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Html/CommentAlignmentTest.php @@ -0,0 +1,110 @@ +getActiveSheet(); + $sheet->getCell('A3')->setValue('A3'); + $sheet->getCell('A4')->setValue('A4'); + $sheet->getComment('A3')->getText()->createText('Comment'); + $sheet->getComment('A4')->getText()->createText('שלום'); + $sheet->getComment('A4')->setAlignment(Alignment::HORIZONTAL_RIGHT); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $type); + $spreadsheet->disconnectWorksheets(); + + self::assertCount(1, $reloadedSpreadsheet->getAllSheets()); + + $rsheet = $reloadedSpreadsheet->getActiveSheet(); + $comment1 = $rsheet->getComment('A3'); + self::assertSame('Comment', $comment1->getText()->getPlainText()); + self::assertSame('general', $comment1->getAlignment()); + $comment2 = $rsheet->getComment('A4'); + self::assertSame('שלום', $comment2->getText()->getPlainText()); + self::assertSame('right', $comment2->getAlignment()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function testIssue4004td(): void + { + $type = 'Html'; + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setRightToLeft(true); + $sheet->getCell('A1')->setValue('ברקוד'); + $comment = $sheet->getComment('A1'); + $comment->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL); + $comment->setAlignment(Alignment::HORIZONTAL_RIGHT); + $text = <<getText()->createTextRun($text); + $comment->setWidth('300pt'); + $comment->setHeight('550pt'); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $type); + $spreadsheet->disconnectWorksheets(); + + self::assertCount(1, $reloadedSpreadsheet->getAllSheets()); + + $rsheet = $reloadedSpreadsheet->getActiveSheet(); + $comment1 = $rsheet->getComment('A1'); + self::assertSame($text, $comment1->getText()->getPlainText()); + $comment->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL); + self::assertSame('right', $comment1->getAlignment()); + self::assertSame('rtl', $comment1->getTextboxDirection()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/CommentAlignmentTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/CommentAlignmentTest.php index 5f869216f..bd337bc8d 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/CommentAlignmentTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/CommentAlignmentTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Comment; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional; @@ -15,6 +16,8 @@ class CommentAlignmentTest extends AbstractFunctional $type = 'Xlsx'; $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); + $sheet->getCell('A3')->setValue('A3'); + $sheet->getCell('A4')->setValue('A4'); $sheet->getComment('A3')->getText()->createText('Comment'); $sheet->getComment('A4')->getText()->createText('שלום'); $sheet->getComment('A4')->setAlignment(Alignment::HORIZONTAL_RIGHT); @@ -30,7 +33,77 @@ class CommentAlignmentTest extends AbstractFunctional self::assertSame('general', $comment1->getAlignment()); $comment2 = $rsheet->getComment('A4'); self::assertSame('שלום', $comment2->getText()->getPlainText()); - self::assertSame('Right', $comment2->getAlignment()); + self::assertSame('right', $comment2->getAlignment()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function testIssue4004td(): void + { + $type = 'Xlsx'; + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setRightToLeft(true); + $sheet->getCell('A1')->setValue('ברקוד'); + $comment = $sheet->getComment('A1'); + $comment->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL); + $comment->setAlignment(Alignment::HORIZONTAL_RIGHT); + $text = <<getText()->createTextRun($text); + $comment->setWidth('300pt'); + $comment->setHeight('550pt'); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $type); + $spreadsheet->disconnectWorksheets(); + + self::assertCount(1, $reloadedSpreadsheet->getAllSheets()); + + $rsheet = $reloadedSpreadsheet->getActiveSheet(); + $comment1 = $rsheet->getComment('A1'); + self::assertSame($text, $comment1->getText()->getPlainText()); + $comment->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL); + self::assertSame('right', $comment1->getAlignment()); + self::assertSame('rtl', $comment1->getTextboxDirection()); $reloadedSpreadsheet->disconnectWorksheets(); } diff --git a/tests/data/Cell/SetValueExplicitException.php b/tests/data/Cell/SetValueExplicitException.php index 6b1508190..d21372bbd 100644 --- a/tests/data/Cell/SetValueExplicitException.php +++ b/tests/data/Cell/SetValueExplicitException.php @@ -5,8 +5,7 @@ declare(strict_types=1); use PhpOffice\PhpSpreadsheet\Cell\DataType; return [ - [ - 'XYZ', - DataType::TYPE_NUMERIC, - ], + 'invalid numeric' => ['XYZ', DataType::TYPE_NUMERIC], + 'invalid array' => [[], DataType::TYPE_STRING], + 'invalid unstringable object' => [new DateTime(), DataType::TYPE_INLINE], ]; diff --git a/tests/data/Reader/XLS/1900_Calendar.xls b/tests/data/Reader/XLS/1900_Calendar.xls new file mode 100644 index 000000000..714670a7d Binary files /dev/null and b/tests/data/Reader/XLS/1900_Calendar.xls differ diff --git a/tests/data/Reader/XLS/1904_Calendar.xls b/tests/data/Reader/XLS/1904_Calendar.xls new file mode 100644 index 000000000..cde7fc193 Binary files /dev/null and b/tests/data/Reader/XLS/1904_Calendar.xls differ diff --git a/tests/data/Reader/XLSX/1900_Calendar.xlsx b/tests/data/Reader/XLSX/1900_Calendar.xlsx new file mode 100644 index 000000000..dfd78efc7 Binary files /dev/null and b/tests/data/Reader/XLSX/1900_Calendar.xlsx differ diff --git a/tests/data/Reader/XLSX/1904_Calendar.xlsx b/tests/data/Reader/XLSX/1904_Calendar.xlsx new file mode 100644 index 000000000..f3a8a12d8 Binary files /dev/null and b/tests/data/Reader/XLSX/1904_Calendar.xlsx differ diff --git a/tests/data/Reader/XLSX/issue.4063.xlsx b/tests/data/Reader/XLSX/issue.4063.xlsx new file mode 100644 index 000000000..43f3ea6c7 Binary files /dev/null and b/tests/data/Reader/XLSX/issue.4063.xlsx differ