diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 8fb4f81978..d640a0b8e8 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -25,6 +25,12 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
+ - name: Install locales
+ run: sudo apt-get install -y language-pack-fr language-pack-de
+
+ - name: Install single-byte locale
+ run: sudo sed -i -e 's/# de_DE@euro/de_DE@euro/g' /etc/locale.gen && sudo locale-gen de_DE@euro
+
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f567e658c6..1d78534a3e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,11 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com)
and this project adheres to [Semantic Versioning](https://semver.org).
-## TBD - 3.7.0
+## TBD - 3.8.0
### Added
-- Nothing yet.
+- CHOOSECOLS, CHOOSEROWS, DROP, TAKE, and EXPAND. [PR #4286](https://github.com/PHPOffice/PhpSpreadsheet/pull/4286)
### Changed
@@ -21,11 +21,28 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Deprecated
+- Nothing yet.
+
+### Fixed
+
+- Xlsx Reader Shared Formula with Boolean Result. Partial solution for [Issue #4280](https://github.com/PHPOffice/PhpSpreadsheet/issues/4280) [PR #4281](https://github.com/PHPOffice/PhpSpreadsheet/pull/4281)
+- Retitling cloned Worksheets. [Issue #641](https://github.com/PHPOffice/PhpSpreadsheet/issues/641) [PR #4302](https://github.com/PHPOffice/PhpSpreadsheet/pull/4302)
+
+## 2024-12-26 - 3.7.0
+
+### Deprecated
+
- Drawing::setIsUrl is unneeded. The property is set when setPath determines whether path is a url.
### Fixed
-- More context options may be needed for http(s) image. [Php issue 17121](https://github.com/php/php-src/issues/17121) [PR #4276](https://github.com/PHPOffice/PhpSpreadsheet/pull/4276)
+- Security patches for Samples.
+- Security patches for Html Writer.
+- Avoid unexpected charset in currency symbol. [PR #4279](https://github.com/PHPOffice/PhpSpreadsheet/pull/4279)
+- Add forceFullCalc option to Xlsx Writer. [Issue #4269](https://github.com/PHPOffice/PhpSpreadsheet/issues/4269) [PR #4271](https://github.com/PHPOffice/PhpSpreadsheet/pull/4271)
+- More context options may be needed for http(s) image. [Php issue 17121](https://github.com/php/php-src/issues/17121) [PR #4276](https://github.com/PHPOffice/PhpSpreadsheet/pull/4276)
+- Coverage-related tweaks to Xls Reader. [PR #4277](https://github.com/PHPOffice/PhpSpreadsheet/pull/4277)
+- Several fixed to ODS Writer. [Issue #4261](https://github.com/PHPOffice/PhpSpreadsheet/issues/4261) [PR #4263](https://github.com/PHPOffice/PhpSpreadsheet/pull/4263) [PR #4264](https://github.com/PHPOffice/PhpSpreadsheet/pull/4264) [PR #4266](https://github.com/PHPOffice/PhpSpreadsheet/pull/4266)
## 2024-12-08 - 3.6.0
diff --git a/LICENSE b/LICENSE
index 3ec5723dde..04a90f083e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019 PhpSpreadsheet Authors
+Copyright (c) 2019-2025 PhpSpreadsheet Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/composer.lock b/composer.lock
index 6533b316ad..cc706e608e 100644
--- a/composer.lock
+++ b/composer.lock
@@ -788,16 +788,16 @@
},
{
"name": "dompdf/dompdf",
- "version": "v3.0.1",
+ "version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
- "reference": "2d622faf9aa1f8f7f24dd094e49b5cf6c0c5d4e6"
+ "reference": "baf4084b27c7f4b5b7a221b19a94d11327664eb8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/dompdf/dompdf/zipball/2d622faf9aa1f8f7f24dd094e49b5cf6c0c5d4e6",
- "reference": "2d622faf9aa1f8f7f24dd094e49b5cf6c0c5d4e6",
+ "url": "https://api.github.com/repos/dompdf/dompdf/zipball/baf4084b27c7f4b5b7a221b19a94d11327664eb8",
+ "reference": "baf4084b27c7f4b5b7a221b19a94d11327664eb8",
"shasum": ""
},
"require": {
@@ -813,7 +813,7 @@
"ext-json": "*",
"ext-zip": "*",
"mockery/mockery": "^1.3",
- "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10",
+ "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "^3.5",
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
},
@@ -846,22 +846,22 @@
"homepage": "https://github.com/dompdf/dompdf",
"support": {
"issues": "https://github.com/dompdf/dompdf/issues",
- "source": "https://github.com/dompdf/dompdf/tree/v3.0.1"
+ "source": "https://github.com/dompdf/dompdf/tree/v3.0.2"
},
- "time": "2024-12-05T14:59:38+00:00"
+ "time": "2024-12-27T20:27:37+00:00"
},
{
"name": "dompdf/php-font-lib",
- "version": "1.0.0",
+ "version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-font-lib.git",
- "reference": "991d6a954f6bbd7e41022198f00586b230731441"
+ "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/991d6a954f6bbd7e41022198f00586b230731441",
- "reference": "991d6a954f6bbd7e41022198f00586b230731441",
+ "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d",
+ "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d",
"shasum": ""
},
"require": {
@@ -891,9 +891,9 @@
"homepage": "https://github.com/dompdf/php-font-lib",
"support": {
"issues": "https://github.com/dompdf/php-font-lib/issues",
- "source": "https://github.com/dompdf/php-font-lib/tree/1.0.0"
+ "source": "https://github.com/dompdf/php-font-lib/tree/1.0.1"
},
- "time": "2024-04-29T13:40:38+00:00"
+ "time": "2024-12-02T14:37:59+00:00"
},
{
"name": "dompdf/php-svg-lib",
@@ -1051,16 +1051,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
- "version": "v3.65.0",
+ "version": "v3.66.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
- "reference": "79d4f3e77b250a7d8043d76c6af8f0695e8a469f"
+ "reference": "5f5f2a142ff36b93c41885bca29cc5f861c013e6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/79d4f3e77b250a7d8043d76c6af8f0695e8a469f",
- "reference": "79d4f3e77b250a7d8043d76c6af8f0695e8a469f",
+ "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/5f5f2a142ff36b93c41885bca29cc5f861c013e6",
+ "reference": "5f5f2a142ff36b93c41885bca29cc5f861c013e6",
"shasum": ""
},
"require": {
@@ -1086,7 +1086,7 @@
"symfony/polyfill-mbstring": "^1.28",
"symfony/polyfill-php80": "^1.28",
"symfony/polyfill-php81": "^1.28",
- "symfony/process": "^5.4 || ^6.0 || ^7.0",
+ "symfony/process": "^5.4 || ^6.0 || ^7.0 <7.2",
"symfony/stopwatch": "^5.4 || ^6.0 || ^7.0"
},
"require-dev": {
@@ -1142,7 +1142,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
- "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.65.0"
+ "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.66.0"
},
"funding": [
{
@@ -1150,7 +1150,7 @@
"type": "github"
}
],
- "time": "2024-11-25T00:39:24+00:00"
+ "time": "2024-12-29T13:46:23+00:00"
},
{
"name": "masterminds/html5",
@@ -1221,16 +1221,16 @@
},
{
"name": "mitoteam/jpgraph",
- "version": "10.4.3",
+ "version": "10.4.4",
"source": {
"type": "git",
"url": "https://github.com/mitoteam/jpgraph.git",
- "reference": "f0db97108aec23a3bbb34721365931af992b83b3"
+ "reference": "9ad8e2fcc30f765c788a28543e9705fb541d499f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/mitoteam/jpgraph/zipball/f0db97108aec23a3bbb34721365931af992b83b3",
- "reference": "f0db97108aec23a3bbb34721365931af992b83b3",
+ "url": "https://api.github.com/repos/mitoteam/jpgraph/zipball/9ad8e2fcc30f765c788a28543e9705fb541d499f",
+ "reference": "9ad8e2fcc30f765c788a28543e9705fb541d499f",
"shasum": ""
},
"require": {
@@ -1270,9 +1270,9 @@
],
"support": {
"issues": "https://github.com/mitoteam/jpgraph/issues",
- "source": "https://github.com/mitoteam/jpgraph/tree/10.4.3"
+ "source": "https://github.com/mitoteam/jpgraph/tree/10.4.4"
},
- "time": "2024-12-01T06:36:31+00:00"
+ "time": "2025-01-01T05:39:20+00:00"
},
{
"name": "mpdf/mpdf",
@@ -3012,24 +3012,24 @@
},
{
"name": "sabberworm/php-css-parser",
- "version": "v8.6.0",
+ "version": "v8.7.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
- "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70"
+ "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d2fb94a9641be84d79c7548c6d39bbebba6e9a70",
- "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70",
+ "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/f414ff953002a9b18e3a116f5e462c56f21237cf",
+ "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf",
"shasum": ""
},
"require": {
"ext-iconv": "*",
- "php": ">=5.6.20"
+ "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
},
"require-dev": {
- "phpunit/phpunit": "^5.7.27"
+ "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.40"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
@@ -3071,9 +3071,9 @@
],
"support": {
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
- "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.6.0"
+ "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.7.0"
},
- "time": "2024-07-01T07:33:21+00:00"
+ "time": "2024-10-27T17:38:32+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -4065,16 +4065,16 @@
},
{
"name": "squizlabs/php_codesniffer",
- "version": "3.11.1",
+ "version": "3.11.2",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
- "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87"
+ "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/19473c30efe4f7b3cd42522d0b2e6e7f243c6f87",
- "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1368f4a58c3c52114b86b1abe8f4098869cb0079",
+ "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079",
"shasum": ""
},
"require": {
@@ -4141,7 +4141,7 @@
"type": "open_collective"
}
],
- "time": "2024-11-16T12:02:36+00:00"
+ "time": "2024-12-11T16:04:26+00:00"
},
{
"name": "symfony/console",
@@ -5425,20 +5425,21 @@
},
{
"name": "tecnickcom/tcpdf",
- "version": "6.7.7",
+ "version": "6.8.0",
"source": {
"type": "git",
"url": "https://github.com/tecnickcom/TCPDF.git",
- "reference": "cfbc0028cc23f057f2baf9e73bdc238153c22086"
+ "reference": "14ffa0e308f5634aa2489568b4b90b24073b6731"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/cfbc0028cc23f057f2baf9e73bdc238153c22086",
- "reference": "cfbc0028cc23f057f2baf9e73bdc238153c22086",
+ "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/14ffa0e308f5634aa2489568b4b90b24073b6731",
+ "reference": "14ffa0e308f5634aa2489568b4b90b24073b6731",
"shasum": ""
},
"require": {
- "php": ">=5.5.0"
+ "ext-curl": "*",
+ "php": ">=7.1.0"
},
"type": "library",
"autoload": {
@@ -5485,7 +5486,7 @@
],
"support": {
"issues": "https://github.com/tecnickcom/TCPDF/issues",
- "source": "https://github.com/tecnickcom/TCPDF/tree/6.7.7"
+ "source": "https://github.com/tecnickcom/TCPDF/tree/6.8.0"
},
"funding": [
{
@@ -5493,7 +5494,7 @@
"type": "custom"
}
],
- "time": "2024-10-26T12:15:02+00:00"
+ "time": "2024-12-23T13:34:57+00:00"
},
{
"name": "theseer/tokenizer",
diff --git a/docs/references/function-list-by-category.md b/docs/references/function-list-by-category.md
index fdd23ae624..e6a702028f 100644
--- a/docs/references/function-list-by-category.md
+++ b/docs/references/function-list-by-category.md
@@ -240,8 +240,12 @@ Excel Function | PhpSpreadsheet Function
ADDRESS | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Address::cell
AREAS | **Not yet Implemented**
CHOOSE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Selection::CHOOSE
+CHOOSECOLS | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ChooseRowsEtc::chooseCols
+CHOOSEROWS | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ChooseRowsEtc::chooseRows
COLUMN | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\RowColumnInformation::COLUMN
COLUMNS | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\RowColumnInformation::COLUMNS
+DROP | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ChooseRowsEtc::drop
+EXPAND | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ChooseRowsEtc::expand
FILTER | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Filter::filter
FORMULATEXT | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Formula::text
GETPIVOTDATA | **Not yet Implemented**
@@ -258,6 +262,7 @@ ROWS | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\RowCo
RTD | **Not yet Implemented**
SORT | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Sort::sort
SORTBY | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Sort::sortBy
+TAKE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ChooseRowsEtc::take
TRANSPOSE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Matrix::transpose
UNIQUE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Unique::unique
VLOOKUP | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\VLookup::lookup
@@ -284,8 +289,6 @@ BASE | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Base::
CEILING | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Ceiling::ceiling
CEILING.MATH | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Ceiling::math
CEILING.PRECISE | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Ceiling::precise
-CHOOSECOLS | **Not yet Implemented**
-CHOOSEROWS | **Not yet Implemented**
COMBIN | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Combinations::withoutRepetition
COMBINA | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Combinations::withRepetition
COS | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Cosine::cos
@@ -296,11 +299,9 @@ CSC | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\C
CSCH | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Cosecant::csch
DECIMAL | **Not yet Implemented**
DEGREES | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Angle::toDegrees
-DROP | **Not yet Implemented**
ECMA.CEILING | **Not yet Implemented**
EVEN | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Round::even
EXP | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Exp::evaluate
-EXPAND | **Not yet Implemented**
FACT | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Factorial::fact
FACTDOUBLE | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Factorial::factDouble
FLOOR | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Floor::floor
@@ -354,7 +355,6 @@ SUMSQ | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\SumSqu
SUMX2MY2 | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\SumSquares::sumXSquaredMinusYSquared
SUMX2PY2 | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\SumSquares::sumXSquaredPlusYSquared
SUMXMY2 | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\SumSquares::sumXMinusYSquared
-TAKE | **Not yet Implemented**
TAN | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Tangent::tan
TANH | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Tangent::tanh
TOCOL | **Not yet Implemented**
diff --git a/docs/references/function-list-by-name.md b/docs/references/function-list-by-name.md
index a403beffe8..3696b95b7c 100644
--- a/docs/references/function-list-by-name.md
+++ b/docs/references/function-list-by-name.md
@@ -79,8 +79,8 @@ CHISQ.INV.RT | CATEGORY_STATISTICAL | \PhpOffice\PhpSpread
CHISQ.TEST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\ChiSquared::test
CHITEST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\ChiSquared::test
CHOOSE | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Selection::CHOOSE
-CHOOSECOLS | CATEGORY_MATH_AND_TRIG | **Not yet Implemented**
-CHOOSEROWS | CATEGORY_MATH_AND_TRIG | **Not yet Implemented**
+CHOOSECOLS | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ChooseRowsEtc::chooseCols
+CHOOSEROWS | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ChooseRowsEtc::chooseRows
CLEAN | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData\Trim::nonPrintable
CODE | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData\CharacterConvert::code
COLUMN | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\RowColumnInformation::COLUMN
@@ -158,7 +158,7 @@ DOLLAR | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpread
DOLLARDE | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial\Dollar::decimal
DOLLARFR | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial\Dollar::fractional
DPRODUCT | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database\DProduct::evaluate
-DROP | CATEGORY_MATH_AND_TRIG | **Not yet Implemented**
+DROP | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ChooseRowsEtc::drop
DSTDEV | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database\DStDev::evaluate
DSTDEVP | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database\DStDevP::evaluate
DSUM | CATEGORY_DATABASE | \PhpOffice\PhpSpreadsheet\Calculation\Database\DSum::evaluate
@@ -183,7 +183,7 @@ ERROR.TYPE | CATEGORY_INFORMATION | \PhpOffice\PhpSpread
EVEN | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Round::even
EXACT | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData\Text::exact
EXP | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Exp::evaluate
-EXPAND | CATEGORY_MATH_AND_TRIG | **Not yet Implemented**
+EXPAND | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ChooseRowsEtc::expand
EXPON.DIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\Exponential::distribution
EXPONDIST | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\Exponential::distribution
@@ -554,7 +554,7 @@ T.DIST.RT | CATEGORY_STATISTICAL | **Not yet Implemente
T.INV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::inverse
T.INV.2T | CATEGORY_STATISTICAL | **Not yet Implemented**
T.TEST | CATEGORY_STATISTICAL | **Not yet Implemented**
-TAKE | CATEGORY_MATH_AND_TRIG | **Not yet Implemented**
+TAKE | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ChooseRowsEtc::take
TAN | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Tangent::tan
TANH | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Tangent::tanh
TBILLEQ | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial\TreasuryBill::bondEquivalentYield
diff --git a/docs/topics/reading-and-writing-to-file.md b/docs/topics/reading-and-writing-to-file.md
index 19fe8a8ed9..28bc3b173e 100644
--- a/docs/topics/reading-and-writing-to-file.md
+++ b/docs/topics/reading-and-writing-to-file.md
@@ -169,6 +169,12 @@ $writer->save("05featuredemo.xlsx");
**Note** Formulas will still be calculated in any column set to be autosized
even if pre-calculated is set to false
+**Note** Prior to release 3.7.0, the use of this feature will cause Excel to be used in a mode where opening a sheet saved in this manner *might* not automatically recalculate a cell's formula when a cell used it the formula changes. Furthermore, that behavior might be applied to all spreadsheets open at the time. To avoid this behavior, add the following statement after `setPreCalculateFormulas` above:
+```php
+$writer->setForceFullCalc(false);
+```
+In a future release, the property's default may change to `false` and that statement may no longer be required.
+
#### Office 2003 compatibility pack
Because of a bug in the Office2003 compatibility pack, there can be some
diff --git a/docs/topics/recipes.md b/docs/topics/recipes.md
index 8b27155d90..c768c49f7e 100644
--- a/docs/topics/recipes.md
+++ b/docs/topics/recipes.md
@@ -925,6 +925,8 @@ getStyle('A1:M500'), rather than styling the cells individually in a
loop. This is much faster compared to looping through cells and styling
them individually.
+**Tip** If you are styling entire row(s) or column(s), e.g. getStyle('A:A'), it is recommended to use applyFromArray as described below rather than setting the styles individually as described above.
+
There is also an alternative manner to set styles. The following code
sets a cell's style to font bold, alignment right, top border thin and a
gradient fill:
diff --git a/samples/Engineering/Convert-Online.php b/samples/Engineering/Convert-Online.php
index a923956b4d..bae13f778c 100644
--- a/samples/Engineering/Convert-Online.php
+++ b/samples/Engineering/Convert-Online.php
@@ -78,14 +78,16 @@
$quantity = $_POST['quantity'];
$fromUnit = $_POST['fromUnit'];
$toUnit = $_POST['toUnit'];
- if (isset($units[$_POST['category']][$fromUnit], $units[$_POST['category']][$toUnit])) {
+ if (!is_numeric($quantity)) {
+ $helper->log('Quantity is not numeric');
+ } elseif (isset($units[$_POST['category']][$fromUnit], $units[$_POST['category']][$toUnit])) {
/** @var float|string */
$result = ConvertUOM::CONVERT($quantity, $fromUnit, $toUnit);
- echo "{$quantity} {$units[$_POST['category']][$fromUnit]} is {$result} {$units[$_POST['category']][$toUnit]}", PHP_EOL;
+ $helper->log("{$quantity} {$units[$_POST['category']][$fromUnit]} is {$result} {$units[$_POST['category']][$toUnit]}");
} else {
- echo 'Please enter quantity and select From Unit and To Unit', PHP_EOL;
+ $helper->log('Please enter quantity and select From Unit and To Unit');
}
} else {
- echo 'Please enter quantity and select From Unit and To Unit', PHP_EOL;
+ $helper->log('Please enter quantity and select From Unit and To Unit');
}
diff --git a/samples/Wizards/NumberFormat/Accounting.php b/samples/Wizards/NumberFormat/Accounting.php
index 0b87dd4812..e453a5fd31 100644
--- a/samples/Wizards/NumberFormat/Accounting.php
+++ b/samples/Wizards/NumberFormat/Accounting.php
@@ -64,13 +64,6 @@
>Trailing
-
@@ -85,21 +78,23 @@
$helper->log('The Sample Number Value must be numeric');
} elseif (!is_numeric($_POST['decimals']) || str_contains((string) $_POST['decimals'], '.') || (int) $_POST['decimals'] < 0) {
$helper->log('The Decimal Places value must be positive integer');
+ } elseif (!in_array($_POST['currency'], array_keys($currencies), true)) {
+ $helper->log('Unrecognized currency symbol');
} else {
try {
- $wizard = new Wizard\Accounting($_POST['currency'], (int) $_POST['decimals'], isset($_POST['thousands']), (bool) $_POST['position'], (bool) $_POST['spacing']);
+ $wizard = new Wizard\Accounting($_POST['currency'], (int) $_POST['decimals'], isset($_POST['thousands']), (bool) $_POST['position']);
$mask = $wizard->format();
$example = (string) NumberFormat::toFormattedString((float) $_POST['number'], $mask);
$helper->log('
Code:
');
$helper->log('use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;');
$helper->log(
- "\$mask = Wizard\\Accounting('{$_POST['currency']}', {$_POST['decimals']}, Wizard\\Number::"
+ "\$wizard = new Wizard\\Accounting('{$_POST['currency']}', {$_POST['decimals']}, Wizard\\Number::"
. (isset($_POST['thousands']) ? 'WITH_THOUSANDS_SEPARATOR' : 'WITHOUT_THOUSANDS_SEPARATOR')
. ', Wizard\Currency::' . (((bool) $_POST['position']) ? 'LEADING_SYMBOL' : 'TRAILING_SYMBOL')
- . ', Wizard\Currency::' . (((bool) $_POST['spacing']) ? 'SYMBOL_WITH_SPACING' : 'SYMBOL_WITHOUT_SPACING')
- . ');
'
+ . ');'
);
- $helper->log('echo (string) $mask;');
+ $helper->log('$mask = $wizard->format();');
+ $helper->log('
echo (string) $mask;');
$helper->log('
Mask:
');
$helper->log($mask . '
');
$helper->log('
Example:
');
diff --git a/samples/Wizards/NumberFormat/Currency.php b/samples/Wizards/NumberFormat/Currency.php
index 2c2b248525..c2a66093b2 100644
--- a/samples/Wizards/NumberFormat/Currency.php
+++ b/samples/Wizards/NumberFormat/Currency.php
@@ -5,6 +5,8 @@
use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\CurrencyNegative;
+use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;
require __DIR__ . '/../Header.php';
@@ -15,6 +17,19 @@
return;
}
+$negatives = [
+ CurrencyNegative::minus,
+ CurrencyNegative::redMinus,
+ CurrencyNegative::parentheses,
+ CurrencyNegative::redParentheses,
+];
+$negativesString = [
+ 'CurrencyNegative::minus',
+ 'CurrencyNegative::redMinus',
+ 'CurrencyNegative::parentheses',
+ 'CurrencyNegative::redParentheses',
+];
+
$currencies = [
'$' => 'US Dollars ($)',
'€' => 'Euro (€)',
@@ -65,10 +80,12 @@
@@ -85,21 +102,27 @@
$helper->log('The Sample Number Value must be numeric');
} elseif (!is_numeric($_POST['decimals']) || str_contains((string) $_POST['decimals'], '.') || (int) $_POST['decimals'] < 0) {
$helper->log('The Decimal Places value must be positive integer');
+ } elseif (!in_array($_POST['currency'], array_keys($currencies), true)) {
+ $helper->log('Unrecognized currency symbol');
} else {
try {
- $wizard = new Wizard\Currency($_POST['currency'], (int) $_POST['decimals'], isset($_POST['thousands']), (bool) $_POST['position'], (bool) $_POST['spacing']);
+ $negative = $negatives[$_POST['negative']] ?? CurrencyNegative::minus;
+ $wizard = new Wizard\Currency($_POST['currency'], (int) $_POST['decimals'], isset($_POST['thousands']), (bool) $_POST['position']);
+ $wizard->setNegative($negative);
$mask = $wizard->format();
- $example = (string) NumberFormat::toFormattedString((float) $_POST['number'], $mask);
+ $example = (string) NumberFormat::toFormattedString((float) $_POST['number'], $mask, [HtmlWriter::class, 'formatColorStatic']);
$helper->log('
Code:');
$helper->log('use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;');
+ $helper->log('use PhpOffice\PhpSpreadsheet\Style\NumberFormat\CurrencyNegative;');
$helper->log(
- "\$mask = Wizard\\Currency('{$_POST['currency']}', {$_POST['decimals']}, Wizard\\Number::"
+ "\$wizard = new Wizard\\Currency('{$_POST['currency']}', {$_POST['decimals']}, Wizard\\Number::"
. (isset($_POST['thousands']) ? 'WITH_THOUSANDS_SEPARATOR' : 'WITHOUT_THOUSANDS_SEPARATOR')
. ', Wizard\Currency::' . (((bool) $_POST['position']) ? 'LEADING_SYMBOL' : 'TRAILING_SYMBOL')
- . ', Wizard\Currency::' . (((bool) $_POST['spacing']) ? 'SYMBOL_WITH_SPACING' : 'SYMBOL_WITHOUT_SPACING')
- . ');
'
+ . ');'
);
- $helper->log('echo (string) $mask;');
+ $helper->log('$wizard->setNegative(' . $negativesString[$_POST['negative']] . ');');
+ $helper->log('$mask = $wizard->format();');
+ $helper->log('
echo (string) $mask;');
$helper->log('
Mask:');
$helper->log($mask . '
');
$helper->log('
Example:');
diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php
index 704332b59e..b256aad3b3 100644
--- a/src/PhpSpreadsheet/Calculation/Calculation.php
+++ b/src/PhpSpreadsheet/Calculation/Calculation.php
@@ -553,13 +553,13 @@ public static function getExcelConstants(string $key): bool|null
'argumentCount' => '2+',
],
'CHOOSECOLS' => [
- 'category' => Category::CATEGORY_MATH_AND_TRIG,
- 'functionCall' => [Functions::class, 'DUMMY'],
+ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => [LookupRef\ChooseRowsEtc::class, 'chooseCols'],
'argumentCount' => '2+',
],
'CHOOSEROWS' => [
- 'category' => Category::CATEGORY_MATH_AND_TRIG,
- 'functionCall' => [Functions::class, 'DUMMY'],
+ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => [LookupRef\ChooseRowsEtc::class, 'chooseRows'],
'argumentCount' => '2+',
],
'CLEAN' => [
@@ -925,8 +925,8 @@ public static function getExcelConstants(string $key): bool|null
'argumentCount' => '3',
],
'DROP' => [
- 'category' => Category::CATEGORY_MATH_AND_TRIG,
- 'functionCall' => [Functions::class, 'DUMMY'],
+ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => [LookupRef\ChooseRowsEtc::class, 'drop'],
'argumentCount' => '2-3',
],
'DSTDEV' => [
@@ -1025,8 +1025,8 @@ public static function getExcelConstants(string $key): bool|null
'argumentCount' => '1',
],
'EXPAND' => [
- 'category' => Category::CATEGORY_MATH_AND_TRIG,
- 'functionCall' => [Functions::class, 'DUMMY'],
+ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => [LookupRef\ChooseRowsEtc::class, 'expand'],
'argumentCount' => '2-4',
],
'EXPONDIST' => [
@@ -2490,8 +2490,8 @@ public static function getExcelConstants(string $key): bool|null
'argumentCount' => '1',
],
'TAKE' => [
- 'category' => Category::CATEGORY_MATH_AND_TRIG,
- 'functionCall' => [Functions::class, 'DUMMY'],
+ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
+ 'functionCall' => [LookupRef\ChooseRowsEtc::class, 'take'],
'argumentCount' => '2-3',
],
'TAN' => [
diff --git a/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php b/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php
index 0107f40453..aa2019e2bb 100644
--- a/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php
+++ b/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php
@@ -145,11 +145,9 @@ private function rows(array $arguments): array
private function columns(array $arguments): array
{
return array_map(
- function (mixed $argument): int {
- return is_array($argument) && is_array($argument[array_keys($argument)[0]])
+ fn (mixed $argument): int => is_array($argument) && is_array($argument[array_keys($argument)[0]])
? count($argument[array_keys($argument)[0]])
- : 1;
- },
+ : 1,
$arguments
);
}
diff --git a/src/PhpSpreadsheet/Calculation/Functions.php b/src/PhpSpreadsheet/Calculation/Functions.php
index 9a094966a3..5ee2dfd7a4 100644
--- a/src/PhpSpreadsheet/Calculation/Functions.php
+++ b/src/PhpSpreadsheet/Calculation/Functions.php
@@ -230,6 +230,32 @@ public static function flattenArray(mixed $array): array
return $flattened;
}
+ /**
+ * Convert a multi-dimensional array to a simple 1-dimensional array.
+ * Same as above but argument is specified in ... format.
+ *
+ * @param mixed $array Array to be flattened
+ *
+ * @return array Flattened array
+ */
+ public static function flattenArray2(mixed ...$array): array
+ {
+ $flattened = [];
+ $stack = array_values($array);
+
+ while (!empty($stack)) {
+ $value = array_shift($stack);
+
+ if (is_array($value)) {
+ array_unshift($stack, ...array_values($value));
+ } else {
+ $flattened[] = $value;
+ }
+ }
+
+ return $flattened;
+ }
+
public static function scalar(mixed $value): mixed
{
if (!is_array($value)) {
diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/ChooseRowsEtc.php b/src/PhpSpreadsheet/Calculation/LookupRef/ChooseRowsEtc.php
new file mode 100644
index 0000000000..97303e7533
--- /dev/null
+++ b/src/PhpSpreadsheet/Calculation/LookupRef/ChooseRowsEtc.php
@@ -0,0 +1,241 @@
+ [$x]) : null, ...$array)); // @phpstan-ignore-line
+ }
+
+ /** @return mixed[] */
+ private static function arrayValues(mixed $array): array
+ {
+ return is_array($array) ? array_values($array) : [$array];
+ }
+
+ /**
+ * CHOOSECOLS.
+ *
+ * @param mixed $input expecting two-dimensional array
+ *
+ * @return mixed[]|string
+ */
+ public static function chooseCols(mixed $input, mixed ...$args): array|string
+ {
+ if (!is_array($input)) {
+ $input = [[$input]];
+ }
+ $retval = self::chooseRows(self::transpose($input), ...$args);
+
+ return is_array($retval) ? self::transpose($retval) : $retval;
+ }
+
+ /**
+ * CHOOSEROWS.
+ *
+ * @param mixed $input expecting two-dimensional array
+ *
+ * @return mixed[]|string
+ */
+ public static function chooseRows(mixed $input, mixed ...$args): array|string
+ {
+ if (!is_array($input)) {
+ $input = [[$input]];
+ }
+ $inputArray = [[]]; // no row 0
+ $numRows = 0;
+ foreach ($input as $inputRow) {
+ $inputArray[] = self::arrayValues($inputRow);
+ ++$numRows;
+ }
+ $outputArray = [];
+ foreach (Functions::flattenArray2(...$args) as $arg) {
+ if (!is_numeric($arg)) {
+ return ExcelError::VALUE();
+ }
+ $index = (int) $arg;
+ if ($index < 0) {
+ $index += $numRows + 1;
+ }
+ if ($index <= 0 || $index > $numRows) {
+ return ExcelError::VALUE();
+ }
+ $outputArray[] = $inputArray[$index];
+ }
+
+ return $outputArray;
+ }
+
+ private static function dropRows(array $array, mixed $offset): array|string
+ {
+ if ($offset === null) {
+ return $array;
+ }
+ if (!is_numeric($offset)) {
+ return ExcelError::VALUE();
+ }
+ $offset = (int) $offset;
+ $count = count($array);
+ if (abs($offset) >= $count) {
+ // In theory, this should be #CALC!, but Excel treats
+ // #CALC! as corrupt, and it's not worth figuring out why
+ return ExcelError::VALUE();
+ }
+ if ($offset === 0) {
+ return $array;
+ }
+ if ($offset > 0) {
+ return array_slice($array, $offset);
+ }
+
+ return array_slice($array, 0, $count + $offset);
+ }
+
+ /**
+ * DROP.
+ *
+ * @param mixed $input expect two-dimensional array
+ *
+ * @return mixed[]|string
+ */
+ public static function drop(mixed $input, mixed $rows = null, mixed $columns = null): array|string
+ {
+ if (!is_array($input)) {
+ $input = [[$input]];
+ }
+ $inputArray = []; // no row 0
+ foreach ($input as $inputRow) {
+ $inputArray[] = self::arrayValues($inputRow);
+ }
+ $outputArray1 = self::dropRows($inputArray, $rows);
+ if (is_string($outputArray1)) {
+ return $outputArray1;
+ }
+ $outputArray2 = self::transpose($outputArray1);
+ $outputArray3 = self::dropRows($outputArray2, $columns);
+ if (is_string($outputArray3)) {
+ return $outputArray3;
+ }
+
+ return self::transpose($outputArray3);
+ }
+
+ private static function takeRows(array $array, mixed $offset): array|string
+ {
+ if ($offset === null) {
+ return $array;
+ }
+ if (!is_numeric($offset)) {
+ return ExcelError::VALUE();
+ }
+ $offset = (int) $offset;
+ if ($offset === 0) {
+ // should be #CALC! - see above
+ return ExcelError::VALUE();
+ }
+ $count = count($array);
+ if (abs($offset) >= $count) {
+ return $array;
+ }
+ if ($offset > 0) {
+ return array_slice($array, 0, $offset);
+ }
+
+ return array_slice($array, $count + $offset);
+ }
+
+ /**
+ * TAKE.
+ *
+ * @param mixed $input expecting two-dimensional array
+ *
+ * @return mixed[]|string
+ */
+ public static function take(mixed $input, mixed $rows, mixed $columns = null): array|string
+ {
+ if (!is_array($input)) {
+ $input = [[$input]];
+ }
+ if ($rows === null && $columns === null) {
+ return $input;
+ }
+ $inputArray = [];
+ foreach ($input as $inputRow) {
+ $inputArray[] = self::arrayValues($inputRow);
+ }
+ $outputArray1 = self::takeRows($inputArray, $rows);
+ if (is_string($outputArray1)) {
+ return $outputArray1;
+ }
+ $outputArray2 = self::transpose($outputArray1);
+ $outputArray3 = self::takeRows($outputArray2, $columns);
+ if (is_string($outputArray3)) {
+ return $outputArray3;
+ }
+
+ return self::transpose($outputArray3);
+ }
+
+ /**
+ * EXPAND.
+ *
+ * @param mixed $input expecting two-dimensional array
+ *
+ * @return mixed[]|string
+ */
+ public static function expand(mixed $input, mixed $rows, mixed $columns = null, mixed $pad = '#N/A'): array|string
+ {
+ if (!is_array($input)) {
+ $input = [[$input]];
+ }
+ if ($rows === null && $columns === null) {
+ return $input;
+ }
+ $numRows = count($input);
+ $rows ??= $numRows;
+ if (!is_numeric($rows)) {
+ return ExcelError::VALUE();
+ }
+ $rows = (int) $rows;
+ if ($rows < count($input)) {
+ return ExcelError::VALUE();
+ }
+ $numCols = 0;
+ foreach ($input as $inputRow) {
+ $numCols = max($numCols, is_array($inputRow) ? count($inputRow) : 1);
+ }
+ $columns ??= $numCols;
+ if (!is_numeric($columns)) {
+ return ExcelError::VALUE();
+ }
+ $columns = (int) $columns;
+ if ($columns < $numCols) {
+ return ExcelError::VALUE();
+ }
+ $inputArray = [];
+ foreach ($input as $inputRow) {
+ $inputArray[] = array_pad(self::arrayValues($inputRow), $columns, $pad);
+ }
+ $outputArray = [];
+ $padRow = array_pad([], $columns, $pad);
+ for ($count = 0; $count < $rows; ++$count) {
+ $outputArray[] = ($count >= $numRows) ? $padRow : $inputArray[$count];
+ }
+
+ return $outputArray;
+ }
+}
diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Random.php b/src/PhpSpreadsheet/Calculation/MathTrig/Random.php
index 5d3516777a..80d4caa7f3 100644
--- a/src/PhpSpreadsheet/Calculation/MathTrig/Random.php
+++ b/src/PhpSpreadsheet/Calculation/MathTrig/Random.php
@@ -86,11 +86,9 @@ public static function randArray(mixed $rows = 1, mixed $columns = 1, mixed $min
return array_chunk(
array_map(
- function () use ($min, $max, $wholeNumber): int|float {
- return $wholeNumber
+ fn (): int|float => $wholeNumber
? mt_rand((int) $min, (int) $max)
- : (mt_rand() / mt_getrandmax()) * ($max - $min) + $min;
- },
+ : (mt_rand() / mt_getrandmax()) * ($max - $min) + $min,
array_fill(0, $rows * $columns, $min)
),
max($columns, 1)
diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php
index 49b0dfc924..edc50ad7aa 100644
--- a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php
+++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php
@@ -133,10 +133,8 @@ public static function inverseRightTail(mixed $probability, mixed $degrees)
return ExcelError::NAN();
}
- $callback = function ($value) use ($degrees): float {
- return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2)
+ $callback = fn ($value): float => 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2)
/ Gamma::gammaValue($degrees / 2));
- };
$newtonRaphson = new NewtonRaphson($callback);
diff --git a/src/PhpSpreadsheet/Calculation/TextData/Text.php b/src/PhpSpreadsheet/Calculation/TextData/Text.php
index f988a6c199..03691a8f27 100644
--- a/src/PhpSpreadsheet/Calculation/TextData/Text.php
+++ b/src/PhpSpreadsheet/Calculation/TextData/Text.php
@@ -171,11 +171,9 @@ private static function applyPadding(array $rows, mixed $padding): array
);
return array_map(
- function (array $row) use ($columnCount, $padding): array {
- return (count($row) < $columnCount)
+ fn (array $row): array => (count($row) < $columnCount)
? array_merge($row, array_fill(0, $columnCount - count($row), $padding))
- : $row;
- },
+ : $row,
$rows
);
}
diff --git a/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php b/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php
index b8918c9153..58463ebea0 100644
--- a/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php
+++ b/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php
@@ -9,6 +9,11 @@
*
* Alternative implementation should leverage off-memory, non-volatile storage
* to reduce overall memory usage.
+ *
+ * Either SimpleCache1 or SimpleCache3, but not both, may be used.
+ * For code coverage testing, it will always be SimpleCache3.
+ *
+ * @codeCoverageIgnore
*/
class SimpleCache1 implements CacheInterface
{
diff --git a/src/PhpSpreadsheet/Helper/Downloader.php b/src/PhpSpreadsheet/Helper/Downloader.php
index 4e98c7925c..85131dc1df 100644
--- a/src/PhpSpreadsheet/Helper/Downloader.php
+++ b/src/PhpSpreadsheet/Helper/Downloader.php
@@ -30,18 +30,18 @@ class Downloader
public function __construct(string $folder, string $filename, ?string $filetype = null)
{
if ((is_dir($folder) === false) || (is_readable($folder) === false)) {
- throw new Exception("Folder {$folder} is not accessable");
+ throw new Exception('Folder is not accessible');
}
$filepath = "{$folder}/{$filename}";
$this->filepath = (string) realpath($filepath);
$this->filename = basename($filepath);
if ((file_exists($this->filepath) === false) || (is_readable($this->filepath) === false)) {
- throw new Exception("{$this->filename} not found, or cannot be read");
+ throw new Exception('File not found, or cannot be read');
}
$filetype ??= pathinfo($filename, PATHINFO_EXTENSION);
if (array_key_exists(strtolower($filetype), self::CONTENT_TYPES) === false) {
- throw new Exception("Invalid filetype: {$filetype} cannot be downloaded");
+ throw new Exception('Invalid filetype: file cannot be downloaded');
}
$this->filetype = strtolower($filetype);
}
diff --git a/src/PhpSpreadsheet/Reader/Xls/Color.php b/src/PhpSpreadsheet/Reader/Xls/Color.php
index 6fd346bfa9..17b6e165ce 100644
--- a/src/PhpSpreadsheet/Reader/Xls/Color.php
+++ b/src/PhpSpreadsheet/Reader/Xls/Color.php
@@ -24,12 +24,6 @@ public static function map(int $color, array $palette, int $version): array
return $palette[$color - 8];
}
- // default color table
- if ($version == Xls::XLS_BIFF8) {
- return Color\BIFF8::lookup($color);
- }
-
- // BIFF5
- return Color\BIFF5::lookup($color);
+ return ($version === Xls::XLS_BIFF8) ? Color\BIFF8::lookup($color) : Color\BIFF5::lookup($color);
}
}
diff --git a/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php b/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php
index 324d12cb21..1c05c63c7e 100644
--- a/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php
+++ b/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php
@@ -36,20 +36,12 @@ class ConditionalFormatting extends Xls
public static function type(int $type): ?string
{
- if (isset(self::$types[$type])) {
- return self::$types[$type];
- }
-
- return null;
+ return self::$types[$type] ?? null;
}
public static function operator(int $operator): ?string
{
- if (isset(self::$operators[$operator])) {
- return self::$operators[$operator];
- }
-
- return null;
+ return self::$operators[$operator] ?? null;
}
/**
diff --git a/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php b/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php
index a0c897efc9..2fef22d211 100644
--- a/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php
+++ b/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php
@@ -48,29 +48,17 @@ class DataValidationHelper extends Xls
public static function type(int $type): ?string
{
- if (isset(self::$types[$type])) {
- return self::$types[$type];
- }
-
- return null;
+ return self::$types[$type] ?? null;
}
public static function errorStyle(int $errorStyle): ?string
{
- if (isset(self::$errorStyles[$errorStyle])) {
- return self::$errorStyles[$errorStyle];
- }
-
- return null;
+ return self::$errorStyles[$errorStyle] ?? null;
}
public static function operator(int $operator): ?string
{
- if (isset(self::$operators[$operator])) {
- return self::$operators[$operator];
- }
-
- return null;
+ return self::$operators[$operator] ?? null;
}
/**
diff --git a/src/PhpSpreadsheet/Reader/Xls/ListFunctions.php b/src/PhpSpreadsheet/Reader/Xls/ListFunctions.php
index 2d8778ff25..6477606b06 100644
--- a/src/PhpSpreadsheet/Reader/Xls/ListFunctions.php
+++ b/src/PhpSpreadsheet/Reader/Xls/ListFunctions.php
@@ -44,12 +44,10 @@ protected function listWorksheetNames2(string $filename, Xls $xls): array
}
foreach ($xls->sheets as $sheet) {
- if ($sheet['sheetType'] != 0x00) {
+ if ($sheet['sheetType'] === 0x00) {
// 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
- continue;
+ $worksheetNames[] = $sheet['name'];
}
-
- $worksheetNames[] = $sheet['name'];
}
return $worksheetNames;
@@ -93,7 +91,7 @@ protected function listWorksheetInfo2(string $filename, Xls $xls): array
// Parse the individual sheets
foreach ($xls->sheets as $sheet) {
- if ($sheet['sheetType'] != 0x00) {
+ if ($sheet['sheetType'] !== 0x00) {
// 0x00: Worksheet
// 0x02: Chart
// 0x06: Visual Basic module
diff --git a/src/PhpSpreadsheet/Reader/Xls/Style/Border.php b/src/PhpSpreadsheet/Reader/Xls/Style/Border.php
index 97cebbd44e..96275bf9f9 100644
--- a/src/PhpSpreadsheet/Reader/Xls/Style/Border.php
+++ b/src/PhpSpreadsheet/Reader/Xls/Style/Border.php
@@ -28,10 +28,6 @@ class Border
public static function lookup(int $index): string
{
- if (isset(self::$borderStyleMap[$index])) {
- return self::$borderStyleMap[$index];
- }
-
- return StyleBorder::BORDER_NONE;
+ return self::$borderStyleMap[$index] ?? StyleBorder::BORDER_NONE;
}
}
diff --git a/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php b/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php
index 4e379509e5..93e3f44979 100644
--- a/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php
+++ b/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php
@@ -37,10 +37,6 @@ class FillPattern
*/
public static function lookup(int $index): string
{
- if (isset(self::$fillPatternMap[$index])) {
- return self::$fillPatternMap[$index];
- }
-
- return Fill::FILL_NONE;
+ return self::$fillPatternMap[$index] ?? Fill::FILL_NONE;
}
}
diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php
index d22f76ff4f..e7cd0b7119 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx.php
@@ -854,6 +854,8 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
}
// Read cell!
+ $useFormula = isset($c->f)
+ && ((string) $c->f !== '' || (isset($c->f->attributes()['t']) && strtolower((string) $c->f->attributes()['t']) === 'shared'));
switch ($cellDataType) {
case DataType::TYPE_STRING:
if ((string) $c->v != '') {
@@ -868,7 +870,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
break;
case DataType::TYPE_BOOL:
- if (!isset($c->f) || ((string) $c->f) === '') {
+ if (!$useFormula) {
if (isset($c->v)) {
$value = self::castToBoolean($c);
} else {
@@ -883,16 +885,16 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
break;
case DataType::TYPE_STRING2:
- if (isset($c->f)) {
+ if ($useFormula) {
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToString');
self::storeFormulaAttributes($c->f, $docSheet, $r);
} else {
- $value = self::castToString($c);
+ $value = self::castToString($c);
}
break;
case DataType::TYPE_INLINE:
- if (isset($c->f)) {
+ if ($useFormula) {
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError');
self::storeFormulaAttributes($c->f, $docSheet, $r);
} else {
@@ -901,7 +903,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
break;
case DataType::TYPE_ERROR:
- if (!isset($c->f)) {
+ if (!$useFormula) {
$value = self::castToError($c);
} else {
// Formula
@@ -916,7 +918,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
break;
default:
- if (!isset($c->f)) {
+ if (!$useFormula) {
$value = self::castToString($c);
if (is_numeric($value)) {
$value += 0;
diff --git a/src/PhpSpreadsheet/Shared/StringHelper.php b/src/PhpSpreadsheet/Shared/StringHelper.php
index aac3836ce4..a16b1cafc0 100644
--- a/src/PhpSpreadsheet/Shared/StringHelper.php
+++ b/src/PhpSpreadsheet/Shared/StringHelper.php
@@ -19,17 +19,17 @@ class StringHelper
/**
* Decimal separator.
*/
- private static ?string $decimalSeparator;
+ private static ?string $decimalSeparator = null;
/**
* Thousands separator.
*/
- private static ?string $thousandsSeparator;
+ private static ?string $thousandsSeparator = null;
/**
* Currency code.
*/
- private static ?string $currencyCode;
+ private static ?string $currencyCode = null;
/**
* Is iconv extension avalable?
@@ -511,6 +511,23 @@ public static function strCaseReverse(string $textValue): string
return implode('', $characters);
}
+ private static function useAlt(string $altValue, string $default, bool $trimAlt): string
+ {
+ return ($trimAlt ? trim($altValue) : $altValue) ?: $default;
+ }
+
+ private static function getLocaleValue(string $key, string $altKey, string $default, bool $trimAlt = false): string
+ {
+ $localeconv = localeconv();
+ $rslt = $localeconv[$key];
+ // win-1252 implements Euro as 0x80 plus other symbols
+ if (preg_match('//u', $rslt) !== 1) {
+ $rslt = '';
+ }
+
+ return $rslt ?: self::useAlt($localeconv[$altKey], $default, $trimAlt);
+ }
+
/**
* Get the decimal separator. If it has not yet been set explicitly, try to obtain number
* formatting information from locale.
@@ -518,14 +535,7 @@ public static function strCaseReverse(string $textValue): string
public static function getDecimalSeparator(): string
{
if (!isset(self::$decimalSeparator)) {
- $localeconv = localeconv();
- self::$decimalSeparator = ($localeconv['decimal_point'] != '')
- ? $localeconv['decimal_point'] : $localeconv['mon_decimal_point'];
-
- if (self::$decimalSeparator == '') {
- // Default to .
- self::$decimalSeparator = '.';
- }
+ self::$decimalSeparator = self::getLocaleValue('decimal_point', 'mon_decimal_point', '.');
}
return self::$decimalSeparator;
@@ -549,14 +559,7 @@ public static function setDecimalSeparator(?string $separator): void
public static function getThousandsSeparator(): string
{
if (!isset(self::$thousandsSeparator)) {
- $localeconv = localeconv();
- self::$thousandsSeparator = ($localeconv['thousands_sep'] != '')
- ? $localeconv['thousands_sep'] : $localeconv['mon_thousands_sep'];
-
- if (self::$thousandsSeparator == '') {
- // Default to .
- self::$thousandsSeparator = ',';
- }
+ self::$thousandsSeparator = self::getLocaleValue('thousands_sep', 'mon_thousands_sep', ',');
}
return self::$thousandsSeparator;
@@ -577,22 +580,10 @@ public static function setThousandsSeparator(?string $separator): void
* Get the currency code. If it has not yet been set explicitly, try to obtain the
* symbol information from locale.
*/
- public static function getCurrencyCode(): string
+ public static function getCurrencyCode(bool $trimAlt = false): string
{
- if (!empty(self::$currencyCode)) {
- return self::$currencyCode;
- }
- self::$currencyCode = '$';
- $localeconv = localeconv();
- if (!empty($localeconv['currency_symbol'])) {
- self::$currencyCode = $localeconv['currency_symbol'];
-
- return self::$currencyCode;
- }
- if (!empty($localeconv['int_curr_symbol'])) {
- self::$currencyCode = $localeconv['int_curr_symbol'];
-
- return self::$currencyCode;
+ if (!isset(self::$currencyCode)) {
+ self::$currencyCode = self::getLocaleValue('currency_symbol', 'int_curr_symbol', '$', $trimAlt);
}
return self::$currencyCode;
diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php
index 943db95cc3..f40a8889ac 100644
--- a/src/PhpSpreadsheet/Spreadsheet.php
+++ b/src/PhpSpreadsheet/Spreadsheet.php
@@ -514,7 +514,7 @@ public function getActiveSheet(): Worksheet
public function createSheet(?int $sheetIndex = null): Worksheet
{
$newSheet = new Worksheet($this);
- $this->addSheet($newSheet, $sheetIndex);
+ $this->addSheet($newSheet, $sheetIndex, true);
return $newSheet;
}
@@ -535,8 +535,20 @@ public function sheetNameExists(string $worksheetName): bool
* @param Worksheet $worksheet The worksheet to add
* @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
*/
- public function addSheet(Worksheet $worksheet, ?int $sheetIndex = null): Worksheet
+ public function addSheet(Worksheet $worksheet, ?int $sheetIndex = null, bool $retitleIfNeeded = false): Worksheet
{
+ if ($retitleIfNeeded) {
+ $title = $worksheet->getTitle();
+ if ($this->sheetNameExists($title)) {
+ $i = 1;
+ $newTitle = "$title $i";
+ while ($this->sheetNameExists($newTitle)) {
+ ++$i;
+ $newTitle = "$title $i";
+ }
+ $worksheet->setTitle($newTitle);
+ }
+ }
if ($this->sheetNameExists($worksheet->getTitle())) {
throw new Exception(
"Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename this worksheet first."
diff --git a/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php b/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
index cb15e8f375..c9032a44d1 100644
--- a/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
+++ b/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
@@ -112,11 +112,11 @@ private static function splitFormatForSectionSelection(array $sections, mixed $v
* @param null|array|bool|float|int|RichText|string $value Value to format
* @param string $format Format code: see = self::FORMAT_* for predefined values;
* or can be any valid MS Excel custom format string
- * @param ?array $callBack Callback function for additional formatting of string
+ * @param null|array|callable $callBack Callback function for additional formatting of string
*
* @return string Formatted string
*/
- public static function toFormattedString($value, string $format, ?array $callBack = null): string
+ public static function toFormattedString($value, string $format, null|array|callable $callBack = null): string
{
while (is_array($value)) {
$value = array_shift($value);
@@ -200,9 +200,8 @@ public static function toFormattedString($value, string $format, ?array $callBac
}
// Additional formatting provided by callback function
- if ($callBack !== null) {
- [$writerInstance, $function] = $callBack;
- $value = $writerInstance->$function($value, $colors);
+ if (is_callable($callBack)) {
+ $value = $callBack($value, $colors);
}
return str_replace(chr(0x00), '.', $value);
diff --git a/src/PhpSpreadsheet/Worksheet/AutoFilter.php b/src/PhpSpreadsheet/Worksheet/AutoFilter.php
index 2c4a469559..4b88fcadc9 100644
--- a/src/PhpSpreadsheet/Worksheet/AutoFilter.php
+++ b/src/PhpSpreadsheet/Worksheet/AutoFilter.php
@@ -50,7 +50,7 @@ public function setEvaluated(bool $value): void
/**
* Create a new AutoFilter.
*
- * @param AddressRange
|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
+ * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
* A simple string containing a Cell range like 'A1:E10' is permitted
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
* or an AddressRange object.
diff --git a/src/PhpSpreadsheet/Worksheet/Table.php b/src/PhpSpreadsheet/Worksheet/Table.php
index 234014a0ff..a7a62a954c 100644
--- a/src/PhpSpreadsheet/Worksheet/Table.php
+++ b/src/PhpSpreadsheet/Worksheet/Table.php
@@ -64,7 +64,7 @@ class Table implements Stringable
/**
* Create a new Table.
*
- * @param AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
+ * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
* A simple string containing a Cell range like 'A1:E10' is permitted
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
* or an AddressRange object.
@@ -268,7 +268,7 @@ public function getRange(): string
/**
* Set Table Cell Range.
*
- * @param AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
+ * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
* A simple string containing a Cell range like 'A1:E10' is permitted
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
* or an AddressRange object.
diff --git a/src/PhpSpreadsheet/Worksheet/Validations.php b/src/PhpSpreadsheet/Worksheet/Validations.php
index 99b5eedbc5..ec78c22b0b 100644
--- a/src/PhpSpreadsheet/Worksheet/Validations.php
+++ b/src/PhpSpreadsheet/Worksheet/Validations.php
@@ -36,7 +36,7 @@ public static function validateCellAddress(null|CellAddress|string|array $cellAd
/**
* Validate a cell address or cell range.
*
- * @param AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
+ * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
* or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]),
* or as a CellAddress or AddressRange object.
*/
@@ -59,7 +59,7 @@ public static function validateCellOrCellRange(AddressRange|CellAddress|int|stri
/**
* Validate a cell range.
*
- * @param AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
+ * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
* or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]),
* or as an AddressRange object.
*/
diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php
index 5943e5a993..6d30ab0f52 100644
--- a/src/PhpSpreadsheet/Worksheet/Worksheet.php
+++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php
@@ -323,6 +323,7 @@ public function __construct(?Spreadsheet $parent = null, string $title = 'Worksh
{
// Set parent and title
$this->parent = $parent;
+ $this->hash = spl_object_id($this);
$this->setTitle($title, false);
// setTitle can change $pTitle
$this->setCodeName($this->getTitle());
@@ -351,7 +352,6 @@ public function __construct(?Spreadsheet $parent = null, string $title = 'Worksh
$this->autoFilter = new AutoFilter('', $this);
// Table collection
$this->tableCollection = new ArrayObject();
- $this->hash = spl_object_id($this);
}
/**
@@ -871,7 +871,7 @@ public function setTitle(string $title, bool $updateFormulaCellReferences = true
// Syntax check
self::checkSheetTitle($title);
- if ($this->parent) {
+ if ($this->parent && $this->parent->getIndex($this, true) >= 0) {
// Is there already such sheet name?
if ($this->parent->sheetNameExists($title)) {
// Use name, but append with lowest possible integer
@@ -901,7 +901,7 @@ public function setTitle(string $title, bool $updateFormulaCellReferences = true
// Set title
$this->title = $title;
- if ($this->parent && $this->parent->getCalculationEngine()) {
+ if ($this->parent && $this->parent->getIndex($this, true) >= 0 && $this->parent->getCalculationEngine()) {
// New title
$newTitle = $this->getTitle();
$this->parent->getCalculationEngine()
@@ -1391,7 +1391,7 @@ public function getStyles(): array
/**
* Get style for cell.
*
- * @param AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $cellCoordinate
+ * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $cellCoordinate
* A simple string containing a cell address like 'A1' or a cell range like 'A1:E10'
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
* or a CellAddress or AddressRange object.
@@ -1695,7 +1695,7 @@ public function getColumnBreaks(): array
/**
* Set merge on a cell range.
*
- * @param AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range A simple string containing a Cell range like 'A1:E10'
+ * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range A simple string containing a Cell range like 'A1:E10'
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
* or an AddressRange.
* @param string $behaviour How the merged cells should behave.
@@ -1820,7 +1820,7 @@ public function mergeCellBehaviour(Cell $cell, string $upperLeft, string $behavi
/**
* Remove merge on a cell range.
*
- * @param AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range A simple string containing a Cell range like 'A1:E10'
+ * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range A simple string containing a Cell range like 'A1:E10'
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
* or an AddressRange.
*
@@ -1871,7 +1871,7 @@ public function setMergeCells(array $mergeCells): static
/**
* Set protection on a cell or cell range.
*
- * @param AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10'
+ * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10'
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
* or a CellAddress or AddressRange object.
* @param string $password Password to unlock the protection
@@ -1894,7 +1894,7 @@ public function protectCells(AddressRange|CellAddress|int|string|array $range, s
/**
* Remove protection on a cell or cell range.
*
- * @param AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10'
+ * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10'
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
* or a CellAddress or AddressRange object.
*
@@ -1952,7 +1952,7 @@ public function getAutoFilter(): AutoFilter
/**
* Set AutoFilter.
*
- * @param AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|AutoFilter|string $autoFilterOrRange
+ * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|AutoFilter|string $autoFilterOrRange
* A simple string containing a Cell range like 'A1:E10' is permitted for backward compatibility
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
* or an AddressRange.
@@ -2698,7 +2698,7 @@ public function setSelectedCell(string $coordinate): static
/**
* Select a range of cells.
*
- * @param AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $coordinate A simple string containing a Cell range like 'A1:E10'
+ * @param AddressRange|AddressRange|AddressRange|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $coordinate A simple string containing a Cell range like 'A1:E10'
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
* or a CellAddress or AddressRange object.
*
diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php
index df1353ea08..d70a067f6f 100644
--- a/src/PhpSpreadsheet/Writer/Html.php
+++ b/src/PhpSpreadsheet/Writer/Html.php
@@ -403,12 +403,12 @@ public function generateHTMLHeader(bool $includeStyles = false): string
} else {
$propertyValue = (string) $propertyValue;
}
- $html .= self::generateMeta($propertyValue, "custom.$propertyQualifier.$customProperty");
+ $html .= self::generateMeta($propertyValue, htmlspecialchars("custom.$propertyQualifier.$customProperty"));
}
}
if (!empty($properties->getHyperlinkBase())) {
- $html .= ' ' . PHP_EOL;
+ $html .= ' ' . PHP_EOL;
}
$html .= $includeStyles ? $this->generateStyles(true) : $this->generatePageDeclarations(true);
@@ -1586,8 +1586,9 @@ private function generateRow(Worksheet $worksheet, array $values, int $row, stri
// Hyperlink?
if ($worksheet->hyperlinkExists($coordinate) && !$worksheet->getHyperlink($coordinate)->isInternal()) {
$url = $worksheet->getHyperlink($coordinate)->getUrl();
- $urldecode = strtolower(html_entity_decode(trim($url), encoding: 'UTF-8'));
- $parseScheme = preg_match('/^(\\w+):/', $urldecode, $matches);
+ $urlDecode1 = html_entity_decode($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
+ $urlTrim = preg_replace('/^\\s+/u', '', $urlDecode1) ?? $urlDecode1;
+ $parseScheme = preg_match('/^([\\w\\s]+):/u', strtolower($urlTrim), $matches);
if ($parseScheme === 1 && !in_array($matches[1], ['http', 'https', 'file', 'ftp', 's3'], true)) {
$cellData = htmlspecialchars($url, Settings::htmlEntityFlags());
} else {
@@ -1720,6 +1721,17 @@ public function setUseInlineCss(bool $useInlineCss): static
* @param string $format Format code
*/
public function formatColor(string $value, string $format): string
+ {
+ return self::formatColorStatic($value, $format);
+ }
+
+ /**
+ * Add color to formatted string as inline style.
+ *
+ * @param string $value Plain formatted value without color
+ * @param string $format Format code
+ */
+ public static function formatColorStatic(string $value, string $format): string
{
// Color information, e.g. [Red] is always at the beginning
$color = null; // initialize
diff --git a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php
index 7573230689..92fc497e77 100644
--- a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php
+++ b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php
@@ -20,6 +20,7 @@ class Style
public const COLUMN_STYLE_PREFIX = 'co';
public const ROW_STYLE_PREFIX = 'ro';
public const TABLE_STYLE_PREFIX = 'ta';
+ public const INDENT_TO_INCHES = 0.1043; // undocumented, used trial and error
private XMLWriter $writer;
@@ -28,12 +29,13 @@ public function __construct(XMLWriter $writer)
$this->writer = $writer;
}
- private function mapHorizontalAlignment(string $horizontalAlignment): string
+ private function mapHorizontalAlignment(?string $horizontalAlignment): string
{
return match ($horizontalAlignment) {
Alignment::HORIZONTAL_CENTER, Alignment::HORIZONTAL_CENTER_CONTINUOUS, Alignment::HORIZONTAL_DISTRIBUTED => 'center',
Alignment::HORIZONTAL_RIGHT => 'end',
Alignment::HORIZONTAL_FILL, Alignment::HORIZONTAL_JUSTIFY => 'justify',
+ Alignment::HORIZONTAL_GENERAL, '', null => '',
default => 'start',
};
}
@@ -145,8 +147,10 @@ private function writeCellProperties(CellStyle $style): void
{
// Align
$hAlign = $style->getAlignment()->getHorizontal();
+ $hAlign = $this->mapHorizontalAlignment($hAlign);
$vAlign = $style->getAlignment()->getVertical();
$wrap = $style->getAlignment()->getWrapText();
+ $indent = $style->getAlignment()->getIndent();
$this->writer->startElement('style:table-cell-properties');
if (!empty($vAlign) || $wrap) {
@@ -168,10 +172,16 @@ private function writeCellProperties(CellStyle $style): void
$this->writer->endElement();
- if (!empty($hAlign)) {
- $hAlign = $this->mapHorizontalAlignment($hAlign);
- $this->writer->startElement('style:paragraph-properties');
- $this->writer->writeAttribute('fo:text-align', $hAlign);
+ if ($hAlign !== '' || !empty($indent)) {
+ $this->writer
+ ->startElement('style:paragraph-properties');
+ if ($hAlign !== '') {
+ $this->writer->writeAttribute('fo:text-align', $hAlign);
+ }
+ if (!empty($indent)) {
+ $indentString = sprintf('%.4f', $indent * self::INDENT_TO_INCHES) . 'in';
+ $this->writer->writeAttribute('fo:margin-left', $indentString);
+ }
$this->writer->endElement();
}
}
@@ -289,6 +299,7 @@ public function writeTableStyle(Worksheet $worksheet, int $sheetId): void
'style:name',
sprintf('%s%d', self::TABLE_STYLE_PREFIX, $sheetId)
);
+ $this->writer->writeAttribute('style:master-page-name', 'Default');
$this->writer->startElement('style:table-properties');
diff --git a/src/PhpSpreadsheet/Writer/Ods/Content.php b/src/PhpSpreadsheet/Writer/Ods/Content.php
index 91ac5d1d21..3b9cc82458 100644
--- a/src/PhpSpreadsheet/Writer/Ods/Content.php
+++ b/src/PhpSpreadsheet/Writer/Ods/Content.php
@@ -20,9 +20,6 @@
*/
class Content extends WriterPart
{
- const NUMBER_COLS_REPEATED_MAX = 1024;
- const NUMBER_ROWS_REPEATED_MAX = 1048576;
-
private Formula $formulaConvertor;
/**
@@ -142,7 +139,6 @@ private function writeSheets(XMLWriter $objWriter): void
sprintf('%s_%d_%d', Style::COLUMN_STYLE_PREFIX, $sheetIndex, $columnDimension->getColumnNumeric())
);
$objWriter->writeAttribute('table:default-cell-style-name', 'ce0');
-// $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
$objWriter->endElement();
}
$this->writeRows($objWriter, $spreadsheet->getSheet($sheetIndex), $sheetIndex);
@@ -155,34 +151,33 @@ private function writeSheets(XMLWriter $objWriter): void
*/
private function writeRows(XMLWriter $objWriter, Worksheet $sheet, int $sheetIndex): void
{
- $numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX;
- $span_row = 0;
+ $spanRow = 0;
$rows = $sheet->getRowIterator();
foreach ($rows as $row) {
- $cellIterator = $row->getCellIterator();
- --$numberRowsRepeated;
- if ($cellIterator->valid()) {
- $objWriter->startElement('table:table-row');
- if ($span_row) {
- if ($span_row > 1) {
- $objWriter->writeAttribute('table:number-rows-repeated', (string) $span_row);
- }
- $objWriter->startElement('table:table-cell');
- $objWriter->writeAttribute('table:number-columns-repeated', (string) self::NUMBER_COLS_REPEATED_MAX);
+ $cellIterator = $row->getCellIterator(iterateOnlyExistingCells: true);
+ $cellIterator->rewind();
+ $rowStyleExists = $sheet->rowDimensionExists($row->getRowIndex()) && $sheet->getRowDimension($row->getRowIndex())->getRowHeight() > 0;
+ if ($cellIterator->valid() || $rowStyleExists) {
+ if ($spanRow) {
+ $objWriter->startElement('table:table-row');
+ $objWriter->writeAttribute(
+ 'table:number-rows-repeated',
+ (string) $spanRow
+ );
$objWriter->endElement();
- $span_row = 0;
- } else {
- if ($sheet->rowDimensionExists($row->getRowIndex()) && $sheet->getRowDimension($row->getRowIndex())->getRowHeight() > 0) {
- $objWriter->writeAttribute(
- 'table:style-name',
- sprintf('%s_%d_%d', Style::ROW_STYLE_PREFIX, $sheetIndex, $row->getRowIndex())
- );
- }
- $this->writeCells($objWriter, $cellIterator);
+ $spanRow = 0;
+ }
+ $objWriter->startElement('table:table-row');
+ if ($rowStyleExists) {
+ $objWriter->writeAttribute(
+ 'table:style-name',
+ sprintf('%s_%d_%d', Style::ROW_STYLE_PREFIX, $sheetIndex, $row->getRowIndex())
+ );
}
+ $this->writeCells($objWriter, $cellIterator);
$objWriter->endElement();
} else {
- ++$span_row;
+ ++$spanRow;
}
}
}
@@ -192,7 +187,6 @@ private function writeRows(XMLWriter $objWriter, Worksheet $sheet, int $sheetInd
*/
private function writeCells(XMLWriter $objWriter, RowCellIterator $cells): void
{
- $numberColsRepeated = self::NUMBER_COLS_REPEATED_MAX;
$prevColumn = -1;
foreach ($cells as $cell) {
/** @var Cell $cell */
@@ -293,17 +287,6 @@ private function writeCells(XMLWriter $objWriter, RowCellIterator $cells): void
$objWriter->endElement();
$prevColumn = $column;
}
-
- $numberColsRepeated = $numberColsRepeated - $prevColumn - 1;
- if ($numberColsRepeated > 0) {
- if ($numberColsRepeated > 1) {
- $objWriter->startElement('table:table-cell');
- $objWriter->writeAttribute('table:number-columns-repeated', (string) $numberColsRepeated);
- $objWriter->endElement();
- } else {
- $objWriter->writeElement('table:table-cell');
- }
- }
}
/**
diff --git a/src/PhpSpreadsheet/Writer/Ods/Styles.php b/src/PhpSpreadsheet/Writer/Ods/Styles.php
index 448b1eff13..6bfdfb1b33 100644
--- a/src/PhpSpreadsheet/Writer/Ods/Styles.php
+++ b/src/PhpSpreadsheet/Writer/Ods/Styles.php
@@ -56,8 +56,17 @@ public function write(): string
$objWriter->writeElement('office:font-face-decls');
$objWriter->writeElement('office:styles');
- $objWriter->writeElement('office:automatic-styles');
- $objWriter->writeElement('office:master-styles');
+ $objWriter->startElement('office:automatic-styles');
+ $objWriter->startElement('style:page-layout');
+ $objWriter->writeAttribute('style:name', 'Mpm1');
+ $objWriter->endElement(); // style:page-layout
+ $objWriter->endElement(); // office:automatic-styles
+ $objWriter->startElement('office:master-styles');
+ $objWriter->startElement('style:master-page');
+ $objWriter->writeAttribute('style:name', 'Default');
+ $objWriter->writeAttribute('style:page-layout-name', 'Mpm1');
+ $objWriter->endElement(); //style:master-page
+ $objWriter->endElement(); //office:master-styles
$objWriter->endElement();
return $objWriter->getData();
diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php
index a9d06db581..b74d810502 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx.php
@@ -140,6 +140,8 @@ class Xlsx extends BaseWriter
private bool $useDynamicArray = false;
+ private ?bool $forceFullCalc = null;
+
/**
* Create a new Xlsx Writer.
*/
@@ -342,7 +344,7 @@ public function save($filename, int $flags = 0): void
$zipContent['xl/styles.xml'] = $this->getWriterPartStyle()->writeStyles($this->spreadSheet);
// Add workbook to ZIP file
- $zipContent['xl/workbook.xml'] = $this->getWriterPartWorkbook()->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas);
+ $zipContent['xl/workbook.xml'] = $this->getWriterPartWorkbook()->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas, $this->forceFullCalc);
$chartCount = 0;
// Add worksheets
@@ -747,4 +749,20 @@ private function determineUseDynamicArrays(): void
{
$this->useDynamicArray = $this->preCalculateFormulas && Calculation::getInstance($this->spreadSheet)->getInstanceArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY && !$this->useCSEArrays;
}
+
+ /**
+ * If this is set when a spreadsheet is opened,
+ * values may not be automatically re-calculated,
+ * and a button will be available to force re-calculation.
+ * This may apply to all spreadsheets open at that time.
+ * If null, this will be set to the opposite of $preCalculateFormulas.
+ * It is likely that false is the desired setting, although
+ * cases have been reported where true is required (issue #456).
+ */
+ public function setForceFullCalc(?bool $forceFullCalc): self
+ {
+ $this->forceFullCalc = $forceFullCalc;
+
+ return $this;
+ }
}
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php b/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php
index 24a2eeb064..c907e100b7 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php
@@ -15,10 +15,11 @@ class Workbook extends WriterPart
* Write workbook to XML format.
*
* @param bool $preCalculateFormulas If true, formulas will be calculated before writing
+ * @param ?bool $forceFullCalc If null, !$preCalculateFormulas
*
* @return string XML Output
*/
- public function writeWorkbook(Spreadsheet $spreadsheet, bool $preCalculateFormulas = false): string
+ public function writeWorkbook(Spreadsheet $spreadsheet, bool $preCalculateFormulas = false, ?bool $forceFullCalc = null): string
{
// Create XML writer
if ($this->getParentWriter()->getUseDiskCaching()) {
@@ -57,7 +58,7 @@ public function writeWorkbook(Spreadsheet $spreadsheet, bool $preCalculateFormul
(new DefinedNamesWriter($objWriter, $spreadsheet))->write();
// calcPr
- $this->writeCalcPr($objWriter, $preCalculateFormulas);
+ $this->writeCalcPr($objWriter, $preCalculateFormulas, $forceFullCalc);
$objWriter->endElement();
@@ -148,7 +149,7 @@ private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spre
*
* @param bool $preCalculateFormulas If true, formulas will be calculated before writing
*/
- private function writeCalcPr(XMLWriter $objWriter, bool $preCalculateFormulas = true): void
+ private function writeCalcPr(XMLWriter $objWriter, bool $preCalculateFormulas, ?bool $forceFullCalc): void
{
$objWriter->startElement('calcPr');
@@ -160,7 +161,11 @@ private function writeCalcPr(XMLWriter $objWriter, bool $preCalculateFormulas =
// fullCalcOnLoad isn't needed if we will calculate before writing
$objWriter->writeAttribute('calcCompleted', ($preCalculateFormulas) ? '1' : '0');
$objWriter->writeAttribute('fullCalcOnLoad', ($preCalculateFormulas) ? '0' : '1');
- $objWriter->writeAttribute('forceFullCalc', ($preCalculateFormulas) ? '0' : '1');
+ if ($forceFullCalc === null) {
+ $objWriter->writeAttribute('forceFullCalc', $preCalculateFormulas ? '0' : '1');
+ } else {
+ $objWriter->writeAttribute('forceFullCalc', $forceFullCalc ? '1' : '0');
+ }
$objWriter->endElement();
}
diff --git a/src/PhpSpreadsheet/Writer/ZipStream2.php b/src/PhpSpreadsheet/Writer/ZipStream2.php
index ed21a7e399..e03e1d0cf9 100644
--- a/src/PhpSpreadsheet/Writer/ZipStream2.php
+++ b/src/PhpSpreadsheet/Writer/ZipStream2.php
@@ -5,6 +5,12 @@
use ZipStream\Option\Archive;
use ZipStream\ZipStream;
+/**
+ * Either ZipStream2 or ZipStream3, but not both, may be used.
+ * For code coverage testing, it will always be ZipStream3.
+ *
+ * @codeCoverageIgnore
+ */
class ZipStream2
{
/**
diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AllSetupTeardown.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AllSetupTeardown.php
index da8d350e4a..0f177c81f4 100644
--- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AllSetupTeardown.php
+++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AllSetupTeardown.php
@@ -4,6 +4,7 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
@@ -15,6 +16,8 @@ class AllSetupTeardown extends TestCase
{
protected string $compatibilityMode;
+ protected string $arrayReturnType;
+
private ?Spreadsheet $spreadsheet = null;
private ?Worksheet $sheet = null;
@@ -22,11 +25,13 @@ class AllSetupTeardown extends TestCase
protected function setUp(): void
{
$this->compatibilityMode = Functions::getCompatibilityMode();
+ $this->arrayReturnType = Calculation::getArrayReturnType();
}
protected function tearDown(): void
{
Functions::setCompatibilityMode($this->compatibilityMode);
+ Calculation::setArrayReturnType($this->arrayReturnType);
$this->sheet = null;
if ($this->spreadsheet !== null) {
$this->spreadsheet->disconnectWorksheets();
diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ChooseColsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ChooseColsTest.php
new file mode 100644
index 0000000000..26c8eb45fb
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ChooseColsTest.php
@@ -0,0 +1,54 @@
+mightHaveException($expectedResult);
+ $sheet = $this->getSheet();
+ $sheet->fromArray(
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ['g', 'h', 'i'],
+ ['j', 'k', 'l'],
+ ['m', 'n', 'o'],
+ ['p', 'q', 'r'],
+ ['s', 't', 'u'],
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ null,
+ 'B3',
+ true
+ );
+ $this->getSpreadsheet()->addNamedRange(
+ new NamedRange(
+ 'definedname',
+ $sheet,
+ '$B$3:$D$11'
+ )
+ );
+
+ $sheet->setCellValue('F3', $formula);
+ $result = $sheet->getCell('F3')->getCalculatedValue();
+ self::assertSame($expectedResult, $result);
+ }
+
+ public static function providerChooseCols(): array
+ {
+ return require 'tests/data/Calculation/LookupRef/CHOOSECOLS.php';
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ChooseRowsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ChooseRowsTest.php
new file mode 100644
index 0000000000..3766ea203b
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ChooseRowsTest.php
@@ -0,0 +1,54 @@
+mightHaveException($expectedResult);
+ $sheet = $this->getSheet();
+ $sheet->fromArray(
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ['g', 'h', 'i'],
+ ['j', 'k', 'l'],
+ ['m', 'n', 'o'],
+ ['p', 'q', 'r'],
+ ['s', 't', 'u'],
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ null,
+ 'B3',
+ true
+ );
+ $this->getSpreadsheet()->addNamedRange(
+ new NamedRange(
+ 'definedname',
+ $sheet,
+ '$B$3:$D$11'
+ )
+ );
+
+ $sheet->setCellValue('F3', $formula);
+ $result = $sheet->getCell('F3')->getCalculatedValue();
+ self::assertSame($expectedResult, $result);
+ }
+
+ public static function providerChooseRows(): array
+ {
+ return require 'tests/data/Calculation/LookupRef/CHOOSEROWS.php';
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/DropTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/DropTest.php
new file mode 100644
index 0000000000..0a8411464d
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/DropTest.php
@@ -0,0 +1,54 @@
+mightHaveException($expectedResult);
+ $sheet = $this->getSheet();
+ $sheet->fromArray(
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ['g', 'h', 'i'],
+ ['j', 'k', 'l'],
+ ['m', 'n', 'o'],
+ ['p', 'q', 'r'],
+ ['s', 't', 'u'],
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ null,
+ 'B3',
+ true
+ );
+ $this->getSpreadsheet()->addNamedRange(
+ new NamedRange(
+ 'definedname',
+ $sheet,
+ '$B$3:$D$11'
+ )
+ );
+
+ $sheet->setCellValue('F3', $formula);
+ $result = $sheet->getCell('F3')->getCalculatedValue();
+ self::assertSame($expectedResult, $result);
+ }
+
+ public static function providerDrop(): array
+ {
+ return require 'tests/data/Calculation/LookupRef/DROP.php';
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ExpandTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ExpandTest.php
new file mode 100644
index 0000000000..b68c64005b
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ExpandTest.php
@@ -0,0 +1,47 @@
+mightHaveException($expectedResult);
+ $sheet = $this->getSheet();
+ $sheet->fromArray(
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ],
+ null,
+ 'B3',
+ true
+ );
+ $this->getSpreadsheet()->addNamedRange(
+ new NamedRange(
+ 'definedname',
+ $sheet,
+ '$B$3:$D$11'
+ )
+ );
+
+ $sheet->setCellValue('F3', $formula);
+ $result = $sheet->getCell('F3')->getCalculatedValue();
+ self::assertSame($expectedResult, $result);
+ }
+
+ public static function providerExpand(): array
+ {
+ return require 'tests/data/Calculation/LookupRef/EXPAND.php';
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/TakeTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/TakeTest.php
new file mode 100644
index 0000000000..d03187ecf0
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/TakeTest.php
@@ -0,0 +1,54 @@
+mightHaveException($expectedResult);
+ $sheet = $this->getSheet();
+ $sheet->fromArray(
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ['g', 'h', 'i'],
+ ['j', 'k', 'l'],
+ ['m', 'n', 'o'],
+ ['p', 'q', 'r'],
+ ['s', 't', 'u'],
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ null,
+ 'B3',
+ true
+ );
+ $this->getSpreadsheet()->addNamedRange(
+ new NamedRange(
+ 'definedname',
+ $sheet,
+ '$B$3:$D$11'
+ )
+ );
+
+ $sheet->setCellValue('F3', $formula);
+ $result = $sheet->getCell('F3')->getCalculatedValue();
+ self::assertSame($expectedResult, $result);
+ }
+
+ public static function providerTake(): array
+ {
+ return require 'tests/data/Calculation/LookupRef/TAKE.php';
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Cell/ColumnRangeTest.php b/tests/PhpSpreadsheetTests/Cell/ColumnRangeTest.php
index 10da1dd21c..f15352aee9 100644
--- a/tests/PhpSpreadsheetTests/Cell/ColumnRangeTest.php
+++ b/tests/PhpSpreadsheetTests/Cell/ColumnRangeTest.php
@@ -43,6 +43,7 @@ public function testCreateColumnRangeWithWorksheet(): void
self::assertSame('E', $columnRange->to());
self::assertSame("'Mark''s Worksheet'!C:E", (string) $columnRange);
self::assertSame("'Mark''s Worksheet'!C1:E1048576", (string) $columnRange->toCellRange());
+ $spreadsheet->disconnectWorksheets();
}
public function testCreateColumnRangeFromArray(): void
@@ -88,4 +89,23 @@ public function testColumnRangePrevious(): void
// Check that original Column Range isn't changed
self::assertSame('C:E', (string) $columnRange);
}
+
+ public function testIssue4309(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $columnRange = new ColumnRange('A', 'A');
+ $columnStyle = $sheet->getStyle($columnRange);
+ $columnStyle->applyFromArray([
+ 'font' => ['bold' => true],
+ ]);
+ $columnXf = $sheet->getColumnDimension('A')->getXfIndex();
+ self::assertNotNull($columnXf);
+ self::assertTrue(
+ $spreadsheet->getCellXfByIndex($columnXf)
+ ->getFont()->getBold()
+ );
+
+ $spreadsheet->disconnectWorksheets();
+ }
}
diff --git a/tests/PhpSpreadsheetTests/Cell/RowRangeTest.php b/tests/PhpSpreadsheetTests/Cell/RowRangeTest.php
index 52fd357f51..005f1af6b2 100644
--- a/tests/PhpSpreadsheetTests/Cell/RowRangeTest.php
+++ b/tests/PhpSpreadsheetTests/Cell/RowRangeTest.php
@@ -39,6 +39,7 @@ public function testCreateRowRangeWithWorksheet(): void
self::assertSame(3, $rowRange->from());
self::assertSame(5, $rowRange->to());
self::assertSame("'Mark''s Worksheet'!3:5", (string) $rowRange);
+ $spreadsheet->disconnectWorksheets();
}
public function testCreateRowRangeFromArray(): void
@@ -74,4 +75,20 @@ public function testRowRangePrevious(): void
// Check that original Row Range isn't changed
self::assertSame('3:5', (string) $rowRange);
}
+
+ public function testIssue4309(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $rowRange = new RowRange(1, 1);
+ $rowStyle = $sheet->getStyle($rowRange);
+ $rowStyle->applyFromArray([
+ 'font' => ['name' => 'Arial'],
+ ]);
+ $rowXf = $sheet->getRowDimension(1)->getXfIndex();
+ self::assertNotNull($rowXf);
+ self::assertSame('Arial', $spreadsheet->getCellXfByIndex($rowXf)->getFont()->getName());
+
+ $spreadsheet->disconnectWorksheets();
+ }
}
diff --git a/tests/PhpSpreadsheetTests/Chart/ChartsDynamicTitleTest.php b/tests/PhpSpreadsheetTests/Chart/ChartsDynamicTitleTest.php
index 7aa484d9cb..2546d60de6 100644
--- a/tests/PhpSpreadsheetTests/Chart/ChartsDynamicTitleTest.php
+++ b/tests/PhpSpreadsheetTests/Chart/ChartsDynamicTitleTest.php
@@ -34,7 +34,6 @@ public function writeCharts(XlsxWriter $writer): void
$writer->setIncludeCharts(true);
}
- #[\PHPUnit\Framework\Attributes\RunInSeparateProcess]
public function testDynamicTitle(): void
{
// based on samples/templates/issue.3797.2007.xlsx
diff --git a/tests/PhpSpreadsheetTests/Reader/Csv/CsvNumberFormatLocaleTest.php b/tests/PhpSpreadsheetTests/Reader/Csv/CsvNumberFormatLocaleTest.php
index 513576b19b..086d780f53 100644
--- a/tests/PhpSpreadsheetTests/Reader/Csv/CsvNumberFormatLocaleTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Csv/CsvNumberFormatLocaleTest.php
@@ -6,8 +6,12 @@
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Reader\Csv;
+use PHPUnit\Framework\Attributes;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
+// separate processes due to setLocale
+#[Attributes\RunTestsInSeparateProcesses]
class CsvNumberFormatLocaleTest extends TestCase
{
private bool $localeAdjusted;
@@ -44,8 +48,7 @@ protected function tearDown(): void
}
}
- #[\PHPUnit\Framework\Attributes\DataProvider('providerNumberFormatNoConversionTest')]
- #[\PHPUnit\Framework\Attributes\RunInSeparateProcess]
+ #[DataProvider('providerNumberFormatNoConversionTest')]
public function testNumberFormatNoConversion(mixed $expectedValue, string $expectedFormat, string $cellAddress): void
{
if (!$this->localeAdjusted) {
@@ -85,8 +88,7 @@ public static function providerNumberFormatNoConversionTest(): array
];
}
- #[\PHPUnit\Framework\Attributes\DataProvider('providerNumberValueConversionTest')]
- #[\PHPUnit\Framework\Attributes\RunInSeparateProcess]
+ #[DataProvider('providerNumberValueConversionTest')]
public function testNumberValueConversion(mixed $expectedValue, string $cellAddress): void
{
if (!$this->localeAdjusted) {
diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/RepeatEmptyCellsAndRowsTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/RepeatEmptyCellsAndRowsTest.php
new file mode 100644
index 0000000000..b64f8fd1dc
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Ods/RepeatEmptyCellsAndRowsTest.php
@@ -0,0 +1,48 @@
+getActiveSheet();
+ $oldSheet->setCellValue('C1', 'xx');
+ $oldSheet->setCellValue('G1', 'aa');
+ $oldSheet->setCellValue('BB1', 'bb');
+ $oldSheet->setCellValue('A6', 'aaa');
+ $oldSheet->setCellValue('B7', 'bbb');
+ $oldSheet->getRowDimension(10)->setRowHeight(12);
+ $oldSheet->setCellValue('A12', 'this is A12');
+ $style = $oldSheet->getStyle('B14:D14');
+ $style->getFont()->setBold(true);
+ $oldSheet->getCell('E15')->setValue('X');
+ $oldSheet->mergeCells('E15:G16');
+ $oldSheet->getCell('J15')->setValue('j15');
+ $oldSheet->getCell('J16')->setValue('j16');
+ $oldSheet->getCell('A19')->setValue('lastrow');
+ $spreadsheet = $this->writeAndReload($spreadsheetOld, 'Ods');
+ $spreadsheetOld->disconnectWorksheets();
+
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame('xx', $sheet->getCell('C1')->getValue());
+ self::assertSame('aa', $sheet->getCell('G1')->getValue());
+ self::assertSame('bb', $sheet->getCell('BB1')->getValue());
+ self::assertSame('aaa', $sheet->getCell('A6')->getValue());
+ self::assertSame('bbb', $sheet->getCell('B7')->getValue());
+ self::assertSame('this is A12', $sheet->getCell('A12')->getValue());
+ // Read styles, including row height, not yet implemented for ODS
+ self::assertSame('j15', $sheet->getCell('J15')->getValue());
+ self::assertSame('j16', $sheet->getCell('J16')->getValue());
+ self::assertSame(['E15:G16' => 'E15:G16'], $sheet->getMergeCells());
+ self::assertSame('lastrow', $sheet->getCell('A19')->getValue());
+
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/Biff8CoverTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/Biff8CoverTest.php
new file mode 100644
index 0000000000..da9b4f9c98
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xls/Biff8CoverTest.php
@@ -0,0 +1,42 @@
+load($filename);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame('=SUM({1;2;3;4;5})', $sheet->getCell('A1')->getValue());
+ self::assertSame(15, $sheet->getCell('A1')->getCalculatedValue());
+ self::assertSame(
+ '=VLOOKUP("hello",'
+ . '{"what",1;"why",TRUE;"hello","there";"when",FALSE}'
+ . ',2,FALSE)',
+ $sheet->getCell('C1')->getValue()
+ );
+ self::assertSame('there', $sheet->getCell('C1')->getCalculatedValue());
+ self::assertSame(2, $sheet->getCell('A3')->getValue());
+ self::assertTrue(
+ $sheet->getStyle('A3')->getFont()->getSuperscript()
+ );
+ self::assertSame('n', $sheet->getCell('B3')->getValue());
+ self::assertTrue(
+ $sheet->getStyle('B3')->getFont()->getSubscript()
+ );
+
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/SharedFormulaeTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/SharedFormulaeTest.php
new file mode 100644
index 0000000000..b613fb2973
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/SharedFormulaeTest.php
@@ -0,0 +1,37 @@
+load($filename);
+ $sheet = $spreadsheet->getActiveSheet();
+ $expected = [
+ [1, '=A1+1', '=A1>3', '="x"&A1'],
+ [2, '=A2+1', '=A2>3', '="x"&A2'],
+ [3, '=A3+1', '=A3>3', '="x"&A3'],
+ [4, '=A4+1', '=A4>3', '="x"&A4'],
+ [5, '=A5+1', '=A5>3', '="x"&A5'],
+ ];
+ self::assertSame($expected, $sheet->toArray(null, false, false));
+ $expected = [
+ [1, 2, false, 'x1'],
+ [2, 3, false, 'x2'],
+ [3, 4, false, 'x3'],
+ [4, 5, true, 'x4'],
+ [5, 6, true, 'x5'],
+ ];
+ self::assertSame($expected, $sheet->toArray(null, true, false));
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Shared/StringHelperLocaleTest.php b/tests/PhpSpreadsheetTests/Shared/StringHelperLocaleTest.php
new file mode 100644
index 0000000000..c4178f53ab
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Shared/StringHelperLocaleTest.php
@@ -0,0 +1,55 @@
+currentLocale = setlocale(LC_ALL, '0');
+ }
+
+ protected function tearDown(): void
+ {
+ if (is_string($this->currentLocale)) {
+ setlocale(LC_ALL, $this->currentLocale);
+ }
+ StringHelper::setCurrencyCode(null);
+ }
+
+ public function testCurrency(): void
+ {
+ if ($this->currentLocale === false || !setlocale(LC_ALL, 'de_DE.UTF-8', 'deu_deu.utf8')) {
+ self::markTestSkipped('Unable to set German UTF8 locale for testing.');
+ }
+ $result = StringHelper::getCurrencyCode();
+ self::assertSame('€', $result);
+ if (!setlocale(LC_ALL, $this->currentLocale)) {
+ self::markTestSkipped('Unable to restore default locale.');
+ }
+ $result = StringHelper::getCurrencyCode();
+ self::assertSame('€', $result, 'result persists despite locale change');
+ StringHelper::setCurrencyCode(null);
+ $result = StringHelper::getCurrencyCode();
+ self::assertSame('$', $result, 'locale now used');
+ StringHelper::setCurrencyCode(null);
+ if (!setlocale(LC_ALL, 'deu_deu', 'de_DE@euro')) {
+ self::markTestSkipped('Unable to set German single-byte locale for testing.');
+ }
+ $result = StringHelper::getCurrencyCode(true); // trim if alt symbol is used
+ self::assertSame('EUR', $result, 'non-UTF8 result ignored');
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Worksheet/Issue641Test.php b/tests/PhpSpreadsheetTests/Worksheet/Issue641Test.php
new file mode 100644
index 0000000000..bbb6c8bf00
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Worksheet/Issue641Test.php
@@ -0,0 +1,82 @@
+removeSheetByIndex(0);
+ $availableWs = [];
+
+ $worksheet = $xlsx->createSheet();
+ $worksheet->setTitle('Condensed A');
+ $worksheet->getCell('A1')->setValue("=SUM('Detailed A'!A1:A10)");
+ $worksheet->getCell('A2')->setValue(mt_rand(1, 30));
+ $availableWs[] = 'Condensed A';
+
+ $worksheet = $xlsx->createSheet();
+ $worksheet->setTitle('Condensed B');
+ $worksheet->getCell('A1')->setValue("=SUM('Detailed B'!A1:A10)");
+ $worksheet->getCell('A2')->setValue(mt_rand(1, 30));
+ $availableWs[] = 'Condensed B';
+
+ // at this point the value in worksheet 'Condensed B' cell A1 is
+ // =SUM('Detailed B'!A1:A10)
+
+ // worksheet in question is cloned and totals are attached
+ $totalWs1 = clone $xlsx->getSheet($xlsx->getSheetCount() - 1);
+ $totalWs1->setTitle('Condensed Total');
+ $xlsx->addSheet($totalWs1);
+ $formula = '=';
+ foreach ($availableWs as $ws) {
+ $formula .= sprintf("+'%s'!A2", $ws);
+ }
+ $totalWs1->getCell('A1')->setValue("=SUM('Detailed Total'!A1:A10)");
+ $totalWs1->getCell('A2')->setValue($formula);
+
+ $availableWs = [];
+
+ $worksheet = $xlsx->createSheet();
+ $worksheet->setTitle('Detailed A');
+ for ($step = 1; $step <= 10; ++$step) {
+ $worksheet->getCell("A{$step}")->setValue(mt_rand(1, 30));
+ }
+ $availableWs[] = 'Detailed A';
+
+ $worksheet = $xlsx->createSheet();
+ $worksheet->setTitle('Detailed B');
+ for ($step = 1; $step <= 10; ++$step) {
+ $worksheet->getCell("A{$step}")->setValue(mt_rand(1, 30));
+ }
+ $availableWs[] = 'Detailed B';
+
+ $totalWs2 = clone $xlsx->getSheet($xlsx->getSheetCount() - 1);
+ $totalWs2->setTitle('Detailed Total');
+ $xlsx->addSheet($totalWs2);
+
+ for ($step = 1; $step <= 10; ++$step) {
+ $formula = '=';
+ foreach ($availableWs as $ws) {
+ $formula .= sprintf("+'%s'!A%s", $ws, $step);
+ }
+ $totalWs2->getCell("A{$step}")->setValue($formula);
+ }
+
+ self::assertSame("=SUM('Detailed A'!A1:A10)", $xlsx->getSheetByName('Condensed A')?->getCell('A1')?->getValue());
+ self::assertSame("=SUM('Detailed B'!A1:A10)", $xlsx->getSheetByName('Condensed B')?->getCell('A1')?->getValue());
+ self::assertSame("=SUM('Detailed Total'!A1:A10)", $xlsx->getSheetByName('Condensed Total')?->getCell('A1')?->getValue());
+ self::assertSame("=+'Detailed A'!A1+'Detailed B'!A1", $xlsx->getSheetByName('Detailed Total')?->getCell('A1')?->getValue());
+
+ $xlsx->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Html/BadCustomPropertyTest.php b/tests/PhpSpreadsheetTests/Writer/Html/BadCustomPropertyTest.php
new file mode 100644
index 0000000000..a9ef67b791
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Html/BadCustomPropertyTest.php
@@ -0,0 +1,23 @@
+load($infile);
+ $writer = new HtmlWriter($spreadsheet);
+ $html = $writer->generateHtmlAll();
+ self::assertStringContainsString('', $html);
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Html/BadHyperlinkBaseTest.php b/tests/PhpSpreadsheetTests/Writer/Html/BadHyperlinkBaseTest.php
new file mode 100644
index 0000000000..1f12bce570
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Html/BadHyperlinkBaseTest.php
@@ -0,0 +1,23 @@
+load($infile);
+ $writer = new HtmlWriter($spreadsheet);
+ $html = $writer->generateHtmlAll();
+ self::assertStringContainsString('', $html);
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Html/BadHyperlinkTest.php b/tests/PhpSpreadsheetTests/Writer/Html/BadHyperlinkTest.php
new file mode 100644
index 0000000000..669594bb1c
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Html/BadHyperlinkTest.php
@@ -0,0 +1,23 @@
+load($infile);
+ $writer = new HtmlWriter($spreadsheet);
+ $html = $writer->generateHtmlAll();
+ self::assertStringContainsString("jav\tascript:alert() | ", $html);
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php b/tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php
new file mode 100644
index 0000000000..7b6a62baa2
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php
@@ -0,0 +1,62 @@
+compatibilityMode = Functions::getCompatibilityMode();
+ Functions::setCompatibilityMode(
+ Functions::COMPATIBILITY_OPENOFFICE
+ );
+ }
+
+ protected function tearDown(): void
+ {
+ parent::tearDown();
+ Functions::setCompatibilityMode($this->compatibilityMode);
+ }
+
+ public function testWriteSpreadsheet(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $sheet->setCellValue('A1', 'aa');
+ $sheet->setCellValue('B1', 'bb');
+ $sheet->setCellValue('A2', 'cc');
+ $sheet->setCellValue('B2', 'dd');
+ $sheet->getStyle('A1')->getAlignment()->setIndent(2);
+ $writer = new Ods($spreadsheet);
+ $content = new Content($writer);
+ $xml = $content->write();
+ self::assertStringContainsString(
+ ''
+ . ''
+ . ''
+ . '',
+ $xml
+ );
+ self::assertStringContainsString(
+ ''
+ . ''
+ . '' // fo:margin-left is what we're looking for
+ . ''
+ . '',
+ $xml
+ );
+ self::assertSame(3, substr_count($xml, 'table:style-name="ce0"'));
+ self::assertSame(1, substr_count($xml, 'table:style-name="ce1"'));
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue4269Test.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue4269Test.php
new file mode 100644
index 0000000000..ffe9e30ae0
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue4269Test.php
@@ -0,0 +1,65 @@
+outputFile !== '') {
+ unlink($this->outputFile);
+ $this->outputFile = '';
+ }
+ }
+
+ #[DataProvider('validationProvider')]
+ public function testWriteArrayFormulaTextJoin(
+ bool $preCalculateFormulas,
+ ?bool $forceFullCalc,
+ string $expected
+ ): void {
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $sheet->setCellValue('A1', '=A2*2');
+ $sheet->setCellValue('A2', 0);
+
+ $writer = new XlsxWriter($spreadsheet);
+ $writer->setPreCalculateFormulas($preCalculateFormulas);
+ if ($forceFullCalc !== null) {
+ $writer->setForceFullCalc($forceFullCalc);
+ }
+ $this->outputFile = File::temporaryFilename();
+ $writer->save($this->outputFile);
+ $spreadsheet->disconnectWorksheets();
+
+ $file = 'zip://';
+ $file .= $this->outputFile;
+ $file .= '#xl/workbook.xml';
+ $data = file_get_contents($file);
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } else {
+ self::assertStringContainsString($expected, $data);
+ }
+ }
+
+ public static function validationProvider(): array
+ {
+ return [
+ 'normal case' => [true, null, 'calcMode="auto" calcCompleted="1" fullCalcOnLoad="0" forceFullCalc="0"'],
+ 'issue 456' => [false, null, 'calcMode="auto" calcCompleted="0" fullCalcOnLoad="1" forceFullCalc="1"'],
+ 'better choice for no precalc' => [false, false, 'calcMode="auto" calcCompleted="0" fullCalcOnLoad="1" forceFullCalc="0"'],
+ 'unlikely use case' => [true, true, 'calcMode="auto" calcCompleted="1" fullCalcOnLoad="0" forceFullCalc="1"'],
+ ];
+ }
+}
diff --git a/tests/data/Calculation/LookupRef/CHOOSECOLS.php b/tests/data/Calculation/LookupRef/CHOOSECOLS.php
new file mode 100644
index 0000000000..b548382cc9
--- /dev/null
+++ b/tests/data/Calculation/LookupRef/CHOOSECOLS.php
@@ -0,0 +1,180 @@
+ [
+ [
+ ['c'],
+ ['f'],
+ ['i'],
+ ['l'],
+ ['o'],
+ ['r'],
+ ['u'],
+ ['x'],
+ ['#'],
+ ],
+ '=CHOOSECOLS(B3:D11, -1)',
+ ],
+ 'select 1 column' => [
+ [
+ ['b'],
+ ['e'],
+ ['h'],
+ ['k'],
+ ['n'],
+ ['q'],
+ ['t'],
+ ['w'],
+ ['z'],
+ ],
+ '=CHOOSECOLS(B3:D11, 2)',
+ ],
+ 'fractional column' => [
+ [
+ ['b'],
+ ['e'],
+ ['h'],
+ ['k'],
+ ['n'],
+ ['q'],
+ ['t'],
+ ['w'],
+ ['z'],
+ ],
+ '=CHOOSECOLS(B3:D11, 2.8)',
+ ],
+ 'numeric string column' => [
+ [
+ ['b'],
+ ['e'],
+ ['h'],
+ ['k'],
+ ['n'],
+ ['q'],
+ ['t'],
+ ['w'],
+ ['z'],
+ ],
+ '=CHOOSECOLS(B3:D11, " 2.8 ")',
+ ],
+ 'multiple columns including duplicates' => [
+ [
+ ['b', 'a', 'b'],
+ ['e', 'd', 'e'],
+ ['h', 'g', 'h'],
+ ['k', 'j', 'k'],
+ ['n', 'm', 'n'],
+ ['q', 'p', 'q'],
+ ['t', 's', 't'],
+ ['w', 'v', 'w'],
+ ['z', 'y', 'z'],
+ ],
+ '=CHOOSECOLS(B3:D11, 2, 1, 2)',
+ ],
+ 'multiple columns mixed +/- mixed scalar/matrix' => [
+ [
+ ['b', 'a', 'b'],
+ ['e', 'd', 'e'],
+ ['h', 'g', 'h'],
+ ['k', 'j', 'k'],
+ ['n', 'm', 'n'],
+ ['q', 'p', 'q'],
+ ['t', 's', 't'],
+ ['w', 'v', 'w'],
+ ['z', 'y', 'z'],
+ ],
+ '=CHOOSECOLS(B3:D11, 2, {-3, 2})',
+ ],
+ 'reverse Columns' => [
+ [
+ ['c', 'b', 'a'],
+ ['f', 'e', 'd'],
+ ['i', 'h', 'g'],
+ ['l', 'k', 'j'],
+ ['o', 'n', 'm'],
+ ['r', 'q', 'p'],
+ ['u', 't', 's'],
+ ['x', 'w', 'v'],
+ ['#', 'z', 'y'],
+ ],
+ '=CHOOSECOLS(B3:D11, SEQUENCE(COLUMNS(B3:D11),,COLUMNS(B3:D11),-1))',
+ ],
+ 'inline array' => [
+ [
+ ['c', 'b'],
+ ['f', 'e'],
+ ['i', 'h'],
+ ['l', 'k'],
+ ['o', 'n'],
+ ['r', 'q'],
+ ['u', 't'],
+ ['x', 'w'],
+ ['#', 'z'],
+ ],
+ '=CHOOSECOLS(B3:D11, {3;2})',
+ ],
+ 'inline array with negative numbers' => [
+ [
+ ['b', 'c'],
+ ['e', 'f'],
+ ['h', 'i'],
+ ['k', 'l'],
+ ['n', 'o'],
+ ['q', 'r'],
+ ['t', 'u'],
+ ['w', 'x'],
+ ['z', '#'],
+ ],
+ '=CHOOSECOLS(B3:D11, {-2;-1})',
+ ],
+ 'defined name' => [
+ [
+ ['b', 'c'],
+ ['e', 'f'],
+ ['h', 'i'],
+ ['k', 'l'],
+ ['n', 'o'],
+ ['q', 'r'],
+ ['t', 'u'],
+ ['w', 'x'],
+ ['z', '#'],
+ ],
+ '=CHOOSECOLS(definedname, {-2;-1})',
+ ],
+ 'only 1 argument' => [
+ 'exception',
+ '=CHOOSECOLS(B3:D11)',
+ ],
+ 'non-numeric column' => [
+ '#VALUE!',
+ '=CHOOSECOLS(B3:D11, "x")',
+ ],
+ 'positive column too large' => [
+ '#VALUE!',
+ '=CHOOSECOLS(B3:D11, 4)',
+ ],
+ 'negative column too large' => [
+ '#VALUE!',
+ '=CHOOSECOLS(B3:D11, 1, -4)',
+ ],
+ 'zero column' => [
+ '#VALUE!',
+ '=CHOOSECOLS(B3:D11, 0)',
+ ],
+ 'single cell' => [
+ [
+ ['a'],
+ ],
+ '=CHOOSECOLS(B3, 1)',
+ ],
+ 'inline array rather than range' => [
+ [
+ [2, 1, 1],
+ [4, 3, 3],
+ [6, 5, 5],
+ ],
+ '=CHOOSECOLS({1,2;3,4;5,6}, " 2.4 ", 1, 1)',
+ ],
+];
diff --git a/tests/data/Calculation/LookupRef/CHOOSEROWS.php b/tests/data/Calculation/LookupRef/CHOOSEROWS.php
new file mode 100644
index 0000000000..1f0e456268
--- /dev/null
+++ b/tests/data/Calculation/LookupRef/CHOOSEROWS.php
@@ -0,0 +1,115 @@
+ [
+ [
+ ['y', 'z', '#'],
+ ],
+ '=CHOOSEROWS(B3:D11, -1)',
+ ],
+ 'fractional row' => [
+ [
+ ['y', 'z', '#'],
+ ],
+ '=CHOOSEROWS(B3:D11, -1.8)',
+ ],
+ 'numeric string row' => [
+ [
+ ['y', 'z', '#'],
+ ],
+ '=CHOOSEROWS(B3:D11, " -1.8 ")',
+ ],
+ 'select 1 row' => [
+ [
+ ['d', 'e', 'f'],
+ ],
+ '=CHOOSEROWS(B3:D11, 2)',
+ ],
+ 'multiple rows including duplicates' => [
+ [
+ ['d', 'e', 'f'],
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ],
+ '=CHOOSEROWS(B3:D11, 2, 1, 2)',
+ ],
+ 'multiple rows mixed +/- mixed scalar/matrix' => [
+ [
+ ['d', 'e', 'f'],
+ ['s', 't', 'u'],
+ ['d', 'e', 'f'],
+ ],
+ '=CHOOSEROWS(B3:D11, 2, {-3; 2})',
+ ],
+ 'reverse Rows' => [
+ [
+ ['y', 'z', '#'],
+ ['v', 'w', 'x'],
+ ['s', 't', 'u'],
+ ['p', 'q', 'r'],
+ ['m', 'n', 'o'],
+ ['j', 'k', 'l'],
+ ['g', 'h', 'i'],
+ ['d', 'e', 'f'],
+ ['a', 'b', 'c'],
+ ],
+ '=CHOOSEROWS(B3:D11, SEQUENCE(ROWS(B3:D11),,ROWS(B3:D11),-1))',
+ ],
+ 'inline array' => [
+ [
+ ['g', 'h', 'i'],
+ ['d', 'e', 'f'],
+ ],
+ '=CHOOSEROWS(B3:D11, {3;2})',
+ ],
+ 'inline array with negative numbers' => [
+ [
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ '=CHOOSEROWS(B3:D11, {-2;-1})',
+ ],
+ 'named range' => [
+ [
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ '=CHOOSEROWS(definedname, {-2;-1})',
+ ],
+ 'only 1 argument' => [
+ 'exception',
+ '=CHOOSEROWS(B3:D11)',
+ ],
+ 'non-numeric row' => [
+ '#VALUE!',
+ '=CHOOSEROWS(B3:D11, "x")',
+ ],
+ 'positive row too large' => [
+ '#VALUE!',
+ '=CHOOSEROWS(B3:D11, 10)',
+ ],
+ 'negative row too large' => [
+ '#VALUE!',
+ '=CHOOSEROWS(B3:D11, 1, -10)',
+ ],
+ 'zero row' => [
+ '#VALUE!',
+ '=CHOOSEROWS(B3:D11, 0)',
+ ],
+ 'single cell' => [
+ [
+ ['a'],
+ ],
+ '=CHOOSEROWS(B3, 1)',
+ ],
+ 'inline array rather than range' => [
+ [
+ [3, 4],
+ [1, 2],
+ [1, 2],
+ ],
+ '=CHOOSEROWS({1,2;3,4;5,6}, " 2.4 ", 1, 1)',
+ ],
+];
diff --git a/tests/data/Calculation/LookupRef/DROP.php b/tests/data/Calculation/LookupRef/DROP.php
new file mode 100644
index 0000000000..628315772e
--- /dev/null
+++ b/tests/data/Calculation/LookupRef/DROP.php
@@ -0,0 +1,178 @@
+ [
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ['g', 'h', 'i'],
+ ['j', 'k', 'l'],
+ ['m', 'n', 'o'],
+ ['p', 'q', 'r'],
+ ['s', 't', 'u'],
+ ['v', 'w', 'x'],
+ ],
+ '=DROP(B3:D11, -1)',
+ ],
+ 'drop first 2 rows' => [
+ [
+ ['g', 'h', 'i'],
+ ['j', 'k', 'l'],
+ ['m', 'n', 'o'],
+ ['p', 'q', 'r'],
+ ['s', 't', 'u'],
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ '=DROP(B3:D11, 2)',
+ ],
+ 'drop last 2 columns' => [
+ [
+ ['a'],
+ ['d'],
+ ['g'],
+ ['j'],
+ ['m'],
+ ['p'],
+ ['s'],
+ ['v'],
+ ['y'],
+ ],
+ '=DROP(B3:D11, , -2)',
+ ],
+ 'drop first column' => [
+ [
+ ['b', 'c'],
+ ['e', 'f'],
+ ['h', 'i'],
+ ['k', 'l'],
+ ['n', 'o'],
+ ['q', 'r'],
+ ['t', 'u'],
+ ['w', 'x'],
+ ['z', '#'],
+ ],
+ '=DROP(B3:D11, , 1)',
+ ],
+ 'drop last row first column' => [
+ [
+ ['b', 'c'],
+ ['e', 'f'],
+ ['h', 'i'],
+ ['k', 'l'],
+ ['n', 'o'],
+ ['q', 'r'],
+ ['t', 'u'],
+ ['w', 'x'],
+ ],
+ '=DROP(B3:D11, -1, 1)',
+ ],
+ 'drop first 2 rows last 2 columns' => [
+ [
+ ['g'],
+ ['j'],
+ ['m'],
+ ['p'],
+ ['s'],
+ ['v'],
+ ['y'],
+ ],
+ '=DROP(B3:D11, 2, -2)',
+ ],
+ 'drop first 2 rows last 2 columns fractional and string' => [
+ [
+ ['g'],
+ ['j'],
+ ['m'],
+ ['p'],
+ ['s'],
+ ['v'],
+ ['y'],
+ ],
+ '=DROP(B3:D11, 2.8, " -2.8 ")',
+ ],
+ 'named range' => [
+ [
+ ['g'],
+ ['j'],
+ ['m'],
+ ['p'],
+ ['s'],
+ ['v'],
+ ['y'],
+ ],
+ '=DROP(definedname, 2,-2)',
+ ],
+ 'only 1 argument' => [
+ 'exception',
+ '=DROP(B3:D11)',
+ ],
+ 'non-numeric row' => [
+ '#VALUE!',
+ '=DROP(B3:D11, "x")',
+ ],
+ 'non-numeric column' => [
+ '#VALUE!',
+ '=DROP(B3:D11, 1, "x")',
+ ],
+ 'positive row too large' => [
+ '#VALUE!',
+ '=DROP(B3:D11, 20)',
+ ],
+ 'negative row too large' => [
+ '#VALUE!',
+ '=DROP(B3:D11, -20)',
+ ],
+ 'positive column too large' => [
+ '#VALUE!',
+ '=DROP(B3:D11, , 20)',
+ ],
+ 'negative column too large' => [
+ '#VALUE!',
+ '=DROP(B3:D11, , -20)',
+ ],
+ 'row okay column too large' => [
+ '#VALUE!',
+ '=DROP(B3:D11, -1, 20)',
+ ],
+ 'zero row' => [
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ['g', 'h', 'i'],
+ ['j', 'k', 'l'],
+ ['m', 'n', 'o'],
+ ['p', 'q', 'r'],
+ ['s', 't', 'u'],
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ '=DROP(B3:D11, 0)',
+ ],
+ 'zero column' => [
+ [
+ ['d', 'e', 'f'],
+ ['g', 'h', 'i'],
+ ['j', 'k', 'l'],
+ ['m', 'n', 'o'],
+ ['p', 'q', 'r'],
+ ['s', 't', 'u'],
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ '=DROP(B3:D11, 1, 0)',
+ ],
+ 'single cell' => [
+ '#VALUE!',
+ '=DROP(B3, 1)',
+ ],
+ 'inline array rather than range' => [
+ [
+ [4],
+ [6],
+ ],
+ '=DROP({1,2;3,4;5,6}, 1, 1)',
+ ],
+];
diff --git a/tests/data/Calculation/LookupRef/EXPAND.php b/tests/data/Calculation/LookupRef/EXPAND.php
new file mode 100644
index 0000000000..d00f7a7005
--- /dev/null
+++ b/tests/data/Calculation/LookupRef/EXPAND.php
@@ -0,0 +1,74 @@
+ [
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ['#N/A', '#N/A', '#N/A'],
+ ['#N/A', '#N/A', '#N/A'],
+ ],
+ '=EXPAND(B3:D4, 4)',
+ ],
+ 'Add 1 colun' => [
+ [
+ ['a', 'b', 'c', '#N/A'],
+ ['d', 'e', 'f', '#N/A'],
+ ],
+ '=EXPAND(B3:D4, , 4)',
+ ],
+ 'Add 1 row 2 columns set cells to 0' => [
+ [
+ ['a', 'b', 'c', 0, 0],
+ ['d', 'e', 'f', 0, 0],
+ [0, 0, 0, 0, 0],
+ ],
+ '=EXPAND(B3:D4, 3, 5, 0)',
+ ],
+ 'Fractional row and column' => [
+ [
+ ['a', 'b', 'c', 0, 0],
+ ['d', 'e', 'f', 0, 0],
+ [0, 0, 0, 0, 0],
+ ],
+ '=EXPAND(B3:D4, 3.2, 5.8, 0)',
+ ],
+ 'only 1 argument' => [
+ 'exception',
+ '=EXPAND(B3:D4)',
+ ],
+ 'non-numeric row' => [
+ '#VALUE!',
+ '=EXPAND(B3:D4, "x")',
+ ],
+ 'non-numeric column' => [
+ '#VALUE!',
+ '=EXPAND(B3:D4, 1, "x")',
+ ],
+ 'row too small' => [
+ '#VALUE!',
+ '=EXPAND(B3:D4, 1)',
+ ],
+ 'column too small' => [
+ '#VALUE!',
+ '=EXPAND(B3:D4, , 2)',
+ ],
+ 'single cell' => [
+ [
+ ['a', 'xx'],
+ ['xx', 'xx'],
+ ],
+ '=EXPAND(B3, 2, 2, "xx")',
+ ],
+ 'inline array rather than range' => [
+ [
+ [1, 2, 0, 0],
+ [3, 4, 0, 0],
+ [5, 6, 0, 0],
+ [0, 0, 0, 0],
+ ],
+ '=EXPAND({1,2;3,4;5,6}, 4.4, " 4.5 ", 0)',
+ ],
+];
diff --git a/tests/data/Calculation/LookupRef/TAKE.php b/tests/data/Calculation/LookupRef/TAKE.php
new file mode 100644
index 0000000000..b5ed8cf472
--- /dev/null
+++ b/tests/data/Calculation/LookupRef/TAKE.php
@@ -0,0 +1,168 @@
+ [
+ [
+ ['y', 'z', '#'],
+ ],
+ '=TAKE(B3:D11, -1)',
+ ],
+ 'take first 2 rows' => [
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ],
+ '=TAKE(B3:D11, 2)',
+ ],
+ 'take last 2 columns' => [
+ [
+ ['b', 'c'],
+ ['e', 'f'],
+ ['h', 'i'],
+ ['k', 'l'],
+ ['n', 'o'],
+ ['q', 'r'],
+ ['t', 'u'],
+ ['w', 'x'],
+ ['z', '#'],
+ ],
+ '=TAKE(B3:D11, , -2)',
+ ],
+ 'take first column' => [
+ [
+ ['a'],
+ ['d'],
+ ['g'],
+ ['j'],
+ ['m'],
+ ['p'],
+ ['s'],
+ ['v'],
+ ['y'],
+ ],
+ '=TAKE(B3:D11, , 1)',
+ ],
+ 'take last row first column' => [
+ [
+ ['y'],
+ ],
+ '=TAKE(B3:D11, -1, 1)',
+ ],
+ 'take first 2 rows last 2 columns' => [
+ [
+ ['b', 'c'],
+ ['e', 'f'],
+ ],
+ '=TAKE(B3:D11, 2, -2)',
+ ],
+ 'take first 2 rows last 2 columns fractional and string' => [
+ [
+ ['b', 'c'],
+ ['e', 'f'],
+ ],
+ '=TAKE(B3:D11, 2.8, " -2.8 ")',
+ ],
+ 'named range' => [
+ [
+ ['x'],
+ ['#'],
+ ],
+ '=TAKE(definedname, -2,-1)',
+ ],
+ 'only 1 argument' => [
+ 'exception',
+ '=TAKE(B3:D11)',
+ ],
+ 'non-numeric row' => [
+ '#VALUE!',
+ '=TAKE(B3:D11, "x")',
+ ],
+ 'non-numeric column' => [
+ '#VALUE!',
+ '=TAKE(B3:D11, 1, "x")',
+ ],
+ 'positive row too large' => [
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ['g', 'h', 'i'],
+ ['j', 'k', 'l'],
+ ['m', 'n', 'o'],
+ ['p', 'q', 'r'],
+ ['s', 't', 'u'],
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ '=TAKE(B3:D11, 20)',
+ ],
+ 'negative row too large' => [
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ['g', 'h', 'i'],
+ ['j', 'k', 'l'],
+ ['m', 'n', 'o'],
+ ['p', 'q', 'r'],
+ ['s', 't', 'u'],
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ '=TAKE(B3:D11, -20)',
+ ],
+ 'positive column too large' => [
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ['g', 'h', 'i'],
+ ['j', 'k', 'l'],
+ ['m', 'n', 'o'],
+ ['p', 'q', 'r'],
+ ['s', 't', 'u'],
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ '=TAKE(B3:D11, , 20)',
+ ],
+ 'negative column too large' => [
+ [
+ ['a', 'b', 'c'],
+ ['d', 'e', 'f'],
+ ['g', 'h', 'i'],
+ ['j', 'k', 'l'],
+ ['m', 'n', 'o'],
+ ['p', 'q', 'r'],
+ ['s', 't', 'u'],
+ ['v', 'w', 'x'],
+ ['y', 'z', '#'],
+ ],
+ '=TAKE(B3:D11, , -20)',
+ ],
+ 'row okay column too large' => [
+ [
+ ['y', 'z', '#'],
+ ],
+ '=TAKE(B3:D11, -1, 20)',
+ ],
+ 'zero row' => [
+ '#VALUE!',
+ '=TAKE(B3:D11, 0)',
+ ],
+ 'zero column' => [
+ '#VALUE!',
+ '=TAKE(B3:D11, 1, 0)',
+ ],
+ 'single cell' => [
+ [
+ ['a'],
+ ],
+ '=TAKE(B3, 1)',
+ ],
+ 'inline array rather than range' => [
+ [
+ [1, 2],
+ ],
+ '=TAKE({1,2;3,4;5,6}, 1, 2)',
+ ],
+];
diff --git a/tests/data/Reader/XLS/biff8cover.xls b/tests/data/Reader/XLS/biff8cover.xls
new file mode 100644
index 0000000000..92da6e12fa
Binary files /dev/null and b/tests/data/Reader/XLS/biff8cover.xls differ
diff --git a/tests/data/Reader/XLSX/sec-j47r.dontuse b/tests/data/Reader/XLSX/sec-j47r.dontuse
new file mode 100644
index 0000000000..9914b1ffd1
Binary files /dev/null and b/tests/data/Reader/XLSX/sec-j47r.dontuse differ
diff --git a/tests/data/Reader/XLSX/sec-p66w.dontuse b/tests/data/Reader/XLSX/sec-p66w.dontuse
new file mode 100644
index 0000000000..7257bfc9d3
Binary files /dev/null and b/tests/data/Reader/XLSX/sec-p66w.dontuse differ
diff --git a/tests/data/Reader/XLSX/sec-q229.dontuse b/tests/data/Reader/XLSX/sec-q229.dontuse
new file mode 100644
index 0000000000..edb9024987
Binary files /dev/null and b/tests/data/Reader/XLSX/sec-q229.dontuse differ
diff --git a/tests/data/Reader/XLSX/sharedformulae.xlsx b/tests/data/Reader/XLSX/sharedformulae.xlsx
new file mode 100644
index 0000000000..eab8198b34
Binary files /dev/null and b/tests/data/Reader/XLSX/sharedformulae.xlsx differ
diff --git a/tests/data/Writer/Ods/content-arrays.xml b/tests/data/Writer/Ods/content-arrays.xml
index a33b7dbfca..8e72601a59 100644
--- a/tests/data/Writer/Ods/content-arrays.xml
+++ b/tests/data/Writer/Ods/content-arrays.xml
@@ -1,2 +1,44 @@
-11133
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1
+
+
+1
+
+
+
+
+1
+
+
+3
+
+
+
+
+3
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/data/Writer/Ods/content-empty.xml b/tests/data/Writer/Ods/content-empty.xml
index 84f4c23977..e9015af896 100644
--- a/tests/data/Writer/Ods/content-empty.xml
+++ b/tests/data/Writer/Ods/content-empty.xml
@@ -3,12 +3,11 @@
-
+
-
@@ -17,10 +16,6 @@
-
-
-
-
diff --git a/tests/data/Writer/Ods/content-hidden-worksheet.xml b/tests/data/Writer/Ods/content-hidden-worksheet.xml
index 88a53257a1..b583466701 100644
--- a/tests/data/Writer/Ods/content-hidden-worksheet.xml
+++ b/tests/data/Writer/Ods/content-hidden-worksheet.xml
@@ -3,15 +3,14 @@
-
+
-
+
-
@@ -24,7 +23,6 @@
1
-
@@ -33,7 +31,6 @@
2
-
diff --git a/tests/data/Writer/Ods/content-with-data.xml b/tests/data/Writer/Ods/content-with-data.xml
index db7d75a747..bde23e29d3 100644
--- a/tests/data/Writer/Ods/content-with-data.xml
+++ b/tests/data/Writer/Ods/content-with-data.xml
@@ -3,65 +3,54 @@
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
@@ -92,7 +81,6 @@
Lorem ipsum
-
@@ -107,10 +95,6 @@
42798.572060185
-
-
-
-
@@ -119,7 +103,6 @@
2
-