diff --git a/CHANGELOG.md b/CHANGELOG.md
index f60d7425..290474a1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,13 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Added
-- Add point size option for scatter charts
+- Add point size option for scatter charts [Issue #2298](https://github.com/PHPOffice/PhpSpreadsheet/issues/2298) [PR #2801](https://github.com/PHPOffice/PhpSpreadsheet/pull/2801)
+- Basic support for Xlsx reading/writing Chart Sheets [PR #2830](https://github.com/PHPOffice/PhpSpreadsheet/pull/2830)
+
+ Note that a ChartSheet is still only written as a normal Worksheet containing a single chart, not as an actual ChartSheet.
+
+- Added Worksheet visibility in Ods Reader [PR #2851](https://github.com/PHPOffice/PhpSpreadsheet/pull/2851) and Gnumeric Reader [PR #2853](https://github.com/PHPOffice/PhpSpreadsheet/pull/2853)
+- Added Worksheet visibility in Ods Writer [PR #2850](https://github.com/PHPOffice/PhpSpreadsheet/pull/2850)
+- Allow Csv Reader to treat string as contents of file [Issue #1285](https://github.com/PHPOffice/PhpSpreadsheet/issues/1285) [PR #2792](https://github.com/PHPOffice/PhpSpreadsheet/pull/2792)
+- Allow Csv Reader to store null string rather than leave cell empty [Issue #2840](https://github.com/PHPOffice/PhpSpreadsheet/issues/2840) [PR #2842](https://github.com/PHPOffice/PhpSpreadsheet/pull/2842)
### Changed
- Memory and speed improvements, particularly for the Cell Collection, and the Writers.
- See [the Discussion](https://github.com/PHPOffice/PhpSpreadsheet/discussions/2821) for details of performance
+ See [the Discussion section on github](https://github.com/PHPOffice/PhpSpreadsheet/discussions/2821) for details of performance across versions
### Deprecated
@@ -28,7 +36,12 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Fixed
- Xls Reader resolving absolute named ranges to relative ranges [Issue #2826](https://github.com/PHPOffice/PhpSpreadsheet/issues/2826) [PR #2827](https://github.com/PHPOffice/PhpSpreadsheet/pull/2827)
-
+- Null value handling in the Excel Math/Trig PRODUCT() function [Issue #2833](https://github.com/PHPOffice/PhpSpreadsheet/issues/2833) [PR #2834](https://github.com/PHPOffice/PhpSpreadsheet/pull/2834)
+- Invalid Print Area defined in Xlsx corrupts internal storage of print area [Issue #2848](https://github.com/PHPOffice/PhpSpreadsheet/issues/2848) [PR #2849](https://github.com/PHPOffice/PhpSpreadsheet/pull/2849)
+- Time interval formatting [Issue #2768](https://github.com/PHPOffice/PhpSpreadsheet/issues/2768) [PR #2772](https://github.com/PHPOffice/PhpSpreadsheet/pull/2772)
+- Copy from Xls(x) to Html/Pdf loses drawings [PR #2788](https://github.com/PHPOffice/PhpSpreadsheet/pull/2788)
+- Html Reader converting cell containing 0 to null string [Issue #2810](https://github.com/PHPOffice/PhpSpreadsheet/issues/2810) [PR #2813](https://github.com/PHPOffice/PhpSpreadsheet/pull/2813)
+- Many fixes for Charts, especially, but not limited to, Scatter, Bubble, and Surface charts. [Issue #2762](https://github.com/PHPOffice/PhpSpreadsheet/issues/2762) [Issue #2299](https://github.com/PHPOffice/PhpSpreadsheet/issues/2299) [Issue #2700](https://github.com/PHPOffice/PhpSpreadsheet/issues/2700) [Issue #2817](https://github.com/PHPOffice/PhpSpreadsheet/issues/2817) [Issue #2763](https://github.com/PHPOffice/PhpSpreadsheet/issues/2763) [PR #2828](https://github.com/PHPOffice/PhpSpreadsheet/pull/2828) [PR #2841](https://github.com/PHPOffice/PhpSpreadsheet/pull/2841) [PR #2846](https://github.com/PHPOffice/PhpSpreadsheet/pull/2846) [PR #2852](https://github.com/PHPOffice/PhpSpreadsheet/pull/2852)
## 1.23.0 - 2022-04-24
diff --git a/README.md b/README.md
index 2a94e0d3..40b025e7 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,44 @@
PhpSpreadsheet is a library written in pure PHP and offers a set of classes that
allow you to read and write various spreadsheet file formats such as Excel and LibreOffice Calc.
+## PHP version support
+
+LTS: Support for PHP versions will only be maintained for a period of six months beyond the
+[end of life of that PHP version](https://www.php.net/eol.php).
+
+Currently the required PHP minimum version is PHP __7.3__.
+
+See the `composer.json` for other requirements.
+
+## Installation
+
+Use [composer](https://getcomposer.org) to install PhpSpreadsheet into your project:
+
+```sh
+composer require phpoffice/phpspreadsheet
+```
+
+If you are building your installation on a development machine that is on a different PHP version to the server where it will be deployed, or if your PHP CLI version is not the same as your run-time such as `php-fpm` or Apache's `mod_php`, then you might want to add the following to your `composer.json` before installing:
+```json lines
+{
+ "require": {
+ "phpoffice/phpspreadsheet": "^1.23"
+ },
+ "config": {
+ "platform": {
+ "php": "7.3"
+ }
+ }
+}
+```
+and then run
+```sh
+composer install
+```
+to ensure that the correct dependencies are retrieved to match your deployment environment.
+
+See [CLI vs Application run-time](https://php.watch/articles/composer-platform-check) for more details.
+
## Documentation
Read more about it, including install instructions, in the [official documentation](https://phpspreadsheet.readthedocs.io). Or check out the [API documentation](https://phpoffice.github.io/PhpSpreadsheet).
diff --git a/docs/index.md b/docs/index.md
index 98a2d3d8..ff137c26 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -30,9 +30,14 @@ for details.
### PHP version support
-Support for PHP versions will only be maintained for a period of six months beyond the
+LTS: Support for PHP versions will only be maintained for a period of six months beyond the
[end of life of that PHP version](https://www.php.net/eol.php).
+Currently the required PHP minimum version is PHP 7.3. The last PHP release was 7.3.33 on 6th December 2021, so PhpSpreadsheet will support PHP 7.3 until 6th June 2022.
+PHP 7.4 is officially [End of Life](https://www.php.net/supported-versions.php) on 28th November 2022, and PhpSpreadsheet will continue to support PHP 7.4 for six months after that date.
+
+See the `composer.json` for other requirements.
+
## Installation
Use [composer](https://getcomposer.org) to install PhpSpreadsheet into your project:
@@ -47,6 +52,26 @@ Or also download the documentation and samples if you plan to use them:
composer require phpoffice/phpspreadsheet --prefer-source
```
+If you are building your installation on a development machine that is on a different PHP version to the server where it will be deployed, or if your PHP CLI version is not the same as your run-time such as `php-fpm` or Apache's `mod_php`, then you might want to add the following to your `composer.json` before installing:
+```json lines
+{
+ "require": {
+ "phpoffice/phpspreadsheet": "^1.23"
+ },
+ "config": {
+ "platform": {
+ "php": "7.3"
+ }
+ }
+}
+```
+and then run
+```sh
+composer install
+```
+to ensure that the correct dependencies are retrieved to match your deployment environment.
+
+See [CLI vs Application run-time](https://php.watch/articles/composer-platform-check) for more details.
## Hello World
diff --git a/docs/references/features-cross-reference.md b/docs/references/features-cross-reference.md
index 399be82e..d18c0969 100644
--- a/docs/references/features-cross-reference.md
+++ b/docs/references/features-cross-reference.md
@@ -16,7 +16,7 @@
|
XLS |
XLSX |
- Excel2003XML |
+ XML (Excel2003XML) |
Ods |
Gnumeric |
CSV |
@@ -732,12 +732,32 @@
|
|
+
+ | Hidden Worksheets |
+ ✔ |
+ ✔ |
+ |
+ ✔ |
+ ✔ |
+ N/A |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
| Coloured Tabs |
|
|
|
|
+ |
N/A |
|
|
diff --git a/docs/topics/reading-and-writing-to-file.md b/docs/topics/reading-and-writing-to-file.md
index 19928f04..0bcc1909 100644
--- a/docs/topics/reading-and-writing-to-file.md
+++ b/docs/topics/reading-and-writing-to-file.md
@@ -449,8 +449,7 @@ $spreadsheet = $reader->loadSpreadsheetFromString($data);
#### Setting CSV options
Often, CSV files are not really "comma separated", or use semicolon (`;`)
-as a separator. You can instruct
-`\PhpOffice\PhpSpreadsheet\Reader\Csv` some options before reading a CSV
+as a separator. You can set some options before reading a CSV
file.
The separator will be auto-detected, so in most cases it should not be necessary
@@ -506,6 +505,12 @@ $reader->setSheetIndex(0);
$spreadsheet = $reader->load('sample.csv');
```
+The CSV reader will normally not load null strings into the spreadsheet.
+To load them:
+```php
+$reader->setPreserveNullString(true);
+```
+
Finally, you can set a callback to be invoked when the constructor is executed,
either through `new Csv()` or `IOFactory::load`,
and have that callback set the customizable attributes to whatever
@@ -584,8 +589,7 @@ $writer->save("05featuredemo.csv");
#### Setting CSV options
Often, CSV files are not really "comma separated", or use semicolon (`;`)
-as a separator. You can instruct
-`\PhpOffice\PhpSpreadsheet\Writer\Csv` some options before writing a CSV
+as a separator. You can set some options before writing a CSV
file:
```php
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 3989e2cf..778090a8 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -125,11 +125,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Calculation/Calculation.php
- -
- message: "#^Parameter \\#3 \\$formula of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:translateSeparator\\(\\) expects string, string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Calculation/Calculation.php
-
-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$cellStack has no type specified\\.$#"
count: 1
@@ -750,11 +745,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php
- -
- message: "#^Parameter \\#1 \\$columnAddress of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:columnIndexFromString\\(\\) expects string, string\\|null given\\.$#"
- count: 3
- path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php
-
-
message: "#^Binary operation \"/\" between array\\|float\\|int\\|string and array\\|float\\|int\\|string results in an error\\.$#"
count: 2
@@ -1160,76 +1150,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Chart/Chart.php
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:getBottomRightXOffset\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:getBottomRightYOffset\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:getTopLeftXOffset\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:getTopLeftYOffset\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightCell\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightCell\\(\\) has parameter \\$cell with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightXOffset\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightXOffset\\(\\) has parameter \\$xOffset with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightYOffset\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightYOffset\\(\\) has parameter \\$yOffset with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setTopLeftXOffset\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setTopLeftXOffset\\(\\) has parameter \\$xOffset with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setTopLeftYOffset\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setTopLeftYOffset\\(\\) has parameter \\$yOffset with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/Chart.php
-
-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$legend \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend\\|null\\.$#"
count: 1
@@ -1285,26 +1205,6 @@ parameters:
count: 2
path: src/PhpSpreadsheet/Chart/DataSeries.php
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:refresh\\(\\) has parameter \\$flatten with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/DataSeriesValues.php
-
- -
- message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:\\$dataSource \\(string\\) does not accept string\\|null\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/DataSeriesValues.php
-
- -
- message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:\\$dataTypeValues has no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/DataSeriesValues.php
-
- -
- message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:\\$fillColor \\(array\\\\|string\\) does not accept array\\\\|string\\|null\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Chart/DataSeriesValues.php
-
-
message: "#^Parameter \\#1 \\$angle of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setShadowAngle\\(\\) expects int, int\\|null given\\.$#"
count: 1
@@ -1800,11 +1700,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Helper/Html.php
- -
- message: "#^Parameter \\#1 \\$text of method PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\ITextElement\\:\\:setText\\(\\) expects string, string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Helper/Html.php
-
-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$bold has no type specified\\.$#"
count: 1
@@ -1905,11 +1800,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Helper/Sample.php
- -
- message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Helper/Sample.php
-
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\IOFactory\\:\\:createReader\\(\\) should return PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\IReader but returns object\\.$#"
count: 1
@@ -2035,36 +1925,11 @@ parameters:
count: 6
path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php
- -
- message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$masterPrintStylesCrossReference has no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php
-
- -
- message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$masterStylesCrossReference has no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php
-
- -
- message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$officeNs has no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php
-
-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$pageLayoutStyles has no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php
- -
- message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$stylesFo has no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php
-
- -
- message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$stylesNs has no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php
-
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:load\\(\\) has parameter \\$namespacesMeta with no type specified\\.$#"
count: 1
@@ -2396,7 +2261,7 @@ parameters:
path: src/PhpSpreadsheet/Reader/Xlsx.php
-
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:boolean\\(\\) has no return type specified\\.$#"
+ message: "#^Comparison operation \"\\>\" between SimpleXMLElement\\|null and 0 results in an error\\.$#"
count: 1
path: src/PhpSpreadsheet/Reader/Xlsx.php
@@ -2470,11 +2335,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Reader/Xlsx.php
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:dirAdd\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx.php
-
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:dirAdd\\(\\) has parameter \\$add with no type specified\\.$#"
count: 1
@@ -2535,21 +2395,11 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Reader/Xlsx.php
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:stripWhiteSpaceFromStyleString\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx.php
-
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:stripWhiteSpaceFromStyleString\\(\\) has parameter \\$string with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Reader/Xlsx.php
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:toCSSArray\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx.php
-
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:toCSSArray\\(\\) has parameter \\$style with no type specified\\.$#"
count: 1
@@ -2625,206 +2475,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php
- -
- message: "#^Cannot call method getFont\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\Run\\|null\\.$#"
- count: 12
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Cannot call method setBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Cannot call method setColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Cannot call method setItalic\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Cannot call method setName\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Cannot call method setSize\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Cannot call method setStrikethrough\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 2
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Cannot call method setSubscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Cannot call method setSuperscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Cannot call method setUnderline\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 3
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeries\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeries\\(\\) has parameter \\$chartDetail with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeries\\(\\) has parameter \\$namespacesChartMeta with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeries\\(\\) has parameter \\$plotType with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValueSet\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValueSet\\(\\) has parameter \\$marker with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValueSet\\(\\) has parameter \\$namespacesChartMeta with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValueSet\\(\\) has parameter \\$seriesDetail with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValues\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValues\\(\\) has parameter \\$dataType with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValues\\(\\) has parameter \\$seriesValueSet with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValuesMultiLevel\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValuesMultiLevel\\(\\) has parameter \\$dataType with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValuesMultiLevel\\(\\) has parameter \\$seriesValueSet with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartLayoutDetails\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartLayoutDetails\\(\\) has parameter \\$chartDetail with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartLayoutDetails\\(\\) has parameter \\$namespacesChartMeta with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartTitle\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:parseRichText\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readChartAttributes\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readChartAttributes\\(\\) has parameter \\$chartDetail with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readColor\\(\\) has no return type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readColor\\(\\) has parameter \\$background with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readColor\\(\\) has parameter \\$color with no type specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Parameter \\#1 \\$position of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend constructor expects string, bool\\|float\\|int\\|string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Parameter \\#3 \\$overlay of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend constructor expects bool, bool\\|float\\|int\\|string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Parameter \\#3 \\$plotOrder of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeries constructor expects array\\, array\\ given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Parameter \\#4 \\$pointCount of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues constructor expects int, null given\\.$#"
- count: 4
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Parameter \\#6 \\$displayBlanksAs of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart constructor expects string, bool\\|float\\|int\\|string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
- -
- message: "#^Parameter \\#7 \\$plotDirection of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeries constructor expects string\\|null, bool\\|float\\|int\\|string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php
-
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredColumn\\(\\) has no return type specified\\.$#"
count: 1
@@ -3035,11 +2685,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/ReferenceHelper.php
- -
- message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/ReferenceHelper.php
-
-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\Run\\:\\:\\$font \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
count: 1
@@ -4035,26 +3680,11 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
- -
- message: "#^Parameter \\#2 \\$subject of function preg_split expects string, string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
-
- -
- message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
-
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\PercentageFormatter\\:\\:format\\(\\) has parameter \\$value with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php
- -
- message: "#^Parameter \\#1 \\$format of function sprintf expects string, string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php
-
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\CellIterator\\:\\:adjustForExistingOnlyRange\\(\\) has no return type specified\\.$#"
count: 1
@@ -4490,11 +4120,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Writer/Html.php
- -
- message: "#^Parameter \\#1 \\$string of function htmlspecialchars expects string, string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Writer/Html.php
-
-
message: "#^Parameter \\#1 \\$vAlign of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:mapVAlign\\(\\) expects string, string\\|null given\\.$#"
count: 1
@@ -4690,16 +4315,6 @@ parameters:
count: 7
path: src/PhpSpreadsheet/Writer/Xls/Parser.php
- -
- message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Writer/Xls/Parser.php
-
- -
- message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Writer/Xls/Parser.php
-
-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:\\$spreadsheet has no type specified\\.$#"
count: 1
@@ -4800,21 +4415,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php
- -
- message: "#^Parameter \\#2 \\$subject of function preg_match expects string, string\\|null given\\.$#"
- count: 2
- path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php
-
- -
- message: "#^Parameter \\#2 \\$subject of function preg_match_all expects string, string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php
-
- -
- message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#"
- count: 2
- path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php
-
-
message: "#^Parameter \\#4 \\$isError of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:writeBoolErr\\(\\) expects bool, int given\\.$#"
count: 3
@@ -4947,7 +4547,7 @@ parameters:
-
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#"
- count: 44
+ count: 42
path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php
-
@@ -4955,26 +4555,6 @@ parameters:
count: 2
path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php
- -
- message: "#^Parameter \\#3 \\$id1 of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeCategoryAxis\\(\\) expects string, int\\|string given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php
-
- -
- message: "#^Parameter \\#4 \\$id1 of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects string, int\\|string given\\.$#"
- count: 2
- path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php
-
- -
- message: "#^Parameter \\#4 \\$id2 of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeCategoryAxis\\(\\) expects string, int\\|string given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php
-
- -
- message: "#^Parameter \\#5 \\$id2 of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects string, int\\|string given\\.$#"
- count: 2
- path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php
-
-
message: "#^Parameter \\#6 \\$yAxis of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeCategoryAxis\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\|null given\\.$#"
count: 1
@@ -5062,7 +4642,7 @@ parameters:
-
message: "#^Cannot call method getBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 2
+ count: 1
path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
-
@@ -5072,12 +4652,12 @@ parameters:
-
message: "#^Cannot call method getItalic\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 2
+ count: 1
path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
-
message: "#^Cannot call method getName\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 2
+ count: 1
path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
-
@@ -5087,7 +4667,7 @@ parameters:
-
message: "#^Cannot call method getStrikethrough\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 2
+ count: 1
path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
-
@@ -5102,7 +4682,7 @@ parameters:
-
message: "#^Cannot call method getUnderline\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
- count: 2
+ count: 1
path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
-
@@ -5137,7 +4717,7 @@ parameters:
-
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, string\\|null given\\.$#"
- count: 5
+ count: 4
path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
-
@@ -5285,8 +4865,3 @@ parameters:
count: 2
path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Xlfn\\:\\:addXlfn\\(\\) should return string but returns string\\|null\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php
-
diff --git a/samples/Chart/33_Chart_create_bubble.php b/samples/Chart/33_Chart_create_bubble.php
new file mode 100644
index 00000000..33feea62
--- /dev/null
+++ b/samples/Chart/33_Chart_create_bubble.php
@@ -0,0 +1,124 @@
+getActiveSheet();
+$worksheet->fromArray(
+ [
+ ['Number of Products', 'Sales in USD', 'Market share'],
+ [14, 12200, 15],
+ [20, 60000, 33],
+ [18, 24400, 10],
+ [22, 32000, 42],
+ [],
+ [12, 8200, 18],
+ [15, 50000, 30],
+ [19, 22400, 15],
+ [25, 25000, 50],
+ ]
+);
+
+// Set the Labels for each data series we want to plot
+// Datatype
+// Cell reference for data
+// Format Code
+// Number of datapoints in series
+// Data values
+// Data Marker
+
+$dataSeriesLabels = [
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, null, null, 1, ['2013']), // 2013
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, null, null, 1, ['2014']), // 2014
+];
+
+// Set the X-Axis values
+// Datatype
+// Cell reference for data
+// Format Code
+// Number of datapoints in series
+// Data values
+// Data Marker
+$dataSeriesCategories = [
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$2:$A$5', null, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$7:$A$10', null, 4),
+];
+
+// Set the Y-Axis values
+// Datatype
+// Cell reference for data
+// Format Code
+// Number of datapoints in series
+// Data values
+// Data Marker
+$dataSeriesValues = [
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$7:$B$10', null, 4),
+];
+
+// Set the Z-Axis values (bubble size)
+// Datatype
+// Cell reference for data
+// Format Code
+// Number of datapoints in series
+// Data values
+// Data Marker
+$dataSeriesBubbles = [
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$7:$C$10', null, 4),
+];
+
+// Build the dataseries
+$series = new DataSeries(
+ DataSeries::TYPE_BUBBLECHART, // plotType
+ null, // plotGrouping
+ range(0, count($dataSeriesValues) - 1), // plotOrder
+ $dataSeriesLabels, // plotLabel
+ $dataSeriesCategories, // plotCategory
+ $dataSeriesValues // plotValues
+);
+$series->setPlotBubbleSizes($dataSeriesBubbles);
+
+// Set the series in the plot area
+$plotArea = new PlotArea(null, [$series]);
+// Set the chart legend
+$legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false);
+
+// Create the chart
+$chart = new Chart(
+ 'chart1', // name
+ null, // title
+ $legend, // legend
+ $plotArea, // plotArea
+ true, // plotVisibleOnly
+ DataSeries::EMPTY_AS_GAP, // displayBlanksAs
+ null, // xAxisLabel
+ null // yAxisLabel
+);
+
+// Set the position where the chart should appear in the worksheet
+$chart->setTopLeftPosition('E1');
+$chart->setBottomRightPosition('M15');
+
+// Add the chart to the worksheet
+$worksheet->addChart($chart);
+$worksheet->getColumnDimension('A')->setAutoSize(true);
+$worksheet->getColumnDimension('B')->setAutoSize(true);
+$worksheet->getColumnDimension('C')->setAutoSize(true);
+
+// Save Excel 2007 file
+$filename = $helper->getFilename(__FILE__);
+$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
+$writer->setIncludeCharts(true);
+$callStartTime = microtime(true);
+$writer->save($filename);
+$helper->logWrite($writer, $filename, $callStartTime);
diff --git a/samples/Chart/33_Chart_create_scatter2.php b/samples/Chart/33_Chart_create_scatter2.php
new file mode 100644
index 00000000..1d6e331c
--- /dev/null
+++ b/samples/Chart/33_Chart_create_scatter2.php
@@ -0,0 +1,121 @@
+getActiveSheet();
+// changed data to simulate a trend chart - Xaxis are dates; Yaxis are 3 meausurements from each date
+$worksheet->fromArray(
+ [
+ ['', 'metric1', 'metric2', 'metric3'],
+ ['=DATEVALUE("2021-01-01")', 12.1, 15.1, 21.1],
+ ['=DATEVALUE("2021-01-04")', 56.2, 73.2, 86.2],
+ ['=DATEVALUE("2021-01-07")', 52.2, 61.2, 69.2],
+ ['=DATEVALUE("2021-01-10")', 30.2, 32.2, 0.2],
+ ]
+);
+$worksheet->getStyle('A2:A5')->getNumberFormat()->setFormatCode(Properties::FORMAT_CODE_DATE_ISO8601);
+$worksheet->getColumnDimension('A')->setAutoSize(true);
+$worksheet->setSelectedCells('A1');
+
+// Set the Labels for each data series we want to plot
+// Datatype
+// Cell reference for data
+// Format Code
+// Number of datapoints in series
+// Data values
+// Data Marker
+$dataSeriesLabels = [
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // was 2010
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // was 2011
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // was 2012
+];
+// Set the X-Axis Labels
+// changed from STRING to NUMBER
+// added 2 additional x-axis values associated with each of the 3 metrics
+// added FORMATE_CODE_NUMBER
+$xAxisTickValues = [
+ //new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$2:$A$5', Properties::FORMAT_CODE_DATE, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$2:$A$5', Properties::FORMAT_CODE_DATE, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$2:$A$5', Properties::FORMAT_CODE_DATE, 4),
+];
+// Set the Data values for each data series we want to plot
+// Datatype
+// Cell reference for data
+// Format Code
+// Number of datapoints in series
+// Data values
+// Data Marker
+// added FORMAT_CODE_NUMBER
+$dataSeriesValues = [
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', Properties::FORMAT_CODE_NUMBER, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', Properties::FORMAT_CODE_NUMBER, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', Properties::FORMAT_CODE_NUMBER, 4),
+];
+ // Added so that Xaxis shows dates instead of Excel-equivalent-year1900-numbers
+$xAxis = new Axis();
+//$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE );
+$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601, true);
+
+// Build the dataseries
+$series = new DataSeries(
+ DataSeries::TYPE_SCATTERCHART, // plotType
+ null, // plotGrouping (Scatter charts don't have any grouping)
+ range(0, count($dataSeriesValues) - 1), // plotOrder
+ $dataSeriesLabels, // plotLabel
+ $xAxisTickValues, // plotCategory
+ $dataSeriesValues, // plotValues
+ null, // plotDirection
+ false, // smooth line
+ //DataSeries::STYLE_LINEMARKER // plotStyle
+ DataSeries::STYLE_MARKER // plotStyle
+);
+
+// Set the series in the plot area
+$plotArea = new PlotArea(null, [$series]);
+// Set the chart legend
+$legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false);
+
+$title = new Title('Test Scatter Trend Chart');
+$yAxisLabel = new Title('Value ($k)');
+
+// Create the chart
+$chart = new Chart(
+ 'chart1', // name
+ $title, // title
+ $legend, // legend
+ $plotArea, // plotArea
+ true, // plotVisibleOnly
+ DataSeries::EMPTY_AS_GAP, // displayBlanksAs
+ null, // xAxisLabel
+ $yAxisLabel, // yAxisLabel
+ // added xAxis for correct date display
+ $xAxis, // xAxis
+);
+
+// Set the position where the chart should appear in the worksheet
+$chart->setTopLeftPosition('A7');
+$chart->setBottomRightPosition('P20');
+// Add the chart to the worksheet
+$worksheet->addChart($chart);
+
+// Save Excel 2007 file
+$filename = $helper->getFilename(__FILE__);
+$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
+$writer->setIncludeCharts(true);
+$callStartTime = microtime(true);
+$writer->save($filename);
+$spreadsheet->disconnectWorksheets();
+$helper->logWrite($writer, $filename, $callStartTime);
diff --git a/samples/templates/32readwriteBubbleChart2.xlsx b/samples/templates/32readwriteBubbleChart2.xlsx
new file mode 100644
index 00000000..206cfaea
Binary files /dev/null and b/samples/templates/32readwriteBubbleChart2.xlsx differ
diff --git a/samples/templates/32readwriteScatterChart6.xlsx b/samples/templates/32readwriteScatterChart6.xlsx
new file mode 100644
index 00000000..ddfa3048
Binary files /dev/null and b/samples/templates/32readwriteScatterChart6.xlsx differ
diff --git a/samples/templates/32readwriteScatterChart7.xlsx b/samples/templates/32readwriteScatterChart7.xlsx
new file mode 100644
index 00000000..6c01bf40
Binary files /dev/null and b/samples/templates/32readwriteScatterChart7.xlsx differ
diff --git a/samples/templates/32readwriteStockChart5.xlsx b/samples/templates/32readwriteStockChart5.xlsx
new file mode 100644
index 00000000..364803f2
Binary files /dev/null and b/samples/templates/32readwriteStockChart5.xlsx differ
diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php
index 7a48a7cb..65105f8e 100644
--- a/src/PhpSpreadsheet/Calculation/Calculation.php
+++ b/src/PhpSpreadsheet/Calculation/Calculation.php
@@ -3197,7 +3197,7 @@ class Calculation
string $toSeparator
): string {
// Function Names
- $formula = preg_replace($from, $to, $formula);
+ $formula = (string) preg_replace($from, $to, $formula);
// Temporarily adjust matrix separators so that they won't be confused with function arguments
$formula = self::translateSeparator(';', '|', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
@@ -4180,7 +4180,7 @@ class Calculation
$length = strlen($val);
if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $val, $matches)) {
- $val = preg_replace('/\s/u', '', $val);
+ $val = (string) preg_replace('/\s/u', '', $val);
if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) { // it's a function
$valToUpper = strtoupper($val);
} else {
diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php
index 466fe4a6..52543a72 100644
--- a/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php
+++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php
@@ -49,7 +49,7 @@ class DateValue
$baseYear = SharedDateHelper::getExcelCalendar();
$dateValue = trim($dateValue ?? '', '"');
// Strip any ordinals because they're allowed in Excel (English only)
- $dateValue = preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue) ?? '';
+ $dateValue = (string) preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue);
// Convert separators (/ . or space) to hyphens (should also handle dot used for ordinals in some countries, e.g. Denmark, Germany)
$dateValue = str_replace(['/', '.', '-', ' '], ' ', $dateValue);
diff --git a/src/PhpSpreadsheet/Calculation/Functions.php b/src/PhpSpreadsheet/Calculation/Functions.php
index 8075811f..1cc980b8 100644
--- a/src/PhpSpreadsheet/Calculation/Functions.php
+++ b/src/PhpSpreadsheet/Calculation/Functions.php
@@ -159,7 +159,7 @@ class Functions
} elseif (!is_numeric($condition)) {
if ($condition !== '""') { // Not an empty string
// Escape any quotes in the string value
- $condition = preg_replace('/"/ui', '""', $condition);
+ $condition = (string) preg_replace('/"/ui', '""', $condition);
}
$condition = Calculation::wrapResult(strtoupper($condition));
}
diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php b/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php
index 28e8df89..7408a66e 100644
--- a/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php
+++ b/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php
@@ -43,7 +43,7 @@ class Helpers
if ($namedRange !== null) {
$workSheet = $namedRange->getWorkSheet();
$sheetTitle = ($workSheet === null) ? '' : $workSheet->getTitle();
- $value = preg_replace('/^=/', '', $namedRange->getValue());
+ $value = (string) preg_replace('/^=/', '', $namedRange->getValue());
self::adjustSheetTitle($sheetTitle, $value);
$cellAddress1 = $sheetTitle . $value;
$cellAddress = $cellAddress1;
diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php b/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php
index 1f848045..8bce07e9 100644
--- a/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php
+++ b/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php
@@ -50,7 +50,7 @@ class RowColumnInformation
if (is_array($cellAddress)) {
foreach ($cellAddress as $columnKey => $value) {
- $columnKey = preg_replace('/[^a-z]/i', '', $columnKey);
+ $columnKey = (string) preg_replace('/[^a-z]/i', '', $columnKey);
return (int) Coordinate::columnIndexFromString($columnKey);
}
@@ -66,8 +66,8 @@ class RowColumnInformation
[, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
if (strpos($cellAddress, ':') !== false) {
[$startAddress, $endAddress] = explode(':', $cellAddress);
- $startAddress = preg_replace('/[^a-z]/i', '', $startAddress);
- $endAddress = preg_replace('/[^a-z]/i', '', $endAddress);
+ $startAddress = (string) preg_replace('/[^a-z]/i', '', $startAddress);
+ $endAddress = (string) preg_replace('/[^a-z]/i', '', $endAddress);
return range(
(int) Coordinate::columnIndexFromString($startAddress),
@@ -75,7 +75,7 @@ class RowColumnInformation
);
}
- $cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress);
+ $cellAddress = (string) preg_replace('/[^a-z]/i', '', $cellAddress);
return (int) Coordinate::columnIndexFromString($cellAddress);
}
@@ -159,14 +159,13 @@ class RowColumnInformation
[, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
if (strpos($cellAddress, ':') !== false) {
[$startAddress, $endAddress] = explode(':', $cellAddress);
- $startAddress = preg_replace('/\D/', '', $startAddress);
- $endAddress = preg_replace('/\D/', '', $endAddress);
+ $startAddress = (string) preg_replace('/\D/', '', $startAddress);
+ $endAddress = (string) preg_replace('/\D/', '', $endAddress);
return array_map(
function ($value) {
return [$value];
},
- // @phpstan-ignore-next-line
range($startAddress, $endAddress)
);
}
diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php b/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php
index 7fd30233..f26da389 100644
--- a/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php
+++ b/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php
@@ -102,29 +102,27 @@ class Operations
*/
public static function product(...$args)
{
+ $args = array_filter(
+ Functions::flattenArray($args),
+ function ($value) {
+ return $value !== null;
+ }
+ );
+
// Return value
- $returnValue = null;
+ $returnValue = (count($args) === 0) ? 0.0 : 1.0;
// Loop through arguments
- foreach (Functions::flattenArray($args) as $arg) {
+ foreach ($args as $arg) {
// Is it a numeric value?
if (is_numeric($arg)) {
- if ($returnValue === null) {
- $returnValue = $arg;
- } else {
- $returnValue *= $arg;
- }
+ $returnValue *= $arg;
} else {
return ExcelError::VALUE();
}
}
- // Return
- if ($returnValue === null) {
- return 0;
- }
-
- return $returnValue;
+ return (float) $returnValue;
}
/**
diff --git a/src/PhpSpreadsheet/Calculation/TextData/Trim.php b/src/PhpSpreadsheet/Calculation/TextData/Trim.php
index e52c0095..27eceb93 100644
--- a/src/PhpSpreadsheet/Calculation/TextData/Trim.php
+++ b/src/PhpSpreadsheet/Calculation/TextData/Trim.php
@@ -14,7 +14,7 @@ class Trim
* @param mixed $stringValue String Value to check
* Or can be an array of values
*
- * @return null|array|string
+ * @return array|string
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -26,7 +26,7 @@ class Trim
$stringValue = Helpers::extractString($stringValue);
- return preg_replace('/[\\x00-\\x1f]/', '', "$stringValue");
+ return (string) preg_replace('/[\\x00-\\x1f]/', '', "$stringValue");
}
/**
diff --git a/src/PhpSpreadsheet/Chart/Axis.php b/src/PhpSpreadsheet/Chart/Axis.php
index eeed326d..089ebdb9 100644
--- a/src/PhpSpreadsheet/Chart/Axis.php
+++ b/src/PhpSpreadsheet/Chart/Axis.php
@@ -18,6 +18,7 @@ class Axis extends Properties
private $axisNumber = [
'format' => self::FORMAT_CODE_GENERAL,
'source_linked' => 1,
+ 'numeric' => null,
];
/**
@@ -131,15 +132,26 @@ class Axis extends Properties
'size' => null,
];
+ private const NUMERIC_FORMAT = [
+ Properties::FORMAT_CODE_NUMBER,
+ Properties::FORMAT_CODE_DATE,
+ ];
+
/**
* Get Series Data Type.
*
* @param mixed $format_code
*/
- public function setAxisNumberProperties($format_code): void
+ public function setAxisNumberProperties($format_code, ?bool $numeric = null): void
{
- $this->axisNumber['format'] = (string) $format_code;
+ $format = (string) $format_code;
+ $this->axisNumber['format'] = $format;
$this->axisNumber['source_linked'] = 0;
+ if (is_bool($numeric)) {
+ $this->axisNumber['numeric'] = $numeric;
+ } elseif (in_array($format, self::NUMERIC_FORMAT, true)) {
+ $this->axisNumber['numeric'] = true;
+ }
}
/**
@@ -162,6 +174,11 @@ class Axis extends Properties
return (string) $this->axisNumber['source_linked'];
}
+ public function getAxisIsNumericFormat(): bool
+ {
+ return (bool) $this->axisNumber['numeric'];
+ }
+
/**
* Set Axis Options Properties.
*
diff --git a/src/PhpSpreadsheet/Chart/Chart.php b/src/PhpSpreadsheet/Chart/Chart.php
index bed89464..ec6342c5 100644
--- a/src/PhpSpreadsheet/Chart/Chart.php
+++ b/src/PhpSpreadsheet/Chart/Chart.php
@@ -124,7 +124,7 @@ class Chart
*
* @var string
*/
- private $bottomRightCellRef = 'A1';
+ private $bottomRightCellRef = '';
/**
* Bottom-Right X-Offset.
@@ -140,6 +140,21 @@ class Chart
*/
private $bottomRightYOffset = 10;
+ /** @var ?int */
+ private $rotX;
+
+ /** @var ?int */
+ private $rotY;
+
+ /** @var ?int */
+ private $rAngAx;
+
+ /** @var ?int */
+ private $perspective;
+
+ /** @var bool */
+ private $oneCellAnchor = false;
+
/**
* Create a new Chart.
*
@@ -351,8 +366,9 @@ class Chart
if ($this->yAxis !== null) {
return $this->yAxis;
}
+ $this->yAxis = new Axis();
- return new Axis();
+ return $this->yAxis;
}
/**
@@ -365,8 +381,9 @@ class Chart
if ($this->xAxis !== null) {
return $this->xAxis;
}
+ $this->xAxis = new Axis();
- return new Axis();
+ return $this->xAxis;
}
/**
@@ -400,15 +417,15 @@ class Chart
/**
* Set the Top Left position for the chart.
*
- * @param string $cell
+ * @param string $cellAddress
* @param int $xOffset
* @param int $yOffset
*
* @return $this
*/
- public function setTopLeftPosition($cell, $xOffset = null, $yOffset = null)
+ public function setTopLeftPosition($cellAddress, $xOffset = null, $yOffset = null)
{
- $this->topLeftCellRef = $cell;
+ $this->topLeftCellRef = $cellAddress;
if ($xOffset !== null) {
$this->setTopLeftXOffset($xOffset);
}
@@ -446,13 +463,13 @@ class Chart
/**
* Set the Top Left cell position for the chart.
*
- * @param string $cell
+ * @param string $cellAddress
*
* @return $this
*/
- public function setTopLeftCell($cell)
+ public function setTopLeftCell($cellAddress)
{
- $this->topLeftCellRef = $cell;
+ $this->topLeftCellRef = $cellAddress;
return $this;
}
@@ -491,6 +508,11 @@ class Chart
];
}
+ /**
+ * @param int $xOffset
+ *
+ * @return $this
+ */
public function setTopLeftXOffset($xOffset)
{
$this->topLeftXOffset = $xOffset;
@@ -498,11 +520,16 @@ class Chart
return $this;
}
- public function getTopLeftXOffset()
+ public function getTopLeftXOffset(): int
{
return $this->topLeftXOffset;
}
+ /**
+ * @param int $yOffset
+ *
+ * @return $this
+ */
public function setTopLeftYOffset($yOffset)
{
$this->topLeftYOffset = $yOffset;
@@ -510,7 +537,7 @@ class Chart
return $this;
}
- public function getTopLeftYOffset()
+ public function getTopLeftYOffset(): int
{
return $this->topLeftYOffset;
}
@@ -518,15 +545,15 @@ class Chart
/**
* Set the Bottom Right position of the chart.
*
- * @param string $cell
+ * @param string $cellAddress
* @param int $xOffset
* @param int $yOffset
*
* @return $this
*/
- public function setBottomRightPosition($cell, $xOffset = null, $yOffset = null)
+ public function setBottomRightPosition($cellAddress = '', $xOffset = null, $yOffset = null)
{
- $this->bottomRightCellRef = $cell;
+ $this->bottomRightCellRef = $cellAddress;
if ($xOffset !== null) {
$this->setBottomRightXOffset($xOffset);
}
@@ -551,19 +578,22 @@ class Chart
];
}
- public function setBottomRightCell($cell)
+ /**
+ * Set the Bottom Right cell for the chart.
+ *
+ * @return $this
+ */
+ public function setBottomRightCell(string $cellAddress = '')
{
- $this->bottomRightCellRef = $cell;
+ $this->bottomRightCellRef = $cellAddress;
return $this;
}
/**
* Get the cell address where the bottom right of the chart is fixed.
- *
- * @return string
*/
- public function getBottomRightCell()
+ public function getBottomRightCell(): string
{
return $this->bottomRightCellRef;
}
@@ -602,6 +632,11 @@ class Chart
];
}
+ /**
+ * @param int $xOffset
+ *
+ * @return $this
+ */
public function setBottomRightXOffset($xOffset)
{
$this->bottomRightXOffset = $xOffset;
@@ -609,11 +644,16 @@ class Chart
return $this;
}
- public function getBottomRightXOffset()
+ public function getBottomRightXOffset(): int
{
return $this->bottomRightXOffset;
}
+ /**
+ * @param int $yOffset
+ *
+ * @return $this
+ */
public function setBottomRightYOffset($yOffset)
{
$this->bottomRightYOffset = $yOffset;
@@ -621,7 +661,7 @@ class Chart
return $this;
}
- public function getBottomRightYOffset()
+ public function getBottomRightYOffset(): int
{
return $this->bottomRightYOffset;
}
@@ -658,4 +698,64 @@ class Chart
return $renderer->render($outputDestination);
}
+
+ public function getRotX(): ?int
+ {
+ return $this->rotX;
+ }
+
+ public function setRotX(?int $rotX): self
+ {
+ $this->rotX = $rotX;
+
+ return $this;
+ }
+
+ public function getRotY(): ?int
+ {
+ return $this->rotY;
+ }
+
+ public function setRotY(?int $rotY): self
+ {
+ $this->rotY = $rotY;
+
+ return $this;
+ }
+
+ public function getRAngAx(): ?int
+ {
+ return $this->rAngAx;
+ }
+
+ public function setRAngAx(?int $rAngAx): self
+ {
+ $this->rAngAx = $rAngAx;
+
+ return $this;
+ }
+
+ public function getPerspective(): ?int
+ {
+ return $this->perspective;
+ }
+
+ public function setPerspective(?int $perspective): self
+ {
+ $this->perspective = $perspective;
+
+ return $this;
+ }
+
+ public function getOneCellAnchor(): bool
+ {
+ return $this->oneCellAnchor;
+ }
+
+ public function setOneCellAnchor(bool $oneCellAnchor): self
+ {
+ $this->oneCellAnchor = $oneCellAnchor;
+
+ return $this;
+ }
}
diff --git a/src/PhpSpreadsheet/Chart/DataSeries.php b/src/PhpSpreadsheet/Chart/DataSeries.php
index 067d30e5..dca1186e 100644
--- a/src/PhpSpreadsheet/Chart/DataSeries.php
+++ b/src/PhpSpreadsheet/Chart/DataSeries.php
@@ -107,6 +107,13 @@ class DataSeries
*/
private $plotValues = [];
+ /**
+ * Plot Bubble Sizes.
+ *
+ * @var DataSeriesValues[]
+ */
+ private $plotBubbleSizes = [];
+
/**
* Create a new DataSeries.
*
@@ -339,6 +346,28 @@ class DataSeries
return false;
}
+ /**
+ * Get Plot Bubble Sizes.
+ *
+ * @return DataSeriesValues[]
+ */
+ public function getPlotBubbleSizes(): array
+ {
+ return $this->plotBubbleSizes;
+ }
+
+ /**
+ * Set Plot Bubble Sizes.
+ *
+ * @param DataSeriesValues[] $plotBubbleSizes
+ */
+ public function setPlotBubbleSizes(array $plotBubbleSizes): self
+ {
+ $this->plotBubbleSizes = $plotBubbleSizes;
+
+ return $this;
+ }
+
/**
* Get Number of Plot Series.
*
diff --git a/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/src/PhpSpreadsheet/Chart/DataSeriesValues.php
index 745f0106..6747934a 100644
--- a/src/PhpSpreadsheet/Chart/DataSeriesValues.php
+++ b/src/PhpSpreadsheet/Chart/DataSeriesValues.php
@@ -12,7 +12,7 @@ class DataSeriesValues
const DATASERIES_TYPE_STRING = 'String';
const DATASERIES_TYPE_NUMBER = 'Number';
- private static $dataTypeValues = [
+ private const DATA_TYPE_VALUES = [
self::DATASERIES_TYPE_STRING,
self::DATASERIES_TYPE_NUMBER,
];
@@ -27,7 +27,7 @@ class DataSeriesValues
/**
* Series Data Source.
*
- * @var string
+ * @var ?string
*/
private $dataSource;
@@ -69,10 +69,13 @@ class DataSeriesValues
/**
* Fill color (can be array with colors if dataseries have custom colors).
*
- * @var string|string[]
+ * @var null|string|string[]
*/
private $fillColor;
+ /** @var string */
+ private $schemeClr = '';
+
/**
* Line Width.
*
@@ -80,6 +83,12 @@ class DataSeriesValues
*/
private $lineWidth = 12700;
+ /** @var bool */
+ private $scatterLines = true;
+
+ /** @var bool */
+ private $bubble3D = false;
+
/**
* Create a new DataSeriesValues object.
*
@@ -90,8 +99,9 @@ class DataSeriesValues
* @param mixed $dataValues
* @param null|mixed $marker
* @param null|string|string[] $fillColor
+ * @param string $pointSize
*/
- public function __construct($dataType = self::DATASERIES_TYPE_NUMBER, $dataSource = null, $formatCode = null, $pointCount = 0, $dataValues = [], $marker = null, $fillColor = null)
+ public function __construct($dataType = self::DATASERIES_TYPE_NUMBER, $dataSource = null, $formatCode = null, $pointCount = 0, $dataValues = [], $marker = null, $fillColor = null, $pointSize = '3')
{
$this->setDataType($dataType);
$this->dataSource = $dataSource;
@@ -100,6 +110,9 @@ class DataSeriesValues
$this->dataValues = $dataValues;
$this->pointMarker = $marker;
$this->fillColor = $fillColor;
+ if (is_numeric($pointSize)) {
+ $this->pointSize = (int) $pointSize;
+ }
}
/**
@@ -126,7 +139,7 @@ class DataSeriesValues
*/
public function setDataType($dataType)
{
- if (!in_array($dataType, self::$dataTypeValues)) {
+ if (!in_array($dataType, self::DATA_TYPE_VALUES)) {
throw new Exception('Invalid datatype for chart data series values');
}
$this->dataType = $dataType;
@@ -137,7 +150,7 @@ class DataSeriesValues
/**
* Get Series Data Source (formula).
*
- * @return string
+ * @return ?string
*/
public function getDataSource()
{
@@ -147,7 +160,7 @@ class DataSeriesValues
/**
* Set Series Data Source (formula).
*
- * @param string $dataSource
+ * @param ?string $dataSource
*
* @return $this
*/
@@ -239,7 +252,7 @@ class DataSeriesValues
/**
* Get fill color.
*
- * @return string|string[] HEX color or array with HEX colors
+ * @return null|string|string[] HEX color or array with HEX colors
*/
public function getFillColor()
{
@@ -249,7 +262,7 @@ class DataSeriesValues
/**
* Set fill color for series.
*
- * @param string|string[] $color HEX color or array with HEX colors
+ * @param null|string|string[] $color HEX color or array with HEX colors
*
* @return DataSeriesValues
*/
@@ -260,7 +273,7 @@ class DataSeriesValues
$this->validateColor($colorValue);
}
} else {
- $this->validateColor($color);
+ $this->validateColor("$color");
}
$this->fillColor = $color;
@@ -315,7 +328,7 @@ class DataSeriesValues
*/
public function isMultiLevelSeries()
{
- if (count($this->dataValues) > 0) {
+ if (!empty($this->dataValues)) {
return is_array(array_values($this->dataValues)[0]);
}
@@ -379,7 +392,7 @@ class DataSeriesValues
return $this;
}
- public function refresh(Worksheet $worksheet, $flatten = true): void
+ public function refresh(Worksheet $worksheet, bool $flatten = true): void
{
if ($this->dataSource !== null) {
$calcEngine = Calculation::getInstance($worksheet->getParent());
@@ -421,4 +434,40 @@ class DataSeriesValues
$this->pointCount = count($this->dataValues);
}
}
+
+ public function getScatterLines(): bool
+ {
+ return $this->scatterLines;
+ }
+
+ public function setScatterLines(bool $scatterLines): self
+ {
+ $this->scatterLines = $scatterLines;
+
+ return $this;
+ }
+
+ public function getBubble3D(): bool
+ {
+ return $this->bubble3D;
+ }
+
+ public function setBubble3D(bool $bubble3D): self
+ {
+ $this->bubble3D = $bubble3D;
+
+ return $this;
+ }
+
+ public function getSchemeClr(): string
+ {
+ return $this->schemeClr;
+ }
+
+ public function setSchemeClr(string $schemeClr): self
+ {
+ $this->schemeClr = $schemeClr;
+
+ return $this;
+ }
}
diff --git a/src/PhpSpreadsheet/Chart/Properties.php b/src/PhpSpreadsheet/Chart/Properties.php
index ef22fb52..9f1a5ce0 100644
--- a/src/PhpSpreadsheet/Chart/Properties.php
+++ b/src/PhpSpreadsheet/Chart/Properties.php
@@ -37,6 +37,7 @@ abstract class Properties
const FORMAT_CODE_CURRENCY = '$#,##0.00';
const FORMAT_CODE_ACCOUNTING = '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)';
const FORMAT_CODE_DATE = 'm/d/yyyy';
+ const FORMAT_CODE_DATE_ISO8601 = 'yyyy-mm-dd';
const FORMAT_CODE_TIME = '[$-F400]h:mm:ss AM/PM';
const FORMAT_CODE_PERCENTAGE = '0.00%';
const FORMAT_CODE_FRACTION = '# ?/?';
diff --git a/src/PhpSpreadsheet/Document/Properties.php b/src/PhpSpreadsheet/Document/Properties.php
index 2d461c59..afdeea99 100644
--- a/src/PhpSpreadsheet/Document/Properties.php
+++ b/src/PhpSpreadsheet/Document/Properties.php
@@ -171,9 +171,9 @@ class Properties
if (is_numeric($timestamp)) {
$timestamp = (float) $timestamp;
} else {
- $timestamp = preg_replace('/[.][0-9]*$/', '', $timestamp) ?? '';
- $timestamp = preg_replace('/^(\\d{4})- (\\d)/', '$1-0$2', $timestamp) ?? '';
- $timestamp = preg_replace('/^(\\d{4}-\\d{2})- (\\d)/', '$1-0$2', $timestamp) ?? '';
+ $timestamp = (string) preg_replace('/[.][0-9]*$/', '', $timestamp);
+ $timestamp = (string) preg_replace('/^(\\d{4})- (\\d)/', '$1-0$2', $timestamp);
+ $timestamp = (string) preg_replace('/^(\\d{4}-\\d{2})- (\\d)/', '$1-0$2', $timestamp);
$timestamp = (float) (new DateTime($timestamp))->format('U');
}
}
diff --git a/src/PhpSpreadsheet/Helper/Html.php b/src/PhpSpreadsheet/Helper/Html.php
index ce5a3d1d..632efebc 100644
--- a/src/PhpSpreadsheet/Helper/Html.php
+++ b/src/PhpSpreadsheet/Helper/Html.php
@@ -642,7 +642,7 @@ class Html
$text = ltrim($text);
}
// Trim any spaces immediately after a line break
- $text = preg_replace('/\n */mu', "\n", $text);
+ $text = (string) preg_replace('/\n */mu', "\n", $text);
$element->setText($text);
}
}
@@ -792,7 +792,7 @@ class Html
protected function parseTextNode(DOMText $textNode): void
{
- $domText = preg_replace(
+ $domText = (string) preg_replace(
'/\s+/u',
' ',
str_replace(["\r", "\n"], ' ', $textNode->nodeValue ?? '')
diff --git a/src/PhpSpreadsheet/Helper/Sample.php b/src/PhpSpreadsheet/Helper/Sample.php
index 257a02a8..8ce37003 100644
--- a/src/PhpSpreadsheet/Helper/Sample.php
+++ b/src/PhpSpreadsheet/Helper/Sample.php
@@ -85,7 +85,7 @@ class Sample
$file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0]));
$info = pathinfo($file);
$category = str_replace('_', ' ', $info['dirname']);
- $name = str_replace('_', ' ', preg_replace('/(|\.php)/', '', $info['filename']));
+ $name = str_replace('_', ' ', (string) preg_replace('/(|\.php)/', '', $info['filename']));
if (!in_array($category, ['.', 'boostrap', 'templates'])) {
if (!isset($files[$category])) {
$files[$category] = [];
diff --git a/src/PhpSpreadsheet/Reader/Csv.php b/src/PhpSpreadsheet/Reader/Csv.php
index b604ceef..65a71edb 100644
--- a/src/PhpSpreadsheet/Reader/Csv.php
+++ b/src/PhpSpreadsheet/Reader/Csv.php
@@ -103,6 +103,9 @@ class Csv extends BaseReader
*/
protected $preserveNumericFormatting = false;
+ /** @var bool */
+ private $preserveNullString = false;
+
/**
* Create a new CSV Reader instance.
*/
@@ -300,9 +303,11 @@ class Csv extends BaseReader
}
}
- public function setTestAutoDetect(bool $value): void
+ public function setTestAutoDetect(bool $value): self
{
$this->testAutodetect = $value;
+
+ return $this;
}
private function setAutoDetect(?string $value): ?string
@@ -390,7 +395,7 @@ class Csv extends BaseReader
foreach ($rowData as $rowDatum) {
$this->convertBoolean($rowDatum, $preserveBooleanString);
$numberFormatMask = $this->convertFormattedNumber($rowDatum);
- if ($rowDatum !== '' && $this->readFilter->readCell($columnLetter, $currentRow)) {
+ if (($rowDatum !== '' || $this->preserveNullString) && $this->readFilter->readCell($columnLetter, $currentRow)) {
if ($this->contiguous) {
if ($noOutputYet) {
$noOutputYet = false;
@@ -625,4 +630,16 @@ class Csv extends BaseReader
return ($encoding === '') ? $dflt : $encoding;
}
+
+ public function setPreserveNullString(bool $value): self
+ {
+ $this->preserveNullString = $value;
+
+ return $this;
+ }
+
+ public function getPreserveNullString(): bool
+ {
+ return $this->preserveNullString;
+ }
}
diff --git a/src/PhpSpreadsheet/Reader/Csv/Delimiter.php b/src/PhpSpreadsheet/Reader/Csv/Delimiter.php
index fc298957..029d4a18 100644
--- a/src/PhpSpreadsheet/Reader/Csv/Delimiter.php
+++ b/src/PhpSpreadsheet/Reader/Csv/Delimiter.php
@@ -140,12 +140,12 @@ class Delimiter
$line = $line . $newLine;
// Drop everything that is enclosed to avoid counting false positives in enclosures
- $line = preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line);
+ $line = (string) preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line);
// See if we have any enclosures left in the line
// if we still have an enclosure then we need to read the next line as well
- } while (preg_match('/(' . $enclosure . ')/', $line ?? '') > 0);
+ } while (preg_match('/(' . $enclosure . ')/', $line) > 0);
- return $line ?? false;
+ return ($line !== '') ? $line : false;
}
}
diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php
index ee2e8d3d..ca087e61 100644
--- a/src/PhpSpreadsheet/Reader/Gnumeric.php
+++ b/src/PhpSpreadsheet/Reader/Gnumeric.php
@@ -272,6 +272,11 @@ class Gnumeric extends BaseReader
// name in line with the formula, not the reverse
$this->spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
+ $visibility = $sheetOrNull->attributes()['Visibility'] ?? 'GNM_SHEET_VISIBILITY_VISIBLE';
+ if ((string) $visibility !== 'GNM_SHEET_VISIBILITY_VISIBLE') {
+ $this->spreadsheet->getActiveSheet()->setSheetState(Worksheet::SHEETSTATE_HIDDEN);
+ }
+
if (!$this->readDataOnly) {
(new PageSetup($this->spreadsheet))
->printInformation($sheet)
diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php
index 437d931d..4edf3cf8 100644
--- a/src/PhpSpreadsheet/Reader/Html.php
+++ b/src/PhpSpreadsheet/Reader/Html.php
@@ -619,7 +619,7 @@ class Html extends BaseReader
{
foreach ($element->childNodes as $child) {
if ($child instanceof DOMText) {
- $domText = preg_replace('/\s+/u', ' ', trim($child->nodeValue ?? ''));
+ $domText = (string) preg_replace('/\s+/u', ' ', trim($child->nodeValue ?? ''));
if (is_string($cellContent)) {
// simply append the text if the cell content is a plain text string
$cellContent .= $domText;
diff --git a/src/PhpSpreadsheet/Reader/Ods.php b/src/PhpSpreadsheet/Reader/Ods.php
index 27c58edb..7e776ab7 100644
--- a/src/PhpSpreadsheet/Reader/Ods.php
+++ b/src/PhpSpreadsheet/Reader/Ods.php
@@ -588,6 +588,7 @@ class Ods extends BaseReader
break;
}
}
+ $pageSettings->setVisibilityForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName);
$pageSettings->setPrintSettingsForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName);
++$worksheetID;
}
diff --git a/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php b/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php
index f2ad1a3d..4abdf11e 100644
--- a/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php
+++ b/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php
@@ -13,17 +13,25 @@ class FormulaTranslator
// Cell range 3-d reference
// As we don't support 3-d ranges, we're just going to take a quick and dirty approach
// and assume that the second worksheet reference is the same as the first
- $excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+):\$?([^\.]+)\.([^\.]+)/miu', '$1!$2:$4', $excelAddress);
- // Cell range reference in another sheet
- $excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+):\.([^\.]+)/miu', '$1!$2:$3', $excelAddress ?? '');
- // Cell reference in another sheet
- $excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+)/miu', '$1!$2', $excelAddress ?? '');
- // Cell range reference
- $excelAddress = preg_replace('/\.([^\.]+):\.([^\.]+)/miu', '$1:$2', $excelAddress ?? '');
- // Simple cell reference
- $excelAddress = preg_replace('/\.([^\.]+)/miu', '$1', $excelAddress ?? '');
+ $excelAddress = (string) preg_replace(
+ [
+ '/\$?([^\.]+)\.([^\.]+):\$?([^\.]+)\.([^\.]+)/miu',
+ '/\$?([^\.]+)\.([^\.]+):\.([^\.]+)/miu', // Cell range reference in another sheet
+ '/\$?([^\.]+)\.([^\.]+)/miu', // Cell reference in another sheet
+ '/\.([^\.]+):\.([^\.]+)/miu', // Cell range reference
+ '/\.([^\.]+)/miu', // Simple cell reference
+ ],
+ [
+ '$1!$2:$4',
+ '$1!$2:$3',
+ '$1!$2',
+ '$1:$2',
+ '$1',
+ ],
+ $excelAddress
+ );
- return $excelAddress ?? '';
+ return $excelAddress;
}
public static function convertToExcelFormulaValue(string $openOfficeFormula): string
@@ -37,16 +45,23 @@ class FormulaTranslator
// Only replace in alternate array entries (i.e. non-quoted blocks)
// so that conversion isn't done in string values
if ($tKey = !$tKey) {
- // Cell range reference in another sheet
- $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', '$1!$2:$3', $value);
- // Cell reference in another sheet
- $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+)\]/miu', '$1!$2', $value ?? '');
- // Cell range reference
- $value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/miu', '$1:$2', $value ?? '');
- // Simple cell reference
- $value = preg_replace('/\[\.([^\.]+)\]/miu', '$1', $value ?? '');
+ $value = (string) preg_replace(
+ [
+ '/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', // Cell range reference in another sheet
+ '/\[\$?([^\.]+)\.([^\.]+)\]/miu', // Cell reference in another sheet
+ '/\[\.([^\.]+):\.([^\.]+)\]/miu', // Cell range reference
+ '/\[\.([^\.]+)\]/miu', // Simple cell reference
+ ],
+ [
+ '$1!$2:$3',
+ '$1!$2',
+ '$1:$2',
+ '$1',
+ ],
+ $value
+ );
// Convert references to defined names/formulae
- $value = str_replace('$$', '', $value ?? '');
+ $value = str_replace('$$', '', $value);
// Convert ODS function argument separators to Excel function argument separators
$value = Calculation::translateSeparator(';', ',', $value, $inFunctionBracesLevel);
@@ -69,7 +84,7 @@ class FormulaTranslator
Calculation::FORMULA_CLOSE_MATRIX_BRACE
);
- $value = preg_replace('/COM\.MICROSOFT\./ui', '', $value);
+ $value = (string) preg_replace('/COM\.MICROSOFT\./ui', '', $value);
}
}
diff --git a/src/PhpSpreadsheet/Reader/Ods/PageSettings.php b/src/PhpSpreadsheet/Reader/Ods/PageSettings.php
index 8d24fd0c..4d2fd992 100644
--- a/src/PhpSpreadsheet/Reader/Ods/PageSettings.php
+++ b/src/PhpSpreadsheet/Reader/Ods/PageSettings.php
@@ -8,16 +8,41 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class PageSettings
{
+ /**
+ * @var string
+ */
private $officeNs;
+ /**
+ * @var string
+ */
private $stylesNs;
+ /**
+ * @var string
+ */
private $stylesFo;
+ /**
+ * @var string
+ */
+ private $tableNs;
+
+ /**
+ * @var string[]
+ */
+ private $tableStylesCrossReference = [];
+
private $pageLayoutStyles = [];
+ /**
+ * @var string[]
+ */
private $masterStylesCrossReference = [];
+ /**
+ * @var string[]
+ */
private $masterPrintStylesCrossReference = [];
public function __construct(DOMDocument $styleDom)
@@ -32,6 +57,7 @@ class PageSettings
$this->officeNs = $styleDom->lookupNamespaceUri('office');
$this->stylesNs = $styleDom->lookupNamespaceUri('style');
$this->stylesFo = $styleDom->lookupNamespaceUri('fo');
+ $this->tableNs = $styleDom->lookupNamespaceUri('table');
}
private function readPageSettingStyles(DOMDocument $styleDom): void
@@ -98,12 +124,33 @@ class PageSettings
foreach ($styleXReferences as $styleXreferenceSet) {
$styleXRefName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'name');
$stylePageLayoutName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'master-page-name');
+ $styleFamilyName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'family');
+ if (!empty($styleFamilyName) && $styleFamilyName === 'table') {
+ $styleVisibility = 'true';
+ foreach ($styleXreferenceSet->getElementsByTagNameNS($this->stylesNs, 'table-properties') as $tableProperties) {
+ $styleVisibility = $tableProperties->getAttributeNS($this->tableNs, 'display');
+ }
+ $this->tableStylesCrossReference[$styleXRefName] = $styleVisibility;
+ }
if (!empty($stylePageLayoutName)) {
$this->masterStylesCrossReference[$styleXRefName] = $stylePageLayoutName;
}
}
}
+ public function setVisibilityForWorksheet(Worksheet $worksheet, string $styleName): void
+ {
+ if (!array_key_exists($styleName, $this->tableStylesCrossReference)) {
+ return;
+ }
+
+ $worksheet->setSheetState(
+ $this->tableStylesCrossReference[$styleName] === 'false'
+ ? Worksheet::SHEETSTATE_HIDDEN
+ : Worksheet::SHEETSTATE_VISIBLE
+ );
+ }
+
public function setPrintSettingsForWorksheet(Worksheet $worksheet, string $styleName): void
{
if (!array_key_exists($styleName, $this->masterStylesCrossReference)) {
diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php
index af1104f5..10f12dfe 100644
--- a/src/PhpSpreadsheet/Reader/Xls.php
+++ b/src/PhpSpreadsheet/Reader/Xls.php
@@ -4521,17 +4521,17 @@ class Xls extends BaseReader
// first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!)
if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) {
- $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells);
+ $selectedCells = (string) preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells);
}
// first row '1' + last row '65536' indicates that full column is selected
if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) {
- $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells);
+ $selectedCells = (string) preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells);
}
// first column 'A' + last column 'IV' indicates that full row is selected
if (preg_match('/^(A\d+\:)IV(\d+)$/', $selectedCells)) {
- $selectedCells = preg_replace('/^(A\d+\:)IV(\d+)$/', '${1}XFD${2}', $selectedCells);
+ $selectedCells = (string) preg_replace('/^(A\d+\:)IV(\d+)$/', '${1}XFD${2}', $selectedCells);
}
$this->phpSheet->setSelectedCells($selectedCells);
diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php
index 166d9576..4cb487a6 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx.php
@@ -158,6 +158,10 @@ class Xlsx extends BaseReader
Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_DRAWING,
];
+ private const REL_TO_CHART = [
+ Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_CHART,
+ ];
+
/**
* Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
*
@@ -227,7 +231,10 @@ class Xlsx extends BaseReader
$worksheets = [];
foreach ($relsWorkbook->Relationship as $elex) {
$ele = self::getAttributes($elex);
- if ((string) $ele['Type'] === "$namespace/worksheet") {
+ if (
+ ((string) $ele['Type'] === "$namespace/worksheet") ||
+ ((string) $ele['Type'] === "$namespace/chartsheet")
+ ) {
$worksheets[(string) $ele['Id']] = $ele['Target'];
}
}
@@ -406,17 +413,21 @@ class Xlsx extends BaseReader
// Read the theme first, because we need the colour scheme when reading the styles
[$workbookBasename, $xmlNamespaceBase] = $this->getWorkbookBaseName();
+ $drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML;
+ $chartNS = self::REL_TO_CHART[$xmlNamespaceBase] ?? Namespaces::CHART;
$wbRels = $this->loadZip("xl/_rels/${workbookBasename}.rels", Namespaces::RELATIONSHIPS);
$theme = null;
$this->styleReader = new Styles();
foreach ($wbRels->Relationship as $relx) {
$rel = self::getAttributes($relx);
$relTarget = (string) $rel['Target'];
+ if (substr($relTarget, 0, 4) === '/xl/') {
+ $relTarget = substr($relTarget, 4);
+ }
switch ($rel['Type']) {
case "$xmlNamespaceBase/theme":
$themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2'];
$themeOrderAdditional = count($themeOrderArray);
- $drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML;
$xmlTheme = $this->loadZip("xl/{$relTarget}", $drawingNS);
$xmlThemeName = self::getAttributes($xmlTheme);
@@ -513,6 +524,12 @@ class Xlsx extends BaseReader
case Namespaces::PURL_WORKSHEET:
$worksheets[(string) $ele['Id']] = $ele['Target'];
+ break;
+ case Namespaces::CHARTSHEET:
+ if ($this->includeCharts === true) {
+ $worksheets[(string) $ele['Id']] = $ele['Target'];
+ }
+
break;
// a vbaProject ? (: some macros)
case Namespaces::VBA:
@@ -691,6 +708,13 @@ class Xlsx extends BaseReader
continue;
}
+ $sheetReferenceId = (string) self::getArrayItem(self::getAttributes($eleSheet, $xmlNamespaceBase), 'id');
+ if (isset($worksheets[$sheetReferenceId]) === false) {
+ ++$countSkippedSheets;
+ $mapSheetId[$oldSheetId] = null;
+
+ continue;
+ }
// Map old sheet id in original workbook to new sheet id.
// They will differ if loadSheetsOnly() is being used
$mapSheetId[$oldSheetId] = $oldSheetId - $countSkippedSheets;
@@ -702,7 +726,8 @@ class Xlsx extends BaseReader
// and we're simply bringing the worksheet name in line with the formula, not the
// reverse
$docSheet->setTitle((string) $eleSheetAttr['name'], false, false);
- $fileWorksheet = (string) $worksheets[(string) self::getArrayItem(self::getAttributes($eleSheet, $xmlNamespaceBase), 'id')];
+
+ $fileWorksheet = (string) $worksheets[$sheetReferenceId];
$xmlSheet = $this->loadZipNoNamespace("$dir/$fileWorksheet", $mainNS);
$xmlSheetNS = $this->loadZip("$dir/$fileWorksheet", $mainNS);
@@ -1189,14 +1214,23 @@ class Xlsx extends BaseReader
. '/_rels/'
. basename($fileWorksheet)
. '.rels';
+ if (substr($drawingFilename, 0, 7) === 'xl//xl/') {
+ $drawingFilename = substr($drawingFilename, 4);
+ }
if ($zip->locateName($drawingFilename)) {
$relsWorksheet = $this->loadZipNoNamespace($drawingFilename, Namespaces::RELATIONSHIPS);
$drawings = [];
foreach ($relsWorksheet->Relationship as $ele) {
if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") {
- $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
+ $eleTarget = (string) $ele['Target'];
+ if (substr($eleTarget, 0, 4) === '/xl/') {
+ $drawings[(string) $ele['Id']] = substr($eleTarget, 1);
+ } else {
+ $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
+ }
}
}
+
if ($xmlSheet->drawing && !$this->readDataOnly) {
$unparsedDrawings = [];
$fileDrawing = null;
@@ -1205,6 +1239,7 @@ class Xlsx extends BaseReader
$fileDrawing = $drawings[$drawingRelId];
$drawingFilename = dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels';
$relsDrawing = $this->loadZipNoNamespace($drawingFilename, $xmlNamespaceBase);
+
$images = [];
$hyperlinks = [];
if ($relsDrawing && $relsDrawing->Relationship) {
@@ -1217,7 +1252,13 @@ class Xlsx extends BaseReader
$images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']);
} elseif ($eleType === "$xmlNamespaceBase/chart") {
if ($this->includeCharts) {
- $charts[self::dirAdd($fileDrawing, $ele['Target'])] = [
+ $eleTarget = (string) $ele['Target'];
+ if (substr($eleTarget, 0, 4) === '/xl/') {
+ $index = substr($eleTarget, 1);
+ } else {
+ $index = self::dirAdd($fileDrawing, $eleTarget);
+ }
+ $charts[$index] = [
'id' => (string) $ele['Id'],
'sheet' => $docSheet->getTitle(),
];
@@ -1225,6 +1266,7 @@ class Xlsx extends BaseReader
}
}
}
+
$xmlDrawing = $this->loadZipNoNamespace($fileDrawing, '');
$xmlDrawingChildren = $xmlDrawing->children(Namespaces::SPREADSHEET_DRAWING);
@@ -1308,6 +1350,7 @@ class Xlsx extends BaseReader
'width' => $width,
'height' => $height,
'worksheetTitle' => $docSheet->getTitle(),
+ 'oneCellAnchor' => true,
];
}
}
@@ -1403,6 +1446,27 @@ class Xlsx extends BaseReader
}
}
}
+ if ($xmlDrawingChildren->absoluteAnchor) {
+ foreach ($xmlDrawingChildren->absoluteAnchor as $absoluteAnchor) {
+ if (($this->includeCharts) && ($absoluteAnchor->graphicFrame)) {
+ $graphic = $absoluteAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic;
+ /** @var SimpleXMLElement $chartRef */
+ $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart;
+ $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase);
+ $width = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cx')[0]);
+ $height = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cy')[0]);
+
+ $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [
+ 'fromCoordinate' => 'A1',
+ 'fromOffsetX' => 0,
+ 'fromOffsetY' => 0,
+ 'width' => $width,
+ 'height' => $height,
+ 'worksheetTitle' => $docSheet->getTitle(),
+ ];
+ }
+ }
+ }
if (empty($relsDrawing) && $xmlDrawing->count() == 0) {
// Save Drawing without rels and children as unparsed
$unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML();
@@ -1422,7 +1486,7 @@ class Xlsx extends BaseReader
}
// unparsed drawing AlternateContent
- $xmlAltDrawing = $this->loadZip($fileDrawing, Namespaces::COMPATIBILITY);
+ $xmlAltDrawing = $this->loadZip((string) $fileDrawing, Namespaces::COMPATIBILITY);
if ($xmlAltDrawing->AlternateContent) {
foreach ($xmlAltDrawing->AlternateContent as $alternateContent) {
@@ -1491,13 +1555,18 @@ class Xlsx extends BaseReader
$rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$newRangeSets = [];
foreach ($rangeSets as $rangeSet) {
- [$sheetName, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true);
+ [, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true);
+ if (empty($rangeSet)) {
+ continue;
+ }
if (strpos($rangeSet, ':') === false) {
$rangeSet = $rangeSet . ':' . $rangeSet;
}
$newRangeSets[] = str_replace('$', '', $rangeSet);
}
- $docSheet->getPageSetup()->setPrintArea(implode(',', $newRangeSets));
+ if (count($newRangeSets) > 0) {
+ $docSheet->getPageSetup()->setPrintArea(implode(',', $newRangeSets));
+ }
break;
default:
@@ -1601,17 +1670,26 @@ class Xlsx extends BaseReader
if ($this->includeCharts) {
$chartEntryRef = ltrim((string) $contentType['PartName'], '/');
$chartElements = $this->loadZip($chartEntryRef);
- $objChart = Chart::readChart($chartElements, basename($chartEntryRef, '.xml'));
-
+ $chartReader = new Chart($chartNS, $drawingNS);
+ $objChart = $chartReader->readChart($chartElements, basename($chartEntryRef, '.xml'));
if (isset($charts[$chartEntryRef])) {
$chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id'];
if (isset($chartDetails[$chartPositionRef])) {
$excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart);
$objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet']));
- $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
+ // For oneCellAnchor or absoluteAnchor positioned charts,
+ // toCoordinate is not in the data. Does it need to be calculated?
if (array_key_exists('toCoordinate', $chartDetails[$chartPositionRef])) {
- // For oneCellAnchor positioned charts, toCoordinate is not in the data. Does it need to be calculated?
+ // twoCellAnchor
+ $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
$objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']);
+ } else {
+ // oneCellAnchor or absoluteAnchor (e.g. Chart sheet)
+ $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
+ $objChart->setBottomRightPosition('', $chartDetails[$chartPositionRef]['width'], $chartDetails[$chartPositionRef]['height']);
+ if (array_key_exists('oneCellAnchor', $chartDetails[$chartPositionRef])) {
+ $objChart->setOneCellAnchor($chartDetails[$chartPositionRef]['oneCellAnchor']);
+ }
}
}
}
@@ -1771,12 +1849,17 @@ class Xlsx extends BaseReader
return $array[$key] ?? null;
}
- private static function dirAdd($base, $add)
+ private static function dirAdd($base, $add): string
{
- return preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add");
+ $add = "$add";
+ if (substr($add, 0, 4) === '/xl/') {
+ $add = substr($add, 4);
+ }
+
+ return (string) preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add");
}
- private static function toCSSArray($style)
+ private static function toCSSArray($style): array
{
$style = self::stripWhiteSpaceFromStyleString($style);
@@ -1807,12 +1890,12 @@ class Xlsx extends BaseReader
return $style;
}
- public static function stripWhiteSpaceFromStyleString($string)
+ public static function stripWhiteSpaceFromStyleString($string): string
{
return trim(str_replace(["\r", "\n", ' '], '', $string), ';');
}
- private static function boolean($value)
+ private static function boolean($value): bool
{
if (is_object($value)) {
$value = (string) $value;
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
index fdc56ce1..374da9f6 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
@@ -22,7 +22,7 @@ class AutoFilter
public function load(): void
{
// Remove all "$" in the auto filter range
- $autoFilterRange = preg_replace('/\$/', '', $this->worksheetXml->autoFilter['ref'] ?? '');
+ $autoFilterRange = (string) preg_replace('/\$/', '', $this->worksheetXml->autoFilter['ref'] ?? '');
if (strpos($autoFilterRange, ':') !== false) {
$this->readAutoFilter($autoFilterRange, $this->worksheetXml);
}
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
index 680335b7..98507af8 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
@@ -16,6 +16,18 @@ use SimpleXMLElement;
class Chart
{
+ /** @var string */
+ private $cNamespace;
+
+ /** @var string */
+ private $aNamespace;
+
+ public function __construct(string $cNamespace = Namespaces::CHART, string $aNamespace = Namespaces::DRAWINGML)
+ {
+ $this->cNamespace = $cNamespace;
+ $this->aNamespace = $aNamespace;
+ }
+
/**
* @param string $name
* @param string $format
@@ -25,7 +37,7 @@ class Chart
private static function getAttribute(SimpleXMLElement $component, $name, $format)
{
$attributes = $component->attributes();
- if (isset($attributes[$name])) {
+ if (@isset($attributes[$name])) {
if ($format == 'string') {
return (string) $attributes[$name];
} elseif ($format == 'integer') {
@@ -42,58 +54,56 @@ class Chart
return null;
}
- private static function readColor($color, $background = false)
- {
- if (isset($color['rgb'])) {
- return (string) $color['rgb'];
- } elseif (isset($color['indexed'])) {
- return Color::indexedColor($color['indexed'] - 7, $background)->getARGB();
- }
- }
-
/**
* @param string $chartName
*
* @return \PhpOffice\PhpSpreadsheet\Chart\Chart
*/
- public static function readChart(SimpleXMLElement $chartElements, $chartName)
+ public function readChart(SimpleXMLElement $chartElements, $chartName)
{
- $namespacesChartMeta = $chartElements->getNamespaces(true);
- $chartElementsC = $chartElements->children($namespacesChartMeta['c']);
+ $chartElementsC = $chartElements->children($this->cNamespace);
$XaxisLabel = $YaxisLabel = $legend = $title = null;
$dispBlanksAs = $plotVisOnly = null;
$plotArea = null;
+ $rotX = $rotY = $rAngAx = $perspective = null;
foreach ($chartElementsC as $chartElementKey => $chartElement) {
switch ($chartElementKey) {
case 'chart':
foreach ($chartElement as $chartDetailsKey => $chartDetails) {
- $chartDetailsC = $chartDetails->children($namespacesChartMeta['c']);
+ $chartDetailsC = $chartDetails->children($this->cNamespace);
switch ($chartDetailsKey) {
+ case 'view3D':
+ $rotX = self::getAttribute($chartDetails->rotX, 'val', 'integer');
+ $rotY = self::getAttribute($chartDetails->rotY, 'val', 'integer');
+ $rAngAx = self::getAttribute($chartDetails->rAngAx, 'val', 'integer');
+ $perspective = self::getAttribute($chartDetails->perspective, 'val', 'integer');
+
+ break;
case 'plotArea':
$plotAreaLayout = $XaxisLabel = $YaxisLabel = null;
$plotSeries = $plotAttributes = [];
foreach ($chartDetails as $chartDetailKey => $chartDetail) {
switch ($chartDetailKey) {
case 'layout':
- $plotAreaLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta);
+ $plotAreaLayout = $this->chartLayoutDetails($chartDetail);
break;
case 'catAx':
if (isset($chartDetail->title)) {
- $XaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta);
+ $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
}
break;
case 'dateAx':
if (isset($chartDetail->title)) {
- $XaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta);
+ $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
}
break;
case 'valAx':
if (isset($chartDetail->title, $chartDetail->axPos)) {
- $axisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta);
+ $axisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
$axPos = self::getAttribute($chartDetail->axPos, 'val', 'string');
switch ($axPos) {
@@ -114,70 +124,72 @@ class Chart
case 'barChart':
case 'bar3DChart':
$barDirection = self::getAttribute($chartDetail->barDir, 'val', 'string');
- $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
- $plotSer->setPlotDirection($barDirection);
+ $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotSer->setPlotDirection("$barDirection");
$plotSeries[] = $plotSer;
- $plotAttributes = self::readChartAttributes($chartDetail);
+ $plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'lineChart':
case 'line3DChart':
- $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
- $plotAttributes = self::readChartAttributes($chartDetail);
+ $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'areaChart':
case 'area3DChart':
- $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
- $plotAttributes = self::readChartAttributes($chartDetail);
+ $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'doughnutChart':
case 'pieChart':
case 'pie3DChart':
$explosion = isset($chartDetail->ser->explosion);
- $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
- $plotSer->setPlotStyle($explosion);
+ $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotSer->setPlotStyle("$explosion");
$plotSeries[] = $plotSer;
- $plotAttributes = self::readChartAttributes($chartDetail);
+ $plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'scatterChart':
+ /** @var string */
$scatterStyle = self::getAttribute($chartDetail->scatterStyle, 'val', 'string');
- $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
+ $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
$plotSer->setPlotStyle($scatterStyle);
$plotSeries[] = $plotSer;
- $plotAttributes = self::readChartAttributes($chartDetail);
+ $plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'bubbleChart':
$bubbleScale = self::getAttribute($chartDetail->bubbleScale, 'val', 'integer');
- $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
- $plotSer->setPlotStyle($bubbleScale);
+ $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotSer->setPlotStyle("$bubbleScale");
$plotSeries[] = $plotSer;
- $plotAttributes = self::readChartAttributes($chartDetail);
+ $plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'radarChart':
+ /** @var string */
$radarStyle = self::getAttribute($chartDetail->radarStyle, 'val', 'string');
- $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
+ $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
$plotSer->setPlotStyle($radarStyle);
$plotSeries[] = $plotSer;
- $plotAttributes = self::readChartAttributes($chartDetail);
+ $plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'surfaceChart':
case 'surface3DChart':
$wireFrame = self::getAttribute($chartDetail->wireframe, 'val', 'boolean');
- $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
- $plotSer->setPlotStyle($wireFrame);
+ $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotSer->setPlotStyle("$wireFrame");
$plotSeries[] = $plotSer;
- $plotAttributes = self::readChartAttributes($chartDetail);
+ $plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'stockChart':
- $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
- $plotAttributes = self::readChartAttributes($plotAreaLayout);
+ $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotAttributes = $this->readChartAttributes($plotAreaLayout);
break;
}
@@ -186,7 +198,7 @@ class Chart
$plotAreaLayout = new Layout();
}
$plotArea = new PlotArea($plotAreaLayout, $plotSeries);
- self::setChartAttributes($plotAreaLayout, $plotAttributes);
+ $this->setChartAttributes($plotAreaLayout, $plotAttributes);
break;
case 'plotVisOnly':
@@ -198,7 +210,7 @@ class Chart
break;
case 'title':
- $title = self::chartTitle($chartDetails, $namespacesChartMeta);
+ $title = $this->chartTitle($chartDetails);
break;
case 'legend':
@@ -216,42 +228,54 @@ class Chart
break;
case 'layout':
- $legendLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta);
+ $legendLayout = $this->chartLayoutDetails($chartDetail);
break;
}
}
- $legend = new Legend($legendPos, $legendLayout, $legendOverlay);
+ $legend = new Legend("$legendPos", $legendLayout, (bool) $legendOverlay);
break;
}
}
}
}
- $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, $dispBlanksAs, $XaxisLabel, $YaxisLabel);
+ $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel);
+ if (is_int($rotX)) {
+ $chart->setRotX($rotX);
+ }
+ if (is_int($rotY)) {
+ $chart->setRotY($rotY);
+ }
+ if (is_int($rAngAx)) {
+ $chart->setRAngAx($rAngAx);
+ }
+ if (is_int($perspective)) {
+ $chart->setPerspective($perspective);
+ }
return $chart;
}
- private static function chartTitle(SimpleXMLElement $titleDetails, array $namespacesChartMeta)
+ private function chartTitle(SimpleXMLElement $titleDetails): Title
{
$caption = [];
$titleLayout = null;
foreach ($titleDetails as $titleDetailKey => $chartDetail) {
switch ($titleDetailKey) {
case 'tx':
- $titleDetails = $chartDetail->rich->children($namespacesChartMeta['a']);
+ $titleDetails = $chartDetail->rich->children($this->aNamespace);
foreach ($titleDetails as $titleKey => $titleDetail) {
switch ($titleKey) {
case 'p':
- $titleDetailPart = $titleDetail->children($namespacesChartMeta['a']);
- $caption[] = self::parseRichText($titleDetailPart);
+ $titleDetailPart = $titleDetail->children($this->aNamespace);
+ $caption[] = $this->parseRichText($titleDetailPart);
}
}
break;
case 'layout':
- $titleLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta);
+ $titleLayout = $this->chartLayoutDetails($chartDetail);
break;
}
@@ -260,12 +284,12 @@ class Chart
return new Title($caption, $titleLayout);
}
- private static function chartLayoutDetails($chartDetail, $namespacesChartMeta)
+ private function chartLayoutDetails(SimpleXMLElement $chartDetail): ?Layout
{
if (!isset($chartDetail->manualLayout)) {
return null;
}
- $details = $chartDetail->manualLayout->children($namespacesChartMeta['c']);
+ $details = $chartDetail->manualLayout->children($this->cNamespace);
if ($details === null) {
return null;
}
@@ -277,13 +301,13 @@ class Chart
return new Layout($layout);
}
- private static function chartDataSeries($chartDetail, $namespacesChartMeta, $plotType)
+ private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType): DataSeries
{
$multiSeriesType = null;
$smoothLine = false;
- $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = [];
+ $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = $seriesBubbles = [];
- $seriesDetailSet = $chartDetail->children($namespacesChartMeta['c']);
+ $seriesDetailSet = $chartDetail->children($this->cNamespace);
foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) {
switch ($seriesDetailKey) {
case 'grouping':
@@ -293,6 +317,12 @@ class Chart
case 'ser':
$marker = null;
$seriesIndex = '';
+ $srgbClr = null;
+ $lineWidth = null;
+ $pointSize = null;
+ $noFill = false;
+ $schemeClr = '';
+ $bubble3D = false;
foreach ($seriesDetails as $seriesKey => $seriesDetail) {
switch ($seriesKey) {
case 'idx':
@@ -305,11 +335,32 @@ class Chart
break;
case 'tx':
- $seriesLabel[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta);
+ $seriesLabel[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail);
+
+ break;
+ case 'spPr':
+ $children = $seriesDetail->children($this->aNamespace);
+ $ln = $children->ln;
+ $lineWidth = self::getAttribute($ln, 'w', 'string');
+ if (is_countable($ln->noFill) && count($ln->noFill) === 1) {
+ $noFill = true;
+ }
+ $sf = $children->solidFill->schemeClr;
+ if ($sf) {
+ $schemeClr = self::getAttribute($sf, 'val', 'string');
+ }
break;
case 'marker':
$marker = self::getAttribute($seriesDetail->symbol, 'val', 'string');
+ $pointSize = self::getAttribute($seriesDetail->size, 'val', 'string');
+ $pointSize = is_numeric($pointSize) ? ((int) $pointSize) : null;
+ if (count($seriesDetail->spPr) === 1) {
+ $ln = $seriesDetail->spPr->children($this->aNamespace);
+ if (count($ln->solidFill) === 1) {
+ $srgbClr = self::getAttribute($ln->solidFill->srgbClr, 'val', 'string');
+ }
+ }
break;
case 'smooth':
@@ -317,37 +368,95 @@ class Chart
break;
case 'cat':
- $seriesCategory[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta);
+ $seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail);
break;
case 'val':
- $seriesValues[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker);
+ $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize");
break;
case 'xVal':
- $seriesCategory[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker);
+ $seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize");
break;
case 'yVal':
- $seriesValues[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker);
+ $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize");
+
+ break;
+ case 'bubbleSize':
+ $seriesBubbles[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize");
+
+ break;
+ case 'bubble3D':
+ $bubble3D = self::getAttribute($seriesDetail, 'val', 'boolean');
break;
}
}
+ if ($noFill) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->setScatterLines(false);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->setScatterLines(false);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->setScatterLines(false);
+ }
+ }
+ if (is_numeric($lineWidth)) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->setLineWidth((int) $lineWidth);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->setLineWidth((int) $lineWidth);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->setLineWidth((int) $lineWidth);
+ }
+ }
+ if ($schemeClr) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->setSchemeClr($schemeClr);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->setSchemeClr($schemeClr);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->setSchemeClr($schemeClr);
+ }
+ }
+ if ($bubble3D) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->setBubble3D($bubble3D);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->setBubble3D($bubble3D);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->setBubble3D($bubble3D);
+ }
+ }
}
}
+ /** @phpstan-ignore-next-line */
+ $series = new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine);
+ $series->setPlotBubbleSizes($seriesBubbles);
- return new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine);
+ return $series;
}
- private static function chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker = null)
+ /**
+ * @return mixed
+ */
+ private function chartDataSeriesValueSet(SimpleXMLElement $seriesDetail, ?string $marker = null, ?string $srgbClr = null, ?string $pointSize = null)
{
if (isset($seriesDetail->strRef)) {
$seriesSource = (string) $seriesDetail->strRef->f;
- $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker);
+ $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $srgbClr, "$pointSize");
if (isset($seriesDetail->strRef->strCache)) {
- $seriesData = self::chartDataSeriesValues($seriesDetail->strRef->strCache->children($namespacesChartMeta['c']), 's');
+ $seriesData = $this->chartDataSeriesValues($seriesDetail->strRef->strCache->children($this->cNamespace), 's');
$seriesValues
->setFormatCode($seriesData['formatCode'])
->setDataValues($seriesData['dataValues']);
@@ -356,9 +465,9 @@ class Chart
return $seriesValues;
} elseif (isset($seriesDetail->numRef)) {
$seriesSource = (string) $seriesDetail->numRef->f;
- $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, null, null, $marker);
+ $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, 0, null, $marker, $srgbClr, "$pointSize");
if (isset($seriesDetail->numRef->numCache)) {
- $seriesData = self::chartDataSeriesValues($seriesDetail->numRef->numCache->children($namespacesChartMeta['c']));
+ $seriesData = $this->chartDataSeriesValues($seriesDetail->numRef->numCache->children($this->cNamespace));
$seriesValues
->setFormatCode($seriesData['formatCode'])
->setDataValues($seriesData['dataValues']);
@@ -367,10 +476,10 @@ class Chart
return $seriesValues;
} elseif (isset($seriesDetail->multiLvlStrRef)) {
$seriesSource = (string) $seriesDetail->multiLvlStrRef->f;
- $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker);
+ $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $srgbClr, "$pointSize");
if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) {
- $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($namespacesChartMeta['c']), 's');
+ $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($this->cNamespace), 's');
$seriesValues
->setFormatCode($seriesData['formatCode'])
->setDataValues($seriesData['dataValues']);
@@ -379,10 +488,10 @@ class Chart
return $seriesValues;
} elseif (isset($seriesDetail->multiLvlNumRef)) {
$seriesSource = (string) $seriesDetail->multiLvlNumRef->f;
- $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker);
+ $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $srgbClr, "$pointSize");
if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) {
- $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($namespacesChartMeta['c']), 's');
+ $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($this->cNamespace), 's');
$seriesValues
->setFormatCode($seriesData['formatCode'])
->setDataValues($seriesData['dataValues']);
@@ -391,10 +500,20 @@ class Chart
return $seriesValues;
}
+ if (isset($seriesDetail->v)) {
+ return new DataSeriesValues(
+ DataSeriesValues::DATASERIES_TYPE_STRING,
+ null,
+ null,
+ 1,
+ [(string) $seriesDetail->v]
+ );
+ }
+
return null;
}
- private static function chartDataSeriesValues($seriesValueSet, $dataType = 'n')
+ private function chartDataSeriesValues(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array
{
$seriesVal = [];
$formatCode = '';
@@ -414,7 +533,7 @@ class Chart
$pointVal = self::getAttribute($seriesValue, 'idx', 'integer');
if ($dataType == 's') {
$seriesVal[$pointVal] = (string) $seriesValue->v;
- } elseif ($seriesValue->v === ExcelError::NA()) {
+ } elseif ((string) $seriesValue->v === ExcelError::NA()) {
$seriesVal[$pointVal] = null;
} else {
$seriesVal[$pointVal] = (float) $seriesValue->v;
@@ -431,7 +550,7 @@ class Chart
];
}
- private static function chartDataSeriesValuesMultiLevel($seriesValueSet, $dataType = 'n')
+ private function chartDataSeriesValuesMultiLevel(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array
{
$seriesVal = [];
$formatCode = '';
@@ -452,7 +571,7 @@ class Chart
$pointVal = self::getAttribute($seriesValue, 'idx', 'integer');
if ($dataType == 's') {
$seriesVal[$pointVal][] = (string) $seriesValue->v;
- } elseif ($seriesValue->v === ExcelError::NA()) {
+ } elseif ((string) $seriesValue->v === ExcelError::NA()) {
$seriesVal[$pointVal] = null;
} else {
$seriesVal[$pointVal][] = (float) $seriesValue->v;
@@ -470,74 +589,227 @@ class Chart
];
}
- private static function parseRichText(SimpleXMLElement $titleDetailPart)
+ private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
{
$value = new RichText();
$objText = null;
- foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) {
- if (isset($titleDetailElement->t)) {
- $objText = $value->createTextRun((string) $titleDetailElement->t);
+ $defaultFontSize = null;
+ $defaultBold = null;
+ $defaultItalic = null;
+ $defaultUnderscore = null;
+ $defaultStrikethrough = null;
+ $defaultBaseline = null;
+ $defaultFontName = null;
+ $defaultLatin = null;
+ $defaultEastAsian = null;
+ $defaultComplexScript = null;
+ $defaultColor = null;
+ if (isset($titleDetailPart->pPr->defRPr)) {
+ /** @var ?int */
+ $defaultFontSize = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer');
+ /** @var ?bool */
+ $defaultBold = self::getAttribute($titleDetailPart->pPr->defRPr, 'b', 'boolean');
+ /** @var ?bool */
+ $defaultItalic = self::getAttribute($titleDetailPart->pPr->defRPr, 'i', 'boolean');
+ /** @var ?string */
+ $defaultUnderscore = self::getAttribute($titleDetailPart->pPr->defRPr, 'u', 'string');
+ /** @var ?string */
+ $defaultStrikethrough = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string');
+ /** @var ?int */
+ $defaultBaseline = self::getAttribute($titleDetailPart->pPr->defRPr, 'baseline', 'integer');
+ if (isset($titleDetailPart->defRPr->rFont['val'])) {
+ $defaultFontName = (string) $titleDetailPart->defRPr->rFont['val'];
}
+ if (isset($titleDetailPart->pPr->defRPr->latin)) {
+ /** @var ?string */
+ $defaultLatin = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string');
+ }
+ if (isset($titleDetailPart->pPr->defRPr->ea)) {
+ /** @var ?string */
+ $defaultEastAsian = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string');
+ }
+ if (isset($titleDetailPart->pPr->defRPr->cs)) {
+ /** @var ?string */
+ $defaultComplexScript = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string');
+ }
+ if (isset($titleDetailPart->pPr->defRPr->solidFill->srgbClr)) {
+ /** @var ?string */
+ $defaultColor = self::getAttribute($titleDetailPart->pPr->defRPr->solidFill->srgbClr, 'val', 'string');
+ }
+ }
+ foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) {
+ if (
+ (string) $titleDetailElementKey !== 'r'
+ || !isset($titleDetailElement->t)
+ ) {
+ continue;
+ }
+ $objText = $value->createTextRun((string) $titleDetailElement->t);
+ if ($objText->getFont() === null) {
+ // @codeCoverageIgnoreStart
+ continue;
+ // @codeCoverageIgnoreEnd
+ }
+ $fontSize = null;
+ $bold = null;
+ $italic = null;
+ $underscore = null;
+ $strikethrough = null;
+ $baseline = null;
+ $fontName = null;
+ $latinName = null;
+ $eastAsian = null;
+ $complexScript = null;
+ $fontColor = null;
+ $uSchemeClr = null;
if (isset($titleDetailElement->rPr)) {
+ // not used now, not sure it ever was, grandfathering
if (isset($titleDetailElement->rPr->rFont['val'])) {
- $objText->getFont()->setName((string) $titleDetailElement->rPr->rFont['val']);
- }
-
- $fontSize = (self::getAttribute($titleDetailElement->rPr, 'sz', 'integer'));
- if (is_int($fontSize)) {
- $objText->getFont()->setSize(floor($fontSize / 100));
- }
-
- $fontColor = (self::getAttribute($titleDetailElement->rPr, 'color', 'string'));
- if ($fontColor !== null) {
- $objText->getFont()->setColor(new Color(self::readColor($fontColor)));
+ // @codeCoverageIgnoreStart
+ $fontName = (string) $titleDetailElement->rPr->rFont['val'];
+ // @codeCoverageIgnoreEnd
+ }
+ if (isset($titleDetailElement->rPr->latin)) {
+ /** @var ?string */
+ $latinName = self::getAttribute($titleDetailElement->rPr->latin, 'typeface', 'string');
+ }
+ if (isset($titleDetailElement->rPr->ea)) {
+ /** @var ?string */
+ $eastAsian = self::getAttribute($titleDetailElement->rPr->ea, 'typeface', 'string');
+ }
+ if (isset($titleDetailElement->rPr->cs)) {
+ /** @var ?string */
+ $complexScript = self::getAttribute($titleDetailElement->rPr->cs, 'typeface', 'string');
+ }
+ /** @var ?int */
+ $fontSize = self::getAttribute($titleDetailElement->rPr, 'sz', 'integer');
+
+ // not used now, not sure it ever was, grandfathering
+ /** @var ?string */
+ $fontColor = self::getAttribute($titleDetailElement->rPr, 'color', 'string');
+ if (isset($titleDetailElement->rPr->solidFill->srgbClr)) {
+ /** @var ?string */
+ $fontColor = self::getAttribute($titleDetailElement->rPr->solidFill->srgbClr, 'val', 'string');
}
+ /** @var ?bool */
$bold = self::getAttribute($titleDetailElement->rPr, 'b', 'boolean');
- if ($bold !== null) {
- $objText->getFont()->setBold($bold);
- }
+ /** @var ?bool */
$italic = self::getAttribute($titleDetailElement->rPr, 'i', 'boolean');
- if ($italic !== null) {
- $objText->getFont()->setItalic($italic);
- }
+ /** @var ?int */
$baseline = self::getAttribute($titleDetailElement->rPr, 'baseline', 'integer');
- if ($baseline !== null) {
- if ($baseline > 0) {
- $objText->getFont()->setSuperscript(true);
- } elseif ($baseline < 0) {
- $objText->getFont()->setSubscript(true);
- }
+
+ /** @var ?string */
+ $underscore = self::getAttribute($titleDetailElement->rPr, 'u', 'string');
+ if (isset($titleDetailElement->rPr->uFill->solidFill->schemeClr)) {
+ /** @var ?string */
+ $uSchemeClr = self::getAttribute($titleDetailElement->rPr->uFill->solidFill->schemeClr, 'val', 'string');
}
- $underscore = (self::getAttribute($titleDetailElement->rPr, 'u', 'string'));
- if ($underscore !== null) {
- if ($underscore == 'sng') {
- $objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
- } elseif ($underscore == 'dbl') {
- $objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE);
- } else {
- $objText->getFont()->setUnderline(Font::UNDERLINE_NONE);
- }
- }
+ /** @var ?string */
+ $strikethrough = self::getAttribute($titleDetailElement->rPr, 'strike', 'string');
+ }
- $strikethrough = (self::getAttribute($titleDetailElement->rPr, 's', 'string'));
- if ($strikethrough !== null) {
- if ($strikethrough == 'noStrike') {
- $objText->getFont()->setStrikethrough(false);
- } else {
- $objText->getFont()->setStrikethrough(true);
- }
+ $fontFound = false;
+ $latinName = $latinName ?? $defaultLatin;
+ if ($latinName !== null) {
+ $objText->getFont()->setLatin($latinName);
+ $fontFound = true;
+ }
+ $eastAsian = $eastAsian ?? $defaultEastAsian;
+ if ($eastAsian !== null) {
+ $objText->getFont()->setEastAsian($eastAsian);
+ $fontFound = true;
+ }
+ $complexScript = $complexScript ?? $defaultComplexScript;
+ if ($complexScript !== null) {
+ $objText->getFont()->setComplexScript($complexScript);
+ $fontFound = true;
+ }
+ $fontName = $fontName ?? $defaultFontName;
+ if ($fontName !== null) {
+ // @codeCoverageIgnoreStart
+ $objText->getFont()->setName($fontName);
+ $fontFound = true;
+ // @codeCoverageIgnoreEnd
+ }
+
+ $fontSize = $fontSize ?? $defaultFontSize;
+ if (is_int($fontSize)) {
+ $objText->getFont()->setSize(floor($fontSize / 100));
+ $fontFound = true;
+ }
+
+ $fontColor = $fontColor ?? $defaultColor;
+ if ($fontColor !== null) {
+ $objText->getFont()->setColor(new Color($fontColor));
+ $fontFound = true;
+ }
+
+ $bold = $bold ?? $defaultBold;
+ if ($bold !== null) {
+ $objText->getFont()->setBold($bold);
+ $fontFound = true;
+ }
+
+ $italic = $italic ?? $defaultItalic;
+ if ($italic !== null) {
+ $objText->getFont()->setItalic($italic);
+ $fontFound = true;
+ }
+
+ $baseline = $baseline ?? $defaultBaseline;
+ if ($baseline !== null) {
+ $objText->getFont()->setBaseLine($baseline);
+ if ($baseline > 0) {
+ $objText->getFont()->setSuperscript(true);
+ } elseif ($baseline < 0) {
+ $objText->getFont()->setSubscript(true);
}
+ $fontFound = true;
+ }
+
+ $underscore = $underscore ?? $defaultUnderscore;
+ if ($underscore !== null) {
+ if ($underscore == 'sng') {
+ $objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
+ } elseif ($underscore == 'dbl') {
+ $objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE);
+ } elseif ($underscore !== '') {
+ $objText->getFont()->setUnderline($underscore);
+ } else {
+ $objText->getFont()->setUnderline(Font::UNDERLINE_NONE);
+ }
+ $fontFound = true;
+ if ($uSchemeClr) {
+ $objText->getFont()->setUSchemeClr($uSchemeClr);
+ }
+ }
+
+ $strikethrough = $strikethrough ?? $defaultStrikethrough;
+ if ($strikethrough !== null) {
+ $objText->getFont()->setStrikeType($strikethrough);
+ if ($strikethrough == 'noStrike') {
+ $objText->getFont()->setStrikethrough(false);
+ } else {
+ $objText->getFont()->setStrikethrough(true);
+ }
+ $fontFound = true;
+ }
+ if ($fontFound === false) {
+ $objText->setFont(null);
}
}
return $value;
}
- private static function readChartAttributes($chartDetail)
+ /**
+ * @param null|Layout|SimpleXMLElement $chartDetail
+ */
+ private function readChartAttributes($chartDetail): array
{
$plotAttributes = [];
if (isset($chartDetail->dLbls)) {
@@ -570,7 +842,7 @@ class Chart
/**
* @param mixed $plotAttributes
*/
- private static function setChartAttributes(Layout $plotArea, $plotAttributes): void
+ private function setChartAttributes(Layout $plotArea, $plotAttributes): void
{
foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) {
switch ($plotAttributeKey) {
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php b/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php
index c0713ae4..57a88bb0 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php
@@ -50,6 +50,8 @@ class Namespaces
const WORKSHEET = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet';
+ const CHARTSHEET = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet';
+
const SCHEMA_MICROSOFT = 'http://schemas.microsoft.com/office/2006/relationships';
const EXTENSIBILITY = 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility';
@@ -74,5 +76,7 @@ class Namespaces
const PURL_DRAWING = 'http://purl.oclc.org/ooxml/drawingml/main';
+ const PURL_CHART = 'http://purl.oclc.org/ooxml/drawingml/chart';
+
const PURL_WORKSHEET = 'http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet';
}
diff --git a/src/PhpSpreadsheet/Reader/Xml.php b/src/PhpSpreadsheet/Reader/Xml.php
index bac40961..0b5e0966 100644
--- a/src/PhpSpreadsheet/Reader/Xml.php
+++ b/src/PhpSpreadsheet/Reader/Xml.php
@@ -90,9 +90,9 @@ class Xml extends BaseReader
// Retrieve charset encoding
if (preg_match('//m', $data, $matches)) {
$charSet = strtoupper($matches[1]);
- if (1 == preg_match('/^ISO-8859-\d[\dL]?$/i', $charSet)) {
+ if (preg_match('/^ISO-8859-\d[\dL]?$/i', $charSet) === 1) {
$data = StringHelper::convertEncoding($data, 'UTF-8', $charSet);
- $data = preg_replace('/()/um', '$1' . 'UTF-8' . '$2', $data, 1);
+ $data = (string) preg_replace('/()/um', '$1' . 'UTF-8' . '$2', $data, 1);
}
}
$this->fileContents = $data;
diff --git a/src/PhpSpreadsheet/Reader/Xml/Properties.php b/src/PhpSpreadsheet/Reader/Xml/Properties.php
index 1c3e421a..9e10526e 100644
--- a/src/PhpSpreadsheet/Reader/Xml/Properties.php
+++ b/src/PhpSpreadsheet/Reader/Xml/Properties.php
@@ -44,7 +44,7 @@ class Properties
foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) {
$propertyAttributes = self::getAttributes($propertyValue, $namespaces['dt']);
- $propertyName = preg_replace_callback('/_x([0-9a-f]{4})_/i', [$this, 'hex2str'], $propertyName);
+ $propertyName = (string) preg_replace_callback('/_x([0-9a-f]{4})_/i', [$this, 'hex2str'], $propertyName);
$this->processCustomProperty($docProps, $propertyName, $propertyValue, $propertyAttributes);
}
diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php
index 4a6ee039..046c5894 100644
--- a/src/PhpSpreadsheet/ReferenceHelper.php
+++ b/src/PhpSpreadsheet/ReferenceHelper.php
@@ -687,7 +687,7 @@ class ReferenceHelper
ksort($cellTokens);
ksort($newCellTokens);
} // Update cell references in the formula
- $formulaBlock = str_replace('\\', '', preg_replace($cellTokens, $newCellTokens, $formulaBlock));
+ $formulaBlock = str_replace('\\', '', (string) preg_replace($cellTokens, $newCellTokens, $formulaBlock));
}
}
}
diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php
index 7d520120..e0dc0efb 100644
--- a/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php
+++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php
@@ -219,7 +219,7 @@ class CellMatcher
foreach ($splitCondition as &$value) {
// Only count/replace in alternating array entries (ie. not in quoted strings)
if ($i = !$i) {
- $value = preg_replace_callback(
+ $value = (string) preg_replace_callback(
'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i',
[$this, 'conditionCellAdjustment'],
$value
@@ -287,7 +287,7 @@ class CellMatcher
$conditions = $this->adjustConditionsForCellReferences($conditional->getConditions());
$expression = array_pop($conditions);
- $expression = preg_replace(
+ $expression = (string) preg_replace(
'/\b' . $this->referenceCell . '\b/i',
(string) $this->wrapCellValue(),
$expression
diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php
index df9daab3..3eb7d54e 100644
--- a/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php
+++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php
@@ -133,7 +133,7 @@ abstract class WizardAbstract
foreach ($splitCondition as &$value) {
// Only count/replace in alternating array entries (ie. not in quoted strings)
if ($i = !$i) {
- $value = preg_replace_callback(
+ $value = (string) preg_replace_callback(
'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i',
function ($matches) use ($referenceColumnIndex, $referenceRow) {
return self::reverseCellAdjustment($matches, $referenceColumnIndex, $referenceRow);
@@ -174,7 +174,7 @@ abstract class WizardAbstract
foreach ($splitCondition as &$value) {
// Only count/replace in alternating array entries (ie. not in quoted strings)
if ($i = !$i) {
- $value = preg_replace_callback(
+ $value = (string) preg_replace_callback(
'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i',
[$this, 'conditionCellAdjustment'],
$value
diff --git a/src/PhpSpreadsheet/Style/Font.php b/src/PhpSpreadsheet/Style/Font.php
index 13fe2b67..e5b056c9 100644
--- a/src/PhpSpreadsheet/Style/Font.php
+++ b/src/PhpSpreadsheet/Style/Font.php
@@ -18,6 +18,29 @@ class Font extends Supervisor
*/
protected $name = 'Calibri';
+ /**
+ * The following 6 are used only for chart titles, I think.
+ *
+ *@var string
+ */
+ private $latin = '';
+
+ /** @var string */
+ private $eastAsian = '';
+
+ /** @var string */
+ private $complexScript = '';
+
+ /** @var int */
+ private $baseLine = 0;
+
+ /** @var string */
+ private $strikeType = '';
+
+ /** @var string */
+ private $uSchemeClr = '';
+ // end of chart title items
+
/**
* Font Size.
*
@@ -170,6 +193,15 @@ class Font extends Supervisor
if (isset($styleArray['name'])) {
$this->setName($styleArray['name']);
}
+ if (isset($styleArray['latin'])) {
+ $this->setLatin($styleArray['latin']);
+ }
+ if (isset($styleArray['eastAsian'])) {
+ $this->setEastAsian($styleArray['eastAsian']);
+ }
+ if (isset($styleArray['complexScript'])) {
+ $this->setComplexScript($styleArray['complexScript']);
+ }
if (isset($styleArray['bold'])) {
$this->setBold($styleArray['bold']);
}
@@ -213,6 +245,33 @@ class Font extends Supervisor
return $this->name;
}
+ public function getLatin(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getLatin();
+ }
+
+ return $this->latin;
+ }
+
+ public function getEastAsian(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getEastAsian();
+ }
+
+ return $this->eastAsian;
+ }
+
+ public function getComplexScript(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getComplexScript();
+ }
+
+ return $this->complexScript;
+ }
+
/**
* Set Name.
*
@@ -235,6 +294,51 @@ class Font extends Supervisor
return $this;
}
+ public function setLatin(string $fontname): self
+ {
+ if ($fontname == '') {
+ $fontname = 'Calibri';
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['latin' => $fontname]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->latin = $fontname;
+ }
+
+ return $this;
+ }
+
+ public function setEastAsian(string $fontname): self
+ {
+ if ($fontname == '') {
+ $fontname = 'Calibri';
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['eastAsian' => $fontname]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->eastAsian = $fontname;
+ }
+
+ return $this;
+ }
+
+ public function setComplexScript(string $fontname): self
+ {
+ if ($fontname == '') {
+ $fontname = 'Calibri';
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['complexScript' => $fontname]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->complexScript = $fontname;
+ }
+
+ return $this;
+ }
+
/**
* Get Size.
*
@@ -418,6 +522,69 @@ class Font extends Supervisor
return $this;
}
+ public function getBaseLine(): int
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBaseLine();
+ }
+
+ return $this->baseLine;
+ }
+
+ public function setBaseLine(int $baseLine): self
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['baseLine' => $baseLine]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->baseLine = $baseLine;
+ }
+
+ return $this;
+ }
+
+ public function getStrikeType(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getStrikeType();
+ }
+
+ return $this->strikeType;
+ }
+
+ public function setStrikeType(string $strikeType): self
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['strikeType' => $strikeType]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->strikeType = $strikeType;
+ }
+
+ return $this;
+ }
+
+ public function getUSchemeClr(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getUSchemeClr();
+ }
+
+ return $this->uSchemeClr;
+ }
+
+ public function setUSchemeClr(string $uSchemeClr): self
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['uSchemeClr' => $uSchemeClr]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->uSchemeClr = $uSchemeClr;
+ }
+
+ return $this;
+ }
+
/**
* Get Underline.
*
@@ -546,6 +713,15 @@ class Font extends Supervisor
$this->underline .
($this->strikethrough ? 't' : 'f') .
$this->color->getHashCode() .
+ '*' .
+ $this->latin .
+ '*' .
+ $this->eastAsian .
+ '*' .
+ $this->complexScript .
+ $this->strikeType .
+ $this->uSchemeClr .
+ (string) $this->baseLine .
__CLASS__
);
}
@@ -553,12 +729,17 @@ class Font extends Supervisor
protected function exportArray1(): array
{
$exportedArray = [];
+ $this->exportArray2($exportedArray, 'baseLine', $this->getBaseLine());
$this->exportArray2($exportedArray, 'bold', $this->getBold());
$this->exportArray2($exportedArray, 'color', $this->getColor());
+ $this->exportArray2($exportedArray, 'complexScript', $this->getComplexScript());
+ $this->exportArray2($exportedArray, 'eastAsian', $this->getEastAsian());
$this->exportArray2($exportedArray, 'italic', $this->getItalic());
+ $this->exportArray2($exportedArray, 'latin', $this->getLatin());
$this->exportArray2($exportedArray, 'name', $this->getName());
$this->exportArray2($exportedArray, 'size', $this->getSize());
$this->exportArray2($exportedArray, 'strikethrough', $this->getStrikethrough());
+ $this->exportArray2($exportedArray, 'strikeType', $this->getStrikeType());
$this->exportArray2($exportedArray, 'subscript', $this->getSubscript());
$this->exportArray2($exportedArray, 'superscript', $this->getSuperscript());
$this->exportArray2($exportedArray, 'underline', $this->getUnderline());
diff --git a/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php b/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php
index b8e14b8b..6c4d9d6b 100644
--- a/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php
+++ b/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php
@@ -129,11 +129,10 @@ class DateFormatter
// but we don't want to change any quoted strings
/** @var callable */
$callable = [self::class, 'setLowercaseCallback'];
- $format = preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', $callable, $format);
+ $format = (string) preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', $callable, $format);
// Only process the non-quoted blocks for date format characters
- /** @phpstan-ignore-next-line */
$blocks = explode('"', $format);
foreach ($blocks as $key => &$block) {
if ($key % 2 == 0) {
diff --git a/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php b/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
index 01407e64..3e4bdc46 100644
--- a/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
+++ b/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
@@ -51,12 +51,12 @@ class Formatter
for ($idx = 0; $idx < $cnt; ++$idx) {
if (preg_match($color_regex, $sections[$idx], $matches)) {
$colors[$idx] = $matches[0];
- $sections[$idx] = preg_replace($color_regex, '', $sections[$idx]);
+ $sections[$idx] = (string) preg_replace($color_regex, '', $sections[$idx]);
}
if (preg_match($cond_regex, $sections[$idx], $matches)) {
$condops[$idx] = $matches[1];
$condvals[$idx] = $matches[2];
- $sections[$idx] = preg_replace($cond_regex, '', $sections[$idx]);
+ $sections[$idx] = (string) preg_replace($cond_regex, '', $sections[$idx]);
}
}
$color = $colors[0];
@@ -112,7 +112,7 @@ class Formatter
return $value;
}
- $format = preg_replace_callback(
+ $format = (string) preg_replace_callback(
'/(["])(?:(?=(\\\\?))\\2.)*?\\1/u',
function ($matches) {
return str_replace('.', chr(0x00), $matches[0]);
@@ -121,7 +121,7 @@ class Formatter
);
// Convert any other escaped characters to quoted strings, e.g. (\T to "T")
- $format = preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/ui', '"${2}"', $format);
+ $format = (string) preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/ui', '"${2}"', $format);
// Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal)
$sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format);
@@ -130,7 +130,7 @@ class Formatter
// In Excel formats, "_" is used to add spacing,
// The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space
- $format = preg_replace('/_.?/ui', ' ', $format);
+ $format = (string) preg_replace('/_.?/ui', ' ', $format);
// Let's begin inspecting the format and converting the value to a formatted string
diff --git a/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php b/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php
index 334c40df..f4d3412b 100644
--- a/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php
+++ b/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php
@@ -35,7 +35,7 @@ class PercentageFormatter extends BaseFormatter
$wholePartSize += $decimalPartSize;
$replacement = "{$wholePartSize}.{$decimalPartSize}";
- $mask = preg_replace('/[#0,]+\.?[?#0,]*/ui', "%{$replacement}f{$placeHolders}", $format);
+ $mask = (string) preg_replace('/[#0,]+\.?[?#0,]*/ui', "%{$replacement}f{$placeHolders}", $format);
/** @var float */
$valueFloat = $value;
diff --git a/src/PhpSpreadsheet/Worksheet/AutoFilter.php b/src/PhpSpreadsheet/Worksheet/AutoFilter.php
index dd33d5d5..05b2e9a0 100644
--- a/src/PhpSpreadsheet/Worksheet/AutoFilter.php
+++ b/src/PhpSpreadsheet/Worksheet/AutoFilter.php
@@ -145,7 +145,7 @@ class AutoFilter
$this->evaluated = false;
if ($this->workSheet !== null) {
$thisrange = $this->range;
- $range = preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange) ?? '';
+ $range = (string) preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange);
if ($range !== $thisrange) {
$this->setRange($range);
}
diff --git a/src/PhpSpreadsheet/Worksheet/Table.php b/src/PhpSpreadsheet/Worksheet/Table.php
index 66839d41..ffdbf9a7 100644
--- a/src/PhpSpreadsheet/Worksheet/Table.php
+++ b/src/PhpSpreadsheet/Worksheet/Table.php
@@ -212,7 +212,7 @@ class Table
{
if ($this->workSheet !== null) {
$thisrange = $this->range;
- $range = preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange) ?? '';
+ $range = (string) preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange);
if ($range !== $thisrange) {
$this->setRange($range);
}
diff --git a/src/PhpSpreadsheet/Worksheet/Validations.php b/src/PhpSpreadsheet/Worksheet/Validations.php
index cd76d2a4..a56dda4c 100644
--- a/src/PhpSpreadsheet/Worksheet/Validations.php
+++ b/src/PhpSpreadsheet/Worksheet/Validations.php
@@ -42,10 +42,9 @@ class Validations
public static function validateCellOrCellRange($cellRange): string
{
if (is_string($cellRange) || is_numeric($cellRange)) {
- // Convert a single column reference like 'A' to 'A:A'
- $cellRange = (string) preg_replace('/^([A-Z]+)$/', '${1}:${1}', (string) $cellRange);
- // Convert a single row reference like '1' to '1:1'
- $cellRange = (string) preg_replace('/^(\d+)$/', '${1}:${1}', $cellRange);
+ // Convert a single column reference like 'A' to 'A:A',
+ // a single row reference like '1' to '1:1'
+ $cellRange = (string) preg_replace('/^([A-Z]+|\d+)$/', '${1}:${1}', (string) $cellRange);
} elseif (is_object($cellRange) && $cellRange instanceof CellAddress) {
$cellRange = new CellRange($cellRange, $cellRange);
}
@@ -66,9 +65,12 @@ class Validations
[$worksheet, $addressRange] = Worksheet::extractSheetTitle($cellRange, true);
// Convert Column ranges like 'A:C' to 'A1:C1048576'
- $addressRange = (string) preg_replace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $addressRange);
- // Convert Row ranges like '1:3' to 'A1:XFD3'
- $addressRange = (string) preg_replace('/^(\\d+):(\\d+)$/', 'A${1}:XFD${2}', $addressRange);
+ // or Row ranges like '1:3' to 'A1:XFD3'
+ $addressRange = (string) preg_replace(
+ ['/^([A-Z]+):([A-Z]+)$/i', '/^(\\d+):(\\d+)$/'],
+ ['${1}1:${2}1048576', 'A${1}:XFD${2}'],
+ $addressRange
+ );
return empty($worksheet) ? strtoupper($addressRange) : $worksheet . '!' . strtoupper($addressRange);
}
diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php
index 45d4ce85..3ef65928 100644
--- a/src/PhpSpreadsheet/Writer/Html.php
+++ b/src/PhpSpreadsheet/Writer/Html.php
@@ -662,13 +662,13 @@ class Html extends BaseWriter
$filename = $drawing->getPath();
// Strip off eventual '.'
- $filename = preg_replace('/^[.]/', '', $filename);
+ $filename = (string) preg_replace('/^[.]/', '', $filename);
// Prepend images root
$filename = $this->getImagesRoot() . $filename;
// Strip off eventual '.' if followed by non-/
- $filename = preg_replace('@^[.]([^/])@', '$1', $filename);
+ $filename = (string) preg_replace('@^[.]([^/])@', '$1', $filename);
// Convert UTF8 data to PCDATA
$filename = htmlspecialchars($filename, Settings::htmlEntityFlags());
@@ -1326,7 +1326,7 @@ class Html extends BaseWriter
// Converts the cell content so that spaces occuring at beginning of each new line are replaced by
// Example: " Hello\n to the world" is converted to " Hello\n to the world"
- $cellData = preg_replace('/(?m)(?:^|\\G) /', ' ', $cellData);
+ $cellData = (string) preg_replace('/(?m)(?:^|\\G) /', ' ', $cellData);
// convert newline "\n" to '
'
$cellData = nl2br($cellData);
diff --git a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php
index 66194468..1bf2c463 100644
--- a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php
+++ b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php
@@ -10,12 +10,14 @@ use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Style\Style as CellStyle;
use PhpOffice\PhpSpreadsheet\Worksheet\ColumnDimension;
use PhpOffice\PhpSpreadsheet\Worksheet\RowDimension;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Style
{
public const CELL_STYLE_PREFIX = 'ce';
public const COLUMN_STYLE_PREFIX = 'co';
public const ROW_STYLE_PREFIX = 'ro';
+ public const TABLE_STYLE_PREFIX = 'ta';
private $writer;
@@ -221,6 +223,26 @@ class Style
$this->writer->endElement(); // Close style:style
}
+ public function writeTableStyle(Worksheet $worksheet, int $sheetId): void
+ {
+ $this->writer->startElement('style:style');
+ $this->writer->writeAttribute('style:family', 'table');
+ $this->writer->writeAttribute(
+ 'style:name',
+ sprintf('%s%d', self::TABLE_STYLE_PREFIX, $sheetId)
+ );
+
+ $this->writer->startElement('style:table-properties');
+
+ $this->writer->writeAttribute(
+ 'table:display',
+ $worksheet->getSheetState() === Worksheet::SHEETSTATE_VISIBLE ? 'true' : 'false'
+ );
+
+ $this->writer->endElement(); // Close style:table-properties
+ $this->writer->endElement(); // Close style:style
+ }
+
public function write(CellStyle $style): void
{
$this->writer->startElement('style:style');
diff --git a/src/PhpSpreadsheet/Writer/Ods/Content.php b/src/PhpSpreadsheet/Writer/Ods/Content.php
index a8da1019..00ab0643 100644
--- a/src/PhpSpreadsheet/Writer/Ods/Content.php
+++ b/src/PhpSpreadsheet/Writer/Ods/Content.php
@@ -123,6 +123,7 @@ class Content extends WriterPart
for ($sheetIndex = 0; $sheetIndex < $sheetCount; ++$sheetIndex) {
$objWriter->startElement('table:table');
$objWriter->writeAttribute('table:name', $spreadsheet->getSheet($sheetIndex)->getTitle());
+ $objWriter->writeAttribute('table:style-name', Style::TABLE_STYLE_PREFIX . (string) ($sheetIndex + 1));
$objWriter->writeElement('office:forms');
foreach ($spreadsheet->getSheet($sheetIndex)->getColumnDimensions() as $columnDimension) {
$objWriter->startElement('table:table-column');
@@ -289,6 +290,8 @@ class Content extends WriterPart
$sheetCount = $spreadsheet->getSheetCount();
for ($i = 0; $i < $sheetCount; ++$i) {
$worksheet = $spreadsheet->getSheet($i);
+ $styleWriter->writeTableStyle($worksheet, $i + 1);
+
$worksheet->calculateColumnWidths();
foreach ($worksheet->getColumnDimensions() as $columnDimension) {
if ($columnDimension->getWidth() !== -1.0) {
diff --git a/src/PhpSpreadsheet/Writer/Xls/Parser.php b/src/PhpSpreadsheet/Writer/Xls/Parser.php
index 4033fd53..2f75f908 100644
--- a/src/PhpSpreadsheet/Writer/Xls/Parser.php
+++ b/src/PhpSpreadsheet/Writer/Xls/Parser.php
@@ -778,8 +778,7 @@ class Parser
*/
private function getRefIndex($ext_ref)
{
- $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading ' if any.
- $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
+ $ext_ref = (string) preg_replace(["/^'/", "/'$/"], ['', ''], $ext_ref); // Remove leading and trailing ' if any.
$ext_ref = str_replace('\'\'', '\'', $ext_ref); // Replace escaped '' with '
// Check if there is a sheet range eg., Sheet1:Sheet2.
diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
index 74e145cc..37865518 100644
--- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
+++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
@@ -1039,7 +1039,7 @@ class Worksheet extends BIFFwriter
$record = 0x01B8; // Record identifier
// Strip URL type
- $url = preg_replace('/^internal:/', '', $url);
+ $url = (string) preg_replace('/^internal:/', '', $url);
// Pack the undocumented parts of the hyperlink stream
$unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
@@ -1095,8 +1095,7 @@ class Worksheet extends BIFFwriter
// Strip URL type and change Unix dir separator to Dos style (if needed)
//
- $url = preg_replace('/^external:/', '', $url);
- $url = preg_replace('/\//', '\\', $url);
+ $url = (string) preg_replace(['/^external:/', '/\//'], ['', '\\'], $url);
// Determine if the link is relative or absolute:
// relative if link contains no dir separator, "somefile.xls"
@@ -1125,7 +1124,7 @@ class Worksheet extends BIFFwriter
$up_count = pack('v', $up_count);
// Store the short dos dir name (null terminated)
- $dir_short = preg_replace('/\\.\\.\\\\/', '', $dir_long) . "\0";
+ $dir_short = (string) preg_replace('/\\.\\.\\\\/', '', $dir_long) . "\0";
// Store the long dir name as a wchar string (non-null terminated)
$dir_long = $dir_long . "\0";
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
index ba7a6545..08d578e6 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
@@ -10,7 +10,6 @@ use PhpOffice\PhpSpreadsheet\Chart\Layout;
use PhpOffice\PhpSpreadsheet\Chart\Legend;
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
use PhpOffice\PhpSpreadsheet\Chart\Title;
-use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
@@ -75,6 +74,33 @@ class Chart extends WriterPart
$objWriter->writeAttribute('val', 0);
$objWriter->endElement();
+ $objWriter->startElement('c:view3D');
+ $rotX = $chart->getRotX();
+ if (is_int($rotX)) {
+ $objWriter->startElement('c:rotX');
+ $objWriter->writeAttribute('val', "$rotX");
+ $objWriter->endElement();
+ }
+ $rotY = $chart->getRotY();
+ if (is_int($rotY)) {
+ $objWriter->startElement('c:rotY');
+ $objWriter->writeAttribute('val', "$rotY");
+ $objWriter->endElement();
+ }
+ $rAngAx = $chart->getRAngAx();
+ if (is_int($rAngAx)) {
+ $objWriter->startElement('c:rAngAx');
+ $objWriter->writeAttribute('val', "$rAngAx");
+ $objWriter->endElement();
+ }
+ $perspective = $chart->getPerspective();
+ if (is_int($perspective)) {
+ $objWriter->startElement('c:perspective');
+ $objWriter->writeAttribute('val', "$perspective");
+ $objWriter->endElement();
+ }
+ $objWriter->endElement(); // view3D
+
$this->writePlotArea($objWriter, $chart->getPlotArea(), $chart->getXAxisLabel(), $chart->getYAxisLabel(), $chart->getChartAxisX(), $chart->getChartAxisY(), $chart->getMajorGridlines(), $chart->getMinorGridlines());
$this->writeLegend($objWriter, $chart->getLegend());
@@ -121,6 +147,10 @@ class Chart extends WriterPart
$objWriter->endElement();
$objWriter->startElement('a:p');
+ $objWriter->startElement('a:pPr');
+ $objWriter->startElement('a:defRPr');
+ $objWriter->endElement();
+ $objWriter->endElement();
$caption = $title->getCaption();
if ((is_array($caption)) && (count($caption) > 0)) {
@@ -196,7 +226,7 @@ class Chart extends WriterPart
return;
}
- $id1 = $id2 = 0;
+ $id1 = $id2 = $id3 = '0';
$this->seriesIndex = 0;
$objWriter->startElement('c:plotArea');
@@ -226,6 +256,10 @@ class Chart extends WriterPart
$objWriter->startElement('c:scatterStyle');
$objWriter->writeAttribute('val', $plotStyle);
$objWriter->endElement();
+ } elseif ($groupType === DataSeries::TYPE_SURFACECHART_3D || $groupType === DataSeries::TYPE_SURFACECHART) {
+ $objWriter->startElement('c:wireframe');
+ $objWriter->writeAttribute('val', $plotStyle ? '1' : '0');
+ $objWriter->endElement();
}
$this->writePlotGroup($plotGroup, $chartType, $objWriter, $catIsMultiLevelSeries, $valIsMultiLevelSeries, $plotGroupingType);
@@ -250,9 +284,12 @@ class Chart extends WriterPart
$objWriter->endElement();
}
} elseif ($chartType === DataSeries::TYPE_BUBBLECHART) {
- $objWriter->startElement('c:bubbleScale');
- $objWriter->writeAttribute('val', 25);
- $objWriter->endElement();
+ $scale = ($plotGroup === null) ? '' : (string) $plotGroup->getPlotStyle();
+ if ($scale !== '') {
+ $objWriter->startElement('c:bubbleScale');
+ $objWriter->writeAttribute('val', $scale);
+ $objWriter->endElement();
+ }
$objWriter->startElement('c:showNegBubbles');
$objWriter->writeAttribute('val', 0);
@@ -276,9 +313,10 @@ class Chart extends WriterPart
$objWriter->endElement();
}
- // Generate 2 unique numbers to use for axId values
- $id1 = '75091328';
- $id2 = '75089408';
+ // Generate 3 unique numbers to use for axId values
+ $id1 = '110438656';
+ $id2 = '110444544';
+ $id3 = '110365312'; // used in Surface Chart
if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
$objWriter->startElement('c:axId');
@@ -287,6 +325,11 @@ class Chart extends WriterPart
$objWriter->startElement('c:axId');
$objWriter->writeAttribute('val', $id2);
$objWriter->endElement();
+ if ($chartType === DataSeries::TYPE_SURFACECHART_3D || $chartType === DataSeries::TYPE_SURFACECHART) {
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id3);
+ $objWriter->endElement();
+ }
} else {
$objWriter->startElement('c:firstSliceAng');
$objWriter->writeAttribute('val', 0);
@@ -304,12 +347,15 @@ class Chart extends WriterPart
if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
if ($chartType === DataSeries::TYPE_BUBBLECHART) {
- $this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id1, $id2, $catIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines);
+ $this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id2, $id1, $catIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines);
} else {
$this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $xAxis);
}
$this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $yAxis, $majorGridlines, $minorGridlines);
+ if ($chartType === DataSeries::TYPE_SURFACECHART_3D || $chartType === DataSeries::TYPE_SURFACECHART) {
+ $this->writeSerAxis($objWriter, $id2, $id3);
+ }
}
$objWriter->endElement();
@@ -369,9 +415,15 @@ class Chart extends WriterPart
*/
private function writeCategoryAxis(XMLWriter $objWriter, ?Title $xAxisLabel, $id1, $id2, $isMultiLevelSeries, Axis $yAxis): void
{
- $objWriter->startElement('c:catAx');
+ // N.B. writeCategoryAxis may be invoked with the last parameter($yAxis) using $xAxis for ScatterChart, etc
+ // In that case, xAxis is NOT a category.
+ if ($yAxis->getAxisIsNumericFormat()) {
+ $objWriter->startElement('c:valAx');
+ } else {
+ $objWriter->startElement('c:catAx');
+ }
- if ($id1 > 0) {
+ if ($id1 !== '0') {
$objWriter->startElement('c:axId');
$objWriter->writeAttribute('val', $id1);
$objWriter->endElement();
@@ -403,20 +455,20 @@ class Chart extends WriterPart
$objWriter->endElement();
$objWriter->startElement('a:p');
- $objWriter->startElement('a:r');
+ $objWriter->startElement('a:pPr');
+ $objWriter->startElement('a:defRPr');
+ $objWriter->endElement();
+ $objWriter->endElement();
$caption = $xAxisLabel->getCaption();
if (is_array($caption)) {
$caption = $caption[0];
}
- $objWriter->startElement('a:t');
- $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption));
- $objWriter->endElement();
+ $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a');
$objWriter->endElement();
$objWriter->endElement();
$objWriter->endElement();
- $objWriter->endElement();
$layout = $xAxisLabel->getLayout();
$this->writeLayout($objWriter, $layout);
@@ -445,7 +497,7 @@ class Chart extends WriterPart
$objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('axis_labels'));
$objWriter->endElement();
- if ($id2 > 0) {
+ if ($id2 !== '0') {
$objWriter->startElement('c:crossAx');
$objWriter->writeAttribute('val', $id2);
$objWriter->endElement();
@@ -487,7 +539,7 @@ class Chart extends WriterPart
{
$objWriter->startElement('c:valAx');
- if ($id2 > 0) {
+ if ($id2 !== '0') {
$objWriter->startElement('c:axId');
$objWriter->writeAttribute('val', $id2);
$objWriter->endElement();
@@ -746,18 +798,17 @@ class Chart extends WriterPart
$objWriter->endElement();
$objWriter->startElement('a:p');
- $objWriter->startElement('a:r');
+ $objWriter->startElement('a:pPr');
+ $objWriter->startElement('a:defRPr');
+ $objWriter->endElement();
+ $objWriter->endElement();
$caption = $yAxisLabel->getCaption();
if (is_array($caption)) {
$caption = $caption[0];
}
+ $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a');
- $objWriter->startElement('a:t');
- $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption));
- $objWriter->endElement();
-
- $objWriter->endElement();
$objWriter->endElement();
$objWriter->endElement();
$objWriter->endElement();
@@ -913,9 +964,9 @@ class Chart extends WriterPart
$objWriter->endElement(); //effectList
$objWriter->endElement(); //end spPr
- if ($id1 > 0) {
+ if ($id1 !== '0') {
$objWriter->startElement('c:crossAx');
- $objWriter->writeAttribute('val', $id2);
+ $objWriter->writeAttribute('val', $id1);
$objWriter->endElement();
if ($xAxis->getAxisOptionsProperty('horizontal_crosses_value') !== null) {
@@ -956,6 +1007,54 @@ class Chart extends WriterPart
$objWriter->endElement();
}
+ /**
+ * Write Ser Axis, for Surface chart.
+ */
+ private function writeSerAxis(XMLWriter $objWriter, string $id2, string $id3): void
+ {
+ $objWriter->startElement('c:serAx');
+
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id3);
+ $objWriter->endElement(); // axId
+
+ $objWriter->startElement('c:scaling');
+ $objWriter->startElement('c:orientation');
+ $objWriter->writeAttribute('val', 'minMax');
+ $objWriter->endElement(); // orientation
+ $objWriter->endElement(); // scaling
+
+ $objWriter->startElement('c:delete');
+ $objWriter->writeAttribute('val', '0');
+ $objWriter->endElement(); // delete
+
+ $objWriter->startElement('c:axPos');
+ $objWriter->writeAttribute('val', 'b');
+ $objWriter->endElement(); // axPos
+
+ $objWriter->startElement('c:majorTickMark');
+ $objWriter->writeAttribute('val', 'out');
+ $objWriter->endElement(); // majorTickMark
+
+ $objWriter->startElement('c:minorTickMark');
+ $objWriter->writeAttribute('val', 'none');
+ $objWriter->endElement(); // minorTickMark
+
+ $objWriter->startElement('c:tickLblPos');
+ $objWriter->writeAttribute('val', 'nextTo');
+ $objWriter->endElement(); // tickLblPos
+
+ $objWriter->startElement('c:crossAx');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement(); // crossAx
+
+ $objWriter->startElement('c:crosses');
+ $objWriter->writeAttribute('val', 'autoZero');
+ $objWriter->endElement(); // crosses
+
+ $objWriter->endElement(); //serAx
+ }
+
/**
* Get the data series type(s) for a chart plot series.
*
@@ -1016,7 +1115,7 @@ class Chart extends WriterPart
* @param bool $valIsMultiLevelSeries Is value set a multi-series set
* @param string $plotGroupingType Type of grouping for multi-series values
*/
- private function writePlotGroup(?DataSeries $plotGroup, $groupType, XMLWriter $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType): void
+ private function writePlotGroup(?DataSeries $plotGroup, string $groupType, XMLWriter $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType): void
{
if ($plotGroup === null) {
return;
@@ -1104,16 +1203,29 @@ class Chart extends WriterPart
}
// Formatting for the points
- if (($groupType == DataSeries::TYPE_LINECHART) || ($groupType == DataSeries::TYPE_STOCKCHART)) {
+ if (
+ $groupType == DataSeries::TYPE_LINECHART
+ || $groupType == DataSeries::TYPE_STOCKCHART
+ || ($groupType === DataSeries::TYPE_SCATTERCHART && $plotSeriesValues !== false && !$plotSeriesValues->getScatterLines())
+ || ($plotSeriesValues !== false && $plotSeriesValues->getSchemeClr())
+ ) {
$plotLineWidth = 12700;
if ($plotSeriesValues) {
$plotLineWidth = $plotSeriesValues->getLineWidth();
}
$objWriter->startElement('c:spPr');
+ $schemeClr = $plotLabel ? $plotLabel->getSchemeClr() : null;
+ if ($schemeClr) {
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', $schemeClr);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
$objWriter->startElement('a:ln');
$objWriter->writeAttribute('w', $plotLineWidth);
- if ($groupType == DataSeries::TYPE_STOCKCHART) {
+ if ($groupType == DataSeries::TYPE_STOCKCHART || $groupType === DataSeries::TYPE_SCATTERCHART) {
$objWriter->startElement('a:noFill');
$objWriter->endElement();
} elseif ($plotLabel) {
@@ -1142,6 +1254,16 @@ class Chart extends WriterPart
$objWriter->startElement('c:size');
$objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointSize());
$objWriter->endElement();
+ $fillColor = $plotSeriesValues->getFillColor();
+ if (is_string($fillColor) && $fillColor !== '') {
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $fillColor);
+ $objWriter->endElement(); // srgbClr
+ $objWriter->endElement(); // solidFill
+ $objWriter->endElement(); // spPr
+ }
}
$objWriter->endElement();
@@ -1176,7 +1298,14 @@ class Chart extends WriterPart
$objWriter->startElement('c:cat');
}
- $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str');
+ // xVals (Categories) are not always 'str'
+ // Test X-axis Label's Datatype to decide 'str' vs 'num'
+ $CategoryDatatype = $plotSeriesCategory->getDataType();
+ if ($CategoryDatatype == DataSeriesValues::DATASERIES_TYPE_NUMBER) {
+ $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'num');
+ } else {
+ $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str');
+ }
$objWriter->endElement();
}
@@ -1192,10 +1321,31 @@ class Chart extends WriterPart
$this->writePlotSeriesValues($plotSeriesValues, $objWriter, $groupType, 'num');
$objWriter->endElement();
+ if ($groupType === DataSeries::TYPE_SCATTERCHART && $plotGroup->getPlotStyle() === 'smoothMarker') {
+ $objWriter->startElement('c:smooth');
+ $objWriter->writeAttribute('val', '1');
+ $objWriter->endElement();
+ }
}
if ($groupType === DataSeries::TYPE_BUBBLECHART) {
- $this->writeBubbles($plotSeriesValues, $objWriter);
+ if (!empty($plotGroup->getPlotBubbleSizes()[$plotSeriesIdx])) {
+ $objWriter->startElement('c:bubbleSize');
+ $this->writePlotSeriesValues(
+ $plotGroup->getPlotBubbleSizes()[$plotSeriesIdx],
+ $objWriter,
+ $groupType,
+ 'num'
+ );
+ $objWriter->endElement();
+ if ($plotSeriesValues !== false) {
+ $objWriter->startElement('c:bubble3D');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getBubble3D() ? '1' : '0');
+ $objWriter->endElement();
+ }
+ } else {
+ $this->writeBubbles($plotSeriesValues, $objWriter);
+ }
}
$objWriter->endElement();
@@ -1289,38 +1439,43 @@ class Chart extends WriterPart
$objWriter->writeRawData($plotSeriesValues->getDataSource());
$objWriter->endElement();
- $objWriter->startElement('c:' . $dataType . 'Cache');
+ $count = $plotSeriesValues->getPointCount();
+ $source = $plotSeriesValues->getDataSource();
+ $values = $plotSeriesValues->getDataValues();
+ if ($count > 1 || ($count === 1 && "=$source" !== (string) $values[0])) {
+ $objWriter->startElement('c:' . $dataType . 'Cache');
- if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
- if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) {
- $objWriter->startElement('c:formatCode');
- $objWriter->writeRawData($plotSeriesValues->getFormatCode());
- $objWriter->endElement();
- }
- }
-
- $objWriter->startElement('c:ptCount');
- $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
- $objWriter->endElement();
-
- $dataValues = $plotSeriesValues->getDataValues();
- if (!empty($dataValues)) {
- if (is_array($dataValues)) {
- foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
- $objWriter->startElement('c:pt');
- $objWriter->writeAttribute('idx', $plotSeriesKey);
-
- $objWriter->startElement('c:v');
- $objWriter->writeRawData($plotSeriesValue);
- $objWriter->endElement();
+ if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
+ if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) {
+ $objWriter->startElement('c:formatCode');
+ $objWriter->writeRawData($plotSeriesValues->getFormatCode());
$objWriter->endElement();
}
}
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
+ $objWriter->endElement();
+
+ $dataValues = $plotSeriesValues->getDataValues();
+ if (!empty($dataValues)) {
+ if (is_array($dataValues)) {
+ foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey);
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData($plotSeriesValue);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ $objWriter->endElement(); // *Cache
}
- $objWriter->endElement();
-
- $objWriter->endElement();
+ $objWriter->endElement(); // *Ref
}
}
@@ -1362,7 +1517,7 @@ class Chart extends WriterPart
$objWriter->endElement();
$objWriter->startElement('c:bubble3D');
- $objWriter->writeAttribute('val', 0);
+ $objWriter->writeAttribute('val', $plotSeriesValues->getBubble3D() ? '1' : '0');
$objWriter->endElement();
}
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
index 816bb9d4..7693c72c 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
@@ -89,22 +89,49 @@ class Drawing extends WriterPart
$tl = $chart->getTopLeftPosition();
$tlColRow = Coordinate::indexesFromString($tl['cell']);
$br = $chart->getBottomRightPosition();
- $brColRow = Coordinate::indexesFromString($br['cell']);
- $objWriter->startElement('xdr:twoCellAnchor');
+ $isTwoCellAnchor = $br['cell'] !== '';
+ if ($isTwoCellAnchor) {
+ $brColRow = Coordinate::indexesFromString($br['cell']);
- $objWriter->startElement('xdr:from');
- $objWriter->writeElement('xdr:col', (string) ($tlColRow[0] - 1));
- $objWriter->writeElement('xdr:colOff', self::stringEmu($tl['xOffset']));
- $objWriter->writeElement('xdr:row', (string) ($tlColRow[1] - 1));
- $objWriter->writeElement('xdr:rowOff', self::stringEmu($tl['yOffset']));
- $objWriter->endElement();
- $objWriter->startElement('xdr:to');
- $objWriter->writeElement('xdr:col', (string) ($brColRow[0] - 1));
- $objWriter->writeElement('xdr:colOff', self::stringEmu($br['xOffset']));
- $objWriter->writeElement('xdr:row', (string) ($brColRow[1] - 1));
- $objWriter->writeElement('xdr:rowOff', self::stringEmu($br['yOffset']));
- $objWriter->endElement();
+ $objWriter->startElement('xdr:twoCellAnchor');
+
+ $objWriter->startElement('xdr:from');
+ $objWriter->writeElement('xdr:col', (string) ($tlColRow[0] - 1));
+ $objWriter->writeElement('xdr:colOff', self::stringEmu($tl['xOffset']));
+ $objWriter->writeElement('xdr:row', (string) ($tlColRow[1] - 1));
+ $objWriter->writeElement('xdr:rowOff', self::stringEmu($tl['yOffset']));
+ $objWriter->endElement();
+ $objWriter->startElement('xdr:to');
+ $objWriter->writeElement('xdr:col', (string) ($brColRow[0] - 1));
+ $objWriter->writeElement('xdr:colOff', self::stringEmu($br['xOffset']));
+ $objWriter->writeElement('xdr:row', (string) ($brColRow[1] - 1));
+ $objWriter->writeElement('xdr:rowOff', self::stringEmu($br['yOffset']));
+ $objWriter->endElement();
+ } elseif ($chart->getOneCellAnchor()) {
+ $objWriter->startElement('xdr:oneCellAnchor');
+
+ $objWriter->startElement('xdr:from');
+ $objWriter->writeElement('xdr:col', (string) ($tlColRow[0] - 1));
+ $objWriter->writeElement('xdr:colOff', self::stringEmu($tl['xOffset']));
+ $objWriter->writeElement('xdr:row', (string) ($tlColRow[1] - 1));
+ $objWriter->writeElement('xdr:rowOff', self::stringEmu($tl['yOffset']));
+ $objWriter->endElement();
+ $objWriter->startElement('xdr:ext');
+ $objWriter->writeAttribute('cx', self::stringEmu($br['xOffset']));
+ $objWriter->writeAttribute('cy', self::stringEmu($br['yOffset']));
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('xdr:absoluteAnchor');
+ $objWriter->startElement('xdr:pos');
+ $objWriter->writeAttribute('x', '0');
+ $objWriter->writeAttribute('y', '0');
+ $objWriter->endElement();
+ $objWriter->startElement('xdr:ext');
+ $objWriter->writeAttribute('cx', self::stringEmu($br['xOffset']));
+ $objWriter->writeAttribute('cy', self::stringEmu($br['yOffset']));
+ $objWriter->endElement();
+ }
$objWriter->startElement('xdr:graphicFrame');
$objWriter->writeAttribute('macro', '');
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php b/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
index a64e0d68..da7d825b 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
@@ -203,7 +203,8 @@ class StringTable extends WriterPart
if (!$richText instanceof RichText) {
$textRun = $richText;
$richText = new RichText();
- $richText->createTextRun($textRun);
+ $run = $richText->createTextRun($textRun);
+ $run->setFont(null);
}
if ($prefix !== null) {
@@ -215,36 +216,75 @@ class StringTable extends WriterPart
foreach ($elements as $element) {
// r
$objWriter->startElement($prefix . 'r');
+ if ($element->getFont() !== null) {
+ // rPr
+ $objWriter->startElement($prefix . 'rPr');
+ $size = $element->getFont()->getSize();
+ if (is_numeric($size)) {
+ $objWriter->writeAttribute('sz', (string) (int) ($size * 100));
+ }
- // rPr
- $objWriter->startElement($prefix . 'rPr');
+ // Bold
+ $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? 1 : 0));
+ // Italic
+ $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? 1 : 0));
+ // Underline
+ $underlineType = $element->getFont()->getUnderline();
+ switch ($underlineType) {
+ case 'single':
+ $underlineType = 'sng';
- // Bold
- $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? 1 : 0));
- // Italic
- $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? 1 : 0));
- // Underline
- $underlineType = $element->getFont()->getUnderline();
- switch ($underlineType) {
- case 'single':
- $underlineType = 'sng';
+ break;
+ case 'double':
+ $underlineType = 'dbl';
- break;
- case 'double':
- $underlineType = 'dbl';
+ break;
+ }
+ $objWriter->writeAttribute('u', $underlineType);
+ // Strikethrough
+ $objWriter->writeAttribute('strike', ($element->getFont()->getStriketype() ?: 'noStrike'));
+ // Superscript/subscript
+ if ($element->getFont()->getBaseLine()) {
+ $objWriter->writeAttribute('baseline', (string) $element->getFont()->getBaseLine());
+ }
- break;
+ // Color
+ $objWriter->startElement($prefix . 'solidFill');
+ $objWriter->startElement($prefix . 'srgbClr');
+ $objWriter->writeAttribute('val', $element->getFont()->getColor()->getRGB());
+ $objWriter->endElement(); // srgbClr
+ $objWriter->endElement(); // solidFill
+
+ // Underscore Color
+ if ($element->getFont()->getUSchemeClr()) {
+ $objWriter->startElement($prefix . 'uFill');
+ $objWriter->startElement($prefix . 'solidFill');
+ $objWriter->startElement($prefix . 'schemeClr');
+ $objWriter->writeAttribute('val', $element->getFont()->getUSchemeClr());
+ $objWriter->endElement(); // schemeClr
+ $objWriter->endElement(); // solidFill
+ $objWriter->endElement(); // uFill
+ }
+
+ // fontName
+ if ($element->getFont()->getLatin()) {
+ $objWriter->startElement($prefix . 'latin');
+ $objWriter->writeAttribute('typeface', $element->getFont()->getLatin());
+ $objWriter->endElement();
+ }
+ if ($element->getFont()->getEastAsian()) {
+ $objWriter->startElement($prefix . 'ea');
+ $objWriter->writeAttribute('typeface', $element->getFont()->getEastAsian());
+ $objWriter->endElement();
+ }
+ if ($element->getFont()->getComplexScript()) {
+ $objWriter->startElement($prefix . 'cs');
+ $objWriter->writeAttribute('typeface', $element->getFont()->getComplexScript());
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
}
- $objWriter->writeAttribute('u', $underlineType);
- // Strikethrough
- $objWriter->writeAttribute('strike', ($element->getFont()->getStrikethrough() ? 'sngStrike' : 'noStrike'));
-
- // rFont
- $objWriter->startElement($prefix . 'latin');
- $objWriter->writeAttribute('typeface', $element->getFont()->getName());
- $objWriter->endElement();
-
- $objWriter->endElement();
// t
$objWriter->startElement($prefix . 't');
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php b/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php
index c88ef245..6fc0c66a 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php
@@ -152,7 +152,7 @@ class Xlfn
*/
public static function addXlfn(string $funcstring): string
{
- return preg_replace(self::XLFNREGEXP, '_xlfn.$1', $funcstring);
+ return (string) preg_replace(self::XLFNREGEXP, '_xlfn.$1', $funcstring);
}
/**
diff --git a/tests/PhpSpreadsheetTests/Functional/AbstractFunctional.php b/tests/PhpSpreadsheetTests/Functional/AbstractFunctional.php
index da821532..60351d71 100644
--- a/tests/PhpSpreadsheetTests/Functional/AbstractFunctional.php
+++ b/tests/PhpSpreadsheetTests/Functional/AbstractFunctional.php
@@ -19,10 +19,13 @@ abstract class AbstractFunctional extends TestCase
*
* @return Spreadsheet
*/
- protected function writeAndReload(Spreadsheet $spreadsheet, $format, ?callable $readerCustomizer = null)
+ protected function writeAndReload(Spreadsheet $spreadsheet, $format, ?callable $readerCustomizer = null, ?callable $writerCustomizer = null)
{
$filename = File::temporaryFilename();
$writer = IOFactory::createWriter($spreadsheet, $format);
+ if ($writerCustomizer) {
+ $writerCustomizer($writer);
+ }
$writer->save($filename);
$reader = IOFactory::createReader($format);
diff --git a/tests/PhpSpreadsheetTests/Reader/Csv/CsvIssue2840Test.php b/tests/PhpSpreadsheetTests/Reader/Csv/CsvIssue2840Test.php
new file mode 100644
index 00000000..34d0a864
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Csv/CsvIssue2840Test.php
@@ -0,0 +1,45 @@
+getPreserveNullString());
+ $inputData = <<loadSpreadsheetFromString($inputData);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame($expected, $sheet->toArray());
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testNullStringLoad(): void
+ {
+ $reader = new Csv();
+ $reader->setPreserveNullString(true);
+ $inputData = <<loadSpreadsheetFromString($inputData);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame($expected, $sheet->toArray());
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Gnumeric/HiddenWorksheetTest.php b/tests/PhpSpreadsheetTests/Reader/Gnumeric/HiddenWorksheetTest.php
new file mode 100644
index 00000000..ffa3b88d
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Gnumeric/HiddenWorksheetTest.php
@@ -0,0 +1,56 @@
+spreadsheet = $reader->load($filename);
+ }
+
+ public function testPageSetup(): void
+ {
+ $assertions = $this->worksheetAssertions();
+
+ foreach ($this->spreadsheet->getAllSheets() as $worksheet) {
+ if (!array_key_exists($worksheet->getTitle(), $assertions)) {
+ continue;
+ }
+
+ $sheetAssertions = $assertions[$worksheet->getTitle()];
+ foreach ($sheetAssertions as $test => $expectedResult) {
+ $actualResult = $worksheet->getSheetState();
+ self::assertSame(
+ $expectedResult,
+ $actualResult,
+ "Failed asserting sheet state {$expectedResult} for Worksheet '{$worksheet->getTitle()}' {$test}"
+ );
+ }
+ }
+ }
+
+ private function worksheetAssertions(): array
+ {
+ return [
+ 'Sheet1' => [
+ 'sheetState' => Worksheet::SHEETSTATE_VISIBLE,
+ ],
+ 'Sheet2' => [
+ 'sheetState' => Worksheet::SHEETSTATE_HIDDEN,
+ ],
+ ];
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/HiddenWorksheetTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/HiddenWorksheetTest.php
new file mode 100644
index 00000000..c02c8771
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Ods/HiddenWorksheetTest.php
@@ -0,0 +1,56 @@
+spreadsheet = $reader->load($filename);
+ }
+
+ public function testPageSetup(): void
+ {
+ $assertions = $this->worksheetAssertions();
+
+ foreach ($this->spreadsheet->getAllSheets() as $worksheet) {
+ if (!array_key_exists($worksheet->getTitle(), $assertions)) {
+ continue;
+ }
+
+ $sheetAssertions = $assertions[$worksheet->getTitle()];
+ foreach ($sheetAssertions as $test => $expectedResult) {
+ $actualResult = $worksheet->getSheetState();
+ self::assertSame(
+ $expectedResult,
+ $actualResult,
+ "Failed asserting sheet state {$expectedResult} for Worksheet '{$worksheet->getTitle()}' {$test}"
+ );
+ }
+ }
+ }
+
+ private function worksheetAssertions(): array
+ {
+ return [
+ 'Sheet1' => [
+ 'sheetState' => Worksheet::SHEETSTATE_VISIBLE,
+ ],
+ 'Sheet2' => [
+ 'sheetState' => Worksheet::SHEETSTATE_HIDDEN,
+ ],
+ ];
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/HiddenWorksheetTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/HiddenWorksheetTest.php
new file mode 100644
index 00000000..82fd6e12
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xls/HiddenWorksheetTest.php
@@ -0,0 +1,56 @@
+spreadsheet = $reader->load($filename);
+ }
+
+ public function testPageSetup(): void
+ {
+ $assertions = $this->worksheetAssertions();
+
+ foreach ($this->spreadsheet->getAllSheets() as $worksheet) {
+ if (!array_key_exists($worksheet->getTitle(), $assertions)) {
+ continue;
+ }
+
+ $sheetAssertions = $assertions[$worksheet->getTitle()];
+ foreach ($sheetAssertions as $test => $expectedResult) {
+ $actualResult = $worksheet->getSheetState();
+ self::assertSame(
+ $expectedResult,
+ $actualResult,
+ "Failed asserting sheet state {$expectedResult} for Worksheet '{$worksheet->getTitle()}' {$test}"
+ );
+ }
+ }
+ }
+
+ private function worksheetAssertions(): array
+ {
+ return [
+ 'Sheet1' => [
+ 'sheetState' => Worksheet::SHEETSTATE_VISIBLE,
+ ],
+ 'Sheet2' => [
+ 'sheetState' => Worksheet::SHEETSTATE_HIDDEN,
+ ],
+ ];
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartSheetTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartSheetTest.php
new file mode 100644
index 00000000..0f1605ff
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartSheetTest.php
@@ -0,0 +1,35 @@
+setIncludeCharts(true);
+ $spreadsheet = $reader->load($filename);
+
+ self::assertCount(2, $spreadsheet->getAllSheets());
+ $chartSheet = $spreadsheet->getSheetByName('Chart1');
+ self::assertInstanceOf(Worksheet::class, $chartSheet);
+ self::assertSame(1, $chartSheet->getChartCount());
+ }
+
+ public function testLoadChartSheetWithoutCharts(): void
+ {
+ $filename = 'tests/data/Reader/XLSX/ChartSheet.xlsx';
+ $reader = new Xlsx();
+ $reader->setIncludeCharts(false);
+ $spreadsheet = $reader->load($filename);
+
+ self::assertCount(1, $spreadsheet->getAllSheets());
+ $chartSheet = $spreadsheet->getSheetByName('Chart1');
+ self::assertNull($chartSheet);
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsOpenpyxlTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsOpenpyxlTest.php
new file mode 100644
index 00000000..a7343af5
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsOpenpyxlTest.php
@@ -0,0 +1,115 @@
+setIncludeCharts(true);
+ $spreadsheet = $reader->load($file);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame(1, $sheet->getChartCount());
+
+ self::assertSame('Sheet', $sheet->getTitle());
+ $charts = $sheet->getChartCollection();
+ self::assertCount(1, $charts);
+ $chart = $charts[0];
+ self::assertNotNull($chart);
+ self::assertEmpty($chart->getTitle());
+ self::assertTrue($chart->getOneCellAnchor());
+
+ $plotArea = $chart->getPlotArea();
+ $plotSeries = $plotArea->getPlotGroup();
+ self::assertCount(1, $plotSeries);
+ $dataSeries = $plotSeries[0];
+ $labels = $dataSeries->getPlotLabels();
+ self::assertCount(2, $labels);
+ self::assertSame(['2013'], $labels[0]->getDataValues());
+ self::assertSame(['2014'], $labels[1]->getDataValues());
+
+ $plotCategories = $dataSeries->getPlotCategories();
+ self::assertCount(2, $plotCategories);
+ $categories = $plotCategories[0];
+ self::assertSame('Number', $categories->getDataType());
+ self::assertSame('\'Sheet\'!$A$2:$A$5', $categories->getDataSource());
+ self::assertFalse($categories->getBubble3D());
+ $categories = $plotCategories[1];
+ self::assertCount(2, $plotCategories);
+ self::assertSame('Number', $categories->getDataType());
+ self::assertSame('\'Sheet\'!$A$7:$A$10', $categories->getDataSource());
+ self::assertFalse($categories->getBubble3D());
+
+ $plotValues = $dataSeries->getPlotValues();
+ self::assertCount(2, $plotValues);
+ $values = $plotValues[0];
+ self::assertSame('Number', $values->getDataType());
+ self::assertSame('\'Sheet\'!$B$2:$B$5', $values->getDataSource());
+ self::assertFalse($values->getBubble3D());
+ $values = $plotValues[1];
+ self::assertCount(2, $plotValues);
+ self::assertSame('Number', $values->getDataType());
+ self::assertSame('\'Sheet\'!$B$7:$B$10', $values->getDataSource());
+ self::assertFalse($values->getBubble3D());
+
+ $plotValues = $dataSeries->getPlotBubbleSizes();
+ self::assertCount(2, $plotValues);
+ $values = $plotValues[0];
+ self::assertSame('Number', $values->getDataType());
+ self::assertSame('\'Sheet\'!$C$2:$C$5', $values->getDataSource());
+ self::assertFalse($values->getBubble3D());
+ $values = $plotValues[1];
+ self::assertCount(2, $plotValues);
+ self::assertSame('Number', $values->getDataType());
+ self::assertSame('\'Sheet\'!$C$7:$C$10', $values->getDataSource());
+ self::assertFalse($values->getBubble3D());
+
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testXml(): void
+ {
+ $infile = self::DIRECTORY . '32readwriteBubbleChart2.xlsx';
+ $file = 'zip://';
+ $file .= $infile;
+ $file .= '#xl/charts/chart1.xml';
+ $data = file_get_contents($file);
+ // confirm that file contains expected tags
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } else {
+ self::assertSame(0, substr_count($data, 'c:'), 'unusual choice of prefix');
+ self::assertSame(0, substr_count($data, 'bubbleScale'));
+ self::assertSame(1, substr_count($data, '2013'), 'v tag for 2013');
+ self::assertSame(1, substr_count($data, '2014'), 'v tag for 2014');
+ self::assertSame(0, substr_count($data, 'numCache'), 'no cached values');
+ }
+ $file = 'zip://';
+ $file .= $infile;
+ $file .= '#xl/drawings/_rels/drawing1.xml.rels';
+ $data = file_get_contents($file);
+ // confirm that file contains expected tags
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } else {
+ self::assertSame(1, substr_count($data, 'Target="/xl/charts/chart1.xml"'), 'Unusual absolute address in drawing rels file');
+ }
+ $file = 'zip://';
+ $file .= $infile;
+ $file .= '#xl/worksheets/_rels/sheet1.xml.rels';
+ $data = file_get_contents($file);
+ // confirm that file contains expected tags
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } else {
+ self::assertSame(1, substr_count($data, 'Target="/xl/drawings/drawing1.xml"'), 'Unusual absolute address in worksheet rels file');
+ }
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenWorksheetTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenWorksheetTest.php
new file mode 100644
index 00000000..130d76a6
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenWorksheetTest.php
@@ -0,0 +1,56 @@
+spreadsheet = $reader->load($filename);
+ }
+
+ public function testPageSetup(): void
+ {
+ $assertions = $this->worksheetAssertions();
+
+ foreach ($this->spreadsheet->getAllSheets() as $worksheet) {
+ if (!array_key_exists($worksheet->getTitle(), $assertions)) {
+ continue;
+ }
+
+ $sheetAssertions = $assertions[$worksheet->getTitle()];
+ foreach ($sheetAssertions as $test => $expectedResult) {
+ $actualResult = $worksheet->getSheetState();
+ self::assertSame(
+ $expectedResult,
+ $actualResult,
+ "Failed asserting sheet state {$expectedResult} for Worksheet '{$worksheet->getTitle()}' {$test}"
+ );
+ }
+ }
+ }
+
+ private function worksheetAssertions(): array
+ {
+ return [
+ 'Sheet1' => [
+ 'sheetState' => Worksheet::SHEETSTATE_VISIBLE,
+ ],
+ 'Sheet2' => [
+ 'sheetState' => Worksheet::SHEETSTATE_HIDDEN,
+ ],
+ ];
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/URLImageTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/URLImageTest.php
index 3b515090..e7f99010 100644
--- a/tests/PhpSpreadsheetTests/Reader/Xlsx/URLImageTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/URLImageTest.php
@@ -26,7 +26,7 @@ class URLImageTest extends TestCase
self::assertInstanceOf(Drawing::class, $drawing);
// Check if the source is a URL or a file path
self::assertTrue($drawing->getIsURL());
- self::assertSame('https://www.globalipmanager.com/DataFiles/Pics/20/Berniaga.comahp2.jpg', $drawing->getPath());
+ self::assertSame('https://phpspreadsheet.readthedocs.io/en/latest/topics/images/01-03-filter-icon-1.png', $drawing->getPath());
$imageContents = file_get_contents($drawing->getPath());
self::assertNotFalse($imageContents);
$filePath = tempnam(sys_get_temp_dir(), 'Drawing');
@@ -36,7 +36,7 @@ class URLImageTest extends TestCase
unlink($filePath);
self::assertNotFalse($mimeType);
$extension = File::mime2ext($mimeType);
- self::assertSame('jpeg', $extension);
+ self::assertSame('png', $extension);
}
}
}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php
index ed01db25..cc2269b2 100644
--- a/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/WorksheetInfoNamesTest.php
@@ -77,4 +77,29 @@ class WorksheetInfoNamesTest extends TestCase
self::assertEquals($expected, $actual);
}
+
+ public function testListWorksheetNamesChartSheet(): void
+ {
+ $filename = 'tests/data/Reader/XLSX/ChartSheet.xlsx';
+ $reader = new Xlsx();
+ $actual = $reader->listWorksheetNames($filename);
+
+ $expected = ['Sheet1', 'Chart1'];
+
+ self::assertEquals($expected, $actual);
+ }
+
+ public function testListWorksheetInfoChartSheet(): void
+ {
+ $filename = 'tests/data/Reader/XLSX/ChartSheet.xlsx';
+ $reader = new Xlsx();
+ $actual = $reader->listWorksheetInfo($filename);
+
+ $chartSheetInfo = $actual[1];
+
+ self::assertSame('Chart1', $chartSheetInfo['worksheetName']);
+ self::assertSame(-1, $chartSheetInfo['lastColumnIndex']);
+ self::assertSame(0, $chartSheetInfo['totalRows']);
+ self::assertSame(0, $chartSheetInfo['totalColumns']);
+ }
}
diff --git a/tests/PhpSpreadsheetTests/Writer/Ods/ContentTest.php b/tests/PhpSpreadsheetTests/Writer/Ods/ContentTest.php
index 917a0410..81c244a7 100644
--- a/tests/PhpSpreadsheetTests/Writer/Ods/ContentTest.php
+++ b/tests/PhpSpreadsheetTests/Writer/Ods/ContentTest.php
@@ -10,6 +10,7 @@ use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Ods;
use PhpOffice\PhpSpreadsheet\Writer\Ods\Content;
use PHPUnit\Framework\TestCase;
@@ -106,4 +107,26 @@ class ContentTest extends TestCase
self::assertXmlStringEqualsXmlFile($this->samplesPath . '/content-with-data.xml', $xml);
}
+
+ public function testWriteWithHiddenWorksheet(): void
+ {
+ $workbook = new Spreadsheet();
+
+ // Worksheet 1
+ $worksheet1 = $workbook->getActiveSheet();
+ $worksheet1->setCellValue('A1', 1);
+
+ // Worksheet 2
+ $worksheet2 = $workbook->createSheet();
+ $worksheet2->setTitle('New Worksheet');
+ $worksheet2->setCellValue('A1', 2);
+
+ $worksheet2->setSheetState(Worksheet::SHEETSTATE_HIDDEN);
+
+ // Write
+ $content = new Content(new Ods($workbook));
+ $xml = $content->write();
+
+ self::assertXmlStringEqualsXmlFile($this->samplesPath . '/content-hidden-worksheet.xml', $xml);
+ }
}
diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/Charts32CatAxValAxTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/Charts32CatAxValAxTest.php
new file mode 100644
index 00000000..af33baa1
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/Charts32CatAxValAxTest.php
@@ -0,0 +1,171 @@
+outputFileName !== '') {
+ unlink($this->outputFileName);
+ $this->outputFileName = '';
+ }
+ }
+
+ /**
+ * @dataProvider providerCatAxValAx
+ */
+ public function test1CatAx1ValAx(?bool $numeric): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $worksheet = $spreadsheet->getActiveSheet();
+ // changed data to simulate a trend chart - Xaxis are dates; Yaxis are 3 meausurements from each date
+ $worksheet->fromArray(
+ [
+ ['', 'metric1', 'metric2', 'metric3'],
+ ['=DATEVALUE("2021-01-01")', 12.1, 15.1, 21.1],
+ ['=DATEVALUE("2021-01-04")', 56.2, 73.2, 86.2],
+ ['=DATEVALUE("2021-01-07")', 52.2, 61.2, 69.2],
+ ['=DATEVALUE("2021-01-10")', 30.2, 32.2, 0.2],
+ ]
+ );
+ $worksheet->getStyle('A2:A5')->getNumberFormat()->setFormatCode(Properties::FORMAT_CODE_DATE_ISO8601);
+ $worksheet->getColumnDimension('A')->setAutoSize(true);
+ $worksheet->setSelectedCells('A1');
+
+ // Set the Labels for each data series we want to plot
+ // Datatype
+ // Cell reference for data
+ // Format Code
+ // Number of datapoints in series
+ // Data values
+ // Data Marker
+ $dataSeriesLabels = [
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // was 2010
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // was 2011
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // was 2012
+ ];
+ // Set the X-Axis Labels
+ // changed from STRING to NUMBER
+ // added 2 additional x-axis values associated with each of the 3 metrics
+ // added FORMATE_CODE_NUMBER
+ $xAxisTickValues = [
+ //new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$2:$A$5', Properties::FORMAT_CODE_DATE, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$2:$A$5', Properties::FORMAT_CODE_DATE, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$2:$A$5', Properties::FORMAT_CODE_DATE, 4),
+ ];
+ // Set the Data values for each data series we want to plot
+ // Datatype
+ // Cell reference for data
+ // Format Code
+ // Number of datapoints in series
+ // Data values
+ // Data Marker
+ // added FORMAT_CODE_NUMBER
+ $dataSeriesValues = [
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', Properties::FORMAT_CODE_NUMBER, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', Properties::FORMAT_CODE_NUMBER, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', Properties::FORMAT_CODE_NUMBER, 4),
+ ];
+ // Added so that Xaxis shows dates instead of Excel-equivalent-year1900-numbers
+ $xAxis = new Axis();
+ //$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE );
+ if (is_bool($numeric)) {
+ $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601, $numeric);
+ } else {
+ $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601);
+ }
+
+ // Build the dataseries
+ $series = new DataSeries(
+ DataSeries::TYPE_SCATTERCHART, // plotType
+ null, // plotGrouping (Scatter charts don't have any grouping)
+ range(0, count($dataSeriesValues) - 1), // plotOrder
+ $dataSeriesLabels, // plotLabel
+ $xAxisTickValues, // plotCategory
+ $dataSeriesValues, // plotValues
+ null, // plotDirection
+ false, // smooth line
+ //DataSeries::STYLE_LINEMARKER // plotStyle
+ DataSeries::STYLE_MARKER // plotStyle
+ );
+
+ // Set the series in the plot area
+ $plotArea = new PlotArea(null, [$series]);
+ // Set the chart legend
+ $legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false);
+
+ $title = new Title('Test Scatter Trend Chart');
+ $yAxisLabel = new Title('Value ($k)');
+
+ // Create the chart
+ $chart = new Chart(
+ 'chart1', // name
+ $title, // title
+ $legend, // legend
+ $plotArea, // plotArea
+ true, // plotVisibleOnly
+ DataSeries::EMPTY_AS_GAP, // displayBlanksAs
+ null, // xAxisLabel
+ $yAxisLabel, // yAxisLabel
+ // added xAxis for correct date display
+ $xAxis, // xAxis
+ );
+
+ // Set the position where the chart should appear in the worksheet
+ $chart->setTopLeftPosition('A7');
+ $chart->setBottomRightPosition('P20');
+ // Add the chart to the worksheet
+ $worksheet->addChart($chart);
+
+ $writer = new XlsxWriter($spreadsheet);
+ $writer->setIncludeCharts(true);
+ $this->outputFileName = File::temporaryFilename();
+ $writer->save($this->outputFileName);
+ $spreadsheet->disconnectWorksheets();
+
+ $file = 'zip://';
+ $file .= $this->outputFileName;
+ $file .= '#xl/charts/chart1.xml';
+ $data = file_get_contents($file);
+ // confirm that file contains expected tags
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } elseif ($numeric === true) {
+ self::assertSame(0, substr_count($data, 'setIncludeCharts(true);
+ }
+
+ public function writeCharts(XlsxWriter $writer): void
+ {
+ $writer->setIncludeCharts(true);
+ }
+
+ public function testStock5(): void
+ {
+ $file = self::DIRECTORY . '32readwriteStockChart5.xlsx';
+ $reader = new XlsxReader();
+ $reader->setIncludeCharts(true);
+ $spreadsheet = $reader->load($file);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame(1, $sheet->getChartCount());
+ /** @var callable */
+ $callableReader = [$this, 'readCharts'];
+ /** @var callable */
+ $callableWriter = [$this, 'writeCharts'];
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter);
+ $spreadsheet->disconnectWorksheets();
+
+ $sheet = $reloadedSpreadsheet->getActiveSheet();
+ self::assertSame('Charts', $sheet->getTitle());
+ $charts = $sheet->getChartCollection();
+ self::assertCount(1, $charts);
+ $chart = $charts[0];
+ self::assertNotNull($chart);
+
+ $xAxisLabel = $chart->getXAxisLabel();
+ $captionArray = $xAxisLabel->getCaption();
+ self::assertIsArray($captionArray);
+ self::assertCount(1, $captionArray);
+ $caption = $captionArray[0];
+ self::assertInstanceOf(RichText::class, $caption);
+ self::assertSame('X-Axis Title in Green', $caption->getPlainText());
+ $elements = $caption->getRichTextElements();
+ self::assertCount(1, $elements);
+ $run = $elements[0];
+ self::assertInstanceOf(Run::class, $run);
+ $font = $run->getFont();
+ self::assertInstanceOf(Font::class, $font);
+ self::assertSame('00B050', $font->getColor()->getRGB());
+
+ $yAxisLabel = $chart->getYAxisLabel();
+ $captionArray = $yAxisLabel->getCaption();
+ self::assertIsArray($captionArray);
+ self::assertCount(1, $captionArray);
+ $caption = $captionArray[0];
+ self::assertInstanceOf(RichText::class, $caption);
+ self::assertSame('Y-Axis Title in Red', $caption->getPlainText());
+ $elements = $caption->getRichTextElements();
+ self::assertCount(1, $elements);
+ $run = $elements[0];
+ self::assertInstanceOf(Run::class, $run);
+ $font = $run->getFont();
+ self::assertInstanceOf(Font::class, $font);
+ self::assertSame('FF0000', $font->getColor()->getRGB());
+
+ $reloadedSpreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/Charts32ScatterTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/Charts32ScatterTest.php
new file mode 100644
index 00000000..76389727
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/Charts32ScatterTest.php
@@ -0,0 +1,333 @@
+setIncludeCharts(true);
+ }
+
+ public function writeCharts(XlsxWriter $writer): void
+ {
+ $writer->setIncludeCharts(true);
+ }
+
+ public function testScatter1(): void
+ {
+ $file = self::DIRECTORY . '32readwriteScatterChart1.xlsx';
+ $reader = new XlsxReader();
+ $reader->setIncludeCharts(true);
+ $spreadsheet = $reader->load($file);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame(1, $sheet->getChartCount());
+ /** @var callable */
+ $callableReader = [$this, 'readCharts'];
+ /** @var callable */
+ $callableWriter = [$this, 'writeCharts'];
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter);
+ $spreadsheet->disconnectWorksheets();
+
+ $sheet = $reloadedSpreadsheet->getActiveSheet();
+ self::assertSame('Charts', $sheet->getTitle());
+ $charts = $sheet->getChartCollection();
+ self::assertCount(1, $charts);
+ $chart = $charts[0];
+ self::assertNotNull($chart);
+ $title = $chart->getTitle();
+ $captionArray = $title->getCaption();
+ self::assertIsArray($captionArray);
+ self::assertCount(1, $captionArray);
+ $caption = $captionArray[0];
+ self::assertInstanceOf(RichText::class, $caption);
+ self::assertSame('Scatter - No Join and Markers', $caption->getPlainText());
+ $elements = $caption->getRichTextElements();
+ self::assertCount(1, $elements);
+ $run = $elements[0];
+ self::assertInstanceOf(Run::class, $run);
+ $font = $run->getFont();
+ self::assertInstanceOf(Font::class, $font);
+ self::assertSame('Calibri', $font->getLatin());
+ self::assertEquals(12, $font->getSize());
+ self::assertTrue($font->getBold());
+ self::assertFalse($font->getItalic());
+ self::assertFalse($font->getSuperscript());
+ self::assertFalse($font->getSubscript());
+ self::assertFalse($font->getStrikethrough());
+ self::assertSame('none', $font->getUnderline());
+ self::assertSame('000000', $font->getColor()->getRGB());
+
+ $plotArea = $chart->getPlotArea();
+ $plotSeries = $plotArea->getPlotGroup();
+ self::assertCount(1, $plotSeries);
+ $dataSeries = $plotSeries[0];
+ $plotValues = $dataSeries->getPlotValues();
+ self::assertCount(3, $plotValues);
+ $values = $plotValues[0];
+ self::assertFalse($values->getScatterLines());
+ self::assertSame(28575, $values->getLineWidth());
+ self::assertSame(3, $values->getPointSize());
+ self::assertSame('', $values->getFillColor());
+ $values = $plotValues[1];
+ self::assertFalse($values->getScatterLines());
+ self::assertSame(28575, $values->getLineWidth());
+ self::assertSame(3, $values->getPointSize());
+ self::assertSame('', $values->getFillColor());
+ $values = $plotValues[2];
+ self::assertFalse($values->getScatterLines());
+ self::assertSame(28575, $values->getLineWidth());
+ self::assertSame(7, $values->getPointSize());
+ self::assertSame('FFFF00', $values->getFillColor());
+
+ $reloadedSpreadsheet->disconnectWorksheets();
+ }
+
+ public function testScatter6(): void
+ {
+ $file = self::DIRECTORY . '32readwriteScatterChart6.xlsx';
+ $reader = new XlsxReader();
+ $reader->setIncludeCharts(true);
+ $spreadsheet = $reader->load($file);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame(1, $sheet->getChartCount());
+ /** @var callable */
+ $callableReader = [$this, 'readCharts'];
+ /** @var callable */
+ $callableWriter = [$this, 'writeCharts'];
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter);
+ $spreadsheet->disconnectWorksheets();
+
+ $sheet = $reloadedSpreadsheet->getActiveSheet();
+ self::assertSame('Charts', $sheet->getTitle());
+ $charts = $sheet->getChartCollection();
+ self::assertCount(1, $charts);
+ $chart = $charts[0];
+ self::assertNotNull($chart);
+ $title = $chart->getTitle();
+ $captionArray = $title->getCaption();
+ self::assertIsArray($captionArray);
+ self::assertCount(1, $captionArray);
+ $caption = $captionArray[0];
+ self::assertInstanceOf(RichText::class, $caption);
+ self::assertSame('Scatter - Rich Text Title No Join and Markers', $caption->getPlainText());
+ $elements = $caption->getRichTextElements();
+ self::assertCount(3, $elements);
+
+ $run = $elements[0];
+ self::assertInstanceOf(Run::class, $run);
+ $font = $run->getFont();
+ self::assertInstanceOf(Font::class, $font);
+ self::assertSame('Calibri', $font->getLatin());
+ self::assertEquals(12, $font->getSize());
+ self::assertTrue($font->getBold());
+ self::assertFalse($font->getItalic());
+ self::assertFalse($font->getSuperscript());
+ self::assertFalse($font->getSubscript());
+ self::assertFalse($font->getStrikethrough());
+ self::assertSame('none', $font->getUnderline());
+ self::assertSame('000000', $font->getColor()->getRGB());
+
+ $run = $elements[1];
+ self::assertInstanceOf(Run::class, $run);
+ $font = $run->getFont();
+ self::assertInstanceOf(Font::class, $font);
+ self::assertSame('Courier New', $font->getLatin());
+ self::assertEquals(10, $font->getSize());
+ self::assertFalse($font->getBold());
+ self::assertFalse($font->getItalic());
+ self::assertFalse($font->getSuperscript());
+ self::assertFalse($font->getSubscript());
+ self::assertFalse($font->getStrikethrough());
+ self::assertSame('single', $font->getUnderline());
+ self::assertSame('00B0F0', $font->getColor()->getRGB());
+
+ $run = $elements[2];
+ self::assertInstanceOf(Run::class, $run);
+ $font = $run->getFont();
+ self::assertInstanceOf(Font::class, $font);
+ self::assertSame('Calibri', $font->getLatin());
+ self::assertEquals(12, $font->getSize());
+ self::assertTrue($font->getBold());
+ self::assertFalse($font->getItalic());
+ self::assertFalse($font->getSuperscript());
+ self::assertFalse($font->getSubscript());
+ self::assertFalse($font->getStrikethrough());
+ self::assertSame('none', $font->getUnderline());
+ self::assertSame('000000', $font->getColor()->getRGB());
+
+ $plotArea = $chart->getPlotArea();
+ $plotSeries = $plotArea->getPlotGroup();
+ self::assertCount(1, $plotSeries);
+ $dataSeries = $plotSeries[0];
+ $plotValues = $dataSeries->getPlotValues();
+ self::assertCount(3, $plotValues);
+ $values = $plotValues[0];
+ self::assertFalse($values->getScatterLines());
+ self::assertSame(28575, $values->getLineWidth());
+ self::assertSame(3, $values->getPointSize());
+ self::assertSame('', $values->getFillColor());
+ $values = $plotValues[1];
+ self::assertFalse($values->getScatterLines());
+ self::assertSame(28575, $values->getLineWidth());
+ self::assertSame(3, $values->getPointSize());
+ self::assertSame('', $values->getFillColor());
+ $values = $plotValues[2];
+ self::assertFalse($values->getScatterLines());
+ self::assertSame(28575, $values->getLineWidth());
+ self::assertSame(7, $values->getPointSize());
+ self::assertSame('FFFF00', $values->getFillColor());
+
+ $reloadedSpreadsheet->disconnectWorksheets();
+ }
+
+ public function testScatter3(): void
+ {
+ $file = self::DIRECTORY . '32readwriteScatterChart3.xlsx';
+ $reader = new XlsxReader();
+ $reader->setIncludeCharts(true);
+ $spreadsheet = $reader->load($file);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame(1, $sheet->getChartCount());
+ /** @var callable */
+ $callableReader = [$this, 'readCharts'];
+ /** @var callable */
+ $callableWriter = [$this, 'writeCharts'];
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter);
+ $spreadsheet->disconnectWorksheets();
+
+ $sheet = $reloadedSpreadsheet->getActiveSheet();
+ self::assertSame('Charts', $sheet->getTitle());
+ $charts = $sheet->getChartCollection();
+ self::assertCount(1, $charts);
+ $chart = $charts[0];
+ self::assertNotNull($chart);
+ $title = $chart->getTitle();
+ $captionArray = $title->getCaption();
+ self::assertIsArray($captionArray);
+ self::assertCount(1, $captionArray);
+ $caption = $captionArray[0];
+ self::assertInstanceOf(RichText::class, $caption);
+ self::assertSame('Scatter - Join Straight Lines and Markers', $caption->getPlainText());
+ $elements = $caption->getRichTextElements();
+ self::assertCount(1, $elements);
+ $run = $elements[0];
+ self::assertInstanceOf(Run::class, $run);
+ $font = $run->getFont();
+ self::assertInstanceOf(Font::class, $font);
+ self::assertSame('Calibri', $font->getLatin());
+ self::assertEquals(12, $font->getSize());
+ self::assertTrue($font->getBold());
+ self::assertFalse($font->getItalic());
+ self::assertFalse($font->getSuperscript());
+ self::assertFalse($font->getSubscript());
+ self::assertFalse($font->getStrikethrough());
+ self::assertSame('none', $font->getUnderline());
+ self::assertSame('000000', $font->getColor()->getRGB());
+
+ $plotArea = $chart->getPlotArea();
+ $plotSeries = $plotArea->getPlotGroup();
+ self::assertCount(1, $plotSeries);
+ $dataSeries = $plotSeries[0];
+ $plotValues = $dataSeries->getPlotValues();
+ self::assertCount(3, $plotValues);
+ $values = $plotValues[0];
+ self::assertTrue($values->getScatterLines());
+ self::assertSame(12700, $values->getLineWidth());
+ self::assertSame(3, $values->getPointSize());
+ self::assertSame('', $values->getFillColor());
+ $values = $plotValues[1];
+ self::assertTrue($values->getScatterLines());
+ self::assertSame(12700, $values->getLineWidth());
+ self::assertSame(3, $values->getPointSize());
+ self::assertSame('', $values->getFillColor());
+ $values = $plotValues[2];
+ self::assertTrue($values->getScatterLines());
+ self::assertSame(12700, $values->getLineWidth());
+ self::assertSame(3, $values->getPointSize());
+ self::assertSame('', $values->getFillColor());
+
+ $reloadedSpreadsheet->disconnectWorksheets();
+ }
+
+ public function testScatter7(): void
+ {
+ $file = self::DIRECTORY . '32readwriteScatterChart7.xlsx';
+ $reader = new XlsxReader();
+ $reader->setIncludeCharts(true);
+ $spreadsheet = $reader->load($file);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame(1, $sheet->getChartCount());
+ /** @var callable */
+ $callableReader = [$this, 'readCharts'];
+ /** @var callable */
+ $callableWriter = [$this, 'writeCharts'];
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter);
+ $spreadsheet->disconnectWorksheets();
+
+ $sheet = $reloadedSpreadsheet->getActiveSheet();
+ self::assertSame('Charts', $sheet->getTitle());
+ $charts = $sheet->getChartCollection();
+ self::assertCount(1, $charts);
+ $chart = $charts[0];
+ self::assertNotNull($chart);
+ $title = $chart->getTitle();
+ $captionArray = $title->getCaption();
+ self::assertIsArray($captionArray);
+ self::assertCount(1, $captionArray);
+ $caption = $captionArray[0];
+ self::assertInstanceOf(RichText::class, $caption);
+ self::assertSame('Latin/EA/CS Title ABCאבגDEFァ', $caption->getPlainText());
+ $elements = $caption->getRichTextElements();
+ self::assertGreaterThan(0, count($elements));
+ foreach ($elements as $run) {
+ self::assertInstanceOf(Run::class, $run);
+ $font = $run->getFont();
+ self::assertInstanceOf(Font::class, $font);
+ self::assertSame('Times New Roman', $font->getLatin());
+ self::assertSame('Malgun Gothic', $font->getEastAsian());
+ self::assertSame('Courier New', $font->getComplexScript());
+ self::assertEquals(12, $font->getSize());
+ self::assertTrue($font->getBold());
+ self::assertFalse($font->getItalic());
+ self::assertFalse($font->getSuperscript());
+ self::assertFalse($font->getSubscript());
+ self::assertFalse($font->getStrikethrough());
+ self::assertSame('none', $font->getUnderline());
+ self::assertSame('000000', $font->getColor()->getRGB());
+ }
+
+ $plotArea = $chart->getPlotArea();
+ $plotSeries = $plotArea->getPlotGroup();
+ self::assertCount(1, $plotSeries);
+ $dataSeries = $plotSeries[0];
+ $plotValues = $dataSeries->getPlotValues();
+ self::assertCount(3, $plotValues);
+ $values = $plotValues[0];
+ self::assertFalse($values->getScatterLines());
+ self::assertSame(28575, $values->getLineWidth());
+ self::assertSame(3, $values->getPointSize());
+ self::assertSame('', $values->getFillColor());
+ $values = $plotValues[1];
+ self::assertFalse($values->getScatterLines());
+ self::assertSame(28575, $values->getLineWidth());
+ self::assertSame(3, $values->getPointSize());
+ self::assertSame('', $values->getFillColor());
+ $values = $plotValues[2];
+ self::assertFalse($values->getScatterLines());
+ self::assertSame(28575, $values->getLineWidth());
+ self::assertSame(7, $values->getPointSize());
+ self::assertSame('FFFF00', $values->getFillColor());
+
+ $reloadedSpreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/Charts32XmlTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/Charts32XmlTest.php
new file mode 100644
index 00000000..9b32518a
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/Charts32XmlTest.php
@@ -0,0 +1,147 @@
+outputFileName !== '') {
+ unlink($this->outputFileName);
+ $this->outputFileName = '';
+ }
+ }
+
+ /**
+ * @dataProvider providerScatterCharts
+ */
+ public function testBezierCount(int $expectedCount, string $inputFile): void
+ {
+ $file = self::DIRECTORY . $inputFile;
+ $reader = new XlsxReader();
+ $reader->setIncludeCharts(true);
+ $spreadsheet = $reader->load($file);
+
+ $writer = new XlsxWriter($spreadsheet);
+ $writer->setIncludeCharts(true);
+ $this->outputFileName = File::temporaryFilename();
+ $writer->save($this->outputFileName);
+ $spreadsheet->disconnectWorksheets();
+
+ $file = 'zip://';
+ $file .= $this->outputFileName;
+ $file .= '#xl/charts/chart2.xml';
+ $data = file_get_contents($file);
+ // confirm that file contains expected tags
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } else {
+ self::assertSame(1, substr_count($data, ''));
+ self::assertSame($expectedCount, substr_count($data, ''));
+ }
+ }
+
+ public function providerScatterCharts(): array
+ {
+ return [
+ 'no line' => [0, '32readwriteScatterChart1.xlsx'],
+ 'smooth line (Bezier)' => [3, '32readwriteScatterChart2.xlsx'],
+ 'straight line' => [0, '32readwriteScatterChart3.xlsx'],
+ ];
+ }
+
+ public function testAreaPercentageNoCat(): void
+ {
+ $file = self::DIRECTORY . '32readwriteAreaPercentageChart1.xlsx';
+ $reader = new XlsxReader();
+ $reader->setIncludeCharts(true);
+ $spreadsheet = $reader->load($file);
+
+ $writer = new XlsxWriter($spreadsheet);
+ $writer->setIncludeCharts(true);
+ $this->outputFileName = File::temporaryFilename();
+ $writer->save($this->outputFileName);
+ $spreadsheet->disconnectWorksheets();
+
+ $file = 'zip://';
+ $file .= $this->outputFileName;
+ $file .= '#xl/charts/chart1.xml';
+ $data = file_get_contents($file);
+ // confirm that file contains expected tags
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } else {
+ self::assertSame(0, substr_count($data, ''));
+ }
+ }
+
+ /**
+ * @dataProvider providerCatAxValAx
+ */
+ public function testCatAxValAx(?bool $numeric): void
+ {
+ $file = self::DIRECTORY . '32readwriteScatterChart1.xlsx';
+ $reader = new XlsxReader();
+ $reader->setIncludeCharts(true);
+ $spreadsheet = $reader->load($file);
+ $sheet = $spreadsheet->getActiveSheet();
+ $charts = $sheet->getChartCollection();
+ self::assertCount(1, $charts);
+ $chart = $charts[0];
+ self::assertNotNull($chart);
+ $xAxis = $chart->getChartAxisX();
+ self::assertSame(Properties::FORMAT_CODE_GENERAL, $xAxis->getAxisNumberFormat());
+ if (is_bool($numeric)) {
+ $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_GENERAL, true);
+ }
+ $yAxis = $chart->getChartAxisY();
+ self::assertSame(Properties::FORMAT_CODE_GENERAL, $yAxis->getAxisNumberFormat());
+ if (is_bool($numeric)) {
+ $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_GENERAL, $numeric);
+ $yAxis->setAxisNumberProperties(Properties::FORMAT_CODE_GENERAL, $numeric);
+ }
+
+ $writer = new XlsxWriter($spreadsheet);
+ $writer->setIncludeCharts(true);
+ $this->outputFileName = File::temporaryFilename();
+ $writer->save($this->outputFileName);
+ $spreadsheet->disconnectWorksheets();
+
+ $file = 'zip://';
+ $file .= $this->outputFileName;
+ $file .= '#xl/charts/chart2.xml';
+ $data = file_get_contents($file);
+ // confirm that file contains expected tags
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } elseif ($numeric === true) {
+ self::assertSame(0, substr_count($data, ''));
+ self::assertSame(2, substr_count($data, ''));
+ } else {
+ self::assertSame(1, substr_count($data, ''));
+ self::assertSame(1, substr_count($data, ''));
+ }
+ }
+
+ public function providerCatAxValAx(): array
+ {
+ return [
+ [true],
+ [false],
+ [null],
+ ];
+ }
+}
diff --git a/tests/data/Calculation/MathTrig/PRODUCT.php b/tests/data/Calculation/MathTrig/PRODUCT.php
index 0e7c4080..657c713c 100644
--- a/tests/data/Calculation/MathTrig/PRODUCT.php
+++ b/tests/data/Calculation/MathTrig/PRODUCT.php
@@ -48,6 +48,30 @@ return [
-6.7800000000000002,
-2,
],
+ [
+ 31.25,
+ 12.5,
+ null,
+ 2.5,
+ ],
+ [
+ 31.25,
+ 12.5,
+ 2.5,
+ null,
+ ],
+ [
+ 12.5,
+ 12.5,
+ null,
+ null,
+ ],
+ [
+ 0.0,
+ null,
+ null,
+ null,
+ ],
['#VALUE!', 1, 'y', 3],
[6, 1, '2', 3],
];
diff --git a/tests/data/Reader/Gnumeric/HiddenSheet.gnumeric b/tests/data/Reader/Gnumeric/HiddenSheet.gnumeric
new file mode 100644
index 00000000..4f503c28
Binary files /dev/null and b/tests/data/Reader/Gnumeric/HiddenSheet.gnumeric differ
diff --git a/tests/data/Reader/Ods/HiddenSheet.ods b/tests/data/Reader/Ods/HiddenSheet.ods
new file mode 100644
index 00000000..9b1d01a6
Binary files /dev/null and b/tests/data/Reader/Ods/HiddenSheet.ods differ
diff --git a/tests/data/Reader/XLS/HiddenSheet.xls b/tests/data/Reader/XLS/HiddenSheet.xls
new file mode 100644
index 00000000..39ced197
Binary files /dev/null and b/tests/data/Reader/XLS/HiddenSheet.xls differ
diff --git a/tests/data/Reader/XLSX/ChartSheet.xlsx b/tests/data/Reader/XLSX/ChartSheet.xlsx
new file mode 100644
index 00000000..861bdeeb
Binary files /dev/null and b/tests/data/Reader/XLSX/ChartSheet.xlsx differ
diff --git a/tests/data/Reader/XLSX/HiddenSheet.xlsx b/tests/data/Reader/XLSX/HiddenSheet.xlsx
new file mode 100644
index 00000000..6d1d56a2
Binary files /dev/null and b/tests/data/Reader/XLSX/HiddenSheet.xlsx differ
diff --git a/tests/data/Reader/XLSX/urlImage.xlsx b/tests/data/Reader/XLSX/urlImage.xlsx
index 01e8e24d..42295822 100644
Binary files a/tests/data/Reader/XLSX/urlImage.xlsx and b/tests/data/Reader/XLSX/urlImage.xlsx differ
diff --git a/tests/data/Writer/Ods/content-empty.xml b/tests/data/Writer/Ods/content-empty.xml
index 867cfa3e..84f4c239 100644
--- a/tests/data/Writer/Ods/content-empty.xml
+++ b/tests/data/Writer/Ods/content-empty.xml
@@ -3,6 +3,9 @@
+
+
+
@@ -12,7 +15,7 @@
-
+
diff --git a/tests/data/Writer/Ods/content-hidden-worksheet.xml b/tests/data/Writer/Ods/content-hidden-worksheet.xml
new file mode 100644
index 00000000..88a53257
--- /dev/null
+++ b/tests/data/Writer/Ods/content-hidden-worksheet.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+ 2
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/data/Writer/Ods/content-with-data.xml b/tests/data/Writer/Ods/content-with-data.xml
index fff47f65..12140fa9 100644
--- a/tests/data/Writer/Ods/content-with-data.xml
+++ b/tests/data/Writer/Ods/content-with-data.xml
@@ -3,6 +3,12 @@
+
+
+
+
+
+
@@ -62,7 +68,7 @@
-
+
@@ -107,7 +113,7 @@
-
+