diff --git a/CHANGELOG.md b/CHANGELOG.md index a2fd0cc1..225ea2af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,15 +9,28 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Added -- Nothing +- Implementation of the new `TEXTBEFORE()`, `TEXTAFTER()` and `TEXTSPLIT()` Excel Functions +- Implementation of the `ARRAYTOTEXT()` and `VALUETOTEXT()` Excel Functions +- Support for [mitoteam/jpgraph](https://packagist.org/packages/mitoteam/jpgraph) implementation of + JpGraph library to render charts added. +- Charts: Add Gradients, Transparency, Hidden Axes, Rounded Corners, Trendlines, Date Axes. ### Changed -- Nothing +- Allow variant behaviour when merging cells [Issue #3065](https://github.com/PHPOffice/PhpSpreadsheet/issues/3065) + - Merge methods now allow an additional `$behaviour` argument. Permitted values are: + - Worksheet::MERGE_CELL_CONTENT_EMPTY - Empty the content of the hidden cells (the default behaviour) + - Worksheet::MERGE_CELL_CONTENT_HIDE - Keep the content of the hidden cells + - Worksheet::MERGE_CELL_CONTENT_MERGE - Move the content of the hidden cells into the first cell ### Deprecated -- Nothing +- Axis getLineProperty deprecated in favor of getLineColorProperty. +- Moved majorGridlines and minorGridlines from Chart to Axis. Setting either in Chart constructor or through Chart methods, or getting either using Chart methods is deprecated. +- Chart::EXCEL_COLOR_TYPE_* copied from Properties to ChartColor; use in Properties is deprecated. +- ChartColor::EXCEL_COLOR_TYPE_ARGB deprecated in favor of EXCEL_COLOR_TYPE_RGB ("A" component was never allowed). +- Misspelled Properties::LINE_STYLE_DASH_SQUERE_DOT deprecated in favor of LINE_STYLE_DASH_SQUARE_DOT. +- Clone not permitted for Spreadsheet. Spreadsheet->copy() can be used instead. ### Removed @@ -25,12 +38,29 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Fixed -- Nothing +- Fix DataValidation sqRef when inserting/deleting rows/columns [Issue #3056](https://github.com/PHPOffice/PhpSpreadsheet/issues/3056) [PR #3074](https://github.com/PHPOffice/PhpSpreadsheet/pull/3074) +- Named ranges not usable as anchors in OFFSET function [Issue #3013](https://github.com/PHPOffice/PhpSpreadsheet/issues/3013) +- Fully flatten an array [Issue #2955](https://github.com/PHPOffice/PhpSpreadsheet/issues/2955) [PR #2956](https://github.com/PHPOffice/PhpSpreadsheet/pull/2956) +- cellExists() and getCell() methods should support UTF-8 named cells [Issue #2987](https://github.com/PHPOffice/PhpSpreadsheet/issues/2987) [PR #2988](https://github.com/PHPOffice/PhpSpreadsheet/pull/2988) +- Spreadsheet copy fixed, clone disabled. [PR #2951](https://github.com/PHPOffice/PhpSpreadsheet/pull/2951) +- Fix PDF problems with text rotation and paper size. [Issue #1747](https://github.com/PHPOffice/PhpSpreadsheet/issues/1747) [Issue #1713](https://github.com/PHPOffice/PhpSpreadsheet/issues/1713) [PR #2960](https://github.com/PHPOffice/PhpSpreadsheet/pull/2960) +- Limited support for chart titles as formulas [Issue #2965](https://github.com/PHPOffice/PhpSpreadsheet/issues/2965) [Issue #749](https://github.com/PHPOffice/PhpSpreadsheet/issues/749) [PR #2971](https://github.com/PHPOffice/PhpSpreadsheet/pull/2971) +- Add Gradients, Transparency, and Hidden Axes to Chart [Issue #2257](https://github.com/PHPOffice/PhpSpreadsheet/issues/2257) [Issue #2229](https://github.com/PHPOffice/PhpSpreadsheet/issues/2929) [Issue #2935](https://github.com/PHPOffice/PhpSpreadsheet/issues/2935) [PR #2950](https://github.com/PHPOffice/PhpSpreadsheet/pull/2950) +- Chart Support for Rounded Corners and Trendlines [Issue #2968](https://github.com/PHPOffice/PhpSpreadsheet/issues/2968) [Issue #2815](https://github.com/PHPOffice/PhpSpreadsheet/issues/2815) [PR #2976](https://github.com/PHPOffice/PhpSpreadsheet/pull/2976) +- Add setName Method for Chart [Issue #2991](https://github.com/PHPOffice/PhpSpreadsheet/issues/2991) [PR #3001](https://github.com/PHPOffice/PhpSpreadsheet/pull/3001) +- Eliminate partial dependency on php-intl in StringHelper [Issue #2982](https://github.com/PHPOffice/PhpSpreadsheet/issues/2982) [PR #2994](https://github.com/PHPOffice/PhpSpreadsheet/pull/2994) +- Minor changes for Pdf [Issue #2999](https://github.com/PHPOffice/PhpSpreadsheet/issues/2999) [PR #3002](https://github.com/PHPOffice/PhpSpreadsheet/pull/3002) [PR #3006](https://github.com/PHPOffice/PhpSpreadsheet/pull/3006) +- Html/Pdf Do net set background color for cells using (default) nofill [PR #3016](https://github.com/PHPOffice/PhpSpreadsheet/pull/3016) +- Add support for Date Axis to Chart [Issue #2967](https://github.com/PHPOffice/PhpSpreadsheet/issues/2967) [PR #3018](https://github.com/PHPOffice/PhpSpreadsheet/pull/3018) +- Reconcile Differences Between Css and Excel for Cell Alignment [PR #3048](https://github.com/PHPOffice/PhpSpreadsheet/pull/3048) +- R1C1 Format Internationalization and Better Support for Relative Offsets [Issue #1704](https://github.com/PHPOffice/PhpSpreadsheet/issues/1704) [PR #3052](https://github.com/PHPOffice/PhpSpreadsheet/pull/3052) +- Minor Fix for Percentage Formatting [Issue #1929](https://github.com/PHPOffice/PhpSpreadsheet/issues/1929) [PR #3053](https://github.com/PHPOffice/PhpSpreadsheet/pull/3053) ## 1.24.1 - 2022-07-18 ### Added +- Support for SimpleCache Interface versions 1.0, 2.0 and 3.0 - Add Chart Axis Option textRotation [Issue #2705](https://github.com/PHPOffice/PhpSpreadsheet/issues/2705) [PR #2940](https://github.com/PHPOffice/PhpSpreadsheet/pull/2940) ### Changed diff --git a/README.md b/README.md index 57560702..ef04cd07 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ 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 +```json { "require": { "phpoffice/phpspreadsheet": "^1.23" @@ -71,15 +71,19 @@ or the appropriate PDF Writer wrapper for the library that you have chosen to in #### Chart Export -For Chart export, we support, which you will also need to install yourself - - jpgraph/jpgraph +For Chart export, we support following packages, which you will also need to install yourself using `composer require` + - [jpgraph/jpgraph](https://packagist.org/packages/jpgraph/jpgraph) (this package was abandoned at version 4.0. + You can manually download the latest version that supports PHP 8 and above from [jpgraph.net](https://jpgraph.net/)) + - [mitoteam/jpgraph](https://packagist.org/packages/mitoteam/jpgraph) (fork with php 8.1 support) and then configure PhpSpreadsheet using: ```php -Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class); +Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class); // to use jpgraph/jpgraph +//or +Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class); // to use mitoteam/jpgraph ``` -You can `composer/require` the github version of jpgraph, but this was abandoned at version 4.0; or manually download the latest version that supports PHP 8 and above from [jpgraph.net](https://jpgraph.net/) +One or the other of these libraries is necessary if you want to generate HTML or PDF files that include charts. ## Documentation diff --git a/composer.json b/composer.json index 16991514..858bd819 100644 --- a/composer.json +++ b/composer.json @@ -69,32 +69,33 @@ "ext-xmlwriter": "*", "ext-zip": "*", "ext-zlib": "*", - "ezyang/htmlpurifier": "^4.13", + "ezyang/htmlpurifier": "4.15", "maennchen/zipstream-php": "^2.1", "markbaker/complex": "^3.0", "markbaker/matrix": "^3.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "psr/simple-cache": "^1.0 || ^2.0" + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "dev-master", "dompdf/dompdf": "^1.0 || ^2.0", "friendsofphp/php-cs-fixer": "^3.2", - "jpgraph/jpgraph": "^4.0", + "mitoteam/jpgraph": "10.2.4", "mpdf/mpdf": "8.1.1", "phpcompatibility/php-compatibility": "^9.3", "phpstan/phpstan": "^1.1", "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^8.5 || ^9.0", "squizlabs/php_codesniffer": "^3.7", - "tecnickcom/tcpdf": "^6.4" + "tecnickcom/tcpdf": "6.5" }, "suggest": { + "ext-intl": "PHP Internationalization Functions", "mpdf/mpdf": "Option for rendering PDF with PDF Writer", - "dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)", - "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)", - "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers" + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 4ac9f631..22513999 100644 --- a/composer.lock +++ b/composer.lock @@ -4,24 +4,34 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6cbb20f8d8f2daae0aeb72431cda0980", + "content-hash": "5062910e2e463ba84b1c753c58624ca9", "packages": [ { "name": "ezyang/htmlpurifier", - "version": "v4.14.0", + "version": "v4.15.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75" + "reference": "8d9f4c9ec154922ff19690ffade9ed915b27a017" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/12ab42bd6e742c70c0a52f7b82477fcd44e64b75", - "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/8d9f4c9ec154922ff19690ffade9ed915b27a017", + "reference": "8d9f4c9ec154922ff19690ffade9ed915b27a017", "shasum": "" }, "require": { - "php": ">=5.2" + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" }, "type": "library", "autoload": { @@ -51,33 +61,39 @@ "keywords": [ "html" ], - "time": "2021-12-25T01:21:49+00:00" + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.15.0" + }, + "time": "2022-09-18T06:23:57+00:00" }, { "name": "maennchen/zipstream-php", - "version": "2.1.0", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58" + "reference": "211e9ba1530ea5260b45d90c9ea252f56ec52729" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58", - "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/211e9ba1530ea5260b45d90c9ea252f56ec52729", + "reference": "211e9ba1530ea5260b45d90c9ea252f56ec52729", "shasum": "" }, "require": { "myclabs/php-enum": "^1.5", - "php": ">= 7.1", + "php": "^7.4 || ^8.0", "psr/http-message": "^1.0", "symfony/polyfill-mbstring": "^1.0" }, "require-dev": { "ext-zip": "*", - "guzzlehttp/guzzle": ">= 6.3", + "guzzlehttp/guzzle": "^6.5.3 || ^7.2.0", "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": ">= 7.5" + "php-coveralls/php-coveralls": "^2.4", + "phpunit/phpunit": "^8.5.8 || ^9.4.2", + "vimeo/psalm": "^4.1" }, "type": "library", "autoload": { @@ -112,13 +128,17 @@ "stream", "zip" ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.2.1" + }, "funding": [ { "url": "https://opencollective.com/zipstream", "type": "open_collective" } ], - "time": "2020-05-30T13:11:16+00:00" + "time": "2022-05-18T15:52:06+00:00" }, { "name": "markbaker/complex", @@ -165,6 +185,10 @@ "complex", "mathematics" ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.1" + }, "time": "2021-06-29T15:32:53+00:00" }, { @@ -217,20 +241,24 @@ "matrix", "vector" ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.0" + }, "time": "2021-07-01T19:01:15+00:00" }, { "name": "myclabs/php-enum", - "version": "1.8.3", + "version": "1.8.4", "source": { "type": "git", "url": "https://github.com/myclabs/php-enum.git", - "reference": "b942d263c641ddb5190929ff840c68f78713e937" + "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/php-enum/zipball/b942d263c641ddb5190929ff840c68f78713e937", - "reference": "b942d263c641ddb5190929ff840c68f78713e937", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/a867478eae49c9f59ece437ae7f9506bfaa27483", + "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483", "shasum": "" }, "require": { @@ -246,7 +274,10 @@ "autoload": { "psr-4": { "MyCLabs\\Enum\\": "src/" - } + }, + "classmap": [ + "stubs/Stringable.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -263,6 +294,10 @@ "keywords": [ "enum" ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.4" + }, "funding": [ { "url": "https://github.com/mnapoli", @@ -273,7 +308,7 @@ "type": "tidelift" } ], - "time": "2021-07-05T08:18:36+00:00" + "time": "2022-08-04T09:53:51+00:00" }, { "name": "psr/http-client", @@ -322,6 +357,9 @@ "psr", "psr-18" ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, "time": "2020-06-29T06:28:15+00:00" }, { @@ -374,6 +412,9 @@ "request", "response" ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, "time": "2019-04-30T12:38:16+00:00" }, { @@ -424,6 +465,9 @@ "request", "response" ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, "time": "2016-08-06T14:39:51+00:00" }, { @@ -472,32 +516,38 @@ "psr-16", "simple-cache" ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, "time": "2017-10-23T01:57:42+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.23.1", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", - "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-mbstring": "*" + }, "suggest": { "ext-mbstring": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -535,6 +585,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -549,36 +602,36 @@ "type": "tidelift" } ], - "time": "2021-05-27T12:26:48+00:00" + "time": "2022-05-24T11:49:31+00:00" } ], "packages-dev": [ { "name": "composer/pcre", - "version": "1.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "3d322d715c43a1ac36c7fe215fa59336265500f2" + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/3d322d715c43a1ac36c7fe215fa59336265500f2", - "reference": "3d322d715c43a1ac36c7fe215fa59336265500f2", + "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1", + "phpstan/phpstan": "^1.3", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^4.2 || ^5" + "symfony/phpunit-bridge": "^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -604,6 +657,10 @@ "regex", "regular expression" ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.0.0" + }, "funding": [ { "url": "https://packagist.com", @@ -618,27 +675,27 @@ "type": "tidelift" } ], - "time": "2021-12-06T15:17:27+00:00" + "time": "2022-02-25T20:21:48+00:00" }, { "name": "composer/semver", - "version": "3.2.6", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "83e511e247de329283478496f7a1e114c9517506" + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/83e511e247de329283478496f7a1e114c9517506", - "reference": "83e511e247de329283478496f7a1e114c9517506", + "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.54", + "phpstan/phpstan": "^1.4", "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", @@ -680,6 +737,11 @@ "validation", "versioning" ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.3.2" + }, "funding": [ { "url": "https://packagist.com", @@ -694,31 +756,31 @@ "type": "tidelift" } ], - "time": "2021-10-25T11:34:17+00:00" + "time": "2022-04-01T19:23:25+00:00" }, { "name": "composer/xdebug-handler", - "version": "2.0.3", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6555461e76962fd0379c444c46fd558a0fcfb65e" + "reference": "ced299686f41dce890debac69273b47ffe98a40c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6555461e76962fd0379c444c46fd558a0fcfb65e", - "reference": "6555461e76962fd0379c444c46fd558a0fcfb65e", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", "shasum": "" }, "require": { - "composer/pcre": "^1", - "php": "^5.3.2 || ^7.0 || ^8.0", + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", "psr/log": "^1 || ^2 || ^3" }, "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" + "symfony/phpunit-bridge": "^6.0" }, "type": "library", "autoload": { @@ -741,6 +803,11 @@ "Xdebug", "performance" ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + }, "funding": [ { "url": "https://packagist.com", @@ -755,7 +822,7 @@ "type": "tidelift" } ], - "time": "2021-12-08T13:07:32+00:00" + "time": "2022-02-25T21:32:43+00:00" }, { "name": "dealerdirect/phpcodesniffer-composer-installer", @@ -763,12 +830,12 @@ "source": { "type": "git", "url": "https://github.com/PHPCSStandards/composer-installer.git", - "reference": "04f4e8f6716241cb9200774ff73cb99fbb81e09a" + "reference": "231b4e82eee01f16537c8ac3fce31c1f83320c80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/04f4e8f6716241cb9200774ff73cb99fbb81e09a", - "reference": "04f4e8f6716241cb9200774ff73cb99fbb81e09a", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/231b4e82eee01f16537c8ac3fce31c1f83320c80", + "reference": "231b4e82eee01f16537c8ac3fce31c1f83320c80", "shasum": "" }, "require": { @@ -784,6 +851,7 @@ "phpcompatibility/php-compatibility": "^9.0", "yoast/phpunit-polyfills": "^1.0" }, + "default-branch": true, "type": "composer-plugin", "extra": { "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" @@ -829,20 +897,24 @@ "stylecheck", "tests" ], - "time": "2022-06-26T10:27:07+00:00" + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2022-07-26T12:51:47+00:00" }, { "name": "doctrine/annotations", - "version": "1.13.2", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "5b668aef16090008790395c02c893b1ba13f7e08" + "reference": "648b0343343565c4a056bfc8392201385e8d89f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/5b668aef16090008790395c02c893b1ba13f7e08", - "reference": "5b668aef16090008790395c02c893b1ba13f7e08", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", + "reference": "648b0343343565c4a056bfc8392201385e8d89f0", "shasum": "" }, "require": { @@ -854,9 +926,10 @@ "require-dev": { "doctrine/cache": "^1.11 || ^2.0", "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^0.12.20", + "phpstan/phpstan": "^1.4.10 || ^1.8.0", "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2" + "symfony/cache": "^4.4 || ^5.2", + "vimeo/psalm": "^4.10" }, "type": "library", "autoload": { @@ -897,7 +970,11 @@ "docblock", "parser" ], - "time": "2021-08-05T19:00:23+00:00" + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.13.3" + }, + "time": "2022-07-02T10:48:51+00:00" }, { "name": "doctrine/instantiator", @@ -949,6 +1026,10 @@ "constructor", "instantiate" ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -967,32 +1048,28 @@ }, { "name": "doctrine/lexer", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan": "^0.11.8", - "phpunit/phpunit": "^8.2" + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" @@ -1025,6 +1102,10 @@ "parser", "php" ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.3" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -1039,7 +1120,7 @@ "type": "tidelift" } ], - "time": "2020-05-25T17:44:05+00:00" + "time": "2022-02-28T11:07:21+00:00" }, { "name": "dompdf/dompdf", @@ -1105,56 +1186,60 @@ ], "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v2.0.0" + }, "time": "2022-06-21T21:14:57+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.4.0", + "version": "v3.11.0", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "47177af1cfb9dab5d1cc4daf91b7179c2efe7fad" + "reference": "7dcdea3f2f5f473464e835be9be55283ff8cfdc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/47177af1cfb9dab5d1cc4daf91b7179c2efe7fad", - "reference": "47177af1cfb9dab5d1cc4daf91b7179c2efe7fad", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/7dcdea3f2f5f473464e835be9be55283ff8cfdc3", + "reference": "7dcdea3f2f5f473464e835be9be55283ff8cfdc3", "shasum": "" }, "require": { "composer/semver": "^3.2", - "composer/xdebug-handler": "^2.0", - "doctrine/annotations": "^1.12", + "composer/xdebug-handler": "^3.0.3", + "doctrine/annotations": "^1.13", "ext-json": "*", "ext-tokenizer": "*", - "php": "^7.2.5 || ^8.0", - "php-cs-fixer/diff": "^2.0", - "symfony/console": "^4.4.20 || ^5.1.3 || ^6.0", - "symfony/event-dispatcher": "^4.4.20 || ^5.0 || ^6.0", - "symfony/filesystem": "^4.4.20 || ^5.0 || ^6.0", - "symfony/finder": "^4.4.20 || ^5.0 || ^6.0", - "symfony/options-resolver": "^4.4.20 || ^5.0 || ^6.0", + "php": "^7.4 || ^8.0", + "sebastian/diff": "^4.0", + "symfony/console": "^5.4 || ^6.0", + "symfony/event-dispatcher": "^5.4 || ^6.0", + "symfony/filesystem": "^5.4 || ^6.0", + "symfony/finder": "^5.4 || ^6.0", + "symfony/options-resolver": "^5.4 || ^6.0", "symfony/polyfill-mbstring": "^1.23", - "symfony/polyfill-php80": "^1.23", - "symfony/polyfill-php81": "^1.23", - "symfony/process": "^4.4.20 || ^5.0 || ^6.0", - "symfony/stopwatch": "^4.4.20 || ^5.0 || ^6.0" + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php81": "^1.25", + "symfony/process": "^5.4 || ^6.0", + "symfony/stopwatch": "^5.4 || ^6.0" }, "require-dev": { "justinrainbow/json-schema": "^5.2", "keradus/cli-executor": "^1.5", - "mikey179/vfsstream": "^1.6.8", + "mikey179/vfsstream": "^1.6.10", "php-coveralls/php-coveralls": "^2.5.2", "php-cs-fixer/accessible-object": "^1.1", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", "phpspec/prophecy": "^1.15", - "phpspec/prophecy-phpunit": "^1.1 || ^2.0", - "phpunit/phpunit": "^8.5.21 || ^9.5", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", "phpunitgoodpractices/polyfill": "^1.5", "phpunitgoodpractices/traits": "^1.9.1", - "symfony/phpunit-bridge": "^5.2.4 || ^6.0", - "symfony/yaml": "^4.4.20 || ^5.0 || ^6.0" + "symfony/phpunit-bridge": "^6.0", + "symfony/yaml": "^5.4 || ^6.0" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -1184,67 +1269,30 @@ } ], "description": "A tool to automatically fix PHP code style", + "support": { + "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.11.0" + }, "funding": [ { "url": "https://github.com/keradus", "type": "github" } ], - "time": "2021-12-11T16:25:08+00:00" - }, - { - "name": "jpgraph/jpgraph", - "version": "4.0.2", - "source": { - "type": "git", - "url": "https://github.com/ztec/JpGraph.git", - "reference": "e82db7da6a546d3926c24c9a346226da7aa49094" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ztec/JpGraph/zipball/e82db7da6a546d3926c24c9a346226da7aa49094", - "reference": "e82db7da6a546d3926c24c9a346226da7aa49094", - "shasum": "" - }, - "type": "library", - "autoload": { - "classmap": [ - "lib/JpGraph.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "QPL 1.0" - ], - "authors": [ - { - "name": "JpGraph team" - } - ], - "description": "jpGraph, library to make graphs and charts", - "homepage": "http://jpgraph.net/", - "keywords": [ - "chart", - "data", - "graph", - "jpgraph", - "pie" - ], - "abandoned": true, - "time": "2017-02-23T09:44:15+00:00" + "time": "2022-09-01T18:24:51+00:00" }, { "name": "masterminds/html5", - "version": "2.7.5", + "version": "2.7.6", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f640ac1bdddff06ea333a920c95bbad8872429ab" + "reference": "897eb517a343a2281f11bc5556d6548db7d93947" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f640ac1bdddff06ea333a920c95bbad8872429ab", - "reference": "f640ac1bdddff06ea333a920c95bbad8872429ab", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/897eb517a343a2281f11bc5556d6548db7d93947", + "reference": "897eb517a343a2281f11bc5556d6548db7d93947", "shasum": "" }, "require": { @@ -1296,7 +1344,57 @@ "serializer", "xml" ], - "time": "2021-07-01T14:25:37+00:00" + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.7.6" + }, + "time": "2022-08-18T16:18:26+00:00" + }, + { + "name": "mitoteam/jpgraph", + "version": "10.2.4", + "source": { + "type": "git", + "url": "https://github.com/mitoteam/jpgraph.git", + "reference": "9ce4d106a89f120c7e220ea22205ef7956a7027b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mitoteam/jpgraph/zipball/9ce4d106a89f120c7e220ea22205ef7956a7027b", + "reference": "9ce4d106a89f120c7e220ea22205ef7956a7027b", + "shasum": "" + }, + "require": { + "php": ">=5.5 <=8.2" + }, + "replace": { + "jpgraph/jpgraph": "4.0.2" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/MtJpGraph.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "QPL-1.0" + ], + "authors": [ + { + "name": "JpGraph team" + } + ], + "description": "JpGraph library composer package with PHP 8.2 support", + "homepage": "https://github.com/mitoteam/jpgraph", + "keywords": [ + "jpgraph" + ], + "support": { + "issues": "https://github.com/mitoteam/jpgraph/issues", + "source": "https://github.com/mitoteam/jpgraph/tree/10.2.4" + }, + "time": "2022-09-15T05:57:43+00:00" }, { "name": "mpdf/mpdf", @@ -1362,6 +1460,11 @@ "php", "utf-8" ], + "support": { + "docs": "http://mpdf.github.io", + "issues": "https://github.com/mpdf/mpdf/issues", + "source": "https://github.com/mpdf/mpdf" + }, "funding": [ { "url": "https://www.paypal.me/mpdf", @@ -1417,6 +1520,10 @@ "object", "object graph" ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + }, "funding": [ { "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", @@ -1427,16 +1534,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.14.0", + "version": "v4.15.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", "shasum": "" }, "require": { @@ -1475,7 +1582,11 @@ "parser", "php" ], - "time": "2022-05-31T20:59:12+00:00" + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" + }, + "time": "2022-09-04T07:30:47+00:00" }, { "name": "paragonie/random_compat", @@ -1520,6 +1631,11 @@ "pseudorandom", "random" ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, "time": "2020-10-15T08:29:30+00:00" }, { @@ -1576,6 +1692,10 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, "time": "2021-07-20T11:28:43+00:00" }, { @@ -1623,6 +1743,10 @@ } ], "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, "time": "2022-02-21T01:04:05+00:00" }, { @@ -1663,6 +1787,10 @@ ], "description": "A library to read, parse, export and make subsets of different types of font files.", "homepage": "https://github.com/PhenX/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/0.5.4" + }, "time": "2021-12-17T19:44:54+00:00" }, { @@ -1705,56 +1833,12 @@ ], "description": "A library to read, parse and export to PDF SVG files.", "homepage": "https://github.com/PhenX/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/0.4.1" + }, "time": "2022-03-07T12:52:04+00:00" }, - { - "name": "php-cs-fixer/diff", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/29dc0d507e838c4580d018bd8b5cb412474f7ec3", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", - "symfony/process": "^3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "sebastian/diff v3 backport support for PHP 5.6+", - "homepage": "https://github.com/PHP-CS-Fixer", - "keywords": [ - "diff" - ], - "time": "2020-10-14T08:32:19+00:00" - }, { "name": "php-http/message-factory", "version": "v1.0.2", @@ -1803,6 +1887,10 @@ "stream", "uri" ], + "support": { + "issues": "https://github.com/php-http/message-factory/issues", + "source": "https://github.com/php-http/message-factory/tree/master" + }, "time": "2015-12-19T14:08:53+00:00" }, { @@ -1861,231 +1949,24 @@ "phpcs", "standards" ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, "time": "2019-12-27T09:44:58+00:00" }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2021-10-19T17:43:47+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" - }, - "require-dev": { - "ext-tokenizer": "*", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2022-03-15T21:29:03+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "v1.15.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.2", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" - }, - "require-dev": { - "phpspec/phpspec": "^6.0 || ^7.0", - "phpunit/phpunit": "^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2021-12-08T12:19:24+00:00" - }, { "name": "phpstan/phpstan", - "version": "1.8.0", + "version": "1.8.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "b7648d4ee9321665acaf112e49da9fd93df8fbd5" + "reference": "f6598a5ff12ca4499a836815e08b4d77a2ddeb20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b7648d4ee9321665acaf112e49da9fd93df8fbd5", - "reference": "b7648d4ee9321665acaf112e49da9fd93df8fbd5", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f6598a5ff12ca4499a836815e08b4d77a2ddeb20", + "reference": "f6598a5ff12ca4499a836815e08b4d77a2ddeb20", "shasum": "" }, "require": { @@ -2109,6 +1990,14 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.8.5" + }, "funding": [ { "url": "https://github.com/ondrejmirtes", @@ -2118,16 +2007,12 @@ "url": "https://github.com/phpstan", "type": "github" }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, { "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", "type": "tidelift" } ], - "time": "2022-06-29T08:53:31+00:00" + "time": "2022-09-07T16:05:32+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -2175,27 +2060,31 @@ "MIT" ], "description": "PHPUnit extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-phpunit/issues", + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.1.1" + }, "time": "2022-04-20T15:24:25+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.15", + "version": "9.2.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.13.0", + "nikic/php-parser": "^4.14", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -2242,13 +2131,17 @@ "testing", "xunit" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2022-03-07T09:28:20+00:00" + "time": "2022-08-30T12:24:04+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2298,6 +2191,10 @@ "filesystem", "iterator" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -2357,6 +2254,10 @@ "keywords": [ "process" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -2412,6 +2313,10 @@ "keywords": [ "template" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -2467,6 +2372,10 @@ "keywords": [ "timer" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -2477,16 +2386,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.21", + "version": "9.5.24", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1" + "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e32b76be457de00e83213528f6bb37e2a38fcb1", - "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", + "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", "shasum": "" }, "require": { @@ -2501,7 +2410,6 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpspec/prophecy": "^1.12.1", "phpunit/php-code-coverage": "^9.2.13", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", @@ -2516,12 +2424,9 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.0", + "sebastian/type": "^3.1", "sebastian/version": "^3.0.2" }, - "require-dev": { - "phpspec/prophecy-phpunit": "^2.0.1" - }, "suggest": { "ext-soap": "*", "ext-xdebug": "*" @@ -2561,6 +2466,10 @@ "testing", "xunit" ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.24" + }, "funding": [ { "url": "https://phpunit.de/sponsors.html", @@ -2571,7 +2480,7 @@ "type": "github" } ], - "time": "2022-06-19T12:14:25+00:00" + "time": "2022-08-30T07:42:16+00:00" }, { "name": "psr/cache", @@ -2617,24 +2526,27 @@ "psr", "psr-6" ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, "time": "2016-08-06T20:24:11+00:00" }, { "name": "psr/container", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=7.4.0" }, "type": "library", "autoload": { @@ -2661,7 +2573,11 @@ "container-interop", "psr" ], - "time": "2021-03-05T17:36:06+00:00" + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" }, { "name": "psr/event-dispatcher", @@ -2707,6 +2623,10 @@ "psr", "psr-14" ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, "time": "2019-01-08T18:20:26+00:00" }, { @@ -2754,6 +2674,9 @@ "psr", "psr-3" ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, "time": "2021-05-03T11:20:27+00:00" }, { @@ -2803,6 +2726,10 @@ "parser", "stylesheet" ], + "support": { + "issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues", + "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.4.0" + }, "time": "2021-12-11T13:40:54+00:00" }, { @@ -2849,6 +2776,10 @@ ], "description": "Library for parsing CLI options", "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -2901,6 +2832,10 @@ ], "description": "Collection of value objects that represent the PHP code units", "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -2952,6 +2887,10 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -2962,16 +2901,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { @@ -3022,13 +2961,17 @@ "compare", "equality" ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-10-26T15:49:45+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", @@ -3075,6 +3018,10 @@ ], "description": "Library for calculating the complexity of PHP code units", "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -3137,6 +3084,10 @@ "unidiff", "unified diff" ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -3196,6 +3147,10 @@ "environment", "hhvm" ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -3206,16 +3161,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", "shasum": "" }, "require": { @@ -3269,13 +3224,17 @@ "export", "exporter" ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2021-11-11T14:18:36+00:00" + "time": "2022-09-14T06:03:37+00:00" }, { "name": "sebastian/global-state", @@ -3329,6 +3288,10 @@ "keywords": [ "global state" ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -3382,6 +3345,10 @@ ], "description": "Library for counting the lines of code in PHP source code", "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -3435,6 +3402,10 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -3486,6 +3457,10 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -3545,6 +3520,10 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -3596,6 +3575,10 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -3606,16 +3589,16 @@ }, { "name": "sebastian/type", - "version": "3.0.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", "shasum": "" }, "require": { @@ -3627,7 +3610,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -3648,13 +3631,17 @@ ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2022-03-15T09:54:48+00:00" + "time": "2022-09-12T14:47:03+00:00" }, { "name": "sebastian/version", @@ -3697,6 +3684,10 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -3765,6 +3756,10 @@ "fpdi", "pdf" ], + "support": { + "issues": "https://github.com/Setasign/FPDI/issues", + "source": "https://github.com/Setasign/FPDI/tree/v2.3.6" + }, "funding": [ { "url": "https://tidelift.com/funding/github/packagist/setasign/fpdi", @@ -3822,20 +3817,25 @@ "phpcs", "standards" ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, "time": "2022-06-18T07:21:10+00:00" }, { "name": "symfony/console", - "version": "v5.4.2", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a2c6b7ced2eb7799a35375fb9022519282b5405e" + "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a2c6b7ced2eb7799a35375fb9022519282b5405e", - "reference": "a2c6b7ced2eb7799a35375fb9022519282b5405e", + "url": "https://api.github.com/repos/symfony/console/zipball/c072aa8f724c3af64e2c7a96b796a4863d24dba1", + "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1", "shasum": "" }, "require": { @@ -3904,6 +3904,9 @@ "console", "terminal" ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.12" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3918,20 +3921,20 @@ "type": "tidelift" } ], - "time": "2021-12-20T16:11:12+00:00" + "time": "2022-08-17T13:18:05+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.0", + "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", - "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", "shasum": "" }, "require": { @@ -3968,6 +3971,9 @@ ], "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3982,20 +3988,20 @@ "type": "tidelift" } ], - "time": "2021-07-12T14:48:14+00:00" + "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.4.0", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "27d39ae126352b9fa3be5e196ccf4617897be3eb" + "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/27d39ae126352b9fa3be5e196ccf4617897be3eb", - "reference": "27d39ae126352b9fa3be5e196ccf4617897be3eb", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", "shasum": "" }, "require": { @@ -4050,6 +4056,9 @@ ], "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.9" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4064,20 +4073,20 @@ "type": "tidelift" } ], - "time": "2021-11-23T10:19:22+00:00" + "time": "2022-05-05T16:45:39+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.5.0", + "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "66bea3b09be61613cd3b4043a65a8ec48cfa6d2a" + "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/66bea3b09be61613cd3b4043a65a8ec48cfa6d2a", - "reference": "66bea3b09be61613cd3b4043a65a8ec48cfa6d2a", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", + "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", "shasum": "" }, "require": { @@ -4126,6 +4135,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4140,20 +4152,20 @@ "type": "tidelift" } ], - "time": "2021-07-12T14:48:14+00:00" + "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/filesystem", - "version": "v5.4.0", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "731f917dc31edcffec2c6a777f3698c33bea8f01" + "reference": "2d67c1f9a1937406a9be3171b4b22250c0a11447" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/731f917dc31edcffec2c6a777f3698c33bea8f01", - "reference": "731f917dc31edcffec2c6a777f3698c33bea8f01", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2d67c1f9a1937406a9be3171b4b22250c0a11447", + "reference": "2d67c1f9a1937406a9be3171b4b22250c0a11447", "shasum": "" }, "require": { @@ -4187,6 +4199,9 @@ ], "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.12" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4201,20 +4216,20 @@ "type": "tidelift" } ], - "time": "2021-10-28T13:39:27+00:00" + "time": "2022-08-02T13:48:16+00:00" }, { "name": "symfony/finder", - "version": "v5.4.2", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "e77046c252be48c48a40816187ed527703c8f76c" + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/e77046c252be48c48a40816187ed527703c8f76c", - "reference": "e77046c252be48c48a40816187ed527703c8f76c", + "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", "shasum": "" }, "require": { @@ -4247,6 +4262,9 @@ ], "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.11" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4261,20 +4279,20 @@ "type": "tidelift" } ], - "time": "2021-12-15T11:06:13+00:00" + "time": "2022-07-29T07:37:50+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.4.0", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "b0fb78576487af19c500aaddb269fd36701d4847" + "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b0fb78576487af19c500aaddb269fd36701d4847", - "reference": "b0fb78576487af19c500aaddb269fd36701d4847", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/54f14e36aa73cb8f7261d7686691fd4d75ea2690", + "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690", "shasum": "" }, "require": { @@ -4313,6 +4331,9 @@ "configuration", "options" ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.4.11" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4327,7 +4348,7 @@ "type": "tidelift" } ], - "time": "2021-11-23T10:19:22+00:00" + "time": "2022-07-20T13:00:38+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4392,6 +4413,9 @@ "polyfill", "portable" ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4410,16 +4434,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.23.1", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "16880ba9c5ebe3642d1995ab866db29270b36535" + "reference": "433d05519ce6990bf3530fba6957499d327395c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/16880ba9c5ebe3642d1995ab866db29270b36535", - "reference": "16880ba9c5ebe3642d1995ab866db29270b36535", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", "shasum": "" }, "require": { @@ -4431,7 +4455,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4470,6 +4494,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4484,20 +4511,20 @@ "type": "tidelift" } ], - "time": "2021-05-27T12:26:48+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.23.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", "shasum": "" }, "require": { @@ -4509,7 +4536,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4551,6 +4578,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4565,20 +4595,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.23.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", "shasum": "" }, "require": { @@ -4587,7 +4617,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4627,6 +4657,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4641,20 +4674,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.23.1", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", - "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", "shasum": "" }, "require": { @@ -4663,7 +4696,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4707,6 +4740,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4721,20 +4757,20 @@ "type": "tidelift" } ], - "time": "2021-07-28T13:41:28+00:00" + "time": "2022-05-10T07:21:04+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.23.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "e66119f3de95efc359483f810c4c3e6436279436" + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436", - "reference": "e66119f3de95efc359483f810c4c3e6436279436", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", "shasum": "" }, "require": { @@ -4743,7 +4779,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4783,6 +4819,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4797,20 +4836,20 @@ "type": "tidelift" } ], - "time": "2021-05-21T13:25:03+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/process", - "version": "v5.4.2", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "2b3ba8722c4aaf3e88011be5e7f48710088fb5e4" + "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/2b3ba8722c4aaf3e88011be5e7f48710088fb5e4", - "reference": "2b3ba8722c4aaf3e88011be5e7f48710088fb5e4", + "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1", + "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1", "shasum": "" }, "require": { @@ -4842,6 +4881,9 @@ ], "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.11" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4856,26 +4898,26 @@ "type": "tidelift" } ], - "time": "2021-12-27T21:01:00+00:00" + "time": "2022-06-27T16:58:25+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.0", + "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", - "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", "shasum": "" }, "require": { "php": ">=7.2.5", "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1" + "symfony/deprecation-contracts": "^2.1|^3" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -4922,6 +4964,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4936,20 +4981,20 @@ "type": "tidelift" } ], - "time": "2021-11-04T16:48:04+00:00" + "time": "2022-05-30T19:17:29+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.4.0", + "version": "v5.4.5", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "208ef96122bfed82a8f3a61458a07113a08bdcfe" + "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/208ef96122bfed82a8f3a61458a07113a08bdcfe", - "reference": "208ef96122bfed82a8f3a61458a07113a08bdcfe", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/4d04b5c24f3c9a1a168a131f6cbe297155bc0d30", + "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30", "shasum": "" }, "require": { @@ -4981,6 +5026,9 @@ ], "description": "Provides a way to profile code", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v5.4.5" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4995,20 +5043,20 @@ "type": "tidelift" } ], - "time": "2021-11-23T10:19:22+00:00" + "time": "2022-02-18T16:06:09+00:00" }, { "name": "symfony/string", - "version": "v5.4.2", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "e6a5d5ecf6589c5247d18e0e74e30b11dfd51a3d" + "reference": "2fc515e512d721bf31ea76bd02fe23ada4640058" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/e6a5d5ecf6589c5247d18e0e74e30b11dfd51a3d", - "reference": "e6a5d5ecf6589c5247d18e0e74e30b11dfd51a3d", + "url": "https://api.github.com/repos/symfony/string/zipball/2fc515e512d721bf31ea76bd02fe23ada4640058", + "reference": "2fc515e512d721bf31ea76bd02fe23ada4640058", "shasum": "" }, "require": { @@ -5064,6 +5112,9 @@ "utf-8", "utf8" ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.12" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -5078,20 +5129,20 @@ "type": "tidelift" } ], - "time": "2021-12-16T21:52:00+00:00" + "time": "2022-08-12T17:03:11+00:00" }, { "name": "tecnickcom/tcpdf", - "version": "6.4.4", + "version": "6.5.0", "source": { "type": "git", "url": "https://github.com/tecnickcom/TCPDF.git", - "reference": "42cd0f9786af7e5db4fcedaa66f717b0d0032320" + "reference": "cc54c1503685e618b23922f53635f46e87653662" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/42cd0f9786af7e5db4fcedaa66f717b0d0032320", - "reference": "42cd0f9786af7e5db4fcedaa66f717b0d0032320", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/cc54c1503685e618b23922f53635f46e87653662", + "reference": "cc54c1503685e618b23922f53635f46e87653662", "shasum": "" }, "require": { @@ -5140,13 +5191,17 @@ "pdf417", "qrcode" ], + "support": { + "issues": "https://github.com/tecnickcom/TCPDF/issues", + "source": "https://github.com/tecnickcom/TCPDF/tree/6.5.0" + }, "funding": [ { "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_donations¤cy_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20tcpdf%20project", "type": "custom" } ], - "time": "2021-12-31T08:39:24+00:00" + "time": "2022-08-12T07:50:54+00:00" }, { "name": "theseer/tokenizer", @@ -5186,6 +5241,10 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, "funding": [ { "url": "https://github.com/theseer", @@ -5193,60 +5252,6 @@ } ], "time": "2021-07-28T10:34:58+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "time": "2022-06-03T18:03:27+00:00" } ], "aliases": [], @@ -5273,5 +5278,5 @@ "ext-zlib": "*" }, "platform-dev": [], - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.2.0" } diff --git a/docs/index.md b/docs/index.md index ff137c26..63d71655 100644 --- a/docs/index.md +++ b/docs/index.md @@ -53,7 +53,7 @@ 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 +```json { "require": { "phpoffice/phpspreadsheet": "^1.23" diff --git a/docs/topics/calculation-engine.md b/docs/topics/calculation-engine.md index 4fd300e8..7dc838f7 100644 --- a/docs/topics/calculation-engine.md +++ b/docs/topics/calculation-engine.md @@ -22,6 +22,13 @@ with PhpSpreadsheet, it evaluates to the value "64": ![09-command-line-calculation.png](./images/09-command-line-calculation.png) +When writing a formula to a cell, formulae should always be set as they would appear in an English version of Microsoft Office Excel, and PhpSpreadsheet handles all formulae internally in this format. This means that the following rules hold: + + - Decimal separator is `.` (period) + - Function argument separator is `,` (comma) + - Matrix row separator is `;` (semicolon) + - English function names must be used + Another nice feature of PhpSpreadsheet's formula parser, is that it can automatically adjust a formula when inserting/removing rows/columns. Here's an example: @@ -43,6 +50,11 @@ inserted 2 new rows), changed to "SUM(E4:E11)". Also, the inserted cells duplicate style information of the previous cell, just like Excel's behaviour. Note that you can both insert rows and columns. +If you want to "anchor" a specific cell for a formula, then you prefix the column and/or the row with a `$` symbol, exactly as you would in MS Excel itself. +So if a formula contains "SUM(E$4:E9)", and you insert 2 new rows after row 1, the formula will be adjusted to read "SUM(E$4:E11)", with the `$` fixing row 4 as the start of the range. + + + ## Calculation Cache Once the Calculation engine has evaluated the formula in a cell, the result diff --git a/docs/topics/images/12-01-MergeCells-Options-2.png b/docs/topics/images/12-01-MergeCells-Options-2.png new file mode 100644 index 00000000..5e745fc9 Binary files /dev/null and b/docs/topics/images/12-01-MergeCells-Options-2.png differ diff --git a/docs/topics/images/12-01-MergeCells-Options-3.png b/docs/topics/images/12-01-MergeCells-Options-3.png new file mode 100644 index 00000000..30ad346e Binary files /dev/null and b/docs/topics/images/12-01-MergeCells-Options-3.png differ diff --git a/docs/topics/images/12-01-MergeCells-Options.png b/docs/topics/images/12-01-MergeCells-Options.png new file mode 100644 index 00000000..34c8f5e3 Binary files /dev/null and b/docs/topics/images/12-01-MergeCells-Options.png differ diff --git a/docs/topics/reading-and-writing-to-file.md b/docs/topics/reading-and-writing-to-file.md index 0bcc1909..55929e85 100644 --- a/docs/topics/reading-and-writing-to-file.md +++ b/docs/topics/reading-and-writing-to-file.md @@ -282,6 +282,7 @@ versions of Microsoft Excel. **Excel 2003 XML limitations** Please note that Excel 2003 XML format has some limits regarding to styling cells and handling large spreadsheets via PHP. +Also, only files using charset UTF-8 are supported. ### \PhpOffice\PhpSpreadsheet\Reader\Xml @@ -701,6 +702,7 @@ extension. **HTML limitations** Please note that HTML file format has some limits regarding to styling cells, number formatting, ... +Also, only files using charset UTF-8 are supported. ### \PhpOffice\PhpSpreadsheet\Reader\Html diff --git a/docs/topics/recipes.md b/docs/topics/recipes.md index e4313382..04a4e6aa 100644 --- a/docs/topics/recipes.md +++ b/docs/topics/recipes.md @@ -1161,7 +1161,7 @@ A column's width can be set using the following code: $spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(12); ``` -If you want to set a column width using a different unit of measure, +If you want to set a column width using a different UoM (Unit of Measure), then you can do so by telling PhpSpreadsheet what UoM the width value that you are setting is measured in. Valid units are `pt` (points), `px` (pixels), `pc` (pica), `in` (inches), @@ -1258,7 +1258,7 @@ Excel measures row height in points, where 1 pt is 1/72 of an inch (or about 0.35mm). The default value is 12.75 pts; and the permitted range of values is between 0 and 409 pts, where 0 pts is a hidden row. -If you want to set a row height using a different unit of measure, +If you want to set a row height using a different UoM (Unit of Measure), then you can do so by telling PhpSpreadsheet what UoM the height value that you are setting is measured in. Valid units are `pt` (points), `px` (pixels), `pc` (pica), `in` (inches), @@ -1332,22 +1332,72 @@ rows (default), or above. The following code adds the summary above: $spreadsheet->getActiveSheet()->setShowSummaryBelow(false); ``` -## Merge/unmerge cells +## Merge/Unmerge cells -If you have a big piece of data you want to display in a worksheet, you -can merge two or more cells together, to become one cell. This can be -done using the following code: +If you have a big piece of data you want to display in a worksheet, or a +heading that needs to span multiple sub-heading columns, you can merge +two or more cells together, to become one cell. This can be done using +the following code: ```php $spreadsheet->getActiveSheet()->mergeCells('A18:E22'); ``` -Removing a merge can be done using the unmergeCells method: +Removing a merge can be done using the `unmergeCells()` method: ```php $spreadsheet->getActiveSheet()->unmergeCells('A18:E22'); ``` +MS Excel itself doesn't yet offer the functionality to simply hide the merged cells, or to merge the content of cells into a single cell, but it is available in Open/Libre Office. + +### Merge with MERGE_CELL_CONTENT_EMPTY + +The default behaviour is to empty all cells except for the top-left corner cell in the merge range; and this is also the default behaviour for the `mergeCells()` method in PhpSpreadsheet. +When this behaviour is applied, those cell values will be set to null; and if they are subsequently Unmerged, they will be empty cells. + +Passing an extra flag value to the `mergeCells()` method in PhpSpreadsheet can change this behaviour. + +![12-01-MergeCells-Options.png](./images/12-01-MergeCells-Options.png) + +Possible flag values are: +- Worksheet::MERGE_CELL_CONTENT_EMPTY (the default) +- Worksheet::MERGE_CELL_CONTENT_HIDE +- Worksheet::MERGE_CELL_CONTENT_MERGE + +### Merge with MERGE_CELL_CONTENT_HIDE + +The first alternative, available only in OpenOffice, is to hide those cells, but to leave their content intact. +When a file saved as `Xlsx` in those applications is opened in MS Excel, and those cells are unmerged, the original content will still be present. + +```php +$spreadsheet->getActiveSheet()->mergeCells('A1:C3', Worksheet::MERGE_CELL_CONTENT_HIDE); +``` + +Will replicate that behaviour. + +### Merge with MERGE_CELL_CONTENT_MERGE + +The second alternative, available in both OpenOffice and LibreOffice is to merge the content of every cell in the merge range into the top-left cell, while setting those hidden cells to empty. + +```php +$spreadsheet->getActiveSheet()->mergeCells('A1:C3', Worksheet::MERGE_CELL_CONTENT_MERGE); +``` + +Particularly when the merged cells contain formulae, the logic for this merge seems strange: +walking through the merge range, each cell is calculated in turn, and appended to the "master" cell, then it is emptied, so any subsequent calculations that reference the cell see an empty cell, not the pre-merge value. +For example, suppose our spreadsheet contains + +![12-01-MergeCells-Options-2.png](./images/12-01-MergeCells-Options-2.png) + +where `B2` is the formula `=5-B1` and `C2` is the formula `=A2/B2`, +and we want to merge cells `A2` to `C2` with all the cell values merged. +The result is: + +![12-01-MergeCells-Options-3.png](./images/12-01-MergeCells-Options-3.png) + +The cell value `12` from cell `A2` is fixed; the value from `B2` is the result of the formula `=5-B1` (`4`, which is appended to our merged value), and cell `B2` is then emptied, so when we evaluate cell `C2` with the formula `=A2/B2` it gives us `12 / 0` which results in a `#DIV/0!` error (so the error `#DIV/0!` is appended to our merged value rather than the original calculation result of `3`). + ## Inserting or Removing rows/columns You can insert/remove rows/columns at a specific position. The following @@ -1670,7 +1720,7 @@ $spreadsheet->getActiveSheet()->getDefaultColumnDimension()->setWidth(12); Excel measures column width in its own proprietary units, based on the number of characters that will be displayed in the default font. -If you want to set the default column width using a different unit of measure, +If you want to set the default column width using a different UoM (Unit of Measure), then you can do so by telling PhpSpreadsheet what UoM the width value that you are setting is measured in. Valid units are `pt` (points), `px` (pixels), `pc` (pica), `in` (inches), @@ -1693,7 +1743,7 @@ Excel measures row height in points, where 1 pt is 1/72 of an inch (or about 0.35mm). The default value is 12.75 pts; and the permitted range of values is between 0 and 409 pts, where 0 pts is a hidden row. -If you want to set a row height using a different unit of measure, +If you want to set a row height using a different UoM (Unit of Measure), then you can do so by telling PhpSpreadsheet what UoM the height value that you are setting is measured in. Valid units are `pt` (points), `px` (pixels), `pc` (pica), `in` (inches), diff --git a/infra/DocumentGenerator.php b/infra/DocumentGenerator.php index e2c3c86c..8a6be076 100644 --- a/infra/DocumentGenerator.php +++ b/infra/DocumentGenerator.php @@ -41,7 +41,7 @@ class DocumentGenerator private static function tableRow(array $lengths, ?array $values = null): string { $result = ''; - foreach (array_map(null, $lengths, $values ?? []) as $i => [$length, $value]) { + foreach (array_map(/** @scrutinizer ignore-type */ null, $lengths, $values ?? []) as $i => [$length, $value]) { $pad = $value === null ? '-' : ' '; if ($i > 0) { $result .= '|' . $pad; diff --git a/infra/LocaleGenerator.php b/infra/LocaleGenerator.php index bb97754d..992bde17 100644 --- a/infra/LocaleGenerator.php +++ b/infra/LocaleGenerator.php @@ -146,7 +146,7 @@ class LocaleGenerator $translationValue = $translationCell->getValue(); if ($this->isFunctionCategoryEntry($translationCell)) { $this->writeFileSectionHeader($functionFile, "{$translationValue} ({$functionName})"); - } elseif (!array_key_exists($functionName, $this->phpSpreadsheetFunctions)) { + } elseif (!array_key_exists($functionName, $this->phpSpreadsheetFunctions) && substr($functionName, 0, 1) !== '*') { $this->log("Function {$functionName} is not defined in PhpSpreadsheet"); } elseif (!empty($translationValue)) { $functionTranslation = "{$functionName} = {$translationValue}" . self::EOL; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5ef5522f..b0271f35 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -515,11 +515,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Calculation/FormulaParser.php - - - message: "#^Strict comparison using \\=\\=\\= between string and null will always evaluate to false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/FormulaParser.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:ifCondition\\(\\) has no return type specified\\.$#" count: 1 @@ -1065,11 +1060,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Calculation/TextData/Text.php - - - message: "#^Elseif branch is unreachable because previous condition is always true\\.$#" - count: 1 - path: src/PhpSpreadsheet/Cell/Cell.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\:\\:getFormulaAttributes\\(\\) has no return type specified\\.$#" count: 1 @@ -1110,11 +1100,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Cell/Coordinate.php - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Memory\\:\\:\\$cache has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Collection/Memory.php - - message: "#^Parameter \\#1 \\$namedRange of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:addNamedRange\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\NamedRange, \\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\) given\\.$#" count: 1 @@ -1675,456 +1660,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xls/RC4.php - - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access offset 0 on array\\\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot access property \\$r on SimpleXMLElement\\|null\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method addChart\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method setBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method setColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method setItalic\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method setName\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method setSize\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method setStrikethrough\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method setSubscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method setSuperscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Cannot call method setUnderline\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Comparison operation \"\\>\" between SimpleXMLElement\\|null and 0 results in an error\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:boolean\\(\\) has parameter \\$value with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToBoolean\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToBoolean\\(\\) has parameter \\$c with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToError\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToError\\(\\) has parameter \\$c with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$c with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$calculatedValue with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$castBaseType with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$cellDataType with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$r with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$sharedFormulas with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$value with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToString\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToString\\(\\) has parameter \\$c with no 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 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:dirAdd\\(\\) has parameter \\$base with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:getArrayItem\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:getArrayItem\\(\\) has parameter \\$array with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:getArrayItem\\(\\) has parameter \\$key with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:getFromZipArchive\\(\\) should return string but returns string\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readFormControlProperties\\(\\) has parameter \\$dir with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readFormControlProperties\\(\\) has parameter \\$docSheet with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readFormControlProperties\\(\\) has parameter \\$fileWorksheet with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readPrinterSettings\\(\\) has parameter \\$dir with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readPrinterSettings\\(\\) has parameter \\$docSheet with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readPrinterSettings\\(\\) has parameter \\$fileWorksheet with no 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 parameter \\$style with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Negated boolean expression is always true\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Parameter \\#1 \\$fontSizeInPoints of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:fontSizeToPixels\\(\\) expects int, string given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Parameter \\#1 \\$haystack of function strpos expects string, int\\|string given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Parameter \\#1 \\$sizeInCm of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:centimeterSizeToPixels\\(\\) expects int, string given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Parameter \\#1 \\$sizeInInch of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:inchSizeToPixels\\(\\) expects int, string given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Parameter \\#1 \\$worksheetName of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getSheetByName\\(\\) expects string, array\\|string given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, int\\|string given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\AutoFilter\\:\\:readAutoFilter\\(\\) has parameter \\$autoFilterRange with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\AutoFilter\\:\\:readAutoFilter\\(\\) has parameter \\$xmlSheet with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php - - - - message: "#^Parameter \\#1 \\$operator of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\\\Rule\\:\\:setRule\\(\\) expects string, null given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\AutoFilter\\:\\:\\$worksheet has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\AutoFilter\\:\\:\\$worksheetXml has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\BaseParserClass\\:\\:boolean\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\BaseParserClass\\:\\:boolean\\(\\) has parameter \\$value with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredColumn\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredColumn\\(\\) has parameter \\$columnCoordinate with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredRow\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredRow\\(\\) has parameter \\$rowCoordinate with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readColumnAttributes\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readColumnAttributes\\(\\) has parameter \\$readDataOnly with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readColumnRangeAttributes\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readColumnRangeAttributes\\(\\) has parameter \\$readDataOnly with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readRowAttributes\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readRowAttributes\\(\\) has parameter \\$readDataOnly with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:\\$worksheet has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:\\$worksheetXml has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readConditionalStyles\\(\\) has parameter \\$xmlSheet with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readDataBarExtLstOfConditionalRule\\(\\) has parameter \\$cfRule with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readDataBarExtLstOfConditionalRule\\(\\) has parameter \\$conditionalFormattingRuleExtensions with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readDataBarOfConditionalRule\\(\\) has parameter \\$cfRule with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readDataBarOfConditionalRule\\(\\) has parameter \\$conditionalFormattingRuleExtensions with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readStyleRules\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readStyleRules\\(\\) has parameter \\$cfRules with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readStyleRules\\(\\) has parameter \\$extLst with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:setConditionalStyles\\(\\) has parameter \\$xmlExtLst with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:\\$dxfs has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:\\$worksheet has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:\\$worksheetXml has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\DataValidations\\:\\:\\$worksheet has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\DataValidations\\:\\:\\$worksheetXml has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Hyperlinks\\:\\:\\$hyperlinks has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Hyperlinks\\:\\:\\$worksheet has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\PageSetup\\:\\:load\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\PageSetup\\:\\:pageSetup\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\PageSetup\\:\\:\\$worksheet has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\PageSetup\\:\\:\\$worksheetXml has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\SheetViewOptions\\:\\:\\$worksheet has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\SheetViewOptions\\:\\:\\$worksheetXml has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php - - message: "#^Parameter \\#1 \\$haystack of function strpos expects string, string\\|false given\\.$#" count: 1 @@ -2270,51 +1805,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php - - - message: "#^Cannot access offset 0 on array\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/Font.php - - - - message: "#^Cannot access offset 2 on array\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/Font.php - - - - message: "#^Cannot access offset 4 on array\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/Font.php - - - - message: "#^Cannot access offset 6 on array\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/Font.php - - - - message: "#^Parameter \\#1 \\$size of function imagettfbbox expects float, float\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/Font.php - - - - message: "#^Parameter \\#2 \\$defaultFont of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Drawing\\:\\:pixelsToCellDimension\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/Font.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:\\$autoSizeMethods has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/Font.php - - - - message: "#^Unreachable statement \\- code above always terminates\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/Font.php - - - - message: "#^Variable \\$cellText on left side of \\?\\? always exists and is not nullable\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/Font.php - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\EigenvalueDecomposition\\:\\:\\$cdivi has no type specified\\.$#" count: 1 @@ -2335,11 +1825,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php - - - message: "#^Call to function is_string\\(\\) with float\\|int will always evaluate to false\\.$#" - count: 5 - path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" count: 1 @@ -2420,11 +1905,6 @@ parameters: count: 2 path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php - - - message: "#^Result of && is always false\\.$#" - count: 10 - path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php - - message: "#^Unreachable statement \\- code above always terminates\\.$#" count: 19 @@ -2860,11 +2340,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Spreadsheet.php - - - message: "#^Comparison operation \"\\<\\=\" between int\\ and 1000 is always true\\.$#" - count: 1 - path: src/PhpSpreadsheet/Spreadsheet.php - - message: "#^Parameter \\#1 \\$worksheet of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getIndex\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet, PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null given\\.$#" count: 1 @@ -2875,21 +2350,11 @@ parameters: count: 1 path: src/PhpSpreadsheet/Spreadsheet.php - - - message: "#^Result of \\|\\| is always true\\.$#" - count: 1 - path: src/PhpSpreadsheet/Spreadsheet.php - - message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet and null will always evaluate to false\\.$#" count: 1 path: src/PhpSpreadsheet/Spreadsheet.php - - - message: "#^Strict comparison using \\=\\=\\= between string and null will always evaluate to false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Spreadsheet.php - - message: "#^Unreachable statement \\- code above always terminates\\.$#" count: 1 @@ -3125,21 +2590,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Worksheet/PageSetup.php - - - message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\PageSetup\\:\\:setFirstPageNumber\\(\\) expects int, null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Worksheet/PageSetup.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\PageSetup\\:\\:\\$pageOrder has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Worksheet/PageSetup.php - - - - message: "#^Strict comparison using \\=\\=\\= between int\\ and null will always evaluate to false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Worksheet/PageSetup.php - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\SheetView\\:\\:\\$sheetViewTypes has no type specified\\.$#" count: 1 @@ -3240,316 +2690,6 @@ parameters: count: 2 path: src/PhpSpreadsheet/Worksheet/Worksheet.php - - - message: "#^Call to function array_key_exists\\(\\) with int and array\\{none\\: 'none', dashDot\\: '1px dashed', dashDotDot\\: '1px dotted', dashed\\: '1px dashed', dotted\\: '1px dotted', double\\: '3px double', hair\\: '1px solid', medium\\: '2px solid', \\.\\.\\.\\} will always evaluate to false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Cannot access offset 'mime' on array\\|false\\.$#" - count: 2 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Cannot access offset 0 on array\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Cannot access offset 1 on array\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Cannot call method getSubscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Cannot call method getSuperscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:calculateSpansOmitRows\\(\\) has parameter \\$candidateSpannedRow with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:calculateSpansOmitRows\\(\\) has parameter \\$sheet with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:calculateSpansOmitRows\\(\\) has parameter \\$sheetIndex with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateHTMLFooter\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateMeta\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateMeta\\(\\) has parameter \\$desc with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateMeta\\(\\) has parameter \\$val with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has parameter \\$cellAddress with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has parameter \\$columnNumber with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has parameter \\$row with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has parameter \\$cell with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has parameter \\$cellType with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has parameter \\$cssClass with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValue\\(\\) has parameter \\$cell with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValue\\(\\) has parameter \\$cellData with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValueRich\\(\\) has parameter \\$cell with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValueRich\\(\\) has parameter \\$cellData with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowIncludeCharts\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowIncludeCharts\\(\\) has parameter \\$coordinate with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowSpans\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowSpans\\(\\) has parameter \\$colSpan with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowSpans\\(\\) has parameter \\$html with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowSpans\\(\\) has parameter \\$rowSpan with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$cellData with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$cellType with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$colNum with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$colSpan with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$coordinate with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$cssClass with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$html with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$row with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$rowSpan with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$sheetIndex with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetPrep\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetStarts\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetStarts\\(\\) has parameter \\$rowMin with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetStarts\\(\\) has parameter \\$sheet with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has parameter \\$row with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has parameter \\$tbodyStart with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has parameter \\$theadEnd with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has parameter \\$theadStart with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableFooter\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTag\\(\\) has parameter \\$html with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTag\\(\\) has parameter \\$id with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTag\\(\\) has parameter \\$sheetIndex with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTagInline\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTagInline\\(\\) has parameter \\$id with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Parameter \\#1 \\$borderStyle of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:mapBorderStyle\\(\\) expects int, string given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Parameter \\#1 \\$font of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:createCSSStyleFont\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Parameter \\#1 \\$hAlign of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:mapHAlign\\(\\) 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 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Parameter \\#2 \\$length of function fread expects int\\<0, max\\>, int\\<0, max\\>\\|false given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - - - message: "#^Parameter \\#3 \\$use_include_path of function fopen expects bool, int given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Html.php - - message: "#^Negated boolean expression is always false\\.$#" count: 1 @@ -3772,7 +2912,7 @@ parameters: - message: "#^Cannot access offset 1 on array\\|false\\.$#" - count: 2 + count: 1 path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php - @@ -3869,289 +3009,3 @@ parameters: message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Xf\\:\\:\\$diag is never read, only written\\.$#" count: 1 path: src/PhpSpreadsheet/Writer/Xls/Xf.php - - - - message: "#^Argument of an invalid type array\\|null supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx.php - - - - message: "#^Parameter \\#1 \\$path of function basename expects string, array\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx.php - - - - message: "#^Parameter \\#1 \\$path of function dirname expects string, array\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx.php - - - - message: "#^Possibly invalid array key type array\\|string\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\:\\:\\$pathNames has no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx.php - - - - message: "#^Parameter \\#1 \\$string of function substr expects string, int given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Comments.php - - - - message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, int given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Writer/Xlsx/Comments.php - - - - message: "#^Expression on left side of \\?\\? is not nullable\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php - - - - message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, int given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/DocProps.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Writer/Xlsx/DocProps.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeUnparsedRelationship\\(\\) has parameter \\$relationship with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeUnparsedRelationship\\(\\) has parameter \\$type with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php - - - - message: "#^Parameter \\#2 \\$id of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeRelationship\\(\\) expects int, string given\\.$#" - count: 5 - path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php - - - - message: "#^Parameter \\#4 \\$target of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeRelationship\\(\\) expects string, array\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php - - - - message: "#^Cannot call method getBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Cannot call method getColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Cannot call method getItalic\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Cannot call method getName\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Cannot call method getSize\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Cannot call method getStrikethrough\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Cannot call method getSubscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 2 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Cannot call method getSuperscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 2 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Cannot call method getUnderline\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Instanceof between \\*NEVER\\* and PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\RichText will always evaluate to false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Instanceof between string and PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\RichText will always evaluate to false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Parameter \\#1 \\$text of method PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\RichText\\:\\:createTextRun\\(\\) expects string, string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, float\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<0, max\\> given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, string\\|null given\\.$#" - count: 4 - path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php - - - - message: "#^Cannot call method getStyle\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Comparison operation \"\\<\" between int\\ and 0 is always true\\.$#" - count: 2 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Parameter \\#2 \\$borders of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Style\\:\\:writeBorder\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Borders, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Borders\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Parameter \\#2 \\$fill of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Style\\:\\:writeFill\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Fill, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Fill\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Parameter \\#2 \\$font of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Style\\:\\:writeFont\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Parameter \\#2 \\$numberFormat of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Style\\:\\:writeNumFmt\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, float given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" - count: 22 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<0, max\\> given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<1, max\\> given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, string\\|null given\\.$#" - count: 7 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Result of \\|\\| is always true\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Style.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" - count: 7 - path: src/PhpSpreadsheet/Writer/Xlsx/Workbook.php - - - - message: "#^Expression on left side of \\?\\? is not nullable\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - - message: "#^If condition is always true\\.$#" - count: 6 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeAttributeIf\\(\\) has parameter \\$condition with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeDataBarElements\\(\\) has parameter \\$dataBar with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeElementIf\\(\\) has parameter \\$condition with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - - message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, int\\|string given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" - count: 15 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<0, max\\> given\\.$#" - count: 3 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<1, max\\> given\\.$#" - count: 9 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - - message: "#^Parameter \\#3 \\$stringTable of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeSheetData\\(\\) expects array\\, array\\\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - - message: "#^Parameter \\#4 \\$val of static method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeAttributeIf\\(\\) expects string, int given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 61672b28..30bd6c2f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -11,17 +11,13 @@ parameters: - tests/ excludePaths: - src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + - src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php parallel: processTimeout: 300.0 checkMissingIterableValueType: false ignoreErrors: - - '~^Parameter \#1 \$im(age)? of function (imagedestroy|imageistruecolor|imagealphablending|imagesavealpha|imagecolortransparent|imagecolorsforindex|imagesavealpha|imagesx|imagesy) expects (GdImage|resource), GdImage\|resource given\.$~' + - '~^Parameter \#1 \$im(age)? of function (imagedestroy|imageistruecolor|imagealphablending|imagesavealpha|imagecolortransparent|imagecolorsforindex|imagesavealpha|imagesx|imagesy|imagepng) expects (GdImage|resource), GdImage\|resource given\.$~' - '~^Parameter \#2 \$src_im(age)? of function imagecopy expects (GdImage|resource), GdImage\|resource given\.$~' # Accept a bit anything for assert methods - '~^Parameter \#2 .* of static method PHPUnit\\Framework\\Assert\:\:assert\w+\(\) expects .*, .* given\.$~' - '~^Method PhpOffice\\PhpSpreadsheetTests\\.*\:\:test.*\(\) has parameter \$args with no type specified\.$~' - - # Some issues in Xls/Parser between 1.6.3 and 1.7.7 - - - message: "#^Offset '(left|right|value)' does not exist on (non-empty-array\\|string|array\\|null)\\.$#" - path: src/PhpSpreadsheet/Writer/Xls/Parser.php diff --git a/samples/Basic/26_Utf8.php b/samples/Basic/26_Utf8.php index 52953251..52a64509 100644 --- a/samples/Basic/26_Utf8.php +++ b/samples/Basic/26_Utf8.php @@ -12,12 +12,10 @@ $spreadsheet = $reader->load(__DIR__ . '/../templates/26template.xlsx'); // at this point, we could do some manipulations with the template, but we skip this step $helper->write($spreadsheet, __FILE__, ['Xlsx', 'Xls', 'Html']); -if (\PHP_VERSION_ID < 80000) { - // Export to PDF (.pdf) - $helper->log('Write to PDF format'); - IOFactory::registerWriter('Pdf', \PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf::class); - $helper->write($spreadsheet, __FILE__, ['Pdf']); -} +// Export to PDF (.pdf) +$helper->log('Write to PDF format'); +IOFactory::registerWriter('Pdf', \PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf::class); +$helper->write($spreadsheet, __FILE__, ['Pdf']); // Remove first two rows with field headers before exporting to CSV $helper->log('Removing first two heading rows for CSV export'); diff --git a/samples/Basic/49_alignment.php b/samples/Basic/49_alignment.php new file mode 100644 index 00000000..83fdb3d4 --- /dev/null +++ b/samples/Basic/49_alignment.php @@ -0,0 +1,80 @@ +log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); +$spreadsheet->getProperties()->setTitle('Alignment'); +$sheet = $spreadsheet->getActiveSheet(); +$hi = 'Hi There'; +$ju = 'This is a longer than normal sentence'; +$sheet->fromArray([ + ['', 'default', 'bottom', 'top', 'center', 'justify', 'distributed'], + ['default', $hi, $hi, $hi, $hi, $hi, $hi], + ['left', $hi, $hi, $hi, $hi, $hi, $hi], + ['right', $hi, $hi, $hi, $hi, $hi, $hi], + ['center', $hi, $hi, $hi, $hi, $hi, $hi], + ['justify', $ju, $ju, $ju, $ju, $ju, $ju], + ['distributed', $ju, $ju, $ju, $ju, $ju, $ju], +]); +$sheet->getColumnDimension('B')->setWidth(20); +$sheet->getColumnDimension('C')->setWidth(20); +$sheet->getColumnDimension('D')->setWidth(20); +$sheet->getColumnDimension('E')->setWidth(20); +$sheet->getColumnDimension('F')->setWidth(20); +$sheet->getColumnDimension('G')->setWidth(20); +$sheet->getRowDimension(2)->setRowHeight(30); +$sheet->getRowDimension(3)->setRowHeight(30); +$sheet->getRowDimension(4)->setRowHeight(30); +$sheet->getRowDimension(5)->setRowHeight(30); +$sheet->getRowDimension(6)->setRowHeight(40); +$sheet->getRowDimension(7)->setRowHeight(40); +$minRow = 2; +$maxRow = 7; +$minCol = 'B'; +$maxCol = 'g'; +$sheet->getStyle("C$minRow:C$maxRow") + ->getAlignment() + ->setVertical(Alignment::VERTICAL_BOTTOM); +$sheet->getStyle("D$minRow:D$maxRow") + ->getAlignment() + ->setVertical(Alignment::VERTICAL_TOP); +$sheet->getStyle("E$minRow:E$maxRow") + ->getAlignment() + ->setVertical(Alignment::VERTICAL_CENTER); +$sheet->getStyle("F$minRow:F$maxRow") + ->getAlignment() + ->setVertical(Alignment::VERTICAL_JUSTIFY); +$sheet->getStyle("G$minRow:G$maxRow") + ->getAlignment() + ->setVertical(Alignment::VERTICAL_DISTRIBUTED); +$sheet->getStyle("{$minCol}3:{$maxCol}3") + ->getAlignment() + ->setHorizontal(Alignment::HORIZONTAL_LEFT); +$sheet->getStyle("{$minCol}4:{$maxCol}4") + ->getAlignment() + ->setHorizontal(Alignment::HORIZONTAL_RIGHT); +$sheet->getStyle("{$minCol}5:{$maxCol}5") + ->getAlignment() + ->setHorizontal(Alignment::HORIZONTAL_CENTER); +$sheet->getStyle("{$minCol}6:{$maxCol}6") + ->getAlignment() + ->setHorizontal(Alignment::HORIZONTAL_JUSTIFY); +$sheet->getStyle("{$minCol}7:{$maxCol}7") + ->getAlignment() + ->setHorizontal(Alignment::HORIZONTAL_DISTRIBUTED); + +$sheet->getCell('A9')->setValue('Center Continuous A9-C9'); +$sheet->getStyle('A9:C9') + ->getAlignment() + ->setHorizontal(Alignment::HORIZONTAL_CENTER_CONTINUOUS); +$sheet->getCell('A10')->setValue('Fill'); +$sheet->getStyle('A10') + ->getAlignment() + ->setHorizontal(Alignment::HORIZONTAL_FILL); +$sheet->setSelectedCells('A1'); + +$helper->write($spreadsheet, __FILE__, ['Xlsx', 'Html', 'Xls']); diff --git a/samples/Calculations/Database/DAVERAGE.php b/samples/Calculations/Database/DAVERAGE.php index 92d84014..fa388c8b 100644 --- a/samples/Calculations/Database/DAVERAGE.php +++ b/samples/Calculations/Database/DAVERAGE.php @@ -4,7 +4,11 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Returns the average of selected database entries.'); +$category = 'Database'; +$functionName = 'DAVERAGE'; +$description = 'Returns the average of selected database entries that match criteria'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -36,21 +40,19 @@ $worksheet->setCellValue('B13', '=DAVERAGE(A4:E10,3,A1:A3)'); $helper->log('Database'); $databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); -var_dump($databaseData); +$helper->displayGrid($databaseData); // Test the formulae $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:B2', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A12')->getValue()); -$helper->log('DAVERAGE() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B12', 'A12'); $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A13')->getValue()); -$helper->log('DAVERAGE() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B13', 'A13'); diff --git a/samples/Calculations/Database/DCOUNT.php b/samples/Calculations/Database/DCOUNT.php index d869a4bc..68d6be18 100644 --- a/samples/Calculations/Database/DCOUNT.php +++ b/samples/Calculations/Database/DCOUNT.php @@ -3,7 +3,12 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Counts the cells that contain numbers in a database.'); + +$category = 'Database'; +$functionName = 'DCOUNT'; +$description = 'Counts the cells that contain numbers in a set of database records that match criteria'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -14,9 +19,9 @@ $database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], ['Apple', 18, 20, 14, 105.00], ['Pear', 12, 12, 10, 96.00], ['Cherry', 13, 14, 9, 105.00], - ['Apple', 14, 15, 10, 75.00], - ['Pear', 9, 8, 8, 76.80], - ['Apple', 8, 9, 6, 45.00], + ['Apple', 14, 'N/A', 10, 75.00], + ['Pear', 9, 8, 8, 77.00], + ['Apple', 12, 11, 6, 45.00], ]; $criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], ['="=Apple"', '>10', null, null, null, '<16'], @@ -26,30 +31,28 @@ $criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], $worksheet->fromArray($criteria, null, 'A1'); $worksheet->fromArray($database, null, 'A4'); -$worksheet->setCellValue('A12', 'The Number of Apple trees over 10\' in height'); -$worksheet->setCellValue('B12', '=DCOUNT(A4:E10,"Yield",A1:B2)'); +$worksheet->setCellValue('A12', 'The Number of Apple trees between 10\' and 16\' in height whose age is known'); +$worksheet->setCellValue('B12', '=DCOUNT(A4:E10,"Age",A1:F2)'); -$worksheet->setCellValue('A13', 'The Number of Apple and Pear trees in the orchard'); +$worksheet->setCellValue('A13', 'The Number of Apple and Pear trees in the orchard with a numeric value in column 3 ("Age")'); $worksheet->setCellValue('B13', '=DCOUNT(A4:E10,3,A1:A3)'); $helper->log('Database'); $databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); -var_dump($databaseData); +$helper->displayGrid($databaseData); // Test the formulae $helper->log('Criteria'); -$criteriaData = $worksheet->rangeToArray('A1:B2', null, true, true, true); -var_dump($criteriaData); +$criteriaData = $worksheet->rangeToArray('A1:F2', null, true, true, true); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A12')->getValue()); -$helper->log('DCOUNT() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B12', 'A12'); $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A13')->getValue()); -$helper->log('DCOUNT() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B13', 'A13'); diff --git a/samples/Calculations/Database/DCOUNTA.php b/samples/Calculations/Database/DCOUNTA.php new file mode 100644 index 00000000..3b4cc16e --- /dev/null +++ b/samples/Calculations/Database/DCOUNTA.php @@ -0,0 +1,58 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], + ['Apple', 18, 20, 14, 105.00], + ['Pear', 12, 12, 10, 96.00], + ['Cherry', 13, 14, 9, 105.00], + ['Apple', 14, 'N/A', 10, 75.00], + ['Pear', 9, 8, 8, 77.00], + ['Apple', 12, 11, 6, 45.00], +]; +$criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], + ['="=Apple"', '>10', null, null, null, '<16'], + ['="=Pear"', null, null, null, null, null], +]; + +$worksheet->fromArray($criteria, null, 'A1'); +$worksheet->fromArray($database, null, 'A4'); + +$worksheet->setCellValue('A12', 'The Number of Apple trees between 10\' and 16\' in height'); +$worksheet->setCellValue('B12', '=DCOUNTA(A4:E10,"Age",A1:F2)'); + +$worksheet->setCellValue('A13', 'The Number of Apple and Pear trees in the orchard'); +$worksheet->setCellValue('B13', '=DCOUNTA(A4:E10,3,A1:A3)'); + +$helper->log('Database'); + +$databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); +$helper->displayGrid($databaseData); + +// Test the formulae +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:F2', null, true, true, true); +$helper->displayGrid($criteriaData); + +$helper->logCalculationResult($worksheet, $functionName, 'B12', 'A12'); + +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); +$helper->displayGrid($criteriaData); + +$helper->logCalculationResult($worksheet, $functionName, 'B13', 'A13'); diff --git a/samples/Calculations/Database/DGET.php b/samples/Calculations/Database/DGET.php index 9f543c91..1b41b777 100644 --- a/samples/Calculations/Database/DGET.php +++ b/samples/Calculations/Database/DGET.php @@ -4,7 +4,11 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Extracts a single value from a column of a list or database that matches conditions that you specify.'); +$category = 'Database'; +$functionName = 'DGET'; +$description = 'Extracts a single value from a column of a list or database that matches criteria that you specify'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -21,7 +25,7 @@ $database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], ]; $criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], ['="=Apple"', '>10', null, null, null, '<16'], - ['="=Pear"', null, null, null, null, null], + ['="=Pear"', '>12', null, null, null, null], ]; $worksheet->fromArray($criteria, null, 'A1'); @@ -30,23 +34,25 @@ $worksheet->fromArray($database, null, 'A4'); $worksheet->setCellValue('A12', 'The height of the Apple tree between 10\' and 16\' tall'); $worksheet->setCellValue('B12', '=DGET(A4:E10,"Height",A1:F2)'); +$worksheet->setCellValue('A13', 'The height of the Apple tree (will return an Excel error, because there is more than one apple tree)'); +$worksheet->setCellValue('B13', '=DGET(A4:E10,"Height",A1:A2)'); + $helper->log('Database'); $databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); -var_dump($databaseData); +$helper->displayGrid($databaseData); // Test the formulae $helper->log('Criteria'); -$helper->log('ALL'); +$criteriaData = $worksheet->rangeToArray('A1:F2', null, true, true, true); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A12')->getValue()); -$helper->log('DMAX() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B12', 'A12'); $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A2', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A13')->getValue()); -$helper->log('DMAX() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B13', 'A13'); diff --git a/samples/Calculations/Database/DMAX.php b/samples/Calculations/Database/DMAX.php index c48928d4..0998904b 100644 --- a/samples/Calculations/Database/DMAX.php +++ b/samples/Calculations/Database/DMAX.php @@ -4,7 +4,11 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Returns the maximum value from selected database entries.'); +$category = 'Database'; +$functionName = 'DMAX'; +$description = 'Returns the maximum value from selected database entries'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -36,20 +40,18 @@ $worksheet->setCellValue('B13', '=DMAX(A4:E10,3,A1:A2)'); $helper->log('Database'); $databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); -var_dump($databaseData); +$helper->displayGrid($databaseData); // Test the formulae $helper->log('Criteria'); $helper->log('ALL'); -$helper->log($worksheet->getCell('A12')->getValue()); -$helper->log('DMAX() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B12', 'A12'); $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A2', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A13')->getValue()); -$helper->log('DMAX() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B13', 'A13'); diff --git a/samples/Calculations/Database/DMIN.php b/samples/Calculations/Database/DMIN.php index 7bcaa206..c0e0a8d8 100644 --- a/samples/Calculations/Database/DMIN.php +++ b/samples/Calculations/Database/DMIN.php @@ -4,7 +4,11 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Returns the minimum value from selected database entries.'); +$category = 'Database'; +$functionName = 'DMIN'; +$description = 'Returns the minimum value from selected database entries'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -36,20 +40,18 @@ $worksheet->setCellValue('B13', '=DMIN(A4:E10,3,A1:A2)'); $helper->log('Database'); $databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); -var_dump($databaseData); +$helper->displayGrid($databaseData); // Test the formulae $helper->log('Criteria'); $helper->log('ALL'); -$helper->log($worksheet->getCell('A12')->getValue()); -$helper->log('DMIN() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B12', 'A12'); $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A2', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A13')->getValue()); -$helper->log('DMIN() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B13', 'A13'); diff --git a/samples/Calculations/Database/DPRODUCT.php b/samples/Calculations/Database/DPRODUCT.php index 7c14ded6..fa666ffe 100644 --- a/samples/Calculations/Database/DPRODUCT.php +++ b/samples/Calculations/Database/DPRODUCT.php @@ -4,7 +4,11 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Multiplies the values in a column of a list or database that match conditions that you specify.'); +$category = 'Database'; +$functionName = 'DPRODUCT'; +$description = 'Multiplies the values in a column of a list or database that match conditions that you specify'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -16,7 +20,7 @@ $database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], ['Pear', 12, 12, 10, 96.00], ['Cherry', 13, 14, 9, 105.00], ['Apple', 14, 15, 10, 75.00], - ['Pear', 9, 8, 8, 76.80], + ['Pear', 9, 8, 8, 77.00], ['Apple', 8, 9, 6, 45.00], ]; $criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], @@ -30,23 +34,25 @@ $worksheet->fromArray($database, null, 'A4'); $worksheet->setCellValue('A12', 'The product of the yields of all Apple trees over 10\' in the orchard'); $worksheet->setCellValue('B12', '=DPRODUCT(A4:E10,"Yield",A1:B2)'); +$worksheet->setCellValue('A13', 'The product of the yields of all Apple trees in the orchard'); +$worksheet->setCellValue('B13', '=DPRODUCT(A4:E10,"Yield",A1:A2)'); + $helper->log('Database'); $databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); -var_dump($databaseData); +$helper->displayGrid($databaseData); // Test the formulae $helper->log('Criteria'); -$helper->log('ALL'); +$criteriaData = $worksheet->rangeToArray('A1:B2', null, true, true, true); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A12')->getValue()); -$helper->log('DMAX() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B12', 'A12'); $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A2', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A13')->getValue()); -$helper->log('DMAX() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B13', 'A13'); diff --git a/samples/Calculations/Database/DSTDEV.php b/samples/Calculations/Database/DSTDEV.php index 7f09fa59..b6499741 100644 --- a/samples/Calculations/Database/DSTDEV.php +++ b/samples/Calculations/Database/DSTDEV.php @@ -4,7 +4,11 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Estimates the standard deviation based on a sample of selected database entries.'); +$category = 'Database'; +$functionName = 'DSTDEV'; +$description = 'Estimates the standard deviation based on a sample of selected database entries'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -36,21 +40,19 @@ $worksheet->setCellValue('B13', '=DSTDEV(A4:E10,2,A1:A3)'); $helper->log('Database'); $databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); -var_dump($databaseData); +$helper->displayGrid($databaseData); // Test the formulae $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A12')->getValue()); -$helper->log('DSTDEV() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B12', 'A12'); $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A13')->getValue()); -$helper->log('DSTDEV() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B13', 'A13'); diff --git a/samples/Calculations/Database/DSTDEVP.php b/samples/Calculations/Database/DSTDEVP.php index 9e999a80..d8bcec4b 100644 --- a/samples/Calculations/Database/DSTDEVP.php +++ b/samples/Calculations/Database/DSTDEVP.php @@ -3,7 +3,12 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Calculates the standard deviation based on the entire population of selected database entries.'); + +$category = 'Database'; +$functionName = 'DSTDEVP'; +$description = 'Calculates the standard deviation based on the entire population of selected database entries'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -35,21 +40,19 @@ $worksheet->setCellValue('B13', '=DSTDEVP(A4:E10,2,A1:A3)'); $helper->log('Database'); $databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); -var_dump($databaseData); +$helper->displayGrid($databaseData); // Test the formulae $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A12')->getValue()); -$helper->log('DSTDEVP() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B12', 'A12'); $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A13')->getValue()); -$helper->log('DSTDEVP() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B13', 'A13'); diff --git a/samples/Calculations/Database/DSUM.php b/samples/Calculations/Database/DSUM.php new file mode 100644 index 00000000..d2d773c9 --- /dev/null +++ b/samples/Calculations/Database/DSUM.php @@ -0,0 +1,58 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$database = [['Tree', 'Height', 'Age', 'Yield', 'Profit'], + ['Apple', 18, 20, 14, 105.00], + ['Pear', 12, 12, 10, 96.00], + ['Cherry', 13, 14, 9, 105.00], + ['Apple', 14, 15, 10, 75.00], + ['Pear', 9, 8, 8, 76.80], + ['Apple', 8, 9, 6, 45.00], +]; +$criteria = [['Tree', 'Height', 'Age', 'Yield', 'Profit', 'Height'], + ['="=Apple"', '>10', null, null, null, '<16'], + ['="=Pear"', null, null, null, null, null], +]; + +$worksheet->fromArray($criteria, null, 'A1'); +$worksheet->fromArray($database, null, 'A4'); + +$worksheet->setCellValue('A12', 'The total profit from apple trees'); +$worksheet->setCellValue('B12', '=DSUM(A4:E10,"Profit",A1:A2)'); + +$worksheet->setCellValue('A13', 'Total profit from apple trees with a height between 10 and 16 feet, and all pear trees'); +$worksheet->setCellValue('B13', '=DSUM(A4:E10,"Profit",A1:F3)'); + +$helper->log('Database'); + +$databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); +$helper->displayGrid($databaseData); + +// Test the formulae +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:A2', null, true, true, true); +$helper->displayGrid($criteriaData); + +$helper->logCalculationResult($worksheet, $functionName, 'B12', 'A12'); + +$helper->log('Criteria'); + +$criteriaData = $worksheet->rangeToArray('A1:F3', null, true, true, true); +$helper->displayGrid($criteriaData); + +$helper->logCalculationResult($worksheet, $functionName, 'B13', 'A13'); diff --git a/samples/Calculations/Database/DVAR.php b/samples/Calculations/Database/DVAR.php index 2a5f8749..4165d076 100644 --- a/samples/Calculations/Database/DVAR.php +++ b/samples/Calculations/Database/DVAR.php @@ -3,7 +3,12 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Estimates variance based on a sample from selected database entries.'); + +$category = 'Database'; +$functionName = 'DVAR'; +$description = 'Estimates variance based on a sample from selected database entries'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -35,21 +40,19 @@ $worksheet->setCellValue('B13', '=DVAR(A4:E10,2,A1:A3)'); $helper->log('Database'); $databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); -var_dump($databaseData); +$helper->displayGrid($databaseData); // Test the formulae $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A12')->getValue()); -$helper->log('DVAR() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B12', 'A12'); $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A13')->getValue()); -$helper->log('DVAR() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B13', 'A13'); diff --git a/samples/Calculations/Database/DVARP.php b/samples/Calculations/Database/DVARP.php index 4f57113b..5a17415e 100644 --- a/samples/Calculations/Database/DVARP.php +++ b/samples/Calculations/Database/DVARP.php @@ -4,7 +4,11 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Calculates variance based on the entire population of selected database entries,'); +$category = 'Database'; +$functionName = 'DVARP'; +$description = 'Calculates variance based on the entire population of selected database entries'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -36,21 +40,19 @@ $worksheet->setCellValue('B13', '=DVARP(A4:E10,2,A1:A3)'); $helper->log('Database'); $databaseData = $worksheet->rangeToArray('A4:E10', null, true, true, true); -var_dump($databaseData); +$helper->displayGrid($databaseData); // Test the formulae $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A12')->getValue()); -$helper->log('DVARP() Result is ' . $worksheet->getCell('B12')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B12', 'A12'); $helper->log('Criteria'); $criteriaData = $worksheet->rangeToArray('A1:A3', null, true, true, true); -var_dump($criteriaData); +$helper->displayGrid($criteriaData); -$helper->log($worksheet->getCell('A13')->getValue()); -$helper->log('DVARP() Result is ' . $worksheet->getCell('B13')->getCalculatedValue()); +$helper->logCalculationResult($worksheet, $functionName, 'B13', 'A13'); diff --git a/samples/Calculations/DateTime/DATE.php b/samples/Calculations/DateTime/DATE.php index 5d758f76..d526cbdb 100644 --- a/samples/Calculations/DateTime/DATE.php +++ b/samples/Calculations/DateTime/DATE.php @@ -4,7 +4,11 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Returns the serial number of a particular date.'); +$category = 'Date/Time'; +$functionName = 'DATE'; +$description = 'Returns the Excel serial number of a particular date'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -27,15 +31,15 @@ for ($row = 1; $row <= $testDateCount; ++$row) { } $worksheet->getStyle('E1:E' . $testDateCount) ->getNumberFormat() - ->setFormatCode('yyyy-mmm-dd'); + ->setFormatCode('yyyy-mm-dd'); // Test the formulae for ($row = 1; $row <= $testDateCount; ++$row) { - $helper->log('Year: ' . $worksheet->getCell('A' . $row)->getFormattedValue()); - $helper->log('Month: ' . $worksheet->getCell('B' . $row)->getFormattedValue()); - $helper->log('Day: ' . $worksheet->getCell('C' . $row)->getFormattedValue()); + $helper->log("(A{$row}) Year: " . $worksheet->getCell('A' . $row)->getFormattedValue()); + $helper->log("(B{$row}) Month: " . $worksheet->getCell('B' . $row)->getFormattedValue()); + $helper->log("(C{$row}) Day: " . $worksheet->getCell('C' . $row)->getFormattedValue()); $helper->log('Formula: ' . $worksheet->getCell('D' . $row)->getValue()); - $helper->log('Excel DateStamp: ' . $worksheet->getCell('D' . $row)->getFormattedValue()); + $helper->log('Excel DateStamp: ' . $worksheet->getCell('D' . $row)->getCalculatedValue()); $helper->log('Formatted DateStamp: ' . $worksheet->getCell('E' . $row)->getFormattedValue()); $helper->log(''); } diff --git a/samples/Calculations/DateTime/DATEDIF.php b/samples/Calculations/DateTime/DATEDIF.php new file mode 100644 index 00000000..3e035f5a --- /dev/null +++ b/samples/Calculations/DateTime/DATEDIF.php @@ -0,0 +1,61 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = [ + [1900, 1, 1], + [1904, 1, 1], + [1936, 3, 17], + [1960, 12, 19], + [1999, 12, 31], + [2000, 1, 1], + [2019, 2, 14], + [2020, 7, 4], + [2020, 2, 29], +]; +$testDateCount = count($testDates); + +$worksheet->fromArray($testDates, null, 'A1', true); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=DATE(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=TODAY()'); + $worksheet->setCellValue('G' . $row, '=DATEDIF(D' . $row . ', F' . $row . ', "Y")'); + $worksheet->setCellValue('H' . $row, '=DATEDIF(D' . $row . ', F' . $row . ', "M")'); + $worksheet->setCellValue('I' . $row, '=DATEDIF(D' . $row . ', F' . $row . ', "D")'); + $worksheet->setCellValue('J' . $row, '=DATEDIF(D' . $row . ', F' . $row . ', "MD")'); + $worksheet->setCellValue('K' . $row, '=DATEDIF(D' . $row . ', F' . $row . ', "YM")'); + $worksheet->setCellValue('L' . $row, '=DATEDIF(D' . $row . ', F' . $row . ', "YD")'); +} +$worksheet->getStyle('E1:F' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log(sprintf( + 'Between: %s and %s', + $worksheet->getCell('E' . $row)->getFormattedValue(), + $worksheet->getCell('F' . $row)->getFormattedValue() + )); + $helper->log('In years ("Y"): ' . $worksheet->getCell('G' . $row)->getCalculatedValue()); + $helper->log('In months ("M"): ' . $worksheet->getCell('H' . $row)->getCalculatedValue()); + $helper->log('In days ("D"): ' . $worksheet->getCell('I' . $row)->getCalculatedValue()); + $helper->log('In days ignoring months and years ("MD"): ' . $worksheet->getCell('J' . $row)->getCalculatedValue()); + $helper->log('In months ignoring days and years ("YM"): ' . $worksheet->getCell('K' . $row)->getCalculatedValue()); + $helper->log('In days ignoring years ("YD"): ' . $worksheet->getCell('L' . $row)->getCalculatedValue()); +} diff --git a/samples/Calculations/DateTime/DATEVALUE.php b/samples/Calculations/DateTime/DATEVALUE.php index 5cdb936d..c506c6f2 100644 --- a/samples/Calculations/DateTime/DATEVALUE.php +++ b/samples/Calculations/DateTime/DATEVALUE.php @@ -4,7 +4,11 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Converts a date in the form of text to a serial number.'); +$category = 'Date/Time'; +$functionName = 'DATEVALUE'; +$description = 'Converts a date in the form of text to an Excel serial number'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -13,8 +17,8 @@ $worksheet = $spreadsheet->getActiveSheet(); // Add some data $testDates = ['26 March 2012', '29 Feb 2012', 'April 1, 2012', '25/12/2012', '2012-Oct-31', '5th November', 'January 1st', 'April 2012', - '17-03', '03-2012', '29 Feb 2011', '03-05-07', - '03-MAY-07', '03-13-07', + '17-03', '03-17', '03-2012', '29 Feb 2011', '03-05-07', + '03-MAY-07', '03-13-07', '13-03-07', '03/13/07', '13/03/07', ]; $testDateCount = count($testDates); @@ -26,14 +30,14 @@ for ($row = 1; $row <= $testDateCount; ++$row) { $worksheet->getStyle('C1:C' . $testDateCount) ->getNumberFormat() - ->setFormatCode('yyyy-mmm-dd'); + ->setFormatCode('yyyy-mm-dd'); // Test the formulae $helper->log('Warning: The PhpSpreadsheet DATEVALUE() function accepts a wider range of date formats than MS Excel DATEFORMAT() function.'); for ($row = 1; $row <= $testDateCount; ++$row) { - $helper->log('Date String: ' . $worksheet->getCell('A' . $row)->getFormattedValue()); + $helper->log("(A{$row}) Date String: " . $worksheet->getCell('A' . $row)->getFormattedValue()); $helper->log('Formula: ' . $worksheet->getCell('B' . $row)->getValue()); - $helper->log('Excel DateStamp: ' . $worksheet->getCell('B' . $row)->getFormattedValue()); - $helper->log('Formatted DateStamp' . $worksheet->getCell('C' . $row)->getFormattedValue()); + $helper->log('Excel DateStamp: ' . $worksheet->getCell('B' . $row)->getCalculatedValue()); + $helper->log('Formatted DateStamp: ' . $worksheet->getCell('C' . $row)->getFormattedValue()); $helper->log(''); } diff --git a/samples/Calculations/DateTime/DAY.php b/samples/Calculations/DateTime/DAY.php new file mode 100644 index 00000000..3a4da7e6 --- /dev/null +++ b/samples/Calculations/DateTime/DAY.php @@ -0,0 +1,50 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = [ + [1900, 1, 1], + [1904, 2, 14], + [1936, 3, 17], + [1964, 4, 29], + [1999, 5, 18], + [2000, 6, 21], + [2019, 7, 4], + [2020, 8, 31], + [1956, 9, 10], + [2010, 10, 10], + [1982, 11, 30], + [1960, 12, 19], + ['=YEAR(TODAY())', '=MONTH(TODAY())', '=DAY(TODAY())'], +]; +$testDateCount = count($testDates); + +$worksheet->fromArray($testDates, null, 'A1', true); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=DATE(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=DAY(D' . $row . ')'); +} +$worksheet->getStyle('E1:E' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log(sprintf('(E%d): %s', $row, $worksheet->getCell('E' . $row)->getFormattedValue())); + $helper->log('Day is: ' . $worksheet->getCell('F' . $row)->getCalculatedValue()); +} diff --git a/samples/Calculations/DateTime/DAYS.php b/samples/Calculations/DateTime/DAYS.php new file mode 100644 index 00000000..15e9a58f --- /dev/null +++ b/samples/Calculations/DateTime/DAYS.php @@ -0,0 +1,53 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = [ + [1900, 1, 1], + [1904, 1, 1], + [1936, 3, 17], + [1960, 12, 19], + [1999, 12, 31], + [2000, 1, 1], + [2019, 2, 14], + [2020, 7, 4], + [2020, 2, 29], + [2029, 12, 31], + [2525, 1, 1], +]; +$testDateCount = count($testDates); + +$worksheet->fromArray($testDates, null, 'A1', true); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=DATE(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=TODAY()'); + $worksheet->setCellValue('G' . $row, '=DAYS(D' . $row . ', F' . $row . ')'); +} +$worksheet->getStyle('E1:F' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log(sprintf( + 'Between: %s and %s', + $worksheet->getCell('E' . $row)->getFormattedValue(), + $worksheet->getCell('F' . $row)->getFormattedValue() + )); + $helper->log('Days: ' . $worksheet->getCell('G' . $row)->getCalculatedValue()); +} diff --git a/samples/Calculations/DateTime/DAYS360.php b/samples/Calculations/DateTime/DAYS360.php new file mode 100644 index 00000000..b0e2fdbb --- /dev/null +++ b/samples/Calculations/DateTime/DAYS360.php @@ -0,0 +1,57 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = [ + [1900, 1, 1], + [1904, 1, 1], + [1936, 3, 17], + [1960, 12, 19], + [1999, 12, 31], + [2000, 1, 1], + [2019, 2, 14], + [2020, 7, 4], + [2029, 12, 31], + [2525, 1, 1], +]; +$testDateCount = count($testDates); + +$worksheet->fromArray($testDates, null, 'A1', true); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=DATE(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=DATE(2022,12,31)'); + $worksheet->setCellValue('G' . $row, '=DAYS360(D' . $row . ', F' . $row . ', FALSE)'); + $worksheet->setCellValue('H' . $row, '=DAYS360(D' . $row . ', F' . $row . ', TRUE)'); +} +$worksheet->getStyle('E1:F' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log(sprintf( + 'Between: %s and %s', + $worksheet->getCell('E' . $row)->getFormattedValue(), + $worksheet->getCell('F' . $row)->getFormattedValue() + )); + $helper->log(sprintf( + 'Days: %d (US) %d (European)', + $worksheet->getCell('G' . $row)->getCalculatedValue(), + $worksheet->getCell('H' . $row)->getCalculatedValue() + )); +} diff --git a/samples/Calculations/DateTime/EDATE.php b/samples/Calculations/DateTime/EDATE.php new file mode 100644 index 00000000..be6e4d19 --- /dev/null +++ b/samples/Calculations/DateTime/EDATE.php @@ -0,0 +1,43 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$months = range(-12, 12); +$testDateCount = count($months); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('A' . $row, '=DATE(2020,12,31)'); + $worksheet->setCellValue('B' . $row, '=A' . $row); + $worksheet->setCellValue('C' . $row, $months[$row - 1]); + $worksheet->setCellValue('D' . $row, '=EDATE(B' . $row . ', C' . $row . ')'); +} +$worksheet->getStyle('B1:B' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +$worksheet->getStyle('D1:D' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log(sprintf( + '%s and %d months is %d (%s)', + $worksheet->getCell('B' . $row)->getFormattedValue(), + $worksheet->getCell('C' . $row)->getFormattedValue(), + $worksheet->getCell('D' . $row)->getCalculatedValue(), + $worksheet->getCell('D' . $row)->getFormattedValue() + )); +} diff --git a/samples/Calculations/DateTime/EOMONTH.php b/samples/Calculations/DateTime/EOMONTH.php new file mode 100644 index 00000000..e0b7568a --- /dev/null +++ b/samples/Calculations/DateTime/EOMONTH.php @@ -0,0 +1,43 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$months = range(-12, 12); +$testDateCount = count($months); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('A' . $row, '=DATE(2020,1,1)'); + $worksheet->setCellValue('B' . $row, '=A' . $row); + $worksheet->setCellValue('C' . $row, $months[$row - 1]); + $worksheet->setCellValue('D' . $row, '=EOMONTH(B' . $row . ', C' . $row . ')'); +} +$worksheet->getStyle('B1:B' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +$worksheet->getStyle('D1:D' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log(sprintf( + '%s and %d months is %d (%s)', + $worksheet->getCell('B' . $row)->getFormattedValue(), + $worksheet->getCell('C' . $row)->getFormattedValue(), + $worksheet->getCell('D' . $row)->getCalculatedValue(), + $worksheet->getCell('D' . $row)->getFormattedValue() + )); +} diff --git a/samples/Calculations/DateTime/HOUR.php b/samples/Calculations/DateTime/HOUR.php new file mode 100644 index 00000000..53c21644 --- /dev/null +++ b/samples/Calculations/DateTime/HOUR.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testTimes = [ + [0, 6, 0], + [1, 12, 15], + [3, 30, 12], + [5, 17, 31], + [8, 15, 45], + [12, 45, 11], + [14, 0, 30], + [17, 55, 50], + [19, 21, 8], + [21, 10, 10], + [23, 59, 59], +]; +$testTimeCount = count($testTimes); + +$worksheet->fromArray($testTimes, null, 'A1', true); + +for ($row = 1; $row <= $testTimeCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=TIME(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=HOUR(D' . $row . ')'); +} +$worksheet->getStyle('E1:E' . $testTimeCount) + ->getNumberFormat() + ->setFormatCode('hh:mm:ss'); + +// Test the formulae +for ($row = 1; $row <= $testTimeCount; ++$row) { + $helper->log(sprintf('(E%d): %s', $row, $worksheet->getCell('E' . $row)->getFormattedValue())); + $helper->log('Hour is: ' . $worksheet->getCell('F' . $row)->getCalculatedValue()); +} diff --git a/samples/Calculations/DateTime/ISOWEEKNUM.php b/samples/Calculations/DateTime/ISOWEEKNUM.php new file mode 100644 index 00000000..4a989fbd --- /dev/null +++ b/samples/Calculations/DateTime/ISOWEEKNUM.php @@ -0,0 +1,50 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = [ + [1900, 1, 1], + [1904, 2, 14], + [1936, 3, 17], + [1964, 4, 29], + [1999, 5, 18], + [2000, 6, 21], + [2019, 7, 4], + [2020, 8, 31], + [1956, 9, 10], + [2010, 10, 10], + [1982, 11, 30], + [1960, 12, 19], + ['=YEAR(TODAY())', '=MONTH(TODAY())', '=DAY(TODAY())'], +]; +$testDateCount = count($testDates); + +$worksheet->fromArray($testDates, null, 'A1', true); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=DATE(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=ISOWEEKNUM(D' . $row . ')'); +} +$worksheet->getStyle('E1:E' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log(sprintf('(E%d): %s', $row, $worksheet->getCell('E' . $row)->getFormattedValue())); + $helper->log('ISO Week number is: ' . $worksheet->getCell('F' . $row)->getCalculatedValue()); +} diff --git a/samples/Calculations/DateTime/MINUTE.php b/samples/Calculations/DateTime/MINUTE.php new file mode 100644 index 00000000..11e90e13 --- /dev/null +++ b/samples/Calculations/DateTime/MINUTE.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testTimes = [ + [0, 6, 0], + [1, 12, 15], + [3, 30, 12], + [5, 17, 31], + [8, 15, 45], + [12, 45, 11], + [14, 0, 30], + [17, 55, 50], + [19, 21, 8], + [21, 10, 10], + [23, 59, 59], +]; +$testTimeCount = count($testTimes); + +$worksheet->fromArray($testTimes, null, 'A1', true); + +for ($row = 1; $row <= $testTimeCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=TIME(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=MINUTE(D' . $row . ')'); +} +$worksheet->getStyle('E1:E' . $testTimeCount) + ->getNumberFormat() + ->setFormatCode('hh:mm:ss'); + +// Test the formulae +for ($row = 1; $row <= $testTimeCount; ++$row) { + $helper->log(sprintf('(E%d): %s', $row, $worksheet->getCell('E' . $row)->getFormattedValue())); + $helper->log('Minute is: ' . $worksheet->getCell('F' . $row)->getCalculatedValue()); +} diff --git a/samples/Calculations/DateTime/MONTH.php b/samples/Calculations/DateTime/MONTH.php new file mode 100644 index 00000000..8cceaf4c --- /dev/null +++ b/samples/Calculations/DateTime/MONTH.php @@ -0,0 +1,50 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = [ + [1900, 1, 1], + [1904, 2, 14], + [1936, 3, 17], + [1964, 4, 29], + [1999, 5, 18], + [2000, 6, 21], + [2019, 7, 4], + [2020, 8, 31], + [1956, 9, 10], + [2010, 10, 10], + [1982, 11, 30], + [1960, 12, 19], + ['=YEAR(TODAY())', '=MONTH(TODAY())', '=DAY(TODAY())'], +]; +$testDateCount = count($testDates); + +$worksheet->fromArray($testDates, null, 'A1', true); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=DATE(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=MONTH(D' . $row . ')'); +} +$worksheet->getStyle('E1:E' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log(sprintf('(E%d): %s', $row, $worksheet->getCell('E' . $row)->getFormattedValue())); + $helper->log('Month is: ' . $worksheet->getCell('F' . $row)->getCalculatedValue()); +} diff --git a/samples/Calculations/DateTime/NETWORKDAYS.php b/samples/Calculations/DateTime/NETWORKDAYS.php new file mode 100644 index 00000000..585c0438 --- /dev/null +++ b/samples/Calculations/DateTime/NETWORKDAYS.php @@ -0,0 +1,66 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$publicHolidays = [ + [2022, 1, 3, '=DATE(G1, H1, I1)', 'New Year'], + [2022, 4, 15, '=DATE(G2, H2, I2)', 'Good Friday'], + [2022, 4, 18, '=DATE(G3, H3, I3)', 'Easter Monday'], + [2022, 5, 2, '=DATE(G4, H4, I4)', 'Early May Bank Holiday'], + [2022, 6, 2, '=DATE(G5, H5, I5)', 'Spring Bank Holiday'], + [2022, 6, 3, '=DATE(G6, H6, I6)', 'Platinum Jubilee Bank Holiday'], + [2022, 8, 29, '=DATE(G7, H7, I7)', 'Summer Bank Holiday'], + [2022, 12, 26, '=DATE(G8, H8, I8)', 'Boxing Day'], + [2022, 12, 27, '=DATE(G9, H9, I9)', 'Christmas Day'], +]; + +$holidayCount = count($publicHolidays); +$worksheet->fromArray($publicHolidays, null, 'G1', true); + +$worksheet->getStyle('J1:J' . $holidayCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +$worksheet->setCellValue('A1', '=DATE(2022,1,1)'); + +for ($numberOfMonths = 0; $numberOfMonths < 12; ++$numberOfMonths) { + $worksheet->setCellValue('B' . ($numberOfMonths + 1), '=EOMONTH(A1, ' . $numberOfMonths . ')'); + $worksheet->setCellValue('C' . ($numberOfMonths + 1), '=NETWORKDAYS(A1, B' . ($numberOfMonths + 1) . ')'); + $worksheet->setCellValue('D' . ($numberOfMonths + 1), '=NETWORKDAYS(A1, B' . ($numberOfMonths + 1) . ', J1:J' . $holidayCount . ')'); +} + +$worksheet->getStyle('A1') + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +$worksheet->getStyle('B1:B12') + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +$helper->log('UK Public Holidays'); +$holidayData = $worksheet->rangeToArray('J1:K' . $holidayCount, null, true, true, true); +$helper->displayGrid($holidayData); + +for ($row = 1; $row <= 12; ++$row) { + $helper->log(sprintf( + 'Between %s and %s is %d working days; %d with public holidays', + $worksheet->getCell('A1')->getFormattedValue(), + $worksheet->getCell('B' . $row)->getFormattedValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + $worksheet->getCell('D' . $row)->getCalculatedValue() + )); +} diff --git a/samples/Calculations/DateTime/NOW.php b/samples/Calculations/DateTime/NOW.php new file mode 100644 index 00000000..858a3162 --- /dev/null +++ b/samples/Calculations/DateTime/NOW.php @@ -0,0 +1,27 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$worksheet->setCellValue('A1', '=NOW()'); +$worksheet->getStyle('A1') + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd hh:mm:ss'); + +// Test the formulae +$helper->log(sprintf( + 'Today is %f (%s)', + $worksheet->getCell('A1')->getCalculatedValue(), + $worksheet->getCell('A1')->getFormattedValue() +)); diff --git a/samples/Calculations/DateTime/SECOND.php b/samples/Calculations/DateTime/SECOND.php new file mode 100644 index 00000000..33806fd5 --- /dev/null +++ b/samples/Calculations/DateTime/SECOND.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testTimes = [ + [0, 6, 0], + [1, 12, 15], + [3, 30, 12], + [5, 17, 31], + [8, 15, 45], + [12, 45, 11], + [14, 0, 30], + [17, 55, 50], + [19, 21, 8], + [21, 10, 10], + [23, 59, 59], +]; +$testTimeCount = count($testTimes); + +$worksheet->fromArray($testTimes, null, 'A1', true); + +for ($row = 1; $row <= $testTimeCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=TIME(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=SECOND(D' . $row . ')'); +} +$worksheet->getStyle('E1:E' . $testTimeCount) + ->getNumberFormat() + ->setFormatCode('hh:mm:ss'); + +// Test the formulae +for ($row = 1; $row <= $testTimeCount; ++$row) { + $helper->log(sprintf('(E%d): %s', $row, $worksheet->getCell('E' . $row)->getFormattedValue())); + $helper->log('Second is: ' . $worksheet->getCell('F' . $row)->getCalculatedValue()); +} diff --git a/samples/Calculations/DateTime/TIME.php b/samples/Calculations/DateTime/TIME.php index 3d4208ad..42c45488 100644 --- a/samples/Calculations/DateTime/TIME.php +++ b/samples/Calculations/DateTime/TIME.php @@ -4,7 +4,11 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Returns the serial number of a particular time.'); +$category = 'Date/Time'; +$functionName = 'TIME'; +$description = 'Returns the Excel serial number of a particular time'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -29,11 +33,11 @@ $worksheet->getStyle('E1:E' . $testDateCount) // Test the formulae for ($row = 1; $row <= $testDateCount; ++$row) { - $helper->log('Hour: ' . $worksheet->getCell('A' . $row)->getFormattedValue()); - $helper->log('Minute: ' . $worksheet->getCell('B' . $row)->getFormattedValue()); - $helper->log('Second: ' . $worksheet->getCell('C' . $row)->getFormattedValue()); + $helper->log("(A{$row}) Hour: " . $worksheet->getCell('A' . $row)->getFormattedValue()); + $helper->log("(B{$row}) Minute: " . $worksheet->getCell('B' . $row)->getFormattedValue()); + $helper->log("(C{$row}) Second: " . $worksheet->getCell('C' . $row)->getFormattedValue()); $helper->log('Formula: ' . $worksheet->getCell('D' . $row)->getValue()); - $helper->log('Excel TimeStamp: ' . $worksheet->getCell('D' . $row)->getFormattedValue()); + $helper->log('Excel TimeStamp: ' . $worksheet->getCell('D' . $row)->getCalculatedValue()); $helper->log('Formatted TimeStamp: ' . $worksheet->getCell('E' . $row)->getFormattedValue()); $helper->log(''); } diff --git a/samples/Calculations/DateTime/TIMEVALUE.php b/samples/Calculations/DateTime/TIMEVALUE.php index f75393cd..15ea8cd9 100644 --- a/samples/Calculations/DateTime/TIMEVALUE.php +++ b/samples/Calculations/DateTime/TIMEVALUE.php @@ -4,7 +4,11 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; require __DIR__ . '/../../Header.php'; -$helper->log('Converts a time in the form of text to a serial number.'); +$category = 'Date/Time'; +$functionName = 'DATEVALUE'; +$description = 'Converts a time in the form of text to an Excel serial number'; + +$helper->titles($category, $functionName, $description); // Create new PhpSpreadsheet object $spreadsheet = new Spreadsheet(); @@ -27,7 +31,7 @@ $worksheet->getStyle('C1:C' . $testDateCount) // Test the formulae for ($row = 1; $row <= $testDateCount; ++$row) { - $helper->log('Time String: ' . $worksheet->getCell('A' . $row)->getFormattedValue()); + $helper->log("(A{$row}) Time String: " . $worksheet->getCell('A' . $row)->getFormattedValue()); $helper->log('Formula: ' . $worksheet->getCell('B' . $row)->getValue()); $helper->log('Excel TimeStamp: ' . $worksheet->getCell('B' . $row)->getFormattedValue()); $helper->log('Formatted TimeStamp: ' . $worksheet->getCell('C' . $row)->getFormattedValue()); diff --git a/samples/Calculations/DateTime/TODAY.php b/samples/Calculations/DateTime/TODAY.php new file mode 100644 index 00000000..031149d5 --- /dev/null +++ b/samples/Calculations/DateTime/TODAY.php @@ -0,0 +1,27 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$worksheet->setCellValue('A1', '=TODAY()'); +$worksheet->getStyle('A1') + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +$helper->log(sprintf( + 'Today is %d (%s)', + $worksheet->getCell('A1')->getCalculatedValue(), + $worksheet->getCell('A1')->getFormattedValue() +)); diff --git a/samples/Calculations/DateTime/WEEKDAY.php b/samples/Calculations/DateTime/WEEKDAY.php new file mode 100644 index 00000000..7d4b4288 --- /dev/null +++ b/samples/Calculations/DateTime/WEEKDAY.php @@ -0,0 +1,58 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = [ + [1900, 1, 1], + [1904, 2, 14], + [1936, 3, 17], + [1964, 4, 29], + [1999, 5, 18], + [2000, 6, 21], + [2019, 7, 4], + [2020, 8, 31], + [1956, 9, 10], + [2010, 10, 10], + [1982, 11, 30], + [1960, 12, 19], + ['=YEAR(TODAY())', '=MONTH(TODAY())', '=DAY(TODAY())'], +]; +$testDateCount = count($testDates); + +$worksheet->fromArray($testDates, null, 'A1', true); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=DATE(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=WEEKDAY(D' . $row . ')'); + $worksheet->setCellValue('G' . $row, '=WEEKDAY(D' . $row . ', 2)'); +} +$worksheet->getStyle('E1:E' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log(sprintf('(E%d): %s', $row, $worksheet->getCell('E' . $row)->getFormattedValue())); + $helper->log(sprintf( + 'Weekday is: %d (1-7 = Sun-Sat)', + $worksheet->getCell('F' . $row)->getCalculatedValue() + )); + $helper->log(sprintf( + 'Weekday is: %d (1-7 = Mon-Sun)', + $worksheet->getCell('G' . $row)->getCalculatedValue() + )); +} diff --git a/samples/Calculations/DateTime/WEEKNUM.php b/samples/Calculations/DateTime/WEEKNUM.php new file mode 100644 index 00000000..1d4c4a12 --- /dev/null +++ b/samples/Calculations/DateTime/WEEKNUM.php @@ -0,0 +1,52 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = [ + [1900, 1, 1], + [1904, 2, 14], + [1936, 3, 17], + [1964, 4, 29], + [1999, 5, 18], + [2000, 6, 21], + [2019, 7, 4], + [2020, 8, 31], + [1956, 9, 10], + [2010, 10, 10], + [1982, 11, 30], + [1960, 12, 19], + ['=YEAR(TODAY())', '=MONTH(TODAY())', '=DAY(TODAY())'], +]; +$testDateCount = count($testDates); + +$worksheet->fromArray($testDates, null, 'A1', true); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=DATE(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=WEEKNUM(D' . $row . ')'); + $worksheet->setCellValue('G' . $row, '=WEEKNUM(D' . $row . ', 21)'); +} +$worksheet->getStyle('E1:E' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log(sprintf('(E%d): %s', $row, $worksheet->getCell('E' . $row)->getFormattedValue())); + $helper->log('System 1 Week number is: ' . $worksheet->getCell('F' . $row)->getCalculatedValue()); + $helper->log('System 2 (ISO-8601) Week number is: ' . $worksheet->getCell('G' . $row)->getCalculatedValue()); +} diff --git a/samples/Calculations/DateTime/WORKDAY.php b/samples/Calculations/DateTime/WORKDAY.php new file mode 100644 index 00000000..d18e3463 --- /dev/null +++ b/samples/Calculations/DateTime/WORKDAY.php @@ -0,0 +1,67 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$publicHolidays = [ + [2022, 1, 3, '=DATE(G1, H1, I1)', 'New Year'], + [2022, 4, 15, '=DATE(G2, H2, I2)', 'Good Friday'], + [2022, 4, 18, '=DATE(G3, H3, I3)', 'Easter Monday'], + [2022, 5, 2, '=DATE(G4, H4, I4)', 'Early May Bank Holiday'], + [2022, 6, 2, '=DATE(G5, H5, I5)', 'Spring Bank Holiday'], + [2022, 6, 3, '=DATE(G6, H6, I6)', 'Platinum Jubilee Bank Holiday'], + [2022, 8, 29, '=DATE(G7, H7, I7)', 'Summer Bank Holiday'], + [2022, 12, 26, '=DATE(G8, H8, I8)', 'Boxing Day'], + [2022, 12, 27, '=DATE(G9, H9, I9)', 'Christmas Day'], +]; + +$holidayCount = count($publicHolidays); +$worksheet->fromArray($publicHolidays, null, 'G1', true); + +$worksheet->getStyle('J1:J' . $holidayCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +$worksheet->setCellValue('A1', '=DATE(2022,1,1)'); + +$workdayStep = 10; +for ($days = $workdayStep; $days <= 366; $days += $workdayStep) { + $worksheet->setCellValue('B' . ((int) $days / $workdayStep + 1), $days); + $worksheet->setCellValue('C' . ((int) $days / $workdayStep + 1), '=WORKDAY(A1, B' . ((int) $days / $workdayStep + 1) . ')'); + $worksheet->setCellValue('D' . ((int) $days / $workdayStep + 1), '=WORKDAY(A1, B' . ((int) $days / $workdayStep + 1) . ', J1:J' . $holidayCount . ')'); +} + +$worksheet->getStyle('A1') + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +$worksheet->getStyle('C1:D50') + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +$helper->log('UK Public Holidays'); +$holidayData = $worksheet->rangeToArray('J1:K' . $holidayCount, null, true, true, true); +$helper->displayGrid($holidayData); + +for ($days = $workdayStep; $days <= 366; $days += $workdayStep) { + $helper->log(sprintf( + '%d workdays from %s is %s; %s with public holidays', + $worksheet->getCell('B' . ((int) $days / $workdayStep + 1))->getFormattedValue(), + $worksheet->getCell('A1')->getFormattedValue(), + $worksheet->getCell('C' . ((int) $days / $workdayStep + 1))->getFormattedValue(), + $worksheet->getCell('D' . ((int) $days / $workdayStep + 1))->getFormattedValue() + )); +} diff --git a/samples/Calculations/DateTime/YEAR.php b/samples/Calculations/DateTime/YEAR.php new file mode 100644 index 00000000..f7bfa6ea --- /dev/null +++ b/samples/Calculations/DateTime/YEAR.php @@ -0,0 +1,50 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = [ + [1900, 1, 1], + [1904, 2, 14], + [1936, 3, 17], + [1964, 4, 29], + [1999, 5, 18], + [2000, 6, 21], + [2019, 7, 4], + [2020, 8, 31], + [1956, 9, 10], + [2010, 10, 10], + [1982, 11, 30], + [1960, 12, 19], + ['=YEAR(TODAY())', '=MONTH(TODAY())', '=DAY(TODAY())'], +]; +$testDateCount = count($testDates); + +$worksheet->fromArray($testDates, null, 'A1', true); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=DATE(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=YEAR(D' . $row . ')'); +} +$worksheet->getStyle('E1:E' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log(sprintf('(E%d): %s', $row, $worksheet->getCell('E' . $row)->getFormattedValue())); + $helper->log('Year is: ' . $worksheet->getCell('F' . $row)->getCalculatedValue()); +} diff --git a/samples/Calculations/DateTime/YEARFRAC.php b/samples/Calculations/DateTime/YEARFRAC.php new file mode 100644 index 00000000..81b36435 --- /dev/null +++ b/samples/Calculations/DateTime/YEARFRAC.php @@ -0,0 +1,76 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testDates = [ + [1900, 1, 1], + [1904, 1, 1], + [1936, 3, 17], + [1960, 12, 19], + [1999, 12, 31], + [2000, 1, 1], + [2019, 2, 14], + [2020, 7, 4], + [2020, 2, 29], + [2029, 12, 31], + [2525, 1, 1], +]; +$testDateCount = count($testDates); + +$worksheet->fromArray($testDates, null, 'A1', true); + +for ($row = 1; $row <= $testDateCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=DATE(A' . $row . ',B' . $row . ',C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=D' . $row); + $worksheet->setCellValue('F' . $row, '=DATE(2022,12,31)'); + $worksheet->setCellValue('G' . $row, '=YEARFRAC(D' . $row . ', F' . $row . ')'); + $worksheet->setCellValue('H' . $row, '=YEARFRAC(D' . $row . ', F' . $row . ', 1)'); + $worksheet->setCellValue('I' . $row, '=YEARFRAC(D' . $row . ', F' . $row . ', 2)'); + $worksheet->setCellValue('J' . $row, '=YEARFRAC(D' . $row . ', F' . $row . ', 3)'); + $worksheet->setCellValue('K' . $row, '=YEARFRAC(D' . $row . ', F' . $row . ', 4)'); +} +$worksheet->getStyle('E1:F' . $testDateCount) + ->getNumberFormat() + ->setFormatCode('yyyy-mm-dd'); + +// Test the formulae +for ($row = 1; $row <= $testDateCount; ++$row) { + $helper->log(sprintf( + 'Between: %s and %s', + $worksheet->getCell('E' . $row)->getFormattedValue(), + $worksheet->getCell('F' . $row)->getFormattedValue() + )); + $helper->log(sprintf( + 'Days: %f - US (NASD) 30/360', + $worksheet->getCell('G' . $row)->getCalculatedValue() + )); + $helper->log(sprintf( + 'Days: %f - Actual', + $worksheet->getCell('H' . $row)->getCalculatedValue() + )); + $helper->log(sprintf( + 'Days: %f - Actual/360', + $worksheet->getCell('I' . $row)->getCalculatedValue() + )); + $helper->log(sprintf( + 'Days: %f - Actual/365', + $worksheet->getCell('J' . $row)->getCalculatedValue() + )); + $helper->log(sprintf( + 'Days: %f - European 30/360', + $worksheet->getCell('K' . $row)->getCalculatedValue() + )); +} diff --git a/samples/Calculations/Engineering/BESSELI.php b/samples/Calculations/Engineering/BESSELI.php new file mode 100644 index 00000000..bd5d9b71 --- /dev/null +++ b/samples/Calculations/Engineering/BESSELI.php @@ -0,0 +1,29 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +for ($n = 0; $n <= 5; ++$n) { + for ($x = 0; $x <= 5; $x = $x + 0.25) { + Calculation::getInstance($spreadsheet)->flushInstance(); + $worksheet->setCellValue('A1', "=BESSELI({$x}, {$n})"); + + $helper->log(sprintf( + '%s = %f', + $worksheet->getCell('A1')->getValue(), + $worksheet->getCell('A1')->getCalculatedValue() + )); + } +} diff --git a/samples/Calculations/Engineering/BESSELJ.php b/samples/Calculations/Engineering/BESSELJ.php new file mode 100644 index 00000000..3aa47886 --- /dev/null +++ b/samples/Calculations/Engineering/BESSELJ.php @@ -0,0 +1,29 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +for ($n = 0; $n <= 5; ++$n) { + for ($x = 0; $x <= 5; $x = $x + 0.25) { + Calculation::getInstance($spreadsheet)->flushInstance(); + $worksheet->setCellValue('A1', "=BESSELJ({$x}, {$n})"); + + $helper->log(sprintf( + '%s = %f', + $worksheet->getCell('A1')->getValue(), + $worksheet->getCell('A1')->getCalculatedValue() + )); + } +} diff --git a/samples/Calculations/Engineering/BESSELK.php b/samples/Calculations/Engineering/BESSELK.php new file mode 100644 index 00000000..ee8698e9 --- /dev/null +++ b/samples/Calculations/Engineering/BESSELK.php @@ -0,0 +1,29 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +for ($n = 0; $n <= 5; ++$n) { + for ($x = 0; $x <= 5; $x = $x + 0.25) { + Calculation::getInstance($spreadsheet)->flushInstance(); + $worksheet->setCellValue('A1', "=BESSELK({$x}, {$n})"); + + $helper->log(sprintf( + '%s = %f', + $worksheet->getCell('A1')->getValue(), + $worksheet->getCell('A1')->getCalculatedValue() + )); + } +} diff --git a/samples/Calculations/Engineering/BESSELY.php b/samples/Calculations/Engineering/BESSELY.php new file mode 100644 index 00000000..750b7204 --- /dev/null +++ b/samples/Calculations/Engineering/BESSELY.php @@ -0,0 +1,29 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +for ($n = 0; $n <= 5; ++$n) { + for ($x = 0; $x <= 5; $x = $x + 0.25) { + Calculation::getInstance($spreadsheet)->flushInstance(); + $worksheet->setCellValue('A1', "=BESSELY({$x}, {$n})"); + + $helper->log(sprintf( + '%s = %f', + $worksheet->getCell('A1')->getValue(), + $worksheet->getCell('A1')->getCalculatedValue() + )); + } +} diff --git a/samples/Calculations/Engineering/BIN2DEC.php b/samples/Calculations/Engineering/BIN2DEC.php new file mode 100644 index 00000000..0c4c4532 --- /dev/null +++ b/samples/Calculations/Engineering/BIN2DEC.php @@ -0,0 +1,46 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [101], + [110110], + [1000000], + [11111111], + [100010101], + [110001100], + [111111111], + [1111111111], + [1100110011], + [1000000000], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=BIN2DEC(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(B%d): Binary %s is decimal %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/BIN2HEX.php b/samples/Calculations/Engineering/BIN2HEX.php new file mode 100644 index 00000000..51a11199 --- /dev/null +++ b/samples/Calculations/Engineering/BIN2HEX.php @@ -0,0 +1,46 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [101], + [110110], + [1000000], + [11111111], + [100010101], + [110001100], + [111111111], + [1111111111], + [1100110011], + [1000000000], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=BIN2HEX(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(B%d): Binary %s is hexadecimal %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/BIN2OCT.php b/samples/Calculations/Engineering/BIN2OCT.php new file mode 100644 index 00000000..c320d360 --- /dev/null +++ b/samples/Calculations/Engineering/BIN2OCT.php @@ -0,0 +1,46 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [101], + [110110], + [1000000], + [11111111], + [100010101], + [110001100], + [111111111], + [1111111111], + [1100110011], + [1000000000], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=BIN2OCT(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(B%d): Binary %s is octal %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/BITAND.php b/samples/Calculations/Engineering/BITAND.php new file mode 100644 index 00000000..2a8f7a3c --- /dev/null +++ b/samples/Calculations/Engineering/BITAND.php @@ -0,0 +1,49 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [1, 5], + [3, 5], + [1, 6], + [9, 6], + [13, 25], + [23, 10], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('C' . $row, '=TEXT(DEC2BIN(A' . $row . '), "00000")'); + $worksheet->setCellValue('D' . $row, '=TEXT(DEC2BIN(B' . $row . '), "00000")'); + $worksheet->setCellValue('E' . $row, '=BITAND(A' . $row . ',B' . $row . ')'); + $worksheet->setCellValue('F' . $row, '=TEXT(DEC2BIN(E' . $row . '), "00000")'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): Bitwise AND of %d (%s) and %d (%s) is %d (%s)', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + $worksheet->getCell('B' . $row)->getValue(), + $worksheet->getCell('D' . $row)->getCalculatedValue(), + $worksheet->getCell('E' . $row)->getCalculatedValue(), + $worksheet->getCell('F' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/BITLSHIFT.php b/samples/Calculations/Engineering/BITLSHIFT.php new file mode 100644 index 00000000..872c8098 --- /dev/null +++ b/samples/Calculations/Engineering/BITLSHIFT.php @@ -0,0 +1,65 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [1], + [3], + [9], + [15], + [26], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=DEC2BIN(A' . $row . ')'); + $worksheet->setCellValue('C' . $row, '=BITLSHIFT(A' . $row . ',1)'); + $worksheet->setCellValue('D' . $row, '=DEC2BIN(C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=BITLSHIFT(A' . $row . ',2)'); + $worksheet->setCellValue('F' . $row, '=DEC2BIN(E' . $row . ')'); + $worksheet->setCellValue('G' . $row, '=BITLSHIFT(A' . $row . ',3)'); + $worksheet->setCellValue('H' . $row, '=DEC2BIN(G' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): Bitwise Left Shift of %d (%s) by 1 bit is %d (%s)', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + $worksheet->getCell('D' . $row)->getCalculatedValue(), + )); + $helper->log(sprintf( + '(E%d): Bitwise Left Shift of %d (%s) by 2 bits is %d (%s)', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + $worksheet->getCell('E' . $row)->getCalculatedValue(), + $worksheet->getCell('F' . $row)->getCalculatedValue(), + )); + $helper->log(sprintf( + '(E%d): Bitwise Left Shift of %d (%s) by 3 bits is %d (%s)', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + $worksheet->getCell('G' . $row)->getCalculatedValue(), + $worksheet->getCell('H' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/BITOR.php b/samples/Calculations/Engineering/BITOR.php new file mode 100644 index 00000000..1bf7f71d --- /dev/null +++ b/samples/Calculations/Engineering/BITOR.php @@ -0,0 +1,49 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [1, 5], + [3, 5], + [1, 6], + [9, 6], + [13, 25], + [23, 10], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('C' . $row, '=TEXT(DEC2BIN(A' . $row . '), "00000")'); + $worksheet->setCellValue('D' . $row, '=TEXT(DEC2BIN(B' . $row . '), "00000")'); + $worksheet->setCellValue('E' . $row, '=BITOR(A' . $row . ',B' . $row . ')'); + $worksheet->setCellValue('F' . $row, '=TEXT(DEC2BIN(E' . $row . '), "00000")'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): Bitwise OR of %d (%s) and %d (%s) is %d (%s)', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + $worksheet->getCell('B' . $row)->getValue(), + $worksheet->getCell('D' . $row)->getCalculatedValue(), + $worksheet->getCell('E' . $row)->getCalculatedValue(), + $worksheet->getCell('F' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/BITRSHIFT.php b/samples/Calculations/Engineering/BITRSHIFT.php new file mode 100644 index 00000000..3e7f3a88 --- /dev/null +++ b/samples/Calculations/Engineering/BITRSHIFT.php @@ -0,0 +1,63 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [9], + [15], + [26], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=DEC2BIN(A' . $row . ')'); + $worksheet->setCellValue('C' . $row, '=BITRSHIFT(A' . $row . ',1)'); + $worksheet->setCellValue('D' . $row, '=DEC2BIN(C' . $row . ')'); + $worksheet->setCellValue('E' . $row, '=BITRSHIFT(A' . $row . ',2)'); + $worksheet->setCellValue('F' . $row, '=DEC2BIN(E' . $row . ')'); + $worksheet->setCellValue('G' . $row, '=BITRSHIFT(A' . $row . ',3)'); + $worksheet->setCellValue('H' . $row, '=DEC2BIN(G' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): Bitwise Right Shift of %d (%s) by 1 bit is %d (%s)', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + $worksheet->getCell('D' . $row)->getCalculatedValue(), + )); + $helper->log(sprintf( + '(E%d): Bitwise Right Shift of %d (%s) by 2 bits is %d (%s)', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + $worksheet->getCell('E' . $row)->getCalculatedValue(), + $worksheet->getCell('F' . $row)->getCalculatedValue(), + )); + $helper->log(sprintf( + '(E%d): Bitwise Right Shift of %d (%s) by 3 bits is %d (%s)', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + $worksheet->getCell('G' . $row)->getCalculatedValue(), + $worksheet->getCell('H' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/BITXOR.php b/samples/Calculations/Engineering/BITXOR.php new file mode 100644 index 00000000..482662cd --- /dev/null +++ b/samples/Calculations/Engineering/BITXOR.php @@ -0,0 +1,49 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [1, 5], + [3, 5], + [1, 6], + [9, 6], + [13, 25], + [23, 10], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('C' . $row, '=TEXT(DEC2BIN(A' . $row . '), "00000")'); + $worksheet->setCellValue('D' . $row, '=TEXT(DEC2BIN(B' . $row . '), "00000")'); + $worksheet->setCellValue('E' . $row, '=BITXOR(A' . $row . ',B' . $row . ')'); + $worksheet->setCellValue('F' . $row, '=TEXT(DEC2BIN(E' . $row . '), "00000")'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): Bitwise XOR of %d (%s) and %d (%s) is %d (%s)', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + $worksheet->getCell('B' . $row)->getValue(), + $worksheet->getCell('D' . $row)->getCalculatedValue(), + $worksheet->getCell('E' . $row)->getCalculatedValue(), + $worksheet->getCell('F' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/COMPLEX.php b/samples/Calculations/Engineering/COMPLEX.php new file mode 100644 index 00000000..c58a1797 --- /dev/null +++ b/samples/Calculations/Engineering/COMPLEX.php @@ -0,0 +1,41 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [3, 4], + [3, 4, '"j"'], + [3.5, 4.75], + [0, 1], + [1, 0], + [0, -1], + [0, 2], + [2, 0], +]; +$testDataCount = count($testData); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('A' . $row, '=COMPLEX(' . implode(',', $testData[$row - 1]) . ')'); +} + +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(A%d): Formula %s result is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('A' . $row)->getCalculatedValue() + )); +} diff --git a/samples/Calculations/Engineering/CONVERT.php b/samples/Calculations/Engineering/CONVERT.php new file mode 100644 index 00000000..bce56ba5 --- /dev/null +++ b/samples/Calculations/Engineering/CONVERT.php @@ -0,0 +1,58 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$conversions = [ + [1, '"lbm"', '"kg"'], + [1, '"gal"', '"l"'], + [24, '"in"', '"ft"'], + [100, '"yd"', '"m"'], + [500, '"mi"', '"km"'], + [7.5, '"min"', '"sec"'], + [5, '"F"', '"C"'], + [32, '"C"', '"K"'], + [100, '"m2"', '"ft2"'], +]; +$testDataCount = count($conversions); + +$worksheet->fromArray($conversions, null, 'A1'); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('D' . $row, '=CONVERT(' . implode(',', $conversions[$row - 1]) . ')'); +} + +$worksheet->setCellValue('H1', '=CONVERT(CONVERT(100,"m","ft"),"m","ft")'); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(A%d): Unit of Measure Conversion Formula %s - %d %s is %f %s', + $row, + $worksheet->getCell('D' . $row)->getValue(), + $worksheet->getCell('A' . $row)->getValue(), + trim($worksheet->getCell('B' . $row)->getValue(), '"'), + $worksheet->getCell('D' . $row)->getCalculatedValue(), + trim($worksheet->getCell('C' . $row)->getValue(), '"') + )); +} + +$helper->log('Old method for area conversions, before MS Excel introduced area Units of Measure'); + +$helper->log(sprintf( + '(A%d): Unit of Measure Conversion Formula %s result is %s', + $row, + $worksheet->getCell('H1')->getValue(), + $worksheet->getCell('H1')->getCalculatedValue() +)); diff --git a/samples/Calculations/Engineering/DEC2BIN.php b/samples/Calculations/Engineering/DEC2BIN.php new file mode 100644 index 00000000..2a064c06 --- /dev/null +++ b/samples/Calculations/Engineering/DEC2BIN.php @@ -0,0 +1,47 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [-255], + [-123], + [-15], + [-1], + [5], + [7], + [19], + [51], + [121], + [256], + [511], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=DEC2BIN(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(B%d): Decimal %s is binary %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/DEC2HEX.php b/samples/Calculations/Engineering/DEC2HEX.php new file mode 100644 index 00000000..0a19ae54 --- /dev/null +++ b/samples/Calculations/Engineering/DEC2HEX.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [-255], + [-123], + [-15], + [-1], + [5], + [7], + [19], + [51], + [121], + [256], + [511], + [12345678], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=DEC2HEX(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(B%d): Decimal %s is hexadecimal %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/DEC2OCT.php b/samples/Calculations/Engineering/DEC2OCT.php new file mode 100644 index 00000000..fc11a832 --- /dev/null +++ b/samples/Calculations/Engineering/DEC2OCT.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [-255], + [-123], + [-15], + [-1], + [5], + [7], + [19], + [51], + [121], + [256], + [511], + [12345678], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=DEC2OCT(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(B%d): Decimal %s is octal %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/DELTA.php b/samples/Calculations/Engineering/DELTA.php new file mode 100644 index 00000000..cd51b161 --- /dev/null +++ b/samples/Calculations/Engineering/DELTA.php @@ -0,0 +1,46 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [4, 5], + [3, 3], + [0.5, 0], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('C' . $row, '=DELTA(A' . $row . ',B' . $row . ')'); +} + +$comparison = [ + 0 => 'The values are not equal', + 1 => 'The values are equal', +]; + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): Compare values %d and %d - Result is %d - %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + $comparison[$worksheet->getCell('C' . $row)->getCalculatedValue()] + )); +} diff --git a/samples/Calculations/Engineering/ERF.php b/samples/Calculations/Engineering/ERF.php new file mode 100644 index 00000000..e6505888 --- /dev/null +++ b/samples/Calculations/Engineering/ERF.php @@ -0,0 +1,67 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData1 = [ + [0.745], + [1], + [1.5], + [-2], +]; + +$testData2 = [ + [0, 1.5], + [1, 2], + [-2, 1], +]; +$testDataCount1 = count($testData1); +$testDataCount2 = count($testData2); +$testData2StartRow = $testDataCount1 + 1; + +$worksheet->fromArray($testData1, null, 'A1', true); +$worksheet->fromArray($testData2, null, "A{$testData2StartRow}", true); + +for ($row = 1; $row <= $testDataCount1; ++$row) { + $worksheet->setCellValue('C' . $row, '=ERF(A' . $row . ')'); +} + +for ($row = $testDataCount1 + 1; $row <= $testDataCount2 + $testDataCount1; ++$row) { + $worksheet->setCellValue('C' . $row, '=ERF(A' . $row . ', B' . $row . ')'); +} + +// Test the formulae +$helper->log('ERF() With a single argument'); +for ($row = 1; $row <= $testDataCount1; ++$row) { + $helper->log(sprintf( + '(C%d): %s The error function integrated between 0 and %f is %f', + $row, + $worksheet->getCell('C' . $row)->getValue(), + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + )); +} + +$helper->log('ERF() With two arguments'); +for ($row = $testDataCount1 + 1; $row <= $testDataCount2 + $testDataCount1; ++$row) { + $helper->log(sprintf( + '(C%d): %s The error function integrated between %f and %f is %f', + $row, + $worksheet->getCell('C' . $row)->getValue(), + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/ERFC.php b/samples/Calculations/Engineering/ERFC.php new file mode 100644 index 00000000..5e7bcc6d --- /dev/null +++ b/samples/Calculations/Engineering/ERFC.php @@ -0,0 +1,41 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [0], + [0.5], + [1], + [-1], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('C' . $row, '=ERFC(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): %s The complementary error function integrated by %f and infinity is %f', + $row, + $worksheet->getCell('C' . $row)->getValue(), + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/GESTEP.php b/samples/Calculations/Engineering/GESTEP.php new file mode 100644 index 00000000..73f0a31c --- /dev/null +++ b/samples/Calculations/Engineering/GESTEP.php @@ -0,0 +1,53 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [5, 4], + [5, 5], + [4, 5], + [-4, -5], + [-5, -4], + [1], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('C' . $row, '=GESTEP(A' . $row . ',B' . $row . ')'); +} + +$comparison = [ + 0 => 'Value %d is less than step %d', + 1 => 'Value %d is greater than or equal to step %d', +]; + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): Compare value %d and step %d - Result is %d - %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + sprintf( + $comparison[$worksheet->getCell('C' . $row)->getCalculatedValue()], + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getValue(), + ) + )); +} diff --git a/samples/Calculations/Engineering/HEX2BIN.php b/samples/Calculations/Engineering/HEX2BIN.php new file mode 100644 index 00000000..2ad08925 --- /dev/null +++ b/samples/Calculations/Engineering/HEX2BIN.php @@ -0,0 +1,46 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [3], + [8], + [42], + [99], + ['A2'], + ['F0'], + ['100'], + ['128'], + ['1AB'], + ['1FF'], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=HEX2BIN(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(B%d): Hexadecimal %s is binary %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/HEX2DEC.php b/samples/Calculations/Engineering/HEX2DEC.php new file mode 100644 index 00000000..745d4110 --- /dev/null +++ b/samples/Calculations/Engineering/HEX2DEC.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['08'], + ['42'], + ['A2'], + ['400'], + ['1000'], + ['1234'], + ['ABCD'], + ['C3B0'], + ['FFFFFFFFF'], + ['FFFFFFFFFF'], + ['FFFFFFF800'], + ['FEDCBA9876'], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=HEX2DEC(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(B%d): Hexadecimal %s is decimal %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/HEX2OCT.php b/samples/Calculations/Engineering/HEX2OCT.php new file mode 100644 index 00000000..3608c1bb --- /dev/null +++ b/samples/Calculations/Engineering/HEX2OCT.php @@ -0,0 +1,46 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['08'], + ['42'], + ['A2'], + ['400'], + ['100'], + ['1234'], + ['ABCD'], + ['C3B0'], + ['FFFFFFFFFF'], + ['FFFFFFF800'], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=HEX2OCT(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(B%d): Hexadecimal %s is octal %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMABS.php b/samples/Calculations/Engineering/IMABS.php new file mode 100644 index 00000000..9c6b843c --- /dev/null +++ b/samples/Calculations/Engineering/IMABS.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMABS(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The absolute value of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMAGINARY.php b/samples/Calculations/Engineering/IMAGINARY.php new file mode 100644 index 00000000..99138938 --- /dev/null +++ b/samples/Calculations/Engineering/IMAGINARY.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMAGINARY(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The imaginary component of %s is %f', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMARGUMENT.php b/samples/Calculations/Engineering/IMARGUMENT.php new file mode 100644 index 00000000..e559e951 --- /dev/null +++ b/samples/Calculations/Engineering/IMARGUMENT.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMARGUMENT(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Theta Argument of %s is %f radians', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMCONJUGATE.php b/samples/Calculations/Engineering/IMCONJUGATE.php new file mode 100644 index 00000000..3b4429ed --- /dev/null +++ b/samples/Calculations/Engineering/IMCONJUGATE.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMCONJUGATE(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Conjugate of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMCOS.php b/samples/Calculations/Engineering/IMCOS.php new file mode 100644 index 00000000..5b8f81ea --- /dev/null +++ b/samples/Calculations/Engineering/IMCOS.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMCOS(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Cosine of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMCOSH.php b/samples/Calculations/Engineering/IMCOSH.php new file mode 100644 index 00000000..9a393766 --- /dev/null +++ b/samples/Calculations/Engineering/IMCOSH.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMCOSH(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Hyperbolic Cosine of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMCOT.php b/samples/Calculations/Engineering/IMCOT.php new file mode 100644 index 00000000..e3d980cd --- /dev/null +++ b/samples/Calculations/Engineering/IMCOT.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMCOT(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Cotangent of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMCSC.php b/samples/Calculations/Engineering/IMCSC.php new file mode 100644 index 00000000..ab6695d0 --- /dev/null +++ b/samples/Calculations/Engineering/IMCSC.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMCSC(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Cosecant of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMCSCH.php b/samples/Calculations/Engineering/IMCSCH.php new file mode 100644 index 00000000..4513d9e9 --- /dev/null +++ b/samples/Calculations/Engineering/IMCSCH.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMCSCH(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Hyperbolic Cosecant of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMDIV.php b/samples/Calculations/Engineering/IMDIV.php new file mode 100644 index 00000000..9512be57 --- /dev/null +++ b/samples/Calculations/Engineering/IMDIV.php @@ -0,0 +1,42 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i', '5-3i'], + ['3+4i', '5+3i'], + ['-238+240i', '10+24i'], + ['1+2i', 30], + ['1+2i', '2i'], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('C' . $row, '=IMDIV(A' . $row . ', B' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Quotient of %s and %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMEXP.php b/samples/Calculations/Engineering/IMEXP.php new file mode 100644 index 00000000..7f5837b2 --- /dev/null +++ b/samples/Calculations/Engineering/IMEXP.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMEXP(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Exponential of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMLN.php b/samples/Calculations/Engineering/IMLN.php new file mode 100644 index 00000000..95618257 --- /dev/null +++ b/samples/Calculations/Engineering/IMLN.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMLN(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Natural Logarithm of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMLOG10.php b/samples/Calculations/Engineering/IMLOG10.php new file mode 100644 index 00000000..d501c3de --- /dev/null +++ b/samples/Calculations/Engineering/IMLOG10.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMLOG10(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Base-10 Logarithm of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMLOG2.php b/samples/Calculations/Engineering/IMLOG2.php new file mode 100644 index 00000000..25986b39 --- /dev/null +++ b/samples/Calculations/Engineering/IMLOG2.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMLOG2(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Base-2 Logarithm of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMPOWER.php b/samples/Calculations/Engineering/IMPOWER.php new file mode 100644 index 00000000..c6674fbe --- /dev/null +++ b/samples/Calculations/Engineering/IMPOWER.php @@ -0,0 +1,49 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i', 2], + ['5-12i', 2], + ['3.25+7.5i', 3], + ['3.25-12.5i', 2], + ['-3.25+7.5i', 3], + ['-3.25-7.5i', 4], + ['0-j', 5], + ['0-2.5j', 3], + ['0+j', 2.5], + ['0+1.25j', 2], + [4, 3], + [-2.5, 2], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('C' . $row, '=IMPOWER(A' . $row . ', B' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): %s raised to the power of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMPRODUCT.php b/samples/Calculations/Engineering/IMPRODUCT.php new file mode 100644 index 00000000..f81bc666 --- /dev/null +++ b/samples/Calculations/Engineering/IMPRODUCT.php @@ -0,0 +1,42 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i', '5-3i'], + ['3+4i', '5+3i'], + ['-238+240i', '10+24i'], + ['1+2i', 30], + ['1+2i', '2i'], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('C' . $row, '=IMPRODUCT(A' . $row . ', B' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Product of %s and %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMREAL.php b/samples/Calculations/Engineering/IMREAL.php new file mode 100644 index 00000000..4e537c0f --- /dev/null +++ b/samples/Calculations/Engineering/IMREAL.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMREAL(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The real component of %s is %f radians', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMSEC.php b/samples/Calculations/Engineering/IMSEC.php new file mode 100644 index 00000000..e6c524b3 --- /dev/null +++ b/samples/Calculations/Engineering/IMSEC.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMSEC(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Secant of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMSECH.php b/samples/Calculations/Engineering/IMSECH.php new file mode 100644 index 00000000..e07b6e08 --- /dev/null +++ b/samples/Calculations/Engineering/IMSECH.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMSECH(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Hyperbolic Secant of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMSIN.php b/samples/Calculations/Engineering/IMSIN.php new file mode 100644 index 00000000..d3b8c281 --- /dev/null +++ b/samples/Calculations/Engineering/IMSIN.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMSIN(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Sine of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMSINH.php b/samples/Calculations/Engineering/IMSINH.php new file mode 100644 index 00000000..ac0a9039 --- /dev/null +++ b/samples/Calculations/Engineering/IMSINH.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMSINH(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Hyperbolic Sine of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMSQRT.php b/samples/Calculations/Engineering/IMSQRT.php new file mode 100644 index 00000000..c2573c91 --- /dev/null +++ b/samples/Calculations/Engineering/IMSQRT.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMSQRT(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Square Root of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMSUB.php b/samples/Calculations/Engineering/IMSUB.php new file mode 100644 index 00000000..90bd27a4 --- /dev/null +++ b/samples/Calculations/Engineering/IMSUB.php @@ -0,0 +1,42 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i', '5-3i'], + ['3+4i', '5+3i'], + ['-238+240i', '10+24i'], + ['1+2i', 30], + ['1+2i', '2i'], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('C' . $row, '=IMSUB(A' . $row . ', B' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Difference between %s and %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMSUM.php b/samples/Calculations/Engineering/IMSUM.php new file mode 100644 index 00000000..2a8be320 --- /dev/null +++ b/samples/Calculations/Engineering/IMSUM.php @@ -0,0 +1,42 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i', '5-3i'], + ['3+4i', '5+3i'], + ['-238+240i', '10+24i'], + ['1+2i', 30], + ['1+2i', '2i'], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('C' . $row, '=IMSUM(A' . $row . ', B' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Sum of %s and %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getValue(), + $worksheet->getCell('C' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/IMTAN.php b/samples/Calculations/Engineering/IMTAN.php new file mode 100644 index 00000000..ffaa53b2 --- /dev/null +++ b/samples/Calculations/Engineering/IMTAN.php @@ -0,0 +1,48 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + ['3+4i'], + ['5-12i'], + ['3.25+7.5i'], + ['3.25-12.5i'], + ['-3.25+7.5i'], + ['-3.25-7.5i'], + ['0-j'], + ['0-2.5j'], + ['0+j'], + ['0+1.25j'], + [4], + [-2.5], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=IMTAN(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(E%d): The Tangent of %s is %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/OCT2BIN.php b/samples/Calculations/Engineering/OCT2BIN.php new file mode 100644 index 00000000..9c4bbf86 --- /dev/null +++ b/samples/Calculations/Engineering/OCT2BIN.php @@ -0,0 +1,47 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [3], + [7], + [42], + [70], + [72], + [77], + [100], + [127], + [177], + [456], + [567], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=OCT2BIN(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(B%d): Octal %s is binary %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/OCT2DEC.php b/samples/Calculations/Engineering/OCT2DEC.php new file mode 100644 index 00000000..ea6afb2f --- /dev/null +++ b/samples/Calculations/Engineering/OCT2DEC.php @@ -0,0 +1,49 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [3], + [7], + [42], + [70], + [72], + [77], + [100], + [127], + [177], + [456], + [4567], + [7777700001], + [7777776543], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=OCT2DEC(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(B%d): Octal %s is decimal %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/Engineering/OCT2HEX.php b/samples/Calculations/Engineering/OCT2HEX.php new file mode 100644 index 00000000..47e9b6e1 --- /dev/null +++ b/samples/Calculations/Engineering/OCT2HEX.php @@ -0,0 +1,49 @@ +titles($category, $functionName, $description); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$testData = [ + [3], + [12], + [42], + [70], + [72], + [77], + [100], + [127], + [177], + [456], + [4567], + [7777700001], + [7777776543], +]; +$testDataCount = count($testData); + +$worksheet->fromArray($testData, null, 'A1', true); + +for ($row = 1; $row <= $testDataCount; ++$row) { + $worksheet->setCellValue('B' . $row, '=OCT2HEX(A' . $row . ')'); +} + +// Test the formulae +for ($row = 1; $row <= $testDataCount; ++$row) { + $helper->log(sprintf( + '(B%d): Octal %s is hexadecimal %s', + $row, + $worksheet->getCell('A' . $row)->getValue(), + $worksheet->getCell('B' . $row)->getCalculatedValue(), + )); +} diff --git a/samples/Calculations/LookupRef/VLOOKUP.php b/samples/Calculations/LookupRef/VLOOKUP.php index 3e7eaa71..16a911e8 100644 --- a/samples/Calculations/LookupRef/VLOOKUP.php +++ b/samples/Calculations/LookupRef/VLOOKUP.php @@ -14,8 +14,8 @@ $worksheet = $spreadsheet->getActiveSheet(); $data = [ ['ID', 'First Name', 'Last Name', 'Salary'], - [72, 'Emily', 'Smith', 64901, null, 'ID', 53, 66, 56], - [66, 'James', 'Anderson', 70855, null, 'Salary'], + [72, 'Emily', 'Smith', 64901], + [66, 'James', 'Anderson', 70855], [14, 'Mia', 'Clark', 188657], [30, 'John', 'Lewis', 97566], [53, 'Jessica', 'Walker', 58339], @@ -25,11 +25,24 @@ $data = [ $worksheet->fromArray($data, null, 'B2'); -$worksheet->getCell('H4')->setValue('=VLOOKUP(H3, B3:E9, 4, FALSE)'); -$worksheet->getCell('I4')->setValue('=VLOOKUP(I3, B3:E9, 4, FALSE)'); -$worksheet->getCell('J4')->setValue('=VLOOKUP(J3, B3:E9, 4, FALSE)'); +$lookupFields = [ + ['ID', 53, 66, 56], + ['Name'], + ['Salary'], +]; + +$worksheet->fromArray($lookupFields, null, 'G3'); + +$worksheet->getCell('H4')->setValue('=VLOOKUP(H3, B3:E9, 2, FALSE) & " " & VLOOKUP(H3, B3:E9, 3, FALSE)'); +$worksheet->getCell('I4')->setValue('=VLOOKUP(I3, B3:E9, 2, FALSE) & " " & VLOOKUP(I3, B3:E9, 3, FALSE)'); +$worksheet->getCell('J4')->setValue('=VLOOKUP(J3, B3:E9, 2, FALSE) & " " & VLOOKUP(J3, B3:E9, 3, FALSE)'); +$worksheet->getCell('H5')->setValue('=VLOOKUP(H3, B3:E9, 4, FALSE)'); +$worksheet->getCell('I5')->setValue('=VLOOKUP(I3, B3:E9, 4, FALSE)'); +$worksheet->getCell('J5')->setValue('=VLOOKUP(J3, B3:E9, 4, FALSE)'); for ($column = 'H'; $column !== 'K'; ++$column) { - $cell = $worksheet->getCell("{$column}4"); - $helper->log("{$column}4: {$cell->getValue()} => {$cell->getCalculatedValue()}"); + for ($row = 4; $row <= 5; ++$row) { + $cell = $worksheet->getCell("{$column}{$row}"); + $helper->log("{$column}{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}"); + } } diff --git a/samples/Chart/32_Chart_read_write_HTML.php b/samples/Chart/32_Chart_read_write_HTML.php index 5febbf93..90d61c5d 100644 --- a/samples/Chart/32_Chart_read_write_HTML.php +++ b/samples/Chart/32_Chart_read_write_HTML.php @@ -6,7 +6,8 @@ use PhpOffice\PhpSpreadsheet\Settings; require __DIR__ . '/../Header.php'; // Change these values to select the Rendering library that you wish to use -Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class); +//Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class); +Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class); $inputFileType = 'Xlsx'; $inputFileNames = __DIR__ . '/../templates/36write*.xlsx'; diff --git a/samples/Chart/32_Chart_read_write_PDF.php b/samples/Chart/32_Chart_read_write_PDF.php index ee3ad0e0..214c3d38 100644 --- a/samples/Chart/32_Chart_read_write_PDF.php +++ b/samples/Chart/32_Chart_read_write_PDF.php @@ -8,7 +8,8 @@ require __DIR__ . '/../Header.php'; IOFactory::registerWriter('Pdf', \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf::class); // Change these values to select the Rendering library that you wish to use -Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class); +//Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class); +Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class); $inputFileType = 'Xlsx'; $inputFileNames = __DIR__ . '/../templates/36write*.xlsx'; diff --git a/samples/Chart/33_Chart_create_line_dateaxis.php b/samples/Chart/33_Chart_create_line_dateaxis.php new file mode 100644 index 00000000..413866e2 --- /dev/null +++ b/samples/Chart/33_Chart_create_line_dateaxis.php @@ -0,0 +1,375 @@ +getActiveSheet(); +$dataSheet->setTitle('Data'); +// changed data to simulate a trend chart - Xaxis are dates; Yaxis are 3 meausurements from each date +// Dates changed not to fall on exact quarter start +$dataSheet->fromArray( + [ + ['', 'date', 'metric1', 'metric2', 'metric3'], + ['=DATEVALUE(B2)', '2021-01-10', 12.1, 15.1, 21.1], + ['=DATEVALUE(B3)', '2021-04-21', 56.2, 73.2, 86.2], + ['=DATEVALUE(B4)', '2021-07-31', 52.2, 61.2, 69.2], + ['=DATEVALUE(B5)', '2021-10-11', 30.2, 22.2, 0.2], + ['=DATEVALUE(B6)', '2022-01-21', 40.1, 38.1, 65.1], + ['=DATEVALUE(B7)', '2022-04-11', 45.2, 44.2, 96.2], + ['=DATEVALUE(B8)', '2022-07-01', 52.2, 51.2, 55.2], + ['=DATEVALUE(B9)', '2022-10-31', 41.2, 72.2, 56.2], + ] +); + +$dataSheet->getStyle('A2:A9')->getNumberFormat()->setFormatCode(Properties::FORMAT_CODE_DATE_ISO8601); +$dataSheet->getColumnDimension('A')->setAutoSize(true); +$dataSheet->getColumnDimension('B')->setAutoSize(true); +$dataSheet->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 +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Data!$C$1', null, 1), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Data!$D$1', null, 1), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Data!$E$1', null, 1), +]; +// Set the X-Axis Labels +// NUMBER, not STRING +// added x-axis values for each of the 3 metrics +// added FORMATE_CODE_NUMBER +// Number of datapoints in series +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$A$2:$A$9', Properties::FORMAT_CODE_DATE_ISO8601, 8), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$A$2:$A$9', Properties::FORMAT_CODE_DATE_ISO8601, 8), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$A$2:$A$9', Properties::FORMAT_CODE_DATE_ISO8601, 8), +]; +// 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 +// Data Marker Color fill/[fill,Border] +// Data Marker size +// Color(s) added +// added FORMAT_CODE_NUMBER +$dataSeriesValues = [ + new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_NUMBER, + 'Data!$C$2:$C$9', + Properties::FORMAT_CODE_NUMBER, + 8, + null, + 'diamond', + null, + 5 + ), + new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_NUMBER, + 'Data!$D$2:$D$9', + Properties::FORMAT_CODE_NUMBER, + 8, + null, + 'square', + '*accent1', + 6 + ), + new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_NUMBER, + 'Data!$E$2:$E$9', + Properties::FORMAT_CODE_NUMBER, + 8, + null, + null, + null, + 7 + ), // let Excel choose marker shape +]; +// series 1 - metric1 +// marker details +$dataSeriesValues[0] + ->getMarkerFillColor() + ->setColorProperties('0070C0', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[0] + ->getMarkerBorderColor() + ->setColorProperties('002060', null, ChartColor::EXCEL_COLOR_TYPE_RGB); + +// line details - dashed, smooth line (Bezier) with arrows, 40% transparent +$dataSeriesValues[0] + ->setSmoothLine(true) + ->setScatterLines(true) + ->setLineColorProperties('accent1', 40, ChartColor::EXCEL_COLOR_TYPE_SCHEME); // value, alpha, type +$dataSeriesValues[0]->setLineStyleProperties( + 2.5, // width in points + Properties::LINE_STYLE_COMPOUND_TRIPLE, // compound + Properties::LINE_STYLE_DASH_SQUARE_DOT, // dash + Properties::LINE_STYLE_CAP_SQUARE, // cap + Properties::LINE_STYLE_JOIN_MITER, // join + Properties::LINE_STYLE_ARROW_TYPE_OPEN, // head type + Properties::LINE_STYLE_ARROW_SIZE_4, // head size preset index + Properties::LINE_STYLE_ARROW_TYPE_ARROW, // end type + Properties::LINE_STYLE_ARROW_SIZE_6 // end size preset index +); + +// series 2 - metric2, straight line - no special effects, connected +$dataSeriesValues[1] // square marker border color + ->getMarkerBorderColor() + ->setColorProperties('accent6', 3, ChartColor::EXCEL_COLOR_TYPE_SCHEME); +$dataSeriesValues[1] // square marker fill color + ->getMarkerFillColor() + ->setColorProperties('0FFF00', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[1] + ->setScatterLines(true) + ->setSmoothLine(false) + ->setLineColorProperties('FF0000', 80, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[1]->setLineWidth(2.0); + +// series 3 - metric3, markers, no line +$dataSeriesValues[2] // triangle? fill + //->setPointMarker('triangle') // let Excel choose shape, which is predicted to be a triangle + ->getMarkerFillColor() + ->setColorProperties('FFFF00', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[2] // triangle border + ->getMarkerBorderColor() + ->setColorProperties('accent4', null, ChartColor::EXCEL_COLOR_TYPE_SCHEME); +$dataSeriesValues[2]->setScatterLines(false); // points not connected +// Added so that Xaxis shows dates instead of Excel-equivalent-year1900-numbers +$xAxis = new Axis(); +$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601); + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_SCATTERCHART, // plotType + null, // plotGrouping (Scatter charts don't have grouping) + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues, // plotValues + null, // plotDirection + null, // smooth line + DataSeries::STYLE_SMOOTHMARKER // 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 Chart'); +$yAxisLabel = new Title('Value ($k)'); +$yAxis = new Axis(); +$yAxis->setMajorGridlines(new GridLines()); + +// 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 + $yAxis, // yAxis +); + +// Set the position of the chart in the chart sheet +$chart->setTopLeftPosition('A1'); +$chart->setBottomRightPosition('P12'); + +// create a 'Chart' worksheet, add $chart to it +$spreadsheet->createSheet(); +$chartSheet = $spreadsheet->getSheet(1); +$chartSheet->setTitle('Scatter+Line Chart'); + +$chartSheet = $spreadsheet->getSheetByName('Scatter+Line Chart'); +// Add the chart to the worksheet +$chartSheet->addChart($chart); + +// ------- Demonstrate Date Xaxis in Line Chart, not possible using Scatter Chart ------------ + +// Set the Labels (Column header) for each data series we want to plot +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Data!$E$1', null, 1), +]; + +// Set the X-Axis Labels - dates, N.B. 01/10/2021 === Jan 10, NOT Oct 1 !! +// x-axis values are the Excel numeric representation of the date - so set +// formatCode=General for the xAxis VALUES, but we want the labels to be +// DISPLAYED as 'yyyy-mm-dd' That is, read a number, display a date. +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$A$2:$A$9', Properties::FORMAT_CODE_DATE_ISO8601, 8), +]; + +// X axis (date) settings +$xAxisLabel = new Title('Date'); +$xAxis = new Axis(); +$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601); // yyyy-mm-dd + +// Set the Data values for each data series we want to plot +$dataSeriesValues = [ + new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_NUMBER, + 'Data!$E$2:$E$9', + Properties::FORMAT_CODE_NUMBER, + 8, + null, + 'triangle', + null, + 7 + ), +]; + +// series - metric3, markers, no line +$dataSeriesValues[0] + ->setScatterlines(false); // disable connecting lines +$dataSeriesValues[0] + ->getMarkerFillColor() + ->setColorProperties('FFFF00', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[0] + ->getMarkerBorderColor() + ->setColorProperties('accent4', null, ChartColor::EXCEL_COLOR_TYPE_SCHEME); + +// Build the dataseries +// must now use LineChart instead of ScatterChart, since ScatterChart does not +// support "dateAx" axis type. +$series = new DataSeries( + DataSeries::TYPE_LINECHART, // plotType + 'standard', // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues, // plotValues + null, // plotDirection + false, // smooth line + DataSeries::STYLE_LINEMARKER // plotStyle + // DataSeries::STYLE_SMOOTHMARKER // plotStyle +); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +// Set the chart legend +$legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false); + +$title = new Title('Test Line-Chart with Date Axis - metric3 values'); + +// X axis (date) settings +$xAxisLabel = new Title('Game Date'); +$xAxis = new Axis(); +// date axis values are Excel numbers, not yyyy-mm-dd Date strings +$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601); + +$xAxis->setAxisType('dateAx'); // dateAx available ONLY for LINECHART, not SCATTERCHART + +// measure the time span in Quarters, of data. +$dateMinMax = dateRange(8, $spreadsheet); // array 'min'=>earliest date of first Q, 'max'=>latest date of final Q +// change xAxis tick marks to match Qtr boundaries + +$nQtrs = sprintf('%3.2f', (($dateMinMax['max'] - $dateMinMax['min']) / 30.5) / 4); +$tickMarkInterval = ($nQtrs > 20) ? 6 : 3; // tick marks every ? months + +$xAxis->setAxisOptionsProperties( + Properties::AXIS_LABELS_NEXT_TO, // axis_label pos + null, // horizontalCrossesValue + null, // horizontalCrosses + null, // axisOrientation + 'in', // major_tick_mark + null, // minor_tick_mark + $dateMinMax['min'], // minimum calculate this from the earliest data: 'Data!$A$2' + $dateMinMax['max'], // maximum calculate this from the last data: 'Data!$A$'.($nrows+1) + $tickMarkInterval, // majorUnit determines tickmarks & Gridlines ? + null, // minorUnit + null, // textRotation + null, // hidden + 'days', // baseTimeUnit + 'months', // majorTimeUnit, + 'months', // minorTimeUnit +); + +$yAxisLabel = new Title('Value ($k)'); +$yAxis = new Axis(); +$yAxis->setMajorGridlines(new GridLines()); +$xAxis->setMajorGridlines(new GridLines()); +$minorGridLines = new GridLines(); +$minorGridLines->activateObject(); +$xAxis->setMinorGridlines($minorGridLines); + +// Create the chart +$chart = new Chart( + 'chart2', // 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 + $yAxis, // yAxis +); + +// Set the position of the chart in the chart sheet below the first chart +$chart->setTopLeftPosition('A13'); +$chart->setBottomRightPosition('P25'); +$chart->setRoundedCorners(true); // Rounded corners in Chart Outline + +// Add the chart to the worksheet $chartSheet +$chartSheet->addChart($chart); +$spreadsheet->setActiveSheetIndex(1); + +// 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); +$spreadsheet->disconnectWorksheets(); + +function dateRange(int $nrows, Spreadsheet $wrkbk): array +{ + $dataSheet = $wrkbk->getSheetByName('Data'); + + // start the xaxis at the beginning of the quarter of the first date + $startDateStr = $dataSheet->getCell('B2')->getValue(); // yyyy-mm-dd date string + $startDate = DateTime::createFromFormat('Y-m-d', $startDateStr); // php date obj + + // get date of first day of the quarter of the start date + $startMonth = (int) $startDate->format('n'); // suppress leading zero + $startYr = (int) $startDate->format('Y'); + $qtr = intdiv($startMonth, 3) + (($startMonth % 3 > 0) ? 1 : 0); + $qtrStartMonth = sprintf('%02d', 1 + (($qtr - 1) * 3)); + $qtrStartStr = "$startYr-$qtrStartMonth-01"; + $ExcelQtrStartDateVal = SharedDate::convertIsoDate($qtrStartStr); + + // end the xaxis at the end of the quarter of the last date + $lastDateStr = $dataSheet->getCellByColumnAndRow(2, $nrows + 1)->getValue(); + $lastDate = DateTime::createFromFormat('Y-m-d', $lastDateStr); + $lastMonth = (int) $lastDate->format('n'); + $lastYr = (int) $lastDate->format('Y'); + $qtr = intdiv($lastMonth, 3) + (($lastMonth % 3 > 0) ? 1 : 0); + $qtrEndMonth = 3 + (($qtr - 1) * 3); + $lastDOM = cal_days_in_month(CAL_GREGORIAN, $qtrEndMonth, $lastYr); + $qtrEndMonth = sprintf('%02d', $qtrEndMonth); + $qtrEndStr = "$lastYr-$qtrEndMonth-$lastDOM"; + $ExcelQtrEndDateVal = SharedDate::convertIsoDate($qtrEndStr); + + $minMaxDates = ['min' => $ExcelQtrStartDateVal, 'max' => $ExcelQtrEndDateVal]; + + return $minMaxDates; +} diff --git a/samples/Chart/33_Chart_create_scatter2.php b/samples/Chart/33_Chart_create_scatter2.php index eed87119..cbc3ec1a 100644 --- a/samples/Chart/33_Chart_create_scatter2.php +++ b/samples/Chart/33_Chart_create_scatter2.php @@ -1,6 +1,6 @@ setColorProperties('accent4', null, ChartColor::EXCEL_COLOR_TYPE_SCHEME); $dataSeriesValues[2]->setScatterLines(false); // points not connected - // Added so that Xaxis shows dates instead of Excel-equivalent-year1900-numbers -$xAxis = new Axis(); +// Added so that Xaxis shows dates instead of Excel-equivalent-year1900-numbers +$xAxis = new ChartAxis(); //$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE ); $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601, true); $xAxis->setAxisOption('textRotation', '45'); -$yAxis = new Axis(); +$yAxis = new ChartAxis(); $yAxis->setLineStyleProperties( 2.5, // width in points Properties::LINE_STYLE_COMPOUND_SIMPLE, diff --git a/samples/Chart/33_Chart_create_scatter3.php b/samples/Chart/33_Chart_create_scatter3.php new file mode 100644 index 00000000..899fca79 --- /dev/null +++ b/samples/Chart/33_Chart_create_scatter3.php @@ -0,0 +1,192 @@ +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), +]; + +// series 1 +// marker details +$dataSeriesValues[0] + ->setPointMarker('diamond') + ->setPointSize(5) + ->getMarkerFillColor() + ->setColorProperties('0070C0', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[0] + ->getMarkerBorderColor() + ->setColorProperties('002060', null, ChartColor::EXCEL_COLOR_TYPE_RGB); + +// line details - smooth line, connected +$dataSeriesValues[0] + ->setScatterLines(true) + ->setSmoothLine(true) + ->setLineColorProperties('accent1', 40, ChartColor::EXCEL_COLOR_TYPE_SCHEME); // value, alpha, type +$dataSeriesValues[0]->setLineStyleProperties( + 2.5, // width in points + Properties::LINE_STYLE_COMPOUND_TRIPLE, // compound + Properties::LINE_STYLE_DASH_SQUARE_DOT, // dash + Properties::LINE_STYLE_CAP_SQUARE, // cap + Properties::LINE_STYLE_JOIN_MITER, // join + Properties::LINE_STYLE_ARROW_TYPE_OPEN, // head type + Properties::LINE_STYLE_ARROW_SIZE_4, // head size preset index + Properties::LINE_STYLE_ARROW_TYPE_ARROW, // end type + Properties::LINE_STYLE_ARROW_SIZE_6 // end size preset index +); + +// series 2 - straight line - no special effects, connected, straight line +$dataSeriesValues[1] // square fill + ->setPointMarker('square') + ->setPointSize(6) + ->getMarkerBorderColor() + ->setColorProperties('accent6', 3, ChartColor::EXCEL_COLOR_TYPE_SCHEME); +$dataSeriesValues[1] // square border + ->getMarkerFillColor() + ->setColorProperties('0FFF00', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[1] + ->setScatterLines(true) + ->setSmoothLine(false) + ->setLineColorProperties('FF0000', 80, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[1]->setLineWidth(2.0); + +// series 3 - markers, no line +$dataSeriesValues[2] // triangle fill + //->setPointMarker('triangle') // let Excel choose shape + ->setPointSize(7) + ->getMarkerFillColor() + ->setColorProperties('FFFF00', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[2] // triangle border + ->getMarkerBorderColor() + ->setColorProperties('accent4', null, ChartColor::EXCEL_COLOR_TYPE_SCHEME); +$dataSeriesValues[2]->setScatterLines(false); // points not connected + +// Added so that Xaxis shows dates instead of Excel-equivalent-year1900-numbers +$xAxis = new ChartAxis(); +//$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE ); +$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601, true); +$xAxis->setAxisOption('textRotation', '45'); +$xAxis->setAxisOption('hidden', '1'); + +$yAxis = new ChartAxis(); +$yAxis->setLineStyleProperties( + 2.5, // width in points + Properties::LINE_STYLE_COMPOUND_SIMPLE, + Properties::LINE_STYLE_DASH_DASH_DOT, + Properties::LINE_STYLE_CAP_FLAT, + Properties::LINE_STYLE_JOIN_BEVEL +); +$yAxis->setLineColorProperties('ffc000', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$yAxis->setAxisOption('hidden', '1'); + +// 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_SMOOTHMARKER // plotStyle +); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +$plotArea->setNoFill(true); +// 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 + null, //$yAxisLabel, // yAxisLabel + // added xAxis for correct date display + $xAxis, // xAxis + $yAxis, // yAxis +); +$chart->setNoFill(true); + +// 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/Chart/33_Chart_create_scatter4.php b/samples/Chart/33_Chart_create_scatter4.php new file mode 100644 index 00000000..d42933b6 --- /dev/null +++ b/samples/Chart/33_Chart_create_scatter4.php @@ -0,0 +1,130 @@ +getActiveSheet(); +$worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] +); + +// 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), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 +]; +// Set the X-Axis Labels +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// 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 +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), +]; + +// 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 + null, // smooth line + DataSeries::STYLE_LINEMARKER // plotStyle +); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); + +$pos1 = 0; // pos = 0% (extreme low side or lower left corner) +$brightness1 = 0; // 0% +$gsColor1 = new ChartColor(); +$gsColor1->setColorProperties('FF0000', 75, 'srgbClr', $brightness1); // red +$gradientStop1 = [$pos1, $gsColor1]; + +$pos2 = 0.5; // pos = 50% (middle) +$brightness2 = 0.5; // 50% +$gsColor2 = new ChartColor(); +$gsColor2->setColorProperties('FFFF00', 50, 'srgbClr', $brightness2); // yellow +$gradientStop2 = [$pos2, $gsColor2]; + +$pos3 = 1.0; // pos = 100% (extreme high side or upper right corner) +$brightness3 = 0.5; // 50% +$gsColor3 = new ChartColor(); +$gsColor3->setColorProperties('00B050', 50, 'srgbClr', $brightness3); // green +$gradientStop3 = [$pos3, $gsColor3]; + +$gradientFillStops = [ + $gradientStop1, + $gradientStop2, + $gradientStop3, +]; +$gradientFillAngle = 315.0; // 45deg above horiz + +$plotArea->setGradientFillProperties($gradientFillStops, $gradientFillAngle); + +// Set the chart legend +$legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false); + +$title = new Title('Test Scatter 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 +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('A7'); +$chart->setBottomRightPosition('H20'); + +// 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); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/samples/Chart/33_Chart_create_scatter5_trendlines.php b/samples/Chart/33_Chart_create_scatter5_trendlines.php new file mode 100644 index 00000000..dcee3e14 --- /dev/null +++ b/samples/Chart/33_Chart_create_scatter5_trendlines.php @@ -0,0 +1,274 @@ +getActiveSheet(); +$dataSheet->setTitle('Data'); +// changed data to simulate a trend chart - Xaxis are dates; Yaxis are 3 meausurements from each date +$dataSheet->fromArray( + [ + ['', 'metric1', 'metric2', 'metric3'], + ['=DATEVALUE("2021-01-01")', 12.1, 15.1, 21.1], + ['=DATEVALUE("2021-04-01")', 56.2, 73.2, 86.2], + ['=DATEVALUE("2021-07-01")', 52.2, 61.2, 69.2], + ['=DATEVALUE("2021-10-01")', 30.2, 22.2, 0.2], + ['=DATEVALUE("2022-01-01")', 40.1, 38.1, 65.1], + ['=DATEVALUE("2022-04-01")', 45.2, 44.2, 96.2], + ['=DATEVALUE("2022-07-01")', 52.2, 51.2, 55.2], + ['=DATEVALUE("2022-10-01")', 41.2, 72.2, 56.2], + ] +); + +$dataSheet->getStyle('A2:A9')->getNumberFormat()->setFormatCode(Properties::FORMAT_CODE_DATE_ISO8601); +$dataSheet->getColumnDimension('A')->setAutoSize(true); +$dataSheet->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 +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Data!$B$1', null, 1), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Data!$C$1', null, 1), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Data!$D$1', null, 1), +]; +// Set the X-Axis Labels +// NUMBER, not STRING +// added x-axis values for each of the 3 metrics +// added FORMATE_CODE_NUMBER +// Number of datapoints in series +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$A$2:$A$9', Properties::FORMAT_CODE_DATE_ISO8601, 8), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$A$2:$A$9', Properties::FORMAT_CODE_DATE_ISO8601, 8), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$A$2:$A$9', Properties::FORMAT_CODE_DATE_ISO8601, 8), +]; +// 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 +// Data Marker Color fill/[fill,Border] +// Data Marker size +// Color(s) added +// added FORMAT_CODE_NUMBER +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$B$2:$B$9', Properties::FORMAT_CODE_NUMBER, 8, null, 'diamond', null, 5), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$C$2:$C$9', Properties::FORMAT_CODE_NUMBER, 8, null, 'square', '*accent1', 6), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$D$2:$D$9', Properties::FORMAT_CODE_NUMBER, 8, null, null, null, 7), // let Excel choose marker shape +]; +// series 1 - metric1 +// marker details +$dataSeriesValues[0] + ->getMarkerFillColor() + ->setColorProperties('0070C0', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[0] + ->getMarkerBorderColor() + ->setColorProperties('002060', null, ChartColor::EXCEL_COLOR_TYPE_RGB); + +// line details - dashed, smooth line (Bezier) with arrows, 40% transparent +$dataSeriesValues[0] + ->setSmoothLine(true) + ->setScatterLines(true) + ->setLineColorProperties('accent1', 40, ChartColor::EXCEL_COLOR_TYPE_SCHEME); // value, alpha, type +$dataSeriesValues[0]->setLineStyleProperties( + 2.5, // width in points + Properties::LINE_STYLE_COMPOUND_TRIPLE, // compound + Properties::LINE_STYLE_DASH_SQUARE_DOT, // dash + Properties::LINE_STYLE_CAP_SQUARE, // cap + Properties::LINE_STYLE_JOIN_MITER, // join + Properties::LINE_STYLE_ARROW_TYPE_OPEN, // head type + Properties::LINE_STYLE_ARROW_SIZE_4, // head size preset index + Properties::LINE_STYLE_ARROW_TYPE_ARROW, // end type + Properties::LINE_STYLE_ARROW_SIZE_6 // end size preset index +); + +// series 2 - metric2, straight line - no special effects, connected +$dataSeriesValues[1] // square marker border color + ->getMarkerBorderColor() + ->setColorProperties('accent6', 3, ChartColor::EXCEL_COLOR_TYPE_SCHEME); +$dataSeriesValues[1] // square marker fill color + ->getMarkerFillColor() + ->setColorProperties('0FFF00', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[1] + ->setScatterLines(true) + ->setSmoothLine(false) + ->setLineColorProperties('FF0000', 80, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[1]->setLineWidth(2.0); + +// series 3 - metric3, markers, no line +$dataSeriesValues[2] // triangle? fill + //->setPointMarker('triangle') // let Excel choose shape, which is predicted to be a triangle + ->getMarkerFillColor() + ->setColorProperties('FFFF00', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[2] // triangle border + ->getMarkerBorderColor() + ->setColorProperties('accent4', null, ChartColor::EXCEL_COLOR_TYPE_SCHEME); +$dataSeriesValues[2]->setScatterLines(false); // points not connected +// Added so that Xaxis shows dates instead of Excel-equivalent-year1900-numbers +$xAxis = new ChartAxis(); +$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 grouping) + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues, // plotValues + null, // plotDirection + null, // smooth line + DataSeries::STYLE_SMOOTHMARKER // 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 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 + // $yAxis, // yAxis +); + +// Set the position of the chart in the chart sheet +$chart->setTopLeftPosition('A1'); +$chart->setBottomRightPosition('P12'); + +// create a 'Chart' worksheet, add $chart to it +$spreadsheet->createSheet(); +$chartSheet = $spreadsheet->getSheet(1); +$chartSheet->setTitle('Scatter Chart'); + +$chartSheet = $spreadsheet->getSheetByName('Scatter Chart'); +// Add the chart to the worksheet +$chartSheet->addChart($chart); + +// ------------ Demonstrate Trendlines for metric3 values in a new chart ------------ + +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Data!$D$1', null, 1), +]; +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$A$2:$A$9', Properties::FORMAT_CODE_DATE_ISO8601, 8), +]; + +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Data!$D$2:$D$9', Properties::FORMAT_CODE_NUMBER, 4, null, 'triangle', null, 7), +]; + +// add 3 trendlines: +// 1- linear, double-ended arrow, w=0.5, same color as marker fill; nodispRSqr, nodispEq +// 2- polynomial (order=3) no-arrow trendline, w=1.25, same color as marker fill; dispRSqr, dispEq +// 3- moving Avg (period=2) single-arrow trendline, w=1.5, same color as marker fill; no dispRSqr, no dispEq +$trendLines = [ + new TrendLine(TrendLine::TRENDLINE_LINEAR, null, null, false, false), + new TrendLine(TrendLine::TRENDLINE_POLYNOMIAL, 3, null, true, true, 20.0, 28.0, 44104.5, 'metric3 polynomial fit'), + new TrendLine(TrendLine::TRENDLINE_MOVING_AVG, null, 2, true), +]; +$dataSeriesValues[0]->setTrendLines($trendLines); + +// Suppress connecting lines; instead, add different Trendline algorithms to +// determine how well the data fits the algorithm (Rsquared="goodness of fit") +// Display RSqr plus the eqn just because we can. + +$dataSeriesValues[0]->setScatterLines(false); // points not connected +$dataSeriesValues[0]->getMarkerFillColor() + ->setColorProperties('FFFF00', null, ChartColor::EXCEL_COLOR_TYPE_RGB); +$dataSeriesValues[0]->getMarkerBorderColor() + ->setColorProperties('accent4', null, ChartColor::EXCEL_COLOR_TYPE_SCHEME); + +// add properties to the trendLines - give each a different color +$dataSeriesValues[0]->getTrendLines()[0]->getLineColor()->setColorProperties('accent4', null, ChartColor::EXCEL_COLOR_TYPE_SCHEME); +$dataSeriesValues[0]->getTrendLines()[0]->setLineStyleProperties(0.5, null, null, null, null, Properties::LINE_STYLE_ARROW_TYPE_STEALTH, 5, Properties::LINE_STYLE_ARROW_TYPE_OPEN, 8); + +$dataSeriesValues[0]->getTrendLines()[1]->getLineColor()->setColorProperties('accent3', null, ChartColor::EXCEL_COLOR_TYPE_SCHEME); +$dataSeriesValues[0]->getTrendLines()[1]->setLineStyleProperties(1.25); + +$dataSeriesValues[0]->getTrendLines()[2]->getLineColor()->setColorProperties('accent2', null, ChartColor::EXCEL_COLOR_TYPE_SCHEME); +$dataSeriesValues[0]->getTrendLines()[2]->setLineStyleProperties(1.5, null, null, null, null, null, null, Properties::LINE_STYLE_ARROW_TYPE_OPEN, 8); + +$xAxis = new ChartAxis(); +$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601); // m/d/yyyy + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_SCATTERCHART, // plotType + null, // plotGrouping (Scatter charts don't have grouping) + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues, // plotValues + null, // plotDirection + null, // smooth line + DataSeries::STYLE_SMOOTHMARKER // 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 Chart - trendlines for metric3 values'); +$yAxisLabel = new Title('Value ($k)'); + +// Create the chart +$chart = new Chart( + 'chart2', // 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 + // $yAxis, // yAxis +); + +// Set the position of the chart in the chart sheet below the first chart +$chart->setTopLeftPosition('A13'); +$chart->setBottomRightPosition('P25'); + +// Add the chart to the worksheet $chartSheet +$chartSheet->addChart($chart); +$spreadsheet->setActiveSheetIndex(1); + +// 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/35_Chart_render.php b/samples/Chart/35_Chart_render.php index ebab16a7..f6dfeb46 100644 --- a/samples/Chart/35_Chart_render.php +++ b/samples/Chart/35_Chart_render.php @@ -5,16 +5,13 @@ use PhpOffice\PhpSpreadsheet\Settings; require __DIR__ . '/../Header.php'; -if (PHP_VERSION_ID >= 80000) { - $helper->log('Jpgraph no longer runs against PHP8'); - exit; -} - // Change these values to select the Rendering library that you wish to use -Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class); +//Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class); +Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class); $inputFileType = 'Xlsx'; $inputFileNames = __DIR__ . '/../templates/32readwrite*[0-9].xlsx'; +//$inputFileNames = __DIR__ . '/../templates/32readwriteStockChart5.xlsx'; if ((isset($argc)) && ($argc > 1)) { $inputFileNames = []; @@ -24,6 +21,16 @@ if ((isset($argc)) && ($argc > 1)) { } else { $inputFileNames = glob($inputFileNames); } +if (count($inputFileNames) === 1) { + $unresolvedErrors = []; +} else { + $unresolvedErrors = [ + // The following spreadsheet was created by 3rd party software, + // and doesn't include the data that usually accompanies a chart. + // That is good enough for Excel, but not for JpGraph. + '32readwriteBubbleChart2.xlsx', + ]; +} foreach ($inputFileNames as $inputFileName) { $inputFileNameShort = basename($inputFileName); @@ -32,6 +39,13 @@ foreach ($inputFileNames as $inputFileName) { continue; } + if (in_array($inputFileNameShort, $unresolvedErrors, true)) { + $helper->log('*****'); + $helper->log('***** File ' . $inputFileNameShort . ' does not yet work with this script'); + $helper->log('*****'); + + continue; + } $helper->log("Load Test from $inputFileType file " . $inputFileNameShort); @@ -59,6 +73,9 @@ foreach ($inputFileNames as $inputFileName) { $helper->log(' ' . $chartName . ' - ' . $caption); $jpegFile = $helper->getFilename('35-' . $inputFileNameShort, 'png'); + if ($i !== 0) { + $jpegFile = substr($jpegFile, 0, -3) . "$i.png"; + } if (file_exists($jpegFile)) { unlink($jpegFile); } @@ -75,6 +92,7 @@ foreach ($inputFileNames as $inputFileName) { $spreadsheet->disconnectWorksheets(); unset($spreadsheet); + gc_collect_cycles(); } $helper->log('Done rendering charts as images'); diff --git a/samples/Pdf/21a_Pdf.php b/samples/Pdf/21a_Pdf.php index b5572afe..33b61c9f 100644 --- a/samples/Pdf/21a_Pdf.php +++ b/samples/Pdf/21a_Pdf.php @@ -12,6 +12,8 @@ $spreadsheet->getActiveSheet()->setShowGridLines(false); $helper->log('Set orientation to landscape'); $spreadsheet->getActiveSheet()->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); $spreadsheet->setActiveSheetIndex(0)->setPrintGridlines(true); +// Issue 2299 - mpdf can't handle hide rows without kludge +$spreadsheet->getActiveSheet()->getRowDimension(2)->setVisible(false); function changeGridlines(string $html): string { diff --git a/samples/Pdf/21b_Pdf.php b/samples/Pdf/21b_Pdf.php index ad2f609b..c67ff3d2 100644 --- a/samples/Pdf/21b_Pdf.php +++ b/samples/Pdf/21b_Pdf.php @@ -32,13 +32,11 @@ $spreadsheet->getActiveSheet()->setShowGridLines(false); $helper->log('Set orientation to landscape'); $spreadsheet->getActiveSheet()->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); -if (\PHP_VERSION_ID < 80000) { - $helper->log('Write to Dompdf'); - $writer = new Dompdf($spreadsheet); - $filename = $helper->getFileName('21b_Pdf_dompdf.xlsx', 'pdf'); - $writer->setEditHtmlCallback('replaceBody'); - $writer->save($filename); -} +$helper->log('Write to Dompdf'); +$writer = new Dompdf($spreadsheet); +$filename = $helper->getFileName('21b_Pdf_dompdf.xlsx', 'pdf'); +$writer->setEditHtmlCallback('replaceBody'); +$writer->save($filename); $helper->log('Write to Mpdf'); $writer = new Mpdf($spreadsheet); @@ -46,10 +44,8 @@ $filename = $helper->getFileName('21b_Pdf_mpdf.xlsx', 'pdf'); $writer->setEditHtmlCallback('replaceBody'); $writer->save($filename); -if (\PHP_VERSION_ID < 80000) { - $helper->log('Write to Tcpdf'); - $writer = new Tcpdf($spreadsheet); - $filename = $helper->getFileName('21b_Pdf_tcpdf.xlsx', 'pdf'); - $writer->setEditHtmlCallback('replaceBody'); - $writer->save($filename); -} +$helper->log('Write to Tcpdf'); +$writer = new Tcpdf($spreadsheet); +$filename = $helper->getFileName('21b_Pdf_tcpdf.xlsx', 'pdf'); +$writer->setEditHtmlCallback('replaceBody'); +$writer->save($filename); diff --git a/samples/Reader/16_Handling_loader_exceptions_using_TryCatch.php b/samples/Reader/16_Handling_loader_exceptions_using_TryCatch.php index 603f6cb8..d984b7b6 100644 --- a/samples/Reader/16_Handling_loader_exceptions_using_TryCatch.php +++ b/samples/Reader/16_Handling_loader_exceptions_using_TryCatch.php @@ -11,5 +11,5 @@ $helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFi try { $spreadsheet = IOFactory::load($inputFileName); } catch (ReaderException $e) { - $helper->log('Error loading file "' . pathinfo($inputFileName, PATHINFO_BASENAME) . '": ' . $e->getMessage()); + $helper->log('Error loading file "' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . '": ' . $e->getMessage()); } diff --git a/samples/templates/32readwriteAreaChart1.xlsx b/samples/templates/32readwriteAreaChart1.xlsx index d44ae534..474affbe 100644 Binary files a/samples/templates/32readwriteAreaChart1.xlsx and b/samples/templates/32readwriteAreaChart1.xlsx differ diff --git a/samples/templates/32readwriteLineDateAxisChart1.xlsx b/samples/templates/32readwriteLineDateAxisChart1.xlsx new file mode 100644 index 00000000..42470303 Binary files /dev/null and b/samples/templates/32readwriteLineDateAxisChart1.xlsx differ diff --git a/samples/templates/32readwriteScatterChart10.xlsx b/samples/templates/32readwriteScatterChart10.xlsx new file mode 100644 index 00000000..7d5ac0d2 Binary files /dev/null and b/samples/templates/32readwriteScatterChart10.xlsx differ diff --git a/samples/templates/32readwriteScatterChart9.xlsx b/samples/templates/32readwriteScatterChart9.xlsx new file mode 100644 index 00000000..3a0b51b2 Binary files /dev/null and b/samples/templates/32readwriteScatterChart9.xlsx differ diff --git a/samples/templates/32readwriteScatterChartTrendlines1.xlsx b/samples/templates/32readwriteScatterChartTrendlines1.xlsx new file mode 100644 index 00000000..c8411d4d Binary files /dev/null and b/samples/templates/32readwriteScatterChartTrendlines1.xlsx differ diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 5b1c5520..94eadd85 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -7,7 +7,6 @@ use PhpOffice\PhpSpreadsheet\Calculation\Engine\CyclicReferenceStack; use PhpOffice\PhpSpreadsheet\Calculation\Engine\Logger; use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue; use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; -use PhpOffice\PhpSpreadsheet\Calculation\Information\Value; use PhpOffice\PhpSpreadsheet\Calculation\Token\Stack; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; @@ -305,8 +304,8 @@ class Calculation ], 'ARRAYTOTEXT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [Functions::class, 'DUMMY'], - 'argumentCount' => '?', + 'functionCall' => [TextData\Text::class, 'fromArray'], + 'argumentCount' => '1,2', ], 'ASC' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, @@ -2490,13 +2489,13 @@ class Calculation ], 'TEXTAFTER' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [Functions::class, 'DUMMY'], - 'argumentCount' => '2-4', + 'functionCall' => [TextData\Extract::class, 'after'], + 'argumentCount' => '2-6', ], 'TEXTBEFORE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [Functions::class, 'DUMMY'], - 'argumentCount' => '2-4', + 'functionCall' => [TextData\Extract::class, 'before'], + 'argumentCount' => '2-6', ], 'TEXTJOIN' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, @@ -2505,8 +2504,8 @@ class Calculation ], 'TEXTSPLIT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [Functions::class, 'DUMMY'], - 'argumentCount' => '2-5', + 'functionCall' => [TextData\Text::class, 'split'], + 'argumentCount' => '2-6', ], 'THAIDAYOFWEEK' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, @@ -2660,8 +2659,8 @@ class Calculation ], 'VALUETOTEXT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [Functions::class, 'DUMMY'], - 'argumentCount' => '?', + 'functionCall' => [TextData\Format::class, 'valueToText'], + 'argumentCount' => '1,2', ], 'VAR' => [ 'category' => Category::CATEGORY_STATISTICAL, @@ -3111,7 +3110,7 @@ class Calculation [$localeFunction] = explode('##', $localeFunction); // Strip out comments if (strpos($localeFunction, '=') !== false) { [$fName, $lfName] = array_map('trim', explode('=', $localeFunction)); - if ((isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) { + if ((substr($fName, 0, 1) === '*' || isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) { self::$localeFunctions[$fName] = $lfName; } } @@ -4757,9 +4756,8 @@ class Calculation break; } - - // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on } elseif (($token === '~') || ($token === '%')) { + // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on if (($arg = $stack->pop()) === null) { return $this->raiseFormulaError('Internal error - Operand value missing from stack'); } @@ -4866,9 +4864,8 @@ class Calculation if (isset($storeKey)) { $branchStore[$storeKey] = $cellValue; } - - // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $token ?? '', $matches)) { + // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on if ($pCellParent) { $cell->attach($pCellParent); } @@ -4978,8 +4975,8 @@ class Calculation if (isset($storeKey)) { $branchStore[$storeKey] = $token; } - // if the token is a named range or formula, evaluate it and push the result onto the stack } elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token, $matches)) { + // if the token is a named range or formula, evaluate it and push the result onto the stack $definedName = $matches[6]; if ($cell === null || $pCellWorksheet === null) { return $this->raiseFormulaError("undefined name '$token'"); diff --git a/src/PhpSpreadsheet/Calculation/Engine/Logger.php b/src/PhpSpreadsheet/Calculation/Engine/Logger.php index c6ee5969..256c3eff 100644 --- a/src/PhpSpreadsheet/Calculation/Engine/Logger.php +++ b/src/PhpSpreadsheet/Calculation/Engine/Logger.php @@ -98,9 +98,9 @@ class Logger $cellReference = implode(' -> ', $this->cellStack->showStack()); if ($this->echoDebugLog) { echo $cellReference, - ($this->cellStack->count() > 0 ? ' => ' : ''), - $message, - PHP_EOL; + ($this->cellStack->count() > 0 ? ' => ' : ''), + $message, + PHP_EOL; } $this->debugLog[] = $cellReference . ($this->cellStack->count() > 0 ? ' => ' : '') . diff --git a/src/PhpSpreadsheet/Calculation/Engineering/Compare.php b/src/PhpSpreadsheet/Calculation/Engineering/Compare.php index 4d4bc07e..6aaf1faa 100644 --- a/src/PhpSpreadsheet/Calculation/Engineering/Compare.php +++ b/src/PhpSpreadsheet/Calculation/Engineering/Compare.php @@ -57,7 +57,7 @@ class Compare * * @param array|float $number the value to test against step * Or can be an array of values - * @param array|float $step The threshold value. If you omit a value for step, GESTEP uses zero. + * @param null|array|float $step The threshold value. If you omit a value for step, GESTEP uses zero. * Or can be an array of values * * @return array|int|string (string in the event of an error) @@ -72,7 +72,7 @@ class Compare try { $number = EngineeringValidations::validateFloat($number); - $step = EngineeringValidations::validateFloat($step); + $step = EngineeringValidations::validateFloat($step ?? 0.0); } catch (Exception $e) { return $e->getMessage(); } diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php b/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php index 677fb0fb..b7c298db 100644 --- a/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php +++ b/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php @@ -106,6 +106,7 @@ class ConvertUOM 'W' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true], 'w' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true], 'PS' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Pferdestärke', 'AllowPrefix' => false], + // Magnetism 'T' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Tesla', 'AllowPrefix' => true], 'ga' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Gauss', 'AllowPrefix' => true], // Temperature diff --git a/src/PhpSpreadsheet/Calculation/FormulaParser.php b/src/PhpSpreadsheet/Calculation/FormulaParser.php index ddf45b23..f71d96fc 100644 --- a/src/PhpSpreadsheet/Calculation/FormulaParser.php +++ b/src/PhpSpreadsheet/Calculation/FormulaParser.php @@ -61,7 +61,7 @@ class FormulaParser /** * Create a new FormulaParser. * - * @param string $formula Formula to parse + * @param ?string $formula Formula to parse */ public function __construct($formula = '') { diff --git a/src/PhpSpreadsheet/Calculation/Functions.php b/src/PhpSpreadsheet/Calculation/Functions.php index ddd3e200..172f2022 100644 --- a/src/PhpSpreadsheet/Calculation/Functions.php +++ b/src/PhpSpreadsheet/Calculation/Functions.php @@ -573,24 +573,20 @@ class Functions return (array) $array; } - $arrayValues = []; - foreach ($array as $value) { + $flattened = []; + $stack = array_values($array); + + while ($stack) { + $value = array_shift($stack); + if (is_array($value)) { - foreach ($value as $val) { - if (is_array($val)) { - foreach ($val as $v) { - $arrayValues[] = $v; - } - } else { - $arrayValues[] = $val; - } - } + array_unshift($stack, ...array_values($value)); } else { - $arrayValues[] = $value; + $flattened[] = $value; } } - return $arrayValues; + return $flattened; } /** @@ -691,7 +687,7 @@ class Functions $worksheet2 = $defined->getWorkSheet(); if (!$defined->isFormula() && $worksheet2 !== null) { $coordinate = "'" . $worksheet2->getTitle() . "'!" . - (string) preg_replace('/^=/', '', $defined->getValue()); + (string) preg_replace('/^=/', '', str_replace('$', '', $defined->getValue())); } } diff --git a/src/PhpSpreadsheet/Calculation/Information/Value.php b/src/PhpSpreadsheet/Calculation/Information/Value.php index 0ac6b669..2e524db5 100644 --- a/src/PhpSpreadsheet/Calculation/Information/Value.php +++ b/src/PhpSpreadsheet/Calculation/Information/Value.php @@ -240,7 +240,7 @@ class Value * * @param null|mixed $value The value you want converted * - * @return number N converts values listed in the following table + * @return number|string N converts values listed in the following table * If value is or refers to N returns * A number That number value * A date The Excel serialized number of that date diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Address.php b/src/PhpSpreadsheet/Calculation/LookupRef/Address.php index c8cdf2dd..0d2db8b2 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef/Address.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Address.php @@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef; use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled; use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; +use PhpOffice\PhpSpreadsheet\Cell\AddressHelper; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; class Address @@ -72,6 +73,9 @@ class Address $sheetName = self::sheetName($sheetName); + if (is_int($referenceStyle)) { + $referenceStyle = (bool) $referenceStyle; + } if ((!is_bool($referenceStyle)) || $referenceStyle === self::REFERENCE_STYLE_A1) { return self::formatAsA1($row, $column, $relativity, $sheetName); } @@ -113,7 +117,8 @@ class Address if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { $row = "[{$row}]"; } + [$rowChar, $colChar] = AddressHelper::getRowAndColumnChars(); - return "{$sheetName}R{$row}C{$column}"; + return "{$sheetName}$rowChar{$row}$colChar{$column}"; } } diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php b/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php index 7408a66e..76a194b3 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php @@ -13,12 +13,12 @@ class Helpers public const CELLADDRESS_USE_R1C1 = false; - private static function convertR1C1(string &$cellAddress1, ?string &$cellAddress2, bool $a1): string + private static function convertR1C1(string &$cellAddress1, ?string &$cellAddress2, bool $a1, ?int $baseRow = null, ?int $baseCol = null): string { if ($a1 === self::CELLADDRESS_USE_R1C1) { - $cellAddress1 = AddressHelper::convertToA1($cellAddress1); + $cellAddress1 = AddressHelper::convertToA1($cellAddress1, $baseRow ?? 1, $baseCol ?? 1); if ($cellAddress2) { - $cellAddress2 = AddressHelper::convertToA1($cellAddress2); + $cellAddress2 = AddressHelper::convertToA1($cellAddress2, $baseRow ?? 1, $baseCol ?? 1); } } @@ -35,7 +35,7 @@ class Helpers } } - public static function extractCellAddresses(string $cellAddress, bool $a1, Worksheet $sheet, string $sheetName = ''): array + public static function extractCellAddresses(string $cellAddress, bool $a1, Worksheet $sheet, string $sheetName = '', ?int $baseRow = null, ?int $baseCol = null): array { $cellAddress1 = $cellAddress; $cellAddress2 = null; @@ -52,7 +52,7 @@ class Helpers if (strpos($cellAddress, ':') !== false) { [$cellAddress1, $cellAddress2] = explode(':', $cellAddress); } - $cellAddress = self::convertR1C1($cellAddress1, $cellAddress2, $a1); + $cellAddress = self::convertR1C1($cellAddress1, $cellAddress2, $a1, $baseRow, $baseCol); return [$cellAddress1, $cellAddress2, $cellAddress]; } diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php b/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php index 417a1f79..91a14491 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php @@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; use PhpOffice\PhpSpreadsheet\Cell\Cell; +use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class Indirect @@ -63,6 +64,8 @@ class Indirect */ public static function INDIRECT($cellAddress, $a1fmt, Cell $cell) { + [$baseCol, $baseRow] = Coordinate::indexesFromString($cell->getCoordinate()); + try { $a1 = self::a1Format($a1fmt); $cellAddress = self::validateAddress($cellAddress); @@ -78,7 +81,11 @@ class Indirect $cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress)); } - [$cellAddress1, $cellAddress2, $cellAddress] = Helpers::extractCellAddresses($cellAddress, $a1, $cell->getWorkSheet(), $sheetName); + try { + [$cellAddress1, $cellAddress2, $cellAddress] = Helpers::extractCellAddresses($cellAddress, $a1, $cell->getWorkSheet(), $sheetName, $baseRow, $baseCol); + } catch (Exception $e) { + return ExcelError::REF(); + } if ( (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress1, $matches)) || diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php b/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php index 9f3377f6..02a25581 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php @@ -99,6 +99,8 @@ class Offset private static function extractWorksheet($cellAddress, Cell $cell): array { + $cellAddress = self::assessCellAddress($cellAddress, $cell); + $sheetName = ''; if (strpos($cellAddress, '!') !== false) { [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); @@ -112,6 +114,15 @@ class Offset return [$cellAddress, $worksheet]; } + private static function assessCellAddress(string $cellAddress, Cell $cell): string + { + if (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $cellAddress) !== false) { + $cellAddress = Functions::expandDefinedName($cellAddress, $cell); + } + + return $cellAddress; + } + private static function adjustEndCellColumnForWidth(string $endCellColumn, $width, int $startCellColumn, $columns) { $endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1; diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Random.php b/src/PhpSpreadsheet/Calculation/MathTrig/Random.php index b9fcfc73..22cad2cf 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Random.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Random.php @@ -17,7 +17,7 @@ class Random */ public static function rand() { - return (mt_rand(0, 10000000)) / 10000000; + return mt_rand(0, 10000000) / 10000000; } /** diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Averages.php b/src/PhpSpreadsheet/Calculation/Statistical/Averages.php index 41b011a5..85195c88 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Averages.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Averages.php @@ -203,7 +203,7 @@ class Averages extends AggregateBase $args, function ($value) { // Is it a numeric value? - return (is_numeric($value)) && (!is_string($value)); + return is_numeric($value) && (!is_string($value)); } ); } diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php index 8574d58d..c8743364 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php @@ -101,7 +101,7 @@ class ChiSquared return 1 - self::distributionRightTail($value, $degrees); } - return (($value ** (($degrees / 2) - 1) * exp(-$value / 2))) / + return ($value ** (($degrees / 2) - 1) * exp(-$value / 2)) / ((2 ** ($degrees / 2)) * Gamma::gammaValue($degrees / 2)); } diff --git a/src/PhpSpreadsheet/Calculation/TextData/Extract.php b/src/PhpSpreadsheet/Calculation/TextData/Extract.php index d29f80ca..ee7e31b7 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Extract.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Extract.php @@ -4,6 +4,9 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled; use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; +use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; +use PhpOffice\PhpSpreadsheet\Shared\StringHelper; class Extract { @@ -95,4 +98,183 @@ class Extract return mb_substr($value ?? '', mb_strlen($value ?? '', 'UTF-8') - $chars, $chars, 'UTF-8'); } + + /** + * TEXTBEFORE. + * + * @param mixed $text the text that you're searching + * Or can be an array of values + * @param null|array|string $delimiter the text that marks the point before which you want to extract + * Multiple delimiters can be passed as an array of string values + * @param mixed $instance The instance of the delimiter after which you want to extract the text. + * By default, this is the first instance (1). + * A negative value means start searching from the end of the text string. + * Or can be an array of values + * @param mixed $matchMode Determines whether the match is case-sensitive or not. + * 0 - Case-sensitive + * 1 - Case-insensitive + * Or can be an array of values + * @param mixed $matchEnd Treats the end of text as a delimiter. + * 0 - Don't match the delimiter against the end of the text. + * 1 - Match the delimiter against the end of the text. + * Or can be an array of values + * @param mixed $ifNotFound value to return if no match is found + * The default is a #N/A Error + * Or can be an array of values + * + * @return mixed|mixed[] the string extracted from text before the delimiter; or the $ifNotFound value + * If an array of values is passed for any of the arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function before($text, $delimiter, $instance = 1, $matchMode = 0, $matchEnd = 0, $ifNotFound = '#N/A') + { + if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) { + return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); + } + + $text = Helpers::extractString($text ?? ''); + $instance = (int) $instance; + $matchMode = (int) $matchMode; + $matchEnd = (int) $matchEnd; + + $split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); + if (is_array($split) === false) { + return $split; + } + if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') { + return ($instance > 0) ? '' : $text; + } + + // Adjustment for a match as the first element of the split + $flags = self::matchFlags($matchMode); + $delimiter = self::buildDelimiter($delimiter); + $adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]); + $oddReverseAdjustment = count($split) % 2; + + $split = ($instance < 0) + ? array_slice($split, 0, max(count($split) - (abs($instance) * 2 - 1) - $adjust - $oddReverseAdjustment, 0)) + : array_slice($split, 0, $instance * 2 - 1 - $adjust); + + return implode('', $split); + } + + /** + * TEXTAFTER. + * + * @param mixed $text the text that you're searching + * @param null|array|string $delimiter the text that marks the point before which you want to extract + * Multiple delimiters can be passed as an array of string values + * @param mixed $instance The instance of the delimiter after which you want to extract the text. + * By default, this is the first instance (1). + * A negative value means start searching from the end of the text string. + * Or can be an array of values + * @param mixed $matchMode Determines whether the match is case-sensitive or not. + * 0 - Case-sensitive + * 1 - Case-insensitive + * Or can be an array of values + * @param mixed $matchEnd Treats the end of text as a delimiter. + * 0 - Don't match the delimiter against the end of the text. + * 1 - Match the delimiter against the end of the text. + * Or can be an array of values + * @param mixed $ifNotFound value to return if no match is found + * The default is a #N/A Error + * Or can be an array of values + * + * @return mixed|mixed[] the string extracted from text before the delimiter; or the $ifNotFound value + * If an array of values is passed for any of the arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function after($text, $delimiter, $instance = 1, $matchMode = 0, $matchEnd = 0, $ifNotFound = '#N/A') + { + if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) { + return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); + } + + $text = Helpers::extractString($text ?? ''); + $instance = (int) $instance; + $matchMode = (int) $matchMode; + $matchEnd = (int) $matchEnd; + + $split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); + if (is_array($split) === false) { + return $split; + } + if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') { + return ($instance < 0) ? '' : $text; + } + + // Adjustment for a match as the first element of the split + $flags = self::matchFlags($matchMode); + $delimiter = self::buildDelimiter($delimiter); + $adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]); + $oddReverseAdjustment = count($split) % 2; + + $split = ($instance < 0) + ? array_slice($split, count($split) - (abs($instance + 1) * 2) - $adjust - $oddReverseAdjustment) + : array_slice($split, $instance * 2 - $adjust); + + return implode('', $split); + } + + /** + * @param null|array|string $delimiter + * @param int $matchMode + * @param int $matchEnd + * @param mixed $ifNotFound + * + * @return string|string[] + */ + private static function validateTextBeforeAfter(string $text, $delimiter, int $instance, $matchMode, $matchEnd, $ifNotFound) + { + $flags = self::matchFlags($matchMode); + $delimiter = self::buildDelimiter($delimiter); + + if (preg_match('/' . $delimiter . "/{$flags}", $text) === 0 && $matchEnd === 0) { + return $ifNotFound; + } + + $split = preg_split('/' . $delimiter . "/{$flags}", $text, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + if ($split === false) { + return ExcelError::NA(); + } + + if ($instance === 0 || abs($instance) > StringHelper::countCharacters($text)) { + return ExcelError::VALUE(); + } + + if ($matchEnd === 0 && (abs($instance) > floor(count($split) / 2))) { + return ExcelError::NA(); + } elseif ($matchEnd !== 0 && (abs($instance) - 1 > ceil(count($split) / 2))) { + return ExcelError::NA(); + } + + return $split; + } + + /** + * @param null|array|string $delimiter the text that marks the point before which you want to extract + * Multiple delimiters can be passed as an array of string values + */ + private static function buildDelimiter($delimiter): string + { + if (is_array($delimiter)) { + $delimiter = Functions::flattenArray($delimiter); + $quotedDelimiters = array_map( + function ($delimiter) { + return preg_quote($delimiter ?? ''); + }, + $delimiter + ); + $delimiters = implode('|', $quotedDelimiters); + + return '(' . $delimiters . ')'; + } + + return '(' . preg_quote($delimiter ?? '') . ')'; + } + + private static function matchFlags(int $matchMode): string + { + return ($matchMode === 0) ? 'mu' : 'miu'; + } } diff --git a/src/PhpSpreadsheet/Calculation/TextData/Format.php b/src/PhpSpreadsheet/Calculation/TextData/Format.php index bec11496..03e75d1d 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Format.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Format.php @@ -4,11 +4,13 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; use DateTimeInterface; use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled; +use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; +use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; @@ -208,6 +210,38 @@ class Format return (float) $value; } + /** + * TEXT. + * + * @param mixed $value The value to format + * Or can be an array of values + * @param mixed $format + * + * @return array|string + * If an array of values is passed for either of the arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function valueToText($value, $format = false) + { + if (is_array($value) || is_array($format)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format); + } + + $format = (bool) $format; + + if (is_object($value) && $value instanceof RichText) { + $value = $value->getPlainText(); + } + if (is_string($value)) { + $value = ($format === true) ? Calculation::wrapResult($value) : $value; + $value = str_replace("\n", '', $value); + } elseif (is_bool($value)) { + $value = Calculation::$localeBoolean[$value === true ? 'TRUE' : 'FALSE']; + } + + return (string) $value; + } + /** * @param mixed $decimalSeparator */ diff --git a/src/PhpSpreadsheet/Calculation/TextData/Text.php b/src/PhpSpreadsheet/Calculation/TextData/Text.php index 490c43c2..83810422 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Text.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Text.php @@ -3,6 +3,8 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled; +use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Calculation\Functions; class Text { @@ -77,4 +79,176 @@ class Text return null; } + + /** + * TEXTSPLIT. + * + * @param mixed $text the text that you're searching + * @param null|array|string $columnDelimiter The text that marks the point where to spill the text across columns. + * Multiple delimiters can be passed as an array of string values + * @param null|array|string $rowDelimiter The text that marks the point where to spill the text down rows. + * Multiple delimiters can be passed as an array of string values + * @param bool $ignoreEmpty Specify FALSE to create an empty cell when two delimiters are consecutive. + * true = create empty cells + * false = skip empty cells + * Defaults to TRUE, which creates an empty cell + * @param bool $matchMode Determines whether the match is case-sensitive or not. + * true = case-sensitive + * false = case-insensitive + * By default, a case-sensitive match is done. + * @param mixed $padding The value with which to pad the result. + * The default is #N/A. + * + * @return array the array built from the text, split by the row and column delimiters + */ + public static function split($text, $columnDelimiter = null, $rowDelimiter = null, bool $ignoreEmpty = false, bool $matchMode = true, $padding = '#N/A') + { + $text = Functions::flattenSingleValue($text); + + $flags = self::matchFlags($matchMode); + + if ($rowDelimiter !== null) { + $delimiter = self::buildDelimiter($rowDelimiter); + $rows = ($delimiter === '()') + ? [$text] + : preg_split("/{$delimiter}/{$flags}", $text); + } else { + $rows = [$text]; + } + + /** @var array $rows */ + if ($ignoreEmpty === true) { + $rows = array_values(array_filter( + $rows, + function ($row) { + return $row !== ''; + } + )); + } + + if ($columnDelimiter !== null) { + $delimiter = self::buildDelimiter($columnDelimiter); + array_walk( + $rows, + function (&$row) use ($delimiter, $flags, $ignoreEmpty): void { + $row = ($delimiter === '()') + ? [$row] + : preg_split("/{$delimiter}/{$flags}", $row); + /** @var array $row */ + if ($ignoreEmpty === true) { + $row = array_values(array_filter( + $row, + function ($value) { + return $value !== ''; + } + )); + } + } + ); + if ($ignoreEmpty === true) { + $rows = array_values(array_filter( + $rows, + function ($row) { + return $row !== [] && $row !== ['']; + } + )); + } + } + + return self::applyPadding($rows, $padding); + } + + /** + * @param mixed $padding + */ + private static function applyPadding(array $rows, $padding): array + { + $columnCount = array_reduce( + $rows, + function (int $counter, array $row): int { + return max($counter, count($row)); + }, + 0 + ); + + return array_map( + function (array $row) use ($columnCount, $padding): array { + return (count($row) < $columnCount) + ? array_merge($row, array_fill(0, $columnCount - count($row), $padding)) + : $row; + }, + $rows + ); + } + + /** + * @param null|array|string $delimiter the text that marks the point before which you want to split + * Multiple delimiters can be passed as an array of string values + */ + private static function buildDelimiter($delimiter): string + { + $valueSet = Functions::flattenArray($delimiter); + + if (is_array($delimiter) && count($valueSet) > 1) { + $quotedDelimiters = array_map( + function ($delimiter) { + return preg_quote($delimiter ?? ''); + }, + $valueSet + ); + $delimiters = implode('|', $quotedDelimiters); + + return '(' . $delimiters . ')'; + } + + return '(' . preg_quote(Functions::flattenSingleValue($delimiter)) . ')'; + } + + private static function matchFlags(bool $matchMode): string + { + return ($matchMode === true) ? 'miu' : 'mu'; + } + + public static function fromArray(array $array, int $format = 0): string + { + $result = []; + foreach ($array as $row) { + $cells = []; + foreach ($row as $cellValue) { + $value = ($format === 1) ? self::formatValueMode1($cellValue) : self::formatValueMode0($cellValue); + $cells[] = $value; + } + $result[] = implode(($format === 1) ? ',' : ', ', $cells); + } + + $result = implode(($format === 1) ? ';' : ', ', $result); + + return ($format === 1) ? '{' . $result . '}' : $result; + } + + /** + * @param mixed $cellValue + */ + private static function formatValueMode0($cellValue): string + { + if (is_bool($cellValue)) { + return ($cellValue) ? Calculation::$localeBoolean['TRUE'] : Calculation::$localeBoolean['FALSE']; + } + + return (string) $cellValue; + } + + /** + * @param mixed $cellValue + */ + private static function formatValueMode1($cellValue): string + { + if (is_string($cellValue) && Functions::isError($cellValue) === false) { + return Calculation::FORMULA_STRING_QUOTE . $cellValue . Calculation::FORMULA_STRING_QUOTE; + } elseif (is_bool($cellValue)) { + return ($cellValue) ? Calculation::$localeBoolean['TRUE'] : Calculation::$localeBoolean['FALSE']; + } + + return (string) $cellValue; + } } diff --git a/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx b/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx index d013aee0..7b9fb0dd 100644 Binary files a/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx and b/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx differ diff --git a/src/PhpSpreadsheet/Calculation/locale/da/functions b/src/PhpSpreadsheet/Calculation/locale/da/functions index 6260760b..03b68c9b 100644 --- a/src/PhpSpreadsheet/Calculation/locale/da/functions +++ b/src/PhpSpreadsheet/Calculation/locale/da/functions @@ -245,6 +245,7 @@ ROWS = RÆKKER RTD = RTD TRANSPOSE = TRANSPONER VLOOKUP = LOPSLAG +*RC = RK ## ## Matematiske og trigonometriske funktioner (Math & Trig Functions) diff --git a/src/PhpSpreadsheet/Calculation/locale/de/functions b/src/PhpSpreadsheet/Calculation/locale/de/functions index 331232f7..d49fc5f1 100644 --- a/src/PhpSpreadsheet/Calculation/locale/de/functions +++ b/src/PhpSpreadsheet/Calculation/locale/de/functions @@ -243,6 +243,7 @@ ROWS = ZEILEN RTD = RTD TRANSPOSE = MTRANS VLOOKUP = SVERWEIS +*RC = ZS ## ## Mathematische und trigonometrische Funktionen (Math & Trig Functions) diff --git a/src/PhpSpreadsheet/Calculation/locale/es/functions b/src/PhpSpreadsheet/Calculation/locale/es/functions index 1f9f2891..88012aa1 100644 --- a/src/PhpSpreadsheet/Calculation/locale/es/functions +++ b/src/PhpSpreadsheet/Calculation/locale/es/functions @@ -245,6 +245,7 @@ ROWS = FILAS RTD = RDTR TRANSPOSE = TRANSPONER VLOOKUP = BUSCARV +*RC = FC ## ## Funciones matemáticas y trigonométricas (Math & Trig Functions) diff --git a/src/PhpSpreadsheet/Calculation/locale/fi/functions b/src/PhpSpreadsheet/Calculation/locale/fi/functions index 33068d93..18f7c8c8 100644 --- a/src/PhpSpreadsheet/Calculation/locale/fi/functions +++ b/src/PhpSpreadsheet/Calculation/locale/fi/functions @@ -245,6 +245,7 @@ ROWS = RIVIT RTD = RTD TRANSPOSE = TRANSPONOI VLOOKUP = PHAKU +*RC = RS ## ## Matemaattiset ja trigonometriset funktiot (Math & Trig Functions) diff --git a/src/PhpSpreadsheet/Calculation/locale/fr/functions b/src/PhpSpreadsheet/Calculation/locale/fr/functions index 78b603e9..621cb0db 100644 --- a/src/PhpSpreadsheet/Calculation/locale/fr/functions +++ b/src/PhpSpreadsheet/Calculation/locale/fr/functions @@ -240,6 +240,7 @@ ROWS = LIGNES RTD = RTD TRANSPOSE = TRANSPOSE VLOOKUP = RECHERCHEV +*RC = LC ## ## Fonctions mathématiques et trigonométriques (Math & Trig Functions) diff --git a/src/PhpSpreadsheet/Calculation/locale/hu/functions b/src/PhpSpreadsheet/Calculation/locale/hu/functions index 46b30127..4a375ea2 100644 --- a/src/PhpSpreadsheet/Calculation/locale/hu/functions +++ b/src/PhpSpreadsheet/Calculation/locale/hu/functions @@ -245,6 +245,7 @@ ROWS = SOROK RTD = VIA TRANSPOSE = TRANSZPONÁLÁS VLOOKUP = FKERES +*RC = SO ## ## Matematikai és trigonometrikus függvények (Math & Trig Functions) diff --git a/src/PhpSpreadsheet/Calculation/locale/nb/functions b/src/PhpSpreadsheet/Calculation/locale/nb/functions index b0a0f949..d352e1f4 100644 --- a/src/PhpSpreadsheet/Calculation/locale/nb/functions +++ b/src/PhpSpreadsheet/Calculation/locale/nb/functions @@ -245,6 +245,7 @@ ROWS = RADER RTD = RTD TRANSPOSE = TRANSPONER VLOOKUP = FINN.RAD +*RC = RK ## ## Matematikk- og trigonometrifunksjoner (Math & Trig Functions) diff --git a/src/PhpSpreadsheet/Calculation/locale/nl/functions b/src/PhpSpreadsheet/Calculation/locale/nl/functions index 0e4f1597..ce0b30cc 100644 --- a/src/PhpSpreadsheet/Calculation/locale/nl/functions +++ b/src/PhpSpreadsheet/Calculation/locale/nl/functions @@ -244,6 +244,7 @@ ROWS = RIJEN RTD = RTG TRANSPOSE = TRANSPONEREN VLOOKUP = VERT.ZOEKEN +*RC = RK ## ## Wiskundige en trigonometrische functies (Math & Trig Functions) diff --git a/src/PhpSpreadsheet/Calculation/locale/pt/br/functions b/src/PhpSpreadsheet/Calculation/locale/pt/br/functions index feba30d9..5781b0c7 100644 --- a/src/PhpSpreadsheet/Calculation/locale/pt/br/functions +++ b/src/PhpSpreadsheet/Calculation/locale/pt/br/functions @@ -242,6 +242,7 @@ ROWS = LINS RTD = RTD TRANSPOSE = TRANSPOR VLOOKUP = PROCV +*RC = LC ## ## Funções matemáticas e trigonométricas (Math & Trig Functions) diff --git a/src/PhpSpreadsheet/Calculation/locale/pt/functions b/src/PhpSpreadsheet/Calculation/locale/pt/functions index 8a94d826..70a3bb0c 100644 --- a/src/PhpSpreadsheet/Calculation/locale/pt/functions +++ b/src/PhpSpreadsheet/Calculation/locale/pt/functions @@ -245,6 +245,7 @@ ROWS = LINS RTD = RTD TRANSPOSE = TRANSPOR VLOOKUP = PROCV +*RC = LC ## ## Funções matemáticas e trigonométricas (Math & Trig Functions) diff --git a/src/PhpSpreadsheet/Calculation/locale/sv/functions b/src/PhpSpreadsheet/Calculation/locale/sv/functions index 2531b4c1..491ecfb9 100644 --- a/src/PhpSpreadsheet/Calculation/locale/sv/functions +++ b/src/PhpSpreadsheet/Calculation/locale/sv/functions @@ -243,6 +243,7 @@ ROWS = RADER RTD = RTD TRANSPOSE = TRANSPONERA VLOOKUP = LETARAD +*RC = RK ## ## Matematiska och trigonometriska funktioner (Math & Trig Functions) diff --git a/src/PhpSpreadsheet/Cell/AddressHelper.php b/src/PhpSpreadsheet/Cell/AddressHelper.php index 535bdee0..a23a78b6 100644 --- a/src/PhpSpreadsheet/Cell/AddressHelper.php +++ b/src/PhpSpreadsheet/Cell/AddressHelper.php @@ -2,23 +2,44 @@ namespace PhpOffice\PhpSpreadsheet\Cell; +use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Exception; class AddressHelper { public const R1C1_COORDINATE_REGEX = '/(R((?:\[-?\d*\])|(?:\d*))?)(C((?:\[-?\d*\])|(?:\d*))?)/i'; + /** @return string[] */ + public static function getRowAndColumnChars() + { + $rowChar = 'R'; + $colChar = 'C'; + if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_EXCEL) { + $rowColChars = Calculation::localeFunc('*RC'); + if (mb_strlen($rowColChars) === 2) { + $rowChar = mb_substr($rowColChars, 0, 1); + $colChar = mb_substr($rowColChars, 1, 1); + } + } + + return [$rowChar, $colChar]; + } + /** * Converts an R1C1 format cell address to an A1 format cell address. */ public static function convertToA1( string $address, int $currentRowNumber = 1, - int $currentColumnNumber = 1 + int $currentColumnNumber = 1, + bool $useLocale = true ): string { - $validityCheck = preg_match('/^(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))$/i', $address, $cellReference); + [$rowChar, $colChar] = $useLocale ? self::getRowAndColumnChars() : ['R', 'C']; + $regex = '/^(' . $rowChar . '(\[?[-+]?\d*\]?))(' . $colChar . '(\[?[-+]?\d*\]?))$/i'; + $validityCheck = preg_match($regex, $address, $cellReference); - if ($validityCheck === 0) { + if (empty($validityCheck)) { throw new Exception('Invalid R1C1-format Cell Reference'); } @@ -92,7 +113,7 @@ class AddressHelper // Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent, // then modify the formula to use that new reference foreach ($cellReferences as $cellReference) { - $A1CellReference = self::convertToA1($cellReference[0][0], $currentRowNumber, $currentColumnNumber); + $A1CellReference = self::convertToA1($cellReference[0][0], $currentRowNumber, $currentColumnNumber, false); $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0])); } } diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index 2005694d..d4d793d7 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -50,14 +50,14 @@ class Cell private $dataType; /** - * Collection of cells. + * The collection of cells that this cell belongs to (i.e. The Cell Collection for the parent Worksheet). * * @var Cells */ private $parent; /** - * Index to cellXf. + * Index to the cellXf reference for the styling of this cell. * * @var int */ @@ -95,9 +95,8 @@ class Cell * Create a new Cell. * * @param mixed $value - * @param string $dataType */ - public function __construct($value, $dataType, Worksheet $worksheet) + public function __construct($value, ?string $dataType, Worksheet $worksheet) { // Initialise cell value $this->value = $value; @@ -111,7 +110,7 @@ class Cell $dataType = DataType::TYPE_STRING; } $this->dataType = $dataType; - } elseif (!self::getValueBinder()->bindValue($this, $value)) { + } elseif (self::getValueBinder()->bindValue($this, $value) === false) { throw new Exception('Value could not be bound to cell.'); } } @@ -167,10 +166,8 @@ class Cell /** * Get cell value with formatting. - * - * @return string */ - public function getFormattedValue() + public function getFormattedValue(): string { return (string) NumberFormat::toFormattedString( $this->getCalculatedValue(), @@ -188,7 +185,7 @@ class Cell * * @return $this */ - public function setValue($value) + public function setValue($value): self { if (!self::getValueBinder()->bindValue($this, $value)) { throw new Exception('Value could not be bound to cell.'); @@ -205,7 +202,7 @@ class Cell * Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this * method, then it is your responsibility as an end-user developer to validate that the value and * the datatype match. - * If you do mismatch value and datatpe, then the value you enter may be changed to match the datatype + * If you do mismatch value and datatype, then the value you enter may be changed to match the datatype * that you specify. * * @return Cell @@ -271,7 +268,7 @@ class Cell * * @return mixed */ - public function getCalculatedValue($resetLog = true) + public function getCalculatedValue(bool $resetLog = true) { if ($this->dataType === DataType::TYPE_FORMULA) { try { @@ -316,10 +313,8 @@ class Cell * Set old calculated value (cached). * * @param mixed $originalValue Value - * - * @return Cell */ - public function setCalculatedValue($originalValue) + public function setCalculatedValue($originalValue): self { if ($originalValue !== null) { $this->calculatedValue = (is_numeric($originalValue)) ? (float) $originalValue : $originalValue; @@ -345,10 +340,8 @@ class Cell /** * Get cell data type. - * - * @return string */ - public function getDataType() + public function getDataType(): string { return $this->dataType; } @@ -357,10 +350,8 @@ class Cell * Set cell data type. * * @param string $dataType see DataType::TYPE_* - * - * @return Cell */ - public function setDataType($dataType) + public function setDataType($dataType): self { if ($dataType == DataType::TYPE_STRING2) { $dataType = DataType::TYPE_STRING; @@ -392,10 +383,8 @@ class Cell /** * Get Data validation rules. - * - * @return DataValidation */ - public function getDataValidation() + public function getDataValidation(): DataValidation { if (!isset($this->parent)) { throw new Exception('Cannot get data validation for cell that is not bound to a worksheet'); @@ -420,10 +409,8 @@ class Cell /** * Does this cell contain valid value? - * - * @return bool */ - public function hasValidValue() + public function hasValidValue(): bool { $validator = new DataValidator(); @@ -432,10 +419,8 @@ class Cell /** * Does this cell contain a Hyperlink? - * - * @return bool */ - public function hasHyperlink() + public function hasHyperlink(): bool { if (!isset($this->parent)) { throw new Exception('Cannot check for hyperlink when cell is not bound to a worksheet'); @@ -446,10 +431,8 @@ class Cell /** * Get Hyperlink. - * - * @return Hyperlink */ - public function getHyperlink() + public function getHyperlink(): Hyperlink { if (!isset($this->parent)) { throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet'); @@ -460,10 +443,8 @@ class Cell /** * Set Hyperlink. - * - * @return Cell */ - public function setHyperlink(?Hyperlink $hyperlink = null) + public function setHyperlink(?Hyperlink $hyperlink = null): self { if (!isset($this->parent)) { throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet'); @@ -486,10 +467,8 @@ class Cell /** * Get parent worksheet. - * - * @return Worksheet */ - public function getWorksheet() + public function getWorksheet(): Worksheet { try { $worksheet = $this->parent->getParent(); @@ -506,27 +485,22 @@ class Cell /** * Is this cell in a merge range. - * - * @return bool */ - public function isInMergeRange() + public function isInMergeRange(): bool { return (bool) $this->getMergeRange(); } /** * Is this cell the master (top left cell) in a merge range (that holds the actual data value). - * - * @return bool */ - public function isMergeRangeValueCell() + public function isMergeRangeValueCell(): bool { if ($mergeRange = $this->getMergeRange()) { $mergeRange = Coordinate::splitRange($mergeRange); [$startCell] = $mergeRange[0]; - if ($this->getCoordinate() === $startCell) { - return true; - } + + return $this->getCoordinate() === $startCell; } return false; @@ -576,10 +550,8 @@ class Cell /** * Re-bind parent. - * - * @return Cell */ - public function rebindParent(Worksheet $parent) + public function rebindParent(Worksheet $parent): self { $this->parent = $parent->getCellCollection(); @@ -590,10 +562,8 @@ class Cell * Is cell in a specific range? * * @param string $range Cell range (e.g. A1:A1) - * - * @return bool */ - public function isInRange($range) + public function isInRange(string $range): bool { [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range); @@ -614,7 +584,7 @@ class Cell * * @return int Result of comparison (always -1 or 1, never zero!) */ - public static function compareCells(self $a, self $b) + public static function compareCells(self $a, self $b): int { if ($a->getRow() < $b->getRow()) { return -1; @@ -629,10 +599,8 @@ class Cell /** * Get value binder to use. - * - * @return IValueBinder */ - public static function getValueBinder() + public static function getValueBinder(): IValueBinder { if (self::$valueBinder === null) { self::$valueBinder = new DefaultValueBinder(); @@ -655,33 +623,27 @@ class Cell public function __clone() { $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if ((is_object($value)) && ($key != 'parent')) { - $this->$key = clone $value; + foreach ($vars as $propertyName => $propertyValue) { + if ((is_object($propertyValue)) && ($propertyName !== 'parent')) { + $this->$propertyName = clone $propertyValue; } else { - $this->$key = $value; + $this->$propertyName = $propertyValue; } } } /** * Get index to cellXf. - * - * @return int */ - public function getXfIndex() + public function getXfIndex(): int { return $this->xfIndex; } /** * Set index to cellXf. - * - * @param int $indexValue - * - * @return Cell */ - public function setXfIndex($indexValue) + public function setXfIndex(int $indexValue): self { $this->xfIndex = $indexValue; @@ -695,7 +657,7 @@ class Cell * * @return $this */ - public function setFormulaAttributes($attributes) + public function setFormulaAttributes($attributes): self { $this->formulaAttributes = $attributes; diff --git a/src/PhpSpreadsheet/Cell/Coordinate.php b/src/PhpSpreadsheet/Cell/Coordinate.php index 50678397..2fca6212 100644 --- a/src/PhpSpreadsheet/Cell/Coordinate.php +++ b/src/PhpSpreadsheet/Cell/Coordinate.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Cell; +use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; @@ -349,6 +350,19 @@ abstract class Coordinate */ public static function extractAllCellReferencesInRange($cellRange): array { + if (substr_count($cellRange, '!') > 1) { + throw new Exception('3-D Range References are not supported'); + } + + [$worksheet, $cellRange] = Worksheet::extractSheetTitle($cellRange, true); + $quoted = ''; + if ($worksheet > '') { + $quoted = Worksheet::nameRequiresQuotes($worksheet) ? "'" : ''; + if (substr($worksheet, 0, 1) === "'" && substr($worksheet, -1, 1) === "'") { + $worksheet = substr($worksheet, 1, -1); + } + $worksheet = str_replace("'", "''", $worksheet); + } [$ranges, $operators] = self::getCellBlocksFromRangeString($cellRange); $cells = []; @@ -364,7 +378,12 @@ abstract class Coordinate $cellList = array_merge(...$cells); - return self::sortCellReferenceArray($cellList); + return array_map( + function ($cellAddress) use ($worksheet, $quoted) { + return ($worksheet !== '') ? "{$quoted}{$worksheet}{$quoted}!{$cellAddress}" : $cellAddress; + }, + self::sortCellReferenceArray($cellList) + ); } private static function processRangeSetOperators(array $operators, array $cells): array diff --git a/src/PhpSpreadsheet/Chart/Axis.php b/src/PhpSpreadsheet/Chart/Axis.php index 70ff1b31..9ebb081f 100644 --- a/src/PhpSpreadsheet/Chart/Axis.php +++ b/src/PhpSpreadsheet/Chart/Axis.php @@ -10,6 +10,14 @@ namespace PhpOffice\PhpSpreadsheet\Chart; */ class Axis extends Properties { + const AXIS_TYPE_CATEGORY = 'catAx'; + const AXIS_TYPE_DATE = 'dateAx'; + const AXIS_TYPE_VALUE = 'valAx'; + + const TIME_UNIT_DAYS = 'days'; + const TIME_UNIT_MONTHS = 'months'; + const TIME_UNIT_YEARS = 'years'; + public function __construct() { parent::__construct(); @@ -61,6 +69,10 @@ class Axis extends Properties 'horizontal_crosses' => self::HORIZONTAL_CROSSES_AUTOZERO, 'horizontal_crosses_value' => null, 'textRotation' => null, + 'hidden' => null, + 'majorTimeUnit' => self::TIME_UNIT_YEARS, + 'minorTimeUnit' => self::TIME_UNIT_MONTHS, + 'baseTimeUnit' => self::TIME_UNIT_DAYS, ]; /** @@ -73,6 +85,7 @@ class Axis extends Properties private const NUMERIC_FORMAT = [ Properties::FORMAT_CODE_NUMBER, Properties::FORMAT_CODE_DATE, + Properties::FORMAT_CODE_DATE_ISO8601, ]; /** @@ -114,12 +127,12 @@ class Axis extends Properties public function getAxisIsNumericFormat(): bool { - return (bool) $this->axisNumber['numeric']; + return $this->axisType === self::AXIS_TYPE_DATE || (bool) $this->axisNumber['numeric']; } public function setAxisOption(string $key, ?string $value): void { - if (!empty($value)) { + if ($value !== null && $value !== '') { $this->axisOptions[$key] = $value; } } @@ -138,7 +151,11 @@ class Axis extends Properties ?string $maximum = null, ?string $majorUnit = null, ?string $minorUnit = null, - ?string $textRotation = null + ?string $textRotation = null, + ?string $hidden = null, + ?string $baseTimeUnit = null, + ?string $majorTimeUnit = null, + ?string $minorTimeUnit = null ): void { $this->axisOptions['axis_labels'] = $axisLabels; $this->setAxisOption('horizontal_crosses_value', $horizontalCrossesValue); @@ -151,6 +168,10 @@ class Axis extends Properties $this->setAxisOption('major_unit', $majorUnit); $this->setAxisOption('minor_unit', $minorUnit); $this->setAxisOption('textRotation', $textRotation); + $this->setAxisOption('hidden', $hidden); + $this->setAxisOption('baseTimeUnit', $baseTimeUnit); + $this->setAxisOption('majorTimeUnit', $majorTimeUnit); + $this->setAxisOption('minorTimeUnit', $minorTimeUnit); } /** @@ -182,7 +203,7 @@ class Axis extends Properties public function setAxisType(string $type): self { - if ($type === 'catAx' || $type === 'valAx') { + if ($type === self::AXIS_TYPE_CATEGORY || $type === self::AXIS_TYPE_VALUE || $type === self::AXIS_TYPE_DATE) { $this->axisType = $type; } else { $this->axisType = ''; @@ -198,7 +219,7 @@ class Axis extends Properties * @param ?int $alpha * @param ?string $AlphaType */ - public function setFillParameters($color, $alpha = null, $AlphaType = self::EXCEL_COLOR_TYPE_ARGB): void + public function setFillParameters($color, $alpha = null, $AlphaType = ChartColor::EXCEL_COLOR_TYPE_RGB): void { $this->fillColor->setColorProperties($color, $alpha, $AlphaType); } diff --git a/src/PhpSpreadsheet/Chart/Chart.php b/src/PhpSpreadsheet/Chart/Chart.php index 3f89e6fb..f41d7883 100644 --- a/src/PhpSpreadsheet/Chart/Chart.php +++ b/src/PhpSpreadsheet/Chart/Chart.php @@ -144,6 +144,12 @@ class Chart /** @var bool */ private $autoTitleDeleted = false; + /** @var bool */ + private $noFill = false; + + /** @var bool */ + private $roundedCorners = false; + /** * Create a new Chart. * majorGridlines and minorGridlines are deprecated, moved to Axis. @@ -182,6 +188,13 @@ class Chart return $this->name; } + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + /** * Get Worksheet. */ @@ -648,14 +661,10 @@ class Chart /** * Render the chart to given file (or stream). - * Unable to cover code until a usable current version of JpGraph - * is made available through Composer. * * @param string $outputDestination Name of the file render to * * @return bool true on success - * - * @codeCoverageIgnore */ public function render($outputDestination = null) { @@ -747,4 +756,30 @@ class Chart return $this; } + + public function getNoFill(): bool + { + return $this->noFill; + } + + public function setNoFill(bool $noFill): self + { + $this->noFill = $noFill; + + return $this; + } + + public function getRoundedCorners(): bool + { + return $this->roundedCorners; + } + + public function setRoundedCorners(?bool $roundedCorners): self + { + if ($roundedCorners !== null) { + $this->roundedCorners = $roundedCorners; + } + + return $this; + } } diff --git a/src/PhpSpreadsheet/Chart/ChartColor.php b/src/PhpSpreadsheet/Chart/ChartColor.php index 7f87e391..87f31020 100644 --- a/src/PhpSpreadsheet/Chart/ChartColor.php +++ b/src/PhpSpreadsheet/Chart/ChartColor.php @@ -24,15 +24,18 @@ class ChartColor /** @var ?int */ private $alpha; + /** @var ?int */ + private $brightness; + /** * @param string|string[] $value */ - public function __construct($value = '', ?int $alpha = null, ?string $type = null) + public function __construct($value = '', ?int $alpha = null, ?string $type = null, ?int $brightness = null) { if (is_array($value)) { $this->setColorPropertiesArray($value); } else { - $this->setColorProperties($value, $alpha, $type); + $this->setColorProperties($value, $alpha, $type, $brightness); } } @@ -72,10 +75,23 @@ class ChartColor return $this; } + public function getBrightness(): ?int + { + return $this->brightness; + } + + public function setBrightness(?int $brightness): self + { + $this->brightness = $brightness; + + return $this; + } + /** * @param null|float|int|string $alpha + * @param null|float|int|string $brightness */ - public function setColorProperties(?string $color, $alpha = null, ?string $type = null): self + public function setColorProperties(?string $color, $alpha = null, ?string $type = null, $brightness = null): self { if (empty($type) && !empty($color)) { if (substr($color, 0, 1) === '*') { @@ -99,6 +115,11 @@ class ChartColor } elseif (is_numeric($alpha)) { $this->setAlpha((int) $alpha); } + if ($brightness === null) { + $this->setBrightness(null); + } elseif (is_numeric($brightness)) { + $this->setBrightness((int) $brightness); + } return $this; } @@ -108,7 +129,8 @@ class ChartColor return $this->setColorProperties( $color['value'] ?? '', $color['alpha'] ?? null, - $color['type'] ?? null + $color['type'] ?? null, + $color['brightness'] ?? null ); } @@ -133,6 +155,8 @@ class ChartColor $retVal = $this->type; } elseif ($propertyName === 'alpha') { $retVal = $this->alpha; + } elseif ($propertyName === 'brightness') { + $retVal = $this->brightness; } return $retVal; diff --git a/src/PhpSpreadsheet/Chart/DataSeries.php b/src/PhpSpreadsheet/Chart/DataSeries.php index 548145e7..5d33e96d 100644 --- a/src/PhpSpreadsheet/Chart/DataSeries.php +++ b/src/PhpSpreadsheet/Chart/DataSeries.php @@ -94,7 +94,7 @@ class DataSeries private $plotCategory = []; /** - * Smooth Line. + * Smooth Line. Must be specified for both DataSeries and DataSeriesValues. * * @var bool */ diff --git a/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/src/PhpSpreadsheet/Chart/DataSeriesValues.php index bc0e04d1..cd166b23 100644 --- a/src/PhpSpreadsheet/Chart/DataSeriesValues.php +++ b/src/PhpSpreadsheet/Chart/DataSeriesValues.php @@ -88,6 +88,9 @@ class DataSeriesValues extends Properties /** @var ?Layout */ private $labelLayout; + /** @var TrendLine[] */ + private $trendLines = []; + /** * Create a new DataSeriesValues object. * @@ -321,13 +324,13 @@ class DataSeriesValues extends Properties if (is_array($this->fillColor)) { $array = []; foreach ($this->fillColor as $chartColor) { - $array[] = self::chartColorToString($chartColor); + $array[] = $this->chartColorToString($chartColor); } return $array; } - return self::chartColorToString($this->fillColor); + return $this->chartColorToString($this->fillColor); } /** @@ -345,13 +348,13 @@ class DataSeriesValues extends Properties if ($fillString instanceof ChartColor) { $this->fillColor[] = $fillString; } else { - $this->fillColor[] = self::stringToChartColor($fillString); + $this->fillColor[] = $this->stringToChartColor($fillString); } } } elseif ($color instanceof ChartColor) { $this->fillColor = $color; - } elseif (is_string($color)) { - $this->fillColor = self::stringToChartColor($color); + } else { + $this->fillColor = $this->stringToChartColor($color); } return $this; @@ -536,7 +539,7 @@ class DataSeriesValues extends Properties } /** - * Smooth Line. + * Smooth Line. Must be specified for both DataSeries and DataSeriesValues. * * @var bool */ @@ -577,4 +580,16 @@ class DataSeriesValues extends Properties return $this; } + + public function setTrendLines(array $trendLines): self + { + $this->trendLines = $trendLines; + + return $this; + } + + public function getTrendLines(): array + { + return $this->trendLines; + } } diff --git a/src/PhpSpreadsheet/Chart/PlotArea.php b/src/PhpSpreadsheet/Chart/PlotArea.php index 4bd49ece..ccde4bb2 100644 --- a/src/PhpSpreadsheet/Chart/PlotArea.php +++ b/src/PhpSpreadsheet/Chart/PlotArea.php @@ -6,6 +6,30 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class PlotArea { + /** + * No fill in plot area (show Excel gridlines through chart). + * + * @var bool + */ + private $noFill = false; + + /** + * PlotArea Gradient Stop list. + * Each entry is a 2-element array. + * First is position in %. + * Second is ChartColor. + * + * @var array[] + */ + private $gradientFillStops = []; + + /** + * PlotArea Gradient Angle. + * + * @var ?float + */ + private $gradientFillAngle; + /** * PlotArea Layout. * @@ -101,4 +125,42 @@ class PlotArea $plotSeries->refresh($worksheet); } } + + public function setNoFill(bool $noFill): self + { + $this->noFill = $noFill; + + return $this; + } + + public function getNoFill(): bool + { + return $this->noFill; + } + + public function setGradientFillProperties(array $gradientFillStops, ?float $gradientFillAngle): self + { + $this->gradientFillStops = $gradientFillStops; + $this->gradientFillAngle = $gradientFillAngle; + + return $this; + } + + /** + * Get gradientFillAngle. + */ + public function getGradientFillAngle(): ?float + { + return $this->gradientFillAngle; + } + + /** + * Get gradientFillStops. + * + * @return array + */ + public function getGradientFillStops() + { + return $this->gradientFillStops; + } } diff --git a/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php b/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php index b276707d..0b0164b4 100644 --- a/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php +++ b/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php @@ -2,68 +2,20 @@ namespace PhpOffice\PhpSpreadsheet\Chart\Renderer; -use AccBarPlot; -use AccLinePlot; -use BarPlot; -use ContourPlot; -use Graph; -use GroupBarPlot; -use LinePlot; -use PhpOffice\PhpSpreadsheet\Chart\Chart; -use PhpOffice\PhpSpreadsheet\Style\NumberFormat; -use PieGraph; -use PiePlot; -use PiePlot3D; -use PiePlotC; -use RadarGraph; -use RadarPlot; -use ScatterPlot; -use Spline; -use StockPlot; - /** - * Jpgraph is not maintained in Composer, and the version there - * is extremely out of date. For that reason, all unit test - * requiring Jpgraph are skipped. So, do not measure - * code coverage for this class till that is fixed. + * Jpgraph is not oficially maintained in Composer, so the version there + * could be out of date. For that reason, all unit test requiring Jpgraph + * are skipped. So, do not measure code coverage for this class till that + * is fixed. + * + * This implementation uses abandoned package + * https://packagist.org/packages/jpgraph/jpgraph * * @codeCoverageIgnore */ -class JpGraph implements IRenderer +class JpGraph extends JpGraphRendererBase { - private static $width = 640; - - private static $height = 480; - - private static $colourSet = [ - 'mediumpurple1', 'palegreen3', 'gold1', 'cadetblue1', - 'darkmagenta', 'coral', 'dodgerblue3', 'eggplant', - 'mediumblue', 'magenta', 'sandybrown', 'cyan', - 'firebrick1', 'forestgreen', 'deeppink4', 'darkolivegreen', - 'goldenrod2', - ]; - - private static $markSet; - - private $chart; - - private $graph; - - private static $plotColour = 0; - - private static $plotMark = 0; - - /** - * Create a new jpgraph. - */ - public function __construct(Chart $chart) - { - self::init(); - $this->graph = null; - $this->chart = $chart; - } - - private static function init(): void + protected static function init(): void { static $loaded = false; if ($loaded) { @@ -81,802 +33,6 @@ class JpGraph implements IRenderer \JpGraph\JpGraph::module('scatter'); \JpGraph\JpGraph::module('stock'); - self::$markSet = [ - 'diamond' => MARK_DIAMOND, - 'square' => MARK_SQUARE, - 'triangle' => MARK_UTRIANGLE, - 'x' => MARK_X, - 'star' => MARK_STAR, - 'dot' => MARK_FILLEDCIRCLE, - 'dash' => MARK_DTRIANGLE, - 'circle' => MARK_CIRCLE, - 'plus' => MARK_CROSS, - ]; - $loaded = true; } - - private function formatPointMarker($seriesPlot, $markerID) - { - $plotMarkKeys = array_keys(self::$markSet); - if ($markerID === null) { - // Use default plot marker (next marker in the series) - self::$plotMark %= count(self::$markSet); - $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); - } elseif ($markerID !== 'none') { - // Use specified plot marker (if it exists) - if (isset(self::$markSet[$markerID])) { - $seriesPlot->mark->SetType(self::$markSet[$markerID]); - } else { - // If the specified plot marker doesn't exist, use default plot marker (next marker in the series) - self::$plotMark %= count(self::$markSet); - $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); - } - } else { - // Hide plot marker - $seriesPlot->mark->Hide(); - } - $seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]); - $seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]); - $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); - - return $seriesPlot; - } - - private function formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation = '') - { - $datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode(); - if ($datasetLabelFormatCode !== null) { - // Retrieve any label formatting code - $datasetLabelFormatCode = stripslashes($datasetLabelFormatCode); - } - - $testCurrentIndex = 0; - foreach ($datasetLabels as $i => $datasetLabel) { - if (is_array($datasetLabel)) { - if ($rotation == 'bar') { - $datasetLabels[$i] = implode(' ', $datasetLabel); - } else { - $datasetLabel = array_reverse($datasetLabel); - $datasetLabels[$i] = implode("\n", $datasetLabel); - } - } else { - // Format labels according to any formatting code - if ($datasetLabelFormatCode !== null) { - $datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode); - } - } - ++$testCurrentIndex; - } - - return $datasetLabels; - } - - private function percentageSumCalculation($groupID, $seriesCount) - { - $sumValues = []; - // Adjust our values to a percentage value across all series in the group - for ($i = 0; $i < $seriesCount; ++$i) { - if ($i == 0) { - $sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); - } else { - $nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); - foreach ($nextValues as $k => $value) { - if (isset($sumValues[$k])) { - $sumValues[$k] += $value; - } else { - $sumValues[$k] = $value; - } - } - } - } - - return $sumValues; - } - - private function percentageAdjustValues($dataValues, $sumValues) - { - foreach ($dataValues as $k => $dataValue) { - $dataValues[$k] = $dataValue / $sumValues[$k] * 100; - } - - return $dataValues; - } - - private function getCaption($captionElement) - { - // Read any caption - $caption = ($captionElement !== null) ? $captionElement->getCaption() : null; - // Test if we have a title caption to display - if ($caption !== null) { - // If we do, it could be a plain string or an array - if (is_array($caption)) { - // Implode an array to a plain string - $caption = implode('', $caption); - } - } - - return $caption; - } - - private function renderTitle(): void - { - $title = $this->getCaption($this->chart->getTitle()); - if ($title !== null) { - $this->graph->title->Set($title); - } - } - - private function renderLegend(): void - { - $legend = $this->chart->getLegend(); - if ($legend !== null) { - $legendPosition = $legend->getPosition(); - switch ($legendPosition) { - case 'r': - $this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); // right - $this->graph->legend->SetColumns(1); - - break; - case 'l': - $this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); // left - $this->graph->legend->SetColumns(1); - - break; - case 't': - $this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); // top - - break; - case 'b': - $this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); // bottom - - break; - default: - $this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); // top-right - $this->graph->legend->SetColumns(1); - - break; - } - } else { - $this->graph->legend->Hide(); - } - } - - private function renderCartesianPlotArea($type = 'textlin'): void - { - $this->graph = new Graph(self::$width, self::$height); - $this->graph->SetScale($type); - - $this->renderTitle(); - - // Rotate for bar rather than column chart - $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection(); - $reverse = $rotation == 'bar'; - - $xAxisLabel = $this->chart->getXAxisLabel(); - if ($xAxisLabel !== null) { - $title = $this->getCaption($xAxisLabel); - if ($title !== null) { - $this->graph->xaxis->SetTitle($title, 'center'); - $this->graph->xaxis->title->SetMargin(35); - if ($reverse) { - $this->graph->xaxis->title->SetAngle(90); - $this->graph->xaxis->title->SetMargin(90); - } - } - } - - $yAxisLabel = $this->chart->getYAxisLabel(); - if ($yAxisLabel !== null) { - $title = $this->getCaption($yAxisLabel); - if ($title !== null) { - $this->graph->yaxis->SetTitle($title, 'center'); - if ($reverse) { - $this->graph->yaxis->title->SetAngle(0); - $this->graph->yaxis->title->SetMargin(-55); - } - } - } - } - - private function renderPiePlotArea(): void - { - $this->graph = new PieGraph(self::$width, self::$height); - - $this->renderTitle(); - } - - private function renderRadarPlotArea(): void - { - $this->graph = new RadarGraph(self::$width, self::$height); - $this->graph->SetScale('lin'); - - $this->renderTitle(); - } - - private function renderPlotLine($groupID, $filled = false, $combination = false, $dimensions = '2d'): void - { - $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); - - $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); - if ($labelCount > 0) { - $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); - $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount); - $this->graph->xaxis->SetTickLabels($datasetLabels); - } - - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $seriesPlots = []; - if ($grouping == 'percentStacked') { - $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); - } else { - $sumValues = []; - } - - // Loop through each data series in turn - for ($i = 0; $i < $seriesCount; ++$i) { - $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); - $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); - - if ($grouping == 'percentStacked') { - $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); - } - - // Fill in any missing values in the $dataValues array - $testCurrentIndex = 0; - foreach ($dataValues as $k => $dataValue) { - while ($k != $testCurrentIndex) { - $dataValues[$testCurrentIndex] = null; - ++$testCurrentIndex; - } - ++$testCurrentIndex; - } - - $seriesPlot = new LinePlot($dataValues); - if ($combination) { - $seriesPlot->SetBarCenter(); - } - - if ($filled) { - $seriesPlot->SetFilled(true); - $seriesPlot->SetColor('black'); - $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); - } else { - // Set the appropriate plot marker - $this->formatPointMarker($seriesPlot, $marker); - } - $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); - $seriesPlot->SetLegend($dataLabel); - - $seriesPlots[] = $seriesPlot; - } - - if ($grouping == 'standard') { - $groupPlot = $seriesPlots; - } else { - $groupPlot = new AccLinePlot($seriesPlots); - } - $this->graph->Add($groupPlot); - } - - private function renderPlotBar($groupID, $dimensions = '2d'): void - { - $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection(); - // Rotate for bar rather than column chart - if (($groupID == 0) && ($rotation == 'bar')) { - $this->graph->Set90AndMargin(); - } - $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); - - $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); - if ($labelCount > 0) { - $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); - $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation); - // Rotate for bar rather than column chart - if ($rotation == 'bar') { - $datasetLabels = array_reverse($datasetLabels); - $this->graph->yaxis->SetPos('max'); - $this->graph->yaxis->SetLabelAlign('center', 'top'); - $this->graph->yaxis->SetLabelSide(SIDE_RIGHT); - } - $this->graph->xaxis->SetTickLabels($datasetLabels); - } - - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $seriesPlots = []; - if ($grouping == 'percentStacked') { - $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); - } else { - $sumValues = []; - } - - // Loop through each data series in turn - for ($j = 0; $j < $seriesCount; ++$j) { - $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues(); - if ($grouping == 'percentStacked') { - $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); - } - - // Fill in any missing values in the $dataValues array - $testCurrentIndex = 0; - foreach ($dataValues as $k => $dataValue) { - while ($k != $testCurrentIndex) { - $dataValues[$testCurrentIndex] = null; - ++$testCurrentIndex; - } - ++$testCurrentIndex; - } - - // Reverse the $dataValues order for bar rather than column chart - if ($rotation == 'bar') { - $dataValues = array_reverse($dataValues); - } - $seriesPlot = new BarPlot($dataValues); - $seriesPlot->SetColor('black'); - $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); - if ($dimensions == '3d') { - $seriesPlot->SetShadow(); - } - if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) { - $dataLabel = ''; - } else { - $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue(); - } - $seriesPlot->SetLegend($dataLabel); - - $seriesPlots[] = $seriesPlot; - } - // Reverse the plot order for bar rather than column chart - if (($rotation == 'bar') && ($grouping != 'percentStacked')) { - $seriesPlots = array_reverse($seriesPlots); - } - - if ($grouping == 'clustered') { - $groupPlot = new GroupBarPlot($seriesPlots); - } elseif ($grouping == 'standard') { - $groupPlot = new GroupBarPlot($seriesPlots); - } else { - $groupPlot = new AccBarPlot($seriesPlots); - if ($dimensions == '3d') { - $groupPlot->SetShadow(); - } - } - - $this->graph->Add($groupPlot); - } - - private function renderPlotScatter($groupID, $bubble): void - { - $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); - $scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); - - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $seriesPlots = []; - - // Loop through each data series in turn - for ($i = 0; $i < $seriesCount; ++$i) { - $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); - $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); - - foreach ($dataValuesY as $k => $dataValueY) { - $dataValuesY[$k] = $k; - } - - $seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY); - if ($scatterStyle == 'lineMarker') { - $seriesPlot->SetLinkPoints(); - $seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]); - } elseif ($scatterStyle == 'smoothMarker') { - $spline = new Spline($dataValuesY, $dataValuesX); - [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20); - $lplot = new LinePlot($splineDataX, $splineDataY); - $lplot->SetColor(self::$colourSet[self::$plotColour]); - - $this->graph->Add($lplot); - } - - if ($bubble) { - $this->formatPointMarker($seriesPlot, 'dot'); - $seriesPlot->mark->SetColor('black'); - $seriesPlot->mark->SetSize($bubbleSize); - } else { - $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); - $this->formatPointMarker($seriesPlot, $marker); - } - $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); - $seriesPlot->SetLegend($dataLabel); - - $this->graph->Add($seriesPlot); - } - } - - private function renderPlotRadar($groupID): void - { - $radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); - - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $seriesPlots = []; - - // Loop through each data series in turn - for ($i = 0; $i < $seriesCount; ++$i) { - $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); - $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); - $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); - - $dataValues = []; - foreach ($dataValuesY as $k => $dataValueY) { - $dataValues[$k] = implode(' ', array_reverse($dataValueY)); - } - $tmp = array_shift($dataValues); - $dataValues[] = $tmp; - $tmp = array_shift($dataValuesX); - $dataValuesX[] = $tmp; - - $this->graph->SetTitles(array_reverse($dataValues)); - - $seriesPlot = new RadarPlot(array_reverse($dataValuesX)); - - $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); - $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); - if ($radarStyle == 'filled') { - $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]); - } - $this->formatPointMarker($seriesPlot, $marker); - $seriesPlot->SetLegend($dataLabel); - - $this->graph->Add($seriesPlot); - } - } - - private function renderPlotContour($groupID): void - { - $contourStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); - - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $seriesPlots = []; - - $dataValues = []; - // Loop through each data series in turn - for ($i = 0; $i < $seriesCount; ++$i) { - $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); - $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); - - $dataValues[$i] = $dataValuesX; - } - $seriesPlot = new ContourPlot($dataValues); - - $this->graph->Add($seriesPlot); - } - - private function renderPlotStock($groupID): void - { - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder(); - - $dataValues = []; - // Loop through each data series in turn and build the plot arrays - foreach ($plotOrder as $i => $v) { - $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues(); - foreach ($dataValuesX as $j => $dataValueX) { - $dataValues[$plotOrder[$i]][$j] = $dataValueX; - } - } - if (empty($dataValues)) { - return; - } - - $dataValuesPlot = []; - // Flatten the plot arrays to a single dimensional array to work with jpgraph - $jMax = count($dataValues[0]); - for ($j = 0; $j < $jMax; ++$j) { - for ($i = 0; $i < $seriesCount; ++$i) { - $dataValuesPlot[] = $dataValues[$i][$j]; - } - } - - // Set the x-axis labels - $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); - if ($labelCount > 0) { - $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); - $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount); - $this->graph->xaxis->SetTickLabels($datasetLabels); - } - - $seriesPlot = new StockPlot($dataValuesPlot); - $seriesPlot->SetWidth(20); - - $this->graph->Add($seriesPlot); - } - - private function renderAreaChart($groupCount, $dimensions = '2d'): void - { - $this->renderCartesianPlotArea(); - - for ($i = 0; $i < $groupCount; ++$i) { - $this->renderPlotLine($i, true, false, $dimensions); - } - } - - private function renderLineChart($groupCount, $dimensions = '2d'): void - { - $this->renderCartesianPlotArea(); - - for ($i = 0; $i < $groupCount; ++$i) { - $this->renderPlotLine($i, false, false, $dimensions); - } - } - - private function renderBarChart($groupCount, $dimensions = '2d'): void - { - $this->renderCartesianPlotArea(); - - for ($i = 0; $i < $groupCount; ++$i) { - $this->renderPlotBar($i, $dimensions); - } - } - - private function renderScatterChart($groupCount): void - { - $this->renderCartesianPlotArea('linlin'); - - for ($i = 0; $i < $groupCount; ++$i) { - $this->renderPlotScatter($i, false); - } - } - - private function renderBubbleChart($groupCount): void - { - $this->renderCartesianPlotArea('linlin'); - - for ($i = 0; $i < $groupCount; ++$i) { - $this->renderPlotScatter($i, true); - } - } - - private function renderPieChart($groupCount, $dimensions = '2d', $doughnut = false, $multiplePlots = false): void - { - $this->renderPiePlotArea(); - - $iLimit = ($multiplePlots) ? $groupCount : 1; - for ($groupID = 0; $groupID < $iLimit; ++$groupID) { - $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); - $exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); - $datasetLabels = []; - if ($groupID == 0) { - $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); - if ($labelCount > 0) { - $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); - $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount); - } - } - - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $seriesPlots = []; - // For pie charts, we only display the first series: doughnut charts generally display all series - $jLimit = ($multiplePlots) ? $seriesCount : 1; - // Loop through each data series in turn - for ($j = 0; $j < $jLimit; ++$j) { - $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues(); - - // Fill in any missing values in the $dataValues array - $testCurrentIndex = 0; - foreach ($dataValues as $k => $dataValue) { - while ($k != $testCurrentIndex) { - $dataValues[$testCurrentIndex] = null; - ++$testCurrentIndex; - } - ++$testCurrentIndex; - } - - if ($dimensions == '3d') { - $seriesPlot = new PiePlot3D($dataValues); - } else { - if ($doughnut) { - $seriesPlot = new PiePlotC($dataValues); - } else { - $seriesPlot = new PiePlot($dataValues); - } - } - - if ($multiplePlots) { - $seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4)); - } - - if ($doughnut) { - $seriesPlot->SetMidColor('white'); - } - - $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); - if (count($datasetLabels) > 0) { - $seriesPlot->SetLabels(array_fill(0, count($datasetLabels), '')); - } - if ($dimensions != '3d') { - $seriesPlot->SetGuideLines(false); - } - if ($j == 0) { - if ($exploded) { - $seriesPlot->ExplodeAll(); - } - $seriesPlot->SetLegends($datasetLabels); - } - - $this->graph->Add($seriesPlot); - } - } - } - - private function renderRadarChart($groupCount): void - { - $this->renderRadarPlotArea(); - - for ($groupID = 0; $groupID < $groupCount; ++$groupID) { - $this->renderPlotRadar($groupID); - } - } - - private function renderStockChart($groupCount): void - { - $this->renderCartesianPlotArea('intint'); - - for ($groupID = 0; $groupID < $groupCount; ++$groupID) { - $this->renderPlotStock($groupID); - } - } - - private function renderContourChart($groupCount, $dimensions): void - { - $this->renderCartesianPlotArea('intint'); - - for ($i = 0; $i < $groupCount; ++$i) { - $this->renderPlotContour($i); - } - } - - private function renderCombinationChart($groupCount, $dimensions, $outputDestination) - { - $this->renderCartesianPlotArea(); - - for ($i = 0; $i < $groupCount; ++$i) { - $dimensions = null; - $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); - switch ($chartType) { - case 'area3DChart': - $dimensions = '3d'; - // no break - case 'areaChart': - $this->renderPlotLine($i, true, true, $dimensions); - - break; - case 'bar3DChart': - $dimensions = '3d'; - // no break - case 'barChart': - $this->renderPlotBar($i, $dimensions); - - break; - case 'line3DChart': - $dimensions = '3d'; - // no break - case 'lineChart': - $this->renderPlotLine($i, false, true, $dimensions); - - break; - case 'scatterChart': - $this->renderPlotScatter($i, false); - - break; - case 'bubbleChart': - $this->renderPlotScatter($i, true); - - break; - default: - $this->graph = null; - - return false; - } - } - - $this->renderLegend(); - - $this->graph->Stroke($outputDestination); - - return true; - } - - public function render($outputDestination) - { - self::$plotColour = 0; - - $groupCount = $this->chart->getPlotArea()->getPlotGroupCount(); - - $dimensions = null; - if ($groupCount == 1) { - $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType(); - } else { - $chartTypes = []; - for ($i = 0; $i < $groupCount; ++$i) { - $chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); - } - $chartTypes = array_unique($chartTypes); - if (count($chartTypes) == 1) { - $chartType = array_pop($chartTypes); - } elseif (count($chartTypes) == 0) { - echo 'Chart is not yet implemented
'; - - return false; - } else { - return $this->renderCombinationChart($groupCount, $dimensions, $outputDestination); - } - } - - switch ($chartType) { - case 'area3DChart': - $dimensions = '3d'; - // no break - case 'areaChart': - $this->renderAreaChart($groupCount, $dimensions); - - break; - case 'bar3DChart': - $dimensions = '3d'; - // no break - case 'barChart': - $this->renderBarChart($groupCount, $dimensions); - - break; - case 'line3DChart': - $dimensions = '3d'; - // no break - case 'lineChart': - $this->renderLineChart($groupCount, $dimensions); - - break; - case 'pie3DChart': - $dimensions = '3d'; - // no break - case 'pieChart': - $this->renderPieChart($groupCount, $dimensions, false, false); - - break; - case 'doughnut3DChart': - $dimensions = '3d'; - // no break - case 'doughnutChart': - $this->renderPieChart($groupCount, $dimensions, true, true); - - break; - case 'scatterChart': - $this->renderScatterChart($groupCount); - - break; - case 'bubbleChart': - $this->renderBubbleChart($groupCount); - - break; - case 'radarChart': - $this->renderRadarChart($groupCount); - - break; - case 'surface3DChart': - $dimensions = '3d'; - // no break - case 'surfaceChart': - $this->renderContourChart($groupCount, $dimensions); - - break; - case 'stockChart': - $this->renderStockChart($groupCount); - - break; - default: - echo $chartType . ' is not yet implemented
'; - - return false; - } - $this->renderLegend(); - - $this->graph->Stroke($outputDestination); - - return true; - } } diff --git a/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php b/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php new file mode 100644 index 00000000..cb9b544b --- /dev/null +++ b/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php @@ -0,0 +1,852 @@ +graph = null; + $this->chart = $chart; + + self::$markSet = [ + 'diamond' => MARK_DIAMOND, + 'square' => MARK_SQUARE, + 'triangle' => MARK_UTRIANGLE, + 'x' => MARK_X, + 'star' => MARK_STAR, + 'dot' => MARK_FILLEDCIRCLE, + 'dash' => MARK_DTRIANGLE, + 'circle' => MARK_CIRCLE, + 'plus' => MARK_CROSS, + ]; + } + + /** + * This method should be overriden in descendants to do real JpGraph library initialization. + */ + abstract protected static function init(): void; + + private function formatPointMarker($seriesPlot, $markerID) + { + $plotMarkKeys = array_keys(self::$markSet); + if ($markerID === null) { + // Use default plot marker (next marker in the series) + self::$plotMark %= count(self::$markSet); + $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); + } elseif ($markerID !== 'none') { + // Use specified plot marker (if it exists) + if (isset(self::$markSet[$markerID])) { + $seriesPlot->mark->SetType(self::$markSet[$markerID]); + } else { + // If the specified plot marker doesn't exist, use default plot marker (next marker in the series) + self::$plotMark %= count(self::$markSet); + $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); + } + } else { + // Hide plot marker + $seriesPlot->mark->Hide(); + } + $seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]); + $seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]); + $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); + + return $seriesPlot; + } + + private function formatDataSetLabels($groupID, $datasetLabels, $rotation = '') + { + $datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode() ?? ''; + // Retrieve any label formatting code + $datasetLabelFormatCode = stripslashes($datasetLabelFormatCode); + + $testCurrentIndex = 0; + foreach ($datasetLabels as $i => $datasetLabel) { + if (is_array($datasetLabel)) { + if ($rotation == 'bar') { + $datasetLabels[$i] = implode(' ', $datasetLabel); + } else { + $datasetLabel = array_reverse($datasetLabel); + $datasetLabels[$i] = implode("\n", $datasetLabel); + } + } else { + // Format labels according to any formatting code + if ($datasetLabelFormatCode !== null) { + $datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode); + } + } + ++$testCurrentIndex; + } + + return $datasetLabels; + } + + private function percentageSumCalculation($groupID, $seriesCount) + { + $sumValues = []; + // Adjust our values to a percentage value across all series in the group + for ($i = 0; $i < $seriesCount; ++$i) { + if ($i == 0) { + $sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + } else { + $nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + foreach ($nextValues as $k => $value) { + if (isset($sumValues[$k])) { + $sumValues[$k] += $value; + } else { + $sumValues[$k] = $value; + } + } + } + } + + return $sumValues; + } + + private function percentageAdjustValues($dataValues, $sumValues) + { + foreach ($dataValues as $k => $dataValue) { + $dataValues[$k] = $dataValue / $sumValues[$k] * 100; + } + + return $dataValues; + } + + private function getCaption($captionElement) + { + // Read any caption + $caption = ($captionElement !== null) ? $captionElement->getCaption() : null; + // Test if we have a title caption to display + if ($caption !== null) { + // If we do, it could be a plain string or an array + if (is_array($caption)) { + // Implode an array to a plain string + $caption = implode('', $caption); + } + } + + return $caption; + } + + private function renderTitle(): void + { + $title = $this->getCaption($this->chart->getTitle()); + if ($title !== null) { + $this->graph->title->Set($title); + } + } + + private function renderLegend(): void + { + $legend = $this->chart->getLegend(); + if ($legend !== null) { + $legendPosition = $legend->getPosition(); + switch ($legendPosition) { + case 'r': + $this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); // right + $this->graph->legend->SetColumns(1); + + break; + case 'l': + $this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); // left + $this->graph->legend->SetColumns(1); + + break; + case 't': + $this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); // top + + break; + case 'b': + $this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); // bottom + + break; + default: + $this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); // top-right + $this->graph->legend->SetColumns(1); + + break; + } + } else { + $this->graph->legend->Hide(); + } + } + + private function renderCartesianPlotArea($type = 'textlin'): void + { + $this->graph = new Graph(self::$width, self::$height); + $this->graph->SetScale($type); + + $this->renderTitle(); + + // Rotate for bar rather than column chart + $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection(); + $reverse = $rotation == 'bar'; + + $xAxisLabel = $this->chart->getXAxisLabel(); + if ($xAxisLabel !== null) { + $title = $this->getCaption($xAxisLabel); + if ($title !== null) { + $this->graph->xaxis->SetTitle($title, 'center'); + $this->graph->xaxis->title->SetMargin(35); + if ($reverse) { + $this->graph->xaxis->title->SetAngle(90); + $this->graph->xaxis->title->SetMargin(90); + } + } + } + + $yAxisLabel = $this->chart->getYAxisLabel(); + if ($yAxisLabel !== null) { + $title = $this->getCaption($yAxisLabel); + if ($title !== null) { + $this->graph->yaxis->SetTitle($title, 'center'); + if ($reverse) { + $this->graph->yaxis->title->SetAngle(0); + $this->graph->yaxis->title->SetMargin(-55); + } + } + } + } + + private function renderPiePlotArea(): void + { + $this->graph = new PieGraph(self::$width, self::$height); + + $this->renderTitle(); + } + + private function renderRadarPlotArea(): void + { + $this->graph = new RadarGraph(self::$width, self::$height); + $this->graph->SetScale('lin'); + + $this->renderTitle(); + } + + private function renderPlotLine($groupID, $filled = false, $combination = false): void + { + $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); + + $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0]; + $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount(); + if ($labelCount > 0) { + $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); + $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels); + $this->graph->xaxis->SetTickLabels($datasetLabels); + } + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $seriesPlots = []; + if ($grouping == 'percentStacked') { + $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); + } else { + $sumValues = []; + } + + // Loop through each data series in turn + for ($i = 0; $i < $seriesCount; ++$i) { + $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$i]; + $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues(); + $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointMarker(); + + if ($grouping == 'percentStacked') { + $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); + } + + // Fill in any missing values in the $dataValues array + $testCurrentIndex = 0; + foreach ($dataValues as $k => $dataValue) { + while ($k != $testCurrentIndex) { + $dataValues[$testCurrentIndex] = null; + ++$testCurrentIndex; + } + ++$testCurrentIndex; + } + + $seriesPlot = new LinePlot($dataValues); + if ($combination) { + $seriesPlot->SetBarCenter(); + } + + if ($filled) { + $seriesPlot->SetFilled(true); + $seriesPlot->SetColor('black'); + $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); + } else { + // Set the appropriate plot marker + $this->formatPointMarker($seriesPlot, $marker); + } + $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($index)->getDataValue(); + $seriesPlot->SetLegend($dataLabel); + + $seriesPlots[] = $seriesPlot; + } + + if ($grouping == 'standard') { + $groupPlot = $seriesPlots; + } else { + $groupPlot = new AccLinePlot($seriesPlots); + } + $this->graph->Add($groupPlot); + } + + private function renderPlotBar($groupID, $dimensions = '2d'): void + { + $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection(); + // Rotate for bar rather than column chart + if (($groupID == 0) && ($rotation == 'bar')) { + $this->graph->Set90AndMargin(); + } + $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); + + $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0]; + $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount(); + if ($labelCount > 0) { + $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); + $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $rotation); + // Rotate for bar rather than column chart + if ($rotation == 'bar') { + $datasetLabels = array_reverse($datasetLabels); + $this->graph->yaxis->SetPos('max'); + $this->graph->yaxis->SetLabelAlign('center', 'top'); + $this->graph->yaxis->SetLabelSide(SIDE_RIGHT); + } + $this->graph->xaxis->SetTickLabels($datasetLabels); + } + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $seriesPlots = []; + if ($grouping == 'percentStacked') { + $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); + } else { + $sumValues = []; + } + + // Loop through each data series in turn + for ($j = 0; $j < $seriesCount; ++$j) { + $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$j]; + $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues(); + if ($grouping == 'percentStacked') { + $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); + } + + // Fill in any missing values in the $dataValues array + $testCurrentIndex = 0; + foreach ($dataValues as $k => $dataValue) { + while ($k != $testCurrentIndex) { + $dataValues[$testCurrentIndex] = null; + ++$testCurrentIndex; + } + ++$testCurrentIndex; + } + + // Reverse the $dataValues order for bar rather than column chart + if ($rotation == 'bar') { + $dataValues = array_reverse($dataValues); + } + $seriesPlot = new BarPlot($dataValues); + $seriesPlot->SetColor('black'); + $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); + if ($dimensions == '3d') { + $seriesPlot->SetShadow(); + } + if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) { + $dataLabel = ''; + } else { + $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue(); + } + $seriesPlot->SetLegend($dataLabel); + + $seriesPlots[] = $seriesPlot; + } + // Reverse the plot order for bar rather than column chart + if (($rotation == 'bar') && ($grouping != 'percentStacked')) { + $seriesPlots = array_reverse($seriesPlots); + } + + if ($grouping == 'clustered') { + $groupPlot = new GroupBarPlot($seriesPlots); + } elseif ($grouping == 'standard') { + $groupPlot = new GroupBarPlot($seriesPlots); + } else { + $groupPlot = new AccBarPlot($seriesPlots); + if ($dimensions == '3d') { + $groupPlot->SetShadow(); + } + } + + $this->graph->Add($groupPlot); + } + + private function renderPlotScatter($groupID, $bubble): void + { + $scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + + // Loop through each data series in turn + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + + foreach ($dataValuesY as $k => $dataValueY) { + $dataValuesY[$k] = $k; + } + + $seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY); + if ($scatterStyle == 'lineMarker') { + $seriesPlot->SetLinkPoints(); + $seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]); + } elseif ($scatterStyle == 'smoothMarker') { + $spline = new Spline($dataValuesY, $dataValuesX); + [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20); + $lplot = new LinePlot($splineDataX, $splineDataY); + $lplot->SetColor(self::$colourSet[self::$plotColour]); + + $this->graph->Add($lplot); + } + + if ($bubble) { + $this->formatPointMarker($seriesPlot, 'dot'); + $seriesPlot->mark->SetColor('black'); + $seriesPlot->mark->SetSize($bubbleSize); + } else { + $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); + $this->formatPointMarker($seriesPlot, $marker); + } + $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); + $seriesPlot->SetLegend($dataLabel); + + $this->graph->Add($seriesPlot); + } + } + + private function renderPlotRadar($groupID): void + { + $radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + + // Loop through each data series in turn + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); + + $dataValues = []; + foreach ($dataValuesY as $k => $dataValueY) { + $dataValues[$k] = implode(' ', array_reverse($dataValueY)); + } + $tmp = array_shift($dataValues); + $dataValues[] = $tmp; + $tmp = array_shift($dataValuesX); + $dataValuesX[] = $tmp; + + $this->graph->SetTitles(array_reverse($dataValues)); + + $seriesPlot = new RadarPlot(array_reverse($dataValuesX)); + + $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); + $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); + if ($radarStyle == 'filled') { + $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]); + } + $this->formatPointMarker($seriesPlot, $marker); + $seriesPlot->SetLegend($dataLabel); + + $this->graph->Add($seriesPlot); + } + } + + private function renderPlotContour($groupID): void + { + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + + $dataValues = []; + // Loop through each data series in turn + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + + $dataValues[$i] = $dataValuesX; + } + $seriesPlot = new ContourPlot($dataValues); + + $this->graph->Add($seriesPlot); + } + + private function renderPlotStock($groupID): void + { + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder(); + + $dataValues = []; + // Loop through each data series in turn and build the plot arrays + foreach ($plotOrder as $i => $v) { + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v); + if ($dataValuesX === false) { + continue; + } + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues(); + foreach ($dataValuesX as $j => $dataValueX) { + $dataValues[$plotOrder[$i]][$j] = $dataValueX; + } + } + if (empty($dataValues)) { + return; + } + + $dataValuesPlot = []; + // Flatten the plot arrays to a single dimensional array to work with jpgraph + $jMax = count($dataValues[0]); + for ($j = 0; $j < $jMax; ++$j) { + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValuesPlot[] = $dataValues[$i][$j] ?? null; + } + } + + // Set the x-axis labels + $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); + if ($labelCount > 0) { + $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); + $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels); + $this->graph->xaxis->SetTickLabels($datasetLabels); + } + + $seriesPlot = new StockPlot($dataValuesPlot); + $seriesPlot->SetWidth(20); + + $this->graph->Add($seriesPlot); + } + + private function renderAreaChart($groupCount): void + { + $this->renderCartesianPlotArea(); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotLine($i, true, false); + } + } + + private function renderLineChart($groupCount): void + { + $this->renderCartesianPlotArea(); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotLine($i, false, false); + } + } + + private function renderBarChart($groupCount, $dimensions = '2d'): void + { + $this->renderCartesianPlotArea(); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotBar($i, $dimensions); + } + } + + private function renderScatterChart($groupCount): void + { + $this->renderCartesianPlotArea('linlin'); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotScatter($i, false); + } + } + + private function renderBubbleChart($groupCount): void + { + $this->renderCartesianPlotArea('linlin'); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotScatter($i, true); + } + } + + private function renderPieChart($groupCount, $dimensions = '2d', $doughnut = false, $multiplePlots = false): void + { + $this->renderPiePlotArea(); + + $iLimit = ($multiplePlots) ? $groupCount : 1; + for ($groupID = 0; $groupID < $iLimit; ++$groupID) { + $exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); + $datasetLabels = []; + if ($groupID == 0) { + $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); + if ($labelCount > 0) { + $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); + $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels); + } + } + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + // For pie charts, we only display the first series: doughnut charts generally display all series + $jLimit = ($multiplePlots) ? $seriesCount : 1; + // Loop through each data series in turn + for ($j = 0; $j < $jLimit; ++$j) { + $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues(); + + // Fill in any missing values in the $dataValues array + $testCurrentIndex = 0; + foreach ($dataValues as $k => $dataValue) { + while ($k != $testCurrentIndex) { + $dataValues[$testCurrentIndex] = null; + ++$testCurrentIndex; + } + ++$testCurrentIndex; + } + + if ($dimensions == '3d') { + $seriesPlot = new PiePlot3D($dataValues); + } else { + if ($doughnut) { + $seriesPlot = new PiePlotC($dataValues); + } else { + $seriesPlot = new PiePlot($dataValues); + } + } + + if ($multiplePlots) { + $seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4)); + } + + if ($doughnut && method_exists($seriesPlot, 'SetMidColor')) { + $seriesPlot->SetMidColor('white'); + } + + $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); + if (count($datasetLabels) > 0) { + $seriesPlot->SetLabels(array_fill(0, count($datasetLabels), '')); + } + if ($dimensions != '3d') { + $seriesPlot->SetGuideLines(false); + } + if ($j == 0) { + if ($exploded) { + $seriesPlot->ExplodeAll(); + } + $seriesPlot->SetLegends($datasetLabels); + } + + $this->graph->Add($seriesPlot); + } + } + } + + private function renderRadarChart($groupCount): void + { + $this->renderRadarPlotArea(); + + for ($groupID = 0; $groupID < $groupCount; ++$groupID) { + $this->renderPlotRadar($groupID); + } + } + + private function renderStockChart($groupCount): void + { + $this->renderCartesianPlotArea('intint'); + + for ($groupID = 0; $groupID < $groupCount; ++$groupID) { + $this->renderPlotStock($groupID); + } + } + + private function renderContourChart($groupCount): void + { + $this->renderCartesianPlotArea('intint'); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotContour($i); + } + } + + private function renderCombinationChart($groupCount, $outputDestination) + { + $this->renderCartesianPlotArea(); + + for ($i = 0; $i < $groupCount; ++$i) { + $dimensions = null; + $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); + switch ($chartType) { + case 'area3DChart': + case 'areaChart': + $this->renderPlotLine($i, true, true); + + break; + case 'bar3DChart': + $dimensions = '3d'; + // no break + case 'barChart': + $this->renderPlotBar($i, $dimensions); + + break; + case 'line3DChart': + case 'lineChart': + $this->renderPlotLine($i, false, true); + + break; + case 'scatterChart': + $this->renderPlotScatter($i, false); + + break; + case 'bubbleChart': + $this->renderPlotScatter($i, true); + + break; + default: + $this->graph = null; + + return false; + } + } + + $this->renderLegend(); + + $this->graph->Stroke($outputDestination); + + return true; + } + + public function render($outputDestination) + { + self::$plotColour = 0; + + $groupCount = $this->chart->getPlotArea()->getPlotGroupCount(); + + $dimensions = null; + if ($groupCount == 1) { + $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType(); + } else { + $chartTypes = []; + for ($i = 0; $i < $groupCount; ++$i) { + $chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); + } + $chartTypes = array_unique($chartTypes); + if (count($chartTypes) == 1) { + $chartType = array_pop($chartTypes); + } elseif (count($chartTypes) == 0) { + echo 'Chart is not yet implemented
'; + + return false; + } else { + return $this->renderCombinationChart($groupCount, $outputDestination); + } + } + + switch ($chartType) { + case 'area3DChart': + $dimensions = '3d'; + // no break + case 'areaChart': + $this->renderAreaChart($groupCount); + + break; + case 'bar3DChart': + $dimensions = '3d'; + // no break + case 'barChart': + $this->renderBarChart($groupCount, $dimensions); + + break; + case 'line3DChart': + $dimensions = '3d'; + // no break + case 'lineChart': + $this->renderLineChart($groupCount); + + break; + case 'pie3DChart': + $dimensions = '3d'; + // no break + case 'pieChart': + $this->renderPieChart($groupCount, $dimensions, false, false); + + break; + case 'doughnut3DChart': + $dimensions = '3d'; + // no break + case 'doughnutChart': + $this->renderPieChart($groupCount, $dimensions, true, true); + + break; + case 'scatterChart': + $this->renderScatterChart($groupCount); + + break; + case 'bubbleChart': + $this->renderBubbleChart($groupCount); + + break; + case 'radarChart': + $this->renderRadarChart($groupCount); + + break; + case 'surface3DChart': + case 'surfaceChart': + $this->renderContourChart($groupCount); + + break; + case 'stockChart': + $this->renderStockChart($groupCount); + + break; + default: + echo $chartType . ' is not yet implemented
'; + + return false; + } + $this->renderLegend(); + + $this->graph->Stroke($outputDestination); + + return true; + } +} diff --git a/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php b/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php new file mode 100644 index 00000000..e1f0f90a --- /dev/null +++ b/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php @@ -0,0 +1,36 @@ +setTrendLineProperties( + $trendLineType, + $order, + $period, + $dispRSqr, + $dispEq, + $backward, + $forward, + $intercept, + $name + ); + } + + public function getTrendLineType(): string + { + return $this->trendLineType; + } + + public function setTrendLineType(string $trendLineType): self + { + $this->trendLineType = $trendLineType; + + return $this; + } + + public function getOrder(): int + { + return $this->order; + } + + public function setOrder(int $order): self + { + $this->order = $order; + + return $this; + } + + public function getPeriod(): int + { + return $this->period; + } + + public function setPeriod(int $period): self + { + $this->period = $period; + + return $this; + } + + public function getDispRSqr(): bool + { + return $this->dispRSqr; + } + + public function setDispRSqr(bool $dispRSqr): self + { + $this->dispRSqr = $dispRSqr; + + return $this; + } + + public function getDispEq(): bool + { + return $this->dispEq; + } + + public function setDispEq(bool $dispEq): self + { + $this->dispEq = $dispEq; + + return $this; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getBackward(): float + { + return $this->backward; + } + + public function setBackward(float $backward): self + { + $this->backward = $backward; + + return $this; + } + + public function getForward(): float + { + return $this->forward; + } + + public function setForward(float $forward): self + { + $this->forward = $forward; + + return $this; + } + + public function getIntercept(): float + { + return $this->intercept; + } + + public function setIntercept(float $intercept): self + { + $this->intercept = $intercept; + + return $this; + } + + public function setTrendLineProperties( + ?string $trendLineType = null, + ?int $order = 0, + ?int $period = 0, + ?bool $dispRSqr = false, + ?bool $dispEq = false, + ?float $backward = null, + ?float $forward = null, + ?float $intercept = null, + ?string $name = null + ): self { + if (!empty($trendLineType)) { + $this->setTrendLineType($trendLineType); + } + if ($order !== null) { + $this->setOrder($order); + } + if ($period !== null) { + $this->setPeriod($period); + } + if ($dispRSqr !== null) { + $this->setDispRSqr($dispRSqr); + } + if ($dispEq !== null) { + $this->setDispEq($dispEq); + } + if ($backward !== null) { + $this->setBackward($backward); + } + if ($forward !== null) { + $this->setForward($forward); + } + if ($intercept !== null) { + $this->setIntercept($intercept); + } + if ($name !== null) { + $this->setName($name); + } + + return $this; + } +} diff --git a/src/PhpSpreadsheet/Collection/Cells.php b/src/PhpSpreadsheet/Collection/Cells.php index 20fccf48..82c9ae1a 100644 --- a/src/PhpSpreadsheet/Collection/Cells.php +++ b/src/PhpSpreadsheet/Collection/Cells.php @@ -91,10 +91,8 @@ class Cells * Whether the collection holds a cell for the given coordinate. * * @param string $cellCoordinate Coordinate of the cell to check - * - * @return bool */ - public function has($cellCoordinate) + public function has($cellCoordinate): bool { return ($cellCoordinate === $this->currentCoordinate) || isset($this->index[$cellCoordinate]); } @@ -103,10 +101,8 @@ class Cells * Add or update a cell in the collection. * * @param Cell $cell Cell to update - * - * @return Cell */ - public function update(Cell $cell) + public function update(Cell $cell): Cell { return $this->add($cell->getCoordinate(), $cell); } @@ -165,10 +161,8 @@ class Cells /** * Return the column coordinate of the currently active cell object. - * - * @return string */ - public function getCurrentColumn() + public function getCurrentColumn(): string { sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row); @@ -177,10 +171,8 @@ class Cells /** * Return the row coordinate of the currently active cell object. - * - * @return int */ - public function getCurrentRow() + public function getCurrentRow(): int { sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row); @@ -276,7 +268,9 @@ class Cells */ private function getUniqueID() { - return Settings::getCache() instanceof Memory + $cacheType = Settings::getCache(); + + return ($cacheType instanceof Memory\SimpleCache1 || $cacheType instanceof Memory\SimpleCache3) ? random_bytes(7) . ':' : uniqid('phpspreadsheet.', true) . '.'; } diff --git a/src/PhpSpreadsheet/Collection/CellsFactory.php b/src/PhpSpreadsheet/Collection/CellsFactory.php index 26f18dfc..b3833bd8 100644 --- a/src/PhpSpreadsheet/Collection/CellsFactory.php +++ b/src/PhpSpreadsheet/Collection/CellsFactory.php @@ -12,9 +12,8 @@ abstract class CellsFactory * * @param Worksheet $worksheet Enable cell caching for this worksheet * - * @return Cells * */ - public static function getInstance(Worksheet $worksheet) + public static function getInstance(Worksheet $worksheet): Cells { return new Cells($worksheet, Settings::getCache()); } diff --git a/src/PhpSpreadsheet/Collection/Memory.php b/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php similarity index 93% rename from src/PhpSpreadsheet/Collection/Memory.php rename to src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php index 2690ab7d..a0eb6ec2 100644 --- a/src/PhpSpreadsheet/Collection/Memory.php +++ b/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php @@ -1,6 +1,6 @@ cache = []; + + return true; + } + + /** + * @param string $key + */ + public function delete($key): bool + { + unset($this->cache[$key]); + + return true; + } + + /** + * @param iterable $keys + */ + public function deleteMultiple($keys): bool + { + foreach ($keys as $key) { + $this->delete($key); + } + + return true; + } + + /** + * @param string $key + * @param mixed $default + */ + public function get($key, $default = null): mixed + { + if ($this->has($key)) { + return $this->cache[$key]; + } + + return $default; + } + + /** + * @param iterable $keys + * @param mixed $default + */ + public function getMultiple($keys, $default = null): iterable + { + $results = []; + foreach ($keys as $key) { + $results[$key] = $this->get($key, $default); + } + + return $results; + } + + /** + * @param string $key + */ + public function has($key): bool + { + return array_key_exists($key, $this->cache); + } + + /** + * @param string $key + * @param mixed $value + * @param null|DateInterval|int $ttl + */ + public function set($key, $value, $ttl = null): bool + { + $this->cache[$key] = $value; + + return true; + } + + /** + * @param iterable $values + * @param null|DateInterval|int $ttl + */ + public function setMultiple($values, $ttl = null): bool + { + foreach ($values as $key => $value) { + $this->set($key, $value); + } + + return true; + } +} diff --git a/src/PhpSpreadsheet/Helper/Dimension.php b/src/PhpSpreadsheet/Helper/Dimension.php index 425c9a61..ff07ce5b 100644 --- a/src/PhpSpreadsheet/Helper/Dimension.php +++ b/src/PhpSpreadsheet/Helper/Dimension.php @@ -55,10 +55,20 @@ class Dimension */ protected $unit; + /** + * Phpstan bug has been fixed; this function allows us to + * pass Phpstan whether fixed or not. + * + * @param mixed $value + */ + private static function stanBugFixed($value): array + { + return is_array($value) ? $value : [null, null]; + } + public function __construct(string $dimension) { - // @phpstan-ignore-next-line - [$size, $unit] = sscanf($dimension, '%[1234567890.]%s'); + [$size, $unit] = self::stanBugFixed(sscanf($dimension, '%[1234567890.]%s')); $unit = strtolower(trim($unit ?? '')); $size = (float) $size; diff --git a/src/PhpSpreadsheet/Helper/Sample.php b/src/PhpSpreadsheet/Helper/Sample.php index 8ce37003..9f7563d6 100644 --- a/src/PhpSpreadsheet/Helper/Sample.php +++ b/src/PhpSpreadsheet/Helper/Sample.php @@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Helper; use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Writer\IWriter; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; @@ -182,6 +183,33 @@ class Sample echo date('H:i:s ') . $message . $eol; } + public function titles(string $category, string $functionName, ?string $description = null): void + { + $this->log(sprintf('%s Functions:', $category)); + $description === null + ? $this->log(sprintf('Function: %s()', rtrim($functionName, '()'))) + : $this->log(sprintf('Function: %s() - %s.', rtrim($functionName, '()'), rtrim($description, '.'))); + } + + public function displayGrid(array $matrix): void + { + $renderer = new TextGrid($matrix, $this->isCli()); + echo $renderer->render(); + } + + public function logCalculationResult( + Worksheet $worksheet, + string $functionName, + string $formulaCell, + ?string $descriptionCell = null + ): void { + if ($descriptionCell !== null) { + $this->log($worksheet->getCell($descriptionCell)->getValue()); + } + $this->log($worksheet->getCell($formulaCell)->getValue()); + $this->log(sprintf('%s() Result is ', $functionName) . $worksheet->getCell($formulaCell)->getCalculatedValue()); + } + /** * Log ending notes. */ diff --git a/src/PhpSpreadsheet/Helper/TextGrid.php b/src/PhpSpreadsheet/Helper/TextGrid.php new file mode 100644 index 00000000..acb9ae60 --- /dev/null +++ b/src/PhpSpreadsheet/Helper/TextGrid.php @@ -0,0 +1,139 @@ +rows = array_keys($matrix); + $this->columns = array_keys($matrix[$this->rows[0]]); + + $matrix = array_values($matrix); + array_walk( + $matrix, + function (&$row): void { + $row = array_values($row); + } + ); + + $this->matrix = $matrix; + $this->isCli = $isCli; + } + + public function render(): string + { + $this->gridDisplay = $this->isCli ? '' : ''; + + $maxRow = max($this->rows); + $maxRowLength = strlen((string) $maxRow) + 1; + $columnWidths = $this->getColumnWidths($this->matrix); + + $this->renderColumnHeader($maxRowLength, $columnWidths); + $this->renderRows($maxRowLength, $columnWidths); + $this->renderFooter($maxRowLength, $columnWidths); + + $this->gridDisplay .= $this->isCli ? '' : ''; + + return $this->gridDisplay; + } + + private function renderRows(int $maxRowLength, array $columnWidths): void + { + foreach ($this->matrix as $row => $rowData) { + $this->gridDisplay .= '|' . str_pad((string) $this->rows[$row], $maxRowLength, ' ', STR_PAD_LEFT) . ' '; + $this->renderCells($rowData, $columnWidths); + $this->gridDisplay .= '|' . PHP_EOL; + } + } + + private function renderCells(array $rowData, array $columnWidths): void + { + foreach ($rowData as $column => $cell) { + $cell = ($this->isCli) ? (string) $cell : htmlentities((string) $cell); + $this->gridDisplay .= '| '; + $this->gridDisplay .= str_pad($cell, $columnWidths[$column] + 1, ' '); + } + } + + private function renderColumnHeader(int $maxRowLength, array $columnWidths): void + { + $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2); + foreach ($this->columns as $column => $reference) { + $this->gridDisplay .= '+-' . str_repeat('-', $columnWidths[$column] + 1); + } + $this->gridDisplay .= '+' . PHP_EOL; + + $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2); + foreach ($this->columns as $column => $reference) { + $this->gridDisplay .= '| ' . str_pad((string) $reference, $columnWidths[$column] + 1, ' '); + } + $this->gridDisplay .= '|' . PHP_EOL; + + $this->renderFooter($maxRowLength, $columnWidths); + } + + private function renderFooter(int $maxRowLength, array $columnWidths): void + { + $this->gridDisplay .= '+' . str_repeat('-', $maxRowLength + 1); + foreach ($this->columns as $column => $reference) { + $this->gridDisplay .= '+-'; + $this->gridDisplay .= str_pad((string) '', $columnWidths[$column] + 1, '-'); + } + $this->gridDisplay .= '+' . PHP_EOL; + } + + private function getColumnWidths(array $matrix): array + { + $columnCount = count($this->matrix, COUNT_RECURSIVE) / count($this->matrix); + $columnWidths = []; + for ($column = 0; $column < $columnCount; ++$column) { + $columnWidths[] = $this->getColumnWidth(array_column($this->matrix, $column)); + } + + return $columnWidths; + } + + private function getColumnWidth(array $columnData): int + { + $columnWidth = 0; + $columnData = array_values($columnData); + + foreach ($columnData as $columnValue) { + if (is_string($columnValue)) { + $columnWidth = max($columnWidth, strlen($columnValue)); + } elseif (is_bool($columnValue)) { + $columnWidth = max($columnWidth, strlen($columnValue ? 'TRUE' : 'FALSE')); + } + + $columnWidth = max($columnWidth, strlen((string) $columnWidth)); + } + + return $columnWidth; + } +} diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php index ca087e61..1dcb0a12 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric.php @@ -363,7 +363,7 @@ class Gnumeric extends BaseReader if ($sheet !== null && isset($sheet->MergedRegions)) { foreach ($sheet->MergedRegions->Merge as $mergeCells) { if (strpos((string) $mergeCells, ':') !== false) { - $this->spreadsheet->getActiveSheet()->mergeCells($mergeCells); + $this->spreadsheet->getActiveSheet()->mergeCells($mergeCells, Worksheet::MERGE_CELL_CONTENT_HIDE); } } } diff --git a/src/PhpSpreadsheet/Reader/Ods.php b/src/PhpSpreadsheet/Reader/Ods.php index 7e776ab7..e3de4731 100644 --- a/src/PhpSpreadsheet/Reader/Ods.php +++ b/src/PhpSpreadsheet/Reader/Ods.php @@ -20,6 +20,7 @@ use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use Throwable; use XMLReader; use ZipArchive; @@ -759,7 +760,7 @@ class Ods extends BaseReader } $cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo; - $spreadsheet->getActiveSheet()->mergeCells($cellRange); + $spreadsheet->getActiveSheet()->mergeCells($cellRange, Worksheet::MERGE_CELL_CONTENT_HIDE); } } } diff --git a/src/PhpSpreadsheet/Reader/Security/XmlScanner.php b/src/PhpSpreadsheet/Reader/Security/XmlScanner.php index 8155b838..40008d01 100644 --- a/src/PhpSpreadsheet/Reader/Security/XmlScanner.php +++ b/src/PhpSpreadsheet/Reader/Security/XmlScanner.php @@ -52,7 +52,7 @@ class XmlScanner public static function threadSafeLibxmlDisableEntityLoaderAvailability() { - if (PHP_MAJOR_VERSION == 7) { + if (PHP_MAJOR_VERSION === 7) { switch (PHP_MINOR_VERSION) { case 2: return PHP_RELEASE_VERSION >= 1; diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index 9f8a3ace..a8de5228 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -4585,7 +4585,7 @@ class Xls extends BaseReader (strpos($cellRangeAddress, ':') !== false) && ($this->includeCellRangeFiltered($cellRangeAddress)) ) { - $this->phpSheet->mergeCells($cellRangeAddress); + $this->phpSheet->mergeCells($cellRangeAddress, Worksheet::MERGE_CELL_CONTENT_HIDE); } } } @@ -6999,7 +6999,7 @@ class Xls extends BaseReader } break; - // Unknown cases // don't know how to deal with + // Unknown cases // don't know how to deal with default: throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula'); diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 52df94e4..26cd1af3 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -31,6 +31,7 @@ use PhpOffice\PhpSpreadsheet\Shared\Font; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Color; +use PhpOffice\PhpSpreadsheet\Style\Font as StyleFont; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing; @@ -293,7 +294,7 @@ class Xlsx extends BaseReader return $worksheetInfo; } - private static function castToBoolean($c) + private static function castToBoolean(SimpleXMLElement $c): bool { $value = isset($c->v) ? (string) $c->v : null; if ($value == '0') { @@ -305,18 +306,25 @@ class Xlsx extends BaseReader return (bool) $c->v; } - private static function castToError($c) + private static function castToError(?SimpleXMLElement $c): ?string { - return isset($c->v) ? (string) $c->v : null; + return isset($c, $c->v) ? (string) $c->v : null; } - private static function castToString($c) + private static function castToString(?SimpleXMLElement $c): ?string { - return isset($c->v) ? (string) $c->v : null; + return isset($c, $c->v) ? (string) $c->v : null; } - private function castToFormula($c, $r, &$cellDataType, &$value, &$calculatedValue, &$sharedFormulas, $castBaseType): void + /** + * @param mixed $value + * @param mixed $calculatedValue + */ + private function castToFormula(?SimpleXMLElement $c, string $r, string &$cellDataType, &$value, &$calculatedValue, array &$sharedFormulas, string $castBaseType): void { + if ($c === null) { + return; + } $attr = $c->f->attributes(); $cellDataType = 'f'; $value = "={$c->f}"; @@ -389,7 +397,7 @@ class Xlsx extends BaseReader $contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE); } - return $contents; + return ($contents === false) ? '' : $contents; } /** @@ -479,7 +487,7 @@ class Xlsx extends BaseReader $propertyReader->readCustomProperties($this->getFromZipArchive($zip, $relTarget)); break; - //Ribbon + // Ribbon case Namespaces::EXTENSIBILITY: $customUI = $relTarget; if ($customUI) { @@ -530,7 +538,7 @@ class Xlsx extends BaseReader } break; - // a vbaProject ? (: some macros) + // a vbaProject ? (: some macros) case Namespaces::VBA: $macros = $ele['Target']; @@ -906,7 +914,7 @@ class Xlsx extends BaseReader foreach ($xmlSheet->mergeCells->mergeCell as $mergeCell) { $mergeRef = (string) $mergeCell['ref']; if (strpos($mergeRef, ':') !== false) { - $docSheet->mergeCells((string) $mergeCell['ref']); + $docSheet->mergeCells((string) $mergeCell['ref'], Worksheet::MERGE_CELL_CONTENT_HIDE); } } } @@ -1143,7 +1151,7 @@ class Xlsx extends BaseReader } // Header/footer images - if ($xmlSheet && $xmlSheet->legacyDrawingHF && !$this->readDataOnly) { + if ($xmlSheet && $xmlSheet->legacyDrawingHF) { if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { $relsWorksheet = $this->loadZipNoNamespace(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels', Namespaces::RELATIONSHIPS); $vmlRelationship = ''; @@ -1550,7 +1558,7 @@ class Xlsx extends BaseReader break; case '_xlnm.Print_Area': - $rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) ?: []; $newRangeSets = []; foreach ($rangeSets as $rangeSet) { [, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true); @@ -1605,7 +1613,7 @@ class Xlsx extends BaseReader if (strpos((string) $definedName, '!') !== false) { $range[0] = str_replace("''", "'", $range[0]); $range[0] = str_replace("'", '', $range[0]); - if ($worksheet = $excel->getSheetByName($range[0])) { + if ($worksheet = $excel->getSheetByName($range[0])) { // @phpstan-ignore-line $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $worksheet, $extractedRange, true, $scope)); } else { $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true, $scope)); @@ -1626,7 +1634,7 @@ class Xlsx extends BaseReader // Need to split on a comma or a space if not in quotes, and extract the first part. $definedNameValueParts = preg_split("/[ ,](?=([^']*'[^']*')*[^']*$)/miuU", $definedRange); // Extract sheet name - [$extractedSheetName] = Worksheet::extractSheetTitle((string) $definedNameValueParts[0], true); + [$extractedSheetName] = Worksheet::extractSheetTitle((string) $definedNameValueParts[0], true); // @phpstan-ignore-line $extractedSheetName = trim($extractedSheetName, "'"); // Locate sheet @@ -1673,7 +1681,7 @@ class Xlsx extends BaseReader if (isset($charts[$chartEntryRef])) { $chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id']; if (isset($chartDetails[$chartPositionRef])) { - $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart); + $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart); // @phpstan-ignore-line $objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet'])); // For oneCellAnchor or absoluteAnchor positioned charts, // toCoordinate is not in the data. Does it need to be calculated? @@ -1695,8 +1703,8 @@ class Xlsx extends BaseReader break; - // unparsed case 'application/vnd.ms-excel.controlproperties+xml': + // unparsed $unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType']; break; @@ -1721,29 +1729,29 @@ class Xlsx extends BaseReader if (isset($is->t)) { $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $is->t)); } else { - if (is_object($is->r)) { - + if (isset($is->r) && is_object($is->r)) { /** @var SimpleXMLElement $run */ foreach ($is->r as $run) { if (!isset($run->rPr)) { $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $run->t)); } else { $objText = $value->createTextRun(StringHelper::controlCharacterOOXML2PHP((string) $run->t)); + $objFont = $objText->getFont() ?? new StyleFont(); if (isset($run->rPr->rFont)) { $attr = $run->rPr->rFont->attributes(); if (isset($attr['val'])) { - $objText->getFont()->setName((string) $attr['val']); + $objFont->setName((string) $attr['val']); } } if (isset($run->rPr->sz)) { $attr = $run->rPr->sz->attributes(); if (isset($attr['val'])) { - $objText->getFont()->setSize((float) $attr['val']); + $objFont->setSize((float) $attr['val']); } } if (isset($run->rPr->color)) { - $objText->getFont()->setColor(new Color($this->styleReader->readColor($run->rPr->color))); + $objFont->setColor(new Color($this->styleReader->readColor($run->rPr->color))); } if (isset($run->rPr->b)) { $attr = $run->rPr->b->attributes(); @@ -1751,7 +1759,7 @@ class Xlsx extends BaseReader (isset($attr['val']) && self::boolean((string) $attr['val'])) || (!isset($attr['val'])) ) { - $objText->getFont()->setBold(true); + $objFont->setBold(true); } } if (isset($run->rPr->i)) { @@ -1760,7 +1768,7 @@ class Xlsx extends BaseReader (isset($attr['val']) && self::boolean((string) $attr['val'])) || (!isset($attr['val'])) ) { - $objText->getFont()->setItalic(true); + $objFont->setItalic(true); } } if (isset($run->rPr->vertAlign)) { @@ -1768,19 +1776,19 @@ class Xlsx extends BaseReader if (isset($attr['val'])) { $vertAlign = strtolower((string) $attr['val']); if ($vertAlign == 'superscript') { - $objText->getFont()->setSuperscript(true); + $objFont->setSuperscript(true); } if ($vertAlign == 'subscript') { - $objText->getFont()->setSubscript(true); + $objFont->setSubscript(true); } } } if (isset($run->rPr->u)) { $attr = $run->rPr->u->attributes(); if (!isset($attr['val'])) { - $objText->getFont()->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE); + $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE); } else { - $objText->getFont()->setUnderline((string) $attr['val']); + $objFont->setUnderline((string) $attr['val']); } } if (isset($run->rPr->strike)) { @@ -1789,7 +1797,7 @@ class Xlsx extends BaseReader (isset($attr['val']) && self::boolean((string) $attr['val'])) || (!isset($attr['val'])) ) { - $objText->getFont()->setStrikethrough(true); + $objFont->setStrikethrough(true); } } } @@ -1842,17 +1850,30 @@ class Xlsx extends BaseReader } } + /** + * @param null|array|bool|SimpleXMLElement $array + * @param int|string $key + * + * @return mixed + */ private static function getArrayItem($array, $key = 0) { - return $array[$key] ?? null; + return ($array === null || is_bool($array)) ? null : ($array[$key] ?? null); } + /** + * @param null|SimpleXMLElement|string $base + * @param null|SimpleXMLElement|string $add + */ private static function dirAdd($base, $add): string { + $base = (string) $base; + $add = (string) $add; + return (string) preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add"); } - private static function toCSSArray($style): array + private static function toCSSArray(string $style): array { $style = self::stripWhiteSpaceFromStyleString($style); @@ -1866,15 +1887,15 @@ class Xlsx extends BaseReader } if (strpos($item[1], 'pt') !== false) { $item[1] = str_replace('pt', '', $item[1]); - $item[1] = Font::fontSizeToPixels($item[1]); + $item[1] = (string) Font::fontSizeToPixels((int) $item[1]); } if (strpos($item[1], 'in') !== false) { $item[1] = str_replace('in', '', $item[1]); - $item[1] = Font::inchSizeToPixels($item[1]); + $item[1] = (string) Font::inchSizeToPixels((int) $item[1]); } if (strpos($item[1], 'cm') !== false) { $item[1] = str_replace('cm', '', $item[1]); - $item[1] = Font::centimeterSizeToPixels($item[1]); + $item[1] = (string) Font::centimeterSizeToPixels((int) $item[1]); } $style[$item[0]] = $item[1]; @@ -1883,11 +1904,14 @@ class Xlsx extends BaseReader return $style; } - public static function stripWhiteSpaceFromStyleString($string): string + public static function stripWhiteSpaceFromStyleString(string $string): string { return trim(str_replace(["\r", "\n", ' '], '', $string), ';'); } + /** + * @param mixed $value + */ private static function boolean($value): bool { if (is_object($value)) { @@ -1956,7 +1980,7 @@ class Xlsx extends BaseReader return $returnValue; } - private function readFormControlProperties(Spreadsheet $excel, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void + private function readFormControlProperties(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void { $zip = $this->zip; if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { @@ -1983,7 +2007,7 @@ class Xlsx extends BaseReader unset($unparsedCtrlProps); } - private function readPrinterSettings(Spreadsheet $excel, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void + private function readPrinterSettings(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void { $zip = $this->zip; if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { @@ -2071,7 +2095,7 @@ class Xlsx extends BaseReader if ($xmlSheet && $xmlSheet->autoFilter) { // In older files, autofilter structure is defined in the worksheet file (new AutoFilter($docSheet, $xmlSheet))->load(); - } elseif ($xmlSheet && $xmlSheet->tableParts && $xmlSheet->tableParts['count'] > 0) { + } elseif ($xmlSheet && $xmlSheet->tableParts && (int) $xmlSheet->tableParts['count'] > 0) { // But for Office365, MS decided to make it all just a bit more complicated $this->readAutoFilterTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet); } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php index 374da9f6..39328adb 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php @@ -9,8 +9,10 @@ use SimpleXMLElement; class AutoFilter { + /** @var Worksheet */ private $worksheet; + /** @var SimpleXMLElement */ private $worksheetXml; public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml) @@ -28,7 +30,7 @@ class AutoFilter } } - private function readAutoFilter($autoFilterRange, $xmlSheet): void + private function readAutoFilter(string $autoFilterRange, SimpleXMLElement $xmlSheet): void { $autoFilter = $this->worksheet->getAutoFilter(); $autoFilter->setRange($autoFilterRange); @@ -39,15 +41,15 @@ class AutoFilter if ($filterColumn->filters) { $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER); $filters = $filterColumn->filters; - if ((isset($filters['blank'])) && ($filters['blank'] == 1)) { + if ((isset($filters['blank'])) && ((int) $filters['blank'] == 1)) { // Operator is undefined, but always treated as EQUAL - $column->createRule()->setRule(null, '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); + $column->createRule()->setRule('', '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); } // Standard filters are always an OR join, so no join rule needs to be set // Entries can be either filter elements foreach ($filters->filter as $filterRule) { // Operator is undefined, but always treated as EQUAL - $column->createRule()->setRule(null, (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); + $column->createRule()->setRule('', (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); } // Or Date Group elements @@ -69,7 +71,7 @@ class AutoFilter foreach ($filters->dateGroupItem as $dateGroupItem) { // Operator is undefined, but always treated as EQUAL $column->createRule()->setRule( - null, + '', [ 'year' => (string) $dateGroupItem['year'], 'month' => (string) $dateGroupItem['month'], @@ -83,9 +85,9 @@ class AutoFilter } } - private function readCustomAutoFilter(SimpleXMLElement $filterColumn, Column $column): void + private function readCustomAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void { - if ($filterColumn->customFilters) { + if (isset($filterColumn, $filterColumn->customFilters)) { $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER); $customFilters = $filterColumn->customFilters; // Custom filters can an AND or an OR join; @@ -102,15 +104,15 @@ class AutoFilter } } - private function readDynamicAutoFilter(SimpleXMLElement $filterColumn, Column $column): void + private function readDynamicAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void { - if ($filterColumn->dynamicFilter) { + if (isset($filterColumn, $filterColumn->dynamicFilter)) { $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); // We should only ever have one dynamic filter foreach ($filterColumn->dynamicFilter as $filterRule) { // Operator is undefined, but always treated as EQUAL $column->createRule()->setRule( - null, + '', (string) $filterRule['val'], (string) $filterRule['type'] )->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); @@ -124,9 +126,9 @@ class AutoFilter } } - private function readTopTenAutoFilter(SimpleXMLElement $filterColumn, Column $column): void + private function readTopTenAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void { - if ($filterColumn->top10) { + if (isset($filterColumn, $filterColumn->top10)) { $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER); // We should only ever have one top10 filter foreach ($filterColumn->top10 as $filterRule) { diff --git a/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php b/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php index 1679f01f..2f146458 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php @@ -4,7 +4,10 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; class BaseParserClass { - protected static function boolean($value) + /** + * @param mixed $value + */ + protected static function boolean($value): bool { if (is_object($value)) { $value = (string) $value; diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index d49a5238..c22334ca 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -11,8 +11,10 @@ use PhpOffice\PhpSpreadsheet\Chart\GridLines; use PhpOffice\PhpSpreadsheet\Chart\Layout; use PhpOffice\PhpSpreadsheet\Chart\Legend; use PhpOffice\PhpSpreadsheet\Chart\PlotArea; -use PhpOffice\PhpSpreadsheet\Chart\Properties; +use PhpOffice\PhpSpreadsheet\Chart\Properties as ChartProperties; use PhpOffice\PhpSpreadsheet\Chart\Title; +use PhpOffice\PhpSpreadsheet\Chart\TrendLine; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Style\Font; use SimpleXMLElement; @@ -73,11 +75,27 @@ class Chart $xAxis = new Axis(); $yAxis = new Axis(); $autoTitleDeleted = null; + $chartNoFill = false; + $gradientArray = []; + $gradientLin = null; + $roundedCorners = false; foreach ($chartElementsC as $chartElementKey => $chartElement) { switch ($chartElementKey) { + case 'spPr': + $possibleNoFill = $chartElementsC->spPr->children($this->aNamespace); + if (isset($possibleNoFill->noFill)) { + $chartNoFill = true; + } + + break; + case 'roundedCorners': + /** @var bool */ + $roundedCorners = self::getAttribute($chartElementsC->roundedCorners, 'val', 'boolean'); + + break; case 'chart': foreach ($chartElement as $chartDetailsKey => $chartDetails) { - $chartDetailsC = $chartDetails->children($this->cNamespace); + $chartDetails = Xlsx::testSimpleXml($chartDetails); switch ($chartDetailsKey) { case 'autoTitleDeleted': /** @var bool */ @@ -95,18 +113,42 @@ class Chart $plotAreaLayout = $XaxisLabel = $YaxisLabel = null; $plotSeries = $plotAttributes = []; $catAxRead = false; + $plotNoFill = false; foreach ($chartDetails as $chartDetailKey => $chartDetail) { + $chartDetail = Xlsx::testSimpleXml($chartDetail); switch ($chartDetailKey) { + case 'spPr': + $possibleNoFill = $chartDetails->spPr->children($this->aNamespace); + if (isset($possibleNoFill->noFill)) { + $plotNoFill = true; + } + if (isset($possibleNoFill->gradFill->gsLst)) { + foreach ($possibleNoFill->gradFill->gsLst->gs as $gradient) { + $gradient = Xlsx::testSimpleXml($gradient); + /** @var float */ + $pos = self::getAttribute($gradient, 'pos', 'float'); + $gradientArray[] = [ + $pos / ChartProperties::PERCENTAGE_MULTIPLIER, + new ChartColor($this->readColor($gradient)), + ]; + } + } + if (isset($possibleNoFill->gradFill->lin)) { + $gradientLin = ChartProperties::XmlToAngle((string) self::getAttribute($possibleNoFill->gradFill->lin, 'ang', 'string')); + } + + break; case 'layout': $plotAreaLayout = $this->chartLayoutDetails($chartDetail); break; - case 'catAx': + case Axis::AXIS_TYPE_CATEGORY: + case Axis::AXIS_TYPE_DATE: $catAxRead = true; if (isset($chartDetail->title)) { $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace)); } - $xAxis->setAxisType('catAx'); + $xAxis->setAxisType($chartDetailKey); $this->readEffects($chartDetail, $xAxis); if (isset($chartDetail->spPr)) { $sppr = $chartDetail->spPr->children($this->aNamespace); @@ -135,13 +177,7 @@ class Chart $this->setAxisProperties($chartDetail, $xAxis); break; - case 'dateAx': - if (isset($chartDetail->title)) { - $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace)); - } - - break; - case 'valAx': + case Axis::AXIS_TYPE_VALUE: $whichAxis = null; $axPos = null; if (isset($chartDetail->axPos)) { @@ -278,7 +314,7 @@ class Chart break; case 'stockChart': $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey); - $plotAttributes = $this->readChartAttributes($plotAreaLayout); + $plotAttributes = $this->readChartAttributes($chartDetail); break; } @@ -288,6 +324,12 @@ class Chart } $plotArea = new PlotArea($plotAreaLayout, $plotSeries); $this->setChartAttributes($plotAreaLayout, $plotAttributes); + if ($plotNoFill) { + $plotArea->setNoFill(true); + } + if (!empty($gradientArray)) { + $plotArea->setGradientFillProperties($gradientArray, $gradientLin); + } break; case 'plotVisOnly': @@ -307,6 +349,7 @@ class Chart $legendLayout = null; $legendOverlay = false; foreach ($chartDetails as $chartDetailKey => $chartDetail) { + $chartDetail = Xlsx::testSimpleXml($chartDetail); switch ($chartDetailKey) { case 'legendPos': $legendPos = self::getAttribute($chartDetail, 'val', 'string'); @@ -330,6 +373,10 @@ class Chart } } $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis); + if ($chartNoFill) { + $chart->setNoFill(true); + } + $chart->setRoundedCorners($roundedCorners); if (is_bool($autoTitleDeleted)) { $chart->setAutoTitleDeleted($autoTitleDeleted); } @@ -354,14 +401,24 @@ class Chart $caption = []; $titleLayout = null; foreach ($titleDetails as $titleDetailKey => $chartDetail) { + $chartDetail = Xlsx::testSimpleXml($chartDetail); switch ($titleDetailKey) { case 'tx': - $titleDetails = $chartDetail->rich->children($this->aNamespace); - foreach ($titleDetails as $titleKey => $titleDetail) { - switch ($titleKey) { - case 'p': - $titleDetailPart = $titleDetail->children($this->aNamespace); - $caption[] = $this->parseRichText($titleDetailPart); + if (isset($chartDetail->rich)) { + $titleDetails = $chartDetail->rich->children($this->aNamespace); + foreach ($titleDetails as $titleKey => $titleDetail) { + $titleDetail = Xlsx::testSimpleXml($titleDetail); + switch ($titleKey) { + case 'p': + $titleDetailPart = $titleDetail->children($this->aNamespace); + $caption[] = $this->parseRichText($titleDetailPart); + } + } + } elseif (isset($chartDetail->strRef->strCache)) { + foreach ($chartDetail->strRef->strCache->pt as $pt) { + if (isset($pt->v)) { + $caption[] = (string) $pt->v; + } } } @@ -387,6 +444,7 @@ class Chart } $layout = []; foreach ($details as $detailKey => $detail) { + $detail = Xlsx::testSimpleXml($detail); $layout[$detailKey] = self::getAttribute($detail, 'val', 'string'); } @@ -413,12 +471,14 @@ class Chart $pointSize = null; $noFill = false; $bubble3D = false; - $dPtColors = []; + $dptColors = []; $markerFillColor = null; $markerBorderColor = null; $lineStyle = null; $labelLayout = null; + $trendLines = []; foreach ($seriesDetails as $seriesKey => $seriesDetail) { + $seriesDetail = Xlsx::testSimpleXml($seriesDetail); switch ($seriesKey) { case 'idx': $seriesIndex = self::getAttribute($seriesDetail, 'val', 'integer'); @@ -435,7 +495,6 @@ class Chart break; case 'spPr': $children = $seriesDetail->children($this->aNamespace); - $ln = $children->ln; if (isset($children->ln)) { $ln = $children->ln; if (is_countable($ln->noFill) && count($ln->noFill) === 1) { @@ -465,6 +524,41 @@ class Chart } } + break; + case 'trendline': + $trendLine = new TrendLine(); + $this->readLineStyle($seriesDetail, $trendLine); + /** @var ?string */ + $trendLineType = self::getAttribute($seriesDetail->trendlineType, 'val', 'string'); + /** @var ?bool */ + $dispRSqr = self::getAttribute($seriesDetail->dispRSqr, 'val', 'boolean'); + /** @var ?bool */ + $dispEq = self::getAttribute($seriesDetail->dispEq, 'val', 'boolean'); + /** @var ?int */ + $order = self::getAttribute($seriesDetail->order, 'val', 'integer'); + /** @var ?int */ + $period = self::getAttribute($seriesDetail->period, 'val', 'integer'); + /** @var ?float */ + $forward = self::getAttribute($seriesDetail->forward, 'val', 'float'); + /** @var ?float */ + $backward = self::getAttribute($seriesDetail->backward, 'val', 'float'); + /** @var ?float */ + $intercept = self::getAttribute($seriesDetail->intercept, 'val', 'float'); + /** @var ?string */ + $name = (string) $seriesDetail->name; + $trendLine->setTrendLineProperties( + $trendLineType, + $order, + $period, + $dispRSqr, + $dispEq, + $backward, + $forward, + $intercept, + $name + ); + $trendLines[] = $trendLine; + break; case 'marker': $marker = self::getAttribute($seriesDetail->symbol, 'val', 'string'); @@ -603,6 +697,17 @@ class Chart $seriesValues[$seriesIndex]->setSmoothLine(true); } } + if (!empty($trendLines)) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->setTrendLines($trendLines); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->setTrendLines($trendLines); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->setTrendLines($trendLines); + } + } } } /** @phpstan-ignore-next-line */ @@ -686,6 +791,7 @@ class Chart $pointCount = 0; foreach ($seriesValueSet as $seriesValueIdx => $seriesValue) { + $seriesValue = Xlsx::testSimpleXml($seriesValue); switch ($seriesValueIdx) { case 'ptCount': $pointCount = self::getAttribute($seriesValue, 'val', 'integer'); @@ -758,7 +864,6 @@ class Chart private function parseRichText(SimpleXMLElement $titleDetailPart): RichText { $value = new RichText(); - $objText = null; $defaultFontSize = null; $defaultBold = null; $defaultItalic = null; @@ -970,7 +1075,7 @@ class Chart } /** - * @param null|Layout|SimpleXMLElement $chartDetail + * @param ?SimpleXMLElement $chartDetail */ private function readChartAttributes($chartDetail): array { @@ -1063,7 +1168,7 @@ class Chart } } - private function readEffects(SimpleXMLElement $chartDetail, ?Properties $chartObject): void + private function readEffects(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void { if (!isset($chartObject, $chartDetail->spPr)) { return; @@ -1071,7 +1176,7 @@ class Chart $sppr = $chartDetail->spPr->children($this->aNamespace); if (isset($sppr->effectLst->glow)) { - $axisGlowSize = (float) self::getAttribute($sppr->effectLst->glow, 'rad', 'integer') / Properties::POINTS_WIDTH_MULTIPLIER; + $axisGlowSize = (float) self::getAttribute($sppr->effectLst->glow, 'rad', 'integer') / ChartProperties::POINTS_WIDTH_MULTIPLIER; if ($axisGlowSize != 0.0) { $colorArray = $this->readColor($sppr->effectLst->glow); $chartObject->setGlowProperties($axisGlowSize, $colorArray['value'], $colorArray['alpha'], $colorArray['type']); @@ -1082,7 +1187,7 @@ class Chart /** @var string */ $softEdgeSize = self::getAttribute($sppr->effectLst->softEdge, 'rad', 'string'); if (is_numeric($softEdgeSize)) { - $chartObject->setSoftEdges((float) Properties::xmlToPoints($softEdgeSize)); + $chartObject->setSoftEdges((float) ChartProperties::xmlToPoints($softEdgeSize)); } } @@ -1097,20 +1202,20 @@ class Chart if ($type !== '') { /** @var string */ $blur = self::getAttribute($sppr->effectLst->$type, 'blurRad', 'string'); - $blur = is_numeric($blur) ? Properties::xmlToPoints($blur) : null; + $blur = is_numeric($blur) ? ChartProperties::xmlToPoints($blur) : null; /** @var string */ $dist = self::getAttribute($sppr->effectLst->$type, 'dist', 'string'); - $dist = is_numeric($dist) ? Properties::xmlToPoints($dist) : null; + $dist = is_numeric($dist) ? ChartProperties::xmlToPoints($dist) : null; /** @var string */ $direction = self::getAttribute($sppr->effectLst->$type, 'dir', 'string'); - $direction = is_numeric($direction) ? Properties::xmlToAngle($direction) : null; + $direction = is_numeric($direction) ? ChartProperties::xmlToAngle($direction) : null; $algn = self::getAttribute($sppr->effectLst->$type, 'algn', 'string'); $rot = self::getAttribute($sppr->effectLst->$type, 'rotWithShape', 'string'); $size = []; foreach (['sx', 'sy'] as $sizeType) { $sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string'); if (is_numeric($sizeValue)) { - $size[$sizeType] = Properties::xmlToTenthOfPercent((string) $sizeValue); + $size[$sizeType] = ChartProperties::xmlToTenthOfPercent((string) $sizeValue); } else { $size[$sizeType] = null; } @@ -1118,7 +1223,7 @@ class Chart foreach (['kx', 'ky'] as $sizeType) { $sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string'); if (is_numeric($sizeValue)) { - $size[$sizeType] = Properties::xmlToAngle((string) $sizeValue); + $size[$sizeType] = ChartProperties::xmlToAngle((string) $sizeValue); } else { $size[$sizeType] = null; } @@ -1147,6 +1252,7 @@ class Chart 'type' => null, 'value' => null, 'alpha' => null, + 'brightness' => null, ]; foreach (ChartColor::EXCEL_COLOR_TYPES as $type) { if (isset($colorXml->$type)) { @@ -1159,6 +1265,13 @@ class Chart $result['alpha'] = ChartColor::alphaFromXml($alpha); } } + if (isset($colorXml->$type->lumMod)) { + /** @var string */ + $brightness = self::getAttribute($colorXml->$type->lumMod, 'val', 'string'); + if (is_numeric($brightness)) { + $result['brightness'] = ChartColor::alphaFromXml($brightness); + } + } break; } @@ -1167,7 +1280,7 @@ class Chart return $result; } - private function readLineStyle(SimpleXMLElement $chartDetail, ?Properties $chartObject): void + private function readLineStyle(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void { if (!isset($chartObject, $chartDetail->spPr)) { return; @@ -1181,7 +1294,7 @@ class Chart /** @var string */ $lineWidthTemp = self::getAttribute($sppr->ln, 'w', 'string'); if (is_numeric($lineWidthTemp)) { - $lineWidth = Properties::xmlToPoints($lineWidthTemp); + $lineWidth = ChartProperties::xmlToPoints($lineWidthTemp); } /** @var string */ $compoundType = self::getAttribute($sppr->ln, 'cmpd', 'string'); @@ -1190,15 +1303,13 @@ class Chart /** @var string */ $capType = self::getAttribute($sppr->ln, 'cap', 'string'); if (isset($sppr->ln->miter)) { - $joinType = Properties::LINE_STYLE_JOIN_MITER; + $joinType = ChartProperties::LINE_STYLE_JOIN_MITER; } elseif (isset($sppr->ln->bevel)) { - $joinType = Properties::LINE_STYLE_JOIN_BEVEL; + $joinType = ChartProperties::LINE_STYLE_JOIN_BEVEL; } else { $joinType = ''; } - $headArrowType = ''; $headArrowSize = ''; - $endArrowType = ''; $endArrowSize = ''; /** @var string */ $headArrowType = self::getAttribute($sppr->ln->headEnd, 'type', 'string'); @@ -1236,6 +1347,9 @@ class Chart if (!isset($whichAxis)) { return; } + if (isset($chartDetail->delete)) { + $whichAxis->setAxisOption('hidden', (string) self::getAttribute($chartDetail->delete, 'val', 'string')); + } if (isset($chartDetail->numFmt)) { $whichAxis->setAxisNumberProperties( (string) self::getAttribute($chartDetail->numFmt, 'formatCode', 'string'), @@ -1279,13 +1393,22 @@ class Chart if (isset($chartDetail->minorUnit)) { $whichAxis->setAxisOption('minor_unit', (string) self::getAttribute($chartDetail->minorUnit, 'val', 'string')); } + if (isset($chartDetail->baseTimeUnit)) { + $whichAxis->setAxisOption('baseTimeUnit', (string) self::getAttribute($chartDetail->baseTimeUnit, 'val', 'string')); + } + if (isset($chartDetail->majorTimeUnit)) { + $whichAxis->setAxisOption('majorTimeUnit', (string) self::getAttribute($chartDetail->majorTimeUnit, 'val', 'string')); + } + if (isset($chartDetail->minorTimeUnit)) { + $whichAxis->setAxisOption('minorTimeUnit', (string) self::getAttribute($chartDetail->minorTimeUnit, 'val', 'string')); + } if (isset($chartDetail->txPr)) { $children = $chartDetail->txPr->children($this->aNamespace); if (isset($children->bodyPr)) { /** @var string */ $textRotation = self::getAttribute($children->bodyPr, 'rot', 'string'); if (is_numeric($textRotation)) { - $whichAxis->setAxisOption('textRotation', (string) Properties::xmlToAngle($textRotation)); + $whichAxis->setAxisOption('textRotation', (string) ChartProperties::xmlToAngle($textRotation)); } } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php b/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php index 2a1e2afd..34705733 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php @@ -10,8 +10,10 @@ use SimpleXMLElement; class ColumnAndRowAttributes extends BaseParserClass { + /** @var Worksheet */ private $worksheet; + /** @var ?SimpleXMLElement */ private $worksheetXml; public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null) @@ -120,7 +122,7 @@ class ColumnAndRowAttributes extends BaseParserClass } } - private function isFilteredColumn(IReadFilter $readFilter, $columnCoordinate, array $rowsAttributes) + private function isFilteredColumn(IReadFilter $readFilter, string $columnCoordinate, array $rowsAttributes): bool { foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) { if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) { @@ -131,7 +133,7 @@ class ColumnAndRowAttributes extends BaseParserClass return false; } - private function readColumnAttributes(SimpleXMLElement $worksheetCols, $readDataOnly) + private function readColumnAttributes(SimpleXMLElement $worksheetCols, bool $readDataOnly): array { $columnAttributes = []; @@ -151,7 +153,7 @@ class ColumnAndRowAttributes extends BaseParserClass return $columnAttributes; } - private function readColumnRangeAttributes(SimpleXMLElement $column, $readDataOnly) + private function readColumnRangeAttributes(SimpleXMLElement $column, bool $readDataOnly): array { $columnAttributes = []; @@ -172,7 +174,7 @@ class ColumnAndRowAttributes extends BaseParserClass return $columnAttributes; } - private function isFilteredRow(IReadFilter $readFilter, $rowCoordinate, array $columnsAttributes) + private function isFilteredRow(IReadFilter $readFilter, int $rowCoordinate, array $columnsAttributes): bool { foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) { if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) { @@ -183,7 +185,7 @@ class ColumnAndRowAttributes extends BaseParserClass return false; } - private function readRowAttributes(SimpleXMLElement $worksheetRow, $readDataOnly) + private function readRowAttributes(SimpleXMLElement $worksheetRow, bool $readDataOnly): array { $rowAttributes = []; diff --git a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php index c631a0fe..7d947bac 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php @@ -10,11 +10,14 @@ use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueO use PhpOffice\PhpSpreadsheet\Style\Style as Style; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; +use stdClass; class ConditionalStyles { + /** @var Worksheet */ private $worksheet; + /** @var SimpleXMLElement */ private $worksheetXml; /** @@ -22,6 +25,7 @@ class ConditionalStyles */ private $ns; + /** @var array */ private $dxfs; public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml, array $dxfs = []) @@ -146,7 +150,7 @@ class ConditionalStyles return $cfStyle; } - private function readConditionalStyles($xmlSheet): array + private function readConditionalStyles(SimpleXMLElement $xmlSheet): array { $conditionals = []; foreach ($xmlSheet->conditionalFormatting as $conditional) { @@ -162,7 +166,7 @@ class ConditionalStyles return $conditionals; } - private function setConditionalStyles(Worksheet $worksheet, array $conditionals, $xmlExtLst): void + private function setConditionalStyles(Worksheet $worksheet, array $conditionals, SimpleXMLElement $xmlExtLst): void { foreach ($conditionals as $cellRangeReference => $cfRules) { ksort($cfRules); @@ -176,7 +180,7 @@ class ConditionalStyles } } - private function readStyleRules($cfRules, $extLst) + private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array { $conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst); $conditionalStyles = []; @@ -213,7 +217,7 @@ class ConditionalStyles if (isset($cfRule->dataBar)) { $objConditional->setDataBar( - $this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions) + $this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions) // @phpstan-ignore-line ); } else { $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]); @@ -225,7 +229,10 @@ class ConditionalStyles return $conditionalStyles; } - private function readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions): ConditionalDataBar + /** + * @param SimpleXMLElement|stdClass $cfRule + */ + private function readDataBarOfConditionalRule($cfRule, array $conditionalFormattingRuleExtensions): ConditionalDataBar { $dataBar = new ConditionalDataBar(); //dataBar attribute @@ -257,7 +264,10 @@ class ConditionalStyles return $dataBar; } - private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, $cfRule, $conditionalFormattingRuleExtensions): void + /** + * @param SimpleXMLElement|stdClass $cfRule + */ + private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, $cfRule, array $conditionalFormattingRuleExtensions): void { if (isset($cfRule->extLst)) { $ns = $cfRule->extLst->getNamespaces(true); diff --git a/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php b/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php index b699cb57..dac76230 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php @@ -8,8 +8,10 @@ use SimpleXMLElement; class DataValidations { + /** @var Worksheet */ private $worksheet; + /** @var SimpleXMLElement */ private $worksheetXml; public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml) @@ -22,7 +24,7 @@ class DataValidations { foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) { // Uppercase coordinate - $range = strtoupper($dataValidation['sqref']); + $range = strtoupper((string) $dataValidation['sqref']); $rangeSet = explode(' ', $range); foreach ($rangeSet as $range) { $stRange = $this->worksheet->shrinkRangeToFit($range); diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php b/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php index 84884996..7d48c796 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php @@ -9,8 +9,10 @@ use SimpleXMLElement; class Hyperlinks { + /** @var Worksheet */ private $worksheet; + /** @var array */ private $hyperlinks = []; public function __construct(Worksheet $workSheet) diff --git a/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php b/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php index 56f18f98..08decd6e 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php @@ -8,8 +8,10 @@ use SimpleXMLElement; class PageSetup extends BaseParserClass { + /** @var Worksheet */ private $worksheet; + /** @var ?SimpleXMLElement */ private $worksheetXml; public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null) @@ -18,16 +20,17 @@ class PageSetup extends BaseParserClass $this->worksheetXml = $worksheetXml; } - public function load(array $unparsedLoadedData) + public function load(array $unparsedLoadedData): array { - if (!$this->worksheetXml) { + $worksheetXml = $this->worksheetXml; + if ($worksheetXml === null) { return $unparsedLoadedData; } - $this->margins($this->worksheetXml, $this->worksheet); - $unparsedLoadedData = $this->pageSetup($this->worksheetXml, $this->worksheet, $unparsedLoadedData); - $this->headerFooter($this->worksheetXml, $this->worksheet); - $this->pageBreaks($this->worksheetXml, $this->worksheet); + $this->margins($worksheetXml, $this->worksheet); + $unparsedLoadedData = $this->pageSetup($worksheetXml, $this->worksheet, $unparsedLoadedData); + $this->headerFooter($worksheetXml, $this->worksheet); + $this->pageBreaks($worksheetXml, $this->worksheet); return $unparsedLoadedData; } @@ -45,7 +48,7 @@ class PageSetup extends BaseParserClass } } - private function pageSetup(SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData) + private function pageSetup(SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData): array { if ($xmlSheet->pageSetup) { $docPageSetup = $worksheet->getPageSetup(); diff --git a/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php b/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php index a302cc56..9c02da9f 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php @@ -7,8 +7,10 @@ use SimpleXMLElement; class SheetViewOptions extends BaseParserClass { + /** @var Worksheet */ private $worksheet; + /** @var ?SimpleXMLElement */ private $worksheetXml; public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null) @@ -24,10 +26,11 @@ class SheetViewOptions extends BaseParserClass } if (isset($this->worksheetXml->sheetPr)) { - $this->tabColor($this->worksheetXml->sheetPr, $styleReader); - $this->codeName($this->worksheetXml->sheetPr); - $this->outlines($this->worksheetXml->sheetPr); - $this->pageSetup($this->worksheetXml->sheetPr); + $sheetPr = $this->worksheetXml->sheetPr; + $this->tabColor($sheetPr, $styleReader); + $this->codeName($sheetPr); + $this->outlines($sheetPr); + $this->pageSetup($sheetPr); } if (isset($this->worksheetXml->sheetFormatPr)) { diff --git a/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php b/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php index 9d61e3d3..4743afbf 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php @@ -29,7 +29,7 @@ class WorkbookView $this->spreadsheet->setActiveSheetIndex(0); $workbookView = $xmlWorkbook->children($mainNS)->bookViews->workbookView; - if (($readDataOnly !== true || !empty($this->loadSheetsOnly)) && !empty($workbookView)) { + if ($readDataOnly !== true && !empty($workbookView)) { $workbookViewAttributes = self::testSimpleXml(self::getAttributes($workbookView)); // active sheet index $activeTab = (int) $workbookViewAttributes->activeTab; // refers to old sheet index diff --git a/src/PhpSpreadsheet/Reader/Xml.php b/src/PhpSpreadsheet/Reader/Xml.php index 0b5e0966..d8f0d9dc 100644 --- a/src/PhpSpreadsheet/Reader/Xml.php +++ b/src/PhpSpreadsheet/Reader/Xml.php @@ -18,6 +18,7 @@ use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; /** @@ -364,7 +365,7 @@ class Xml extends BaseReader $rowTo = $rowTo + $cell_ss['MergeDown']; } $cellRange .= ':' . $columnTo . $rowTo; - $spreadsheet->getActiveSheet()->mergeCells($cellRange); + $spreadsheet->getActiveSheet()->mergeCells($cellRange, Worksheet::MERGE_CELL_CONTENT_HIDE); } $hasCalculatedValue = false; diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index 08a38b3b..822b754a 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -253,10 +253,11 @@ class ReferenceHelper ? uksort($aDataValidationCollection, [self::class, 'cellReverseSort']) : uksort($aDataValidationCollection, [self::class, 'cellSort']); - foreach ($aDataValidationCollection as $cellAddress => $value) { + foreach ($aDataValidationCollection as $cellAddress => $dataValidation) { $newReference = $this->updateCellReference($cellAddress); if ($cellAddress !== $newReference) { - $worksheet->setDataValidation($newReference, $value); + $dataValidation->setSqref($newReference); + $worksheet->setDataValidation($newReference, $dataValidation); $worksheet->setDataValidation($cellAddress, null); } } diff --git a/src/PhpSpreadsheet/Settings.php b/src/PhpSpreadsheet/Settings.php index 5fbbadb6..3282a596 100644 --- a/src/PhpSpreadsheet/Settings.php +++ b/src/PhpSpreadsheet/Settings.php @@ -8,6 +8,7 @@ use PhpOffice\PhpSpreadsheet\Collection\Memory; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\SimpleCache\CacheInterface; +use ReflectionClass; class Settings { @@ -161,12 +162,19 @@ class Settings public static function getCache(): CacheInterface { if (!self::$cache) { - self::$cache = new Memory(); + self::$cache = self::useSimpleCacheVersion3() ? new Memory\SimpleCache3() : new Memory\SimpleCache1(); } return self::$cache; } + public static function useSimpleCacheVersion3(): bool + { + return + PHP_MAJOR_VERSION === 8 && + (new ReflectionClass(CacheInterface::class))->getMethod('get')->getReturnType() !== null; + } + /** * Set the HTTP client implementation to be used for network request. */ diff --git a/src/PhpSpreadsheet/Shared/Font.php b/src/PhpSpreadsheet/Shared/Font.php index 1adf213e..e90c679b 100644 --- a/src/PhpSpreadsheet/Shared/Font.php +++ b/src/PhpSpreadsheet/Shared/Font.php @@ -13,7 +13,7 @@ class Font const AUTOSIZE_METHOD_APPROX = 'approx'; const AUTOSIZE_METHOD_EXACT = 'exact'; - private static $autoSizeMethods = [ + private const AUTOSIZE_METHODS = [ self::AUTOSIZE_METHOD_APPROX, self::AUTOSIZE_METHOD_EXACT, ]; @@ -101,6 +101,105 @@ class Font const VERDANA_ITALIC = 'verdanai.ttf'; const VERDANA_BOLD_ITALIC = 'verdanaz.ttf'; + const FONT_FILE_NAMES = [ + 'Arial' => [ + 'x' => self::ARIAL, + 'xb' => self::ARIAL_BOLD, + 'xi' => self::ARIAL_ITALIC, + 'xbi' => self::ARIAL_BOLD_ITALIC, + ], + 'Calibri' => [ + 'x' => self::CALIBRI, + 'xb' => self::CALIBRI_BOLD, + 'xi' => self::CALIBRI_ITALIC, + 'xbi' => self::CALIBRI_BOLD_ITALIC, + ], + 'Comic Sans MS' => [ + 'x' => self::COMIC_SANS_MS, + 'xb' => self::COMIC_SANS_MS_BOLD, + 'xi' => self::COMIC_SANS_MS, + 'xbi' => self::COMIC_SANS_MS_BOLD, + ], + 'Courier New' => [ + 'x' => self::COURIER_NEW, + 'xb' => self::COURIER_NEW_BOLD, + 'xi' => self::COURIER_NEW_ITALIC, + 'xbi' => self::COURIER_NEW_BOLD_ITALIC, + ], + 'Georgia' => [ + 'x' => self::GEORGIA, + 'xb' => self::GEORGIA_BOLD, + 'xi' => self::GEORGIA_ITALIC, + 'xbi' => self::GEORGIA_BOLD_ITALIC, + ], + 'Impact' => [ + 'x' => self::IMPACT, + 'xb' => self::IMPACT, + 'xi' => self::IMPACT, + 'xbi' => self::IMPACT, + ], + 'Liberation Sans' => [ + 'x' => self::LIBERATION_SANS, + 'xb' => self::LIBERATION_SANS_BOLD, + 'xi' => self::LIBERATION_SANS_ITALIC, + 'xbi' => self::LIBERATION_SANS_BOLD_ITALIC, + ], + 'Lucida Console' => [ + 'x' => self::LUCIDA_CONSOLE, + 'xb' => self::LUCIDA_CONSOLE, + 'xi' => self::LUCIDA_CONSOLE, + 'xbi' => self::LUCIDA_CONSOLE, + ], + 'Lucida Sans Unicode' => [ + 'x' => self::LUCIDA_SANS_UNICODE, + 'xb' => self::LUCIDA_SANS_UNICODE, + 'xi' => self::LUCIDA_SANS_UNICODE, + 'xbi' => self::LUCIDA_SANS_UNICODE, + ], + 'Microsoft Sans Serif' => [ + 'x' => self::MICROSOFT_SANS_SERIF, + 'xb' => self::MICROSOFT_SANS_SERIF, + 'xi' => self::MICROSOFT_SANS_SERIF, + 'xbi' => self::MICROSOFT_SANS_SERIF, + ], + 'Palatino Linotype' => [ + 'x' => self::PALATINO_LINOTYPE, + 'xb' => self::PALATINO_LINOTYPE_BOLD, + 'xi' => self::PALATINO_LINOTYPE_ITALIC, + 'xbi' => self::PALATINO_LINOTYPE_BOLD_ITALIC, + ], + 'Symbol' => [ + 'x' => self::SYMBOL, + 'xb' => self::SYMBOL, + 'xi' => self::SYMBOL, + 'xbi' => self::SYMBOL, + ], + 'Tahoma' => [ + 'x' => self::TAHOMA, + 'xb' => self::TAHOMA_BOLD, + 'xi' => self::TAHOMA, + 'xbi' => self::TAHOMA_BOLD, + ], + 'Times New Roman' => [ + 'x' => self::TIMES_NEW_ROMAN, + 'xb' => self::TIMES_NEW_ROMAN_BOLD, + 'xi' => self::TIMES_NEW_ROMAN_ITALIC, + 'xbi' => self::TIMES_NEW_ROMAN_BOLD_ITALIC, + ], + 'Trebuchet MS' => [ + 'x' => self::TREBUCHET_MS, + 'xb' => self::TREBUCHET_MS_BOLD, + 'xi' => self::TREBUCHET_MS_ITALIC, + 'xbi' => self::TREBUCHET_MS_BOLD_ITALIC, + ], + 'Verdana' => [ + 'x' => self::VERDANA, + 'xb' => self::VERDANA_BOLD, + 'xi' => self::VERDANA_ITALIC, + 'xbi' => self::VERDANA_BOLD_ITALIC, + ], + ]; + /** * AutoSize method. * @@ -113,54 +212,65 @@ class Font * * @var string */ - private static $trueTypeFontPath; + private static $trueTypeFontPath = ''; /** * How wide is a default column for a given default font and size? * Empirical data found by inspecting real Excel files and reading off the pixel width * in Microsoft Office Excel 2007. + * Added height in points. + */ + public const DEFAULT_COLUMN_WIDTHS = [ + 'Arial' => [ + 1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], + 2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], + 3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0], + + 4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75], + 5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25], + 6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25], + 7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0], + 8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25], + 9 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.0], + 10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75], + ], + 'Calibri' => [ + 1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], + 2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], + 3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.00], + 4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75], + 5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25], + 6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25], + 7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0], + 8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25], + 9 => ['px' => 56, 'width' => 9.33203125, 'height' => 12.0], + 10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75], + 11 => ['px' => 64, 'width' => 9.14062500, 'height' => 15.0], + ], + 'Verdana' => [ + 1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], + 2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], + 3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0], + 4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75], + 5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25], + 6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25], + 7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0], + 8 => ['px' => 64, 'width' => 9.14062500, 'height' => 10.5], + 9 => ['px' => 72, 'width' => 9.00000000, 'height' => 11.25], + 10 => ['px' => 72, 'width' => 9.00000000, 'height' => 12.75], + ], + ]; + + /** + * List of column widths. Replaced by constant; + * previously it was public and updateable, allowing + * user to make inappropriate alterations. + * + * @deprecated 1.25.0 Use DEFAULT_COLUMN_WIDTHS constant instead. * * @var array */ - public static $defaultColumnWidths = [ - 'Arial' => [ - 1 => ['px' => 24, 'width' => 12.00000000], - 2 => ['px' => 24, 'width' => 12.00000000], - 3 => ['px' => 32, 'width' => 10.66406250], - 4 => ['px' => 32, 'width' => 10.66406250], - 5 => ['px' => 40, 'width' => 10.00000000], - 6 => ['px' => 48, 'width' => 9.59765625], - 7 => ['px' => 48, 'width' => 9.59765625], - 8 => ['px' => 56, 'width' => 9.33203125], - 9 => ['px' => 64, 'width' => 9.14062500], - 10 => ['px' => 64, 'width' => 9.14062500], - ], - 'Calibri' => [ - 1 => ['px' => 24, 'width' => 12.00000000], - 2 => ['px' => 24, 'width' => 12.00000000], - 3 => ['px' => 32, 'width' => 10.66406250], - 4 => ['px' => 32, 'width' => 10.66406250], - 5 => ['px' => 40, 'width' => 10.00000000], - 6 => ['px' => 48, 'width' => 9.59765625], - 7 => ['px' => 48, 'width' => 9.59765625], - 8 => ['px' => 56, 'width' => 9.33203125], - 9 => ['px' => 56, 'width' => 9.33203125], - 10 => ['px' => 64, 'width' => 9.14062500], - 11 => ['px' => 64, 'width' => 9.14062500], - ], - 'Verdana' => [ - 1 => ['px' => 24, 'width' => 12.00000000], - 2 => ['px' => 24, 'width' => 12.00000000], - 3 => ['px' => 32, 'width' => 10.66406250], - 4 => ['px' => 32, 'width' => 10.66406250], - 5 => ['px' => 40, 'width' => 10.00000000], - 6 => ['px' => 48, 'width' => 9.59765625], - 7 => ['px' => 48, 'width' => 9.59765625], - 8 => ['px' => 64, 'width' => 9.14062500], - 9 => ['px' => 72, 'width' => 9.00000000], - 10 => ['px' => 72, 'width' => 9.00000000], - ], - ]; + public static $defaultColumnWidths = self::DEFAULT_COLUMN_WIDTHS; /** * Set autoSize method. @@ -171,7 +281,7 @@ class Font */ public static function setAutoSizeMethod($method) { - if (!in_array($method, self::$autoSizeMethods)) { + if (!in_array($method, self::AUTOSIZE_METHODS)) { return false; } self::$autoSizeMethod = $method; @@ -219,7 +329,7 @@ class Font * Calculate an (approximate) OpenXML column width, based on font size and text contained. * * @param FontStyle $font Font object - * @param RichText|string $cellText Text to calculate width + * @param null|RichText|string $cellText Text to calculate width * @param int $rotation Rotation angle * @param null|FontStyle $defaultFont Font object * @param bool $filterAdjustment Add space for Autofilter or Table dropdown @@ -238,7 +348,8 @@ class Font } // Special case if there are one or more newline characters ("\n") - if (strpos($cellText ?? '', "\n") !== false) { + $cellText = $cellText ?? ''; + if (strpos(/** @scrutinizer ignore-type */ $cellText, "\n") !== false) { $lineTexts = explode("\n", $cellText); $lineWidths = []; foreach ($lineTexts as $lineText) { @@ -281,7 +392,7 @@ class Font } // Convert from pixel width to column width - $columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont); + $columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont ?? new FontStyle()); // Return return (int) round($columnWidth, 6); @@ -299,7 +410,12 @@ class Font // font size should really be supplied in pixels in GD2, // but since GD2 seems to assume 72dpi, pixels and points are the same $fontFile = self::getTrueTypeFontFileFromFont($font); - $textBox = imagettfbbox($font->getSize(), $rotation, $fontFile, $text); + $textBox = imagettfbbox($font->getSize() ?? 10.0, $rotation, $fontFile, $text); + if ($textBox === false) { + // @codeCoverageIgnoreStart + throw new PhpSpreadsheetException('imagettfbbox failed'); + // @codeCoverageIgnoreEnd + } // Get corners positions $lowerLeftCornerX = $textBox[0]; @@ -409,129 +525,48 @@ class Font * * @return string Path to TrueType font file */ - public static function getTrueTypeFontFileFromFont(FontStyle $font) + public static function getTrueTypeFontFileFromFont(FontStyle $font, bool $checkPath = true) { - if (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath)) { + if ($checkPath && (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath))) { throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified'); } $name = $font->getName(); + if (!isset(self::FONT_FILE_NAMES[$name])) { + throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file'); + } $bold = $font->getBold(); $italic = $font->getItalic(); - - // Check if we can map font to true type font file - switch ($name) { - case 'Arial': - $fontFile = ( - $bold ? ($italic ? self::ARIAL_BOLD_ITALIC : self::ARIAL_BOLD) - : ($italic ? self::ARIAL_ITALIC : self::ARIAL) - ); - - break; - case 'Calibri': - $fontFile = ( - $bold ? ($italic ? self::CALIBRI_BOLD_ITALIC : self::CALIBRI_BOLD) - : ($italic ? self::CALIBRI_ITALIC : self::CALIBRI) - ); - - break; - case 'Courier New': - $fontFile = ( - $bold ? ($italic ? self::COURIER_NEW_BOLD_ITALIC : self::COURIER_NEW_BOLD) - : ($italic ? self::COURIER_NEW_ITALIC : self::COURIER_NEW) - ); - - break; - case 'Comic Sans MS': - $fontFile = ( - $bold ? self::COMIC_SANS_MS_BOLD : self::COMIC_SANS_MS - ); - - break; - case 'Georgia': - $fontFile = ( - $bold ? ($italic ? self::GEORGIA_BOLD_ITALIC : self::GEORGIA_BOLD) - : ($italic ? self::GEORGIA_ITALIC : self::GEORGIA) - ); - - break; - case 'Impact': - $fontFile = self::IMPACT; - - break; - case 'Liberation Sans': - $fontFile = ( - $bold ? ($italic ? self::LIBERATION_SANS_BOLD_ITALIC : self::LIBERATION_SANS_BOLD) - : ($italic ? self::LIBERATION_SANS_ITALIC : self::LIBERATION_SANS) - ); - - break; - case 'Lucida Console': - $fontFile = self::LUCIDA_CONSOLE; - - break; - case 'Lucida Sans Unicode': - $fontFile = self::LUCIDA_SANS_UNICODE; - - break; - case 'Microsoft Sans Serif': - $fontFile = self::MICROSOFT_SANS_SERIF; - - break; - case 'Palatino Linotype': - $fontFile = ( - $bold ? ($italic ? self::PALATINO_LINOTYPE_BOLD_ITALIC : self::PALATINO_LINOTYPE_BOLD) - : ($italic ? self::PALATINO_LINOTYPE_ITALIC : self::PALATINO_LINOTYPE) - ); - - break; - case 'Symbol': - $fontFile = self::SYMBOL; - - break; - case 'Tahoma': - $fontFile = ( - $bold ? self::TAHOMA_BOLD : self::TAHOMA - ); - - break; - case 'Times New Roman': - $fontFile = ( - $bold ? ($italic ? self::TIMES_NEW_ROMAN_BOLD_ITALIC : self::TIMES_NEW_ROMAN_BOLD) - : ($italic ? self::TIMES_NEW_ROMAN_ITALIC : self::TIMES_NEW_ROMAN) - ); - - break; - case 'Trebuchet MS': - $fontFile = ( - $bold ? ($italic ? self::TREBUCHET_MS_BOLD_ITALIC : self::TREBUCHET_MS_BOLD) - : ($italic ? self::TREBUCHET_MS_ITALIC : self::TREBUCHET_MS) - ); - - break; - case 'Verdana': - $fontFile = ( - $bold ? ($italic ? self::VERDANA_BOLD_ITALIC : self::VERDANA_BOLD) - : ($italic ? self::VERDANA_ITALIC : self::VERDANA) - ); - - break; - default: - throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file'); - - break; + $index = 'x'; + if ($bold) { + $index .= 'b'; } + if ($italic) { + $index .= 'i'; + } + $fontFile = self::FONT_FILE_NAMES[$name][$index]; - $fontFile = self::$trueTypeFontPath . $fontFile; + $separator = ''; + if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') { + $separator = DIRECTORY_SEPARATOR; + } + $fontFile = self::$trueTypeFontPath . $separator . $fontFile; // Check if file actually exists - if (!file_exists($fontFile)) { + if ($checkPath && !file_exists($fontFile)) { throw new PhpSpreadsheetException('TrueType Font file not found'); } return $fontFile; } + public const CHARSET_FROM_FONT_NAME = [ + 'EucrosiaUPC' => self::CHARSET_ANSI_THAI, + 'Wingdings' => self::CHARSET_SYMBOL, + 'Wingdings 2' => self::CHARSET_SYMBOL, + 'Wingdings 3' => self::CHARSET_SYMBOL, + ]; + /** * Returns the associated charset for the font name. * @@ -541,19 +576,7 @@ class Font */ public static function getCharsetFromFontName($fontName) { - switch ($fontName) { - // Add more cases. Check FONT records in real Excel files. - case 'EucrosiaUPC': - return self::CHARSET_ANSI_THAI; - case 'Wingdings': - return self::CHARSET_SYMBOL; - case 'Wingdings 2': - return self::CHARSET_SYMBOL; - case 'Wingdings 3': - return self::CHARSET_SYMBOL; - default: - return self::CHARSET_ANSI_LATIN; - } + return self::CHARSET_FROM_FONT_NAME[$fontName] ?? self::CHARSET_ANSI_LATIN; } /** @@ -567,17 +590,17 @@ class Font */ public static function getDefaultColumnWidthByFont(FontStyle $font, $returnAsPixels = false) { - if (isset(self::$defaultColumnWidths[$font->getName()][$font->getSize()])) { + if (isset(self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()])) { // Exact width can be determined $columnWidth = $returnAsPixels ? - self::$defaultColumnWidths[$font->getName()][$font->getSize()]['px'] - : self::$defaultColumnWidths[$font->getName()][$font->getSize()]['width']; + self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['px'] + : self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['width']; } else { // We don't have data for this particular font and size, use approximation by // extrapolating from Calibri 11 $columnWidth = $returnAsPixels ? - self::$defaultColumnWidths['Calibri'][11]['px'] - : self::$defaultColumnWidths['Calibri'][11]['width']; + self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px'] + : self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width']; $columnWidth = $columnWidth * $font->getSize() / 11; // Round pixels to closest integer @@ -599,173 +622,14 @@ class Font */ public static function getDefaultRowHeightByFont(FontStyle $font) { - switch ($font->getName()) { - case 'Arial': - switch ($font->getSize()) { - case 10: - // inspection of Arial 10 workbook says 12.75pt ~17px - $rowHeight = 12.75; - - break; - case 9: - // inspection of Arial 9 workbook says 12.00pt ~16px - $rowHeight = 12; - - break; - case 8: - // inspection of Arial 8 workbook says 11.25pt ~15px - $rowHeight = 11.25; - - break; - case 7: - // inspection of Arial 7 workbook says 9.00pt ~12px - $rowHeight = 9; - - break; - case 6: - case 5: - // inspection of Arial 5,6 workbook says 8.25pt ~11px - $rowHeight = 8.25; - - break; - case 4: - // inspection of Arial 4 workbook says 6.75pt ~9px - $rowHeight = 6.75; - - break; - case 3: - // inspection of Arial 3 workbook says 6.00pt ~8px - $rowHeight = 6; - - break; - case 2: - case 1: - // inspection of Arial 1,2 workbook says 5.25pt ~7px - $rowHeight = 5.25; - - break; - default: - // use Arial 10 workbook as an approximation, extrapolation - $rowHeight = 12.75 * $font->getSize() / 10; - - break; - } - - break; - case 'Calibri': - switch ($font->getSize()) { - case 11: - // inspection of Calibri 11 workbook says 15.00pt ~20px - $rowHeight = 15; - - break; - case 10: - // inspection of Calibri 10 workbook says 12.75pt ~17px - $rowHeight = 12.75; - - break; - case 9: - // inspection of Calibri 9 workbook says 12.00pt ~16px - $rowHeight = 12; - - break; - case 8: - // inspection of Calibri 8 workbook says 11.25pt ~15px - $rowHeight = 11.25; - - break; - case 7: - // inspection of Calibri 7 workbook says 9.00pt ~12px - $rowHeight = 9; - - break; - case 6: - case 5: - // inspection of Calibri 5,6 workbook says 8.25pt ~11px - $rowHeight = 8.25; - - break; - case 4: - // inspection of Calibri 4 workbook says 6.75pt ~9px - $rowHeight = 6.75; - - break; - case 3: - // inspection of Calibri 3 workbook says 6.00pt ~8px - $rowHeight = 6.00; - - break; - case 2: - case 1: - // inspection of Calibri 1,2 workbook says 5.25pt ~7px - $rowHeight = 5.25; - - break; - default: - // use Calibri 11 workbook as an approximation, extrapolation - $rowHeight = 15 * $font->getSize() / 11; - - break; - } - - break; - case 'Verdana': - switch ($font->getSize()) { - case 10: - // inspection of Verdana 10 workbook says 12.75pt ~17px - $rowHeight = 12.75; - - break; - case 9: - // inspection of Verdana 9 workbook says 11.25pt ~15px - $rowHeight = 11.25; - - break; - case 8: - // inspection of Verdana 8 workbook says 10.50pt ~14px - $rowHeight = 10.50; - - break; - case 7: - // inspection of Verdana 7 workbook says 9.00pt ~12px - $rowHeight = 9.00; - - break; - case 6: - case 5: - // inspection of Verdana 5,6 workbook says 8.25pt ~11px - $rowHeight = 8.25; - - break; - case 4: - // inspection of Verdana 4 workbook says 6.75pt ~9px - $rowHeight = 6.75; - - break; - case 3: - // inspection of Verdana 3 workbook says 6.00pt ~8px - $rowHeight = 6; - - break; - case 2: - case 1: - // inspection of Verdana 1,2 workbook says 5.25pt ~7px - $rowHeight = 5.25; - - break; - default: - // use Verdana 10 workbook as an approximation, extrapolation - $rowHeight = 12.75 * $font->getSize() / 10; - - break; - } - - break; - default: - // just use Calibri as an approximation - $rowHeight = 15 * $font->getSize() / 11; - - break; + $name = $font->getName(); + $size = $font->getSize(); + if (isset(self::DEFAULT_COLUMN_WIDTHS[$name][$size])) { + $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][$size]['height']; + } elseif ($name === 'Arial' || $name === 'Verdana') { + $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][10]['height'] * $size / 10.0; + } else { + $rowHeight = self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['height'] * $size / 11.0; } return $rowHeight; diff --git a/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php b/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php index 66111b6c..1ec7f6ab 100644 --- a/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php +++ b/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php @@ -495,7 +495,7 @@ class EigenvalueDecomposition $this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n]; $this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z; } - // Complex pair + // Complex pair } else { $this->d[$n - 1] = $x + $p; $this->d[$n] = $x + $p; @@ -671,7 +671,7 @@ class EigenvalueDecomposition } else { $this->H[$i][$n] = -$r / ($eps * $norm); } - // Solve real equations + // Solve real equations } else { $x = $this->H[$i][$i + 1]; $y = $this->H[$i + 1][$i]; @@ -693,7 +693,7 @@ class EigenvalueDecomposition } } } - // Complex vector + // Complex vector } elseif ($q < 0) { $l = $n - 1; // Last vector component imaginary so matrix is triangular diff --git a/src/PhpSpreadsheet/Shared/JAMA/Matrix.php b/src/PhpSpreadsheet/Shared/JAMA/Matrix.php index ab78ef18..5e35d491 100644 --- a/src/PhpSpreadsheet/Shared/JAMA/Matrix.php +++ b/src/PhpSpreadsheet/Shared/JAMA/Matrix.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared\JAMA; use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException; +use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; @@ -66,21 +67,21 @@ class Matrix $this->A = $args[0]; break; - //Square matrix - n x n + //Square matrix - n x n case 'integer': $this->m = $args[0]; $this->n = $args[0]; $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0)); break; - //Rectangular matrix - m x n + //Rectangular matrix - m x n case 'integer,integer': $this->m = $args[0]; $this->n = $args[1]; $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0)); break; - //Rectangular matrix - m x n initialized from packed array + //Rectangular matrix - m x n initialized from packed array case 'array,integer': $this->m = $args[1]; if ($this->m != 0) { @@ -190,7 +191,7 @@ class Matrix return $R; break; - //A($i0...$iF; $j0...$jF) + //A($i0...$iF; $j0...$jF) case 'integer,integer,integer,integer': [$i0, $iF, $j0, $jF] = $args; if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) { @@ -213,7 +214,7 @@ class Matrix return $R; break; - //$R = array of row indices; $C = array of column indices + //$R = array of row indices; $C = array of column indices case 'array,array': [$RL, $CL] = $args; if (count($RL) > 0) { @@ -236,7 +237,7 @@ class Matrix return $R; break; - //A($i0...$iF); $CL = array of column indices + //A($i0...$iF); $CL = array of column indices case 'integer,integer,array': [$i0, $iF, $CL] = $args; if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) { @@ -259,7 +260,7 @@ class Matrix return $R; break; - //$RL = array of row indices + //$RL = array of row indices case 'array,integer,integer': [$RL, $j0, $jF] = $args; if (count($RL) > 0) { @@ -532,14 +533,8 @@ class Matrix for ($j = 0; $j < $this->n; ++$j) { $validValues = true; $value = $M->get($i, $j); - if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { - $this->A[$i][$j] = trim($this->A[$i][$j], '"'); - $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); - } - if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { - $value = trim($value, '"'); - $validValues &= StringHelper::convertToNumberIfFraction($value); - } + [$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues); + [$value, $validValues] = $this->validateExtractedValue($value, $validValues); if ($validValues) { $this->A[$i][$j] += $value; } else { @@ -632,14 +627,8 @@ class Matrix for ($j = 0; $j < $this->n; ++$j) { $validValues = true; $value = $M->get($i, $j); - if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { - $this->A[$i][$j] = trim($this->A[$i][$j], '"'); - $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); - } - if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { - $value = trim($value, '"'); - $validValues &= StringHelper::convertToNumberIfFraction($value); - } + [$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues); + [$value, $validValues] = $this->validateExtractedValue($value, $validValues); if ($validValues) { $this->A[$i][$j] -= $value; } else { @@ -734,14 +723,8 @@ class Matrix for ($j = 0; $j < $this->n; ++$j) { $validValues = true; $value = $M->get($i, $j); - if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { - $this->A[$i][$j] = trim($this->A[$i][$j], '"'); - $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); - } - if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { - $value = trim($value, '"'); - $validValues &= StringHelper::convertToNumberIfFraction($value); - } + [$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues); + [$value, $validValues] = $this->validateExtractedValue($value, $validValues); if ($validValues) { $this->A[$i][$j] *= $value; } else { @@ -792,14 +775,8 @@ class Matrix for ($j = 0; $j < $this->n; ++$j) { $validValues = true; $value = $M->get($i, $j); - if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { - $this->A[$i][$j] = trim($this->A[$i][$j], '"'); - $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); - } - if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { - $value = trim($value, '"'); - $validValues &= StringHelper::convertToNumberIfFraction($value); - } + [$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues); + [$value, $validValues] = $this->validateExtractedValue($value, $validValues); if ($validValues) { if ($value == 0) { // Trap for Divide by Zero error @@ -1079,14 +1056,8 @@ class Matrix for ($j = 0; $j < $this->n; ++$j) { $validValues = true; $value = $M->get($i, $j); - if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { - $this->A[$i][$j] = trim($this->A[$i][$j], '"'); - $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); - } - if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { - $value = trim($value, '"'); - $validValues &= StringHelper::convertToNumberIfFraction($value); - } + [$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues); + [$value, $validValues] = $this->validateExtractedValue($value, $validValues); if ($validValues) { $this->A[$i][$j] = $this->A[$i][$j] ** $value; } else { @@ -1187,4 +1158,20 @@ class Matrix return $L->det(); } + + /** + * @param mixed $value + */ + private function validateExtractedValue($value, bool $validValues): array + { + if (!is_numeric($value) && is_array($value)) { + $value = Functions::flattenArray($value)[0]; + } + if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { + $value = trim($value, '"'); + $validValues &= StringHelper::convertToNumberIfFraction($value); + } + + return [$value, $validValues]; + } } diff --git a/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php b/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php index 6c8999d0..b809bfa1 100644 --- a/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php +++ b/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php @@ -315,7 +315,7 @@ class SingularValueDecomposition } break; - // Split at negligible s(k). + // Split at negligible s(k). case 2: $f = $e[$k - 1]; $e[$k - 1] = 0.0; @@ -336,7 +336,7 @@ class SingularValueDecomposition } break; - // Perform one qr step. + // Perform one qr step. case 3: // Calculate the shift. $scale = max(max(max(max(abs($this->s[$p - 1]), abs($this->s[$p - 2])), abs($e[$p - 2])), abs($this->s[$k])), abs($e[$k])); @@ -396,7 +396,7 @@ class SingularValueDecomposition $iter = $iter + 1; break; - // Convergence. + // Convergence. case 4: // Make the singular values positive. if ($this->s[$k] <= 0.0) { diff --git a/src/PhpSpreadsheet/Shared/StringHelper.php b/src/PhpSpreadsheet/Shared/StringHelper.php index 030df66d..16026c3c 100644 --- a/src/PhpSpreadsheet/Shared/StringHelper.php +++ b/src/PhpSpreadsheet/Shared/StringHelper.php @@ -3,7 +3,6 @@ namespace PhpOffice\PhpSpreadsheet\Shared; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use UConverter; class StringHelper { @@ -334,26 +333,23 @@ class StringHelper public static function sanitizeUTF8(string $textValue): string { $textValue = str_replace(["\xef\xbf\xbe", "\xef\xbf\xbf"], "\xef\xbf\xbd", $textValue); - if (class_exists(UConverter::class)) { - $returnValue = UConverter::transcode($textValue, 'UTF-8', 'UTF-8'); - if ($returnValue !== false) { - return $returnValue; - } - } - // @codeCoverageIgnoreStart - // I don't think any of the code below should ever be executed. - if (self::getIsIconvEnabled()) { - $returnValue = @iconv('UTF-8', 'UTF-8', $textValue); - if ($returnValue !== false) { - return $returnValue; - } - } - + $subst = mb_substitute_character(); // default is question mark + mb_substitute_character(65533); // Unicode substitution character // Phpstan does not think this can return false. $returnValue = mb_convert_encoding($textValue, 'UTF-8', 'UTF-8'); + mb_substitute_character(/** @scrutinizer ignore-type */ $subst); - return $returnValue; - // @codeCoverageIgnoreEnd + return self::returnString($returnValue); + } + + /** + * Strictly to satisfy Scrutinizer. + * + * @param mixed $value + */ + private static function returnString($value): string + { + return is_string($value) ? $value : ''; } /** @@ -447,7 +443,7 @@ class StringHelper } } - return mb_convert_encoding($textValue, $to, $from); + return self::returnString(mb_convert_encoding($textValue, $to, $from)); } /** diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php index 33b4fe0c..364700e2 100644 --- a/src/PhpSpreadsheet/Spreadsheet.php +++ b/src/PhpSpreadsheet/Spreadsheet.php @@ -3,10 +3,13 @@ namespace PhpOffice\PhpSpreadsheet; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; +use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\Iterator; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter; class Spreadsheet { @@ -721,6 +724,19 @@ class Spreadsheet return null; } + /** + * Get sheet by name, throwing exception if not found. + */ + public function getSheetByNameOrThrow(string $worksheetName): Worksheet + { + $worksheet = $this->getSheetByName($worksheetName); + if ($worksheet === null) { + throw new Exception("Sheet $worksheetName does not exist."); + } + + return $worksheet; + } + /** * Get index for sheet. * @@ -1120,28 +1136,24 @@ class Spreadsheet */ public function copy() { - $copied = clone $this; + $filename = File::temporaryFilename(); + $writer = new XlsxWriter($this); + $writer->setIncludeCharts(true); + $writer->save($filename); - $worksheetCount = count($this->workSheetCollection); - for ($i = 0; $i < $worksheetCount; ++$i) { - $this->workSheetCollection[$i] = $this->workSheetCollection[$i]->copy(); - $this->workSheetCollection[$i]->rebindParent($this); - } + $reader = new XlsxReader(); + $reader->setIncludeCharts(true); + $reloadedSpreadsheet = $reader->load($filename); + unlink($filename); - return $copied; + return $reloadedSpreadsheet; } - /** - * Implement PHP __clone to create a deep clone, not just a shallow copy. - */ public function __clone() { - // @phpstan-ignore-next-line - foreach ($this as $key => $val) { - if (is_object($val) || (is_array($val))) { - $this->{$key} = unserialize(serialize($val)); - } - } + throw new Exception( + 'Do not use clone on spreadsheet. Use spreadsheet->copy() instead.' + ); } /** @@ -1562,7 +1574,7 @@ class Spreadsheet * Workbook window is hidden and cannot be shown in the * user interface. * - * @param string $visibility visibility status of the workbook + * @param null|string $visibility visibility status of the workbook */ public function setVisibility($visibility): void { @@ -1596,7 +1608,7 @@ class Spreadsheet */ public function setTabRatio($tabRatio): void { - if ($tabRatio >= 0 || $tabRatio <= 1000) { + if ($tabRatio >= 0 && $tabRatio <= 1000) { $this->tabRatio = (int) $tabRatio; } else { throw new Exception('Tab ratio must be between 0 and 1000.'); diff --git a/src/PhpSpreadsheet/Style/Alignment.php b/src/PhpSpreadsheet/Style/Alignment.php index 83ac5b0d..68edfaca 100644 --- a/src/PhpSpreadsheet/Style/Alignment.php +++ b/src/PhpSpreadsheet/Style/Alignment.php @@ -15,6 +15,27 @@ class Alignment extends Supervisor const HORIZONTAL_JUSTIFY = 'justify'; const HORIZONTAL_FILL = 'fill'; const HORIZONTAL_DISTRIBUTED = 'distributed'; // Excel2007 only + private const HORIZONTAL_CENTER_CONTINUOUS_LC = 'centercontinuous'; + // Mapping for horizontal alignment + const HORIZONTAL_ALIGNMENT_FOR_XLSX = [ + self::HORIZONTAL_LEFT => self::HORIZONTAL_LEFT, + self::HORIZONTAL_RIGHT => self::HORIZONTAL_RIGHT, + self::HORIZONTAL_CENTER => self::HORIZONTAL_CENTER, + self::HORIZONTAL_CENTER_CONTINUOUS => self::HORIZONTAL_CENTER_CONTINUOUS, + self::HORIZONTAL_JUSTIFY => self::HORIZONTAL_JUSTIFY, + self::HORIZONTAL_FILL => self::HORIZONTAL_FILL, + self::HORIZONTAL_DISTRIBUTED => self::HORIZONTAL_DISTRIBUTED, + ]; + // Mapping for horizontal alignment CSS + const HORIZONTAL_ALIGNMENT_FOR_HTML = [ + self::HORIZONTAL_LEFT => self::HORIZONTAL_LEFT, + self::HORIZONTAL_RIGHT => self::HORIZONTAL_RIGHT, + self::HORIZONTAL_CENTER => self::HORIZONTAL_CENTER, + self::HORIZONTAL_CENTER_CONTINUOUS => self::HORIZONTAL_CENTER, + self::HORIZONTAL_JUSTIFY => self::HORIZONTAL_JUSTIFY, + //self::HORIZONTAL_FILL => self::HORIZONTAL_FILL, // no reasonable equivalent for fill + self::HORIZONTAL_DISTRIBUTED => self::HORIZONTAL_JUSTIFY, + ]; // Vertical alignment styles const VERTICAL_BOTTOM = 'bottom'; @@ -22,6 +43,45 @@ class Alignment extends Supervisor const VERTICAL_CENTER = 'center'; const VERTICAL_JUSTIFY = 'justify'; const VERTICAL_DISTRIBUTED = 'distributed'; // Excel2007 only + // Vertical alignment CSS + private const VERTICAL_BASELINE = 'baseline'; + private const VERTICAL_MIDDLE = 'middle'; + private const VERTICAL_SUB = 'sub'; + private const VERTICAL_SUPER = 'super'; + private const VERTICAL_TEXT_BOTTOM = 'text-bottom'; + private const VERTICAL_TEXT_TOP = 'text-top'; + + // Mapping for vertical alignment + const VERTICAL_ALIGNMENT_FOR_XLSX = [ + self::VERTICAL_BOTTOM => self::VERTICAL_BOTTOM, + self::VERTICAL_TOP => self::VERTICAL_TOP, + self::VERTICAL_CENTER => self::VERTICAL_CENTER, + self::VERTICAL_JUSTIFY => self::VERTICAL_JUSTIFY, + self::VERTICAL_DISTRIBUTED => self::VERTICAL_DISTRIBUTED, + // css settings that arent't in sync with Excel + self::VERTICAL_BASELINE => self::VERTICAL_BOTTOM, + self::VERTICAL_MIDDLE => self::VERTICAL_CENTER, + self::VERTICAL_SUB => self::VERTICAL_BOTTOM, + self::VERTICAL_SUPER => self::VERTICAL_TOP, + self::VERTICAL_TEXT_BOTTOM => self::VERTICAL_BOTTOM, + self::VERTICAL_TEXT_TOP => self::VERTICAL_TOP, + ]; + + // Mapping for vertical alignment for Html + const VERTICAL_ALIGNMENT_FOR_HTML = [ + self::VERTICAL_BOTTOM => self::VERTICAL_BOTTOM, + self::VERTICAL_TOP => self::VERTICAL_TOP, + self::VERTICAL_CENTER => self::VERTICAL_MIDDLE, + self::VERTICAL_JUSTIFY => self::VERTICAL_MIDDLE, + self::VERTICAL_DISTRIBUTED => self::VERTICAL_MIDDLE, + // css settings that arent't in sync with Excel + self::VERTICAL_BASELINE => self::VERTICAL_BASELINE, + self::VERTICAL_MIDDLE => self::VERTICAL_MIDDLE, + self::VERTICAL_SUB => self::VERTICAL_SUB, + self::VERTICAL_SUPER => self::VERTICAL_SUPER, + self::VERTICAL_TEXT_BOTTOM => self::VERTICAL_TEXT_BOTTOM, + self::VERTICAL_TEXT_TOP => self::VERTICAL_TEXT_TOP, + ]; // Read order const READORDER_CONTEXT = 0; @@ -202,8 +262,9 @@ class Alignment extends Supervisor */ public function setHorizontal(string $horizontalAlignment) { - if ($horizontalAlignment == '') { - $horizontalAlignment = self::HORIZONTAL_GENERAL; + $horizontalAlignment = strtolower($horizontalAlignment); + if ($horizontalAlignment === self::HORIZONTAL_CENTER_CONTINUOUS_LC) { + $horizontalAlignment = self::HORIZONTAL_CENTER_CONTINUOUS; } if ($this->isSupervisor) { @@ -239,9 +300,7 @@ class Alignment extends Supervisor */ public function setVertical($verticalAlignment) { - if ($verticalAlignment == '') { - $verticalAlignment = self::VERTICAL_BOTTOM; - } + $verticalAlignment = strtolower($verticalAlignment); if ($this->isSupervisor) { $styleArray = $this->getStyleArray(['vertical' => $verticalAlignment]); diff --git a/src/PhpSpreadsheet/Style/Color.php b/src/PhpSpreadsheet/Style/Color.php index 9ab0c98f..922be803 100644 --- a/src/PhpSpreadsheet/Style/Color.php +++ b/src/PhpSpreadsheet/Style/Color.php @@ -387,8 +387,6 @@ class Color extends Supervisor * @param int $colorIndex Index entry point into the colour array * @param bool $background Flag to indicate whether default background or foreground colour * should be returned if the indexed colour doesn't exist - * - * @return Color */ public static function indexedColor($colorIndex, $background = false, ?array $palette = null): self { diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php index 54513670..f7a2eee1 100644 --- a/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php +++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php @@ -11,10 +11,10 @@ class ConditionalDataBar /** children */ - /** @var ConditionalFormatValueObject */ + /** @var ?ConditionalFormatValueObject */ private $minimumConditionalFormatValueObject; - /** @var ConditionalFormatValueObject */ + /** @var ?ConditionalFormatValueObject */ private $maximumConditionalFormatValueObject; /** @var string */ @@ -22,7 +22,7 @@ class ConditionalDataBar /** */ - /** @var ConditionalFormattingRuleExtension */ + /** @var ?ConditionalFormattingRuleExtension */ private $conditionalFormattingRuleExt; /** @@ -43,10 +43,7 @@ class ConditionalDataBar return $this; } - /** - * @return ConditionalFormatValueObject - */ - public function getMinimumConditionalFormatValueObject() + public function getMinimumConditionalFormatValueObject(): ?ConditionalFormatValueObject { return $this->minimumConditionalFormatValueObject; } @@ -58,10 +55,7 @@ class ConditionalDataBar return $this; } - /** - * @return ConditionalFormatValueObject - */ - public function getMaximumConditionalFormatValueObject() + public function getMaximumConditionalFormatValueObject(): ?ConditionalFormatValueObject { return $this->maximumConditionalFormatValueObject; } @@ -85,10 +79,7 @@ class ConditionalDataBar return $this; } - /** - * @return ConditionalFormattingRuleExtension - */ - public function getConditionalFormattingRuleExt() + public function getConditionalFormattingRuleExt(): ?ConditionalFormattingRuleExtension { return $this->conditionalFormattingRuleExt; } diff --git a/src/PhpSpreadsheet/Style/Font.php b/src/PhpSpreadsheet/Style/Font.php index 19d67563..3d7bc1bc 100644 --- a/src/PhpSpreadsheet/Style/Font.php +++ b/src/PhpSpreadsheet/Style/Font.php @@ -743,14 +743,14 @@ class Font extends Supervisor private function hashChartColor(?ChartColor $underlineColor): string { - if ($this->underlineColor === null) { + if ($underlineColor === null) { return ''; } return - $this->underlineColor->getValue() - . $this->underlineColor->getType() - . (string) $this->underlineColor->getAlpha(); + $underlineColor->getValue() + . $underlineColor->getType() + . (string) $underlineColor->getAlpha(); } /** diff --git a/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php b/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php index f4d3412b..07aaff1f 100644 --- a/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php +++ b/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php @@ -20,7 +20,8 @@ class PercentageFormatter extends BaseFormatter $format = str_replace('%', '%%', $format); $wholePartSize = strlen((string) floor($value)); - $decimalPartSize = $placeHolders = 0; + $decimalPartSize = 0; + $placeHolders = ''; // Number of decimals if (preg_match('/\.([?0]+)/u', $format, $matches)) { $decimalPartSize = strlen($matches[1]); @@ -29,12 +30,13 @@ class PercentageFormatter extends BaseFormatter $placeHolders = str_repeat(' ', strlen($matches[1]) - $decimalPartSize); } // Number of digits to display before the decimal - if (preg_match('/([#0,]+)\./u', $format, $matches)) { - $wholePartSize = max($wholePartSize, strlen($matches[1])); + if (preg_match('/([#0,]+)\.?/u', $format, $matches)) { + $firstZero = preg_replace('/^[#,]*/', '', $matches[1]); + $wholePartSize = max($wholePartSize, strlen($firstZero)); } - $wholePartSize += $decimalPartSize; - $replacement = "{$wholePartSize}.{$decimalPartSize}"; + $wholePartSize += $decimalPartSize + (int) ($decimalPartSize > 0); + $replacement = "0{$wholePartSize}.{$decimalPartSize}"; $mask = (string) preg_replace('/[#0,]+\.?[?#0,]*/ui', "%{$replacement}f{$placeHolders}", $format); /** @var float */ diff --git a/src/PhpSpreadsheet/Worksheet/CellIterator.php b/src/PhpSpreadsheet/Worksheet/CellIterator.php index 17286f9c..94877f66 100644 --- a/src/PhpSpreadsheet/Worksheet/CellIterator.php +++ b/src/PhpSpreadsheet/Worksheet/CellIterator.php @@ -8,6 +8,7 @@ use PhpOffice\PhpSpreadsheet\Collection\Cells; /** * @template TKey + * * @implements Iterator */ abstract class CellIterator implements Iterator diff --git a/src/PhpSpreadsheet/Worksheet/PageSetup.php b/src/PhpSpreadsheet/Worksheet/PageSetup.php index c23bfc59..4bdc2d4c 100644 --- a/src/PhpSpreadsheet/Worksheet/PageSetup.php +++ b/src/PhpSpreadsheet/Worksheet/PageSetup.php @@ -259,10 +259,11 @@ class PageSetup /** * First page number. * - * @var int + * @var ?int */ private $firstPageNumber; + /** @var string */ private $pageOrder = self::PAGEORDER_DOWN_THEN_OVER; /** @@ -375,7 +376,7 @@ class PageSetup { // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface, // but it is apparently still able to handle any scale >= 0, where 0 results in 100 - if (($scale >= 0) || $scale === null) { + if ($scale === null || $scale >= 0) { $this->scale = $scale; if ($update) { $this->fitToPage = false; @@ -845,7 +846,7 @@ class PageSetup /** * Get first page number. * - * @return int + * @return ?int */ public function getFirstPageNumber() { @@ -855,7 +856,7 @@ class PageSetup /** * Set first page number. * - * @param int $value + * @param ?int $value * * @return $this */ diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 45111f3c..55327932 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -32,14 +32,20 @@ use PhpOffice\PhpSpreadsheet\Style\Style; class Worksheet implements IComparable { // Break types - const BREAK_NONE = 0; - const BREAK_ROW = 1; - const BREAK_COLUMN = 2; + public const BREAK_NONE = 0; + public const BREAK_ROW = 1; + public const BREAK_COLUMN = 2; // Sheet state - const SHEETSTATE_VISIBLE = 'visible'; - const SHEETSTATE_HIDDEN = 'hidden'; - const SHEETSTATE_VERYHIDDEN = 'veryHidden'; + public const SHEETSTATE_VISIBLE = 'visible'; + public const SHEETSTATE_HIDDEN = 'hidden'; + public const SHEETSTATE_VERYHIDDEN = 'veryHidden'; + + public const MERGE_CELL_CONTENT_EMPTY = 'empty'; + public const MERGE_CELL_CONTENT_HIDE = 'hide'; + public const MERGE_CELL_CONTENT_MERGE = 'merge'; + + protected const SHEET_NAME_REQUIRES_NO_QUOTES = '/^[_\p{L}][_\p{L}\p{N}]*$/mui'; /** * Maximum 31 characters allowed for sheet title. @@ -1265,7 +1271,7 @@ class Worksheet implements IComparable } } elseif ( !preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $coordinate) && - preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/i', $coordinate) + preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/iu', $coordinate) ) { // Named range? $namedRange = $this->validateNamedRange($coordinate, true); @@ -1336,7 +1342,7 @@ class Worksheet implements IComparable * * @return Cell Cell that was created */ - public function createNewCell($coordinate) + public function createNewCell($coordinate): Cell { [$column, $row, $columnString] = Coordinate::indexesFromString($coordinate); $cell = new Cell(null, DataType::TYPE_NULL, $this); @@ -1357,7 +1363,7 @@ class Worksheet implements IComparable if ($rowDimension !== null && $rowDimension->getXfIndex() > 0) { // then there is a row dimension with explicit style, assign it to the cell - $cell->setXfIndex($rowDimension->getXfIndex()); + $cell->setXfIndex(/** @scrutinizer ignore-type */ $rowDimension->getXfIndex()); } elseif ($columnDimension !== null && $columnDimension->getXfIndex() > 0) { // then there is a column dimension, assign it to the cell $cell->setXfIndex($columnDimension->getXfIndex()); @@ -1756,10 +1762,15 @@ class Worksheet implements IComparable * @param AddressRange|array|string $range A simple string containing a Cell range like 'A1:E10' * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), * or an AddressRange. + * @param string $behaviour How the merged cells should behave. + * Possible values are: + * MERGE_CELL_CONTENT_EMPTY - Empty the content of the hidden cells + * MERGE_CELL_CONTENT_HIDE - Keep the content of the hidden cells + * MERGE_CELL_CONTENT_MERGE - Move the content of the hidden cells into the first cell * * @return $this */ - public function mergeCells($range) + public function mergeCells($range, $behaviour = self::MERGE_CELL_CONTENT_EMPTY) { $range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range)); @@ -1791,18 +1802,22 @@ class Worksheet implements IComparable $this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL); } - // Blank out the rest of the cells in the range (if they exist) - if ($numberRows > $numberColumns) { - $this->clearMergeCellsByColumn($firstColumn, $lastColumn, $firstRow, $lastRow, $upperLeft); - } else { - $this->clearMergeCellsByRow($firstColumn, $lastColumnIndex, $firstRow, $lastRow, $upperLeft); + if ($behaviour !== self::MERGE_CELL_CONTENT_HIDE) { + // Blank out the rest of the cells in the range (if they exist) + if ($numberRows > $numberColumns) { + $this->clearMergeCellsByColumn($firstColumn, $lastColumn, $firstRow, $lastRow, $upperLeft, $behaviour); + } else { + $this->clearMergeCellsByRow($firstColumn, $lastColumnIndex, $firstRow, $lastRow, $upperLeft, $behaviour); + } } return $this; } - private function clearMergeCellsByColumn(string $firstColumn, string $lastColumn, int $firstRow, int $lastRow, string $upperLeft): void + private function clearMergeCellsByColumn(string $firstColumn, string $lastColumn, int $firstRow, int $lastRow, string $upperLeft, string $behaviour): void { + $leftCellValue = [$this->getCell($upperLeft)->getFormattedValue()]; + foreach ($this->getColumnIterator($firstColumn, $lastColumn) as $column) { $iterator = $column->getCellIterator($firstRow); $iterator->setIterateOnlyExistingCells(true); @@ -1812,17 +1827,21 @@ class Worksheet implements IComparable if ($row > $lastRow) { break; } - $thisCell = $cell->getColumn() . $row; - if ($upperLeft !== $thisCell) { - $cell->setValueExplicit(null, DataType::TYPE_NULL); - } + $leftCellValue = $this->mergeCellBehaviour($cell, $upperLeft, $behaviour, $leftCellValue); } } } + + $leftCellValue = implode(' ', $leftCellValue); + if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) { + $this->getCell($upperLeft)->setValueExplicit($leftCellValue, DataType::TYPE_STRING); + } } - private function clearMergeCellsByRow(string $firstColumn, int $lastColumnIndex, int $firstRow, int $lastRow, string $upperLeft): void + private function clearMergeCellsByRow(string $firstColumn, int $lastColumnIndex, int $firstRow, int $lastRow, string $upperLeft, string $behaviour): void { + $leftCellValue = [$this->getCell($upperLeft)->getFormattedValue()]; + foreach ($this->getRowIterator($firstRow, $lastRow) as $row) { $iterator = $row->getCellIterator($firstColumn); $iterator->setIterateOnlyExistingCells(true); @@ -1833,13 +1852,31 @@ class Worksheet implements IComparable if ($columnIndex > $lastColumnIndex) { break; } - $thisCell = $column . $cell->getRow(); - if ($upperLeft !== $thisCell) { - $cell->setValueExplicit(null, DataType::TYPE_NULL); - } + $leftCellValue = $this->mergeCellBehaviour($cell, $upperLeft, $behaviour, $leftCellValue); } } } + + $leftCellValue = implode(' ', $leftCellValue); + if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) { + $this->getCell($upperLeft)->setValueExplicit($leftCellValue, DataType::TYPE_STRING); + } + } + + public function mergeCellBehaviour(Cell $cell, string $upperLeft, string $behaviour, array $leftCellValue): array + { + if ($cell->getCoordinate() !== $upperLeft) { + Calculation::getInstance($cell->getWorksheet()->getParent())->flushInstance(); + if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) { + $cellValue = $cell->getFormattedValue(); + if ($cellValue !== '') { + $leftCellValue[] = $cellValue; + } + } + $cell->setValueExplicit(null, DataType::TYPE_NULL); + } + + return $leftCellValue; } /** @@ -1854,17 +1891,22 @@ class Worksheet implements IComparable * @param int $row1 Numeric row coordinate of the first cell * @param int $columnIndex2 Numeric column coordinate of the last cell * @param int $row2 Numeric row coordinate of the last cell + * @param string $behaviour How the merged cells should behave. + * Possible values are: + * MERGE_CELL_CONTENT_EMPTY - Empty the content of the hidden cells + * MERGE_CELL_CONTENT_HIDE - Keep the content of the hidden cells + * MERGE_CELL_CONTENT_MERGE - Move the content of the hidden cells into the first cell * * @return $this */ - public function mergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2) + public function mergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2, $behaviour = self::MERGE_CELL_CONTENT_EMPTY) { $cellRange = new CellRange( CellAddress::fromColumnAndRow($columnIndex1, $row1), CellAddress::fromColumnAndRow($columnIndex2, $row2) ); - return $this->mergeCells($cellRange); + return $this->mergeCells($cellRange, $behaviour); } /** @@ -2459,10 +2501,8 @@ class Worksheet implements IComparable /** * Show gridlines? - * - * @return bool */ - public function getShowGridlines() + public function getShowGridlines(): bool { return $this->showGridlines; } @@ -2474,7 +2514,7 @@ class Worksheet implements IComparable * * @return $this */ - public function setShowGridlines($showGridLines) + public function setShowGridlines(bool $showGridLines): self { $this->showGridlines = $showGridLines; @@ -2483,10 +2523,8 @@ class Worksheet implements IComparable /** * Print gridlines? - * - * @return bool */ - public function getPrintGridlines() + public function getPrintGridlines(): bool { return $this->printGridlines; } @@ -2498,7 +2536,7 @@ class Worksheet implements IComparable * * @return $this */ - public function setPrintGridlines($printGridLines) + public function setPrintGridlines(bool $printGridLines): self { $this->printGridlines = $printGridLines; @@ -2507,10 +2545,8 @@ class Worksheet implements IComparable /** * Show row and column headers? - * - * @return bool */ - public function getShowRowColHeaders() + public function getShowRowColHeaders(): bool { return $this->showRowColHeaders; } @@ -2522,7 +2558,7 @@ class Worksheet implements IComparable * * @return $this */ - public function setShowRowColHeaders($showRowColHeaders) + public function setShowRowColHeaders(bool $showRowColHeaders): self { $this->showRowColHeaders = $showRowColHeaders; @@ -2531,10 +2567,8 @@ class Worksheet implements IComparable /** * Show summary below? (Row/Column outlining). - * - * @return bool */ - public function getShowSummaryBelow() + public function getShowSummaryBelow(): bool { return $this->showSummaryBelow; } @@ -2546,7 +2580,7 @@ class Worksheet implements IComparable * * @return $this */ - public function setShowSummaryBelow($showSummaryBelow) + public function setShowSummaryBelow(bool $showSummaryBelow): self { $this->showSummaryBelow = $showSummaryBelow; @@ -2555,10 +2589,8 @@ class Worksheet implements IComparable /** * Show summary right? (Row/Column outlining). - * - * @return bool */ - public function getShowSummaryRight() + public function getShowSummaryRight(): bool { return $this->showSummaryRight; } @@ -2570,7 +2602,7 @@ class Worksheet implements IComparable * * @return $this */ - public function setShowSummaryRight($showSummaryRight) + public function setShowSummaryRight(bool $showSummaryRight): self { $this->showSummaryRight = $showSummaryRight; @@ -2594,7 +2626,7 @@ class Worksheet implements IComparable * * @return $this */ - public function setComments(array $comments) + public function setComments(array $comments): self { $this->comments = $comments; @@ -2609,7 +2641,7 @@ class Worksheet implements IComparable * * @return $this */ - public function removeComment($cellCoordinate) + public function removeComment($cellCoordinate): self { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($cellCoordinate)); @@ -2633,10 +2665,8 @@ class Worksheet implements IComparable * * @param array|CellAddress|string $cellCoordinate Coordinate of the cell as a string, eg: 'C5'; * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. - * - * @return Comment */ - public function getComment($cellCoordinate) + public function getComment($cellCoordinate): Comment { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($cellCoordinate)); @@ -2669,10 +2699,8 @@ class Worksheet implements IComparable * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell - * - * @return Comment */ - public function getCommentByColumnAndRow($columnIndex, $row) + public function getCommentByColumnAndRow($columnIndex, $row): Comment { return $this->getComment(Coordinate::stringFromColumnIndex($columnIndex) . $row); } @@ -3051,7 +3079,11 @@ class Worksheet implements IComparable * Extract worksheet title from range. * * Example: extractSheetTitle("testSheet!A1") ==> 'A1' + * Example: extractSheetTitle("testSheet!A1:C3") ==> 'A1:C3' * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> ['testSheet 1', 'A1']; + * Example: extractSheetTitle("'testSheet 1'!A1:C3", true) ==> ['testSheet 1', 'A1:C3']; + * Example: extractSheetTitle("A1", true) ==> ['', 'A1']; + * Example: extractSheetTitle("A1:C3", true) ==> ['', 'A1:C3'] * * @param string $range Range to extract title from * @param bool $returnRange Return range? (see example) @@ -3450,4 +3482,9 @@ class Worksheet implements IComparable { return $this->codeName !== null; } + + public static function nameRequiresQuotes(string $sheetName): bool + { + return preg_match(self::SHEET_NAME_REQUIRES_NO_QUOTES, $sheetName) !== 1; + } } diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index da32025a..0fef0f60 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -24,6 +24,7 @@ use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; +use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class Html extends BaseWriter @@ -54,7 +55,7 @@ class Html extends BaseWriter * * @var bool */ - private $embedImages = false; + protected $embedImages = false; /** * Use inline CSS? @@ -126,6 +127,13 @@ class Html extends BaseWriter */ protected $isPdf = false; + /** + * Is the current writer creating mPDF? + * + * @var bool + */ + protected $isMPdf = false; + /** * Generate the Navigation block. * @@ -223,13 +231,6 @@ class Html extends BaseWriter $this->editHtmlCallback = $callback; } - const VALIGN_ARR = [ - Alignment::VERTICAL_BOTTOM => 'bottom', - Alignment::VERTICAL_TOP => 'top', - Alignment::VERTICAL_CENTER => 'middle', - Alignment::VERTICAL_JUSTIFY => 'middle', - ]; - /** * Map VAlign. * @@ -239,17 +240,9 @@ class Html extends BaseWriter */ private function mapVAlign($vAlign) { - return array_key_exists($vAlign, self::VALIGN_ARR) ? self::VALIGN_ARR[$vAlign] : 'baseline'; + return Alignment::VERTICAL_ALIGNMENT_FOR_HTML[$vAlign] ?? ''; } - const HALIGN_ARR = [ - Alignment::HORIZONTAL_LEFT => 'left', - Alignment::HORIZONTAL_RIGHT => 'right', - Alignment::HORIZONTAL_CENTER => 'center', - Alignment::HORIZONTAL_CENTER_CONTINUOUS => 'center', - Alignment::HORIZONTAL_JUSTIFY => 'justify', - ]; - /** * Map HAlign. * @@ -259,7 +252,7 @@ class Html extends BaseWriter */ private function mapHAlign($hAlign) { - return array_key_exists($hAlign, self::HALIGN_ARR) ? self::HALIGN_ARR[$hAlign] : ''; + return Alignment::HORIZONTAL_ALIGNMENT_FOR_HTML[$hAlign] ?? ''; } const BORDER_ARR = [ @@ -280,7 +273,7 @@ class Html extends BaseWriter /** * Map border style. * - * @param int $borderStyle Sheet index + * @param int|string $borderStyle Sheet index * * @return string */ @@ -347,7 +340,7 @@ class Html extends BaseWriter return $this; } - private static function generateMeta($val, $desc) + private static function generateMeta(?string $val, string $desc): string { return $val ? (' ' . PHP_EOL) @@ -391,7 +384,7 @@ class Html extends BaseWriter return $html; } - private function generateSheetPrep() + private function generateSheetPrep(): array { // Ensure that Spans have been calculated? $this->calculateSpans(); @@ -406,7 +399,7 @@ class Html extends BaseWriter return $sheets; } - private function generateSheetStarts($sheet, $rowMin) + private function generateSheetStarts(Worksheet $sheet, int $rowMin): array { // calculate start of , $tbodyStart = $rowMin; @@ -425,7 +418,7 @@ class Html extends BaseWriter return [$theadStart, $theadEnd, $tbodyStart]; } - private function generateSheetTags($row, $theadStart, $theadEnd, $tbodyStart) + private function generateSheetTags(int $row, int $theadStart, int $theadEnd, int $tbodyStart): array { // ? $startTag = ($row == $theadStart) ? (' ' . PHP_EOL) : ''; @@ -544,15 +537,10 @@ class Html extends BaseWriter * Extend Row if chart is placed after nominal end of row. * This code should be exercised by sample: * Chart/32_Chart_read_write_PDF.php. - * However, that test is suppressed due to out-of-date - * Jpgraph code issuing warnings. So, don't measure - * code coverage for this function till that is fixed. * * @param int $row Row to check for charts * * @return array - * - * @codeCoverageIgnore */ private function extendRowsForCharts(Worksheet $worksheet, int $row) { @@ -628,11 +616,12 @@ class Html extends BaseWriter * * @return string */ - public static function winFileToUrl($filename) + public static function winFileToUrl($filename, bool $mpdf = false) { // Windows filename if (substr($filename, 1, 2) === ':\\') { - $filename = 'file:///' . str_replace('\\', '/', $filename); + $protocol = $mpdf ? '' : 'file:///'; + $filename = $protocol . str_replace('\\', '/', $filename); } return $filename; @@ -674,12 +663,12 @@ class Html extends BaseWriter $filename = htmlspecialchars($filename, Settings::htmlEntityFlags()); $html .= PHP_EOL; - $imageData = self::winFileToUrl($filename); + $imageData = self::winFileToUrl($filename, $this->isMPdf); - if (($this->embedImages && !$this->isPdf) || substr($imageData, 0, 6) === 'zip://') { + if ($this->embedImages || substr($imageData, 0, 6) === 'zip://') { $picture = @file_get_contents($filename); if ($picture !== false) { - $imageDetails = getimagesize($filename); + $imageDetails = getimagesize($filename) ?: []; // base64 encode the binary data $base64 = base64_encode($picture); $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; @@ -694,12 +683,10 @@ class Html extends BaseWriter $imageResource = $drawing->getImageResource(); if ($imageResource) { ob_start(); // Let's start output buffering. - // @phpstan-ignore-next-line imagepng($imageResource); // This will normally output the image, but because of ob_start(), it won't. - $contents = ob_get_contents(); // Instead, output above is saved to $contents + $contents = (string) ob_get_contents(); // Instead, output above is saved to $contents ob_end_clean(); // End the output buffer. - /** @phpstan-ignore-next-line */ $dataUri = 'data:image/png;base64,' . base64_encode($contents); // Because of the nature of tables, width is more important than height. @@ -718,11 +705,6 @@ class Html extends BaseWriter * Generate chart tag in cell. * This code should be exercised by sample: * Chart/32_Chart_read_write_PDF.php. - * However, that test is suppressed due to out-of-date - * Jpgraph code issuing warnings. So, don't measure - * code coverage for this function till that is fixed. - * - * @codeCoverageIgnore */ private function writeChartInCell(Worksheet $worksheet, string $coordinates): string { @@ -740,21 +722,18 @@ class Html extends BaseWriter } $html .= PHP_EOL; - $imageDetails = getimagesize($chartFileName); + $imageDetails = getimagesize($chartFileName) ?: []; $filedesc = $chart->getTitle(); $filedesc = $filedesc ? $filedesc->getCaptionText() : ''; $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded chart'; - if ($fp = fopen($chartFileName, 'rb', 0)) { - $picture = fread($fp, filesize($chartFileName)); - fclose($fp); - /** @phpstan-ignore-next-line */ + $picture = file_get_contents($chartFileName); + if ($picture !== false) { $base64 = base64_encode($picture); $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; $html .= '' . $filedesc . '' . PHP_EOL; - - unlink($chartFileName); } + unlink($chartFileName); } } } @@ -942,8 +921,8 @@ class Html extends BaseWriter // Calculate cell style hashes foreach ($this->spreadsheet->getCellXfCollection() as $index => $style) { - $css['td.style' . $index] = $this->createCSSStyle($style); - $css['th.style' . $index] = $this->createCSSStyle($style); + $css['td.style' . $index . ', th.style' . $index] = $this->createCSSStyle($style); + //$css['th.style' . $index] = $this->createCSSStyle($style); } // Fetch sheets @@ -995,14 +974,25 @@ class Html extends BaseWriter $css = []; // Create CSS - $css['vertical-align'] = $this->mapVAlign($alignment->getVertical()); - $textAlign = $this->mapHAlign($alignment->getHorizontal()); + $verticalAlign = $this->mapVAlign($alignment->getVertical() ?? ''); + if ($verticalAlign) { + $css['vertical-align'] = $verticalAlign; + } + $textAlign = $this->mapHAlign($alignment->getHorizontal() ?? ''); if ($textAlign) { $css['text-align'] = $textAlign; if (in_array($textAlign, ['left', 'right'])) { $css['padding-' . $textAlign] = (string) ((int) $alignment->getIndent() * 9) . 'px'; } } + $rotation = $alignment->getTextRotation(); + if ($rotation !== 0 && $rotation !== Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) { + if ($this->isMPdf) { + $css['text-rotate'] = "$rotation"; + } else { + $css['transform'] = "rotate({$rotation}deg)"; + } + } return $css; } @@ -1064,10 +1054,8 @@ class Html extends BaseWriter * Create CSS style. * * @param Border $border Border - * - * @return string */ - private function createCSSStyleBorder(Border $border) + private function createCSSStyleBorder(Border $border): string { // Create CSS - add !important to non-none border styles for merged cells $borderStyle = $this->mapBorderStyle($border->getBorderStyle()); @@ -1088,9 +1076,11 @@ class Html extends BaseWriter $css = []; // Create CSS - $value = $fill->getFillType() == Fill::FILL_NONE ? - 'white' : '#' . $fill->getStartColor()->getRGB(); - $css['background-color'] = $value; + if ($fill->getFillType() !== Fill::FILL_NONE) { + $value = $fill->getFillType() == Fill::FILL_NONE ? + 'white' : '#' . $fill->getStartColor()->getRGB(); + $css['background-color'] = $value; + } return $css; } @@ -1098,7 +1088,7 @@ class Html extends BaseWriter /** * Generate HTML footer. */ - public function generateHTMLFooter() + public function generateHTMLFooter(): string { // Construct HTML $html = ''; @@ -1108,7 +1098,7 @@ class Html extends BaseWriter return $html; } - private function generateTableTagInline(Worksheet $worksheet, $id) + private function generateTableTagInline(Worksheet $worksheet, string $id): string { $style = isset($this->cssStyles['table']) ? $this->assembleCSS($this->cssStyles['table']) : ''; @@ -1128,7 +1118,7 @@ class Html extends BaseWriter return $html; } - private function generateTableTag(Worksheet $worksheet, $id, &$html, $sheetIndex): void + private function generateTableTag(Worksheet $worksheet, string $id, string &$html, int $sheetIndex): void { if (!$this->useInlineCss) { $gridlines = $worksheet->getShowGridlines() ? ' gridlines' : ''; @@ -1155,9 +1145,9 @@ class Html extends BaseWriter $html = ''; $id = $showid ? "id='sheet$sheetIndex'" : ''; if ($showid) { - $html .= "
\n"; + $html .= "
" . PHP_EOL; } else { - $html .= "
\n"; + $html .= "
" . PHP_EOL; } $this->generateTableTag($worksheet, $id, $html, $sheetIndex); @@ -1181,7 +1171,7 @@ class Html extends BaseWriter /** * Generate table footer. */ - private function generateTableFooter() + private function generateTableFooter(): string { return ' ' . PHP_EOL . '
' . PHP_EOL; } @@ -1227,7 +1217,7 @@ class Html extends BaseWriter return $html; } - private function generateRowCellCss(Worksheet $worksheet, $cellAddress, $row, $columnNumber) + private function generateRowCellCss(Worksheet $worksheet, string $cellAddress, int $row, int $columnNumber): array { $cell = ($cellAddress > '') ? $worksheet->getCellCollection()->get($cellAddress) : ''; $coordinate = Coordinate::stringFromColumnIndex($columnNumber + 1) . ($row + 1); @@ -1253,22 +1243,24 @@ class Html extends BaseWriter return [$cell, $cssClass, $coordinate]; } - private function generateRowCellDataValueRich($cell, &$cellData): void + private function generateRowCellDataValueRich(Cell $cell, string &$cellData): void { // Loop through rich text elements $elements = $cell->getValue()->getRichTextElements(); foreach ($elements as $element) { // Rich text start? if ($element instanceof Run) { - $cellData .= ''; - $cellEnd = ''; - if ($element->getFont()->getSuperscript()) { - $cellData .= ''; - $cellEnd = ''; - } elseif ($element->getFont()->getSubscript()) { - $cellData .= ''; - $cellEnd = ''; + if ($element->getFont() !== null) { + $cellData .= ''; + + if ($element->getFont()->getSuperscript()) { + $cellData .= ''; + $cellEnd = ''; + } elseif ($element->getFont()->getSubscript()) { + $cellData .= ''; + $cellEnd = ''; + } } // Convert UTF8 data to PCDATA @@ -1286,23 +1278,22 @@ class Html extends BaseWriter } } - private function generateRowCellDataValue(Worksheet $worksheet, $cell, &$cellData): void + private function generateRowCellDataValue(Worksheet $worksheet, Cell $cell, string &$cellData): void { if ($cell->getValue() instanceof RichText) { $this->generateRowCellDataValueRich($cell, $cellData); } else { $origData = $this->preCalculateFormulas ? $cell->getCalculatedValue() : $cell->getValue(); $formatCode = $worksheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(); - if ($formatCode !== null) { - $cellData = NumberFormat::toFormattedString( - $origData, - $formatCode, - [$this, 'formatColor'] - ); - } + + $cellData = NumberFormat::toFormattedString( + $origData ?? '', + $formatCode ?? NumberFormat::FORMAT_GENERAL, + [$this, 'formatColor'] + ); if ($cellData === $origData) { - $cellData = htmlspecialchars($cellData ?? '', Settings::htmlEntityFlags()); + $cellData = htmlspecialchars($cellData, Settings::htmlEntityFlags()); } if ($worksheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) { $cellData = '' . $cellData . ''; @@ -1312,7 +1303,11 @@ class Html extends BaseWriter } } - private function generateRowCellData(Worksheet $worksheet, $cell, &$cssClass, $cellType) + /** + * @param null|Cell|string $cell + * @param array|string $cssClass + */ + private function generateRowCellData(Worksheet $worksheet, $cell, &$cssClass, string $cellType): string { $cellData = ' '; if ($cell instanceof Cell) { @@ -1332,10 +1327,10 @@ class Html extends BaseWriter $cellData = nl2br($cellData); // Extend CSS class? - if (!$this->useInlineCss) { + if (!$this->useInlineCss && is_string($cssClass)) { $cssClass .= ' style' . $cell->getXfIndex(); $cssClass .= ' ' . $cell->getDataType(); - } else { + } elseif (is_array($cssClass)) { if ($cellType == 'th') { if (isset($this->cssStyles['th.style' . $cell->getXfIndex()])) { $cssClass = array_merge($cssClass, $this->cssStyles['th.style' . $cell->getXfIndex()]); @@ -1365,12 +1360,12 @@ class Html extends BaseWriter return $cellData; } - private function generateRowIncludeCharts(Worksheet $worksheet, $coordinate) + private function generateRowIncludeCharts(Worksheet $worksheet, string $coordinate): string { return $this->includeCharts ? $this->writeChartInCell($worksheet, $coordinate) : ''; } - private function generateRowSpans($html, $rowSpan, $colSpan) + private function generateRowSpans(string $html, int $rowSpan, int $colSpan): string { $html .= ($colSpan > 1) ? (' colspan="' . $colSpan . '"') : ''; $html .= ($rowSpan > 1) ? (' rowspan="' . $rowSpan . '"') : ''; @@ -1378,7 +1373,10 @@ class Html extends BaseWriter return $html; } - private function generateRowWriteCell(&$html, Worksheet $worksheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $row): void + /** + * @param array|string $cssClass + */ + private function generateRowWriteCell(string &$html, Worksheet $worksheet, string $coordinate, string $cellType, string $cellData, int $colSpan, int $rowSpan, $cssClass, int $colNum, int $sheetIndex, int $row): void { // Image? $htmlx = $this->writeImageInCell($worksheet, $coordinate); @@ -1386,7 +1384,7 @@ class Html extends BaseWriter $htmlx .= $this->generateRowIncludeCharts($worksheet, $coordinate); // Column start $html .= ' <' . $cellType; - if (!$this->useInlineCss && !$this->isPdf) { + if (!$this->useInlineCss && !$this->isPdf && is_string($cssClass)) { $html .= ' class="' . $cssClass . '"'; if ($htmlx) { $html .= " style='position: relative;'"; @@ -1396,9 +1394,11 @@ class Html extends BaseWriter // We must explicitly write the width of the element because TCPDF // does not recognize e.g. if ($this->useInlineCss) { - $xcssClass = $cssClass; + $xcssClass = is_array($cssClass) ? $cssClass : []; } else { - $html .= ' class="' . $cssClass . '"'; + if (is_string($cssClass)) { + $html .= ' class="' . $cssClass . '"'; + } $xcssClass = []; } $width = 0; @@ -1409,8 +1409,7 @@ class Html extends BaseWriter $width += $this->columnWidths[$sheetIndex][$i]; } } - $xcssClass['width'] = $width . 'pt'; - + $xcssClass['width'] = (string) $width . 'pt'; // We must also explicitly write the height of the element because TCPDF // does not recognize e.g. if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $row]['height'])) { @@ -1452,15 +1451,16 @@ class Html extends BaseWriter // Sheet index $sheetIndex = $worksheet->getParent()->getIndex($worksheet); $html = $this->generateRowStart($worksheet, $sheetIndex, $row); + $generateDiv = $this->isMPdf && $worksheet->getRowDimension($row + 1)->getVisible() === false; + if ($generateDiv) { + $html .= '
' . PHP_EOL; + } // Write cells $colNum = 0; foreach ($values as $cellAddress) { [$cell, $cssClass, $coordinate] = $this->generateRowCellCss($worksheet, $cellAddress, $row, $colNum); - $colSpan = 1; - $rowSpan = 1; - // Cell Data $cellData = $this->generateRowCellData($worksheet, $cell, $cssClass, $cellType); @@ -1474,8 +1474,8 @@ class Html extends BaseWriter && $this->isSpannedCell[$worksheet->getParent()->getIndex($worksheet)][$row + 1][$colNum]); // Colspan and Rowspan - $colspan = 1; - $rowspan = 1; + $colSpan = 1; + $rowSpan = 1; if (isset($this->isBaseCell[$worksheet->getParent()->getIndex($worksheet)][$row + 1][$colNum])) { $spans = $this->isBaseCell[$worksheet->getParent()->getIndex($worksheet)][$row + 1][$colNum]; $rowSpan = $spans['rowspan']; @@ -1499,6 +1499,9 @@ class Html extends BaseWriter } // Write row end + if ($generateDiv) { + $html .= '
' . PHP_EOL; + } $html .= ' ' . PHP_EOL; // Return @@ -1722,7 +1725,7 @@ class Html extends BaseWriter $this->spansAreCalculated = true; } - private function calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow): void + private function calculateSpansOmitRows(Worksheet $sheet, int $sheetIndex, array $candidateSpannedRow): void { // Identify which rows should be omitted in HTML. These are the rows where all the cells // participate in a merge and the where base cells are somewhere above. @@ -1785,7 +1788,8 @@ class Html extends BaseWriter public function getOrientation(): ?string { - return null; + // Expect Pdf classes to override this method. + return $this->isPdf ? PageSetup::ORIENTATION_PORTRAIT : null; } /** @@ -1824,31 +1828,31 @@ class Html extends BaseWriter $bottom = StringHelper::FormatNumber($worksheet->getPageMargins()->getBottom()) . 'in; '; $htmlPage .= 'margin-bottom: ' . $bottom; $orientation = $this->getOrientation() ?? $worksheet->getPageSetup()->getOrientation(); - if ($orientation === \PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_LANDSCAPE) { + if ($orientation === PageSetup::ORIENTATION_LANDSCAPE) { $htmlPage .= 'size: landscape; '; - } elseif ($orientation === \PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_PORTRAIT) { + } elseif ($orientation === PageSetup::ORIENTATION_PORTRAIT) { $htmlPage .= 'size: portrait; '; } - $htmlPage .= "}\n"; + $htmlPage .= '}' . PHP_EOL; ++$sheetId; } - $htmlPage .= <<div {margin-top: 5px;} - body>div:first-child {margin-top: 0;} - .scrpgbrk {margin-top: 1px;} -} -@media print { - .gridlinesp td {border: 1px solid black;} - .gridlinesp th {border: 1px solid black;} - .navigation {display: none;} -} - -EOF; + $htmlPage .= implode(PHP_EOL, [ + '.navigation {page-break-after: always;}', + '.scrpgbrk, div + div {page-break-before: always;}', + '@media screen {', + ' .gridlines td {border: 1px solid black;}', + ' .gridlines th {border: 1px solid black;}', + ' body>div {margin-top: 5px;}', + ' body>div:first-child {margin-top: 0;}', + ' .scrpgbrk {margin-top: 1px;}', + '}', + '@media print {', + ' .gridlinesp td {border: 1px solid black;}', + ' .gridlinesp th {border: 1px solid black;}', + ' .navigation {display: none;}', + '}', + '', + ]); $htmlPage .= $generateSurroundingHTML ? ('' . PHP_EOL) : ''; return $htmlPage; diff --git a/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php b/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php index fc96f904..690b0c54 100644 --- a/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php +++ b/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php @@ -7,6 +7,13 @@ use PhpOffice\PhpSpreadsheet\Writer\Pdf; class Dompdf extends Pdf { + /** + * embed images, or link to images. + * + * @var bool + */ + protected $embedImages = true; + /** * Gets the implementation of external PDF library that should be used. * @@ -26,15 +33,15 @@ class Dompdf extends Pdf { $fileHandle = parent::prepareForSave($filename); - // Default PDF paper size - $paperSize = 'LETTER'; // Letter (8.5 in. by 11 in.) - // Check for paper size and page orientation $setup = $this->spreadsheet->getSheet($this->getSheetIndex() ?? 0)->getPageSetup(); $orientation = $this->getOrientation() ?? $setup->getOrientation(); $orientation = ($orientation === PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; $printPaperSize = $this->getPaperSize() ?? $setup->getPaperSize(); $paperSize = self::$paperSizes[$printPaperSize] ?? PageSetup::getPaperSizeDefault(); + if (is_array($paperSize) && count($paperSize) === 2) { + $paperSize = [0.0, 0.0, $paperSize[0], $paperSize[1]]; + } $orientation = ($orientation == 'L') ? 'landscape' : 'portrait'; diff --git a/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php b/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php index 281e1a4f..d0ce9ed4 100644 --- a/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php +++ b/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php @@ -8,6 +8,9 @@ use PhpOffice\PhpSpreadsheet\Writer\Pdf; class Mpdf extends Pdf { + /** @var bool */ + protected $isMPdf = true; + /** * Gets the implementation of external PDF library that should be used. * diff --git a/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php b/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php index d29d4764..aefc6b56 100644 --- a/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php +++ b/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php @@ -77,7 +77,7 @@ class Tcpdf extends Pdf $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator()); // Write to file - fwrite($fileHandle, $pdf->output($filename, 'S')); + fwrite($fileHandle, $pdf->output('', 'S')); parent::restoreStateAfterSave(); } diff --git a/src/PhpSpreadsheet/Writer/Xls.php b/src/PhpSpreadsheet/Writer/Xls.php index eadf0083..69457357 100644 --- a/src/PhpSpreadsheet/Writer/Xls.php +++ b/src/PhpSpreadsheet/Writer/Xls.php @@ -732,7 +732,6 @@ class Xls extends BaseWriter } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length // Null-terminated string $dataProp['data']['data'] .= chr(0); - // @phpstan-ignore-next-line ++$dataProp['data']['length']; // Complete the string with null string for being a %4 $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4)); @@ -750,7 +749,6 @@ class Xls extends BaseWriter } else { $dataSection_Content .= $dataProp['data']['data']; - // @phpstan-ignore-next-line $dataSection_Content_Offset += 4 + $dataProp['data']['length']; } } diff --git a/src/PhpSpreadsheet/Writer/Xls/Parser.php b/src/PhpSpreadsheet/Writer/Xls/Parser.php index 2f75f908..ca407d2a 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Parser.php +++ b/src/PhpSpreadsheet/Writer/Xls/Parser.php @@ -78,7 +78,7 @@ class Parser /** * The parse tree to be generated. * - * @var string + * @var array|string */ public $parseTree; @@ -531,7 +531,7 @@ class Parser { return($this->convertFunction($token, $this->_func_args)); }*/ - // if it's an argument, ignore the token (the argument remains) + // if it's an argument, ignore the token (the argument remains) } elseif ($token == 'arg') { return ''; } @@ -1445,6 +1445,9 @@ class Parser if (empty($tree)) { // If it's the first call use parseTree $tree = $this->parseTree; } + if (!is_array($tree) || !isset($tree['left'], $tree['right'], $tree['value'])) { + throw new WriterException('Unexpected non-array'); + } if (is_array($tree['left'])) { $converted_tree = $this->toReversePolish($tree['left']); @@ -1475,7 +1478,7 @@ class Parser $left_tree = ''; } - // add it's left subtree and return. + // add its left subtree and return. return $left_tree . $this->convertFunction($tree['value'], $tree['right']); } $converted_tree = $this->convert($tree['value']); diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index 37865518..78fda517 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -465,7 +465,7 @@ class Worksheet extends BIFFwriter switch ($calctype) { case 'integer': case 'double': - $this->writeNumber($row, $column, $calculatedValue, $xfIndex); + $this->writeNumber($row, $column, (float) $calculatedValue, $xfIndex); break; case 'string': @@ -473,7 +473,7 @@ class Worksheet extends BIFFwriter break; case 'boolean': - $this->writeBoolErr($row, $column, $calculatedValue, 0, $xfIndex); + $this->writeBoolErr($row, $column, (int) $calculatedValue, 0, $xfIndex); break; default: @@ -2405,10 +2405,12 @@ class Worksheet extends BIFFwriter for ($i = 0; $i < $width; ++$i) { /** @phpstan-ignore-next-line */ $color = imagecolorsforindex($image, imagecolorat($image, $i, $j)); - foreach (['red', 'green', 'blue'] as $key) { - $color[$key] = $color[$key] + (int) round((255 - $color[$key]) * $color['alpha'] / 127); + if ($color !== false) { + foreach (['red', 'green', 'blue'] as $key) { + $color[$key] = $color[$key] + (int) round((255 - $color[$key]) * $color['alpha'] / 127); + } + $data .= chr($color['blue']) . chr($color['green']) . chr($color['red']); } - $data .= chr($color['blue']) . chr($color['green']) . chr($color['red']); } if (3 * $width % 4) { $data .= str_repeat("\x00", 4 - 3 * $width % 4); @@ -2836,7 +2838,7 @@ class Worksheet extends BIFFwriter $operatorType = 0x01; break; - // not OPERATOR_NOTBETWEEN 0x02 + // not OPERATOR_NOTBETWEEN 0x02 } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index 5aca5117..c9446e70 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -349,12 +349,15 @@ class Xlsx extends BaseWriter //a custom UI in this workbook ? add it ("base" xml and additional objects (pictures) and rels) if ($this->spreadSheet->hasRibbon()) { $tmpRibbonTarget = $this->spreadSheet->getRibbonXMLData('target'); + $tmpRibbonTarget = is_string($tmpRibbonTarget) ? $tmpRibbonTarget : ''; $zipContent[$tmpRibbonTarget] = $this->spreadSheet->getRibbonXMLData('data'); if ($this->spreadSheet->hasRibbonBinObjects()) { $tmpRootPath = dirname($tmpRibbonTarget) . '/'; $ribbonBinObjects = $this->spreadSheet->getRibbonBinObjects('data'); //the files to write - foreach ($ribbonBinObjects as $aPath => $aContent) { - $zipContent[$tmpRootPath . $aPath] = $aContent; + if (is_array($ribbonBinObjects)) { + foreach ($ribbonBinObjects as $aPath => $aContent) { + $zipContent[$tmpRootPath . $aPath] = $aContent; + } } //the rels for files $zipContent[$tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels'] = $this->getWriterPartRelsRibbon()->writeRibbonRelationships($this->spreadSheet); @@ -684,6 +687,7 @@ class Xlsx extends BaseWriter return $this; } + /** @var array */ private $pathNames = []; private function addZipFile(string $path, string $content): void diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index d9d96da6..3b64bd8e 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -11,6 +11,7 @@ use PhpOffice\PhpSpreadsheet\Chart\Legend; use PhpOffice\PhpSpreadsheet\Chart\PlotArea; use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Chart\Title; +use PhpOffice\PhpSpreadsheet\Chart\TrendLine; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; @@ -58,7 +59,7 @@ class Chart extends WriterPart $objWriter->writeAttribute('val', 'en-GB'); $objWriter->endElement(); $objWriter->startElement('c:roundedCorners'); - $objWriter->writeAttribute('val', '0'); + $objWriter->writeAttribute('val', $chart->getRoundedCorners() ? '1' : '0'); $objWriter->endElement(); $this->writeAlternateContent($objWriter); @@ -106,11 +107,17 @@ class Chart extends WriterPart $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); - $objWriter->endElement(); + $objWriter->endElement(); // c:chart + if ($chart->getNoFill()) { + $objWriter->startElement('c:spPr'); + $objWriter->startElement('a:noFill'); + $objWriter->endElement(); // a:noFill + $objWriter->endElement(); // c:spPr + } $this->writePrintSettings($objWriter); - $objWriter->endElement(); + $objWriter->endElement(); // c:chartSpace // Return return $objWriter->getData(); @@ -226,8 +233,6 @@ class Chart extends WriterPart if ($plotArea === null) { return; } - $majorGridlines = ($yAxis === null) ? null : $yAxis->getMajorGridlines(); - $minorGridlines = ($yAxis === null) ? null : $yAxis->getMinorGridlines(); $id1 = $id2 = $id3 = '0'; $this->seriesIndex = 0; @@ -360,8 +365,35 @@ class Chart extends WriterPart $this->writeSerAxis($objWriter, $id2, $id3); } } + $stops = $plotArea->getGradientFillStops(); + if ($plotArea->getNoFill() || !empty($stops)) { + $objWriter->startElement('c:spPr'); + if ($plotArea->getNoFill()) { + $objWriter->startElement('a:noFill'); + $objWriter->endElement(); // a:noFill + } + if (!empty($stops)) { + $objWriter->startElement('a:gradFill'); + $objWriter->startElement('a:gsLst'); + foreach ($stops as $stop) { + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', (string) (Properties::PERCENTAGE_MULTIPLIER * (float) $stop[0])); + $this->writeColor($objWriter, $stop[1], false); + $objWriter->endElement(); // a:gs + } + $objWriter->endElement(); // a:gsLst + $angle = $plotArea->getGradientFillAngle(); + if ($angle !== null) { + $objWriter->startElement('a:lin'); + $objWriter->writeAttribute('ang', Properties::angleToXml($angle)); + $objWriter->endElement(); // a:lin + } + $objWriter->endElement(); // a:gradFill + } + $objWriter->endElement(); // c:spPr + } - $objWriter->endElement(); + $objWriter->endElement(); // c:plotArea } private function writeDataLabelsBool(XMLWriter $objWriter, string $name, ?bool $value): void @@ -456,13 +488,14 @@ class Chart extends WriterPart private function writeCategoryAxis(XMLWriter $objWriter, ?Title $xAxisLabel, $id1, $id2, $isMultiLevelSeries, Axis $yAxis): void { // 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->getAxisType() !== '') { - $objWriter->startElement('c:' . $yAxis->getAxisType()); + // In that case, xAxis may contain values like the yAxis, or it may be a date axis (LINECHART). + $axisType = $yAxis->getAxisType(); + if ($axisType !== '') { + $objWriter->startElement("c:$axisType"); } elseif ($yAxis->getAxisIsNumericFormat()) { - $objWriter->startElement('c:valAx'); + $objWriter->startElement('c:' . Axis::AXIS_TYPE_VALUE); } else { - $objWriter->startElement('c:catAx'); + $objWriter->startElement('c:' . Axis::AXIS_TYPE_CATEGORY); } $majorGridlines = $yAxis->getMajorGridlines(); $minorGridlines = $yAxis->getMinorGridlines(); @@ -492,7 +525,7 @@ class Chart extends WriterPart $objWriter->endElement(); // c:scaling $objWriter->startElement('c:delete'); - $objWriter->writeAttribute('val', '0'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('hidden') ?? '0'); $objWriter->endElement(); $objWriter->startElement('c:axPos'); @@ -620,7 +653,8 @@ class Chart extends WriterPart } $objWriter->startElement('c:auto'); - $objWriter->writeAttribute('val', '1'); + // LineChart with dateAx wants '0' + $objWriter->writeAttribute('val', ($axisType === Axis::AXIS_TYPE_DATE) ? '0' : '1'); $objWriter->endElement(); $objWriter->startElement('c:lblAlgn'); @@ -631,6 +665,30 @@ class Chart extends WriterPart $objWriter->writeAttribute('val', '100'); $objWriter->endElement(); + if ($axisType === Axis::AXIS_TYPE_DATE) { + $property = 'baseTimeUnit'; + $propertyVal = $yAxis->getAxisOptionsProperty($property); + if (!empty($propertyVal)) { + $objWriter->startElement("c:$property"); + $objWriter->writeAttribute('val', $propertyVal); + $objWriter->endElement(); + } + $property = 'majorTimeUnit'; + $propertyVal = $yAxis->getAxisOptionsProperty($property); + if (!empty($propertyVal)) { + $objWriter->startElement("c:$property"); + $objWriter->writeAttribute('val', $propertyVal); + $objWriter->endElement(); + } + $property = 'minorTimeUnit'; + $propertyVal = $yAxis->getAxisOptionsProperty($property); + if (!empty($propertyVal)) { + $objWriter->startElement("c:$property"); + $objWriter->writeAttribute('val', $propertyVal); + $objWriter->endElement(); + } + } + if ($isMultiLevelSeries) { $objWriter->startElement('c:noMultiLvlLbl'); $objWriter->writeAttribute('val', '0'); @@ -649,7 +707,7 @@ class Chart extends WriterPart */ private function writeValueAxis(XMLWriter $objWriter, ?Title $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries, Axis $xAxis): void { - $objWriter->startElement('c:valAx'); + $objWriter->startElement('c:' . Axis::AXIS_TYPE_VALUE); $majorGridlines = $xAxis->getMajorGridlines(); $minorGridlines = $xAxis->getMinorGridlines(); @@ -682,7 +740,7 @@ class Chart extends WriterPart $objWriter->endElement(); // c:scaling $objWriter->startElement('c:delete'); - $objWriter->writeAttribute('val', '0'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('hidden') ?? '0'); $objWriter->endElement(); $objWriter->startElement('c:axPos'); @@ -1045,7 +1103,7 @@ class Chart extends WriterPart $objWriter->endElement(); // a:ln } } - $nofill = $groupType == DataSeries::TYPE_STOCKCHART || ($groupType === DataSeries::TYPE_SCATTERCHART && !$plotSeriesValues->getScatterLines()); + $nofill = $groupType === DataSeries::TYPE_STOCKCHART || (($groupType === DataSeries::TYPE_SCATTERCHART || $groupType === DataSeries::TYPE_LINECHART) && !$plotSeriesValues->getScatterLines()); if ($callLineStyles) { $this->writeLineStyles($objWriter, $plotSeriesValues, $nofill); $this->writeEffects($objWriter, $plotSeriesValues); @@ -1090,6 +1148,88 @@ class Chart extends WriterPart $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); } + // Trendlines + if ($plotSeriesValues !== false) { + foreach ($plotSeriesValues->getTrendLines() as $trendLine) { + $trendLineType = $trendLine->getTrendLineType(); + $order = $trendLine->getOrder(); + $period = $trendLine->getPeriod(); + $dispRSqr = $trendLine->getDispRSqr(); + $dispEq = $trendLine->getDispEq(); + $forward = $trendLine->getForward(); + $backward = $trendLine->getBackward(); + $intercept = $trendLine->getIntercept(); + $name = $trendLine->getName(); + $trendLineColor = $trendLine->getLineColor(); // ChartColor + + $objWriter->startElement('c:trendline'); // N.B. lowercase 'ell' + if ($name !== '') { + $objWriter->startElement('c:name'); + $objWriter->writeRawData($name); + $objWriter->endElement(); // c:name + } + $objWriter->startElement('c:spPr'); + + if (!$trendLineColor->isUsable()) { + // use dataSeriesValues line color as a backup if $trendLineColor is null + $dsvLineColor = $plotSeriesValues->getLineColor(); + if ($dsvLineColor->isUsable()) { + $trendLine + ->getLineColor() + ->setColorProperties($dsvLineColor->getValue(), $dsvLineColor->getAlpha(), $dsvLineColor->getType()); + } + } // otherwise, hope Excel does the right thing + + $this->writeLineStyles($objWriter, $trendLine, false); // suppress noFill + + $objWriter->endElement(); // spPr + + $objWriter->startElement('c:trendlineType'); // N.B lowercase 'ell' + $objWriter->writeAttribute('val', $trendLineType); + $objWriter->endElement(); // trendlineType + if ($backward !== 0.0) { + $objWriter->startElement('c:backward'); + $objWriter->writeAttribute('val', "$backward"); + $objWriter->endElement(); // c:backward + } + if ($forward !== 0.0) { + $objWriter->startElement('c:forward'); + $objWriter->writeAttribute('val', "$forward"); + $objWriter->endElement(); // c:forward + } + if ($intercept !== 0.0) { + $objWriter->startElement('c:intercept'); + $objWriter->writeAttribute('val', "$intercept"); + $objWriter->endElement(); // c:intercept + } + if ($trendLineType == TrendLine::TRENDLINE_POLYNOMIAL) { + $objWriter->startElement('c:order'); + $objWriter->writeAttribute('val', $order); + $objWriter->endElement(); // order + } + if ($trendLineType == TrendLine::TRENDLINE_MOVING_AVG) { + $objWriter->startElement('c:period'); + $objWriter->writeAttribute('val', $period); + $objWriter->endElement(); // period + } + $objWriter->startElement('c:dispRSqr'); + $objWriter->writeAttribute('val', $dispRSqr ? '1' : '0'); + $objWriter->endElement(); + $objWriter->startElement('c:dispEq'); + $objWriter->writeAttribute('val', $dispEq ? '1' : '0'); + $objWriter->endElement(); + if ($groupType === DataSeries::TYPE_SCATTERCHART || $groupType === DataSeries::TYPE_LINECHART) { + $objWriter->startElement('c:trendlineLbl'); + $objWriter->startElement('c:numFmt'); + $objWriter->writeAttribute('formatCode', 'General'); + $objWriter->writeAttribute('sourceLinked', '0'); + $objWriter->endElement(); // numFmt + $objWriter->endElement(); // trendlineLbl + } + + $objWriter->endElement(); // trendline + } + } // Category Labels $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesIdx); @@ -1612,7 +1752,18 @@ class Chart extends WriterPart if (is_numeric($alpha)) { $objWriter->startElement('a:alpha'); $objWriter->writeAttribute('val', ChartColor::alphaToXml((int) $alpha)); - $objWriter->endElement(); + $objWriter->endElement(); // a:alpha + } + $brightness = $chartColor->getBrightness(); + if (is_numeric($brightness)) { + $brightness = (int) $brightness; + $lumOff = 100 - $brightness; + $objWriter->startElement('a:lumMod'); + $objWriter->writeAttribute('val', ChartColor::alphaToXml($brightness)); + $objWriter->endElement(); // a:lumMod + $objWriter->startElement('a:lumOff'); + $objWriter->writeAttribute('val', ChartColor::alphaToXml($lumOff)); + $objWriter->endElement(); // a:lumOff } $objWriter->endElement(); //a:srgbClr/schemeClr/prstClr if ($solidFill) { diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Comments.php b/src/PhpSpreadsheet/Writer/Xlsx/Comments.php index ea0f1faa..5045e8f3 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Comments.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Comments.php @@ -165,7 +165,7 @@ class Comments extends WriterPart // Metadata [$column, $row] = Coordinate::indexesFromString($cellReference); $id = 1024 + $column + $row; - $id = substr($id, 0, 4); + $id = substr("$id", 0, 4); // v:shape $objWriter->startElement('v:shape'); @@ -223,10 +223,10 @@ class Comments extends WriterPart $objWriter->writeElement('x:AutoFill', 'False'); // x:Row - $objWriter->writeElement('x:Row', ($row - 1)); + $objWriter->writeElement('x:Row', (string) ($row - 1)); // x:Column - $objWriter->writeElement('x:Column', ($column - 1)); + $objWriter->writeElement('x:Column', (string) ($column - 1)); $objWriter->endElement(); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php b/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php index b8285fcb..b3338c07 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php @@ -118,7 +118,7 @@ class DefinedNames $range[1] = Coordinate::absoluteCoordinate($range[1]); $range = implode(':', $range); - $this->objWriter->writeRawData('\'' . str_replace("'", "''", $worksheet->getTitle() ?? '') . '\'!' . $range); + $this->objWriter->writeRawData('\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!' . $range); $this->objWriter->endElement(); } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php b/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php index 43ce442f..cb8758c2 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php @@ -56,7 +56,7 @@ class DocProps extends WriterPart // Variant $objWriter->startElement('vt:variant'); - $objWriter->writeElement('vt:i4', $spreadsheet->getSheetCount()); + $objWriter->writeElement('vt:i4', (string) $spreadsheet->getSheetCount()); $objWriter->endElement(); $objWriter->endElement(); @@ -68,7 +68,7 @@ class DocProps extends WriterPart // Vector $objWriter->startElement('vt:vector'); - $objWriter->writeAttribute('size', $spreadsheet->getSheetCount()); + $objWriter->writeAttribute('size', (string) $spreadsheet->getSheetCount()); $objWriter->writeAttribute('baseType', 'lpstr'); $sheetCount = $spreadsheet->getSheetCount(); @@ -207,7 +207,7 @@ class DocProps extends WriterPart $objWriter->startElement('property'); $objWriter->writeAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}'); - $objWriter->writeAttribute('pid', $key + 2); + $objWriter->writeAttribute('pid', (string) ($key + 2)); $objWriter->writeAttribute('name', $customProperty); switch ($propertyType) { diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php index 238fb5bf..99fa2d34 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php @@ -67,12 +67,13 @@ class Rels extends WriterPart 'xl/workbook.xml' ); // a custom UI in workbook ? + $target = $spreadsheet->getRibbonXMLData('target'); if ($spreadsheet->hasRibbon()) { $this->writeRelationShip( $objWriter, 5, 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility', - $spreadsheet->getRibbonXMLData('target') + is_string($target) ? $target : '' ); } @@ -284,7 +285,7 @@ class Rels extends WriterPart return $objWriter->getData(); } - private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, XMLWriter $objWriter, $relationship, $type): void + private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, XMLWriter $objWriter, string $relationship, string $type): void { $unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData(); if (!isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()][$relationship])) { @@ -448,7 +449,7 @@ class Rels extends WriterPart /** * Write Override content type. * - * @param int $id Relationship ID. rId will be prepended! + * @param int|string $id Relationship ID. rId will be prepended! * @param string $type Relationship type * @param string $target Relationship target * @param string $targetMode Relationship target mode diff --git a/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php b/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php index 8b293bc1..078f940a 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php @@ -66,7 +66,7 @@ class StringTable extends WriterPart /** * Write string table to XML format. * - * @param string[] $stringTable + * @param (string|RichText)[] $stringTable * * @return string XML Output */ @@ -86,13 +86,13 @@ class StringTable extends WriterPart // String table $objWriter->startElement('sst'); $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); - $objWriter->writeAttribute('uniqueCount', count($stringTable)); + $objWriter->writeAttribute('uniqueCount', (string) count($stringTable)); // Loop through string table foreach ($stringTable as $textElement) { $objWriter->startElement('si'); - if (!$textElement instanceof RichText) { + if (!($textElement instanceof RichText)) { $textToWrite = StringHelper::controlCharacterPHP2OOXML($textElement); $objWriter->startElement('t'); if ($textToWrite !== trim($textToWrite)) { @@ -100,7 +100,7 @@ class StringTable extends WriterPart } $objWriter->writeRawData($textToWrite); $objWriter->endElement(); - } elseif ($textElement instanceof RichText) { + } else { $this->writeRichText($objWriter, $textElement); } @@ -130,14 +130,16 @@ class StringTable extends WriterPart $objWriter->startElement($prefix . 'r'); // rPr - if ($element instanceof Run) { + if ($element instanceof Run && $element->getFont() !== null) { // rPr $objWriter->startElement($prefix . 'rPr'); // rFont - $objWriter->startElement($prefix . 'rFont'); - $objWriter->writeAttribute('val', $element->getFont()->getName()); - $objWriter->endElement(); + if ($element->getFont()->getName() !== null) { + $objWriter->startElement($prefix . 'rFont'); + $objWriter->writeAttribute('val', $element->getFont()->getName()); + $objWriter->endElement(); + } // Bold $objWriter->startElement($prefix . 'b'); @@ -166,19 +168,25 @@ class StringTable extends WriterPart $objWriter->endElement(); // Color - $objWriter->startElement($prefix . 'color'); - $objWriter->writeAttribute('rgb', $element->getFont()->getColor()->getARGB()); - $objWriter->endElement(); + if ($element->getFont()->getColor()->getARGB() !== null) { + $objWriter->startElement($prefix . 'color'); + $objWriter->writeAttribute('rgb', $element->getFont()->getColor()->getARGB()); + $objWriter->endElement(); + } // Size - $objWriter->startElement($prefix . 'sz'); - $objWriter->writeAttribute('val', $element->getFont()->getSize()); - $objWriter->endElement(); + if ($element->getFont()->getSize() !== null) { + $objWriter->startElement($prefix . 'sz'); + $objWriter->writeAttribute('val', (string) $element->getFont()->getSize()); + $objWriter->endElement(); + } // Underline - $objWriter->startElement($prefix . 'u'); - $objWriter->writeAttribute('val', $element->getFont()->getUnderline()); - $objWriter->endElement(); + if ($element->getFont()->getUnderline() !== null) { + $objWriter->startElement($prefix . 'u'); + $objWriter->writeAttribute('val', $element->getFont()->getUnderline()); + $objWriter->endElement(); + } $objWriter->endElement(); } @@ -201,10 +209,10 @@ class StringTable extends WriterPart */ public function writeRichTextForCharts(XMLWriter $objWriter, $richText = null, $prefix = ''): void { - if (!$richText instanceof RichText) { + if (!($richText instanceof RichText)) { $textRun = $richText; $richText = new RichText(); - $run = $richText->createTextRun($textRun); + $run = $richText->createTextRun($textRun ?? ''); $run->setFont(null); } @@ -226,9 +234,9 @@ class StringTable extends WriterPart } // Bold - $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? 1 : 0)); + $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? '1' : '0')); // Italic - $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? 1 : 0)); + $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? '1' : '0')); // Underline $underlineType = $element->getFont()->getUnderline(); switch ($underlineType) { @@ -241,7 +249,9 @@ class StringTable extends WriterPart break; } - $objWriter->writeAttribute('u', $underlineType); + if ($underlineType !== null) { + $objWriter->writeAttribute('u', $underlineType); + } // Strikethrough $objWriter->writeAttribute('strike', ($element->getFont()->getStriketype() ?: 'noStrike')); // Superscript/subscript diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Style.php b/src/PhpSpreadsheet/Writer/Xlsx/Style.php index cb2e3850..0442c25d 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Style.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Style.php @@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Borders; use PhpOffice\PhpSpreadsheet\Style\Conditional; @@ -40,7 +41,7 @@ class Style extends WriterPart // numFmts $objWriter->startElement('numFmts'); - $objWriter->writeAttribute('count', $this->getParentWriter()->getNumFmtHashTable()->count()); + $objWriter->writeAttribute('count', (string) $this->getParentWriter()->getNumFmtHashTable()->count()); // numFmt for ($i = 0; $i < $this->getParentWriter()->getNumFmtHashTable()->count(); ++$i) { @@ -51,54 +52,63 @@ class Style extends WriterPart // fonts $objWriter->startElement('fonts'); - $objWriter->writeAttribute('count', $this->getParentWriter()->getFontHashTable()->count()); + $objWriter->writeAttribute('count', (string) $this->getParentWriter()->getFontHashTable()->count()); // font for ($i = 0; $i < $this->getParentWriter()->getFontHashTable()->count(); ++$i) { - $this->writeFont($objWriter, $this->getParentWriter()->getFontHashTable()->getByIndex($i)); + $thisfont = $this->getParentWriter()->getFontHashTable()->getByIndex($i); + if ($thisfont !== null) { + $this->writeFont($objWriter, $thisfont); + } } $objWriter->endElement(); // fills $objWriter->startElement('fills'); - $objWriter->writeAttribute('count', $this->getParentWriter()->getFillHashTable()->count()); + $objWriter->writeAttribute('count', (string) $this->getParentWriter()->getFillHashTable()->count()); // fill for ($i = 0; $i < $this->getParentWriter()->getFillHashTable()->count(); ++$i) { - $this->writeFill($objWriter, $this->getParentWriter()->getFillHashTable()->getByIndex($i)); + $thisfill = $this->getParentWriter()->getFillHashTable()->getByIndex($i); + if ($thisfill !== null) { + $this->writeFill($objWriter, $thisfill); + } } $objWriter->endElement(); // borders $objWriter->startElement('borders'); - $objWriter->writeAttribute('count', $this->getParentWriter()->getBordersHashTable()->count()); + $objWriter->writeAttribute('count', (string) $this->getParentWriter()->getBordersHashTable()->count()); // border for ($i = 0; $i < $this->getParentWriter()->getBordersHashTable()->count(); ++$i) { - $this->writeBorder($objWriter, $this->getParentWriter()->getBordersHashTable()->getByIndex($i)); + $thisborder = $this->getParentWriter()->getBordersHashTable()->getByIndex($i); + if ($thisborder !== null) { + $this->writeBorder($objWriter, $thisborder); + } } $objWriter->endElement(); // cellStyleXfs $objWriter->startElement('cellStyleXfs'); - $objWriter->writeAttribute('count', 1); + $objWriter->writeAttribute('count', '1'); // xf $objWriter->startElement('xf'); - $objWriter->writeAttribute('numFmtId', 0); - $objWriter->writeAttribute('fontId', 0); - $objWriter->writeAttribute('fillId', 0); - $objWriter->writeAttribute('borderId', 0); + $objWriter->writeAttribute('numFmtId', '0'); + $objWriter->writeAttribute('fontId', '0'); + $objWriter->writeAttribute('fillId', '0'); + $objWriter->writeAttribute('borderId', '0'); $objWriter->endElement(); $objWriter->endElement(); // cellXfs $objWriter->startElement('cellXfs'); - $objWriter->writeAttribute('count', count($spreadsheet->getCellXfCollection())); + $objWriter->writeAttribute('count', (string) count($spreadsheet->getCellXfCollection())); // xf foreach ($spreadsheet->getCellXfCollection() as $cellXf) { @@ -109,24 +119,27 @@ class Style extends WriterPart // cellStyles $objWriter->startElement('cellStyles'); - $objWriter->writeAttribute('count', 1); + $objWriter->writeAttribute('count', '1'); // cellStyle $objWriter->startElement('cellStyle'); $objWriter->writeAttribute('name', 'Normal'); - $objWriter->writeAttribute('xfId', 0); - $objWriter->writeAttribute('builtinId', 0); + $objWriter->writeAttribute('xfId', '0'); + $objWriter->writeAttribute('builtinId', '0'); $objWriter->endElement(); $objWriter->endElement(); // dxfs $objWriter->startElement('dxfs'); - $objWriter->writeAttribute('count', $this->getParentWriter()->getStylesConditionalHashTable()->count()); + $objWriter->writeAttribute('count', (string) $this->getParentWriter()->getStylesConditionalHashTable()->count()); // dxf for ($i = 0; $i < $this->getParentWriter()->getStylesConditionalHashTable()->count(); ++$i) { - $this->writeCellStyleDxf($objWriter, $this->getParentWriter()->getStylesConditionalHashTable()->getByIndex($i)->getStyle()); + $thisstyle = $this->getParentWriter()->getStylesConditionalHashTable()->getByIndex($i); + if ($thisstyle !== null) { + $this->writeCellStyleDxf($objWriter, $thisstyle->getStyle()); + } } $objWriter->endElement(); @@ -171,17 +184,19 @@ class Style extends WriterPart // gradientFill $objWriter->startElement('gradientFill'); - $objWriter->writeAttribute('type', $fill->getFillType()); - $objWriter->writeAttribute('degree', $fill->getRotation()); + $objWriter->writeAttribute('type', (string) $fill->getFillType()); + $objWriter->writeAttribute('degree', (string) $fill->getRotation()); // stop $objWriter->startElement('stop'); $objWriter->writeAttribute('position', '0'); // color - $objWriter->startElement('color'); - $objWriter->writeAttribute('rgb', $fill->getStartColor()->getARGB()); - $objWriter->endElement(); + if ($fill->getStartColor()->getARGB() !== null) { + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $fill->getStartColor()->getARGB()); + $objWriter->endElement(); + } $objWriter->endElement(); @@ -190,9 +205,11 @@ class Style extends WriterPart $objWriter->writeAttribute('position', '1'); // color - $objWriter->startElement('color'); - $objWriter->writeAttribute('rgb', $fill->getEndColor()->getARGB()); - $objWriter->endElement(); + if ($fill->getEndColor()->getARGB() !== null) { + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $fill->getEndColor()->getARGB()); + $objWriter->endElement(); + } $objWriter->endElement(); @@ -220,7 +237,7 @@ class Style extends WriterPart // patternFill $objWriter->startElement('patternFill'); - $objWriter->writeAttribute('patternType', $fill->getFillType()); + $objWriter->writeAttribute('patternType', (string) $fill->getFillType()); if (self::writePatternColors($fill)) { // fgColor @@ -360,20 +377,20 @@ class Style extends WriterPart { // xf $objWriter->startElement('xf'); - $objWriter->writeAttribute('xfId', 0); - $objWriter->writeAttribute('fontId', (int) $this->getParentWriter()->getFontHashTable()->getIndexForHashCode($style->getFont()->getHashCode())); + $objWriter->writeAttribute('xfId', '0'); + $objWriter->writeAttribute('fontId', (string) (int) $this->getParentWriter()->getFontHashTable()->getIndexForHashCode($style->getFont()->getHashCode())); if ($style->getQuotePrefix()) { - $objWriter->writeAttribute('quotePrefix', 1); + $objWriter->writeAttribute('quotePrefix', '1'); } if ($style->getNumberFormat()->getBuiltInFormatCode() === false) { - $objWriter->writeAttribute('numFmtId', (int) ($this->getParentWriter()->getNumFmtHashTable()->getIndexForHashCode($style->getNumberFormat()->getHashCode()) + 164)); + $objWriter->writeAttribute('numFmtId', (string) (int) ($this->getParentWriter()->getNumFmtHashTable()->getIndexForHashCode($style->getNumberFormat()->getHashCode()) + 164)); } else { - $objWriter->writeAttribute('numFmtId', (int) $style->getNumberFormat()->getBuiltInFormatCode()); + $objWriter->writeAttribute('numFmtId', (string) (int) $style->getNumberFormat()->getBuiltInFormatCode()); } - $objWriter->writeAttribute('fillId', (int) $this->getParentWriter()->getFillHashTable()->getIndexForHashCode($style->getFill()->getHashCode())); - $objWriter->writeAttribute('borderId', (int) $this->getParentWriter()->getBordersHashTable()->getIndexForHashCode($style->getBorders()->getHashCode())); + $objWriter->writeAttribute('fillId', (string) (int) $this->getParentWriter()->getFillHashTable()->getIndexForHashCode($style->getFill()->getHashCode())); + $objWriter->writeAttribute('borderId', (string) (int) $this->getParentWriter()->getBordersHashTable()->getIndexForHashCode($style->getBorders()->getHashCode())); // Apply styles? $objWriter->writeAttribute('applyFont', ($spreadsheet->getDefaultStyle()->getFont()->getHashCode() != $style->getFont()->getHashCode()) ? '1' : '0'); @@ -387,25 +404,31 @@ class Style extends WriterPart // alignment $objWriter->startElement('alignment'); - $objWriter->writeAttribute('horizontal', $style->getAlignment()->getHorizontal()); - $objWriter->writeAttribute('vertical', $style->getAlignment()->getVertical()); + $vertical = Alignment::VERTICAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getVertical()] ?? ''; + $horizontal = Alignment::HORIZONTAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getHorizontal()] ?? ''; + if ($horizontal !== '') { + $objWriter->writeAttribute('horizontal', $horizontal); + } + if ($vertical !== '') { + $objWriter->writeAttribute('vertical', $vertical); + } $textRotation = 0; if ($style->getAlignment()->getTextRotation() >= 0) { $textRotation = $style->getAlignment()->getTextRotation(); - } elseif ($style->getAlignment()->getTextRotation() < 0) { + } else { $textRotation = 90 - $style->getAlignment()->getTextRotation(); } - $objWriter->writeAttribute('textRotation', $textRotation); + $objWriter->writeAttribute('textRotation', (string) $textRotation); $objWriter->writeAttribute('wrapText', ($style->getAlignment()->getWrapText() ? 'true' : 'false')); $objWriter->writeAttribute('shrinkToFit', ($style->getAlignment()->getShrinkToFit() ? 'true' : 'false')); if ($style->getAlignment()->getIndent() > 0) { - $objWriter->writeAttribute('indent', $style->getAlignment()->getIndent()); + $objWriter->writeAttribute('indent', (string) $style->getAlignment()->getIndent()); } if ($style->getAlignment()->getReadOrder() > 0) { - $objWriter->writeAttribute('readingOrder', $style->getAlignment()->getReadOrder()); + $objWriter->writeAttribute('readingOrder', (string) $style->getAlignment()->getReadOrder()); } $objWriter->endElement(); @@ -443,21 +466,23 @@ class Style extends WriterPart // alignment $objWriter->startElement('alignment'); - if ($style->getAlignment()->getHorizontal() !== null) { - $objWriter->writeAttribute('horizontal', $style->getAlignment()->getHorizontal()); + $horizontal = Alignment::HORIZONTAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getHorizontal()] ?? ''; + if ($horizontal) { + $objWriter->writeAttribute('horizontal', $horizontal); } - if ($style->getAlignment()->getVertical() !== null) { - $objWriter->writeAttribute('vertical', $style->getAlignment()->getVertical()); + $vertical = Alignment::VERTICAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getVertical()] ?? ''; + if ($vertical) { + $objWriter->writeAttribute('vertical', $vertical); } if ($style->getAlignment()->getTextRotation() !== null) { $textRotation = 0; if ($style->getAlignment()->getTextRotation() >= 0) { $textRotation = $style->getAlignment()->getTextRotation(); - } elseif ($style->getAlignment()->getTextRotation() < 0) { + } else { $textRotation = 90 - $style->getAlignment()->getTextRotation(); } - $objWriter->writeAttribute('textRotation', $textRotation); + $objWriter->writeAttribute('textRotation', (string) $textRotation); } $objWriter->endElement(); @@ -465,7 +490,7 @@ class Style extends WriterPart $this->writeBorder($objWriter, $style->getBorders()); // protection - if (($style->getProtection()->getLocked() !== null) || ($style->getProtection()->getHidden() !== null)) { + if ((!empty($style->getProtection()->getLocked())) || (!empty($style->getProtection()->getHidden()))) { if ( $style->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT || $style->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT @@ -503,11 +528,13 @@ class Style extends WriterPart $objWriter->writeAttribute('style', $border->getBorderStyle()); // color - $objWriter->startElement('color'); - $objWriter->writeAttribute('rgb', $border->getColor()->getARGB()); - $objWriter->endElement(); + if ($border->getColor()->getARGB() !== null) { + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $border->getColor()->getARGB()); + $objWriter->endElement(); - $objWriter->endElement(); + $objWriter->endElement(); + } } } @@ -516,15 +543,15 @@ class Style extends WriterPart * * @param int $id Number Format identifier */ - private function writeNumFmt(XMLWriter $objWriter, NumberFormat $numberFormat, $id = 0): void + private function writeNumFmt(XMLWriter $objWriter, ?NumberFormat $numberFormat, $id = 0): void { // Translate formatcode - $formatCode = $numberFormat->getFormatCode(); + $formatCode = ($numberFormat === null) ? null : $numberFormat->getFormatCode(); // numFmt if ($formatCode !== null) { $objWriter->startElement('numFmt'); - $objWriter->writeAttribute('numFmtId', ($id + 164)); + $objWriter->writeAttribute('numFmtId', (string) ($id + 164)); $objWriter->writeAttribute('formatCode', $formatCode); $objWriter->endElement(); } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php b/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php index f9d7197d..7d08388d 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php @@ -104,14 +104,14 @@ class Workbook extends WriterPart // workbookView $objWriter->startElement('workbookView'); - $objWriter->writeAttribute('activeTab', $spreadsheet->getActiveSheetIndex()); + $objWriter->writeAttribute('activeTab', (string) $spreadsheet->getActiveSheetIndex()); $objWriter->writeAttribute('autoFilterDateGrouping', ($spreadsheet->getAutoFilterDateGrouping() ? 'true' : 'false')); - $objWriter->writeAttribute('firstSheet', $spreadsheet->getFirstSheetIndex()); + $objWriter->writeAttribute('firstSheet', (string) $spreadsheet->getFirstSheetIndex()); $objWriter->writeAttribute('minimized', ($spreadsheet->getMinimized() ? 'true' : 'false')); $objWriter->writeAttribute('showHorizontalScroll', ($spreadsheet->getShowHorizontalScroll() ? 'true' : 'false')); $objWriter->writeAttribute('showSheetTabs', ($spreadsheet->getShowSheetTabs() ? 'true' : 'false')); $objWriter->writeAttribute('showVerticalScroll', ($spreadsheet->getShowVerticalScroll() ? 'true' : 'false')); - $objWriter->writeAttribute('tabRatio', $spreadsheet->getTabRatio()); + $objWriter->writeAttribute('tabRatio', (string) $spreadsheet->getTabRatio()); $objWriter->writeAttribute('visibility', $spreadsheet->getVisibility()); $objWriter->endElement(); @@ -157,9 +157,9 @@ class Workbook extends WriterPart $objWriter->writeAttribute('calcId', '999999'); $objWriter->writeAttribute('calcMode', 'auto'); // fullCalcOnLoad isn't needed if we've recalculating for the save - $objWriter->writeAttribute('calcCompleted', ($recalcRequired) ? 1 : 0); - $objWriter->writeAttribute('fullCalcOnLoad', ($recalcRequired) ? 0 : 1); - $objWriter->writeAttribute('forceFullCalc', ($recalcRequired) ? 0 : 1); + $objWriter->writeAttribute('calcCompleted', ($recalcRequired) ? '1' : '0'); + $objWriter->writeAttribute('fullCalcOnLoad', ($recalcRequired) ? '0' : '1'); + $objWriter->writeAttribute('forceFullCalc', ($recalcRequired) ? '0' : '1'); $objWriter->endElement(); } @@ -200,7 +200,7 @@ class Workbook extends WriterPart // Write sheet $objWriter->startElement('sheet'); $objWriter->writeAttribute('name', $worksheetName); - $objWriter->writeAttribute('sheetId', $worksheetId); + $objWriter->writeAttribute('sheetId', (string) $worksheetId); if ($sheetState !== 'visible' && $sheetState != '') { $objWriter->writeAttribute('state', $sheetState); } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 4b0cb632..1aa4f1ca 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -28,7 +28,7 @@ class Worksheet extends WriterPart * * @return string XML Output */ - public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, $stringTable = null, $includeCharts = false) + public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, $stringTable = [], $includeCharts = false) { // Create XML writer $objWriter = null; @@ -149,7 +149,7 @@ class Worksheet extends WriterPart } $autoFilterRange = $worksheet->getAutoFilter()->getRange(); if (!empty($autoFilterRange)) { - $objWriter->writeAttribute('filterMode', 1); + $objWriter->writeAttribute('filterMode', '1'); if (!$worksheet->getAutoFilter()->getEvaluated()) { $worksheet->getAutoFilter()->showHideRows(); } @@ -158,7 +158,7 @@ class Worksheet extends WriterPart // tabColor if ($worksheet->isTabColorSet()) { $objWriter->startElement('tabColor'); - $objWriter->writeAttribute('rgb', $worksheet->getTabColor()->getARGB()); + $objWriter->writeAttribute('rgb', $worksheet->getTabColor()->getARGB() ?? ''); $objWriter->endElement(); } @@ -218,7 +218,7 @@ class Worksheet extends WriterPart // Show zeros (Excel also writes this attribute only if set to false) if ($worksheet->getSheetView()->getShowZeros() === false) { - $objWriter->writeAttribute('showZeros', 0); + $objWriter->writeAttribute('showZeros', '0'); } // View Layout Type @@ -252,7 +252,7 @@ class Worksheet extends WriterPart // Pane $pane = ''; if ($worksheet->getFreezePane()) { - [$xSplit, $ySplit] = Coordinate::coordinateFromString($worksheet->getFreezePane() ?? ''); + [$xSplit, $ySplit] = Coordinate::coordinateFromString($worksheet->getFreezePane()); $xSplit = Coordinate::columnIndexFromString($xSplit); --$xSplit; --$ySplit; @@ -261,7 +261,7 @@ class Worksheet extends WriterPart $pane = 'topRight'; $objWriter->startElement('pane'); if ($xSplit > 0) { - $objWriter->writeAttribute('xSplit', $xSplit); + $objWriter->writeAttribute('xSplit', "$xSplit"); } if ($ySplit > 0) { $objWriter->writeAttribute('ySplit', $ySplit); @@ -334,7 +334,7 @@ class Worksheet extends WriterPart $outlineLevelRow = $dimension->getOutlineLevel(); } } - $objWriter->writeAttribute('outlineLevelRow', (int) $outlineLevelRow); + $objWriter->writeAttribute('outlineLevelRow', (string) (int) $outlineLevelRow); // Outline level - column $outlineLevelCol = 0; @@ -343,7 +343,7 @@ class Worksheet extends WriterPart $outlineLevelCol = $dimension->getOutlineLevel(); } } - $objWriter->writeAttribute('outlineLevelCol', (int) $outlineLevelCol); + $objWriter->writeAttribute('outlineLevelCol', (string) (int) $outlineLevelCol); $objWriter->endElement(); } @@ -363,8 +363,8 @@ class Worksheet extends WriterPart foreach ($worksheet->getColumnDimensions() as $colDimension) { // col $objWriter->startElement('col'); - $objWriter->writeAttribute('min', Coordinate::columnIndexFromString($colDimension->getColumnIndex())); - $objWriter->writeAttribute('max', Coordinate::columnIndexFromString($colDimension->getColumnIndex())); + $objWriter->writeAttribute('min', (string) Coordinate::columnIndexFromString($colDimension->getColumnIndex())); + $objWriter->writeAttribute('max', (string) Coordinate::columnIndexFromString($colDimension->getColumnIndex())); if ($colDimension->getWidth() < 0) { // No width set, apply default of 10 @@ -396,11 +396,11 @@ class Worksheet extends WriterPart // Outline level if ($colDimension->getOutlineLevel() > 0) { - $objWriter->writeAttribute('outlineLevel', $colDimension->getOutlineLevel()); + $objWriter->writeAttribute('outlineLevel', (string) $colDimension->getOutlineLevel()); } // Style - $objWriter->writeAttribute('style', $colDimension->getXfIndex()); + $objWriter->writeAttribute('style', (string) $colDimension->getXfIndex()); $objWriter->endElement(); } @@ -423,7 +423,7 @@ class Worksheet extends WriterPart $objWriter->writeAttribute('algorithmName', $protection->getAlgorithm()); $objWriter->writeAttribute('hashValue', $protection->getPassword()); $objWriter->writeAttribute('saltValue', $protection->getSalt()); - $objWriter->writeAttribute('spinCount', $protection->getSpinCount()); + $objWriter->writeAttribute('spinCount', (string) $protection->getSpinCount()); } elseif ($protection->getPassword() !== '') { $objWriter->writeAttribute('password', $protection->getPassword()); } @@ -447,7 +447,7 @@ class Worksheet extends WriterPart $objWriter->endElement(); } - private static function writeAttributeIf(XMLWriter $objWriter, $condition, string $attr, string $val): void + private static function writeAttributeIf(XMLWriter $objWriter, ?bool $condition, string $attr, string $val): void { if ($condition) { $objWriter->writeAttribute($attr, $val); @@ -461,7 +461,7 @@ class Worksheet extends WriterPart } } - private static function writeElementIf(XMLWriter $objWriter, $condition, string $attr, string $val): void + private static function writeElementIf(XMLWriter $objWriter, bool $condition, string $attr, string $val): void { if ($condition) { $objWriter->writeElement($attr, $val); @@ -503,7 +503,7 @@ class Worksheet extends WriterPart private static function writeTimePeriodCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void { $txt = $conditional->getText(); - if ($txt !== null) { + if (!empty($txt)) { $objWriter->writeAttribute('timePeriod', $txt); if (empty($conditional->getConditions())) { if ($conditional->getOperatorType() == Conditional::TIMEPERIOD_TODAY) { @@ -536,7 +536,7 @@ class Worksheet extends WriterPart private static function writeTextCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void { $txt = $conditional->getText(); - if ($txt !== null) { + if (!empty($txt)) { $objWriter->writeAttribute('text', $txt); if (empty($conditional->getConditions())) { if ($conditional->getOperatorType() == Conditional::OPERATOR_CONTAINSTEXT) { @@ -568,7 +568,7 @@ class Worksheet extends WriterPart $objWriter->writeAttribute($attrKey, $val); } $minCfvo = $dataBar->getMinimumConditionalFormatValueObject(); - if ($minCfvo) { + if ($minCfvo !== null) { $objWriter->startElementNs($prefix, 'cfvo', null); $objWriter->writeAttribute('type', $minCfvo->getType()); if ($minCfvo->getCellFormula()) { @@ -578,7 +578,7 @@ class Worksheet extends WriterPart } $maxCfvo = $dataBar->getMaximumConditionalFormatValueObject(); - if ($maxCfvo) { + if ($maxCfvo !== null) { $objWriter->startElementNs($prefix, 'cfvo', null); $objWriter->writeAttribute('type', $maxCfvo->getType()); if ($maxCfvo->getCellFormula()) { @@ -600,9 +600,8 @@ class Worksheet extends WriterPart $objWriter->endElement(); //end conditionalFormatting } - private static function writeDataBarElements(XMLWriter $objWriter, $dataBar): void + private static function writeDataBarElements(XMLWriter $objWriter, ?ConditionalDataBar $dataBar): void { - /** @var ConditionalDataBar $dataBar */ if ($dataBar) { $objWriter->startElement('dataBar'); self::writeAttributeIf($objWriter, null !== $dataBar->getShowValue(), 'showValue', $dataBar->getShowValue() ? '1' : '0'); @@ -669,7 +668,7 @@ class Worksheet extends WriterPart 'dxfId', (string) $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) ); - $objWriter->writeAttribute('priority', $id++); + $objWriter->writeAttribute('priority', (string) $id++); self::writeAttributeif( $objWriter, @@ -724,7 +723,7 @@ class Worksheet extends WriterPart if (!empty($dataValidationCollection)) { $dataValidationCollection = Coordinate::mergeRangesInCollection($dataValidationCollection); $objWriter->startElement('dataValidations'); - $objWriter->writeAttribute('count', count($dataValidationCollection)); + $objWriter->writeAttribute('count', (string) count($dataValidationCollection)); foreach ($dataValidationCollection as $coordinate => $dv) { $objWriter->startElement('dataValidation'); @@ -922,11 +921,11 @@ class Worksheet extends WriterPart $rules = $column->getRules(); if (count($rules) > 0) { $objWriter->startElement('filterColumn'); - $objWriter->writeAttribute('colId', $worksheet->getAutoFilter()->getColumnOffset($columnID)); + $objWriter->writeAttribute('colId', (string) $worksheet->getAutoFilter()->getColumnOffset($columnID)); $objWriter->startElement($column->getFilterType()); if ($column->getJoin() == Column::AUTOFILTER_COLUMN_JOIN_AND) { - $objWriter->writeAttribute('and', 1); + $objWriter->writeAttribute('and', '1'); } foreach ($rules as $rule) { @@ -936,7 +935,7 @@ class Worksheet extends WriterPart ($rule->getValue() === '') ) { // Filter rule for Blanks - $objWriter->writeAttribute('blank', 1); + $objWriter->writeAttribute('blank', '1'); } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER) { // Dynamic Filter Rule $objWriter->writeAttribute('type', $rule->getGrouping()); @@ -1019,24 +1018,24 @@ class Worksheet extends WriterPart { // pageSetup $objWriter->startElement('pageSetup'); - $objWriter->writeAttribute('paperSize', $worksheet->getPageSetup()->getPaperSize()); + $objWriter->writeAttribute('paperSize', (string) $worksheet->getPageSetup()->getPaperSize()); $objWriter->writeAttribute('orientation', $worksheet->getPageSetup()->getOrientation()); if ($worksheet->getPageSetup()->getScale() !== null) { - $objWriter->writeAttribute('scale', $worksheet->getPageSetup()->getScale()); + $objWriter->writeAttribute('scale', (string) $worksheet->getPageSetup()->getScale()); } if ($worksheet->getPageSetup()->getFitToHeight() !== null) { - $objWriter->writeAttribute('fitToHeight', $worksheet->getPageSetup()->getFitToHeight()); + $objWriter->writeAttribute('fitToHeight', (string) $worksheet->getPageSetup()->getFitToHeight()); } else { $objWriter->writeAttribute('fitToHeight', '0'); } if ($worksheet->getPageSetup()->getFitToWidth() !== null) { - $objWriter->writeAttribute('fitToWidth', $worksheet->getPageSetup()->getFitToWidth()); + $objWriter->writeAttribute('fitToWidth', (string) $worksheet->getPageSetup()->getFitToWidth()); } else { $objWriter->writeAttribute('fitToWidth', '0'); } - if ($worksheet->getPageSetup()->getFirstPageNumber() !== null) { - $objWriter->writeAttribute('firstPageNumber', $worksheet->getPageSetup()->getFirstPageNumber()); + if (!empty($worksheet->getPageSetup()->getFirstPageNumber())) { + $objWriter->writeAttribute('firstPageNumber', (string) $worksheet->getPageSetup()->getFirstPageNumber()); $objWriter->writeAttribute('useFirstPageNumber', '1'); } $objWriter->writeAttribute('pageOrder', $worksheet->getPageSetup()->getPageOrder()); @@ -1089,8 +1088,8 @@ class Worksheet extends WriterPart // rowBreaks if (!empty($aRowBreaks)) { $objWriter->startElement('rowBreaks'); - $objWriter->writeAttribute('count', count($aRowBreaks)); - $objWriter->writeAttribute('manualBreakCount', count($aRowBreaks)); + $objWriter->writeAttribute('count', (string) count($aRowBreaks)); + $objWriter->writeAttribute('manualBreakCount', (string) count($aRowBreaks)); foreach ($aRowBreaks as $cell) { $coords = Coordinate::coordinateFromString($cell); @@ -1107,14 +1106,14 @@ class Worksheet extends WriterPart // Second, write column breaks if (!empty($aColumnBreaks)) { $objWriter->startElement('colBreaks'); - $objWriter->writeAttribute('count', count($aColumnBreaks)); - $objWriter->writeAttribute('manualBreakCount', count($aColumnBreaks)); + $objWriter->writeAttribute('count', (string) count($aColumnBreaks)); + $objWriter->writeAttribute('manualBreakCount', (string) count($aColumnBreaks)); foreach ($aColumnBreaks as $cell) { $coords = Coordinate::coordinateFromString($cell); $objWriter->startElement('brk'); - $objWriter->writeAttribute('id', Coordinate::columnIndexFromString($coords[0]) - 1); + $objWriter->writeAttribute('id', (string) (Coordinate::columnIndexFromString($coords[0]) - 1)); $objWriter->writeAttribute('man', '1'); $objWriter->endElement(); } @@ -1166,7 +1165,7 @@ class Worksheet extends WriterPart if ($writeCurrentRow) { // Start a new row $objWriter->startElement('row'); - $objWriter->writeAttribute('r', $currentRow); + $objWriter->writeAttribute('r', "$currentRow"); $objWriter->writeAttribute('spans', '1:' . $colCount); // Row dimensions @@ -1187,12 +1186,12 @@ class Worksheet extends WriterPart // Outline level if ($rowDimension->getOutlineLevel() > 0) { - $objWriter->writeAttribute('outlineLevel', $rowDimension->getOutlineLevel()); + $objWriter->writeAttribute('outlineLevel', (string) $rowDimension->getOutlineLevel()); } // Style if ($rowDimension->getXfIndex() !== null) { - $objWriter->writeAttribute('s', $rowDimension->getXfIndex()); + $objWriter->writeAttribute('s', (string) $rowDimension->getXfIndex()); $objWriter->writeAttribute('customFormat', '1'); } @@ -1229,7 +1228,7 @@ class Worksheet extends WriterPart StringHelper::controlCharacterPHP2OOXML(htmlspecialchars($cellValue, Settings::htmlEntityFlags())) ); $objWriter->endElement(); - } elseif ($cellValue instanceof RichText) { + } else { $objWriter->startElement('is'); $this->getParentWriter()->getWriterPartstringtable()->writeRichText($objWriter, $cellValue); $objWriter->endElement(); @@ -1263,7 +1262,7 @@ class Worksheet extends WriterPart $cellValue = $cellValue . '.0'; } } - $objWriter->writeElement('v', $cellValue); + $objWriter->writeElement('v', "$cellValue"); } private function writeCellBoolean(XMLWriter $objWriter, string $mappedType, bool $cellValue): void @@ -1332,7 +1331,7 @@ class Worksheet extends WriterPart // Sheet styles $xfi = $pCell->getXfIndex(); - self::writeAttributeIf($objWriter, $xfi, 's', $xfi); + self::writeAttributeIf($objWriter, (bool) $xfi, 's', "$xfi"); // If cell value is supplied, write cell value $cellValue = $pCell->getValue(); @@ -1449,7 +1448,6 @@ class Worksheet extends WriterPart /** @var Conditional $conditional */ foreach ($conditionalStyles as $conditional) { $dataBar = $conditional->getDataBar(); - // @phpstan-ignore-next-line if ($dataBar && $dataBar->getConditionalFormattingRuleExt()) { $conditionalFormattingRuleExtList[] = $dataBar->getConditionalFormattingRuleExt(); } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php b/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php index 6fc0c66a..a1bdf96a 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php @@ -144,6 +144,9 @@ class Xlfn . '|call' . '|let' . '|register[.]id' + . '|textafter' + . '|textbefore' + . '|textsplit' . '|valuetotext' . ')(?=\\s*[(])/i'; diff --git a/tests/PhpSpreadsheetTests/Calculation/ArrayTest.php b/tests/PhpSpreadsheetTests/Calculation/ArrayTest.php new file mode 100644 index 00000000..60c336cf --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/ArrayTest.php @@ -0,0 +1,34 @@ + [ + 0 => [ + 32 => [ + 'B' => 'PHP', + ], + ], + ], + 1 => [ + 0 => [ + 32 => [ + 'C' => 'Spreadsheet', + ], + ], + ], + ]; + + $values = Functions::flattenArray($array); + + self::assertIsNotArray($values[0]); + self::assertIsNotArray($values[1]); + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/FormulaParserTest.php b/tests/PhpSpreadsheetTests/Calculation/FormulaParserTest.php new file mode 100644 index 00000000..d401f9c9 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/FormulaParserTest.php @@ -0,0 +1,151 @@ +expectException(CalcException::class); + $this->expectExceptionMessage('Invalid parameter passed: formula'); + new FormulaParser(null); + } + + public function testInvalidTokenId(): void + { + $this->expectException(CalcException::class); + $this->expectExceptionMessage('Token with id 1 does not exist.'); + $result = new FormulaParser('=2'); + $result->getToken(1); + } + + public function testNoFormula(): void + { + $result = new FormulaParser(''); + self::assertSame(0, $result->getTokenCount()); + } + + /** + * @dataProvider providerFormulaParser + */ + public function testFormulaParser(string $formula, array $expectedResult): void + { + $formula = "=$formula"; + $result = new FormulaParser($formula); + self::assertSame($formula, $result->getFormula()); + self::assertSame(count($expectedResult), $result->getTokenCount()); + $tokens = $result->getTokens(); + $token0 = $result->getToken(0); + self::assertSame($tokens[0], $token0); + $idx = -1; + foreach ($expectedResult as $resultArray) { + ++$idx; + self::assertSame($resultArray[0], $tokens[$idx]->getValue()); + self::assertSame($resultArray[1], $tokens[$idx]->getTokenType()); + self::assertSame($resultArray[2], $tokens[$idx]->getTokenSubType()); + } + } + + public function providerFormulaParser(): array + { + return [ + ['5%*(2+(-3))+A3', + [ + ['5', 'Operand', 'Number'], + ['%', 'OperatorPostfix', 'Nothing'], + ['*', 'OperatorInfix', 'Math'], + ['', 'Subexpression', 'Start'], + ['2', 'Operand', 'Number'], + ['+', 'OperatorInfix', 'Math'], + ['', 'Subexpression', 'Start'], + ['-', 'OperatorPrefix', 'Nothing'], + ['3', 'Operand', 'Number'], + ['', 'Subexpression', 'Stop'], + ['', 'Subexpression', 'Stop'], + ['+', 'OperatorInfix', 'Math'], + ['A3', 'Operand', 'Range'], + ], + ], + ['"hello" & "goodbye"', + [ + ['hello', 'Operand', 'Text'], + ['&', 'OperatorInfix', 'Concatenation'], + ['goodbye', 'Operand', 'Text'], + ], + ], + ['+1.23E5', + [ + ['1.23E5', 'Operand', 'Number'], + ], + ], + ['#DIV/0!', + [ + ['#DIV/0!', 'Operand', 'Error'], + ], + ], + ['"HE""LLO"', + [ + ['HE"LLO', 'Operand', 'Text'], + ], + ], + ['MINVERSE({3,1;4,2})', + [ + ['MINVERSE', 'Function', 'Start'], + ['ARRAY', 'Function', 'Start'], + ['ARRAYROW', 'Function', 'Start'], + ['3', 'Operand', 'Number'], + [',', 'OperatorInfix', 'Union'], + ['1', 'Operand', 'Number'], + ['', 'Function', 'Stop'], + [',', 'Argument', 'Nothing'], + ['ARRAYROW', 'Function', 'Start'], + ['4', 'Operand', 'Number'], + [',', 'OperatorInfix', 'Union'], + ['2', 'Operand', 'Number'], + ['', 'Function', 'Stop'], + ['', 'Function', 'Stop'], + ['', 'Function', 'Stop'], + ], + ], + ['[1,1]*5', + [ + ['[1,1]', 'Operand', 'Range'], + ['*', 'OperatorInfix', 'Math'], + ['5', 'Operand', 'Number'], + ], + ], + ['IF(A1>=0,2,3)', + [ + ['IF', 'Function', 'Start'], + ['A1', 'Operand', 'Range'], + ['>=', 'OperatorInfix', 'Logical'], + ['0', 'Operand', 'Number'], + [',', 'OperatorInfix', 'Union'], + ['2', 'Operand', 'Number'], + [',', 'OperatorInfix', 'Union'], + ['3', 'Operand', 'Number'], + ['', 'Function', 'Stop'], + ], + ], + ["'Worksheet'!A1:A3", + [ + ['Worksheet!A1:A3', 'Operand', 'Range'], + ], + ], + ["'Worksh''eet'!A1:A3", + [ + ['Worksh\'eet!A1:A3', 'Operand', 'Range'], + ], + ], + ['true', + [ + ['true', 'Operand', 'Logical'], + ], + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ConvertUoMTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ConvertUoMTest.php index 9a448824..f15725cb 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ConvertUoMTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ConvertUoMTest.php @@ -4,15 +4,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PHPUnit\Framework\TestCase; class ConvertUoMTest extends TestCase { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } + const UOM_PRECISION = 1E-12; public function testGetConversionGroups(): void { @@ -52,7 +48,7 @@ class ConvertUoMTest extends TestCase public function testCONVERTUOM($expectedResult, ...$args): void { $result = Engineering::CONVERTUOM(...$args); - self::assertEquals($expectedResult, $result); + self::assertEqualsWithDelta($expectedResult, $result, self::UOM_PRECISION); } public function providerCONVERTUOM(): array @@ -69,7 +65,7 @@ class ConvertUoMTest extends TestCase $formula = "=CONVERT({$value}, {$fromUoM}, {$toUoM})"; $result = $calculation->_calculateFormulaValue($formula); - self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14); + self::assertEqualsWithDelta($expectedResult, $result, self::UOM_PRECISION); } public function providerConvertUoMArray(): array diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfCTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfCTest.php index f0a721c7..88e808ab 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfCTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfCTest.php @@ -4,18 +4,12 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PHPUnit\Framework\TestCase; class ErfCTest extends TestCase { const ERF_PRECISION = 1E-12; - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerERFC * @@ -25,7 +19,6 @@ class ErfCTest extends TestCase public function testERFC($expectedResult, $lower): void { $result = Engineering::ERFC($lower); - self::assertEquals($expectedResult, $result); self::assertEqualsWithDelta($expectedResult, $result, self::ERF_PRECISION); } @@ -43,7 +36,7 @@ class ErfCTest extends TestCase $formula = "=ERFC({$lower})"; $result = $calculation->_calculateFormulaValue($formula); - self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14); + self::assertEqualsWithDelta($expectedResult, $result, self::ERF_PRECISION); } public function providerErfCArray(): array diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfPreciseTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfPreciseTest.php index dc3ee84c..8a069ff1 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfPreciseTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfPreciseTest.php @@ -4,18 +4,12 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PHPUnit\Framework\TestCase; class ErfPreciseTest extends TestCase { const ERF_PRECISION = 1E-12; - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerERFPRECISE * @@ -25,7 +19,6 @@ class ErfPreciseTest extends TestCase public function testERFPRECISE($expectedResult, $limit): void { $result = Engineering::ERFPRECISE($limit); - self::assertEquals($expectedResult, $result); self::assertEqualsWithDelta($expectedResult, $result, self::ERF_PRECISION); } @@ -43,7 +36,7 @@ class ErfPreciseTest extends TestCase $formula = "=ERF.PRECISE({$limit})"; $result = $calculation->_calculateFormulaValue($formula); - self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14); + self::assertEqualsWithDelta($expectedResult, $result, self::ERF_PRECISION); } public function providerErfPreciseArray(): array diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfTest.php index 4d13d47d..26e0381c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfTest.php @@ -4,18 +4,12 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PHPUnit\Framework\TestCase; class ErfTest extends TestCase { const ERF_PRECISION = 1E-12; - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerERF * @@ -26,7 +20,6 @@ class ErfTest extends TestCase public function testERF($expectedResult, $lower, $upper = null): void { $result = Engineering::ERF($lower, $upper); - self::assertEquals($expectedResult, $result); self::assertEqualsWithDelta($expectedResult, $result, self::ERF_PRECISION); } @@ -44,7 +37,7 @@ class ErfTest extends TestCase $formula = "=ERF({$lower}, {$upper})"; $result = $calculation->_calculateFormulaValue($formula); - self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14); + self::assertEqualsWithDelta($expectedResult, $result, self::ERF_PRECISION); } public function providerErfArray(): array diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ParseComplexTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ParseComplexTest.php index 1022052e..a32d946f 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ParseComplexTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ParseComplexTest.php @@ -3,21 +3,15 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; use PhpOffice\PhpSpreadsheet\Calculation\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PHPUnit\Framework\TestCase; class ParseComplexTest extends TestCase { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - public function testParseComplex(): void { [$real, $imaginary, $suffix] = [1.23e-4, 5.67e+8, 'j']; - $result = Engineering::parseComplex('1.23e-4+5.67e+8j'); + $result = /** @scrutinizer ignore-deprecated */ Engineering::parseComplex('1.23e-4+5.67e+8j'); self::assertArrayHasKey('real', $result); self::assertEquals($real, $result['real']); self::assertArrayHasKey('imaginary', $result); diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/IrrTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/IrrTest.php index 3c4bdb5a..2565f6c2 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/IrrTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/IrrTest.php @@ -2,26 +2,45 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Financial; -use PhpOffice\PhpSpreadsheet\Calculation\Financial; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PHPUnit\Framework\TestCase; - -class IrrTest extends TestCase +class IrrTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerIRR * * @param mixed $expectedResult + * @param mixed $values */ - public function testIRR($expectedResult, ...$args): void + public function testIRR($expectedResult, $values = null): void { - $result = Financial::IRR(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + $formula = '=IRR('; + if ($values !== null) { + if (is_array($values)) { + $row = 0; + foreach ($values as $value) { + if (is_array($value)) { + foreach ($value as $arrayValue) { + ++$row; + $sheet->getCell("A$row")->setValue($arrayValue); + } + } else { + ++$row; + $sheet->getCell("A$row")->setValue($value); + } + } + $formula .= "A1:A$row"; + } else { + $sheet->getCell('A1')->setValue($values); + $formula .= 'A1'; + } + } + $formula .= ')'; + $sheet->getCell('D1')->setValue($formula); + $result = $sheet->getCell('D1')->getCalculatedValue(); + $this->adjustResult($result, $expectedResult); + + self::assertEqualsWithDelta($expectedResult, $result, 0.1E-8); } public function providerIRR(): array diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AddressInternationalTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AddressInternationalTest.php new file mode 100644 index 00000000..87e0bb4e --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AddressInternationalTest.php @@ -0,0 +1,79 @@ +locale = Settings::getLocale(); + } + + protected function tearDown(): void + { + Settings::setLocale($this->locale); + // CompatibilityMode is restored in parent + parent::tearDown(); + } + + /** + * @dataProvider providerInternational + */ + public function testR1C1International(string $locale, string $r, string $c): void + { + if ($locale !== '') { + Settings::setLocale($locale); + } + $sheet = $this->getSheet(); + $sheet->getCell('A1')->setValue('=LEFT(ADDRESS(1,1,1,0),1)'); + $sheet->getCell('A2')->setValue('=MID(ADDRESS(1,1,1,0),3,1)'); + self::assertSame($r, $sheet->getCell('A1')->getCalculatedValue()); + self::assertSame($c, $sheet->getCell('A2')->getCalculatedValue()); + } + + public function providerInternational(): array + { + return [ + 'Default' => ['', 'R', 'C'], + 'English' => ['en', 'R', 'C'], + 'French' => ['fr', 'L', 'C'], + 'German' => ['de', 'Z', 'S'], + 'Made-up' => ['xx', 'R', 'C'], + 'Spanish' => ['es', 'F', 'C'], + 'Bulgarian' => ['bg', 'R', 'C'], + 'Czech' => ['cs', 'R', 'C'], // maybe should be R/S + 'Polish' => ['pl', 'R', 'C'], // maybe should be W/K + 'Turkish' => ['tr', 'R', 'C'], + ]; + } + + /** + * @dataProvider providerCompatibility + */ + public function testCompatibilityInternational(string $compatibilityMode, string $r, string $c): void + { + Functions::setCompatibilityMode($compatibilityMode); + Settings::setLocale('de'); + $sheet = $this->getSheet(); + $sheet->getCell('A1')->setValue('=LEFT(ADDRESS(1,1,1,0),1)'); + $sheet->getCell('A2')->setValue('=MID(ADDRESS(1,1,1,0),3,1)'); + self::assertSame($r, $sheet->getCell('A1')->getCalculatedValue()); + self::assertSame($c, $sheet->getCell('A2')->getCalculatedValue()); + } + + public function providerCompatibility(): array + { + return [ + [Functions::COMPATIBILITY_EXCEL, 'Z', 'S'], + [Functions::COMPATIBILITY_OPENOFFICE, 'R', 'C'], + [Functions::COMPATIBILITY_GNUMERIC, 'R', 'C'], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AddressTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AddressTest.php index 2b92030e..2a6ae883 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AddressTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/AddressTest.php @@ -3,17 +3,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\LookupRef; use PHPUnit\Framework\TestCase; class AddressTest extends TestCase { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerADDRESS * diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnsTest.php index f4f9bb9e..e8790cbf 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnsTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnsTest.php @@ -3,17 +3,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\LookupRef; use PHPUnit\Framework\TestCase; class ColumnsTest extends TestCase { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerCOLUMNS * @@ -21,7 +15,7 @@ class ColumnsTest extends TestCase */ public function testCOLUMNS($expectedResult, ...$args): void { - $result = LookupRef::COLUMNS(...$args); + $result = LookupRef::COLUMNS(/** @scrutinizer ignore-type */ ...$args); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/FormulaTextTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/FormulaTextTest.php index ccd689b1..6c91f3fc 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/FormulaTextTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/FormulaTextTest.php @@ -11,6 +11,7 @@ class FormulaTextTest extends AllSetupTeardown { /** * @param mixed $value + * * @dataProvider providerFormulaText */ public function testFormulaText(string $expectedResult, $value): void diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/HLookupTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/HLookupTest.php index 0ce3513b..084e3d9f 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/HLookupTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/HLookupTest.php @@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\LookupRef; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\NamedRange; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; @@ -91,6 +92,44 @@ class HLookupTest extends TestCase self::assertSame($expectedResult, $result); } + /** + * @dataProvider providerHLookupNamedRange + */ + public function testHLookupNamedRange(string $expectedResult, string $cellAddress): void + { + $lookupData = [ + ['Rating', 1, 2, 3, 4], + ['Level', 'Poor', 'Average', 'Good', 'Excellent'], + ]; + $formData = [ + ['Category', 'Rating', 'Level'], + ['Service', 2, '=HLOOKUP(C5,Lookup_Table,2,FALSE)'], + ['Quality', 3, '=HLOOKUP(C6,Lookup_Table,2,FALSE)'], + ['Value', 4, '=HLOOKUP(C7,Lookup_Table,2,FALSE)'], + ['Cleanliness', 3, '=HLOOKUP(C8,Lookup_Table,2,FALSE)'], + ]; + + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray($lookupData, null, 'F4'); + $worksheet->fromArray($formData, null, 'B4'); + + $spreadsheet->addNamedRange(new NamedRange('Lookup_Table', $worksheet, '=$G$4:$J$5')); + + $result = $worksheet->getCell($cellAddress)->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function providerHLookupNamedRange(): array + { + return [ + ['Average', 'D5'], + ['Good', 'D6'], + ['Excellent', 'D7'], + ['Good', 'D8'], + ]; + } + /** * @dataProvider providerHLookupArray */ diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php index 03c87b50..fa3f4848 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php @@ -3,17 +3,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\LookupRef; use PHPUnit\Framework\TestCase; class IndexTest extends TestCase { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerINDEX * @@ -21,7 +15,7 @@ class IndexTest extends TestCase */ public function testINDEX($expectedResult, ...$args): void { - $result = LookupRef::INDEX(...$args); + $result = LookupRef::INDEX(/** @scrutinizer ignore-type */ ...$args); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectInternationalTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectInternationalTest.php new file mode 100644 index 00000000..7d6d0a65 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectInternationalTest.php @@ -0,0 +1,132 @@ +locale = Settings::getLocale(); + } + + protected function tearDown(): void + { + Settings::setLocale($this->locale); + // CompatibilityMode is restored in parent + parent::tearDown(); + } + + /** + * @dataProvider providerInternational + */ + public function testR1C1International(string $locale): void + { + Settings::setLocale($locale); + $sameAsEnglish = ['en', 'xx', 'ru', 'tr', 'cs', 'pl']; + $sheet = $this->getSheet(); + $sheet->getCell('C1')->setValue('text'); + $sheet->getCell('A2')->setValue('en'); + $sheet->getCell('B2')->setValue('=INDIRECT("R1C3", false)'); + $sheet->getCell('A3')->setValue('fr'); + $sheet->getCell('B3')->setValue('=INDIRECT("L1C3", false)'); + $sheet->getCell('A4')->setValue('de'); + $sheet->getCell('B4')->setValue('=INDIRECT("Z1S3", false)'); + $sheet->getCell('A5')->setValue('es'); + $sheet->getCell('B5')->setValue('=INDIRECT("F1C3", false)'); + $sheet->getCell('A6')->setValue('xx'); + $sheet->getCell('B6')->setValue('=INDIRECT("R1C3", false)'); + $sheet->getCell('A7')->setValue('ru'); + $sheet->getCell('B7')->setValue('=INDIRECT("R1C3", false)'); + $sheet->getCell('A8')->setValue('cs'); + $sheet->getCell('B8')->setValue('=INDIRECT("R1C3", false)'); + $sheet->getCell('A9')->setValue('tr'); + $sheet->getCell('B9')->setValue('=INDIRECT("R1C3", false)'); + $sheet->getCell('A10')->setValue('pl'); + $sheet->getCell('B10')->setValue('=INDIRECT("R1C3", false)'); + $maxRow = $sheet->getHighestRow(); + for ($row = 2; $row <= $maxRow; ++$row) { + $rowLocale = $sheet->getCell("A$row")->getValue(); + if (in_array($rowLocale, $sameAsEnglish, true) && in_array($locale, $sameAsEnglish, true)) { + $expectedResult = 'text'; + } else { + $expectedResult = ($locale === $sheet->getCell("A$row")->getValue()) ? 'text' : '#REF!'; + } + self::assertSame($expectedResult, $sheet->getCell("B$row")->getCalculatedValue(), "Locale $locale error in cell B$row $rowLocale"); + } + } + + public function providerInternational(): array + { + return [ + 'English' => ['en'], + 'French' => ['fr'], + 'German' => ['de'], + 'Made-up' => ['xx'], + 'Spanish' => ['es'], + 'Russian' => ['ru'], + 'Czech' => ['cs'], + 'Polish' => ['pl'], + 'Turkish' => ['tr'], + ]; + } + + /** + * @dataProvider providerRelativeInternational + */ + public function testRelativeInternational(string $locale, string $cell, string $relative): void + { + Settings::setLocale($locale); + $sheet = $this->getSheet(); + $sheet->getCell('C3')->setValue('text'); + $sheet->getCell($cell)->setValue("=INDIRECT(\"$relative\", false)"); + self::assertSame('text', $sheet->getCell($cell)->getCalculatedValue()); + } + + public function providerRelativeInternational(): array + { + return [ + 'English A3' => ['en', 'A3', 'R[]C[+2]'], + 'French B4' => ['fr', 'B4', 'L[-1]C[+1]'], + 'German C5' => ['de', 'C5', 'Z[-2]S[]'], + 'Spanish E1' => ['es', 'E1', 'F[+2]C[-2]'], + ]; + } + + /** + * @dataProvider providerCompatibility + */ + public function testCompatibilityInternational(string $compatibilityMode): void + { + Functions::setCompatibilityMode($compatibilityMode); + if ($compatibilityMode === Functions::COMPATIBILITY_EXCEL) { + $expected1 = '#REF!'; + $expected2 = 'text'; + } else { + $expected2 = '#REF!'; + $expected1 = 'text'; + } + Settings::setLocale('fr'); + $sheet = $this->getSheet(); + $sheet->getCell('C3')->setValue('text'); + $sheet->getCell('A1')->setValue('=INDIRECT("R3C3", false)'); + $sheet->getCell('A2')->setValue('=INDIRECT("L3C3", false)'); + self::assertSame($expected1, $sheet->getCell('A1')->getCalculatedValue()); + self::assertSame($expected2, $sheet->getCell('A2')->getCalculatedValue()); + } + + public function providerCompatibility(): array + { + return [ + [Functions::COMPATIBILITY_EXCEL], + [Functions::COMPATIBILITY_OPENOFFICE], + [Functions::COMPATIBILITY_GNUMERIC], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php index 7601e336..accfc058 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php @@ -132,4 +132,48 @@ class IndirectTest extends AllSetupTeardown $result = \PhpOffice\PhpSpreadsheet\Calculation\Functions::flattenSingleValue($result); self::assertSame('This is it', $result); } + + /** + * @param null|int|string $expectedResult + * + * @dataProvider providerRelative + */ + public function testR1C1Relative($expectedResult, string $address): void + { + $sheet = $this->getSheet(); + $sheet->fromArray([ + ['a1', 'b1', 'c1'], + ['a2', 'b2', 'c2'], + ['a3', 'b3', 'c3'], + ['a4', 'b4', 'c4'], + ]); + $sheet->getCell('B2')->setValue('=INDIRECT("' . $address . '", false)'); + self::assertSame($expectedResult, $sheet->getCell('B2')->getCalculatedValue()); + } + + public function providerRelative(): array + { + return [ + 'same row with bracket next column' => ['c2', 'R[]C[+1]'], + 'same row without bracket next column' => ['c2', 'RC[+1]'], + 'same row without bracket next column no plus sign' => ['c2', 'RC[1]'], + 'same row previous column' => ['a2', 'RC[-1]'], + 'previous row previous column' => ['a1', 'R[-1]C[-1]'], + 'previous row same column with bracket' => ['b1', 'R[-1]C[]'], + 'previous row same column without bracket' => ['b1', 'R[-1]C'], + 'previous row next column' => ['c1', 'R[-1]C[+1]'], + 'next row no plus sign previous column' => ['a3', 'R[1]C[-1]'], + 'next row previous column' => ['a3', 'R[+1]C[-1]'], + 'next row same column' => ['b3', 'R[+1]C'], + 'next row next column' => ['c3', 'R[+1]C[+1]'], + 'two rows down same column' => ['b4', 'R[+2]C'], + 'invalid row' => ['#REF!', 'R[-2]C'], + 'invalid column' => ['#REF!', 'RC[-2]'], + 'circular reference' => [0, 'RC'], // matches Excel's treatment + 'absolute row absolute column' => ['c2', 'R2C3'], + 'absolute row relative column' => ['a2', 'R2C[-1]'], + 'relative row absolute column lowercase' => ['a2', 'rc1'], + 'uninitialized cell' => [null, 'RC[+2]'], // Excel result is 0 + ]; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php index e0786505..22787e25 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef; use PhpOffice\PhpSpreadsheet\Calculation\LookupRef; +use PhpOffice\PhpSpreadsheet\NamedRange; class OffsetTest extends AllSetupTeardown { @@ -46,4 +47,18 @@ class OffsetTest extends AllSetupTeardown $sheet->getCell('A5')->setValue('=OFFSET(C1, 0, 0)'); self::assertSame(5, $sheet->getCell('A5')->getCalculatedValue()); } + + public function testOffsetNamedRange(): void + { + $workSheet = $this->getSheet(); + $workSheet->setCellValue('A1', 1); + $workSheet->setCellValue('A2', 2); + + $this->getSpreadsheet()->addNamedRange(new NamedRange('demo', $workSheet, '=$A$1')); + + $workSheet->setCellValue('B1', '=demo'); + $workSheet->setCellValue('B2', '=OFFSET(demo, 1, 0)'); + + self::assertSame(2, $workSheet->getCell('B2')->getCalculatedValue()); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/RowsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/RowsTest.php index 2155bdf1..fd9902f4 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/RowsTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/RowsTest.php @@ -3,17 +3,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\LookupRef; use PHPUnit\Framework\TestCase; class RowsTest extends TestCase { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerROWS * @@ -21,7 +15,7 @@ class RowsTest extends TestCase */ public function testROWS($expectedResult, ...$args): void { - $result = LookupRef::ROWS(...$args); + $result = LookupRef::ROWS(/** @scrutinizer ignore-type */ ...$args); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactTest.php index 328773e0..6bdfa570 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactTest.php @@ -6,6 +6,8 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation; class FactTest extends AllSetupTeardown { + const FACT_PRECISION = 1E-12; + /** * @dataProvider providerFACT * @@ -53,7 +55,7 @@ class FactTest extends AllSetupTeardown $sheet->getCell('B1')->setValue('=FACT(A1)'); } $result = $sheet->getCell('B1')->getCalculatedValue(); - self::assertEquals($expectedResult, $result); + self::assertEqualsWithDelta($expectedResult, $result, self::FACT_PRECISION); } public function providerFACTGnumeric(): array @@ -70,7 +72,7 @@ class FactTest extends AllSetupTeardown $formula = "=FACT({$array})"; $result = $calculation->_calculateFormulaValue($formula); - self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14); + self::assertEqualsWithDelta($expectedResult, $result, self::FACT_PRECISION); } public function providerFactArray(): array diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MUnitTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MUnitTest.php index 96ebc45b..9ac68ee5 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MUnitTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MUnitTest.php @@ -6,6 +6,8 @@ use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\MatrixFunctions; class MUnitTest extends AllSetupTeardown { + const MU_PRECISION = 1.0E-12; + public function testMUNIT(): void { $identity = MatrixFunctions::identity(3); @@ -15,7 +17,7 @@ class MUnitTest extends AllSetupTeardown self::assertEquals($startArray, $resultArray); $inverseArray = MatrixFunctions::inverse($startArray); $resultArray = MatrixFunctions::multiply($startArray, $inverseArray); - self::assertEquals($identity, $resultArray); + self::assertEqualsWithDelta($identity, $resultArray, self::MU_PRECISION); self::assertEquals('#VALUE!', MatrixFunctions::identity(0)); self::assertEquals('#VALUE!', MatrixFunctions::identity(-1)); self::assertEquals('#VALUE!', MatrixFunctions::identity('X')); diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandBetweenTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandBetweenTest.php index 99cc1fa8..50b7117c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandBetweenTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandBetweenTest.php @@ -60,7 +60,7 @@ class RandBetweenTest extends AllSetupTeardown $formula = "=RandBetween({$argument1}, {$argument2})"; $result = $calculation->_calculateFormulaValue($formula); self::assertIsArray($result); - self::assertCount($expectedRows, $result); + self::assertCount($expectedRows, /** @scrutinizer ignore-type */ $result); self::assertIsArray($result[0]); self::assertCount($expectedColumns, /** @scrutinizer ignore-type */ $result[0]); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumTest.php index 3f80b8ec..780b9623 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumTest.php @@ -2,6 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; +use PhpOffice\PhpSpreadsheet\Spreadsheet; + class SumTest extends AllSetupTeardown { /** @@ -44,4 +46,37 @@ class SumTest extends AllSetupTeardown { return require 'tests/data/Calculation/MathTrig/SUMLITERALS.php'; } + + /** + * @dataProvider providerSUMWITHINDEXMATCH + * + * @param mixed $expectedResult + */ + public function testSumWithIndexMatch($expectedResult, string $formula): void + { + $spreadsheet = new Spreadsheet(); + $sheet1 = $spreadsheet->getActiveSheet(); + $sheet1->setTitle('Formula'); + $sheet1->fromArray( + [ + ['Number', 'Formula'], + [83, $formula], + ] + ); + $sheet2 = $spreadsheet->createSheet(); + $sheet2->setTitle('Lookup'); + $sheet2->fromArray( + [ + ['Lookup', 'Match'], + [83, 16], + ] + ); + self::assertSame($expectedResult, $sheet1->getCell('B2')->getCalculatedValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function providerSUMWITHINDEXMATCH(): array + { + return require 'tests/data/Calculation/MathTrig/SUMWITHINDEXMATCH.php'; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ArrayToTextTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ArrayToTextTest.php new file mode 100644 index 00000000..81fa5a53 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ArrayToTextTest.php @@ -0,0 +1,24 @@ +getSheet(); + $worksheet->fromArray($testData, null, 'A1', true); + $worksheet->getCell('H1')->setValue("=ARRAYTOTEXT(A1:C5, {$mode})"); + + $result = $worksheet->getCell('H1')->getCalculatedValue(); + self::assertSame($expectedResult, $result); + } + + public function providerARRAYTOTEXT(): array + { + return require 'tests/data/Calculation/TextData/ARRAYTOTEXT.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateTest.php index 6c9a871d..53ce2435 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateTest.php @@ -2,6 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; +use PhpOffice\PhpSpreadsheet\Spreadsheet; + class ConcatenateTest extends AllSetupTeardown { /** @@ -30,4 +32,27 @@ class ConcatenateTest extends AllSetupTeardown { return require 'tests/data/Calculation/TextData/CONCATENATE.php'; } + + public function testConcatenateWithIndexMatch(): void + { + $spreadsheet = new Spreadsheet(); + $sheet1 = $spreadsheet->getActiveSheet(); + $sheet1->setTitle('Formula'); + $sheet1->fromArray( + [ + ['Number', 'Formula'], + [52101293, '=CONCAT(INDEX(Lookup!B2, MATCH(A2, Lookup!A2, 0)))'], + ] + ); + $sheet2 = $spreadsheet->createSheet(); + $sheet2->setTitle('Lookup'); + $sheet2->fromArray( + [ + ['Lookup', 'Match'], + [52101293, 'PHP'], + ] + ); + self::assertSame('PHP', $sheet1->getCell('B2')->getCalculatedValue()); + $spreadsheet->disconnectWorksheets(); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/NumberValueTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/NumberValueTest.php index a100695f..1a909932 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/NumberValueTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/NumberValueTest.php @@ -6,6 +6,8 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation; class NumberValueTest extends AllSetupTeardown { + const NV_PRECISION = 1.0E-8; + /** * @dataProvider providerNUMBERVALUE * @@ -34,7 +36,7 @@ class NumberValueTest extends AllSetupTeardown $sheet->getCell('B1')->setValue('=NUMBERVALUE(A1, A2, A3)'); } $result = $sheet->getCell('B1')->getCalculatedValue(); - self::assertEquals($expectedResult, $result); + self::assertEqualsWithDelta($expectedResult, $result, self::NV_PRECISION); } public function providerNUMBERVALUE(): array @@ -51,7 +53,7 @@ class NumberValueTest extends AllSetupTeardown $formula = "=NumberValue({$argument1}, {$argument2}, {$argument3})"; $result = $calculation->_calculateFormulaValue($formula); - self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14); + self::assertEqualsWithDelta($expectedResult, $result, self::NV_PRECISION); } public function providerNumberValueArray(): array diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextAfterTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextAfterTest.php new file mode 100644 index 00000000..00483260 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextAfterTest.php @@ -0,0 +1,36 @@ +getSheet(); + $worksheet->getCell('A1')->setValue($text); + $worksheet->getCell('A2')->setValue((is_array($delimiter)) ? $delimiter[0] : $delimiter); + if (is_array($delimiter)) { + $worksheet->getCell('A3')->setValue($delimiter[1]); + } + $worksheet->getCell('B1')->setValue("=TEXTAFTER({$args})"); + + $result = $worksheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function providerTEXTAFTER(): array + { + return require 'tests/data/Calculation/TextData/TEXTAFTER.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextBeforeTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextBeforeTest.php new file mode 100644 index 00000000..37e46636 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextBeforeTest.php @@ -0,0 +1,36 @@ +getSheet(); + $worksheet->getCell('A1')->setValue($text); + $worksheet->getCell('A2')->setValue((is_array($delimiter)) ? $delimiter[0] : $delimiter); + if (is_array($delimiter)) { + $worksheet->getCell('A3')->setValue($delimiter[1]); + } + $worksheet->getCell('B1')->setValue("=TEXTBEFORE({$args})"); + + $result = $worksheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function providerTEXTBEFORE(): array + { + return require 'tests/data/Calculation/TextData/TEXTBEFORE.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextSplitTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextSplitTest.php new file mode 100644 index 00000000..e0fec8b9 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextSplitTest.php @@ -0,0 +1,60 @@ + $value) { + ++$index; + $worksheet->getCell("{$column}{$index}")->setValue($value); + } + } else { + $worksheet->getCell("{$column}1")->setValue($argument); + } + } + + /** + * @dataProvider providerTEXTSPLIT + */ + public function testTextSplit(array $expectedResult, array $arguments): void + { + $text = $arguments[0]; + $columnDelimiter = $arguments[1]; + $rowDelimiter = $arguments[2]; + + $args = 'A1'; + $args .= (is_array($columnDelimiter)) ? ', ' . $this->setDelimiterArgument($columnDelimiter, 'B') : ', B1'; + $args .= (is_array($rowDelimiter)) ? ', ' . $this->setDelimiterArgument($rowDelimiter, 'C') : ', C1'; + $args .= (isset($arguments[3])) ? ", {$arguments[3]}" : ','; + $args .= (isset($arguments[4])) ? ", {$arguments[4]}" : ','; + $args .= (isset($arguments[5])) ? ", {$arguments[5]}" : ','; + + $worksheet = $this->getSheet(); + $worksheet->getCell('A1')->setValue($text); + $this->setDelimiterValues($worksheet, 'B', $columnDelimiter); + $this->setDelimiterValues($worksheet, 'C', $rowDelimiter); + $worksheet->getCell('H1')->setValue("=TEXTSPLIT({$args})"); + + $result = Calculation::getInstance($this->getSpreadsheet())->calculateCellValue($worksheet->getCell('H1')); + self::assertSame($expectedResult, $result); + } + + public function providerTEXTSPLIT(): array + { + return require 'tests/data/Calculation/TextData/TEXTSPLIT.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ValueToTextTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ValueToTextTest.php new file mode 100644 index 00000000..574d4f56 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ValueToTextTest.php @@ -0,0 +1,28 @@ +getSheet(); + $this->setCell('A1', $value); + $sheet->getCell('B1')->setValue("=VALUETOTEXT(A1, {$format})"); + + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertSame($expectedResult, $result); + } + + public function providerVALUE(): array + { + return require 'tests/data/Calculation/TextData/VALUETOTEXT.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php b/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php index e2384460..ac1f15a1 100644 --- a/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php @@ -19,18 +19,23 @@ class TranslationTest extends TestCase */ private $returnDate; + /** @var string */ + private $locale; + protected function setUp(): void { $this->compatibilityMode = Functions::getCompatibilityMode(); $this->returnDate = Functions::getReturnDateType(); Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); + $this->locale = Settings::getLocale(); } protected function tearDown(): void { Functions::setCompatibilityMode($this->compatibilityMode); Functions::setReturnDateType($this->returnDate); + Settings::setLocale($this->locale); } /** diff --git a/tests/PhpSpreadsheetTests/Calculation/XlfnFunctionsTest.php b/tests/PhpSpreadsheetTests/Calculation/XlfnFunctionsTest.php index f8f02f0e..3edd22c8 100644 --- a/tests/PhpSpreadsheetTests/Calculation/XlfnFunctionsTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/XlfnFunctionsTest.php @@ -1,6 +1,6 @@ getActiveSheet(); $sheet->getCell('A1')->setValue($value); - self::assertEquals($valueBinded, $sheet->getCell('A1')->getValue()); + self::assertEqualsWithDelta($valueBinded, $sheet->getCell('A1')->getValue(), self::AVB_PRECISION); $spreadsheet->disconnectWorksheets(); } diff --git a/tests/PhpSpreadsheetTests/Chart/AxisGlowTest.php b/tests/PhpSpreadsheetTests/Chart/AxisGlowTest.php index 0ed4a1b4..b14d63fb 100644 --- a/tests/PhpSpreadsheetTests/Chart/AxisGlowTest.php +++ b/tests/PhpSpreadsheetTests/Chart/AxisGlowTest.php @@ -3,11 +3,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Chart; use PhpOffice\PhpSpreadsheet\Chart\Chart; +use PhpOffice\PhpSpreadsheet\Chart\ChartColor; use PhpOffice\PhpSpreadsheet\Chart\DataSeries; use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues; use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend; use PhpOffice\PhpSpreadsheet\Chart\PlotArea; -use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Chart\Title; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -107,7 +107,7 @@ class AxisGlowTest extends AbstractFunctional $yAxis = $chart->getChartAxisY(); $xAxis = $chart->getChartAxisX(); $yGlowSize = 10.0; - $yAxis->setGlowProperties($yGlowSize, 'FFFF00', 30, Properties::EXCEL_COLOR_TYPE_ARGB); + $yAxis->setGlowProperties($yGlowSize, 'FFFF00', 30, ChartColor::EXCEL_COLOR_TYPE_RGB); $expectedGlowColor = [ 'type' => 'srgbClr', 'value' => 'FFFF00', @@ -231,7 +231,7 @@ class AxisGlowTest extends AbstractFunctional ); $yAxis = $chart->getChartAxisX(); // deliberate $yGlowSize = 20.0; - $yAxis->setGlowProperties($yGlowSize, 'accent1', 20, Properties::EXCEL_COLOR_TYPE_SCHEME); + $yAxis->setGlowProperties($yGlowSize, 'accent1', 20, ChartColor::EXCEL_COLOR_TYPE_SCHEME); $expectedGlowColor = [ 'type' => 'schemeClr', 'value' => 'accent1', diff --git a/tests/PhpSpreadsheetTests/Chart/AxisPropertiesTest.php b/tests/PhpSpreadsheetTests/Chart/AxisPropertiesTest.php index 91df25cb..0bfc2966 100644 --- a/tests/PhpSpreadsheetTests/Chart/AxisPropertiesTest.php +++ b/tests/PhpSpreadsheetTests/Chart/AxisPropertiesTest.php @@ -109,7 +109,9 @@ class AxisPropertiesTest extends AbstractFunctional '8', //minimum '68', //maximum '20', //majorUnit - '5' //minorUnit + '5', //minorUnit + '6', //textRotation + '0', //hidden ); self::assertSame(Properties::AXIS_LABELS_HIGH, $xAxis->getAxisOptionsProperty('axis_labels')); self::assertNull($xAxis->getAxisOptionsProperty('horizontal_crosses_value')); @@ -121,6 +123,8 @@ class AxisPropertiesTest extends AbstractFunctional self::assertSame('68', $xAxis->getAxisOptionsProperty('maximum')); self::assertSame('20', $xAxis->getAxisOptionsProperty('major_unit')); self::assertSame('5', $xAxis->getAxisOptionsProperty('minor_unit')); + self::assertSame('6', $xAxis->getAxisOptionsProperty('textRotation')); + self::assertSame('0', $xAxis->getAxisOptionsProperty('hidden')); $yAxis = new Axis(); $yAxis->setFillParameters('accent1', 30, 'schemeClr'); @@ -158,6 +162,8 @@ class AxisPropertiesTest extends AbstractFunctional self::assertSame('68', $xAxis2->getAxisOptionsProperty('maximum')); self::assertSame('20', $xAxis2->getAxisOptionsProperty('major_unit')); self::assertSame('5', $xAxis2->getAxisOptionsProperty('minor_unit')); + self::assertSame('6', $xAxis2->getAxisOptionsProperty('textRotation')); + self::assertSame('0', $xAxis2->getAxisOptionsProperty('hidden')); $yAxis2 = $chart->getChartAxisY(); self::assertSame('accent1', $yAxis2->getFillProperty('value')); @@ -198,6 +204,8 @@ class AxisPropertiesTest extends AbstractFunctional self::assertSame('68', $xAxis3->getAxisOptionsProperty('maximum')); self::assertSame('20', $xAxis3->getAxisOptionsProperty('major_unit')); self::assertSame('5', $xAxis3->getAxisOptionsProperty('minor_unit')); + self::assertSame('6', $xAxis3->getAxisOptionsProperty('textRotation')); + self::assertSame('0', $xAxis3->getAxisOptionsProperty('hidden')); $yAxis3 = $chart2->getChartAxisY(); self::assertSame('accent1', $yAxis3->getFillProperty('value')); diff --git a/tests/PhpSpreadsheetTests/Chart/AxisShadowTest.php b/tests/PhpSpreadsheetTests/Chart/AxisShadowTest.php index d6f122ef..78deece4 100644 --- a/tests/PhpSpreadsheetTests/Chart/AxisShadowTest.php +++ b/tests/PhpSpreadsheetTests/Chart/AxisShadowTest.php @@ -3,11 +3,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Chart; use PhpOffice\PhpSpreadsheet\Chart\Chart; +use PhpOffice\PhpSpreadsheet\Chart\ChartColor; use PhpOffice\PhpSpreadsheet\Chart\DataSeries; use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues; use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend; use PhpOffice\PhpSpreadsheet\Chart\PlotArea; -use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Chart\Title; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -113,7 +113,7 @@ class AxisShadowTest extends AbstractFunctional 'distance' => 3, 'rotWithShape' => 0, 'color' => [ - 'type' => Properties::EXCEL_COLOR_TYPE_STANDARD, + 'type' => ChartColor::EXCEL_COLOR_TYPE_STANDARD, 'value' => 'black', 'alpha' => 40, ], @@ -139,7 +139,7 @@ class AxisShadowTest extends AbstractFunctional 'ky' => null, ], 'color' => [ - 'type' => Properties::EXCEL_COLOR_TYPE_ARGB, + 'type' => ChartColor::EXCEL_COLOR_TYPE_RGB, 'value' => 'FF0000', 'alpha' => 20, ], diff --git a/tests/PhpSpreadsheetTests/Chart/BarChartCustomColorsTest.php b/tests/PhpSpreadsheetTests/Chart/BarChartCustomColorsTest.php index 72c541fb..431ad670 100644 --- a/tests/PhpSpreadsheetTests/Chart/BarChartCustomColorsTest.php +++ b/tests/PhpSpreadsheetTests/Chart/BarChartCustomColorsTest.php @@ -1,6 +1,6 @@ setName('chart1') ->setLegend($legend) ->setPlotArea($plotArea) ->setPlotVisibleOnly(true) diff --git a/tests/PhpSpreadsheetTests/Chart/Charts32CatAxValAxTest.php b/tests/PhpSpreadsheetTests/Chart/Charts32CatAxValAxTest.php index 268ee094..1f046af9 100644 --- a/tests/PhpSpreadsheetTests/Chart/Charts32CatAxValAxTest.php +++ b/tests/PhpSpreadsheetTests/Chart/Charts32CatAxValAxTest.php @@ -23,6 +23,8 @@ class Charts32CatAxValAxTest extends TestCase /** @var string */ private $outputFileName = ''; + private const FORMAT_CODE_DATE_ISO8601_SLASH = 'yyyy/mm/dd'; // not automatically treated as numeric + protected function tearDown(): void { if ($this->outputFileName !== '') { @@ -48,7 +50,7 @@ class Charts32CatAxValAxTest extends TestCase ['=DATEVALUE("2021-01-10")', 30.2, 32.2, 0.2], ] ); - $worksheet->getStyle('A2:A5')->getNumberFormat()->setFormatCode(Properties::FORMAT_CODE_DATE_ISO8601); + $worksheet->getStyle('A2:A5')->getNumberFormat()->setFormatCode(self::FORMAT_CODE_DATE_ISO8601_SLASH); $worksheet->getColumnDimension('A')->setAutoSize(true); $worksheet->setSelectedCells('A1'); @@ -91,9 +93,9 @@ class Charts32CatAxValAxTest extends TestCase $xAxis = new Axis(); //$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE ); if (is_bool($numeric)) { - $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601, $numeric); + $xAxis->setAxisNumberProperties(self::FORMAT_CODE_DATE_ISO8601_SLASH, $numeric); } else { - $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601); + $xAxis->setAxisNumberProperties(self::FORMAT_CODE_DATE_ISO8601_SLASH); } // Build the dataseries diff --git a/tests/PhpSpreadsheetTests/Chart/Charts32ScatterTest.php b/tests/PhpSpreadsheetTests/Chart/Charts32ScatterTest.php index 77b0a9b2..fcde7ae3 100644 --- a/tests/PhpSpreadsheetTests/Chart/Charts32ScatterTest.php +++ b/tests/PhpSpreadsheetTests/Chart/Charts32ScatterTest.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Chart; +use PhpOffice\PhpSpreadsheet\Chart\ChartColor; use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; use PhpOffice\PhpSpreadsheet\RichText\RichText; @@ -447,4 +448,88 @@ class Charts32ScatterTest extends AbstractFunctional $reloadedSpreadsheet->disconnectWorksheets(); } + + public function testScatter9(): void + { + // gradient testing + $file = self::DIRECTORY . '32readwriteScatterChart9.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('Worksheet', $sheet->getTitle()); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); + self::assertFalse($chart->getNoFill()); + $plotArea = $chart->getPlotArea(); + self::assertNotNull($plotArea); + self::assertFalse($plotArea->getNoFill()); + self::assertEquals(315.0, $plotArea->getGradientFillAngle()); + $stops = $plotArea->getGradientFillStops(); + self::assertCount(3, $stops); + self::assertEquals(0.43808, $stops[0][0]); + self::assertEquals(0, $stops[1][0]); + self::assertEquals(0.91, $stops[2][0]); + $color = $stops[0][1]; + self::assertInstanceOf(ChartColor::class, $color); + self::assertSame('srgbClr', $color->getType()); + self::assertSame('CDDBEC', $color->getValue()); + self::assertNull($color->getAlpha()); + self::assertSame(20, $color->getBrightness()); + $color = $stops[1][1]; + self::assertInstanceOf(ChartColor::class, $color); + self::assertSame('srgbClr', $color->getType()); + self::assertSame('FFC000', $color->getValue()); + self::assertNull($color->getAlpha()); + self::assertNull($color->getBrightness()); + $color = $stops[2][1]; + self::assertInstanceOf(ChartColor::class, $color); + self::assertSame('srgbClr', $color->getType()); + self::assertSame('00B050', $color->getValue()); + self::assertNull($color->getAlpha()); + self::assertSame(4, $color->getBrightness()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function testScatter10(): void + { + // nofill for Chart and PlotArea, hidden Axis + $file = self::DIRECTORY . '32readwriteScatterChart10.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('Worksheet', $sheet->getTitle()); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); + self::assertTrue($chart->getNoFill()); + $plotArea = $chart->getPlotArea(); + self::assertNotNull($plotArea); + self::assertTrue($plotArea->getNoFill()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } } diff --git a/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php b/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php index 6a4673fd..4cc62360 100644 --- a/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php +++ b/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php @@ -177,4 +177,45 @@ class Charts32XmlTest extends TestCase ) ); } + + public function testDateAx(): void + { + $file = self::DIRECTORY . '32readwriteLineDateAxisChart1.xlsx'; + $reader = new XlsxReader(); + $reader->setIncludeCharts(true); + $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); + $charts = $sheet->getChartCollection(); + self::assertCount(2, $charts); + $chart = $charts[1]; + self::assertNotNull($chart); + + $writer = new XlsxWriter($spreadsheet); + $writer->setIncludeCharts(true); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart); + $spreadsheet->disconnectWorksheets(); + + self::assertSame( + 1, + substr_count( + $data, + '' + ) + ); + self::assertSame( + 1, + substr_count( + $data, + '' + ) + ); + self::assertSame( + 1, + substr_count( + $data, + '' + ) + ); + } } diff --git a/tests/PhpSpreadsheetTests/Chart/GridlinesShadowGlowTest.php b/tests/PhpSpreadsheetTests/Chart/GridlinesShadowGlowTest.php index e2c91eba..e1215441 100644 --- a/tests/PhpSpreadsheetTests/Chart/GridlinesShadowGlowTest.php +++ b/tests/PhpSpreadsheetTests/Chart/GridlinesShadowGlowTest.php @@ -4,12 +4,12 @@ namespace PhpOffice\PhpSpreadsheetTests\Chart; use PhpOffice\PhpSpreadsheet\Chart\Axis; use PhpOffice\PhpSpreadsheet\Chart\Chart; +use PhpOffice\PhpSpreadsheet\Chart\ChartColor; use PhpOffice\PhpSpreadsheet\Chart\DataSeries; use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues; use PhpOffice\PhpSpreadsheet\Chart\GridLines; use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend; use PhpOffice\PhpSpreadsheet\Chart\PlotArea; -use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Chart\Title; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -98,7 +98,7 @@ class GridlinesShadowGlowTest extends AbstractFunctional $majorGridlines = new GridLines(); $yAxis->setMajorGridlines($majorGridlines); $majorGlowSize = 10.0; - $majorGridlines->setGlowProperties($majorGlowSize, 'FFFF00', 30, Properties::EXCEL_COLOR_TYPE_ARGB); + $majorGridlines->setGlowProperties($majorGlowSize, 'FFFF00', 30, ChartColor::EXCEL_COLOR_TYPE_RGB); $softEdgeSize = 2.5; $majorGridlines->setSoftEdges($softEdgeSize); $expectedGlowColor = [ @@ -122,7 +122,7 @@ class GridlinesShadowGlowTest extends AbstractFunctional 'distance' => 3, 'rotWithShape' => 0, 'color' => [ - 'type' => Properties::EXCEL_COLOR_TYPE_STANDARD, + 'type' => ChartColor::EXCEL_COLOR_TYPE_STANDARD, 'value' => 'black', 'alpha' => 40, ], diff --git a/tests/PhpSpreadsheetTests/Chart/Issue2506Test.php b/tests/PhpSpreadsheetTests/Chart/Issue2506Test.php index a2a14c9d..e1c30770 100644 --- a/tests/PhpSpreadsheetTests/Chart/Issue2506Test.php +++ b/tests/PhpSpreadsheetTests/Chart/Issue2506Test.php @@ -23,7 +23,7 @@ class Issue2506Test extends AbstractFunctional public function testDataSeriesValues(): void { $reader = new XlsxReader(); - self::readCharts($reader); + $this->readCharts($reader); $spreadsheet = $reader->load(self::DIRECTORY . 'issue.2506.xlsx'); $worksheet = $spreadsheet->getActiveSheet(); $charts = $worksheet->getChartCollection(); diff --git a/tests/PhpSpreadsheetTests/Chart/Issue2965Test.php b/tests/PhpSpreadsheetTests/Chart/Issue2965Test.php new file mode 100644 index 00000000..8294d39b --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/Issue2965Test.php @@ -0,0 +1,42 @@ +Sheet1!$A$1NewTitle', $data); + } + } + + public function testChartTitleFormula(): void + { + $reader = new XlsxReader(); + $reader->setIncludeCharts(true); + $spreadsheet = $reader->load(self::DIRECTORY . 'issue.2965.xlsx'); + $worksheet = $spreadsheet->getActiveSheet(); + $charts = $worksheet->getChartCollection(); + self::assertCount(1, $charts); + $originalChart1 = $charts[0]; + self::assertNotNull($originalChart1); + $originalTitle1 = $originalChart1->getTitle(); + self::assertNotNull($originalTitle1); + self::assertSame('NewTitle', $originalTitle1->getCaptionText()); + + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Chart/MultiplierTest.php b/tests/PhpSpreadsheetTests/Chart/MultiplierTest.php index 35161ff7..db602e8a 100644 --- a/tests/PhpSpreadsheetTests/Chart/MultiplierTest.php +++ b/tests/PhpSpreadsheetTests/Chart/MultiplierTest.php @@ -3,11 +3,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Chart; use PhpOffice\PhpSpreadsheet\Chart\Chart; +use PhpOffice\PhpSpreadsheet\Chart\ChartColor; use PhpOffice\PhpSpreadsheet\Chart\DataSeries; use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues; use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend; use PhpOffice\PhpSpreadsheet\Chart\PlotArea; -use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Chart\Title; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -109,7 +109,7 @@ class MultiplierTest extends TestCase 'ky' => null, ], 'color' => [ - 'type' => Properties::EXCEL_COLOR_TYPE_ARGB, + 'type' => ChartColor::EXCEL_COLOR_TYPE_RGB, 'value' => 'FF0000', 'alpha' => 20, ], diff --git a/tests/PhpSpreadsheetTests/Chart/PieFillTest.php b/tests/PhpSpreadsheetTests/Chart/PieFillTest.php index d659f21c..5b35ca3d 100644 --- a/tests/PhpSpreadsheetTests/Chart/PieFillTest.php +++ b/tests/PhpSpreadsheetTests/Chart/PieFillTest.php @@ -1,6 +1,6 @@ render()); + } +} diff --git a/tests/PhpSpreadsheetTests/Chart/RoundedCornersTest.php b/tests/PhpSpreadsheetTests/Chart/RoundedCornersTest.php new file mode 100644 index 00000000..93bbaa23 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/RoundedCornersTest.php @@ -0,0 +1,74 @@ +setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setIncludeCharts(true); + } + + public function testRounded(): void + { + $file = self::DIRECTORY . '32readwriteAreaChart1.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('Data', $sheet->getTitle()); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); + self::assertTrue($chart->getRoundedCorners()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function testNotRounded(): void + { + $file = self::DIRECTORY . '32readwriteAreaChart2.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('Data', $sheet->getTitle()); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); + self::assertFalse($chart->getRoundedCorners()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Chart/ShadowPresetsTest.php b/tests/PhpSpreadsheetTests/Chart/ShadowPresetsTest.php index e96d6c14..20cacbc1 100644 --- a/tests/PhpSpreadsheetTests/Chart/ShadowPresetsTest.php +++ b/tests/PhpSpreadsheetTests/Chart/ShadowPresetsTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Chart; use PhpOffice\PhpSpreadsheet\Chart\Axis; +use PhpOffice\PhpSpreadsheet\Chart\ChartColor; use PhpOffice\PhpSpreadsheet\Chart\GridLines; use PhpOffice\PhpSpreadsheet\Chart\Properties; use PHPUnit\Framework\TestCase; @@ -131,7 +132,7 @@ class ShadowPresetsTest extends TestCase 'presets' => Properties::SHADOW_PRESETS_NOSHADOW, 'effect' => null, 'color' => [ - 'type' => Properties::EXCEL_COLOR_TYPE_STANDARD, + 'type' => ChartColor::EXCEL_COLOR_TYPE_STANDARD, 'value' => 'black', 'alpha' => 40, ], @@ -160,7 +161,7 @@ class ShadowPresetsTest extends TestCase 'presets' => Properties::SHADOW_PRESETS_NOSHADOW, 'effect' => null, 'color' => [ - 'type' => Properties::EXCEL_COLOR_TYPE_STANDARD, + 'type' => ChartColor::EXCEL_COLOR_TYPE_STANDARD, 'value' => 'black', 'alpha' => 40, ], @@ -189,7 +190,7 @@ class ShadowPresetsTest extends TestCase 'presets' => Properties::SHADOW_PRESETS_NOSHADOW, 'effect' => null, 'color' => [ - 'type' => Properties::EXCEL_COLOR_TYPE_STANDARD, + 'type' => ChartColor::EXCEL_COLOR_TYPE_STANDARD, 'value' => 'black', 'alpha' => 40, ], diff --git a/tests/PhpSpreadsheetTests/Chart/TrendLineTest.php b/tests/PhpSpreadsheetTests/Chart/TrendLineTest.php new file mode 100644 index 00000000..954a7336 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/TrendLineTest.php @@ -0,0 +1,109 @@ +setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setIncludeCharts(true); + } + + public function testTrendLine(): void + { + $file = self::DIRECTORY . '32readwriteScatterChartTrendlines1.xlsx'; + $reader = new XlsxReader(); + $reader->setIncludeCharts(true); + $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getSheet(1); + self::assertSame(2, $sheet->getChartCount()); + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getSheet(1); + self::assertSame('Scatter Chart', $sheet->getTitle()); + $charts = $sheet->getChartCollection(); + self::assertCount(2, $charts); + + $chart = $charts[0]; + self::assertNotNull($chart); + $plotArea = $chart->getPlotArea(); + self::assertNotNull($plotArea); + $plotSeriesArray = $plotArea->getPlotGroup(); + self::assertCount(1, $plotSeriesArray); + $plotSeries = $plotSeriesArray[0]; + $valuesArray = $plotSeries->getPlotValues(); + self::assertCount(3, $valuesArray); + self::assertEmpty($valuesArray[0]->getTrendLines()); + self::assertEmpty($valuesArray[1]->getTrendLines()); + self::assertEmpty($valuesArray[2]->getTrendLines()); + + $chart = $charts[1]; + self::assertNotNull($chart); + $plotArea = $chart->getPlotArea(); + self::assertNotNull($plotArea); + $plotSeriesArray = $plotArea->getPlotGroup(); + self::assertCount(1, $plotSeriesArray); + $plotSeries = $plotSeriesArray[0]; + $valuesArray = $plotSeries->getPlotValues(); + self::assertCount(1, $valuesArray); + $trendLines = $valuesArray[0]->getTrendLines(); + self::assertCount(3, $trendLines); + + $trendLine = $trendLines[0]; + self::assertSame('linear', $trendLine->getTrendLineType()); + self::assertFalse($trendLine->getDispRSqr()); + self::assertFalse($trendLine->getDispEq()); + $lineColor = $trendLine->getLineColor(); + self::assertSame('accent4', $lineColor->getValue()); + self::assertSame('stealth', $trendLine->getLineStyleProperty(['arrow', 'head', 'type'])); + self::assertEquals(0.5, $trendLine->getLineStyleProperty('width')); + self::assertSame('', $trendLine->getName()); + self::assertSame(0.0, $trendLine->getBackward()); + self::assertSame(0.0, $trendLine->getForward()); + self::assertSame(0.0, $trendLine->getIntercept()); + + $trendLine = $trendLines[1]; + self::assertSame('poly', $trendLine->getTrendLineType()); + self::assertTrue($trendLine->getDispRSqr()); + self::assertTrue($trendLine->getDispEq()); + $lineColor = $trendLine->getLineColor(); + self::assertSame('accent3', $lineColor->getValue()); + self::assertNull($trendLine->getLineStyleProperty(['arrow', 'head', 'type'])); + self::assertEquals(1.25, $trendLine->getLineStyleProperty('width')); + self::assertSame('metric3 polynomial', $trendLine->getName()); + self::assertSame(20.0, $trendLine->getBackward()); + self::assertSame(28.0, $trendLine->getForward()); + self::assertSame(14400.5, $trendLine->getIntercept()); + + $trendLine = $trendLines[2]; + self::assertSame('movingAvg', $trendLine->getTrendLineType()); + self::assertTrue($trendLine->getDispRSqr()); + self::assertFalse($trendLine->getDispEq()); + $lineColor = $trendLine->getLineColor(); + self::assertSame('accent2', $lineColor->getValue()); + self::assertNull($trendLine->getLineStyleProperty(['arrow', 'head', 'type'])); + self::assertEquals(1.5, $trendLine->getLineStyleProperty('width')); + self::assertSame('', $trendLine->getName()); + self::assertSame(0.0, $trendLine->getBackward()); + self::assertSame(0.0, $trendLine->getForward()); + self::assertSame(0.0, $trendLine->getIntercept()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Collection/CellsTest.php b/tests/PhpSpreadsheetTests/Collection/CellsTest.php index 5731d581..9e662b92 100644 --- a/tests/PhpSpreadsheetTests/Collection/CellsTest.php +++ b/tests/PhpSpreadsheetTests/Collection/CellsTest.php @@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Collection; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Collection\Cells; use PhpOffice\PhpSpreadsheet\Collection\Memory; +use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PHPUnit\Framework\TestCase; @@ -107,7 +108,10 @@ class CellsTest extends TestCase $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); $collection = $this->getMockBuilder(Cells::class) - ->setConstructorArgs([new Worksheet(), new Memory()]) + ->setConstructorArgs([ + new Worksheet(), + Settings::useSimpleCacheVersion3() ? new Memory\SimpleCache3() : new Memory\SimpleCache1(), + ]) ->onlyMethods(['has']) ->getMock(); @@ -121,7 +125,9 @@ class CellsTest extends TestCase { $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); - $cache = $this->createMock(Memory::class); + $cache = $this->createMock( + Settings::useSimpleCacheVersion3() ? Memory\SimpleCache3::class : Memory\SimpleCache1::class + ); $cell = $this->createMock(Cell::class); $cache->method('set') ->willReturn(false); diff --git a/tests/PhpSpreadsheetTests/DefinedNameTest.php b/tests/PhpSpreadsheetTests/DefinedNameTest.php index 43eddc8a..c001f204 100644 --- a/tests/PhpSpreadsheetTests/DefinedNameTest.php +++ b/tests/PhpSpreadsheetTests/DefinedNameTest.php @@ -58,7 +58,7 @@ class DefinedNameTest extends TestCase DefinedName::createInstance('Foo', $this->spreadsheet->getActiveSheet(), '=A1') ); $this->spreadsheet->addDefinedName( - DefinedName::createInstance('FOO', $this->spreadsheet->getSheetByName('Sheet #2'), '=B1', true) + DefinedName::createInstance('FOO', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'), '=B1', true) ); self::assertCount(2, $this->spreadsheet->getDefinedNames()); @@ -66,7 +66,7 @@ class DefinedNameTest extends TestCase self::assertNotNull($definedName1); self::assertSame('=A1', $definedName1->getValue()); - $definedName2 = $this->spreadsheet->getDefinedName('foo', $this->spreadsheet->getSheetByName('Sheet #2')); + $definedName2 = $this->spreadsheet->getDefinedName('foo', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2')); self::assertNotNull($definedName2); self::assertSame('=B1', $definedName2->getValue()); } @@ -85,19 +85,31 @@ class DefinedNameTest extends TestCase self::assertCount(1, $this->spreadsheet->getDefinedNames()); } + public function testRemoveGlobalDefinedName(): void + { + $this->spreadsheet->addDefinedName( + DefinedName::createInstance('Any', $this->spreadsheet->getActiveSheet(), '=A1') + ); + self::assertCount(1, $this->spreadsheet->getDefinedNames()); + + $this->spreadsheet->removeDefinedName('Any'); + self::assertCount(0, $this->spreadsheet->getDefinedNames()); + $this->spreadsheet->removeDefinedName('Other'); + } + public function testRemoveGlobalDefinedNameWhenDuplicateNames(): void { $this->spreadsheet->addDefinedName( DefinedName::createInstance('Foo', $this->spreadsheet->getActiveSheet(), '=A1') ); $this->spreadsheet->addDefinedName( - DefinedName::createInstance('FOO', $this->spreadsheet->getSheetByName('Sheet #2'), '=B1', true) + DefinedName::createInstance('FOO', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'), '=B1', true) ); $this->spreadsheet->removeDefinedName('Foo', $this->spreadsheet->getActiveSheet()); self::assertCount(1, $this->spreadsheet->getDefinedNames()); - $definedName = $this->spreadsheet->getDefinedName('foo', $this->spreadsheet->getSheetByName('Sheet #2')); + $definedName = $this->spreadsheet->getDefinedName('foo', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2')); self::assertNotNull($definedName); self::assertSame('=B1', $definedName->getValue()); } @@ -108,10 +120,10 @@ class DefinedNameTest extends TestCase DefinedName::createInstance('Foo', $this->spreadsheet->getActiveSheet(), '=A1') ); $this->spreadsheet->addDefinedName( - DefinedName::createInstance('FOO', $this->spreadsheet->getSheetByName('Sheet #2'), '=B1', true) + DefinedName::createInstance('FOO', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'), '=B1', true) ); - $this->spreadsheet->removeDefinedName('Foo', $this->spreadsheet->getSheetByName('Sheet #2')); + $this->spreadsheet->removeDefinedName('Foo', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2')); self::assertCount(1, $this->spreadsheet->getDefinedNames()); $definedName = $this->spreadsheet->getDefinedName('foo'); @@ -142,10 +154,8 @@ class DefinedNameTest extends TestCase public function testChangeWorksheet(): void { - $sheet1 = $this->spreadsheet->getSheetByName('Sheet #1'); - $sheet2 = $this->spreadsheet->getSheetByName('Sheet #2'); - self::assertNotNull($sheet1); - self::assertNotNull($sheet2); + $sheet1 = $this->spreadsheet->getSheetByNameOrThrow('Sheet #1'); + $sheet2 = $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'); $sheet1->getCell('A1')->setValue(1); $sheet2->getCell('A1')->setValue(2); @@ -160,10 +170,8 @@ class DefinedNameTest extends TestCase public function testLocalOnly(): void { - $sheet1 = $this->spreadsheet->getSheetByName('Sheet #1'); - $sheet2 = $this->spreadsheet->getSheetByName('Sheet #2'); - self::assertNotNull($sheet1); - self::assertNotNull($sheet2); + $sheet1 = $this->spreadsheet->getSheetByNameOrThrow('Sheet #1'); + $sheet2 = $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'); $sheet1->getCell('A1')->setValue(1); $sheet2->getCell('A1')->setValue(2); @@ -178,10 +186,8 @@ class DefinedNameTest extends TestCase public function testScope(): void { - $sheet1 = $this->spreadsheet->getSheetByName('Sheet #1'); - $sheet2 = $this->spreadsheet->getSheetByName('Sheet #2'); - self::assertNotNull($sheet1); - self::assertNotNull($sheet2); + $sheet1 = $this->spreadsheet->getSheetByNameOrThrow('Sheet #1'); + $sheet2 = $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'); $sheet1->getCell('A1')->setValue(1); $sheet2->getCell('A1')->setValue(2); @@ -196,10 +202,8 @@ class DefinedNameTest extends TestCase public function testClone(): void { - $sheet1 = $this->spreadsheet->getSheetByName('Sheet #1'); - $sheet2 = $this->spreadsheet->getSheetByName('Sheet #2'); - self::assertNotNull($sheet1); - self::assertNotNull($sheet2); + $sheet1 = $this->spreadsheet->getSheetByNameOrThrow('Sheet #1'); + $sheet2 = $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'); $sheet1->getCell('A1')->setValue(1); $sheet2->getCell('A1')->setValue(2); diff --git a/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php b/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php index 700c7c7f..921c59f5 100644 --- a/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php +++ b/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php @@ -58,8 +58,7 @@ class PrintAreaTest extends AbstractFunctional private static function getPrintArea(Spreadsheet $spreadsheet, string $name): string { - $sheet = $spreadsheet->getSheetByName($name); - self::assertNotNull($sheet, "Unable to get sheet $name"); + $sheet = $spreadsheet->getSheetByNameOrThrow($name); return $sheet->getPageSetup()->getPrintArea(); } diff --git a/tests/PhpSpreadsheetTests/Functional/StreamTest.php b/tests/PhpSpreadsheetTests/Functional/StreamTest.php index 3911aaa6..05b87ab9 100644 --- a/tests/PhpSpreadsheetTests/Functional/StreamTest.php +++ b/tests/PhpSpreadsheetTests/Functional/StreamTest.php @@ -17,15 +17,10 @@ class StreamTest extends TestCase ['Csv'], ['Html'], ['Mpdf'], + ['Dompdf'], + ['Tcpdf'], ]; - if (\PHP_VERSION_ID < 80000) { - $providerFormats = array_merge( - $providerFormats, - [['Tcpdf'], ['Dompdf']] - ); - } - return $providerFormats; } diff --git a/tests/PhpSpreadsheetTests/Helper/SampleTest.php b/tests/PhpSpreadsheetTests/Helper/SampleTest.php index 50a650f8..384011e8 100644 --- a/tests/PhpSpreadsheetTests/Helper/SampleTest.php +++ b/tests/PhpSpreadsheetTests/Helper/SampleTest.php @@ -9,7 +9,9 @@ class SampleTest extends TestCase { /** * @runInSeparateProcess + * * @preserveGlobalState disabled + * * @dataProvider providerSample */ public function testSample(string $sample): void @@ -26,9 +28,6 @@ class SampleTest extends TestCase public function providerSample(): array { $skipped = [ - 'Chart/32_Chart_read_write_PDF.php', // Unfortunately JpGraph is not up to date for latest PHP and raise many warnings - 'Chart/32_Chart_read_write_HTML.php', // idem - 'Chart/35_Chart_render.php', // idem ]; // Unfortunately some tests are too long to run with code-coverage diff --git a/tests/PhpSpreadsheetTests/LocaleGeneratorTest.php b/tests/PhpSpreadsheetTests/LocaleGeneratorTest.php index e7429a25..ca9d5183 100644 --- a/tests/PhpSpreadsheetTests/LocaleGeneratorTest.php +++ b/tests/PhpSpreadsheetTests/LocaleGeneratorTest.php @@ -11,31 +11,59 @@ class LocaleGeneratorTest extends TestCase { public function testLocaleGenerator(): void { + $directory = realpath(__DIR__ . '/../../src/PhpSpreadsheet/Calculation/locale/') ?: ''; + self::assertNotEquals('', $directory); $phpSpreadsheetFunctionsProperty = (new ReflectionClass(Calculation::class)) ->getProperty('phpSpreadsheetFunctions'); $phpSpreadsheetFunctionsProperty->setAccessible(true); $phpSpreadsheetFunctions = $phpSpreadsheetFunctionsProperty->getValue(); $localeGenerator = new LocaleGenerator( - (string) realpath(__DIR__ . '/../../src/PhpSpreadsheet/Calculation/locale/'), + $directory . DIRECTORY_SEPARATOR, 'Translations.xlsx', $phpSpreadsheetFunctions ); $localeGenerator->generateLocales(); $testLocales = [ + 'bg', + 'cs', + 'da', + 'de', + 'en', + 'es', + 'fi', 'fr', + 'hu', + 'it', + 'nb', 'nl', + 'pl', 'pt', - 'pt_br', 'ru', + 'sv', + 'tr', ]; - foreach ($testLocales as $locale) { - $locale = str_replace('_', '/', $locale); - $path = realpath(__DIR__ . "/../../src/PhpSpreadsheet/Calculation/locale/{$locale}"); - self::assertFileExists("{$path}/config"); - self::assertFileExists("{$path}/functions"); + $count = count(glob($directory . DIRECTORY_SEPARATOR . '*') ?: []) - 1; // exclude Translations.xlsx + self::assertCount($count, $testLocales); + $testLocales[] = 'pt_br'; + $testLocales[] = 'en_uk'; + $noconfig = ['en']; + $nofunctions = ['en', 'en_uk']; + foreach ($testLocales as $originalLocale) { + $locale = str_replace('_', DIRECTORY_SEPARATOR, $originalLocale); + $path = $directory . DIRECTORY_SEPARATOR . $locale; + if (in_array($originalLocale, $noconfig, true)) { + self::assertFileDoesNotExist($path . DIRECTORY_SEPARATOR . 'config'); + } else { + self::assertFileExists($path . DIRECTORY_SEPARATOR . 'config'); + } + if (in_array($originalLocale, $nofunctions, true)) { + self::assertFileDoesNotExist($path . DIRECTORY_SEPARATOR . 'functions'); + } else { + self::assertFileExists($path . DIRECTORY_SEPARATOR . 'functions'); + } } } } diff --git a/tests/PhpSpreadsheetTests/NamedFormulaTest.php b/tests/PhpSpreadsheetTests/NamedFormulaTest.php index 4c4a6b11..28857abe 100644 --- a/tests/PhpSpreadsheetTests/NamedFormulaTest.php +++ b/tests/PhpSpreadsheetTests/NamedFormulaTest.php @@ -62,7 +62,7 @@ class NamedFormulaTest extends TestCase new NamedFormula('Foo', $this->spreadsheet->getActiveSheet(), '=19%') ); $this->spreadsheet->addNamedFormula( - new NamedFormula('FOO', $this->spreadsheet->getSheetByName('Sheet #2'), '=16%', true) + new NamedFormula('FOO', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'), '=16%', true) ); self::assertCount(2, $this->spreadsheet->getNamedFormulae()); @@ -72,7 +72,7 @@ class NamedFormulaTest extends TestCase '=19%', $formula->getValue() ); - $formula = $this->spreadsheet->getNamedFormula('foo', $this->spreadsheet->getSheetByName('Sheet #2')); + $formula = $this->spreadsheet->getNamedFormula('foo', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2')); self::assertNotNull($formula); self::assertSame( '=16%', @@ -100,13 +100,13 @@ class NamedFormulaTest extends TestCase new NamedFormula('Foo', $this->spreadsheet->getActiveSheet(), '=19%') ); $this->spreadsheet->addNamedFormula( - new NamedFormula('FOO', $this->spreadsheet->getSheetByName('Sheet #2'), '=16%', true) + new NamedFormula('FOO', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'), '=16%', true) ); $this->spreadsheet->removeNamedFormula('Foo', $this->spreadsheet->getActiveSheet()); self::assertCount(1, $this->spreadsheet->getNamedFormulae()); - $formula = $this->spreadsheet->getNamedFormula('foo', $this->spreadsheet->getSheetByName('Sheet #2')); + $formula = $this->spreadsheet->getNamedFormula('foo', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2')); self::assertNotNull($formula); self::assertSame( '=16%', @@ -120,10 +120,10 @@ class NamedFormulaTest extends TestCase new NamedFormula('Foo', $this->spreadsheet->getActiveSheet(), '=19%') ); $this->spreadsheet->addNamedFormula( - new NamedFormula('FOO', $this->spreadsheet->getSheetByName('Sheet #2'), '=16%', true) + new NamedFormula('FOO', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'), '=16%', true) ); - $this->spreadsheet->removeNamedFormula('Foo', $this->spreadsheet->getSheetByName('Sheet #2')); + $this->spreadsheet->removeNamedFormula('Foo', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2')); self::assertCount(1, $this->spreadsheet->getNamedFormulae()); $formula = $this->spreadsheet->getNamedFormula('foo'); @@ -133,4 +133,10 @@ class NamedFormulaTest extends TestCase $formula->getValue() ); } + + public function testRemoveNonExistentNamedFormula(): void + { + self::assertCount(0, $this->spreadsheet->getNamedFormulae()); + $this->spreadsheet->removeNamedFormula('Any'); + } } diff --git a/tests/PhpSpreadsheetTests/NamedRangeTest.php b/tests/PhpSpreadsheetTests/NamedRangeTest.php index c72b7b73..9440ef21 100644 --- a/tests/PhpSpreadsheetTests/NamedRangeTest.php +++ b/tests/PhpSpreadsheetTests/NamedRangeTest.php @@ -62,7 +62,7 @@ class NamedRangeTest extends TestCase new NamedRange('Foo', $this->spreadsheet->getActiveSheet(), '=A1') ); $this->spreadsheet->addNamedRange( - new NamedRange('FOO', $this->spreadsheet->getSheetByName('Sheet #2'), '=B1', true) + new NamedRange('FOO', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'), '=B1', true) ); self::assertCount(2, $this->spreadsheet->getNamedRanges()); @@ -72,7 +72,7 @@ class NamedRangeTest extends TestCase '=A1', $range->getValue() ); - $range = $this->spreadsheet->getNamedRange('foo', $this->spreadsheet->getSheetByName('Sheet #2')); + $range = $this->spreadsheet->getNamedRange('foo', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2')); self::assertNotNull($range); self::assertSame( '=B1', @@ -100,13 +100,13 @@ class NamedRangeTest extends TestCase new NamedRange('Foo', $this->spreadsheet->getActiveSheet(), '=A1') ); $this->spreadsheet->addNamedRange( - new NamedRange('FOO', $this->spreadsheet->getSheetByName('Sheet #2'), '=B1', true) + new NamedRange('FOO', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'), '=B1', true) ); $this->spreadsheet->removeNamedRange('Foo', $this->spreadsheet->getActiveSheet()); self::assertCount(1, $this->spreadsheet->getNamedRanges()); - $sheet = $this->spreadsheet->getNamedRange('foo', $this->spreadsheet->getSheetByName('Sheet #2')); + $sheet = $this->spreadsheet->getNamedRange('foo', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2')); self::assertNotNull($sheet); self::assertSame( '=B1', @@ -120,10 +120,10 @@ class NamedRangeTest extends TestCase new NamedRange('Foo', $this->spreadsheet->getActiveSheet(), '=A1') ); $this->spreadsheet->addNamedRange( - new NamedRange('FOO', $this->spreadsheet->getSheetByName('Sheet #2'), '=B1', true) + new NamedRange('FOO', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2'), '=B1', true) ); - $this->spreadsheet->removeNamedRange('Foo', $this->spreadsheet->getSheetByName('Sheet #2')); + $this->spreadsheet->removeNamedRange('Foo', $this->spreadsheet->getSheetByNameOrThrow('Sheet #2')); self::assertCount(1, $this->spreadsheet->getNamedRanges()); $range = $this->spreadsheet->getNamedRange('foo'); @@ -133,4 +133,10 @@ class NamedRangeTest extends TestCase $range->getValue() ); } + + public function testRemoveNonExistentNamedRange(): void + { + self::assertCount(0, $this->spreadsheet->getNamedRanges()); + $this->spreadsheet->removeNamedRange('Any'); + } } diff --git a/tests/PhpSpreadsheetTests/Reader/Csv/CsvContiguousTest.php b/tests/PhpSpreadsheetTests/Reader/Csv/CsvContiguousTest.php index 291a29d0..8c28c1e7 100644 --- a/tests/PhpSpreadsheetTests/Reader/Csv/CsvContiguousTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Csv/CsvContiguousTest.php @@ -56,13 +56,11 @@ class CsvContiguousTest extends TestCase private static function getCellValue(Spreadsheet $spreadsheet, string $sheetName, string $cellAddress): string { - $sheet = $spreadsheet->getSheetByName($sheetName); + $sheet = $spreadsheet->getSheetByNameOrThrow($sheetName); $result = ''; - if ($sheet !== null) { - $value = $sheet->getCell($cellAddress)->getValue(); - if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) { - $result = (string) $value; - } + $value = $sheet->getCell($cellAddress)->getValue(); + if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) { + $result = (string) $value; } return $result; diff --git a/tests/PhpSpreadsheetTests/Reader/Csv/CsvIssue2232Test.php b/tests/PhpSpreadsheetTests/Reader/Csv/CsvIssue2232Test.php index f9321102..429874dc 100644 --- a/tests/PhpSpreadsheetTests/Reader/Csv/CsvIssue2232Test.php +++ b/tests/PhpSpreadsheetTests/Reader/Csv/CsvIssue2232Test.php @@ -2,11 +2,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader\Csv; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\IValueBinder; use PhpOffice\PhpSpreadsheet\Cell\StringValueBinder; use PhpOffice\PhpSpreadsheet\Reader\Csv; +use PhpOffice\PhpSpreadsheet\Settings; use PHPUnit\Framework\TestCase; class CsvIssue2232Test extends TestCase @@ -16,14 +16,19 @@ class CsvIssue2232Test extends TestCase */ private $valueBinder; + /** @var string */ + private $locale; + protected function setUp(): void { $this->valueBinder = Cell::getValueBinder(); + $this->locale = Settings::getLocale(); } protected function tearDown(): void { Cell::setValueBinder($this->valueBinder); + Settings::setLocale($this->locale); } /** @@ -78,7 +83,7 @@ class CsvIssue2232Test extends TestCase Cell::setValueBinder($binder); } - Calculation::getInstance()->setLocale('fr'); + Settings::setLocale('fr'); $reader = new Csv(); $filename = 'tests/data/Reader/CSV/issue.2232.csv'; diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/HiddenMergeCellsTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/HiddenMergeCellsTest.php new file mode 100644 index 00000000..710b292a --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Ods/HiddenMergeCellsTest.php @@ -0,0 +1,48 @@ +spreadsheet = $reader->load($filename); + } + + public function testHiddenMergeCells(): void + { + $c2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange(); + self::assertTrue($c2InMergeRange); + $a2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange(); + self::assertTrue($a2InMergeRange); + $a2MergeRangeValue = $this->spreadsheet->getActiveSheet()->getCell('A2')->isMergeRangeValueCell(); + self::assertTrue($a2MergeRangeValue); + + $cellArray = $this->spreadsheet->getActiveSheet()->rangeToArray('A2:C2'); + self::assertSame([[12, 4, 3]], $cellArray); + } + + public function testUnmergeHiddenMergeCells(): void + { + $this->spreadsheet->getActiveSheet()->unmergeCells('A2:C2'); + + $c2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange(); + self::assertFalse($c2InMergeRange); + $a2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange(); + self::assertFalse($a2InMergeRange); + + $cellArray = $this->spreadsheet->getActiveSheet()->rangeToArray('A2:C2', null, false, false, false); + self::assertSame([[12, '=6-B1', '=A2/B2']], $cellArray); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/HiddenMergeCellsTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/HiddenMergeCellsTest.php new file mode 100644 index 00000000..c7085281 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xls/HiddenMergeCellsTest.php @@ -0,0 +1,48 @@ +spreadsheet = $reader->load($filename); + } + + public function testHiddenMergeCells(): void + { + $c2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange(); + self::assertTrue($c2InMergeRange); + $a2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange(); + self::assertTrue($a2InMergeRange); + $a2MergeRangeValue = $this->spreadsheet->getActiveSheet()->getCell('A2')->isMergeRangeValueCell(); + self::assertTrue($a2MergeRangeValue); + + $cellArray = $this->spreadsheet->getActiveSheet()->rangeToArray('A2:C2'); + self::assertSame([[12, 4, 3]], $cellArray); + } + + public function testUnmergeHiddenMergeCells(): void + { + $this->spreadsheet->getActiveSheet()->unmergeCells('A2:C2'); + + $c2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange(); + self::assertFalse($c2InMergeRange); + $a2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange(); + self::assertFalse($a2InMergeRange); + + $cellArray = $this->spreadsheet->getActiveSheet()->rangeToArray('A2:C2', null, false, false, false); + self::assertSame([[12, '=6-B1', '=A2/B2']], $cellArray); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/NumberFormatGeneralTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/NumberFormatGeneralTest.php index a67fbb34..80867892 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xls/NumberFormatGeneralTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xls/NumberFormatGeneralTest.php @@ -15,20 +15,16 @@ class NumberFormatGeneralTest extends AbstractFunctional $reader = new Xls(); $spreadsheet = $reader->load($filename); - $sheet = $spreadsheet->getSheetByName('Blad1'); - if ($sheet === null) { - self::fail('Expected to find sheet Blad1'); - } else { - $array = $sheet->toArray(); - self::assertSame('€ 2.95', $array[1][3]); - self::assertSame(2.95, $sheet->getCell('D2')->getValue()); - self::assertSame(2.95, $sheet->getCell('D2')->getCalculatedValue()); - self::assertSame('€ 2.95', $sheet->getCell('D2')->getFormattedValue()); - self::assertSame(21, $array[1][4]); - self::assertSame(21, $sheet->getCell('E2')->getValue()); - self::assertSame(21, $sheet->getCell('E2')->getCalculatedValue()); - self::assertSame('21', $sheet->getCell('E2')->getFormattedValue()); - } + $sheet = $spreadsheet->getSheetByNameOrThrow('Blad1'); + $array = $sheet->toArray(); + self::assertSame('€ 2.95', $array[1][3]); + self::assertSame(2.95, $sheet->getCell('D2')->getValue()); + self::assertSame(2.95, $sheet->getCell('D2')->getCalculatedValue()); + self::assertSame('€ 2.95', $sheet->getCell('D2')->getFormattedValue()); + self::assertSame(21, $array[1][4]); + self::assertSame(21, $sheet->getCell('E2')->getValue()); + self::assertSame(21, $sheet->getCell('E2')->getCalculatedValue()); + self::assertSame('21', $sheet->getCell('E2')->getFormattedValue()); $spreadsheet->disconnectWorksheets(); } } diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/Rc4Test.php b/tests/PhpSpreadsheetTests/Reader/Xls/Rc4Test.php new file mode 100644 index 00000000..c3056c08 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xls/Rc4Test.php @@ -0,0 +1,21 @@ +RC4($string)); + $expectedResult = '2ac2fecdd8fbb84638e3a4820eb205cc8e29c28b9d5d6b2ef974f311964971c90e8b9ca16467ef2dc6fc3520'; + self::assertSame($expectedResult, $result); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/RichTextSizeTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/RichTextSizeTest.php index 54274e8c..cf45dec1 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xls/RichTextSizeTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xls/RichTextSizeTest.php @@ -12,8 +12,7 @@ class RichTextSizeTest extends AbstractFunctional $filename = 'tests/data/Reader/XLS/RichTextFontSize.xls'; $reader = new Xls(); $spreadsheet = $reader->load($filename); - $sheet = $spreadsheet->getSheetByName('橱柜门板'); - self::assertNotNull($sheet); + $sheet = $spreadsheet->getSheetByNameOrThrow('橱柜门板'); $text = $sheet->getCell('L15')->getValue(); $elements = $text->getRichTextElements(); self::assertEquals(10, $elements[2]->getFont()->getSize()); diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilter2Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilter2Test.php index 6d6949d8..7a264394 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilter2Test.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilter2Test.php @@ -11,12 +11,14 @@ class AutoFilter2Test extends TestCase { private const TESTBOOK = 'tests/data/Reader/XLSX/autofilter2.xlsx'; - public function getVisibleSheet(Worksheet $sheet, int $maxRow): array + public function getVisibleSheet(?Worksheet $sheet, int $maxRow): array { $actualVisible = []; - for ($row = 2; $row <= $maxRow; ++$row) { - if ($sheet->getRowDimension($row)->getVisible()) { - $actualVisible[] = $row; + if ($sheet !== null) { + for ($row = 2; $row <= $maxRow; ++$row) { + if ($sheet->getRowDimension($row)->getVisible()) { + $actualVisible[] = $row; + } } } @@ -26,8 +28,7 @@ class AutoFilter2Test extends TestCase public function testReadDateRange(): void { $spreadsheet = IOFactory::load(self::TESTBOOK); - $sheet = $spreadsheet->getSheetByName('daterange'); - self::assertNotNull($sheet); + $sheet = $spreadsheet->getSheetByNameOrThrow('daterange'); $filter = $sheet->getAutoFilter(); $maxRow = 30; self::assertSame("A1:A$maxRow", $filter->getRange()); @@ -35,13 +36,14 @@ class AutoFilter2Test extends TestCase self::assertCount(1, $columns); $column = $columns['A'] ?? null; self::assertNotNull($column); + /** @scrutinizer ignore-call */ $ruleset = $column->getRules(); self::assertCount(1, $ruleset); $rule = $ruleset[0]; self::assertSame(Rule::AUTOFILTER_RULETYPE_DATEGROUP, $rule->getRuleType()); $value = $rule->getValue(); self::assertIsArray($value); - self::assertCount(6, $value); + self::assertCount(6, /** @scrutinizer ignore-type */ $value); self::assertSame('2002', $value['year']); self::assertSame('', $value['month']); self::assertSame('', $value['day']); @@ -58,8 +60,7 @@ class AutoFilter2Test extends TestCase public function testReadTopTen(): void { $spreadsheet = IOFactory::load(self::TESTBOOK); - $sheet = $spreadsheet->getSheetByName('top10'); - self::assertNotNull($sheet); + $sheet = $spreadsheet->getSheetByNameOrThrow('top10'); $filter = $sheet->getAutoFilter(); $maxRow = 65; self::assertSame("A1:A$maxRow", $filter->getRange()); @@ -67,6 +68,7 @@ class AutoFilter2Test extends TestCase self::assertCount(1, $columns); $column = $columns['A'] ?? null; self::assertNotNull($column); + /** @scrutinizer ignore-call */ $ruleset = $column->getRules(); self::assertCount(1, $ruleset); $rule = $ruleset[0]; @@ -83,8 +85,7 @@ class AutoFilter2Test extends TestCase public function testReadDynamic(): void { $spreadsheet = IOFactory::load(self::TESTBOOK); - $sheet = $spreadsheet->getSheetByName('dynamic'); - self::assertNotNull($sheet); + $sheet = $spreadsheet->getSheetByNameOrThrow('dynamic'); $filter = $sheet->getAutoFilter(); $maxRow = 30; self::assertSame("A1:A$maxRow", $filter->getRange()); @@ -92,6 +93,7 @@ class AutoFilter2Test extends TestCase self::assertCount(1, $columns); $column = $columns['A'] ?? null; self::assertNotNull($column); + /** @scrutinizer ignore-call */ $ruleset = $column->getRules(); self::assertCount(1, $ruleset); $rule = $ruleset[0]; diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartSheetTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartSheetTest.php index 0f1605ff..e7862697 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartSheetTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartSheetTest.php @@ -3,7 +3,6 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Reader\Xlsx; -use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PHPUnit\Framework\TestCase; class ChartSheetTest extends TestCase @@ -16,8 +15,7 @@ class ChartSheetTest extends TestCase $spreadsheet = $reader->load($filename); self::assertCount(2, $spreadsheet->getAllSheets()); - $chartSheet = $spreadsheet->getSheetByName('Chart1'); - self::assertInstanceOf(Worksheet::class, $chartSheet); + $chartSheet = $spreadsheet->getSheetByNameOrThrow('Chart1'); self::assertSame(1, $chartSheet->getChartCount()); } @@ -29,7 +27,6 @@ class ChartSheetTest extends TestCase $spreadsheet = $reader->load($filename); self::assertCount(1, $spreadsheet->getAllSheets()); - $chartSheet = $spreadsheet->getSheetByName('Chart1'); - self::assertNull($chartSheet); + self::assertNull($spreadsheet->getSheetByName('Chart1')); } } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php index 4b051dd0..0ec25d8e 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php @@ -86,8 +86,8 @@ class ConditionalFormattingDataBarXlsxTest extends TestCase $dataBar = $conditionalRule->getDataBar(); self::assertNotNull($dataBar); - self::assertNotEmpty($dataBar->getMinimumConditionalFormatValueObject()); - self::assertNotEmpty($dataBar->getMaximumConditionalFormatValueObject()); + self::assertNotNull($dataBar->getMinimumConditionalFormatValueObject()); + self::assertNotNull($dataBar->getMaximumConditionalFormatValueObject()); self::assertEquals('min', $dataBar->getMinimumConditionalFormatValueObject()->getType()); self::assertEquals('max', $dataBar->getMaximumConditionalFormatValueObject()->getType()); self::assertEquals(Color::COLOR_GREEN, $dataBar->getColor()); @@ -109,8 +109,8 @@ class ConditionalFormattingDataBarXlsxTest extends TestCase self::assertNotEmpty($dataBar); self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); self::assertNotNull($dataBar); - self::assertNotEmpty($dataBar->getMinimumConditionalFormatValueObject()); - self::assertNotEmpty($dataBar->getMaximumConditionalFormatValueObject()); + self::assertNotNull($dataBar->getMinimumConditionalFormatValueObject()); + self::assertNotNull($dataBar->getMaximumConditionalFormatValueObject()); self::assertEquals('min', $dataBar->getMinimumConditionalFormatValueObject()->getType()); self::assertEquals('max', $dataBar->getMaximumConditionalFormatValueObject()->getType()); @@ -118,6 +118,7 @@ class ConditionalFormattingDataBarXlsxTest extends TestCase self::assertNotEmpty($dataBar->getConditionalFormattingRuleExt()); //ext $rule1ext = $dataBar->getConditionalFormattingRuleExt(); + self::assertNotNull($rule1ext); self::assertEquals('{72C64AE0-5CD9-164F-83D1-AB720F263E79}', $rule1ext->getId()); self::assertEquals('dataBar', $rule1ext->getCfRule()); self::assertEquals('A3:A23', $rule1ext->getSqref()); @@ -165,8 +166,8 @@ class ConditionalFormattingDataBarXlsxTest extends TestCase self::assertNotEmpty($dataBar); self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); self::assertNotNull($dataBar); - self::assertNotEmpty($dataBar->getMinimumConditionalFormatValueObject()); - self::assertNotEmpty($dataBar->getMaximumConditionalFormatValueObject()); + self::assertNotNull($dataBar->getMinimumConditionalFormatValueObject()); + self::assertNotNull($dataBar->getMaximumConditionalFormatValueObject()); self::assertEquals('num', $dataBar->getMinimumConditionalFormatValueObject()->getType()); self::assertEquals('num', $dataBar->getMaximumConditionalFormatValueObject()->getType()); self::assertEquals('-5', $dataBar->getMinimumConditionalFormatValueObject()->getValue()); @@ -175,6 +176,7 @@ class ConditionalFormattingDataBarXlsxTest extends TestCase self::assertNotEmpty($dataBar->getConditionalFormattingRuleExt()); //ext $rule1ext = $dataBar->getConditionalFormattingRuleExt(); + self::assertNotNull($rule1ext); self::assertEquals('{98904F60-57F0-DF47-B480-691B20D325E3}', $rule1ext->getId()); self::assertEquals('dataBar', $rule1ext->getCfRule()); self::assertEquals('B3:B23', $rule1ext->getSqref()); @@ -224,8 +226,8 @@ class ConditionalFormattingDataBarXlsxTest extends TestCase self::assertNotEmpty($dataBar); self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); self::assertNotNull($dataBar); - self::assertNotEmpty($dataBar->getMinimumConditionalFormatValueObject()); - self::assertNotEmpty($dataBar->getMaximumConditionalFormatValueObject()); + self::assertNotNull($dataBar->getMinimumConditionalFormatValueObject()); + self::assertNotNull($dataBar->getMaximumConditionalFormatValueObject()); self::assertEquals('min', $dataBar->getMinimumConditionalFormatValueObject()->getType()); self::assertEquals('max', $dataBar->getMaximumConditionalFormatValueObject()->getType()); self::assertEmpty($dataBar->getMinimumConditionalFormatValueObject()->getValue()); @@ -235,6 +237,7 @@ class ConditionalFormattingDataBarXlsxTest extends TestCase //ext $rule1ext = $dataBar->getConditionalFormattingRuleExt(); + self::assertNotNull($rule1ext); self::assertEquals('{453C04BA-7ABD-8548-8A17-D9CFD2BDABE9}', $rule1ext->getId()); self::assertEquals('dataBar', $rule1ext->getCfRule()); self::assertEquals('C3:C23', $rule1ext->getSqref()); @@ -286,8 +289,8 @@ class ConditionalFormattingDataBarXlsxTest extends TestCase self::assertNotNull($dataBar); self::assertTrue($dataBar->getShowValue()); - self::assertNotEmpty($dataBar->getMinimumConditionalFormatValueObject()); - self::assertNotEmpty($dataBar->getMaximumConditionalFormatValueObject()); + self::assertNotNull($dataBar->getMinimumConditionalFormatValueObject()); + self::assertNotNull($dataBar->getMaximumConditionalFormatValueObject()); self::assertEquals('formula', $dataBar->getMinimumConditionalFormatValueObject()->getType()); self::assertEquals('formula', $dataBar->getMaximumConditionalFormatValueObject()->getType()); self::assertEquals('3+2', $dataBar->getMinimumConditionalFormatValueObject()->getValue()); @@ -297,6 +300,7 @@ class ConditionalFormattingDataBarXlsxTest extends TestCase //ext $rule1ext = $dataBar->getConditionalFormattingRuleExt(); + self::assertNotNull($rule1ext); self::assertEquals('{6C1E066A-E240-3D4A-98F8-8CC218B0DFD2}', $rule1ext->getId()); self::assertEquals('dataBar', $rule1ext->getCfRule()); self::assertEquals('D3:D23', $rule1ext->getSqref()); diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenMergeCellsTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenMergeCellsTest.php new file mode 100644 index 00000000..4919cd27 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenMergeCellsTest.php @@ -0,0 +1,48 @@ +spreadsheet = $reader->load($filename); + } + + public function testHiddenMergeCells(): void + { + $c2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange(); + self::assertTrue($c2InMergeRange); + $a2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange(); + self::assertTrue($a2InMergeRange); + $a2MergeRangeValue = $this->spreadsheet->getActiveSheet()->getCell('A2')->isMergeRangeValueCell(); + self::assertTrue($a2MergeRangeValue); + + $cellArray = $this->spreadsheet->getActiveSheet()->rangeToArray('A2:C2'); + self::assertSame([[12, 4, 3]], $cellArray); + } + + public function testUnmergeHiddenMergeCells(): void + { + $this->spreadsheet->getActiveSheet()->unmergeCells('A2:C2'); + + $c2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('C2')->isInMergeRange(); + self::assertFalse($c2InMergeRange); + $a2InMergeRange = $this->spreadsheet->getActiveSheet()->getCell('A2')->isInMergeRange(); + self::assertFalse($a2InMergeRange); + + $cellArray = $this->spreadsheet->getActiveSheet()->rangeToArray('A2:C2', null, false, false, false); + self::assertSame([[12, '=6-B1', '=A2/B2']], $cellArray); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2301Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2301Test.php index 4484b9e0..7a5829f7 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2301Test.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2301Test.php @@ -24,5 +24,7 @@ class Issue2301Test extends \PHPUnit\Framework\TestCase self::assertSame('Arial CE', $font->getName()); self::assertSame(9.0, $font->getSize()); self::assertSame('protected', $sheet->getCell('BT10')->getStyle()->getProtection()->getHidden()); + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); } } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2501Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2501Test.php index 6b090fe8..57958245 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2501Test.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2501Test.php @@ -42,28 +42,20 @@ class Issue2501Test extends TestCase $filename = self::$testbook; $reader = IOFactory::createReader('Xlsx'); $spreadsheet = $reader->load($filename); - $sheet = $spreadsheet->getSheetByName('Columns'); + $sheet = $spreadsheet->getSheetByNameOrThrow('Columns'); $expected = [ 'A1:A1048576', 'B1:D1048576', 'E2:E4', ]; - if ($sheet === null) { - self::fail('Unable to find sheet Columns'); - } else { - self::assertSame($expected, array_values($sheet->getMergeCells())); - } - $sheet = $spreadsheet->getSheetByName('Rows'); + self::assertSame($expected, array_values($sheet->getMergeCells())); + $sheet = $spreadsheet->getSheetByNameOrThrow('Rows'); $expected = [ 'A1:XFD1', 'A2:XFD4', 'B5:D5', ]; - if ($sheet === null) { - self::fail('Unable to find sheet Rows'); - } else { - self::assertSame($expected, array_values($sheet->getMergeCells())); - } + self::assertSame($expected, array_values($sheet->getMergeCells())); $spreadsheet->disconnectWorksheets(); } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceIssue2109bTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceIssue2109bTest.php index 4c1628fc..b970f084 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceIssue2109bTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceIssue2109bTest.php @@ -1,6 +1,6 @@ getFreezePane()); self::assertSame('A2', $sheet->getTopLeftCell()); self::assertSame('B3', $sheet->getSelectedCells()); - $sheet = $spreadsheet->getSheetByName('SylkTest'); - if ($sheet === null) { - self::fail('Unable to load expected sheet'); - } else { - self::assertNull($sheet->getFreezePane()); - self::assertNull($sheet->getTopLeftCell()); - } + $sheet = $spreadsheet->getSheetByNameOrThrow('SylkTest'); + self::assertNull($sheet->getFreezePane()); + self::assertNull($sheet->getTopLeftCell()); } public function testLoadXlsx(): void diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceOpenpyxl35Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceOpenpyxl35Test.php index d57dc049..afb36862 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceOpenpyxl35Test.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespaceOpenpyxl35Test.php @@ -1,6 +1,6 @@ $array1) { - $sheet = $spreadsheet->getSheetByName($sheetName); - if ($sheet === null) { - self::fail("Unable to find sheet $sheetName"); - } else { - foreach ($array1 as $key => $value) { - self::assertSame($value, self::getCellValue($sheet, $key), "error in sheet $sheetName cell $key"); - } + $sheet = $spreadsheet->getSheetByNameOrThrow($sheetName); + foreach ($array1 as $key => $value) { + self::assertSame($value, self::getCellValue($sheet, $key), "error in sheet $sheetName cell $key"); } } $spreadsheet->disconnectWorksheets(); diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php index ddd6ac5d..e8e9eaba 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/NamespacePurlTest.php @@ -1,6 +1,6 @@ getFreezePane()); self::assertSame('A2', $sheet->getTopLeftCell()); self::assertSame('B3', $sheet->getSelectedCells()); - $sheet = $spreadsheet->getSheetByName('SylkTest'); - if ($sheet === null) { - self::fail('Unable to load expected sheet'); - } else { - self::assertNull($sheet->getFreezePane()); - self::assertNull($sheet->getTopLeftCell()); - } + $sheet = $spreadsheet->getSheetByNameOrThrow('SylkTest'); + self::assertNull($sheet->getFreezePane()); + self::assertNull($sheet->getTopLeftCell()); } public function testLoadXlsx(): void diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/PageSetup2Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/PageSetup2Test.php index 1bbc88ac..12675b41 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/PageSetup2Test.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/PageSetup2Test.php @@ -28,8 +28,7 @@ class PageSetup2Test extends TestCase public function testColumnBreak(): void { $spreadsheet = IOFactory::load(self::TESTBOOK); - $sheet = $spreadsheet->getSheetByName('colbreak'); - self::assertNotNull($sheet); + $sheet = $spreadsheet->getSheetByNameOrThrow('colbreak'); $breaks = $sheet->getBreaks(); self::assertCount(1, $breaks); $break = $breaks['D1'] ?? null; diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php index 197ad47f..68b3bb01 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php @@ -24,14 +24,14 @@ class RibbonTest extends AbstractFunctional self::assertSame('customUI/customUI.xml', $target); $data = $spreadsheet->getRibbonXMLData('data'); self::assertIsString($data); - self::assertSame(1522, strlen($data)); + self::assertSame(1522, strlen(/** @scrutinizer ignore-type */ $data)); $vbaCode = (string) $spreadsheet->getMacrosCode(); self::assertSame(13312, strlen($vbaCode)); self::assertNull($spreadsheet->getRibbonBinObjects()); - self::assertNull($spreadsheet->getRibbonBinObjects('names')); - self::assertNull($spreadsheet->getRibbonBinObjects('data')); + foreach (['names', 'data', 'xxxxx'] as $type) { + self::assertNull($spreadsheet->getRibbonBinObjects($type), "Expecting null when type is $type"); + } self::assertEmpty($spreadsheet->getRibbonBinObjects('types')); - self::assertNull($spreadsheet->getRibbonBinObjects('xxxxx')); $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); $spreadsheet->disconnectWorksheets(); @@ -44,4 +44,34 @@ class RibbonTest extends AbstractFunctional self::assertNull($reloadedSpreadsheet->getRibbonBinObjects()); $reloadedSpreadsheet->disconnectWorksheets(); } + + /** + * Same as above but discard macros. + */ + public function testDiscardMacros(): void + { + $filename = 'tests/data/Reader/XLSX/ribbon.donotopen.zip'; + $reader = IOFactory::createReader('Xlsx'); + $spreadsheet = $reader->load($filename); + self::assertTrue($spreadsheet->hasRibbon()); + $target = $spreadsheet->getRibbonXMLData('target'); + self::assertSame('customUI/customUI.xml', $target); + $data = $spreadsheet->getRibbonXMLData('data'); + self::assertIsString($data); + self::assertSame(1522, strlen(/** @scrutinizer ignore-type */ $data)); + $vbaCode = (string) $spreadsheet->getMacrosCode(); + self::assertSame(13312, strlen($vbaCode)); + $spreadsheet->discardMacros(); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + self::assertTrue($reloadedSpreadsheet->hasRibbon()); + $ribbonData = $reloadedSpreadsheet->getRibbonXmlData(); + self::assertIsArray($ribbonData); + self::assertSame($target, $ribbonData['target'] ?? ''); + self::assertSame($data, $ribbonData['data'] ?? ''); + self::assertNull($reloadedSpreadsheet->getMacrosCode()); + self::assertNull($reloadedSpreadsheet->getRibbonBinObjects()); + $reloadedSpreadsheet->disconnectWorksheets(); + } } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php index e1271b9a..6b0ebf67 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php @@ -15,6 +15,8 @@ use PHPUnit\Framework\TestCase; class XlsxTest extends TestCase { + const XLSX_PRECISION = 1.0E-8; + public function testLoadXlsxRowColumnAttributes(): void { $filename = 'tests/data/Reader/XLSX/rowColumnAttributeTest.xlsx'; @@ -133,10 +135,10 @@ class XlsxTest extends TestCase $pageMargins = $worksheet->getPageMargins(); // Convert from inches to cm for testing - self::assertEquals(2.5, $pageMargins->getTop() * 2.54); - self::assertEquals(3.3, $pageMargins->getLeft() * 2.54); - self::assertEquals(3.3, $pageMargins->getRight() * 2.54); - self::assertEquals(1.3, $pageMargins->getHeader() * 2.54); + self::assertEqualsWithDelta(2.5, $pageMargins->getTop() * 2.54, self::XLSX_PRECISION); + self::assertEqualsWithDelta(3.3, $pageMargins->getLeft() * 2.54, self::XLSX_PRECISION); + self::assertEqualsWithDelta(3.3, $pageMargins->getRight() * 2.54, self::XLSX_PRECISION); + self::assertEqualsWithDelta(1.3, $pageMargins->getHeader() * 2.54, self::XLSX_PRECISION); self::assertEquals(PageSetup::PAPERSIZE_A4, $worksheet->getPageSetup()->getPaperSize()); self::assertEquals(['A10', 'A20', 'A30', 'A40', 'A50'], array_keys($worksheet->getBreaks())); diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/PageSetupTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/PageSetupTest.php index 97476ed5..ae79dd0e 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xml/PageSetupTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xml/PageSetupTest.php @@ -14,19 +14,25 @@ class PageSetupTest extends TestCase private const MARGIN_UNIT_CONVERSION = 2.54; // Inches to cm /** - * @var Spreadsheet + * @var ?Spreadsheet */ private $spreadsheet; - protected function setup(): void + /** @var string */ + private $filename = 'tests/data/Reader/Xml/PageSetup.xml'; + + protected function tearDown(): void { - $filename = 'tests/data/Reader/Xml/PageSetup.xml'; - $reader = new Xml(); - $this->spreadsheet = $reader->load($filename); + if ($this->spreadsheet !== null) { + $this->spreadsheet->disconnectWorksheets(); + $this->spreadsheet = null; + } } public function testPageSetup(): void { + $reader = new Xml(); + $this->spreadsheet = $reader->load($this->filename); $assertions = $this->pageSetupAssertions(); foreach ($this->spreadsheet->getAllSheets() as $worksheet) { @@ -49,6 +55,8 @@ class PageSetupTest extends TestCase public function testPageMargins(): void { + $reader = new Xml(); + $this->spreadsheet = $reader->load($this->filename); $assertions = $this->pageMarginAssertions(); foreach ($this->spreadsheet->getAllSheets() as $worksheet) { diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/XmlLoadTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/XmlLoadTest.php index 29d81299..9846b861 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xml/XmlLoadTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xml/XmlLoadTest.php @@ -4,18 +4,51 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader\Xml; use DateTimeZone; use PhpOffice\PhpSpreadsheet\Reader\Xml; +use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class XmlLoadTest extends TestCase { - public function testLoad(): void + /** @var ?Spreadsheet */ + private $spreadsheet; + + /** @var string */ + private $locale; + + protected function setUp(): void + { + $this->locale = Settings::getLocale(); + } + + protected function tearDown(): void + { + if ($this->spreadsheet !== null) { + $this->spreadsheet->disconnectWorksheets(); + $this->spreadsheet = null; + } + Settings::setLocale($this->locale); + } + + public function testLoadEnglish(): void + { + $this->xtestLoad(); + } + + public function testLoadFrench(): void + { + Settings::setLocale('fr'); + $this->xtestLoad(); + } + + public function xtestLoad(): void { $filename = __DIR__ . '/../../../..' . '/samples/templates/excel2003.xml'; $reader = new Xml(); - $spreadsheet = $reader->load($filename); + $this->spreadsheet = $spreadsheet = $reader->load($filename); self::assertEquals(2, $spreadsheet->getSheetCount()); $sheet = $spreadsheet->getSheet(1); @@ -71,7 +104,7 @@ class XmlLoadTest extends TestCase $reader = new Xml(); $filter = new XmlFilter(); $reader->setReadFilter($filter); - $spreadsheet = $reader->load($filename); + $this->spreadsheet = $spreadsheet = $reader->load($filename); self::assertEquals(2, $spreadsheet->getSheetCount()); $sheet = $spreadsheet->getSheet(1); self::assertEquals('Report Data', $sheet->getTitle()); @@ -87,7 +120,7 @@ class XmlLoadTest extends TestCase . '/samples/templates/excel2003.xml'; $reader = new Xml(); $reader->setLoadSheetsOnly(['Unknown Sheet', 'Report Data']); - $spreadsheet = $reader->load($filename); + $this->spreadsheet = $spreadsheet = $reader->load($filename); self::assertEquals(1, $spreadsheet->getSheetCount()); $sheet = $spreadsheet->getSheet(0); self::assertEquals('Report Data', $sheet->getTitle()); @@ -102,7 +135,7 @@ class XmlLoadTest extends TestCase . '/../../../..' . '/samples/templates/excel2003.short.bad.xml'; $reader = new Xml(); - $spreadsheet = $reader->load($filename); + $this->spreadsheet = $spreadsheet = $reader->load($filename); self::assertEquals(1, $spreadsheet->getSheetCount()); $sheet = $spreadsheet->getSheet(0); self::assertEquals('Sample Data', $sheet->getTitle()); diff --git a/tests/PhpSpreadsheetTests/ReferenceHelperTest.php b/tests/PhpSpreadsheetTests/ReferenceHelperTest.php index 2640d80c..fbd60155 100644 --- a/tests/PhpSpreadsheetTests/ReferenceHelperTest.php +++ b/tests/PhpSpreadsheetTests/ReferenceHelperTest.php @@ -311,6 +311,7 @@ class ReferenceHelperTest extends TestCase self::assertFalse($sheet->getCell($cellAddress)->hasDataValidation()); self::assertTrue($sheet->getCell('E7')->hasDataValidation()); + self::assertSame('E7', $sheet->getDataValidation('E7')->getSqref()); } public function testDeleteRowsWithDataValidation(): void @@ -326,6 +327,7 @@ class ReferenceHelperTest extends TestCase self::assertFalse($sheet->getCell($cellAddress)->hasDataValidation()); self::assertTrue($sheet->getCell('E3')->hasDataValidation()); + self::assertSame('E3', $sheet->getDataValidation('E3')->getSqref()); } public function testDeleteColumnsWithDataValidation(): void @@ -341,6 +343,7 @@ class ReferenceHelperTest extends TestCase self::assertFalse($sheet->getCell($cellAddress)->hasDataValidation()); self::assertTrue($sheet->getCell('C5')->hasDataValidation()); + self::assertSame('C5', $sheet->getDataValidation('C5')->getSqref()); } public function testInsertColumnsWithDataValidation(): void @@ -356,6 +359,7 @@ class ReferenceHelperTest extends TestCase self::assertFalse($sheet->getCell($cellAddress)->hasDataValidation()); self::assertTrue($sheet->getCell('G5')->hasDataValidation()); + self::assertSame('G5', $sheet->getDataValidation('G5')->getSqref()); } private function setDataValidation(Worksheet $sheet, string $cellAddress): void diff --git a/tests/PhpSpreadsheetTests/RichTextTest.php b/tests/PhpSpreadsheetTests/RichTextTest.php index e2dfcce7..e6f55514 100644 --- a/tests/PhpSpreadsheetTests/RichTextTest.php +++ b/tests/PhpSpreadsheetTests/RichTextTest.php @@ -28,7 +28,6 @@ class RichTextTest extends TestCase public function testTextElements(): void { $element1 = new TextElement('A'); - self::assertNull($element1->getFont()); $element2 = new TextElement('B'); $element3 = new TextElement('C'); $richText = new RichText(); diff --git a/tests/PhpSpreadsheetTests/Shared/FileTest.php b/tests/PhpSpreadsheetTests/Shared/FileTest.php index ddc54b5e..e65ec810 100644 --- a/tests/PhpSpreadsheetTests/Shared/FileTest.php +++ b/tests/PhpSpreadsheetTests/Shared/FileTest.php @@ -87,12 +87,14 @@ class FileTest extends TestCase public function testNotReadable(): void { - if (PHP_OS_FAMILY === 'Windows') { + if (PHP_OS_FAMILY === 'Windows' || stristr(PHP_OS, 'CYGWIN') !== false) { self::markTestSkipped('chmod does not work reliably on Windows'); } $this->tempfile = $temp = File::temporaryFileName(); file_put_contents($temp, ''); - chmod($temp, 0070); + if (chmod($temp, 0070) === false) { + self::markTestSkipped('chmod failed'); + } self::assertFalse(File::testFileNoThrow($temp)); $this->expectException(ReaderException::class); $this->expectExceptionMessage('for reading'); diff --git a/tests/PhpSpreadsheetTests/Shared/Font2Test.php b/tests/PhpSpreadsheetTests/Shared/Font2Test.php new file mode 100644 index 00000000..4bb247cf --- /dev/null +++ b/tests/PhpSpreadsheetTests/Shared/Font2Test.php @@ -0,0 +1,149 @@ +providerCharsetFromFontName(); + foreach ($tests as $test) { + $thisTest = $test[0]; + if (array_key_exists($thisTest, $covered)) { + $covered[$thisTest] = 1; + } else { + $defaultCovered = true; + } + } + foreach ($covered as $key => $val) { + self::assertEquals(1, $val, "FontName $key not tested"); + } + self::assertTrue($defaultCovered, 'Default key not tested'); + } + + public function providerCharsetFromFontName(): array + { + return [ + ['EucrosiaUPC', Font::CHARSET_ANSI_THAI], + ['Wingdings', Font::CHARSET_SYMBOL], + ['Wingdings 2', Font::CHARSET_SYMBOL], + ['Wingdings 3', Font::CHARSET_SYMBOL], + ['Default', Font::CHARSET_ANSI_LATIN], + ]; + } + + public function testColumnWidths(): void + { + $widths = Font::DEFAULT_COLUMN_WIDTHS; + $fontNames = ['Arial', 'Calibri', 'Verdana']; + $font = new StyleFont(); + foreach ($fontNames as $fontName) { + $font->setName($fontName); + $array = $widths[$fontName]; + foreach ($array as $points => $array2) { + $font->setSize($points); + $px = $array2['px']; + $width = $array2['width']; + self::assertEquals($px, Font::getDefaultColumnWidthByFont($font, true), "$fontName $points px"); + self::assertEquals($width, Font::getDefaultColumnWidthByFont($font, false), "$fontName $points ooxml-units"); + } + } + $pxCalibri11 = $widths['Calibri'][11]['px']; + $widthCalibri11 = $widths['Calibri'][11]['width']; + $fontName = 'unknown'; + $points = 11; + $font->setName($fontName); + $font->setSize($points); + self::assertEquals($pxCalibri11, Font::getDefaultColumnWidthByFont($font, true), "$fontName $points px"); + self::assertEquals($widthCalibri11, Font::getDefaultColumnWidthByFont($font, false), "$fontName $points ooxml-units"); + $points = 22; + $font->setSize($points); + self::assertEquals(2 * $pxCalibri11, Font::getDefaultColumnWidthByFont($font, true), "$fontName $points px"); + self::assertEquals(2 * $widthCalibri11, Font::getDefaultColumnWidthByFont($font, false), "$fontName $points ooxml-units"); + $fontName = 'Arial'; + $points = 33; + $font->setName($fontName); + $font->setSize($points); + self::assertEquals(3 * $pxCalibri11, Font::getDefaultColumnWidthByFont($font, true), "$fontName $points px"); + self::assertEquals(3 * $widthCalibri11, Font::getDefaultColumnWidthByFont($font, false), "$fontName $points ooxml-units"); + } + + public function testRowHeights(): void + { + $heights = Font::DEFAULT_COLUMN_WIDTHS; + $fontNames = ['Arial', 'Calibri', 'Verdana']; + $font = new StyleFont(); + foreach ($fontNames as $fontName) { + $font->setName($fontName); + $array = $heights[$fontName]; + foreach ($array as $points => $array2) { + $font->setSize($points); + $height = $array2['height']; + self::assertEquals($height, Font::getDefaultRowHeightByFont($font), "$fontName $points points"); + } + } + $heightArial10 = $heights['Arial'][10]['height']; + $fontName = 'Arial'; + $points = 20; + $font->setName($fontName); + $font->setSize($points); + self::assertEquals(2 * $heightArial10, Font::getDefaultRowHeightByFont($font), "$fontName $points points"); + $heightVerdana10 = $heights['Verdana'][10]['height']; + $fontName = 'Verdana'; + $points = 30; + $font->setName($fontName); + $font->setSize($points); + self::assertEquals(3 * $heightVerdana10, Font::getDefaultRowHeightByFont($font), "$fontName $points points"); + $heightCalibri11 = $heights['Calibri'][11]['height']; + $fontName = 'Calibri'; + $points = 22; + $font->setName($fontName); + $font->setSize($points); + self::assertEquals(2 * $heightCalibri11, Font::getDefaultRowHeightByFont($font), "$fontName $points points"); + $fontName = 'unknown'; + $points = 33; + $font->setName($fontName); + $font->setSize($points); + self::assertEquals(3 * $heightCalibri11, Font::getDefaultRowHeightByFont($font), "$fontName $points points"); + } + + public function testGetTrueTypeFontFileFromFont(): void + { + $fileNames = Font::FONT_FILE_NAMES; + $font = new StyleFont(); + foreach ($fileNames as $fontName => $fontNameArray) { + $font->setName($fontName); + $font->setBold(false); + $font->setItalic(false); + self::assertSame($fileNames[$fontName]['x'], Font::getTrueTypeFontFileFromFont($font, false), "$fontName not bold not italic"); + $font->setBold(true); + $font->setItalic(false); + self::assertSame($fileNames[$fontName]['xb'], Font::getTrueTypeFontFileFromFont($font, false), "$fontName bold not italic"); + $font->setBold(false); + $font->setItalic(true); + self::assertSame($fileNames[$fontName]['xi'], Font::getTrueTypeFontFileFromFont($font, false), "$fontName not bold italic"); + $font->setBold(true); + $font->setItalic(true); + self::assertSame($fileNames[$fontName]['xbi'], Font::getTrueTypeFontFileFromFont($font, false), "$fontName bold italic"); + } + } +} diff --git a/tests/PhpSpreadsheetTests/Shared/Font3Test.php b/tests/PhpSpreadsheetTests/Shared/Font3Test.php new file mode 100644 index 00000000..e91e7acf --- /dev/null +++ b/tests/PhpSpreadsheetTests/Shared/Font3Test.php @@ -0,0 +1,53 @@ +holdDirectory = Font::getTrueTypeFontPath(); + } + + protected function tearDown(): void + { + Font::setTrueTypeFontPath($this->holdDirectory); + } + + public function testGetTrueTypeException1(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('Valid directory to TrueType Font files not specified'); + $font = new StyleFont(); + $font->setName('unknown'); + Font::getTrueTypeFontFileFromFont($font); + } + + public function testGetTrueTypeException2(): void + { + Font::setTrueTypeFontPath(__DIR__); + $this->expectException(SSException::class); + $this->expectExceptionMessage('Unknown font name'); + $font = new StyleFont(); + $font->setName('unknown'); + Font::getTrueTypeFontFileFromFont($font); + } + + public function testGetTrueTypeException3(): void + { + Font::setTrueTypeFontPath(__DIR__); + $this->expectException(SSException::class); + $this->expectExceptionMessage('TrueType Font file not found'); + $font = new StyleFont(); + $font->setName('Calibri'); + Font::getTrueTypeFontFileFromFont($font); + } +} diff --git a/tests/PhpSpreadsheetTests/Shared/FontTest.php b/tests/PhpSpreadsheetTests/Shared/FontTest.php index c733f8ee..2d5feb6e 100644 --- a/tests/PhpSpreadsheetTests/Shared/FontTest.php +++ b/tests/PhpSpreadsheetTests/Shared/FontTest.php @@ -8,6 +8,8 @@ use PHPUnit\Framework\TestCase; class FontTest extends TestCase { + const FONT_PRECISION = 1.0E-12; + public function testGetAutoSizeMethod(): void { $expectedResult = Font::AUTOSIZE_METHOD_APPROX; @@ -63,7 +65,7 @@ class FontTest extends TestCase public function testInchSizeToPixels($expectedResult, $size): void { $result = Font::inchSizeToPixels($size); - self::assertEquals($expectedResult, $result); + self::assertEqualsWithDelta($expectedResult, $result, self::FONT_PRECISION); } public function providerInchSizeToPixels(): array @@ -80,7 +82,7 @@ class FontTest extends TestCase public function testCentimeterSizeToPixels($expectedResult, $size): void { $result = Font::centimeterSizeToPixels($size); - self::assertEquals($expectedResult, $result); + self::assertEqualsWithDelta($expectedResult, $result, self::FONT_PRECISION); } public function providerCentimeterSizeToPixels(): array diff --git a/tests/PhpSpreadsheetTests/Shared/PasswordHasherTest.php b/tests/PhpSpreadsheetTests/Shared/PasswordHasherTest.php index 4b7923d8..c9912b8c 100644 --- a/tests/PhpSpreadsheetTests/Shared/PasswordHasherTest.php +++ b/tests/PhpSpreadsheetTests/Shared/PasswordHasherTest.php @@ -10,16 +10,27 @@ class PasswordHasherTest extends TestCase { /** * @dataProvider providerHashPassword - * - * @param mixed $expectedResult */ - public function testHashPassword($expectedResult, ...$args): void - { + public function testHashPassword( + string $expectedResult, + string $password, + ?string $algorithm = null, + ?string $salt = null, + ?int $spinCount = null + ): void { if ($expectedResult === 'exception') { $this->expectException(SpException::class); } - $result = PasswordHasher::hashPassword(...$args); - self::assertEquals($expectedResult, $result); + if ($algorithm === null) { + $result = PasswordHasher::hashPassword($password); + } elseif ($salt === null) { + $result = PasswordHasher::hashPassword($password, $algorithm); + } elseif ($spinCount === null) { + $result = PasswordHasher::hashPassword($password, $algorithm, $salt); + } else { + $result = PasswordHasher::hashPassword($password, $algorithm, $salt, $spinCount); + } + self::assertSame($expectedResult, $result); } public function providerHashPassword(): array diff --git a/tests/PhpSpreadsheetTests/Shared/Trend/LinearBestFitTest.php b/tests/PhpSpreadsheetTests/Shared/Trend/LinearBestFitTest.php index 9ada87a5..34321227 100644 --- a/tests/PhpSpreadsheetTests/Shared/Trend/LinearBestFitTest.php +++ b/tests/PhpSpreadsheetTests/Shared/Trend/LinearBestFitTest.php @@ -7,6 +7,8 @@ use PHPUnit\Framework\TestCase; class LinearBestFitTest extends TestCase { + const LBF_PRECISION = 1.0E-8; + /** * @dataProvider providerLinearBestFit * @@ -27,13 +29,13 @@ class LinearBestFitTest extends TestCase ): void { $bestFit = new LinearBestFit($yValues, $xValues); $slope = $bestFit->getSlope(1); - self::assertEquals($expectedSlope[0], $slope); + self::assertEqualsWithDelta($expectedSlope[0], $slope, self::LBF_PRECISION); $slope = $bestFit->getSlope(); - self::assertEquals($expectedSlope[1], $slope); + self::assertEqualsWithDelta($expectedSlope[1], $slope, self::LBF_PRECISION); $intersect = $bestFit->getIntersect(1); - self::assertEquals($expectedIntersect[0], $intersect); + self::assertEqualsWithDelta($expectedIntersect[0], $intersect, self::LBF_PRECISION); $intersect = $bestFit->getIntersect(); - self::assertEquals($expectedIntersect[1], $intersect); + self::assertEqualsWithDelta($expectedIntersect[1], $intersect, self::LBF_PRECISION); $equation = $bestFit->getEquation(2); self::assertEquals($expectedEquation, $equation); diff --git a/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php b/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php new file mode 100644 index 00000000..a455a7d2 --- /dev/null +++ b/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php @@ -0,0 +1,208 @@ +spreadsheet !== null) { + $this->spreadsheet->disconnectWorksheets(); + $this->spreadsheet = null; + } + if ($this->spreadsheet2 !== null) { + $this->spreadsheet2->disconnectWorksheets(); + $this->spreadsheet2 = null; + } + } + + public function testDocumentProperties(): void + { + $this->spreadsheet = new Spreadsheet(); + $properties = $this->spreadsheet->getProperties(); + $properties->setCreator('Anyone'); + $properties->setTitle('Description'); + $this->spreadsheet2 = new Spreadsheet(); + self::assertNotEquals($properties, $this->spreadsheet2->getProperties()); + $properties2 = clone $properties; + $this->spreadsheet2->setProperties($properties2); + self::assertEquals($properties, $this->spreadsheet2->getProperties()); + } + + public function testDocumentSecurity(): void + { + $this->spreadsheet = new Spreadsheet(); + $security = $this->spreadsheet->getSecurity(); + $security->setLockRevision(true); + $revisionsPassword = 'revpasswd'; + $security->setRevisionsPassword($revisionsPassword); + $this->spreadsheet2 = new Spreadsheet(); + self::assertNotEquals($security, $this->spreadsheet2->getSecurity()); + $security2 = clone $security; + $this->spreadsheet2->setSecurity($security2); + self::assertEquals($security, $this->spreadsheet2->getSecurity()); + } + + public function testCellXfCollection(): void + { + $this->spreadsheet = new Spreadsheet(); + $sheet = $this->spreadsheet->getActiveSheet(); + $sheet->getStyle('A1')->getFont()->setName('font1'); + $sheet->getStyle('A2')->getFont()->setName('font2'); + $sheet->getStyle('A3')->getFont()->setName('font3'); + $sheet->getStyle('B1')->getFont()->setName('font1'); + $sheet->getStyle('B2')->getFont()->setName('font2'); + $collection = $this->spreadsheet->getCellXfCollection(); + self::assertCount(4, $collection); + $font1Style = $collection[1]; + self::assertTrue($this->spreadsheet->cellXfExists($font1Style)); + self::assertSame('font1', $this->spreadsheet->getCellXfCollection()[1]->getFont()->getName()); + self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName()); + self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName()); + self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName()); + self::assertSame('font1', $sheet->getStyle('B1')->getFont()->getName()); + self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName()); + + $this->spreadsheet->removeCellXfByIndex(1); + self::assertFalse($this->spreadsheet->cellXfExists($font1Style)); + self::assertSame('font2', $this->spreadsheet->getCellXfCollection()[1]->getFont()->getName()); + self::assertSame('Calibri', $sheet->getStyle('A1')->getFont()->getName()); + self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName()); + self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName()); + self::assertSame('Calibri', $sheet->getStyle('B1')->getFont()->getName()); + self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName()); + } + + public function testInvalidRemoveCellXfByIndex(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('CellXf index is out of bounds.'); + $this->spreadsheet = new Spreadsheet(); + $sheet = $this->spreadsheet->getActiveSheet(); + $sheet->getStyle('A1')->getFont()->setName('font1'); + $sheet->getStyle('A2')->getFont()->setName('font2'); + $sheet->getStyle('A3')->getFont()->setName('font3'); + $sheet->getStyle('B1')->getFont()->setName('font1'); + $sheet->getStyle('B2')->getFont()->setName('font2'); + $this->spreadsheet->removeCellXfByIndex(5); + } + + public function testInvalidRemoveDefaultStyle(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('No default style found for this workbook'); + // Removing default style probably should be disallowed. + $this->spreadsheet = new Spreadsheet(); + $this->spreadsheet->removeCellXfByIndex(0); + $this->spreadsheet->getDefaultStyle(); + } + + public function testCellStyleXF(): void + { + $this->spreadsheet = new Spreadsheet(); + $collection = $this->spreadsheet->getCellStyleXfCollection(); + self::assertCount(1, $collection); + $styleXf = $collection[0]; + self::assertSame($styleXf, $this->spreadsheet->getCellStyleXfByIndex(0)); + $hash = $styleXf->getHashCode(); + self::assertSame($styleXf, $this->spreadsheet->getCellStyleXfByHashCode($hash)); + self::assertFalse($this->spreadsheet->getCellStyleXfByHashCode($hash . 'x')); + } + + public function testInvalidRemoveCellStyleXfByIndex(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('CellStyleXf index is out of bounds.'); + $this->spreadsheet = new Spreadsheet(); + $this->spreadsheet->removeCellStyleXfByIndex(5); + } + + public function testInvalidFirstSheetIndex(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('First sheet index must be a positive integer.'); + $this->spreadsheet = new Spreadsheet(); + $this->spreadsheet->setFirstSheetIndex(-1); + } + + public function testInvalidVisibility(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('Invalid visibility value.'); + $this->spreadsheet = new Spreadsheet(); + $this->spreadsheet->setVisibility(Spreadsheet::VISIBILITY_HIDDEN); + self::assertSame(Spreadsheet::VISIBILITY_HIDDEN, $this->spreadsheet->getVisibility()); + $this->spreadsheet->setVisibility(null); + self::assertSame(Spreadsheet::VISIBILITY_VISIBLE, $this->spreadsheet->getVisibility()); + $this->spreadsheet->setVisibility('badvalue'); + } + + public function testInvalidTabRatio(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('Tab ratio must be between 0 and 1000.'); + $this->spreadsheet = new Spreadsheet(); + $this->spreadsheet->setTabRatio(2000); + } + + public function testCopy(): void + { + $this->spreadsheet = new Spreadsheet(); + $sheet = $this->spreadsheet->getActiveSheet(); + $sheet->getStyle('A1')->getFont()->setName('font1'); + $sheet->getStyle('A2')->getFont()->setName('font2'); + $sheet->getStyle('A3')->getFont()->setName('font3'); + $sheet->getStyle('B1')->getFont()->setName('font1'); + $sheet->getStyle('B2')->getFont()->setName('font2'); + $sheet->getCell('A1')->setValue('this is a1'); + $sheet->getCell('A2')->setValue('this is a2'); + $sheet->getCell('A3')->setValue('this is a3'); + $sheet->getCell('B1')->setValue('this is b1'); + $sheet->getCell('B2')->setValue('this is b2'); + $this->spreadsheet2 = $this->spreadsheet->copy(); + $copysheet = $this->spreadsheet2->getActiveSheet(); + $copysheet->getStyle('A2')->getFont()->setName('font12'); + $copysheet->getCell('A2')->setValue('this was a2'); + + self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName()); + self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName()); + self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName()); + self::assertSame('font1', $sheet->getStyle('B1')->getFont()->getName()); + self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName()); + self::assertSame('this is a1', $sheet->getCell('A1')->getValue()); + self::assertSame('this is a2', $sheet->getCell('A2')->getValue()); + self::assertSame('this is a3', $sheet->getCell('A3')->getValue()); + self::assertSame('this is b1', $sheet->getCell('B1')->getValue()); + self::assertSame('this is b2', $sheet->getCell('B2')->getValue()); + + self::assertSame('font1', $copysheet->getStyle('A1')->getFont()->getName()); + self::assertSame('font12', $copysheet->getStyle('A2')->getFont()->getName()); + self::assertSame('font3', $copysheet->getStyle('A3')->getFont()->getName()); + self::assertSame('font1', $copysheet->getStyle('B1')->getFont()->getName()); + self::assertSame('font2', $copysheet->getStyle('B2')->getFont()->getName()); + self::assertSame('this is a1', $copysheet->getCell('A1')->getValue()); + self::assertSame('this was a2', $copysheet->getCell('A2')->getValue()); + self::assertSame('this is a3', $copysheet->getCell('A3')->getValue()); + self::assertSame('this is b1', $copysheet->getCell('B1')->getValue()); + self::assertSame('this is b2', $copysheet->getCell('B2')->getValue()); + } + + public function testClone(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('Do not use clone on spreadsheet. Use spreadsheet->copy() instead.'); + $this->spreadsheet = new Spreadsheet(); + $this->spreadsheet2 = clone $this->spreadsheet; + } +} diff --git a/tests/PhpSpreadsheetTests/SpreadsheetTest.php b/tests/PhpSpreadsheetTests/SpreadsheetTest.php index 11fb56e4..76e28b3d 100644 --- a/tests/PhpSpreadsheetTests/SpreadsheetTest.php +++ b/tests/PhpSpreadsheetTests/SpreadsheetTest.php @@ -2,28 +2,38 @@ namespace PhpOffice\PhpSpreadsheetTests; +use PhpOffice\PhpSpreadsheet\Exception as ssException; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PHPUnit\Framework\TestCase; class SpreadsheetTest extends TestCase { - /** @var Spreadsheet */ - private $object; + /** @var ?Spreadsheet */ + private $spreadsheet; - protected function setUp(): void + protected function tearDown(): void { - parent::setUp(); - $this->object = new Spreadsheet(); - $sheet = $this->object->getActiveSheet(); + if ($this->spreadsheet !== null) { + $this->spreadsheet->disconnectWorksheets(); + $this->spreadsheet = null; + } + } + + private function getSpreadsheet(): Spreadsheet + { + $this->spreadsheet = $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('someSheet1'); $sheet = new Worksheet(); $sheet->setTitle('someSheet2'); - $this->object->addSheet($sheet); + $spreadsheet->addSheet($sheet); $sheet = new Worksheet(); $sheet->setTitle('someSheet 3'); - $this->object->addSheet($sheet); + $spreadsheet->addSheet($sheet); + + return $spreadsheet; } public function dataProviderForSheetNames(): array @@ -35,6 +45,7 @@ class SpreadsheetTest extends TestCase [1, "'someSheet2'"], [2, 'someSheet 3'], [2, "'someSheet 3'"], + [null, 'someSheet 33'], ]; return $array; @@ -43,135 +54,153 @@ class SpreadsheetTest extends TestCase /** * @dataProvider dataProviderForSheetNames */ - public function testGetSheetByName(int $index, string $sheetName): void + public function testGetSheetByName(?int $index, string $sheetName): void { - self::assertSame($this->object->getSheet($index), $this->object->getSheetByName($sheetName)); + $spreadsheet = $this->getSpreadsheet(); + if ($index === null) { + self::assertNull($spreadsheet->getSheetByName($sheetName)); + } else { + self::assertSame($spreadsheet->getSheet($index), $spreadsheet->getSheetByName($sheetName)); + } } public function testAddSheetDuplicateTitle(): void { - $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); + $spreadsheet = $this->getSpreadsheet(); + $this->expectException(ssException::class); $sheet = new Worksheet(); $sheet->setTitle('someSheet2'); - $this->object->addSheet($sheet); + $spreadsheet->addSheet($sheet); } public function testAddSheetNoAdjustActive(): void { - $this->object->setActiveSheetIndex(2); - self::assertEquals(2, $this->object->getActiveSheetIndex()); + $spreadsheet = $this->getSpreadsheet(); + $spreadsheet->setActiveSheetIndex(2); + self::assertEquals(2, $spreadsheet->getActiveSheetIndex()); $sheet = new Worksheet(); $sheet->setTitle('someSheet4'); - $this->object->addSheet($sheet); - self::assertEquals(2, $this->object->getActiveSheetIndex()); + $spreadsheet->addSheet($sheet); + self::assertEquals(2, $spreadsheet->getActiveSheetIndex()); } public function testAddSheetAdjustActive(): void { - $this->object->setActiveSheetIndex(2); - self::assertEquals(2, $this->object->getActiveSheetIndex()); + $spreadsheet = $this->getSpreadsheet(); + $spreadsheet->setActiveSheetIndex(2); + self::assertEquals(2, $spreadsheet->getActiveSheetIndex()); $sheet = new Worksheet(); $sheet->setTitle('someSheet0'); - $this->object->addSheet($sheet, 0); - self::assertEquals(3, $this->object->getActiveSheetIndex()); + $spreadsheet->addSheet($sheet, 0); + self::assertEquals(3, $spreadsheet->getActiveSheetIndex()); } public function testRemoveSheetIndexTooHigh(): void { - $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); - $this->object->removeSheetByIndex(4); + $spreadsheet = $this->getSpreadsheet(); + $this->expectException(ssException::class); + $spreadsheet->removeSheetByIndex(4); } public function testRemoveSheetNoAdjustActive(): void { - $this->object->setActiveSheetIndex(1); - self::assertEquals(1, $this->object->getActiveSheetIndex()); - $this->object->removeSheetByIndex(2); - self::assertEquals(1, $this->object->getActiveSheetIndex()); + $spreadsheet = $this->getSpreadsheet(); + $spreadsheet->setActiveSheetIndex(1); + self::assertEquals(1, $spreadsheet->getActiveSheetIndex()); + $spreadsheet->removeSheetByIndex(2); + self::assertEquals(1, $spreadsheet->getActiveSheetIndex()); } public function testRemoveSheetAdjustActive(): void { - $this->object->setActiveSheetIndex(2); - self::assertEquals(2, $this->object->getActiveSheetIndex()); - $this->object->removeSheetByIndex(1); - self::assertEquals(1, $this->object->getActiveSheetIndex()); + $spreadsheet = $this->getSpreadsheet(); + $spreadsheet->setActiveSheetIndex(2); + self::assertEquals(2, $spreadsheet->getActiveSheetIndex()); + $spreadsheet->removeSheetByIndex(1); + self::assertEquals(1, $spreadsheet->getActiveSheetIndex()); } public function testGetSheetIndexTooHigh(): void { - $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); - $this->object->getSheet(4); + $spreadsheet = $this->getSpreadsheet(); + $this->expectException(ssException::class); + $spreadsheet->getSheet(4); } public function testGetIndexNonExistent(): void { - $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); + $spreadsheet = $this->getSpreadsheet(); + $this->expectException(ssException::class); $sheet = new Worksheet(); $sheet->setTitle('someSheet4'); - $this->object->getIndex($sheet); + $spreadsheet->getIndex($sheet); } public function testSetIndexByName(): void { - $this->object->setIndexByName('someSheet1', 1); - self::assertEquals('someSheet2', $this->object->getSheet(0)->getTitle()); - self::assertEquals('someSheet1', $this->object->getSheet(1)->getTitle()); - self::assertEquals('someSheet 3', $this->object->getSheet(2)->getTitle()); + $spreadsheet = $this->getSpreadsheet(); + $spreadsheet->setIndexByName('someSheet1', 1); + self::assertEquals('someSheet2', $spreadsheet->getSheet(0)->getTitle()); + self::assertEquals('someSheet1', $spreadsheet->getSheet(1)->getTitle()); + self::assertEquals('someSheet 3', $spreadsheet->getSheet(2)->getTitle()); } public function testRemoveAllSheets(): void { - $this->object->setActiveSheetIndex(2); - self::assertEquals(2, $this->object->getActiveSheetIndex()); - $this->object->removeSheetByIndex(0); - self::assertEquals(1, $this->object->getActiveSheetIndex()); - $this->object->removeSheetByIndex(0); - self::assertEquals(0, $this->object->getActiveSheetIndex()); - $this->object->removeSheetByIndex(0); - self::assertEquals(-1, $this->object->getActiveSheetIndex()); + $spreadsheet = $this->getSpreadsheet(); + $spreadsheet->setActiveSheetIndex(2); + self::assertEquals(2, $spreadsheet->getActiveSheetIndex()); + $spreadsheet->removeSheetByIndex(0); + self::assertEquals(1, $spreadsheet->getActiveSheetIndex()); + $spreadsheet->removeSheetByIndex(0); + self::assertEquals(0, $spreadsheet->getActiveSheetIndex()); + $spreadsheet->removeSheetByIndex(0); + self::assertEquals(-1, $spreadsheet->getActiveSheetIndex()); $sheet = new Worksheet(); $sheet->setTitle('someSheet4'); - $this->object->addSheet($sheet); - self::assertEquals(0, $this->object->getActiveSheetIndex()); + $spreadsheet->addSheet($sheet); + self::assertEquals(0, $spreadsheet->getActiveSheetIndex()); } public function testBug1735(): void { - $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); - $spreadsheet->createSheet()->setTitle('addedsheet'); - $spreadsheet->setActiveSheetIndex(1); - $spreadsheet->removeSheetByIndex(0); - $sheet = $spreadsheet->getActiveSheet(); + $spreadsheet1 = new Spreadsheet(); + $spreadsheet1->createSheet()->setTitle('addedsheet'); + $spreadsheet1->setActiveSheetIndex(1); + $spreadsheet1->removeSheetByIndex(0); + $sheet = $spreadsheet1->getActiveSheet(); self::assertEquals('addedsheet', $sheet->getTitle()); } public function testSetActiveSheetIndexTooHigh(): void { - $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); - $this->object->setActiveSheetIndex(4); + $spreadsheet = $this->getSpreadsheet(); + $this->expectException(ssException::class); + $spreadsheet->setActiveSheetIndex(4); } public function testSetActiveSheetNoSuchName(): void { - $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); - $this->object->setActiveSheetIndexByName('unknown'); + $spreadsheet = $this->getSpreadsheet(); + $this->expectException(ssException::class); + $spreadsheet->setActiveSheetIndexByName('unknown'); } public function testAddExternal(): void { - $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); - $sheet = $spreadsheet->createSheet()->setTitle('someSheet19'); + $spreadsheet = $this->getSpreadsheet(); + $spreadsheet1 = new Spreadsheet(); + $sheet = $spreadsheet1->createSheet()->setTitle('someSheet19'); $sheet->getCell('A1')->setValue(1); $sheet->getCell('A1')->getStyle()->getFont()->setBold(true); $sheet->getCell('B1')->getStyle()->getFont()->setSuperscript(true); $sheet->getCell('C1')->getStyle()->getFont()->setSubscript(true); - self::assertCount(4, $spreadsheet->getCellXfCollection()); + self::assertCount(4, $spreadsheet1->getCellXfCollection()); self::assertEquals(1, $sheet->getCell('A1')->getXfIndex()); - $this->object->getActiveSheet()->getCell('A1')->getStyle()->getFont()->setBold(true); - self::assertCount(2, $this->object->getCellXfCollection()); - $sheet3 = $this->object->addExternalSheet($sheet); - self::assertCount(6, $this->object->getCellXfCollection()); + $spreadsheet->getActiveSheet()->getCell('A1')->getStyle()->getFont()->setBold(true); + self::assertCount(2, $spreadsheet->getCellXfCollection()); + $sheet3 = $spreadsheet->addExternalSheet($sheet); + self::assertCount(6, $spreadsheet->getCellXfCollection()); self::assertEquals('someSheet19', $sheet3->getTitle()); self::assertEquals(1, $sheet3->getCell('A1')->getValue()); self::assertTrue($sheet3->getCell('A1')->getStyle()->getFont()->getBold()); @@ -181,17 +210,17 @@ class SpreadsheetTest extends TestCase public function testAddExternalDuplicateName(): void { - $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); - $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $this->expectException(ssException::class); + $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->createSheet()->setTitle('someSheet1'); $sheet->getCell('A1')->setValue(1); $sheet->getCell('A1')->getStyle()->getFont()->setBold(true); - $this->object->addExternalSheet($sheet); + $spreadsheet->addExternalSheet($sheet); } public function testAddExternalColumnDimensionStyles(): void { - $spreadsheet1 = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $spreadsheet1 = new Spreadsheet(); $sheet1 = $spreadsheet1->createSheet()->setTitle('sheetWithColumnDimension'); $sheet1->getCell('A1')->setValue(1); $sheet1->getCell('A1')->getStyle()->getFont()->setItalic(true); @@ -200,7 +229,7 @@ class SpreadsheetTest extends TestCase self::assertEquals(1, $index); self::assertCount(2, $spreadsheet1->getCellXfCollection()); - $spreadsheet2 = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $spreadsheet2 = new Spreadsheet(); $sheet2 = $spreadsheet2->createSheet()->setTitle('sheetWithTwoStyles'); $sheet2->getCell('A1')->setValue(1); $sheet2->getCell('A1')->getStyle()->getFont()->setBold(true); @@ -220,7 +249,7 @@ class SpreadsheetTest extends TestCase public function testAddExternalRowDimensionStyles(): void { - $spreadsheet1 = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $spreadsheet1 = new Spreadsheet(); $sheet1 = $spreadsheet1->createSheet()->setTitle('sheetWithColumnDimension'); $sheet1->getCell('A1')->setValue(1); $sheet1->getCell('A1')->getStyle()->getFont()->setItalic(true); @@ -229,7 +258,7 @@ class SpreadsheetTest extends TestCase self::assertEquals(1, $index); self::assertCount(2, $spreadsheet1->getCellXfCollection()); - $spreadsheet2 = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $spreadsheet2 = new Spreadsheet(); $sheet2 = $spreadsheet2->createSheet()->setTitle('sheetWithTwoStyles'); $sheet2->getCell('A1')->setValue(1); $sheet2->getCell('A1')->getStyle()->getFont()->setBold(true); diff --git a/tests/PhpSpreadsheetTests/Style/AlignmentMiddleTest.php b/tests/PhpSpreadsheetTests/Style/AlignmentMiddleTest.php new file mode 100644 index 00000000..1d260a6e --- /dev/null +++ b/tests/PhpSpreadsheetTests/Style/AlignmentMiddleTest.php @@ -0,0 +1,87 @@ +spreadsheet !== null) { + $this->spreadsheet->disconnectWorksheets(); + $this->spreadsheet = null; + } + if ($this->outputFileName !== '') { + unlink($this->outputFileName); + $this->outputFileName = ''; + } + } + + public function testCenterWriteHtml(): void + { + // Html Writer changes vertical align center to middle + $this->spreadsheet = new Spreadsheet(); + $sheet = $this->spreadsheet->getActiveSheet(); + $sheet->getCell('A1')->setValue('Cell1'); + $sheet->getStyle('A1') + ->getAlignment() + ->setVertical(Alignment::VERTICAL_CENTER); + $writer = new HTML($this->spreadsheet); + $html = $writer->generateHtmlAll(); + self::assertStringContainsString('vertical-align:middle', $html); + self::assertStringNotContainsString('vertical-align:center', $html); + } + + public function testCenterWriteXlsx(): void + { + // Xlsx Writer uses vertical align center unchanged + $this->spreadsheet = new Spreadsheet(); + $sheet = $this->spreadsheet->getActiveSheet(); + $sheet->getCell('A1')->setValue('Cell1'); + $sheet->getStyle('A1') + ->getAlignment() + ->setVertical(Alignment::VERTICAL_CENTER); + $this->outputFileName = File::temporaryFilename(); + $writer = new Xlsx($this->spreadsheet); + $writer->save($this->outputFileName); + $zip = new ZipArchive(); + $zip->open($this->outputFileName); + $html = $zip->getFromName('xl/styles.xml'); + $zip->close(); + self::assertStringContainsString('vertical="center"', $html); + self::assertStringNotContainsString('vertical="middle"', $html); + } + + public function testCenterWriteXlsx2(): void + { + // Xlsx Writer changes vertical align middle to center + $this->spreadsheet = new Spreadsheet(); + $sheet = $this->spreadsheet->getActiveSheet(); + $sheet->getCell('A1')->setValue('Cell1'); + $sheet->getStyle('A1') + ->getAlignment() + ->setVertical('middle'); + $this->outputFileName = File::temporaryFilename(); + $writer = new Xlsx($this->spreadsheet); + $writer->save($this->outputFileName); + $zip = new ZipArchive(); + $zip->open($this->outputFileName); + $html = $zip->getFromName('xl/styles.xml'); + $zip->close(); + self::assertStringContainsString('vertical="center"', $html); + self::assertStringNotContainsString('vertical="middle"', $html); + } +} diff --git a/tests/PhpSpreadsheetTests/Style/AlignmentTest.php b/tests/PhpSpreadsheetTests/Style/AlignmentTest.php index d10e3211..6f50ce22 100644 --- a/tests/PhpSpreadsheetTests/Style/AlignmentTest.php +++ b/tests/PhpSpreadsheetTests/Style/AlignmentTest.php @@ -9,10 +9,21 @@ use PHPUnit\Framework\TestCase; class AlignmentTest extends TestCase { + /** @var ?Spreadsheet */ + private $spreadsheet; + + protected function tearDown(): void + { + if ($this->spreadsheet !== null) { + $this->spreadsheet->disconnectWorksheets(); + $this->spreadsheet = null; + } + } + public function testAlignment(): void { - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); + $this->spreadsheet = new Spreadsheet(); + $sheet = $this->spreadsheet->getActiveSheet(); $cell1 = $sheet->getCell('A1'); $cell1->setValue('Cell1'); $cell1->getStyle()->getAlignment()->setTextRotation(45); @@ -31,8 +42,8 @@ class AlignmentTest extends TestCase public function testRotationTooHigh(): void { $this->expectException(PhpSpreadsheetException::class); - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); + $this->spreadsheet = new Spreadsheet(); + $sheet = $this->spreadsheet->getActiveSheet(); $cell1 = $sheet->getCell('A1'); $cell1->setValue('Cell1'); $cell1->getStyle()->getAlignment()->setTextRotation(91); @@ -42,8 +53,8 @@ class AlignmentTest extends TestCase public function testRotationTooLow(): void { $this->expectException(PhpSpreadsheetException::class); - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); + $this->spreadsheet = new Spreadsheet(); + $sheet = $this->spreadsheet->getActiveSheet(); $cell1 = $sheet->getCell('A1'); $cell1->setValue('Cell1'); $cell1->getStyle()->getAlignment()->setTextRotation(-91); @@ -52,8 +63,8 @@ class AlignmentTest extends TestCase public function testHorizontal(): void { - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); + $this->spreadsheet = new Spreadsheet(); + $sheet = $this->spreadsheet->getActiveSheet(); $cell1 = $sheet->getCell('A1'); $cell1->setValue('X'); $cell1->getStyle()->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT)->setIndent(1); @@ -74,8 +85,8 @@ class AlignmentTest extends TestCase public function testReadOrder(): void { - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); + $this->spreadsheet = new Spreadsheet(); + $sheet = $this->spreadsheet->getActiveSheet(); $cell1 = $sheet->getCell('A1'); $cell1->setValue('ABC'); $cell1->getStyle()->getAlignment()->setReadOrder(0); diff --git a/tests/PhpSpreadsheetTests/Style/ConditionalFormatting/CellMatcherTest.php b/tests/PhpSpreadsheetTests/Style/ConditionalFormatting/CellMatcherTest.php index 3c96403b..decbad5b 100644 --- a/tests/PhpSpreadsheetTests/Style/ConditionalFormatting/CellMatcherTest.php +++ b/tests/PhpSpreadsheetTests/Style/ConditionalFormatting/CellMatcherTest.php @@ -2,23 +2,45 @@ namespace PhpOffice\PhpSpreadsheetTests\Style\ConditionalFormatting; +use PhpOffice\PhpSpreadsheet\Cell\Cell; +use PhpOffice\PhpSpreadsheet\Exception as ssException; use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellMatcher; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PHPUnit\Framework\TestCase; class CellMatcherTest extends TestCase { /** - * @var Spreadsheet + * @var ?Spreadsheet */ protected $spreadsheet; - protected function setUp(): void + protected function loadSpreadsheet(): Spreadsheet { $filename = 'tests/data/Style/ConditionalFormatting/CellMatcher.xlsx'; $reader = IOFactory::createReader('Xlsx'); - $this->spreadsheet = $reader->load($filename); + + return $reader->load($filename); + } + + protected function tearDown(): void + { + if ($this->spreadsheet !== null) { + $this->spreadsheet->disconnectWorksheets(); + $this->spreadsheet = null; + } + } + + private function confirmString(Worksheet $worksheet, Cell $cell, string $cellAddress): string + { + $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()) ?? ''; + if ($cfRange === '') { + self::fail("{$cellAddress} is not in a Conditional Format range"); + } + + return $cfRange; } /** @@ -26,16 +48,11 @@ class CellMatcherTest extends TestCase */ public function testBasicCellIsComparison(string $sheetname, string $cellAddress, array $expectedMatches): void { - $worksheet = $this->spreadsheet->getSheetByName($sheetname); - if ($worksheet === null) { - self::markTestSkipped("{$sheetname} not found in test workbook"); - } + $this->spreadsheet = $this->loadSpreadsheet(); + $worksheet = $this->spreadsheet->getSheetByNameOrThrow($sheetname); $cell = $worksheet->getCell($cellAddress); - $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); - if ($cfRange === null) { - self::markTestSkipped("{$cellAddress} is not in a Conditional Format range"); - } + $cfRange = $this->confirmString($worksheet, $cell, $cellAddress); $cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate()); $matcher = new CellMatcher($cell, $cfRange); @@ -77,21 +94,35 @@ class CellMatcherTest extends TestCase ]; } + public function testNotInRange(): void + { + $this->spreadsheet = $this->loadSpreadsheet(); + $sheetname = 'cellIs Comparison'; + $worksheet = $this->spreadsheet->getSheetByNameOrThrow($sheetname); + $cell = $worksheet->getCell('J20'); + + $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); + self::assertNull($cfRange); + } + + public function testUnknownSheet(): void + { + $this->expectException(ssException::class); + $this->spreadsheet = $this->loadSpreadsheet(); + $sheetname = 'cellIs Comparisonxxx'; + $this->spreadsheet->getSheetByNameOrThrow($sheetname); + } + /** * @dataProvider rangeCellIsComparisonDataProvider */ public function testRangeCellIsComparison(string $sheetname, string $cellAddress, bool $expectedMatch): void { - $worksheet = $this->spreadsheet->getSheetByName($sheetname); - if ($worksheet === null) { - self::markTestSkipped("{$sheetname} not found in test workbook"); - } + $this->spreadsheet = $this->loadSpreadsheet(); + $worksheet = $this->spreadsheet->getSheetByNameOrThrow($sheetname); $cell = $worksheet->getCell($cellAddress); - $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); - if ($cfRange === null) { - self::markTestSkipped("{$cellAddress} is not in a Conditional Format range"); - } + $cfRange = $this->confirmString($worksheet, $cell, $cellAddress); $cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate()); $matcher = new CellMatcher($cell, $cfRange); @@ -128,16 +159,11 @@ class CellMatcherTest extends TestCase */ public function testCellIsMultipleExpression(string $sheetname, string $cellAddress, array $expectedMatches): void { - $worksheet = $this->spreadsheet->getSheetByName($sheetname); - if ($worksheet === null) { - self::markTestSkipped("{$sheetname} not found in test workbook"); - } + $this->spreadsheet = $this->loadSpreadsheet(); + $worksheet = $this->spreadsheet->getSheetByNameOrThrow($sheetname); $cell = $worksheet->getCell($cellAddress); - $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); - if ($cfRange === null) { - self::markTestSkipped("{$cellAddress} is not in a Conditional Format range"); - } + $cfRange = $this->confirmString($worksheet, $cell, $cellAddress); $cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate()); $matcher = new CellMatcher($cell, $cfRange); @@ -167,16 +193,11 @@ class CellMatcherTest extends TestCase */ public function testCellIsExpression(string $sheetname, string $cellAddress, bool $expectedMatch): void { - $worksheet = $this->spreadsheet->getSheetByName($sheetname); - if ($worksheet === null) { - self::markTestSkipped("{$sheetname} not found in test workbook"); - } + $this->spreadsheet = $this->loadSpreadsheet(); + $worksheet = $this->spreadsheet->getSheetByNameOrThrow($sheetname); $cell = $worksheet->getCell($cellAddress); - $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); - if ($cfRange === null) { - self::markTestSkipped("{$cellAddress} is not in a Conditional Format range"); - } + $cfRange = $this->confirmString($worksheet, $cell, $cellAddress); $cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate()); $matcher = new CellMatcher($cell, $cfRange); @@ -216,16 +237,11 @@ class CellMatcherTest extends TestCase */ public function testTextExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void { - $worksheet = $this->spreadsheet->getSheetByName($sheetname); - if ($worksheet === null) { - self::markTestSkipped("{$sheetname} not found in test workbook"); - } + $this->spreadsheet = $this->loadSpreadsheet(); + $worksheet = $this->spreadsheet->getSheetByNameOrThrow($sheetname); $cell = $worksheet->getCell($cellAddress); - $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); - if ($cfRange === null) { - self::markTestSkipped("{$cellAddress} is not in a Conditional Format range"); - } + $cfRange = $this->confirmString($worksheet, $cell, $cellAddress); $cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate()); $matcher = new CellMatcher($cell, $cfRange); @@ -329,16 +345,11 @@ class CellMatcherTest extends TestCase */ public function testBlankExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void { - $worksheet = $this->spreadsheet->getSheetByName($sheetname); - if ($worksheet === null) { - self::markTestSkipped("{$sheetname} not found in test workbook"); - } + $this->spreadsheet = $this->loadSpreadsheet(); + $worksheet = $this->spreadsheet->getSheetByNameOrThrow($sheetname); $cell = $worksheet->getCell($cellAddress); - $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); - if ($cfRange === null) { - self::markTestSkipped("{$cellAddress} is not in a Conditional Format range"); - } + $cfRange = $this->confirmString($worksheet, $cell, $cellAddress); $cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate()); $matcher = new CellMatcher($cell, $cfRange); @@ -365,16 +376,11 @@ class CellMatcherTest extends TestCase */ public function testErrorExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void { - $worksheet = $this->spreadsheet->getSheetByName($sheetname); - if ($worksheet === null) { - self::markTestSkipped("{$sheetname} not found in test workbook"); - } + $this->spreadsheet = $this->loadSpreadsheet(); + $worksheet = $this->spreadsheet->getSheetByNameOrThrow($sheetname); $cell = $worksheet->getCell($cellAddress); - $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); - if ($cfRange === null) { - self::markTestSkipped("{$cellAddress} is not in a Conditional Format range"); - } + $cfRange = $this->confirmString($worksheet, $cell, $cellAddress); $cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate()); $matcher = new CellMatcher($cell, $cfRange); @@ -400,16 +406,11 @@ class CellMatcherTest extends TestCase */ public function testDateOccurringExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void { - $worksheet = $this->spreadsheet->getSheetByName($sheetname); - if ($worksheet === null) { - self::markTestSkipped("{$sheetname} not found in test workbook"); - } + $this->spreadsheet = $this->loadSpreadsheet(); + $worksheet = $this->spreadsheet->getSheetByNameOrThrow($sheetname); $cell = $worksheet->getCell($cellAddress); - $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); - if ($cfRange === null) { - self::markTestSkipped("{$cellAddress} is not in a Conditional Format range"); - } + $cfRange = $this->confirmString($worksheet, $cell, $cellAddress); $cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate()); $matcher = new CellMatcher($cell, $cfRange); @@ -447,16 +448,11 @@ class CellMatcherTest extends TestCase */ public function testDuplicatesExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void { - $worksheet = $this->spreadsheet->getSheetByName($sheetname); - if ($worksheet === null) { - self::markTestSkipped("{$sheetname} not found in test workbook"); - } + $this->spreadsheet = $this->loadSpreadsheet(); + $worksheet = $this->spreadsheet->getSheetByNameOrThrow($sheetname); $cell = $worksheet->getCell($cellAddress); - $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); - if ($cfRange === null) { - self::markTestSkipped("{$cellAddress} is not in a Conditional Format range"); - } + $cfRange = $this->confirmString($worksheet, $cell, $cellAddress); $cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate()); $matcher = new CellMatcher($cell, $cfRange); @@ -486,16 +482,11 @@ class CellMatcherTest extends TestCase */ public function testCrossWorksheetExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void { - $worksheet = $this->spreadsheet->getSheetByName($sheetname); - if ($worksheet === null) { - self::markTestSkipped("{$sheetname} not found in test workbook"); - } + $this->spreadsheet = $this->loadSpreadsheet(); + $worksheet = $this->spreadsheet->getSheetByNameOrThrow($sheetname); $cell = $worksheet->getCell($cellAddress); - $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); - if ($cfRange === null) { - self::markTestSkipped("{$cellAddress} is not in a Conditional Format range"); - } + $cfRange = $this->confirmString($worksheet, $cell, $cellAddress); $cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate()); $matcher = new CellMatcher($cell, $cfRange); diff --git a/tests/PhpSpreadsheetTests/Style/ConditionalFormatting/Wizard/DateValueWizardTest.php b/tests/PhpSpreadsheetTests/Style/ConditionalFormatting/Wizard/DateValueWizardTest.php index 06d76bb2..4bbda920 100644 --- a/tests/PhpSpreadsheetTests/Style/ConditionalFormatting/Wizard/DateValueWizardTest.php +++ b/tests/PhpSpreadsheetTests/Style/ConditionalFormatting/Wizard/DateValueWizardTest.php @@ -1,6 +1,6 @@ $expectedWizard + * @psalm-param class-string $expectedWizard */ - public function testBasicWizardFactory(string $ruleType, $expectedWizard): void + public function testBasicWizardFactory(string $ruleType, string $expectedWizard): void { $wizard = $this->wizardFactory->newRule($ruleType); self::assertInstanceOf($expectedWizard, $wizard); @@ -54,10 +54,7 @@ class WizardFactoryTest extends TestCase $filename = 'tests/data/Style/ConditionalFormatting/CellMatcher.xlsx'; $reader = IOFactory::createReader('Xlsx'); $spreadsheet = $reader->load($filename); - $worksheet = $spreadsheet->getSheetByName($sheetName); - if ($worksheet === null) { - self::markTestSkipped("{$sheetName} not found in test workbook"); - } + $worksheet = $spreadsheet->getSheetByNameOrThrow($sheetName); $cell = $worksheet->getCell($cellAddress); $cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); diff --git a/tests/PhpSpreadsheetTests/Style/FontTest.php b/tests/PhpSpreadsheetTests/Style/FontTest.php index 02814afa..6cd4d950 100644 --- a/tests/PhpSpreadsheetTests/Style/FontTest.php +++ b/tests/PhpSpreadsheetTests/Style/FontTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Style; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Font; use PHPUnit\Framework\TestCase; class FontTest extends TestCase @@ -88,4 +89,20 @@ class FontTest extends TestCase self::assertEquals('Calibri', $font->getName(), 'Null string changed to default'); $spreadsheet->disconnectWorksheets(); } + + public function testUnderlineHash(): void + { + $font1 = new Font(); + $font2 = new Font(); + $font2aHash = $font2->getHashCode(); + self::assertSame($font1->getHashCode(), $font2aHash); + $font2->setUnderlineColor( + [ + 'type' => 'srgbClr', + 'value' => 'FF0000', + ] + ); + $font2bHash = $font2->getHashCode(); + self::assertNotEquals($font1->getHashCode(), $font2bHash); + } } diff --git a/tests/PhpSpreadsheetTests/Style/NumberFormatTest.php b/tests/PhpSpreadsheetTests/Style/NumberFormatTest.php index e386b292..f09c34d7 100644 --- a/tests/PhpSpreadsheetTests/Style/NumberFormatTest.php +++ b/tests/PhpSpreadsheetTests/Style/NumberFormatTest.php @@ -47,7 +47,7 @@ class NumberFormatTest extends TestCase public function testFormatValueWithMask($expectedResult, ...$args): void { $result = NumberFormat::toFormattedString(...$args); - self::assertEquals($expectedResult, $result); + self::assertSame($expectedResult, $result); } public function providerNumberFormat(): array diff --git a/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIteratorTest.php b/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIteratorTest.php index 1a7ff675..02b96426 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIteratorTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIteratorTest.php @@ -40,6 +40,7 @@ class ColumnCellIteratorTest extends TestCase $values = []; foreach ($iterator as $key => $ColumnCell) { self::assertNotNull($ColumnCell); + /** @scrutinizer ignore-call */ $values[] = $ColumnCell->getValue(); self::assertEquals($ColumnCellIndexResult++, $key); self::assertInstanceOf(Cell::class, $ColumnCell); @@ -60,6 +61,7 @@ class ColumnCellIteratorTest extends TestCase $values = []; foreach ($iterator as $key => $ColumnCell) { self::assertNotNull($ColumnCell); + /** @scrutinizer ignore-call */ $values[] = $ColumnCell->getValue(); self::assertEquals($ColumnCellIndexResult++, $key); self::assertInstanceOf(Cell::class, $ColumnCell); @@ -81,6 +83,7 @@ class ColumnCellIteratorTest extends TestCase while ($iterator->valid()) { $current = $iterator->current(); self::assertNotNull($current); + /** @scrutinizer ignore-call */ $cell = $current->getCoordinate(); $values[] = $sheet->getCell($cell)->getValue(); $iterator->prev(); diff --git a/tests/PhpSpreadsheetTests/Worksheet/MergeBehaviourTest.php b/tests/PhpSpreadsheetTests/Worksheet/MergeBehaviourTest.php new file mode 100644 index 00000000..68c9d87a --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/MergeBehaviourTest.php @@ -0,0 +1,166 @@ +getActiveSheet(); + $worksheet->fromArray($this->testDataRaw, null, 'A1', true); + $worksheet->mergeCells($mergeRange); + + $mergeResult = $worksheet->toArray(null, true, true, false); + self::assertSame($expectedResult, $mergeResult); + } + + public function testMergeCellsDefaultBehaviourFormatted(): void + { + $expectedResult = [ + ['1960-12-19', null], + ]; + + $mergeRange = 'A1:B1'; + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray($this->testDataFormatted, null, 'A1', true); + $worksheet->getStyle($mergeRange)->getNumberFormat()->setFormatCode('yyyy-mm-dd'); + $worksheet->mergeCells($mergeRange); + + $mergeResult = $worksheet->toArray(null, true, true, false); + self::assertSame($expectedResult, $mergeResult); + } + + public function testMergeCellsHideBehaviour(): void + { + $expectedResult = [ + [1.1, 2.2, 3.3], + [4.4, 5.5, 9.9], + [5.5, 7.7, 13.2], + ]; + + $mergeRange = 'A1:C3'; + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray($this->testDataRaw, null, 'A1', true); + $worksheet->mergeCells($mergeRange, Worksheet::MERGE_CELL_CONTENT_HIDE); + + $mergeResult = $worksheet->toArray(null, true, true, false); + self::assertSame($expectedResult, $mergeResult); + } + + public function testMergeCellsHideBehaviourFormatted(): void + { + $expectedResult = [ + ['1960-12-19', '2022-09-15'], + ]; + + $mergeRange = 'A1:B1'; + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray($this->testDataFormatted, null, 'A1', true); + $worksheet->getStyle($mergeRange)->getNumberFormat()->setFormatCode('yyyy-mm-dd'); + $worksheet->mergeCells($mergeRange, Worksheet::MERGE_CELL_CONTENT_HIDE); + + $mergeResult = $worksheet->toArray(null, true, true, false); + self::assertSame($expectedResult, $mergeResult); + } + + /** + * @dataProvider mergeCellsMergeBehaviourProvider + */ + public function testMergeCellsMergeBehaviour(array $testData, string $mergeRange, array $expectedResult): void + { + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray($testData, null, 'A1', true); + // Force a precalculation to populate the calculation cache, so that we can verify that it is being cleared + $worksheet->toArray(); + $worksheet->mergeCells($mergeRange, Worksheet::MERGE_CELL_CONTENT_MERGE); + + $mergeResult = $worksheet->toArray(null, true, true, false); + self::assertSame($expectedResult, $mergeResult); + } + + public function mergeCellsMergeBehaviourProvider(): array + { + return [ + 'With Calculated Values' => [ + $this->testDataRaw, + 'A1:C3', + [ + ['1.1 2.2 1.1 4.4 5.5 0 1.1 0 0', null, null], + [null, null, null], + [null, null, null], + ], + ], + 'With Empty Cells' => [ + [ + [1, '', 2], + [null, 3, null], + [4, null, 5], + ], + 'A1:C3', + [ + ['1 2 3 4 5', null, null], + [null, null, null], + [null, null, null], + ], + ], + [ + [ + [12, '=5+1', '=A1/A2'], + ], + 'A1:C1', + [ + ['12 6 #DIV/0!', null, null], + ], + ], + ]; + } + + public function testMergeCellsMergeBehaviourFormatted(): void + { + $expectedResult = [ + ['1960-12-19 2022-09-15', null], + ]; + + $mergeRange = 'A1:B1'; + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray($this->testDataFormatted, null, 'A1', true); + $worksheet->getStyle($mergeRange)->getNumberFormat()->setFormatCode('yyyy-mm-dd'); + $worksheet->mergeCells($mergeRange, Worksheet::MERGE_CELL_CONTENT_MERGE); + + $mergeResult = $worksheet->toArray(null, true, true, false); + self::assertSame($expectedResult, $mergeResult); + } +} diff --git a/tests/PhpSpreadsheetTests/Worksheet/RowCellIteratorTest.php b/tests/PhpSpreadsheetTests/Worksheet/RowCellIteratorTest.php index b0606804..9afda3cb 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/RowCellIteratorTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/RowCellIteratorTest.php @@ -40,6 +40,7 @@ class RowCellIteratorTest extends TestCase $values = []; foreach ($iterator as $key => $RowCell) { self::assertNotNull($RowCell); + /** @scrutinizer ignore-call */ $values[] = $RowCell->getValue(); self::assertEquals($RowCellIndexResult++, $key); self::assertInstanceOf(Cell::class, $RowCell); @@ -59,6 +60,7 @@ class RowCellIteratorTest extends TestCase $values = []; foreach ($iterator as $key => $RowCell) { self::assertNotNull($RowCell); + /** @scrutinizer ignore-call */ $values[] = $RowCell->getValue(); self::assertEquals($RowCellIndexResult++, $key); self::assertInstanceOf(Cell::class, $RowCell); @@ -80,6 +82,7 @@ class RowCellIteratorTest extends TestCase while ($iterator->valid()) { $current = $iterator->current(); self::assertNotNull($current); + /** @scrutinizer ignore-call */ $cell = $current->getCoordinate(); $values[] = $sheet->getCell($cell)->getValue(); $iterator->prev(); diff --git a/tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php b/tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php index b367583b..506e753c 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php @@ -29,6 +29,15 @@ class WorksheetNamedRangesTest extends TestCase self::assertTrue($cellExists); } + public function testCellExistsUtf8(): void + { + $namedCell = 'Χαιρετισμός'; + + $worksheet = $this->spreadsheet->getActiveSheet(); + $cellExists = $worksheet->cellExists($namedCell); + self::assertTrue($cellExists); + } + public function testCellNotExists(): void { $namedCell = 'GOODBYE'; @@ -67,6 +76,15 @@ class WorksheetNamedRangesTest extends TestCase self::assertSame('Hello', $cell->getValue()); } + public function testGetCellUtf8(): void + { + $namedCell = 'Χαιρετισμός'; + + $worksheet = $this->spreadsheet->getActiveSheet(); + $cell = $worksheet->getCell($namedCell); + self::assertSame('नमस्ते', $cell->getValue()); + } + public function testGetCellNotExists(): void { $namedCell = 'GOODBYE'; diff --git a/tests/PhpSpreadsheetTests/Worksheet/WorksheetTest.php b/tests/PhpSpreadsheetTests/Worksheet/WorksheetTest.php index 17de5c32..30da1d75 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/WorksheetTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/WorksheetTest.php @@ -31,6 +31,7 @@ class WorksheetTest extends TestCase /** * @param string $title * @param string $expectMessage + * * @dataProvider setTitleInvalidProvider */ public function testSetTitleInvalid($title, $expectMessage): void @@ -89,6 +90,7 @@ class WorksheetTest extends TestCase /** * @param string $codeName * @param string $expectMessage + * * @dataProvider setCodeNameInvalidProvider */ public function testSetCodeNameInvalid($codeName, $expectMessage): void @@ -153,6 +155,7 @@ class WorksheetTest extends TestCase * @param string $expectTitle * @param string $expectCell * @param string $expectCell2 + * * @dataProvider extractSheetTitleProvider */ public function testExtractSheetTitle($range, $expectTitle, $expectCell, $expectCell2): void diff --git a/tests/PhpSpreadsheetTests/Writer/Dompdf/PaperSizeArrayTest.php b/tests/PhpSpreadsheetTests/Writer/Dompdf/PaperSizeArrayTest.php new file mode 100644 index 00000000..815d7e3d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Dompdf/PaperSizeArrayTest.php @@ -0,0 +1,65 @@ +outfile !== '') { + unlink($this->outfile); + $this->outfile = ''; + } + } + + public function testPaperSizeArray(): void + { + // Issue 1713 - array in PhpSpreadsheet is 2 elements, + // but in Dompdf it is 4 elements, first 2 are zero. + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + // TABLOID is a 2-element array in Writer/Pdf.php $paperSizes + $size = PageSetup::PAPERSIZE_TABLOID; + $sheet->getPageSetup()->setPaperSize($size); + $sheet->setPrintGridlines(true); + $sheet->getStyle('A7')->getAlignment()->setTextRotation(90); + $sheet->setCellValue('A7', 'Lorem Ipsum'); + $writer = new Dompdf($spreadsheet); + $this->outfile = File::temporaryFilename(); + $writer->save($this->outfile); + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); + $contents = file_get_contents($this->outfile); + self::assertNotFalse($contents); + self::assertStringContainsString('/MediaBox [0.000 0.000 792.000 1224.000]', $contents); + } + + public function testPaperSizeNotArray(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + // LETTER is a string in Writer/Pdf.php $paperSizes + $size = PageSetup::PAPERSIZE_LETTER; + $sheet->getPageSetup()->setPaperSize($size); + $sheet->setPrintGridlines(true); + $sheet->getStyle('A7')->getAlignment()->setTextRotation(90); + $sheet->setCellValue('A7', 'Lorem Ipsum'); + $writer = new Dompdf($spreadsheet); + $this->outfile = File::temporaryFilename(); + $writer->save($this->outfile); + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); + $contents = file_get_contents($this->outfile); + self::assertNotFalse($contents); + self::assertStringContainsString('/MediaBox [0.000 0.000 612.000 792.000]', $contents); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Dompdf/TextRotationTest.php b/tests/PhpSpreadsheetTests/Writer/Dompdf/TextRotationTest.php new file mode 100644 index 00000000..2aa9d5ed --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Dompdf/TextRotationTest.php @@ -0,0 +1,24 @@ +getActiveSheet(); + $sheet->setPrintGridlines(true); + $sheet->getStyle('A7')->getAlignment()->setTextRotation(90); + $sheet->setCellValue('A7', 'Lorem Ipsum'); + $writer = new Dompdf($spreadsheet); + $html = $writer->generateHtmlAll(); + self::assertStringContainsString(' transform:rotate(90deg);', $html); + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Html/TextRotationTest.php b/tests/PhpSpreadsheetTests/Writer/Html/TextRotationTest.php new file mode 100644 index 00000000..da928457 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Html/TextRotationTest.php @@ -0,0 +1,24 @@ +getActiveSheet(); + $sheet->setPrintGridlines(true); + $sheet->getStyle('A7')->getAlignment()->setTextRotation(90); + $sheet->setCellValue('A7', 'Lorem Ipsum'); + $writer = new Html($spreadsheet); + $html = $writer->generateHtmlAll(); + self::assertStringContainsString(' transform:rotate(90deg);', $html); + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Html/VisibilityTest.php b/tests/PhpSpreadsheetTests/Writer/Html/VisibilityTest.php index c5d4da68..1c1ffb06 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/VisibilityTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/VisibilityTest.php @@ -99,11 +99,11 @@ class VisibilityTest extends Functional\AbstractFunctional self::assertEquals(1, $rowsrch); $rowsrch = preg_match('/^\\s*table[.]sheet0 tr[.]row1 [{] height:25pt [}]\\s*$/m', $html); self::assertEquals(1, $rowsrch); - $rowsrch = preg_match('/^\\s*td[.]style1 [{].*text-decoration:line-through;.*[}]\\s*$/m', $html); + $rowsrch = preg_match('/^\\s*td[.]style1, th[.]style1 [{].*text-decoration:line-through;.*[}]\\s*$/m', $html); self::assertEquals(1, $rowsrch); - $rowsrch = preg_match('/^\\s*td[.]style2 [{].*text-decoration:underline line-through;.*[}]\\s*$/m', $html); + $rowsrch = preg_match('/^\\s*td[.]style2, th[.]style2 [{].*text-decoration:underline line-through;.*[}]\\s*$/m', $html); self::assertEquals(1, $rowsrch); - $rowsrch = preg_match('/^\\s*td[.]style3 [{].*text-decoration:underline;.*[}]\\s*$/m', $html); + $rowsrch = preg_match('/^\\s*td[.]style3, th[.]style3 [{].*text-decoration:underline;.*[}]\\s*$/m', $html); self::assertEquals(1, $rowsrch); $this->writeAndReload($spreadsheet, 'Html'); diff --git a/tests/PhpSpreadsheetTests/Writer/Mpdf/ImageCopyPdfTest.php b/tests/PhpSpreadsheetTests/Writer/Mpdf/ImageCopyPdfTest.php index 111848da..5d31bc01 100644 --- a/tests/PhpSpreadsheetTests/Writer/Mpdf/ImageCopyPdfTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Mpdf/ImageCopyPdfTest.php @@ -1,6 +1,6 @@ getActiveSheet(); + $sheet->setPrintGridlines(true); + $sheet->getStyle('A7')->getAlignment()->setTextRotation(90); + $sheet->setCellValue('A7', 'Lorem Ipsum'); + $writer = new Mpdf($spreadsheet); + $html = $writer->generateHtmlAll(); + self::assertStringContainsString(' text-rotate:90;', $html); + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/ParserTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/ParserTest.php new file mode 100644 index 00000000..38fd29d3 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xls/ParserTest.php @@ -0,0 +1,56 @@ +spreadsheet !== null) { + $this->spreadsheet->disconnectWorksheets(); + $this->spreadsheet = null; + } + } + + public function testNonArray(): void + { + $this->expectException(WriterException::class); + $this->expectExceptionMessage('Unexpected non-array'); + $this->spreadsheet = new Spreadsheet(); + $parser = new Parser($this->spreadsheet); + $parser->toReversePolish(); + } + + public function testMissingIndex(): void + { + $this->expectException(WriterException::class); + $this->expectExceptionMessage('Unexpected non-array'); + $this->spreadsheet = new Spreadsheet(); + $parser = new Parser($this->spreadsheet); + $parser->toReversePolish(['left' => 0]); + } + + public function testParseError(): void + { + $this->expectException(WriterException::class); + $this->expectExceptionMessage('Unknown token +'); + $this->spreadsheet = new Spreadsheet(); + $parser = new Parser($this->spreadsheet); + $parser->toReversePolish(['left' => 1, 'right' => 2, 'value' => '+']); + } + + public function testGoodParse(): void + { + $this->spreadsheet = new Spreadsheet(); + $parser = new Parser($this->spreadsheet); + self::assertSame('1e01001e02001e0300', bin2hex($parser->toReversePolish(['left' => 1, 'right' => 2, 'value' => 3]))); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/VisibilityTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/VisibilityTest.php index 7de39328..8c4aa1b9 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xls/VisibilityTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xls/VisibilityTest.php @@ -79,7 +79,7 @@ class VisibilityTest extends AbstractFunctional $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xls'); foreach ($visibleSheets as $sheetName => $visibility) { - $reloadedWorksheet = $reloadedSpreadsheet->getSheetByName($sheetName) ?? new Worksheet(); + $reloadedWorksheet = $reloadedSpreadsheet->getSheetByNameOrThrow($sheetName); self::assertSame($visibility, $reloadedWorksheet->getSheetState()); } } diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php index f9535714..eba7288f 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php @@ -77,19 +77,15 @@ class UnparsedDataCloneTest extends TestCase $reader1 = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $spreadsheet1 = $reader1->load($resultFilename1); unlink($resultFilename1); - $sheet1c = $spreadsheet1->getSheetByName('Clone'); - self::assertNotNull($sheet1c); - $sheet1o = $spreadsheet1->getSheetByName('Original'); - self::assertNotNull($sheet1o); + $sheet1c = $spreadsheet1->getSheetByNameOrThrow('Clone'); + $sheet1o = $spreadsheet1->getSheetByNameOrThrow('Original'); $writer->save($resultFilename2); $reader2 = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $spreadsheet2 = $reader2->load($resultFilename2); unlink($resultFilename2); - $sheet2c = $spreadsheet2->getSheetByName('Clone'); - self::assertNotNull($sheet2c); - $sheet2o = $spreadsheet2->getSheetByName('Original'); - self::assertNotNull($sheet2o); + $sheet2c = $spreadsheet2->getSheetByNameOrThrow('Clone'); + $sheet2o = $spreadsheet2->getSheetByNameOrThrow('Original'); self::assertEquals($spreadsheet1->getSheetCount(), $spreadsheet2->getSheetCount()); self::assertCount(1, $sheet1c->getDrawingCollection()); diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/VisibilityTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/VisibilityTest.php index 7e1ca967..ec2534fd 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/VisibilityTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/VisibilityTest.php @@ -79,7 +79,7 @@ class VisibilityTest extends AbstractFunctional $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); foreach ($visibleSheets as $sheetName => $visibility) { - $reloadedWorksheet = $reloadedSpreadsheet->getSheetByName($sheetName) ?? new Worksheet(); + $reloadedWorksheet = $reloadedSpreadsheet->getSheetByNameOrThrow($sheetName); self::assertSame($visibility, $reloadedWorksheet->getSheetState()); } } diff --git a/tests/data/Calculation/DateTime/DATE.php b/tests/data/Calculation/DateTime/DATE.php index 72816b76..d8b4303a 100644 --- a/tests/data/Calculation/DateTime/DATE.php +++ b/tests/data/Calculation/DateTime/DATE.php @@ -1,7 +1,9 @@ ['exception'], ]; diff --git a/tests/data/Calculation/LookupRef/ADDRESS.php b/tests/data/Calculation/LookupRef/ADDRESS.php index b9e170d5..14a3cf7d 100644 --- a/tests/data/Calculation/LookupRef/ADDRESS.php +++ b/tests/data/Calculation/LookupRef/ADDRESS.php @@ -48,6 +48,22 @@ return [ false, 'EXCEL SHEET', ], + '0 instead of bool for 4th arg' => [ + "'EXCEL SHEET'!R2C3", + 2, + 3, + null, + 0, + 'EXCEL SHEET', + ], + '1 instead of bool for 4th arg' => [ + "'EXCEL SHEET'!\$C\$2", + 2, + 3, + null, + 1, + 'EXCEL SHEET', + ], [ "'EXCEL SHEET'!\$C\$2", 2, diff --git a/tests/data/Calculation/MathTrig/SUMWITHINDEXMATCH.php b/tests/data/Calculation/MathTrig/SUMWITHINDEXMATCH.php new file mode 100644 index 00000000..b62cfd30 --- /dev/null +++ b/tests/data/Calculation/MathTrig/SUMWITHINDEXMATCH.php @@ -0,0 +1,34 @@ + [ + "'s red hood", + [ + "Red riding hood's red hood", + 'hood', + ], + ], + 'END Case-sensitive Offset 2' => [ + '', + [ + "Red riding hood's red hood", + 'hood', + 2, + ], + ], + 'END Case-sensitive Offset -1' => [ + '', + [ + "Red riding hood's red hood", + 'hood', + -1, + ], + ], + 'END Case-sensitive Offset -2' => [ + "'s red hood", + [ + "Red riding hood's red hood", + 'hood', + -2, + ], + ], + 'END Case-sensitive Offset 3' => [ + ExcelError::NA(), + [ + "Red riding hood's red hood", + 'hood', + 3, + ], + ], + 'END Case-sensitive Offset -3' => [ + ExcelError::NA(), + [ + "Red riding hood's red hood", + 'hood', + -3, + ], + ], + 'END Case-sensitive Offset 3 with end' => [ + '', + [ + "Red riding hood's red hood", + 'hood', + 3, + 0, + 1, + ], + ], + 'END Case-sensitive Offset -3 with end' => [ + "Red riding hood's red hood", + [ + "Red riding hood's red hood", + 'hood', + -3, + 0, + 1, + ], + ], + 'END Case-sensitive - No Match' => [ + ExcelError::NA(), + [ + "Red riding hood's red hood", + 'HOOD', + ], + ], + 'END Case-insensitive Offset 1' => [ + "'s red hood", + [ + "Red riding hood's red hood", + 'HOOD', + 1, + 1, + ], + ], + 'END Case-insensitive Offset 2' => [ + '', + [ + "Red riding hood's red hood", + 'HOOD', + 2, + 1, + ], + ], + 'END Offset 0' => [ + ExcelError::VALUE(), + [ + "Red riding hood's red hood", + 'hood', + 0, + ], + ], + 'Empty match positive' => [ + "Red riding hood's red hood", + [ + "Red riding hood's red hood", + '', + ], + ], + 'Empty match negative' => [ + '', + [ + "Red riding hood's red hood", + '', + -1, + ], + ], + 'START Case-sensitive Offset 1' => [ + ' riding hood', + [ + "Red Riding Hood's red riding hood", + 'red', + ], + ], + 'START Case-insensitive Offset 1' => [ + " Riding Hood's red riding hood", + [ + "Red Riding Hood's red riding hood", + 'red', + 1, + 1, + ], + ], + 'START Case-sensitive Offset -2' => [ + "Red Riding Hood's red riding hood", + [ + "Red Riding Hood's red riding hood", + 'red', + -2, + 0, + 1, + ], + ], + 'START Case-insensitive Offset -2' => [ + " Riding Hood's red riding hood", + [ + "Red Riding Hood's red riding hood", + 'red', + -2, + 1, + 1, + ], + ], + [ + ' riding hood', + [ + "Red Riding Hood's red riding hood", + 'red', + 1, + 0, + ], + ], + [ + " Riding Hood's red riding hood", + [ + "Red Riding Hood's red riding hood", + 'red', + 1, + 1, + ], + ], + [ + "Red Riding Hood's red riding hood", + [ + "Red Riding Hood's red riding hood", + 'red', + -2, + 0, + 1, + ], + ], + [ + " Riding Hood's red riding hood", + [ + "Red Riding Hood's red riding hood", + 'red', + -2, + 1, + 1, + ], + ], + [ + ExcelError::NA(), + [ + 'Socrates', + ' ', + 1, + 0, + 0, + ], + ], + [ + '', + [ + 'Socrates', + ' ', + 1, + 0, + 1, + ], + ], + 'Multi-delimiter Case-Insensitive Offset 1' => [ + " riding hood's red riding hood", + [ + "Little Red riding hood's red riding hood", + ['HOOD', 'RED'], + 1, + 1, + ], + ], + 'Multi-delimiter Case-Insensitive Offset 2' => [ + "'s red riding hood", + [ + "Little Red riding hood's red riding hood", + ['HOOD', 'RED'], + 2, + 1, + ], + ], + 'Multi-delimiter Case-Insensitive Offset 3' => [ + ' riding hood', + [ + "Little Red riding hood's red riding hood", + ['HOOD', 'RED'], + 3, + 1, + ], + ], + 'Multi-delimiter Case-Insensitive Offset -2' => [ + ' riding hood', + [ + "Little Red riding hood's red riding hood", + ['HOOD', 'RED'], + -2, + 1, + ], + ], +]; diff --git a/tests/data/Calculation/TextData/TEXTBEFORE.php b/tests/data/Calculation/TextData/TEXTBEFORE.php new file mode 100644 index 00000000..1929354c --- /dev/null +++ b/tests/data/Calculation/TextData/TEXTBEFORE.php @@ -0,0 +1,243 @@ + [ + 'Red riding ', + [ + "Red riding hood's red hood", + 'hood', + ], + ], + 'END Case-sensitive Offset 2' => [ + "Red riding hood's red ", + [ + "Red riding hood's red hood", + 'hood', + 2, + ], + ], + 'END Case-sensitive Offset -1' => [ + "Red riding hood's red ", + [ + "Red riding hood's red hood", + 'hood', + -1, + ], + ], + 'END Case-sensitive Offset -2' => [ + 'Red riding ', + [ + "Red riding hood's red hood", + 'hood', + -2, + ], + ], + 'END Case-sensitive Offset 3' => [ + ExcelError::NA(), + [ + "Red riding hood's red hood", + 'hood', + 3, + ], + ], + 'END Case-sensitive Offset -3' => [ + ExcelError::NA(), + [ + "Red riding hood's red hood", + 'hood', + -3, + ], + ], + 'END Case-sensitive Offset 3 with end' => [ + "Red riding hood's red hood", + [ + "Red riding hood's red hood", + 'hood', + 3, + 0, + 1, + ], + ], + 'END Case-sensitive Offset -3 with end' => [ + '', + [ + "Red riding hood's red hood", + 'hood', + -3, + 0, + 1, + ], + ], + 'END Case-sensitive - No Match' => [ + ExcelError::NA(), + [ + "Red riding hood's red hood", + 'HOOD', + ], + ], + 'END Case-insensitive Offset 1' => [ + 'Red riding ', + [ + "Red riding hood's red hood", + 'HOOD', + 1, + 1, + ], + ], + 'END Case-insensitive Offset 2' => [ + "Red riding hood's red ", + [ + "Red riding hood's red hood", + 'HOOD', + 2, + 1, + ], + ], + 'END Offset 0' => [ + ExcelError::VALUE(), + [ + "Red riding hood's red hood", + 'hood', + 0, + ], + ], + 'Empty match positive' => [ + '', + [ + "Red riding hood's red hood", + '', + ], + ], + 'Empty match negative' => [ + "Red riding hood's red hood", + [ + "Red riding hood's red hood", + '', + -1, + ], + ], + 'START Case-sensitive Offset 1' => [ + "Red Riding Hood's ", + [ + "Red Riding Hood's red riding hood", + 'red', + ], + ], + 'START Case-insensitive Offset 1' => [ + '', + [ + "Red Riding Hood's red riding hood", + 'red', + 1, + 1, + ], + ], + 'START Case-sensitive Offset -2' => [ + '', + [ + "Red Riding Hood's red riding hood", + 'red', + -2, + 0, + 1, + ], + ], + 'START Case-insensitive Offset -2' => [ + '', + [ + "Red Riding Hood's red riding hood", + 'red', + -2, + 1, + 1, + ], + ], + [ + ExcelError::NA(), + [ + 'ABACADAEA', + 'A', + 6, + 0, + 0, + ], + ], + [ + 'ABACADAEA', + [ + 'ABACADAEA', + 'A', + 6, + 0, + 1, + ], + ], + [ + ExcelError::NA(), + [ + 'Socrates', + ' ', + 1, + 0, + 0, + ], + ], + [ + 'Socrates', + [ + 'Socrates', + ' ', + 1, + 0, + 1, + ], + ], + [ + 'Immanuel', + [ + 'Immanuel Kant', + ' ', + 1, + 0, + 1, + ], + ], + 'Multi-delimiter Case-Insensitive Offset 1' => [ + 'Little ', + [ + "Little Red riding hood's red riding hood", + ['HOOD', 'RED'], + 1, + 1, + ], + ], + 'Multi-delimiter Case-Insensitive Offset 2' => [ + 'Little Red riding ', + [ + "Little Red riding hood's red riding hood", + ['HOOD', 'RED'], + 2, + 1, + ], + ], + 'Multi-delimiter Case-Insensitive Offset 3' => [ + "Little Red riding hood's ", + [ + "Little Red riding hood's red riding hood", + ['HOOD', 'RED'], + 3, + 1, + ], + ], + 'Multi-delimiter Case-Insensitive Offset -2' => [ + "Little Red riding hood's ", + [ + "Little Red riding hood's red riding hood", + ['HOOD', 'RED'], + -2, + 1, + ], + ], +]; diff --git a/tests/data/Calculation/TextData/TEXTSPLIT.php b/tests/data/Calculation/TextData/TEXTSPLIT.php new file mode 100644 index 00000000..64016ca7 --- /dev/null +++ b/tests/data/Calculation/TextData/TEXTSPLIT.php @@ -0,0 +1,107 @@ +createTextRun('Hello'); +$richText1->createText(' World'); + +$richText2 = new RichText(); +$richText2->createTextRun('Hello'); +$richText2->createText("\nWorld"); + +return [ + ['1', 1, 0], + ['1.23', 1.23, 0], + ['-123.456', -123.456, 0], + ['TRUE', true, 0], + ['FALSE', false, 0], + ['Hello World', 'Hello World', 0], + ['HelloWorld', "Hello\nWorld", 0], + ['"Hello World"', 'Hello World', 1], + ['"HelloWorld"', "Hello\nWorld", 1], + ['Hello World', $richText1, 0], + ['HelloWorld', $richText2, 0], + ['"Hello World"', $richText1, 1], + ['"HelloWorld"', $richText2, 1], +]; diff --git a/tests/data/Calculation/Translations.php b/tests/data/Calculation/Translations.php index 604c6721..0965bac1 100644 --- a/tests/data/Calculation/Translations.php +++ b/tests/data/Calculation/Translations.php @@ -80,4 +80,14 @@ return [ 'nb', '=MAX(ABS({2,-3;-4,5}), ABS{-2,3;4,-5})', ], + 'not fooled by *RC' => [ + '=3*RC(B1)', + 'fr', + '=3*RC(B1)', + ], + 'handle * for ROW' => [ + '=3*LIGNE(B1)', + 'fr', + '=3*ROW(B1)', + ], ]; diff --git a/tests/data/CellExtractAllCellReferencesInRange.php b/tests/data/CellExtractAllCellReferencesInRange.php index a2ecbdd2..192500ce 100644 --- a/tests/data/CellExtractAllCellReferencesInRange.php +++ b/tests/data/CellExtractAllCellReferencesInRange.php @@ -130,4 +130,37 @@ return [ ], 'Z2:AA3', ], + [ + [ + 'Sheet1!D3', + ], + 'Sheet1!D3', + ], + [ + [ + 'Sheet1!D3', + 'Sheet1!E3', + 'Sheet1!D4', + 'Sheet1!E4', + ], + 'Sheet1!D3:E4', + ], + [ + [ + "'Sheet 1'!D3", + "'Sheet 1'!E3", + "'Sheet 1'!D4", + "'Sheet 1'!E4", + ], + "'Sheet 1'!D3:E4", + ], + [ + [ + "'Mark''s Sheet'!D3", + "'Mark''s Sheet'!E3", + "'Mark''s Sheet'!D4", + "'Mark''s Sheet'!E4", + ], + "'Mark's Sheet'!D3:E4", + ], ]; diff --git a/tests/data/Reader/Ods/HiddenMergeCellsTest.ods b/tests/data/Reader/Ods/HiddenMergeCellsTest.ods new file mode 100644 index 00000000..b8beb972 Binary files /dev/null and b/tests/data/Reader/Ods/HiddenMergeCellsTest.ods differ diff --git a/tests/data/Reader/XLS/HiddenMergeCellsTest.xls b/tests/data/Reader/XLS/HiddenMergeCellsTest.xls new file mode 100644 index 00000000..fefbcecc Binary files /dev/null and b/tests/data/Reader/XLS/HiddenMergeCellsTest.xls differ diff --git a/tests/data/Reader/XLSX/HiddenMergeCellsTest.xlsx b/tests/data/Reader/XLSX/HiddenMergeCellsTest.xlsx new file mode 100644 index 00000000..124f0561 Binary files /dev/null and b/tests/data/Reader/XLSX/HiddenMergeCellsTest.xlsx differ diff --git a/tests/data/Reader/XLSX/issue.2965.xlsx b/tests/data/Reader/XLSX/issue.2965.xlsx new file mode 100644 index 00000000..7c0672d3 Binary files /dev/null and b/tests/data/Reader/XLSX/issue.2965.xlsx differ diff --git a/tests/data/Style/NumberFormat.php b/tests/data/Style/NumberFormat.php index 80f7080b..b307e23d 100644 --- a/tests/data/Style/NumberFormat.php +++ b/tests/data/Style/NumberFormat.php @@ -146,13 +146,13 @@ return [ '#,###', ], [ - 12, + '12', 12000, '#,', ], // Scaling test [ - 12.199999999999999, + '12.2', 12200000, '0.0,,', ], @@ -1486,4 +1486,9 @@ return [ '-1111.119', NumberFormat::FORMAT_ACCOUNTING_EUR, ], + 'issue 1929' => ['(79.3%)', -0.793, '#,##0.0%;(#,##0.0%)'], + 'percent without leading 0' => ['6.2%', 0.062, '##.0%'], + 'percent with leading 0' => ['06.2%', 0.062, '00.0%'], + 'percent lead0 no decimal' => ['06%', 0.062, '00%'], + 'percent nolead0 no decimal' => ['6%', 0.062, '##%'], ]; diff --git a/tests/data/Worksheet/namedRangeTest.xlsx b/tests/data/Worksheet/namedRangeTest.xlsx index a4383bb0..b0936a5a 100644 Binary files a/tests/data/Worksheet/namedRangeTest.xlsx and b/tests/data/Worksheet/namedRangeTest.xlsx differ