diff --git a/.gitattributes b/.gitattributes index 19c90f40..9f2b2f6e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,7 +3,6 @@ /.github export-ignore /.gitignore export-ignore /.php_cs.dist export-ignore -/.sami.php export-ignore /.scrutinizer.yml export-ignore /CHANGELOG.PHPExcel.md export-ignore /bin export-ignore diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a01892ae..87d933e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,12 +5,17 @@ jobs: runs-on: ubuntu-latest strategy: matrix: + experimental: + - false php-version: - '7.2' - '7.3' - '7.4' - '8.0' - - '8.1' + + include: + - php-version: '8.1' + experimental: true name: PHP ${{ matrix.php-version }} @@ -38,13 +43,13 @@ jobs: - name: Delete composer lock file id: composer-lock - if: ${{ matrix.php-version == '8.0' || matrix.php-version == '8.1' }} + if: ${{ matrix.php-version == '8.1' }} run: | rm composer.lock echo "::set-output name=flags::--ignore-platform-reqs" - name: Install dependencies - run: composer install --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }} + run: composer update --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }} - name: Setup problem matchers for PHP run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" @@ -52,8 +57,10 @@ jobs: - name: Setup problem matchers for PHPUnit run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - name: Test with PHPUnit - run: ./vendor/bin/phpunit + - name: "Run PHPUnit tests (Experimental: ${{ matrix.experimental }})" + env: + FAILURE_ACTION: "${{ matrix.experimental == true }}" + run: vendor/bin/phpunit --verbose || $FAILURE_ACTION php-cs-fixer: runs-on: ubuntu-latest @@ -117,6 +124,37 @@ jobs: - name: Code style with PHP_CodeSniffer run: ./vendor/bin/phpcs -q --report=checkstyle | cs2pr + phpstan: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: none + tools: cs2pr + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Static analysis with PHPStan + run: ./vendor/bin/phpstan analyse + coverage: runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index 0723541d..eac08567 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ *.project /.settings /.idea + +## mkdocs output +/site diff --git a/.php_cs.dist b/.php_cs.dist index f8797e88..1a646420 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -160,7 +160,7 @@ return PhpCsFixer\Config::create() 'php_unit_test_annotation' => true, 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], 'php_unit_test_class_requires_covers' => false, // We don't care as much as we should about coverage - 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_add_missing_param_annotation' => false, // Don't add things that bring no value 'phpdoc_align' => false, // Waste of time 'phpdoc_annotation_without_dot' => true, 'phpdoc_indent' => true, diff --git a/CHANGELOG.md b/CHANGELOG.md index 64efac09..bb09f628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Added -- CSV Reader - Best Guess for Encoding, and Handle Null-string Escape [#1647](https://github.com/PHPOffice/PhpSpreadsheet/issues/1647) +- Implemented the CHITEST(), CHISQ.DIST() and CHISQ.INV() and equivalent Statistical functions, for both left- and right-tailed distributions. +- Support for ActiveSheet and SelectedCells in the ODS Reader and Writer. [PR #1908](https://github.com/PHPOffice/PhpSpreadsheet/pull/1908) ### Changed @@ -25,22 +26,106 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Fixed +- Fixed issue where array key check for existince before accessing arrays in Xlsx.php. [PR #1970](https://github.com/PHPOffice/PhpSpreadsheet/pull/1970) +- Fixed issue with quoted strings in number format mask rendered with toFormattedString() [Issue 1972#](https://github.com/PHPOffice/PhpSpreadsheet/issues/1972) [PR #1978](https://github.com/PHPOffice/PhpSpreadsheet/pull/1978) +- Fixed issue with percentage formats in number format mask rendered with toFormattedString() [Issue 1929#](https://github.com/PHPOffice/PhpSpreadsheet/issues/1929) [PR #1928](https://github.com/PHPOffice/PhpSpreadsheet/pull/1928) +- Fixed issue with _ spacing character in number format mask corrupting output from toFormattedString() [Issue 1924#](https://github.com/PHPOffice/PhpSpreadsheet/issues/1924) [PR #1927](https://github.com/PHPOffice/PhpSpreadsheet/pull/1927) +- Fix for [Issue #1887](https://github.com/PHPOffice/PhpSpreadsheet/issues/1887) - Lose Track of Selected Cells After Save +- Fixed issue with Xlsx@listWorksheetInfo not returning any data +- Fixed invalid arguments triggering mb_substr() error in LEFT(), MID() and RIGHT() text functions. [Issue #640](https://github.com/PHPOffice/PhpSpreadsheet/issues/640) +- Fix for [Issue #1916](https://github.com/PHPOffice/PhpSpreadsheet/issues/1916) - Invalid signature check for XML files + +## 1.17.1 - 2021-03-01 + +### Added + +- Implementation of the Excel `AVERAGEIFS()` functions as part of a restructuring of Database functions and Conditional Statistical functions. +- Support for date values and percentages in query parameters for Database functions, and the IF expressions in functions like COUNTIF() and AVERAGEIF(). [#1875](https://github.com/PHPOffice/PhpSpreadsheet/pull/1875) +- Support for booleans, and for wildcard text search in query parameters for Database functions, and the IF expressions in functions like COUNTIF() and AVERAGEIF(). [#1876](https://github.com/PHPOffice/PhpSpreadsheet/pull/1876) +- Implemented DataBar for conditional formatting in Xlsx, providing read/write and creation of (type, value, direction, fills, border, axis position, color settings) as DataBar options in Excel. [#1754](https://github.com/PHPOffice/PhpSpreadsheet/pull/1754) +- Alignment for ODS Writer [#1796](https://github.com/PHPOffice/PhpSpreadsheet/issues/1796) +- Basic implementation of the PERMUTATIONA() Statistical Function + +### Changed + +- Formula functions that previously called PHP functions directly are now processed through the Excel Functions classes; resolving issues with PHP8 stricter typing. [#1789](https://github.com/PHPOffice/PhpSpreadsheet/issues/1789) + + The following MathTrig functions are affected: + `ABS()`, `ACOS()`, `ACOSH()`, `ASIN()`, `ASINH()`, `ATAN()`, `ATANH()`, + `COS()`, `COSH()`, `DEGREES()` (rad2deg), `EXP()`, `LN()` (log), `LOG10()`, + `RADIANS()` (deg2rad), `SIN()`, `SINH()`, `SQRT()`, `TAN()`, `TANH()`. + + One TextData function is also affected: `REPT()` (str_repeat). +- `formatAsDate` correctly matches language metadata, reverting c55272e +- Formulae that previously crashed on sub function call returning excel error value now return said value. + The following functions are affected `CUMPRINC()`, `CUMIPMT()`, `AMORLINC()`, + `AMORDEGRC()`. +- Adapt some function error return value to match excel's error. + The following functions are affected `PPMT()`, `IPMT()`. + +### Deprecated + +- Calling many of the Excel formula functions directly rather than through the Calculation Engine. + + The logic for these Functions is now being moved out of the categorised `Database`, `DateTime`, `Engineering`, `Financial`, `Logical`, `LookupRef`, `MathTrig`, `Statistical`, `TextData` and `Web` classes into small, dedicated classes for individual functions or related groups of functions. + + This makes the logic in these classes easier to maintain; and will reduce the memory footprint required to execute formulae when calling these functions. + +### Removed + +- Nothing. + +### Fixed + +- Avoid Duplicate Titles When Reading Multiple HTML Files.[Issue #1823](https://github.com/PHPOffice/PhpSpreadsheet/issues/1823) [PR #1829](https://github.com/PHPOffice/PhpSpreadsheet/pull/1829) +- Fixed issue with Worksheet's `getCell()` method when trying to get a cell by defined name. [#1858](https://github.com/PHPOffice/PhpSpreadsheet/issues/1858) +- Fix possible endless loop in NumberFormat Masks [#1792](https://github.com/PHPOffice/PhpSpreadsheet/issues/1792) +- Fix problem resulting from literal dot inside quotes in number format masks. [PR #1830](https://github.com/PHPOffice/PhpSpreadsheet/pull/1830) +- Resolve Google Sheets Xlsx charts issue. Google Sheets uses oneCellAnchor positioning and does not include *Cache values in the exported Xlsx. [PR #1761](https://github.com/PHPOffice/PhpSpreadsheet/pull/1761) +- Fix for Xlsx Chart axis titles mapping to correct X or Y axis label when only one is present. [PR #1760](https://github.com/PHPOffice/PhpSpreadsheet/pull/1760) +- Fix For Null Exception on ODS Read of Page Settings. [#1772](https://github.com/PHPOffice/PhpSpreadsheet/issues/1772) +- Fix Xlsx reader overriding manually set number format with builtin number format. [PR #1805](https://github.com/PHPOffice/PhpSpreadsheet/pull/1805) +- Fix Xlsx reader cell alignment. [PR #1710](https://github.com/PHPOffice/PhpSpreadsheet/pull/1710) +- Fix for not yet implemented data-types in Open Document writer [Issue #1674](https://github.com/PHPOffice/PhpSpreadsheet/issues/1674) +- Fix XLSX reader when having a corrupt numeric cell data type [PR #1664](https://github.com/phpoffice/phpspreadsheet/pull/1664) +- Fix on `CUMPRINC()`, `CUMIPMT()`, `AMORLINC()`, `AMORDEGRC()` usage. When those functions called one of `YEARFRAC()`, `PPMT()`, `IPMT()` and they would get back an error value (represented as a string), trying to use numeral operands (`+`, `/`, `-`, `*`) on said return value and a number (`float or `int`) would fail. + +## 1.16.0 - 2020-12-31 + +### Added + +- CSV Reader - Best Guess for Encoding, and Handle Null-string Escape [#1647](https://github.com/PHPOffice/PhpSpreadsheet/issues/1647) + +### Changed + +- Updated the CONVERT() function to support all current MS Excel categories and Units of Measure. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + - Fixed issue with absolute path in worksheets' Target. [PR #1769](https://github.com/PHPOffice/PhpSpreadsheet/pull/1769) - Fix for Xls Reader when SST has a bad length [#1592](https://github.com/PHPOffice/PhpSpreadsheet/issues/1592) -- Resolve Xlsx loader issue whe hyperlinks don't have a destination +- Resolve Xlsx loader issue whe hyperlinks don't have a destination - Resolve issues when printer settings resources IDs clash with drawing IDs - Resolve issue with SLK long filenames [#1612](https://github.com/PHPOffice/PhpSpreadsheet/issues/1612) - ROUNDUP and ROUNDDOWN return incorrect results for values of 0 [#1627](https://github.com/phpoffice/phpspreadsheet/pull/1627) - Apply Column and Row Styles to Existing Cells [#1712](https://github.com/PHPOffice/PhpSpreadsheet/issues/1712) [PR #1721](https://github.com/PHPOffice/PhpSpreadsheet/pull/1721) - Resolve issues with defined names where worksheet doesn't exist (#1686)[https://github.com/PHPOffice/PhpSpreadsheet/issues/1686] and [#1723](https://github.com/PHPOffice/PhpSpreadsheet/issues/1723) - [PR #1742](https://github.com/PHPOffice/PhpSpreadsheet/pull/1742) -- Fix for issue [#1735](https://github.com/PHPOffice/PhpSpreadsheet/issues/1735) Incorrect activeSheetIndex after RemoveSheetByIndex - [PR #1743](https://github.com/PHPOffice/PhpSpreadsheet/pull/1743) +- Fix for issue [#1735](https://github.com/PHPOffice/PhpSpreadsheet/issues/1735) Incorrect activeSheetIndex after RemoveSheetByIndex - [PR #1743](https://github.com/PHPOffice/PhpSpreadsheet/pull/1743) - Ensure that the list of shared formulae is maintained when an xlsx file is chunked with readFilter[Issue #169](https://github.com/PHPOffice/PhpSpreadsheet/issues/1669). - Fix for notice during accessing "cached magnification factor" offset [#1354](https://github.com/PHPOffice/PhpSpreadsheet/pull/1354) - Fix compatibility with ext-gd on php 8 ### Security Fix (CVE-2020-7776) -- Prevent XSS through cell comments in the HTML Writer. +- Prevent XSS through cell comments in the HTML Writer. ## 1.15.0 - 2020-10-11 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aed13fe2..f5953533 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,3 +9,12 @@ If you would like to contribute, here are some notes and guidelines: - All code changes must be validated by `composer check` - [Helpful article about forking](https://help.github.com/articles/fork-a-repo/ "Forking a GitHub repository") - [Helpful article about pull requests](https://help.github.com/articles/using-pull-requests/ "Pull Requests") + +## How to release + +1. Complete CHANGELOG.md and commit +2. Create an annotated tag + 1. `git tag -a 1.2.3` + 2. Tag subject must be the version number, eg: `1.2.3` + 3. Tag body must be a copy-paste of the changelog entries +3. Push tag with `git push --tags`, GitHub Actions will create a GitHub release automatically diff --git a/composer.json b/composer.json index c6f8e30e..6cd65021 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,19 @@ { "name": "phpoffice/phpspreadsheet", "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", - "keywords": ["PHP", "OpenXML", "Excel", "xlsx", "xls", "ods", "gnumeric", "spreadsheet"], + "keywords": [ + "PHP", + "OpenXML", + "Excel", + "xlsx", + "xls", + "ods", + "gnumeric", + "spreadsheet" + ], + "config": { + "sort-packages": true + }, "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", "type": "library", "license": "MIT", @@ -29,7 +41,8 @@ "check": [ "php-cs-fixer fix --ansi --dry-run --diff", "phpcs", - "phpunit --color=always" + "phpunit --color=always", + "phpstan analyse --ansi" ], "fix": [ "php-cs-fixer fix --ansi" @@ -39,35 +52,37 @@ ] }, "require": { - "php": "^7.2||^8.0", + "php": "^7.2 || ^8.0", + "ext-simplexml": "*", "ext-ctype": "*", "ext-dom": "*", + "ext-fileinfo": "*", "ext-gd": "*", "ext-iconv": "*", - "ext-fileinfo": "*", "ext-libxml": "*", "ext-mbstring": "*", - "ext-SimpleXML": "*", "ext-xml": "*", "ext-xmlreader": "*", "ext-xmlwriter": "*", "ext-zip": "*", "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.13", "maennchen/zipstream-php": "^2.1", - "markbaker/complex": "^1.5||^2.0", - "markbaker/matrix": "^1.2||^2.0", - "psr/simple-cache": "^1.0", + "markbaker/complex": "^2.0", + "markbaker/matrix": "^2.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "ezyang/htmlpurifier": "^4.13" + "psr/simple-cache": "^1.0" }, "require-dev": { - "dompdf/dompdf": "^0.8.5", - "friendsofphp/php-cs-fixer": "^2.16", + "dompdf/dompdf": "^1.0", + "friendsofphp/php-cs-fixer": "^2.18", "jpgraph/jpgraph": "^4.0", "mpdf/mpdf": "^8.0", "phpcompatibility/php-compatibility": "^9.3", - "phpunit/phpunit": "^8.5||^9.3", + "phpstan/phpstan": "^0.12.82", + "phpstan/phpstan-phpunit": "^0.12.18", + "phpunit/phpunit": "^8.5", "squizlabs/php_codesniffer": "^3.5", "tecnickcom/tcpdf": "^6.3" }, diff --git a/composer.lock b/composer.lock index a1fb99b7..3670f857 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "458fe4e974b469230da589a8781d1e0e", + "content-hash": "3be2673a6367d296c616bf9c34b77953", "packages": [ { "name": "ezyang/htmlpurifier", @@ -119,6 +119,16 @@ "stream", "zip" ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/master" + }, + "funding": [ + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], "time": "2020-05-30T13:11:16+00:00" }, { @@ -214,24 +224,28 @@ "complex", "mathematics" ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/PHP8" + }, "time": "2020-08-26T10:42:07+00:00" }, { "name": "markbaker/matrix", - "version": "2.0.0", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/MarkBaker/PHPMatrix.git", - "reference": "9567d9c4c519fbe40de01dbd1e4469dbbb66f46a" + "reference": "361c0f545c3172ee26c3d596a0aa03f0cef65e6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/9567d9c4c519fbe40de01dbd1e4469dbbb66f46a", - "reference": "9567d9c4c519fbe40de01dbd1e4469dbbb66f46a", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/361c0f545c3172ee26c3d596a0aa03f0cef65e6a", + "reference": "361c0f545c3172ee26c3d596a0aa03f0cef65e6a", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", @@ -249,22 +263,22 @@ "Matrix\\": "classes/src/" }, "files": [ - "classes/src/functions/adjoint.php", - "classes/src/functions/antidiagonal.php", - "classes/src/functions/cofactors.php", - "classes/src/functions/determinant.php", - "classes/src/functions/diagonal.php", - "classes/src/functions/identity.php", - "classes/src/functions/inverse.php", - "classes/src/functions/minors.php", - "classes/src/functions/trace.php", - "classes/src/functions/transpose.php", - "classes/src/operations/add.php", - "classes/src/operations/directsum.php", - "classes/src/operations/subtract.php", - "classes/src/operations/multiply.php", - "classes/src/operations/divideby.php", - "classes/src/operations/divideinto.php" + "classes/src/Functions/adjoint.php", + "classes/src/Functions/antidiagonal.php", + "classes/src/Functions/cofactors.php", + "classes/src/Functions/determinant.php", + "classes/src/Functions/diagonal.php", + "classes/src/Functions/identity.php", + "classes/src/Functions/inverse.php", + "classes/src/Functions/minors.php", + "classes/src/Functions/trace.php", + "classes/src/Functions/transpose.php", + "classes/src/Operations/add.php", + "classes/src/Operations/directsum.php", + "classes/src/Operations/subtract.php", + "classes/src/Operations/multiply.php", + "classes/src/Operations/divideby.php", + "classes/src/Operations/divideinto.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -284,20 +298,24 @@ "matrix", "vector" ], - "time": "2020-08-28T17:11:00+00:00" + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/2.1.2" + }, + "time": "2021-01-23T16:37:31+00:00" }, { "name": "myclabs/php-enum", - "version": "1.7.6", + "version": "1.7.7", "source": { "type": "git", "url": "https://github.com/myclabs/php-enum.git", - "reference": "5f36467c7a87e20fbdc51e524fd8f9d1de80187c" + "reference": "d178027d1e679832db9f38248fcc7200647dc2b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/php-enum/zipball/5f36467c7a87e20fbdc51e524fd8f9d1de80187c", - "reference": "5f36467c7a87e20fbdc51e524fd8f9d1de80187c", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/d178027d1e679832db9f38248fcc7200647dc2b7", + "reference": "d178027d1e679832db9f38248fcc7200647dc2b7", "shasum": "" }, "require": { @@ -330,7 +348,21 @@ "keywords": [ "enum" ], - "time": "2020-02-14T08:15:52+00:00" + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.7.7" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2020-11-14T18:14:52+00:00" }, { "name": "psr/http-client", @@ -379,6 +411,9 @@ "psr", "psr-18" ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, "time": "2020-06-29T06:28:15+00:00" }, { @@ -431,6 +466,9 @@ "request", "response" ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, "time": "2019-04-30T12:38:16+00:00" }, { @@ -481,6 +519,9 @@ "request", "response" ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, "time": "2016-08-06T14:39:51+00:00" }, { @@ -529,24 +570,27 @@ "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.18.1", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", - "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { "ext-mbstring": "For best performance" @@ -554,7 +598,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.22-dev" }, "thanks": { "name": "symfony/polyfill", @@ -592,6 +636,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -606,34 +653,35 @@ "type": "tidelift" } ], - "time": "2020-07-14T12:35:20+00:00" + "time": "2021-01-22T09:19:47+00:00" } ], "packages-dev": [ { "name": "composer/semver", - "version": "1.7.1", + "version": "3.2.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "38276325bd896f90dfcfe30029aa5db40df387a7" + "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/38276325bd896f90dfcfe30029aa5db40df387a7", - "reference": "38276325bd896f90dfcfe30029aa5db40df387a7", + "url": "https://api.github.com/repos/composer/semver/zipball/a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.5 || ^5.0.5" + "phpstan/phpstan": "^0.12.54", + "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -669,6 +717,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.2.4" + }, "funding": [ { "url": "https://packagist.com", @@ -683,20 +736,20 @@ "type": "tidelift" } ], - "time": "2020-09-27T13:13:07+00:00" + "time": "2020-11-13T08:59:24+00:00" }, { "name": "composer/xdebug-handler", - "version": "1.4.3", + "version": "1.4.6", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ebd27a9866ae8254e873866f795491f02418c5a5" + "reference": "f27e06cd9675801df441b3656569b328e04aa37c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ebd27a9866ae8254e873866f795491f02418c5a5", - "reference": "ebd27a9866ae8254e873866f795491f02418c5a5", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/f27e06cd9675801df441b3656569b328e04aa37c", + "reference": "f27e06cd9675801df441b3656569b328e04aa37c", "shasum": "" }, "require": { @@ -704,7 +757,8 @@ "psr/log": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "autoload": { @@ -727,6 +781,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/1.4.6" + }, "funding": [ { "url": "https://packagist.com", @@ -741,20 +800,20 @@ "type": "tidelift" } ], - "time": "2020-08-19T10:27:58+00:00" + "time": "2021-03-25T17:01:18+00:00" }, { "name": "doctrine/annotations", - "version": "1.10.4", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "bfe91e31984e2ba76df1c1339681770401ec262f" + "reference": "b17c5014ef81d212ac539f07a1001832df1b6d3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/bfe91e31984e2ba76df1c1339681770401ec262f", - "reference": "bfe91e31984e2ba76df1c1339681770401ec262f", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/b17c5014ef81d212ac539f07a1001832df1b6d3b", + "reference": "b17c5014ef81d212ac539f07a1001832df1b6d3b", "shasum": "" }, "require": { @@ -764,15 +823,11 @@ }, "require-dev": { "doctrine/cache": "1.*", + "doctrine/coding-standard": "^6.0 || ^8.1", "phpstan/phpstan": "^0.12.20", "phpunit/phpunit": "^7.5 || ^9.1.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" @@ -805,46 +860,45 @@ } ], "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", "keywords": [ "annotations", "docblock", "parser" ], - "time": "2020-08-10T19:35:50+00:00" + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.12.1" + }, + "time": "2021-02-21T21:00:45+00:00" }, { "name": "doctrine/instantiator", - "version": "1.3.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", - "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0", + "doctrine/coding-standard": "^8.0", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.13", - "phpstan/phpstan-phpunit": "^0.11", - "phpstan/phpstan-shim": "^0.11", - "phpunit/phpunit": "^7.0" + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" @@ -858,7 +912,7 @@ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "homepage": "https://ocramius.github.io/" } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", @@ -867,7 +921,25 @@ "constructor", "instantiate" ], - "time": "2020-05-29T17:27:14+00:00" + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" }, { "name": "doctrine/lexer", @@ -929,20 +1001,38 @@ "parser", "php" ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], "time": "2020-05-25T17:44:05+00:00" }, { "name": "dompdf/dompdf", - "version": "v0.8.6", + "version": "v1.0.2", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "db91d81866c69a42dad1d2926f61515a1e3f42c5" + "reference": "8768448244967a46d6e67b891d30878e0e15d25c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/db91d81866c69a42dad1d2926f61515a1e3f42c5", - "reference": "db91d81866c69a42dad1d2926f61515a1e3f42c5", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/8768448244967a46d6e67b891d30878e0e15d25c", + "reference": "8768448244967a46d6e67b891d30878e0e15d25c", "shasum": "" }, "require": { @@ -950,11 +1040,11 @@ "ext-mbstring": "*", "phenx/php-font-lib": "^0.5.2", "phenx/php-svg-lib": "^0.3.3", - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "require-dev": { "mockery/mockery": "^1.3", - "phpunit/phpunit": "^7.5", + "phpunit/phpunit": "^7.5 || ^8 || ^9", "squizlabs/php_codesniffer": "^3.5" }, "suggest": { @@ -997,31 +1087,35 @@ ], "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", "homepage": "https://github.com/dompdf/dompdf", - "time": "2020-08-30T22:54:22+00:00" + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v1.0.2" + }, + "time": "2021-01-08T14:18:52+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.16.4", + "version": "v2.18.4", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "1023c3458137ab052f6ff1e09621a721bfdeca13" + "reference": "06f764e3cb6d60822d8f5135205f9d32b5508a31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/1023c3458137ab052f6ff1e09621a721bfdeca13", - "reference": "1023c3458137ab052f6ff1e09621a721bfdeca13", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/06f764e3cb6d60822d8f5135205f9d32b5508a31", + "reference": "06f764e3cb6d60822d8f5135205f9d32b5508a31", "shasum": "" }, "require": { - "composer/semver": "^1.4", + "composer/semver": "^1.4 || ^2.0 || ^3.0", "composer/xdebug-handler": "^1.2", "doctrine/annotations": "^1.2", "ext-json": "*", "ext-tokenizer": "*", - "php": "^5.6 || ^7.0", + "php": "^5.6 || ^7.0 || ^8.0", "php-cs-fixer/diff": "^1.3", - "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "symfony/console": "^3.4.43 || ^4.1.6 || ^5.0", "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", "symfony/finder": "^3.0 || ^4.0 || ^5.0", @@ -1032,17 +1126,19 @@ "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" }, "require-dev": { - "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", "justinrainbow/json-schema": "^5.0", - "keradus/cli-executor": "^1.2", + "keradus/cli-executor": "^1.4", "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^2.1", + "php-coveralls/php-coveralls": "^2.4.2", "php-cs-fixer/accessible-object": "^1.0", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.1", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.1", - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.1", - "phpunitgoodpractices/traits": "^1.8", - "symfony/phpunit-bridge": "^5.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", + "phpspec/prophecy-phpunit": "^1.1 || ^2.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.13 || ^9.5", + "phpunitgoodpractices/polyfill": "^1.5", + "phpunitgoodpractices/traits": "^1.9.1", + "sanmai/phpunit-legacy-adapter": "^6.4 || ^8.2.1", + "symfony/phpunit-bridge": "^5.2.1", "symfony/yaml": "^3.0 || ^4.0 || ^5.0" }, "suggest": { @@ -1070,6 +1166,7 @@ "tests/Test/IntegrationCaseFactoryInterface.php", "tests/Test/InternalIntegrationCaseFactory.php", "tests/Test/IsIdenticalConstraint.php", + "tests/Test/TokensWithObservedTransformers.php", "tests/TestCase.php" ] }, @@ -1088,7 +1185,17 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2020-06-27T23:57:46+00:00" + "support": { + "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.18.4" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2021-03-20T14:52:33+00:00" }, { "name": "jpgraph/jpgraph", @@ -1128,36 +1235,40 @@ "jpgraph", "pie" ], + "support": { + "issues": "https://github.com/ztec/JpGraph/issues", + "source": "https://github.com/ztec/JpGraph/tree/4.x" + }, "abandoned": true, "time": "2017-02-23T09:44:15+00:00" }, { "name": "mpdf/mpdf", - "version": "v8.0.7", + "version": "v8.0.10", "source": { "type": "git", "url": "https://github.com/mpdf/mpdf.git", - "reference": "7daf07f15334ed59a276bd52131dcca48794cdbd" + "reference": "1333a962cd2f7ae1a127b7534b7734b58179186f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mpdf/mpdf/zipball/7daf07f15334ed59a276bd52131dcca48794cdbd", - "reference": "7daf07f15334ed59a276bd52131dcca48794cdbd", + "url": "https://api.github.com/repos/mpdf/mpdf/zipball/1333a962cd2f7ae1a127b7534b7734b58179186f", + "reference": "1333a962cd2f7ae1a127b7534b7734b58179186f", "shasum": "" }, "require": { "ext-gd": "*", "ext-mbstring": "*", "myclabs/deep-copy": "^1.7", - "paragonie/random_compat": "^1.4|^2.0|9.99.99", - "php": "^5.6 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": "^5.6 || ^7.0 || ~8.0.0", "psr/log": "^1.0", "setasign/fpdi": "^2.1" }, "require-dev": { - "mockery/mockery": "^0.9.5", - "mpdf/qrcode": "^1.0.0", - "phpunit/phpunit": "^5.0", + "mockery/mockery": "^1.3.0", + "mpdf/qrcode": "^1.1.0", + "phpunit/phpunit": "^5.7", "squizlabs/php_codesniffer": "^3.5.0", "tracy/tracy": "^2.4" }, @@ -1167,11 +1278,6 @@ "ext-zlib": "Needed for compression of embedded resources, such as fonts" }, "type": "library", - "extra": { - "branch-alias": { - "dev-development": "7.x-dev" - } - }, "autoload": { "psr-4": { "Mpdf\\": "src/" @@ -1198,26 +1304,31 @@ "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", "type": "custom" } ], - "time": "2020-07-15T09:48:00+00:00" + "time": "2021-01-08T14:59:28+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.10.1", + "version": "1.10.2", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", - "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", "shasum": "" }, "require": { @@ -1252,30 +1363,34 @@ "object", "object graph" ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, "funding": [ { "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", "type": "tidelift" } ], - "time": "2020-06-29T13:22:24+00:00" + "time": "2020-11-13T09:40:50+00:00" }, { "name": "paragonie/random_compat", - "version": "v9.99.99", + "version": "v9.99.100", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", "shasum": "" }, "require": { - "php": "^7" + "php": ">= 7" }, "require-dev": { "phpunit/phpunit": "4.*|5.*", @@ -1303,32 +1418,38 @@ "pseudorandom", "random" ], - "time": "2018-07-02T15:55:56+00:00" + "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" }, { "name": "phar-io/manifest", - "version": "1.0.3", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", "shasum": "" }, "require": { "ext-dom": "*", "ext-phar": "*", - "phar-io/version": "^2.0", - "php": "^5.6 || ^7.0" + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1358,24 +1479,28 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2018-07-08T19:23:20+00:00" + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/master" + }, + "time": "2020-06-27T14:33:11+00:00" }, { "name": "phar-io/version", - "version": "2.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + "reference": "bae7c545bef187884426f042434e561ab1ddb182" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -1405,7 +1530,11 @@ } ], "description": "Library for handling version information and constraints", - "time": "2018-07-08T19:19:57+00:00" + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.1.0" + }, + "time": "2021-02-23T14:00:09+00:00" }, { "name": "phenx/php-font-lib", @@ -1442,6 +1571,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/PhenX/php-font-lib/issues", + "source": "https://github.com/PhenX/php-font-lib/tree/0.5.2" + }, "time": "2020-03-08T15:31:32+00:00" }, { @@ -1482,6 +1615,10 @@ ], "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/PhenX/php-svg-lib/issues", + "source": "https://github.com/PhenX/php-svg-lib/tree/master" + }, "time": "2019-09-11T20:02:13+00:00" }, { @@ -1533,6 +1670,10 @@ "keywords": [ "diff" ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/diff/issues", + "source": "https://github.com/PHP-CS-Fixer/diff/tree/v1.3.1" + }, "time": "2020-10-14T08:39:05+00:00" }, { @@ -1591,6 +1732,10 @@ "phpcs", "standards" ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, "time": "2019-12-27T09:44:58+00:00" }, { @@ -1640,6 +1785,10 @@ "reflection", "static analysis" ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, "time": "2020-06-27T09:03:43+00:00" }, { @@ -1692,6 +1841,10 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, "time": "2020-09-03T19:13:55+00:00" }, { @@ -1737,20 +1890,24 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, "time": "2020-09-17T18:55:26+00:00" }, { "name": "phpspec/prophecy", - "version": "1.12.1", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d" + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/8ce87516be71aae9b956f81906aaf0338e0d8a2d", - "reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", "shasum": "" }, "require": { @@ -1762,7 +1919,7 @@ }, "require-dev": { "phpspec/phpspec": "^6.0", - "phpunit/phpunit": "^8.0 || ^9.0 <9.3" + "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { @@ -1800,29 +1957,148 @@ "spy", "stub" ], - "time": "2020-09-29T09:10:42+00:00" + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + }, + "time": "2021-03-17T13:42:18+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "7.0.10", + "name": "phpstan/phpstan", + "version": "0.12.82", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf" + "url": "https://github.com/phpstan/phpstan.git", + "reference": "3920f0fb0aff39263d3a4cb0bca120a67a1a6a11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f1884187926fbb755a9aaf0b3836ad3165b478bf", - "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3920f0fb0aff39263d3a4cb0bca120a67a1a6a11", + "reference": "3920f0fb0aff39263d3a4cb0bca120a67a1a6a11", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/0.12.82" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2021-03-19T06:08:17+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "0.12.18", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "ab44aec7cfb5cb267b8bc30a8caea86dd50d1f72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/ab44aec7cfb5cb267b8bc30a8caea86dd50d1f72", + "reference": "ab44aec7cfb5cb267b8bc30a8caea86dd50d1f72", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^0.12.60" + }, + "conflict": { + "phpunit/phpunit": "<7.0" + }, + "require-dev": { + "phing/phing": "^2.16.3", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-strict-rules": "^0.12.6", + "phpunit/phpunit": "^7.5.20" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "0.12-dev" + }, + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "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/0.12.18" + }, + "time": "2021-03-06T11:51:27+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "7.0.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bb7c9a210c72e4709cdde67f8b7362f672f2225c", + "reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.2", + "php": ">=7.2", "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.1.1", + "phpunit/php-token-stream": "^3.1.1 || ^4.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", "sebastian/environment": "^4.2.2", "sebastian/version": "^2.0.1", @@ -1863,27 +2139,37 @@ "testing", "xunit" ], - "time": "2019-11-20T13:55:58+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.14" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-12-02T13:39:03+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "050bedf145a257b1ff02746c31894800e5122946" + "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", - "reference": "050bedf145a257b1ff02746c31894800e5122946", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4b49fb70f067272b659ef0174ff9ca40fdaa6357", + "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^7.1" + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { @@ -1913,7 +2199,17 @@ "filesystem", "iterator" ], - "time": "2018-09-13T20:33:42+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:25:21+00:00" }, { "name": "phpunit/php-text-template", @@ -1954,27 +2250,31 @@ "keywords": [ "template" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + }, "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", - "version": "2.1.2", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" + "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", - "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { @@ -2003,25 +2303,35 @@ "keywords": [ "timer" ], - "time": "2019-06-07T04:22:29+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:20:02+00:00" }, { "name": "phpunit/php-token-stream", - "version": "3.1.1", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" + "reference": "472b687829041c24b25f475e14c2f38a09edf1c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", - "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/472b687829041c24b25f475e14c2f38a09edf1c2", + "reference": "472b687829041c24b25f475e14c2f38a09edf1c2", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": "^7.1" + "php": ">=7.1" }, "require-dev": { "phpunit/phpunit": "^7.0" @@ -2052,43 +2362,54 @@ "keywords": [ "tokenizer" ], - "time": "2019-09-17T06:23:10+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "abandoned": true, + "time": "2020-11-30T08:38:46+00:00" }, { "name": "phpunit/phpunit", - "version": "8.5.8", + "version": "8.5.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "34c18baa6a44f1d1fbf0338907139e9dce95b997" + "reference": "038d4196d8e8cb405cd5e82cedfe413ad6eef9ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/34c18baa6a44f1d1fbf0338907139e9dce95b997", - "reference": "34c18baa6a44f1d1fbf0338907139e9dce95b997", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/038d4196d8e8cb405cd5e82cedfe413ad6eef9ef", + "reference": "038d4196d8e8cb405cd5e82cedfe413ad6eef9ef", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.2.0", + "doctrine/instantiator": "^1.3.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.9.1", - "phar-io/manifest": "^1.0.3", - "phar-io/version": "^2.0.1", - "php": "^7.2", - "phpspec/prophecy": "^1.8.1", - "phpunit/php-code-coverage": "^7.0.7", + "myclabs/deep-copy": "^1.10.0", + "phar-io/manifest": "^2.0.1", + "phar-io/version": "^3.0.2", + "php": ">=7.2", + "phpspec/prophecy": "^1.10.3", + "phpunit/php-code-coverage": "^7.0.12", "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^2.1.2", "sebastian/comparator": "^3.0.2", "sebastian/diff": "^3.0.2", - "sebastian/environment": "^4.2.2", - "sebastian/exporter": "^3.1.1", + "sebastian/environment": "^4.2.3", + "sebastian/exporter": "^3.1.2", "sebastian/global-state": "^3.0.0", "sebastian/object-enumerator": "^3.0.3", "sebastian/resource-operations": "^2.0.1", @@ -2135,31 +2456,40 @@ "testing", "xunit" ], - "time": "2020-06-22T07:06:58+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.15" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-03-17T07:27:54+00:00" }, { "name": "psr/container", - "version": "1.0.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.2.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -2172,7 +2502,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common Container Interface (PHP FIG PSR-11)", @@ -2184,7 +2514,11 @@ "container-interop", "psr" ], - "time": "2017-02-14T16:28:37+00:00" + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" }, { "name": "psr/event-dispatcher", @@ -2230,6 +2564,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" }, { @@ -2277,6 +2615,9 @@ "psr", "psr-3" ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.3" + }, "time": "2020-03-23T09:12:05+00:00" }, { @@ -2322,27 +2663,31 @@ "parser", "stylesheet" ], + "support": { + "issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues", + "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.3.1" + }, "time": "2020-06-01T09:10:00+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { @@ -2367,29 +2712,39 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:15:22+00:00" }, { "name": "sebastian/comparator", - "version": "3.0.2", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + "reference": "1071dfcef776a57013124ff35e1fc41ccd294758" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758", + "reference": "1071dfcef776a57013124ff35e1fc41ccd294758", "shasum": "" }, "require": { - "php": "^7.1", + "php": ">=7.1", "sebastian/diff": "^3.0", "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "^7.1" + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { @@ -2407,6 +2762,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -2418,10 +2777,6 @@ { "name": "Bernhard Schussek", "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" } ], "description": "Provides the functionality to compare PHP values for equality", @@ -2431,24 +2786,34 @@ "compare", "equality" ], - "time": "2018-07-12T15:12:46+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:04:30+00:00" }, { "name": "sebastian/diff", - "version": "3.0.2", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", - "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", + "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=7.1" }, "require-dev": { "phpunit/phpunit": "^7.5 || ^8.0", @@ -2470,13 +2835,13 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], "description": "Diff implementation", @@ -2487,24 +2852,34 @@ "unidiff", "unified diff" ], - "time": "2019-02-04T06:01:07+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:59:04+00:00" }, { "name": "sebastian/environment", - "version": "4.2.3", + "version": "4.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" + "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", - "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=7.1" }, "require-dev": { "phpunit/phpunit": "^7.5" @@ -2540,24 +2915,34 @@ "environment", "hhvm" ], - "time": "2019-11-20T08:46:58+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:53:42+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.2", + "version": "3.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", - "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/6b853149eab67d4da22291d36f5b0631c0fd856e", + "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e", "shasum": "" }, "require": { - "php": "^7.0", + "php": ">=7.0", "sebastian/recursion-context": "^3.0" }, "require-dev": { @@ -2607,24 +2992,34 @@ "export", "exporter" ], - "time": "2019-09-14T09:02:43+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:47:53+00:00" }, { "name": "sebastian/global-state", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" + "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", - "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/474fb9edb7ab891665d3bfc6317f42a0a150454b", + "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b", "shasum": "" }, "require": { - "php": "^7.2", + "php": ">=7.2", "sebastian/object-reflector": "^1.1.1", "sebastian/recursion-context": "^3.0" }, @@ -2661,24 +3056,34 @@ "keywords": [ "global state" ], - "time": "2019-02-01T05:30:01+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:43:24+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", "shasum": "" }, "require": { - "php": "^7.0", + "php": ">=7.0", "sebastian/object-reflector": "^1.1.1", "sebastian/recursion-context": "^3.0" }, @@ -2708,24 +3113,34 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03T12:35:26+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:40:27+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.0" }, "require-dev": { "phpunit/phpunit": "^6.0" @@ -2753,24 +3168,34 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:37:18+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.0" }, "require-dev": { "phpunit/phpunit": "^6.0" @@ -2791,14 +3216,14 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, { "name": "Adam Harvey", "email": "aharvey@php.net" @@ -2806,24 +3231,34 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:34:24+00:00" }, { "name": "sebastian/resource-operations", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", - "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=7.1" }, "type": "library", "extra": { @@ -2848,24 +3283,34 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2018-10-04T04:07:39+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:30:19+00:00" }, { "name": "sebastian/type", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3" + "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3", - "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4", "shasum": "" }, "require": { - "php": "^7.2" + "php": ">=7.2" }, "require-dev": { "phpunit/phpunit": "^8.2" @@ -2894,7 +3339,17 @@ ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", - "time": "2019-07-02T08:10:15+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/1.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:25:11+00:00" }, { "name": "sebastian/version", @@ -2937,25 +3392,29 @@ ], "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/master" + }, "time": "2016-10-03T07:35:21+00:00" }, { "name": "setasign/fpdi", - "version": "v2.3.4", + "version": "v2.3.6", "source": { "type": "git", "url": "https://github.com/Setasign/FPDI.git", - "reference": "2b5fb811c04f937ef257ef3f798cebeded33c136" + "reference": "6231e315f73e4f62d72b73f3d6d78ff0eed93c31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDI/zipball/2b5fb811c04f937ef257ef3f798cebeded33c136", - "reference": "2b5fb811c04f937ef257ef3f798cebeded33c136", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/6231e315f73e4f62d72b73f3d6d78ff0eed93c31", + "reference": "6231e315f73e4f62d72b73f3d6d78ff0eed93c31", "shasum": "" }, "require": { "ext-zlib": "*", - "php": "^5.6 || ^7.0" + "php": "^5.6 || ^7.0 || ^8.0" }, "conflict": { "setasign/tfpdf": "<1.31" @@ -2999,26 +3458,30 @@ "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", "type": "tidelift" } ], - "time": "2020-08-27T06:55:47+00:00" + "time": "2021-02-11T11:37:01+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.5.6", + "version": "3.5.8", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "e97627871a7eab2f70e59166072a6b767d5834e0" + "reference": "9d583721a7157ee997f235f327de038e7ea6dac4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/e97627871a7eab2f70e59166072a6b767d5834e0", - "reference": "e97627871a7eab2f70e59166072a6b767d5834e0", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/9d583721a7157ee997f235f327de038e7ea6dac4", + "reference": "9d583721a7157ee997f235f327de038e7ea6dac4", "shasum": "" }, "require": { @@ -3056,20 +3519,25 @@ "phpcs", "standards" ], - "time": "2020-08-10T04:50:15+00:00" + "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": "2020-10-23T02:01:07+00:00" }, { "name": "symfony/console", - "version": "v5.1.7", + "version": "v5.2.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "ae789a8a2ad189ce7e8216942cdb9b77319f5eb8" + "reference": "938ebbadae1b0a9c9d1ec313f87f9708609f1b79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/ae789a8a2ad189ce7e8216942cdb9b77319f5eb8", - "reference": "ae789a8a2ad189ce7e8216942cdb9b77319f5eb8", + "url": "https://api.github.com/repos/symfony/console/zipball/938ebbadae1b0a9c9d1ec313f87f9708609f1b79", + "reference": "938ebbadae1b0a9c9d1ec313f87f9708609f1b79", "shasum": "" }, "require": { @@ -3106,11 +3574,6 @@ "symfony/process": "" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" @@ -3133,8 +3596,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Console Component", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.2.5" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3149,7 +3621,7 @@ "type": "tidelift" } ], - "time": "2020-10-07T15:23:00+00:00" + "time": "2021-03-06T13:42:15+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3199,6 +3671,9 @@ ], "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/master" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3217,16 +3692,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v5.1.7", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "d5de97d6af175a9e8131c546db054ca32842dd0f" + "reference": "d08d6ec121a425897951900ab692b612a61d6240" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d5de97d6af175a9e8131c546db054ca32842dd0f", - "reference": "d5de97d6af175a9e8131c546db054ca32842dd0f", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d08d6ec121a425897951900ab692b612a61d6240", + "reference": "d08d6ec121a425897951900ab692b612a61d6240", "shasum": "" }, "require": { @@ -3257,11 +3732,6 @@ "symfony/http-kernel": "" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" @@ -3284,8 +3754,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony EventDispatcher Component", + "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.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3300,7 +3773,7 @@ "type": "tidelift" } ], - "time": "2020-09-18T14:27:32+00:00" + "time": "2021-02-18T17:12:37+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3362,6 +3835,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.2.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3380,16 +3856,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.1.7", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "1a8697545a8d87b9f2f6b1d32414199cc5e20aae" + "reference": "710d364200997a5afde34d9fe57bd52f3cc1e108" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/1a8697545a8d87b9f2f6b1d32414199cc5e20aae", - "reference": "1a8697545a8d87b9f2f6b1d32414199cc5e20aae", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/710d364200997a5afde34d9fe57bd52f3cc1e108", + "reference": "710d364200997a5afde34d9fe57bd52f3cc1e108", "shasum": "" }, "require": { @@ -3397,11 +3873,6 @@ "symfony/polyfill-ctype": "~1.8" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" @@ -3424,8 +3895,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3440,31 +3914,26 @@ "type": "tidelift" } ], - "time": "2020-09-27T14:02:37+00:00" + "time": "2021-02-12T10:38:38+00:00" }, { "name": "symfony/finder", - "version": "v5.1.7", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "2c3ba7ad6884e6c4451ce2340e2dc23f6fa3e0d8" + "reference": "0d639a0943822626290d169965804f79400e6a04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2c3ba7ad6884e6c4451ce2340e2dc23f6fa3e0d8", - "reference": "2c3ba7ad6884e6c4451ce2340e2dc23f6fa3e0d8", + "url": "https://api.github.com/repos/symfony/finder/zipball/0d639a0943822626290d169965804f79400e6a04", + "reference": "0d639a0943822626290d169965804f79400e6a04", "shasum": "" }, "require": { "php": ">=7.2.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" @@ -3487,8 +3956,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3503,33 +3975,29 @@ "type": "tidelift" } ], - "time": "2020-09-02T16:23:27+00:00" + "time": "2021-02-15T18:55:04+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.1.7", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "4c7e155bf7d93ea4ba3824d5a14476694a5278dd" + "reference": "5d0f633f9bbfcf7ec642a2b5037268e61b0a62ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/4c7e155bf7d93ea4ba3824d5a14476694a5278dd", - "reference": "4c7e155bf7d93ea4ba3824d5a14476694a5278dd", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/5d0f633f9bbfcf7ec642a2b5037268e61b0a62ce", + "reference": "5d0f633f9bbfcf7ec642a2b5037268e61b0a62ce", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php73": "~1.0", "symfony/polyfill-php80": "^1.15" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" @@ -3552,13 +4020,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony OptionsResolver Component", + "description": "Provides an improved replacement for the array_replace PHP function", "homepage": "https://symfony.com", "keywords": [ "config", "configuration", "options" ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3573,24 +4044,24 @@ "type": "tidelift" } ], - "time": "2020-09-27T03:44:28+00:00" + "time": "2021-01-27T12:56:27+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.18.1", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", - "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { "ext-ctype": "For best performance" @@ -3598,7 +4069,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.22-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3635,6 +4106,9 @@ "polyfill", "portable" ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3649,24 +4123,24 @@ "type": "tidelift" } ], - "time": "2020-07-14T12:35:20+00:00" + "time": "2021-01-07T16:49:33+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.18.1", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b740103edbdcc39602239ee8860f0f45a8eb9aa5" + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b740103edbdcc39602239ee8860f0f45a8eb9aa5", - "reference": "b740103edbdcc39602239ee8860f0f45a8eb9aa5", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170", + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { "ext-intl": "For best performance" @@ -3674,7 +4148,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.22-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3713,6 +4187,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3727,24 +4204,24 @@ "type": "tidelift" } ], - "time": "2020-07-14T12:35:20+00:00" + "time": "2021-01-22T09:19:47+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.18.1", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e" + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e", - "reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248", + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { "ext-intl": "For best performance" @@ -3752,7 +4229,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.22-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3794,6 +4271,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3808,47 +4288,35 @@ "type": "tidelift" } ], - "time": "2020-07-14T12:35:20+00:00" + "time": "2021-01-22T09:19:47+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.18.1", + "version": "v1.20.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "0dd93f2c578bdc9c72697eaa5f1dd25644e618d3" + "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/0dd93f2c578bdc9c72697eaa5f1dd25644e618d3", - "reference": "0dd93f2c578bdc9c72697eaa5f1dd25644e618d3", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644", + "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0|~9.99", - "php": ">=5.3.3" + "php": ">=7.1" }, - "type": "library", + "type": "metapackage", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.20-dev" }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] - }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -3871,6 +4339,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php70/tree/v1.20.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3885,29 +4356,29 @@ "type": "tidelift" } ], - "time": "2020-07-14T12:35:20+00:00" + "time": "2020-10-23T14:02:19+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.18.1", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "639447d008615574653fb3bc60d1986d7172eaae" + "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/639447d008615574653fb3bc60d1986d7172eaae", - "reference": "639447d008615574653fb3bc60d1986d7172eaae", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.22-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3944,6 +4415,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3958,29 +4432,29 @@ "type": "tidelift" } ], - "time": "2020-07-14T12:35:20+00:00" + "time": "2021-01-07T16:49:33+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.18.1", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca" + "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fffa1a52a023e782cdcc221d781fe1ec8f87fcca", - "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.22-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4020,6 +4494,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4034,29 +4511,29 @@ "type": "tidelift" } ], - "time": "2020-07-14T12:35:20+00:00" + "time": "2021-01-07T16:49:33+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.18.1", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981" + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981", - "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", "shasum": "" }, "require": { - "php": ">=7.0.8" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.22-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4100,6 +4577,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4114,20 +4594,20 @@ "type": "tidelift" } ], - "time": "2020-07-14T12:35:20+00:00" + "time": "2021-01-07T16:49:33+00:00" }, { "name": "symfony/process", - "version": "v5.1.7", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d3a2e64866169586502f0cd9cab69135ad12cee9" + "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d3a2e64866169586502f0cd9cab69135ad12cee9", - "reference": "d3a2e64866169586502f0cd9cab69135ad12cee9", + "url": "https://api.github.com/repos/symfony/process/zipball/313a38f09c77fbcdc1d223e57d368cea76a2fd2f", + "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f", "shasum": "" }, "require": { @@ -4135,11 +4615,6 @@ "symfony/polyfill-php80": "^1.15" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Process\\": "" @@ -4162,8 +4637,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Process Component", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4178,7 +4656,7 @@ "type": "tidelift" } ], - "time": "2020-09-02T16:23:27+00:00" + "time": "2021-01-27T10:15:41+00:00" }, { "name": "symfony/service-contracts", @@ -4240,6 +4718,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/master" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4258,16 +4739,16 @@ }, { "name": "symfony/stopwatch", - "version": "v5.1.7", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323" + "reference": "b12274acfab9d9850c52583d136a24398cdf1a0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/0f7c58cf81dbb5dd67d423a89d577524a2ec0323", - "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b12274acfab9d9850c52583d136a24398cdf1a0c", + "reference": "b12274acfab9d9850c52583d136a24398cdf1a0c", "shasum": "" }, "require": { @@ -4275,11 +4756,6 @@ "symfony/service-contracts": "^1.0|^2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Stopwatch\\": "" @@ -4302,8 +4778,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Stopwatch Component", + "description": "Provides a way to profile code", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4318,20 +4797,20 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2021-01-27T10:15:41+00:00" }, { "name": "symfony/string", - "version": "v5.1.7", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "4a9afe9d07bac506f75bcee8ed3ce76da5a9343e" + "reference": "4e78d7d47061fa183639927ec40d607973699609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/4a9afe9d07bac506f75bcee8ed3ce76da5a9343e", - "reference": "4a9afe9d07bac506f75bcee8ed3ce76da5a9343e", + "url": "https://api.github.com/repos/symfony/string/zipball/4e78d7d47061fa183639927ec40d607973699609", + "reference": "4e78d7d47061fa183639927ec40d607973699609", "shasum": "" }, "require": { @@ -4349,11 +4828,6 @@ "symfony/var-exporter": "^4.4|^5.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\String\\": "" @@ -4379,7 +4853,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony String component", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", "keywords": [ "grapheme", @@ -4389,6 +4863,9 @@ "utf-8", "utf8" ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4403,20 +4880,20 @@ "type": "tidelift" } ], - "time": "2020-09-15T12:23:47+00:00" + "time": "2021-02-16T10:20:28+00:00" }, { "name": "tecnickcom/tcpdf", - "version": "6.3.5", + "version": "6.4.1", "source": { "type": "git", "url": "https://github.com/tecnickcom/TCPDF.git", - "reference": "19a535eaa7fb1c1cac499109deeb1a7a201b4549" + "reference": "5ba838befdb37ef06a16d9f716f35eb03cb1b329" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/19a535eaa7fb1c1cac499109deeb1a7a201b4549", - "reference": "19a535eaa7fb1c1cac499109deeb1a7a201b4549", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/5ba838befdb37ef06a16d9f716f35eb03cb1b329", + "reference": "5ba838befdb37ef06a16d9f716f35eb03cb1b329", "shasum": "" }, "require": { @@ -4465,7 +4942,17 @@ "pdf417", "qrcode" ], - "time": "2020-02-14T14:20:12+00:00" + "support": { + "issues": "https://github.com/tecnickcom/TCPDF/issues", + "source": "https://github.com/tecnickcom/TCPDF/tree/6.4.1" + }, + "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-03-27T16:00:33+00:00" }, { "name": "theseer/tokenizer", @@ -4505,6 +4992,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/master" + }, "funding": [ { "url": "https://github.com/theseer", @@ -4515,30 +5006,35 @@ }, { "name": "webmozart/assert", - "version": "1.9.1", + "version": "1.10.0", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0 || ^8.0", + "php": "^7.2 || ^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<3.9.1" + "vimeo/psalm": "<4.6.1 || 4.6.2" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" + "phpunit/phpunit": "^8.5.13" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -4560,7 +5056,11 @@ "check", "validate" ], - "time": "2020-07-08T17:02:28+00:00" + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" } ], "aliases": [], @@ -4569,15 +5069,15 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.2||^8.0", + "php": "^7.2 || ^8.0", + "ext-simplexml": "*", "ext-ctype": "*", "ext-dom": "*", + "ext-fileinfo": "*", "ext-gd": "*", "ext-iconv": "*", - "ext-fileinfo": "*", "ext-libxml": "*", "ext-mbstring": "*", - "ext-simplexml": "*", "ext-xml": "*", "ext-xmlreader": "*", "ext-xmlwriter": "*", diff --git a/docs/extra/extrajs.js b/docs/extra/extrajs.js new file mode 100644 index 00000000..9ad135e8 --- /dev/null +++ b/docs/extra/extrajs.js @@ -0,0 +1,5 @@ +document.addEventListener("DOMContentLoaded", function() { + document.querySelectorAll("table").forEach(function(table) { + table.classList.add("docutils"); + }); +}); \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index b1207d53..42acedf9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -89,7 +89,7 @@ php vendor/phpoffice/phpspreadsheet/samples/Basic/01_Simple.php ## Learn by documentation -For more in-depth documentation, you may read about an [overview of the +For more documentation in depth, you may read about an [overview of the architecture](./topics/architecture.md), [creating a spreadsheet](./topics/creating-spreadsheet.md), [worksheets](./topics/worksheets.md), diff --git a/docs/topics/images/10-databar-of-conditional-formatting.png b/docs/topics/images/10-databar-of-conditional-formatting.png new file mode 100644 index 00000000..10c88f9f Binary files /dev/null and b/docs/topics/images/10-databar-of-conditional-formatting.png differ diff --git a/docs/topics/reading-and-writing-to-file.md b/docs/topics/reading-and-writing-to-file.md index e1b7e3a2..a9f767aa 100644 --- a/docs/topics/reading-and-writing-to-file.md +++ b/docs/topics/reading-and-writing-to-file.md @@ -580,6 +580,18 @@ $writer->setUseBOM(true); $writer->save("05featuredemo.csv"); ``` +#### Writing CSV files with desired encoding + +It can be set to output with the encoding that can be specified by PHP's mb_convert_encoding. +This looks like the following code: + +```php +$writer = new \PhpOffice\PhpSpreadsheet\Writer\Csv($spreadsheet); +$writer->setUseBOM(false); +$writer->setOutputEncoding('SJIS-WIN'); +$writer->save("05featuredemo.csv"); +``` + #### Decimal and thousands separators If the worksheet you are exporting contains numbers with decimal or diff --git a/docs/topics/recipes.md b/docs/topics/recipes.md index db09642d..bbdc29a8 100644 --- a/docs/topics/recipes.md +++ b/docs/topics/recipes.md @@ -884,6 +884,44 @@ $spreadsheet->getActiveSheet() ); ``` +### DataBar of Conditional formatting +The basics are the same as conditional formatting. +Additional DataBar object to conditional formatting. + +For example, the following code will result in the conditional formatting shown in the image. +```php +$conditional = new Conditional(); +$conditional->setConditionType(Conditional::CONDITION_DATABAR); +$conditional->setDataBar(new ConditionalDataBar()); +$conditional->getDataBar() + ->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject('num', '2')) + ->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject('max')) + ->setColor('FFFF555A'); +$ext = $conditional + ->getDataBar() + ->setConditionalFormattingRuleExt(new ConditionalFormattingRuleExtension()) + ->getConditionalFormattingRuleExt(); + +$ext->setCfRule('dataBar'); +$ext->setSqref('A1:A5'); // target CellCoordinates +$ext->setDataBarExt(new ConditionalDataBarExtension()); +$ext->getDataBarExt() + ->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject('num', '2')) + ->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject('autoMax')) + ->setMinLength(0) + ->setMaxLength(100) + ->setBorder(true) + ->setDirection('rightToLeft') + ->setNegativeBarBorderColorSameAsPositive(false) + ->setBorderColor('FFFF555A') + ->setNegativeFillColor('FFFF0000') + ->setNegativeBorderColor('FFFF0000') + ->setAxisColor('FF000000'); + +``` + +![10-databar-of-conditional-formatting.png](./images/10-databar-of-conditional-formatting.png) + ## Add a comment to a cell To add a comment to a cell, use the following code. The example below diff --git a/mkdocs.yml b/mkdocs.yml index cf87a142..f79acb69 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,3 +5,5 @@ edit_uri: edit/master/docs/ theme: readthedocs extra_css: - extra/extra.css +extra_javascript: + - extra/extrajs.js diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 00000000..ced31605 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,8827 @@ +parameters: + ignoreErrors: + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$returnArrayAsType has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$branchPruningEnabled has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$cellStack has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$cyclicFormulaCell has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$localeFunctions has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$phpSpreadsheetFunctions has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$controlFunctions has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Binary operation \"\\-\" between 0 and string\\|false results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$spreadsheet \\(PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#3 \\$formula of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:translateSeparator\\(\\) expects string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$functionReplaceFromExcel has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$functionReplaceToLocale has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:_translateFormulaToLocale\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:_translateFormulaToLocale\\(\\) has parameter \\$formula with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$str of function preg_quote expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$functionReplaceFromLocale has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$functionReplaceToExcel has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:_translateFormulaToEnglish\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:_translateFormulaToEnglish\\(\\) has parameter \\$formula with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:localeFunc\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:localeFunc\\(\\) has parameter \\$function with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getCell\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 6 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$operatorAssociativity has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$comparisonOperators has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$operatorPrecedence has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Strict comparison using \\=\\=\\= between mixed and null will always evaluate to false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$haystack of function stripos expects string, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:dataTestReference\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:dataTestReference\\(\\) has parameter \\$operandData with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getTitle\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getColumn\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getCoordinate\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getRow\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method has\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$parent of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\:\\:attach\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells, PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method attach\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:validateBinaryOperand\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:validateBinaryOperand\\(\\) has parameter \\$operand with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:validateBinaryOperand\\(\\) has parameter \\$stack with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$pValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:strCaseReverse\\(\\) expects string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:raiseFormulaError\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:raiseFormulaError\\(\\) has parameter \\$errorMessage with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method cellExists\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 4 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#2 \\$pSheet of static method PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\:\\:resolveName\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet, PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getHighestRow\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getHighestColumn\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:getUnusedBranchStoreKey\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:getTokensAsString\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:getTokensAsString\\(\\) has parameter \\$tokens with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DCountA\\:\\:evaluate\\(\\) expects int\\|string, int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DMAX\\(\\) should return float but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DMIN\\(\\) should return float but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DPRODUCT\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSTDEV\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSTDEVP\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSUM\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DVAR\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DVARP\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:evaluate\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:evaluate\\(\\) has parameter \\$criteria with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:evaluate\\(\\) has parameter \\$database with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:evaluate\\(\\) has parameter \\$field with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:buildCondition\\(\\) has parameter \\$criterion with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:processCondition\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTime\\:\\:NETWORKDAYS\\(\\) has parameter \\$dateArgs with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTime.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTime\\:\\:WORKDAY\\(\\) has parameter \\$dateArgs with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTime.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php + + - + message: "#^Parameter \\#1 \\$PHPDateArray of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\DateValue\\:\\:finalResults\\(\\) expects array\\|false, array\\|bool given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php + + - + message: "#^Parameter \\#1 \\$testVal1 of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\Helpers\\:\\:adjustYear\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php + + - + message: "#^Parameter \\#2 \\$testVal2 of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\Helpers\\:\\:adjustYear\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:testStringAsNumeric\\(\\) expects string, float\\|int\\|string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Datefunc.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\Day\\:\\:weirdCondition\\(\\) has parameter \\$dateValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Day.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\Helpers\\:\\:adjustDateByMonths\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\Helpers\\:\\:adjustDateByMonths\\(\\) has parameter \\$adjustmentMonths with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\Helpers\\:\\:adjustDateByMonths\\(\\) has parameter \\$dateValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php + + - + message: "#^Parameter \\#1 \\$excelTimestamp of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:excelToTimestamp\\(\\) expects float\\|int, bool\\|float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\Helpers\\:\\:validateNumericNull\\(\\) should return float\\|int but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\IsoWeekNum\\:\\:apparentBug\\(\\) has parameter \\$dateValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/IsoWeekNum.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\NetworkDays\\:\\:funcNetworkDays\\(\\) has parameter \\$dateArgs with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\NetworkDays\\:\\:applySign\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\Time\\:\\:toIntWithNullBool\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php + + - + message: "#^Cannot access offset 0 on array\\\\|false\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php + + - + message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, array\\\\|false given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\WeekDay\\:\\:validateStyle\\(\\) has parameter \\$style with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekDay.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\WorkDay\\:\\:funcWorkDay\\(\\) has parameter \\$dateArgs with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php + + - + message: "#^Binary operation \"\\-\" between 7 and int\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php + + - + message: "#^Binary operation \"\\-\" between 7 and int\\<5, max\\>\\|string results in an error\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php + + - + message: "#^Binary operation \"\\-\" between 4 and int\\<5, max\\>\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php + + - + message: "#^Binary operation \"\\-\" between int\\|string and int\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php + + - + message: "#^Binary operation \"\\*\" between 100 and int\\|string results in an error\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engine\\\\Logger\\:\\:writeDebugLog\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engine/Logger.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:BITLSHIFT\\(\\) should return int\\|string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:BITRSHIFT\\(\\) should return int\\|string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\BesselJ\\:\\:besselj2a\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\BesselJ\\:\\:besselj2b\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\BesselK\\:\\:besselK2\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/BesselK.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\BitWise\\:\\:validateBitwiseArgument\\(\\) never returns int so it can be removed from the return typehint\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/BitWise.php + + - + message: "#^Parameter \\#1 \\$number of function floor expects float, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/BitWise.php + + - + message: "#^Parameter \\#1 \\$number of function floor expects float, float\\|int\\<0, 281474976710655\\>\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/BitWise.php + + - + message: "#^Parameter \\#1 \\$power of method Complex\\\\Complex\\:\\:pow\\(\\) expects float\\|int, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertBase\\:\\:validateValue\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertBase\\:\\:validatePlaces\\(\\) has parameter \\$places with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertUOM\\:\\:getUOMDetails\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertUOM\\:\\:resolveTemperatureSynonyms\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\Erf\\:\\:\\$twoSqrtPi has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/Erf.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\Erf\\:\\:erfValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/Erf.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\Erf\\:\\:erfValue\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/Erf.php + + - + message: "#^Binary operation \"\\-\" between 1 and float\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/Erf.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ErfC\\:\\:\\$oneSqrtPi has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ErfC.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ErfC\\:\\:erfcValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ErfC.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ErfC\\:\\:erfcValue\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ErfC.php + + - + message: "#^Binary operation \"\\-\" between 2 and float\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ErfC.php + + - + message: "#^Parameter \\#1 \\$callback of function set_error_handler expects \\(callable\\(int, string, string, int, array\\)\\: bool\\)\\|null, array\\('PhpOffice\\\\\\\\PhpSpreadsheet\\\\\\\\Calculation\\\\\\\\Exception', 'errorHandlerCallback'\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/ExceptionHandler.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\:\\:ISPMT\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\:\\:ISPMT\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\:\\:NPV\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:schedulePayment\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has parameter \\$futureValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has parameter \\$numberOfPeriods with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has parameter \\$payment with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has parameter \\$presentValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has parameter \\$rate with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has parameter \\$type with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\InterestAndPrincipal\\:\\:\\$interest has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\InterestAndPrincipal\\:\\:\\$principal has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php + + - + message: "#^Binary operation \"\\-\" between float\\|string and 0\\|float results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:bothNegAndPos\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:bothNegAndPos\\(\\) has parameter \\$neg with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:bothNegAndPos\\(\\) has parameter \\$pos with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xirrPart1\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xirrPart1\\(\\) has parameter \\$dates with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xirrPart1\\(\\) has parameter \\$values with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xirrPart2\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xirrPart2\\(\\) has parameter \\$values with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xirrPart3\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xirrPart3\\(\\) has parameter \\$dates with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xirrPart3\\(\\) has parameter \\$values with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xirrPart3\\(\\) has parameter \\$x1 with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xirrPart3\\(\\) has parameter \\$x2 with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xnpvOrdered\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xnpvOrdered\\(\\) has parameter \\$dates with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xnpvOrdered\\(\\) has parameter \\$ordered with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xnpvOrdered\\(\\) has parameter \\$rate with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xnpvOrdered\\(\\) has parameter \\$values with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:validateXnpv\\(\\) has parameter \\$dates with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:validateXnpv\\(\\) has parameter \\$rate with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:validateXnpv\\(\\) has parameter \\$values with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\Periodic\\:\\:presentValue\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php + + - + message: "#^Binary operation \"\\*\" between float\\|string and int results in an error\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Financial/Coupons.php + + - + message: "#^Binary operation \"\\*\" between float\\|string and int\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Coupons.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Coupons\\:\\:couponFirstPeriodDate\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Coupons.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Coupons\\:\\:couponFirstPeriodDate\\(\\) has parameter \\$maturity with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Coupons.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Coupons\\:\\:couponFirstPeriodDate\\(\\) has parameter \\$next with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Coupons.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Coupons\\:\\:couponFirstPeriodDate\\(\\) has parameter \\$settlement with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Coupons.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Coupons\\:\\:validateCouponPeriod\\(\\) has parameter \\$maturity with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Coupons.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Coupons\\:\\:validateCouponPeriod\\(\\) has parameter \\$settlement with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Coupons.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validateCost\\(\\) has parameter \\$cost with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Depreciation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validateSalvage\\(\\) has parameter \\$salvage with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Depreciation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validateLife\\(\\) has parameter \\$life with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Depreciation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validatePeriod\\(\\) has parameter \\$period with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Depreciation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validateMonth\\(\\) has parameter \\$month with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Depreciation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validateFactor\\(\\) has parameter \\$factor with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Depreciation.php + + - + message: "#^Strict comparison using \\=\\=\\= between string and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Cannot call method setValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#" + count: 5 + path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Cannot call method setTokenSubType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#" + count: 5 + path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Cannot call method getTokenType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#" + count: 9 + path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Cannot call method getTokenSubType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#" + count: 4 + path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:isMatrixValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:isMatrixValue\\(\\) has parameter \\$idx with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:isValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:isValue\\(\\) has parameter \\$idx with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:isCellValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:isCellValue\\(\\) has parameter \\$idx with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:ifCondition\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:ifCondition\\(\\) has parameter \\$condition with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:operandSpecialHandling\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:operandSpecialHandling\\(\\) has parameter \\$operand with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Cannot call method getCell\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Internal\\\\MakeMatrix\\:\\:make\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Internal\\\\WildcardMatch\\:\\:wildcard\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Internal\\\\WildcardMatch\\:\\:compare\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php + + - + message: "#^Call to function is_string\\(\\) with null will always evaluate to false\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/Logical/Operations.php + + - + message: "#^Result of && is always false\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/Logical/Operations.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\:\\:OFFSET\\(\\) should return array\\|string but returns array\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\:\\:CHOOSE\\(\\) has parameter \\$chooseArgs with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef.php + + - + message: "#^Call to function is_bool\\(\\) with float\\|int\\|\\(string&numeric\\) will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef.php + + - + message: "#^Cannot call method getCell\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/LookupRef.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Address\\:\\:sheetName\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Address.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:MATCH\\(\\) should return int\\|string but returns float\\|int\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchFirstValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchFirstValue\\(\\) has parameter \\$lookupArray with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchFirstValue\\(\\) has parameter \\$lookupValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchLargestValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchLargestValue\\(\\) has parameter \\$keySet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchLargestValue\\(\\) has parameter \\$lookupArray with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchLargestValue\\(\\) has parameter \\$lookupValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchSmallestValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchSmallestValue\\(\\) has parameter \\$lookupArray with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchSmallestValue\\(\\) has parameter \\$lookupValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:validateLookupValue\\(\\) has parameter \\$lookupValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:validateMatchType\\(\\) has parameter \\$matchType with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:validateLookupArray\\(\\) has parameter \\$lookupArray with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:prepareLookupArray\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:prepareLookupArray\\(\\) has parameter \\$lookupArray with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:prepareLookupArray\\(\\) has parameter \\$matchType with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Binary operation \"\\-\" between int\\|string and 1 results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\HLookup\\:\\:hLookupSearch\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\HLookup\\:\\:hLookupSearch\\(\\) has parameter \\$column with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\HLookup\\:\\:hLookupSearch\\(\\) has parameter \\$lookupArray with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\HLookup\\:\\:hLookupSearch\\(\\) has parameter \\$lookupValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\HLookup\\:\\:hLookupSearch\\(\\) has parameter \\$notExactMatch with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Indirect\\:\\:extractRequiredCells\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Indirect\\:\\:extractWorksheet\\(\\) has parameter \\$cellAddress with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Lookup\\:\\:verifyResultVector\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Lookup\\:\\:verifyResultVector\\(\\) has parameter \\$resultVector with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\LookupBase\\:\\:validateIndexLookup\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\LookupBase\\:\\:validateIndexLookup\\(\\) has parameter \\$index_number with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\LookupBase\\:\\:validateIndexLookup\\(\\) has parameter \\$lookup_array with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\LookupBase\\:\\:checkMatch\\(\\) has parameter \\$notExactMatch with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php + + - + message: "#^Parameter \\#3 \\$rowNum of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Matrix\\:\\:extractRowValue\\(\\) expects int, float\\|int\\<0, max\\>\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Matrix\\:\\:extractRowValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:extractRequiredCells\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:extractWorksheet\\(\\) has parameter \\$cellAddress with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:adjustEndCellColumnForWidth\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:adjustEndCellColumnForWidth\\(\\) has parameter \\$columns with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:adjustEndCellColumnForWidth\\(\\) has parameter \\$width with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:adustEndCellRowForHeight\\(\\) has parameter \\$endCellRow with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:adustEndCellRowForHeight\\(\\) has parameter \\$height with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:adustEndCellRowForHeight\\(\\) has parameter \\$rows with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Parameter \\#1 \\$pString of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:columnIndexFromString\\(\\) expects string, string\\|null given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php + + - + message: "#^Cannot cast array\\|string to string\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php + + - + message: "#^Parameter \\#1 \\$low of function range expects float\\|int\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php + + - + message: "#^Parameter \\#2 \\$high of function range expects float\\|int\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php + + - + message: "#^Parameter \\#2 \\$cmp_function of function uasort expects callable\\(mixed, mixed\\)\\: int, array\\('self', 'vlookupSort'\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vlookupSort\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vlookupSort\\(\\) has parameter \\$a with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vlookupSort\\(\\) has parameter \\$b with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has parameter \\$column with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has parameter \\$lookupArray with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has parameter \\$lookupValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has parameter \\$notExactMatch with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\:\\:COMBIN\\(\\) should return int\\|string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\:\\:EVEN\\(\\) should return int\\|string but returns float\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\:\\:FACT\\(\\) should return int\\|string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\:\\:FACTDOUBLE\\(\\) should return int\\|string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\:\\:MOD\\(\\) should return int\\|string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\:\\:ODD\\(\\) should return int\\|string but returns float\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\:\\:SUMIFS\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php + + - + message: "#^Parameter \\#1 \\$number of function base_convert expects string, int\\<0, 9007199254740991\\> given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Base.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Helpers\\:\\:validateNumericNullBool\\(\\) should return float\\|int but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Helpers\\:\\:validateNumericNullSubstitution\\(\\) should return float\\|int but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Lcm\\:\\:factors\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Lcm\\:\\:factors\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Roman\\:\\:romanCut\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Roman.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Roman\\:\\:romanCut\\(\\) has parameter \\$n with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Roman.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Roman\\:\\:romanCut\\(\\) has parameter \\$num with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Roman.php + + - + message: "#^Parameter \\#2 \\$precision of function round expects int, float\\|int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/MathTrig/RoundDown.php + + - + message: "#^Parameter \\#2 \\$precision of function round expects int, float\\|int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/MathTrig/RoundUp.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Subtotal\\:\\:filterHiddenArgs\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Subtotal\\:\\:filterHiddenArgs\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Subtotal\\:\\:filterHiddenArgs\\(\\) has parameter \\$cellReference with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Subtotal\\:\\:filterFormulaArgs\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Subtotal\\:\\:filterFormulaArgs\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Subtotal\\:\\:filterFormulaArgs\\(\\) has parameter \\$cellReference with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php + + - + message: "#^Parameter \\#1 \\$function of function call_user_func_array expects callable\\(\\)\\: mixed, array\\('PhpOffice…'\\|'PhpOffice…'\\|'PhpOffice…'\\|'PhpOffice…'\\|'PhpOffice…'\\|'PhpOffice…'\\|'PhpOffice…'\\|'PhpOffice…', string\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php + + - + message: "#^Parameter \\#1 \\$str of function rtrim expects string, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php + + - + message: "#^Binary operation \"\\-\" between float\\|int and float\\|string results in an error\\.$#" + count: 4 + path: src/PhpSpreadsheet/Calculation/Statistical.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:MAXIFS\\(\\) should return float but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:MINIFS\\(\\) should return float but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Averages\\:\\:filterArguments\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Averages.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Averages\\:\\:filterArguments\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Averages.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Averages\\:\\:modeCalc\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Averages.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Averages\\:\\:modeCalc\\(\\) has parameter \\$data with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Averages.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Percentiles\\:\\:percentileFilterValues\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Percentiles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Percentiles\\:\\:rankFilterValues\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Percentiles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Trends\\:\\:checkTrendArrays\\(\\) has parameter \\$array1 with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Trends\\:\\:checkTrendArrays\\(\\) has parameter \\$array2 with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Trends\\:\\:GROWTH\\(\\) should return array\\ but returns array\\\\>\\>\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Trends\\:\\:TREND\\(\\) should return array\\ but returns array\\\\>\\>\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:SUMIF\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:buildConditionSet\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:buildConditionSetForValueRange\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:buildConditions\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:buildDatabase\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:buildDatabaseWithValueRange\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:buildDataSet\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\Beta\\:\\:\\$logBetaCacheP has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\Beta\\:\\:\\$logBetaCacheQ has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\Beta\\:\\:\\$logBetaCacheResult has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php + + - + message: "#^Binary operation \"\\-\" between 1 and float\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:pchisq\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:pchisq\\(\\) has parameter \\$chi2 with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:pchisq\\(\\) has parameter \\$degrees with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gammp\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gammp\\(\\) has parameter \\$n with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gammp\\(\\) has parameter \\$x with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gser\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gser\\(\\) has parameter \\$n with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gser\\(\\) has parameter \\$x with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gcf\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gcf\\(\\) has parameter \\$n with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gcf\\(\\) has parameter \\$x with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\Normal\\:\\:inverseNcdf\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\Normal\\:\\:inverseNcdf\\(\\) has parameter \\$p with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:calculateDistribution\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:calculateInverse\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:\\$logGammaCacheResult has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:\\$logGammaCacheX has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:logGamma1\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:logGamma2\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:logGamma3\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:logGamma4\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\NewtonRaphson\\:\\:\\$callback has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\NewtonRaphson\\:\\:execute\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php + + - + message: "#^Binary operation \"\\-\" between 1 and float\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php + + - + message: "#^Binary operation \"\\-\" between float\\|string and float\\|int\\|\\(string&numeric\\) results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\MaxMinBase\\:\\:datatypeAdjustmentAllowStrings\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\MaxMinBase\\:\\:datatypeAdjustmentAllowStrings\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\VarianceBase\\:\\:datatypeAdjustmentAllowStrings\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\VarianceBase\\:\\:datatypeAdjustmentAllowStrings\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\VarianceBase\\:\\:datatypeAdjustmentBooleans\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\VarianceBase\\:\\:datatypeAdjustmentBooleans\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:TRIMNONPRINTABLE\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:TRIMSPACES\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:CONCATENATE\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:SEARCHSENSITIVE\\(\\) should return string but returns int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:SEARCHINSENSITIVE\\(\\) should return string but returns int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:character\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:unicodeToOrd\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:unicodeToOrd\\(\\) has parameter \\$character with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php + + - + message: "#^Cannot access offset 1 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php + + - + message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:convertBooleanValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:convertBooleanValue\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Concatenate\\:\\:CONCATENATE\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Concatenate.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Concatenate\\:\\:convertBooleanValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Concatenate.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Concatenate\\:\\:convertBooleanValue\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Concatenate.php + + - + message: "#^Parameter \\#3 \\$length of function mb_substr expects int\\|null, float\\|int\\<0, max\\>\\|string given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/TextData/Extract.php + + - + message: "#^Parameter \\#2 \\$start of function mb_substr expects int, float\\|int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/TextData/Extract.php + + - + message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Mround\\:\\:funcMround\\(\\) expects float, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Format.php + + - + message: "#^Parameter \\#1 \\$number of function floor expects float, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Format.php + + - + message: "#^Parameter \\#1 \\$number of function round expects float, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Format.php + + - + message: "#^Cannot cast array\\|float\\|int\\|string to float\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Format.php + + - + message: "#^Parameter \\#3 \\$offset of function mb_strpos expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Search.php + + - + message: "#^Parameter \\#3 \\$offset of function mb_stripos expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Search.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Trim\\:\\:\\$invalidChars has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Trim.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, float\\|int\\|string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/TextData/Trim.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Trim.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has parameter \\$onlyIf with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has parameter \\$onlyIfNot with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has parameter \\$reference with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has parameter \\$storeKey with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has parameter \\$type with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\:\\:\\$formulaAttributes has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Elseif branch is unreachable because previous condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Parameter \\#2 \\$format of static method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\:\\:toFormattedString\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\:\\:getFormulaAttributes\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Parameter \\#2 \\$str of function explode expects string, array\\\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Parameter \\#4 \\$currentRow of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:validateRange\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Parameter \\#5 \\$endRow of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:validateRange\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Call to an undefined method object\\:\\:getHashCode\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Parameter \\#1 \\$input of function array_chunk expects array, array\\\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Parameter \\#1 \\$pValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:substring\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/DataType.php + + - + message: "#^Parameter \\#2 \\$alpha of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowColor\\(\\) expects int, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Parameter \\#1 \\$blur of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowBlur\\(\\) expects float, float\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Parameter \\#1 \\$angle of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowAngle\\(\\) expects int, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Parameter \\#1 \\$distance of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowDistance\\(\\) expects float, float\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$title \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$legend \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$xAxisLabel \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$yAxisLabel \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$plotArea \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\PlotArea\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\PlotArea\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$xAxis \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$yAxis \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$majorGridlines \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$minorGridlines \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$worksheet \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setTopLeftXOffset\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setTopLeftXOffset\\(\\) has parameter \\$xOffset with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:getTopLeftXOffset\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setTopLeftYOffset\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setTopLeftYOffset\\(\\) has parameter \\$yOffset with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:getTopLeftYOffset\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightCell\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightCell\\(\\) has parameter \\$cell with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightXOffset\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightXOffset\\(\\) has parameter \\$xOffset with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:getBottomRightXOffset\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightYOffset\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightYOffset\\(\\) has parameter \\$yOffset with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:getBottomRightYOffset\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues and null will always evaluate to false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Chart/DataSeries.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:\\$dataTypeValues has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:\\$dataSource \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:\\$fillColor \\(array\\\\|string\\) does not accept array\\\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:refresh\\(\\) has parameter \\$flatten with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:\\$objectState has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:\\$lineProperties has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:\\$shadowProperties has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:\\$glowProperties has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:\\$softEdges has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Parameter \\#1 \\$color of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setGlowColor\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Parameter \\#2 \\$alpha of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setGlowColor\\(\\) expects int, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Parameter \\#3 \\$type of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setGlowColor\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Parameter \\#1 \\$blur of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setShadowBlur\\(\\) expects float, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Parameter \\#1 \\$angle of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setShadowAngle\\(\\) expects int, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Parameter \\#1 \\$distance of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setShadowDistance\\(\\) expects float, float\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend\\:\\:\\$positionXLref has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Legend.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend\\:\\:\\$layout \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Legend.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\PlotArea\\:\\:\\$layout \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/PlotArea.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getTrueAlpha\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getTrueAlpha\\(\\) has parameter \\$alpha with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:setColorProperties\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:setColorProperties\\(\\) has parameter \\$alpha with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:setColorProperties\\(\\) has parameter \\$color with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:setColorProperties\\(\\) has parameter \\$type with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getLineStyleArrowSize\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getLineStyleArrowSize\\(\\) has parameter \\$array_kay_selector with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getLineStyleArrowSize\\(\\) has parameter \\$array_selector with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getShadowPresetsMap\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getShadowPresetsMap\\(\\) has parameter \\$shadow_presets_option with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getArrayElementsValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getArrayElementsValue\\(\\) has parameter \\$elements with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getArrayElementsValue\\(\\) has parameter \\$properties with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$width has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$height has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$colourSet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$markSet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$chart has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$graph has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$plotColour has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$plotMark has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatPointMarker\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatPointMarker\\(\\) has parameter \\$markerID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatPointMarker\\(\\) has parameter \\$seriesPlot with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatDataSetLabels\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatDataSetLabels\\(\\) has parameter \\$datasetLabels with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatDataSetLabels\\(\\) has parameter \\$groupID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatDataSetLabels\\(\\) has parameter \\$labelCount with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatDataSetLabels\\(\\) has parameter \\$rotation with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:percentageSumCalculation\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:percentageSumCalculation\\(\\) has parameter \\$groupID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:percentageSumCalculation\\(\\) has parameter \\$seriesCount with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:percentageAdjustValues\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:percentageAdjustValues\\(\\) has parameter \\$dataValues with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:percentageAdjustValues\\(\\) has parameter \\$sumValues with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:getCaption\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:getCaption\\(\\) has parameter \\$captionElement with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderCartesianPlotArea\\(\\) has parameter \\$type with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotLine\\(\\) has parameter \\$combination with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotLine\\(\\) has parameter \\$dimensions with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotLine\\(\\) has parameter \\$filled with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotLine\\(\\) has parameter \\$groupID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotBar\\(\\) has parameter \\$dimensions with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotBar\\(\\) has parameter \\$groupID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotScatter\\(\\) has parameter \\$bubble with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotScatter\\(\\) has parameter \\$groupID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotRadar\\(\\) has parameter \\$groupID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotContour\\(\\) has parameter \\$groupID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotStock\\(\\) has parameter \\$groupID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderAreaChart\\(\\) has parameter \\$dimensions with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderAreaChart\\(\\) has parameter \\$groupCount with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderLineChart\\(\\) has parameter \\$dimensions with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderLineChart\\(\\) has parameter \\$groupCount with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderBarChart\\(\\) has parameter \\$dimensions with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderBarChart\\(\\) has parameter \\$groupCount with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderScatterChart\\(\\) has parameter \\$groupCount with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderBubbleChart\\(\\) has parameter \\$groupCount with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPieChart\\(\\) has parameter \\$dimensions with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPieChart\\(\\) has parameter \\$doughnut with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPieChart\\(\\) has parameter \\$groupCount with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPieChart\\(\\) has parameter \\$multiplePlots with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderRadarChart\\(\\) has parameter \\$groupCount with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderStockChart\\(\\) has parameter \\$groupCount with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderContourChart\\(\\) has parameter \\$dimensions with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderContourChart\\(\\) has parameter \\$groupCount with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderCombinationChart\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderCombinationChart\\(\\) has parameter \\$dimensions with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderCombinationChart\\(\\) has parameter \\$groupCount with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderCombinationChart\\(\\) has parameter \\$outputDestination with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\:\\:\\$layout \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Title.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\:\\:getParent\\(\\) should return PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet but returns PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Collection/Cells.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Collection/Cells.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\:\\:getCurrentCoordinate\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Collection/Cells.php + + - + message: "#^Parameter \\#1 \\$str of function sscanf expects string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Collection/Cells.php + + - + message: "#^Parameter \\#1 \\$columnIndex of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:stringFromColumnIndex\\(\\) expects int, int\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Collection/Cells.php + + - + message: "#^Possibly invalid array key type \\(array\\|string\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Collection/Cells.php + + - + message: "#^Cannot call method detach\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Collection/Cells.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Memory\\:\\:\\$cache has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Collection/Memory.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\:\\:\\$worksheet \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/DefinedName.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\:\\:\\$scope \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/DefinedName.php + + - + message: "#^Parameter \\#1 \\$namedRange of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:addNamedRange\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\NamedRange, \\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/DefinedName.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:\\$created \\(int\\) does not accept int\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Document/Properties.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:\\$modified \\(int\\) does not accept int\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Document/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:identifyPropertyType\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Document/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:identifyPropertyType\\(\\) has parameter \\$propertyValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Document/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:convertProperty\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Document/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:convertProperty\\(\\) has parameter \\$propertyValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Document/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\DocumentGenerator\\:\\:getPhpSpreadsheetFunctionText\\(\\) has parameter \\$functionCall with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/DocumentGenerator.php + + - + message: "#^Cannot access offset 0 on \\(int\\|string\\)\\.$#" + count: 2 + path: src/PhpSpreadsheet/DocumentGenerator.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\HashTable\\:\\:getIndexForHashCode\\(\\) should return int but returns int\\|string\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/HashTable.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$colourMap has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$face has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$size has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$color has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$bold has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$italic has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$underline has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$superscript has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$subscript has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$strikethrough has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$startTagCallbacks has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$endTagCallbacks has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$stack has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$stringData has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Parameter \\#1 \\$text of method PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\ITextElement\\:\\:setText\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setName\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setSize\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setItalic\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setUnderline\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setSuperscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setSubscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setStrikethrough\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:rgbToColour\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:rgbToColour\\(\\) has parameter \\$rgb with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:startFontTag\\(\\) has parameter \\$tag with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Parameter \\#1 \\$function of function call_user_func expects callable\\(\\)\\: mixed, array\\(\\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\), mixed\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Parameter \\#1 \\$directory of class RecursiveDirectoryIterator constructor expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Parameter \\#1 \\$path of function pathinfo expects string, array\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Sample\\:\\:getSamples\\(\\) should return array\\\\> but returns array\\\\>\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Parameter \\#1 \\$filename of function unlink expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Sample\\:\\:log\\(\\) has parameter \\$message with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\IOFactory\\:\\:\\$readers has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/IOFactory.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\IOFactory\\:\\:\\$writers has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/IOFactory.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\BaseReader\\:\\:\\$fileHandle has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/BaseReader.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\BaseReader\\:\\:getSecurityScanner\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/BaseReader.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Csv\\:\\:\\$delimiter \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv.php + + - + message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, array\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Csv\\:\\:openFileOrMemory\\(\\) has parameter \\$pFilename with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:convertEncoding\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv.php + + - + message: "#^Parameter \\#1 \\$fp of function fwrite expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv.php + + - + message: "#^Argument of an invalid type array\\|null supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv.php + + - + message: "#^Parameter \\#2 \\$newvalue of function ini_set expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv.php + + - + message: "#^Call to function is_array\\(\\) with string will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Csv\\\\Delimiter\\:\\:\\$fileHandle has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Csv\\\\Delimiter\\:\\:\\$escapeCharacter has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Csv\\\\Delimiter\\:\\:\\$enclosure has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Csv\\\\Delimiter\\:\\:\\$counts has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Csv\\\\Delimiter\\:\\:\\$numberLines has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Csv\\\\Delimiter\\:\\:\\$delimiter has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Csv\\\\Delimiter\\:\\:__construct\\(\\) has parameter \\$enclosure with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Csv\\\\Delimiter\\:\\:__construct\\(\\) has parameter \\$escapeCharacter with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Csv\\\\Delimiter\\:\\:__construct\\(\\) has parameter \\$fileHandle with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Csv\\\\Delimiter\\:\\:getNextLine\\(\\) should return string\\|false but returns string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:\\$referenceHelper has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Parameter \\#1 \\$fp of function fread expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Parameter \\#1 \\$fp of function fclose expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:\\$mappings has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Offset 'No' does not exist on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Offset 'Unit' does not exist on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Cannot call method setWidth\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Cannot call method setVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Offset 'DefaultSizePts' does not exist on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Cannot call method setRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Cannot call method setVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseBorderAttributes\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseBorderAttributes\\(\\) has parameter \\$borderAttributes with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseRichText\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseRichText\\(\\) has parameter \\$is with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseGnumericColour\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseGnumericColour\\(\\) has parameter \\$gnmColour with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:\\$rowspan has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:readBeginning\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:readEnding\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Parameter \\#2 \\$length of function fread expects int, int\\\\|int\\<1, 2048\\>\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:startsWithTag\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:startsWithTag\\(\\) has parameter \\$data with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:endsWithTag\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:endsWithTag\\(\\) has parameter \\$data with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:containsTags\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:containsTags\\(\\) has parameter \\$data with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:\\$dataArray has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:\\$tableLevel has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:\\$nestedColumn has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:setTableStartColumn\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:setTableStartColumn\\(\\) has parameter \\$column with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:getTableStartColumn\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:releaseTableStartColumn\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:flushCell\\(\\) has parameter \\$cellContent with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:flushCell\\(\\) has parameter \\$column with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:flushCell\\(\\) has parameter \\$row with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Argument of an invalid type DOMNamedNodeMap\\|null supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:\\$spanEtc has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:\\$h1Etc has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Cannot call method setRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 4 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Cannot call method setWidth\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:setBorderStyle\\(\\) expects string, string\\|null given\\.$#" + count: 5 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Parameter \\#1 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Alignment\\:\\:setHorizontal\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Parameter \\#1 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Alignment\\:\\:setVertical\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:getStyleColor\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:\\$borderMappings has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Cannot call method getNamespaces\\(\\) on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Cannot call method children\\(\\) on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:listWorksheetNames\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Cannot call method getElementsByTagNameNS\\(\\) on DOMElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#1 \\$element of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:scanElementForText\\(\\) expects DOMNode, DOMElement\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Cannot call method getAttributeNS\\(\\) on DOMElement\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#1 \\$settings of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:lookForActiveSheet\\(\\) expects DOMElement, DOMElement\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#1 \\$settings of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:lookForSelectedCells\\(\\) expects DOMElement, DOMElement\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Cannot call method setSelectedCellByColumnAndRow\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Cannot call method getNamedItem\\(\\) on DOMNamedNodeMap\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" + count: 7 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:convertToExcelAddressValue\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#3 \\$formula of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:translateSeparator\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$officeNs has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$stylesNs has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$stylesFo has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$pageLayoutStyles has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$masterStylesCrossReference has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$masterPrintStylesCrossReference has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Cannot call method getElementsByTagNameNS\\(\\) on DOMElement\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:\\$spreadsheet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:load\\(\\) has parameter \\$namespacesMeta with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Parameter \\#1 \\$timestamp of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setCreated\\(\\) expects int\\|string\\|null, int\\|false given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Parameter \\#1 \\$timestamp of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setModified\\(\\) expects int\\|string\\|null, int\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:setMetaProperties\\(\\) has parameter \\$namespacesMeta with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:setMetaProperties\\(\\) has parameter \\$propertyName with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:setUserDefinedProperty\\(\\) has parameter \\$propertyValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:setUserDefinedProperty\\(\\) has parameter \\$propertyValueAttributes with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:\\$callback has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:\\$libxmlDisableEntityLoaderValue has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:__construct\\(\\) has parameter \\$pattern with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:getInstance\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:threadSafeLibxmlDisableEntityLoaderAvailability\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:toUtf8\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:toUtf8\\(\\) has parameter \\$xml with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, array\\\\|string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Parameter \\#1 \\$haystack of function substr_count expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Slk.php + + - + message: "#^Parameter \\#2 \\$str of function explode expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Slk.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Slk\\:\\:\\$colorArray has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Slk.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Slk\\:\\:\\$fontStyleMappings has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Slk.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Slk\\:\\:\\$styleSettingsFont has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Slk.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Slk\\:\\:\\$styleSettingsBorder has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Slk.php + + - + message: "#^Parameter \\#1 \\$columnIndex of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:stringFromColumnIndex\\(\\) expects int, string given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Slk.php + + - + message: "#^Cannot call method setWidth\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Slk.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:getDgContainer\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getNestingLevel\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getStartCoordinates\\(\\)\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getEndCoordinates\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getStartOffsetX\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getStartOffsetY\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getEndOffsetX\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getEndOffsetY\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#2 \\$startRow of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Xls\\:\\:getDistanceY\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#4 \\$endRow of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Xls\\:\\:getDistanceY\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#2 \\$row of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Xls\\:\\:sizeRow\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getOPT\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:getDggContainer\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\MemoryDrawing\\:\\:setImageResource\\(\\) expects GdImage\\|resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\BaseDrawing\\:\\:setOffsetX\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\BaseDrawing\\:\\:setOffsetY\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#2 \\$row of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\IReadFilter\\:\\:readCell\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$block of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:makeKey\\(\\) expects int, float given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:\\$data \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:\\$summaryInformation \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:\\$documentSummaryInformation \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$codePage of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\CodePage\\:\\:numberToName\\(\\) expects int, int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$title of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setTitle\\(\\) expects string, int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$subject of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setSubject\\(\\) expects string, int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$creator of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setCreator\\(\\) expects string, int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$keywords of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setKeywords\\(\\) expects string, int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$description of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setDescription\\(\\) expects string, int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$modifier of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setLastModifiedBy\\(\\) expects string, int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$codePage of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\CodePage\\:\\:numberToName\\(\\) expects int, bool\\|int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$category of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setCategory\\(\\) expects string, bool\\|int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$manager of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setManager\\(\\) expects string, bool\\|int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$company of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setCompany\\(\\) expects string, bool\\|int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#2 \\$pos of static method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:getUInt2d\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#2 \\$pos of static method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:getInt4d\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#2 \\$start of function substr expects int, float\\|int given\\.$#" + count: 5 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:setShowSummaryBelow\\(\\) expects bool, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:setShowSummaryRight\\(\\) expects bool, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot call method setWidth\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot call method setVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot call method setOutlineLevel\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot call method setCollapsed\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot call method setXfIndex\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot call method setRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot call method setOutlineLevel\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot call method setCollapsed\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot call method setVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot call method setXfIndex\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:includeCellRangeFiltered\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:includeCellRangeFiltered\\(\\) has parameter \\$cellRangeAddress with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataValidation\\:\\:setType\\(\\) expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataValidation\\:\\:setErrorStyle\\(\\) expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataValidation\\:\\:setOperator\\(\\) expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 8 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot access offset 1 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:parseRichText\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:parseRichText\\(\\) has parameter \\$is with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\Color\\\\BIFF5\\:\\:\\$map has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\Color\\\\BIFF8\\:\\:\\$map has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\Color\\\\BuiltIn\\:\\:\\$map has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\ErrorCode\\:\\:\\$map has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/ErrorCode.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setDggContainer\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setBstoreContainer\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:addBSE\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setBlip\\(\\)\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setDgContainer\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:addChild\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:addChild\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setStartCoordinates\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setStartOffsetX\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setStartOffsetY\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setEndCoordinates\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setEndOffsetX\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setEndOffsetY\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setOPT\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Parameter \\#1 \\$input of function array_values expects array, array\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\MD5\\:\\:f\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\MD5\\:\\:g\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\MD5\\:\\:h\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\MD5\\:\\:i\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\MD5\\:\\:step\\(\\) has parameter \\$func with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\MD5\\:\\:rotate\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\RC4\\:\\:\\$s has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/RC4.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\RC4\\:\\:\\$i has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/RC4.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\RC4\\:\\:\\$j has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/RC4.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\Style\\\\Border\\:\\:\\$map has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Style/Border.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\Style\\\\FillPattern\\:\\:\\$map has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php + + - + message: "#^Cannot access property \\$Relationship on SimpleXMLElement\\|false\\.$#" + count: 12 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot access property \\$sheets on SimpleXMLElement\\|false\\.$#" + count: 6 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method registerXPathNamespace\\(\\) on SimpleXMLElement\\|false\\.$#" + count: 4 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToBoolean\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToBoolean\\(\\) has parameter \\$c with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToError\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToError\\(\\) has parameter \\$c with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToString\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToString\\(\\) has parameter \\$c with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$c with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$calculatedValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$castBaseType with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$cellDataType with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$r with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$sharedFormulas with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$value with no typehint 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: "#^Offset 'name' does not exist on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method xpath\\(\\) on SimpleXMLElement\\|false\\.$#" + count: 4 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$is of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:parseRichText\\(\\) expects SimpleXMLElement\\|null, object given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$styleXml of class PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles constructor expects SimpleXMLElement, SimpleXMLElement\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot access property \\$workbookPr on SimpleXMLElement\\|false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#2 \\$xmlWorkbook of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readProtection\\(\\) expects SimpleXMLElement, SimpleXMLElement\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$relsWorksheet of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Hyperlinks\\:\\:readHyperlinks\\(\\) expects SimpleXMLElement, SimpleXMLElement\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot access property \\$authors on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot access property \\$commentList on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Negated boolean expression is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot access property \\$drawing on SimpleXMLElement\\|false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method children\\(\\) on SimpleXMLElement\\|false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method count\\(\\) on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method asXML\\(\\) on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot access property \\$definedNames on SimpleXMLElement\\|false\\.$#" + count: 4 + path: src/PhpSpreadsheet/Reader/Xlsx.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: "#^Parameter \\#1 \\$pName of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getSheetByName\\(\\) expects string, array\\|string given\\.$#" + 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 \\$bookViews on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot access property \\$Default on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot access property \\$Override on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$chartElements of static method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readChart\\(\\) expects SimpleXMLElement, SimpleXMLElement\\|false given\\.$#" + count: 1 + 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: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readColor\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readColor\\(\\) has parameter \\$background with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readColor\\(\\) has parameter \\$color with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$hex of static method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\:\\:changeBrightness\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\:\\:setSize\\(\\) expects float, string given\\.$#" + 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 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 setColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|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 setItalic\\(\\) 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 setSubscript\\(\\) 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: "#^Cannot call method setStrikethrough\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:getArrayItem\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:getArrayItem\\(\\) has parameter \\$array with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:getArrayItem\\(\\) has parameter \\$key with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:dirAdd\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:dirAdd\\(\\) has parameter \\$add with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:dirAdd\\(\\) has parameter \\$base with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:toCSSArray\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:toCSSArray\\(\\) has parameter \\$style with no typehint specified\\.$#" + 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 \\#3 \\$subject of function str_replace expects array\\|string, int\\|string given\\.$#" + count: 2 + 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 \\$sizeInCm of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:centimeterSizeToPixels\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:stripWhiteSpaceFromStyleString\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:stripWhiteSpaceFromStyleString\\(\\) has parameter \\$string with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:boolean\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:boolean\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Offset 'id' does not exist on SimpleXMLElement\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readFormControlProperties\\(\\) has parameter \\$dir with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readFormControlProperties\\(\\) has parameter \\$docSheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readFormControlProperties\\(\\) has parameter \\$fileWorksheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readPrinterSettings\\(\\) has parameter \\$dir with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readPrinterSettings\\(\\) has parameter \\$docSheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readPrinterSettings\\(\\) has parameter \\$fileWorksheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\AutoFilter\\:\\:\\$worksheet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\AutoFilter\\:\\:\\$worksheetXml has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\AutoFilter\\:\\:readAutoFilter\\(\\) has parameter \\$autoFilterRange with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\AutoFilter\\:\\:readAutoFilter\\(\\) has parameter \\$xmlSheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php + + - + message: "#^Parameter \\#1 \\$pOperator of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\\\Rule\\:\\:setRule\\(\\) expects string, null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\BaseParserClass\\:\\:boolean\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\BaseParserClass\\:\\:boolean\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readColor\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readColor\\(\\) has parameter \\$background with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readColor\\(\\) has parameter \\$color with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$position of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend constructor expects string, bool\\|float\\|int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#3 \\$overlay of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend constructor expects bool, bool\\|float\\|int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#6 \\$displayBlanksAs of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart constructor expects string, bool\\|float\\|int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartTitle\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartLayoutDetails\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartLayoutDetails\\(\\) has parameter \\$chartDetail with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartLayoutDetails\\(\\) has parameter \\$namespacesChartMeta with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeries\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeries\\(\\) has parameter \\$chartDetail with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeries\\(\\) has parameter \\$namespacesChartMeta with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeries\\(\\) has parameter \\$plotType with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#3 \\$plotOrder of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeries constructor expects array\\, array\\ given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#7 \\$plotDirection of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeries constructor expects string\\|null, bool\\|float\\|int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValueSet\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValueSet\\(\\) has parameter \\$marker with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValueSet\\(\\) has parameter \\$namespacesChartMeta with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValueSet\\(\\) has parameter \\$seriesDetail with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#4 \\$pointCount of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues constructor expects int, null given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValues\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValues\\(\\) has parameter \\$dataType with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValues\\(\\) has parameter \\$seriesValueSet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValuesMultiLevel\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValuesMultiLevel\\(\\) has parameter \\$dataType with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValuesMultiLevel\\(\\) has parameter \\$seriesValueSet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:parseRichText\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method getFont\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\Run\\|null\\.$#" + count: 12 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setName\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setSize\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setItalic\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setSuperscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setSubscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setUnderline\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setStrikethrough\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readChartAttributes\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readChartAttributes\\(\\) has parameter \\$chartDetail with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:\\$worksheet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:\\$worksheetXml has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredColumn\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredColumn\\(\\) has parameter \\$columnCoordinate with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readColumnAttributes\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readColumnAttributes\\(\\) has parameter \\$readDataOnly with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readColumnRangeAttributes\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readColumnRangeAttributes\\(\\) has parameter \\$readDataOnly with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredRow\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredRow\\(\\) has parameter \\$rowCoordinate with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readRowAttributes\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readRowAttributes\\(\\) has parameter \\$readDataOnly with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:\\$worksheet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:\\$worksheetXml has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:\\$dxfs has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readConditionalStyles\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readConditionalStyles\\(\\) has parameter \\$xmlSheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:setConditionalStyles\\(\\) has parameter \\$xmlExtLst with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readStyleRules\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readStyleRules\\(\\) has parameter \\$cfRules with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readStyleRules\\(\\) has parameter \\$extLst with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readDataBarOfConditionalRule\\(\\) has parameter \\$cfRule with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readDataBarOfConditionalRule\\(\\) has parameter \\$conditionalFormattingRuleExtensions with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readDataBarExtLstOfConditionalRule\\(\\) has parameter \\$cfRule with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readDataBarExtLstOfConditionalRule\\(\\) has parameter \\$conditionalFormattingRuleExtensions with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\DataValidations\\:\\:\\$worksheet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\DataValidations\\:\\:\\$worksheetXml has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Hyperlinks\\:\\:\\$worksheet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Hyperlinks\\:\\:\\$hyperlinks has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\PageSetup\\:\\:\\$worksheet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\PageSetup\\:\\:\\$worksheetXml has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\PageSetup\\:\\:load\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\PageSetup\\:\\:pageSetup\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:\\$securityScanner has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:\\$docProps has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:extractPropertyData\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:extractPropertyData\\(\\) has parameter \\$propertyData with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:readCoreProperties\\(\\) has parameter \\$propertyData with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php + + - + message: "#^Call to an undefined method object\\:\\:registerXPathNamespace\\(\\)\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php + + - + message: "#^Call to an undefined method object\\:\\:xpath\\(\\)\\.$#" + count: 9 + path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:readExtendedProperties\\(\\) has parameter \\$propertyData with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:readCustomProperties\\(\\) has parameter \\$propertyData with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php + + - + message: "#^Argument of an invalid type object supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:getArrayItem\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Properties\\:\\:getArrayItem\\(\\) has parameter \\$key with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Properties.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\SheetViewOptions\\:\\:\\$worksheet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\SheetViewOptions\\:\\:\\$worksheetXml has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\SheetViews\\:\\:\\$sheetViewXml has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\SheetViews\\:\\:\\$worksheet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:\\$styles has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:\\$cellStyles has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:\\$styleXml has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:setStyleBaseData\\(\\) has parameter \\$cellStyles with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:setStyleBaseData\\(\\) has parameter \\$styles with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Static property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:\\$theme \\(PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Theme\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Theme\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Cannot call method count\\(\\) on SimpleXMLElement\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readStyle\\(\\) has parameter \\$style with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Parameter \\#2 \\$alignmentXml of static method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readAlignmentStyle\\(\\) expects SimpleXMLElement, object given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readProtectionLocked\\(\\) has parameter \\$style with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readProtectionHidden\\(\\) has parameter \\$style with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readColor\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readColor\\(\\) has parameter \\$background with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readColor\\(\\) has parameter \\$color with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Parameter \\#1 \\$hex of static method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\:\\:changeBrightness\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:dxfs\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:dxfs\\(\\) has parameter \\$readDataOnly with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:styles\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:getArrayItem\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:getArrayItem\\(\\) has parameter \\$array with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:getArrayItem\\(\\) has parameter \\$key with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:\\$fileContents has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:\\$mappings has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:convertEncoding\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Cannot call method getNamespaces\\(\\) on SimpleXMLElement\\|false\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Cannot call method children\\(\\) on SimpleXMLElement\\|false\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:identifyFixedStyleValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:identifyFixedStyleValue\\(\\) has parameter \\$styleAttributeValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:identifyFixedStyleValue\\(\\) has parameter \\$styleList with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:hex2str\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:hex2str\\(\\) has parameter \\$hex with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Cannot access property \\$DocumentProperties on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Parameter \\#1 \\$timestamp of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setCreated\\(\\) expects int\\|string\\|null, int\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Parameter \\#1 \\$timestamp of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setModified\\(\\) expects int\\|string\\|null, int\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Parameter \\#2 \\$callback of function preg_replace_callback expects callable\\(\\)\\: mixed, array\\('self', 'hex2str'\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Parameter \\#1 \\$xml of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:parseStyles\\(\\) expects SimpleXMLElement, SimpleXMLElement\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Cannot call method setWidth\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Cannot call method setRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Cannot access property \\$Names on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:parseRichText\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:parseRichText\\(\\) has parameter \\$is with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:\\$borderPositions has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:parseStyleBorders\\(\\) has parameter \\$styleID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:\\$underlineStyles has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:parseStyleInterior\\(\\) has parameter \\$styleID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:parseStyleNumberFormat\\(\\) has parameter \\$styleID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Parameter \\#2 \\$cmp_function of function uksort expects callable\\(mixed, mixed\\)\\: int, array\\('self', 'cellReverseSort'\\) given\\.$#" + count: 4 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#2 \\$cmp_function of function uksort expects callable\\(mixed, mixed\\)\\: int, array\\('self', 'cellSort'\\) given\\.$#" + count: 4 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#2 \\$pPassword of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:protectCells\\(\\) expects string, array given\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#1 \\$index of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\:\\:setRowIndex\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Cannot call method getRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Cannot call method setRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Cannot call method getVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Cannot call method setVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Cannot call method getOutlineLevel\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Cannot call method setOutlineLevel\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Cannot call method getCollapsed\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Cannot call method setCollapsed\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Cannot call method setXfIndex\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#1 \\$columnIndex of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:stringFromColumnIndex\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#1 \\$pCellRange of method PhpOffice\\\\PhpSpreadsheet\\\\ReferenceHelper\\:\\:updateCellReference\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\Run\\:\\:\\$font \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/RichText/Run.php + + - + message: "#^Result of && is always false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Settings.php + + - + message: "#^Strict comparison using \\=\\=\\= between int and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Settings.php + + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Settings.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:getHttpClient\\(\\) should return Psr\\\\Http\\\\Client\\\\ClientInterface but returns Psr\\\\Http\\\\Client\\\\ClientInterface\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Settings.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:getRequestFactory\\(\\) should return Psr\\\\Http\\\\Message\\\\RequestFactoryInterface but returns Psr\\\\Http\\\\Message\\\\RequestFactoryInterface\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Settings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\CodePage\\:\\:\\$pageArray has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/CodePage.php + + - + message: "#^Parameter \\#1 \\$dateValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:timestampToExcel\\(\\) expects int, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Date.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/Date.php + + - + message: "#^Parameter \\#1 \\$pFormatCode of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:isDateTimeFormatCode\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Date.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:\\$possibleDateFormatCharacters has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Date.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Drawing\\:\\:pixelsToCellDimension\\(\\) should return int but returns float\\|int\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#1 \\$fp of function fread expects resource, resource\\|false given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#1 \\$fp of function feof expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Cannot access offset 1 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#1 \\$x_size of function imagecreatetruecolor expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#2 \\$y_size of function imagecreatetruecolor expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#1 \\$im of function imagecolorallocate expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#2 \\$red of function imagecolorallocate expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#3 \\$green of function imagecolorallocate expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#4 \\$blue of function imagecolorallocate expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#1 \\$im of function imagesetpixel expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#3 \\$y of function imagesetpixel expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#4 \\$col of function imagesetpixel expects int, int\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Drawing\\:\\:imagecreatefrombmp\\(\\) should return GdImage\\|resource but returns resource\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:\\$spgrContainer has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:getDgId\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:setDgId\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:getLastSpId\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:setLastSpId\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:getSpgrContainer\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:setSpgrContainer\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:setSpgrContainer\\(\\) has parameter \\$spgrContainer with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\:\\:getChildren\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php + + - + message: "#^Strict comparison using \\=\\=\\= between string\\|false and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/File.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\File\\:\\:realpath\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/File.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\File\\:\\:sysGetTempDir\\(\\) should return string but returns string\\|false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/File.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:\\$autoSizeMethods has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.php + + - + message: "#^Parameter \\#1 \\$pValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Drawing\\:\\:pixelsToCellDimension\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.php + + - + message: "#^Parameter \\#2 \\$pDefaultFont 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: "#^Parameter \\#1 \\$size of function imagettfbbox expects float, float\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.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: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\EigenvalueDecomposition\\:\\:\\$e has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\EigenvalueDecomposition\\:\\:\\$cdivi has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php + + - + message: "#^Else branch is unreachable because previous condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\LUDecomposition\\:\\:getDoublePivot\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:__construct\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 19 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:getMatrix\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:plus\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:plusEquals\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.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: "#^Result of && is always false\\.$#" + count: 10 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:minus\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:minusEquals\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:arrayTimes\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:arrayTimesEquals\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:arrayRightDivide\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Parameter \\#3 \\$c of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:set\\(\\) expects float\\|int\\|null, string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:arrayRightDivideEquals\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:arrayLeftDivide\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:arrayLeftDivideEquals\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:times\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:power\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:concat\\(\\) has parameter \\$args with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Left side of && is always true\\.$#" + count: 4 + path: src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php + + - + message: "#^If condition is always true\\.$#" + count: 7 + path: src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\:\\:getStream\\(\\) should return resource but returns resource\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#1 \\$time_1st of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#2 \\$time_2nd of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#1 \\$No of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#2 \\$name of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects string, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#3 \\$type of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#4 \\$prev of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#5 \\$next of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#6 \\$dir of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#7 \\$time_1st of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#8 \\$time_2nd of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#9 \\$data of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects string, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#1 \\$oleTimestamp of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\:\\:OLE2LocalDate\\(\\) expects string, string\\|false given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\:\\:getData\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Cannot access offset 3 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Cannot access offset 4 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Cannot access offset 1 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Cannot access offset 2 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php + + - + message: "#^Parameter \\#3 \\$length of function array_slice expects int\\|null, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS.php + + - + message: "#^Parameter \\#2 \\$offset of function array_slice expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS.php + + - + message: "#^Parameter \\#1 \\$No of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php + + - + message: "#^Parameter \\#4 \\$prev of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php + + - + message: "#^Parameter \\#5 \\$next of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php + + - + message: "#^Parameter \\#6 \\$dir of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php + + - + message: "#^Parameter \\#7 \\$time_1st of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php + + - + message: "#^Parameter \\#8 \\$time_2nd of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php + + - + message: "#^Parameter \\#1 \\$No of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#4 \\$prev of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#5 \\$next of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#6 \\$dir of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#9 \\$data of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects string, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#1 \\$iSBDcnt of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveHeader\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#2 \\$iBBcnt of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveHeader\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#3 \\$iPPScnt of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveHeader\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#1 \\$iStBlk of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveBigData\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#1 \\$iSbdSize of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveBbd\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#2 \\$iBsize of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveBbd\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#3 \\$iPpsCnt of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveBbd\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLERead\\:\\:\\$data has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLERead\\:\\:\\$wrkbook has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLERead\\:\\:\\$summaryInformation has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLERead\\:\\:\\$documentSummaryInformation has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Parameter \\#1 \\$data of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLERead\\:\\:getInt4d\\(\\) expects string, string\\|false given\\.$#" + count: 8 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Strict comparison using \\=\\=\\= between int and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/PasswordHasher.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:sanitizeUTF8\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:formatNumber\\(\\) should return string but returns array\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:countCharacters\\(\\) should return int but returns int\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:mbIsUpper\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:mbIsUpper\\(\\) has parameter \\$char with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:mbStrSplit\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:mbStrSplit\\(\\) has parameter \\$string with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Parameter \\#1 \\$string of function strlen expects string, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Else branch is unreachable because previous condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/TimeZone.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$goodnessOfFit has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$stdevOfResiduals has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$covariance has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$correlation has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$SSRegression has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$SSResiduals has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$DFResiduals has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$f has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$slope has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$slopeSE has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$intersect has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$intersectSE has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$xOffset has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$yOffset has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:getError\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:getBestFitType\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$const with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$meanX with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$meanY with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$sumX with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$sumX2 with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$sumXY with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$sumY with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$sumY2 with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:sumSquares\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\PolynomialBestFit\\:\\:getCoefficients\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\PolynomialBestFit\\:\\:getCoefficients\\(\\) has parameter \\$dp with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php + + - + message: "#^Parameter \\#2 \\.\\.\\.\\$args of function array_merge expects array, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\Trend\\:\\:calculate\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\Trend\\:\\:calculate\\(\\) has parameter \\$const with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\Trend\\:\\:calculate\\(\\) has parameter \\$trendType with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\Trend\\:\\:calculate\\(\\) has parameter \\$xValues with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\Trend\\:\\:calculate\\(\\) has parameter \\$yValues with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Parameter \\#1 \\$order of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\PolynomialBestFit constructor expects int, string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\XMLWriter\\:\\:\\$debugEnabled has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/XMLWriter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\XMLWriter\\:\\:\\$tempFileName \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/XMLWriter.php + + - + message: "#^Parameter \\#1 \\$uri of method XMLWriter\\:\\:openUri\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/XMLWriter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\XMLWriter\\:\\:getData\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/XMLWriter.php + + - + message: "#^Parameter \\#1 \\$pValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Drawing\\:\\:pointsToPixels\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Xls.php + + - + message: "#^Parameter \\#1 \\$fontSizeInPoints of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:fontSizeToPixels\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:\\$workbookViewVisibilityValues has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Call to function is_array\\(\\) with string will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + 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: "#^Parameter \\#1 \\$pSheet of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getIndex\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet, PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Cannot call method getTitle\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + 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: "#^Comparison operation \"\\<\\=\" between int\\ and 1000 is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Result of \\|\\| is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Alignment.php + + - + message: "#^Parameter \\#1 \\$parent of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Supervisor\\:\\:bindParent\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style, \\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Border\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Border.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Border.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getStyleArray\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Border.php + + - + message: "#^Right side of && is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Border.php + + - + message: "#^Parameter \\#1 \\$parent of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Supervisor\\:\\:bindParent\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style, \\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Borders\\) given\\.$#" + count: 10 + path: src/PhpSpreadsheet/Style/Borders.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Borders.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Color.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Border\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Fill\\:\\:getEndColor\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Color.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Border\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Fill\\:\\:getStartColor\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Color.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Border\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Fill\\:\\:getColor\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Color.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getStyleArray\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Color.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\:\\:getColourComponent\\(\\) should return int\\|string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Color.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:\\$condition \\(array\\\\) does not accept array\\\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Conditional.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:\\$style \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\:\\:setShowValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\:\\:setMinimumConditionalFormatValueObject\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\:\\:setMaximumConditionalFormatValueObject\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\:\\:setConditionalFormattingRuleExt\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBarExtension\\:\\:getXmlAttributes\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBarExtension\\:\\:getXmlElements\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBarExtension\\:\\:setMaximumConditionalFormatValueObject\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBarExtension\\:\\:setMinimumConditionalFormatValueObject\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:\\$type has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:\\$value has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:\\$cellFormula has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:__construct\\(\\) has parameter \\$type with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:__construct\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:setType\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:setValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:setCellFormula\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormattingRuleExtension\\:\\:\\$id has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormattingRuleExtension\\:\\:__construct\\(\\) has parameter \\$id with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormattingRuleExtension\\:\\:generateUuid\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormattingRuleExtension\\:\\:parseExtLstXml\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormattingRuleExtension\\:\\:parseExtLstXml\\(\\) has parameter \\$extLstXml with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$minLength on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$maxLength on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$border on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$gradient on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$direction on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$negativeBarBorderColorSameAsPositive on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$axisPosition on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormattingRuleExtension\\:\\:parseExtDataBarElementChildrenFromXml\\(\\) has parameter \\$ns with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Offset 'rgb' does not exist on SimpleXMLElement\\|null\\.$#" + count: 4 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Offset 'theme' does not exist on SimpleXMLElement\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Offset 'tint' does not exist on SimpleXMLElement\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Parameter \\#1 \\$parent of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Supervisor\\:\\:bindParent\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style, \\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Fill\\) given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/Fill.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Fill.php + + - + message: "#^Parameter \\#1 \\$parent of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Supervisor\\:\\:bindParent\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style, \\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Font.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Font.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\:\\:\\$builtInFormatCode \\(int\\|false\\) does not accept bool\\|int\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\DateFormatter\\:\\:format\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#2 \\$callback of function preg_replace_callback expects callable\\(\\)\\: mixed, array\\('self', 'setLowercaseCallback'\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace_callback expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#2 \\$str of function explode expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#2 \\$replace of function str_replace expects array\\|string, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#2 \\$callback of function preg_replace_callback expects callable\\(\\)\\: mixed, array\\('self', 'escapeQuotesCallback'\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#1 \\$format of method DateTime\\:\\:format\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\DateFormatter\\:\\:setLowercaseCallback\\(\\) has parameter \\$matches with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\DateFormatter\\:\\:escapeQuotesCallback\\(\\) has parameter \\$matches with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$cond with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$dfcond with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$dfval with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$val with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormat\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormat\\(\\) has parameter \\$sections with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormat\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:toFormattedString\\(\\) should return string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_split expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\FractionFormatter\\:\\:format\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\NumberFormatter\\:\\:mergeComplexNumberFormatMasks\\(\\) has parameter \\$masks with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\NumberFormatter\\:\\:mergeComplexNumberFormatMasks\\(\\) has parameter \\$numbers with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\NumberFormatter\\:\\:processComplexNumberFormatMask\\(\\) has parameter \\$mask with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\NumberFormatter\\:\\:processComplexNumberFormatMask\\(\\) has parameter \\$number with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\NumberFormatter\\:\\:complexNumberFormatMask\\(\\) has parameter \\$mask with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\NumberFormatter\\:\\:complexNumberFormatMask\\(\\) has parameter \\$number with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\NumberFormatter\\:\\:complexNumberFormatMask\\(\\) has parameter \\$splitOnPoint with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Parameter \\#2 \\$str of function explode expects string, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\NumberFormatter\\:\\:formatStraightNumericValue\\(\\) has parameter \\$format with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\NumberFormatter\\:\\:formatStraightNumericValue\\(\\) has parameter \\$useThousands with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\NumberFormatter\\:\\:formatStraightNumericValue\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\NumberFormatter\\:\\:format\\(\\) has parameter \\$format with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\NumberFormatter\\:\\:format\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, array\\|string\\|null given\\.$#" + count: 6 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, array\\|string\\|null given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Parameter \\#2 \\$format of static method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\FractionFormatter\\:\\:format\\(\\) expects string, array\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\PercentageFormatter\\:\\:format\\(\\) has parameter \\$value with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php + + - + message: "#^Parameter \\#1 \\$format of function sprintf expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Protection.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getCellXfByIndex\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Style.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getParent\\(\\) should return PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet but returns PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Style.php + + - + message: "#^Cannot call method getXfIndex\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/Style.php + + - + message: "#^Cannot call method setXfIndex\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Style.php + + - + message: "#^Cannot call method getXfIndex\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/Style/Style.php + + - + message: "#^Cannot call method setXfIndex\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Style.php + + - + message: "#^Cannot call method getXfIndex\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/Style.php + + - + message: "#^Cannot call method setXfIndex\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Style.php + + - + message: "#^Result of && is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#1 \\$excelTimestamp of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:excelToTimestamp\\(\\) expects float\\|int, float\\|int\\|string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#1 \\$number of function floor expects float, float\\|int\\<1, max\\>\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:\\$toReplace has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#2 \\$now of function strtotime expects int, int\\|false given\\.$#" + count: 6 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#5 \\$day of function gmmktime expects int, string given\\.$#" + count: 8 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#6 \\$year of function gmmktime expects int, string given\\.$#" + count: 13 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#4 \\$mon of function gmmktime expects int, string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#5 \\$day of function gmmktime expects int, float given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#1 \\$attributes of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\:\\:setAttributes\\(\\) expects array\\, array\\ given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:calculateTopTenValue\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:calculateTopTenValue\\(\\) has parameter \\$columnID with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:calculateTopTenValue\\(\\) has parameter \\$endRow with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:calculateTopTenValue\\(\\) has parameter \\$ruleType with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:calculateTopTenValue\\(\\) has parameter \\$ruleValue with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:calculateTopTenValue\\(\\) has parameter \\$startRow with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot call method rangeToArray\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot call method getRowDimension\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot call method setVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot assign offset 'year' to array\\\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot assign offset 'month' to array\\\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot assign offset 'day' to array\\\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot assign offset 'hour' to array\\\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot assign offset 'minute' to array\\\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot assign offset 'second' to array\\\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#1 \\$str of function preg_quote expects string, array\\\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot call method getCell\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Binary operation \"\\*\" between 0\\|array\\\\|string and float\\|int results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Left side of && is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#1 \\$function of function call_user_func_array expects callable\\(\\)\\: mixed, array\\('PhpOffice\\\\\\\\PhpSpreadsheet\\\\\\\\Worksheet\\\\\\\\AutoFilter', mixed\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\\\Rule\\:\\:\\$ruleTypes has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\\\Rule\\:\\:\\$dateTimeGroups has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\\\Rule\\:\\:\\$dynamicTypes has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\\\Rule\\:\\:\\$operators has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\\\Rule\\:\\:\\$topTenValue has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\\\Rule\\:\\:\\$topTenType has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\\\Rule\\:\\:\\$parent \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php + + - + message: "#^Cannot call method getCell\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/BaseDrawing.php + + - + message: "#^Cannot call method getDrawingCollection\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/BaseDrawing.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\BaseDrawing\\:\\:\\$shadow \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Drawing\\\\Shadow\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Drawing\\\\Shadow\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/BaseDrawing.php + + - + message: "#^Cannot call method getHashCode\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/BaseDrawing.php + + - + message: "#^Class PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\CellIterator implements generic interface Iterator but does not specify its types\\: TKey, TValue$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/CellIterator.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\CellIterator\\:\\:adjustForExistingOnlyRange\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/CellIterator.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Column\\:\\:\\$parent \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Column.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\CellIterator\\:\\:\\$worksheet \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php + + - + message: "#^Class PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnIterator implements generic interface Iterator but does not specify its types\\: TKey, TValue$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/ColumnIterator.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Drawing\\\\Shadow\\:\\:\\$color \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php + + - + message: "#^Class PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Iterator implements generic interface Iterator but does not specify its types\\: TKey, TValue$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Iterator.php + + - + message: "#^Parameter \\#1 \\$im of function imagesx expects resource, GdImage\\|resource given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/MemoryDrawing.php + + - + message: "#^Parameter \\#1 \\$im of function imagesy expects resource, GdImage\\|resource given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/MemoryDrawing.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\PageSetup\\:\\:\\$pageOrder has no typehint 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: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\PageSetup\\:\\:getPrintArea\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/PageSetup.php + + - + message: "#^Parameter \\#2 \\$str of function explode expects string, string\\|null given\\.$#" + count: 5 + 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\\\\Row\\:\\:\\$worksheet \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Row.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\CellIterator\\:\\:\\$worksheet \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/RowCellIterator.php + + - + message: "#^Class PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowIterator implements generic interface Iterator but does not specify its types\\: TKey, TValue$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/RowIterator.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\SheetView\\:\\:\\$sheetViewTypes has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/SheetView.php + + - + message: "#^Strict comparison using \\=\\=\\= between int\\ and null will always evaluate to false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/SheetView.php + + - + message: "#^Strict comparison using \\=\\=\\= between string and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/SheetView.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:\\$drawingCollection with generic class ArrayObject does not specify its types\\: TKey, TValue$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:\\$chartCollection with generic class ArrayObject does not specify its types\\: TKey, TValue$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:\\$parent \\(PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$pIndex of class PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$pIndex of class PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension constructor expects string, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$pRange of class PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter constructor expects string, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getDrawingCollection\\(\\) return type with generic class ArrayObject does not specify its types\\: TKey, TValue$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getChartCollection\\(\\) return type with generic class ArrayObject does not specify its types\\: TKey, TValue$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$input of function array_splice expects array, ArrayObject&iterable\\ given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Strict comparison using \\=\\=\\= between string and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$pRange of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:rangeDimension\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#2 \\$format of static method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\:\\:toFormattedString\\(\\) expects string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#3 \\$rotation of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:calculateColumnWidth\\(\\) expects int, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method setWidth\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^If condition is always true\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Left side of && is always true\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method renameCalculationCacheForWorksheet\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method setValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method setValueExplicit\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#2 \\$start of function substr expects int, int\\<0, max\\>\\|false given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$pRow of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getRowDimension\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Result of && is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$pRange of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:setRange\\(\\) expects string, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getFreezePane\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$row of method PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\:\\:removeRow\\(\\) expects string, int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$coord of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:coordinateIsRange\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$pRange of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:splitRange\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:\\$activeCell \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:\\$selectedCells \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method getValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 4 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method getCalculatedValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method getXfIndex\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Right side of && is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method getHashCode\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method getWorksheet\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method getValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method rangeToArray\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Csv\\:\\:\\$enclosureRequired has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Csv.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: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:getSheetIndex\\(\\) should return int but returns int\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateMeta\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateMeta\\(\\) has parameter \\$desc with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateMeta\\(\\) has parameter \\$val with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetPrep\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetStarts\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetStarts\\(\\) has parameter \\$rowMin with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetStarts\\(\\) has parameter \\$sheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has parameter \\$row with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has parameter \\$tbodyStart with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has parameter \\$theadEnd with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has parameter \\$theadStart with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#1 \\$string of function htmlspecialchars expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Cannot access offset 'mime' on array\\|false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#1 \\$im of function imagepng expects resource, GdImage\\|resource given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#1 \\$str of function base64_encode expects string, string\\|false given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Ternary operator condition is always true\\.$#" + 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: "#^Parameter \\#2 \\$length of function fread expects int, int\\|false given\\.$#" + count: 1 + 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: "#^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 \\#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 \\$borderStyle of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:mapBorderStyle\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateHTMLFooter\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTagInline\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTagInline\\(\\) has parameter \\$id with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTagInline\\(\\) has parameter \\$pSheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTag\\(\\) has parameter \\$html with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTag\\(\\) has parameter \\$id with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTag\\(\\) has parameter \\$pSheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTag\\(\\) has parameter \\$sheetIndex with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableFooter\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has parameter \\$cellAddress with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has parameter \\$colNum with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has parameter \\$pRow with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has parameter \\$pSheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValueRich\\(\\) has parameter \\$cell with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValueRich\\(\\) has parameter \\$cellData with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#1 \\$pStyle 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: "#^Cannot call method getSuperscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + 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: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValue\\(\\) has parameter \\$cell with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValue\\(\\) has parameter \\$cellData with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValue\\(\\) has parameter \\$pSheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has parameter \\$cell with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has parameter \\$cellType with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has parameter \\$cssClass with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has parameter \\$pSheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowIncludeCharts\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowIncludeCharts\\(\\) has parameter \\$coordinate with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowIncludeCharts\\(\\) has parameter \\$pSheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowSpans\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowSpans\\(\\) has parameter \\$colSpan with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowSpans\\(\\) has parameter \\$html with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowSpans\\(\\) has parameter \\$rowSpan with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$cellData with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$cellType with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$colNum with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$colSpan with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$coordinate with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$cssClass with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$html with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$pRow with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$pSheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$rowSpan with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$sheetIndex with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:calculateSpansOmitRows\\(\\) has parameter \\$candidateSpannedRow with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:calculateSpansOmitRows\\(\\) has parameter \\$sheet with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:calculateSpansOmitRows\\(\\) has parameter \\$sheetIndex with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\IWriter\\:\\:save\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/IWriter.php + + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\Cell\\\\Style\\:\\:\\$writer has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/Cell/Style.php + + - + message: "#^If condition is always true\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Ods/Cell/Style.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\Content\\:\\:\\$formulaConvertor has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/Content.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Writer/Ods/Content.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<2, max\\> given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Ods/Content.php + + - + message: "#^Parameter \\#1 \\$pRange of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:splitRange\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/Content.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\Formula\\:\\:\\$definedNames has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/Formula.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\NamedExpressions\\:\\:\\$objWriter has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\NamedExpressions\\:\\:\\$spreadsheet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\NamedExpressions\\:\\:\\$formulaConvertor has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\NamedExpressions\\:\\:__construct\\(\\) has parameter \\$formulaConvertor with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php + + - + message: "#^Cannot call method getTitle\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php + + - + message: "#^Parameter \\#1 \\$content of method XMLWriter\\:\\:text\\(\\) expects string, int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Ods/Settings.php + + - + message: "#^Strict comparison using \\=\\=\\= between int and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Pdf/Dompdf.php + + - + message: "#^Parameter \\#2 \\$str of function fwrite expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Pdf/Dompdf.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and int will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Pdf/Mpdf.php + + - + message: "#^Strict comparison using \\=\\=\\= between int and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php + + - + message: "#^Cannot call method getHashCode\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Parameter \\#1 \\$font of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:addFont\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Offset 'startCoordinates' does not exist on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Offset 'startOffsetX' does not exist on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Offset 'startOffsetY' does not exist on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Offset 'endCoordinates' does not exist on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Offset 'endOffsetX' does not exist on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Offset 'endOffsetY' does not exist on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Parameter \\#1 \\$data of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\\\Blip\\:\\:setData\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Parameter \\#1 \\$im of function imagepng expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Parameter \\#1 \\$im of function imagepng expects resource, GdImage\\|resource given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Parameter \\#1 \\$blipType of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setBlipType\\(\\) expects int, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Parameter \\#2 \\$pad_length of function str_pad expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\BIFFwriter\\:\\:writeEof\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Escher\\:\\:\\$object has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Escher.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Escher\\:\\:\\$data has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Escher.php + + - + message: "#^If condition is always true\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xls/Escher.php + + - + message: "#^Elseif condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Escher.php + + - + message: "#^Parameter \\#1 \\$name of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:getCharsetFromFontName\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Font.php + + - + message: "#^If condition is always false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Font.php + + - + message: "#^Parameter \\#1 \\$bold of static method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Font\\:\\:mapBold\\(\\) expects bool, bool\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Font.php + + - + message: "#^Parameter \\#1 \\$underline of static method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Font\\:\\:mapUnderline\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Font.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:UTF8toBIFF8UnicodeShort\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Font.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:\\$spreadsheet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 5 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:advance\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Offset 'left' does not exist on \\(array&nonEmpty\\)\\|string\\.$#" + count: 6 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Offset 'right' does not exist on \\(array&nonEmpty\\)\\|string\\.$#" + count: 5 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Offset 'value' does not exist on \\(array&nonEmpty\\)\\|string\\.$#" + count: 7 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:\\$colors has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Cannot call method getTitle\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:writeAllDefinedNamesBiff8\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Parameter \\#1 \\$pSheet of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getIndex\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet, PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:writeSupbookInternal\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:writeExternalsheetBiff8\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Cannot access offset 'encoding' on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:writeMsoDrawingGroup\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:\\$escher \\(PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:\\$colors has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$height of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:writeRow\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#4 \\$hidden of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:writeRow\\(\\) expects bool, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Cannot call method getHashCode\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#4 \\$isError of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:writeBoolErr\\(\\) expects bool, int given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$pieces of function implode expects array, array\\\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match_all expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$hexadecimal_number of function hexdec expects string, array given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$coordinates of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:indexesFromString\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:\\$activePane \\(int\\) does not accept int\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#5 \\$width of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:positionImage\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#6 \\$height of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:positionImage\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$im of function imagesx expects resource, GdImage\\|resource given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$im of function imagesy expects resource, GdImage\\|resource given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$im of function imagecolorat expects resource, GdImage\\|resource given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$im of function imagecolorsforindex expects resource, GdImage\\|resource given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$col of function imagecolorsforindex expects int, int\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$ascii of function chr expects int, float given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$length of function fread expects int, int\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$string of function strlen expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Cannot access offset 'ident' on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Cannot access offset 'sa' on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Cannot access offset 1 on array\\|false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Cannot access offset 2 on array\\|false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Cannot access offset 'comp' on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:\\$escher \\(PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:UTF8toBIFF8UnicodeLong\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$fillType of static method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Xf\\:\\:mapFillType\\(\\) expects string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Xf.php + + - + message: "#^Parameter \\#1 \\$hAlign of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Xf\\:\\:mapHAlign\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Xf.php + + - + message: "#^Parameter \\#1 \\$vAlign of static method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Xf\\:\\:mapVAlign\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Xf.php + + - + message: "#^Parameter \\#1 \\$textRotation of static method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Xf\\:\\:mapTextRotation\\(\\) expects int, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Xf.php + + - + message: "#^Possibly invalid array key type array\\|string\\|null\\.$#" + 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: "#^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 \\$function of function call_user_func expects callable\\(\\)\\: mixed, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\:\\:\\$pathNames has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:\\$calculateCellValues has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" + count: 45 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Call to function is_array\\(\\) with string will always evaluate to false\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Result of && is always false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\PlotArea and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Argument of an invalid type array\\|string supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$yAxisLabel of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#4 \\$id1 of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects string, int\\|string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#5 \\$id2 of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects string, int\\|string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#7 \\$xAxis of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#8 \\$majorGridlines of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#9 \\$minorGridlines of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$xAxisLabel of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeCategoryAxis\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#3 \\$id1 of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeCategoryAxis\\(\\) expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#4 \\$id2 of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeCategoryAxis\\(\\) expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#6 \\$yAxis of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeCategoryAxis\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Part \\$xAxis\\-\\>getShadowProperty\\('effect'\\) \\(array\\|int\\|string\\|null\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, array\\|int\\|string given\\.$#" + count: 8 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Part \\$xAxis\\-\\>getShadowProperty\\(\\['color', 'type'\\]\\) \\(array\\|int\\|string\\|null\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, array\\|int\\|string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Else branch is unreachable because previous condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:getChartType\\(\\) never returns string so it can be removed from the return typehint\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeries and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Cannot call method getFillColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Cannot call method getDataValues\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$plotSeriesValues of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeBubbles\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues and null will always evaluate to false\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$text of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\XMLWriter\\:\\:writeRawData\\(\\) expects array\\\\|string, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, float\\|int given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, float given\\.$#" + count: 6 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.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: "#^Parameter \\#1 \\$arr1 of function array_diff expects array, array\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php + + - + message: "#^Cannot access offset 2 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\DefinedNames\\:\\:\\$objWriter has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\DefinedNames\\:\\:\\$spreadsheet has no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php + + - + message: "#^Cannot call method getTitle\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + 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: "#^Parameter \\#1 \\$index of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getChartByIndex\\(\\) expects string, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php + + - + message: "#^Parameter \\#2 \\$pChart of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Drawing\\:\\:writeChart\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php + + - + message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, int given\\.$#" + count: 12 + path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" + count: 8 + path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<0, max\\> given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php + + - + message: "#^Parameter \\#4 \\$pTarget of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeRelationship\\(\\) expects string, array\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php + + - + message: "#^Parameter \\#2 \\$pId of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeRelationship\\(\\) expects int, string given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeUnparsedRelationship\\(\\) has parameter \\$relationship with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeUnparsedRelationship\\(\\) has parameter \\$type with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeDrawingHyperLink\\(\\) has parameter \\$i with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeDrawingHyperLink\\(\\) has parameter \\$objWriter with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php + + - + message: "#^Parameter \\#2 \\$pId of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeRelationship\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeDrawingHyperLink\\(\\) should return int but returns float\\|int\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Rels.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: "#^Instanceof between string and PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\RichText will always evaluate to false\\.$#" + 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: "#^Cannot call method getName\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + 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 getBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Cannot call method getItalic\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + 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 getStrikethrough\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + 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 getSize\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + 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: "#^Cannot call method getUnderline\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Parameter \\#1 \\$pText 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, int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.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 \\$pNumberFormat 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 \\$pFont 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 \\$pFill 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 \\$pBorders 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 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<0, max\\> given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Cannot call method getStyle\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, string\\|null given\\.$#" + count: 4 + 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: "#^Comparison operation \"\\<\" between int\\ and 0 is always true\\.$#" + 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, int\\<1, max\\> given\\.$#" + count: 2 + 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: "#^Parameter \\#3 \\$pStringTable of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeSheetData\\(\\) expects array\\, array\\\\|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: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" + count: 19 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<1, max\\> given\\.$#" + count: 7 + 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\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeAttributeIf\\(\\) has parameter \\$condition with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeElementIf\\(\\) has parameter \\$condition with no typehint specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#3 \\$namespace of method XMLWriter\\:\\:startElementNs\\(\\) expects string, null given\\.$#" + count: 8 + 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\\:\\:writeDataBarElements\\(\\) has parameter \\$dataBar with no typehint specified\\.$#" + 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: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, array given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, array\\\\|string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Argument of an invalid type array\\\\|string supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Cannot call method getCollapsed\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Cannot call method getOutlineLevel\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Cannot call method getRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Cannot call method getVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Cannot call method getXfIndex\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 3 + 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: "#^Right side of && is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Xlfn\\:\\:addXlfn\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php + + - + message: "#^Cannot call method setVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SubTotalTest.php + + - + message: "#^Cannot call method setVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SubTotalTest.php + + - + message: "#^Cannot call method setAutoSize\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/XlfnFunctionsTest.php + + - + message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertSame\\(\\) with arguments PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell, null and 'should get exact…' will always evaluate to false\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Collection/CellsTest.php + + - + message: "#^Cannot call method getParent\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Collection/CellsTest.php + + - + message: "#^Cannot call method getCoordinate\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Collection/CellsTest.php + + - + message: "#^Parameter \\#1 \\$row of method PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\:\\:getHighestColumn\\(\\) expects string\\|null, int given\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Collection/CellsTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Functional\\\\ColumnWidthTest\\:\\:testReadColumnWidth\\(\\) has parameter \\$format with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/ColumnWidthTest.php + + - + message: "#^Cannot call method setWidth\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/ColumnWidthTest.php + + - + message: "#^Cannot call method getWidth\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/ColumnWidthTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Functional\\\\CommentsTest\\:\\:testComments\\(\\) has parameter \\$format with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/CommentsTest.php + + - + message: "#^Parameter \\#1 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:addCondition\\(\\) expects string, float given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Functional/ConditionalStopIfTrueTest.php + + - + message: "#^Cannot call method setAutoSize\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/ConditionalTextTest.php + + - + message: "#^Parameter \\#1 \\$im of function imagecolorallocate expects resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php + + - + message: "#^Parameter \\#1 \\$im of function imagestring expects resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php + + - + message: "#^Parameter \\#6 \\$col of function imagestring expects int, int\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\MemoryDrawing\\:\\:setImageResource\\(\\) expects GdImage\\|resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php + + - + message: "#^Cannot call method getUrl\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Hyperlink\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php + + - + message: "#^Cannot call method getPageSetup\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php + + - + message: "#^Cannot access offset 'size' on array\\(0 \\=\\> int, 1 \\=\\> int, 2 \\=\\> int, 3 \\=\\> int, 4 \\=\\> int, 5 \\=\\> int, 6 \\=\\> int, 7 \\=\\> int, \\.\\.\\.\\)\\|false\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Functional/StreamTest.php + + - + message: "#^Parameter \\#1 \\$fp of function fstat expects resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/StreamTest.php + + - + message: "#^Parameter \\#1 \\$pFilename of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\IWriter\\:\\:save\\(\\) expects resource\\|string, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/StreamTest.php + + - + message: "#^Parameter \\#1 \\$expected of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) expects class\\-string\\, string given\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/IOFactoryTest.php + + - + message: "#^Cannot call method getValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\NamedFormula\\|null\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/NamedFormulaTest.php + + - + message: "#^Cannot call method getValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\NamedRange\\|null\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/NamedRangeTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\CsvContiguousFilter\\:\\:setFilterType\\(\\) has parameter \\$type with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/CsvContiguousFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\CsvContiguousFilter\\:\\:filter1\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/CsvContiguousFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\CsvContiguousFilter\\:\\:filter1\\(\\) has parameter \\$row with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/CsvContiguousFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\CsvContiguousFilter\\:\\:filter0\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/CsvContiguousFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\CsvContiguousFilter\\:\\:filter0\\(\\) has parameter \\$row with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/CsvContiguousFilter.php + + - + message: "#^Cannot call method getCell\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Reader/CsvContiguousTest.php + + - + message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertNull\\(\\) with string will always evaluate to false\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/CsvTest.php + + - + message: "#^Cannot call method getVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php + + - + message: "#^Cannot call method getRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Reader/Html/HtmlTagsTest.php + + - + message: "#^Cannot call method getWidth\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php + + - + message: "#^Cannot call method getRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Ods/OdsTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Security\\\\XmlScannerTest\\:\\:testValidXML\\(\\) has parameter \\$libxmlDisableEntityLoader with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php + + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Security\\\\XmlScannerTest\\:\\:testInvalidXML\\(\\) has parameter \\$libxmlDisableEntityLoader with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php + + - + message: "#^Cannot call method getFormattedValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/XlsTest.php + + - + message: "#^Cannot call method getCoordinate\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/XlsTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xlsx\\\\AutoFilterTest\\:\\:getWorksheetInstance\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xlsx\\\\AutoFilterTest\\:\\:getXMLInstance\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xlsx\\\\AutoFilterTest\\:\\:getXMLInstance\\(\\) has parameter \\$ref with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xlsx\\\\AutoFilterTest\\:\\:getAutoFilterInstance\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php + + - + message: "#^Function PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\getTitleText\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php + + - + message: "#^Function PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\getTitleText\\(\\) has parameter \\$title with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php + + - + message: "#^Cannot call method setMinimumConditionalFormatValueObject\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php + + - + message: "#^Cannot call method getMinimumConditionalFormatValueObject\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" + count: 13 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php + + - + message: "#^Cannot call method getMaximumConditionalFormatValueObject\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" + count: 13 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php + + - + message: "#^Cannot call method getColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php + + - + message: "#^Cannot call method getConditionalFormattingRuleExt\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" + count: 8 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php + + - + message: "#^Cannot call method getShowValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php + + - + message: "#^Cannot call method getRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/XlsxTest.php + + - + message: "#^Cannot call method getVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/XlsxTest.php + + - + message: "#^Cannot call method getWidth\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/XlsxTest.php + + - + message: "#^Cannot call method getVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/XlsxTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\XlsxTest\\:\\:testStripsWhiteSpaceFromStyleString\\(\\) has parameter \\$string with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/XlsxTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xml\\\\XmlTest\\:\\:testInvalidSimpleXML\\(\\) has parameter \\$filename with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xml/XmlTest.php + + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xml/XmlTest.php + + - + message: "#^Parameter \\#1 \\$timeZone of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:setDefaultTimezone\\(\\) expects DateTimeZone\\|string, DateTimeZone\\|null given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/DateTest.php + + - + message: "#^Parameter \\#1 \\$pValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:setCurrencyCode\\(\\) expects string, null given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/StringHelperTest.php + + - + message: "#^Parameter \\#1 \\$timeZone of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:setDefaultTimezone\\(\\) expects DateTimeZone\\|string, DateTimeZone\\|null given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php + + - + message: "#^Cannot call method getTimestamp\\(\\) on DateTime\\|false\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php + + - + message: "#^Parameter \\#1 \\$timezone of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\TimeZone\\:\\:getTimeZoneAdjustment\\(\\) expects string, null given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\SpreadsheetTest\\:\\:testGetSheetByName\\(\\) has parameter \\$index with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/SpreadsheetTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\SpreadsheetTest\\:\\:testGetSheetByName\\(\\) has parameter \\$sheetName with no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/SpreadsheetTest.php + + - + message: "#^Parameter \\#1 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:addCondition\\(\\) expects string, float given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Style/ConditionalTest.php + + - + message: "#^Parameter \\#1 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:setConditions\\(\\) expects array\\\\|bool\\|float\\|int\\|string, array\\ given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Style/ConditionalTest.php + + - + message: "#^Parameter \\#1 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\\\Rule\\:\\:setValue\\(\\) expects array\\\\|string, int given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/AutoFilter/Column/RuleTest.php + + - + message: "#^Parameter \\#1 \\$attributes of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\:\\:setAttributes\\(\\) expects array\\, array\\ given\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Worksheet/AutoFilter/ColumnTest.php + + - + message: "#^Parameter \\#2 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\:\\:setAttribute\\(\\) expects string, int given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/AutoFilter/ColumnTest.php + + - + message: "#^Parameter \\#1 \\$pColumn of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:setColumn\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\|string, float given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php + + - + message: "#^Parameter \\#1 \\$im of function imagecolorallocate expects resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php + + - + message: "#^Parameter \\#1 \\$im of function imagestring expects resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php + + - + message: "#^Parameter \\#6 \\$col of function imagestring expects int, int\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\MemoryDrawing\\:\\:setImageResource\\(\\) expects GdImage\\|resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php + + - + message: "#^Parameter \\#2 \\$rowIndex of class PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowCellIterator constructor expects int, string given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/RowCellIterator2Test.php + + - + message: "#^Parameter \\#1 \\$options of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLibXmlLoaderOptions\\(\\) expects int, null given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheetTests\\\\Writer\\\\Csv\\\\CsvEnclosureTest\\:\\:\\$cellValues has no typehint specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|false given\\.$#" + count: 4 + path: tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Writer\\\\Html\\\\CallbackTest\\:\\:yellowBody\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Html/CallbackTest.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Html/CallbackTest.php + + - + message: "#^Cannot call method getColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Writer/Html/GridlinesTest.php + + - + message: "#^Cannot call method setSuperScript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Html/GridlinesTest.php + + - + message: "#^Cannot call method setSubScript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Writer/Html/GridlinesTest.php + + - + message: "#^Cannot call method setBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Writer/Html/HtmlCommentsTest.php + + - + message: "#^Parameter \\#1 \\$directory of function chdir expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Html/ImagesRootTest.php + + - + message: "#^Cannot call method setVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension\\|null\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Writer/Html/VisibilityTest.php + + - + message: "#^Cannot call method setVisible\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Writer/Html/VisibilityTest.php + + - + message: "#^Cannot call method setRowHeight\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Html/VisibilityTest.php + + - + message: "#^Parameter \\#1 \\$options of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLibXmlLoaderOptions\\(\\) expects int, null given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php + + - + message: "#^Parameter \\#2 \\$locale of function setlocale expects string\\|null, string\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php + + - + message: "#^Parameter \\#1 \\$options of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLibXmlLoaderOptions\\(\\) expects int, null given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/StartsWithHashTest.php + + - + message: "#^Parameter \\#1 \\$options of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLibXmlLoaderOptions\\(\\) expects int, null given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php + + - + message: "#^Cannot call method getDrawingCollection\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 4 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php + + - + message: "#^Cannot call method getCell\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 4 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php + + - + message: "#^Parameter \\#1 \\$options of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLibXmlLoaderOptions\\(\\) expects int, null given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php + + - + message: "#^Parameter \\#1 \\$data of function simplexml_load_string expects string, string\\|false given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php + + - + message: "#^Cannot access property \\$workbookProtection on SimpleXMLElement\\|false\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php + + - + message: "#^Cannot access property \\$sheets on SimpleXMLElement\\|false\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php + + - + message: "#^Cannot access property \\$pageSetup on SimpleXMLElement\\|false\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php + + - + message: "#^Cannot access property \\$sheetProtection on SimpleXMLElement\\|false\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php + diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..dd39aa34 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,27 @@ +includes: + - phpstan-baseline.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon + +parameters: + level: max + paths: + - src/ + - tests/ + parallel: + processTimeout: 300.0 + checkMissingIterableValueType: false + ignoreErrors: + - '~^Class GdImage not found\.$~' + - '~^Return typehint of method .* has invalid type GdImage\.$~' + - '~^Property .* has unknown class GdImage as its type\.$~' + - '~^Parameter .* of method .* has invalid typehint type GdImage\.$~' + # 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 typehint specified\.$~' + + # Ignore all JpGraph issues + - '~^Constant (MARK_CIRCLE|MARK_CROSS|MARK_DIAMOND|MARK_DTRIANGLE|MARK_FILLEDCIRCLE|MARK_SQUARE|MARK_STAR|MARK_UTRIANGLE|MARK_X|SIDE_RIGHT) not found\.$~' + - '~^Instantiated class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot) not found\.$~' + - '~^Call to method .*\(\) on an unknown class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot)\.$~' + - '~^Access to property .* on an unknown class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot)\.$~' diff --git a/samples/Basic/30_Templatebiff5.php b/samples/Basic/30_Templatebiff5.php new file mode 100644 index 00000000..53c4c2a8 --- /dev/null +++ b/samples/Basic/30_Templatebiff5.php @@ -0,0 +1,43 @@ +log('Load from Xls template'); +$reader = IOFactory::createReader('Xls'); +$spreadsheet = $reader->load(__DIR__ . '/../templates/30templatebiff5.xls'); + +$helper->log('Add new data to the template'); +$data = [['title' => 'Excel for dummies', + 'price' => 17.99, + 'quantity' => 2, +], + ['title' => 'PHP for dummies', + 'price' => 15.99, + 'quantity' => 1, + ], + ['title' => 'Inside OOP', + 'price' => 12.95, + 'quantity' => 1, + ], +]; + +$spreadsheet->getActiveSheet()->setCellValue('D1', Date::PHPToExcel(time())); + +$baseRow = 5; +foreach ($data as $r => $dataRow) { + $row = $baseRow + $r; + $spreadsheet->getActiveSheet()->insertNewRowBefore($row, 1); + + $spreadsheet->getActiveSheet()->setCellValue('A' . $row, $r + 1) + ->setCellValue('B' . $row, $dataRow['title']) + ->setCellValue('C' . $row, $dataRow['price']) + ->setCellValue('D' . $row, $dataRow['quantity']) + ->setCellValue('E' . $row, '=C' . $row . '*D' . $row); +} +$spreadsheet->getActiveSheet()->removeRow($baseRow - 1, 1); + +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/samples/Basic/40_Duplicate_style.php b/samples/Basic/40_Duplicate_style.php index 0366703d..38f7fb49 100644 --- a/samples/Basic/40_Duplicate_style.php +++ b/samples/Basic/40_Duplicate_style.php @@ -30,7 +30,7 @@ for ($col = 1; $col <= 50; ++$col) { } } $d = microtime(true) - $t; -$helper->log('Add data (end) . time: ' . round((string) ($d . 2)) . ' s'); +$helper->log('Add data (end) . time: ' . (string) round($d, 2) . ' s'); // Save $helper->write($spreadsheet, __FILE__); diff --git a/samples/Basic/43_Merge_workbooks.php b/samples/Basic/43_Merge_workbooks.php index 86314b3b..28353cc6 100644 --- a/samples/Basic/43_Merge_workbooks.php +++ b/samples/Basic/43_Merge_workbooks.php @@ -18,6 +18,10 @@ $helper->logRead('Xlsx', $filename2, $callStartTime); foreach ($spreadsheet2->getSheetNames() as $sheetName) { $sheet = $spreadsheet2->getSheetByName($sheetName); + if ($sheet === null) { + continue; + } + $sheet->setTitle($sheet->getTitle() . ' copied'); $spreadsheet1->addExternalSheet($sheet); } diff --git a/samples/Calculations/Financial/ACCRINT.php b/samples/Calculations/Financial/ACCRINT.php new file mode 100644 index 00000000..6f24f651 --- /dev/null +++ b/samples/Calculations/Financial/ACCRINT.php @@ -0,0 +1,35 @@ +log('Returns the accrued interest for a security that pays periodic interest.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Issue Date', DateHelper::getDateValue('01-Jan-2012')], + ['First Interest Date', DateHelper::getDateValue('01-Apr-2012')], + ['Settlement Date', DateHelper::getDateValue('31-Dec-2013')], + ['Annual Coupon Rate', 0.08], + ['Par Value', 10000], + ['Frequency', 4], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B3')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); +$worksheet->getStyle('B4')->getNumberFormat()->setFormatCode('0.00%'); +$worksheet->getStyle('B5')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +$worksheet->setCellValue('B10', '=ACCRINT(B1, B2, B3, B4, B5, B6)'); +$worksheet->getStyle('B10')->getNumberFormat()->setFormatCode('$#,##0.00'); + +$helper->log($worksheet->getCell('B10')->getValue()); +$helper->log('ACCRINT() Result is ' . $worksheet->getCell('B10')->getFormattedValue()); diff --git a/samples/Calculations/Financial/ACCRINTM.php b/samples/Calculations/Financial/ACCRINTM.php new file mode 100644 index 00000000..8cc73899 --- /dev/null +++ b/samples/Calculations/Financial/ACCRINTM.php @@ -0,0 +1,33 @@ +log('Returns the accrued interest for a security that pays interest at maturity.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Issue Date', DateHelper::getDateValue('01-Jan-2012')], + ['Settlement Date', DateHelper::getDateValue('31-Dec-2012')], + ['Annual Coupon Rate', 0.08], + ['Par Value', 10000], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B2')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); +$worksheet->getStyle('B3')->getNumberFormat()->setFormatCode('0.00%'); +$worksheet->getStyle('B4')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +$worksheet->setCellValue('B6', '=ACCRINTM(B1, B2, B3, B4)'); +$worksheet->getStyle('B6')->getNumberFormat()->setFormatCode('$#,##0.00'); + +$helper->log($worksheet->getCell('B6')->getValue()); +$helper->log('ACCRINTM() Result is ' . $worksheet->getCell('B6')->getFormattedValue()); diff --git a/samples/Calculations/Financial/AMORDEGRC.php b/samples/Calculations/Financial/AMORDEGRC.php new file mode 100644 index 00000000..c9e0c4c9 --- /dev/null +++ b/samples/Calculations/Financial/AMORDEGRC.php @@ -0,0 +1,38 @@ +log('Returns the prorated linear depreciation of an asset for a specified accounting period.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Cost', 150.00], + ['Date Purchased', DateHelper::getDateValue('01-Jan-2015')], + ['First Period Date', DateHelper::getDateValue('30-Sep-2015')], + ['Salvage Value', 20.00], + ['Number of Periods', 1], + ['Depreciation Rate', 0.20], + ['Basis', FinancialConstants::BASIS_DAYS_PER_YEAR_360_EUROPEAN], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1')->getNumberFormat()->setFormatCode('$#,##0.00'); +$worksheet->getStyle('B2:B3')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); +$worksheet->getStyle('B4')->getNumberFormat()->setFormatCode('$#,##0.00'); +$worksheet->getStyle('B6')->getNumberFormat()->setFormatCode('0.00%'); + +// Now the formula +$worksheet->setCellValue('B10', '=AMORDEGRC(B1, B2, B3, B4, B5, B6, B7)'); +$worksheet->getStyle('B10')->getNumberFormat()->setFormatCode('$#,##0.00'); + +$helper->log($worksheet->getCell('B10')->getValue()); +$helper->log('AMORDEGRC() Result is ' . $worksheet->getCell('B10')->getFormattedValue()); diff --git a/samples/Calculations/Financial/AMORLINC.php b/samples/Calculations/Financial/AMORLINC.php new file mode 100644 index 00000000..3491eae0 --- /dev/null +++ b/samples/Calculations/Financial/AMORLINC.php @@ -0,0 +1,38 @@ +log('Returns the prorated linear depreciation of an asset for a specified accounting period.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Cost', 150.00], + ['Date Purchased', DateHelper::getDateValue('01-Jan-2015')], + ['First Period Date', DateHelper::getDateValue('30-Sep-2015')], + ['Salvage Value', 20.00], + ['Period', 1], + ['Depreciation Rate', 0.20], + ['Basis', FinancialConstants::BASIS_DAYS_PER_YEAR_360_EUROPEAN], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1')->getNumberFormat()->setFormatCode('$#,##0.00'); +$worksheet->getStyle('B2:B3')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); +$worksheet->getStyle('B4')->getNumberFormat()->setFormatCode('$#,##0.00'); +$worksheet->getStyle('B6')->getNumberFormat()->setFormatCode('0.00%'); + +// Now the formula +$worksheet->setCellValue('B10', '=AMORLINC(B1, B2, B3, B4, B5, B6, B7)'); +$worksheet->getStyle('B10')->getNumberFormat()->setFormatCode('$#,##0.00'); + +$helper->log($worksheet->getCell('B10')->getValue()); +$helper->log('AMORLINC() Result is ' . $worksheet->getCell('B10')->getFormattedValue()); diff --git a/samples/Calculations/Financial/COUPDAYBS.php b/samples/Calculations/Financial/COUPDAYBS.php new file mode 100644 index 00000000..727d17d9 --- /dev/null +++ b/samples/Calculations/Financial/COUPDAYBS.php @@ -0,0 +1,29 @@ +log('Returns the number of days from the beginning of a coupon\'s period to the settlement date.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Settlement Date', DateHelper::getDateValue('01-Jan-2011')], + ['Maturity Date', DateHelper::getDateValue('25-Oct-2012')], + ['Frequency', 4], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B2')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); + +// Now the formula +$worksheet->setCellValue('B6', '=COUPDAYBS(B1, B2, B3)'); + +$helper->log($worksheet->getCell('B6')->getValue()); +$helper->log('COUPDAYBS() Result is ' . $worksheet->getCell('B6')->getFormattedValue()); diff --git a/samples/Calculations/Financial/COUPDAYS.php b/samples/Calculations/Financial/COUPDAYS.php new file mode 100644 index 00000000..f6e148cc --- /dev/null +++ b/samples/Calculations/Financial/COUPDAYS.php @@ -0,0 +1,29 @@ +log('Returns the number of days in the coupon period that contains the settlement date.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Settlement Date', DateHelper::getDateValue('01-Jan-2011')], + ['Maturity Date', DateHelper::getDateValue('25-Oct-2012')], + ['Frequency', 4], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B2')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); + +// Now the formula +$worksheet->setCellValue('B6', '=COUPDAYS(B1, B2, B3)'); + +$helper->log($worksheet->getCell('B6')->getValue()); +$helper->log('COUPDAYS() Result is ' . $worksheet->getCell('B6')->getFormattedValue()); diff --git a/samples/Calculations/Financial/COUPDAYSNC.php b/samples/Calculations/Financial/COUPDAYSNC.php new file mode 100644 index 00000000..dfb21dd7 --- /dev/null +++ b/samples/Calculations/Financial/COUPDAYSNC.php @@ -0,0 +1,29 @@ +log('Returns the number of days from the settlement date to the next coupon date.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Settlement Date', DateHelper::getDateValue('01-Jan-2011')], + ['Maturity Date', DateHelper::getDateValue('25-Oct-2012')], + ['Frequency', 4], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B2')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); + +// Now the formula +$worksheet->setCellValue('B6', '=COUPDAYSNC(B1, B2, B3)'); + +$helper->log($worksheet->getCell('B6')->getValue()); +$helper->log('COUPDAYSNC() Result is ' . $worksheet->getCell('B6')->getFormattedValue()); diff --git a/samples/Calculations/Financial/COUPNCD.php b/samples/Calculations/Financial/COUPNCD.php new file mode 100644 index 00000000..b38faf03 --- /dev/null +++ b/samples/Calculations/Financial/COUPNCD.php @@ -0,0 +1,30 @@ +log('Returns the next coupon date, after the settlement date.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Settlement Date', DateHelper::getDateValue('01-Jan-2011')], + ['Maturity Date', DateHelper::getDateValue('25-Oct-2012')], + ['Frequency', 4], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B2')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); + +// Now the formula +$worksheet->setCellValue('B6', '=COUPNCD(B1, B2, B3)'); +$worksheet->getStyle('B6')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); + +$helper->log($worksheet->getCell('B6')->getValue()); +$helper->log('COUPNCD() Result is ' . $worksheet->getCell('B6')->getFormattedValue()); diff --git a/samples/Calculations/Financial/COUPNUM.php b/samples/Calculations/Financial/COUPNUM.php new file mode 100644 index 00000000..b1bd0d52 --- /dev/null +++ b/samples/Calculations/Financial/COUPNUM.php @@ -0,0 +1,30 @@ +log('Returns the number of coupons payable, between a security\'s settlement date and maturity date,'); +$helper->log('rounded up to the nearest whole coupon.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Settlement Date', DateHelper::getDateValue('01-Jan-2011')], + ['Maturity Date', DateHelper::getDateValue('25-Oct-2012')], + ['Frequency', 4], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B2')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); + +// Now the formula +$worksheet->setCellValue('B6', '=COUPNUM(B1, B2, B3)'); + +$helper->log($worksheet->getCell('B6')->getValue()); +$helper->log('COUPNUM() Result is ' . $worksheet->getCell('B6')->getFormattedValue()); diff --git a/samples/Calculations/Financial/COUPPCD.php b/samples/Calculations/Financial/COUPPCD.php new file mode 100644 index 00000000..8ac78606 --- /dev/null +++ b/samples/Calculations/Financial/COUPPCD.php @@ -0,0 +1,30 @@ +log('Returns the previous coupon date, before the settlement date for a security.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Settlement Date', DateHelper::getDateValue('01-Jan-2011')], + ['Maturity Date', DateHelper::getDateValue('25-Oct-2012')], + ['Frequency', 4], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B2')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); + +// Now the formula +$worksheet->setCellValue('B6', '=COUPPCD(B1, B2, B3)'); +$worksheet->getStyle('B6')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); + +$helper->log($worksheet->getCell('B6')->getValue()); +$helper->log('COUPPCD() Result is ' . $worksheet->getCell('B6')->getFormattedValue()); diff --git a/samples/Calculations/Financial/CUMIPMT.php b/samples/Calculations/Financial/CUMIPMT.php new file mode 100644 index 00000000..f97caec8 --- /dev/null +++ b/samples/Calculations/Financial/CUMIPMT.php @@ -0,0 +1,38 @@ +log('Returns the cumulative interest paid on a loan or investment, between two specified periods.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Interest Rate (per period)', 0.05 / 12], + ['Number of Periods', 5 * 12], + ['Present Value', 50000], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1')->getNumberFormat()->setFormatCode('0.00%'); +$worksheet->getStyle('B3')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +$baseRow = 5; +for ($year = 1; $year <= 5; ++$year) { + $row = (string) ($baseRow + $year); + $yearStartPeriod = (int) $year * 12 - 11; + $yearEndPeriod = (int) $year * 12; + + $worksheet->setCellValue("A{$row}", "Yr {$year}"); + $worksheet->setCellValue("B{$row}", "=CUMIPMT(\$B\$1, \$B\$2, \$B\$3, {$yearStartPeriod}, {$yearEndPeriod}, 0)"); + $worksheet->getStyle("B{$row}")->getNumberFormat()->setFormatCode('$#,##0.00;-$#,##0.00'); + + $helper->log($worksheet->getCell("B{$row}")->getValue()); + $helper->log("CUMIPMT() Year {$year} Result is " . $worksheet->getCell("B{$row}")->getFormattedValue()); +} diff --git a/samples/Calculations/Financial/CUMPRINC.php b/samples/Calculations/Financial/CUMPRINC.php new file mode 100644 index 00000000..86de3070 --- /dev/null +++ b/samples/Calculations/Financial/CUMPRINC.php @@ -0,0 +1,38 @@ +log('Returns the cumulative payment on the principal of a loan or investment, between two specified periods.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Interest Rate (per period)', 0.05 / 12], + ['Number of Periods', 5 * 12], + ['Present Value', 50000], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1')->getNumberFormat()->setFormatCode('0.00%'); +$worksheet->getStyle('B3')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +$baseRow = 5; +for ($year = 1; $year <= 5; ++$year) { + $row = (string) ($baseRow + $year); + $yearStartPeriod = (int) $year * 12 - 11; + $yearEndPeriod = (int) $year * 12; + + $worksheet->setCellValue("A{$row}", "Yr {$year}"); + $worksheet->setCellValue("B{$row}", "=CUMPRINC(\$B\$1, \$B\$2, \$B\$3, {$yearStartPeriod}, {$yearEndPeriod}, 0)"); + $worksheet->getStyle("B{$row}")->getNumberFormat()->setFormatCode('$#,##0.00;-$#,##0.00'); + + $helper->log($worksheet->getCell("B{$row}")->getValue()); + $helper->log("CUMPRINC() Year {$year} Result is " . $worksheet->getCell("B{$row}")->getFormattedValue()); +} diff --git a/samples/Calculations/Financial/DB.php b/samples/Calculations/Financial/DB.php new file mode 100644 index 00000000..0759c0aa --- /dev/null +++ b/samples/Calculations/Financial/DB.php @@ -0,0 +1,50 @@ +log('Returns the depreciation of an asset, using the Fixed Declining Balance Method,'); +$helper->log('for each period of the asset\'s lifetime.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Cost Value', 10000], + ['Salvage', 1000], + ['Life', 5, 'Years'], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B2')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +$baseRow = 5; +for ($year = 1; $year <= 5; ++$year) { + $row = (string) ($baseRow + $year); + + $worksheet->setCellValue("A{$row}", "Depreciation after Yr {$year}"); + $worksheet->setCellValue("B{$row}", "=DB(\$B\$1, \$B\$2, \$B\$3, {$year})"); + $worksheet->getStyle("B{$row}")->getNumberFormat()->setFormatCode('$#,##0.00;-$#,##0.00'); + + $helper->log($worksheet->getCell("B{$row}")->getValue()); + $helper->log("DB() Year {$year} Result is " . $worksheet->getCell("B{$row}")->getFormattedValue()); +} + +$helper->log('And with depreciation only starting after 6 months.'); + +$baseRow = 12; +for ($year = 1; $year <= 6; ++$year) { + $row = (string) ($baseRow + $year); + + $worksheet->setCellValue("A{$row}", "Depreciation after Yr {$year}"); + $worksheet->setCellValue("B{$row}", "=DB(\$B\$1, \$B\$2, \$B\$3, {$year}, 6)"); + $worksheet->getStyle("B{$row}")->getNumberFormat()->setFormatCode('$#,##0.00;-$#,##0.00'); + + $helper->log($worksheet->getCell("B{$row}")->getValue()); + $helper->log("DB() Year {$year} Result is " . $worksheet->getCell("B{$row}")->getFormattedValue()); +} diff --git a/samples/Calculations/Financial/DDB.php b/samples/Calculations/Financial/DDB.php new file mode 100644 index 00000000..d261d4bd --- /dev/null +++ b/samples/Calculations/Financial/DDB.php @@ -0,0 +1,36 @@ +log('Returns the depreciation of an asset, using the Double Declining Balance Method,'); +$helper->log('for each period of the asset\'s lifetime.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Cost Value', 10000], + ['Salvage', 1000], + ['Life', 5, 'Years'], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B2')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +$baseRow = 5; +for ($year = 1; $year <= 5; ++$year) { + $row = (string) ($baseRow + $year); + + $worksheet->setCellValue("A{$row}", "Depreciation after Yr {$year}"); + $worksheet->setCellValue("B{$row}", "=DDB(\$B\$1, \$B\$2, \$B\$3, {$year})"); + $worksheet->getStyle("B{$row}")->getNumberFormat()->setFormatCode('$#,##0.00;-$#,##0.00'); + + $helper->log($worksheet->getCell("B{$row}")->getValue()); + $helper->log("DDB() Year {$year} Result is " . $worksheet->getCell("B{$row}")->getFormattedValue()); +} diff --git a/samples/Calculations/Financial/DISC.php b/samples/Calculations/Financial/DISC.php new file mode 100644 index 00000000..389d75e0 --- /dev/null +++ b/samples/Calculations/Financial/DISC.php @@ -0,0 +1,32 @@ +log('Returns the the Discount Rate for a security.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Settlement Date', DateHelper::getDateValue('01-Apr-2016')], + ['Maturity Date', DateHelper::getDateValue('31-Mar-2021')], + ['Par Value', 95.00], + ['Redemption Value', 100.00], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B2')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); +$worksheet->getStyle('B3:B4')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +$worksheet->setCellValue('B7', '=DISC(B1, B2, B3, B4)'); +$worksheet->getStyle('B7')->getNumberFormat()->setFormatCode('0.00%'); + +$helper->log($worksheet->getCell('B7')->getValue()); +$helper->log('DISC() Result is ' . $worksheet->getCell('B7')->getFormattedValue()); diff --git a/samples/Calculations/Financial/DOLLARDE.php b/samples/Calculations/Financial/DOLLARDE.php new file mode 100644 index 00000000..d7187401 --- /dev/null +++ b/samples/Calculations/Financial/DOLLARDE.php @@ -0,0 +1,30 @@ +log('Returns the dollar value in fractional notation, into a dollar value expressed as a decimal.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + [1.01, 16], + [1.1, 16], + [1.03, 32], + [1.3, 32], + [1.12, 32], +]; + +$worksheet->fromArray($arguments, null, 'A1'); + +// Now the formula +for ($row = 1; $row <= 5; ++$row) { + $worksheet->setCellValue("C{$row}", "=DOLLARDE(A{$row}, B{$row})"); + + $helper->log($worksheet->getCell("C{$row}")->getValue()); + $helper->log('DOLLARDE() Result is ' . $worksheet->getCell("C{$row}")->getFormattedValue()); +} diff --git a/samples/Calculations/Financial/DOLLARFR.php b/samples/Calculations/Financial/DOLLARFR.php new file mode 100644 index 00000000..75424be0 --- /dev/null +++ b/samples/Calculations/Financial/DOLLARFR.php @@ -0,0 +1,30 @@ +log('Returns the dollar value expressed as a decimal number, into a dollar price, expressed as a fraction.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + [1.0625, 16], + [1.625, 16], + [1.09375, 32], + [1.9375, 32], + [1.375, 32], +]; + +$worksheet->fromArray($arguments, null, 'A1'); + +// Now the formula +for ($row = 1; $row <= 5; ++$row) { + $worksheet->setCellValue("C{$row}", "=DOLLARFR(A{$row}, B{$row})"); + + $helper->log($worksheet->getCell("C{$row}")->getValue()); + $helper->log('DOLLARFR() Result is ' . $worksheet->getCell("C{$row}")->getFormattedValue()); +} diff --git a/samples/Calculations/Financial/EFFECT.php b/samples/Calculations/Financial/EFFECT.php new file mode 100644 index 00000000..a74f4500 --- /dev/null +++ b/samples/Calculations/Financial/EFFECT.php @@ -0,0 +1,31 @@ +log('Returns the effective annual interest rate for a given nominal interest rate and number of'); +$helper->log('compounding periods per year.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + [0.10, 4], + [0.10, 2], + [0.025, 2], +]; + +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B3')->getNumberFormat()->setFormatCode('0.00%'); + +// Now the formula +for ($row = 1; $row <= 3; ++$row) { + $worksheet->setCellValue("C{$row}", "=EFFECT(A{$row}, B{$row})"); + $worksheet->getStyle("C{$row}")->getNumberFormat()->setFormatCode('0.00%'); + + $helper->log($worksheet->getCell("C{$row}")->getValue()); + $helper->log('EFFECT() Result is ' . $worksheet->getCell("C{$row}")->getFormattedValue()); +} diff --git a/samples/Calculations/Financial/FV.php b/samples/Calculations/Financial/FV.php new file mode 100644 index 00000000..31ddb630 --- /dev/null +++ b/samples/Calculations/Financial/FV.php @@ -0,0 +1,36 @@ +log('Returns the Future Value of an investment with periodic constant payments and a constant interest rate.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Interest Rate', 0.05, 0.10], + ['Pament Frequency', 12, 4], + ['Duration (Years)', 5, 4], + ['Investment', -1000.00, -2000.00], + ['Payment Type', 0, 1], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:C1')->getNumberFormat()->setFormatCode('0.00%'); +$worksheet->getStyle('B4:C4')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +$worksheet->setCellValue('B8', '=FV(B1/B2, B3*B2, B4)'); +$worksheet->setCellValue('C8', '=FV(C1/C2, C3*C2, C4, null, C5)'); +$worksheet->getStyle('B8:C8')->getNumberFormat()->setFormatCode('$#,##0.00'); + +$helper->log($worksheet->getCell('B8')->getValue()); +$helper->log('FV() Result is ' . $worksheet->getCell('B8')->getFormattedValue()); + +$helper->log($worksheet->getCell('C6')->getValue()); +$helper->log('FV() Result is ' . $worksheet->getCell('C8')->getFormattedValue()); diff --git a/samples/Calculations/Financial/FVSCHEDULE.php b/samples/Calculations/Financial/FVSCHEDULE.php new file mode 100644 index 00000000..dfd2abbc --- /dev/null +++ b/samples/Calculations/Financial/FVSCHEDULE.php @@ -0,0 +1,36 @@ +log('Returns the Future Value of an initial principal, after applying a series of compound interest rates.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Principal'], + [10000.00], + [null], + ['Schedule'], + [0.05], + [0.05], + [0.035], + [0.035], + [0.035], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('A2')->getNumberFormat()->setFormatCode('$#,##0.00'); +$worksheet->getStyle('A5:A9')->getNumberFormat()->setFormatCode('0.00%'); + +// Now the formula +$worksheet->setCellValue('B1', '=FVSCHEDULE(A2, A5:A9)'); +$worksheet->getStyle('B1')->getNumberFormat()->setFormatCode('$#,##0.00'); + +$helper->log($worksheet->getCell('B1')->getValue()); +$helper->log('FVSCHEDULE() Result is ' . $worksheet->getCell('B1')->getFormattedValue()); diff --git a/samples/Calculations/Financial/INTRATE.php b/samples/Calculations/Financial/INTRATE.php new file mode 100644 index 00000000..594fb6cb --- /dev/null +++ b/samples/Calculations/Financial/INTRATE.php @@ -0,0 +1,32 @@ +log('Returns the interest rate for a fully invested security.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Settlement Date', DateHelper::getDateValue('01-Apr-2005')], + ['Maturity Date', DateHelper::getDateValue('31-Mar-2010')], + ['Investment', 1000.00], + ['Investment', 2125.00], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B2')->getNumberFormat()->setFormatCode('dd-mmm-yyyy'); +$worksheet->getStyle('B3:B4')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +$worksheet->setCellValue('B7', '=INTRATE(B1, B2, B3, B4)'); +$worksheet->getStyle('B7')->getNumberFormat()->setFormatCode('0.00%'); + +$helper->log($worksheet->getCell('B7')->getValue()); +$helper->log('INTRATE() Result is ' . $worksheet->getCell('B7')->getFormattedValue()); diff --git a/samples/Calculations/Financial/IPMT.php b/samples/Calculations/Financial/IPMT.php new file mode 100644 index 00000000..138ec378 --- /dev/null +++ b/samples/Calculations/Financial/IPMT.php @@ -0,0 +1,37 @@ +log('Returns the interest payment, during a specific period of a loan or investment that is paid in,'); +$helper->log('constant periodic payments, with a constant interest rate.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Interest Rate', 0.05], + ['Number of Years', 5], + ['Present Value', 50000.00], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1')->getNumberFormat()->setFormatCode('0.00%'); +$worksheet->getStyle('B3')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +$baseRow = 6; +for ($month = 1; $month <= 12; ++$month) { + $row = (string) ($baseRow + $month); + + $worksheet->setCellValue("A{$row}", "Payment for Mth {$month}"); + $worksheet->setCellValue("B{$row}", "=IPMT(\$B\$1/12, {$month}, \$B\$2*12, \$B\$3)"); + $worksheet->getStyle("B{$row}")->getNumberFormat()->setFormatCode('$#,##0.00;-$#,##0.00'); + + $helper->log($worksheet->getCell("B{$row}")->getValue()); + $helper->log("IPMT() Month {$month} Result is " . $worksheet->getCell("B{$row}")->getFormattedValue()); +} diff --git a/samples/Calculations/Financial/IRR.php b/samples/Calculations/Financial/IRR.php new file mode 100644 index 00000000..e489af93 --- /dev/null +++ b/samples/Calculations/Financial/IRR.php @@ -0,0 +1,38 @@ +log('Returns the Internal Rate of Return for a supplied series of periodic cash flows.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Initial Investment', -100.00], + ['Year 1 Income', 20.00], + ['Year 2 Income', 24.00, 'IRR after 3 Years'], + ['Year 3 Income', 28.80], + ['Year 4 Income', 34.56, 'IRR after 5 Years'], + ['Year 5 Income', 41.47], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B6')->getNumberFormat()->setFormatCode('$#,##0.00;-$#,##0.00'); + +// Now the formula +$worksheet->setCellValue('C4', '=IRR(B1:B4)'); +$worksheet->getStyle('C4')->getNumberFormat()->setFormatCode('0.00%'); + +$helper->log($worksheet->getCell('C4')->getValue()); +$helper->log('IRR() Result is ' . $worksheet->getCell('C4')->getFormattedValue()); + +$worksheet->setCellValue('C6', '=IRR(B1:B6)'); +$worksheet->getStyle('C6')->getNumberFormat()->setFormatCode('0.00%'); + +$helper->log($worksheet->getCell('C6')->getValue()); +$helper->log('IRR() Result is ' . $worksheet->getCell('C6')->getFormattedValue()); diff --git a/samples/Calculations/Financial/ISPMT.php b/samples/Calculations/Financial/ISPMT.php new file mode 100644 index 00000000..cbede7ce --- /dev/null +++ b/samples/Calculations/Financial/ISPMT.php @@ -0,0 +1,36 @@ +log('Returns the interest paid during a specific period of a loan or investment.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Interest Rate', 0.05], + ['Number of Years', 5], + ['Present Value', 50000.00], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1')->getNumberFormat()->setFormatCode('0.00%'); +$worksheet->getStyle('B3')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +$baseRow = 6; +for ($month = 1; $month <= 12; ++$month) { + $row = (string) ($baseRow + $month); + + $worksheet->setCellValue("A{$row}", "Payment for Mth {$month}"); + $worksheet->setCellValue("B{$row}", "=ISPMT(\$B\$1/12, {$month}, \$B\$2*12, \$B\$3)"); + $worksheet->getStyle("B{$row}")->getNumberFormat()->setFormatCode('$#,##0.00;-$#,##0.00'); + + $helper->log($worksheet->getCell("B{$row}")->getValue()); + $helper->log("ISPMT() Month {$month} Result is " . $worksheet->getCell("B{$row}")->getFormattedValue()); +} diff --git a/samples/Calculations/Financial/MIRR.php b/samples/Calculations/Financial/MIRR.php new file mode 100644 index 00000000..313e10bd --- /dev/null +++ b/samples/Calculations/Financial/MIRR.php @@ -0,0 +1,42 @@ +log('Returns the Modified Internal Rate of Return for a supplied series of periodic cash flows.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Initial Investment', -100.00], + ['Year 1 Income', 18.00], + ['Year 2 Income', 22.50, 'MIRR after 3 Years'], + ['Year 3 Income', 28.00], + ['Year 4 Income', 35.50, 'MIRR after 5 Years'], + ['Year 5 Income', 45.00], + [null], + ['Finance Rate', 0.055], + ['Re-invest Rate', 0.05], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B6')->getNumberFormat()->setFormatCode('$#,##0.00;-$#,##0.00'); +$worksheet->getStyle('B8:B9')->getNumberFormat()->setFormatCode('0.00%'); + +// Now the formula +$worksheet->setCellValue('C4', '=MIRR(B1:B4, B8, B9)'); +$worksheet->getStyle('C4')->getNumberFormat()->setFormatCode('0.00%'); + +$helper->log($worksheet->getCell('C4')->getValue()); +$helper->log('MIRR() Result is ' . $worksheet->getCell('C4')->getFormattedValue()); + +$worksheet->setCellValue('C6', '=MIRR(B1:B6, B8, B9)'); +$worksheet->getStyle('C6')->getNumberFormat()->setFormatCode('0.00%'); + +$helper->log($worksheet->getCell('C6')->getValue()); +$helper->log('MIRR() Result is ' . $worksheet->getCell('C6')->getFormattedValue()); diff --git a/samples/Calculations/Financial/NOMINAL.php b/samples/Calculations/Financial/NOMINAL.php new file mode 100644 index 00000000..6b7951a1 --- /dev/null +++ b/samples/Calculations/Financial/NOMINAL.php @@ -0,0 +1,31 @@ +log('Returns the nominal interest rate for a given effective interest rate and number of'); +$helper->log('compounding periods per year.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + [0.10, 4], + [0.10, 2], + [0.025, 12], +]; + +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:B3')->getNumberFormat()->setFormatCode('0.00%'); + +// Now the formula +for ($row = 1; $row <= 3; ++$row) { + $worksheet->setCellValue("C{$row}", "=NOMINAL(A{$row}, B{$row})"); + $worksheet->getStyle("C{$row}")->getNumberFormat()->setFormatCode('0.00%'); + + $helper->log($worksheet->getCell("C{$row}")->getValue()); + $helper->log('NOMINAL() Result is ' . $worksheet->getCell("C{$row}")->getFormattedValue()); +} diff --git a/samples/Calculations/Financial/NPER.php b/samples/Calculations/Financial/NPER.php new file mode 100644 index 00000000..8acae473 --- /dev/null +++ b/samples/Calculations/Financial/NPER.php @@ -0,0 +1,39 @@ +log('Returns the number of periods required to pay off a loan, for a constant periodic payment'); +$helper->log('and a constant interest rate.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Interest Rate', 0.04, 0.06], + ['Payments per Year', 1, 4], + ['Payment Amount', -6000.00, -2000], + ['Present Value', 50000, 60000], + ['Future Value', null, 30000], + ['Payment Type', null, FinancialConstants::PAYMENT_BEGINNING_OF_PERIOD], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:C1')->getNumberFormat()->setFormatCode('0.00%'); +$worksheet->getStyle('B3:C5')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +$worksheet->setCellValue('B8', '=NPER(B1/B2, B3, B4)'); + +$helper->log($worksheet->getCell('B8')->getValue()); +$helper->log('NPER() Result is ' . $worksheet->getCell('B8')->getFormattedValue()); + +$worksheet->setCellValue('C8', '=NPER(C1/C2, C3, C4, C5, C6)'); + +$helper->log($worksheet->getCell('C8')->getValue()); +$helper->log('NPER() Result is ' . $worksheet->getCell('C8')->getFormattedValue()); diff --git a/samples/Calculations/Financial/NPV.php b/samples/Calculations/Financial/NPV.php new file mode 100644 index 00000000..fa76d542 --- /dev/null +++ b/samples/Calculations/Financial/NPV.php @@ -0,0 +1,43 @@ +log('Returns the Net Present Value of an investment, based on a supplied discount rate,'); +$helper->log('and a series of future payments and income.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +// Add some data +$arguments = [ + ['Annual Discount Rate', 0.02, 0.05], + ['Initial Investment Cost', -5000.00, -10000], + ['Return from Year 1', 800.00, 2000.00], + ['Return from Year 2', 950.00, 2400.00], + ['Return from Year 3', 1080.00, 2900.00], + ['Return from Year 4', 1220.00, 3500.00], + ['Return from Year 5', 1500.00, 4100.00], +]; + +// Some basic formatting for the data +$worksheet->fromArray($arguments, null, 'A1'); +$worksheet->getStyle('B1:C1')->getNumberFormat()->setFormatCode('0.00%'); +$worksheet->getStyle('B2:C7')->getNumberFormat()->setFormatCode('$#,##0.00'); + +// Now the formula +// When initial investment is made at the end of the first period +$worksheet->setCellValue('B10', '=NPV(B1, B2:B7)'); +$worksheet->getStyle('B10')->getNumberFormat()->setFormatCode('$#,##0.00'); + +$helper->log($worksheet->getCell('B10')->getValue()); +$helper->log('NPV() Result is ' . $worksheet->getCell('B10')->getFormattedValue()); + +// When initial investment is made at the start of the first period +$worksheet->setCellValue('C10', '=NPV(C1, C3:C7) + C2'); +$worksheet->getStyle('C10')->getNumberFormat()->setFormatCode('$#,##0.00'); + +$helper->log($worksheet->getCell('C10')->getValue()); +$helper->log('NPV() Result is ' . $worksheet->getCell('C10')->getFormattedValue()); diff --git a/samples/Calculations/LookupRef/ADDRESS.php b/samples/Calculations/LookupRef/ADDRESS.php new file mode 100644 index 00000000..2377541d --- /dev/null +++ b/samples/Calculations/LookupRef/ADDRESS.php @@ -0,0 +1,22 @@ +log('Returns a text reference to a single cell in a worksheet.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$worksheet->getCell('A1')->setValue('=ADDRESS(2,3)'); +$worksheet->getCell('A2')->setValue('=ADDRESS(2,3,2)'); +$worksheet->getCell('A3')->setValue('=ADDRESS(2,3,2,FALSE)'); +$worksheet->getCell('A4')->setValue('=ADDRESS(2,3,1,FALSE,"[Book1]Sheet1")'); +$worksheet->getCell('A5')->setValue('=ADDRESS(2,3,1,FALSE,"EXCEL SHEET")'); + +for ($row = 1; $row <= 5; ++$row) { + $cell = $worksheet->getCell("A{$row}"); + $helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}"); +} diff --git a/samples/Calculations/LookupRef/COLUMN.php b/samples/Calculations/LookupRef/COLUMN.php new file mode 100644 index 00000000..e9e58466 --- /dev/null +++ b/samples/Calculations/LookupRef/COLUMN.php @@ -0,0 +1,23 @@ +log('Returns the column index of a cell.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$worksheet->getCell('A1')->setValue('=COLUMN(C13)'); +$worksheet->getCell('A2')->setValue('=COLUMN(E13:G15)'); +$worksheet->getCell('F1')->setValue('=COLUMN()'); + +for ($row = 1; $row <= 2; ++$row) { + $cell = $worksheet->getCell("A{$row}"); + $helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}"); +} + +$cell = $worksheet->getCell('F1'); +$helper->log("F1: {$cell->getValue()} => {$cell->getCalculatedValue()}"); diff --git a/samples/Calculations/LookupRef/COLUMNS.php b/samples/Calculations/LookupRef/COLUMNS.php new file mode 100644 index 00000000..4d7f8d10 --- /dev/null +++ b/samples/Calculations/LookupRef/COLUMNS.php @@ -0,0 +1,21 @@ +log('Returns the number of columns in an array or reference.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$worksheet->getCell('A1')->setValue('=COLUMNS(C1:G4)'); +$worksheet->getCell('A2')->setValue('=COLUMNS({1,2,3;4,5,6})'); +$worksheet->getCell('A3')->setValue('=ROWS(C1:E4 D3:G5)'); +$worksheet->getCell('A4')->setValue('=COLUMNS(1:1)'); + +for ($row = 1; $row <= 4; ++$row) { + $cell = $worksheet->getCell("A{$row}"); + $helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}"); +} diff --git a/samples/Calculations/LookupRef/INDEX.php b/samples/Calculations/LookupRef/INDEX.php new file mode 100644 index 00000000..9ef0b945 --- /dev/null +++ b/samples/Calculations/LookupRef/INDEX.php @@ -0,0 +1,39 @@ +log('Returns the row index of a cell.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$data1 = [ + ['Apples', 'Lemons'], + ['Bananas', 'Pears'], +]; + +$data2 = [ + [4, 6], + [5, 3], + [6, 9], + [7, 5], + [8, 3], +]; + +$worksheet->fromArray($data1, null, 'A1'); +$worksheet->fromArray($data2, null, 'C1'); + +$worksheet->getCell('A11')->setValue('=INDEX(A1:B2, 2, 2)'); +$worksheet->getCell('A12')->setValue('=INDEX(A1:B2, 2, 1)'); +$worksheet->getCell('A13')->setValue('=INDEX({1,2;3,4}, 0, 2)'); +$worksheet->getCell('A14')->setValue('=INDEX(C1:C5, 5)'); +$worksheet->getCell('A15')->setValue('=INDEX(C1:D5, 5, 2)'); +$worksheet->getCell('A16')->setValue('=SUM(INDEX(C1:D5, 5, 0))'); + +for ($row = 11; $row <= 16; ++$row) { + $cell = $worksheet->getCell("A{$row}"); + $helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}"); +} diff --git a/samples/Calculations/LookupRef/INDIRECT.php b/samples/Calculations/LookupRef/INDIRECT.php new file mode 100644 index 00000000..ffbada9a --- /dev/null +++ b/samples/Calculations/LookupRef/INDIRECT.php @@ -0,0 +1,33 @@ +log('Returns the cell specified by a text string.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$data = [ + [8, 9, 0], + [3, 4, 5], + [9, 1, 3], + [4, 6, 2], +]; +$worksheet->fromArray($data, null, 'C1'); + +$spreadsheet->addNamedRange(new NamedRange('NAMED_RANGE_FOR_CELL_D4', $worksheet, '="$D$4"')); + +$worksheet->getCell('A1')->setValue('=INDIRECT("C1")'); +$worksheet->getCell('A2')->setValue('=INDIRECT("D"&4)'); +$worksheet->getCell('A3')->setValue('=INDIRECT("E"&ROW())'); +$worksheet->getCell('A4')->setValue('=SUM(INDIRECT("$C$4:$E$4"))'); +$worksheet->getCell('A5')->setValue('=INDIRECT(NAMED_RANGE_FOR_CELL_D4)'); + +for ($row = 1; $row <= 5; ++$row) { + $cell = $worksheet->getCell("A{$row}"); + $helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}"); +} diff --git a/samples/Calculations/LookupRef/OFFSET.php b/samples/Calculations/LookupRef/OFFSET.php new file mode 100644 index 00000000..ae613ec5 --- /dev/null +++ b/samples/Calculations/LookupRef/OFFSET.php @@ -0,0 +1,33 @@ +log('Returns a cell range that is a specified number of rows and columns from a cell or range of cells.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$data = [ + [null, 'Week 1', 'Week 2', 'Week 3', 'Week 4'], + ['Sunday', 4500, 2200, 3800, 1500], + ['Monday', 5500, 6100, 5200, 4800], + ['Tuesday', 7000, 6200, 5000, 7100], + ['Wednesday', 8000, 4000, 3900, 7600], + ['Thursday', 5900, 5500, 6900, 7100], + ['Friday', 4900, 6300, 6900, 5200], + ['Saturday', 3500, 3900, 5100, 4100], +]; +$worksheet->fromArray($data, null, 'A3'); + +$worksheet->getCell('H1')->setValue('=OFFSET(A3, 3, 1)'); +$worksheet->getCell('H2')->setValue('=SUM(OFFSET(A3, 3, 1, 1, 4))'); +$worksheet->getCell('H3')->setValue('=SUM(OFFSET(B3:E3, 3, 0))'); +$worksheet->getCell('H4')->setValue('=SUM(OFFSET(E3, 1, -3, 7))'); + +for ($row = 1; $row <= 4; ++$row) { + $cell = $worksheet->getCell("H{$row}"); + $helper->log("H{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}"); +} diff --git a/samples/Calculations/LookupRef/ROW.php b/samples/Calculations/LookupRef/ROW.php new file mode 100644 index 00000000..560639a5 --- /dev/null +++ b/samples/Calculations/LookupRef/ROW.php @@ -0,0 +1,20 @@ +log('Returns the row index of a cell.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$worksheet->getCell('A1')->setValue('=ROW(C13)'); +$worksheet->getCell('A2')->setValue('=ROW(E19:G21)'); +$worksheet->getCell('A3')->setValue('=ROW()'); + +for ($row = 1; $row <= 3; ++$row) { + $cell = $worksheet->getCell("A{$row}"); + $helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}"); +} diff --git a/samples/Calculations/LookupRef/ROWS.php b/samples/Calculations/LookupRef/ROWS.php new file mode 100644 index 00000000..3cdf085b --- /dev/null +++ b/samples/Calculations/LookupRef/ROWS.php @@ -0,0 +1,20 @@ +log('Returns the row index of a cell.'); + +// Create new PhpSpreadsheet object +$spreadsheet = new Spreadsheet(); +$worksheet = $spreadsheet->getActiveSheet(); + +$worksheet->getCell('A1')->setValue('=ROWS(C1:E4)'); +$worksheet->getCell('A2')->setValue('=ROWS({1,2,3;4,5,6})'); +$worksheet->getCell('A3')->setValue('=ROWS(C1:E4 D3:G5)'); + +for ($row = 1; $row <= 3; ++$row) { + $cell = $worksheet->getCell("A{$row}"); + $helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}"); +} diff --git a/samples/Chart/35_Chart_render.php b/samples/Chart/35_Chart_render.php index 9638c679..ebab16a7 100644 --- a/samples/Chart/35_Chart_render.php +++ b/samples/Chart/35_Chart_render.php @@ -5,6 +5,11 @@ 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); diff --git a/samples/Reader/20_Reader_worksheet_hyperlink_image.php b/samples/Reader/20_Reader_worksheet_hyperlink_image.php index 9dad4b6c..19d837a5 100644 --- a/samples/Reader/20_Reader_worksheet_hyperlink_image.php +++ b/samples/Reader/20_Reader_worksheet_hyperlink_image.php @@ -1,5 +1,6 @@ log('Write link: ' . $baseUrl); $drawing->setWorksheet($aSheet); -$filename = tempnam(\PhpOffice\PhpSpreadsheet\Shared\File::sysGetTempDir(), 'phpspreadsheet-test'); +$filename = File::temporaryFilename(); $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, $inputFileType); $writer->save($filename); diff --git a/samples/templates/30template.xls b/samples/templates/30template.xls index af8de03b..58fe333d 100644 Binary files a/samples/templates/30template.xls and b/samples/templates/30template.xls differ diff --git a/samples/templates/30templatebiff5.xls b/samples/templates/30templatebiff5.xls new file mode 100644 index 00000000..0523e83d Binary files /dev/null and b/samples/templates/30templatebiff5.xls differ diff --git a/samples/templates/sampleSpreadsheet.php b/samples/templates/sampleSpreadsheet.php index 6d9568be..b698ba89 100644 --- a/samples/templates/sampleSpreadsheet.php +++ b/samples/templates/sampleSpreadsheet.php @@ -31,7 +31,9 @@ $spreadsheet->getProperties()->setCreator('Maarten Balliauw') $helper->log('Add some data'); $spreadsheet->setActiveSheetIndex(0); $spreadsheet->getActiveSheet()->setCellValue('B1', 'Invoice'); -$spreadsheet->getActiveSheet()->setCellValue('D1', Date::PHPToExcel(gmmktime(0, 0, 0, date('m'), date('d'), date('Y')))); +$date = new DateTime('now'); +$date->setTime(0, 0, 0); +$spreadsheet->getActiveSheet()->setCellValue('D1', Date::PHPToExcel($date)); $spreadsheet->getActiveSheet()->getStyle('D1')->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_XLSX15); $spreadsheet->getActiveSheet()->setCellValue('E1', '#12566'); diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 99260e3b..e1deeb6a 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -130,7 +130,7 @@ class Calculation /** * Error message for any error that was raised/thrown by the calculation engine. * - * @var string + * @var null|string */ public $formulaError; @@ -204,7 +204,7 @@ class Calculation /** * Locale-specific translations for Excel constants (True, False and Null). * - * @var string[] + * @var array */ public static $localeBoolean = [ 'TRUE' => 'TRUE', @@ -216,7 +216,7 @@ class Calculation * Excel constant string translations to their PHP equivalents * Constant conversion from text name/value to actual (datatyped) value. * - * @var string[] + * @var array */ private static $excelConstants = [ 'TRUE' => true, @@ -228,42 +228,42 @@ class Calculation private static $phpSpreadsheetFunctions = [ 'ABS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'abs', + 'functionCall' => [MathTrig\Absolute::class, 'evaluate'], 'argumentCount' => '1', ], 'ACCRINT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'ACCRINT'], - 'argumentCount' => '4-7', + 'functionCall' => [Financial\Securities\AccruedInterest::class, 'periodic'], + 'argumentCount' => '4-8', ], 'ACCRINTM' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'ACCRINTM'], + 'functionCall' => [Financial\Securities\AccruedInterest::class, 'atMaturity'], 'argumentCount' => '3-5', ], 'ACOS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'acos', + 'functionCall' => [MathTrig\Acos::class, 'funcAcos'], 'argumentCount' => '1', ], 'ACOSH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'acosh', + 'functionCall' => [MathTrig\Acosh::class, 'funcAcosh'], 'argumentCount' => '1', ], 'ACOT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ACOT'], + 'functionCall' => [MathTrig\Acot::class, 'funcAcot'], 'argumentCount' => '1', ], 'ACOTH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ACOTH'], + 'functionCall' => [MathTrig\Acoth::class, 'funcAcoth'], 'argumentCount' => '1', ], 'ADDRESS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'cellAddress'], + 'functionCall' => [LookupRef\Address::class, 'cell'], 'argumentCount' => '2-5', ], 'AGGREGATE' => [ @@ -273,22 +273,22 @@ class Calculation ], 'AMORDEGRC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'AMORDEGRC'], + 'functionCall' => [Financial\Amortization::class, 'AMORDEGRC'], 'argumentCount' => '6,7', ], 'AMORLINC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'AMORLINC'], + 'functionCall' => [Financial\Amortization::class, 'AMORLINC'], 'argumentCount' => '6,7', ], 'AND' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'logicalAnd'], + 'functionCall' => [Logical\Operations::class, 'logicalAnd'], 'argumentCount' => '1+', ], 'ARABIC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ARABIC'], + 'functionCall' => [MathTrig\Arabic::class, 'evaluate'], 'argumentCount' => '1', ], 'AREAS' => [ @@ -303,52 +303,52 @@ class Calculation ], 'ASIN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'asin', + 'functionCall' => [MathTrig\Asin::class, 'funcAsin'], 'argumentCount' => '1', ], 'ASINH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'asinh', + 'functionCall' => [MathTrig\Asinh::class, 'funcAsinh'], 'argumentCount' => '1', ], 'ATAN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'atan', + 'functionCall' => [MathTrig\Atan::class, 'funcAtan'], 'argumentCount' => '1', ], 'ATAN2' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ATAN2'], + 'functionCall' => [MathTrig\Atan2::class, 'funcAtan2'], 'argumentCount' => '2', ], 'ATANH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'atanh', + 'functionCall' => [MathTrig\Atanh::class, 'funcAtanh'], 'argumentCount' => '1', ], 'AVEDEV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'AVEDEV'], + 'functionCall' => [Statistical\Averages::class, 'averageDeviations'], 'argumentCount' => '1+', ], 'AVERAGE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'AVERAGE'], + 'functionCall' => [Statistical\Averages::class, 'average'], 'argumentCount' => '1+', ], 'AVERAGEA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'AVERAGEA'], + 'functionCall' => [Statistical\Averages::class, 'averageA'], 'argumentCount' => '1+', ], 'AVERAGEIF' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'AVERAGEIF'], + 'functionCall' => [Statistical\Conditional::class, 'AVERAGEIF'], 'argumentCount' => '2,3', ], 'AVERAGEIFS' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Statistical\Conditional::class, 'AVERAGEIFS'], 'argumentCount' => '3+', ], 'BAHTTEXT' => [ @@ -358,32 +358,32 @@ class Calculation ], 'BASE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'BASE'], + 'functionCall' => [MathTrig\Base::class, 'funcBase'], 'argumentCount' => '2,3', ], 'BESSELI' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BESSELI'], + 'functionCall' => [Engineering\BesselI::class, 'BESSELI'], 'argumentCount' => '2', ], 'BESSELJ' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BESSELJ'], + 'functionCall' => [Engineering\BesselJ::class, 'BESSELJ'], 'argumentCount' => '2', ], 'BESSELK' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BESSELK'], + 'functionCall' => [Engineering\BesselK::class, 'BESSELK'], 'argumentCount' => '2', ], 'BESSELY' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BESSELY'], + 'functionCall' => [Engineering\BesselY::class, 'BESSELY'], 'argumentCount' => '2', ], 'BETADIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'BETADIST'], + 'functionCall' => [Statistical\Distributions\Beta::class, 'distribution'], 'argumentCount' => '3-5', ], 'BETA.DIST' => [ @@ -393,88 +393,88 @@ class Calculation ], 'BETAINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'BETAINV'], + 'functionCall' => [Statistical\Distributions\Beta::class, 'inverse'], 'argumentCount' => '3-5', ], 'BETA.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'BETAINV'], + 'functionCall' => [Statistical\Distributions\Beta::class, 'inverse'], 'argumentCount' => '3-5', ], 'BIN2DEC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BINTODEC'], + 'functionCall' => [Engineering\ConvertBinary::class, 'toDecimal'], 'argumentCount' => '1', ], 'BIN2HEX' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BINTOHEX'], + 'functionCall' => [Engineering\ConvertBinary::class, 'toHex'], 'argumentCount' => '1,2', ], 'BIN2OCT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BINTOOCT'], + 'functionCall' => [Engineering\ConvertBinary::class, 'toOctal'], 'argumentCount' => '1,2', ], 'BINOMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'BINOMDIST'], + 'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'], 'argumentCount' => '4', ], 'BINOM.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'BINOMDIST'], + 'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'], 'argumentCount' => '4', ], 'BINOM.DIST.RANGE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Statistical\Distributions\Binomial::class, 'range'], 'argumentCount' => '3,4', ], 'BINOM.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'], 'argumentCount' => '3', ], 'BITAND' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BITAND'], + 'functionCall' => [Engineering\BitWise::class, 'BITAND'], 'argumentCount' => '2', ], 'BITOR' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BITOR'], + 'functionCall' => [Engineering\BitWise::class, 'BITOR'], 'argumentCount' => '2', ], 'BITXOR' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BITOR'], + 'functionCall' => [Engineering\BitWise::class, 'BITXOR'], 'argumentCount' => '2', ], 'BITLSHIFT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BITLSHIFT'], + 'functionCall' => [Engineering\BitWise::class, 'BITLSHIFT'], 'argumentCount' => '2', ], 'BITRSHIFT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BITRSHIFT'], + 'functionCall' => [Engineering\BitWise::class, 'BITRSHIFT'], 'argumentCount' => '2', ], 'CEILING' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'CEILING'], - 'argumentCount' => '2', + 'functionCall' => [MathTrig\Ceiling::class, 'funcCeiling'], + 'argumentCount' => '1-2', // 2 for Excel, 1-2 for Ods/Gnumeric ], 'CEILING.MATH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [Functions::class, 'DUMMY'], - 'argumentCount' => '3', + 'functionCall' => [MathTrig\CeilingMath::class, 'funcCeilingMath'], + 'argumentCount' => '1-3', ], 'CEILING.PRECISE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [Functions::class, 'DUMMY'], - 'argumentCount' => '2', + 'functionCall' => [MathTrig\CeilingPrecise::class, 'funcCeilingPrecise'], + 'argumentCount' => '1,2', ], 'CELL' => [ 'category' => Category::CATEGORY_INFORMATION, @@ -483,47 +483,47 @@ class Calculation ], 'CHAR' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'CHARACTER'], + 'functionCall' => [TextData\CharacterConvert::class, 'character'], 'argumentCount' => '1', ], 'CHIDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CHIDIST'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'], 'argumentCount' => '2', ], 'CHISQ.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionLeftTail'], 'argumentCount' => '3', ], 'CHISQ.DIST.RT' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CHIDIST'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'], 'argumentCount' => '2', ], 'CHIINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CHIINV'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'], 'argumentCount' => '2', ], 'CHISQ.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseLeftTail'], 'argumentCount' => '2', ], 'CHISQ.INV.RT' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CHIINV'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'], 'argumentCount' => '2', ], 'CHITEST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'test'], 'argumentCount' => '2', ], 'CHISQ.TEST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'test'], 'argumentCount' => '2', ], 'CHOOSE' => [ @@ -533,58 +533,59 @@ class Calculation ], 'CLEAN' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'TRIMNONPRINTABLE'], + 'functionCall' => [TextData\Trim::class, 'nonPrintable'], 'argumentCount' => '1', ], 'CODE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'ASCIICODE'], + 'functionCall' => [TextData\CharacterConvert::class, 'code'], 'argumentCount' => '1', ], 'COLUMN' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'COLUMN'], + 'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMN'], 'argumentCount' => '-1', + 'passCellReference' => true, 'passByReference' => [true], ], 'COLUMNS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'COLUMNS'], + 'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMNS'], 'argumentCount' => '1', ], 'COMBIN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'COMBIN'], + 'functionCall' => [MathTrig\Combinations::class, 'withoutRepetition'], 'argumentCount' => '2', ], 'COMBINA' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [MathTrig\Combinations::class, 'withRepetition'], 'argumentCount' => '2', ], 'COMPLEX' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'COMPLEX'], + 'functionCall' => [Engineering\Complex::class, 'COMPLEX'], 'argumentCount' => '2,3', ], 'CONCAT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'CONCATENATE'], + 'functionCall' => [TextData\Concatenate::class, 'CONCATENATE'], 'argumentCount' => '1+', ], 'CONCATENATE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'CONCATENATE'], + 'functionCall' => [TextData\Concatenate::class, 'CONCATENATE'], 'argumentCount' => '1+', ], 'CONFIDENCE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CONFIDENCE'], + 'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'], 'argumentCount' => '3', ], 'CONFIDENCE.NORM' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CONFIDENCE'], + 'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'], 'argumentCount' => '3', ], 'CONFIDENCE.T' => [ @@ -594,97 +595,97 @@ class Calculation ], 'CONVERT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'CONVERTUOM'], + 'functionCall' => [Engineering\ConvertUOM::class, 'CONVERT'], 'argumentCount' => '3', ], 'CORREL' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CORREL'], + 'functionCall' => [Statistical\Trends::class, 'CORREL'], 'argumentCount' => '2', ], 'COS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'cos', + 'functionCall' => [MathTrig\Cos::class, 'funcCos'], 'argumentCount' => '1', ], 'COSH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'cosh', + 'functionCall' => [MathTrig\Cosh::class, 'funcCosh'], 'argumentCount' => '1', ], 'COT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'COT'], + 'functionCall' => [MathTrig\Cot::class, 'funcCot'], 'argumentCount' => '1', ], 'COTH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'COTH'], + 'functionCall' => [MathTrig\Coth::class, 'funcCoth'], 'argumentCount' => '1', ], 'COUNT' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COUNT'], + 'functionCall' => [Statistical\Counts::class, 'COUNT'], 'argumentCount' => '1+', ], 'COUNTA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COUNTA'], + 'functionCall' => [Statistical\Counts::class, 'COUNTA'], 'argumentCount' => '1+', ], 'COUNTBLANK' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COUNTBLANK'], + 'functionCall' => [Statistical\Counts::class, 'COUNTBLANK'], 'argumentCount' => '1', ], 'COUNTIF' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COUNTIF'], + 'functionCall' => [Statistical\Conditional::class, 'COUNTIF'], 'argumentCount' => '2', ], 'COUNTIFS' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COUNTIFS'], + 'functionCall' => [Statistical\Conditional::class, 'COUNTIFS'], 'argumentCount' => '2+', ], 'COUPDAYBS' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'COUPDAYBS'], + 'functionCall' => [Financial\Coupons::class, 'COUPDAYBS'], 'argumentCount' => '3,4', ], 'COUPDAYS' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'COUPDAYS'], + 'functionCall' => [Financial\Coupons::class, 'COUPDAYS'], 'argumentCount' => '3,4', ], 'COUPDAYSNC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'COUPDAYSNC'], + 'functionCall' => [Financial\Coupons::class, 'COUPDAYSNC'], 'argumentCount' => '3,4', ], 'COUPNCD' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'COUPNCD'], + 'functionCall' => [Financial\Coupons::class, 'COUPNCD'], 'argumentCount' => '3,4', ], 'COUPNUM' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'COUPNUM'], + 'functionCall' => [Financial\Coupons::class, 'COUPNUM'], 'argumentCount' => '3,4', ], 'COUPPCD' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'COUPPCD'], + 'functionCall' => [Financial\Coupons::class, 'COUPPCD'], 'argumentCount' => '3,4', ], 'COVAR' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COVAR'], + 'functionCall' => [Statistical\Trends::class, 'COVAR'], 'argumentCount' => '2', ], 'COVARIANCE.P' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COVAR'], + 'functionCall' => [Statistical\Trends::class, 'COVAR'], 'argumentCount' => '2', ], 'COVARIANCE.S' => [ @@ -694,17 +695,17 @@ class Calculation ], 'CRITBINOM' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CRITBINOM'], + 'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'], 'argumentCount' => '3', ], 'CSC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'CSC'], + 'functionCall' => [MathTrig\Csc::class, 'funcCsc'], 'argumentCount' => '1', ], 'CSCH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'CSCH'], + 'functionCall' => [MathTrig\Csch::class, 'funcCsch'], 'argumentCount' => '1', ], 'CUBEKPIMEMBER' => [ @@ -744,47 +745,47 @@ class Calculation ], 'CUMIPMT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'CUMIPMT'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class, 'interest'], 'argumentCount' => '6', ], 'CUMPRINC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'CUMPRINC'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class, 'principal'], 'argumentCount' => '6', ], 'DATE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATE'], + 'functionCall' => [DateTimeExcel\Datefunc::class, 'funcDate'], 'argumentCount' => '3', ], 'DATEDIF' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATEDIF'], + 'functionCall' => [DateTimeExcel\DateDif::class, 'funcDateDif'], 'argumentCount' => '2,3', ], 'DATEVALUE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATEVALUE'], + 'functionCall' => [DateTimeExcel\DateValue::class, 'funcDateValue'], 'argumentCount' => '1', ], 'DAVERAGE' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DAVERAGE'], + 'functionCall' => [Database\DAverage::class, 'evaluate'], 'argumentCount' => '3', ], 'DAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DAYOFMONTH'], + 'functionCall' => [DateTimeExcel\Day::class, 'funcDay'], 'argumentCount' => '1', ], 'DAYS' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DAYS'], + 'functionCall' => [DateTimeExcel\Days::class, 'funcDays'], 'argumentCount' => '2', ], 'DAYS360' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DAYS360'], + 'functionCall' => [DateTimeExcel\Days360::class, 'funcDays360'], 'argumentCount' => '2,3', ], 'DB' => [ @@ -799,12 +800,12 @@ class Calculation ], 'DCOUNT' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DCOUNT'], + 'functionCall' => [Database\DCount::class, 'evaluate'], 'argumentCount' => '3', ], 'DCOUNTA' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DCOUNTA'], + 'functionCall' => [Database\DCountA::class, 'evaluate'], 'argumentCount' => '3', ], 'DDB' => [ @@ -814,17 +815,17 @@ class Calculation ], 'DEC2BIN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'DECTOBIN'], + 'functionCall' => [Engineering\ConvertDecimal::class, 'toBinary'], 'argumentCount' => '1,2', ], 'DEC2HEX' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'DECTOHEX'], + 'functionCall' => [Engineering\ConvertDecimal::class, 'toHex'], 'argumentCount' => '1,2', ], 'DEC2OCT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'DECTOOCT'], + 'functionCall' => [Engineering\ConvertDecimal::class, 'toOctal'], 'argumentCount' => '1,2', ], 'DECIMAL' => [ @@ -834,12 +835,12 @@ class Calculation ], 'DEGREES' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'rad2deg', + 'functionCall' => [MathTrig\Degrees::class, 'evaluate'], 'argumentCount' => '1', ], 'DELTA' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'DELTA'], + 'functionCall' => [Engineering\Compare::class, 'DELTA'], 'argumentCount' => '1,2', ], 'DEVSQ' => [ @@ -849,57 +850,57 @@ class Calculation ], 'DGET' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DGET'], + 'functionCall' => [Database\DGet::class, 'evaluate'], 'argumentCount' => '3', ], 'DISC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'DISC'], + 'functionCall' => [Financial\Securities\Rates::class, 'discount'], 'argumentCount' => '4,5', ], 'DMAX' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DMAX'], + 'functionCall' => [Database\DMax::class, 'evaluate'], 'argumentCount' => '3', ], 'DMIN' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DMIN'], + 'functionCall' => [Database\DMin::class, 'evaluate'], 'argumentCount' => '3', ], 'DOLLAR' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'DOLLAR'], + 'functionCall' => [TextData\Format::class, 'DOLLAR'], 'argumentCount' => '1,2', ], 'DOLLARDE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'DOLLARDE'], + 'functionCall' => [Financial\Dollar::class, 'decimal'], 'argumentCount' => '2', ], 'DOLLARFR' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'DOLLARFR'], + 'functionCall' => [Financial\Dollar::class, 'fractional'], 'argumentCount' => '2', ], 'DPRODUCT' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DPRODUCT'], + 'functionCall' => [Database\DProduct::class, 'evaluate'], 'argumentCount' => '3', ], 'DSTDEV' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DSTDEV'], + 'functionCall' => [Database\DStDev::class, 'evaluate'], 'argumentCount' => '3', ], 'DSTDEVP' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DSTDEVP'], + 'functionCall' => [Database\DStDevP::class, 'evaluate'], 'argumentCount' => '3', ], 'DSUM' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DSUM'], + 'functionCall' => [Database\DSum::class, 'evaluate'], 'argumentCount' => '3', ], 'DURATION' => [ @@ -909,22 +910,22 @@ class Calculation ], 'DVAR' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DVAR'], + 'functionCall' => [Database\DVar::class, 'evaluate'], 'argumentCount' => '3', ], 'DVARP' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DVARP'], + 'functionCall' => [Database\DVarP::class, 'evaluate'], 'argumentCount' => '3', ], 'EDATE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'EDATE'], + 'functionCall' => [DateTimeExcel\EDate::class, 'funcEDate'], 'argumentCount' => '2', ], 'EFFECT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'EFFECT'], + 'functionCall' => [Financial\InterestRate::class, 'effective'], 'argumentCount' => '2', ], 'ENCODEURL' => [ @@ -934,27 +935,27 @@ class Calculation ], 'EOMONTH' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'EOMONTH'], + 'functionCall' => [DateTimeExcel\EoMonth::class, 'funcEoMonth'], 'argumentCount' => '2', ], 'ERF' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'ERF'], + 'functionCall' => [Engineering\Erf::class, 'ERF'], 'argumentCount' => '1,2', ], 'ERF.PRECISE' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'ERFPRECISE'], + 'functionCall' => [Engineering\Erf::class, 'ERFPRECISE'], 'argumentCount' => '1', ], 'ERFC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'ERFC'], + 'functionCall' => [Engineering\ErfC::class, 'ERFC'], 'argumentCount' => '1', ], 'ERFC.PRECISE' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'ERFC'], + 'functionCall' => [Engineering\ErfC::class, 'ERFC'], 'argumentCount' => '1', ], 'ERROR.TYPE' => [ @@ -964,42 +965,42 @@ class Calculation ], 'EVEN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'EVEN'], + 'functionCall' => [MathTrig\Even::class, 'funcEven'], 'argumentCount' => '1', ], 'EXACT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'EXACT'], + 'functionCall' => [TextData\Text::class, 'exact'], 'argumentCount' => '2', ], 'EXP' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'exp', + 'functionCall' => [MathTrig\Exp::class, 'evaluate'], 'argumentCount' => '1', ], 'EXPONDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'EXPONDIST'], + 'functionCall' => [Statistical\Distributions\Exponential::class, 'distribution'], 'argumentCount' => '3', ], 'EXPON.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'EXPONDIST'], + 'functionCall' => [Statistical\Distributions\Exponential::class, 'distribution'], 'argumentCount' => '3', ], 'FACT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'FACT'], + 'functionCall' => [MathTrig\Fact::class, 'funcFact'], 'argumentCount' => '1', ], 'FACTDOUBLE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'FACTDOUBLE'], + 'functionCall' => [MathTrig\FactDouble::class, 'evaluate'], 'argumentCount' => '1', ], 'FALSE' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'FALSE'], + 'functionCall' => [Logical\Boolean::class, 'FALSE'], 'argumentCount' => '0', ], 'FDIST' => [ @@ -1009,7 +1010,7 @@ class Calculation ], 'F.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'FDIST2'], + 'functionCall' => [Statistical\Distributions\F::class, 'distribution'], 'argumentCount' => '4', ], 'F.DIST.RT' => [ @@ -1029,12 +1030,12 @@ class Calculation ], 'FIND' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'SEARCHSENSITIVE'], + 'functionCall' => [TextData\Search::class, 'sensitive'], 'argumentCount' => '2,3', ], 'FINDB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'SEARCHSENSITIVE'], + 'functionCall' => [TextData\Search::class, 'sensitive'], 'argumentCount' => '2,3', ], 'FINV' => [ @@ -1054,37 +1055,37 @@ class Calculation ], 'FISHER' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'FISHER'], + 'functionCall' => [Statistical\Distributions\Fisher::class, 'distribution'], 'argumentCount' => '1', ], 'FISHERINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'FISHERINV'], + 'functionCall' => [Statistical\Distributions\Fisher::class, 'inverse'], 'argumentCount' => '1', ], 'FIXED' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'FIXEDFORMAT'], + 'functionCall' => [TextData\Format::class, 'FIXEDFORMAT'], 'argumentCount' => '1-3', ], 'FLOOR' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'FLOOR'], - 'argumentCount' => '2', + 'functionCall' => [MathTrig\Floor::class, 'funcFloor'], + 'argumentCount' => '1-2', // Excel requries 2, Ods/Gnumeric 1-2 ], 'FLOOR.MATH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'FLOORMATH'], - 'argumentCount' => '3', + 'functionCall' => [MathTrig\FloorMath::class, 'funcFloorMath'], + 'argumentCount' => '1-3', ], 'FLOOR.PRECISE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'FLOORPRECISE'], - 'argumentCount' => '2', + 'functionCall' => [MathTrig\FloorPrecise::class, 'funcFloorPrecise'], + 'argumentCount' => '1-2', ], 'FORECAST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'FORECAST'], + 'functionCall' => [Statistical\Trends::class, 'FORECAST'], 'argumentCount' => '3', ], 'FORECAST.ETS' => [ @@ -1109,7 +1110,7 @@ class Calculation ], 'FORECAST.LINEAR' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'FORECAST'], + 'functionCall' => [Statistical\Trends::class, 'FORECAST'], 'argumentCount' => '3', ], 'FORMULATEXT' => [ @@ -1136,47 +1137,47 @@ class Calculation ], 'FV' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'FV'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'futureValue'], 'argumentCount' => '3-5', ], 'FVSCHEDULE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'FVSCHEDULE'], + 'functionCall' => [Financial\CashFlow\Single::class, 'futureValue'], 'argumentCount' => '2', ], 'GAMMA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GAMMAFunction'], + 'functionCall' => [Statistical\Distributions\Gamma::class, 'gamma'], 'argumentCount' => '1', ], 'GAMMADIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GAMMADIST'], + 'functionCall' => [Statistical\Distributions\Gamma::class, 'distribution'], 'argumentCount' => '4', ], 'GAMMA.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GAMMADIST'], + 'functionCall' => [Statistical\Distributions\Gamma::class, 'distribution'], 'argumentCount' => '4', ], 'GAMMAINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GAMMAINV'], + 'functionCall' => [Statistical\Distributions\Gamma::class, 'inverse'], 'argumentCount' => '3', ], 'GAMMA.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GAMMAINV'], + 'functionCall' => [Statistical\Distributions\Gamma::class, 'inverse'], 'argumentCount' => '3', ], 'GAMMALN' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GAMMALN'], + 'functionCall' => [Statistical\Distributions\Gamma::class, 'ln'], 'argumentCount' => '1', ], 'GAMMALN.PRECISE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GAMMALN'], + 'functionCall' => [Statistical\Distributions\Gamma::class, 'ln'], 'argumentCount' => '1', ], 'GAUSS' => [ @@ -1186,7 +1187,7 @@ class Calculation ], 'GCD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'GCD'], + 'functionCall' => [MathTrig\Gcd::class, 'evaluate'], 'argumentCount' => '1+', ], 'GEOMEAN' => [ @@ -1196,7 +1197,7 @@ class Calculation ], 'GESTEP' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'GESTEP'], + 'functionCall' => [Engineering\Compare::class, 'GESTEP'], 'argumentCount' => '1,2', ], 'GETPIVOTDATA' => [ @@ -1206,7 +1207,7 @@ class Calculation ], 'GROWTH' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GROWTH'], + 'functionCall' => [Statistical\Trends::class, 'GROWTH'], 'argumentCount' => '1-4', ], 'HARMEAN' => [ @@ -1216,27 +1217,27 @@ class Calculation ], 'HEX2BIN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'HEXTOBIN'], + 'functionCall' => [Engineering\ConvertHex::class, 'toBinary'], 'argumentCount' => '1,2', ], 'HEX2DEC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'HEXTODEC'], + 'functionCall' => [Engineering\ConvertHex::class, 'toDecimal'], 'argumentCount' => '1', ], 'HEX2OCT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'HEXTOOCT'], + 'functionCall' => [Engineering\ConvertHex::class, 'toOctal'], 'argumentCount' => '1,2', ], 'HLOOKUP' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'HLOOKUP'], + 'functionCall' => [LookupRef\HLookup::class, 'lookup'], 'argumentCount' => '3,4', ], 'HOUR' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'HOUROFDAY'], + 'functionCall' => [DateTimeExcel\Hour::class, 'funcHour'], 'argumentCount' => '1', ], 'HYPERLINK' => [ @@ -1247,7 +1248,7 @@ class Calculation ], 'HYPGEOMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'HYPGEOMDIST'], + 'functionCall' => [Statistical\Distributions\HyperGeometric::class, 'distribution'], 'argumentCount' => '4', ], 'HYPGEOM.DIST' => [ @@ -1257,147 +1258,147 @@ class Calculation ], 'IF' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'statementIf'], + 'functionCall' => [Logical\Conditional::class, 'statementIf'], 'argumentCount' => '1-3', ], 'IFERROR' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'IFERROR'], + 'functionCall' => [Logical\Conditional::class, 'IFERROR'], 'argumentCount' => '2', ], 'IFNA' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'IFNA'], + 'functionCall' => [Logical\Conditional::class, 'IFNA'], 'argumentCount' => '2', ], 'IFS' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'IFS'], + 'functionCall' => [Logical\Conditional::class, 'IFS'], 'argumentCount' => '2+', ], 'IMABS' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMABS'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMABS'], 'argumentCount' => '1', ], 'IMAGINARY' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMAGINARY'], + 'functionCall' => [Engineering\Complex::class, 'IMAGINARY'], 'argumentCount' => '1', ], 'IMARGUMENT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMARGUMENT'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMARGUMENT'], 'argumentCount' => '1', ], 'IMCONJUGATE' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMCONJUGATE'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCONJUGATE'], 'argumentCount' => '1', ], 'IMCOS' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMCOS'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOS'], 'argumentCount' => '1', ], 'IMCOSH' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMCOSH'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOSH'], 'argumentCount' => '1', ], 'IMCOT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMCOT'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOT'], 'argumentCount' => '1', ], 'IMCSC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMCSC'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCSC'], 'argumentCount' => '1', ], 'IMCSCH' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMCSCH'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCSCH'], 'argumentCount' => '1', ], 'IMDIV' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMDIV'], + 'functionCall' => [Engineering\ComplexOperations::class, 'IMDIV'], 'argumentCount' => '2', ], 'IMEXP' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMEXP'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMEXP'], 'argumentCount' => '1', ], 'IMLN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMLN'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLN'], 'argumentCount' => '1', ], 'IMLOG10' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMLOG10'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLOG10'], 'argumentCount' => '1', ], 'IMLOG2' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMLOG2'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLOG2'], 'argumentCount' => '1', ], 'IMPOWER' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMPOWER'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMPOWER'], 'argumentCount' => '2', ], 'IMPRODUCT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMPRODUCT'], + 'functionCall' => [Engineering\ComplexOperations::class, 'IMPRODUCT'], 'argumentCount' => '1+', ], 'IMREAL' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMREAL'], + 'functionCall' => [Engineering\Complex::class, 'IMREAL'], 'argumentCount' => '1', ], 'IMSEC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSEC'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSEC'], 'argumentCount' => '1', ], 'IMSECH' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSECH'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSECH'], 'argumentCount' => '1', ], 'IMSIN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSIN'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSIN'], 'argumentCount' => '1', ], 'IMSINH' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSINH'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSINH'], 'argumentCount' => '1', ], 'IMSQRT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSQRT'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSQRT'], 'argumentCount' => '1', ], 'IMSUB' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSUB'], + 'functionCall' => [Engineering\ComplexOperations::class, 'IMSUB'], 'argumentCount' => '2', ], 'IMSUM' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSUM'], + 'functionCall' => [Engineering\ComplexOperations::class, 'IMSUM'], 'argumentCount' => '1+', ], 'IMTAN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMTAN'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMTAN'], 'argumentCount' => '1', ], 'INDEX' => [ @@ -1407,7 +1408,7 @@ class Calculation ], 'INDIRECT' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'INDIRECT'], + 'functionCall' => [LookupRef\Indirect::class, 'INDIRECT'], 'argumentCount' => '1,2', 'passCellReference' => true, ], @@ -1418,27 +1419,27 @@ class Calculation ], 'INT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'INT'], + 'functionCall' => [MathTrig\IntClass::class, 'funcInt'], 'argumentCount' => '1', ], 'INTERCEPT' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'INTERCEPT'], + 'functionCall' => [Statistical\Trends::class, 'INTERCEPT'], 'argumentCount' => '2', ], 'INTRATE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'INTRATE'], + 'functionCall' => [Financial\Securities\Rates::class, 'interest'], 'argumentCount' => '4,5', ], 'IPMT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'IPMT'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'payment'], 'argumentCount' => '4-6', ], 'IRR' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'IRR'], + 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'rate'], 'argumentCount' => '1,2', ], 'ISBLANK' => [ @@ -1500,12 +1501,12 @@ class Calculation ], 'ISOWEEKNUM' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'ISOWEEKNUM'], + 'functionCall' => [DateTimeExcel\IsoWeekNum::class, 'funcIsoWeekNum'], 'argumentCount' => '1', ], 'ISPMT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'ISPMT'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'schedulePayment'], 'argumentCount' => '4', ], 'ISREF' => [ @@ -1535,107 +1536,107 @@ class Calculation ], 'LCM' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'LCM'], + 'functionCall' => [MathTrig\Lcm::class, 'funcLcm'], 'argumentCount' => '1+', ], 'LEFT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'LEFT'], + 'functionCall' => [TextData\Extract::class, 'left'], 'argumentCount' => '1,2', ], 'LEFTB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'LEFT'], + 'functionCall' => [TextData\Extract::class, 'left'], 'argumentCount' => '1,2', ], 'LEN' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'STRINGLENGTH'], + 'functionCall' => [TextData\Text::class, 'length'], 'argumentCount' => '1', ], 'LENB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'STRINGLENGTH'], + 'functionCall' => [TextData\Text::class, 'length'], 'argumentCount' => '1', ], 'LINEST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LINEST'], + 'functionCall' => [Statistical\Trends::class, 'LINEST'], 'argumentCount' => '1-4', ], 'LN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'log', + 'functionCall' => [MathTrig\Logarithms::class, 'natural'], 'argumentCount' => '1', ], 'LOG' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'logBase'], + 'functionCall' => [MathTrig\Logarithms::class, 'withBase'], 'argumentCount' => '1,2', ], 'LOG10' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'log10', + 'functionCall' => [MathTrig\Logarithms::class, 'base10'], 'argumentCount' => '1', ], 'LOGEST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LOGEST'], + 'functionCall' => [Statistical\Trends::class, 'LOGEST'], 'argumentCount' => '1-4', ], 'LOGINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LOGINV'], + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'], 'argumentCount' => '3', ], 'LOGNORMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LOGNORMDIST'], + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'cumulative'], 'argumentCount' => '3', ], 'LOGNORM.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LOGNORMDIST2'], + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'distribution'], 'argumentCount' => '4', ], 'LOGNORM.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LOGINV'], + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'], 'argumentCount' => '3', ], 'LOOKUP' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'LOOKUP'], + 'functionCall' => [LookupRef\Lookup::class, 'lookup'], 'argumentCount' => '2,3', ], 'LOWER' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'LOWERCASE'], + 'functionCall' => [TextData\CaseConvert::class, 'lower'], 'argumentCount' => '1', ], 'MATCH' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'MATCH'], + 'functionCall' => [LookupRef\ExcelMatch::class, 'MATCH'], 'argumentCount' => '2,3', ], 'MAX' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MAX'], + 'functionCall' => [Statistical\Maximum::class, 'MAX'], 'argumentCount' => '1+', ], 'MAXA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MAXA'], + 'functionCall' => [Statistical\Maximum::class, 'MAXA'], 'argumentCount' => '1+', ], 'MAXIFS' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MAXIFS'], + 'functionCall' => [Statistical\Conditional::class, 'MAXIFS'], 'argumentCount' => '3+', ], 'MDETERM' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MDETERM'], + 'functionCall' => [MathTrig\MatrixFunctions::class, 'determinant'], 'argumentCount' => '1', ], 'MDURATION' => [ @@ -1645,7 +1646,7 @@ class Calculation ], 'MEDIAN' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MEDIAN'], + 'functionCall' => [Statistical\Averages::class, 'median'], 'argumentCount' => '1+', ], 'MEDIANIF' => [ @@ -1655,57 +1656,57 @@ class Calculation ], 'MID' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'MID'], + 'functionCall' => [TextData\Extract::class, 'mid'], 'argumentCount' => '3', ], 'MIDB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'MID'], + 'functionCall' => [TextData\Extract::class, 'mid'], 'argumentCount' => '3', ], 'MIN' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MIN'], + 'functionCall' => [Statistical\Minimum::class, 'MIN'], 'argumentCount' => '1+', ], 'MINA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MINA'], + 'functionCall' => [Statistical\Minimum::class, 'MINA'], 'argumentCount' => '1+', ], 'MINIFS' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MINIFS'], + 'functionCall' => [Statistical\Conditional::class, 'MINIFS'], 'argumentCount' => '3+', ], 'MINUTE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'MINUTE'], + 'functionCall' => [DateTimeExcel\Minute::class, 'funcMinute'], 'argumentCount' => '1', ], 'MINVERSE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MINVERSE'], + 'functionCall' => [MathTrig\MatrixFunctions::class, 'inverse'], 'argumentCount' => '1', ], 'MIRR' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'MIRR'], + 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'modifiedRate'], 'argumentCount' => '3', ], 'MMULT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MMULT'], + 'functionCall' => [MathTrig\MatrixFunctions::class, 'multiply'], 'argumentCount' => '2', ], 'MOD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MOD'], + 'functionCall' => [MathTrig\Mod::class, 'evaluate'], 'argumentCount' => '2', ], 'MODE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MODE'], + 'functionCall' => [Statistical\Averages::class, 'mode'], 'argumentCount' => '1+', ], 'MODE.MULT' => [ @@ -1715,27 +1716,27 @@ class Calculation ], 'MODE.SNGL' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MODE'], + 'functionCall' => [Statistical\Averages::class, 'mode'], 'argumentCount' => '1+', ], 'MONTH' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'MONTHOFYEAR'], + 'functionCall' => [DateTimeExcel\Month::class, 'funcMonth'], 'argumentCount' => '1', ], 'MROUND' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MROUND'], + 'functionCall' => [MathTrig\Mround::class, 'funcMround'], 'argumentCount' => '2', ], 'MULTINOMIAL' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MULTINOMIAL'], + 'functionCall' => [MathTrig\Multinomial::class, 'funcMultinomial'], 'argumentCount' => '1+', ], 'MUNIT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [MathTrig\MatrixFunctions::class, 'funcMUnit'], 'argumentCount' => '1', ], 'N' => [ @@ -1750,7 +1751,7 @@ class Calculation ], 'NEGBINOMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NEGBINOMDIST'], + 'functionCall' => [Statistical\Distributions\Binomial::class, 'negative'], 'argumentCount' => '3', ], 'NEGBINOM.DIST' => [ @@ -1760,7 +1761,7 @@ class Calculation ], 'NETWORKDAYS' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'NETWORKDAYS'], + 'functionCall' => [DateTimeExcel\NetworkDays::class, 'funcNetworkDays'], 'argumentCount' => '2-3', ], 'NETWORKDAYS.INTL' => [ @@ -1770,92 +1771,92 @@ class Calculation ], 'NOMINAL' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'NOMINAL'], + 'functionCall' => [Financial\InterestRate::class, 'nominal'], 'argumentCount' => '2', ], 'NORMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMDIST'], + 'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'], 'argumentCount' => '4', ], 'NORM.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMDIST'], + 'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'], 'argumentCount' => '4', ], 'NORMINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMINV'], + 'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'], 'argumentCount' => '3', ], 'NORM.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMINV'], + 'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'], 'argumentCount' => '3', ], 'NORMSDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMSDIST'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'cumulative'], 'argumentCount' => '1', ], 'NORM.S.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMSDIST2'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'distribution'], 'argumentCount' => '1,2', ], 'NORMSINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMSINV'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'], 'argumentCount' => '1', ], 'NORM.S.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMSINV'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'], 'argumentCount' => '1', ], 'NOT' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'NOT'], + 'functionCall' => [Logical\Operations::class, 'NOT'], 'argumentCount' => '1', ], 'NOW' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATETIMENOW'], + 'functionCall' => [DateTimeExcel\Now::class, 'funcNow'], 'argumentCount' => '0', ], 'NPER' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'NPER'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'periods'], 'argumentCount' => '3-5', ], 'NPV' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'NPV'], + 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'presentValue'], 'argumentCount' => '2+', ], 'NUMBERVALUE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'NUMBERVALUE'], + 'functionCall' => [TextData\Format::class, 'NUMBERVALUE'], 'argumentCount' => '1+', ], 'OCT2BIN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'OCTTOBIN'], + 'functionCall' => [Engineering\ConvertOctal::class, 'toBinary'], 'argumentCount' => '1,2', ], 'OCT2DEC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'OCTTODEC'], + 'functionCall' => [Engineering\ConvertOctal::class, 'toDecimal'], 'argumentCount' => '1', ], 'OCT2HEX' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'OCTTOHEX'], + 'functionCall' => [Engineering\ConvertOctal::class, 'toHex'], 'argumentCount' => '1,2', ], 'ODD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ODD'], + 'functionCall' => [MathTrig\Odd::class, 'funcOdd'], 'argumentCount' => '1', ], 'ODDFPRICE' => [ @@ -1880,29 +1881,29 @@ class Calculation ], 'OFFSET' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'OFFSET'], + 'functionCall' => [LookupRef\Offset::class, 'OFFSET'], 'argumentCount' => '3-5', 'passCellReference' => true, 'passByReference' => [true], ], 'OR' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'logicalOr'], + 'functionCall' => [Logical\Operations::class, 'logicalOr'], 'argumentCount' => '1+', ], 'PDURATION' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PDURATION'], + 'functionCall' => [Financial\CashFlow\Single::class, 'periods'], 'argumentCount' => '3', ], 'PEARSON' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CORREL'], + 'functionCall' => [Statistical\Trends::class, 'CORREL'], 'argumentCount' => '2', ], 'PERCENTILE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'PERCENTILE'], + 'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'], 'argumentCount' => '2', ], 'PERCENTILE.EXC' => [ @@ -1912,12 +1913,12 @@ class Calculation ], 'PERCENTILE.INC' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'PERCENTILE'], + 'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'], 'argumentCount' => '2', ], 'PERCENTRANK' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'PERCENTRANK'], + 'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'], 'argumentCount' => '2,3', ], 'PERCENTRANK.EXC' => [ @@ -1927,17 +1928,17 @@ class Calculation ], 'PERCENTRANK.INC' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'PERCENTRANK'], + 'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'], 'argumentCount' => '2,3', ], 'PERMUT' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'PERMUT'], + 'functionCall' => [Statistical\Permutations::class, 'PERMUT'], 'argumentCount' => '2', ], 'PERMUTATIONA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Statistical\Permutations::class, 'PERMUTATIONA'], 'argumentCount' => '2', ], 'PHONETIC' => [ @@ -1957,42 +1958,42 @@ class Calculation ], 'PMT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PMT'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class, 'annuity'], 'argumentCount' => '3-5', ], 'POISSON' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'POISSON'], + 'functionCall' => [Statistical\Distributions\Poisson::class, 'distribution'], 'argumentCount' => '3', ], 'POISSON.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'POISSON'], + 'functionCall' => [Statistical\Distributions\Poisson::class, 'distribution'], 'argumentCount' => '3', ], 'POWER' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'POWER'], + 'functionCall' => [MathTrig\Power::class, 'evaluate'], 'argumentCount' => '2', ], 'PPMT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PPMT'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class, 'interestPayment'], 'argumentCount' => '4-6', ], 'PRICE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PRICE'], + 'functionCall' => [Financial\Securities\Price::class, 'price'], 'argumentCount' => '6,7', ], 'PRICEDISC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PRICEDISC'], + 'functionCall' => [Financial\Securities\Price::class, 'priceDiscounted'], 'argumentCount' => '4,5', ], 'PRICEMAT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PRICEMAT'], + 'functionCall' => [Financial\Securities\Price::class, 'priceAtMaturity'], 'argumentCount' => '5,6', ], 'PROB' => [ @@ -2002,22 +2003,22 @@ class Calculation ], 'PRODUCT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'PRODUCT'], + 'functionCall' => [MathTrig\Product::class, 'funcProduct'], 'argumentCount' => '1+', ], 'PROPER' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'PROPERCASE'], + 'functionCall' => [TextData\CaseConvert::class, 'proper'], 'argumentCount' => '1', ], 'PV' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PV'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'presentValue'], 'argumentCount' => '3-5', ], 'QUARTILE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'QUARTILE'], + 'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'], 'argumentCount' => '2', ], 'QUARTILE.EXC' => [ @@ -2027,22 +2028,22 @@ class Calculation ], 'QUARTILE.INC' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'QUARTILE'], + 'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'], 'argumentCount' => '2', ], 'QUOTIENT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'QUOTIENT'], + 'functionCall' => [MathTrig\Quotient::class, 'funcQuotient'], 'argumentCount' => '2', ], 'RADIANS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'deg2rad', + 'functionCall' => [MathTrig\Radians::class, 'evaluate'], 'argumentCount' => '1', ], 'RAND' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'RAND'], + 'functionCall' => [MathTrig\Random::class, 'randNoArgs'], 'argumentCount' => '0', ], 'RANDARRAY' => [ @@ -2052,12 +2053,12 @@ class Calculation ], 'RANDBETWEEN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'RAND'], + 'functionCall' => [MathTrig\Random::class, 'randBetween'], 'argumentCount' => '2', ], 'RANK' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'RANK'], + 'functionCall' => [Statistical\Percentiles::class, 'RANK'], 'argumentCount' => '2,3', ], 'RANK.AVG' => [ @@ -2072,78 +2073,79 @@ class Calculation ], 'RATE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'RATE'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'rate'], 'argumentCount' => '3-6', ], 'RECEIVED' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'RECEIVED'], + 'functionCall' => [Financial\Securities\Price::class, 'received'], 'argumentCount' => '4-5', ], 'REPLACE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'REPLACE'], + 'functionCall' => [TextData\Replace::class, 'replace'], 'argumentCount' => '4', ], 'REPLACEB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'REPLACE'], + 'functionCall' => [TextData\Replace::class, 'replace'], 'argumentCount' => '4', ], 'REPT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => 'str_repeat', + 'functionCall' => [TextData\Concatenate::class, 'builtinREPT'], 'argumentCount' => '2', ], 'RIGHT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'RIGHT'], + 'functionCall' => [TextData\Extract::class, 'right'], 'argumentCount' => '1,2', ], 'RIGHTB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'RIGHT'], + 'functionCall' => [TextData\Extract::class, 'right'], 'argumentCount' => '1,2', ], 'ROMAN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ROMAN'], + 'functionCall' => [MathTrig\Roman::class, 'funcRoman'], 'argumentCount' => '1,2', ], 'ROUND' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'round', + 'functionCall' => [MathTrig\Round::class, 'builtinROUND'], 'argumentCount' => '2', ], 'ROUNDDOWN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ROUNDDOWN'], + 'functionCall' => [MathTrig\RoundDown::class, 'funcRoundDown'], 'argumentCount' => '2', ], 'ROUNDUP' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ROUNDUP'], + 'functionCall' => [MathTrig\RoundUp::class, 'funcRoundUp'], 'argumentCount' => '2', ], 'ROW' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'ROW'], + 'functionCall' => [LookupRef\RowColumnInformation::class, 'ROW'], 'argumentCount' => '-1', + 'passCellReference' => true, 'passByReference' => [true], ], 'ROWS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'ROWS'], + 'functionCall' => [LookupRef\RowColumnInformation::class, 'ROWS'], 'argumentCount' => '1', ], 'RRI' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'RRI'], + 'functionCall' => [Financial\CashFlow\Single::class, 'interestRate'], 'argumentCount' => '3', ], 'RSQ' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'RSQ'], + 'functionCall' => [Statistical\Trends::class, 'RSQ'], 'argumentCount' => '2', ], 'RTD' => [ @@ -2153,27 +2155,27 @@ class Calculation ], 'SEARCH' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'SEARCHINSENSITIVE'], + 'functionCall' => [TextData\Search::class, 'insensitive'], 'argumentCount' => '2,3', ], 'SEARCHB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'SEARCHINSENSITIVE'], + 'functionCall' => [TextData\Search::class, 'insensitive'], 'argumentCount' => '2,3', ], 'SEC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SEC'], + 'functionCall' => [MathTrig\Sec::class, 'funcSec'], 'argumentCount' => '1', ], 'SECH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SECH'], + 'functionCall' => [MathTrig\Sech::class, 'funcSech'], 'argumentCount' => '1', ], 'SECOND' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'SECOND'], + 'functionCall' => [DateTimeExcel\Second::class, 'funcSecond'], 'argumentCount' => '1', ], 'SEQUENCE' => [ @@ -2183,7 +2185,7 @@ class Calculation ], 'SERIESSUM' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SERIESSUM'], + 'functionCall' => [MathTrig\SeriesSum::class, 'funcSeriesSum'], 'argumentCount' => '4', ], 'SHEET' => [ @@ -2198,17 +2200,17 @@ class Calculation ], 'SIGN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SIGN'], + 'functionCall' => [MathTrig\Sign::class, 'funcSign'], 'argumentCount' => '1', ], 'SIN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'sin', + 'functionCall' => [MathTrig\Sin::class, 'funcSin'], 'argumentCount' => '1', ], 'SINH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'sinh', + 'functionCall' => [MathTrig\Sinh::class, 'funcSinh'], 'argumentCount' => '1', ], 'SKEW' => [ @@ -2223,12 +2225,12 @@ class Calculation ], 'SLN' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'SLN'], + 'functionCall' => [Financial\Depreciation::class, 'SLN'], 'argumentCount' => '3', ], 'SLOPE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'SLOPE'], + 'functionCall' => [Statistical\Trends::class, 'SLOPE'], 'argumentCount' => '2', ], 'SMALL' => [ @@ -2248,12 +2250,12 @@ class Calculation ], 'SQRT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'sqrt', + 'functionCall' => [MathTrig\Sqrt::class, 'evaluate'], 'argumentCount' => '1', ], 'SQRTPI' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SQRTPI'], + 'functionCall' => [MathTrig\SqrtPi::class, 'evaluate'], 'argumentCount' => '1', ], 'STANDARDIZE' => [ @@ -2263,22 +2265,22 @@ class Calculation ], 'STDEV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STDEV'], + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEV'], 'argumentCount' => '1+', ], 'STDEV.S' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STDEV'], + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEV'], 'argumentCount' => '1+', ], 'STDEV.P' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STDEVP'], + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVP'], 'argumentCount' => '1+', ], 'STDEVA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STDEVA'], + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVA'], 'argumentCount' => '1+', ], 'STDEVP' => [ @@ -2293,103 +2295,103 @@ class Calculation ], 'STEYX' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STEYX'], + 'functionCall' => [Statistical\Trends::class, 'STEYX'], 'argumentCount' => '2', ], 'SUBSTITUTE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'SUBSTITUTE'], + 'functionCall' => [TextData\Replace::class, 'substitute'], 'argumentCount' => '3,4', ], 'SUBTOTAL' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUBTOTAL'], + 'functionCall' => [MathTrig\Subtotal::class, 'funcSubtotal'], 'argumentCount' => '2+', 'passCellReference' => true, ], 'SUM' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUM'], + 'functionCall' => [MathTrig\Sum::class, 'funcSumNoStrings'], 'argumentCount' => '1+', ], 'SUMIF' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMIF'], + 'functionCall' => [Statistical\Conditional::class, 'SUMIF'], 'argumentCount' => '2,3', ], 'SUMIFS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMIFS'], + 'functionCall' => [Statistical\Conditional::class, 'SUMIFS'], 'argumentCount' => '3+', ], 'SUMPRODUCT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMPRODUCT'], + 'functionCall' => [MathTrig\SumProduct::class, 'funcSumProduct'], 'argumentCount' => '1+', ], 'SUMSQ' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMSQ'], + 'functionCall' => [MathTrig\SumSquares::class, 'sumSquare'], 'argumentCount' => '1+', ], 'SUMX2MY2' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMX2MY2'], + 'functionCall' => [MathTrig\SumSquares::class, 'sumXSquaredMinusYSquared'], 'argumentCount' => '2', ], 'SUMX2PY2' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMX2PY2'], + 'functionCall' => [MathTrig\SumSquares::class, 'sumXSquaredPlusYSquared'], 'argumentCount' => '2', ], 'SUMXMY2' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMXMY2'], + 'functionCall' => [MathTrig\SumSquares::class, 'sumXMinusYSquared'], 'argumentCount' => '2', ], 'SWITCH' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'statementSwitch'], + 'functionCall' => [Logical\Conditional::class, 'statementSwitch'], 'argumentCount' => '3+', ], 'SYD' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'SYD'], + 'functionCall' => [Financial\Depreciation::class, 'SYD'], 'argumentCount' => '4', ], 'T' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'RETURNSTRING'], + 'functionCall' => [TextData\Text::class, 'test'], 'argumentCount' => '1', ], 'TAN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'tan', + 'functionCall' => [MathTrig\Tan::class, 'funcTan'], 'argumentCount' => '1', ], 'TANH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'tanh', + 'functionCall' => [MathTrig\Tanh::class, 'funcTanh'], 'argumentCount' => '1', ], 'TBILLEQ' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'TBILLEQ'], + 'functionCall' => [Financial\TreasuryBill::class, 'bondEquivalentYield'], 'argumentCount' => '3', ], 'TBILLPRICE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'TBILLPRICE'], + 'functionCall' => [Financial\TreasuryBill::class, 'price'], 'argumentCount' => '3', ], 'TBILLYIELD' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'TBILLYIELD'], + 'functionCall' => [Financial\TreasuryBill::class, 'yield'], 'argumentCount' => '3', ], 'TDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'TDIST'], + 'functionCall' => [Statistical\Distributions\StudentT::class, 'distribution'], 'argumentCount' => '3', ], 'T.DIST' => [ @@ -2409,7 +2411,7 @@ class Calculation ], 'TEXT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'TEXTFORMAT'], + 'functionCall' => [TextData\Format::class, 'TEXTFORMAT'], 'argumentCount' => '2', ], 'TEXTJOIN' => [ @@ -2419,22 +2421,22 @@ class Calculation ], 'TIME' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'TIME'], + 'functionCall' => [DateTimeExcel\Time::class, 'funcTime'], 'argumentCount' => '3', ], 'TIMEVALUE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'TIMEVALUE'], + 'functionCall' => [DateTimeExcel\TimeValue::class, 'funcTimeValue'], 'argumentCount' => '1', ], 'TINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'TINV'], + 'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'], 'argumentCount' => '2', ], 'T.INV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'TINV'], + 'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'], 'argumentCount' => '2', ], 'T.INV.2T' => [ @@ -2444,22 +2446,22 @@ class Calculation ], 'TODAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATENOW'], + 'functionCall' => [DateTimeExcel\Today::class, 'funcToday'], 'argumentCount' => '0', ], 'TRANSPOSE' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'TRANSPOSE'], + 'functionCall' => [LookupRef\Matrix::class, 'transpose'], 'argumentCount' => '1', ], 'TREND' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'TREND'], + 'functionCall' => [Statistical\Trends::class, 'TREND'], 'argumentCount' => '1-4', ], 'TRIM' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'TRIMSPACES'], + 'functionCall' => [TextData\Trim::class, 'spaces'], 'argumentCount' => '1', ], 'TRIMMEAN' => [ @@ -2469,12 +2471,12 @@ class Calculation ], 'TRUE' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'TRUE'], + 'functionCall' => [Logical\Boolean::class, 'TRUE'], 'argumentCount' => '0', ], 'TRUNC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'TRUNC'], + 'functionCall' => [MathTrig\Trunc::class, 'funcTrunc'], 'argumentCount' => '1,2', ], 'TTEST' => [ @@ -2494,12 +2496,12 @@ class Calculation ], 'UNICHAR' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'CHARACTER'], + 'functionCall' => [TextData\CharacterConvert::class, 'character'], 'argumentCount' => '1', ], 'UNICODE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'ASCIICODE'], + 'functionCall' => [TextData\CharacterConvert::class, 'code'], 'argumentCount' => '1', ], 'UNIQUE' => [ @@ -2509,7 +2511,7 @@ class Calculation ], 'UPPER' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'UPPERCASE'], + 'functionCall' => [TextData\CaseConvert::class, 'upper'], 'argumentCount' => '1', ], 'USDOLLAR' => [ @@ -2519,37 +2521,37 @@ class Calculation ], 'VALUE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'VALUE'], + 'functionCall' => [TextData\Format::class, 'VALUE'], 'argumentCount' => '1', ], 'VAR' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'VARFunc'], + 'functionCall' => [Statistical\Variances::class, 'VAR'], 'argumentCount' => '1+', ], 'VAR.P' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'VARP'], + 'functionCall' => [Statistical\Variances::class, 'VARP'], 'argumentCount' => '1+', ], 'VAR.S' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'VARFunc'], + 'functionCall' => [Statistical\Variances::class, 'VAR'], 'argumentCount' => '1+', ], 'VARA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'VARA'], + 'functionCall' => [Statistical\Variances::class, 'VARA'], 'argumentCount' => '1+', ], 'VARP' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'VARP'], + 'functionCall' => [Statistical\Variances::class, 'VARP'], 'argumentCount' => '1+', ], 'VARPA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'VARPA'], + 'functionCall' => [Statistical\Variances::class, 'VARPA'], 'argumentCount' => '1+', ], 'VDB' => [ @@ -2559,7 +2561,7 @@ class Calculation ], 'VLOOKUP' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'VLOOKUP'], + 'functionCall' => [LookupRef\VLookup::class, 'lookup'], 'argumentCount' => '3,4', ], 'WEBSERVICE' => [ @@ -2569,27 +2571,27 @@ class Calculation ], 'WEEKDAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'WEEKDAY'], + 'functionCall' => [DateTimeExcel\WeekDay::class, 'funcWeekDay'], 'argumentCount' => '1,2', ], 'WEEKNUM' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'WEEKNUM'], + 'functionCall' => [DateTimeExcel\WeekNum::class, 'funcWeekNum'], 'argumentCount' => '1,2', ], 'WEIBULL' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'WEIBULL'], + 'functionCall' => [Statistical\Distributions\Weibull::class, 'distribution'], 'argumentCount' => '4', ], 'WEIBULL.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'WEIBULL'], + 'functionCall' => [Statistical\Distributions\Weibull::class, 'distribution'], 'argumentCount' => '4', ], 'WORKDAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'WORKDAY'], + 'functionCall' => [DateTimeExcel\WorkDay::class, 'funcWorkDay'], 'argumentCount' => '2-3', ], 'WORKDAY.INTL' => [ @@ -2619,17 +2621,17 @@ class Calculation ], 'XOR' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'logicalXor'], + 'functionCall' => [Logical\Operations::class, 'logicalXor'], 'argumentCount' => '1+', ], 'YEAR' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'YEAR'], + 'functionCall' => [DateTimeExcel\Year::class, 'funcYear'], 'argumentCount' => '1', ], 'YEARFRAC' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'YEARFRAC'], + 'functionCall' => [DateTimeExcel\YearFrac::class, 'funcYearFrac'], 'argumentCount' => '2,3', ], 'YIELD' => [ @@ -2639,22 +2641,22 @@ class Calculation ], 'YIELDDISC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'YIELDDISC'], + 'functionCall' => [Financial\Securities\Yields::class, 'yieldDiscounted'], 'argumentCount' => '4,5', ], 'YIELDMAT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'YIELDMAT'], + 'functionCall' => [Financial\Securities\Yields::class, 'yieldAtMaturity'], 'argumentCount' => '5,6', ], 'ZTEST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'ZTEST'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'], 'argumentCount' => '2-3', ], 'Z.TEST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'ZTEST'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'], 'argumentCount' => '2-3', ], ]; @@ -2663,12 +2665,16 @@ class Calculation private static $controlFunctions = [ 'MKMATRIX' => [ 'argumentCount' => '*', - 'functionCall' => [__CLASS__, 'mkMatrix'], + 'functionCall' => [Internal\MakeMatrix::class, 'make'], ], 'NAME.ERROR' => [ 'argumentCount' => '*', 'functionCall' => [Functions::class, 'NAME'], ], + 'WILDCARDMATCH' => [ + 'argumentCount' => '2', + 'functionCall' => [Internal\WildcardMatch::class, 'compare'], + ], ]; public function __construct(?Spreadsheet $spreadsheet = null) @@ -2695,12 +2701,10 @@ class Calculation /** * Get an instance of this class. * - * @param Spreadsheet $spreadsheet Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object, - * or NULL to create a standalone claculation engine - * - * @return Calculation + * @param ?Spreadsheet $spreadsheet Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object, + * or NULL to create a standalone calculation engine */ - public static function getInstance(?Spreadsheet $spreadsheet = null) + public static function getInstance(?Spreadsheet $spreadsheet = null): self { if ($spreadsheet !== null) { $instance = $spreadsheet->getCalculationEngine(); @@ -2749,7 +2753,7 @@ class Calculation * * @return string locale-specific translation of TRUE */ - public static function getTRUE() + public static function getTRUE(): string { return self::$localeBoolean['TRUE']; } @@ -2759,7 +2763,7 @@ class Calculation * * @return string locale-specific translation of FALSE */ - public static function getFALSE() + public static function getFALSE(): string { return self::$localeBoolean['FALSE']; } @@ -3150,6 +3154,7 @@ class Calculation // Return Excel errors "as is" return $value; } + // Return strings wrapped in quotes return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE; } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) { @@ -3341,18 +3346,15 @@ class Calculation } /** - * @param string $cellReference * @param mixed $cellValue - * - * @return bool */ - public function getValueFromCache($cellReference, &$cellValue) + public function getValueFromCache(string $cellReference, &$cellValue): bool { + $this->debugLog->writeDebugLog("Testing cache value for cell {$cellReference}"); // Is calculation cacheing enabled? - // Is the value present in calculation cache? - $this->debugLog->writeDebugLog('Testing cache value for cell ', $cellReference); + // If so, is the required value present in calculation cache? if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) { - $this->debugLog->writeDebugLog('Retrieving value for cell ', $cellReference, ' from cache'); + $this->debugLog->writeDebugLog("Retrieving value for cell {$cellReference} from cache"); // Return the cached result $cellValue = $this->calculationCache[$cellReference]; @@ -3414,7 +3416,7 @@ class Calculation if (($cellID !== null) && ($this->getValueFromCache($wsCellReference, $cellValue))) { return $cellValue; } - $this->debugLog->writeDebugLog('Evaluating formula for cell ', $wsCellReference); + $this->debugLog->writeDebugLog("Evaluating formula for cell {$wsCellReference}"); if (($wsTitle[0] !== "\x00") && ($this->cyclicReferenceStack->onStack($wsCellReference))) { if ($this->cyclicFormulaCount <= 0) { @@ -3436,9 +3438,10 @@ class Calculation } } - $this->debugLog->writeDebugLog('Formula for cell ', $wsCellReference, ' is ', $formula); + $this->debugLog->writeDebugLog("Formula for cell {$wsCellReference} is {$formula}"); // Parse the formula onto the token stack and calculate the value $this->cyclicReferenceStack->push($wsCellReference); + $cellValue = $this->processTokenStack($this->internalParseFormula($formula, $pCell), $cellID, $pCell); $this->cyclicReferenceStack->pop(); @@ -3454,8 +3457,8 @@ class Calculation /** * Ensure that paired matrix operands are both matrices and of the same size. * - * @param mixed &$operand1 First matrix operand - * @param mixed &$operand2 Second matrix operand + * @param mixed $operand1 First matrix operand + * @param mixed $operand2 Second matrix operand * @param int $resize Flag indicating whether the matrices should be resized to match * and (if so), whether the smaller dimension should grow or the * larger should shrink. @@ -3499,7 +3502,7 @@ class Calculation /** * Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0. * - * @param array &$matrix matrix operand + * @param array $matrix matrix operand * * @return int[] An array comprising the number of rows, and number of columns */ @@ -3524,8 +3527,8 @@ class Calculation /** * Ensure that paired matrix operands are both matrices of the same size. * - * @param mixed &$matrix1 First matrix operand - * @param mixed &$matrix2 Second matrix operand + * @param mixed $matrix1 First matrix operand + * @param mixed $matrix2 Second matrix operand * @param int $matrix1Rows Row size of first matrix operand * @param int $matrix1Columns Column size of first matrix operand * @param int $matrix2Rows Row size of second matrix operand @@ -3567,8 +3570,8 @@ class Calculation /** * Ensure that paired matrix operands are both matrices of the same size. * - * @param mixed &$matrix1 First matrix operand - * @param mixed &$matrix2 Second matrix operand + * @param mixed $matrix1 First matrix operand + * @param mixed $matrix2 Second matrix operand * @param int $matrix1Rows Row size of first matrix operand * @param int $matrix1Columns Column size of first matrix operand * @param int $matrix2Rows Row size of second matrix operand @@ -3685,6 +3688,8 @@ class Calculation return $typeString . ' with a value of ' . $this->showValue($value); } + + return null; } /** @@ -3743,11 +3748,6 @@ class Calculation return $formula; } - private static function mkMatrix(...$args) - { - return $args; - } - // Binary Operators // These operators always work on two values // Array key is the operator, the value indicates whether this is a left or right associative operator @@ -3784,7 +3784,7 @@ class Calculation /** * @param string $formula * - * @return bool + * @return array|false */ private function internalParseFormula($formula, ?Cell $pCell = null) { @@ -3797,13 +3797,13 @@ class Calculation $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null; $regexpMatchString = '/^(' . self::CALCULATION_REGEXP_FUNCTION . - '|' . self::CALCULATION_REGEXP_CELLREF . - '|' . self::CALCULATION_REGEXP_NUMBER . - '|' . self::CALCULATION_REGEXP_STRING . - '|' . self::CALCULATION_REGEXP_OPENBRACE . - '|' . self::CALCULATION_REGEXP_DEFINEDNAME . - '|' . self::CALCULATION_REGEXP_ERROR . - ')/sui'; + '|' . self::CALCULATION_REGEXP_CELLREF . + '|' . self::CALCULATION_REGEXP_NUMBER . + '|' . self::CALCULATION_REGEXP_STRING . + '|' . self::CALCULATION_REGEXP_OPENBRACE . + '|' . self::CALCULATION_REGEXP_DEFINEDNAME . + '|' . self::CALCULATION_REGEXP_ERROR . + ')/sui'; // Start with initialisation $index = 0; @@ -3942,6 +3942,7 @@ class Calculation } // Check the argument count $argumentCountError = false; + $expectedArgumentCountString = null; if (is_numeric($expectedArgumentCount)) { if ($expectedArgumentCount < 0) { if ($argumentCount > abs($expectedArgumentCount)) { @@ -4206,7 +4207,7 @@ class Calculation ((preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) && ($output[count($output) - 1]['type'] == 'Cell Reference') || (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', substr($formula, $index), $match)) && - ($output[count($output) - 1]['type'] == 'Defined Name' || $output[count($output) - 1]['type'] == 'Value') + ($output[count($output) - 1]['type'] == 'Defined Name' || $output[count($output) - 1]['type'] == 'Value') ) ) { while ( @@ -4255,7 +4256,7 @@ class Calculation * @param mixed $tokens * @param null|string $cellID * - * @return bool + * @return array|false */ private function processTokenStack($tokens, $cellID = null, ?Cell $pCell = null) { @@ -4648,6 +4649,9 @@ class Calculation $this->debugLog->writeDebugLog('Evaluating Function ', self::localeFunc($functionName), '() with ', (($argCount == 0) ? 'no' : $argCount), ' argument', (($argCount == 1) ? '' : 's')); } if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) { // function + $passByReference = false; + $passCellReference = false; + $functionCall = null; if (isset(self::$phpSpreadsheetFunctions[$functionName])) { $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall']; $passByReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference']); @@ -4805,6 +4809,53 @@ class Calculation return true; } + /** + * @param null|string $cellID + * @param mixed $operand1 + * @param mixed $operand2 + * @param string $operation + * + * @return array + */ + private function executeArrayComparison($cellID, $operand1, $operand2, $operation, Stack &$stack, bool $recursingArrays) + { + $result = []; + if (!is_array($operand2)) { + // Operand 1 is an array, Operand 2 is a scalar + foreach ($operand1 as $x => $operandData) { + $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2)); + $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2, $operation, $stack); + $r = $stack->pop(); + $result[$x] = $r['value']; + } + } elseif (!is_array($operand1)) { + // Operand 1 is a scalar, Operand 2 is an array + foreach ($operand2 as $x => $operandData) { + $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operand1), ' ', $operation, ' ', $this->showValue($operandData)); + $this->executeBinaryComparisonOperation($cellID, $operand1, $operandData, $operation, $stack); + $r = $stack->pop(); + $result[$x] = $r['value']; + } + } else { + // Operand 1 and Operand 2 are both arrays + if (!$recursingArrays) { + self::checkMatrixOperands($operand1, $operand2, 2); + } + foreach ($operand1 as $x => $operandData) { + $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2[$x])); + $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2[$x], $operation, $stack, true); + $r = $stack->pop(); + $result[$x] = $r['value']; + } + } + // Log the result details + $this->debugLog->writeDebugLog('Comparison Evaluation Result is ', $this->showTypeDetails($result)); + // And push the result onto the stack + $stack->push('Array', $result); + + return $result; + } + /** * @param null|string $cellID * @param mixed $operand1 @@ -4818,38 +4869,7 @@ class Calculation { // If we're dealing with matrix operations, we want a matrix result if ((is_array($operand1)) || (is_array($operand2))) { - $result = []; - if ((is_array($operand1)) && (!is_array($operand2))) { - foreach ($operand1 as $x => $operandData) { - $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2)); - $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2, $operation, $stack); - $r = $stack->pop(); - $result[$x] = $r['value']; - } - } elseif ((!is_array($operand1)) && (is_array($operand2))) { - foreach ($operand2 as $x => $operandData) { - $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operand1), ' ', $operation, ' ', $this->showValue($operandData)); - $this->executeBinaryComparisonOperation($cellID, $operand1, $operandData, $operation, $stack); - $r = $stack->pop(); - $result[$x] = $r['value']; - } - } else { - if (!$recursingArrays) { - self::checkMatrixOperands($operand1, $operand2, 2); - } - foreach ($operand1 as $x => $operandData) { - $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2[$x])); - $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2[$x], $operation, $stack, true); - $r = $stack->pop(); - $result[$x] = $r['value']; - } - } - // Log the result details - $this->debugLog->writeDebugLog('Comparison Evaluation Result is ', $this->showTypeDetails($result)); - // And push the result onto the stack - $stack->push('Array', $result); - - return $result; + return $this->executeArrayComparison($cellID, $operand1, $operand2, $operation, $stack, $recursingArrays); } // Simple validate the two operands if they are string values @@ -4863,10 +4883,10 @@ class Calculation // Use case insensitive comparaison if not OpenOffice mode if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) { if (is_string($operand1)) { - $operand1 = strtoupper($operand1); + $operand1 = Shared\StringHelper::strToUpper($operand1); } if (is_string($operand2)) { - $operand2 = strtoupper($operand2); + $operand2 = Shared\StringHelper::strToUpper($operand2); } } @@ -4897,7 +4917,7 @@ class Calculation if (is_numeric($operand1) && is_numeric($operand2)) { $result = (abs($operand1 - $operand2) < $this->delta); } else { - $result = strcmp($operand1, $operand2) == 0; + $result = $this->strcmpAllowNull($operand1, $operand2) == 0; } break; @@ -4908,7 +4928,7 @@ class Calculation } elseif ($useLowercaseFirstComparison) { $result = $this->strcmpLowercaseFirst($operand1, $operand2) >= 0; } else { - $result = strcmp($operand1, $operand2) >= 0; + $result = $this->strcmpAllowNull($operand1, $operand2) >= 0; } break; @@ -4919,7 +4939,7 @@ class Calculation } elseif ($useLowercaseFirstComparison) { $result = $this->strcmpLowercaseFirst($operand1, $operand2) <= 0; } else { - $result = strcmp($operand1, $operand2) <= 0; + $result = $this->strcmpAllowNull($operand1, $operand2) <= 0; } break; @@ -4928,10 +4948,13 @@ class Calculation if (is_numeric($operand1) && is_numeric($operand2)) { $result = (abs($operand1 - $operand2) > 1E-14); } else { - $result = strcmp($operand1, $operand2) != 0; + $result = $this->strcmpAllowNull($operand1, $operand2) != 0; } break; + + default: + throw new Exception('Unsupported binary comparison operation'); } // Log the result details @@ -4945,8 +4968,8 @@ class Calculation /** * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters. * - * @param string $str1 First string value for the comparison - * @param string $str2 Second string value for the comparison + * @param null|string $str1 First string value for the comparison + * @param null|string $str2 Second string value for the comparison * * @return int */ @@ -4955,7 +4978,20 @@ class Calculation $inversedStr1 = Shared\StringHelper::strCaseReverse($str1); $inversedStr2 = Shared\StringHelper::strCaseReverse($str2); - return strcmp($inversedStr1, $inversedStr2); + return strcmp($inversedStr1 ?? '', $inversedStr2 ?? ''); + } + + /** + * PHP8.1 deprecates passing null to strcmp. + * + * @param null|string $str1 First string value for the comparison + * @param null|string $str2 Second string value for the comparison + * + * @return int + */ + private function strcmpAllowNull($str1, $str2) + { + return strcmp($str1 ?? '', $str2 ?? ''); } /** @@ -5036,6 +5072,9 @@ class Calculation $result = $operand1 ** $operand2; break; + + default: + throw new Exception('Unsupported numeric binary operation'); } } } @@ -5064,7 +5103,7 @@ class Calculation /** * Extract range values. * - * @param string &$pRange String based range representation + * @param string $pRange String based range representation * @param Worksheet $pSheet Worksheet * @param bool $resetLog Flag indicating whether calculation log should be reset or not * @@ -5117,7 +5156,7 @@ class Calculation /** * Extract range values. * - * @param string &$pRange String based range representation + * @param string $pRange String based range representation * @param Worksheet $pSheet Worksheet * @param bool $resetLog Flag indicating whether calculation log should be reset or not * @@ -5195,10 +5234,8 @@ class Calculation /** * Get a list of all implemented functions as an array of function objects. - * - * @return array of Category */ - public function getFunctions() + public function getFunctions(): array { return self::$phpSpreadsheetFunctions; } @@ -5297,9 +5334,7 @@ class Calculation $recursiveCalculationCell = ($definedNameWorksheet !== null && $definedNameWorksheet !== $pCellWorksheet) ? $definedNameWorksheet->getCell('A1') : $pCell; - $recursiveCalculationCellAddress = $recursiveCalculationCell !== null - ? $recursiveCalculationCell->getCoordinate() - : null; + $recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate(); // Adjust relative references in ranges and formulae so that we execute the calculation for the correct rows and columns $definedNameValue = self::$referenceHelper->updateFormulaReferencesAnyWorksheet( diff --git a/src/PhpSpreadsheet/Calculation/Database.php b/src/PhpSpreadsheet/Calculation/Database.php index 2ba4af2d..a4c4d7d2 100644 --- a/src/PhpSpreadsheet/Calculation/Database.php +++ b/src/PhpSpreadsheet/Calculation/Database.php @@ -2,126 +2,11 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; +/** + * @deprecated 1.17.0 + */ class Database { - /** - * fieldExtract. - * - * Extracts the column ID to use for the data field. - * - * @param mixed[] $database The range of cells that makes up the list or database. - * A database is a list of related data in which rows of related - * information are records, and columns of data are fields. The - * first row of the list contains labels for each column. - * @param mixed $field Indicates which column is used in the function. Enter the - * column label enclosed between double quotation marks, such as - * "Age" or "Yield," or a number (without quotation marks) that - * represents the position of the column within the list: 1 for - * the first column, 2 for the second column, and so on. - * - * @return null|string - */ - private static function fieldExtract($database, $field) - { - $field = strtoupper(Functions::flattenSingleValue($field)); - $fieldNames = array_map('strtoupper', array_shift($database)); - - if (is_numeric($field)) { - $keys = array_keys($fieldNames); - - return $keys[$field - 1]; - } - $key = array_search($field, $fieldNames); - - return ($key) ? $key : null; - } - - /** - * filter. - * - * Parses the selection criteria, extracts the database rows that match those criteria, and - * returns that subset of rows. - * - * @param mixed[] $database The range of cells that makes up the list or database. - * A database is a list of related data in which rows of related - * information are records, and columns of data are fields. The - * first row of the list contains labels for each column. - * @param mixed[] $criteria The range of cells that contains the conditions you specify. - * You can use any range for the criteria argument, as long as it - * includes at least one column label and at least one cell below - * the column label in which you specify a condition for the - * column. - * - * @return array of mixed - */ - private static function filter($database, $criteria) - { - $fieldNames = array_shift($database); - $criteriaNames = array_shift($criteria); - - // Convert the criteria into a set of AND/OR conditions with [:placeholders] - $testConditions = $testValues = []; - $testConditionsCount = 0; - foreach ($criteriaNames as $key => $criteriaName) { - $testCondition = []; - $testConditionCount = 0; - foreach ($criteria as $row => $criterion) { - if ($criterion[$key] > '') { - $testCondition[] = '[:' . $criteriaName . ']' . Functions::ifCondition($criterion[$key]); - ++$testConditionCount; - } - } - if ($testConditionCount > 1) { - $testConditions[] = 'OR(' . implode(',', $testCondition) . ')'; - ++$testConditionsCount; - } elseif ($testConditionCount == 1) { - $testConditions[] = $testCondition[0]; - ++$testConditionsCount; - } - } - - if ($testConditionsCount > 1) { - $testConditionSet = 'AND(' . implode(',', $testConditions) . ')'; - } elseif ($testConditionsCount == 1) { - $testConditionSet = $testConditions[0]; - } - - // Loop through each row of the database - foreach ($database as $dataRow => $dataValues) { - // Substitute actual values from the database row for our [:placeholders] - $testConditionList = $testConditionSet; - foreach ($criteriaNames as $key => $criteriaName) { - $k = array_search($criteriaName, $fieldNames); - if (isset($dataValues[$k])) { - $dataValue = $dataValues[$k]; - $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue; - $testConditionList = str_replace('[:' . $criteriaName . ']', $dataValue, $testConditionList); - } - } - // evaluate the criteria against the row data - $result = Calculation::getInstance()->_calculateFormulaValue('=' . $testConditionList); - // If the row failed to meet the criteria, remove it from the database - if (!$result) { - unset($database[$dataRow]); - } - } - - return $database; - } - - private static function getFilteredColumn($database, $field, $criteria) - { - // reduce the database to a set of rows that match all the criteria - $database = self::filter($database, $criteria); - // extract an array of values for the requested column - $colData = []; - foreach ($database as $row) { - $colData[] = $row[$field]; - } - - return $colData; - } - /** * DAVERAGE. * @@ -130,6 +15,10 @@ class Database * Excel Function: * DAVERAGE(database,field,criteria) * + * @Deprecated 1.17.0 + * + * @see Use the evaluate() method in the Database\DAverage class instead + * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The @@ -145,19 +34,11 @@ class Database * the column label in which you specify a condition for the * column. * - * @return float|string + * @return null|float|string */ public static function DAVERAGE($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::AVERAGE( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DAverage::evaluate($database, $field, $criteria); } /** @@ -169,14 +50,15 @@ class Database * Excel Function: * DCOUNT(database,[field],criteria) * - * Excel Function: - * DAVERAGE(database,field,criteria) + * @Deprecated 1.17.0 + * + * @see Use the evaluate() method in the Database\DCount class instead * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The * first row of the list contains labels for each column. - * @param int|string $field Indicates which column is used in the function. Enter the + * @param null|int|string $field Indicates which column is used in the function. Enter the * column label enclosed between double quotation marks, such as * "Age" or "Yield," or a number (without quotation marks) that * represents the position of the column within the list: 1 for @@ -194,15 +76,7 @@ class Database */ public static function DCOUNT($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::COUNT( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DCount::evaluate($database, $field, $criteria); } /** @@ -213,11 +87,15 @@ class Database * Excel Function: * DCOUNTA(database,[field],criteria) * + * @Deprecated 1.17.0 + * + * @see Use the evaluate() method in the Database\DCountA class instead + * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The * first row of the list contains labels for each column. - * @param int|string $field Indicates which column is used in the function. Enter the + * @param null|int|string $field Indicates which column is used in the function. Enter the * column label enclosed between double quotation marks, such as * "Age" or "Yield," or a number (without quotation marks) that * represents the position of the column within the list: 1 for @@ -229,29 +107,10 @@ class Database * column. * * @return int - * - * @TODO The field argument is optional. If field is omitted, DCOUNTA counts all records in the - * database that match the criteria. */ public static function DCOUNTA($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // reduce the database to a set of rows that match all the criteria - $database = self::filter($database, $criteria); - // extract an array of values for the requested column - $colData = []; - foreach ($database as $row) { - $colData[] = $row[$field]; - } - - // Return - return Statistical::COUNTA( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DCountA::evaluate($database, $field, $criteria); } /** @@ -263,6 +122,10 @@ class Database * Excel Function: * DGET(database,field,criteria) * + * @Deprecated 1.17.0 + * + * @see Use the evaluate() method in the Database\DGet class instead + * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The @@ -282,18 +145,7 @@ class Database */ public static function DGET($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - $colData = self::getFilteredColumn($database, $field, $criteria); - if (count($colData) > 1) { - return Functions::NAN(); - } - - return $colData[0]; + return Database\DGet::evaluate($database, $field, $criteria); } /** @@ -305,6 +157,10 @@ class Database * Excel Function: * DMAX(database,field,criteria) * + * @Deprecated 1.17.0 + * + * @see Use the evaluate() method in the Database\DMax class instead + * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The @@ -324,15 +180,7 @@ class Database */ public static function DMAX($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::MAX( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DMax::evaluate($database, $field, $criteria); } /** @@ -344,6 +192,10 @@ class Database * Excel Function: * DMIN(database,field,criteria) * + * @Deprecated 1.17.0 + * + * @see Use the evaluate() method in the Database\DMin class instead + * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The @@ -363,15 +215,7 @@ class Database */ public static function DMIN($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::MIN( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DMin::evaluate($database, $field, $criteria); } /** @@ -382,6 +226,10 @@ class Database * Excel Function: * DPRODUCT(database,field,criteria) * + * @Deprecated 1.17.0 + * + * @see Use the evaluate() method in the Database\DProduct class instead + * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The @@ -397,19 +245,11 @@ class Database * the column label in which you specify a condition for the * column. * - * @return float + * @return float|string */ public static function DPRODUCT($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return MathTrig::PRODUCT( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DProduct::evaluate($database, $field, $criteria); } /** @@ -421,6 +261,10 @@ class Database * Excel Function: * DSTDEV(database,field,criteria) * + * @Deprecated 1.17.0 + * + * @see Use the evaluate() method in the Database\DStDev class instead + * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The @@ -440,15 +284,7 @@ class Database */ public static function DSTDEV($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::STDEV( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DStDev::evaluate($database, $field, $criteria); } /** @@ -460,6 +296,10 @@ class Database * Excel Function: * DSTDEVP(database,field,criteria) * + * @Deprecated 1.17.0 + * + * @see Use the evaluate() method in the Database\DStDevP class instead + * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The @@ -479,15 +319,7 @@ class Database */ public static function DSTDEVP($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::STDEVP( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DStDevP::evaluate($database, $field, $criteria); } /** @@ -498,6 +330,10 @@ class Database * Excel Function: * DSUM(database,field,criteria) * + * @Deprecated 1.17.0 + * + * @see Use the evaluate() method in the Database\DSum class instead + * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The @@ -513,19 +349,11 @@ class Database * the column label in which you specify a condition for the * column. * - * @return float + * @return float|string */ public static function DSUM($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return MathTrig::SUM( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DSum::evaluate($database, $field, $criteria); } /** @@ -537,6 +365,10 @@ class Database * Excel Function: * DVAR(database,field,criteria) * + * @Deprecated 1.17.0 + * + * @see Use the evaluate() method in the Database\DVar class instead + * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The @@ -556,15 +388,7 @@ class Database */ public static function DVAR($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::VARFunc( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DVar::evaluate($database, $field, $criteria); } /** @@ -576,6 +400,10 @@ class Database * Excel Function: * DVARP(database,field,criteria) * + * @Deprecated 1.17.0 + * + * @see Use the evaluate() method in the Database\DVarP class instead + * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The @@ -595,14 +423,6 @@ class Database */ public static function DVARP($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::VARP( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DVarP::evaluate($database, $field, $criteria); } } diff --git a/src/PhpSpreadsheet/Calculation/Database/DAverage.php b/src/PhpSpreadsheet/Calculation/Database/DAverage.php new file mode 100644 index 00000000..e30842dc --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Database/DAverage.php @@ -0,0 +1,45 @@ + 1) { + return Functions::NAN(); + } + + $row = array_pop($columnData); + + return array_pop($row); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Database/DMax.php b/src/PhpSpreadsheet/Calculation/Database/DMax.php new file mode 100644 index 00000000..e84a0bfc --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Database/DMax.php @@ -0,0 +1,46 @@ + $row) { + $keys = array_keys($row); + $key = $keys[$field] ?? null; + $columnKey = $key ?? 'A'; + $columnData[$rowKey][$columnKey] = $row[$key] ?? $defaultReturnColumnValue; + } + + return $columnData; + } + + private static function buildQuery(array $criteriaNames, array $criteria): string + { + $baseQuery = []; + foreach ($criteria as $key => $criterion) { + foreach ($criterion as $field => $value) { + $criterionName = $criteriaNames[$field]; + if ($value !== null && $value !== '') { + $condition = self::buildCondition($value, $criterionName); + $baseQuery[$key][] = $condition; + } + } + } + + $rowQuery = array_map( + function ($rowValue) { + return (count($rowValue) > 1) ? 'AND(' . implode(',', $rowValue) . ')' : $rowValue[0]; + }, + $baseQuery + ); + + return (count($rowQuery) > 1) ? 'OR(' . implode(',', $rowQuery) . ')' : $rowQuery[0]; + } + + private static function buildCondition($criterion, string $criterionName): string + { + $ifCondition = Functions::ifCondition($criterion); + + // Check for wildcard characters used in the condition + $result = preg_match('/(?[^"]*)(?".*[*?].*")/ui', $ifCondition, $matches); + if ($result !== 1) { + return "[:{$criterionName}]{$ifCondition}"; + } + + $trueFalse = ($matches['operator'] !== '<>'); + $wildcard = WildcardMatch::wildcard($matches['operand']); + $condition = "WILDCARDMATCH([:{$criterionName}],{$wildcard})"; + if ($trueFalse === false) { + $condition = "NOT({$condition})"; + } + + return $condition; + } + + private static function executeQuery(array $database, string $query, array $criteria, array $fields): array + { + foreach ($database as $dataRow => $dataValues) { + // Substitute actual values from the database row for our [:placeholders] + $conditions = $query; + foreach ($criteria as $criterion) { + $conditions = self::processCondition($criterion, $fields, $dataValues, $conditions); + } + + // evaluate the criteria against the row data + $result = Calculation::getInstance()->_calculateFormulaValue('=' . $conditions); + + // If the row failed to meet the criteria, remove it from the database + if ($result !== true) { + unset($database[$dataRow]); + } + } + + return $database; + } + + private static function processCondition(string $criterion, array $fields, array $dataValues, string $conditions) + { + $key = array_search($criterion, $fields, true); + + $dataValue = 'NULL'; + if (is_bool($dataValues[$key])) { + $dataValue = ($dataValues[$key]) ? 'TRUE' : 'FALSE'; + } elseif ($dataValues[$key] !== null) { + $dataValue = $dataValues[$key]; + // escape quotes if we have a string containing quotes + if (is_string($dataValue) && strpos($dataValue, '"') !== false) { + $dataValue = str_replace('"', '""', $dataValue); + } + $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue; + } + + return str_replace('[:' . $criterion . ']', $dataValue, $conditions); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTime.php b/src/PhpSpreadsheet/Calculation/DateTime.php index 4c2b108a..7643ed0b 100644 --- a/src/PhpSpreadsheet/Calculation/DateTime.php +++ b/src/PhpSpreadsheet/Calculation/DateTime.php @@ -2,127 +2,40 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; -use DateTimeImmutable; use DateTimeInterface; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PhpOffice\PhpSpreadsheet\Shared\StringHelper; class DateTime { /** * Identify if a year is a leap year or not. * + * @Deprecated 2.0.0 Use the method isLeapYear in the DateTimeExcel\Helpers class instead + * * @param int|string $year The year to test * * @return bool TRUE if the year is a leap year, otherwise FALSE */ public static function isLeapYear($year) { - return (($year % 4) === 0) && (($year % 100) !== 0) || (($year % 400) === 0); - } - - /** - * Return the number of days between two dates based on a 360 day calendar. - * - * @param int $startDay Day of month of the start date - * @param int $startMonth Month of the start date - * @param int $startYear Year of the start date - * @param int $endDay Day of month of the start date - * @param int $endMonth Month of the start date - * @param int $endYear Year of the start date - * @param bool $methodUS Whether to use the US method or the European method of calculation - * - * @return int Number of days between the start date and the end date - */ - private static function dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, $methodUS) - { - if ($startDay == 31) { - --$startDay; - } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !self::isLeapYear($startYear))))) { - $startDay = 30; - } - if ($endDay == 31) { - if ($methodUS && $startDay != 30) { - $endDay = 1; - if ($endMonth == 12) { - ++$endYear; - $endMonth = 1; - } else { - ++$endMonth; - } - } else { - $endDay = 30; - } - } - - return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360; + return DateTimeExcel\Helpers::isLeapYear($year); } /** * getDateValue. * + * @Deprecated 2.0.0 Use the method getDateValue in the DateTimeExcel\Helpers class instead + * * @param mixed $dateValue * * @return mixed Excel date/time serial value, or string if error */ public static function getDateValue($dateValue) { - if (!is_numeric($dateValue)) { - if ((is_object($dateValue)) && ($dateValue instanceof DateTimeInterface)) { - $dateValue = Date::PHPToExcel($dateValue); - } else { - $saveReturnDateType = Functions::getReturnDateType(); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - $dateValue = self::DATEVALUE($dateValue); - Functions::setReturnDateType($saveReturnDateType); - } + try { + return DateTimeExcel\Helpers::getDateValue($dateValue); + } catch (Exception $e) { + return $e->getMessage(); } - - return $dateValue; - } - - /** - * getTimeValue. - * - * @param string $timeValue - * - * @return mixed Excel date/time serial value, or string if error - */ - private static function getTimeValue($timeValue) - { - $saveReturnDateType = Functions::getReturnDateType(); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - $timeValue = self::TIMEVALUE($timeValue); - Functions::setReturnDateType($saveReturnDateType); - - return $timeValue; - } - - private static function adjustDateByMonths($dateValue = 0, $adjustmentMonths = 0) - { - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - $oMonth = (int) $PHPDateObject->format('m'); - $oYear = (int) $PHPDateObject->format('Y'); - - $adjustmentMonthsString = (string) $adjustmentMonths; - if ($adjustmentMonths > 0) { - $adjustmentMonthsString = '+' . $adjustmentMonths; - } - if ($adjustmentMonths != 0) { - $PHPDateObject->modify($adjustmentMonthsString . ' months'); - } - $nMonth = (int) $PHPDateObject->format('m'); - $nYear = (int) $PHPDateObject->format('Y'); - - $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12); - if ($monthDiff != $adjustmentMonths) { - $adjustDays = (int) $PHPDateObject->format('d'); - $adjustDaysString = '-' . $adjustDays . ' days'; - $PHPDateObject->modify($adjustDaysString); - } - - return $PHPDateObject; } /** @@ -136,6 +49,8 @@ class DateTime * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. * + * @Deprecated 2.0.0 Use the funcNow method in the DateTimeExcel\Now class instead + * * Excel Function: * NOW() * @@ -144,26 +59,7 @@ class DateTime */ public static function DATETIMENOW() { - $saveTimeZone = date_default_timezone_get(); - date_default_timezone_set('UTC'); - $retValue = false; - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - $retValue = (float) Date::PHPToExcel(time()); - - break; - case Functions::RETURNDATE_UNIX_TIMESTAMP: - $retValue = (int) time(); - - break; - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - $retValue = new \DateTime(); - - break; - } - date_default_timezone_set($saveTimeZone); - - return $retValue; + return DateTimeExcel\Now::funcNow(); } /** @@ -177,6 +73,8 @@ class DateTime * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. * + * @Deprecated 2.0.0 Use the funcToday method in the DateTimeExcel\Today class instead + * * Excel Function: * TODAY() * @@ -185,27 +83,7 @@ class DateTime */ public static function DATENOW() { - $saveTimeZone = date_default_timezone_get(); - date_default_timezone_set('UTC'); - $retValue = false; - $excelDateTime = floor(Date::PHPToExcel(time())); - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - $retValue = (float) $excelDateTime; - - break; - case Functions::RETURNDATE_UNIX_TIMESTAMP: - $retValue = (int) Date::excelToTimestamp($excelDateTime); - - break; - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - $retValue = Date::excelToDateTimeObject($excelDateTime); - - break; - } - date_default_timezone_set($saveTimeZone); - - return $retValue; + return DateTimeExcel\Today::funcToday(); } /** @@ -216,6 +94,8 @@ class DateTime * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. * + * @Deprecated 2.0.0 Use the funcDate method in the DateTimeExcel\Date class instead + * * Excel Function: * DATE(year,month,day) * @@ -259,71 +139,7 @@ class DateTime */ public static function DATE($year = 0, $month = 1, $day = 1) { - $year = Functions::flattenSingleValue($year); - $month = Functions::flattenSingleValue($month); - $day = Functions::flattenSingleValue($day); - - if (($month !== null) && (!is_numeric($month))) { - $month = Date::monthStringToNumber($month); - } - - if (($day !== null) && (!is_numeric($day))) { - $day = Date::dayStringToNumber($day); - } - - $year = ($year !== null) ? StringHelper::testStringAsNumeric($year) : 0; - $month = ($month !== null) ? StringHelper::testStringAsNumeric($month) : 0; - $day = ($day !== null) ? StringHelper::testStringAsNumeric($day) : 0; - if ( - (!is_numeric($year)) || - (!is_numeric($month)) || - (!is_numeric($day)) - ) { - return Functions::VALUE(); - } - $year = (int) $year; - $month = (int) $month; - $day = (int) $day; - - $baseYear = Date::getExcelCalendar(); - // Validate parameters - if ($year < ($baseYear - 1900)) { - return Functions::NAN(); - } - if ((($baseYear - 1900) != 0) && ($year < $baseYear) && ($year >= 1900)) { - return Functions::NAN(); - } - - if (($year < $baseYear) && ($year >= ($baseYear - 1900))) { - $year += 1900; - } - - if ($month < 1) { - // Handle year/month adjustment if month < 1 - --$month; - $year += ceil($month / 12) - 1; - $month = 13 - abs($month % 12); - } elseif ($month > 12) { - // Handle year/month adjustment if month > 12 - $year += floor($month / 12); - $month = ($month % 12); - } - - // Re-validate the year parameter after adjustments - if (($year < $baseYear) || ($year >= 10000)) { - return Functions::NAN(); - } - - // Execute function - $excelDateValue = Date::formattedPHPToExcel($year, $month, $day); - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - return (float) $excelDateValue; - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) Date::excelToTimestamp($excelDateValue); - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - return Date::excelToDateTimeObject($excelDateValue); - } + return DateTimeExcel\Datefunc::funcDate($year, $month, $day); } /** @@ -334,6 +150,8 @@ class DateTime * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. * + * @Deprecated 2.0.0 Use the funcTime method in the DateTimeExcel\Time class instead + * * Excel Function: * TIME(hour,minute,second) * @@ -354,85 +172,7 @@ class DateTime */ public static function TIME($hour = 0, $minute = 0, $second = 0) { - $hour = Functions::flattenSingleValue($hour); - $minute = Functions::flattenSingleValue($minute); - $second = Functions::flattenSingleValue($second); - - if ($hour == '') { - $hour = 0; - } - if ($minute == '') { - $minute = 0; - } - if ($second == '') { - $second = 0; - } - - if ((!is_numeric($hour)) || (!is_numeric($minute)) || (!is_numeric($second))) { - return Functions::VALUE(); - } - $hour = (int) $hour; - $minute = (int) $minute; - $second = (int) $second; - - if ($second < 0) { - $minute += floor($second / 60); - $second = 60 - abs($second % 60); - if ($second == 60) { - $second = 0; - } - } elseif ($second >= 60) { - $minute += floor($second / 60); - $second = $second % 60; - } - if ($minute < 0) { - $hour += floor($minute / 60); - $minute = 60 - abs($minute % 60); - if ($minute == 60) { - $minute = 0; - } - } elseif ($minute >= 60) { - $hour += floor($minute / 60); - $minute = $minute % 60; - } - - if ($hour > 23) { - $hour = $hour % 24; - } elseif ($hour < 0) { - return Functions::NAN(); - } - - // Execute function - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - $date = 0; - $calendar = Date::getExcelCalendar(); - if ($calendar != Date::CALENDAR_WINDOWS_1900) { - $date = 1; - } - - return (float) Date::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second); - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) Date::excelToTimestamp(Date::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600 - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - $dayAdjust = 0; - if ($hour < 0) { - $dayAdjust = floor($hour / 24); - $hour = 24 - abs($hour % 24); - if ($hour == 24) { - $hour = 0; - } - } elseif ($hour >= 24) { - $dayAdjust = floor($hour / 24); - $hour = $hour % 24; - } - $phpDateObject = new \DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second); - if ($dayAdjust != 0) { - $phpDateObject->modify($dayAdjust . ' days'); - } - - return $phpDateObject; - } + return DateTimeExcel\Time::funcTime($hour, $minute, $second); } /** @@ -445,6 +185,8 @@ class DateTime * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. * + * @Deprecated 2.0.0 Use the funcDateValue method in the DateTimeExcel\DateValue class instead + * * Excel Function: * DATEVALUE(dateValue) * @@ -460,112 +202,9 @@ class DateTime * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, * depending on the value of the ReturnDateType flag */ - public static function DATEVALUE($dateValue = 1) + public static function DATEVALUE($dateValue) { - $dateValue = trim(Functions::flattenSingleValue($dateValue), '"'); - // Strip any ordinals because they're allowed in Excel (English only) - $dateValue = preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue); - // Convert separators (/ . or space) to hyphens (should also handle dot used for ordinals in some countries, e.g. Denmark, Germany) - $dateValue = str_replace(['/', '.', '-', ' '], ' ', $dateValue); - - $yearFound = false; - $t1 = explode(' ', $dateValue); - foreach ($t1 as &$t) { - if ((is_numeric($t)) && ($t > 31)) { - if ($yearFound) { - return Functions::VALUE(); - } - if ($t < 100) { - $t += 1900; - } - $yearFound = true; - } - } - if ((count($t1) == 1) && (strpos($t, ':') !== false)) { - // We've been fed a time value without any date - return 0.0; - } elseif (count($t1) == 2) { - // We only have two parts of the date: either day/month or month/year - if ($yearFound) { - array_unshift($t1, 1); - } else { - if (is_numeric($t1[1]) && $t1[1] > 29) { - $t1[1] += 1900; - array_unshift($t1, 1); - } else { - $t1[] = date('Y'); - } - } - } - unset($t); - $dateValue = implode(' ', $t1); - - $PHPDateArray = date_parse($dateValue); - if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { - $testVal1 = strtok($dateValue, '- '); - if ($testVal1 !== false) { - $testVal2 = strtok('- '); - if ($testVal2 !== false) { - $testVal3 = strtok('- '); - if ($testVal3 === false) { - $testVal3 = strftime('%Y'); - } - } else { - return Functions::VALUE(); - } - } else { - return Functions::VALUE(); - } - if ($testVal1 < 31 && $testVal2 < 12 && $testVal3 < 12 && strlen($testVal3) == 2) { - $testVal3 += 2000; - } - $PHPDateArray = date_parse($testVal1 . '-' . $testVal2 . '-' . $testVal3); - if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { - $PHPDateArray = date_parse($testVal2 . '-' . $testVal1 . '-' . $testVal3); - if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { - return Functions::VALUE(); - } - } - } - - if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { - // Execute function - if ($PHPDateArray['year'] == '') { - $PHPDateArray['year'] = strftime('%Y'); - } - if ($PHPDateArray['year'] < 1900) { - return Functions::VALUE(); - } - if ($PHPDateArray['month'] == '') { - $PHPDateArray['month'] = strftime('%m'); - } - if ($PHPDateArray['day'] == '') { - $PHPDateArray['day'] = strftime('%d'); - } - if (!checkdate($PHPDateArray['month'], $PHPDateArray['day'], $PHPDateArray['year'])) { - return Functions::VALUE(); - } - $excelDateValue = floor( - Date::formattedPHPToExcel( - $PHPDateArray['year'], - $PHPDateArray['month'], - $PHPDateArray['day'], - $PHPDateArray['hour'], - $PHPDateArray['minute'], - $PHPDateArray['second'] - ) - ); - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - return (float) $excelDateValue; - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) Date::excelToTimestamp($excelDateValue); - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - return new \DateTime($PHPDateArray['year'] . '-' . $PHPDateArray['month'] . '-' . $PHPDateArray['day'] . ' 00:00:00'); - } - } - - return Functions::VALUE(); + return DateTimeExcel\DateValue::funcDateValue($dateValue); } /** @@ -578,6 +217,8 @@ class DateTime * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. * + * @Deprecated 2.0.0 Use the funcTimeValue method in the DateTimeExcel\TimeValue class instead + * * Excel Function: * TIMEVALUE(timeValue) * @@ -591,46 +232,14 @@ class DateTime */ public static function TIMEVALUE($timeValue) { - $timeValue = trim(Functions::flattenSingleValue($timeValue), '"'); - $timeValue = str_replace(['/', '.'], '-', $timeValue); - - $arraySplit = preg_split('/[\/:\-\s]/', $timeValue); - if ((count($arraySplit) == 2 || count($arraySplit) == 3) && $arraySplit[0] > 24) { - $arraySplit[0] = ($arraySplit[0] % 24); - $timeValue = implode(':', $arraySplit); - } - - $PHPDateArray = date_parse($timeValue); - if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $excelDateValue = Date::formattedPHPToExcel( - $PHPDateArray['year'], - $PHPDateArray['month'], - $PHPDateArray['day'], - $PHPDateArray['hour'], - $PHPDateArray['minute'], - $PHPDateArray['second'] - ); - } else { - $excelDateValue = Date::formattedPHPToExcel(1900, 1, 1, $PHPDateArray['hour'], $PHPDateArray['minute'], $PHPDateArray['second']) - 1; - } - - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - return (float) $excelDateValue; - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) $phpDateValue = Date::excelToTimestamp($excelDateValue + 25569) - 3600; - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - return new \DateTime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']); - } - } - - return Functions::VALUE(); + return DateTimeExcel\TimeValue::funcTimeValue($timeValue); } /** * DATEDIF. * + * @Deprecated 2.0.0 Use the funcDateDif method in the DateTimeExcel\DateDif class instead + * * @param mixed $startDate Excel date serial value, PHP date/time stamp, PHP DateTime object * or a standard date string * @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object @@ -641,95 +250,7 @@ class DateTime */ public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D') { - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - $unit = strtoupper(Functions::flattenSingleValue($unit)); - - if (is_string($startDate = self::getDateValue($startDate))) { - return Functions::VALUE(); - } - if (is_string($endDate = self::getDateValue($endDate))) { - return Functions::VALUE(); - } - - // Validate parameters - if ($startDate > $endDate) { - return Functions::NAN(); - } - - // Execute function - $difference = $endDate - $startDate; - - $PHPStartDateObject = Date::excelToDateTimeObject($startDate); - $startDays = $PHPStartDateObject->format('j'); - $startMonths = $PHPStartDateObject->format('n'); - $startYears = $PHPStartDateObject->format('Y'); - - $PHPEndDateObject = Date::excelToDateTimeObject($endDate); - $endDays = $PHPEndDateObject->format('j'); - $endMonths = $PHPEndDateObject->format('n'); - $endYears = $PHPEndDateObject->format('Y'); - - $PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject); - - switch ($unit) { - case 'D': - $retVal = (int) $difference; - - break; - case 'M': - $retVal = (int) 12 * $PHPDiffDateObject->format('%y') + $PHPDiffDateObject->format('%m'); - - break; - case 'Y': - $retVal = (int) $PHPDiffDateObject->format('%y'); - - break; - case 'MD': - if ($endDays < $startDays) { - $retVal = $endDays; - $PHPEndDateObject->modify('-' . $endDays . ' days'); - $adjustDays = $PHPEndDateObject->format('j'); - $retVal += ($adjustDays - $startDays); - } else { - $retVal = (int) $PHPDiffDateObject->format('%d'); - } - - break; - case 'YM': - $retVal = (int) $PHPDiffDateObject->format('%m'); - - break; - case 'YD': - $retVal = (int) $difference; - if ($endYears > $startYears) { - $isLeapStartYear = $PHPStartDateObject->format('L'); - $wasLeapEndYear = $PHPEndDateObject->format('L'); - - // Adjust end year to be as close as possible as start year - while ($PHPEndDateObject >= $PHPStartDateObject) { - $PHPEndDateObject->modify('-1 year'); - $endYears = $PHPEndDateObject->format('Y'); - } - $PHPEndDateObject->modify('+1 year'); - - // Get the result - $retVal = $PHPEndDateObject->diff($PHPStartDateObject)->days; - - // Adjust for leap years cases - $isLeapEndYear = $PHPEndDateObject->format('L'); - $limit = new \DateTime($PHPEndDateObject->format('Y-02-29')); - if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) { - --$retVal; - } - } - - break; - default: - $retVal = Functions::VALUE(); - } - - return $retVal; + return DateTimeExcel\DateDif::funcDateDif($startDate, $endDate, $unit); } /** @@ -737,43 +258,21 @@ class DateTime * * Returns the number of days between two dates * + * @Deprecated 2.0.0 Use the funcDays method in the DateTimeExcel\Days class instead + * * Excel Function: * DAYS(endDate, startDate) * - * @param DateTimeImmutable|float|int|string $endDate Excel date serial value (float), + * @param DateTimeInterface|float|int|string $endDate Excel date serial value (float), * PHP date timestamp (integer), PHP DateTime object, or a standard date string - * @param DateTimeImmutable|float|int|string $startDate Excel date serial value (float), + * @param DateTimeInterface|float|int|string $startDate Excel date serial value (float), * PHP date timestamp (integer), PHP DateTime object, or a standard date string * * @return int|string Number of days between start date and end date or an error */ public static function DAYS($endDate = 0, $startDate = 0) { - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - - $startDate = self::getDateValue($startDate); - if (is_string($startDate)) { - return Functions::VALUE(); - } - - $endDate = self::getDateValue($endDate); - if (is_string($endDate)) { - return Functions::VALUE(); - } - - // Execute function - $PHPStartDateObject = Date::excelToDateTimeObject($startDate); - $PHPEndDateObject = Date::excelToDateTimeObject($endDate); - - $diff = $PHPStartDateObject->diff($PHPEndDateObject); - $days = $diff->days; - - if ($diff->invert) { - $days = -$days; - } - - return $days; + return DateTimeExcel\Days::funcDays($endDate, $startDate); } /** @@ -783,6 +282,8 @@ class DateTime * which is used in some accounting calculations. Use this function to help compute payments if * your accounting system is based on twelve 30-day months. * + * @Deprecated 2.0.0 Use the funcDays360 method in the DateTimeExcel\Days360 class instead + * * Excel Function: * DAYS360(startDate,endDate[,method]) * @@ -806,32 +307,7 @@ class DateTime */ public static function DAYS360($startDate = 0, $endDate = 0, $method = false) { - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - - if (is_string($startDate = self::getDateValue($startDate))) { - return Functions::VALUE(); - } - if (is_string($endDate = self::getDateValue($endDate))) { - return Functions::VALUE(); - } - - if (!is_bool($method)) { - return Functions::VALUE(); - } - - // Execute function - $PHPStartDateObject = Date::excelToDateTimeObject($startDate); - $startDay = $PHPStartDateObject->format('j'); - $startMonth = $PHPStartDateObject->format('n'); - $startYear = $PHPStartDateObject->format('Y'); - - $PHPEndDateObject = Date::excelToDateTimeObject($endDate); - $endDay = $PHPEndDateObject->format('j'); - $endMonth = $PHPEndDateObject->format('n'); - $endYear = $PHPEndDateObject->format('Y'); - - return self::dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, !$method); + return DateTimeExcel\Days360::funcDays360($startDate, $endDate, $method); } /** @@ -842,6 +318,8 @@ class DateTime * Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or * obligations to assign to a specific term. * + * @Deprecated 2.0.0 Use the funcYearFrac method in the DateTimeExcel\YearFrac class instead + * * Excel Function: * YEARFRAC(startDate,endDate[,method]) * See https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html @@ -862,78 +340,7 @@ class DateTime */ public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0) { - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - $method = Functions::flattenSingleValue($method); - - if (is_string($startDate = self::getDateValue($startDate))) { - return Functions::VALUE(); - } - if (is_string($endDate = self::getDateValue($endDate))) { - return Functions::VALUE(); - } - if ($startDate > $endDate) { - $temp = $startDate; - $startDate = $endDate; - $endDate = $temp; - } - - if (((is_numeric($method)) && (!is_string($method))) || ($method == '')) { - switch ($method) { - case 0: - return self::DAYS360($startDate, $endDate) / 360; - case 1: - $days = self::DATEDIF($startDate, $endDate); - $startYear = self::YEAR($startDate); - $endYear = self::YEAR($endDate); - $years = $endYear - $startYear + 1; - $startMonth = self::MONTHOFYEAR($startDate); - $startDay = self::DAYOFMONTH($startDate); - $endMonth = self::MONTHOFYEAR($endDate); - $endDay = self::DAYOFMONTH($endDate); - $startMonthDay = 100 * $startMonth + $startDay; - $endMonthDay = 100 * $endMonth + $endDay; - if ($years == 1) { - if (self::isLeapYear($endYear)) { - $tmpCalcAnnualBasis = 366; - } else { - $tmpCalcAnnualBasis = 365; - } - } elseif ($years == 2 && $startMonthDay >= $endMonthDay) { - if (self::isLeapYear($startYear)) { - if ($startMonthDay <= 229) { - $tmpCalcAnnualBasis = 366; - } else { - $tmpCalcAnnualBasis = 365; - } - } elseif (self::isLeapYear($endYear)) { - if ($endMonthDay >= 229) { - $tmpCalcAnnualBasis = 366; - } else { - $tmpCalcAnnualBasis = 365; - } - } else { - $tmpCalcAnnualBasis = 365; - } - } else { - $tmpCalcAnnualBasis = 0; - for ($year = $startYear; $year <= $endYear; ++$year) { - $tmpCalcAnnualBasis += self::isLeapYear($year) ? 366 : 365; - } - $tmpCalcAnnualBasis /= $years; - } - - return $days / $tmpCalcAnnualBasis; - case 2: - return self::DATEDIF($startDate, $endDate) / 360; - case 3: - return self::DATEDIF($startDate, $endDate) / 365; - case 4: - return self::DAYS360($startDate, $endDate, true) / 360; - } - } - - return Functions::VALUE(); + return DateTimeExcel\YearFrac::funcYearFrac($startDate, $endDate, $method); } /** @@ -944,6 +351,8 @@ class DateTime * Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days * worked during a specific term. * + * @Deprecated 2.0.0 Use the funcNetworkDays method in the DateTimeExcel\NetworkDays class instead + * * Excel Function: * NETWORKDAYS(startDate,endDate[,holidays[,holiday[,...]]]) * @@ -956,62 +365,7 @@ class DateTime */ public static function NETWORKDAYS($startDate, $endDate, ...$dateArgs) { - // Retrieve the mandatory start and end date that are referenced in the function definition - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - // Get the optional days - $dateArgs = Functions::flattenArray($dateArgs); - - // Validate the start and end dates - if (is_string($startDate = $sDate = self::getDateValue($startDate))) { - return Functions::VALUE(); - } - $startDate = (float) floor($startDate); - if (is_string($endDate = $eDate = self::getDateValue($endDate))) { - return Functions::VALUE(); - } - $endDate = (float) floor($endDate); - - if ($sDate > $eDate) { - $startDate = $eDate; - $endDate = $sDate; - } - - // Execute function - $startDoW = 6 - self::WEEKDAY($startDate, 2); - if ($startDoW < 0) { - $startDoW = 0; - } - $endDoW = self::WEEKDAY($endDate, 2); - if ($endDoW >= 6) { - $endDoW = 0; - } - - $wholeWeekDays = floor(($endDate - $startDate) / 7) * 5; - $partWeekDays = $endDoW + $startDoW; - if ($partWeekDays > 5) { - $partWeekDays -= 5; - } - - // Test any extra holiday parameters - $holidayCountedArray = []; - foreach ($dateArgs as $holidayDate) { - if (is_string($holidayDate = self::getDateValue($holidayDate))) { - return Functions::VALUE(); - } - if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { - if ((self::WEEKDAY($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) { - --$partWeekDays; - $holidayCountedArray[] = $holidayDate; - } - } - } - - if ($sDate > $eDate) { - return 0 - ($wholeWeekDays + $partWeekDays); - } - - return $wholeWeekDays + $partWeekDays; + return DateTimeExcel\NetworkDays::funcNetworkDays($startDate, $endDate, ...$dateArgs); } /** @@ -1022,6 +376,8 @@ class DateTime * Use WORKDAY to exclude weekends or holidays when you calculate invoice due dates, expected * delivery times, or the number of days of work performed. * + * @Deprecated 2.0.0 Use the funcWorkDay method in the DateTimeExcel\WorkDay class instead + * * Excel Function: * WORKDAY(startDate,endDays[,holidays[,holiday[,...]]]) * @@ -1036,91 +392,7 @@ class DateTime */ public static function WORKDAY($startDate, $endDays, ...$dateArgs) { - // Retrieve the mandatory start date and days that are referenced in the function definition - $startDate = Functions::flattenSingleValue($startDate); - $endDays = Functions::flattenSingleValue($endDays); - // Get the optional days - $dateArgs = Functions::flattenArray($dateArgs); - - if ((is_string($startDate = self::getDateValue($startDate))) || (!is_numeric($endDays))) { - return Functions::VALUE(); - } - $startDate = (float) floor($startDate); - $endDays = (int) floor($endDays); - // If endDays is 0, we always return startDate - if ($endDays == 0) { - return $startDate; - } - - $decrementing = $endDays < 0; - - // Adjust the start date if it falls over a weekend - - $startDoW = self::WEEKDAY($startDate, 3); - if (self::WEEKDAY($startDate, 3) >= 5) { - $startDate += ($decrementing) ? -$startDoW + 4 : 7 - $startDoW; - ($decrementing) ? $endDays++ : $endDays--; - } - - // Add endDays - $endDate = (float) $startDate + ((int) ($endDays / 5) * 7) + ($endDays % 5); - - // Adjust the calculated end date if it falls over a weekend - $endDoW = self::WEEKDAY($endDate, 3); - if ($endDoW >= 5) { - $endDate += ($decrementing) ? -$endDoW + 4 : 7 - $endDoW; - } - - // Test any extra holiday parameters - if (!empty($dateArgs)) { - $holidayCountedArray = $holidayDates = []; - foreach ($dateArgs as $holidayDate) { - if (($holidayDate !== null) && (trim($holidayDate) > '')) { - if (is_string($holidayDate = self::getDateValue($holidayDate))) { - return Functions::VALUE(); - } - if (self::WEEKDAY($holidayDate, 3) < 5) { - $holidayDates[] = $holidayDate; - } - } - } - if ($decrementing) { - rsort($holidayDates, SORT_NUMERIC); - } else { - sort($holidayDates, SORT_NUMERIC); - } - foreach ($holidayDates as $holidayDate) { - if ($decrementing) { - if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) { - if (!in_array($holidayDate, $holidayCountedArray)) { - --$endDate; - $holidayCountedArray[] = $holidayDate; - } - } - } else { - if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { - if (!in_array($holidayDate, $holidayCountedArray)) { - ++$endDate; - $holidayCountedArray[] = $holidayDate; - } - } - } - // Adjust the calculated end date if it falls over a weekend - $endDoW = self::WEEKDAY($endDate, 3); - if ($endDoW >= 5) { - $endDate += ($decrementing) ? -$endDoW + 4 : 7 - $endDoW; - } - } - } - - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - return (float) $endDate; - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) Date::excelToTimestamp($endDate); - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - return Date::excelToDateTimeObject($endDate); - } + return DateTimeExcel\WorkDay::funcWorkDay($startDate, $endDays, ...$dateArgs); } /** @@ -1129,6 +401,8 @@ class DateTime * Returns the day of the month, for a specified date. The day is given as an integer * ranging from 1 to 31. * + * @Deprecated 2.0.0 Use the funcDay method in the DateTimeExcel\Day class instead + * * Excel Function: * DAY(dateValue) * @@ -1139,26 +413,7 @@ class DateTime */ public static function DAYOFMONTH($dateValue = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - - if ($dateValue === null) { - $dateValue = 1; - } elseif (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } - - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { - if ($dateValue < 0.0) { - return Functions::NAN(); - } elseif ($dateValue < 1.0) { - return 0; - } - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - - return (int) $PHPDateObject->format('j'); + return DateTimeExcel\Day::funcDay($dateValue); } /** @@ -1167,10 +422,12 @@ class DateTime * Returns the day of the week for a specified date. The day is given as an integer * ranging from 0 to 7 (dependent on the requested style). * + * @Deprecated 2.0.0 Use the funcWeekDay method in the DateTimeExcel\WeekDay class instead + * * Excel Function: * WEEKDAY(dateValue[,style]) * - * @param int $dateValue Excel date serial value (float), PHP date timestamp (integer), + * @param float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param int $style A number that determines the type of return value * 1 or omitted Numbers 1 (Sunday) through 7 (Saturday). @@ -1181,79 +438,169 @@ class DateTime */ public static function WEEKDAY($dateValue = 1, $style = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - $style = Functions::flattenSingleValue($style); - - if (!is_numeric($style)) { - return Functions::VALUE(); - } elseif (($style < 1) || ($style > 3)) { - return Functions::NAN(); - } - $style = floor($style); - - if ($dateValue === null) { - $dateValue = 1; - } elseif (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } elseif ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - $DoW = (int) $PHPDateObject->format('w'); - - $firstDay = 1; - switch ($style) { - case 1: - ++$DoW; - - break; - case 2: - if ($DoW === 0) { - $DoW = 7; - } - - break; - case 3: - if ($DoW === 0) { - $DoW = 7; - } - $firstDay = 0; - --$DoW; - - break; - } - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { - // Test for Excel's 1900 leap year, and introduce the error as required - if (($PHPDateObject->format('Y') == 1900) && ($PHPDateObject->format('n') <= 2)) { - --$DoW; - if ($DoW < $firstDay) { - $DoW += 7; - } - } - } - - return $DoW; + return DateTimeExcel\WeekDay::funcWeekDay($dateValue, $style); } + /** + * STARTWEEK_SUNDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_SUNDAY instead + */ const STARTWEEK_SUNDAY = 1; + + /** + * STARTWEEK_MONDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_MONDAY instead + */ const STARTWEEK_MONDAY = 2; + + /** + * STARTWEEK_MONDAY_ALT. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_MONDAY_ALT instead + */ const STARTWEEK_MONDAY_ALT = 11; + + /** + * STARTWEEK_TUESDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_TUESDAY instead + */ const STARTWEEK_TUESDAY = 12; + + /** + * STARTWEEK_WEDNESDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_WEDNESDAY instead + */ const STARTWEEK_WEDNESDAY = 13; + + /** + * STARTWEEK_THURSDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_THURSDAY instead + */ const STARTWEEK_THURSDAY = 14; + + /** + * STARTWEEK_FRIDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_FRIDAY instead + */ const STARTWEEK_FRIDAY = 15; + + /** + * STARTWEEK_SATURDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_SATURDAY instead + */ const STARTWEEK_SATURDAY = 16; + + /** + * STARTWEEK_SUNDAY_ALT. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_SUNDAY_ALT instead + */ const STARTWEEK_SUNDAY_ALT = 17; + + /** + * DOW_SUNDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_SUNDAY instead + */ const DOW_SUNDAY = 1; + + /** + * DOW_MONDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_MONDAY instead + */ const DOW_MONDAY = 2; + + /** + * DOW_TUESDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_TUESDAY instead + */ const DOW_TUESDAY = 3; + + /** + * DOW_WEDNESDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_WEDNESDAY instead + */ const DOW_WEDNESDAY = 4; + + /** + * DOW_THURSDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_THURSDAY instead + */ const DOW_THURSDAY = 5; + + /** + * DOW_FRIDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_FRIDAY instead + */ const DOW_FRIDAY = 6; + + /** + * DOW_SATURDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_SATURDAY instead + */ const DOW_SATURDAY = 7; + + /** + * STARTWEEK_MONDAY_ISO. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_MONDAY_ISO instead + */ const STARTWEEK_MONDAY_ISO = 21; + + /** + * METHODARR. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\METHODARR instead + */ const METHODARR = [ self::STARTWEEK_SUNDAY => self::DOW_SUNDAY, self::DOW_MONDAY, @@ -1277,6 +624,8 @@ class DateTime * three days or less in the first week of January, the WEEKNUM function returns week numbers * that are incorrect according to the European standard. * + * @Deprecated 2.0.0 Use the funcWeekNum method in the DateTimeExcel\WeekNum class instead + * * Excel Function: * WEEKNUM(dateValue[,style]) * @@ -1298,40 +647,7 @@ class DateTime */ public static function WEEKNUM($dateValue = 1, $method = self::STARTWEEK_SUNDAY) { - $dateValue = Functions::flattenSingleValue($dateValue); - $method = Functions::flattenSingleValue($method); - - if (!is_numeric($method)) { - return Functions::VALUE(); - } - $method = (int) $method; - if (!array_key_exists($method, self::METHODARR)) { - return Functions::NaN(); - } - $method = self::METHODARR[$method]; - - $dateValue = self::getDateValue($dateValue); - if (is_string($dateValue)) { - return Functions::VALUE(); - } - if ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - if ($method == self::STARTWEEK_MONDAY_ISO) { - return (int) $PHPDateObject->format('W'); - } - $dayOfYear = $PHPDateObject->format('z'); - $PHPDateObject->modify('-' . $dayOfYear . ' days'); - $firstDayOfFirstWeek = $PHPDateObject->format('w'); - $daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7; - $daysInFirstWeek += 7 * !$daysInFirstWeek; - $endFirstWeek = $daysInFirstWeek - 1; - $weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7); - - return (int) $weekOfYear; + return DateTimeExcel\WeekNum::funcWeekNum($dateValue, $method); } /** @@ -1339,6 +655,8 @@ class DateTime * * Returns the ISO 8601 week number of the year for a specified date. * + * @Deprecated 2.0.0 Use the funcIsoWeeknum method in the DateTimeExcel\IsoWeekNum class instead + * * Excel Function: * ISOWEEKNUM(dateValue) * @@ -1349,20 +667,7 @@ class DateTime */ public static function ISOWEEKNUM($dateValue = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - - if ($dateValue === null) { - $dateValue = 1; - } elseif (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } elseif ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - - return (int) $PHPDateObject->format('W'); + return DateTimeExcel\IsoWeekNum::funcIsoWeekNum($dateValue); } /** @@ -1371,6 +676,8 @@ class DateTime * Returns the month of a date represented by a serial number. * The month is given as an integer, ranging from 1 (January) to 12 (December). * + * @Deprecated 2.0.0 Use the funcMonth method in the DateTimeExcel\Month class instead + * * Excel Function: * MONTH(dateValue) * @@ -1381,21 +688,7 @@ class DateTime */ public static function MONTHOFYEAR($dateValue = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - - if (empty($dateValue)) { - $dateValue = 1; - } - if (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } elseif ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - - return (int) $PHPDateObject->format('n'); + return DateTimeExcel\Month::funcMonth($dateValue); } /** @@ -1404,6 +697,8 @@ class DateTime * Returns the year corresponding to a date. * The year is returned as an integer in the range 1900-9999. * + * @Deprecated 2.0.0 Use the funcYear method in the DateTimeExcel\Year class instead + * * Excel Function: * YEAR(dateValue) * @@ -1414,20 +709,7 @@ class DateTime */ public static function YEAR($dateValue = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - - if ($dateValue === null) { - $dateValue = 1; - } elseif (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } elseif ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - - return (int) $PHPDateObject->format('Y'); + return DateTimeExcel\Year::funcYear($dateValue); } /** @@ -1436,6 +718,8 @@ class DateTime * Returns the hour of a time value. * The hour is given as an integer, ranging from 0 (12:00 A.M.) to 23 (11:00 P.M.). * + * @Deprecated 2.0.0 Use the funcHour method in the DateTimeExcel\Hour class instead + * * Excel Function: * HOUR(timeValue) * @@ -1446,29 +730,7 @@ class DateTime */ public static function HOUROFDAY($timeValue = 0) { - $timeValue = Functions::flattenSingleValue($timeValue); - - if (!is_numeric($timeValue)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $testVal = strtok($timeValue, '/-: '); - if (strlen($testVal) < strlen($timeValue)) { - return Functions::VALUE(); - } - } - $timeValue = self::getTimeValue($timeValue); - if (is_string($timeValue)) { - return Functions::VALUE(); - } - } - // Execute function - if ($timeValue >= 1) { - $timeValue = fmod($timeValue, 1); - } elseif ($timeValue < 0.0) { - return Functions::NAN(); - } - $timeValue = Date::excelToTimestamp($timeValue); - - return (int) gmdate('G', $timeValue); + return DateTimeExcel\Hour::funcHour($timeValue); } /** @@ -1477,6 +739,8 @@ class DateTime * Returns the minutes of a time value. * The minute is given as an integer, ranging from 0 to 59. * + * @Deprecated 2.0.0 Use the funcMinute method in the DateTimeExcel\Minute class instead + * * Excel Function: * MINUTE(timeValue) * @@ -1487,29 +751,7 @@ class DateTime */ public static function MINUTE($timeValue = 0) { - $timeValue = $timeTester = Functions::flattenSingleValue($timeValue); - - if (!is_numeric($timeValue)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $testVal = strtok($timeValue, '/-: '); - if (strlen($testVal) < strlen($timeValue)) { - return Functions::VALUE(); - } - } - $timeValue = self::getTimeValue($timeValue); - if (is_string($timeValue)) { - return Functions::VALUE(); - } - } - // Execute function - if ($timeValue >= 1) { - $timeValue = fmod($timeValue, 1); - } elseif ($timeValue < 0.0) { - return Functions::NAN(); - } - $timeValue = Date::excelToTimestamp($timeValue); - - return (int) gmdate('i', $timeValue); + return DateTimeExcel\Minute::funcMinute($timeValue); } /** @@ -1518,6 +760,8 @@ class DateTime * Returns the seconds of a time value. * The second is given as an integer in the range 0 (zero) to 59. * + * @Deprecated 2.0.0 Use the funcSecond method in the DateTimeExcel\Second class instead + * * Excel Function: * SECOND(timeValue) * @@ -1528,29 +772,7 @@ class DateTime */ public static function SECOND($timeValue = 0) { - $timeValue = Functions::flattenSingleValue($timeValue); - - if (!is_numeric($timeValue)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $testVal = strtok($timeValue, '/-: '); - if (strlen($testVal) < strlen($timeValue)) { - return Functions::VALUE(); - } - } - $timeValue = self::getTimeValue($timeValue); - if (is_string($timeValue)) { - return Functions::VALUE(); - } - } - // Execute function - if ($timeValue >= 1) { - $timeValue = fmod($timeValue, 1); - } elseif ($timeValue < 0.0) { - return Functions::NAN(); - } - $timeValue = Date::excelToTimestamp($timeValue); - - return (int) gmdate('s', $timeValue); + return DateTimeExcel\Second::funcSecond($timeValue); } /** @@ -1561,6 +783,8 @@ class DateTime * Use EDATE to calculate maturity dates or due dates that fall on the same day of the month * as the date of issue. * + * @Deprecated 2.0.0 Use the funcEDate method in the DateTimeExcel\EDate class instead + * * Excel Function: * EDATE(dateValue,adjustmentMonths) * @@ -1575,29 +799,7 @@ class DateTime */ public static function EDATE($dateValue = 1, $adjustmentMonths = 0) { - $dateValue = Functions::flattenSingleValue($dateValue); - $adjustmentMonths = Functions::flattenSingleValue($adjustmentMonths); - - if (!is_numeric($adjustmentMonths)) { - return Functions::VALUE(); - } - $adjustmentMonths = floor($adjustmentMonths); - - if (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } - - // Execute function - $PHPDateObject = self::adjustDateByMonths($dateValue, $adjustmentMonths); - - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - return (float) Date::PHPToExcel($PHPDateObject); - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) Date::excelToTimestamp(Date::PHPToExcel($PHPDateObject)); - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - return $PHPDateObject; - } + return DateTimeExcel\EDate::funcEDate($dateValue, $adjustmentMonths); } /** @@ -1607,6 +809,8 @@ class DateTime * before or after start_date. * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. * + * @Deprecated 2.0.0 Use the funcEoMonth method in the DateTimeExcel\EoMonth class instead + * * Excel Function: * EOMONTH(dateValue,adjustmentMonths) * @@ -1621,31 +825,6 @@ class DateTime */ public static function EOMONTH($dateValue = 1, $adjustmentMonths = 0) { - $dateValue = Functions::flattenSingleValue($dateValue); - $adjustmentMonths = Functions::flattenSingleValue($adjustmentMonths); - - if (!is_numeric($adjustmentMonths)) { - return Functions::VALUE(); - } - $adjustmentMonths = floor($adjustmentMonths); - - if (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } - - // Execute function - $PHPDateObject = self::adjustDateByMonths($dateValue, $adjustmentMonths + 1); - $adjustDays = (int) $PHPDateObject->format('d'); - $adjustDaysString = '-' . $adjustDays . ' days'; - $PHPDateObject->modify($adjustDaysString); - - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - return (float) Date::PHPToExcel($PHPDateObject); - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) Date::excelToTimestamp(Date::PHPToExcel($PHPDateObject)); - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - return $PHPDateObject; - } + return DateTimeExcel\EoMonth::funcEoMonth($dateValue, $adjustmentMonths); } } diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php new file mode 100644 index 00000000..da1b81c1 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php @@ -0,0 +1,37 @@ + self::DOW_SUNDAY, + self::DOW_MONDAY, + self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY, + self::DOW_TUESDAY, + self::DOW_WEDNESDAY, + self::DOW_THURSDAY, + self::DOW_FRIDAY, + self::DOW_SATURDAY, + self::DOW_SUNDAY, + self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO, + ]; +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateDif.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateDif.php new file mode 100644 index 00000000..c0d1fa4b --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateDif.php @@ -0,0 +1,146 @@ +getMessage(); + } + + // Execute function + $PHPStartDateObject = Date::excelToDateTimeObject($startDate); + $startDays = (int) $PHPStartDateObject->format('j'); + $startMonths = (int) $PHPStartDateObject->format('n'); + $startYears = (int) $PHPStartDateObject->format('Y'); + + $PHPEndDateObject = Date::excelToDateTimeObject($endDate); + $endDays = (int) $PHPEndDateObject->format('j'); + $endMonths = (int) $PHPEndDateObject->format('n'); + $endYears = (int) $PHPEndDateObject->format('Y'); + + $PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject); + + $retVal = false; + $retVal = self::replaceRetValue($retVal, $unit, 'D') ?? self::datedifD($difference); + $retVal = self::replaceRetValue($retVal, $unit, 'M') ?? self::datedifM($PHPDiffDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'MD') ?? self::datedifMD($startDays, $endDays, $PHPEndDateObject, $PHPDiffDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'Y') ?? self::datedifY($PHPDiffDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'YD') ?? self::datedifYD($difference, $startYears, $endYears, $PHPStartDateObject, $PHPEndDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'YM') ?? self::datedifYM($PHPDiffDateObject); + + return is_bool($retVal) ? Functions::VALUE() : $retVal; + } + + private static function initialDiff(float $startDate, float $endDate): float + { + // Validate parameters + if ($startDate > $endDate) { + throw new Exception(Functions::NAN()); + } + + return $endDate - $startDate; + } + + /** + * Decide whether it's time to set retVal. + * + * @param bool|int $retVal + * + * @return null|bool|int + */ + private static function replaceRetValue($retVal, string $unit, string $compare) + { + if ($retVal !== false || $unit !== $compare) { + return $retVal; + } + + return null; + } + + private static function datedifD(float $difference): int + { + return (int) $difference; + } + + private static function datedifM(DateInterval $PHPDiffDateObject): int + { + return 12 * (int) $PHPDiffDateObject->format('%y') + (int) $PHPDiffDateObject->format('%m'); + } + + private static function datedifMD(int $startDays, int $endDays, DateTime $PHPEndDateObject, DateInterval $PHPDiffDateObject): int + { + if ($endDays < $startDays) { + $retVal = $endDays; + $PHPEndDateObject->modify('-' . $endDays . ' days'); + $adjustDays = (int) $PHPEndDateObject->format('j'); + $retVal += ($adjustDays - $startDays); + } else { + $retVal = (int) $PHPDiffDateObject->format('%d'); + } + + return $retVal; + } + + private static function datedifY(DateInterval $PHPDiffDateObject): int + { + return (int) $PHPDiffDateObject->format('%y'); + } + + private static function datedifYD(float $difference, int $startYears, int $endYears, DateTime $PHPStartDateObject, DateTime $PHPEndDateObject): int + { + $retVal = (int) $difference; + if ($endYears > $startYears) { + $isLeapStartYear = $PHPStartDateObject->format('L'); + $wasLeapEndYear = $PHPEndDateObject->format('L'); + + // Adjust end year to be as close as possible as start year + while ($PHPEndDateObject >= $PHPStartDateObject) { + $PHPEndDateObject->modify('-1 year'); + $endYears = $PHPEndDateObject->format('Y'); + } + $PHPEndDateObject->modify('+1 year'); + + // Get the result + $retVal = $PHPEndDateObject->diff($PHPStartDateObject)->days; + + // Adjust for leap years cases + $isLeapEndYear = $PHPEndDateObject->format('L'); + $limit = new DateTime($PHPEndDateObject->format('Y-02-29')); + if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) { + --$retVal; + } + } + + return (int) $retVal; + } + + private static function datedifYM(DateInterval $PHPDiffDateObject): int + { + return (int) $PHPDiffDateObject->format('%m'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php new file mode 100644 index 00000000..86b8d3d9 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php @@ -0,0 +1,151 @@ + 31)) { + if ($yearFound) { + return Functions::VALUE(); + } + if ($t < 100) { + $t += 1900; + } + $yearFound = true; + } + } + if (count($t1) === 1) { + // We've been fed a time value without any date + return ((strpos($t, ':') === false)) ? Functions::Value() : 0.0; + } + unset($t); + + $dateValue = self::t1ToString($t1, $dti, $yearFound); + + $PHPDateArray = self::setUpArray($dateValue, $dti); + + return self::finalResults($PHPDateArray, $dti, $baseYear); + } + + private static function t1ToString(array $t1, DateTimeImmutable $dti, bool $yearFound): string + { + if (count($t1) == 2) { + // We only have two parts of the date: either day/month or month/year + if ($yearFound) { + array_unshift($t1, 1); + } else { + if (is_numeric($t1[1]) && $t1[1] > 29) { + $t1[1] += 1900; + array_unshift($t1, 1); + } else { + $t1[] = $dti->format('Y'); + } + } + } + $dateValue = implode(' ', $t1); + + return $dateValue; + } + + /** + * Parse date. + * + * @return array|bool + */ + private static function setUpArray(string $dateValue, DateTimeImmutable $dti) + { + $PHPDateArray = date_parse($dateValue); + if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { + // If original count was 1, we've already returned. + // If it was 2, we added another. + // Therefore, neither of the first 2 stroks below can fail. + $testVal1 = strtok($dateValue, '- '); + $testVal2 = strtok('- '); + $testVal3 = strtok('- ') ?: $dti->format('Y'); + Helpers::adjustYear($testVal1, $testVal2, $testVal3); + $PHPDateArray = date_parse($testVal1 . '-' . $testVal2 . '-' . $testVal3); + if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { + $PHPDateArray = date_parse($testVal2 . '-' . $testVal1 . '-' . $testVal3); + } + } + + return $PHPDateArray; + } + + /** + * Final results. + * + * @param array|false $PHPDateArray + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + private static function finalResults($PHPDateArray, DateTimeImmutable $dti, int $baseYear) + { + $retValue = Functions::Value(); + if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { + // Execute function + Helpers::replaceIfEmpty($PHPDateArray['year'], $dti->format('Y')); + if ($PHPDateArray['year'] < $baseYear) { + return Functions::VALUE(); + } + Helpers::replaceIfEmpty($PHPDateArray['month'], $dti->format('m')); + Helpers::replaceIfEmpty($PHPDateArray['day'], $dti->format('d')); + $PHPDateArray['hour'] = 0; + $PHPDateArray['minute'] = 0; + $PHPDateArray['second'] = 0; + $month = (int) $PHPDateArray['month']; + $day = (int) $PHPDateArray['day']; + $year = (int) $PHPDateArray['year']; + if (!checkdate($month, $day, $year)) { + return ($year === 1900 && $month === 2 && $day === 29) ? Helpers::returnIn3FormatsFloat(60.0) : Functions::VALUE(); + } + $retValue = Helpers::returnIn3FormatsArray($PHPDateArray, true); + } + + return $retValue; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Datefunc.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Datefunc.php new file mode 100644 index 00000000..ec8be2df --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Datefunc.php @@ -0,0 +1,168 @@ +getMessage(); + } + + // Execute function + $excelDateValue = Date::formattedPHPToExcel($year, $month, $day); + + return Helpers::returnIn3FormatsFloat($excelDateValue); + } + + /** + * Convert year from multiple formats to int. + * + * @param mixed $year + */ + private static function getYear($year, int $baseYear): int + { + $year = Functions::flattenSingleValue($year); + $year = ($year !== null) ? StringHelper::testStringAsNumeric($year) : 0; + if (!is_numeric($year)) { + throw new Exception(Functions::VALUE()); + } + $year = (int) $year; + + if ($year < ($baseYear - 1900)) { + throw new Exception(Functions::NAN()); + } + if ((($baseYear - 1900) !== 0) && ($year < $baseYear) && ($year >= 1900)) { + throw new Exception(Functions::NAN()); + } + + if (($year < $baseYear) && ($year >= ($baseYear - 1900))) { + $year += 1900; + } + + return $year; + } + + /** + * Convert month from multiple formats to int. + * + * @param mixed $month + */ + private static function getMonth($month): int + { + $month = Functions::flattenSingleValue($month); + + if (($month !== null) && (!is_numeric($month))) { + $month = Date::monthStringToNumber($month); + } + + $month = ($month !== null) ? StringHelper::testStringAsNumeric($month) : 0; + if (!is_numeric($month)) { + throw new Exception(Functions::VALUE()); + } + + return (int) $month; + } + + /** + * Convert day from multiple formats to int. + * + * @param mixed $day + */ + private static function getDay($day): int + { + $day = Functions::flattenSingleValue($day); + + if (($day !== null) && (!is_numeric($day))) { + $day = Date::dayStringToNumber($day); + } + + $day = ($day !== null) ? StringHelper::testStringAsNumeric($day) : 0; + if (!is_numeric($day)) { + throw new Exception(Functions::VALUE()); + } + + return (int) $day; + } + + private static function adjustYearMonth(int &$year, int &$month, int $baseYear): void + { + if ($month < 1) { + // Handle year/month adjustment if month < 1 + --$month; + $year += ceil($month / 12) - 1; + $month = 13 - abs($month % 12); + } elseif ($month > 12) { + // Handle year/month adjustment if month > 12 + $year += floor($month / 12); + $month = ($month % 12); + } + + // Re-validate the year parameter after adjustments + if (($year < $baseYear) || ($year >= 10000)) { + throw new Exception(Functions::NAN()); + } + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Day.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Day.php new file mode 100644 index 00000000..6ab27184 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Day.php @@ -0,0 +1,61 @@ += 0) { + return $weirdResult; + } + + try { + $dateValue = Helpers::getDateValue($dateValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + + return (int) $PHPDateObject->format('j'); + } + + private static function weirdCondition($dateValue): int + { + // Excel does not treat 0 consistently for DAY vs. (MONTH or YEAR) + if (Date::getExcelCalendar() === DATE::CALENDAR_WINDOWS_1900 && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { + if (is_bool($dateValue)) { + return (int) $dateValue; + } + if ($dateValue === null) { + return 0; + } + if (is_numeric($dateValue) && $dateValue < 1 && $dateValue >= 0) { + return 0; + } + } + + return -1; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php new file mode 100644 index 00000000..2c814e8e --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php @@ -0,0 +1,51 @@ +getMessage(); + } + + // Execute function + $PHPStartDateObject = Date::excelToDateTimeObject($startDate); + $PHPEndDateObject = Date::excelToDateTimeObject($endDate); + + $days = Functions::VALUE(); + $diff = $PHPStartDateObject->diff($PHPEndDateObject); + if ($diff !== false && !is_bool($diff->days)) { + $days = $diff->days; + if ($diff->invert) { + $days = -$days; + } + } + + return $days; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php new file mode 100644 index 00000000..47de02c3 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php @@ -0,0 +1,106 @@ +getMessage(); + } + + if (!is_bool($method)) { + return Functions::VALUE(); + } + + // Execute function + $PHPStartDateObject = Date::excelToDateTimeObject($startDate); + $startDay = $PHPStartDateObject->format('j'); + $startMonth = $PHPStartDateObject->format('n'); + $startYear = $PHPStartDateObject->format('Y'); + + $PHPEndDateObject = Date::excelToDateTimeObject($endDate); + $endDay = $PHPEndDateObject->format('j'); + $endMonth = $PHPEndDateObject->format('n'); + $endYear = $PHPEndDateObject->format('Y'); + + return self::dateDiff360((int) $startDay, (int) $startMonth, (int) $startYear, (int) $endDay, (int) $endMonth, (int) $endYear, !$method); + } + + /** + * Return the number of days between two dates based on a 360 day calendar. + */ + private static function dateDiff360(int $startDay, int $startMonth, int $startYear, int $endDay, int $endMonth, int $endYear, bool $methodUS): int + { + $startDay = self::getStartDay($startDay, $startMonth, $startYear, $methodUS); + $endDay = self::getEndDay($endDay, $endMonth, $endYear, $startDay, $methodUS); + + return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360; + } + + private static function getStartDay(int $startDay, int $startMonth, int $startYear, bool $methodUS): int + { + if ($startDay == 31) { + --$startDay; + } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !Helpers::isLeapYear($startYear))))) { + $startDay = 30; + } + + return $startDay; + } + + private static function getEndDay(int $endDay, int &$endMonth, int &$endYear, int $startDay, bool $methodUS): int + { + if ($endDay == 31) { + if ($methodUS && $startDay != 30) { + $endDay = 1; + if ($endMonth == 12) { + ++$endYear; + $endMonth = 1; + } else { + ++$endMonth; + } + } else { + $endDay = 30; + } + } + + return $endDay; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/EDate.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/EDate.php new file mode 100644 index 00000000..43af694f --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/EDate.php @@ -0,0 +1,45 @@ +getMessage(); + } + $adjustmentMonths = floor($adjustmentMonths); + + // Execute function + $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths); + + return Helpers::returnIn3FormatsObject($PHPDateObject); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/EoMonth.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/EoMonth.php new file mode 100644 index 00000000..6b39a609 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/EoMonth.php @@ -0,0 +1,47 @@ +getMessage(); + } + $adjustmentMonths = floor($adjustmentMonths); + + // Execute function + $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths + 1); + $adjustDays = (int) $PHPDateObject->format('d'); + $adjustDaysString = '-' . $adjustDays . ' days'; + $PHPDateObject->modify($adjustDaysString); + + return Helpers::returnIn3FormatsObject($PHPDateObject); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php new file mode 100644 index 00000000..5b3a8067 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php @@ -0,0 +1,275 @@ +format('m'); + $oYear = (int) $PHPDateObject->format('Y'); + + $adjustmentMonthsString = (string) $adjustmentMonths; + if ($adjustmentMonths > 0) { + $adjustmentMonthsString = '+' . $adjustmentMonths; + } + if ($adjustmentMonths != 0) { + $PHPDateObject->modify($adjustmentMonthsString . ' months'); + } + $nMonth = (int) $PHPDateObject->format('m'); + $nYear = (int) $PHPDateObject->format('Y'); + + $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12); + if ($monthDiff != $adjustmentMonths) { + $adjustDays = (int) $PHPDateObject->format('d'); + $adjustDaysString = '-' . $adjustDays . ' days'; + $PHPDateObject->modify($adjustDaysString); + } + + return $PHPDateObject; + } + + /** + * Help reduce perceived complexity of some tests. + * + * @param mixed $value + * @param mixed $altValue + */ + public static function replaceIfEmpty(&$value, $altValue): void + { + $value = $value ?: $altValue; + } + + /** + * Adjust year in ambiguous situations. + */ + public static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void + { + if (!is_numeric($testVal1) || $testVal1 < 31) { + if (!is_numeric($testVal2) || $testVal2 < 12) { + if (is_numeric($testVal3) && $testVal3 < 12) { + $testVal3 += 2000; + } + } + } + } + + /** + * Return result in one of three formats. + * + * @return mixed + */ + public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false) + { + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { + return new DateTime( + $dateArray['year'] + . '-' . $dateArray['month'] + . '-' . $dateArray['day'] + . ' ' . $dateArray['hour'] + . ':' . $dateArray['minute'] + . ':' . $dateArray['second'] + ); + } + $excelDateValue = + Date::formattedPHPToExcel( + $dateArray['year'], + $dateArray['month'], + $dateArray['day'], + $dateArray['hour'], + $dateArray['minute'], + $dateArray['second'] + ); + if ($retType === Functions::RETURNDATE_EXCEL) { + return $noFrac ? floor($excelDateValue) : (float) $excelDateValue; + } + // RETURNDATE_UNIX_TIMESTAMP) + + return (int) Date::excelToTimestamp($excelDateValue); + } + + /** + * Return result in one of three formats. + * + * @return mixed + */ + public static function returnIn3FormatsFloat(float $excelDateValue) + { + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_EXCEL) { + return $excelDateValue; + } + if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { + return (int) Date::excelToTimestamp($excelDateValue); + } + // RETURNDATE_PHP_DATETIME_OBJECT + + return Date::excelToDateTimeObject($excelDateValue); + } + + /** + * Return result in one of three formats. + * + * @return mixed + */ + public static function returnIn3FormatsObject(DateTime $PHPDateObject) + { + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { + return $PHPDateObject; + } + if ($retType === Functions::RETURNDATE_EXCEL) { + return (float) Date::PHPToExcel($PHPDateObject); + } + // RETURNDATE_UNIX_TIMESTAMP + + return (int) Date::excelToTimestamp(Date::PHPToExcel($PHPDateObject)); + } + + private static function baseDate(): int + { + if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { + return 0; + } + if (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904) { + return 0; + } + + return 1; + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @param mixed $number + */ + public static function nullFalseTrueToNumber(&$number, bool $allowBool = true): void + { + $number = Functions::flattenSingleValue($number); + $nullVal = self::baseDate(); + if ($number === null) { + $number = $nullVal; + } elseif ($allowBool && is_bool($number)) { + $number = $nullVal + (int) $number; + } + } + + /** + * Many functions accept null argument treated as 0. + * + * @param mixed $number + * + * @return float|int + */ + public static function validateNumericNull($number) + { + $number = Functions::flattenSingleValue($number); + if ($number === null) { + return 0; + } + if (is_numeric($number)) { + return $number; + } + + throw new Exception(Functions::VALUE()); + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @param mixed $number + * + * @return float + */ + public static function validateNotNegative($number) + { + if (!is_numeric($number)) { + throw new Exception(Functions::VALUE()); + } + if ($number >= 0) { + return (float) $number; + } + + throw new Exception(Functions::NAN()); + } + + public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void + { + $isoDate = $PHPDateObject->format('c'); + if ($isoDate < '1900-03-01') { + $PHPDateObject->modify($mod); + } + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Hour.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Hour.php new file mode 100644 index 00000000..98d4570d --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Hour.php @@ -0,0 +1,44 @@ +getMessage(); + } + + // Execute function + $timeValue = fmod($timeValue, 1); + $timeValue = Date::excelToDateTimeObject($timeValue); + + return (int) $timeValue->format('H'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/IsoWeekNum.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/IsoWeekNum.php new file mode 100644 index 00000000..41959d9a --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/IsoWeekNum.php @@ -0,0 +1,55 @@ +getMessage(); + } + + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + Helpers::silly1900($PHPDateObject); + + return (int) $PHPDateObject->format('W'); + } + + private static function apparentBug($dateValue): bool + { + if (Date::getExcelCalendar() !== DATE::CALENDAR_MAC_1904) { + if (is_bool($dateValue)) { + return true; + } + if (is_numeric($dateValue) && !((int) $dateValue)) { + return true; + } + } + + return false; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Minute.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Minute.php new file mode 100644 index 00000000..a1747ec9 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Minute.php @@ -0,0 +1,44 @@ +getMessage(); + } + + // Execute function + $timeValue = fmod($timeValue, 1); + $timeValue = Date::excelToDateTimeObject($timeValue); + + return (int) $timeValue->format('i'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php new file mode 100644 index 00000000..a9fb8ece --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php @@ -0,0 +1,40 @@ +getMessage(); + } + if ($dateValue < 1 && Date::getExcelCalendar() === DATE::CALENDAR_WINDOWS_1900) { + return 1; + } + + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + + return (int) $PHPDateObject->format('n'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php new file mode 100644 index 00000000..c700c834 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php @@ -0,0 +1,102 @@ +getMessage(); + } + + // Execute function + $startDow = self::calcStartDow($startDate); + $endDow = self::calcEndDow($endDate); + $wholeWeekDays = (int) floor(($endDate - $startDate) / 7) * 5; + $partWeekDays = self::calcPartWeekDays($startDow, $endDow); + + // Test any extra holiday parameters + $holidayCountedArray = []; + foreach ($holidayArray as $holidayDate) { + if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { + if ((WeekDay::funcWeekDay($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) { + --$partWeekDays; + $holidayCountedArray[] = $holidayDate; + } + } + } + + return self::applySign($wholeWeekDays + $partWeekDays, $sDate, $eDate); + } + + private static function calcStartDow(float $startDate): int + { + $startDow = 6 - (int) WeekDay::funcWeekDay($startDate, 2); + if ($startDow < 0) { + $startDow = 5; + } + + return $startDow; + } + + private static function calcEndDow(float $endDate): int + { + $endDow = (int) WeekDay::funcWeekDay($endDate, 2); + if ($endDow >= 6) { + $endDow = 0; + } + + return $endDow; + } + + private static function calcPartWeekDays(int $startDow, int $endDow): int + { + $partWeekDays = $endDow + $startDow; + if ($partWeekDays > 5) { + $partWeekDays -= 5; + } + + return $partWeekDays; + } + + private static function applySign(int $result, float $sDate, float $eDate) + { + return ($sDate > $eDate) ? -$result : $result; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Now.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Now.php new file mode 100644 index 00000000..6e6bd171 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Now.php @@ -0,0 +1,34 @@ +format('c')); + + return is_array($dateArray) ? Helpers::returnIn3FormatsArray($dateArray) : Functions::VALUE(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Second.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Second.php new file mode 100644 index 00000000..c4749993 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Second.php @@ -0,0 +1,44 @@ +getMessage(); + } + + // Execute function + $timeValue = fmod($timeValue, 1); + $timeValue = Date::excelToDateTimeObject($timeValue); + + return (int) $timeValue->format('s'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php new file mode 100644 index 00000000..450f9d50 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php @@ -0,0 +1,116 @@ +getMessage(); + } + + self::adjustSecond($second, $minute); + self::adjustMinute($minute, $hour); + + if ($hour > 23) { + $hour = $hour % 24; + } elseif ($hour < 0) { + return Functions::NAN(); + } + + // Execute function + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_EXCEL) { + $calendar = Date::getExcelCalendar(); + $date = (int) ($calendar !== Date::CALENDAR_WINDOWS_1900); + + return (float) Date::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second); + } + if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { + return (int) Date::excelToTimestamp(Date::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600 + } + // RETURNDATE_PHP_DATETIME_OBJECT + // Hour has already been normalized (0-23) above + $phpDateObject = new DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second); + + return $phpDateObject; + } + + private static function adjustSecond(int &$second, int &$minute): void + { + if ($second < 0) { + $minute += floor($second / 60); + $second = 60 - abs($second % 60); + if ($second == 60) { + $second = 0; + } + } elseif ($second >= 60) { + $minute += floor($second / 60); + $second = $second % 60; + } + } + + private static function adjustMinute(int &$minute, int &$hour): void + { + if ($minute < 0) { + $hour += floor($minute / 60); + $minute = 60 - abs($minute % 60); + if ($minute == 60) { + $minute = 0; + } + } elseif ($minute >= 60) { + $hour += floor($minute / 60); + $minute = $minute % 60; + } + } + + private static function toIntWithNullBool($value): int + { + $value = Functions::flattenSingleValue($value); + $value = $value ?? 0; + if (is_bool($value)) { + $value = (int) $value; + } + if (!is_numeric($value)) { + throw new Exception(Functions::VALUE()); + } + + return (int) $value; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php new file mode 100644 index 00000000..2366b1d6 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php @@ -0,0 +1,61 @@ + 24) { + $arraySplit[0] = ($arraySplit[0] % 24); + $timeValue = implode(':', $arraySplit); + } + + $PHPDateArray = date_parse($timeValue); + $retValue = Functions::VALUE(); + if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { + // OpenOffice-specific code removed - it works just like Excel + $excelDateValue = Date::formattedPHPToExcel(1900, 1, 1, $PHPDateArray['hour'], $PHPDateArray['minute'], $PHPDateArray['second']) - 1; + + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_EXCEL) { + $retValue = (float) $excelDateValue; + } elseif ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { + $retValue = (int) $phpDateValue = Date::excelToTimestamp($excelDateValue + 25569) - 3600; + } else { + $retValue = new DateTime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']); + } + } + + return $retValue; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Today.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Today.php new file mode 100644 index 00000000..5e459410 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Today.php @@ -0,0 +1,34 @@ +format('c')); + + return is_array($dateArray) ? Helpers::returnIn3FormatsArray($dateArray, true) : Functions::VALUE(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekDay.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekDay.php new file mode 100644 index 00000000..ea4fe340 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekDay.php @@ -0,0 +1,80 @@ +getMessage(); + } + + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + Helpers::silly1900($PHPDateObject); + $DoW = (int) $PHPDateObject->format('w'); + + switch ($style) { + case 1: + ++$DoW; + + break; + case 2: + $DoW = self::dow0Becomes7($DoW); + + break; + case 3: + $DoW = self::dow0Becomes7($DoW) - 1; + + break; + } + + return $DoW; + } + + private static function validateStyle($style): int + { + $style = Functions::flattenSingleValue($style); + + if (!is_numeric($style)) { + throw new Exception(Functions::VALUE()); + } + $style = (int) $style; + if (($style < 1) || ($style > 3)) { + throw new Exception(Functions::NAN()); + } + + return $style; + } + + private static function dow0Becomes7(int $DoW): int + { + return ($DoW === 0) ? 7 : $DoW; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekNum.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekNum.php new file mode 100644 index 00000000..9b2de4d0 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekNum.php @@ -0,0 +1,130 @@ +getMessage(); + } + + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + if ($method == Constants::STARTWEEK_MONDAY_ISO) { + Helpers::silly1900($PHPDateObject); + + return (int) $PHPDateObject->format('W'); + } + if (self::buggyWeekNum1904($method, $origDateValueNull, $PHPDateObject)) { + return 0; + } + Helpers::silly1900($PHPDateObject, '+ 5 years'); // 1905 calendar matches + $dayOfYear = (int) $PHPDateObject->format('z'); + $PHPDateObject->modify('-' . $dayOfYear . ' days'); + $firstDayOfFirstWeek = (int) $PHPDateObject->format('w'); + $daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7; + $daysInFirstWeek += 7 * !$daysInFirstWeek; + $endFirstWeek = $daysInFirstWeek - 1; + $weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7); + + return (int) $weekOfYear; + } + + /** + * Validate dateValue parameter. + * + * @param mixed $dateValue + */ + private static function validateDateValue($dateValue): float + { + if (is_bool($dateValue)) { + throw new Exception(Functions::VALUE()); + } + + return Helpers::getDateValue($dateValue); + } + + /** + * Validate method parameter. + * + * @param mixed $method + */ + private static function validateMethod($method): int + { + if ($method === null) { + $method = Constants::STARTWEEK_SUNDAY; + } + $method = Functions::flattenSingleValue($method); + if (!is_numeric($method)) { + throw new Exception(Functions::VALUE()); + } + + $method = (int) $method; + if (!array_key_exists($method, Constants::METHODARR)) { + throw new Exception(Functions::NAN()); + } + $method = Constants::METHODARR[$method]; + + return $method; + } + + private static function buggyWeekNum1900(int $method): bool + { + return $method === Constants::DOW_SUNDAY && Date::getExcelCalendar() === Date::CALENDAR_WINDOWS_1900; + } + + private static function buggyWeekNum1904(int $method, bool $origNull, DateTime $dateObject): bool + { + // This appears to be another Excel bug. + + return $method === Constants::DOW_SUNDAY && Date::getExcelCalendar() === Date::CALENDAR_MAC_1904 && !$origNull && $dateObject->format('Y-m-d') === '1904-01-01'; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php new file mode 100644 index 00000000..09816d33 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php @@ -0,0 +1,184 @@ +getMessage(); + } + + $startDate = (float) floor($startDate); + $endDays = (int) floor($endDays); + // If endDays is 0, we always return startDate + if ($endDays == 0) { + return $startDate; + } + if ($endDays < 0) { + return self::decrementing($startDate, $endDays, $holidayArray); + } + + return self::incrementing($startDate, $endDays, $holidayArray); + } + + /** + * Use incrementing logic to determine Workday. + * + * @return mixed + */ + private static function incrementing(float $startDate, int $endDays, array $holidayArray) + { + // Adjust the start date if it falls over a weekend + + $startDoW = WeekDay::funcWeekDay($startDate, 3); + if (WeekDay::funcWeekDay($startDate, 3) >= 5) { + $startDate += 7 - $startDoW; + --$endDays; + } + + // Add endDays + $endDate = (float) $startDate + ((int) ($endDays / 5) * 7); + $endDays = $endDays % 5; + while ($endDays > 0) { + ++$endDate; + // Adjust the calculated end date if it falls over a weekend + $endDow = WeekDay::funcWeekDay($endDate, 3); + if ($endDow >= 5) { + $endDate += 7 - $endDow; + } + --$endDays; + } + + // Test any extra holiday parameters + if (!empty($holidayArray)) { + $endDate = self::incrementingArray($startDate, $endDate, $holidayArray); + } + + return Helpers::returnIn3FormatsFloat($endDate); + } + + private static function incrementingArray(float $startDate, float $endDate, array $holidayArray): float + { + $holidayCountedArray = $holidayDates = []; + foreach ($holidayArray as $holidayDate) { + if (WeekDay::funcWeekDay($holidayDate, 3) < 5) { + $holidayDates[] = $holidayDate; + } + } + sort($holidayDates, SORT_NUMERIC); + foreach ($holidayDates as $holidayDate) { + if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { + if (!in_array($holidayDate, $holidayCountedArray)) { + ++$endDate; + $holidayCountedArray[] = $holidayDate; + } + } + // Adjust the calculated end date if it falls over a weekend + $endDoW = WeekDay::funcWeekDay($endDate, 3); + if ($endDoW >= 5) { + $endDate += 7 - $endDoW; + } + } + + return $endDate; + } + + /** + * Use decrementing logic to determine Workday. + * + * @return mixed + */ + private static function decrementing(float $startDate, int $endDays, array $holidayArray) + { + // Adjust the start date if it falls over a weekend + + $startDoW = WeekDay::funcWeekDay($startDate, 3); + if (WeekDay::funcWeekDay($startDate, 3) >= 5) { + // @phpstan-ignore-next-line + $startDate += -$startDoW + 4; + ++$endDays; + } + + // Add endDays + $endDate = (float) $startDate + ((int) ($endDays / 5) * 7); + $endDays = $endDays % 5; + while ($endDays < 0) { + --$endDate; + // Adjust the calculated end date if it falls over a weekend + $endDow = WeekDay::funcWeekDay($endDate, 3); + if ($endDow >= 5) { + $endDate += 4 - $endDow; + } + ++$endDays; + } + + // Test any extra holiday parameters + if (!empty($holidayArray)) { + $endDate = self::decrementingArray($startDate, $endDate, $holidayArray); + } + + return Helpers::returnIn3FormatsFloat($endDate); + } + + private static function decrementingArray(float $startDate, float $endDate, array $holidayArray): float + { + $holidayCountedArray = $holidayDates = []; + foreach ($holidayArray as $holidayDate) { + if (WeekDay::funcWeekDay($holidayDate, 3) < 5) { + $holidayDates[] = $holidayDate; + } + } + rsort($holidayDates, SORT_NUMERIC); + foreach ($holidayDates as $holidayDate) { + if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) { + if (!in_array($holidayDate, $holidayCountedArray)) { + --$endDate; + $holidayCountedArray[] = $holidayDate; + } + } + // Adjust the calculated end date if it falls over a weekend + $endDoW = WeekDay::funcWeekDay($endDate, 3); + if ($endDoW >= 5) { + // @phpstan-ignore-next-line + $endDate += -$endDoW + 4; + } + } + + return $endDate; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Year.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Year.php new file mode 100644 index 00000000..5fcac739 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Year.php @@ -0,0 +1,40 @@ +getMessage(); + } + + if ($dateValue < 1 && Date::getExcelCalendar() === DATE::CALENDAR_WINDOWS_1900) { + return 1900; + } + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + + return (int) $PHPDateObject->format('Y'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php new file mode 100644 index 00000000..a99b1c7f --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php @@ -0,0 +1,120 @@ +getMessage(); + } + + switch ($method) { + case 0: + return Days360::funcDays360($startDate, $endDate) / 360; + case 1: + return self::method1($startDate, $endDate); + case 2: + return DateDif::funcDateDif($startDate, $endDate) / 360; + case 3: + return DateDif::funcDateDif($startDate, $endDate) / 365; + case 4: + return Days360::funcDays360($startDate, $endDate, true) / 360; + } + + return Functions::NAN(); + } + + /** + * Excel 1900 calendar treats date argument of null as 1900-01-00. Really. + * + * @param mixed $startDate + * @param mixed $endDate + */ + private static function excelBug(float $sDate, $startDate, $endDate, int $method): float + { + if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE && Date::getExcelCalendar() !== Date::CALENDAR_MAC_1904) { + if ($endDate === null && $startDate !== null) { + if (Month::funcMonth($sDate) == 12 && Day::funcDay($sDate) === 31 && $method === 0) { + $sDate += 2; + } else { + ++$sDate; + } + } + } + + return $sDate; + } + + private static function method1(float $startDate, float $endDate): float + { + $days = DateDif::funcDateDif($startDate, $endDate); + $startYear = Year::funcYear($startDate); + $endYear = Year::funcYear($endDate); + $years = $endYear - $startYear + 1; + $startMonth = Month::funcMonth($startDate); + $startDay = Day::funcDay($startDate); + $endMonth = Month::funcMonth($endDate); + $endDay = Day::funcDay($endDate); + $startMonthDay = 100 * $startMonth + $startDay; + $endMonthDay = 100 * $endMonth + $endDay; + if ($years == 1) { + $tmpCalcAnnualBasis = 365 + (int) Helpers::isLeapYear($endYear); + } elseif ($years == 2 && $startMonthDay >= $endMonthDay) { + if (Helpers::isLeapYear($startYear)) { + $tmpCalcAnnualBasis = 365 + (int) ($startMonthDay <= 229); + } elseif (Helpers::isLeapYear($endYear)) { + $tmpCalcAnnualBasis = 365 + (int) ($endMonthDay >= 229); + } else { + $tmpCalcAnnualBasis = 365; + } + } else { + $tmpCalcAnnualBasis = 0; + for ($year = $startYear; $year <= $endYear; ++$year) { + $tmpCalcAnnualBasis += 365 + (int) Helpers::isLeapYear($year); + } + $tmpCalcAnnualBasis /= $years; + } + + return $days / $tmpCalcAnnualBasis; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering.php b/src/PhpSpreadsheet/Calculation/Engineering.php index 1256dd90..a70ddac5 100644 --- a/src/PhpSpreadsheet/Calculation/Engineering.php +++ b/src/PhpSpreadsheet/Calculation/Engineering.php @@ -3,725 +3,28 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; use Complex\Complex; -use Complex\Exception as ComplexException; +use PhpOffice\PhpSpreadsheet\Calculation\Engineering\ComplexFunctions; +use PhpOffice\PhpSpreadsheet\Calculation\Engineering\ComplexOperations; +/** + * @deprecated 1.18.0 + */ class Engineering { /** * EULER. - */ - const EULER = 2.71828182845904523536; - - /** - * Details of the Units of measure that can be used in CONVERTUOM(). * - * @var mixed[] + * @deprecated 1.18.0 + * @see Use Engineering\Constants\EULER instead */ - private static $conversionUnits = [ - 'g' => ['Group' => 'Mass', 'Unit Name' => 'Gram', 'AllowPrefix' => true], - 'sg' => ['Group' => 'Mass', 'Unit Name' => 'Slug', 'AllowPrefix' => false], - 'lbm' => ['Group' => 'Mass', 'Unit Name' => 'Pound mass (avoirdupois)', 'AllowPrefix' => false], - 'u' => ['Group' => 'Mass', 'Unit Name' => 'U (atomic mass unit)', 'AllowPrefix' => true], - 'ozm' => ['Group' => 'Mass', 'Unit Name' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => false], - 'm' => ['Group' => 'Distance', 'Unit Name' => 'Meter', 'AllowPrefix' => true], - 'mi' => ['Group' => 'Distance', 'Unit Name' => 'Statute mile', 'AllowPrefix' => false], - 'Nmi' => ['Group' => 'Distance', 'Unit Name' => 'Nautical mile', 'AllowPrefix' => false], - 'in' => ['Group' => 'Distance', 'Unit Name' => 'Inch', 'AllowPrefix' => false], - 'ft' => ['Group' => 'Distance', 'Unit Name' => 'Foot', 'AllowPrefix' => false], - 'yd' => ['Group' => 'Distance', 'Unit Name' => 'Yard', 'AllowPrefix' => false], - 'ang' => ['Group' => 'Distance', 'Unit Name' => 'Angstrom', 'AllowPrefix' => true], - 'Pica' => ['Group' => 'Distance', 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false], - 'yr' => ['Group' => 'Time', 'Unit Name' => 'Year', 'AllowPrefix' => false], - 'day' => ['Group' => 'Time', 'Unit Name' => 'Day', 'AllowPrefix' => false], - 'hr' => ['Group' => 'Time', 'Unit Name' => 'Hour', 'AllowPrefix' => false], - 'mn' => ['Group' => 'Time', 'Unit Name' => 'Minute', 'AllowPrefix' => false], - 'sec' => ['Group' => 'Time', 'Unit Name' => 'Second', 'AllowPrefix' => true], - 'Pa' => ['Group' => 'Pressure', 'Unit Name' => 'Pascal', 'AllowPrefix' => true], - 'p' => ['Group' => 'Pressure', 'Unit Name' => 'Pascal', 'AllowPrefix' => true], - 'atm' => ['Group' => 'Pressure', 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true], - 'at' => ['Group' => 'Pressure', 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true], - 'mmHg' => ['Group' => 'Pressure', 'Unit Name' => 'mm of Mercury', 'AllowPrefix' => true], - 'N' => ['Group' => 'Force', 'Unit Name' => 'Newton', 'AllowPrefix' => true], - 'dyn' => ['Group' => 'Force', 'Unit Name' => 'Dyne', 'AllowPrefix' => true], - 'dy' => ['Group' => 'Force', 'Unit Name' => 'Dyne', 'AllowPrefix' => true], - 'lbf' => ['Group' => 'Force', 'Unit Name' => 'Pound force', 'AllowPrefix' => false], - 'J' => ['Group' => 'Energy', 'Unit Name' => 'Joule', 'AllowPrefix' => true], - 'e' => ['Group' => 'Energy', 'Unit Name' => 'Erg', 'AllowPrefix' => true], - 'c' => ['Group' => 'Energy', 'Unit Name' => 'Thermodynamic calorie', 'AllowPrefix' => true], - 'cal' => ['Group' => 'Energy', 'Unit Name' => 'IT calorie', 'AllowPrefix' => true], - 'eV' => ['Group' => 'Energy', 'Unit Name' => 'Electron volt', 'AllowPrefix' => true], - 'ev' => ['Group' => 'Energy', 'Unit Name' => 'Electron volt', 'AllowPrefix' => true], - 'HPh' => ['Group' => 'Energy', 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false], - 'hh' => ['Group' => 'Energy', 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false], - 'Wh' => ['Group' => 'Energy', 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true], - 'wh' => ['Group' => 'Energy', 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true], - 'flb' => ['Group' => 'Energy', 'Unit Name' => 'Foot-pound', 'AllowPrefix' => false], - 'BTU' => ['Group' => 'Energy', 'Unit Name' => 'BTU', 'AllowPrefix' => false], - 'btu' => ['Group' => 'Energy', 'Unit Name' => 'BTU', 'AllowPrefix' => false], - 'HP' => ['Group' => 'Power', 'Unit Name' => 'Horsepower', 'AllowPrefix' => false], - 'h' => ['Group' => 'Power', 'Unit Name' => 'Horsepower', 'AllowPrefix' => false], - 'W' => ['Group' => 'Power', 'Unit Name' => 'Watt', 'AllowPrefix' => true], - 'w' => ['Group' => 'Power', 'Unit Name' => 'Watt', 'AllowPrefix' => true], - 'T' => ['Group' => 'Magnetism', 'Unit Name' => 'Tesla', 'AllowPrefix' => true], - 'ga' => ['Group' => 'Magnetism', 'Unit Name' => 'Gauss', 'AllowPrefix' => true], - 'C' => ['Group' => 'Temperature', 'Unit Name' => 'Celsius', 'AllowPrefix' => false], - 'cel' => ['Group' => 'Temperature', 'Unit Name' => 'Celsius', 'AllowPrefix' => false], - 'F' => ['Group' => 'Temperature', 'Unit Name' => 'Fahrenheit', 'AllowPrefix' => false], - 'fah' => ['Group' => 'Temperature', 'Unit Name' => 'Fahrenheit', 'AllowPrefix' => false], - 'K' => ['Group' => 'Temperature', 'Unit Name' => 'Kelvin', 'AllowPrefix' => false], - 'kel' => ['Group' => 'Temperature', 'Unit Name' => 'Kelvin', 'AllowPrefix' => false], - 'tsp' => ['Group' => 'Liquid', 'Unit Name' => 'Teaspoon', 'AllowPrefix' => false], - 'tbs' => ['Group' => 'Liquid', 'Unit Name' => 'Tablespoon', 'AllowPrefix' => false], - 'oz' => ['Group' => 'Liquid', 'Unit Name' => 'Fluid Ounce', 'AllowPrefix' => false], - 'cup' => ['Group' => 'Liquid', 'Unit Name' => 'Cup', 'AllowPrefix' => false], - 'pt' => ['Group' => 'Liquid', 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false], - 'us_pt' => ['Group' => 'Liquid', 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false], - 'uk_pt' => ['Group' => 'Liquid', 'Unit Name' => 'U.K. Pint', 'AllowPrefix' => false], - 'qt' => ['Group' => 'Liquid', 'Unit Name' => 'Quart', 'AllowPrefix' => false], - 'gal' => ['Group' => 'Liquid', 'Unit Name' => 'Gallon', 'AllowPrefix' => false], - 'l' => ['Group' => 'Liquid', 'Unit Name' => 'Litre', 'AllowPrefix' => true], - 'lt' => ['Group' => 'Liquid', 'Unit Name' => 'Litre', 'AllowPrefix' => true], - ]; - - /** - * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). - * - * @var mixed[] - */ - private static $conversionMultipliers = [ - 'Y' => ['multiplier' => 1E24, 'name' => 'yotta'], - 'Z' => ['multiplier' => 1E21, 'name' => 'zetta'], - 'E' => ['multiplier' => 1E18, 'name' => 'exa'], - 'P' => ['multiplier' => 1E15, 'name' => 'peta'], - 'T' => ['multiplier' => 1E12, 'name' => 'tera'], - 'G' => ['multiplier' => 1E9, 'name' => 'giga'], - 'M' => ['multiplier' => 1E6, 'name' => 'mega'], - 'k' => ['multiplier' => 1E3, 'name' => 'kilo'], - 'h' => ['multiplier' => 1E2, 'name' => 'hecto'], - 'e' => ['multiplier' => 1E1, 'name' => 'deka'], - 'd' => ['multiplier' => 1E-1, 'name' => 'deci'], - 'c' => ['multiplier' => 1E-2, 'name' => 'centi'], - 'm' => ['multiplier' => 1E-3, 'name' => 'milli'], - 'u' => ['multiplier' => 1E-6, 'name' => 'micro'], - 'n' => ['multiplier' => 1E-9, 'name' => 'nano'], - 'p' => ['multiplier' => 1E-12, 'name' => 'pico'], - 'f' => ['multiplier' => 1E-15, 'name' => 'femto'], - 'a' => ['multiplier' => 1E-18, 'name' => 'atto'], - 'z' => ['multiplier' => 1E-21, 'name' => 'zepto'], - 'y' => ['multiplier' => 1E-24, 'name' => 'yocto'], - ]; - - /** - * Details of the Units of measure conversion factors, organised by group. - * - * @var mixed[] - */ - private static $unitConversions = [ - 'Mass' => [ - 'g' => [ - 'g' => 1.0, - 'sg' => 6.85220500053478E-05, - 'lbm' => 2.20462291469134E-03, - 'u' => 6.02217000000000E+23, - 'ozm' => 3.52739718003627E-02, - ], - 'sg' => [ - 'g' => 1.45938424189287E+04, - 'sg' => 1.0, - 'lbm' => 3.21739194101647E+01, - 'u' => 8.78866000000000E+27, - 'ozm' => 5.14782785944229E+02, - ], - 'lbm' => [ - 'g' => 4.5359230974881148E+02, - 'sg' => 3.10810749306493E-02, - 'lbm' => 1.0, - 'u' => 2.73161000000000E+26, - 'ozm' => 1.60000023429410E+01, - ], - 'u' => [ - 'g' => 1.66053100460465E-24, - 'sg' => 1.13782988532950E-28, - 'lbm' => 3.66084470330684E-27, - 'u' => 1.0, - 'ozm' => 5.85735238300524E-26, - ], - 'ozm' => [ - 'g' => 2.83495152079732E+01, - 'sg' => 1.94256689870811E-03, - 'lbm' => 6.24999908478882E-02, - 'u' => 1.70725600000000E+25, - 'ozm' => 1.0, - ], - ], - 'Distance' => [ - 'm' => [ - 'm' => 1.0, - 'mi' => 6.21371192237334E-04, - 'Nmi' => 5.39956803455724E-04, - 'in' => 3.93700787401575E+01, - 'ft' => 3.28083989501312E+00, - 'yd' => 1.09361329797891E+00, - 'ang' => 1.00000000000000E+10, - 'Pica' => 2.83464566929116E+03, - ], - 'mi' => [ - 'm' => 1.60934400000000E+03, - 'mi' => 1.0, - 'Nmi' => 8.68976241900648E-01, - 'in' => 6.33600000000000E+04, - 'ft' => 5.28000000000000E+03, - 'yd' => 1.76000000000000E+03, - 'ang' => 1.60934400000000E+13, - 'Pica' => 4.56191999999971E+06, - ], - 'Nmi' => [ - 'm' => 1.85200000000000E+03, - 'mi' => 1.15077944802354E+00, - 'Nmi' => 1.0, - 'in' => 7.29133858267717E+04, - 'ft' => 6.07611548556430E+03, - 'yd' => 2.02537182785694E+03, - 'ang' => 1.85200000000000E+13, - 'Pica' => 5.24976377952723E+06, - ], - 'in' => [ - 'm' => 2.54000000000000E-02, - 'mi' => 1.57828282828283E-05, - 'Nmi' => 1.37149028077754E-05, - 'in' => 1.0, - 'ft' => 8.33333333333333E-02, - 'yd' => 2.77777777686643E-02, - 'ang' => 2.54000000000000E+08, - 'Pica' => 7.19999999999955E+01, - ], - 'ft' => [ - 'm' => 3.04800000000000E-01, - 'mi' => 1.89393939393939E-04, - 'Nmi' => 1.64578833693305E-04, - 'in' => 1.20000000000000E+01, - 'ft' => 1.0, - 'yd' => 3.33333333223972E-01, - 'ang' => 3.04800000000000E+09, - 'Pica' => 8.63999999999946E+02, - ], - 'yd' => [ - 'm' => 9.14400000300000E-01, - 'mi' => 5.68181818368230E-04, - 'Nmi' => 4.93736501241901E-04, - 'in' => 3.60000000118110E+01, - 'ft' => 3.00000000000000E+00, - 'yd' => 1.0, - 'ang' => 9.14400000300000E+09, - 'Pica' => 2.59200000085023E+03, - ], - 'ang' => [ - 'm' => 1.00000000000000E-10, - 'mi' => 6.21371192237334E-14, - 'Nmi' => 5.39956803455724E-14, - 'in' => 3.93700787401575E-09, - 'ft' => 3.28083989501312E-10, - 'yd' => 1.09361329797891E-10, - 'ang' => 1.0, - 'Pica' => 2.83464566929116E-07, - ], - 'Pica' => [ - 'm' => 3.52777777777800E-04, - 'mi' => 2.19205948372629E-07, - 'Nmi' => 1.90484761219114E-07, - 'in' => 1.38888888888898E-02, - 'ft' => 1.15740740740748E-03, - 'yd' => 3.85802469009251E-04, - 'ang' => 3.52777777777800E+06, - 'Pica' => 1.0, - ], - ], - 'Time' => [ - 'yr' => [ - 'yr' => 1.0, - 'day' => 365.25, - 'hr' => 8766.0, - 'mn' => 525960.0, - 'sec' => 31557600.0, - ], - 'day' => [ - 'yr' => 2.73785078713210E-03, - 'day' => 1.0, - 'hr' => 24.0, - 'mn' => 1440.0, - 'sec' => 86400.0, - ], - 'hr' => [ - 'yr' => 1.14077116130504E-04, - 'day' => 4.16666666666667E-02, - 'hr' => 1.0, - 'mn' => 60.0, - 'sec' => 3600.0, - ], - 'mn' => [ - 'yr' => 1.90128526884174E-06, - 'day' => 6.94444444444444E-04, - 'hr' => 1.66666666666667E-02, - 'mn' => 1.0, - 'sec' => 60.0, - ], - 'sec' => [ - 'yr' => 3.16880878140289E-08, - 'day' => 1.15740740740741E-05, - 'hr' => 2.77777777777778E-04, - 'mn' => 1.66666666666667E-02, - 'sec' => 1.0, - ], - ], - 'Pressure' => [ - 'Pa' => [ - 'Pa' => 1.0, - 'p' => 1.0, - 'atm' => 9.86923299998193E-06, - 'at' => 9.86923299998193E-06, - 'mmHg' => 7.50061707998627E-03, - ], - 'p' => [ - 'Pa' => 1.0, - 'p' => 1.0, - 'atm' => 9.86923299998193E-06, - 'at' => 9.86923299998193E-06, - 'mmHg' => 7.50061707998627E-03, - ], - 'atm' => [ - 'Pa' => 1.01324996583000E+05, - 'p' => 1.01324996583000E+05, - 'atm' => 1.0, - 'at' => 1.0, - 'mmHg' => 760.0, - ], - 'at' => [ - 'Pa' => 1.01324996583000E+05, - 'p' => 1.01324996583000E+05, - 'atm' => 1.0, - 'at' => 1.0, - 'mmHg' => 760.0, - ], - 'mmHg' => [ - 'Pa' => 1.33322363925000E+02, - 'p' => 1.33322363925000E+02, - 'atm' => 1.31578947368421E-03, - 'at' => 1.31578947368421E-03, - 'mmHg' => 1.0, - ], - ], - 'Force' => [ - 'N' => [ - 'N' => 1.0, - 'dyn' => 1.0E+5, - 'dy' => 1.0E+5, - 'lbf' => 2.24808923655339E-01, - ], - 'dyn' => [ - 'N' => 1.0E-5, - 'dyn' => 1.0, - 'dy' => 1.0, - 'lbf' => 2.24808923655339E-06, - ], - 'dy' => [ - 'N' => 1.0E-5, - 'dyn' => 1.0, - 'dy' => 1.0, - 'lbf' => 2.24808923655339E-06, - ], - 'lbf' => [ - 'N' => 4.448222, - 'dyn' => 4.448222E+5, - 'dy' => 4.448222E+5, - 'lbf' => 1.0, - ], - ], - 'Energy' => [ - 'J' => [ - 'J' => 1.0, - 'e' => 9.99999519343231E+06, - 'c' => 2.39006249473467E-01, - 'cal' => 2.38846190642017E-01, - 'eV' => 6.24145700000000E+18, - 'ev' => 6.24145700000000E+18, - 'HPh' => 3.72506430801000E-07, - 'hh' => 3.72506430801000E-07, - 'Wh' => 2.77777916238711E-04, - 'wh' => 2.77777916238711E-04, - 'flb' => 2.37304222192651E+01, - 'BTU' => 9.47815067349015E-04, - 'btu' => 9.47815067349015E-04, - ], - 'e' => [ - 'J' => 1.00000048065700E-07, - 'e' => 1.0, - 'c' => 2.39006364353494E-08, - 'cal' => 2.38846305445111E-08, - 'eV' => 6.24146000000000E+11, - 'ev' => 6.24146000000000E+11, - 'HPh' => 3.72506609848824E-14, - 'hh' => 3.72506609848824E-14, - 'Wh' => 2.77778049754611E-11, - 'wh' => 2.77778049754611E-11, - 'flb' => 2.37304336254586E-06, - 'BTU' => 9.47815522922962E-11, - 'btu' => 9.47815522922962E-11, - ], - 'c' => [ - 'J' => 4.18399101363672E+00, - 'e' => 4.18398900257312E+07, - 'c' => 1.0, - 'cal' => 9.99330315287563E-01, - 'eV' => 2.61142000000000E+19, - 'ev' => 2.61142000000000E+19, - 'HPh' => 1.55856355899327E-06, - 'hh' => 1.55856355899327E-06, - 'Wh' => 1.16222030532950E-03, - 'wh' => 1.16222030532950E-03, - 'flb' => 9.92878733152102E+01, - 'BTU' => 3.96564972437776E-03, - 'btu' => 3.96564972437776E-03, - ], - 'cal' => [ - 'J' => 4.18679484613929E+00, - 'e' => 4.18679283372801E+07, - 'c' => 1.00067013349059E+00, - 'cal' => 1.0, - 'eV' => 2.61317000000000E+19, - 'ev' => 2.61317000000000E+19, - 'HPh' => 1.55960800463137E-06, - 'hh' => 1.55960800463137E-06, - 'Wh' => 1.16299914807955E-03, - 'wh' => 1.16299914807955E-03, - 'flb' => 9.93544094443283E+01, - 'BTU' => 3.96830723907002E-03, - 'btu' => 3.96830723907002E-03, - ], - 'eV' => [ - 'J' => 1.60219000146921E-19, - 'e' => 1.60218923136574E-12, - 'c' => 3.82933423195043E-20, - 'cal' => 3.82676978535648E-20, - 'eV' => 1.0, - 'ev' => 1.0, - 'HPh' => 5.96826078912344E-26, - 'hh' => 5.96826078912344E-26, - 'Wh' => 4.45053000026614E-23, - 'wh' => 4.45053000026614E-23, - 'flb' => 3.80206452103492E-18, - 'BTU' => 1.51857982414846E-22, - 'btu' => 1.51857982414846E-22, - ], - 'ev' => [ - 'J' => 1.60219000146921E-19, - 'e' => 1.60218923136574E-12, - 'c' => 3.82933423195043E-20, - 'cal' => 3.82676978535648E-20, - 'eV' => 1.0, - 'ev' => 1.0, - 'HPh' => 5.96826078912344E-26, - 'hh' => 5.96826078912344E-26, - 'Wh' => 4.45053000026614E-23, - 'wh' => 4.45053000026614E-23, - 'flb' => 3.80206452103492E-18, - 'BTU' => 1.51857982414846E-22, - 'btu' => 1.51857982414846E-22, - ], - 'HPh' => [ - 'J' => 2.68451741316170E+06, - 'e' => 2.68451612283024E+13, - 'c' => 6.41616438565991E+05, - 'cal' => 6.41186757845835E+05, - 'eV' => 1.67553000000000E+25, - 'ev' => 1.67553000000000E+25, - 'HPh' => 1.0, - 'hh' => 1.0, - 'Wh' => 7.45699653134593E+02, - 'wh' => 7.45699653134593E+02, - 'flb' => 6.37047316692964E+07, - 'BTU' => 2.54442605275546E+03, - 'btu' => 2.54442605275546E+03, - ], - 'hh' => [ - 'J' => 2.68451741316170E+06, - 'e' => 2.68451612283024E+13, - 'c' => 6.41616438565991E+05, - 'cal' => 6.41186757845835E+05, - 'eV' => 1.67553000000000E+25, - 'ev' => 1.67553000000000E+25, - 'HPh' => 1.0, - 'hh' => 1.0, - 'Wh' => 7.45699653134593E+02, - 'wh' => 7.45699653134593E+02, - 'flb' => 6.37047316692964E+07, - 'BTU' => 2.54442605275546E+03, - 'btu' => 2.54442605275546E+03, - ], - 'Wh' => [ - 'J' => 3.59999820554720E+03, - 'e' => 3.59999647518369E+10, - 'c' => 8.60422069219046E+02, - 'cal' => 8.59845857713046E+02, - 'eV' => 2.24692340000000E+22, - 'ev' => 2.24692340000000E+22, - 'HPh' => 1.34102248243839E-03, - 'hh' => 1.34102248243839E-03, - 'Wh' => 1.0, - 'wh' => 1.0, - 'flb' => 8.54294774062316E+04, - 'BTU' => 3.41213254164705E+00, - 'btu' => 3.41213254164705E+00, - ], - 'wh' => [ - 'J' => 3.59999820554720E+03, - 'e' => 3.59999647518369E+10, - 'c' => 8.60422069219046E+02, - 'cal' => 8.59845857713046E+02, - 'eV' => 2.24692340000000E+22, - 'ev' => 2.24692340000000E+22, - 'HPh' => 1.34102248243839E-03, - 'hh' => 1.34102248243839E-03, - 'Wh' => 1.0, - 'wh' => 1.0, - 'flb' => 8.54294774062316E+04, - 'BTU' => 3.41213254164705E+00, - 'btu' => 3.41213254164705E+00, - ], - 'flb' => [ - 'J' => 4.21400003236424E-02, - 'e' => 4.21399800687660E+05, - 'c' => 1.00717234301644E-02, - 'cal' => 1.00649785509554E-02, - 'eV' => 2.63015000000000E+17, - 'ev' => 2.63015000000000E+17, - 'HPh' => 1.56974211145130E-08, - 'hh' => 1.56974211145130E-08, - 'Wh' => 1.17055614802000E-05, - 'wh' => 1.17055614802000E-05, - 'flb' => 1.0, - 'BTU' => 3.99409272448406E-05, - 'btu' => 3.99409272448406E-05, - ], - 'BTU' => [ - 'J' => 1.05505813786749E+03, - 'e' => 1.05505763074665E+10, - 'c' => 2.52165488508168E+02, - 'cal' => 2.51996617135510E+02, - 'eV' => 6.58510000000000E+21, - 'ev' => 6.58510000000000E+21, - 'HPh' => 3.93015941224568E-04, - 'hh' => 3.93015941224568E-04, - 'Wh' => 2.93071851047526E-01, - 'wh' => 2.93071851047526E-01, - 'flb' => 2.50369750774671E+04, - 'BTU' => 1.0, - 'btu' => 1.0, - ], - 'btu' => [ - 'J' => 1.05505813786749E+03, - 'e' => 1.05505763074665E+10, - 'c' => 2.52165488508168E+02, - 'cal' => 2.51996617135510E+02, - 'eV' => 6.58510000000000E+21, - 'ev' => 6.58510000000000E+21, - 'HPh' => 3.93015941224568E-04, - 'hh' => 3.93015941224568E-04, - 'Wh' => 2.93071851047526E-01, - 'wh' => 2.93071851047526E-01, - 'flb' => 2.50369750774671E+04, - 'BTU' => 1.0, - 'btu' => 1.0, - ], - ], - 'Power' => [ - 'HP' => [ - 'HP' => 1.0, - 'h' => 1.0, - 'W' => 7.45701000000000E+02, - 'w' => 7.45701000000000E+02, - ], - 'h' => [ - 'HP' => 1.0, - 'h' => 1.0, - 'W' => 7.45701000000000E+02, - 'w' => 7.45701000000000E+02, - ], - 'W' => [ - 'HP' => 1.34102006031908E-03, - 'h' => 1.34102006031908E-03, - 'W' => 1.0, - 'w' => 1.0, - ], - 'w' => [ - 'HP' => 1.34102006031908E-03, - 'h' => 1.34102006031908E-03, - 'W' => 1.0, - 'w' => 1.0, - ], - ], - 'Magnetism' => [ - 'T' => [ - 'T' => 1.0, - 'ga' => 10000.0, - ], - 'ga' => [ - 'T' => 0.0001, - 'ga' => 1.0, - ], - ], - 'Liquid' => [ - 'tsp' => [ - 'tsp' => 1.0, - 'tbs' => 3.33333333333333E-01, - 'oz' => 1.66666666666667E-01, - 'cup' => 2.08333333333333E-02, - 'pt' => 1.04166666666667E-02, - 'us_pt' => 1.04166666666667E-02, - 'uk_pt' => 8.67558516821960E-03, - 'qt' => 5.20833333333333E-03, - 'gal' => 1.30208333333333E-03, - 'l' => 4.92999408400710E-03, - 'lt' => 4.92999408400710E-03, - ], - 'tbs' => [ - 'tsp' => 3.00000000000000E+00, - 'tbs' => 1.0, - 'oz' => 5.00000000000000E-01, - 'cup' => 6.25000000000000E-02, - 'pt' => 3.12500000000000E-02, - 'us_pt' => 3.12500000000000E-02, - 'uk_pt' => 2.60267555046588E-02, - 'qt' => 1.56250000000000E-02, - 'gal' => 3.90625000000000E-03, - 'l' => 1.47899822520213E-02, - 'lt' => 1.47899822520213E-02, - ], - 'oz' => [ - 'tsp' => 6.00000000000000E+00, - 'tbs' => 2.00000000000000E+00, - 'oz' => 1.0, - 'cup' => 1.25000000000000E-01, - 'pt' => 6.25000000000000E-02, - 'us_pt' => 6.25000000000000E-02, - 'uk_pt' => 5.20535110093176E-02, - 'qt' => 3.12500000000000E-02, - 'gal' => 7.81250000000000E-03, - 'l' => 2.95799645040426E-02, - 'lt' => 2.95799645040426E-02, - ], - 'cup' => [ - 'tsp' => 4.80000000000000E+01, - 'tbs' => 1.60000000000000E+01, - 'oz' => 8.00000000000000E+00, - 'cup' => 1.0, - 'pt' => 5.00000000000000E-01, - 'us_pt' => 5.00000000000000E-01, - 'uk_pt' => 4.16428088074541E-01, - 'qt' => 2.50000000000000E-01, - 'gal' => 6.25000000000000E-02, - 'l' => 2.36639716032341E-01, - 'lt' => 2.36639716032341E-01, - ], - 'pt' => [ - 'tsp' => 9.60000000000000E+01, - 'tbs' => 3.20000000000000E+01, - 'oz' => 1.60000000000000E+01, - 'cup' => 2.00000000000000E+00, - 'pt' => 1.0, - 'us_pt' => 1.0, - 'uk_pt' => 8.32856176149081E-01, - 'qt' => 5.00000000000000E-01, - 'gal' => 1.25000000000000E-01, - 'l' => 4.73279432064682E-01, - 'lt' => 4.73279432064682E-01, - ], - 'us_pt' => [ - 'tsp' => 9.60000000000000E+01, - 'tbs' => 3.20000000000000E+01, - 'oz' => 1.60000000000000E+01, - 'cup' => 2.00000000000000E+00, - 'pt' => 1.0, - 'us_pt' => 1.0, - 'uk_pt' => 8.32856176149081E-01, - 'qt' => 5.00000000000000E-01, - 'gal' => 1.25000000000000E-01, - 'l' => 4.73279432064682E-01, - 'lt' => 4.73279432064682E-01, - ], - 'uk_pt' => [ - 'tsp' => 1.15266000000000E+02, - 'tbs' => 3.84220000000000E+01, - 'oz' => 1.92110000000000E+01, - 'cup' => 2.40137500000000E+00, - 'pt' => 1.20068750000000E+00, - 'us_pt' => 1.20068750000000E+00, - 'uk_pt' => 1.0, - 'qt' => 6.00343750000000E-01, - 'gal' => 1.50085937500000E-01, - 'l' => 5.68260698087162E-01, - 'lt' => 5.68260698087162E-01, - ], - 'qt' => [ - 'tsp' => 1.92000000000000E+02, - 'tbs' => 6.40000000000000E+01, - 'oz' => 3.20000000000000E+01, - 'cup' => 4.00000000000000E+00, - 'pt' => 2.00000000000000E+00, - 'us_pt' => 2.00000000000000E+00, - 'uk_pt' => 1.66571235229816E+00, - 'qt' => 1.0, - 'gal' => 2.50000000000000E-01, - 'l' => 9.46558864129363E-01, - 'lt' => 9.46558864129363E-01, - ], - 'gal' => [ - 'tsp' => 7.68000000000000E+02, - 'tbs' => 2.56000000000000E+02, - 'oz' => 1.28000000000000E+02, - 'cup' => 1.60000000000000E+01, - 'pt' => 8.00000000000000E+00, - 'us_pt' => 8.00000000000000E+00, - 'uk_pt' => 6.66284940919265E+00, - 'qt' => 4.00000000000000E+00, - 'gal' => 1.0, - 'l' => 3.78623545651745E+00, - 'lt' => 3.78623545651745E+00, - ], - 'l' => [ - 'tsp' => 2.02840000000000E+02, - 'tbs' => 6.76133333333333E+01, - 'oz' => 3.38066666666667E+01, - 'cup' => 4.22583333333333E+00, - 'pt' => 2.11291666666667E+00, - 'us_pt' => 2.11291666666667E+00, - 'uk_pt' => 1.75975569552166E+00, - 'qt' => 1.05645833333333E+00, - 'gal' => 2.64114583333333E-01, - 'l' => 1.0, - 'lt' => 1.0, - ], - 'lt' => [ - 'tsp' => 2.02840000000000E+02, - 'tbs' => 6.76133333333333E+01, - 'oz' => 3.38066666666667E+01, - 'cup' => 4.22583333333333E+00, - 'pt' => 2.11291666666667E+00, - 'us_pt' => 2.11291666666667E+00, - 'uk_pt' => 1.75975569552166E+00, - 'qt' => 1.05645833333333E+00, - 'gal' => 2.64114583333333E-01, - 'l' => 1.0, - 'lt' => 1.0, - ], - ], - ]; + public const EULER = 2.71828182845904523536; /** * parseComplex. * * Parses a complex number into its real and imaginary parts, and an I or J suffix * - * @deprecated 2.0.0 No longer used by internal code. Please use the Complex\Complex class instead + * @deprecated 1.12.0 No longer used by internal code. Please use the \Complex\Complex class instead * * @param string $complexNumber The complex number * @@ -738,35 +41,6 @@ class Engineering ]; } - /** - * Formats a number base string value with leading zeroes. - * - * @param string $xVal The "number" to pad - * @param int $places The length that we want to pad this value - * - * @return string The padded "number" - */ - private static function nbrConversionFormat($xVal, $places) - { - if ($places !== null) { - if (is_numeric($places)) { - $places = (int) $places; - } else { - return Functions::VALUE(); - } - if ($places < 0) { - return Functions::NAN(); - } - if (strlen($xVal) <= $places) { - return substr(str_pad($xVal, $places, '0', STR_PAD_LEFT), -10); - } - - return Functions::NAN(); - } - - return substr($xVal, -10); - } - /** * BESSELI. * @@ -776,6 +50,10 @@ class Engineering * Excel Function: * BESSELI(x,ord) * + * @Deprecated 1.17.0 + * + * @see Use the BESSELI() method in the Engineering\BesselI class instead + * * @param float $x The value at which to evaluate the function. * If x is nonnumeric, BESSELI returns the #VALUE! error value. * @param int $ord The order of the Bessel function. @@ -787,38 +65,7 @@ class Engineering */ public static function BESSELI($x, $ord) { - $x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x); - $ord = ($ord === null) ? 0.0 : Functions::flattenSingleValue($ord); - - if ((is_numeric($x)) && (is_numeric($ord))) { - $ord = floor($ord); - if ($ord < 0) { - return Functions::NAN(); - } - - if (abs($x) <= 30) { - $fResult = $fTerm = ($x / 2) ** $ord / MathTrig::FACT($ord); - $ordK = 1; - $fSqrX = ($x * $x) / 4; - do { - $fTerm *= $fSqrX; - $fTerm /= ($ordK * ($ordK + $ord)); - $fResult += $fTerm; - } while ((abs($fTerm) > 1e-12) && (++$ordK < 100)); - } else { - $f_2_PI = 2 * M_PI; - - $fXAbs = abs($x); - $fResult = exp($fXAbs) / sqrt($f_2_PI * $fXAbs); - if (($ord & 1) && ($x < 0)) { - $fResult = -$fResult; - } - } - - return (is_nan($fResult)) ? Functions::NAN() : $fResult; - } - - return Functions::VALUE(); + return Engineering\BesselI::BESSELI($x, $ord); } /** @@ -829,6 +76,10 @@ class Engineering * Excel Function: * BESSELJ(x,ord) * + * @Deprecated 1.17.0 + * + * @see Use the BESSELJ() method in the Engineering\BesselJ class instead + * * @param float $x The value at which to evaluate the function. * If x is nonnumeric, BESSELJ returns the #VALUE! error value. * @param int $ord The order of the Bessel function. If n is not an integer, it is truncated. @@ -839,76 +90,7 @@ class Engineering */ public static function BESSELJ($x, $ord) { - $x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x); - $ord = ($ord === null) ? 0.0 : Functions::flattenSingleValue($ord); - - if ((is_numeric($x)) && (is_numeric($ord))) { - $ord = floor($ord); - if ($ord < 0) { - return Functions::NAN(); - } - - $fResult = 0; - if (abs($x) <= 30) { - $fResult = $fTerm = ($x / 2) ** $ord / MathTrig::FACT($ord); - $ordK = 1; - $fSqrX = ($x * $x) / -4; - do { - $fTerm *= $fSqrX; - $fTerm /= ($ordK * ($ordK + $ord)); - $fResult += $fTerm; - } while ((abs($fTerm) > 1e-12) && (++$ordK < 100)); - } else { - $f_PI_DIV_2 = M_PI / 2; - $f_PI_DIV_4 = M_PI / 4; - - $fXAbs = abs($x); - $fResult = sqrt(Functions::M_2DIVPI / $fXAbs) * cos($fXAbs - $ord * $f_PI_DIV_2 - $f_PI_DIV_4); - if (($ord & 1) && ($x < 0)) { - $fResult = -$fResult; - } - } - - return (is_nan($fResult)) ? Functions::NAN() : $fResult; - } - - return Functions::VALUE(); - } - - private static function besselK0($fNum) - { - if ($fNum <= 2) { - $fNum2 = $fNum * 0.5; - $y = ($fNum2 * $fNum2); - $fRet = -log($fNum2) * self::BESSELI($fNum, 0) + - (-0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y * - (0.10750e-3 + $y * 0.74e-5)))))); - } else { - $y = 2 / $fNum; - $fRet = exp(-$fNum) / sqrt($fNum) * - (1.25331414 + $y * (-0.7832358e-1 + $y * (0.2189568e-1 + $y * (-0.1062446e-1 + $y * - (0.587872e-2 + $y * (-0.251540e-2 + $y * 0.53208e-3)))))); - } - - return $fRet; - } - - private static function besselK1($fNum) - { - if ($fNum <= 2) { - $fNum2 = $fNum * 0.5; - $y = ($fNum2 * $fNum2); - $fRet = log($fNum2) * self::BESSELI($fNum, 1) + - (1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y * - (-0.110404e-2 + $y * (-0.4686e-4))))))) / $fNum; - } else { - $y = 2 / $fNum; - $fRet = exp(-$fNum) / sqrt($fNum) * - (1.25331414 + $y * (0.23498619 + $y * (-0.3655620e-1 + $y * (0.1504268e-1 + $y * (-0.780353e-2 + $y * - (0.325614e-2 + $y * (-0.68245e-3))))))); - } - - return $fRet; + return Engineering\BesselJ::BESSELJ($x, $ord); } /** @@ -920,6 +102,10 @@ class Engineering * Excel Function: * BESSELK(x,ord) * + * @Deprecated 1.17.0 + * + * @see Use the BESSELK() method in the Engineering\BesselK class instead + * * @param float $x The value at which to evaluate the function. * If x is nonnumeric, BESSELK returns the #VALUE! error value. * @param int $ord The order of the Bessel function. If n is not an integer, it is truncated. @@ -930,73 +116,7 @@ class Engineering */ public static function BESSELK($x, $ord) { - $x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x); - $ord = ($ord === null) ? 0.0 : Functions::flattenSingleValue($ord); - - if ((is_numeric($x)) && (is_numeric($ord))) { - if (($ord < 0) || ($x == 0.0)) { - return Functions::NAN(); - } - - switch (floor($ord)) { - case 0: - $fBk = self::besselK0($x); - - break; - case 1: - $fBk = self::besselK1($x); - - break; - default: - $fTox = 2 / $x; - $fBkm = self::besselK0($x); - $fBk = self::besselK1($x); - for ($n = 1; $n < $ord; ++$n) { - $fBkp = $fBkm + $n * $fTox * $fBk; - $fBkm = $fBk; - $fBk = $fBkp; - } - } - - return (is_nan($fBk)) ? Functions::NAN() : $fBk; - } - - return Functions::VALUE(); - } - - private static function besselY0($fNum) - { - if ($fNum < 8.0) { - $y = ($fNum * $fNum); - $f1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y * (-86327.92757 + $y * 228.4622733)))); - $f2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y * (47447.26470 + $y * (226.1030244 + $y)))); - $fRet = $f1 / $f2 + 0.636619772 * self::BESSELJ($fNum, 0) * log($fNum); - } else { - $z = 8.0 / $fNum; - $y = ($z * $z); - $xx = $fNum - 0.785398164; - $f1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6))); - $f2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y * (-0.934945152e-7)))); - $fRet = sqrt(0.636619772 / $fNum) * (sin($xx) * $f1 + $z * cos($xx) * $f2); - } - - return $fRet; - } - - private static function besselY1($fNum) - { - if ($fNum < 8.0) { - $y = ($fNum * $fNum); - $f1 = $fNum * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y * (0.7349264551e9 + $y * - (-0.4237922726e7 + $y * 0.8511937935e4))))); - $f2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y * - (0.1020426050e6 + $y * (0.3549632885e3 + $y))))); - $fRet = $f1 / $f2 + 0.636619772 * (self::BESSELJ($fNum, 1) * log($fNum) - 1 / $fNum); - } else { - $fRet = sqrt(0.636619772 / $fNum) * sin($fNum - 2.356194491); - } - - return $fRet; + return Engineering\BesselK::BESSELK($x, $ord); } /** @@ -1007,48 +127,21 @@ class Engineering * Excel Function: * BESSELY(x,ord) * + * @Deprecated 1.17.0 + * + * @see Use the BESSELY() method in the Engineering\BesselY class instead + * * @param float $x The value at which to evaluate the function. - * If x is nonnumeric, BESSELK returns the #VALUE! error value. + * If x is nonnumeric, BESSELY returns the #VALUE! error value. * @param int $ord The order of the Bessel function. If n is not an integer, it is truncated. - * If $ord is nonnumeric, BESSELK returns the #VALUE! error value. - * If $ord < 0, BESSELK returns the #NUM! error value. + * If $ord is nonnumeric, BESSELY returns the #VALUE! error value. + * If $ord < 0, BESSELY returns the #NUM! error value. * * @return float|string Result, or a string containing an error */ public static function BESSELY($x, $ord) { - $x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x); - $ord = ($ord === null) ? 0.0 : Functions::flattenSingleValue($ord); - - if ((is_numeric($x)) && (is_numeric($ord))) { - if (($ord < 0) || ($x == 0.0)) { - return Functions::NAN(); - } - - switch (floor($ord)) { - case 0: - $fBy = self::besselY0($x); - - break; - case 1: - $fBy = self::besselY1($x); - - break; - default: - $fTox = 2 / $x; - $fBym = self::besselY0($x); - $fBy = self::besselY1($x); - for ($n = 1; $n < $ord; ++$n) { - $fByp = $n * $fTox * $fBy - $fBym; - $fBym = $fBy; - $fBy = $fByp; - } - } - - return (is_nan($fBy)) ? Functions::NAN() : $fBy; - } - - return Functions::VALUE(); + return Engineering\BesselY::BESSELY($x, $ord); } /** @@ -1059,7 +152,11 @@ class Engineering * Excel Function: * BIN2DEC(x) * - * @param string $x The binary number (as a string) that you want to convert. The number + * @Deprecated 1.17.0 + * + * @see Use the toDecimal() method in the Engineering\ConvertBinary class instead + * + * @param mixed $x The binary number (as a string) that you want to convert. The number * cannot contain more than 10 characters (10 bits). The most significant * bit of number is the sign bit. The remaining 9 bits are magnitude bits. * Negative numbers are represented using two's-complement notation. @@ -1070,32 +167,7 @@ class Engineering */ public static function BINTODEC($x) { - $x = Functions::flattenSingleValue($x); - - if (is_bool($x)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $x = (int) $x; - } else { - return Functions::VALUE(); - } - } - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $x = floor($x); - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[01]/', $x, $out)) { - return Functions::NAN(); - } - if (strlen($x) > 10) { - return Functions::NAN(); - } elseif (strlen($x) == 10) { - // Two's Complement - $x = substr($x, -9); - - return '-' . (512 - bindec($x)); - } - - return bindec($x); + return Engineering\ConvertBinary::toDecimal($x); } /** @@ -1106,13 +178,17 @@ class Engineering * Excel Function: * BIN2HEX(x[,places]) * - * @param string $x The binary number (as a string) that you want to convert. The number + * @Deprecated 1.17.0 + * + * @see Use the toHex() method in the Engineering\ConvertBinary class instead + * + * @param mixed $x The binary number (as a string) that you want to convert. The number * cannot contain more than 10 characters (10 bits). The most significant * bit of number is the sign bit. The remaining 9 bits are magnitude bits. * Negative numbers are represented using two's-complement notation. * If number is not a valid binary number, or if number contains more than * 10 characters (10 bits), BIN2HEX returns the #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, BIN2HEX uses the + * @param mixed $places The number of characters to use. If places is omitted, BIN2HEX uses the * minimum number of characters necessary. Places is useful for padding the * return value with leading 0s (zeros). * If places is not an integer, it is truncated. @@ -1123,33 +199,7 @@ class Engineering */ public static function BINTOHEX($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - // Argument X - if (is_bool($x)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $x = (int) $x; - } else { - return Functions::VALUE(); - } - } - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $x = floor($x); - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[01]/', $x, $out)) { - return Functions::NAN(); - } - if (strlen($x) > 10) { - return Functions::NAN(); - } elseif (strlen($x) == 10) { - // Two's Complement - return str_repeat('F', 8) . substr(strtoupper(dechex(bindec(substr($x, -9)))), -2); - } - $hexVal = (string) strtoupper(dechex(bindec($x))); - - return self::nbrConversionFormat($hexVal, $places); + return Engineering\ConvertBinary::toHex($x, $places); } /** @@ -1160,13 +210,17 @@ class Engineering * Excel Function: * BIN2OCT(x[,places]) * - * @param string $x The binary number (as a string) that you want to convert. The number + * @Deprecated 1.17.0 + * + * @see Use the toOctal() method in the Engineering\ConvertBinary class instead + * + * @param mixed $x The binary number (as a string) that you want to convert. The number * cannot contain more than 10 characters (10 bits). The most significant * bit of number is the sign bit. The remaining 9 bits are magnitude bits. * Negative numbers are represented using two's-complement notation. * If number is not a valid binary number, or if number contains more than * 10 characters (10 bits), BIN2OCT returns the #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, BIN2OCT uses the + * @param mixed $places The number of characters to use. If places is omitted, BIN2OCT uses the * minimum number of characters necessary. Places is useful for padding the * return value with leading 0s (zeros). * If places is not an integer, it is truncated. @@ -1177,32 +231,7 @@ class Engineering */ public static function BINTOOCT($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $x = (int) $x; - } else { - return Functions::VALUE(); - } - } - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $x = floor($x); - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[01]/', $x, $out)) { - return Functions::NAN(); - } - if (strlen($x) > 10) { - return Functions::NAN(); - } elseif (strlen($x) == 10) { - // Two's Complement - return str_repeat('7', 7) . substr(strtoupper(decoct(bindec(substr($x, -9)))), -3); - } - $octVal = (string) decoct(bindec($x)); - - return self::nbrConversionFormat($octVal, $places); + return Engineering\ConvertBinary::toOctal($x, $places); } /** @@ -1213,7 +242,11 @@ class Engineering * Excel Function: * DEC2BIN(x[,places]) * - * @param string $x The decimal integer you want to convert. If number is negative, + * @Deprecated 1.17.0 + * + * @see Use the toBinary() method in the Engineering\ConvertDecimal class instead + * + * @param mixed $x The decimal integer you want to convert. If number is negative, * valid place values are ignored and DEC2BIN returns a 10-character * (10-bit) binary number in which the most significant bit is the sign * bit. The remaining 9 bits are magnitude bits. Negative numbers are @@ -1223,7 +256,7 @@ class Engineering * If number is nonnumeric, DEC2BIN returns the #VALUE! error value. * If DEC2BIN requires more than places characters, it returns the #NUM! * error value. - * @param int $places The number of characters to use. If places is omitted, DEC2BIN uses + * @param mixed $places The number of characters to use. If places is omitted, DEC2BIN uses * the minimum number of characters necessary. Places is useful for * padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. @@ -1234,34 +267,7 @@ class Engineering */ public static function DECTOBIN($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $x = (int) $x; - } else { - return Functions::VALUE(); - } - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[-0123456789.]/', $x, $out)) { - return Functions::VALUE(); - } - - $x = (string) floor($x); - if ($x < -512 || $x > 511) { - return Functions::NAN(); - } - - $r = decbin($x); - // Two's Complement - $r = substr($r, -10); - if (strlen($r) >= 11) { - return Functions::NAN(); - } - - return self::nbrConversionFormat($r, $places); + return Engineering\ConvertDecimal::toBinary($x, $places); } /** @@ -1272,7 +278,11 @@ class Engineering * Excel Function: * DEC2HEX(x[,places]) * - * @param string $x The decimal integer you want to convert. If number is negative, + * @Deprecated 1.17.0 + * + * @see Use the toHex() method in the Engineering\ConvertDecimal class instead + * + * @param mixed $x The decimal integer you want to convert. If number is negative, * places is ignored and DEC2HEX returns a 10-character (40-bit) * hexadecimal number in which the most significant bit is the sign * bit. The remaining 39 bits are magnitude bits. Negative numbers @@ -1282,7 +292,7 @@ class Engineering * If number is nonnumeric, DEC2HEX returns the #VALUE! error value. * If DEC2HEX requires more than places characters, it returns the * #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, DEC2HEX uses + * @param mixed $places The number of characters to use. If places is omitted, DEC2HEX uses * the minimum number of characters necessary. Places is useful for * padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. @@ -1293,28 +303,7 @@ class Engineering */ public static function DECTOHEX($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $x = (int) $x; - } else { - return Functions::VALUE(); - } - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[-0123456789.]/', $x, $out)) { - return Functions::VALUE(); - } - $x = (string) floor($x); - $r = strtoupper(dechex($x)); - if (strlen($r) == 8) { - // Two's Complement - $r = 'FF' . $r; - } - - return self::nbrConversionFormat($r, $places); + return Engineering\ConvertDecimal::toHex($x, $places); } /** @@ -1325,7 +314,11 @@ class Engineering * Excel Function: * DEC2OCT(x[,places]) * - * @param string $x The decimal integer you want to convert. If number is negative, + * @Deprecated 1.17.0 + * + * @see Use the toOctal() method in the Engineering\ConvertDecimal class instead + * + * @param mixed $x The decimal integer you want to convert. If number is negative, * places is ignored and DEC2OCT returns a 10-character (30-bit) * octal number in which the most significant bit is the sign bit. * The remaining 29 bits are magnitude bits. Negative numbers are @@ -1335,7 +328,7 @@ class Engineering * If number is nonnumeric, DEC2OCT returns the #VALUE! error value. * If DEC2OCT requires more than places characters, it returns the * #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, DEC2OCT uses + * @param mixed $places The number of characters to use. If places is omitted, DEC2OCT uses * the minimum number of characters necessary. Places is useful for * padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. @@ -1346,29 +339,7 @@ class Engineering */ public static function DECTOOCT($x, $places = null) { - $xorig = $x; - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $x = (int) $x; - } else { - return Functions::VALUE(); - } - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[-0123456789.]/', $x, $out)) { - return Functions::VALUE(); - } - $x = (string) floor($x); - $r = decoct($x); - if (strlen($r) == 11) { - // Two's Complement - $r = substr($r, -10); - } - - return self::nbrConversionFormat($r, $places); + return Engineering\ConvertDecimal::toOctal($x, $places); } /** @@ -1379,7 +350,11 @@ class Engineering * Excel Function: * HEX2BIN(x[,places]) * - * @param string $x the hexadecimal number you want to convert. + * @Deprecated 1.17.0 + * + * @see Use the toBinary() method in the Engineering\ConvertHex class instead + * + * @param mixed $x the hexadecimal number (as a string) that you want to convert. * Number cannot contain more than 10 characters. * The most significant bit of number is the sign bit (40th bit from the right). * The remaining 9 bits are magnitude bits. @@ -1389,7 +364,7 @@ class Engineering * and if number is positive, it cannot be greater than 1FF. * If number is not a valid hexadecimal number, HEX2BIN returns the #NUM! error value. * If HEX2BIN requires more than places characters, it returns the #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, + * @param mixed $places The number of characters to use. If places is omitted, * HEX2BIN uses the minimum number of characters necessary. Places * is useful for padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. @@ -1400,18 +375,7 @@ class Engineering */ public static function HEXTOBIN($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - return Functions::VALUE(); - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/', strtoupper($x), $out)) { - return Functions::NAN(); - } - - return self::DECTOBIN(self::HEXTODEC($x), $places); + return Engineering\ConvertHex::toBinary($x, $places); } /** @@ -1422,7 +386,11 @@ class Engineering * Excel Function: * HEX2DEC(x) * - * @param string $x The hexadecimal number you want to convert. This number cannot + * @Deprecated 1.17.0 + * + * @see Use the toDecimal() method in the Engineering\ConvertHex class instead + * + * @param mixed $x The hexadecimal number (as a string) that you want to convert. This number cannot * contain more than 10 characters (40 bits). The most significant * bit of number is the sign bit. The remaining 39 bits are magnitude * bits. Negative numbers are represented using two's-complement @@ -1434,33 +402,7 @@ class Engineering */ public static function HEXTODEC($x) { - $x = Functions::flattenSingleValue($x); - - if (is_bool($x)) { - return Functions::VALUE(); - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/', strtoupper($x), $out)) { - return Functions::NAN(); - } - - if (strlen($x) > 10) { - return Functions::NAN(); - } - - $binX = ''; - foreach (str_split($x) as $char) { - $binX .= str_pad(base_convert($char, 16, 2), 4, '0', STR_PAD_LEFT); - } - if (strlen($binX) == 40 && $binX[0] == '1') { - for ($i = 0; $i < 40; ++$i) { - $binX[$i] = ($binX[$i] == '1' ? '0' : '1'); - } - - return (bindec($binX) + 1) * -1; - } - - return bindec($binX); + return Engineering\ConvertHex::toDecimal($x); } /** @@ -1471,7 +413,11 @@ class Engineering * Excel Function: * HEX2OCT(x[,places]) * - * @param string $x The hexadecimal number you want to convert. Number cannot + * @Deprecated 1.17.0 + * + * @see Use the toOctal() method in the Engineering\ConvertHex class instead + * + * @param mixed $x The hexadecimal number (as a string) that you want to convert. Number cannot * contain more than 10 characters. The most significant bit of * number is the sign bit. The remaining 39 bits are magnitude * bits. Negative numbers are represented using two's-complement @@ -1484,7 +430,7 @@ class Engineering * the #NUM! error value. * If HEX2OCT requires more than places characters, it returns * the #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, HEX2OCT + * @param mixed $places The number of characters to use. If places is omitted, HEX2OCT * uses the minimum number of characters necessary. Places is * useful for padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. @@ -1496,23 +442,7 @@ class Engineering */ public static function HEXTOOCT($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - return Functions::VALUE(); - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/', strtoupper($x), $out)) { - return Functions::NAN(); - } - - $decimal = self::HEXTODEC($x); - if ($decimal < -536870912 || $decimal > 536870911) { - return Functions::NAN(); - } - - return self::DECTOOCT($decimal, $places); + return Engineering\ConvertHex::toOctal($x, $places); } /** @@ -1523,7 +453,11 @@ class Engineering * Excel Function: * OCT2BIN(x[,places]) * - * @param string $x The octal number you want to convert. Number may not + * @Deprecated 1.17.0 + * + * @see Use the toBinary() method in the Engineering\ConvertOctal class instead + * + * @param mixed $x The octal number you want to convert. Number may not * contain more than 10 characters. The most significant * bit of number is the sign bit. The remaining 29 bits * are magnitude bits. Negative numbers are represented @@ -1536,7 +470,7 @@ class Engineering * the #NUM! error value. * If OCT2BIN requires more than places characters, it * returns the #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, + * @param mixed $places The number of characters to use. If places is omitted, * OCT2BIN uses the minimum number of characters necessary. * Places is useful for padding the return value with * leading 0s (zeros). @@ -1550,18 +484,7 @@ class Engineering */ public static function OCTTOBIN($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - return Functions::VALUE(); - } - $x = (string) $x; - if (preg_match_all('/[01234567]/', $x, $out) != strlen($x)) { - return Functions::NAN(); - } - - return self::DECTOBIN(self::OCTTODEC($x), $places); + return Engineering\ConvertOctal::toBinary($x, $places); } /** @@ -1572,7 +495,11 @@ class Engineering * Excel Function: * OCT2DEC(x) * - * @param string $x The octal number you want to convert. Number may not contain + * @Deprecated 1.17.0 + * + * @see Use the toDecimal() method in the Engineering\ConvertOctal class instead + * + * @param mixed $x The octal number you want to convert. Number may not contain * more than 10 octal characters (30 bits). The most significant * bit of number is the sign bit. The remaining 29 bits are * magnitude bits. Negative numbers are represented using @@ -1584,28 +511,7 @@ class Engineering */ public static function OCTTODEC($x) { - $x = Functions::flattenSingleValue($x); - - if (is_bool($x)) { - return Functions::VALUE(); - } - $x = (string) $x; - if (preg_match_all('/[01234567]/', $x, $out) != strlen($x)) { - return Functions::NAN(); - } - $binX = ''; - foreach (str_split($x) as $char) { - $binX .= str_pad(decbin((int) $char), 3, '0', STR_PAD_LEFT); - } - if (strlen($binX) == 30 && $binX[0] == '1') { - for ($i = 0; $i < 30; ++$i) { - $binX[$i] = ($binX[$i] == '1' ? '0' : '1'); - } - - return (bindec($binX) + 1) * -1; - } - - return bindec($binX); + return Engineering\ConvertOctal::toDecimal($x); } /** @@ -1616,7 +522,11 @@ class Engineering * Excel Function: * OCT2HEX(x[,places]) * - * @param string $x The octal number you want to convert. Number may not contain + * @Deprecated 1.17.0 + * + * @see Use the toHex() method in the Engineering\ConvertOctal class instead + * + * @param mixed $x The octal number you want to convert. Number may not contain * more than 10 octal characters (30 bits). The most significant * bit of number is the sign bit. The remaining 29 bits are * magnitude bits. Negative numbers are represented using @@ -1627,7 +537,7 @@ class Engineering * #NUM! error value. * If OCT2HEX requires more than places characters, it returns * the #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, OCT2HEX + * @param mixed $places The number of characters to use. If places is omitted, OCT2HEX * uses the minimum number of characters necessary. Places is useful * for padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. @@ -1638,19 +548,7 @@ class Engineering */ public static function OCTTOHEX($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - return Functions::VALUE(); - } - $x = (string) $x; - if (preg_match_all('/[01234567]/', $x, $out) != strlen($x)) { - return Functions::NAN(); - } - $hexVal = strtoupper(dechex(self::OCTTODEC($x))); - - return self::nbrConversionFormat($hexVal, $places); + return Engineering\ConvertOctal::toHex($x, $places); } /** @@ -1661,6 +559,10 @@ class Engineering * Excel Function: * COMPLEX(realNumber,imaginary[,suffix]) * + * @Deprecated 1.18.0 + * + * @see Use the COMPLEX() method in the Engineering\Complex class instead + * * @param float $realNumber the real coefficient of the complex number * @param float $imaginary the imaginary coefficient of the complex number * @param string $suffix The suffix for the imaginary component of the complex number. @@ -1670,20 +572,7 @@ class Engineering */ public static function COMPLEX($realNumber = 0.0, $imaginary = 0.0, $suffix = 'i') { - $realNumber = ($realNumber === null) ? 0.0 : Functions::flattenSingleValue($realNumber); - $imaginary = ($imaginary === null) ? 0.0 : Functions::flattenSingleValue($imaginary); - $suffix = ($suffix === null) ? 'i' : Functions::flattenSingleValue($suffix); - - if ( - ((is_numeric($realNumber)) && (is_numeric($imaginary))) && - (($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) - ) { - $complex = new Complex($realNumber, $imaginary, $suffix); - - return (string) $complex; - } - - return Functions::VALUE(); + return Engineering\Complex::COMPLEX($realNumber, $imaginary, $suffix); } /** @@ -1694,16 +583,18 @@ class Engineering * Excel Function: * IMAGINARY(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMAGINARY() method in the Engineering\Complex class instead + * * @param string $complexNumber the complex number for which you want the imaginary * coefficient * - * @return float + * @return float|string */ public static function IMAGINARY($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (new Complex($complexNumber))->getImaginary(); + return Engineering\Complex::IMAGINARY($complexNumber); } /** @@ -1714,15 +605,17 @@ class Engineering * Excel Function: * IMREAL(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMREAL() method in the Engineering\Complex class instead + * * @param string $complexNumber the complex number for which you want the real coefficient * - * @return float + * @return float|string */ public static function IMREAL($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (new Complex($complexNumber))->getReal(); + return Engineering\Complex::IMREAL($complexNumber); } /** @@ -1733,15 +626,17 @@ class Engineering * Excel Function: * IMABS(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMABS() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the absolute value * - * @return float + * @return float|string */ public static function IMABS($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (new Complex($complexNumber))->abs(); + return ComplexFunctions::IMABS($complexNumber); } /** @@ -1753,20 +648,17 @@ class Engineering * Excel Function: * IMARGUMENT(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMARGUMENT() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the argument theta * * @return float|string */ public static function IMARGUMENT($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - $complex = new Complex($complexNumber); - if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { - return Functions::DIV0(); - } - - return $complex->argument(); + return ComplexFunctions::IMARGUMENT($complexNumber); } /** @@ -1777,15 +669,17 @@ class Engineering * Excel Function: * IMCONJUGATE(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMARGUMENT() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the conjugate * * @return string */ public static function IMCONJUGATE($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->conjugate(); + return ComplexFunctions::IMCONJUGATE($complexNumber); } /** @@ -1796,15 +690,17 @@ class Engineering * Excel Function: * IMCOS(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMCOS() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the cosine * * @return float|string */ public static function IMCOS($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->cos(); + return ComplexFunctions::IMCOS($complexNumber); } /** @@ -1815,15 +711,17 @@ class Engineering * Excel Function: * IMCOSH(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMCOSH() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the hyperbolic cosine * * @return float|string */ public static function IMCOSH($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->cosh(); + return ComplexFunctions::IMCOSH($complexNumber); } /** @@ -1834,15 +732,17 @@ class Engineering * Excel Function: * IMCOT(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMCOT() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the cotangent * * @return float|string */ public static function IMCOT($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->cot(); + return ComplexFunctions::IMCOT($complexNumber); } /** @@ -1853,15 +753,17 @@ class Engineering * Excel Function: * IMCSC(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMCSC() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the cosecant * * @return float|string */ public static function IMCSC($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->csc(); + return ComplexFunctions::IMCSC($complexNumber); } /** @@ -1872,15 +774,17 @@ class Engineering * Excel Function: * IMCSCH(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMCSCH() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the hyperbolic cosecant * * @return float|string */ public static function IMCSCH($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->csch(); + return ComplexFunctions::IMCSCH($complexNumber); } /** @@ -1891,15 +795,17 @@ class Engineering * Excel Function: * IMSIN(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMSIN() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the sine * * @return float|string */ public static function IMSIN($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->sin(); + return ComplexFunctions::IMSIN($complexNumber); } /** @@ -1910,15 +816,17 @@ class Engineering * Excel Function: * IMSINH(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMSINH() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the hyperbolic sine * * @return float|string */ public static function IMSINH($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->sinh(); + return ComplexFunctions::IMSINH($complexNumber); } /** @@ -1929,15 +837,17 @@ class Engineering * Excel Function: * IMSEC(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMSEC() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the secant * * @return float|string */ public static function IMSEC($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->sec(); + return ComplexFunctions::IMSEC($complexNumber); } /** @@ -1948,15 +858,17 @@ class Engineering * Excel Function: * IMSECH(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMSECH() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the hyperbolic secant * * @return float|string */ public static function IMSECH($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->sech(); + return ComplexFunctions::IMSECH($complexNumber); } /** @@ -1967,15 +879,17 @@ class Engineering * Excel Function: * IMTAN(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMTAN() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the tangent * * @return float|string */ public static function IMTAN($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->tan(); + return ComplexFunctions::IMTAN($complexNumber); } /** @@ -1986,20 +900,17 @@ class Engineering * Excel Function: * IMSQRT(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMSQRT() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the square root * * @return string */ public static function IMSQRT($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - $theta = self::IMARGUMENT($complexNumber); - if ($theta === Functions::DIV0()) { - return '0'; - } - - return (string) (new Complex($complexNumber))->sqrt(); + return ComplexFunctions::IMSQRT($complexNumber); } /** @@ -2010,20 +921,17 @@ class Engineering * Excel Function: * IMLN(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMLN() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the natural logarithm * * @return string */ public static function IMLN($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - $complex = new Complex($complexNumber); - if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { - return Functions::NAN(); - } - - return (string) (new Complex($complexNumber))->ln(); + return ComplexFunctions::IMLN($complexNumber); } /** @@ -2034,20 +942,17 @@ class Engineering * Excel Function: * IMLOG10(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMLOG10() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the common logarithm * * @return string */ public static function IMLOG10($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - $complex = new Complex($complexNumber); - if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { - return Functions::NAN(); - } - - return (string) (new Complex($complexNumber))->log10(); + return ComplexFunctions::IMLOG10($complexNumber); } /** @@ -2058,20 +963,17 @@ class Engineering * Excel Function: * IMLOG2(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMLOG2() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the base-2 logarithm * * @return string */ public static function IMLOG2($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - $complex = new Complex($complexNumber); - if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { - return Functions::NAN(); - } - - return (string) (new Complex($complexNumber))->log2(); + return ComplexFunctions::IMLOG2($complexNumber); } /** @@ -2082,15 +984,17 @@ class Engineering * Excel Function: * IMEXP(complexNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMEXP() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number for which you want the exponential * * @return string */ public static function IMEXP($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->exp(); + return ComplexFunctions::IMEXP($complexNumber); } /** @@ -2101,6 +1005,10 @@ class Engineering * Excel Function: * IMPOWER(complexNumber,realNumber) * + * @Deprecated 1.18.0 + * + * @see Use the IMPOWER() method in the Engineering\ComplexFunctions class instead + * * @param string $complexNumber the complex number you want to raise to a power * @param float $realNumber the power to which you want to raise the complex number * @@ -2108,14 +1016,7 @@ class Engineering */ public static function IMPOWER($complexNumber, $realNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - $realNumber = Functions::flattenSingleValue($realNumber); - - if (!is_numeric($realNumber)) { - return Functions::VALUE(); - } - - return (string) (new Complex($complexNumber))->pow($realNumber); + return ComplexFunctions::IMPOWER($complexNumber, $realNumber); } /** @@ -2126,6 +1027,10 @@ class Engineering * Excel Function: * IMDIV(complexDividend,complexDivisor) * + * @Deprecated 1.18.0 + * + * @see Use the IMDIV() method in the Engineering\ComplexOperations class instead + * * @param string $complexDividend the complex numerator or dividend * @param string $complexDivisor the complex denominator or divisor * @@ -2133,14 +1038,7 @@ class Engineering */ public static function IMDIV($complexDividend, $complexDivisor) { - $complexDividend = Functions::flattenSingleValue($complexDividend); - $complexDivisor = Functions::flattenSingleValue($complexDivisor); - - try { - return (string) (new Complex($complexDividend))->divideby(new Complex($complexDivisor)); - } catch (ComplexException $e) { - return Functions::NAN(); - } + return ComplexOperations::IMDIV($complexDividend, $complexDivisor); } /** @@ -2151,6 +1049,10 @@ class Engineering * Excel Function: * IMSUB(complexNumber1,complexNumber2) * + * @Deprecated 1.18.0 + * + * @see Use the IMSUB() method in the Engineering\ComplexOperations class instead + * * @param string $complexNumber1 the complex number from which to subtract complexNumber2 * @param string $complexNumber2 the complex number to subtract from complexNumber1 * @@ -2158,14 +1060,7 @@ class Engineering */ public static function IMSUB($complexNumber1, $complexNumber2) { - $complexNumber1 = Functions::flattenSingleValue($complexNumber1); - $complexNumber2 = Functions::flattenSingleValue($complexNumber2); - - try { - return (string) (new Complex($complexNumber1))->subtract(new Complex($complexNumber2)); - } catch (ComplexException $e) { - return Functions::NAN(); - } + return ComplexOperations::IMSUB($complexNumber1, $complexNumber2); } /** @@ -2176,26 +1071,17 @@ class Engineering * Excel Function: * IMSUM(complexNumber[,complexNumber[,...]]) * + * @Deprecated 1.18.0 + * + * @see Use the IMSUM() method in the Engineering\ComplexOperations class instead + * * @param string ...$complexNumbers Series of complex numbers to add * * @return string */ public static function IMSUM(...$complexNumbers) { - // Return value - $returnValue = new Complex(0.0); - $aArgs = Functions::flattenArray($complexNumbers); - - try { - // Loop through the arguments - foreach ($aArgs as $complex) { - $returnValue = $returnValue->add(new Complex($complex)); - } - } catch (ComplexException $e) { - return Functions::NAN(); - } - - return (string) $returnValue; + return ComplexOperations::IMSUM(...$complexNumbers); } /** @@ -2206,50 +1092,42 @@ class Engineering * Excel Function: * IMPRODUCT(complexNumber[,complexNumber[,...]]) * + * @Deprecated 1.18.0 + * + * @see Use the IMPRODUCT() method in the Engineering\ComplexOperations class instead + * * @param string ...$complexNumbers Series of complex numbers to multiply * * @return string */ public static function IMPRODUCT(...$complexNumbers) { - // Return value - $returnValue = new Complex(1.0); - $aArgs = Functions::flattenArray($complexNumbers); - - try { - // Loop through the arguments - foreach ($aArgs as $complex) { - $returnValue = $returnValue->multiply(new Complex($complex)); - } - } catch (ComplexException $e) { - return Functions::NAN(); - } - - return (string) $returnValue; + return ComplexOperations::IMPRODUCT(...$complexNumbers); } /** * DELTA. * * Tests whether two values are equal. Returns 1 if number1 = number2; returns 0 otherwise. - * Use this function to filter a set of values. For example, by summing several DELTA - * functions you calculate the count of equal pairs. This function is also known as the - * Kronecker Delta function. + * Use this function to filter a set of values. For example, by summing several DELTA + * functions you calculate the count of equal pairs. This function is also known as the + * Kronecker Delta function. * * Excel Function: * DELTA(a[,b]) * + * @Deprecated 1.17.0 + * + * @see Use the DELTA() method in the Engineering\Compare class instead + * * @param float $a the first number * @param float $b The second number. If omitted, b is assumed to be zero. * - * @return int + * @return int|string (string in the event of an error) */ public static function DELTA($a, $b = 0) { - $a = Functions::flattenSingleValue($a); - $b = Functions::flattenSingleValue($b); - - return (int) ($a == $b); + return Engineering\Compare::DELTA($a, $b); } /** @@ -2260,77 +1138,20 @@ class Engineering * * Returns 1 if number >= step; returns 0 (zero) otherwise * Use this function to filter a set of values. For example, by summing several GESTEP - * functions you calculate the count of values that exceed a threshold. + * functions you calculate the count of values that exceed a threshold. + * + * @Deprecated 1.17.0 + * + * @see Use the GESTEP() method in the Engineering\Compare class instead * * @param float $number the value to test against step - * @param float $step The threshold value. - * If you omit a value for step, GESTEP uses zero. + * @param float $step The threshold value. If you omit a value for step, GESTEP uses zero. * - * @return int + * @return int|string (string in the event of an error) */ public static function GESTEP($number, $step = 0) { - $number = Functions::flattenSingleValue($number); - $step = Functions::flattenSingleValue($step); - - return (int) ($number >= $step); - } - - // - // Private method to calculate the erf value - // - private static $twoSqrtPi = 1.128379167095512574; - - public static function erfVal($x) - { - if (abs($x) > 2.2) { - return 1 - self::erfcVal($x); - } - $sum = $term = $x; - $xsqr = ($x * $x); - $j = 1; - do { - $term *= $xsqr / $j; - $sum -= $term / (2 * $j + 1); - ++$j; - $term *= $xsqr / $j; - $sum += $term / (2 * $j + 1); - ++$j; - if ($sum == 0.0) { - break; - } - } while (abs($term / $sum) > Functions::PRECISION); - - return self::$twoSqrtPi * $sum; - } - - /** - * Validate arguments passed to the bitwise functions. - * - * @param mixed $value - * - * @return int - */ - private static function validateBitwiseArgument($value) - { - $value = Functions::flattenSingleValue($value); - - if (is_int($value)) { - return $value; - } elseif (is_numeric($value)) { - if ($value == (int) ($value)) { - $value = (int) ($value); - if (($value > 2 ** 48 - 1) || ($value < 0)) { - throw new Exception(Functions::NAN()); - } - - return $value; - } - - throw new Exception(Functions::NAN()); - } - - throw new Exception(Functions::VALUE()); + return Engineering\Compare::GESTEP($number, $step); } /** @@ -2341,6 +1162,10 @@ class Engineering * Excel Function: * BITAND(number1, number2) * + * @Deprecated 1.17.0 + * + * @see Use the BITAND() method in the Engineering\BitWise class instead + * * @param int $number1 * @param int $number2 * @@ -2348,14 +1173,7 @@ class Engineering */ public static function BITAND($number1, $number2) { - try { - $number1 = self::validateBitwiseArgument($number1); - $number2 = self::validateBitwiseArgument($number2); - } catch (Exception $e) { - return $e->getMessage(); - } - - return $number1 & $number2; + return Engineering\BitWise::BITAND($number1, $number2); } /** @@ -2366,6 +1184,10 @@ class Engineering * Excel Function: * BITOR(number1, number2) * + * @Deprecated 1.17.0 + * + * @see Use the BITOR() method in the Engineering\BitWise class instead + * * @param int $number1 * @param int $number2 * @@ -2373,14 +1195,7 @@ class Engineering */ public static function BITOR($number1, $number2) { - try { - $number1 = self::validateBitwiseArgument($number1); - $number2 = self::validateBitwiseArgument($number2); - } catch (Exception $e) { - return $e->getMessage(); - } - - return $number1 | $number2; + return Engineering\BitWise::BITOR($number1, $number2); } /** @@ -2391,6 +1206,10 @@ class Engineering * Excel Function: * BITXOR(number1, number2) * + * @Deprecated 1.17.0 + * + * @see Use the BITXOR() method in the Engineering\BitWise class instead + * * @param int $number1 * @param int $number2 * @@ -2398,14 +1217,7 @@ class Engineering */ public static function BITXOR($number1, $number2) { - try { - $number1 = self::validateBitwiseArgument($number1); - $number2 = self::validateBitwiseArgument($number2); - } catch (Exception $e) { - return $e->getMessage(); - } - - return $number1 ^ $number2; + return Engineering\BitWise::BITXOR($number1, $number2); } /** @@ -2416,6 +1228,10 @@ class Engineering * Excel Function: * BITLSHIFT(number, shift_amount) * + * @Deprecated 1.17.0 + * + * @see Use the BITLSHIFT() method in the Engineering\BitWise class instead + * * @param int $number * @param int $shiftAmount * @@ -2423,20 +1239,7 @@ class Engineering */ public static function BITLSHIFT($number, $shiftAmount) { - try { - $number = self::validateBitwiseArgument($number); - } catch (Exception $e) { - return $e->getMessage(); - } - - $shiftAmount = Functions::flattenSingleValue($shiftAmount); - - $result = $number << $shiftAmount; - if ($result > 2 ** 48 - 1) { - return Functions::NAN(); - } - - return $result; + return Engineering\BitWise::BITLSHIFT($number, $shiftAmount); } /** @@ -2447,6 +1250,10 @@ class Engineering * Excel Function: * BITRSHIFT(number, shift_amount) * + * @Deprecated 1.17.0 + * + * @see Use the BITRSHIFT() method in the Engineering\BitWise class instead + * * @param int $number * @param int $shiftAmount * @@ -2454,15 +1261,7 @@ class Engineering */ public static function BITRSHIFT($number, $shiftAmount) { - try { - $number = self::validateBitwiseArgument($number); - } catch (Exception $e) { - return $e->getMessage(); - } - - $shiftAmount = Functions::flattenSingleValue($shiftAmount); - - return $number >> $shiftAmount; + return Engineering\BitWise::BITRSHIFT($number, $shiftAmount); } /** @@ -2478,6 +1277,10 @@ class Engineering * Excel Function: * ERF(lower[,upper]) * + * @Deprecated 1.17.0 + * + * @see Use the ERF() method in the Engineering\Erf class instead + * * @param float $lower lower bound for integrating ERF * @param float $upper upper bound for integrating ERF. * If omitted, ERF integrates between zero and lower_limit @@ -2486,19 +1289,7 @@ class Engineering */ public static function ERF($lower, $upper = null) { - $lower = Functions::flattenSingleValue($lower); - $upper = Functions::flattenSingleValue($upper); - - if (is_numeric($lower)) { - if ($upper === null) { - return self::erfVal($lower); - } - if (is_numeric($upper)) { - return self::erfVal($upper) - self::erfVal($lower); - } - } - - return Functions::VALUE(); + return Engineering\Erf::ERF($lower, $upper); } /** @@ -2509,48 +1300,17 @@ class Engineering * Excel Function: * ERF.PRECISE(limit) * + * @Deprecated 1.17.0 + * + * @see Use the ERFPRECISE() method in the Engineering\Erf class instead + * * @param float $limit bound for integrating ERF * * @return float|string */ public static function ERFPRECISE($limit) { - $limit = Functions::flattenSingleValue($limit); - - return self::ERF($limit); - } - - // - // Private method to calculate the erfc value - // - private static $oneSqrtPi = 0.564189583547756287; - - private static function erfcVal($x) - { - if (abs($x) < 2.2) { - return 1 - self::erfVal($x); - } - if ($x < 0) { - return 2 - self::ERFC(-$x); - } - $a = $n = 1; - $b = $c = $x; - $d = ($x * $x) + 0.5; - $q1 = $q2 = $b / $d; - $t = 0; - do { - $t = $a * $n + $b * $x; - $a = $b; - $b = $t; - $t = $c * $n + $d * $x; - $c = $d; - $d = $t; - $n += 0.5; - $q1 = $q2; - $q2 = $b / $d; - } while ((abs($q1 - $q2) / $q2) > Functions::PRECISION); - - return self::$oneSqrtPi * exp(-$x * $x) * $q2; + return Engineering\Erf::ERFPRECISE($limit); } /** @@ -2566,88 +1326,97 @@ class Engineering * Excel Function: * ERFC(x) * + * @Deprecated 1.17.0 + * + * @see Use the ERFC() method in the Engineering\ErfC class instead + * * @param float $x The lower bound for integrating ERFC * * @return float|string */ public static function ERFC($x) { - $x = Functions::flattenSingleValue($x); - - if (is_numeric($x)) { - return self::erfcVal($x); - } - - return Functions::VALUE(); + return Engineering\ErfC::ERFC($x); } /** * getConversionGroups * Returns a list of the different conversion groups for UOM conversions. * + * @Deprecated 1.16.0 + * + * @see Use the getConversionCategories() method in the Engineering\ConvertUOM class instead + * * @return array */ public static function getConversionGroups() { - $conversionGroups = []; - foreach (self::$conversionUnits as $conversionUnit) { - $conversionGroups[] = $conversionUnit['Group']; - } - - return array_merge(array_unique($conversionGroups)); + return Engineering\ConvertUOM::getConversionCategories(); } /** * getConversionGroupUnits * Returns an array of units of measure, for a specified conversion group, or for all groups. * - * @param string $group The group whose units of measure you want to retrieve + * @Deprecated 1.16.0 + * + * @see Use the getConversionCategoryUnits() method in the ConvertUOM class instead + * + * @param null|mixed $category * * @return array */ - public static function getConversionGroupUnits($group = null) + public static function getConversionGroupUnits($category = null) { - $conversionGroups = []; - foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { - if (($group === null) || ($conversionGroup['Group'] == $group)) { - $conversionGroups[$conversionGroup['Group']][] = $conversionUnit; - } - } - - return $conversionGroups; + return Engineering\ConvertUOM::getConversionCategoryUnits($category); } /** * getConversionGroupUnitDetails. * - * @param string $group The group whose units of measure you want to retrieve + * @Deprecated 1.16.0 + * + * @see Use the getConversionCategoryUnitDetails() method in the ConvertUOM class instead + * + * @param null|mixed $category * * @return array */ - public static function getConversionGroupUnitDetails($group = null) + public static function getConversionGroupUnitDetails($category = null) { - $conversionGroups = []; - foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { - if (($group === null) || ($conversionGroup['Group'] == $group)) { - $conversionGroups[$conversionGroup['Group']][] = [ - 'unit' => $conversionUnit, - 'description' => $conversionGroup['Unit Name'], - ]; - } - } - - return $conversionGroups; + return Engineering\ConvertUOM::getConversionCategoryUnitDetails($category); } /** * getConversionMultipliers * Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). * - * @return array of mixed + * @Deprecated 1.16.0 + * + * @see Use the getConversionMultipliers() method in the ConvertUOM class instead + * + * @return mixed[] */ public static function getConversionMultipliers() { - return self::$conversionMultipliers; + return Engineering\ConvertUOM::getConversionMultipliers(); + } + + /** + * getBinaryConversionMultipliers. + * + * Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure + * in CONVERTUOM(). + * + * @Deprecated 1.16.0 + * + * @see Use the getBinaryConversionMultipliers() method in the ConvertUOM class instead + * + * @return mixed[] + */ + public static function getBinaryConversionMultipliers() + { + return Engineering\ConvertUOM::getBinaryConversionMultipliers(); } /** @@ -2660,7 +1429,11 @@ class Engineering * Excel Function: * CONVERT(value,fromUOM,toUOM) * - * @param float $value the value in fromUOM to convert + * @Deprecated 1.16.0 + * + * @see Use the CONVERT() method in the ConvertUOM class instead + * + * @param float|int $value the value in fromUOM to convert * @param string $fromUOM the units for value * @param string $toUOM the units for the result * @@ -2668,93 +1441,6 @@ class Engineering */ public static function CONVERTUOM($value, $fromUOM, $toUOM) { - $value = Functions::flattenSingleValue($value); - $fromUOM = Functions::flattenSingleValue($fromUOM); - $toUOM = Functions::flattenSingleValue($toUOM); - - if (!is_numeric($value)) { - return Functions::VALUE(); - } - $fromMultiplier = 1.0; - if (isset(self::$conversionUnits[$fromUOM])) { - $unitGroup1 = self::$conversionUnits[$fromUOM]['Group']; - } else { - $fromMultiplier = substr($fromUOM, 0, 1); - $fromUOM = substr($fromUOM, 1); - if (isset(self::$conversionMultipliers[$fromMultiplier])) { - $fromMultiplier = self::$conversionMultipliers[$fromMultiplier]['multiplier']; - } else { - return Functions::NA(); - } - if ((isset(self::$conversionUnits[$fromUOM])) && (self::$conversionUnits[$fromUOM]['AllowPrefix'])) { - $unitGroup1 = self::$conversionUnits[$fromUOM]['Group']; - } else { - return Functions::NA(); - } - } - $value *= $fromMultiplier; - - $toMultiplier = 1.0; - if (isset(self::$conversionUnits[$toUOM])) { - $unitGroup2 = self::$conversionUnits[$toUOM]['Group']; - } else { - $toMultiplier = substr($toUOM, 0, 1); - $toUOM = substr($toUOM, 1); - if (isset(self::$conversionMultipliers[$toMultiplier])) { - $toMultiplier = self::$conversionMultipliers[$toMultiplier]['multiplier']; - } else { - return Functions::NA(); - } - if ((isset(self::$conversionUnits[$toUOM])) && (self::$conversionUnits[$toUOM]['AllowPrefix'])) { - $unitGroup2 = self::$conversionUnits[$toUOM]['Group']; - } else { - return Functions::NA(); - } - } - if ($unitGroup1 != $unitGroup2) { - return Functions::NA(); - } - - if (($fromUOM == $toUOM) && ($fromMultiplier == $toMultiplier)) { - // We've already factored $fromMultiplier into the value, so we need - // to reverse it again - return $value / $fromMultiplier; - } elseif ($unitGroup1 == 'Temperature') { - if (($fromUOM == 'F') || ($fromUOM == 'fah')) { - if (($toUOM == 'F') || ($toUOM == 'fah')) { - return $value; - } - $value = (($value - 32) / 1.8); - if (($toUOM == 'K') || ($toUOM == 'kel')) { - $value += 273.15; - } - - return $value; - } elseif ( - (($fromUOM == 'K') || ($fromUOM == 'kel')) && - (($toUOM == 'K') || ($toUOM == 'kel')) - ) { - return $value; - } elseif ( - (($fromUOM == 'C') || ($fromUOM == 'cel')) && - (($toUOM == 'C') || ($toUOM == 'cel')) - ) { - return $value; - } - if (($toUOM == 'F') || ($toUOM == 'fah')) { - if (($fromUOM == 'K') || ($fromUOM == 'kel')) { - $value -= 273.15; - } - - return ($value * 1.8) + 32; - } - if (($toUOM == 'C') || ($toUOM == 'cel')) { - return $value - 273.15; - } - - return $value + 273.15; - } - - return ($value * self::$unitConversions[$unitGroup1][$fromUOM][$toUOM]) / $toMultiplier; + return Engineering\ConvertUOM::CONVERT($value, $fromUOM, $toUOM); } } diff --git a/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php b/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php new file mode 100644 index 00000000..ea2577cd --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php @@ -0,0 +1,137 @@ +getMessage(); + } + + if ($ord < 0) { + return Functions::NAN(); + } + + $fResult = self::calculate($x, $ord); + + return (is_nan($fResult)) ? Functions::NAN() : $fResult; + } + + private static function calculate(float $x, int $ord): float + { + // special cases + switch ($ord) { + case 0: + return self::besselI0($x); + case 1: + return self::besselI1($x); + } + + return self::besselI2($x, $ord); + } + + private static function besselI0(float $x): float + { + $ax = abs($x); + + if ($ax < 3.75) { + $y = $x / 3.75; + $y = $y * $y; + + return 1.0 + $y * (3.5156229 + $y * (3.0899424 + $y * (1.2067492 + + $y * (0.2659732 + $y * (0.360768e-1 + $y * 0.45813e-2))))); + } + + $y = 3.75 / $ax; + + return (exp($ax) / sqrt($ax)) * (0.39894228 + $y * (0.1328592e-1 + $y * (0.225319e-2 + $y * (-0.157565e-2 + + $y * (0.916281e-2 + $y * (-0.2057706e-1 + $y * (0.2635537e-1 + + $y * (-0.1647633e-1 + $y * 0.392377e-2)))))))); + } + + private static function besselI1(float $x): float + { + $ax = abs($x); + + if ($ax < 3.75) { + $y = $x / 3.75; + $y = $y * $y; + $ans = $ax * (0.5 + $y * (0.87890594 + $y * (0.51498869 + $y * (0.15084934 + $y * (0.2658733e-1 + + $y * (0.301532e-2 + $y * 0.32411e-3)))))); + + return ($x < 0.0) ? -$ans : $ans; + } + + $y = 3.75 / $ax; + $ans = 0.2282967e-1 + $y * (-0.2895312e-1 + $y * (0.1787654e-1 - $y * 0.420059e-2)); + $ans = 0.39894228 + $y * (-0.3988024e-1 + $y * (-0.362018e-2 + $y * (0.163801e-2 + + $y * (-0.1031555e-1 + $y * $ans)))); + $ans *= exp($ax) / sqrt($ax); + + return ($x < 0.0) ? -$ans : $ans; + } + + private static function besselI2(float $x, int $ord): float + { + if ($x === 0.0) { + return 0.0; + } + + $tox = 2.0 / abs($x); + $bip = 0; + $ans = 0.0; + $bi = 1.0; + + for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) { + $bim = $bip + $j * $tox * $bi; + $bip = $bi; + $bi = $bim; + + if (abs($bi) > 1.0e+12) { + $ans *= 1.0e-12; + $bi *= 1.0e-12; + $bip *= 1.0e-12; + } + + if ($j === $ord) { + $ans = $bip; + } + } + + $ans *= self::besselI0($x) / $bi; + + return ($x < 0.0 && (($ord % 2) === 1)) ? -$ans : $ans; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php b/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php new file mode 100644 index 00000000..7ea45a74 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php @@ -0,0 +1,172 @@ + 8. This code provides a more accurate calculation + * + * @param mixed $x A float value at which to evaluate the function. + * If x is nonnumeric, BESSELJ returns the #VALUE! error value. + * @param mixed $ord The integer order of the Bessel function. + * If ord is not an integer, it is truncated. + * If $ord is nonnumeric, BESSELJ returns the #VALUE! error value. + * If $ord < 0, BESSELJ returns the #NUM! error value. + * + * @return float|string Result, or a string containing an error + */ + public static function BESSELJ($x, $ord) + { + $x = Functions::flattenSingleValue($x); + $ord = Functions::flattenSingleValue($ord); + + try { + $x = EngineeringValidations::validateFloat($x); + $ord = EngineeringValidations::validateInt($ord); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($ord < 0) { + return Functions::NAN(); + } + + $fResult = self::calculate($x, $ord); + + return (is_nan($fResult)) ? Functions::NAN() : $fResult; + } + + private static function calculate(float $x, int $ord): float + { + // special cases + switch ($ord) { + case 0: + return self::besselJ0($x); + case 1: + return self::besselJ1($x); + } + + return self::besselJ2($x, $ord); + } + + private static function besselJ0(float $x): float + { + $ax = abs($x); + + if ($ax < 8.0) { + $y = $x * $x; + $ans1 = 57568490574.0 + $y * (-13362590354.0 + $y * (651619640.7 + $y * (-11214424.18 + $y * + (77392.33017 + $y * (-184.9052456))))); + $ans2 = 57568490411.0 + $y * (1029532985.0 + $y * (9494680.718 + $y * (59272.64853 + $y * + (267.8532712 + $y * 1.0)))); + + return $ans1 / $ans2; + } + + $z = 8.0 / $ax; + $y = $z * $z; + $xx = $ax - 0.785398164; + $ans1 = 1.0 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6))); + $ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * + (0.7621095161e-6 - $y * 0.934935152e-7))); + + return sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2); + } + + private static function besselJ1(float $x): float + { + $ax = abs($x); + + if ($ax < 8.0) { + $y = $x * $x; + $ans1 = $x * (72362614232.0 + $y * (-7895059235.0 + $y * (242396853.1 + $y * + (-2972611.439 + $y * (15704.48260 + $y * (-30.16036606)))))); + $ans2 = 144725228442.0 + $y * (2300535178.0 + $y * (18583304.74 + $y * (99447.43394 + $y * + (376.9991397 + $y * 1.0)))); + + return $ans1 / $ans2; + } + + $z = 8.0 / $ax; + $y = $z * $z; + $xx = $ax - 2.356194491; + + $ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6)))); + $ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y * + (-0.88228987e-6 + $y * 0.105787412e-6))); + $ans = sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2); + + return ($x < 0.0) ? -$ans : $ans; + } + + private static function besselJ2(float $x, int $ord): float + { + $ax = abs($x); + if ($ax === 0.0) { + return 0.0; + } + + if ($ax > $ord) { + return self::besselj2a($ax, $ord, $x); + } + + return self::besselj2b($ax, $ord, $x); + } + + private static function besselj2a(float $ax, int $ord, float $x) + { + $tox = 2.0 / $ax; + $bjm = self::besselJ0($ax); + $bj = self::besselJ1($ax); + for ($j = 1; $j < $ord; ++$j) { + $bjp = $j * $tox * $bj - $bjm; + $bjm = $bj; + $bj = $bjp; + } + $ans = $bj; + + return ($x < 0.0 && ($ord % 2) == 1) ? -$ans : $ans; + } + + private static function besselj2b(float $ax, int $ord, float $x) + { + $tox = 2.0 / $ax; + $jsum = false; + $bjp = $ans = $sum = 0.0; + $bj = 1.0; + for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) { + $bjm = $j * $tox * $bj - $bjp; + $bjp = $bj; + $bj = $bjm; + if (abs($bj) > 1.0e+10) { + $bj *= 1.0e-10; + $bjp *= 1.0e-10; + $ans *= 1.0e-10; + $sum *= 1.0e-10; + } + if ($jsum === true) { + $sum += $bj; + } + $jsum = !$jsum; + if ($j === $ord) { + $ans = $bjp; + } + } + $sum = 2.0 * $sum - $bj; + $ans /= $sum; + + return ($x < 0.0 && ($ord % 2) === 1) ? -$ans : $ans; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php b/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php new file mode 100644 index 00000000..8facdffc --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php @@ -0,0 +1,111 @@ +getMessage(); + } + + if (($ord < 0) || ($x <= 0.0)) { + return Functions::NAN(); + } + + $fBk = self::calculate($x, $ord); + + return (is_nan($fBk)) ? Functions::NAN() : $fBk; + } + + private static function calculate(float $x, int $ord): float + { + // special cases + switch ($ord) { + case 0: + return self::besselK0($x); + case 1: + return self::besselK1($x); + } + + return self::besselK2($x, $ord); + } + + private static function besselK0(float $x): float + { + if ($x <= 2) { + $fNum2 = $x * 0.5; + $y = ($fNum2 * $fNum2); + + return -log($fNum2) * BesselI::BESSELI($x, 0) + + (-0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y * + (0.10750e-3 + $y * 0.74e-5)))))); + } + + $y = 2 / $x; + + return exp(-$x) / sqrt($x) * + (1.25331414 + $y * (-0.7832358e-1 + $y * (0.2189568e-1 + $y * (-0.1062446e-1 + $y * + (0.587872e-2 + $y * (-0.251540e-2 + $y * 0.53208e-3)))))); + } + + private static function besselK1(float $x): float + { + if ($x <= 2) { + $fNum2 = $x * 0.5; + $y = ($fNum2 * $fNum2); + + return log($fNum2) * BesselI::BESSELI($x, 1) + + (1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y * + (-0.110404e-2 + $y * (-0.4686e-4))))))) / $x; + } + + $y = 2 / $x; + + return exp(-$x) / sqrt($x) * + (1.25331414 + $y * (0.23498619 + $y * (-0.3655620e-1 + $y * (0.1504268e-1 + $y * (-0.780353e-2 + $y * + (0.325614e-2 + $y * (-0.68245e-3))))))); + } + + private static function besselK2(float $x, int $ord) + { + $fTox = 2 / $x; + $fBkm = self::besselK0($x); + $fBk = self::besselK1($x); + for ($n = 1; $n < $ord; ++$n) { + $fBkp = $fBkm + $n * $fTox * $fBk; + $fBkm = $fBk; + $fBk = $fBkp; + } + + return $fBk; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php b/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php new file mode 100644 index 00000000..7f387497 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php @@ -0,0 +1,118 @@ +getMessage(); + } + + if (($ord < 0) || ($x <= 0.0)) { + return Functions::NAN(); + } + + $fBy = self::calculate($x, $ord); + + return (is_nan($fBy)) ? Functions::NAN() : $fBy; + } + + private static function calculate(float $x, int $ord): float + { + // special cases + switch ($ord) { + case 0: + return self::besselY0($x); + case 1: + return self::besselY1($x); + } + + return self::besselY2($x, $ord); + } + + private static function besselY0(float $x): float + { + if ($x < 8.0) { + $y = ($x * $x); + $ans1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y * + (-86327.92757 + $y * 228.4622733)))); + $ans2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y * + (47447.26470 + $y * (226.1030244 + $y)))); + + return $ans1 / $ans2 + 0.636619772 * BesselJ::BESSELJ($x, 0) * log($x); + } + + $z = 8.0 / $x; + $y = ($z * $z); + $xx = $x - 0.785398164; + $ans1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6))); + $ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y * + (-0.934945152e-7)))); + + return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2); + } + + private static function besselY1(float $x): float + { + if ($x < 8.0) { + $y = ($x * $x); + $ans1 = $x * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y * + (0.7349264551e9 + $y * (-0.4237922726e7 + $y * 0.8511937935e4))))); + $ans2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y * + (0.1020426050e6 + $y * (0.3549632885e3 + $y))))); + + return ($ans1 / $ans2) + 0.636619772 * (BesselJ::BESSELJ($x, 1) * log($x) - 1 / $x); + } + + $z = 8.0 / $x; + $y = $z * $z; + $xx = $x - 2.356194491; + $ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6)))); + $ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y * + (-0.88228987e-6 + $y * 0.105787412e-6))); + + return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2); + } + + private static function besselY2(float $x, int $ord): float + { + $fTox = 2.0 / $x; + $fBym = self::besselY0($x); + $fBy = self::besselY1($x); + for ($n = 1; $n < $ord; ++$n) { + $fByp = $n * $fTox * $fBy - $fBym; + $fBym = $fBy; + $fBy = $fByp; + } + + return $fBy; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php b/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php new file mode 100644 index 00000000..9958f054 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php @@ -0,0 +1,227 @@ +getMessage(); + } + $split1 = self::splitNumber($number1); + $split2 = self::splitNumber($number2); + + return self::SPLIT_DIVISOR * ($split1[0] & $split2[0]) + ($split1[1] & $split2[1]); + } + + /** + * BITOR. + * + * Returns the bitwise OR of two integer values. + * + * Excel Function: + * BITOR(number1, number2) + * + * @param int $number1 + * @param int $number2 + * + * @return int|string + */ + public static function BITOR($number1, $number2) + { + try { + $number1 = self::validateBitwiseArgument($number1); + $number2 = self::validateBitwiseArgument($number2); + } catch (Exception $e) { + return $e->getMessage(); + } + + $split1 = self::splitNumber($number1); + $split2 = self::splitNumber($number2); + + return self::SPLIT_DIVISOR * ($split1[0] | $split2[0]) + ($split1[1] | $split2[1]); + } + + /** + * BITXOR. + * + * Returns the bitwise XOR of two integer values. + * + * Excel Function: + * BITXOR(number1, number2) + * + * @param int $number1 + * @param int $number2 + * + * @return int|string + */ + public static function BITXOR($number1, $number2) + { + try { + $number1 = self::validateBitwiseArgument($number1); + $number2 = self::validateBitwiseArgument($number2); + } catch (Exception $e) { + return $e->getMessage(); + } + + $split1 = self::splitNumber($number1); + $split2 = self::splitNumber($number2); + + return self::SPLIT_DIVISOR * ($split1[0] ^ $split2[0]) + ($split1[1] ^ $split2[1]); + } + + /** + * BITLSHIFT. + * + * Returns the number value shifted left by shift_amount bits. + * + * Excel Function: + * BITLSHIFT(number, shift_amount) + * + * @param int $number + * @param int $shiftAmount + * + * @return float|int|string + */ + public static function BITLSHIFT($number, $shiftAmount) + { + try { + $number = self::validateBitwiseArgument($number); + $shiftAmount = self::validateShiftAmount($shiftAmount); + } catch (Exception $e) { + return $e->getMessage(); + } + + $result = floor($number * (2 ** $shiftAmount)); + if ($result > 2 ** 48 - 1) { + return Functions::NAN(); + } + + return $result; + } + + /** + * BITRSHIFT. + * + * Returns the number value shifted right by shift_amount bits. + * + * Excel Function: + * BITRSHIFT(number, shift_amount) + * + * @param int $number + * @param int $shiftAmount + * + * @return float|int|string + */ + public static function BITRSHIFT($number, $shiftAmount) + { + try { + $number = self::validateBitwiseArgument($number); + $shiftAmount = self::validateShiftAmount($shiftAmount); + } catch (Exception $e) { + return $e->getMessage(); + } + + $result = floor($number / (2 ** $shiftAmount)); + if ($result > 2 ** 48 - 1) { // possible because shiftAmount can be negative + return Functions::NAN(); + } + + return $result; + } + + /** + * Validate arguments passed to the bitwise functions. + * + * @param mixed $value + * + * @return float|int + */ + private static function validateBitwiseArgument($value) + { + self::nullFalseTrueToNumber($value); + + if (is_numeric($value)) { + if ($value == floor($value)) { + if (($value > 2 ** 48 - 1) || ($value < 0)) { + throw new Exception(Functions::NAN()); + } + + return floor($value); + } + + throw new Exception(Functions::NAN()); + } + + throw new Exception(Functions::VALUE()); + } + + /** + * Validate arguments passed to the bitwise functions. + * + * @param mixed $value + * + * @return int + */ + private static function validateShiftAmount($value) + { + self::nullFalseTrueToNumber($value); + + if (is_numeric($value)) { + if (abs($value) > 53) { + throw new Exception(Functions::NAN()); + } + + return (int) $value; + } + + throw new Exception(Functions::VALUE()); + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @param mixed $number + */ + public static function nullFalseTrueToNumber(&$number): void + { + $number = Functions::flattenSingleValue($number); + if ($number === null) { + $number = 0; + } elseif (is_bool($number)) { + $number = (int) $number; + } + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/Compare.php b/src/PhpSpreadsheet/Calculation/Engineering/Compare.php new file mode 100644 index 00000000..0a634206 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/Compare.php @@ -0,0 +1,70 @@ +getMessage(); + } + + return (int) ($a == $b); + } + + /** + * GESTEP. + * + * Excel Function: + * GESTEP(number[,step]) + * + * Returns 1 if number >= step; returns 0 (zero) otherwise + * Use this function to filter a set of values. For example, by summing several GESTEP + * functions you calculate the count of values that exceed a threshold. + * + * @param float $number the value to test against step + * @param float $step The threshold value. If you omit a value for step, GESTEP uses zero. + * + * @return int|string (string in the event of an error) + */ + public static function GESTEP($number, $step = 0) + { + $number = Functions::flattenSingleValue($number); + $step = Functions::flattenSingleValue($step); + + try { + $number = EngineeringValidations::validateFloat($number); + $step = EngineeringValidations::validateFloat($step); + } catch (Exception $e) { + return $e->getMessage(); + } + + return (int) ($number >= $step); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/Complex.php b/src/PhpSpreadsheet/Calculation/Engineering/Complex.php new file mode 100644 index 00000000..1c2f5f77 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/Complex.php @@ -0,0 +1,99 @@ +getMessage(); + } + + if (($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) { + $complex = new ComplexObject($realNumber, $imaginary, $suffix); + + return (string) $complex; + } + + return Functions::VALUE(); + } + + /** + * IMAGINARY. + * + * Returns the imaginary coefficient of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMAGINARY(complexNumber) + * + * @param string $complexNumber the complex number for which you want the imaginary + * coefficient + * + * @return float|string + */ + public static function IMAGINARY($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return $complex->getImaginary(); + } + + /** + * IMREAL. + * + * Returns the real coefficient of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMREAL(complexNumber) + * + * @param string $complexNumber the complex number for which you want the real coefficient + * + * @return float|string + */ + public static function IMREAL($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return $complex->getReal(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php b/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php new file mode 100644 index 00000000..3f37f373 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php @@ -0,0 +1,513 @@ +abs(); + } + + /** + * IMARGUMENT. + * + * Returns the argument theta of a complex number, i.e. the angle in radians from the real + * axis to the representation of the number in polar coordinates. + * + * Excel Function: + * IMARGUMENT(complexNumber) + * + * @param string $complexNumber the complex number for which you want the argument theta + * + * @return float|string + */ + public static function IMARGUMENT($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return Functions::DIV0(); + } + + return $complex->argument(); + } + + /** + * IMCONJUGATE. + * + * Returns the complex conjugate of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCONJUGATE(complexNumber) + * + * @param string $complexNumber the complex number for which you want the conjugate + * + * @return string + */ + public static function IMCONJUGATE($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->conjugate(); + } + + /** + * IMCOS. + * + * Returns the cosine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOS(complexNumber) + * + * @param string $complexNumber the complex number for which you want the cosine + * + * @return float|string + */ + public static function IMCOS($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->cos(); + } + + /** + * IMCOSH. + * + * Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOSH(complexNumber) + * + * @param string $complexNumber the complex number for which you want the hyperbolic cosine + * + * @return float|string + */ + public static function IMCOSH($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->cosh(); + } + + /** + * IMCOT. + * + * Returns the cotangent of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOT(complexNumber) + * + * @param string $complexNumber the complex number for which you want the cotangent + * + * @return float|string + */ + public static function IMCOT($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->cot(); + } + + /** + * IMCSC. + * + * Returns the cosecant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCSC(complexNumber) + * + * @param string $complexNumber the complex number for which you want the cosecant + * + * @return float|string + */ + public static function IMCSC($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->csc(); + } + + /** + * IMCSCH. + * + * Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCSCH(complexNumber) + * + * @param string $complexNumber the complex number for which you want the hyperbolic cosecant + * + * @return float|string + */ + public static function IMCSCH($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->csch(); + } + + /** + * IMSIN. + * + * Returns the sine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSIN(complexNumber) + * + * @param string $complexNumber the complex number for which you want the sine + * + * @return float|string + */ + public static function IMSIN($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->sin(); + } + + /** + * IMSINH. + * + * Returns the hyperbolic sine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSINH(complexNumber) + * + * @param string $complexNumber the complex number for which you want the hyperbolic sine + * + * @return float|string + */ + public static function IMSINH($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->sinh(); + } + + /** + * IMSEC. + * + * Returns the secant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSEC(complexNumber) + * + * @param string $complexNumber the complex number for which you want the secant + * + * @return float|string + */ + public static function IMSEC($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->sec(); + } + + /** + * IMSECH. + * + * Returns the hyperbolic secant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSECH(complexNumber) + * + * @param string $complexNumber the complex number for which you want the hyperbolic secant + * + * @return float|string + */ + public static function IMSECH($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->sech(); + } + + /** + * IMTAN. + * + * Returns the tangent of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMTAN(complexNumber) + * + * @param string $complexNumber the complex number for which you want the tangent + * + * @return float|string + */ + public static function IMTAN($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->tan(); + } + + /** + * IMSQRT. + * + * Returns the square root of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSQRT(complexNumber) + * + * @param string $complexNumber the complex number for which you want the square root + * + * @return string + */ + public static function IMSQRT($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + $theta = self::IMARGUMENT($complexNumber); + if ($theta === Functions::DIV0()) { + return '0'; + } + + return (string) $complex->sqrt(); + } + + /** + * IMLN. + * + * Returns the natural logarithm of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMLN(complexNumber) + * + * @param string $complexNumber the complex number for which you want the natural logarithm + * + * @return string + */ + public static function IMLN($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return Functions::NAN(); + } + + return (string) $complex->ln(); + } + + /** + * IMLOG10. + * + * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMLOG10(complexNumber) + * + * @param string $complexNumber the complex number for which you want the common logarithm + * + * @return string + */ + public static function IMLOG10($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return Functions::NAN(); + } + + return (string) $complex->log10(); + } + + /** + * IMLOG2. + * + * Returns the base-2 logarithm of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMLOG2(complexNumber) + * + * @param string $complexNumber the complex number for which you want the base-2 logarithm + * + * @return string + */ + public static function IMLOG2($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return Functions::NAN(); + } + + return (string) $complex->log2(); + } + + /** + * IMEXP. + * + * Returns the exponential of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMEXP(complexNumber) + * + * @param string $complexNumber the complex number for which you want the exponential + * + * @return string + */ + public static function IMEXP($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->exp(); + } + + /** + * IMPOWER. + * + * Returns a complex number in x + yi or x + yj text format raised to a power. + * + * Excel Function: + * IMPOWER(complexNumber,realNumber) + * + * @param string $complexNumber the complex number you want to raise to a power + * @param float $realNumber the power to which you want to raise the complex number + * + * @return string + */ + public static function IMPOWER($complexNumber, $realNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + $realNumber = Functions::flattenSingleValue($realNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + if (!is_numeric($realNumber)) { + return Functions::VALUE(); + } + + return (string) $complex->pow($realNumber); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php b/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php new file mode 100644 index 00000000..681aad8c --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php @@ -0,0 +1,120 @@ +divideby(new ComplexObject($complexDivisor)); + } catch (ComplexException $e) { + return Functions::NAN(); + } + } + + /** + * IMSUB. + * + * Returns the difference of two complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMSUB(complexNumber1,complexNumber2) + * + * @param string $complexNumber1 the complex number from which to subtract complexNumber2 + * @param string $complexNumber2 the complex number to subtract from complexNumber1 + * + * @return string + */ + public static function IMSUB($complexNumber1, $complexNumber2) + { + $complexNumber1 = Functions::flattenSingleValue($complexNumber1); + $complexNumber2 = Functions::flattenSingleValue($complexNumber2); + + try { + return (string) (new ComplexObject($complexNumber1))->subtract(new ComplexObject($complexNumber2)); + } catch (ComplexException $e) { + return Functions::NAN(); + } + } + + /** + * IMSUM. + * + * Returns the sum of two or more complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMSUM(complexNumber[,complexNumber[,...]]) + * + * @param string ...$complexNumbers Series of complex numbers to add + * + * @return string + */ + public static function IMSUM(...$complexNumbers) + { + // Return value + $returnValue = new ComplexObject(0.0); + $aArgs = Functions::flattenArray($complexNumbers); + + try { + // Loop through the arguments + foreach ($aArgs as $complex) { + $returnValue = $returnValue->add(new ComplexObject($complex)); + } + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $returnValue; + } + + /** + * IMPRODUCT. + * + * Returns the product of two or more complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMPRODUCT(complexNumber[,complexNumber[,...]]) + * + * @param string ...$complexNumbers Series of complex numbers to multiply + * + * @return string + */ + public static function IMPRODUCT(...$complexNumbers) + { + // Return value + $returnValue = new ComplexObject(1.0); + $aArgs = Functions::flattenArray($complexNumbers); + + try { + // Loop through the arguments + foreach ($aArgs as $complex) { + $returnValue = $returnValue->multiply(new ComplexObject($complex)); + } + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $returnValue; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/Constants.php b/src/PhpSpreadsheet/Calculation/Engineering/Constants.php new file mode 100644 index 00000000..a926db6e --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/Constants.php @@ -0,0 +1,11 @@ + 10) { + throw new Exception(Functions::NAN()); + } + + return (int) $places; + } + + throw new Exception(Functions::VALUE()); + } + + /** + * Formats a number base string value with leading zeroes. + * + * @param string $value The "number" to pad + * @param ?int $places The length that we want to pad this value + * + * @return string The padded "number" + */ + protected static function nbrConversionFormat(string $value, ?int $places): string + { + if ($places !== null) { + if (strlen($value) <= $places) { + return substr(str_pad($value, $places, '0', STR_PAD_LEFT), -10); + } + + return Functions::NAN(); + } + + return substr($value, -10); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php b/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php new file mode 100644 index 00000000..a662b78d --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php @@ -0,0 +1,134 @@ +getMessage(); + } + + if (strlen($value) == 10) { + // Two's Complement + $value = substr($value, -9); + + return '-' . (512 - bindec($value)); + } + + return (string) bindec($value); + } + + /** + * toHex. + * + * Return a binary value as hex. + * + * Excel Function: + * BIN2HEX(x[,places]) + * + * @param string $value The binary number (as a string) that you want to convert. The number + * cannot contain more than 10 characters (10 bits). The most significant + * bit of number is the sign bit. The remaining 9 bits are magnitude bits. + * Negative numbers are represented using two's-complement notation. + * If number is not a valid binary number, or if number contains more than + * 10 characters (10 bits), BIN2HEX returns the #NUM! error value. + * @param int $places The number of characters to use. If places is omitted, BIN2HEX uses the + * minimum number of characters necessary. Places is useful for padding the + * return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, BIN2HEX returns the #VALUE! error value. + * If places is negative, BIN2HEX returns the #NUM! error value. + */ + public static function toHex($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateBinary($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (strlen($value) == 10) { + $high2 = substr($value, 0, 2); + $low8 = substr($value, 2); + $xarr = ['00' => '00000000', '01' => '00000001', '10' => 'FFFFFFFE', '11' => 'FFFFFFFF']; + + return $xarr[$high2] . strtoupper(substr('0' . dechex((int) bindec($low8)), -2)); + } + $hexVal = (string) strtoupper(dechex((int) bindec($value))); + + return self::nbrConversionFormat($hexVal, $places); + } + + /** + * toOctal. + * + * Return a binary value as octal. + * + * Excel Function: + * BIN2OCT(x[,places]) + * + * @param string $value The binary number (as a string) that you want to convert. The number + * cannot contain more than 10 characters (10 bits). The most significant + * bit of number is the sign bit. The remaining 9 bits are magnitude bits. + * Negative numbers are represented using two's-complement notation. + * If number is not a valid binary number, or if number contains more than + * 10 characters (10 bits), BIN2OCT returns the #NUM! error value. + * @param int $places The number of characters to use. If places is omitted, BIN2OCT uses the + * minimum number of characters necessary. Places is useful for padding the + * return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, BIN2OCT returns the #VALUE! error value. + * If places is negative, BIN2OCT returns the #NUM! error value. + */ + public static function toOctal($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateBinary($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (strlen($value) == 10 && substr($value, 0, 1) === '1') { // Two's Complement + return str_repeat('7', 6) . strtoupper(decoct((int) bindec("11$value"))); + } + $octVal = (string) decoct((int) bindec($value)); + + return self::nbrConversionFormat($octVal, $places); + } + + protected static function validateBinary(string $value): string + { + if ((strlen($value) > preg_match_all('/[01]/', $value)) || (strlen($value) > 10)) { + throw new Exception(Functions::NAN()); + } + + return $value; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php b/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php new file mode 100644 index 00000000..a34332fb --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php @@ -0,0 +1,183 @@ + 511, DEC2BIN returns the #NUM! error + * value. + * If number is nonnumeric, DEC2BIN returns the #VALUE! error value. + * If DEC2BIN requires more than places characters, it returns the #NUM! + * error value. + * @param int $places The number of characters to use. If places is omitted, DEC2BIN uses + * the minimum number of characters necessary. Places is useful for + * padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, DEC2BIN returns the #VALUE! error value. + * If places is zero or negative, DEC2BIN returns the #NUM! error value. + */ + public static function toBinary($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateDecimal($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + $value = (int) floor((float) $value); + if ($value > self::LARGEST_BINARY_IN_DECIMAL || $value < self::SMALLEST_BINARY_IN_DECIMAL) { + return Functions::NAN(); + } + + $r = decbin($value); + // Two's Complement + $r = substr($r, -10); + + return self::nbrConversionFormat($r, $places); + } + + /** + * toHex. + * + * Return a decimal value as hex. + * + * Excel Function: + * DEC2HEX(x[,places]) + * + * @param string $value The decimal integer you want to convert. If number is negative, + * places is ignored and DEC2HEX returns a 10-character (40-bit) + * hexadecimal number in which the most significant bit is the sign + * bit. The remaining 39 bits are magnitude bits. Negative numbers + * are represented using two's-complement notation. + * If number < -549,755,813,888 or if number > 549,755,813,887, + * DEC2HEX returns the #NUM! error value. + * If number is nonnumeric, DEC2HEX returns the #VALUE! error value. + * If DEC2HEX requires more than places characters, it returns the + * #NUM! error value. + * @param int $places The number of characters to use. If places is omitted, DEC2HEX uses + * the minimum number of characters necessary. Places is useful for + * padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, DEC2HEX returns the #VALUE! error value. + * If places is zero or negative, DEC2HEX returns the #NUM! error value. + */ + public static function toHex($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateDecimal($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + $value = floor((float) $value); + if ($value > self::LARGEST_HEX_IN_DECIMAL || $value < self::SMALLEST_HEX_IN_DECIMAL) { + return Functions::NAN(); + } + $r = strtoupper(dechex((int) $value)); + $r = self::hex32bit($value, $r); + + return self::nbrConversionFormat($r, $places); + } + + public static function hex32bit(float $value, string $hexstr, bool $force = false): string + { + if (PHP_INT_SIZE === 4 || $force) { + if ($value >= 2 ** 32) { + $quotient = (int) ($value / (2 ** 32)); + + return strtoupper(substr('0' . dechex($quotient), -2) . $hexstr); + } + if ($value < -(2 ** 32)) { + $quotient = 256 - (int) ceil((-$value) / (2 ** 32)); + + return strtoupper(substr('0' . dechex($quotient), -2) . substr("00000000$hexstr", -8)); + } + if ($value < 0) { + return "FF$hexstr"; + } + } + + return $hexstr; + } + + /** + * toOctal. + * + * Return an decimal value as octal. + * + * Excel Function: + * DEC2OCT(x[,places]) + * + * @param string $value The decimal integer you want to convert. If number is negative, + * places is ignored and DEC2OCT returns a 10-character (30-bit) + * octal number in which the most significant bit is the sign bit. + * The remaining 29 bits are magnitude bits. Negative numbers are + * represented using two's-complement notation. + * If number < -536,870,912 or if number > 536,870,911, DEC2OCT + * returns the #NUM! error value. + * If number is nonnumeric, DEC2OCT returns the #VALUE! error value. + * If DEC2OCT requires more than places characters, it returns the + * #NUM! error value. + * @param int $places The number of characters to use. If places is omitted, DEC2OCT uses + * the minimum number of characters necessary. Places is useful for + * padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, DEC2OCT returns the #VALUE! error value. + * If places is zero or negative, DEC2OCT returns the #NUM! error value. + */ + public static function toOctal($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateDecimal($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + $value = (int) floor((float) $value); + if ($value > self::LARGEST_OCTAL_IN_DECIMAL || $value < self::SMALLEST_OCTAL_IN_DECIMAL) { + return Functions::NAN(); + } + $r = decoct($value); + $r = substr($r, -10); + + return self::nbrConversionFormat($r, $places); + } + + protected static function validateDecimal(string $value): string + { + if (strlen($value) > preg_match_all('/[-0123456789.]/', $value)) { + throw new Exception(Functions::VALUE()); + } + + return $value; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php b/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php new file mode 100644 index 00000000..de1b0704 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php @@ -0,0 +1,146 @@ +getMessage(); + } + + $dec = self::toDecimal($value); + + return ConvertDecimal::toBinary($dec, $places); + } + + /** + * toDecimal. + * + * Return a hex value as decimal. + * + * Excel Function: + * HEX2DEC(x) + * + * @param string $value The hexadecimal number you want to convert. This number cannot + * contain more than 10 characters (40 bits). The most significant + * bit of number is the sign bit. The remaining 39 bits are magnitude + * bits. Negative numbers are represented using two's-complement + * notation. + * If number is not a valid hexadecimal number, HEX2DEC returns the + * #NUM! error value. + */ + public static function toDecimal($value): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateHex($value); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (strlen($value) > 10) { + return Functions::NAN(); + } + + $binX = ''; + foreach (str_split($value) as $char) { + $binX .= str_pad(base_convert($char, 16, 2), 4, '0', STR_PAD_LEFT); + } + if (strlen($binX) == 40 && $binX[0] == '1') { + for ($i = 0; $i < 40; ++$i) { + $binX[$i] = ($binX[$i] == '1' ? '0' : '1'); + } + + return (string) ((bindec($binX) + 1) * -1); + } + + return (string) bindec($binX); + } + + /** + * toOctal. + * + * Return a hex value as octal. + * + * Excel Function: + * HEX2OCT(x[,places]) + * + * @param string $value The hexadecimal number you want to convert. Number cannot + * contain more than 10 characters. The most significant bit of + * number is the sign bit. The remaining 39 bits are magnitude + * bits. Negative numbers are represented using two's-complement + * notation. + * If number is negative, HEX2OCT ignores places and returns a + * 10-character octal number. + * If number is negative, it cannot be less than FFE0000000, and + * if number is positive, it cannot be greater than 1FFFFFFF. + * If number is not a valid hexadecimal number, HEX2OCT returns + * the #NUM! error value. + * If HEX2OCT requires more than places characters, it returns + * the #NUM! error value. + * @param int $places The number of characters to use. If places is omitted, HEX2OCT + * uses the minimum number of characters necessary. Places is + * useful for padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, HEX2OCT returns the #VALUE! error + * value. + * If places is negative, HEX2OCT returns the #NUM! error value. + */ + public static function toOctal($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateHex($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + $decimal = self::toDecimal($value); + + return ConvertDecimal::toOctal($decimal, $places); + } + + protected static function validateHex(string $value): string + { + if (strlen($value) > preg_match_all('/[0123456789ABCDEF]/', $value)) { + throw new Exception(Functions::NAN()); + } + + return $value; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php b/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php new file mode 100644 index 00000000..1181e2ee --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php @@ -0,0 +1,145 @@ +getMessage(); + } + + return ConvertDecimal::toBinary(self::toDecimal($value), $places); + } + + /** + * toDecimal. + * + * Return an octal value as decimal. + * + * Excel Function: + * OCT2DEC(x) + * + * @param string $value The octal number you want to convert. Number may not contain + * more than 10 octal characters (30 bits). The most significant + * bit of number is the sign bit. The remaining 29 bits are + * magnitude bits. Negative numbers are represented using + * two's-complement notation. + * If number is not a valid octal number, OCT2DEC returns the + * #NUM! error value. + */ + public static function toDecimal($value): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateOctal($value); + } catch (Exception $e) { + return $e->getMessage(); + } + + $binX = ''; + foreach (str_split($value) as $char) { + $binX .= str_pad(decbin((int) $char), 3, '0', STR_PAD_LEFT); + } + if (strlen($binX) == 30 && $binX[0] == '1') { + for ($i = 0; $i < 30; ++$i) { + $binX[$i] = ($binX[$i] == '1' ? '0' : '1'); + } + + return (string) ((bindec($binX) + 1) * -1); + } + + return (string) bindec($binX); + } + + /** + * toHex. + * + * Return an octal value as hex. + * + * Excel Function: + * OCT2HEX(x[,places]) + * + * @param string $value The octal number you want to convert. Number may not contain + * more than 10 octal characters (30 bits). The most significant + * bit of number is the sign bit. The remaining 29 bits are + * magnitude bits. Negative numbers are represented using + * two's-complement notation. + * If number is negative, OCT2HEX ignores places and returns a + * 10-character hexadecimal number. + * If number is not a valid octal number, OCT2HEX returns the + * #NUM! error value. + * If OCT2HEX requires more than places characters, it returns + * the #NUM! error value. + * @param int $places The number of characters to use. If places is omitted, OCT2HEX + * uses the minimum number of characters necessary. Places is useful + * for padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, OCT2HEX returns the #VALUE! error value. + * If places is negative, OCT2HEX returns the #NUM! error value. + */ + public static function toHex($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateOctal($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + $hexVal = strtoupper(dechex((int) self::toDecimal($value))); + $hexVal = (PHP_INT_SIZE === 4 && strlen($value) === 10 && $value[0] >= '4') ? "FF$hexVal" : $hexVal; + + return self::nbrConversionFormat($hexVal, $places); + } + + protected static function validateOctal(string $value): string + { + $numDigits = (int) preg_match_all('/[01234567]/', $value); + if (strlen($value) > $numDigits || $numDigits > 10) { + throw new Exception(Functions::NAN()); + } + + return $value; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php b/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php new file mode 100644 index 00000000..d169ae54 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php @@ -0,0 +1,684 @@ + ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Gram', 'AllowPrefix' => true], + 'sg' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Slug', 'AllowPrefix' => false], + 'lbm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Pound mass (avoirdupois)', 'AllowPrefix' => false], + 'u' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U (atomic mass unit)', 'AllowPrefix' => true], + 'ozm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => false], + 'grain' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Grain', 'AllowPrefix' => false], + 'cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false], + 'shweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false], + 'uk_cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false], + 'lcwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false], + 'hweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false], + 'stone' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Stone', 'AllowPrefix' => false], + 'ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ton', 'AllowPrefix' => false], + 'uk_ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false], + 'LTON' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false], + 'brton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false], + // Distance + 'm' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Meter', 'AllowPrefix' => true], + 'mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Statute mile', 'AllowPrefix' => false], + 'Nmi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Nautical mile', 'AllowPrefix' => false], + 'in' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Inch', 'AllowPrefix' => false], + 'ft' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Foot', 'AllowPrefix' => false], + 'yd' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Yard', 'AllowPrefix' => false], + 'ang' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Angstrom', 'AllowPrefix' => true], + 'ell' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Ell', 'AllowPrefix' => false], + 'ly' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Light Year', 'AllowPrefix' => false], + 'parsec' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false], + 'pc' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false], + 'Pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false], + 'Picapt' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false], + 'pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/6 in)', 'AllowPrefix' => false], + 'survey_mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'U.S survey mile (statute mile)', 'AllowPrefix' => false], + // Time + 'yr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Year', 'AllowPrefix' => false], + 'day' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false], + 'd' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false], + 'hr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Hour', 'AllowPrefix' => false], + 'mn' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false], + 'min' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false], + 'sec' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true], + 's' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true], + // Pressure + 'Pa' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true], + 'p' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true], + 'atm' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true], + 'at' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true], + 'mmHg' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'mm of Mercury', 'AllowPrefix' => true], + 'psi' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'PSI', 'AllowPrefix' => true], + 'Torr' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Torr', 'AllowPrefix' => true], + // Force + 'N' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Newton', 'AllowPrefix' => true], + 'dyn' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true], + 'dy' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true], + 'lbf' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pound force', 'AllowPrefix' => false], + 'pond' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pond', 'AllowPrefix' => true], + // Energy + 'J' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Joule', 'AllowPrefix' => true], + 'e' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Erg', 'AllowPrefix' => true], + 'c' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Thermodynamic calorie', 'AllowPrefix' => true], + 'cal' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'IT calorie', 'AllowPrefix' => true], + 'eV' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true], + 'ev' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true], + 'HPh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false], + 'hh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false], + 'Wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true], + 'wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true], + 'flb' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Foot-pound', 'AllowPrefix' => false], + 'BTU' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false], + 'btu' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false], + // Power + 'HP' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false], + 'h' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false], + '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], + 'T' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Tesla', 'AllowPrefix' => true], + 'ga' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Gauss', 'AllowPrefix' => true], + // Temperature + 'C' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false], + 'cel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false], + 'F' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false], + 'fah' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false], + 'K' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false], + 'kel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false], + 'Rank' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Rankine', 'AllowPrefix' => false], + 'Reau' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Réaumur', 'AllowPrefix' => false], + // Volume + 'l' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true], + 'L' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true], + 'lt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true], + 'tsp' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Teaspoon', 'AllowPrefix' => false], + 'tspm' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Modern Teaspoon', 'AllowPrefix' => false], + 'tbs' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Tablespoon', 'AllowPrefix' => false], + 'oz' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Fluid Ounce', 'AllowPrefix' => false], + 'cup' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cup', 'AllowPrefix' => false], + 'pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false], + 'us_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false], + 'uk_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.K. Pint', 'AllowPrefix' => false], + 'qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Quart', 'AllowPrefix' => false], + 'uk_qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Quart (UK)', 'AllowPrefix' => false], + 'gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gallon', 'AllowPrefix' => false], + 'uk_gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Gallon (UK)', 'AllowPrefix' => false], + 'ang3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true], + 'ang^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true], + 'barrel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Oil Barrel', 'AllowPrefix' => false], + 'bushel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Bushel', 'AllowPrefix' => false], + 'in3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false], + 'in^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false], + 'ft3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false], + 'ft^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false], + 'ly3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false], + 'ly^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false], + 'm3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true], + 'm^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true], + 'mi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false], + 'mi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false], + 'yd3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false], + 'yd^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false], + 'Nmi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false], + 'Nmi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false], + 'Pica3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], + 'Pica^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], + 'Picapt3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], + 'Picapt^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], + 'GRT' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false], + 'regton' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false], + 'MTON' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Measurement Ton (Freight Ton)', 'AllowPrefix' => false], + // Area + 'ha' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Hectare', 'AllowPrefix' => true], + 'uk_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'International Acre', 'AllowPrefix' => false], + 'us_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'US Survey/Statute Acre', 'AllowPrefix' => false], + 'ang2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true], + 'ang^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true], + 'ar' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Are', 'AllowPrefix' => true], + 'ft2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false], + 'ft^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false], + 'in2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false], + 'in^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false], + 'ly2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false], + 'ly^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false], + 'm2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true], + 'm^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true], + 'Morgen' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Morgen', 'AllowPrefix' => false], + 'mi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false], + 'mi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false], + 'Nmi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false], + 'Nmi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false], + 'Pica2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], + 'Pica^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], + 'Picapt2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], + 'Picapt^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], + 'yd2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false], + 'yd^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false], + // Information + 'byte' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Byte', 'AllowPrefix' => true], + 'bit' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Bit', 'AllowPrefix' => true], + // Speed + 'm/s' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true], + 'm/sec' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true], + 'm/h' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true], + 'm/hr' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true], + 'mph' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Miles per hour', 'AllowPrefix' => false], + 'admkn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Admiralty Knot', 'AllowPrefix' => false], + 'kn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Knot', 'AllowPrefix' => false], + ]; + + /** + * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). + * + * @var mixed[] + */ + private static $conversionMultipliers = [ + 'Y' => ['multiplier' => 1E24, 'name' => 'yotta'], + 'Z' => ['multiplier' => 1E21, 'name' => 'zetta'], + 'E' => ['multiplier' => 1E18, 'name' => 'exa'], + 'P' => ['multiplier' => 1E15, 'name' => 'peta'], + 'T' => ['multiplier' => 1E12, 'name' => 'tera'], + 'G' => ['multiplier' => 1E9, 'name' => 'giga'], + 'M' => ['multiplier' => 1E6, 'name' => 'mega'], + 'k' => ['multiplier' => 1E3, 'name' => 'kilo'], + 'h' => ['multiplier' => 1E2, 'name' => 'hecto'], + 'e' => ['multiplier' => 1E1, 'name' => 'dekao'], + 'da' => ['multiplier' => 1E1, 'name' => 'dekao'], + 'd' => ['multiplier' => 1E-1, 'name' => 'deci'], + 'c' => ['multiplier' => 1E-2, 'name' => 'centi'], + 'm' => ['multiplier' => 1E-3, 'name' => 'milli'], + 'u' => ['multiplier' => 1E-6, 'name' => 'micro'], + 'n' => ['multiplier' => 1E-9, 'name' => 'nano'], + 'p' => ['multiplier' => 1E-12, 'name' => 'pico'], + 'f' => ['multiplier' => 1E-15, 'name' => 'femto'], + 'a' => ['multiplier' => 1E-18, 'name' => 'atto'], + 'z' => ['multiplier' => 1E-21, 'name' => 'zepto'], + 'y' => ['multiplier' => 1E-24, 'name' => 'yocto'], + ]; + + /** + * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). + * + * @var mixed[] + */ + private static $binaryConversionMultipliers = [ + 'Yi' => ['multiplier' => 2 ** 80, 'name' => 'yobi'], + 'Zi' => ['multiplier' => 2 ** 70, 'name' => 'zebi'], + 'Ei' => ['multiplier' => 2 ** 60, 'name' => 'exbi'], + 'Pi' => ['multiplier' => 2 ** 50, 'name' => 'pebi'], + 'Ti' => ['multiplier' => 2 ** 40, 'name' => 'tebi'], + 'Gi' => ['multiplier' => 2 ** 30, 'name' => 'gibi'], + 'Mi' => ['multiplier' => 2 ** 20, 'name' => 'mebi'], + 'ki' => ['multiplier' => 2 ** 10, 'name' => 'kibi'], + ]; + + /** + * Details of the Units of measure conversion factors, organised by group. + * + * @var mixed[] + */ + private static $unitConversions = [ + // Conversion uses gram (g) as an intermediate unit + self::CATEGORY_WEIGHT_AND_MASS => [ + 'g' => 1.0, + 'sg' => 6.85217658567918E-05, + 'lbm' => 2.20462262184878E-03, + 'u' => 6.02214179421676E+23, + 'ozm' => 3.52739619495804E-02, + 'grain' => 1.54323583529414E+01, + 'cwt' => 2.20462262184878E-05, + 'shweight' => 2.20462262184878E-05, + 'uk_cwt' => 1.96841305522212E-05, + 'lcwt' => 1.96841305522212E-05, + 'hweight' => 1.96841305522212E-05, + 'stone' => 1.57473044417770E-04, + 'ton' => 1.10231131092439E-06, + 'uk_ton' => 9.84206527611061E-07, + 'LTON' => 9.84206527611061E-07, + 'brton' => 9.84206527611061E-07, + ], + // Conversion uses meter (m) as an intermediate unit + self::CATEGORY_DISTANCE => [ + 'm' => 1.0, + 'mi' => 6.21371192237334E-04, + 'Nmi' => 5.39956803455724E-04, + 'in' => 3.93700787401575E+01, + 'ft' => 3.28083989501312E+00, + 'yd' => 1.09361329833771E+00, + 'ang' => 1.0E+10, + 'ell' => 8.74890638670166E-01, + 'ly' => 1.05700083402462E-16, + 'parsec' => 3.24077928966473E-17, + 'pc' => 3.24077928966473E-17, + 'Pica' => 2.83464566929134E+03, + 'Picapt' => 2.83464566929134E+03, + 'pica' => 2.36220472440945E+02, + 'survey_mi' => 6.21369949494950E-04, + ], + // Conversion uses second (s) as an intermediate unit + self::CATEGORY_TIME => [ + 'yr' => 3.16880878140289E-08, + 'day' => 1.15740740740741E-05, + 'd' => 1.15740740740741E-05, + 'hr' => 2.77777777777778E-04, + 'mn' => 1.66666666666667E-02, + 'min' => 1.66666666666667E-02, + 'sec' => 1.0, + 's' => 1.0, + ], + // Conversion uses Pascal (Pa) as an intermediate unit + self::CATEGORY_PRESSURE => [ + 'Pa' => 1.0, + 'p' => 1.0, + 'atm' => 9.86923266716013E-06, + 'at' => 9.86923266716013E-06, + 'mmHg' => 7.50063755419211E-03, + 'psi' => 1.45037737730209E-04, + 'Torr' => 7.50061682704170E-03, + ], + // Conversion uses Newton (N) as an intermediate unit + self::CATEGORY_FORCE => [ + 'N' => 1.0, + 'dyn' => 1.0E+5, + 'dy' => 1.0E+5, + 'lbf' => 2.24808923655339E-01, + 'pond' => 1.01971621297793E+02, + ], + // Conversion uses Joule (J) as an intermediate unit + self::CATEGORY_ENERGY => [ + 'J' => 1.0, + 'e' => 9.99999519343231E+06, + 'c' => 2.39006249473467E-01, + 'cal' => 2.38846190642017E-01, + 'eV' => 6.24145700000000E+18, + 'ev' => 6.24145700000000E+18, + 'HPh' => 3.72506430801000E-07, + 'hh' => 3.72506430801000E-07, + 'Wh' => 2.77777916238711E-04, + 'wh' => 2.77777916238711E-04, + 'flb' => 2.37304222192651E+01, + 'BTU' => 9.47815067349015E-04, + 'btu' => 9.47815067349015E-04, + ], + // Conversion uses Horsepower (HP) as an intermediate unit + self::CATEGORY_POWER => [ + 'HP' => 1.0, + 'h' => 1.0, + 'W' => 7.45699871582270E+02, + 'w' => 7.45699871582270E+02, + 'PS' => 1.01386966542400E+00, + ], + // Conversion uses Tesla (T) as an intermediate unit + self::CATEGORY_MAGNETISM => [ + 'T' => 1.0, + 'ga' => 10000.0, + ], + // Conversion uses litre (l) as an intermediate unit + self::CATEGORY_VOLUME => [ + 'l' => 1.0, + 'L' => 1.0, + 'lt' => 1.0, + 'tsp' => 2.02884136211058E+02, + 'tspm' => 2.0E+02, + 'tbs' => 6.76280454036860E+01, + 'oz' => 3.38140227018430E+01, + 'cup' => 4.22675283773038E+00, + 'pt' => 2.11337641886519E+00, + 'us_pt' => 2.11337641886519E+00, + 'uk_pt' => 1.75975398639270E+00, + 'qt' => 1.05668820943259E+00, + 'uk_qt' => 8.79876993196351E-01, + 'gal' => 2.64172052358148E-01, + 'uk_gal' => 2.19969248299088E-01, + 'ang3' => 1.0E+27, + 'ang^3' => 1.0E+27, + 'barrel' => 6.28981077043211E-03, + 'bushel' => 2.83775932584017E-02, + 'in3' => 6.10237440947323E+01, + 'in^3' => 6.10237440947323E+01, + 'ft3' => 3.53146667214886E-02, + 'ft^3' => 3.53146667214886E-02, + 'ly3' => 1.18093498844171E-51, + 'ly^3' => 1.18093498844171E-51, + 'm3' => 1.0E-03, + 'm^3' => 1.0E-03, + 'mi3' => 2.39912758578928E-13, + 'mi^3' => 2.39912758578928E-13, + 'yd3' => 1.30795061931439E-03, + 'yd^3' => 1.30795061931439E-03, + 'Nmi3' => 1.57426214685811E-13, + 'Nmi^3' => 1.57426214685811E-13, + 'Pica3' => 2.27769904358706E+07, + 'Pica^3' => 2.27769904358706E+07, + 'Picapt3' => 2.27769904358706E+07, + 'Picapt^3' => 2.27769904358706E+07, + 'GRT' => 3.53146667214886E-04, + 'regton' => 3.53146667214886E-04, + 'MTON' => 8.82866668037215E-04, + ], + // Conversion uses hectare (ha) as an intermediate unit + self::CATEGORY_AREA => [ + 'ha' => 1.0, + 'uk_acre' => 2.47105381467165E+00, + 'us_acre' => 2.47104393046628E+00, + 'ang2' => 1.0E+24, + 'ang^2' => 1.0E+24, + 'ar' => 1.0E+02, + 'ft2' => 1.07639104167097E+05, + 'ft^2' => 1.07639104167097E+05, + 'in2' => 1.55000310000620E+07, + 'in^2' => 1.55000310000620E+07, + 'ly2' => 1.11725076312873E-28, + 'ly^2' => 1.11725076312873E-28, + 'm2' => 1.0E+04, + 'm^2' => 1.0E+04, + 'Morgen' => 4.0E+00, + 'mi2' => 3.86102158542446E-03, + 'mi^2' => 3.86102158542446E-03, + 'Nmi2' => 2.91553349598123E-03, + 'Nmi^2' => 2.91553349598123E-03, + 'Pica2' => 8.03521607043214E+10, + 'Pica^2' => 8.03521607043214E+10, + 'Picapt2' => 8.03521607043214E+10, + 'Picapt^2' => 8.03521607043214E+10, + 'yd2' => 1.19599004630108E+04, + 'yd^2' => 1.19599004630108E+04, + ], + // Conversion uses bit (bit) as an intermediate unit + self::CATEGORY_INFORMATION => [ + 'bit' => 1.0, + 'byte' => 0.125, + ], + // Conversion uses Meters per Second (m/s) as an intermediate unit + self::CATEGORY_SPEED => [ + 'm/s' => 1.0, + 'm/sec' => 1.0, + 'm/h' => 3.60E+03, + 'm/hr' => 3.60E+03, + 'mph' => 2.23693629205440E+00, + 'admkn' => 1.94260256941567E+00, + 'kn' => 1.94384449244060E+00, + ], + ]; + + /** + * getConversionGroups + * Returns a list of the different conversion groups for UOM conversions. + * + * @return array + */ + public static function getConversionCategories() + { + $conversionGroups = []; + foreach (self::$conversionUnits as $conversionUnit) { + $conversionGroups[] = $conversionUnit['Group']; + } + + return array_merge(array_unique($conversionGroups)); + } + + /** + * getConversionGroupUnits + * Returns an array of units of measure, for a specified conversion group, or for all groups. + * + * @param string $category The group whose units of measure you want to retrieve + * + * @return array + */ + public static function getConversionCategoryUnits($category = null) + { + $conversionGroups = []; + foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { + if (($category === null) || ($conversionGroup['Group'] == $category)) { + $conversionGroups[$conversionGroup['Group']][] = $conversionUnit; + } + } + + return $conversionGroups; + } + + /** + * getConversionGroupUnitDetails. + * + * @param string $category The group whose units of measure you want to retrieve + * + * @return array + */ + public static function getConversionCategoryUnitDetails($category = null) + { + $conversionGroups = []; + foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { + if (($category === null) || ($conversionGroup['Group'] == $category)) { + $conversionGroups[$conversionGroup['Group']][] = [ + 'unit' => $conversionUnit, + 'description' => $conversionGroup['Unit Name'], + ]; + } + } + + return $conversionGroups; + } + + /** + * getConversionMultipliers + * Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). + * + * @return mixed[] + */ + public static function getConversionMultipliers() + { + return self::$conversionMultipliers; + } + + /** + * getBinaryConversionMultipliers + * Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure in CONVERTUOM(). + * + * @return mixed[] + */ + public static function getBinaryConversionMultipliers() + { + return self::$binaryConversionMultipliers; + } + + /** + * CONVERT. + * + * Converts a number from one measurement system to another. + * For example, CONVERT can translate a table of distances in miles to a table of distances + * in kilometers. + * + * Excel Function: + * CONVERT(value,fromUOM,toUOM) + * + * @param float|int $value the value in fromUOM to convert + * @param string $fromUOM the units for value + * @param string $toUOM the units for the result + * + * @return float|string + */ + public static function CONVERT($value, $fromUOM, $toUOM) + { + $value = Functions::flattenSingleValue($value); + $fromUOM = Functions::flattenSingleValue($fromUOM); + $toUOM = Functions::flattenSingleValue($toUOM); + + if (!is_numeric($value)) { + return Functions::VALUE(); + } + + try { + [$fromUOM, $fromCategory, $fromMultiplier] = self::getUOMDetails($fromUOM); + [$toUOM, $toCategory, $toMultiplier] = self::getUOMDetails($toUOM); + } catch (Exception $e) { + return Functions::NA(); + } + + if ($fromCategory !== $toCategory) { + return Functions::NA(); + } + + $value *= $fromMultiplier; + + if (($fromUOM === $toUOM) && ($fromMultiplier === $toMultiplier)) { + // We've already factored $fromMultiplier into the value, so we need + // to reverse it again + return $value / $fromMultiplier; + } elseif ($fromUOM === $toUOM) { + return $value / $toMultiplier; + } elseif ($fromCategory === self::CATEGORY_TEMPERATURE) { + return self::convertTemperature($fromUOM, $toUOM, $value); + } + + $baseValue = $value * (1.0 / self::$unitConversions[$fromCategory][$fromUOM]); + + return ($baseValue * self::$unitConversions[$fromCategory][$toUOM]) / $toMultiplier; + } + + private static function getUOMDetails(string $uom) + { + if (isset(self::$conversionUnits[$uom])) { + $unitCategory = self::$conversionUnits[$uom]['Group']; + + return [$uom, $unitCategory, 1.0]; + } + + // Check 1-character standard metric multiplier prefixes + $multiplierType = substr($uom, 0, 1); + $uom = substr($uom, 1); + if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) { + if (self::$conversionUnits[$uom]['AllowPrefix'] === false) { + throw new Exception('Prefix not allowed for UoM'); + } + $unitCategory = self::$conversionUnits[$uom]['Group']; + + return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']]; + } + + $multiplierType .= substr($uom, 0, 1); + $uom = substr($uom, 1); + + // Check 2-character standard metric multiplier prefixes + if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) { + if (self::$conversionUnits[$uom]['AllowPrefix'] === false) { + throw new Exception('Prefix not allowed for UoM'); + } + $unitCategory = self::$conversionUnits[$uom]['Group']; + + return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']]; + } + + // Check 2-character binary multiplier prefixes + if (isset(self::$conversionUnits[$uom], self::$binaryConversionMultipliers[$multiplierType])) { + if (self::$conversionUnits[$uom]['AllowPrefix'] === false) { + throw new Exception('Prefix not allowed for UoM'); + } + $unitCategory = self::$conversionUnits[$uom]['Group']; + if ($unitCategory !== 'Information') { + throw new Exception('Binary Prefix is only allowed for Information UoM'); + } + + return [$uom, $unitCategory, self::$binaryConversionMultipliers[$multiplierType]['multiplier']]; + } + + throw new Exception('UoM Not Found'); + } + + /** + * @param float|int $value + * + * @return float|int + */ + protected static function convertTemperature(string $fromUOM, string $toUOM, $value) + { + $fromUOM = self::resolveTemperatureSynonyms($fromUOM); + $toUOM = self::resolveTemperatureSynonyms($toUOM); + + if ($fromUOM === $toUOM) { + return $value; + } + + // Convert to Kelvin + switch ($fromUOM) { + case 'F': + $value = ($value - 32) / 1.8 + 273.15; + + break; + case 'C': + $value += 273.15; + + break; + case 'Rank': + $value /= 1.8; + + break; + case 'Reau': + $value = $value * 1.25 + 273.15; + + break; + } + + // Convert from Kelvin + switch ($toUOM) { + case 'F': + $value = ($value - 273.15) * 1.8 + 32.00; + + break; + case 'C': + $value -= 273.15; + + break; + case 'Rank': + $value *= 1.8; + + break; + case 'Reau': + $value = ($value - 273.15) * 0.80000; + + break; + } + + return $value; + } + + private static function resolveTemperatureSynonyms(string $uom) + { + switch ($uom) { + case 'fah': + return 'F'; + case 'cel': + return 'C'; + case 'kel': + return 'K'; + } + + return $uom; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php b/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php new file mode 100644 index 00000000..01630af3 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php @@ -0,0 +1,33 @@ + 2.2) { + return 1 - ErfC::ERFC($value); + } + $sum = $term = $value; + $xsqr = ($value * $value); + $j = 1; + do { + $term *= $xsqr / $j; + $sum -= $term / (2 * $j + 1); + ++$j; + $term *= $xsqr / $j; + $sum += $term / (2 * $j + 1); + ++$j; + if ($sum == 0.0) { + break; + } + } while (abs($term / $sum) > Functions::PRECISION); + + return self::$twoSqrtPi * $sum; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php b/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php new file mode 100644 index 00000000..c57a28f4 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php @@ -0,0 +1,68 @@ + Functions::PRECISION); + + return self::$oneSqrtPi * exp(-$value * $value) * $q2; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial.php b/src/PhpSpreadsheet/Calculation/Financial.php index 5a908aa5..9d933b4a 100644 --- a/src/PhpSpreadsheet/Calculation/Financial.php +++ b/src/PhpSpreadsheet/Calculation/Financial.php @@ -2,167 +2,81 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; -use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Amortization; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Depreciation; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Dollar; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\InterestRate; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\TreasuryBill; +/** + * @deprecated 1.18.0 + */ class Financial { const FINANCIAL_MAX_ITERATIONS = 128; const FINANCIAL_PRECISION = 1.0e-08; - /** - * isLastDayOfMonth. - * - * Returns a boolean TRUE/FALSE indicating if this date is the last date of the month - * - * @param \DateTime $testDate The date for testing - * - * @return bool - */ - private static function isLastDayOfMonth(\DateTime $testDate) - { - return $testDate->format('d') == $testDate->format('t'); - } - - private static function couponFirstPeriodDate($settlement, $maturity, $frequency, $next) - { - $months = 12 / $frequency; - - $result = Date::excelToDateTimeObject($maturity); - $eom = self::isLastDayOfMonth($result); - - while ($settlement < Date::PHPToExcel($result)) { - $result->modify('-' . $months . ' months'); - } - if ($next) { - $result->modify('+' . $months . ' months'); - } - - if ($eom) { - $result->modify('-1 day'); - } - - return Date::PHPToExcel($result); - } - - private static function isValidFrequency($frequency) - { - if (($frequency == 1) || ($frequency == 2) || ($frequency == 4)) { - return true; - } - - return false; - } - - /** - * daysPerYear. - * - * Returns the number of days in a specified year, as defined by the "basis" value - * - * @param int|string $year The year against which we're testing - * @param int|string $basis The type of day count: - * 0 or omitted US (NASD) 360 - * 1 Actual (365 or 366 in a leap year) - * 2 360 - * 3 365 - * 4 European 360 - * - * @return int|string Result, or a string containing an error - */ - private static function daysPerYear($year, $basis = 0) - { - switch ($basis) { - case 0: - case 2: - case 4: - $daysPerYear = 360; - - break; - case 3: - $daysPerYear = 365; - - break; - case 1: - $daysPerYear = (DateTime::isLeapYear($year)) ? 366 : 365; - - break; - default: - return Functions::NAN(); - } - - return $daysPerYear; - } - - private static function interestAndPrincipal($rate = 0, $per = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0) - { - $pmt = self::PMT($rate, $nper, $pv, $fv, $type); - $capital = $pv; - for ($i = 1; $i <= $per; ++$i) { - $interest = ($type && $i == 1) ? 0 : -$capital * $rate; - $principal = $pmt - $interest; - $capital += $principal; - } - - return [$interest, $principal]; - } - /** * ACCRINT. * * Returns the accrued interest for a security that pays periodic interest. * * Excel Function: - * ACCRINT(issue,firstinterest,settlement,rate,par,frequency[,basis]) + * ACCRINT(issue,firstinterest,settlement,rate,par,frequency[,basis][,calc_method]) + * + * @Deprecated 1.18.0 + * + * @see Financial\Securities\AccruedInterest::periodic() + * Use the periodic() method in the Financial\Securities\AccruedInterest class instead * * @param mixed $issue the security's issue date - * @param mixed $firstinterest the security's first interest date + * @param mixed $firstInterest the security's first interest date * @param mixed $settlement The security's settlement date. - * The security settlement date is the date after the issue date - * when the security is traded to the buyer. - * @param float $rate the security's annual coupon rate - * @param float $par The security's par value. - * If you omit par, ACCRINT uses $1,000. - * @param int $frequency the number of coupon payments per year. - * Valid frequency values are: - * 1 Annual - * 2 Semi-Annual - * 4 Quarterly - * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * The security settlement date is the date after the issue date + * when the security is traded to the buyer. + * @param mixed $rate the security's annual coupon rate + * @param mixed $parValue The security's par value. + * If you omit par, ACCRINT uses $1,000. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * @param mixed $calcMethod + * If true, use Issue to Settlement + * If false, use FirstInterest to Settlement * * @return float|string Result, or a string containing an error */ - public static function ACCRINT($issue, $firstinterest, $settlement, $rate, $par = 1000, $frequency = 1, $basis = 0) - { - $issue = Functions::flattenSingleValue($issue); - $firstinterest = Functions::flattenSingleValue($firstinterest); - $settlement = Functions::flattenSingleValue($settlement); - $rate = Functions::flattenSingleValue($rate); - $par = ($par === null) ? 1000 : Functions::flattenSingleValue($par); - $frequency = ($frequency === null) ? 1 : Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis); - - // Validate - if ((is_numeric($rate)) && (is_numeric($par))) { - $rate = (float) $rate; - $par = (float) $par; - if (($rate <= 0) || ($par <= 0)) { - return Functions::NAN(); - } - $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis); - if (!is_numeric($daysBetweenIssueAndSettlement)) { - // return date error - return $daysBetweenIssueAndSettlement; - } - - return $par * $rate * $daysBetweenIssueAndSettlement; - } - - return Functions::VALUE(); + public static function ACCRINT( + $issue, + $firstInterest, + $settlement, + $rate, + $parValue = 1000, + $frequency = 1, + $basis = 0, + $calcMethod = true + ) { + return Securities\AccruedInterest::periodic( + $issue, + $firstInterest, + $settlement, + $rate, + $parValue, + $frequency, + $basis, + $calcMethod + ); } /** @@ -173,45 +87,28 @@ class Financial * Excel Function: * ACCRINTM(issue,settlement,rate[,par[,basis]]) * + * @Deprecated 1.18.0 + * + * @see Financial\Securities\AccruedInterest::atMaturity() + * Use the atMaturity() method in the Financial\Securities\AccruedInterest class instead + * * @param mixed $issue The security's issue date * @param mixed $settlement The security's settlement (or maturity) date - * @param float $rate The security's annual coupon rate - * @param float $par The security's par value. - * If you omit par, ACCRINT uses $1,000. - * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * @param mixed $rate The security's annual coupon rate + * @param mixed $parValue The security's par value. + * If you omit par, ACCRINT uses $1,000. + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 * * @return float|string Result, or a string containing an error */ - public static function ACCRINTM($issue, $settlement, $rate, $par = 1000, $basis = 0) + public static function ACCRINTM($issue, $settlement, $rate, $parValue = 1000, $basis = 0) { - $issue = Functions::flattenSingleValue($issue); - $settlement = Functions::flattenSingleValue($settlement); - $rate = Functions::flattenSingleValue($rate); - $par = ($par === null) ? 1000 : Functions::flattenSingleValue($par); - $basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis); - - // Validate - if ((is_numeric($rate)) && (is_numeric($par))) { - $rate = (float) $rate; - $par = (float) $par; - if (($rate <= 0) || ($par <= 0)) { - return Functions::NAN(); - } - $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis); - if (!is_numeric($daysBetweenIssueAndSettlement)) { - // return date error - return $daysBetweenIssueAndSettlement; - } - - return $par * $rate * $daysBetweenIssueAndSettlement; - } - - return Functions::VALUE(); + return Securities\AccruedInterest::atMaturity($issue, $settlement, $rate, $parValue, $basis); } /** @@ -229,6 +126,11 @@ class Financial * Excel Function: * AMORDEGRC(cost,purchased,firstPeriod,salvage,period,rate[,basis]) * + * @Deprecated 1.18.0 + * + * @see Financial\Amortization::AMORDEGRC() + * Use the AMORDEGRC() method in the Financial\Amortization class instead + * * @param float $cost The cost of the asset * @param mixed $purchased Date of the purchase of the asset * @param mixed $firstPeriod Date of the end of the first period @@ -236,63 +138,17 @@ class Financial * @param float $period The period * @param float $rate Rate of depreciation * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 * - * @return float + * @return float|string (string containing the error type if there is an error) */ public static function AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0) { - $cost = Functions::flattenSingleValue($cost); - $purchased = Functions::flattenSingleValue($purchased); - $firstPeriod = Functions::flattenSingleValue($firstPeriod); - $salvage = Functions::flattenSingleValue($salvage); - $period = floor(Functions::flattenSingleValue($period)); - $rate = Functions::flattenSingleValue($rate); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - // The depreciation coefficients are: - // Life of assets (1/rate) Depreciation coefficient - // Less than 3 years 1 - // Between 3 and 4 years 1.5 - // Between 5 and 6 years 2 - // More than 6 years 2.5 - $fUsePer = 1.0 / $rate; - if ($fUsePer < 3.0) { - $amortiseCoeff = 1.0; - } elseif ($fUsePer < 5.0) { - $amortiseCoeff = 1.5; - } elseif ($fUsePer <= 6.0) { - $amortiseCoeff = 2.0; - } else { - $amortiseCoeff = 2.5; - } - - $rate *= $amortiseCoeff; - $fNRate = round(DateTime::YEARFRAC($purchased, $firstPeriod, $basis) * $rate * $cost, 0); - $cost -= $fNRate; - $fRest = $cost - $salvage; - - for ($n = 0; $n < $period; ++$n) { - $fNRate = round($rate * $cost, 0); - $fRest -= $fNRate; - - if ($fRest < 0.0) { - switch ($period - $n) { - case 0: - case 1: - return round($cost * 0.5, 0); - default: - return 0.0; - } - } - $cost -= $fNRate; - } - - return $fNRate; + return Amortization::AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis); } /** @@ -305,6 +161,11 @@ class Financial * Excel Function: * AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis]) * + * @Deprecated 1.18.0 + * + * @see Financial\Amortization::AMORLINC() + * Use the AMORLINC() method in the Financial\Amortization class instead + * * @param float $cost The cost of the asset * @param mixed $purchased Date of the purchase of the asset * @param mixed $firstPeriod Date of the end of the first period @@ -312,46 +173,17 @@ class Financial * @param float $period The period * @param float $rate Rate of depreciation * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 * - * @return float + * @return float|string (string containing the error type if there is an error) */ public static function AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0) { - $cost = Functions::flattenSingleValue($cost); - $purchased = Functions::flattenSingleValue($purchased); - $firstPeriod = Functions::flattenSingleValue($firstPeriod); - $salvage = Functions::flattenSingleValue($salvage); - $period = Functions::flattenSingleValue($period); - $rate = Functions::flattenSingleValue($rate); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - $fOneRate = $cost * $rate; - $fCostDelta = $cost - $salvage; - // Note, quirky variation for leap years on the YEARFRAC for this function - $purchasedYear = DateTime::YEAR($purchased); - $yearFrac = DateTime::YEARFRAC($purchased, $firstPeriod, $basis); - - if (($basis == 1) && ($yearFrac < 1) && (DateTime::isLeapYear($purchasedYear))) { - $yearFrac *= 365 / 366; - } - - $f0Rate = $yearFrac * $rate * $cost; - $nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate); - - if ($period == 0) { - return $f0Rate; - } elseif ($period <= $nNumOfFullPeriods) { - return $fOneRate; - } elseif ($period == ($nNumOfFullPeriods + 1)) { - return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate; - } - - return 0.0; + return Amortization::AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis); } /** @@ -362,6 +194,11 @@ class Financial * Excel Function: * COUPDAYBS(settlement,maturity,frequency[,basis]) * + * @Deprecated 1.18.0 + * + * @see Financial\Coupons::COUPDAYBS() + * Use the COUPDAYBS() method in the Financial\Coupons class instead + * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue * date when the security is traded to the buyer. @@ -383,34 +220,7 @@ class Financial */ public static function COUPDAYBS($settlement, $maturity, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if ( - ($settlement >= $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4)) - ) { - return Functions::NAN(); - } - - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, false); - - if ($basis == 1) { - return abs(DateTime::DAYS($prev, $settlement)); - } - - return DateTime::YEARFRAC($prev, $settlement, $basis) * $daysPerYear; + return Coupons::COUPDAYBS($settlement, $maturity, $frequency, $basis); } /** @@ -421,6 +231,11 @@ class Financial * Excel Function: * COUPDAYS(settlement,maturity,frequency[,basis]) * + * @Deprecated 1.18.0 + * + * @see Financial\Coupons::COUPDAYS() + * Use the COUPDAYS() method in the Financial\Coupons class instead + * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue * date when the security is traded to the buyer. @@ -442,45 +257,7 @@ class Financial */ public static function COUPDAYS($settlement, $maturity, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if ( - ($settlement >= $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4)) - ) { - return Functions::NAN(); - } - - switch ($basis) { - case 3: - // Actual/365 - return 365 / $frequency; - case 1: - // Actual/actual - if ($frequency == 1) { - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - - return $daysPerYear / $frequency; - } - $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, false); - $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true); - - return $next - $prev; - default: - // US (NASD) 30/360, Actual/360 or European 30/360 - return 360 / $frequency; - } + return Coupons::COUPDAYS($settlement, $maturity, $frequency, $basis); } /** @@ -491,6 +268,11 @@ class Financial * Excel Function: * COUPDAYSNC(settlement,maturity,frequency[,basis]) * + * @Deprecated 1.18.0 + * + * @see Financial\Coupons::COUPDAYSNC() + * Use the COUPDAYSNC() method in the Financial\Coupons class instead + * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue * date when the security is traded to the buyer. @@ -512,30 +294,7 @@ class Financial */ public static function COUPDAYSNC($settlement, $maturity, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if ( - ($settlement >= $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4)) - ) { - return Functions::NAN(); - } - - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true); - - return DateTime::YEARFRAC($settlement, $next, $basis) * $daysPerYear; + return Coupons::COUPDAYSNC($settlement, $maturity, $frequency, $basis); } /** @@ -546,6 +305,11 @@ class Financial * Excel Function: * COUPNCD(settlement,maturity,frequency[,basis]) * + * @Deprecated 1.18.0 + * + * @see Financial\Coupons::COUPNCD() + * Use the COUPNCD() method in the Financial\Coupons class instead + * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue * date when the security is traded to the buyer. @@ -568,27 +332,7 @@ class Financial */ public static function COUPNCD($settlement, $maturity, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if ( - ($settlement >= $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4)) - ) { - return Functions::NAN(); - } - - return self::couponFirstPeriodDate($settlement, $maturity, $frequency, true); + return Coupons::COUPNCD($settlement, $maturity, $frequency, $basis); } /** @@ -600,6 +344,11 @@ class Financial * Excel Function: * COUPNUM(settlement,maturity,frequency[,basis]) * + * @Deprecated 1.18.0 + * + * @see Financial\Coupons::COUPNUM() + * Use the COUPNUM() method in the Financial\Coupons class instead + * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue * date when the security is traded to the buyer. @@ -621,29 +370,7 @@ class Financial */ public static function COUPNUM($settlement, $maturity, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if ( - ($settlement >= $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4)) - ) { - return Functions::NAN(); - } - - $yearsBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, 0); - - return ceil($yearsBetweenSettlementAndMaturity * $frequency); + return Coupons::COUPNUM($settlement, $maturity, $frequency, $basis); } /** @@ -654,6 +381,11 @@ class Financial * Excel Function: * COUPPCD(settlement,maturity,frequency[,basis]) * + * @Deprecated 1.18.0 + * + * @see Financial\Coupons::COUPPCD() + * Use the COUPPCD() method in the Financial\Coupons class instead + * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue * date when the security is traded to the buyer. @@ -676,27 +408,7 @@ class Financial */ public static function COUPPCD($settlement, $maturity, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if ( - ($settlement >= $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4)) - ) { - return Functions::NAN(); - } - - return self::couponFirstPeriodDate($settlement, $maturity, $frequency, false); + return Coupons::COUPPCD($settlement, $maturity, $frequency, $basis); } /** @@ -707,42 +419,26 @@ class Financial * Excel Function: * CUMIPMT(rate,nper,pv,start,end[,type]) * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Constant\Periodic\Cumulative::interest() + * Use the interest() method in the Financial\CashFlow\Constant\Periodic\Cumulative class instead + * * @param float $rate The Interest rate * @param int $nper The total number of payment periods * @param float $pv Present Value * @param int $start The first period in the calculation. - * Payment periods are numbered beginning with 1. + * Payment periods are numbered beginning with 1. * @param int $end the last period in the calculation * @param int $type A number 0 or 1 and indicates when payments are due: - * 0 or omitted At the end of the period. - * 1 At the beginning of the period. + * 0 or omitted At the end of the period. + * 1 At the beginning of the period. * * @return float|string */ public static function CUMIPMT($rate, $nper, $pv, $start, $end, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $nper = (int) Functions::flattenSingleValue($nper); - $pv = Functions::flattenSingleValue($pv); - $start = (int) Functions::flattenSingleValue($start); - $end = (int) Functions::flattenSingleValue($end); - $type = (int) Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - if ($start < 1 || $start > $end) { - return Functions::VALUE(); - } - - // Calculate - $interest = 0; - for ($per = $start; $per <= $end; ++$per) { - $interest += self::IPMT($rate, $per, $nper, $pv, 0, $type); - } - - return $interest; + return Financial\CashFlow\Constant\Periodic\Cumulative::interest($rate, $nper, $pv, $start, $end, $type); } /** @@ -753,42 +449,26 @@ class Financial * Excel Function: * CUMPRINC(rate,nper,pv,start,end[,type]) * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Constant\Periodic\Cumulative::principal() + * Use the principal() method in the Financial\CashFlow\Constant\Periodic\Cumulative class instead + * * @param float $rate The Interest rate * @param int $nper The total number of payment periods * @param float $pv Present Value * @param int $start The first period in the calculation. - * Payment periods are numbered beginning with 1. + * Payment periods are numbered beginning with 1. * @param int $end the last period in the calculation * @param int $type A number 0 or 1 and indicates when payments are due: - * 0 or omitted At the end of the period. - * 1 At the beginning of the period. + * 0 or omitted At the end of the period. + * 1 At the beginning of the period. * * @return float|string */ public static function CUMPRINC($rate, $nper, $pv, $start, $end, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $nper = (int) Functions::flattenSingleValue($nper); - $pv = Functions::flattenSingleValue($pv); - $start = (int) Functions::flattenSingleValue($start); - $end = (int) Functions::flattenSingleValue($end); - $type = (int) Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - if ($start < 1 || $start > $end) { - return Functions::VALUE(); - } - - // Calculate - $principal = 0; - for ($per = $start; $per <= $end; ++$per) { - $principal += self::PPMT($rate, $per, $nper, $pv, 0, $type); - } - - return $principal; + return Financial\CashFlow\Constant\Periodic\Cumulative::principal($rate, $nper, $pv, $start, $end, $type); } /** @@ -804,6 +484,11 @@ class Financial * Excel Function: * DB(cost,salvage,life,period[,month]) * + * @Deprecated 1.18.0 + * + * @see Financial\Depreciation::DB() + * Use the DB() method in the Financial\Depreciation class instead + * * @param float $cost Initial cost of the asset * @param float $salvage Value at the end of the depreciation. * (Sometimes called the salvage value of the asset) @@ -818,46 +503,7 @@ class Financial */ public static function DB($cost, $salvage, $life, $period, $month = 12) { - $cost = Functions::flattenSingleValue($cost); - $salvage = Functions::flattenSingleValue($salvage); - $life = Functions::flattenSingleValue($life); - $period = Functions::flattenSingleValue($period); - $month = Functions::flattenSingleValue($month); - - // Validate - if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($month))) { - $cost = (float) $cost; - $salvage = (float) $salvage; - $life = (int) $life; - $period = (int) $period; - $month = (int) $month; - if ($cost == 0) { - return 0.0; - } elseif (($cost < 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($month < 1)) { - return Functions::NAN(); - } - // Set Fixed Depreciation Rate - $fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life); - $fixedDepreciationRate = round($fixedDepreciationRate, 3); - - // Loop through each period calculating the depreciation - $previousDepreciation = 0; - $depreciation = 0; - for ($per = 1; $per <= $period; ++$per) { - if ($per == 1) { - $depreciation = $cost * $fixedDepreciationRate * $month / 12; - } elseif ($per == ($life + 1)) { - $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12; - } else { - $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate; - } - $previousDepreciation += $depreciation; - } - - return $depreciation; - } - - return Functions::VALUE(); + return Depreciation::DB($cost, $salvage, $life, $period, $month); } /** @@ -869,6 +515,11 @@ class Financial * Excel Function: * DDB(cost,salvage,life,period[,factor]) * + * @Deprecated 1.18.0 + * + * @see Financial\Depreciation::DDB() + * Use the DDB() method in the Financial\Depreciation class instead + * * @param float $cost Initial cost of the asset * @param float $salvage Value at the end of the depreciation. * (Sometimes called the salvage value of the asset) @@ -884,38 +535,7 @@ class Financial */ public static function DDB($cost, $salvage, $life, $period, $factor = 2.0) { - $cost = Functions::flattenSingleValue($cost); - $salvage = Functions::flattenSingleValue($salvage); - $life = Functions::flattenSingleValue($life); - $period = Functions::flattenSingleValue($period); - $factor = Functions::flattenSingleValue($factor); - - // Validate - if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($factor))) { - $cost = (float) $cost; - $salvage = (float) $salvage; - $life = (int) $life; - $period = (int) $period; - $factor = (float) $factor; - if (($cost <= 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($factor <= 0.0) || ($period > $life)) { - return Functions::NAN(); - } - // Set Fixed Depreciation Rate - $fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life); - $fixedDepreciationRate = round($fixedDepreciationRate, 3); - - // Loop through each period calculating the depreciation - $previousDepreciation = 0; - $depreciation = 0; - for ($per = 1; $per <= $period; ++$per) { - $depreciation = min(($cost - $previousDepreciation) * ($factor / $life), ($cost - $salvage - $previousDepreciation)); - $previousDepreciation += $depreciation; - } - - return $depreciation; - } - - return Functions::VALUE(); + return Depreciation::DDB($cost, $salvage, $life, $period, $factor); } /** @@ -926,6 +546,11 @@ class Financial * Excel Function: * DISC(settlement,maturity,price,redemption[,basis]) * + * @Deprecated 1.18.0 + * + * @see Financial\Securities\Rates::discount() + * Use the discount() method in the Financial\Securities\Rates class instead + * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue * date when the security is traded to the buyer. @@ -944,30 +569,7 @@ class Financial */ public static function DISC($settlement, $maturity, $price, $redemption, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $price = Functions::flattenSingleValue($price); - $redemption = Functions::flattenSingleValue($redemption); - $basis = Functions::flattenSingleValue($basis); - - // Validate - if ((is_numeric($price)) && (is_numeric($redemption)) && (is_numeric($basis))) { - $price = (float) $price; - $redemption = (float) $redemption; - $basis = (int) $basis; - if (($price <= 0) || ($redemption <= 0)) { - return Functions::NAN(); - } - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - - return (1 - $price / $redemption) / $daysBetweenSettlementAndMaturity; - } - - return Functions::VALUE(); + return Financial\Securities\Rates::discount($settlement, $maturity, $price, $redemption, $basis); } /** @@ -980,6 +582,11 @@ class Financial * Excel Function: * DOLLARDE(fractional_dollar,fraction) * + * @Deprecated 1.18.0 + * + * @see Financial\Dollar::decimal() + * Use the decimal() method in the Financial\Dollar class instead + * * @param float $fractional_dollar Fractional Dollar * @param int $fraction Fraction * @@ -987,23 +594,7 @@ class Financial */ public static function DOLLARDE($fractional_dollar = null, $fraction = 0) { - $fractional_dollar = Functions::flattenSingleValue($fractional_dollar); - $fraction = (int) Functions::flattenSingleValue($fraction); - - // Validate parameters - if ($fractional_dollar === null || $fraction < 0) { - return Functions::NAN(); - } - if ($fraction == 0) { - return Functions::DIV0(); - } - - $dollars = floor($fractional_dollar); - $cents = fmod($fractional_dollar, 1); - $cents /= $fraction; - $cents *= 10 ** ceil(log10($fraction)); - - return $dollars + $cents; + return Dollar::decimal($fractional_dollar, $fraction); } /** @@ -1016,6 +607,11 @@ class Financial * Excel Function: * DOLLARFR(decimal_dollar,fraction) * + * @Deprecated 1.18.0 + * + * @see Financial\Dollar::fractional() + * Use the fractional() method in the Financial\Dollar class instead + * * @param float $decimal_dollar Decimal Dollar * @param int $fraction Fraction * @@ -1023,23 +619,7 @@ class Financial */ public static function DOLLARFR($decimal_dollar = null, $fraction = 0) { - $decimal_dollar = Functions::flattenSingleValue($decimal_dollar); - $fraction = (int) Functions::flattenSingleValue($fraction); - - // Validate parameters - if ($decimal_dollar === null || $fraction < 0) { - return Functions::NAN(); - } - if ($fraction == 0) { - return Functions::DIV0(); - } - - $dollars = floor($decimal_dollar); - $cents = fmod($decimal_dollar, 1); - $cents *= $fraction; - $cents *= 10 ** (-ceil(log10($fraction))); - - return $dollars + $cents; + return Dollar::fractional($decimal_dollar, $fraction); } /** @@ -1051,22 +631,19 @@ class Financial * Excel Function: * EFFECT(nominal_rate,npery) * - * @param float $nominal_rate Nominal interest rate - * @param int $npery Number of compounding payments per year + * @Deprecated 1.18.0 + * + * @see Financial\InterestRate::effective() + * Use the effective() method in the Financial\InterestRate class instead + * + * @param float $nominalRate Nominal interest rate + * @param int $periodsPerYear Number of compounding payments per year * * @return float|string */ - public static function EFFECT($nominal_rate = 0, $npery = 0) + public static function EFFECT($nominalRate = 0, $periodsPerYear = 0) { - $nominal_rate = Functions::flattenSingleValue($nominal_rate); - $npery = (int) Functions::flattenSingleValue($npery); - - // Validate parameters - if ($nominal_rate <= 0 || $npery < 1) { - return Functions::NAN(); - } - - return (1 + $nominal_rate / $npery) ** $npery - 1; + return Financial\InterestRate::effective($nominalRate, $periodsPerYear); } /** @@ -1077,6 +654,11 @@ class Financial * Excel Function: * FV(rate,nper,pmt[,pv[,type]]) * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Constant\Periodic::futureValue() + * Use the futureValue() method in the Financial\CashFlow\Constant\Periodic class instead + * * @param float $rate The interest rate per period * @param int $nper Total number of payment periods in an annuity * @param float $pmt The payment made each period: it cannot change over the @@ -1092,23 +674,7 @@ class Financial */ public static function FV($rate = 0, $nper = 0, $pmt = 0, $pv = 0, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $nper = Functions::flattenSingleValue($nper); - $pmt = Functions::flattenSingleValue($pmt); - $pv = Functions::flattenSingleValue($pv); - $type = Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - - // Calculate - if ($rate !== null && $rate != 0) { - return -$pv * (1 + $rate) ** $nper - $pmt * (1 + $rate * $type) * ((1 + $rate) ** $nper - 1) / $rate; - } - - return -$pv - $pmt * $nper; + return Financial\CashFlow\Constant\Periodic::futureValue($rate, $nper, $pmt, $pv, $type); } /** @@ -1120,21 +686,19 @@ class Financial * Excel Function: * FVSCHEDULE(principal,schedule) * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Single::futureValue() + * Use the futureValue() method in the Financial\CashFlow\Single class instead + * * @param float $principal the present value * @param float[] $schedule an array of interest rates to apply * - * @return float + * @return float|string */ public static function FVSCHEDULE($principal, $schedule) { - $principal = Functions::flattenSingleValue($principal); - $schedule = Functions::flattenArray($schedule); - - foreach ($schedule as $rate) { - $principal *= 1 + $rate; - } - - return $principal; + return Financial\CashFlow\Single::futureValue($principal, $schedule); } /** @@ -1145,57 +709,46 @@ class Financial * Excel Function: * INTRATE(settlement,maturity,investment,redemption[,basis]) * + * @Deprecated 1.18.0 + * + * @see Financial\Securities\Rates::interest() + * Use the interest() method in the Financial\Securities\Rates class instead + * * @param mixed $settlement The security's settlement date. - * The security settlement date is the date after the issue date when the security is traded to the buyer. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param int $investment the amount invested in the security * @param int $redemption the amount to be received at maturity * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 * * @return float|string */ public static function INTRATE($settlement, $maturity, $investment, $redemption, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $investment = Functions::flattenSingleValue($investment); - $redemption = Functions::flattenSingleValue($redemption); - $basis = Functions::flattenSingleValue($basis); - - // Validate - if ((is_numeric($investment)) && (is_numeric($redemption)) && (is_numeric($basis))) { - $investment = (float) $investment; - $redemption = (float) $redemption; - $basis = (int) $basis; - if (($investment <= 0) || ($redemption <= 0)) { - return Functions::NAN(); - } - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - - return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity); - } - - return Functions::VALUE(); + return Financial\Securities\Rates::interest($settlement, $maturity, $investment, $redemption, $basis); } /** * IPMT. * - * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate. + * Returns the interest payment for a given period for an investment based on periodic, constant payments + * and a constant interest rate. * * Excel Function: * IPMT(rate,per,nper,pv[,fv][,type]) * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Constant\Periodic\Interest::payment() + * Use the payment() method in the Financial\CashFlow\Constant\Periodic class instead + * * @param float $rate Interest rate per period * @param int $per Period for which we want to find the interest * @param int $nper Number of periods @@ -1207,25 +760,7 @@ class Financial */ public static function IPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $per = (int) Functions::flattenSingleValue($per); - $nper = (int) Functions::flattenSingleValue($nper); - $pv = Functions::flattenSingleValue($pv); - $fv = Functions::flattenSingleValue($fv); - $type = (int) Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - if ($per <= 0 || $per > $nper) { - return Functions::VALUE(); - } - - // Calculate - $interestAndPrincipal = self::interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type); - - return $interestAndPrincipal[0]; + return Financial\CashFlow\Constant\Periodic\Interest::payment($rate, $per, $nper, $pv, $fv, $type); } /** @@ -1240,63 +775,22 @@ class Financial * Excel Function: * IRR(values[,guess]) * - * @param float[] $values An array or a reference to cells that contain numbers for which you want + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Variable\Periodic::rate() + * Use the rate() method in the Financial\CashFlow\Variable\Periodic class instead + * + * @param mixed $values An array or a reference to cells that contain numbers for which you want * to calculate the internal rate of return. * Values must contain at least one positive value and one negative value to * calculate the internal rate of return. - * @param float $guess A number that you guess is close to the result of IRR + * @param mixed $guess A number that you guess is close to the result of IRR * * @return float|string */ public static function IRR($values, $guess = 0.1) { - if (!is_array($values)) { - return Functions::VALUE(); - } - $values = Functions::flattenArray($values); - $guess = Functions::flattenSingleValue($guess); - - // create an initial range, with a root somewhere between 0 and guess - $x1 = 0.0; - $x2 = $guess; - $f1 = self::NPV($x1, $values); - $f2 = self::NPV($x2, $values); - for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { - if (($f1 * $f2) < 0.0) { - break; - } - if (abs($f1) < abs($f2)) { - $f1 = self::NPV($x1 += 1.6 * ($x1 - $x2), $values); - } else { - $f2 = self::NPV($x2 += 1.6 * ($x2 - $x1), $values); - } - } - if (($f1 * $f2) > 0.0) { - return Functions::VALUE(); - } - - $f = self::NPV($x1, $values); - if ($f < 0.0) { - $rtb = $x1; - $dx = $x2 - $x1; - } else { - $rtb = $x2; - $dx = $x1 - $x2; - } - - for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { - $dx *= 0.5; - $x_mid = $rtb + $dx; - $f_mid = self::NPV($x_mid, $values); - if ($f_mid <= 0.0) { - $rtb = $x_mid; - } - if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { - return $x_mid; - } - } - - return Functions::VALUE(); + return Financial\CashFlow\Variable\Periodic::rate($values, $guess); } /** @@ -1305,7 +799,12 @@ class Financial * Returns the interest payment for an investment based on an interest rate and a constant payment schedule. * * Excel Function: - * =ISPMT(interest_rate, period, number_payments, PV) + * =ISPMT(interest_rate, period, number_payments, pv) + * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Constant\Periodic\Interest::schedulePayment() + * Use the schedulePayment() method in the Financial\CashFlow\Constant\Periodic class instead * * interest_rate is the interest rate for the investment * @@ -1313,32 +812,11 @@ class Financial * * number_payments is the number of payments for the annuity * - * PV is the loan amount or present value of the payments + * pv is the loan amount or present value of the payments */ public static function ISPMT(...$args) { - // Return value - $returnValue = 0; - - // Get the parameters - $aArgs = Functions::flattenArray($args); - $interestRate = array_shift($aArgs); - $period = array_shift($aArgs); - $numberPeriods = array_shift($aArgs); - $principleRemaining = array_shift($aArgs); - - // Calculate - $principlePayment = ($principleRemaining * 1.0) / ($numberPeriods * 1.0); - for ($i = 0; $i <= $period; ++$i) { - $returnValue = $interestRate * $principleRemaining * -1; - $principleRemaining -= $principlePayment; - // principle needs to be 0 after the last payment, don't let floating point screw it up - if ($i == $numberPeriods) { - $returnValue = 0; - } - } - - return $returnValue; + return Financial\CashFlow\Constant\Periodic\Interest::schedulePayment(...$args); } /** @@ -1350,44 +828,22 @@ class Financial * Excel Function: * MIRR(values,finance_rate, reinvestment_rate) * - * @param float[] $values An array or a reference to cells that contain a series of payments and - * income occurring at regular intervals. - * Payments are negative value, income is positive values. - * @param float $finance_rate The interest rate you pay on the money used in the cash flows - * @param float $reinvestment_rate The interest rate you receive on the cash flows as you reinvest them + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Variable\Periodic::modifiedRate() + * Use the modifiedRate() method in the Financial\CashFlow\Variable\Periodic class instead + * + * @param mixed $values An array or a reference to cells that contain a series of payments and + * income occurring at regular intervals. + * Payments are negative value, income is positive values. + * @param mixed $finance_rate The interest rate you pay on the money used in the cash flows + * @param mixed $reinvestment_rate The interest rate you receive on the cash flows as you reinvest them * * @return float|string Result, or a string containing an error */ public static function MIRR($values, $finance_rate, $reinvestment_rate) { - if (!is_array($values)) { - return Functions::VALUE(); - } - $values = Functions::flattenArray($values); - $finance_rate = Functions::flattenSingleValue($finance_rate); - $reinvestment_rate = Functions::flattenSingleValue($reinvestment_rate); - $n = count($values); - - $rr = 1.0 + $reinvestment_rate; - $fr = 1.0 + $finance_rate; - - $npv_pos = $npv_neg = 0.0; - foreach ($values as $i => $v) { - if ($v >= 0) { - $npv_pos += $v / $rr ** $i; - } else { - $npv_neg += $v / $fr ** $i; - } - } - - if (($npv_neg == 0) || ($npv_pos == 0) || ($reinvestment_rate <= -1)) { - return Functions::VALUE(); - } - - $mirr = ((-$npv_pos * $rr ** $n) - / ($npv_neg * ($rr))) ** (1.0 / ($n - 1)) - 1.0; - - return is_finite($mirr) ? $mirr : Functions::VALUE(); + return Financial\CashFlow\Variable\Periodic::modifiedRate($values, $finance_rate, $reinvestment_rate); } /** @@ -1395,23 +851,22 @@ class Financial * * Returns the nominal interest rate given the effective rate and the number of compounding payments per year. * - * @param float $effect_rate Effective interest rate - * @param int $npery Number of compounding payments per year + * Excel Function: + * NOMINAL(effect_rate, npery) + * + * @Deprecated 1.18.0 + * + * @see Financial\InterestRate::nominal() + * Use the nominal() method in the Financial\InterestRate class instead + * + * @param float $effectiveRate Effective interest rate + * @param int $periodsPerYear Number of compounding payments per year * * @return float|string Result, or a string containing an error */ - public static function NOMINAL($effect_rate = 0, $npery = 0) + public static function NOMINAL($effectiveRate = 0, $periodsPerYear = 0) { - $effect_rate = Functions::flattenSingleValue($effect_rate); - $npery = (int) Functions::flattenSingleValue($npery); - - // Validate parameters - if ($effect_rate <= 0 || $npery < 1) { - return Functions::NAN(); - } - - // Calculate - return $npery * (($effect_rate + 1) ** (1 / $npery) - 1); + return InterestRate::nominal($effectiveRate, $periodsPerYear); } /** @@ -1419,6 +874,8 @@ class Financial * * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate. * + * @Deprecated 1.18.0 + * * @param float $rate Interest rate per period * @param int $pmt Periodic payment (annuity) * @param float $pv Present Value @@ -1426,33 +883,13 @@ class Financial * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period * * @return float|string Result, or a string containing an error + * + *@see Financial\CashFlow\Constant\Periodic::periods() + * Use the periods() method in the Financial\CashFlow\Constant\Periodic class instead */ public static function NPER($rate = 0, $pmt = 0, $pv = 0, $fv = 0, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $pmt = Functions::flattenSingleValue($pmt); - $pv = Functions::flattenSingleValue($pv); - $fv = Functions::flattenSingleValue($fv); - $type = Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - - // Calculate - if ($rate !== null && $rate != 0) { - if ($pmt == 0 && $pv == 0) { - return Functions::NAN(); - } - - return log(($pmt * (1 + $rate * $type) / $rate - $fv) / ($pv + $pmt * (1 + $rate * $type) / $rate)) / log(1 + $rate); - } - if ($pmt == 0) { - return Functions::NAN(); - } - - return (-$pv - $fv) / $pmt; + return Financial\CashFlow\Constant\Periodic::periods($rate, $pmt, $pv, $fv, $type); } /** @@ -1460,28 +897,16 @@ class Financial * * Returns the Net Present Value of a cash flow series given a discount rate. * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Variable\Periodic::presentValue() + * Use the presentValue() method in the Financial\CashFlow\Variable\Periodic class instead + * * @return float */ public static function NPV(...$args) { - // Return value - $returnValue = 0; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - - // Calculate - $rate = array_shift($aArgs); - $countArgs = count($aArgs); - for ($i = 1; $i <= $countArgs; ++$i) { - // Is it a numeric value? - if (is_numeric($aArgs[$i - 1])) { - $returnValue += $aArgs[$i - 1] / (1 + $rate) ** $i; - } - } - - // Return - return $returnValue; + return Financial\CashFlow\Variable\Periodic::presentValue(...$args); } /** @@ -1489,6 +914,11 @@ class Financial * * Calculates the number of periods required for an investment to reach a specified value. * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Single::periods() + * Use the periods() method in the Financial\CashFlow\Single class instead + * * @param float $rate Interest rate per period * @param float $pv Present Value * @param float $fv Future Value @@ -1497,18 +927,7 @@ class Financial */ public static function PDURATION($rate = 0, $pv = 0, $fv = 0) { - $rate = Functions::flattenSingleValue($rate); - $pv = Functions::flattenSingleValue($pv); - $fv = Functions::flattenSingleValue($fv); - - // Validate parameters - if (!is_numeric($rate) || !is_numeric($pv) || !is_numeric($fv)) { - return Functions::VALUE(); - } elseif ($rate <= 0.0 || $pv <= 0.0 || $fv <= 0.0) { - return Functions::NAN(); - } - - return (log($fv) - log($pv)) / log(1 + $rate); + return Financial\CashFlow\Single::periods($rate, $pv, $fv); } /** @@ -1516,6 +935,11 @@ class Financial * * Returns the constant payment (annuity) for a cash flow with a constant interest rate. * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Constant\Periodic\Payments::annuity() + * Use the annuity() method in the Financial\CashFlow\Constant\Periodic\Payments class instead + * * @param float $rate Interest rate per period * @param int $nper Number of periods * @param float $pv Present Value @@ -1526,29 +950,19 @@ class Financial */ public static function PMT($rate = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $nper = Functions::flattenSingleValue($nper); - $pv = Functions::flattenSingleValue($pv); - $fv = Functions::flattenSingleValue($fv); - $type = Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - - // Calculate - if ($rate !== null && $rate != 0) { - return (-$fv - $pv * (1 + $rate) ** $nper) / (1 + $rate * $type) / (((1 + $rate) ** $nper - 1) / $rate); - } - - return (-$pv - $fv) / $nper; + return Financial\CashFlow\Constant\Periodic\Payments::annuity($rate, $nper, $pv, $fv, $type); } /** * PPMT. * - * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate. + * Returns the interest payment for a given period for an investment based on periodic, constant payments + * and a constant interest rate. + * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Constant\Periodic\Payments::interestPayment() + * Use the interestPayment() method in the Financial\CashFlow\Constant\Periodic\Payments class instead * * @param float $rate Interest rate per period * @param int $per Period for which we want to find the interest @@ -1561,100 +975,43 @@ class Financial */ public static function PPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $per = (int) Functions::flattenSingleValue($per); - $nper = (int) Functions::flattenSingleValue($nper); - $pv = Functions::flattenSingleValue($pv); - $fv = Functions::flattenSingleValue($fv); - $type = (int) Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - if ($per <= 0 || $per > $nper) { - return Functions::VALUE(); - } - - // Calculate - $interestAndPrincipal = self::interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type); - - return $interestAndPrincipal[1]; - } - - private static function validatePrice($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis) - { - if (is_string($settlement)) { - return Functions::VALUE(); - } - if (is_string($maturity)) { - return Functions::VALUE(); - } - if (!is_numeric($rate)) { - return Functions::VALUE(); - } - if (!is_numeric($yield)) { - return Functions::VALUE(); - } - if (!is_numeric($redemption)) { - return Functions::VALUE(); - } - if (!is_numeric($frequency)) { - return Functions::VALUE(); - } - if (!is_numeric($basis)) { - return Functions::VALUE(); - } - - return ''; + return Financial\CashFlow\Constant\Periodic\Payments::interestPayment($rate, $per, $nper, $pv, $fv, $type); } + /** + * PRICE. + * + * Returns the price per $100 face value of a security that pays periodic interest. + * + * @Deprecated 1.18.0 + * + * @see Financial\Securities\Price::price() + * Use the price() method in the Financial\Securities\Price class instead + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param float $rate the security's annual coupon rate + * @param float $yield the security's annual yield + * @param float $redemption The number of coupon payments per year. + * For annual payments, frequency = 1; + * for semiannual, frequency = 2; + * for quarterly, frequency = 4. + * @param int $frequency + * @param int $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ public static function PRICE($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $rate = Functions::flattenSingleValue($rate); - $yield = Functions::flattenSingleValue($yield); - $redemption = Functions::flattenSingleValue($redemption); - $frequency = Functions::flattenSingleValue($frequency); - $basis = Functions::flattenSingleValue($basis); - - $settlement = DateTime::getDateValue($settlement); - $maturity = DateTime::getDateValue($maturity); - $rslt = self::validatePrice($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis); - if ($rslt) { - return $rslt; - } - $rate = (float) $rate; - $yield = (float) $yield; - $redemption = (float) $redemption; - $frequency = (int) $frequency; - $basis = (int) $basis; - - if ( - ($settlement > $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4)) - ) { - return Functions::NAN(); - } - - $dsc = self::COUPDAYSNC($settlement, $maturity, $frequency, $basis); - $e = self::COUPDAYS($settlement, $maturity, $frequency, $basis); - $n = self::COUPNUM($settlement, $maturity, $frequency, $basis); - $a = self::COUPDAYBS($settlement, $maturity, $frequency, $basis); - - $baseYF = 1.0 + ($yield / $frequency); - $rfp = 100 * ($rate / $frequency); - $de = $dsc / $e; - - $result = $redemption / $baseYF ** (--$n + $de); - for ($k = 0; $k <= $n; ++$k) { - $result += $rfp / ($baseYF ** ($k + $de)); - } - $result -= $rfp * ($a / $e); - - return $result; + return Securities\Price::price($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis); } /** @@ -1662,10 +1019,16 @@ class Financial * * Returns the price per $100 face value of a discounted security. * + * @Deprecated 1.18.0 + * + * @see Financial\Securities\Price::priceDiscounted() + * Use the priceDiscounted() method in the Financial\Securities\Price class instead + * * @param mixed $settlement The security's settlement date. - * The security settlement date is the date after the issue date when the security is traded to the buyer. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param int $discount The security's discount rate * @param int $redemption The security's redemption value per $100 face value * @param int $basis The type of day count to use. @@ -1679,27 +1042,7 @@ class Financial */ public static function PRICEDISC($settlement, $maturity, $discount, $redemption, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $discount = (float) Functions::flattenSingleValue($discount); - $redemption = (float) Functions::flattenSingleValue($redemption); - $basis = (int) Functions::flattenSingleValue($basis); - - // Validate - if ((is_numeric($discount)) && (is_numeric($redemption)) && (is_numeric($basis))) { - if (($discount <= 0) || ($redemption <= 0)) { - return Functions::NAN(); - } - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - - return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity); - } - - return Functions::VALUE(); + return Securities\Price::priceDiscounted($settlement, $maturity, $discount, $redemption, $basis); } /** @@ -1707,10 +1050,16 @@ class Financial * * Returns the price per $100 face value of a security that pays interest at maturity. * + * @Deprecated 1.18.0 + * + * @see Financial\Securities\Price::priceAtMaturity() + * Use the priceAtMaturity() method in the Financial\Securities\Price class instead + * * @param mixed $settlement The security's settlement date. - * The security's settlement date is the date after the issue date when the security is traded to the buyer. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param mixed $issue The security's issue date * @param int $rate The security's interest rate at date of issue * @param int $yield The security's annual yield @@ -1725,47 +1074,7 @@ class Financial */ public static function PRICEMAT($settlement, $maturity, $issue, $rate, $yield, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $issue = Functions::flattenSingleValue($issue); - $rate = Functions::flattenSingleValue($rate); - $yield = Functions::flattenSingleValue($yield); - $basis = (int) Functions::flattenSingleValue($basis); - - // Validate - if (is_numeric($rate) && is_numeric($yield)) { - if (($rate <= 0) || ($yield <= 0)) { - return Functions::NAN(); - } - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - if (!is_numeric($daysPerYear)) { - return $daysPerYear; - } - $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis); - if (!is_numeric($daysBetweenIssueAndSettlement)) { - // return date error - return $daysBetweenIssueAndSettlement; - } - $daysBetweenIssueAndSettlement *= $daysPerYear; - $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis); - if (!is_numeric($daysBetweenIssueAndMaturity)) { - // return date error - return $daysBetweenIssueAndMaturity; - } - $daysBetweenIssueAndMaturity *= $daysPerYear; - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - $daysBetweenSettlementAndMaturity *= $daysPerYear; - - return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) / - (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) - - (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100); - } - - return Functions::VALUE(); + return Securities\Price::priceAtMaturity($settlement, $maturity, $issue, $rate, $yield, $basis); } /** @@ -1773,6 +1082,11 @@ class Financial * * Returns the Present Value of a cash flow with constant payments and interest rate (annuities). * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Constant\Periodic::presentValue() + * Use the presentValue() method in the Financial\CashFlow\Constant\Periodic class instead + * * @param float $rate Interest rate per period * @param int $nper Number of periods * @param float $pmt Periodic payment (annuity) @@ -1783,23 +1097,7 @@ class Financial */ public static function PV($rate = 0, $nper = 0, $pmt = 0, $fv = 0, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $nper = Functions::flattenSingleValue($nper); - $pmt = Functions::flattenSingleValue($pmt); - $fv = Functions::flattenSingleValue($fv); - $type = Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - - // Calculate - if ($rate !== null && $rate != 0) { - return (-$pmt * (1 + $rate * $type) * (((1 + $rate) ** $nper - 1) / $rate) - $fv) / (1 + $rate) ** $nper; - } - - return -$fv - $pmt * $nper; + return Financial\CashFlow\Constant\Periodic::presentValue($rate, $nper, $pmt, $fv, $type); } /** @@ -1813,112 +1111,63 @@ class Financial * Excel Function: * RATE(nper,pmt,pv[,fv[,type[,guess]]]) * - * @param float $nper The total number of payment periods in an annuity - * @param float $pmt The payment made each period and cannot change over the life + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Constant\Periodic\Interest::rate() + * Use the rate() method in the Financial\CashFlow\Constant\Periodic class instead + * + * @param mixed $nper The total number of payment periods in an annuity + * @param mixed $pmt The payment made each period and cannot change over the life * of the annuity. * Typically, pmt includes principal and interest but no other * fees or taxes. - * @param float $pv The present value - the total amount that a series of future + * @param mixed $pv The present value - the total amount that a series of future * payments is worth now - * @param float $fv The future value, or a cash balance you want to attain after + * @param mixed $fv The future value, or a cash balance you want to attain after * the last payment is made. If fv is omitted, it is assumed * to be 0 (the future value of a loan, for example, is 0). - * @param int $type A number 0 or 1 and indicates when payments are due: + * @param mixed $type A number 0 or 1 and indicates when payments are due: * 0 or omitted At the end of the period. * 1 At the beginning of the period. - * @param float $guess Your guess for what the rate will be. + * @param mixed $guess Your guess for what the rate will be. * If you omit guess, it is assumed to be 10 percent. * * @return float|string */ public static function RATE($nper, $pmt, $pv, $fv = 0.0, $type = 0, $guess = 0.1) { - $nper = (int) Functions::flattenSingleValue($nper); - $pmt = Functions::flattenSingleValue($pmt); - $pv = Functions::flattenSingleValue($pv); - $fv = ($fv === null) ? 0.0 : Functions::flattenSingleValue($fv); - $type = ($type === null) ? 0 : (int) Functions::flattenSingleValue($type); - $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess); - - $rate = $guess; - // rest of code adapted from python/numpy - $close = false; - $iter = 0; - while (!$close && $iter < self::FINANCIAL_MAX_ITERATIONS) { - $nextdiff = self::rateNextGuess($rate, $nper, $pmt, $pv, $fv, $type); - if (!is_numeric($nextdiff)) { - break; - } - $rate1 = $rate - $nextdiff; - $close = abs($rate1 - $rate) < self::FINANCIAL_PRECISION; - ++$iter; - $rate = $rate1; - } - - return $close ? $rate : Functions::NAN(); - } - - private static function rateNextGuess($rate, $nper, $pmt, $pv, $fv, $type) - { - if ($rate == 0) { - return Functions::NAN(); - } - $tt1 = ($rate + 1) ** $nper; - $tt2 = ($rate + 1) ** ($nper - 1); - $numerator = $fv + $tt1 * $pv + $pmt * ($tt1 - 1) * ($rate * $type + 1) / $rate; - $denominator = $nper * $tt2 * $pv - $pmt * ($tt1 - 1) * ($rate * $type + 1) / ($rate * $rate) - + $nper * $pmt * $tt2 * ($rate * $type + 1) / $rate - + $pmt * ($tt1 - 1) * $type / $rate; - if ($denominator == 0) { - return Functions::NAN(); - } - - return $numerator / $denominator; + return Financial\CashFlow\Constant\Periodic\Interest::rate($nper, $pmt, $pv, $fv, $type, $guess); } /** * RECEIVED. * - * Returns the price per $100 face value of a discounted security. + * Returns the amount received at maturity for a fully invested Security. + * + * @Deprecated 1.18.0 + * + * @see Financial\Securities\Price::received() + * Use the received() method in the Financial\Securities\Price class instead * * @param mixed $settlement The security's settlement date. - * The security settlement date is the date after the issue date when the security is traded to the buyer. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. - * @param int $investment The amount invested in the security - * @param int $discount The security's discount rate - * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * The maturity date is the date when the security expires. + * @param mixed $investment The amount invested in the security + * @param mixed $discount The security's discount rate + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 * * @return float|string Result, or a string containing an error */ public static function RECEIVED($settlement, $maturity, $investment, $discount, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $investment = (float) Functions::flattenSingleValue($investment); - $discount = (float) Functions::flattenSingleValue($discount); - $basis = (int) Functions::flattenSingleValue($basis); - - // Validate - if ((is_numeric($investment)) && (is_numeric($discount)) && (is_numeric($basis))) { - if (($investment <= 0) || ($discount <= 0)) { - return Functions::NAN(); - } - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - - return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity)); - } - - return Functions::VALUE(); + return Financial\Securities\Price::received($settlement, $maturity, $investment, $discount, $basis); } /** @@ -1926,6 +1175,11 @@ class Financial * * Calculates the interest rate required for an investment to grow to a specified future value . * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Single::interestRate() + * Use the interestRate() method in the Financial\CashFlow\Single class instead + * * @param float $nper The number of periods over which the investment is made * @param float $pv Present Value * @param float $fv Future Value @@ -1934,18 +1188,7 @@ class Financial */ public static function RRI($nper = 0, $pv = 0, $fv = 0) { - $nper = Functions::flattenSingleValue($nper); - $pv = Functions::flattenSingleValue($pv); - $fv = Functions::flattenSingleValue($fv); - - // Validate parameters - if (!is_numeric($nper) || !is_numeric($pv) || !is_numeric($fv)) { - return Functions::VALUE(); - } elseif ($nper <= 0.0 || $pv <= 0.0 || $fv < 0.0) { - return Functions::NAN(); - } - - return ($fv / $pv) ** (1 / $nper) - 1; + return Financial\CashFlow\Single::interestRate($nper, $pv, $fv); } /** @@ -1953,6 +1196,11 @@ class Financial * * Returns the straight-line depreciation of an asset for one period * + * @Deprecated 1.18.0 + * + * @see Financial\Depreciation::SLN() + * Use the SLN() method in the Financial\Depreciation class instead + * * @param mixed $cost Initial cost of the asset * @param mixed $salvage Value at the end of the depreciation * @param mixed $life Number of periods over which the asset is depreciated @@ -1961,20 +1209,7 @@ class Financial */ public static function SLN($cost, $salvage, $life) { - $cost = Functions::flattenSingleValue($cost); - $salvage = Functions::flattenSingleValue($salvage); - $life = Functions::flattenSingleValue($life); - - // Calculate - if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life))) { - if ($life < 0) { - return Functions::NAN(); - } - - return ($cost - $salvage) / $life; - } - - return Functions::VALUE(); + return Depreciation::SLN($cost, $salvage, $life); } /** @@ -1982,6 +1217,11 @@ class Financial * * Returns the sum-of-years' digits depreciation of an asset for a specified period. * + * @Deprecated 1.18.0 + * + * @see Financial\Depreciation::SYD() + * Use the SYD() method in the Financial\Depreciation class instead + * * @param mixed $cost Initial cost of the asset * @param mixed $salvage Value at the end of the depreciation * @param mixed $life Number of periods over which the asset is depreciated @@ -1991,21 +1231,7 @@ class Financial */ public static function SYD($cost, $salvage, $life, $period) { - $cost = Functions::flattenSingleValue($cost); - $salvage = Functions::flattenSingleValue($salvage); - $life = Functions::flattenSingleValue($life); - $period = Functions::flattenSingleValue($period); - - // Calculate - if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period))) { - if (($life < 1) || ($period > $life)) { - return Functions::NAN(); - } - - return (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1)); - } - - return Functions::VALUE(); + return Depreciation::SYD($cost, $salvage, $life, $period); } /** @@ -2013,8 +1239,14 @@ class Financial * * Returns the bond-equivalent yield for a Treasury bill. * + * @Deprecated 1.18.0 + * + * @see Financial\TreasuryBill::bondEquivalentYield() + * Use the bondEquivalentYield() method in the Financial\TreasuryBill class instead + * * @param mixed $settlement The Treasury bill's settlement date. - * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer. + * The Treasury bill's settlement date is the date after the issue date when the + * Treasury bill is traded to the buyer. * @param mixed $maturity The Treasury bill's maturity date. * The maturity date is the date when the Treasury bill expires. * @param int $discount The Treasury bill's discount rate @@ -2023,37 +1255,22 @@ class Financial */ public static function TBILLEQ($settlement, $maturity, $discount) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $discount = Functions::flattenSingleValue($discount); - - // Use TBILLPRICE for validation - $testValue = self::TBILLPRICE($settlement, $maturity, $discount); - if (is_string($testValue)) { - return $testValue; - } - - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - ++$maturity; - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360; - } else { - $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement)); - } - - return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity); + return TreasuryBill::bondEquivalentYield($settlement, $maturity, $discount); } /** * TBILLPRICE. * - * Returns the yield for a Treasury bill. + * Returns the price per $100 face value for a Treasury bill. + * + * @Deprecated 1.18.0 + * + * @see Financial\TreasuryBill::price() + * Use the price() method in the Financial\TreasuryBill class instead * * @param mixed $settlement The Treasury bill's settlement date. - * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer. + * The Treasury bill's settlement date is the date after the issue date + * when the Treasury bill is traded to the buyer. * @param mixed $maturity The Treasury bill's maturity date. * The maturity date is the date when the Treasury bill expires. * @param int $discount The Treasury bill's discount rate @@ -2062,44 +1279,7 @@ class Financial */ public static function TBILLPRICE($settlement, $maturity, $discount) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $discount = Functions::flattenSingleValue($discount); - - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - // Validate - if (is_numeric($discount)) { - if ($discount <= 0) { - return Functions::NAN(); - } - - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - ++$maturity; - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360; - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - } else { - $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement)); - } - - if ($daysBetweenSettlementAndMaturity > 360) { - return Functions::NAN(); - } - - $price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360)); - if ($price <= 0) { - return Functions::NAN(); - } - - return $price; - } - - return Functions::VALUE(); + return TreasuryBill::price($settlement, $maturity, $discount); } /** @@ -2107,8 +1287,14 @@ class Financial * * Returns the yield for a Treasury bill. * + * @Deprecated 1.18.0 + * + * @see Financial\TreasuryBill::yield() + * Use the yield() method in the Financial\TreasuryBill class instead + * * @param mixed $settlement The Treasury bill's settlement date. - * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer. + * The Treasury bill's settlement date is the date after the issue date + * when the Treasury bill is traded to the buyer. * @param mixed $maturity The Treasury bill's maturity date. * The maturity date is the date when the Treasury bill expires. * @param int $price The Treasury bill's price per $100 face value @@ -2117,113 +1303,7 @@ class Financial */ public static function TBILLYIELD($settlement, $maturity, $price) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $price = Functions::flattenSingleValue($price); - - // Validate - if (is_numeric($price)) { - if ($price <= 0) { - return Functions::NAN(); - } - - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - ++$maturity; - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360; - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - } else { - $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement)); - } - - if ($daysBetweenSettlementAndMaturity > 360) { - return Functions::NAN(); - } - - return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity); - } - - return Functions::VALUE(); - } - - private static function bothNegAndPos($neg, $pos) - { - return $neg && $pos; - } - - private static function xirrPart2(&$values) - { - $valCount = count($values); - $foundpos = false; - $foundneg = false; - for ($i = 0; $i < $valCount; ++$i) { - $fld = $values[$i]; - if (!is_numeric($fld)) { - return Functions::VALUE(); - } elseif ($fld > 0) { - $foundpos = true; - } elseif ($fld < 0) { - $foundneg = true; - } - } - if (!self::bothNegAndPos($foundneg, $foundpos)) { - return Functions::NAN(); - } - - return ''; - } - - private static function xirrPart1(&$values, &$dates) - { - if ((!is_array($values)) && (!is_array($dates))) { - return Functions::NA(); - } - $values = Functions::flattenArray($values); - $dates = Functions::flattenArray($dates); - if (count($values) != count($dates)) { - return Functions::NAN(); - } - - $datesCount = count($dates); - for ($i = 0; $i < $datesCount; ++$i) { - $dates[$i] = DateTime::getDateValue($dates[$i]); - if (!is_numeric($dates[$i])) { - return Functions::VALUE(); - } - } - - return self::xirrPart2($values); - } - - private static function xirrPart3($values, $dates, $x1, $x2) - { - $f = self::xnpvOrdered($x1, $values, $dates, false); - if ($f < 0.0) { - $rtb = $x1; - $dx = $x2 - $x1; - } else { - $rtb = $x2; - $dx = $x1 - $x2; - } - - $rslt = Functions::VALUE(); - for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { - $dx *= 0.5; - $x_mid = $rtb + $dx; - $f_mid = self::xnpvOrdered($x_mid, $values, $dates, false); - if ($f_mid <= 0.0) { - $rtb = $x_mid; - } - if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { - $rslt = $x_mid; - - break; - } - } - - return $rslt; + return TreasuryBill::yield($settlement, $maturity, $price); } /** @@ -2234,6 +1314,11 @@ class Financial * Excel Function: * =XIRR(values,dates,guess) * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Variable\NonPeriodic::rate() + * Use the rate() method in the Financial\CashFlow\Variable\NonPeriodic class instead + * * @param float[] $values A series of cash flow payments * The series of values must contain at least one positive value & one negative value * @param mixed[] $dates A series of payment dates @@ -2245,37 +1330,7 @@ class Financial */ public static function XIRR($values, $dates, $guess = 0.1) { - $rslt = self::xirrPart1($values, $dates); - if ($rslt) { - return $rslt; - } - - // create an initial range, with a root somewhere between 0 and guess - $guess = Functions::flattenSingleValue($guess); - $x1 = 0.0; - $x2 = $guess ? $guess : 0.1; - $f1 = self::xnpvOrdered($x1, $values, $dates, false); - $f2 = self::xnpvOrdered($x2, $values, $dates, false); - $found = false; - for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { - if (!is_numeric($f1) || !is_numeric($f2)) { - break; - } - if (($f1 * $f2) < 0.0) { - $found = true; - - break; - } elseif (abs($f1) < abs($f2)) { - $f1 = self::xnpvOrdered($x1 += 1.6 * ($x1 - $x2), $values, $dates, false); - } else { - $f2 = self::xnpvOrdered($x2 += 1.6 * ($x2 - $x1), $values, $dates, false); - } - } - if (!$found) { - return Functions::NAN(); - } - - return self::xirrPart3($values, $dates, $x1, $x2); + return Financial\CashFlow\Variable\NonPeriodic::rate($values, $dates, $guess); } /** @@ -2287,74 +1342,27 @@ class Financial * Excel Function: * =XNPV(rate,values,dates) * + * @Deprecated 1.18.0 + * + * @see Financial\CashFlow\Variable\NonPeriodic::presentValue() + * Use the presentValue() method in the Financial\CashFlow\Variable\NonPeriodic class instead + * * @param float $rate the discount rate to apply to the cash flows - * @param float[] $values A series of cash flows that corresponds to a schedule of payments in dates. - * The first payment is optional and corresponds to a cost or payment that occurs at the beginning of the investment. - * If the first value is a cost or payment, it must be a negative value. All succeeding payments are discounted based on a 365-day year. - * The series of values must contain at least one positive value and one negative value. - * @param mixed[] $dates A schedule of payment dates that corresponds to the cash flow payments. - * The first payment date indicates the beginning of the schedule of payments. - * All other dates must be later than this date, but they may occur in any order. + * @param float[] $values A series of cash flows that corresponds to a schedule of payments in dates. + * The first payment is optional and corresponds to a cost or payment that occurs + * at the beginning of the investment. + * If the first value is a cost or payment, it must be a negative value. + * All succeeding payments are discounted based on a 365-day year. + * The series of values must contain at least one positive value and one negative value. + * @param mixed[] $dates A schedule of payment dates that corresponds to the cash flow payments. + * The first payment date indicates the beginning of the schedule of payments. + * All other dates must be later than this date, but they may occur in any order. * * @return float|mixed|string */ public static function XNPV($rate, $values, $dates) { - return self::xnpvOrdered($rate, $values, $dates, true); - } - - private static function validateXnpv($rate, $values, $dates) - { - if (!is_numeric($rate)) { - return Functions::VALUE(); - } - $valCount = count($values); - if ($valCount != count($dates)) { - return Functions::NAN(); - } - if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) { - return Functions::NAN(); - } - $date0 = DateTime::getDateValue($dates[0]); - if (is_string($date0)) { - return Functions::VALUE(); - } - - return ''; - } - - private static function xnpvOrdered($rate, $values, $dates, $ordered = true) - { - $rate = Functions::flattenSingleValue($rate); - $values = Functions::flattenArray($values); - $dates = Functions::flattenArray($dates); - $valCount = count($values); - $date0 = DateTime::getDateValue($dates[0]); - $rslt = self::validateXnpv($rate, $values, $dates); - if ($rslt) { - return $rslt; - } - $xnpv = 0.0; - for ($i = 0; $i < $valCount; ++$i) { - if (!is_numeric($values[$i])) { - return Functions::VALUE(); - } - $datei = DateTime::getDateValue($dates[$i]); - if (is_string($datei)) { - return Functions::VALUE(); - } - if ($date0 > $datei) { - $dif = $ordered ? Functions::NAN() : -DateTime::DATEDIF($datei, $date0, 'd'); - } else { - $dif = DateTime::DATEDIF($date0, $datei, 'd'); - } - if (!is_numeric($dif)) { - return $dif; - } - $xnpv += $values[$i] / (1 + $rate) ** ($dif / 365); - } - - return is_finite($xnpv) ? $xnpv : Functions::VALUE(); + return Financial\CashFlow\Variable\NonPeriodic::presentValue($rate, $values, $dates); } /** @@ -2362,10 +1370,16 @@ class Financial * * Returns the annual yield of a security that pays interest at maturity. * + * @Deprecated 1.18.0 + * + * @see Financial\Securities\Yields::yieldDiscounted() + * Use the yieldDiscounted() method in the Financial\Securities\Yields class instead + * * @param mixed $settlement The security's settlement date. - * The security's settlement date is the date after the issue date when the security is traded to the buyer. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param int $price The security's price per $100 face value * @param int $redemption The security's redemption value per $100 face value * @param int $basis The type of day count to use. @@ -2379,32 +1393,7 @@ class Financial */ public static function YIELDDISC($settlement, $maturity, $price, $redemption, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $price = Functions::flattenSingleValue($price); - $redemption = Functions::flattenSingleValue($redemption); - $basis = (int) Functions::flattenSingleValue($basis); - - // Validate - if (is_numeric($price) && is_numeric($redemption)) { - if (($price <= 0) || ($redemption <= 0)) { - return Functions::NAN(); - } - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - if (!is_numeric($daysPerYear)) { - return $daysPerYear; - } - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - $daysBetweenSettlementAndMaturity *= $daysPerYear; - - return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity); - } - - return Functions::VALUE(); + return Securities\Yields::yieldDiscounted($settlement, $maturity, $price, $redemption, $basis); } /** @@ -2412,64 +1401,30 @@ class Financial * * Returns the annual yield of a security that pays interest at maturity. * + * @Deprecated 1.18.0 + * + * @see Financial\Securities\Yields::yieldAtMaturity() + * Use the yieldAtMaturity() method in the Financial\Securities\Yields class instead + * * @param mixed $settlement The security's settlement date. - * The security's settlement date is the date after the issue date when the security is traded to the buyer. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param mixed $issue The security's issue date * @param int $rate The security's interest rate at date of issue * @param int $price The security's price per $100 face value * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 * * @return float|string Result, or a string containing an error */ public static function YIELDMAT($settlement, $maturity, $issue, $rate, $price, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $issue = Functions::flattenSingleValue($issue); - $rate = Functions::flattenSingleValue($rate); - $price = Functions::flattenSingleValue($price); - $basis = (int) Functions::flattenSingleValue($basis); - - // Validate - if (is_numeric($rate) && is_numeric($price)) { - if (($rate <= 0) || ($price <= 0)) { - return Functions::NAN(); - } - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - if (!is_numeric($daysPerYear)) { - return $daysPerYear; - } - $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis); - if (!is_numeric($daysBetweenIssueAndSettlement)) { - // return date error - return $daysBetweenIssueAndSettlement; - } - $daysBetweenIssueAndSettlement *= $daysPerYear; - $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis); - if (!is_numeric($daysBetweenIssueAndMaturity)) { - // return date error - return $daysBetweenIssueAndMaturity; - } - $daysBetweenIssueAndMaturity *= $daysPerYear; - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - $daysBetweenSettlementAndMaturity *= $daysPerYear; - - return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) / - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) * - ($daysPerYear / $daysBetweenSettlementAndMaturity); - } - - return Functions::VALUE(); + return Securities\Yields::yieldAtMaturity($settlement, $maturity, $issue, $rate, $price, $basis); } } diff --git a/src/PhpSpreadsheet/Calculation/Financial/Amortization.php b/src/PhpSpreadsheet/Calculation/Financial/Amortization.php new file mode 100644 index 00000000..2ea0f4fe --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Amortization.php @@ -0,0 +1,210 @@ +getMessage(); + } + + $yearFrac = DateTimeExcel\YearFrac::funcYearFrac($purchased, $firstPeriod, $basis); + if (is_string($yearFrac)) { + return $yearFrac; + } + + $amortiseCoeff = self::getAmortizationCoefficient($rate); + + $rate *= $amortiseCoeff; + $fNRate = round($yearFrac * $rate * $cost, 0); + $cost -= $fNRate; + $fRest = $cost - $salvage; + + for ($n = 0; $n < $period; ++$n) { + $fNRate = round($rate * $cost, 0); + $fRest -= $fNRate; + + if ($fRest < 0.0) { + switch ($period - $n) { + case 0: + case 1: + return round($cost * 0.5, 0); + default: + return 0.0; + } + } + $cost -= $fNRate; + } + + return $fNRate; + } + + /** + * AMORLINC. + * + * Returns the depreciation for each accounting period. + * This function is provided for the French accounting system. If an asset is purchased in + * the middle of the accounting period, the prorated depreciation is taken into account. + * + * Excel Function: + * AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis]) + * + * @param mixed $cost The cost of the asset as a float + * @param mixed $purchased Date of the purchase of the asset + * @param mixed $firstPeriod Date of the end of the first period + * @param mixed $salvage The salvage value at the end of the life of the asset + * @param mixed $period The period as a float + * @param mixed $rate Rate of depreciation as float + * @param mixed $basis Integer indicating the type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string (string containing the error type if there is an error) + */ + public static function AMORLINC( + $cost, + $purchased, + $firstPeriod, + $salvage, + $period, + $rate, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $cost = Functions::flattenSingleValue($cost); + $purchased = Functions::flattenSingleValue($purchased); + $firstPeriod = Functions::flattenSingleValue($firstPeriod); + $salvage = Functions::flattenSingleValue($salvage); + $period = Functions::flattenSingleValue($period); + $rate = Functions::flattenSingleValue($rate); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $cost = FinancialValidations::validateFloat($cost); + $purchased = FinancialValidations::validateDate($purchased); + $firstPeriod = FinancialValidations::validateDate($firstPeriod); + $salvage = FinancialValidations::validateFloat($salvage); + $period = FinancialValidations::validateFloat($period); + $rate = FinancialValidations::validateFloat($rate); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $fOneRate = $cost * $rate; + $fCostDelta = $cost - $salvage; + // Note, quirky variation for leap years on the YEARFRAC for this function + $purchasedYear = DateTimeExcel\Year::funcYear($purchased); + $yearFrac = DateTimeExcel\YearFrac::funcYearFrac($purchased, $firstPeriod, $basis); + if (is_string($yearFrac)) { + return $yearFrac; + } + + if ( + ($basis == FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) && + ($yearFrac < 1) && (DateTimeExcel\Helpers::isLeapYear($purchasedYear)) + ) { + $yearFrac *= 365 / 366; + } + + $f0Rate = $yearFrac * $rate * $cost; + $nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate); + + if ($period == 0) { + return $f0Rate; + } elseif ($period <= $nNumOfFullPeriods) { + return $fOneRate; + } elseif ($period == ($nNumOfFullPeriods + 1)) { + return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate; + } + + return 0.0; + } + + private static function getAmortizationCoefficient(float $rate): float + { + // The depreciation coefficients are: + // Life of assets (1/rate) Depreciation coefficient + // Less than 3 years 1 + // Between 3 and 4 years 1.5 + // Between 5 and 6 years 2 + // More than 6 years 2.5 + $fUsePer = 1.0 / $rate; + + if ($fUsePer < 3.0) { + return 1.0; + } elseif ($fUsePer < 4.0) { + return 1.5; + } elseif ($fUsePer <= 6.0) { + return 2.0; + } + + return 2.5; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php new file mode 100644 index 00000000..fde68860 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php @@ -0,0 +1,56 @@ +getMessage(); + } + + return self::calculateFutureValue($rate, $numberOfPeriods, $payment, $presentValue, $type); + } + + /** + * PV. + * + * Returns the Present Value of a cash flow with constant payments and interest rate (annuities). + * + * @param mixed $rate Interest rate per period + * @param mixed $numberOfPeriods Number of periods as an integer + * @param mixed $payment Periodic payment (annuity) + * @param mixed $futureValue Future Value + * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period + * + * @return float|string Result, or a string containing an error + */ + public static function presentValue( + $rate, + $numberOfPeriods, + $payment = 0.0, + $futureValue = 0.0, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD + ) { + $rate = Functions::flattenSingleValue($rate); + $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); + $payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment); + $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + + try { + $rate = CashFlowValidations::validateRate($rate); + $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $payment = CashFlowValidations::validateFloat($payment); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + $type = CashFlowValidations::validatePeriodType($type); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($numberOfPeriods < 0) { + return Functions::NAN(); + } + + return self::calculatePresentValue($rate, $numberOfPeriods, $payment, $futureValue, $type); + } + + /** + * NPER. + * + * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate. + * + * @param mixed $rate Interest rate per period + * @param mixed $payment Periodic payment (annuity) + * @param mixed $presentValue Present Value + * @param mixed $futureValue Future Value + * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period + * + * @return float|string Result, or a string containing an error + */ + public static function periods( + $rate, + $payment, + $presentValue, + $futureValue = 0.0, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD + ) { + $rate = Functions::flattenSingleValue($rate); + $payment = Functions::flattenSingleValue($payment); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + + try { + $rate = CashFlowValidations::validateRate($rate); + $payment = CashFlowValidations::validateFloat($payment); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + $type = CashFlowValidations::validatePeriodType($type); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($payment == 0.0) { + return Functions::NAN(); + } + + return self::calculatePeriods($rate, $payment, $presentValue, $futureValue, $type); + } + + private static function calculateFutureValue( + float $rate, + int $numberOfPeriods, + float $payment, + float $presentValue, + int $type + ): float { + if ($rate !== null && $rate != 0) { + return -$presentValue * + (1 + $rate) ** $numberOfPeriods - $payment * (1 + $rate * $type) * ((1 + $rate) ** $numberOfPeriods - 1) + / $rate; + } + + return -$presentValue - $payment * $numberOfPeriods; + } + + private static function calculatePresentValue( + float $rate, + int $numberOfPeriods, + float $payment, + float $futureValue, + int $type + ): float { + if ($rate != 0.0) { + return (-$payment * (1 + $rate * $type) + * (((1 + $rate) ** $numberOfPeriods - 1) / $rate) - $futureValue) / (1 + $rate) ** $numberOfPeriods; + } + + return -$futureValue - $payment * $numberOfPeriods; + } + + /** + * @return float|string + */ + private static function calculatePeriods( + float $rate, + float $payment, + float $presentValue, + float $futureValue, + int $type + ) { + if ($rate != 0.0) { + if ($presentValue == 0.0) { + return Functions::NAN(); + } + + return log(($payment * (1 + $rate * $type) / $rate - $futureValue) / + ($presentValue + $payment * (1 + $rate * $type) / $rate)) / log(1 + $rate); + } + + return (-$presentValue - $futureValue) / $payment; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php new file mode 100644 index 00000000..b7f6011c --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php @@ -0,0 +1,141 @@ +getMessage(); + } + + // Validate parameters + if ($start < 1 || $start > $end) { + return Functions::NAN(); + } + + // Calculate + $interest = 0; + for ($per = $start; $per <= $end; ++$per) { + $ipmt = Interest::payment($rate, $per, $periods, $presentValue, 0, $type); + if (is_string($ipmt)) { + return $ipmt; + } + + $interest += $ipmt; + } + + return $interest; + } + + /** + * CUMPRINC. + * + * Returns the cumulative principal paid on a loan between the start and end periods. + * + * Excel Function: + * CUMPRINC(rate,nper,pv,start,end[,type]) + * + * @param mixed $rate The Interest rate + * @param mixed $periods The total number of payment periods as an integer + * @param mixed $presentValue Present Value + * @param mixed $start The first period in the calculation. + * Payment periods are numbered beginning with 1. + * @param mixed $end the last period in the calculation + * @param mixed $type A number 0 or 1 and indicates when payments are due: + * 0 or omitted At the end of the period. + * 1 At the beginning of the period. + * + * @return float|string + */ + public static function principal( + $rate, + $periods, + $presentValue, + $start, + $end, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD + ) { + $rate = Functions::flattenSingleValue($rate); + $periods = Functions::flattenSingleValue($periods); + $presentValue = Functions::flattenSingleValue($presentValue); + $start = Functions::flattenSingleValue($start); + $end = Functions::flattenSingleValue($end); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + + try { + $rate = CashFlowValidations::validateRate($rate); + $periods = CashFlowValidations::validateInt($periods); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $start = CashFlowValidations::validateInt($start); + $end = CashFlowValidations::validateInt($end); + $type = CashFlowValidations::validatePeriodType($type); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($start < 1 || $start > $end) { + return Functions::VALUE(); + } + + // Calculate + $principal = 0; + for ($per = $start; $per <= $end; ++$per) { + $ppmt = Payments::interestPayment($rate, $per, $periods, $presentValue, 0, $type); + if (is_string($ppmt)) { + return $ppmt; + } + + $principal += $ppmt; + } + + return $principal; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php new file mode 100644 index 00000000..56d2e379 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php @@ -0,0 +1,216 @@ +getMessage(); + } + + // Validate parameters + if ($period <= 0 || $period > $numberOfPeriods) { + return Functions::NAN(); + } + + // Calculate + $interestAndPrincipal = new InterestAndPrincipal( + $interestRate, + $period, + $numberOfPeriods, + $presentValue, + $futureValue, + $type + ); + + return $interestAndPrincipal->interest(); + } + + /** + * ISPMT. + * + * Returns the interest payment for an investment based on an interest rate and a constant payment schedule. + * + * Excel Function: + * =ISPMT(interest_rate, period, number_payments, pv) + * + * @param mixed $interestRate is the interest rate for the investment + * @param mixed $period is the period to calculate the interest rate. It must be betweeen 1 and number_payments. + * @param mixed $numberOfPeriods is the number of payments for the annuity + * @param mixed $principleRemaining is the loan amount or present value of the payments + */ + public static function schedulePayment($interestRate, $period, $numberOfPeriods, $principleRemaining) + { + $interestRate = Functions::flattenSingleValue($interestRate); + $period = Functions::flattenSingleValue($period); + $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); + $principleRemaining = Functions::flattenSingleValue($principleRemaining); + + try { + $interestRate = CashFlowValidations::validateRate($interestRate); + $period = CashFlowValidations::validateInt($period); + $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $principleRemaining = CashFlowValidations::validateFloat($principleRemaining); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($period <= 0 || $period > $numberOfPeriods) { + return Functions::NAN(); + } + + // Return value + $returnValue = 0; + + // Calculate + $principlePayment = ($principleRemaining * 1.0) / ($numberOfPeriods * 1.0); + for ($i = 0; $i <= $period; ++$i) { + $returnValue = $interestRate * $principleRemaining * -1; + $principleRemaining -= $principlePayment; + // principle needs to be 0 after the last payment, don't let floating point screw it up + if ($i == $numberOfPeriods) { + $returnValue = 0.0; + } + } + + return $returnValue; + } + + /** + * RATE. + * + * Returns the interest rate per period of an annuity. + * RATE is calculated by iteration and can have zero or more solutions. + * If the successive results of RATE do not converge to within 0.0000001 after 20 iterations, + * RATE returns the #NUM! error value. + * + * Excel Function: + * RATE(nper,pmt,pv[,fv[,type[,guess]]]) + * + * @param mixed $numberOfPeriods The total number of payment periods in an annuity + * @param mixed $payment The payment made each period and cannot change over the life of the annuity. + * Typically, pmt includes principal and interest but no other fees or taxes. + * @param mixed $presentValue The present value - the total amount that a series of future payments is worth now + * @param mixed $futureValue The future value, or a cash balance you want to attain after the last payment is made. + * If fv is omitted, it is assumed to be 0 (the future value of a loan, + * for example, is 0). + * @param mixed $type A number 0 or 1 and indicates when payments are due: + * 0 or omitted At the end of the period. + * 1 At the beginning of the period. + * @param mixed $guess Your guess for what the rate will be. + * If you omit guess, it is assumed to be 10 percent. + * + * @return float|string + */ + public static function rate( + $numberOfPeriods, + $payment, + $presentValue, + $futureValue = 0.0, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD, + $guess = 0.1 + ) { + $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); + $payment = Functions::flattenSingleValue($payment); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess); + + try { + $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $payment = CashFlowValidations::validateFloat($payment); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + $type = CashFlowValidations::validatePeriodType($type); + $guess = CashFlowValidations::validateFloat($guess); + } catch (Exception $e) { + return $e->getMessage(); + } + + $rate = $guess; + // rest of code adapted from python/numpy + $close = false; + $iter = 0; + while (!$close && $iter < self::FINANCIAL_MAX_ITERATIONS) { + $nextdiff = self::rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type); + if (!is_numeric($nextdiff)) { + break; + } + $rate1 = $rate - $nextdiff; + $close = abs($rate1 - $rate) < self::FINANCIAL_PRECISION; + ++$iter; + $rate = $rate1; + } + + return $close ? $rate : Functions::NAN(); + } + + private static function rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type) + { + if ($rate == 0.0) { + return Functions::NAN(); + } + $tt1 = ($rate + 1) ** $numberOfPeriods; + $tt2 = ($rate + 1) ** ($numberOfPeriods - 1); + $numerator = $futureValue + $tt1 * $presentValue + $payment * ($tt1 - 1) * ($rate * $type + 1) / $rate; + $denominator = $numberOfPeriods * $tt2 * $presentValue - $payment * ($tt1 - 1) + * ($rate * $type + 1) / ($rate * $rate) + $numberOfPeriods + * $payment * $tt2 * ($rate * $type + 1) / $rate + $payment * ($tt1 - 1) * $type / $rate; + if ($denominator == 0) { + return Functions::NAN(); + } + + return $numerator / $denominator; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php new file mode 100644 index 00000000..ca989e00 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php @@ -0,0 +1,44 @@ +interest = $interest; + $this->principal = $principal; + } + + public function interest(): float + { + return $this->interest; + } + + public function principal(): float + { + return $this->principal; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php new file mode 100644 index 00000000..e103f923 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php @@ -0,0 +1,115 @@ +getMessage(); + } + + // Calculate + if ($interestRate != 0.0) { + return (-$futureValue - $presentValue * (1 + $interestRate) ** $numberOfPeriods) / + (1 + $interestRate * $type) / (((1 + $interestRate) ** $numberOfPeriods - 1) / $interestRate); + } + + return (-$presentValue - $futureValue) / $numberOfPeriods; + } + + /** + * PPMT. + * + * Returns the interest payment for a given period for an investment based on periodic, constant payments + * and a constant interest rate. + * + * @param mixed $interestRate Interest rate per period + * @param mixed $period Period for which we want to find the interest + * @param mixed $numberOfPeriods Number of periods + * @param mixed $presentValue Present Value + * @param mixed $futureValue Future Value + * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period + * + * @return float|string Result, or a string containing an error + */ + public static function interestPayment( + $interestRate, + $period, + $numberOfPeriods, + $presentValue, + $futureValue = 0, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD + ) { + $interestRate = Functions::flattenSingleValue($interestRate); + $period = Functions::flattenSingleValue($period); + $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + + try { + $interestRate = CashFlowValidations::validateRate($interestRate); + $period = CashFlowValidations::validateInt($period); + $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + $type = CashFlowValidations::validatePeriodType($type); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($period <= 0 || $period > $numberOfPeriods) { + return Functions::NAN(); + } + + // Calculate + $interestAndPrincipal = new InterestAndPrincipal( + $interestRate, + $period, + $numberOfPeriods, + $presentValue, + $futureValue, + $type + ); + + return $interestAndPrincipal->principal(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php new file mode 100644 index 00000000..a30634da --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php @@ -0,0 +1,108 @@ +getMessage(); + } + + return $principal; + } + + /** + * PDURATION. + * + * Calculates the number of periods required for an investment to reach a specified value. + * + * @param mixed $rate Interest rate per period + * @param mixed $presentValue Present Value + * @param mixed $futureValue Future Value + * + * @return float|string Result, or a string containing an error + */ + public static function periods($rate, $presentValue, $futureValue) + { + $rate = Functions::flattenSingleValue($rate); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = Functions::flattenSingleValue($futureValue); + + try { + $rate = CashFlowValidations::validateRate($rate); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($rate <= 0.0 || $presentValue <= 0.0 || $futureValue <= 0.0) { + return Functions::NAN(); + } + + return (log($futureValue) - log($presentValue)) / log(1 + $rate); + } + + /** + * RRI. + * + * Calculates the interest rate required for an investment to grow to a specified future value . + * + * @param float $periods The number of periods over which the investment is made + * @param float $presentValue Present Value + * @param float $futureValue Future Value + * + * @return float|string Result, or a string containing an error + */ + public static function interestRate($periods = 0.0, $presentValue = 0.0, $futureValue = 0.0) + { + $periods = Functions::flattenSingleValue($periods); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = Functions::flattenSingleValue($futureValue); + + try { + $periods = CashFlowValidations::validateFloat($periods); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($periods <= 0.0 || $presentValue <= 0.0 || $futureValue < 0.0) { + return Functions::NAN(); + } + + return ($futureValue / $presentValue) ** (1 / $periods) - 1; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php new file mode 100644 index 00000000..40df776f --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php @@ -0,0 +1,226 @@ +getMessage(); + } + } + + return self::xirrPart2($values); + } + + private static function xirrPart2(&$values) + { + $valCount = count($values); + $foundpos = false; + $foundneg = false; + for ($i = 0; $i < $valCount; ++$i) { + $fld = $values[$i]; + if (!is_numeric($fld)) { + return Functions::VALUE(); + } elseif ($fld > 0) { + $foundpos = true; + } elseif ($fld < 0) { + $foundneg = true; + } + } + if (!self::bothNegAndPos($foundneg, $foundpos)) { + return Functions::NAN(); + } + + return ''; + } + + private static function xirrPart3($values, $dates, $x1, $x2) + { + $f = self::xnpvOrdered($x1, $values, $dates, false); + if ($f < 0.0) { + $rtb = $x1; + $dx = $x2 - $x1; + } else { + $rtb = $x2; + $dx = $x1 - $x2; + } + + $rslt = Functions::VALUE(); + for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { + $dx *= 0.5; + $x_mid = $rtb + $dx; + $f_mid = self::xnpvOrdered($x_mid, $values, $dates, false); + if ($f_mid <= 0.0) { + $rtb = $x_mid; + } + if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { + $rslt = $x_mid; + + break; + } + } + + return $rslt; + } + + private static function xnpvOrdered($rate, $values, $dates, $ordered = true) + { + $rate = Functions::flattenSingleValue($rate); + $values = Functions::flattenArray($values); + $dates = Functions::flattenArray($dates); + $valCount = count($values); + + try { + self::validateXnpv($rate, $values, $dates); + $date0 = DateTimeExcel\Helpers::getDateValue($dates[0]); + } catch (Exception $e) { + return $e->getMessage(); + } + + $xnpv = 0.0; + for ($i = 0; $i < $valCount; ++$i) { + if (!is_numeric($values[$i])) { + return Functions::VALUE(); + } + + try { + $datei = DateTimeExcel\Helpers::getDateValue($dates[$i]); + } catch (Exception $e) { + return $e->getMessage(); + } + if ($date0 > $datei) { + /** @phpstan-ignore-next-line */ + $dif = $ordered ? Functions::NAN() : -DateTimeExcel\DateDif::funcDateDif($datei, $date0, 'd'); + } else { + $dif = DateTimeExcel\DateDif::funcDateDif($date0, $datei, 'd'); + } + if (!is_numeric($dif)) { + return $dif; + } + $xnpv += $values[$i] / (1 + $rate) ** ($dif / 365); + } + + return is_finite($xnpv) ? $xnpv : Functions::VALUE(); + } + + private static function validateXnpv($rate, $values, $dates): void + { + if (!is_numeric($rate)) { + throw new Exception(Functions::VALUE()); + } + $valCount = count($values); + if ($valCount != count($dates)) { + throw new Exception(Functions::NAN()); + } + if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) { + throw new Exception(Functions::NAN()); + } + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php new file mode 100644 index 00000000..c42df0c3 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php @@ -0,0 +1,160 @@ + 0.0) { + return Functions::VALUE(); + } + + $f = self::presentValue($x1, $values); + if ($f < 0.0) { + $rtb = $x1; + $dx = $x2 - $x1; + } else { + $rtb = $x2; + $dx = $x1 - $x2; + } + + for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { + $dx *= 0.5; + $x_mid = $rtb + $dx; + $f_mid = self::presentValue($x_mid, $values); + if ($f_mid <= 0.0) { + $rtb = $x_mid; + } + if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { + return $x_mid; + } + } + + return Functions::VALUE(); + } + + /** + * MIRR. + * + * Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both + * the cost of the investment and the interest received on reinvestment of cash. + * + * Excel Function: + * MIRR(values,finance_rate, reinvestment_rate) + * + * @param mixed $values An array or a reference to cells that contain a series of payments and + * income occurring at regular intervals. + * Payments are negative value, income is positive values. + * @param mixed $financeRate The interest rate you pay on the money used in the cash flows + * @param mixed $reinvestmentRate The interest rate you receive on the cash flows as you reinvest them + * + * @return float|string Result, or a string containing an error + */ + public static function modifiedRate($values, $financeRate, $reinvestmentRate) + { + if (!is_array($values)) { + return Functions::VALUE(); + } + $values = Functions::flattenArray($values); + $financeRate = Functions::flattenSingleValue($financeRate); + $reinvestmentRate = Functions::flattenSingleValue($reinvestmentRate); + $n = count($values); + + $rr = 1.0 + $reinvestmentRate; + $fr = 1.0 + $financeRate; + + $npvPos = $npvNeg = 0.0; + foreach ($values as $i => $v) { + if ($v >= 0) { + $npvPos += $v / $rr ** $i; + } else { + $npvNeg += $v / $fr ** $i; + } + } + + if (($npvNeg === 0.0) || ($npvPos === 0.0) || ($reinvestmentRate <= -1.0)) { + return Functions::VALUE(); + } + + $mirr = ((-$npvPos * $rr ** $n) + / ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0; + + return is_finite($mirr) ? $mirr : Functions::VALUE(); + } + + /** + * NPV. + * + * Returns the Net Present Value of a cash flow series given a discount rate. + * + * @param mixed $rate + * + * @return float + */ + public static function presentValue($rate, ...$args) + { + $returnValue = 0; + + $rate = Functions::flattenSingleValue($rate); + $aArgs = Functions::flattenArray($args); + + // Calculate + $countArgs = count($aArgs); + for ($i = 1; $i <= $countArgs; ++$i) { + // Is it a numeric value? + if (is_numeric($aArgs[$i - 1])) { + $returnValue += $aArgs[$i - 1] / (1 + $rate) ** $i; + } + } + + return $returnValue; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/Constants.php b/src/PhpSpreadsheet/Calculation/Financial/Constants.php new file mode 100644 index 00000000..17740b0a --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Constants.php @@ -0,0 +1,19 @@ +getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($settlement), $basis); + if (is_string($daysPerYear)) { + return Functions::VALUE(); + } + $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); + + if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) { + return abs(DateTimeExcel\Days::funcDays($prev, $settlement)); + } + + return DateTimeExcel\YearFrac::funcYearFrac($prev, $settlement, $basis) * $daysPerYear; + } + + /** + * COUPDAYS. + * + * Returns the number of days in the coupon period that contains the settlement date. + * + * Excel Function: + * COUPDAYS(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int). + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string + */ + public static function COUPDAYS( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + switch ($basis) { + case FinancialConstants::BASIS_DAYS_PER_YEAR_365: + // Actual/365 + return 365 / $frequency; + case FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL: + // Actual/actual + if ($frequency == FinancialConstants::FREQUENCY_ANNUAL) { + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($settlement), $basis); + + return $daysPerYear / $frequency; + } + $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); + $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT); + + return $next - $prev; + default: + // US (NASD) 30/360, Actual/360 or European 30/360 + return 360 / $frequency; + } + } + + /** + * COUPDAYSNC. + * + * Returns the number of days from the settlement date to the next coupon date. + * + * Excel Function: + * COUPDAYSNC(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int) . + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string + */ + public static function COUPDAYSNC( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($settlement), $basis); + $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT); + + if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_NASD) { + $settlementDate = Date::excelToDateTimeObject($settlement); + $settlementEoM = Helpers::isLastDayOfMonth($settlementDate); + if ($settlementEoM) { + ++$settlement; + } + } + + return DateTimeExcel\YearFrac::funcYearFrac($settlement, $next, $basis) * $daysPerYear; + } + + /** + * COUPNCD. + * + * Returns the next coupon date after the settlement date. + * + * Excel Function: + * COUPNCD(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int). + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function COUPNCD( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT); + } + + /** + * COUPNUM. + * + * Returns the number of coupons payable between the settlement date and maturity date, + * rounded up to the nearest whole coupon. + * + * Excel Function: + * COUPNUM(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int). + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return int|string + */ + public static function COUPNUM( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $yearsBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::funcYearFrac( + $settlement, + $maturity, + FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ); + + return (int) ceil($yearsBetweenSettlementAndMaturity * $frequency); + } + + /** + * COUPPCD. + * + * Returns the previous coupon date before the settlement date. + * + * Excel Function: + * COUPPCD(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int). + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function COUPPCD( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); + } + + private static function couponFirstPeriodDate($settlement, $maturity, int $frequency, $next) + { + $months = 12 / $frequency; + + $result = Date::excelToDateTimeObject($maturity); + $maturityEoM = Helpers::isLastDayOfMonth($result); + + while ($settlement < Date::PHPToExcel($result)) { + $result->modify('-' . $months . ' months'); + } + if ($next === true) { + $result->modify('+' . $months . ' months'); + } + + if ($maturityEoM === true) { + $result->modify('-1 day'); + } + + return Date::PHPToExcel($result); + } + + private static function validateCouponPeriod($settlement, $maturity): void + { + if ($settlement >= $maturity) { + throw new Exception(Functions::NAN()); + } + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/Depreciation.php b/src/PhpSpreadsheet/Calculation/Financial/Depreciation.php new file mode 100644 index 00000000..650a4861 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Depreciation.php @@ -0,0 +1,266 @@ +getMessage(); + } + + if ($cost === 0.0) { + return 0.0; + } + + // Set Fixed Depreciation Rate + $fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life); + $fixedDepreciationRate = round($fixedDepreciationRate, 3); + + // Loop through each period calculating the depreciation + // TODO Handle period value between 0 and 1 (e.g. 0.5) + $previousDepreciation = 0; + $depreciation = 0; + for ($per = 1; $per <= $period; ++$per) { + if ($per == 1) { + $depreciation = $cost * $fixedDepreciationRate * $month / 12; + } elseif ($per == ($life + 1)) { + $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12; + } else { + $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate; + } + $previousDepreciation += $depreciation; + } + + return $depreciation; + } + + /** + * DDB. + * + * Returns the depreciation of an asset for a specified period using the + * double-declining balance method or some other method you specify. + * + * Excel Function: + * DDB(cost,salvage,life,period[,factor]) + * + * @param mixed $cost Initial cost of the asset + * @param mixed $salvage Value at the end of the depreciation. + * (Sometimes called the salvage value of the asset) + * @param mixed $life Number of periods over which the asset is depreciated. + * (Sometimes called the useful life of the asset) + * @param mixed $period The period for which you want to calculate the + * depreciation. Period must use the same units as life. + * @param mixed $factor The rate at which the balance declines. + * If factor is omitted, it is assumed to be 2 (the + * double-declining balance method). + * + * @return float|string + */ + public static function DDB($cost, $salvage, $life, $period, $factor = 2.0) + { + $cost = Functions::flattenSingleValue($cost); + $salvage = Functions::flattenSingleValue($salvage); + $life = Functions::flattenSingleValue($life); + $period = Functions::flattenSingleValue($period); + $factor = Functions::flattenSingleValue($factor); + + try { + $cost = self::validateCost($cost); + $salvage = self::validateSalvage($salvage); + $life = self::validateLife($life); + $period = self::validatePeriod($period); + $factor = self::validateFactor($factor); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($period > $life) { + return Functions::NAN(); + } + + // Loop through each period calculating the depreciation + // TODO Handling for fractional $period values + $previousDepreciation = 0; + $depreciation = 0; + for ($per = 1; $per <= $period; ++$per) { + $depreciation = min( + ($cost - $previousDepreciation) * ($factor / $life), + ($cost - $salvage - $previousDepreciation) + ); + $previousDepreciation += $depreciation; + } + + return $depreciation; + } + + /** + * SLN. + * + * Returns the straight-line depreciation of an asset for one period + * + * @param mixed $cost Initial cost of the asset + * @param mixed $salvage Value at the end of the depreciation + * @param mixed $life Number of periods over which the asset is depreciated + * + * @return float|string Result, or a string containing an error + */ + public static function SLN($cost, $salvage, $life) + { + $cost = Functions::flattenSingleValue($cost); + $salvage = Functions::flattenSingleValue($salvage); + $life = Functions::flattenSingleValue($life); + + try { + $cost = self::validateCost($cost, true); + $salvage = self::validateSalvage($salvage, true); + $life = self::validateLife($life, true); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($life === 0.0) { + return Functions::DIV0(); + } + + return ($cost - $salvage) / $life; + } + + /** + * SYD. + * + * Returns the sum-of-years' digits depreciation of an asset for a specified period. + * + * @param mixed $cost Initial cost of the asset + * @param mixed $salvage Value at the end of the depreciation + * @param mixed $life Number of periods over which the asset is depreciated + * @param mixed $period Period + * + * @return float|string Result, or a string containing an error + */ + public static function SYD($cost, $salvage, $life, $period) + { + $cost = Functions::flattenSingleValue($cost); + $salvage = Functions::flattenSingleValue($salvage); + $life = Functions::flattenSingleValue($life); + $period = Functions::flattenSingleValue($period); + + try { + $cost = self::validateCost($cost, true); + $salvage = self::validateSalvage($salvage); + $life = self::validateLife($life); + $period = self::validatePeriod($period); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($period > $life) { + return Functions::NAN(); + } + + $syd = (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1)); + + return $syd; + } + + private static function validateCost($cost, bool $negativeValueAllowed = false): float + { + $cost = FinancialValidations::validateFloat($cost); + if ($cost < 0.0 && $negativeValueAllowed === false) { + throw new Exception(Functions::NAN()); + } + + return $cost; + } + + private static function validateSalvage($salvage, bool $negativeValueAllowed = false): float + { + $salvage = FinancialValidations::validateFloat($salvage); + if ($salvage < 0.0 && $negativeValueAllowed === false) { + throw new Exception(Functions::NAN()); + } + + return $salvage; + } + + private static function validateLife($life, bool $negativeValueAllowed = false): float + { + $life = FinancialValidations::validateFloat($life); + if ($life < 0.0 && $negativeValueAllowed === false) { + throw new Exception(Functions::NAN()); + } + + return $life; + } + + private static function validatePeriod($period, bool $negativeValueAllowed = false): float + { + $period = FinancialValidations::validateFloat($period); + if ($period <= 0.0 && $negativeValueAllowed === false) { + throw new Exception(Functions::NAN()); + } + + return $period; + } + + private static function validateMonth($month): int + { + $month = FinancialValidations::validateInt($month); + if ($month < 1) { + throw new Exception(Functions::NAN()); + } + + return $month; + } + + private static function validateFactor($factor): float + { + $factor = FinancialValidations::validateFloat($factor); + if ($factor <= 0.0) { + throw new Exception(Functions::NAN()); + } + + return $factor; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/Dollar.php b/src/PhpSpreadsheet/Calculation/Financial/Dollar.php new file mode 100644 index 00000000..b25b0c2e --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Dollar.php @@ -0,0 +1,80 @@ + 4)) { + throw new Exception(Functions::NAN()); + } + + return $basis; + } + + /** + * @param mixed $price + */ + public static function validatePrice($price): float + { + $price = self::validateFloat($price); + if ($price < 0.0) { + throw new Exception(Functions::NAN()); + } + + return $price; + } + + /** + * @param mixed $parValue + */ + public static function validateParValue($parValue): float + { + $parValue = self::validateFloat($parValue); + if ($parValue < 0.0) { + throw new Exception(Functions::NAN()); + } + + return $parValue; + } + + /** + * @param mixed $yield + */ + public static function validateYield($yield): float + { + $yield = self::validateFloat($yield); + if ($yield < 0.0) { + throw new Exception(Functions::NAN()); + } + + return $yield; + } + + /** + * @param mixed $discount + */ + public static function validateDiscount($discount): float + { + $discount = self::validateFloat($discount); + if ($discount <= 0.0) { + throw new Exception(Functions::NAN()); + } + + return $discount; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/Helpers.php b/src/PhpSpreadsheet/Calculation/Financial/Helpers.php new file mode 100644 index 00000000..d339b134 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Helpers.php @@ -0,0 +1,58 @@ +format('d') === $date->format('t'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php b/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php new file mode 100644 index 00000000..72df31e1 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php @@ -0,0 +1,72 @@ +getMessage(); + } + + if ($nominalRate <= 0 || $periodsPerYear < 1) { + return Functions::NAN(); + } + + return ((1 + $nominalRate / $periodsPerYear) ** $periodsPerYear) - 1; + } + + /** + * NOMINAL. + * + * Returns the nominal interest rate given the effective rate and the number of compounding payments per year. + * + * @param mixed $effectiveRate Effective interest rate as a float + * @param mixed $periodsPerYear Integer number of compounding payments per year + * + * @return float|string Result, or a string containing an error + */ + public static function nominal($effectiveRate = 0, $periodsPerYear = 0) + { + $effectiveRate = Functions::flattenSingleValue($effectiveRate); + $periodsPerYear = Functions::flattenSingleValue($periodsPerYear); + + try { + $effectiveRate = FinancialValidations::validateFloat($effectiveRate); + $periodsPerYear = FinancialValidations::validateInt($periodsPerYear); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($effectiveRate <= 0 || $periodsPerYear < 1) { + return Functions::NAN(); + } + + // Calculate + return $periodsPerYear * (($effectiveRate + 1) ** (1 / $periodsPerYear) - 1); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php b/src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php new file mode 100644 index 00000000..004b47f3 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php @@ -0,0 +1,151 @@ +getMessage(); + } + + $daysBetweenIssueAndSettlement = YearFrac::funcYearFrac($issue, $settlement, $basis); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + $daysBetweenFirstInterestAndSettlement = YearFrac::funcYearFrac($firstInterest, $settlement, $basis); + if (!is_numeric($daysBetweenFirstInterestAndSettlement)) { + // return date error + return $daysBetweenFirstInterestAndSettlement; + } + + return $parValue * $rate * $daysBetweenIssueAndSettlement; + } + + /** + * ACCRINTM. + * + * Returns the accrued interest for a security that pays interest at maturity. + * + * Excel Function: + * ACCRINTM(issue,settlement,rate[,par[,basis]]) + * + * @param mixed $issue The security's issue date + * @param mixed $settlement The security's settlement (or maturity) date + * @param mixed $rate The security's annual coupon rate + * @param mixed $parValue The security's par value. + * If you omit parValue, ACCRINT uses $1,000. + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function atMaturity( + $issue, + $settlement, + $rate, + $parValue = 1000, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $issue = Functions::flattenSingleValue($issue); + $settlement = Functions::flattenSingleValue($settlement); + $rate = Functions::flattenSingleValue($rate); + $parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $issue = SecurityValidations::validateIssueDate($issue); + $settlement = SecurityValidations::validateSettlementDate($settlement); + SecurityValidations::validateSecurityPeriod($issue, $settlement); + $rate = SecurityValidations::validateRate($rate); + $parValue = SecurityValidations::validateParValue($parValue); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysBetweenIssueAndSettlement = YearFrac::funcYearFrac($issue, $settlement, $basis); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + + return $parValue * $rate * $daysBetweenIssueAndSettlement; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php b/src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php new file mode 100644 index 00000000..158347e7 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php @@ -0,0 +1,283 @@ +getMessage(); + } + + $dsc = Coupons::COUPDAYSNC($settlement, $maturity, $frequency, $basis); + $e = Coupons::COUPDAYS($settlement, $maturity, $frequency, $basis); + $n = Coupons::COUPNUM($settlement, $maturity, $frequency, $basis); + $a = Coupons::COUPDAYBS($settlement, $maturity, $frequency, $basis); + + $baseYF = 1.0 + ($yield / $frequency); + $rfp = 100 * ($rate / $frequency); + $de = $dsc / $e; + + $result = $redemption / $baseYF ** (--$n + $de); + for ($k = 0; $k <= $n; ++$k) { + $result += $rfp / ($baseYF ** ($k + $de)); + } + $result -= $rfp * ($a / $e); + + return $result; + } + + /** + * PRICEDISC. + * + * Returns the price per $100 face value of a discounted security. + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $discount The security's discount rate + * @param mixed $redemption The security's redemption value per $100 face value + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function priceDiscounted( + $settlement, + $maturity, + $discount, + $redemption, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $discount = Functions::flattenSingleValue($discount); + $redemption = Functions::flattenSingleValue($redemption); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $discount = SecurityValidations::validateDiscount($discount); + $redemption = SecurityValidations::validateRedemption($redemption); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + + return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity); + } + + /** + * PRICEMAT. + * + * Returns the price per $100 face value of a security that pays interest at maturity. + * + * @param mixed $settlement The security's settlement date. + * The security's settlement date is the date after the issue date when the + * security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $issue The security's issue date + * @param mixed $rate The security's interest rate at date of issue + * @param mixed $yield The security's annual yield + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function priceAtMaturity( + $settlement, + $maturity, + $issue, + $rate, + $yield, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $issue = Functions::flattenSingleValue($issue); + $rate = Functions::flattenSingleValue($rate); + $yield = Functions::flattenSingleValue($yield); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $issue = SecurityValidations::validateIssueDate($issue); + $rate = SecurityValidations::validateRate($rate); + $yield = SecurityValidations::validateYield($yield); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($settlement), $basis); + if (!is_numeric($daysPerYear)) { + return $daysPerYear; + } + $daysBetweenIssueAndSettlement = DateTimeExcel\YearFrac::funcYearFrac($issue, $settlement, $basis); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + $daysBetweenIssueAndSettlement *= $daysPerYear; + $daysBetweenIssueAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($issue, $maturity, $basis); + if (!is_numeric($daysBetweenIssueAndMaturity)) { + // return date error + return $daysBetweenIssueAndMaturity; + } + $daysBetweenIssueAndMaturity *= $daysPerYear; + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + $daysBetweenSettlementAndMaturity *= $daysPerYear; + + return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) / + (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) - + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100); + } + + /** + * RECEIVED. + * + * Returns the amount received at maturity for a fully invested Security. + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $investment The amount invested in the security + * @param mixed $discount The security's discount rate + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function received( + $settlement, + $maturity, + $investment, + $discount, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $investment = Functions::flattenSingleValue($investment); + $discount = Functions::flattenSingleValue($discount); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $investment = SecurityValidations::validateFloat($investment); + $discount = SecurityValidations::validateDiscount($discount); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($investment <= 0) { + return Functions::NAN(); + } + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + + return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php b/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php new file mode 100644 index 00000000..5a32d1d1 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php @@ -0,0 +1,137 @@ +getMessage(); + } + + if ($price <= 0.0) { + return Functions::NAN(); + } + + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + + return (1 - $price / $redemption) / $daysBetweenSettlementAndMaturity; + } + + /** + * INTRATE. + * + * Returns the interest rate for a fully invested security. + * + * Excel Function: + * INTRATE(settlement,maturity,investment,redemption[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $investment the amount invested in the security + * @param mixed $redemption the amount to be received at maturity + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string + */ + public static function interest( + $settlement, + $maturity, + $investment, + $redemption, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $investment = Functions::flattenSingleValue($investment); + $redemption = Functions::flattenSingleValue($redemption); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $investment = SecurityValidations::validateFloat($investment); + $redemption = SecurityValidations::validateRedemption($redemption); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($investment <= 0) { + return Functions::NAN(); + } + + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + + return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php b/src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php new file mode 100644 index 00000000..497197b8 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php @@ -0,0 +1,42 @@ += $maturity) { + throw new Exception(Functions::NAN()); + } + } + + /** + * @param mixed $redemption + */ + public static function validateRedemption($redemption): float + { + $redemption = self::validateFloat($redemption); + if ($redemption <= 0.0) { + throw new Exception(Functions::NAN()); + } + + return $redemption; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php b/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php new file mode 100644 index 00000000..bdd638fa --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php @@ -0,0 +1,153 @@ +getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($settlement), $basis); + if (!is_numeric($daysPerYear)) { + return $daysPerYear; + } + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + $daysBetweenSettlementAndMaturity *= $daysPerYear; + + return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity); + } + + /** + * YIELDMAT. + * + * Returns the annual yield of a security that pays interest at maturity. + * + * @param mixed $settlement The security's settlement date. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $issue The security's issue date + * @param mixed $rate The security's interest rate at date of issue + * @param mixed $price The security's price per $100 face value + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function yieldAtMaturity( + $settlement, + $maturity, + $issue, + $rate, + $price, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $issue = Functions::flattenSingleValue($issue); + $rate = Functions::flattenSingleValue($rate); + $price = Functions::flattenSingleValue($price); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $issue = SecurityValidations::validateIssueDate($issue); + $rate = SecurityValidations::validateRate($rate); + $price = SecurityValidations::validatePrice($price); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($settlement), $basis); + if (!is_numeric($daysPerYear)) { + return $daysPerYear; + } + $daysBetweenIssueAndSettlement = DateTimeExcel\YearFrac::funcYearFrac($issue, $settlement, $basis); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + $daysBetweenIssueAndSettlement *= $daysPerYear; + $daysBetweenIssueAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($issue, $maturity, $basis); + if (!is_numeric($daysBetweenIssueAndMaturity)) { + // return date error + return $daysBetweenIssueAndMaturity; + } + $daysBetweenIssueAndMaturity *= $daysPerYear; + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + $daysBetweenSettlementAndMaturity *= $daysPerYear; + + return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - + (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) / + (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) * + ($daysPerYear / $daysBetweenSettlementAndMaturity); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php b/src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php new file mode 100644 index 00000000..31146c31 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php @@ -0,0 +1,147 @@ +getMessage(); + } + + if ($discount <= 0) { + return Functions::NAN(); + } + + $daysBetweenSettlementAndMaturity = $maturity - $settlement; + $daysPerYear = Helpers::daysPerYear( + DateTimeExcel\Year::funcYear($maturity), + FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL + ); + + if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) { + return Functions::NAN(); + } + + return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity); + } + + /** + * TBILLPRICE. + * + * Returns the price per $100 face value for a Treasury bill. + * + * @param mixed $settlement The Treasury bill's settlement date. + * The Treasury bill's settlement date is the date after the issue date + * when the Treasury bill is traded to the buyer. + * @param mixed $maturity The Treasury bill's maturity date. + * The maturity date is the date when the Treasury bill expires. + * @param mixed $discount The Treasury bill's discount rate + * + * @return float|string Result, or a string containing an error + */ + public static function price($settlement, $maturity, $discount) + { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $discount = Functions::flattenSingleValue($discount); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + $discount = FinancialValidations::validateFloat($discount); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($discount <= 0) { + return Functions::NAN(); + } + + $daysBetweenSettlementAndMaturity = $maturity - $settlement; + $daysPerYear = Helpers::daysPerYear( + DateTimeExcel\Year::funcYear($maturity), + FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL + ); + + if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) { + return Functions::NAN(); + } + + $price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360)); + if ($price < 0.0) { + return Functions::NAN(); + } + + return $price; + } + + /** + * TBILLYIELD. + * + * Returns the yield for a Treasury bill. + * + * @param mixed $settlement The Treasury bill's settlement date. + * The Treasury bill's settlement date is the date after the issue date when + * the Treasury bill is traded to the buyer. + * @param mixed $maturity The Treasury bill's maturity date. + * The maturity date is the date when the Treasury bill expires. + * @param mixed $price The Treasury bill's price per $100 face value + * + * @return float|string + */ + public static function yield($settlement, $maturity, $price) + { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $price = Functions::flattenSingleValue($price); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + $price = FinancialValidations::validatePrice($price); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysBetweenSettlementAndMaturity = $maturity - $settlement; + $daysPerYear = Helpers::daysPerYear( + DateTimeExcel\Year::funcYear($maturity), + FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL + ); + + if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) { + return Functions::NAN(); + } + + return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity); + } +} diff --git a/src/PhpSpreadsheet/Calculation/FormulaParser.php b/src/PhpSpreadsheet/Calculation/FormulaParser.php index c11af834..cd1402fd 100644 --- a/src/PhpSpreadsheet/Calculation/FormulaParser.php +++ b/src/PhpSpreadsheet/Calculation/FormulaParser.php @@ -90,10 +90,8 @@ class FormulaParser * Get Token. * * @param int $pId Token id - * - * @return string */ - public function getToken($pId = 0) + public function getToken(int $pId = 0): FormulaToken { if (isset($this->tokens[$pId])) { return $this->tokens[$pId]; diff --git a/src/PhpSpreadsheet/Calculation/Functions.php b/src/PhpSpreadsheet/Calculation/Functions.php index 2e8a7ecf..aea2323e 100644 --- a/src/PhpSpreadsheet/Calculation/Functions.php +++ b/src/PhpSpreadsheet/Calculation/Functions.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Cell; +use PhpOffice\PhpSpreadsheet\Shared\Date; class Functions { @@ -252,9 +253,11 @@ class Functions if ($condition === '') { $condition = '=""'; } - if (!is_string($condition) || !in_array($condition[0], ['>', '<', '='])) { - if (!is_numeric($condition)) { + $condition = self::operandSpecialHandling($condition); + if (is_bool($condition)) { + return '=' . ($condition ? 'TRUE' : 'FALSE'); + } elseif (!is_numeric($condition)) { $condition = Calculation::wrapResult(strtoupper($condition)); } @@ -263,9 +266,10 @@ class Functions preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches); [, $operator, $operand] = $matches; + $operand = self::operandSpecialHandling($operand); if (is_numeric(trim($operand, '"'))) { $operand = trim($operand, '"'); - } elseif (!is_numeric($operand)) { + } elseif (!is_numeric($operand) && $operand !== 'FALSE' && $operand !== 'TRUE') { $operand = str_replace('"', '""', $operand); $operand = Calculation::wrapResult(strtoupper($operand)); } @@ -273,12 +277,33 @@ class Functions return str_replace('""""', '""', $operator . $operand); } + private static function operandSpecialHandling($operand) + { + if (is_numeric($operand) || is_bool($operand)) { + return $operand; + } elseif (strtoupper($operand) === Calculation::getTRUE() || strtoupper($operand) === Calculation::getFALSE()) { + return strtoupper($operand); + } + + // Check for percentage + if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $operand)) { + return ((float) rtrim($operand, '%')) / 100; + } + + // Check for dates + if (($dateValueOperand = Date::stringToExcel($operand)) !== false) { + return $dateValueOperand; + } + + return $operand; + } + /** * ERROR_TYPE. * * @param mixed $value Value to check * - * @return bool + * @return int|string */ public static function errorType($value = '') { @@ -551,7 +576,7 @@ class Functions /** * Convert a multi-dimensional array to a simple 1-dimensional array. * - * @param array $array Array to be flattened + * @param array|mixed $array Array to be flattened * * @return array Flattened array */ @@ -584,7 +609,7 @@ class Functions /** * Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing. * - * @param array $array Array to be flattened + * @param array|mixed $array Array to be flattened * * @return array Flattened array */ diff --git a/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php b/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php new file mode 100644 index 00000000..8b53464f --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php @@ -0,0 +1,11 @@ + 0) && ($returnValue == $argCount); + return Logical\Operations::logicalAnd(...$args); } /** @@ -114,8 +92,13 @@ class Logical * * Boolean arguments are treated as True or False as appropriate * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False - * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds - * the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * + * @Deprecated 1.17.0 + * + * @see Logical\Operations::logicalOr() + * Use the logicalOr() method in the Logical\Operations class instead * * @param mixed $args Data values * @@ -123,29 +106,15 @@ class Logical */ public static function logicalOr(...$args) { - $args = Functions::flattenArray($args); - - if (count($args) == 0) { - return Functions::VALUE(); - } - - $args = array_filter($args, function ($value) { - return $value !== null || (is_string($value) && trim($value) == ''); - }); - - $returnValue = self::countTrueValues($args); - if (is_string($returnValue)) { - return $returnValue; - } - - return $returnValue > 0; + return Logical\Operations::logicalOr(...$args); } /** * LOGICAL_XOR. * * Returns the Exclusive Or logical operation for one or more supplied conditions. - * i.e. the Xor function returns TRUE if an odd number of the supplied conditions evaluate to TRUE, and FALSE otherwise. + * i.e. the Xor function returns TRUE if an odd number of the supplied conditions evaluate to TRUE, + * and FALSE otherwise. * * Excel Function: * =XOR(logical1[,logical2[, ...]]) @@ -155,8 +124,13 @@ class Logical * * Boolean arguments are treated as True or False as appropriate * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False - * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds - * the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * + * @Deprecated 1.17.0 + * + * @see Logical\Operations::logicalXor() + * Use the logicalXor() method in the Logical\Operations class instead * * @param mixed $args Data values * @@ -164,22 +138,7 @@ class Logical */ public static function logicalXor(...$args) { - $args = Functions::flattenArray($args); - - if (count($args) == 0) { - return Functions::VALUE(); - } - - $args = array_filter($args, function ($value) { - return $value !== null || (is_string($value) && trim($value) == ''); - }); - - $returnValue = self::countTrueValues($args); - if (is_string($returnValue)) { - return $returnValue; - } - - return $returnValue % 2 == 1; + return Logical\Operations::logicalXor(...$args); } /** @@ -194,8 +153,13 @@ class Logical * * Boolean arguments are treated as True or False as appropriate * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False - * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds - * the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * + * @Deprecated 1.17.0 + * + * @see Logical\Operations::NOT() + * Use the NOT() method in the Logical\Operations class instead * * @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE * @@ -203,20 +167,7 @@ class Logical */ public static function NOT($logical = false) { - $logical = Functions::flattenSingleValue($logical); - - if (is_string($logical)) { - $logical = strtoupper($logical); - if (($logical == 'TRUE') || ($logical == Calculation::getTRUE())) { - return false; - } elseif (($logical == 'FALSE') || ($logical == Calculation::getFALSE())) { - return true; - } - - return Functions::VALUE(); - } - - return !$logical; + return Logical\Operations::NOT($logical); } /** @@ -232,18 +183,23 @@ class Logical * the expression evaluates to TRUE. Otherwise, the expression evaluates to FALSE. * This argument can use any comparison calculation operator. * ReturnIfTrue is the value that is returned if condition evaluates to TRUE. - * For example, if this argument is the text string "Within budget" and the condition argument evaluates to TRUE, - * then the IF function returns the text "Within budget" - * If condition is TRUE and ReturnIfTrue is blank, this argument returns 0 (zero). To display the word TRUE, use - * the logical value TRUE for this argument. + * For example, if this argument is the text string "Within budget" and the condition argument + * evaluates to TRUE, then the IF function returns the text "Within budget" + * If condition is TRUE and ReturnIfTrue is blank, this argument returns 0 (zero). + * To display the word TRUE, use the logical value TRUE for this argument. * ReturnIfTrue can be another formula. * ReturnIfFalse is the value that is returned if condition evaluates to FALSE. - * For example, if this argument is the text string "Over budget" and the condition argument evaluates to FALSE, - * then the IF function returns the text "Over budget". + * For example, if this argument is the text string "Over budget" and the condition argument + * evaluates to FALSE, then the IF function returns the text "Over budget". * If condition is FALSE and ReturnIfFalse is omitted, then the logical value FALSE is returned. * If condition is FALSE and ReturnIfFalse is blank, then the value 0 (zero) is returned. * ReturnIfFalse can be another formula. * + * @Deprecated 1.17.0 + * + * @see Logical\Conditional::statementIf() + * Use the statementIf() method in the Logical\Conditional class instead + * * @param mixed $condition Condition to evaluate * @param mixed $returnIfTrue Value to return when condition is true * @param mixed $returnIfFalse Optional value to return when condition is false @@ -252,15 +208,7 @@ class Logical */ public static function statementIf($condition = true, $returnIfTrue = 0, $returnIfFalse = false) { - if (Functions::isError($condition)) { - return $condition; - } - - $condition = ($condition === null) ? true : (bool) Functions::flattenSingleValue($condition); - $returnIfTrue = ($returnIfTrue === null) ? 0 : Functions::flattenSingleValue($returnIfTrue); - $returnIfFalse = ($returnIfFalse === null) ? false : Functions::flattenSingleValue($returnIfFalse); - - return ($condition) ? $returnIfTrue : $returnIfFalse; + return Logical\Conditional::statementIf($condition, $returnIfTrue, $returnIfFalse); } /** @@ -274,11 +222,19 @@ class Logical * Expression * The expression to compare to a list of values. * value1, value2, ... value_n - * A list of values that are compared to expression. The SWITCH function is looking for the first value that matches the expression. + * A list of values that are compared to expression. + * The SWITCH function is looking for the first value that matches the expression. * result1, result2, ... result_n - * A list of results. The SWITCH function returns the corresponding result when a value matches expression. + * A list of results. The SWITCH function returns the corresponding result when a value + * matches expression. * default - * Optional. It is the default to return if expression does not match any of the values (value1, value2, ... value_n). + * Optional. It is the default to return if expression does not match any of the values + * (value1, value2, ... value_n). + * + * @Deprecated 1.17.0 + * + * @see Logical\Conditional::statementSwitch() + * Use the statementSwitch() method in the Logical\Conditional class instead * * @param mixed $arguments Statement arguments * @@ -286,33 +242,7 @@ class Logical */ public static function statementSwitch(...$arguments) { - $result = Functions::VALUE(); - - if (count($arguments) > 0) { - $targetValue = Functions::flattenSingleValue($arguments[0]); - $argc = count($arguments) - 1; - $switchCount = floor($argc / 2); - $switchSatisfied = false; - $hasDefaultClause = $argc % 2 !== 0; - $defaultClause = $argc % 2 === 0 ? null : $arguments[count($arguments) - 1]; - - if ($switchCount) { - for ($index = 0; $index < $switchCount; ++$index) { - if ($targetValue == $arguments[$index * 2 + 1]) { - $result = $arguments[$index * 2 + 2]; - $switchSatisfied = true; - - break; - } - } - } - - if (!$switchSatisfied) { - $result = $hasDefaultClause ? $defaultClause : Functions::NA(); - } - } - - return $result; + return Logical\Conditional::statementSwitch(...$arguments); } /** @@ -321,6 +251,11 @@ class Logical * Excel Function: * =IFERROR(testValue,errorpart) * + * @Deprecated 1.17.0 + * + * @see Logical\Conditional::IFERROR() + * Use the IFERROR() method in the Logical\Conditional class instead + * * @param mixed $testValue Value to check, is also the value returned when no error * @param mixed $errorpart Value to return when testValue is an error condition * @@ -328,10 +263,7 @@ class Logical */ public static function IFERROR($testValue = '', $errorpart = '') { - $testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue); - $errorpart = ($errorpart === null) ? '' : Functions::flattenSingleValue($errorpart); - - return self::statementIf(Functions::isError($testValue), $errorpart, $testValue); + return Logical\Conditional::IFERROR($testValue, $errorpart); } /** @@ -340,6 +272,11 @@ class Logical * Excel Function: * =IFNA(testValue,napart) * + * @Deprecated 1.17.0 + * + * @see Logical\Conditional::IFNA() + * Use the IFNA() method in the Logical\Conditional class instead + * * @param mixed $testValue Value to check, is also the value returned when not an NA * @param mixed $napart Value to return when testValue is an NA condition * @@ -347,10 +284,7 @@ class Logical */ public static function IFNA($testValue = '', $napart = '') { - $testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue); - $napart = ($napart === null) ? '' : Functions::flattenSingleValue($napart); - - return self::statementIf(Functions::isNa($testValue), $napart, $testValue); + return Logical\Conditional::IFNA($testValue, $napart); } /** @@ -364,27 +298,17 @@ class Logical * returnIfTrue1 ... returnIfTrue_n * Value returned if corresponding testValue (nth) was true * + * @Deprecated 1.17.0 + * + * @see Logical\Conditional::IFS() + * Use the IFS() method in the Logical\Conditional class instead + * * @param mixed ...$arguments Statement arguments * * @return mixed|string The value of returnIfTrue_n, if testValue_n was true. #N/A if none of testValues was true */ public static function IFS(...$arguments) { - if (count($arguments) % 2 != 0) { - return Functions::NA(); - } - // We use instance of Exception as a falseValue in order to prevent string collision with value in cell - $falseValueException = new Exception(); - for ($i = 0; $i < count($arguments); $i += 2) { - $testValue = ($arguments[$i] === null) ? '' : Functions::flattenSingleValue($arguments[$i]); - $returnIfTrue = ($arguments[$i + 1] === null) ? '' : Functions::flattenSingleValue($arguments[$i + 1]); - $result = self::statementIf($testValue, $returnIfTrue, $falseValueException); - - if ($result !== $falseValueException) { - return $result; - } - } - - return Functions::NA(); + return Logical\Conditional::IFS(...$arguments); } } diff --git a/src/PhpSpreadsheet/Calculation/Logical/Boolean.php b/src/PhpSpreadsheet/Calculation/Logical/Boolean.php new file mode 100644 index 00000000..8f1e9354 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Logical/Boolean.php @@ -0,0 +1,36 @@ + 0) { + $targetValue = Functions::flattenSingleValue($arguments[0]); + $argc = count($arguments) - 1; + $switchCount = floor($argc / 2); + $hasDefaultClause = $argc % 2 !== 0; + $defaultClause = $argc % 2 === 0 ? null : $arguments[$argc]; + + $switchSatisfied = false; + if ($switchCount > 0) { + for ($index = 0; $index < $switchCount; ++$index) { + if ($targetValue == $arguments[$index * 2 + 1]) { + $result = $arguments[$index * 2 + 2]; + $switchSatisfied = true; + + break; + } + } + } + + if ($switchSatisfied !== true) { + $result = $hasDefaultClause ? $defaultClause : Functions::NA(); + } + } + + return $result; + } + + /** + * IFERROR. + * + * Excel Function: + * =IFERROR(testValue,errorpart) + * + * @param mixed $testValue Value to check, is also the value returned when no error + * @param mixed $errorpart Value to return when testValue is an error condition + * + * @return mixed The value of errorpart or testValue determined by error condition + */ + public static function IFERROR($testValue = '', $errorpart = '') + { + $testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue); + $errorpart = ($errorpart === null) ? '' : Functions::flattenSingleValue($errorpart); + + return self::statementIf(Functions::isError($testValue), $errorpart, $testValue); + } + + /** + * IFNA. + * + * Excel Function: + * =IFNA(testValue,napart) + * + * @param mixed $testValue Value to check, is also the value returned when not an NA + * @param mixed $napart Value to return when testValue is an NA condition + * + * @return mixed The value of errorpart or testValue determined by error condition + */ + public static function IFNA($testValue = '', $napart = '') + { + $testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue); + $napart = ($napart === null) ? '' : Functions::flattenSingleValue($napart); + + return self::statementIf(Functions::isNa($testValue), $napart, $testValue); + } + + /** + * IFS. + * + * Excel Function: + * =IFS(testValue1;returnIfTrue1;testValue2;returnIfTrue2;...;testValue_n;returnIfTrue_n) + * + * testValue1 ... testValue_n + * Conditions to Evaluate + * returnIfTrue1 ... returnIfTrue_n + * Value returned if corresponding testValue (nth) was true + * + * @param mixed ...$arguments Statement arguments + * + * @return mixed|string The value of returnIfTrue_n, if testValue_n was true. #N/A if none of testValues was true + */ + public static function IFS(...$arguments) + { + $argumentCount = count($arguments); + + if ($argumentCount % 2 != 0) { + return Functions::NA(); + } + // We use instance of Exception as a falseValue in order to prevent string collision with value in cell + $falseValueException = new Exception(); + for ($i = 0; $i < $argumentCount; $i += 2) { + $testValue = ($arguments[$i] === null) ? '' : Functions::flattenSingleValue($arguments[$i]); + $returnIfTrue = ($arguments[$i + 1] === null) ? '' : Functions::flattenSingleValue($arguments[$i + 1]); + $result = self::statementIf($testValue, $returnIfTrue, $falseValueException); + + if ($result !== $falseValueException) { + return $result; + } + } + + return Functions::NA(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Logical/Operations.php b/src/PhpSpreadsheet/Calculation/Logical/Operations.php new file mode 100644 index 00000000..6bfb6a54 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Logical/Operations.php @@ -0,0 +1,198 @@ + 0) && ($returnValue == $argCount); + } + + /** + * LOGICAL_OR. + * + * Returns boolean TRUE if any argument is TRUE; returns FALSE if all arguments are FALSE. + * + * Excel Function: + * =OR(logical1[,logical2[, ...]]) + * + * The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays + * or references that contain logical values. + * + * Boolean arguments are treated as True or False as appropriate + * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * + * @param mixed $args Data values + * + * @return bool|string the logical OR of the arguments + */ + public static function logicalOr(...$args) + { + $args = Functions::flattenArray($args); + + if (count($args) == 0) { + return Functions::VALUE(); + } + + $args = array_filter($args, function ($value) { + return $value !== null || (is_string($value) && trim($value) == ''); + }); + + $returnValue = self::countTrueValues($args); + if (is_string($returnValue)) { + return $returnValue; + } + + return $returnValue > 0; + } + + /** + * LOGICAL_XOR. + * + * Returns the Exclusive Or logical operation for one or more supplied conditions. + * i.e. the Xor function returns TRUE if an odd number of the supplied conditions evaluate to TRUE, + * and FALSE otherwise. + * + * Excel Function: + * =XOR(logical1[,logical2[, ...]]) + * + * The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays + * or references that contain logical values. + * + * Boolean arguments are treated as True or False as appropriate + * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * + * @param mixed $args Data values + * + * @return bool|string the logical XOR of the arguments + */ + public static function logicalXor(...$args) + { + $args = Functions::flattenArray($args); + + if (count($args) == 0) { + return Functions::VALUE(); + } + + $args = array_filter($args, function ($value) { + return $value !== null || (is_string($value) && trim($value) == ''); + }); + + $returnValue = self::countTrueValues($args); + if (is_string($returnValue)) { + return $returnValue; + } + + return $returnValue % 2 == 1; + } + + /** + * NOT. + * + * Returns the boolean inverse of the argument. + * + * Excel Function: + * =NOT(logical) + * + * The argument must evaluate to a logical value such as TRUE or FALSE + * + * Boolean arguments are treated as True or False as appropriate + * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * + * @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE + * + * @return bool|string the boolean inverse of the argument + */ + public static function NOT($logical = false) + { + $logical = Functions::flattenSingleValue($logical); + + if (is_string($logical)) { + $logical = mb_strtoupper($logical, 'UTF-8'); + if (($logical == 'TRUE') || ($logical == Calculation::getTRUE())) { + return false; + } elseif (($logical == 'FALSE') || ($logical == Calculation::getFALSE())) { + return true; + } + + return Functions::VALUE(); + } + + return !$logical; + } + + /** + * @return int|string + */ + private static function countTrueValues(array $args) + { + $trueValueCount = 0; + + foreach ($args as $arg) { + // Is it a boolean value? + if (is_bool($arg)) { + $trueValueCount += $arg; + } elseif ((is_numeric($arg)) && (!is_string($arg))) { + $trueValueCount += ((int) $arg != 0); + } elseif (is_string($arg)) { + $arg = mb_strtoupper($arg, 'UTF-8'); + if (($arg == 'TRUE') || ($arg == Calculation::getTRUE())) { + $arg = true; + } elseif (($arg == 'FALSE') || ($arg == Calculation::getFALSE())) { + $arg = false; + } else { + return Functions::VALUE(); + } + $trueValueCount += ($arg != 0); + } + } + + return $trueValueCount; + } +} diff --git a/src/PhpSpreadsheet/Calculation/LookupRef.php b/src/PhpSpreadsheet/Calculation/LookupRef.php index 45aa9239..6a89f7da 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef.php @@ -2,9 +2,15 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Address; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\HLookup; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Indirect; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Lookup; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Matrix; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Offset; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\RowColumnInformation; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\VLookup; use PhpOffice\PhpSpreadsheet\Cell\Cell; -use PhpOffice\PhpSpreadsheet\Cell\Coordinate; -use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class LookupRef @@ -17,15 +23,19 @@ class LookupRef * Excel Function: * =ADDRESS(row, column, [relativity], [referenceStyle], [sheetText]) * + * @Deprecated 1.18.0 + * + * @see Use the cell() method in the LookupRef\Address class instead + * * @param mixed $row Row number to use in the cell reference * @param mixed $column Column number to use in the cell reference * @param int $relativity Flag indicating the type of reference to return * 1 or omitted Absolute - * 2 Absolute row; relative column - * 3 Relative row; absolute column - * 4 Relative + * 2 Absolute row; relative column + * 3 Relative row; absolute column + * 4 Relative * @param bool $referenceStyle A logical value that specifies the A1 or R1C1 reference style. - * TRUE or omitted CELL_ADDRESS returns an A1-style reference + * TRUE or omitted CELL_ADDRESS returns an A1-style reference * FALSE CELL_ADDRESS returns an R1C1-style reference * @param string $sheetText Optional Name of worksheet to use * @@ -33,87 +43,33 @@ class LookupRef */ public static function cellAddress($row, $column, $relativity = 1, $referenceStyle = true, $sheetText = '') { - $row = Functions::flattenSingleValue($row); - $column = Functions::flattenSingleValue($column); - $relativity = Functions::flattenSingleValue($relativity); - $sheetText = Functions::flattenSingleValue($sheetText); - - if (($row < 1) || ($column < 1)) { - return Functions::VALUE(); - } - - if ($sheetText > '') { - if (strpos($sheetText, ' ') !== false) { - $sheetText = "'" . $sheetText . "'"; - } - $sheetText .= '!'; - } - if ((!is_bool($referenceStyle)) || $referenceStyle) { - $rowRelative = $columnRelative = '$'; - $column = Coordinate::stringFromColumnIndex($column); - if (($relativity == 2) || ($relativity == 4)) { - $columnRelative = ''; - } - if (($relativity == 3) || ($relativity == 4)) { - $rowRelative = ''; - } - - return $sheetText . $columnRelative . $column . $rowRelative . $row; - } - if (($relativity == 2) || ($relativity == 4)) { - $column = '[' . $column . ']'; - } - if (($relativity == 3) || ($relativity == 4)) { - $row = '[' . $row . ']'; - } - - return $sheetText . 'R' . $row . 'C' . $column; + return Address::cell($row, $column, $relativity, $referenceStyle, $sheetText); } /** * COLUMN. * * Returns the column number of the given cell reference - * If the cell reference is a range of cells, COLUMN returns the column numbers of each column in the reference as a horizontal array. - * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the - * reference of the cell in which the COLUMN function appears; otherwise this function returns 0. + * If the cell reference is a range of cells, COLUMN returns the column numbers of each column + * in the reference as a horizontal array. + * If cell reference is omitted, and the function is being called through the calculation engine, + * then it is assumed to be the reference of the cell in which the COLUMN function appears; + * otherwise this function returns 1. * * Excel Function: * =COLUMN([cellAddress]) * + * @Deprecated 1.18.0 + * + * @see Use the COLUMN() method in the LookupRef\RowColumnInformation class instead + * * @param null|array|string $cellAddress A reference to a range of cells for which you want the column numbers * * @return int|int[] */ - public static function COLUMN($cellAddress = null) + public static function COLUMN($cellAddress = null, ?Cell $cell = null) { - if ($cellAddress === null || trim($cellAddress) === '') { - return 0; - } - - if (is_array($cellAddress)) { - foreach ($cellAddress as $columnKey => $value) { - $columnKey = preg_replace('/[^a-z]/i', '', $columnKey); - - return (int) Coordinate::columnIndexFromString($columnKey); - } - } else { - [$sheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); - if (strpos($cellAddress, ':') !== false) { - [$startAddress, $endAddress] = explode(':', $cellAddress); - $startAddress = preg_replace('/[^a-z]/i', '', $startAddress); - $endAddress = preg_replace('/[^a-z]/i', '', $endAddress); - $returnValue = []; - do { - $returnValue[] = (int) Coordinate::columnIndexFromString($startAddress); - } while ($startAddress++ != $endAddress); - - return $returnValue; - } - $cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress); - - return (int) Coordinate::columnIndexFromString($cellAddress); - } + return RowColumnInformation::COLUMN($cellAddress, $cell); } /** @@ -124,73 +80,44 @@ class LookupRef * Excel Function: * =COLUMNS(cellAddress) * - * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells for which you want the number of columns + * @Deprecated 1.18.0 + * + * @see Use the COLUMNS() method in the LookupRef\RowColumnInformation class instead + * + * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells + * for which you want the number of columns * * @return int|string The number of columns in cellAddress, or a string if arguments are invalid */ public static function COLUMNS($cellAddress = null) { - if ($cellAddress === null || $cellAddress === '') { - return 1; - } elseif (!is_array($cellAddress)) { - return Functions::VALUE(); - } - - reset($cellAddress); - $isMatrix = (is_numeric(key($cellAddress))); - [$columns, $rows] = Calculation::getMatrixDimensions($cellAddress); - - if ($isMatrix) { - return $rows; - } - - return $columns; + return RowColumnInformation::COLUMNS($cellAddress); } /** * ROW. * * Returns the row number of the given cell reference - * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference as a vertical array. - * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the - * reference of the cell in which the ROW function appears; otherwise this function returns 0. + * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference + * as a vertical array. + * If cell reference is omitted, and the function is being called through the calculation engine, + * then it is assumed to be the reference of the cell in which the ROW function appears; + * otherwise this function returns 1. * * Excel Function: * =ROW([cellAddress]) * + * @Deprecated 1.18.0 + * + * @see Use the ROW() method in the LookupRef\RowColumnInformation class instead + * * @param null|array|string $cellAddress A reference to a range of cells for which you want the row numbers * * @return int|mixed[]|string */ - public static function ROW($cellAddress = null) + public static function ROW($cellAddress = null, ?Cell $cell = null) { - if ($cellAddress === null || trim($cellAddress) === '') { - return 0; - } - - if (is_array($cellAddress)) { - foreach ($cellAddress as $columnKey => $rowValue) { - foreach ($rowValue as $rowKey => $cellValue) { - return (int) preg_replace('/\D/', '', $rowKey); - } - } - } else { - [$sheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); - if (strpos($cellAddress, ':') !== false) { - [$startAddress, $endAddress] = explode(':', $cellAddress); - $startAddress = preg_replace('/\D/', '', $startAddress); - $endAddress = preg_replace('/\D/', '', $endAddress); - $returnValue = []; - do { - $returnValue[][] = (int) $startAddress; - } while ($startAddress++ != $endAddress); - - return $returnValue; - } - [$cellAddress] = explode(':', $cellAddress); - - return (int) preg_replace('/\D/', '', $cellAddress); - } + return RowColumnInformation::ROW($cellAddress, $cell); } /** @@ -201,27 +128,18 @@ class LookupRef * Excel Function: * =ROWS(cellAddress) * - * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells for which you want the number of rows + * @Deprecated 1.18.0 + * + * @see Use the ROWS() method in the LookupRef\RowColumnInformation class instead + * + * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells + * for which you want the number of rows * * @return int|string The number of rows in cellAddress, or a string if arguments are invalid */ public static function ROWS($cellAddress = null) { - if ($cellAddress === null || $cellAddress === '') { - return 1; - } elseif (!is_array($cellAddress)) { - return Functions::VALUE(); - } - - reset($cellAddress); - $isMatrix = (is_numeric(key($cellAddress))); - [$columns, $rows] = Calculation::getMatrixDimensions($cellAddress); - - if ($isMatrix) { - return $columns; - } - - return $rows; + return RowColumnInformation::ROWS($cellAddress); } /** @@ -230,8 +148,8 @@ class LookupRef * Excel Function: * =HYPERLINK(linkURL,displayName) * - * @param string $linkURL Value to check, is also the value returned when no error - * @param string $displayName Value to return when testValue is an error condition + * @param mixed $linkURL URL Value to check, is also the value returned when no error + * @param mixed $displayName String Value to return when testValue is an error condition * @param Cell $pCell The cell to set the hyperlink in * * @return mixed The value of $displayName (or $linkURL if $displayName was blank) @@ -264,56 +182,22 @@ class LookupRef * Excel Function: * =INDIRECT(cellAddress) * + * @Deprecated 1.18.0 + * + * @see Use the INDIRECT() method in the LookupRef\Indirect class instead + * * NOTE - INDIRECT() does not yet support the optional a1 parameter introduced in Excel 2010 * * @param null|array|string $cellAddress $cellAddress The cell address of the current cell (containing this formula) * @param Cell $pCell The current cell (containing this formula) * - * @return mixed The cells referenced by cellAddress + * @return array|string An array containing a cell or range of cells, or a string on error * * @TODO Support for the optional a1 parameter introduced in Excel 2010 */ public static function INDIRECT($cellAddress = null, ?Cell $pCell = null) { - $cellAddress = Functions::flattenSingleValue($cellAddress); - if ($cellAddress === null || $cellAddress === '') { - return Functions::REF(); - } - - $cellAddress1 = $cellAddress; - $cellAddress2 = null; - if (strpos($cellAddress, ':') !== false) { - [$cellAddress1, $cellAddress2] = explode(':', $cellAddress); - } - - if ( - (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress1, $matches)) || - (($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress2, $matches))) - ) { - if (!preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/i', $cellAddress1, $matches)) { - return Functions::REF(); - } - - if (strpos($cellAddress, '!') !== false) { - [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); - $sheetName = trim($sheetName, "'"); - $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName); - } else { - $pSheet = $pCell->getWorksheet(); - } - - return Calculation::getInstance()->extractNamedRange($cellAddress, $pSheet, false); - } - - if (strpos($cellAddress, '!') !== false) { - [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); - $sheetName = trim($sheetName, "'"); - $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName); - } else { - $pSheet = $pCell->getWorksheet(); - } - - return Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false); + return Indirect::INDIRECT($cellAddress, $pCell); } /** @@ -326,87 +210,33 @@ class LookupRef * Excel Function: * =OFFSET(cellAddress, rows, cols, [height], [width]) * - * @param null|string $cellAddress The reference from which you want to base the offset. Reference must refer to a cell or - * range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value. - * @param mixed $rows The number of rows, up or down, that you want the upper-left cell to refer to. - * Using 5 as the rows argument specifies that the upper-left cell in the reference is - * five rows below reference. Rows can be positive (which means below the starting reference) - * or negative (which means above the starting reference). - * @param mixed $columns The number of columns, to the left or right, that you want the upper-left cell of the result - * to refer to. Using 5 as the cols argument specifies that the upper-left cell in the - * reference is five columns to the right of reference. Cols can be positive (which means - * to the right of the starting reference) or negative (which means to the left of the - * starting reference). - * @param mixed $height The height, in number of rows, that you want the returned reference to be. Height must be a positive number. - * @param mixed $width The width, in number of columns, that you want the returned reference to be. Width must be a positive number. + * @Deprecated 1.18.0 * - * @return string A reference to a cell or range of cells + * @see Use the OFFSET() method in the LookupRef\Offset class instead + * + * @param null|string $cellAddress The reference from which you want to base the offset. + * Reference must refer to a cell or range of adjacent cells; + * otherwise, OFFSET returns the #VALUE! error value. + * @param mixed $rows The number of rows, up or down, that you want the upper-left cell to refer to. + * Using 5 as the rows argument specifies that the upper-left cell in the + * reference is five rows below reference. Rows can be positive (which means + * below the starting reference) or negative (which means above the starting + * reference). + * @param mixed $columns The number of columns, to the left or right, that you want the upper-left cell + * of the result to refer to. Using 5 as the cols argument specifies that the + * upper-left cell in the reference is five columns to the right of reference. + * Cols can be positive (which means to the right of the starting reference) + * or negative (which means to the left of the starting reference). + * @param mixed $height The height, in number of rows, that you want the returned reference to be. + * Height must be a positive number. + * @param mixed $width The width, in number of columns, that you want the returned reference to be. + * Width must be a positive number. + * + * @return array|string An array containing a cell or range of cells, or a string on error */ public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $height = null, $width = null, ?Cell $pCell = null) { - $rows = Functions::flattenSingleValue($rows); - $columns = Functions::flattenSingleValue($columns); - $height = Functions::flattenSingleValue($height); - $width = Functions::flattenSingleValue($width); - if ($cellAddress === null) { - return 0; - } - - if (!is_object($pCell)) { - return Functions::REF(); - } - - $sheetName = null; - if (strpos($cellAddress, '!')) { - [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); - $sheetName = trim($sheetName, "'"); - } - if (strpos($cellAddress, ':')) { - [$startCell, $endCell] = explode(':', $cellAddress); - } else { - $startCell = $endCell = $cellAddress; - } - [$startCellColumn, $startCellRow] = Coordinate::coordinateFromString($startCell); - [$endCellColumn, $endCellRow] = Coordinate::coordinateFromString($endCell); - - $startCellRow += $rows; - $startCellColumn = Coordinate::columnIndexFromString($startCellColumn) - 1; - $startCellColumn += $columns; - - if (($startCellRow <= 0) || ($startCellColumn < 0)) { - return Functions::REF(); - } - $endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1; - if (($width != null) && (!is_object($width))) { - $endCellColumn = $startCellColumn + $width - 1; - } else { - $endCellColumn += $columns; - } - $startCellColumn = Coordinate::stringFromColumnIndex($startCellColumn + 1); - - if (($height != null) && (!is_object($height))) { - $endCellRow = $startCellRow + $height - 1; - } else { - $endCellRow += $rows; - } - - if (($endCellRow <= 0) || ($endCellColumn < 0)) { - return Functions::REF(); - } - $endCellColumn = Coordinate::stringFromColumnIndex($endCellColumn + 1); - - $cellAddress = $startCellColumn . $startCellRow; - if (($startCellColumn != $endCellColumn) || ($startCellRow != $endCellRow)) { - $cellAddress .= ':' . $endCellColumn . $endCellRow; - } - - if ($sheetName !== null) { - $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName); - } else { - $pSheet = $pCell->getWorksheet(); - } - - return Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false); + return Offset::OFFSET($cellAddress, $rows, $columns, $height, $width, $pCell); } /** @@ -453,6 +283,10 @@ class LookupRef * Excel Function: * =MATCH(lookup_value, lookup_array, [match_type]) * + * @Deprecated 1.18.0 + * + * @see Use the MATCH() method in the LookupRef\ExcelMatch class instead + * * @param mixed $lookupValue The value that you want to match in lookup_array * @param mixed $lookupArray The range of cells being searched * @param mixed $matchType The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below. @@ -462,145 +296,7 @@ class LookupRef */ public static function MATCH($lookupValue, $lookupArray, $matchType = 1) { - $lookupArray = Functions::flattenArray($lookupArray); - $lookupValue = Functions::flattenSingleValue($lookupValue); - $matchType = ($matchType === null) ? 1 : (int) Functions::flattenSingleValue($matchType); - - // MATCH is not case sensitive, so we convert lookup value to be lower cased in case it's string type. - if (is_string($lookupValue)) { - $lookupValue = StringHelper::strToLower($lookupValue); - } - - // Lookup_value type has to be number, text, or logical values - if ((!is_numeric($lookupValue)) && (!is_string($lookupValue)) && (!is_bool($lookupValue))) { - return Functions::NA(); - } - - // Match_type is 0, 1 or -1 - if (($matchType !== 0) && ($matchType !== -1) && ($matchType !== 1)) { - return Functions::NA(); - } - - // Lookup_array should not be empty - $lookupArraySize = count($lookupArray); - if ($lookupArraySize <= 0) { - return Functions::NA(); - } - - if ($matchType == 1) { - // If match_type is 1 the list has to be processed from last to first - - $lookupArray = array_reverse($lookupArray); - $keySet = array_reverse(array_keys($lookupArray)); - } - - // Lookup_array should contain only number, text, or logical values, or empty (null) cells - foreach ($lookupArray as $i => $lookupArrayValue) { - // check the type of the value - if ( - (!is_numeric($lookupArrayValue)) && (!is_string($lookupArrayValue)) && - (!is_bool($lookupArrayValue)) && ($lookupArrayValue !== null) - ) { - return Functions::NA(); - } - // Convert strings to lowercase for case-insensitive testing - if (is_string($lookupArrayValue)) { - $lookupArray[$i] = StringHelper::strToLower($lookupArrayValue); - } - if (($lookupArrayValue === null) && (($matchType == 1) || ($matchType == -1))) { - unset($lookupArray[$i]); - } - } - - // ** - // find the match - // ** - - if ($matchType === 0 || $matchType === 1) { - foreach ($lookupArray as $i => $lookupArrayValue) { - $typeMatch = ((gettype($lookupValue) === gettype($lookupArrayValue)) || (is_numeric($lookupValue) && is_numeric($lookupArrayValue))); - $exactTypeMatch = $typeMatch && $lookupArrayValue === $lookupValue; - $nonOnlyNumericExactMatch = !$typeMatch && $lookupArrayValue === $lookupValue; - $exactMatch = $exactTypeMatch || $nonOnlyNumericExactMatch; - - if ($matchType === 0) { - if ($typeMatch && is_string($lookupValue) && (bool) preg_match('/([\?\*])/', $lookupValue)) { - $splitString = $lookupValue; - $chars = array_map(function ($i) use ($splitString) { - return mb_substr($splitString, $i, 1); - }, range(0, mb_strlen($splitString) - 1)); - - $length = count($chars); - $pattern = '/^'; - for ($j = 0; $j < $length; ++$j) { - if ($chars[$j] === '~') { - if (isset($chars[$j + 1])) { - if ($chars[$j + 1] === '*') { - $pattern .= preg_quote($chars[$j + 1], '/'); - ++$j; - } elseif ($chars[$j + 1] === '?') { - $pattern .= preg_quote($chars[$j + 1], '/'); - ++$j; - } - } else { - $pattern .= preg_quote($chars[$j], '/'); - } - } elseif ($chars[$j] === '*') { - $pattern .= '.*'; - } elseif ($chars[$j] === '?') { - $pattern .= '.{1}'; - } else { - $pattern .= preg_quote($chars[$j], '/'); - } - } - - $pattern .= '$/'; - if ((bool) preg_match($pattern, $lookupArrayValue)) { - // exact match - return $i + 1; - } - } elseif ($exactMatch) { - // exact match - return $i + 1; - } - } elseif (($matchType === 1) && $typeMatch && ($lookupArrayValue <= $lookupValue)) { - $i = array_search($i, $keySet); - - // The current value is the (first) match - return $i + 1; - } - } - } else { - $maxValueKey = null; - - // The basic algorithm is: - // Iterate and keep the highest match until the next element is smaller than the searched value. - // Return immediately if perfect match is found - foreach ($lookupArray as $i => $lookupArrayValue) { - $typeMatch = gettype($lookupValue) === gettype($lookupArrayValue); - $exactTypeMatch = $typeMatch && $lookupArrayValue === $lookupValue; - $nonOnlyNumericExactMatch = !$typeMatch && $lookupArrayValue === $lookupValue; - $exactMatch = $exactTypeMatch || $nonOnlyNumericExactMatch; - - if ($exactMatch) { - // Another "special" case. If a perfect match is found, - // the algorithm gives up immediately - return $i + 1; - } elseif ($typeMatch & $lookupArrayValue >= $lookupValue) { - $maxValueKey = $i + 1; - } elseif ($typeMatch & $lookupArrayValue < $lookupValue) { - //Excel algorithm gives up immediately if the first element is smaller than the searched value - break; - } - } - - if ($maxValueKey !== null) { - return $maxValueKey; - } - } - - // Unsuccessful in finding a match, return #N/A error value - return Functions::NA(); + return LookupRef\ExcelMatch::MATCH($lookupValue, $lookupArray, $matchType); } /** @@ -611,262 +307,94 @@ class LookupRef * Excel Function: * =INDEX(range_array, row_num, [column_num]) * - * @param mixed $arrayValues A range of cells or an array constant - * @param mixed $rowNum The row in array from which to return a value. If row_num is omitted, column_num is required. - * @param mixed $columnNum The column in array from which to return a value. If column_num is omitted, row_num is required. + * @Deprecated 1.18.0 + * + * @see Use the index() method in the LookupRef\Matrix class instead + * + * @param mixed $rowNum The row in the array or range from which to return a value. + * If row_num is omitted, column_num is required. + * @param mixed $columnNum The column in the array or range from which to return a value. + * If column_num is omitted, row_num is required. + * @param mixed $matrix * * @return mixed the value of a specified cell or array of cells */ - public static function INDEX($arrayValues, $rowNum = 0, $columnNum = 0) + public static function INDEX($matrix, $rowNum = 0, $columnNum = 0) { - $rowNum = Functions::flattenSingleValue($rowNum); - $columnNum = Functions::flattenSingleValue($columnNum); - - if (($rowNum < 0) || ($columnNum < 0)) { - return Functions::VALUE(); - } - - if (!is_array($arrayValues) || ($rowNum > count($arrayValues))) { - return Functions::REF(); - } - - $rowKeys = array_keys($arrayValues); - $columnKeys = @array_keys($arrayValues[$rowKeys[0]]); - - if ($columnNum > count($columnKeys)) { - return Functions::VALUE(); - } elseif ($columnNum == 0) { - if ($rowNum == 0) { - return $arrayValues; - } - $rowNum = $rowKeys[--$rowNum]; - $returnArray = []; - foreach ($arrayValues as $arrayColumn) { - if (is_array($arrayColumn)) { - if (isset($arrayColumn[$rowNum])) { - $returnArray[] = $arrayColumn[$rowNum]; - } else { - return [$rowNum => $arrayValues[$rowNum]]; - } - } else { - return $arrayValues[$rowNum]; - } - } - - return $returnArray; - } - $columnNum = $columnKeys[--$columnNum]; - if ($rowNum > count($rowKeys)) { - return Functions::VALUE(); - } elseif ($rowNum == 0) { - return $arrayValues[$columnNum]; - } - $rowNum = $rowKeys[--$rowNum]; - - return $arrayValues[$rowNum][$columnNum]; + return Matrix::index($matrix, $rowNum, $columnNum); } /** * TRANSPOSE. * + * @Deprecated 1.18.0 + * + * @see Use the transpose() method in the LookupRef\Matrix class instead + * * @param array $matrixData A matrix of values * * @return array * - * Unlike the Excel TRANSPOSE function, which will only work on a single row or column, this function will transpose a full matrix + * Unlike the Excel TRANSPOSE function, which will only work on a single row or column, + * this function will transpose a full matrix */ public static function TRANSPOSE($matrixData) { - $returnMatrix = []; - if (!is_array($matrixData)) { - $matrixData = [[$matrixData]]; - } - - $column = 0; - foreach ($matrixData as $matrixRow) { - $row = 0; - foreach ($matrixRow as $matrixCell) { - $returnMatrix[$row][$column] = $matrixCell; - ++$row; - } - ++$column; - } - - return $returnMatrix; - } - - private static function vlookupSort($a, $b) - { - reset($a); - $firstColumn = key($a); - $aLower = StringHelper::strToLower($a[$firstColumn]); - $bLower = StringHelper::strToLower($b[$firstColumn]); - if ($aLower == $bLower) { - return 0; - } - - return ($aLower < $bLower) ? -1 : 1; + return Matrix::transpose($matrixData); } /** * VLOOKUP - * The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value in the same row based on the index_number. + * The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value + * in the same row based on the index_number. + * + * @Deprecated 1.18.0 + * + * @see Use the lookup() method in the LookupRef\VLookup class instead * * @param mixed $lookup_value The value that you want to match in lookup_array * @param mixed $lookup_array The range of cells being searched - * @param mixed $index_number The column number in table_array from which the matching value must be returned. The first column is 1. + * @param mixed $index_number The column number in table_array from which the matching value must be returned. + * The first column is 1. * @param mixed $not_exact_match determines if you are looking for an exact match based on lookup_value * * @return mixed The value of the found cell */ public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true) { - $lookup_value = Functions::flattenSingleValue($lookup_value); - $index_number = Functions::flattenSingleValue($index_number); - $not_exact_match = Functions::flattenSingleValue($not_exact_match); - - // index_number must be greater than or equal to 1 - if ($index_number < 1) { - return Functions::VALUE(); - } - - // index_number must be less than or equal to the number of columns in lookup_array - if ((!is_array($lookup_array)) || (empty($lookup_array))) { - return Functions::REF(); - } - $f = array_keys($lookup_array); - $firstRow = array_pop($f); - if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array[$firstRow]))) { - return Functions::REF(); - } - $columnKeys = array_keys($lookup_array[$firstRow]); - $returnColumn = $columnKeys[--$index_number]; - $firstColumn = array_shift($columnKeys); - - if (!$not_exact_match) { - uasort($lookup_array, ['self', 'vlookupSort']); - } - - $lookupLower = StringHelper::strToLower($lookup_value); - $rowNumber = $rowValue = false; - foreach ($lookup_array as $rowKey => $rowData) { - $firstLower = StringHelper::strToLower($rowData[$firstColumn]); - - // break if we have passed possible keys - if ( - (is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) || - (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && ($firstLower > $lookupLower)) - ) { - break; - } - // remember the last key, but only if datatypes match - if ( - (is_numeric($lookup_value) && is_numeric($rowData[$firstColumn])) || - (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn])) - ) { - if ($not_exact_match) { - $rowNumber = $rowKey; - - continue; - } elseif ( - ($firstLower == $lookupLower) - // Spreadsheets software returns first exact match, - // we have sorted and we might have broken key orders - // we want the first one (by its initial index) - && (($rowNumber == false) || ($rowKey < $rowNumber)) - ) { - $rowNumber = $rowKey; - } - } - } - - if ($rowNumber !== false) { - // return the appropriate value - return $lookup_array[$rowNumber][$returnColumn]; - } - - return Functions::NA(); + return VLookup::lookup($lookup_value, $lookup_array, $index_number, $not_exact_match); } /** * HLOOKUP - * The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value in the same column based on the index_number. + * The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value + * in the same column based on the index_number. + * + * @Deprecated 1.18.0 + * + * @see Use the lookup() method in the LookupRef\HLookup class instead * * @param mixed $lookup_value The value that you want to match in lookup_array * @param mixed $lookup_array The range of cells being searched - * @param mixed $index_number The row number in table_array from which the matching value must be returned. The first row is 1. + * @param mixed $index_number The row number in table_array from which the matching value must be returned. + * The first row is 1. * @param mixed $not_exact_match determines if you are looking for an exact match based on lookup_value * * @return mixed The value of the found cell */ public static function HLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true) { - $lookup_value = Functions::flattenSingleValue($lookup_value); - $index_number = Functions::flattenSingleValue($index_number); - $not_exact_match = Functions::flattenSingleValue($not_exact_match); - - // index_number must be greater than or equal to 1 - if ($index_number < 1) { - return Functions::VALUE(); - } - - // index_number must be less than or equal to the number of columns in lookup_array - if ((!is_array($lookup_array)) || (empty($lookup_array))) { - return Functions::REF(); - } - $f = array_keys($lookup_array); - $firstRow = reset($f); - if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array))) { - return Functions::REF(); - } - - $firstkey = $f[0] - 1; - $returnColumn = $firstkey + $index_number; - $firstColumn = array_shift($f); - $rowNumber = null; - foreach ($lookup_array[$firstColumn] as $rowKey => $rowData) { - // break if we have passed possible keys - $bothNumeric = is_numeric($lookup_value) && is_numeric($rowData); - $bothNotNumeric = !is_numeric($lookup_value) && !is_numeric($rowData); - $lookupLower = StringHelper::strToLower($lookup_value); - $rowDataLower = StringHelper::strToLower($rowData); - - if ( - $not_exact_match && ( - ($bothNumeric && $rowData > $lookup_value) || - ($bothNotNumeric && $rowDataLower > $lookupLower) - ) - ) { - break; - } - - // Remember the last key, but only if datatypes match (as in VLOOKUP) - if ($bothNumeric || $bothNotNumeric) { - if ($not_exact_match) { - $rowNumber = $rowKey; - - continue; - } elseif ( - $rowDataLower === $lookupLower - && ($rowNumber === null || $rowKey < $rowNumber) - ) { - $rowNumber = $rowKey; - } - } - } - - if ($rowNumber !== null) { - // otherwise return the appropriate value - return $lookup_array[$returnColumn][$rowNumber]; - } - - return Functions::NA(); + return HLookup::lookup($lookup_value, $lookup_array, $index_number, $not_exact_match); } /** * LOOKUP * The LOOKUP function searches for value either from a one-row or one-column range or from an array. * + * @Deprecated 1.18.0 + * + * @see Use the lookup() method in the LookupRef\Lookup class instead + * * @param mixed $lookup_value The value that you want to match in lookup_array * @param mixed $lookup_vector The range of cells being searched * @param null|mixed $result_vector The column from which the matching value must be returned @@ -875,66 +403,7 @@ class LookupRef */ public static function LOOKUP($lookup_value, $lookup_vector, $result_vector = null) { - $lookup_value = Functions::flattenSingleValue($lookup_value); - - if (!is_array($lookup_vector)) { - return Functions::NA(); - } - $hasResultVector = isset($result_vector); - $lookupRows = count($lookup_vector); - $l = array_keys($lookup_vector); - $l = array_shift($l); - $lookupColumns = count($lookup_vector[$l]); - // we correctly orient our results - if (($lookupRows === 1 && $lookupColumns > 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) { - $lookup_vector = self::TRANSPOSE($lookup_vector); - $lookupRows = count($lookup_vector); - $l = array_keys($lookup_vector); - $lookupColumns = count($lookup_vector[array_shift($l)]); - } - - if ($result_vector === null) { - $result_vector = $lookup_vector; - } - $resultRows = count($result_vector); - $l = array_keys($result_vector); - $l = array_shift($l); - $resultColumns = count($result_vector[$l]); - // we correctly orient our results - if ($resultRows === 1 && $resultColumns > 1) { - $result_vector = self::TRANSPOSE($result_vector); - $resultRows = count($result_vector); - $r = array_keys($result_vector); - $resultColumns = count($result_vector[array_shift($r)]); - } - - if ($lookupRows === 2 && !$hasResultVector) { - $result_vector = array_pop($lookup_vector); - $lookup_vector = array_shift($lookup_vector); - } - - if ($lookupColumns !== 2) { - foreach ($lookup_vector as &$value) { - if (is_array($value)) { - $k = array_keys($value); - $key1 = $key2 = array_shift($k); - ++$key2; - $dataValue1 = $value[$key1]; - } else { - $key1 = 0; - $key2 = 1; - $dataValue1 = $value; - } - $dataValue2 = array_shift($result_vector); - if (is_array($dataValue2)) { - $dataValue2 = array_shift($dataValue2); - } - $value = [$key1 => $dataValue1, $key2 => $dataValue2]; - } - unset($value); - } - - return self::VLOOKUP($lookup_value, $lookup_vector, 2); + return Lookup::lookup($lookup_value, $lookup_vector, $result_vector); } /** diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Address.php b/src/PhpSpreadsheet/Calculation/LookupRef/Address.php new file mode 100644 index 00000000..c217a1e4 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Address.php @@ -0,0 +1,97 @@ + '') { + if (strpos($sheetName, ' ') !== false || strpos($sheetName, '[') !== false) { + $sheetName = "'{$sheetName}'"; + } + $sheetName .= '!'; + } + + return $sheetName; + } + + private static function formatAsA1(int $row, int $column, int $relativity, string $sheetName): string + { + $rowRelative = $columnRelative = '$'; + if (($relativity == self::ADDRESS_COLUMN_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { + $columnRelative = ''; + } + if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { + $rowRelative = ''; + } + $column = Coordinate::stringFromColumnIndex($column); + + return "{$sheetName}{$columnRelative}{$column}{$rowRelative}{$row}"; + } + + private static function formatAsR1C1(int $row, int $column, int $relativity, string $sheetName): string + { + if (($relativity == self::ADDRESS_COLUMN_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { + $column = "[{$column}]"; + } + if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { + $row = "[{$row}]"; + } + + return "{$sheetName}R{$row}C{$column}"; + } +} diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php b/src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php new file mode 100644 index 00000000..71358bf3 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php @@ -0,0 +1,198 @@ +getMessage(); + } + + // MATCH() is not case sensitive, so we convert lookup value to be lower cased if it's a string type. + if (is_string($lookupValue)) { + $lookupValue = StringHelper::strToLower($lookupValue); + } + + $valueKey = null; + switch ($matchType) { + case self::MATCHTYPE_LARGEST_VALUE: + $valueKey = self::matchLargestValue($lookupArray, $lookupValue, $keySet); + + break; + case self::MATCHTYPE_FIRST_VALUE: + $valueKey = self::matchFirstValue($lookupArray, $lookupValue); + + break; + case self::MATCHTYPE_SMALLEST_VALUE: + default: + $valueKey = self::matchSmallestValue($lookupArray, $lookupValue); + } + + if ($valueKey !== null) { + return ++$valueKey; + } + + // Unsuccessful in finding a match, return #N/A error value + return Functions::NA(); + } + + private static function matchFirstValue($lookupArray, $lookupValue) + { + $wildcardLookup = ((bool) preg_match('/([\?\*])/', $lookupValue)); + $wildcard = WildcardMatch::wildcard($lookupValue); + + foreach ($lookupArray as $i => $lookupArrayValue) { + $typeMatch = ((gettype($lookupValue) === gettype($lookupArrayValue)) || + (is_numeric($lookupValue) && is_numeric($lookupArrayValue))); + + if ( + $typeMatch && is_string($lookupValue) && + $wildcardLookup && WildcardMatch::compare($lookupArrayValue, $wildcard) + ) { + // wildcard match + return $i; + } elseif ($lookupArrayValue === $lookupValue) { + // exact match + return $i; + } + } + + return null; + } + + private static function matchLargestValue($lookupArray, $lookupValue, $keySet) + { + foreach ($lookupArray as $i => $lookupArrayValue) { + $typeMatch = ((gettype($lookupValue) === gettype($lookupArrayValue)) || + (is_numeric($lookupValue) && is_numeric($lookupArrayValue))); + + if ($typeMatch && ($lookupArrayValue <= $lookupValue)) { + return array_search($i, $keySet); + } + } + + return null; + } + + private static function matchSmallestValue($lookupArray, $lookupValue) + { + $valueKey = null; + + // The basic algorithm is: + // Iterate and keep the highest match until the next element is smaller than the searched value. + // Return immediately if perfect match is found + foreach ($lookupArray as $i => $lookupArrayValue) { + $typeMatch = gettype($lookupValue) === gettype($lookupArrayValue); + + if ($lookupArrayValue === $lookupValue) { + // Another "special" case. If a perfect match is found, + // the algorithm gives up immediately + return $i; + } elseif ($typeMatch && $lookupArrayValue >= $lookupValue) { + $valueKey = $i; + } elseif ($typeMatch && $lookupArrayValue < $lookupValue) { + //Excel algorithm gives up immediately if the first element is smaller than the searched value + break; + } + } + + return $valueKey; + } + + private static function validateLookupValue($lookupValue): void + { + // Lookup_value type has to be number, text, or logical values + if ((!is_numeric($lookupValue)) && (!is_string($lookupValue)) && (!is_bool($lookupValue))) { + throw new Exception(Functions::NA()); + } + } + + private static function validateMatchType($matchType): void + { + // Match_type is 0, 1 or -1 + if ( + ($matchType !== self::MATCHTYPE_FIRST_VALUE) && + ($matchType !== self::MATCHTYPE_LARGEST_VALUE) && ($matchType !== self::MATCHTYPE_SMALLEST_VALUE) + ) { + throw new Exception(Functions::NA()); + } + } + + private static function validateLookupArray($lookupArray): void + { + // Lookup_array should not be empty + $lookupArraySize = count($lookupArray); + if ($lookupArraySize <= 0) { + throw new Exception(Functions::NA()); + } + } + + private static function prepareLookupArray($lookupArray, $matchType) + { + // Lookup_array should contain only number, text, or logical values, or empty (null) cells + foreach ($lookupArray as $i => $value) { + // check the type of the value + if ((!is_numeric($value)) && (!is_string($value)) && (!is_bool($value)) && ($value !== null)) { + throw new Exception(Functions::NA()); + } + // Convert strings to lowercase for case-insensitive testing + if (is_string($value)) { + $lookupArray[$i] = StringHelper::strToLower($value); + } + if ( + ($value === null) && + (($matchType == self::MATCHTYPE_LARGEST_VALUE) || ($matchType == self::MATCHTYPE_SMALLEST_VALUE)) + ) { + unset($lookupArray[$i]); + } + } + + return $lookupArray; + } +} diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php b/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php new file mode 100644 index 00000000..559fe7d1 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php @@ -0,0 +1,86 @@ +getMessage(); + } + + $f = array_keys($lookupArray); + $firstRow = reset($f); + if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray))) { + return Functions::REF(); + } + + $firstkey = $f[0] - 1; + $returnColumn = $firstkey + $indexNumber; + $firstColumn = array_shift($f); + $rowNumber = self::hLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch); + + if ($rowNumber !== null) { + // otherwise return the appropriate value + return $lookupArray[$returnColumn][$rowNumber]; + } + + return Functions::NA(); + } + + private static function hLookupSearch($lookupValue, $lookupArray, $column, $notExactMatch) + { + $lookupLower = StringHelper::strToLower($lookupValue); + + $rowNumber = null; + foreach ($lookupArray[$column] as $rowKey => $rowData) { + // break if we have passed possible keys + $bothNumeric = is_numeric($lookupValue) && is_numeric($rowData); + $bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData); + $cellDataLower = StringHelper::strToLower($rowData); + + if ( + $notExactMatch && + (($bothNumeric && $rowData > $lookupValue) || ($bothNotNumeric && $cellDataLower > $lookupLower)) + ) { + break; + } + + $rowNumber = self::checkMatch( + $bothNumeric, + $bothNotNumeric, + $notExactMatch, + $rowKey, + $cellDataLower, + $lookupLower, + $rowNumber + ); + } + + return $rowNumber; + } +} diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php b/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php new file mode 100644 index 00000000..c34dd965 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php @@ -0,0 +1,75 @@ +getParent() : null) + ->extractCellRange($cellAddress, $pSheet, false); + } + + private static function extractWorksheet($cellAddress, Cell $pCell): array + { + $sheetName = ''; + if (strpos($cellAddress, '!') !== false) { + [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); + $sheetName = trim($sheetName, "'"); + } + + $pSheet = ($sheetName !== '') + ? $pCell->getWorksheet()->getParent()->getSheetByName($sheetName) + : $pCell->getWorksheet(); + + return [$cellAddress, $pSheet]; + } +} diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php b/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php new file mode 100644 index 00000000..e21d35dc --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php @@ -0,0 +1,105 @@ + 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) { + $lookupVector = LookupRef\Matrix::transpose($lookupVector); + $lookupRows = self::rowCount($lookupVector); + $lookupColumns = self::columnCount($lookupVector); + } + + $resultVector = self::verifyResultVector($lookupVector, $resultVector); + + if ($lookupRows === 2 && !$hasResultVector) { + $resultVector = array_pop($lookupVector); + $lookupVector = array_shift($lookupVector); + } + + if ($lookupColumns !== 2) { + $lookupVector = self::verifyLookupValues($lookupVector, $resultVector); + } + + return VLookup::lookup($lookupValue, $lookupVector, 2); + } + + private static function verifyLookupValues(array $lookupVector, array $resultVector): array + { + foreach ($lookupVector as &$value) { + if (is_array($value)) { + $k = array_keys($value); + $key1 = $key2 = array_shift($k); + ++$key2; + $dataValue1 = $value[$key1]; + } else { + $key1 = 0; + $key2 = 1; + $dataValue1 = $value; + } + + $dataValue2 = array_shift($resultVector); + if (is_array($dataValue2)) { + $dataValue2 = array_shift($dataValue2); + } + $value = [$key1 => $dataValue1, $key2 => $dataValue2]; + } + unset($value); + + return $lookupVector; + } + + private static function verifyResultVector(array $lookupVector, $resultVector) + { + if ($resultVector === null) { + $resultVector = $lookupVector; + } + + $resultRows = self::rowCount($resultVector); + $resultColumns = self::columnCount($resultVector); + + // we correctly orient our results + if ($resultRows === 1 && $resultColumns > 1) { + $resultVector = LookupRef\Matrix::transpose($resultVector); + } + + return $resultVector; + } + + private static function rowCount(array $dataArray): int + { + return count($dataArray); + } + + private static function columnCount(array $dataArray): int + { + $rowKeys = array_keys($dataArray); + $row = array_shift($rowKeys); + + return count($dataArray[$row]); + } +} diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php b/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php new file mode 100644 index 00000000..80fc99ad --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php @@ -0,0 +1,48 @@ + count($matrix))) { + return Functions::REF(); + } + + $rowKeys = array_keys($matrix); + $columnKeys = @array_keys($matrix[$rowKeys[0]]); + + if ($columnNum > count($columnKeys)) { + return Functions::REF(); + } + + if ($columnNum == 0) { + return self::extractRowValue($matrix, $rowKeys, $rowNum); + } + + $columnNum = $columnKeys[--$columnNum]; + if ($rowNum == 0) { + return array_map( + function ($value) { + return [$value]; + }, + array_column($matrix, $columnNum) + ); + } + $rowNum = $rowKeys[--$rowNum]; + + return $matrix[$rowNum][$columnNum]; + } + + private static function extractRowValue(array $matrix, array $rowKeys, int $rowNum) + { + if ($rowNum == 0) { + return $matrix; + } + + $rowNum = $rowKeys[--$rowNum]; + $row = $matrix[$rowNum]; + if (is_array($row)) { + return [$rowNum => $row]; + } + + return $row; + } +} diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php b/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php new file mode 100644 index 00000000..c027d83c --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php @@ -0,0 +1,136 @@ +getParent() : null) + ->extractCellRange($cellAddress, $pSheet, false); + } + + private static function extractWorksheet($cellAddress, Cell $pCell): array + { + $sheetName = ''; + if (strpos($cellAddress, '!') !== false) { + [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); + $sheetName = trim($sheetName, "'"); + } + + $pSheet = ($sheetName !== '') + ? $pCell->getWorksheet()->getParent()->getSheetByName($sheetName) + : $pCell->getWorksheet(); + + return [$cellAddress, $pSheet]; + } + + private static function adjustEndCellColumnForWidth(string $endCellColumn, $width, int $startCellColumn, $columns) + { + $endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1; + if (($width !== null) && (!is_object($width))) { + $endCellColumn = $startCellColumn + (int) $width - 1; + } else { + $endCellColumn += (int) $columns; + } + + return $endCellColumn; + } + + private static function adustEndCellRowForHeight($height, int $startCellRow, $rows, $endCellRow): int + { + if (($height !== null) && (!is_object($height))) { + $endCellRow = $startCellRow + (int) $height - 1; + } else { + $endCellRow += (int) $rows; + } + + return $endCellRow; + } +} diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php b/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php new file mode 100644 index 00000000..19d0d5ff --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php @@ -0,0 +1,173 @@ +getColumn()) : 1; + } + + if (is_array($cellAddress)) { + foreach ($cellAddress as $columnKey => $value) { + $columnKey = preg_replace('/[^a-z]/i', '', $columnKey); + + return (int) Coordinate::columnIndexFromString($columnKey); + } + } + + [, $cellAddress] = Worksheet::extractSheetTitle((string) $cellAddress, true); + if (strpos($cellAddress, ':') !== false) { + [$startAddress, $endAddress] = explode(':', $cellAddress); + $startAddress = preg_replace('/[^a-z]/i', '', $startAddress); + $endAddress = preg_replace('/[^a-z]/i', '', $endAddress); + + return range( + (int) Coordinate::columnIndexFromString($startAddress), + (int) Coordinate::columnIndexFromString($endAddress) + ); + } + + $cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress); + + return (int) Coordinate::columnIndexFromString($cellAddress); + } + + /** + * COLUMNS. + * + * Returns the number of columns in an array or reference. + * + * Excel Function: + * =COLUMNS(cellAddress) + * + * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells + * for which you want the number of columns + * + * @return int|string The number of columns in cellAddress, or a string if arguments are invalid + */ + public static function COLUMNS($cellAddress = null) + { + if ($cellAddress === null || (is_string($cellAddress) && trim($cellAddress) === '')) { + return 1; + } elseif (!is_array($cellAddress)) { + return Functions::VALUE(); + } + + reset($cellAddress); + $isMatrix = (is_numeric(key($cellAddress))); + [$columns, $rows] = Calculation::getMatrixDimensions($cellAddress); + + if ($isMatrix) { + return $rows; + } + + return $columns; + } + + /** + * ROW. + * + * Returns the row number of the given cell reference + * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference + * as a vertical array. + * If cell reference is omitted, and the function is being called through the calculation engine, + * then it is assumed to be the reference of the cell in which the ROW function appears; + * otherwise this function returns 1. + * + * Excel Function: + * =ROW([cellAddress]) + * + * @param null|array|string $cellAddress A reference to a range of cells for which you want the row numbers + * + * @return int|mixed[]|string + */ + public static function ROW($cellAddress = null, ?Cell $pCell = null) + { + if ($cellAddress === null || (!is_array($cellAddress) && trim($cellAddress) === '')) { + return ($pCell !== null) ? $pCell->getRow() : 1; + } + + if (is_array($cellAddress)) { + foreach ($cellAddress as $rowKey => $rowValue) { + foreach ($rowValue as $columnKey => $cellValue) { + return (int) preg_replace('/\D/', '', $rowKey); + } + } + } + + [, $cellAddress] = Worksheet::extractSheetTitle((string) $cellAddress, true); + if (strpos($cellAddress, ':') !== false) { + [$startAddress, $endAddress] = explode(':', $cellAddress); + $startAddress = preg_replace('/\D/', '', $startAddress); + $endAddress = preg_replace('/\D/', '', $endAddress); + + return array_map( + function ($value) { + return [$value]; + }, + range($startAddress, $endAddress) + ); + } + [$cellAddress] = explode(':', $cellAddress); + + return (int) preg_replace('/\D/', '', $cellAddress); + } + + /** + * ROWS. + * + * Returns the number of rows in an array or reference. + * + * Excel Function: + * =ROWS(cellAddress) + * + * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells + * for which you want the number of rows + * + * @return int|string The number of rows in cellAddress, or a string if arguments are invalid + */ + public static function ROWS($cellAddress = null) + { + if ($cellAddress === null || (is_string($cellAddress) && trim($cellAddress) === '')) { + return 1; + } elseif (!is_array($cellAddress)) { + return Functions::VALUE(); + } + + reset($cellAddress); + $isMatrix = (is_numeric(key($cellAddress))); + [$columns, $rows] = Calculation::getMatrixDimensions($cellAddress); + + if ($isMatrix) { + return $columns; + } + + return $rows; + } +} diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php b/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php new file mode 100644 index 00000000..f890e496 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php @@ -0,0 +1,105 @@ +getMessage(); + } + + $f = array_keys($lookupArray); + $firstRow = array_pop($f); + if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray[$firstRow]))) { + return Functions::REF(); + } + $columnKeys = array_keys($lookupArray[$firstRow]); + $returnColumn = $columnKeys[--$indexNumber]; + $firstColumn = array_shift($columnKeys); + + if (!$notExactMatch) { + uasort($lookupArray, ['self', 'vlookupSort']); + } + + $rowNumber = self::vLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch); + + if ($rowNumber !== null) { + // return the appropriate value + return $lookupArray[$rowNumber][$returnColumn]; + } + + return Functions::NA(); + } + + private static function vlookupSort($a, $b) + { + reset($a); + $firstColumn = key($a); + $aLower = StringHelper::strToLower($a[$firstColumn]); + $bLower = StringHelper::strToLower($b[$firstColumn]); + + if ($aLower == $bLower) { + return 0; + } + + return ($aLower < $bLower) ? -1 : 1; + } + + private static function vLookupSearch($lookupValue, $lookupArray, $column, $notExactMatch) + { + $lookupLower = StringHelper::strToLower($lookupValue); + + $rowNumber = null; + foreach ($lookupArray as $rowKey => $rowData) { + $bothNumeric = is_numeric($lookupValue) && is_numeric($rowData[$column]); + $bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData[$column]); + $cellDataLower = StringHelper::strToLower($rowData[$column]); + + // break if we have passed possible keys + if ( + $notExactMatch && + (($bothNumeric && ($rowData[$column] > $lookupValue)) || + ($bothNotNumeric && ($cellDataLower > $lookupLower))) + ) { + break; + } + + $rowNumber = self::checkMatch( + $bothNumeric, + $bothNotNumeric, + $notExactMatch, + $rowKey, + $cellDataLower, + $lookupLower, + $rowNumber + ); + } + + return $rowNumber; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig.php b/src/PhpSpreadsheet/Calculation/MathTrig.php index 823f6ef2..131f1dbb 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig.php @@ -2,43 +2,8 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; -use Exception; -use Matrix\Exception as MatrixException; -use Matrix\Matrix; - class MathTrig { - // - // Private method to return an array of the factors of the input value - // - private static function factors($value) - { - $startVal = floor(sqrt($value)); - - $factorArray = []; - for ($i = $startVal; $i > 1; --$i) { - if (($value % $i) == 0) { - $factorArray = array_merge($factorArray, self::factors($value / $i)); - $factorArray = array_merge($factorArray, self::factors($i)); - if ($i <= sqrt($value)) { - break; - } - } - } - if (!empty($factorArray)) { - rsort($factorArray); - - return $factorArray; - } - - return [(int) $value]; - } - - private static function romanCut($num, $n) - { - return ($num - ($num % $n)) / $n; - } - /** * ARABIC. * @@ -47,75 +12,18 @@ class MathTrig * Excel Function: * ARABIC(text) * + * @Deprecated 1.18.0 + * + * @See MathTrig\Arabic::evaluate() + * Use the evaluate method in the MathTrig\Arabic class instead + * * @param string $roman * * @return int|string the arabic numberal contrived from the roman numeral */ public static function ARABIC($roman) { - // An empty string should return 0 - $roman = substr(trim(strtoupper((string) Functions::flattenSingleValue($roman))), 0, 255); - if ($roman === '') { - return 0; - } - - // Convert the roman numeral to an arabic number - $negativeNumber = $roman[0] === '-'; - if ($negativeNumber) { - $roman = substr($roman, 1); - } - - try { - $arabic = self::calculateArabic(str_split($roman)); - } catch (Exception $e) { - return Functions::VALUE(); // Invalid character detected - } - - if ($negativeNumber) { - $arabic *= -1; // The number should be negative - } - - return $arabic; - } - - /** - * Recursively calculate the arabic value of a roman numeral. - * - * @param int $sum - * @param int $subtract - * - * @return int - */ - protected static function calculateArabic(array $roman, &$sum = 0, $subtract = 0) - { - $lookup = [ - 'M' => 1000, - 'D' => 500, - 'C' => 100, - 'L' => 50, - 'X' => 10, - 'V' => 5, - 'I' => 1, - ]; - - $numeral = array_shift($roman); - if (!isset($lookup[$numeral])) { - throw new Exception('Invalid character detected'); - } - - $arabic = $lookup[$numeral]; - if (count($roman) > 0 && isset($lookup[$roman[0]]) && $arabic < $lookup[$roman[0]]) { - $subtract += $arabic; - } else { - $sum += ($arabic - $subtract); - $subtract = 0; - } - - if (count($roman) > 0) { - self::calculateArabic($roman, $sum, $subtract); - } - - return $sum; + return MathTrig\Arabic::evaluate($roman); } /** @@ -134,6 +42,11 @@ class MathTrig * Excel Function: * ATAN2(xCoordinate,yCoordinate) * + * @Deprecated 1.18.0 + * + * @See MathTrig\Atan2::funcAtan2() + * Use the funcAtan2 method in the MathTrig\Atan2 class instead + * * @param float $xCoordinate the x-coordinate of the point * @param float $yCoordinate the y-coordinate of the point * @@ -141,27 +54,7 @@ class MathTrig */ public static function ATAN2($xCoordinate = null, $yCoordinate = null) { - $xCoordinate = Functions::flattenSingleValue($xCoordinate); - $yCoordinate = Functions::flattenSingleValue($yCoordinate); - - $xCoordinate = ($xCoordinate !== null) ? $xCoordinate : 0.0; - $yCoordinate = ($yCoordinate !== null) ? $yCoordinate : 0.0; - - if ( - ((is_numeric($xCoordinate)) || (is_bool($xCoordinate))) && - ((is_numeric($yCoordinate))) || (is_bool($yCoordinate)) - ) { - $xCoordinate = (float) $xCoordinate; - $yCoordinate = (float) $yCoordinate; - - if (($xCoordinate == 0) && ($yCoordinate == 0)) { - return Functions::DIV0(); - } - - return atan2($yCoordinate, $xCoordinate); - } - - return Functions::VALUE(); + return MathTrig\Atan2::funcAtan2($xCoordinate, $yCoordinate); } /** @@ -172,6 +65,11 @@ class MathTrig * Excel Function: * BASE(Number, Radix [Min_length]) * + * @Deprecated 1.18.0 + * + * @See MathTrig\Base::funcBase() + * Use the funcBase method in the MathTrig\Base class instead + * * @param float $number * @param float $radix * @param int $minLength @@ -180,29 +78,7 @@ class MathTrig */ public static function BASE($number, $radix, $minLength = null) { - $number = Functions::flattenSingleValue($number); - $radix = Functions::flattenSingleValue($radix); - $minLength = Functions::flattenSingleValue($minLength); - - if (is_numeric($number) && is_numeric($radix) && ($minLength === null || is_numeric($minLength))) { - // Truncate to an integer - $number = (int) $number; - $radix = (int) $radix; - $minLength = (int) $minLength; - - if ($number < 0 || $number >= 2 ** 53 || $radix < 2 || $radix > 36) { - return Functions::NAN(); // Numeric range constraints - } - - $outcome = strtoupper((string) base_convert($number, 10, $radix)); - if ($minLength !== null) { - $outcome = str_pad($outcome, $minLength, '0', STR_PAD_LEFT); // String padding - } - - return $outcome; - } - - return Functions::VALUE(); + return MathTrig\Base::funcBase($number, $radix, $minLength); } /** @@ -216,6 +92,11 @@ class MathTrig * Excel Function: * CEILING(number[,significance]) * + * @Deprecated 1.17.0 + * + * @see MathTrig\Ceiling::funcCeiling() + * Use the funcCeiling() method in the MathTrig\Ceiling class instead + * * @param float $number the number you want to round * @param float $significance the multiple to which you want to round * @@ -223,27 +104,7 @@ class MathTrig */ public static function CEILING($number, $significance = null) { - $number = Functions::flattenSingleValue($number); - $significance = Functions::flattenSingleValue($significance); - - if ( - ($significance === null) && - (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) - ) { - $significance = $number / abs($number); - } - - if ((is_numeric($number)) && (is_numeric($significance))) { - if (($number == 0.0) || ($significance == 0.0)) { - return 0.0; - } elseif (self::SIGN($number) == self::SIGN($significance)) { - return ceil($number / $significance) * $significance; - } - - return Functions::NAN(); - } - - return Functions::VALUE(); + return MathTrig\Ceiling::funcCeiling($number, $significance); } /** @@ -255,6 +116,11 @@ class MathTrig * Excel Function: * COMBIN(numObjs,numInSet) * + * @Deprecated 1.18.0 + * + * @see MathTrig\Combinations::withoutRepetition() + * Use the withoutRepetition() method in the MathTrig\Combinations class instead + * * @param int $numObjs Number of different objects * @param int $numInSet Number of objects in each combination * @@ -262,20 +128,7 @@ class MathTrig */ public static function COMBIN($numObjs, $numInSet) { - $numObjs = Functions::flattenSingleValue($numObjs); - $numInSet = Functions::flattenSingleValue($numInSet); - - if ((is_numeric($numObjs)) && (is_numeric($numInSet))) { - if ($numObjs < $numInSet) { - return Functions::NAN(); - } elseif ($numInSet < 0) { - return Functions::NAN(); - } - - return round(self::FACT($numObjs) / self::FACT($numObjs - $numInSet)) / self::FACT($numInSet); - } - - return Functions::VALUE(); + return MathTrig\Combinations::withoutRepetition($numObjs, $numInSet); } /** @@ -290,27 +143,31 @@ class MathTrig * Excel Function: * EVEN(number) * + * @Deprecated 1.18.0 + * + * @see MathTrig\Even::funcEven() + * Use the funcEven() method in the MathTrig\Even class instead + * * @param float $number Number to round * * @return int|string Rounded Number, or a string containing an error */ public static function EVEN($number) { - $number = Functions::flattenSingleValue($number); + return MathTrig\Even::funcEven($number); + } - if ($number === null) { - return 0; - } elseif (is_bool($number)) { - $number = (int) $number; - } - - if (is_numeric($number)) { - $significance = 2 * self::SIGN($number); - - return (int) self::CEILING($number, $significance); - } - - return Functions::VALUE(); + /** + * Helper function for Even. + * + * @Deprecated 1.18.0 + * + * @see MathTrig\Helpers::getEven() + * Use the funcEven() method in the MathTrig\Helpers class instead + */ + public static function getEven(float $number): int + { + return (int) MathTrig\Helpers::getEven($number); } /** @@ -322,35 +179,18 @@ class MathTrig * Excel Function: * FACT(factVal) * + * @Deprecated 1.18.0 + * + * @see MathTrig\Fact::funcFact() + * Use the funcFact() method in the MathTrig\Fact class instead + * * @param float $factVal Factorial Value * * @return int|string Factorial, or a string containing an error */ public static function FACT($factVal) { - $factVal = Functions::flattenSingleValue($factVal); - - if (is_numeric($factVal)) { - if ($factVal < 0) { - return Functions::NAN(); - } - $factLoop = floor($factVal); - if ( - (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) && - ($factVal > $factLoop) - ) { - return Functions::NAN(); - } - - $factorial = 1; - while ($factLoop > 1) { - $factorial *= $factLoop--; - } - - return $factorial; - } - - return Functions::VALUE(); + return MathTrig\Fact::funcFact($factVal); } /** @@ -361,29 +201,18 @@ class MathTrig * Excel Function: * FACTDOUBLE(factVal) * + * @Deprecated 1.18.0 + * + * @see MathTrig\FactDouble::evaluate() + * Use the evaluate() method in the MathTrig\FactDouble class instead + * * @param float $factVal Factorial Value * * @return int|string Double Factorial, or a string containing an error */ public static function FACTDOUBLE($factVal) { - $factLoop = Functions::flattenSingleValue($factVal); - - if (is_numeric($factLoop)) { - $factLoop = floor($factLoop); - if ($factVal < 0) { - return Functions::NAN(); - } - $factorial = 1; - while ($factLoop > 1) { - $factorial *= $factLoop--; - --$factLoop; - } - - return $factorial; - } - - return Functions::VALUE(); + return MathTrig\FactDouble::evaluate($factVal); } /** @@ -394,6 +223,11 @@ class MathTrig * Excel Function: * FLOOR(number[,significance]) * + * @Deprecated 1.17.0 + * + * @see MathTrig\Floor::funcFloor() + * Use the funcFloor() method in the MathTrig\Floor class instead + * * @param float $number Number to round * @param float $significance Significance * @@ -401,31 +235,7 @@ class MathTrig */ public static function FLOOR($number, $significance = null) { - $number = Functions::flattenSingleValue($number); - $significance = Functions::flattenSingleValue($significance); - - if ( - ($significance === null) && - (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) - ) { - $significance = $number / abs($number); - } - - if ((is_numeric($number)) && (is_numeric($significance))) { - if ($significance == 0.0) { - return Functions::DIV0(); - } elseif ($number == 0.0) { - return 0.0; - } elseif (self::SIGN($significance) == 1) { - return floor($number / $significance) * $significance; - } elseif (self::SIGN($number) == -1 && self::SIGN($significance) == -1) { - return floor($number / $significance) * $significance; - } - - return Functions::NAN(); - } - - return Functions::VALUE(); + return MathTrig\Floor::funcFloor($number, $significance); } /** @@ -436,6 +246,11 @@ class MathTrig * Excel Function: * FLOOR.MATH(number[,significance[,mode]]) * + * @Deprecated 1.17.0 + * + * @see MathTrig\FloorMath::funcFloorMath() + * Use the funcFloorMath() method in the MathTrig\FloorMath class instead + * * @param float $number Number to round * @param float $significance Significance * @param int $mode direction to round negative numbers @@ -444,27 +259,7 @@ class MathTrig */ public static function FLOORMATH($number, $significance = null, $mode = 0) { - $number = Functions::flattenSingleValue($number); - $significance = Functions::flattenSingleValue($significance); - $mode = Functions::flattenSingleValue($mode); - - if (is_numeric($number) && $significance === null) { - $significance = $number / abs($number); - } - - if (is_numeric($number) && is_numeric($significance) && is_numeric($mode)) { - if ($significance == 0.0) { - return Functions::DIV0(); - } elseif ($number == 0.0) { - return 0.0; - } elseif (self::SIGN($significance) == -1 || (self::SIGN($number) == -1 && !empty($mode))) { - return ceil($number / $significance) * $significance; - } - - return floor($number / $significance) * $significance; - } - - return Functions::VALUE(); + return MathTrig\FloorMath::funcFloorMath($number, $significance, $mode); } /** @@ -475,6 +270,11 @@ class MathTrig * Excel Function: * FLOOR.PRECISE(number[,significance]) * + * @Deprecated 1.17.0 + * + * @see MathTrig\FloorPrecise::funcFloorPrecise() + * Use the funcFloorPrecise() method in the MathTrig\FloorPrecise class instead + * * @param float $number Number to round * @param float $significance Significance * @@ -482,25 +282,29 @@ class MathTrig */ public static function FLOORPRECISE($number, $significance = 1) { - $number = Functions::flattenSingleValue($number); - $significance = Functions::flattenSingleValue($significance); - - if ((is_numeric($number)) && (is_numeric($significance))) { - if ($significance == 0.0) { - return Functions::DIV0(); - } elseif ($number == 0.0) { - return 0.0; - } - - return floor($number / abs($significance)) * abs($significance); - } - - return Functions::VALUE(); + return MathTrig\FloorPrecise::funcFloorPrecise($number, $significance); } - private static function evaluateGCD($a, $b) + /** + * INT. + * + * Casts a floating point value to an integer + * + * Excel Function: + * INT(number) + * + * @Deprecated 1.17.0 + * + * @see MathTrig\IntClass::funcInt() + * Use the funcInt() method in the MathTrig\IntClass class instead + * + * @param float $number Number to cast to an integer + * + * @return int|string Integer value, or a string containing an error + */ + public static function INT($number) { - return $b ? self::evaluateGCD($b, $a % $b) : $a; + return MathTrig\IntClass::funcInt($number); } /** @@ -513,56 +317,18 @@ class MathTrig * Excel Function: * GCD(number1[,number2[, ...]]) * + * @Deprecated 1.18.0 + * + * @see MathTrig\Gcd::evaluate() + * Use the evaluate() method in the MathTrig\Gcd class instead + * * @param mixed ...$args Data values * * @return int|mixed|string Greatest Common Divisor, or a string containing an error */ public static function GCD(...$args) { - $args = Functions::flattenArray($args); - // Loop through arguments - foreach (Functions::flattenArray($args) as $value) { - if (!is_numeric($value)) { - return Functions::VALUE(); - } elseif ($value < 0) { - return Functions::NAN(); - } - } - - $gcd = (int) array_pop($args); - do { - $gcd = self::evaluateGCD($gcd, (int) array_pop($args)); - } while (!empty($args)); - - return $gcd; - } - - /** - * INT. - * - * Casts a floating point value to an integer - * - * Excel Function: - * INT(number) - * - * @param float $number Number to cast to an integer - * - * @return int|string Integer value, or a string containing an error - */ - public static function INT($number) - { - $number = Functions::flattenSingleValue($number); - - if ($number === null) { - return 0; - } elseif (is_bool($number)) { - return (int) $number; - } - if (is_numeric($number)) { - return (int) floor($number); - } - - return Functions::VALUE(); + return MathTrig\Gcd::evaluate(...$args); } /** @@ -576,45 +342,18 @@ class MathTrig * Excel Function: * LCM(number1[,number2[, ...]]) * + * @Deprecated 1.18.0 + * + * @see MathTrig\Lcm::funcLcm() + * Use the funcLcm() method in the MathTrig\Lcm class instead + * * @param mixed ...$args Data values * * @return int|string Lowest Common Multiplier, or a string containing an error */ public static function LCM(...$args) { - $returnValue = 1; - $allPoweredFactors = []; - // Loop through arguments - foreach (Functions::flattenArray($args) as $value) { - if (!is_numeric($value)) { - return Functions::VALUE(); - } - if ($value == 0) { - return 0; - } elseif ($value < 0) { - return Functions::NAN(); - } - $myFactors = self::factors(floor($value)); - $myCountedFactors = array_count_values($myFactors); - $myPoweredFactors = []; - foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) { - $myPoweredFactors[$myCountedFactor] = $myCountedFactor ** $myCountedPower; - } - foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) { - if (isset($allPoweredFactors[$myPoweredValue])) { - if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) { - $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; - } - } else { - $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; - } - } - } - foreach ($allPoweredFactors as $allPoweredFactor) { - $returnValue *= (int) $allPoweredFactor; - } - - return $returnValue; + return MathTrig\Lcm::funcLcm(...$args); } /** @@ -625,24 +364,19 @@ class MathTrig * Excel Function: * LOG(number[,base]) * + * @Deprecated 1.18.0 + * + * @see MathTrig\Logarithms::withBase() + * Use the withBase() method in the MathTrig\Logarithms class instead + * * @param float $number The positive real number for which you want the logarithm * @param float $base The base of the logarithm. If base is omitted, it is assumed to be 10. * * @return float|string The result, or a string containing an error */ - public static function logBase($number = null, $base = 10) + public static function logBase($number, $base = 10) { - $number = Functions::flattenSingleValue($number); - $base = ($base === null) ? 10 : (float) Functions::flattenSingleValue($base); - - if ((!is_numeric($base)) || (!is_numeric($number))) { - return Functions::VALUE(); - } - if (($base <= 0) || ($number <= 0)) { - return Functions::NAN(); - } - - return log($number, $base); + return MathTrig\Logarithms::withBase($number, $base); } /** @@ -653,46 +387,18 @@ class MathTrig * Excel Function: * MDETERM(array) * + * @Deprecated 1.18.0 + * + * @see MathTrig\MatrixFunctions::determinant() + * Use the determinant() method in the MathTrig\MatrixFunctions class instead + * * @param array $matrixValues A matrix of values * * @return float|string The result, or a string containing an error */ public static function MDETERM($matrixValues) { - $matrixData = []; - if (!is_array($matrixValues)) { - $matrixValues = [[$matrixValues]]; - } - - $row = $maxColumn = 0; - foreach ($matrixValues as $matrixRow) { - if (!is_array($matrixRow)) { - $matrixRow = [$matrixRow]; - } - $column = 0; - foreach ($matrixRow as $matrixCell) { - if ((is_string($matrixCell)) || ($matrixCell === null)) { - return Functions::VALUE(); - } - $matrixData[$row][$column] = $matrixCell; - ++$column; - } - if ($column > $maxColumn) { - $maxColumn = $column; - } - ++$row; - } - - $matrix = new Matrix($matrixData); - if (!$matrix->isSquare()) { - return Functions::VALUE(); - } - - try { - return $matrix->determinant(); - } catch (MatrixException $ex) { - return Functions::VALUE(); - } + return MathTrig\MatrixFunctions::determinant($matrixValues); } /** @@ -703,55 +409,28 @@ class MathTrig * Excel Function: * MINVERSE(array) * + * @Deprecated 1.18.0 + * + * @see MathTrig\MatrixFunctions::inverse() + * Use the inverse() method in the MathTrig\MatrixFunctions class instead + * * @param array $matrixValues A matrix of values * * @return array|string The result, or a string containing an error */ public static function MINVERSE($matrixValues) { - $matrixData = []; - if (!is_array($matrixValues)) { - $matrixValues = [[$matrixValues]]; - } - - $row = $maxColumn = 0; - foreach ($matrixValues as $matrixRow) { - if (!is_array($matrixRow)) { - $matrixRow = [$matrixRow]; - } - $column = 0; - foreach ($matrixRow as $matrixCell) { - if ((is_string($matrixCell)) || ($matrixCell === null)) { - return Functions::VALUE(); - } - $matrixData[$row][$column] = $matrixCell; - ++$column; - } - if ($column > $maxColumn) { - $maxColumn = $column; - } - ++$row; - } - - $matrix = new Matrix($matrixData); - if (!$matrix->isSquare()) { - return Functions::VALUE(); - } - - if ($matrix->determinant() == 0.0) { - return Functions::NAN(); - } - - try { - return $matrix->inverse()->toArray(); - } catch (MatrixException $ex) { - return Functions::VALUE(); - } + return MathTrig\MatrixFunctions::inverse($matrixValues); } /** * MMULT. * + * @Deprecated 1.18.0 + * + * @see MathTrig\MatrixFunctions::multiply() + * Use the multiply() method in the MathTrig\MatrixFunctions class instead + * * @param array $matrixData1 A matrix of values * @param array $matrixData2 A matrix of values * @@ -759,61 +438,17 @@ class MathTrig */ public static function MMULT($matrixData1, $matrixData2) { - $matrixAData = $matrixBData = []; - if (!is_array($matrixData1)) { - $matrixData1 = [[$matrixData1]]; - } - if (!is_array($matrixData2)) { - $matrixData2 = [[$matrixData2]]; - } - - try { - $rowA = 0; - foreach ($matrixData1 as $matrixRow) { - if (!is_array($matrixRow)) { - $matrixRow = [$matrixRow]; - } - $columnA = 0; - foreach ($matrixRow as $matrixCell) { - if ((!is_numeric($matrixCell)) || ($matrixCell === null)) { - return Functions::VALUE(); - } - $matrixAData[$rowA][$columnA] = $matrixCell; - ++$columnA; - } - ++$rowA; - } - $matrixA = new Matrix($matrixAData); - $rowB = 0; - foreach ($matrixData2 as $matrixRow) { - if (!is_array($matrixRow)) { - $matrixRow = [$matrixRow]; - } - $columnB = 0; - foreach ($matrixRow as $matrixCell) { - if ((!is_numeric($matrixCell)) || ($matrixCell === null)) { - return Functions::VALUE(); - } - $matrixBData[$rowB][$columnB] = $matrixCell; - ++$columnB; - } - ++$rowB; - } - $matrixB = new Matrix($matrixBData); - - if ($columnA != $rowB) { - return Functions::VALUE(); - } - - return $matrixA->multiply($matrixB)->toArray(); - } catch (MatrixException $ex) { - return Functions::VALUE(); - } + return MathTrig\MatrixFunctions::multiply($matrixData1, $matrixData2); } /** * MOD. * + * @Deprecated 1.18.0 + * + * @see MathTrig\Mod::evaluate() + * Use the evaluate() method in the MathTrig\Mod class instead + * * @param int $a Dividend * @param int $b Divisor * @@ -821,18 +456,7 @@ class MathTrig */ public static function MOD($a = 1, $b = 1) { - $a = (float) Functions::flattenSingleValue($a); - $b = (float) Functions::flattenSingleValue($b); - - if ($b == 0.0) { - return Functions::DIV0(); - } elseif (($a < 0.0) && ($b > 0.0)) { - return $b - fmod(abs($a), $b); - } elseif (($a > 0.0) && ($b < 0.0)) { - return $b + fmod($a, abs($b)); - } - - return fmod($a, $b); + return MathTrig\Mod::evaluate($a, $b); } /** @@ -840,6 +464,11 @@ class MathTrig * * Rounds a number to the nearest multiple of a specified value * + * @Deprecated 1.17.0 + * + * @see MathTrig\Mround::funcMround() + * Use the funcMround() method in the MathTrig\Mround class instead + * * @param float $number Number to round * @param int $multiple Multiple to which you want to round $number * @@ -847,23 +476,7 @@ class MathTrig */ public static function MROUND($number, $multiple) { - $number = Functions::flattenSingleValue($number); - $multiple = Functions::flattenSingleValue($multiple); - - if ((is_numeric($number)) && (is_numeric($multiple))) { - if ($multiple == 0) { - return 0; - } - if ((self::SIGN($number)) == (self::SIGN($multiple))) { - $multiplier = 1 / $multiple; - - return round($number * $multiplier) / $multiplier; - } - - return Functions::NAN(); - } - - return Functions::VALUE(); + return MathTrig\Mround::funcMround($number, $multiple); } /** @@ -871,36 +484,18 @@ class MathTrig * * Returns the ratio of the factorial of a sum of values to the product of factorials. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Multinomial::funcMultinomial() + * Use the funcMultinomial method in the MathTrig\Multinomial class instead + * * @param mixed[] $args An array of mixed values for the Data Series * * @return float|string The result, or a string containing an error */ public static function MULTINOMIAL(...$args) { - $summer = 0; - $divisor = 1; - // Loop through arguments - foreach (Functions::flattenArray($args) as $arg) { - // Is it a numeric value? - if (is_numeric($arg)) { - if ($arg < 1) { - return Functions::NAN(); - } - $summer += floor($arg); - $divisor *= self::FACT($arg); - } else { - return Functions::VALUE(); - } - } - - // Return - if ($summer > 0) { - $summer = self::FACT($summer); - - return $summer / $divisor; - } - - return 0; + return MathTrig\Multinomial::funcMultinomial(...$args); } /** @@ -908,33 +503,18 @@ class MathTrig * * Returns number rounded up to the nearest odd integer. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Odd::funcOdd() + * Use the funcOdd method in the MathTrig\Odd class instead + * * @param float $number Number to round * * @return int|string Rounded Number, or a string containing an error */ public static function ODD($number) { - $number = Functions::flattenSingleValue($number); - - if ($number === null) { - return 1; - } elseif (is_bool($number)) { - return 1; - } elseif (is_numeric($number)) { - $significance = self::SIGN($number); - if ($significance == 0) { - return 1; - } - - $result = self::CEILING($number, $significance); - if ($result == self::EVEN($result)) { - $result += $significance; - } - - return (int) $result; - } - - return Functions::VALUE(); + return MathTrig\Odd::funcOdd($number); } /** @@ -942,27 +522,19 @@ class MathTrig * * Computes x raised to the power y. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Power::evaluate() + * Use the evaluate method in the MathTrig\Power class instead + * * @param float $x * @param float $y * - * @return float|string The result, or a string containing an error + * @return float|int|string The result, or a string containing an error */ public static function POWER($x = 0, $y = 2) { - $x = Functions::flattenSingleValue($x); - $y = Functions::flattenSingleValue($y); - - // Validate parameters - if ($x == 0.0 && $y == 0.0) { - return Functions::NAN(); - } elseif ($x == 0.0 && $y < 0.0) { - return Functions::DIV0(); - } - - // Return - $result = $x ** $y; - - return (!is_nan($result) && !is_infinite($result)) ? $result : Functions::NAN(); + return MathTrig\Power::evaluate($x, $y); } /** @@ -970,36 +542,21 @@ class MathTrig * * PRODUCT returns the product of all the values and cells referenced in the argument list. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Product::funcProduct() + * Use the funcProduct method in the MathTrig\Product class instead + * * Excel Function: * PRODUCT(value1[,value2[, ...]]) * * @param mixed ...$args Data values * - * @return float + * @return float|string */ public static function PRODUCT(...$args) { - // Return value - $returnValue = null; - - // Loop through arguments - foreach (Functions::flattenArray($args) as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if ($returnValue === null) { - $returnValue = $arg; - } else { - $returnValue *= $arg; - } - } - } - - // Return - if ($returnValue === null) { - return 0; - } - - return $returnValue; + return MathTrig\Product::funcProduct(...$args); } /** @@ -1008,88 +565,60 @@ class MathTrig * QUOTIENT function returns the integer portion of a division. Numerator is the divided number * and denominator is the divisor. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Quotient::funcQuotient() + * Use the funcQuotient method in the MathTrig\Quotient class instead + * * Excel Function: * QUOTIENT(value1[,value2[, ...]]) * - * @param mixed ...$args Data values + * @param mixed $numerator + * @param mixed $denominator * - * @return float + * @return int|string */ - public static function QUOTIENT(...$args) + public static function QUOTIENT($numerator, $denominator) { - // Return value - $returnValue = null; - - // Loop through arguments - foreach (Functions::flattenArray($args) as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if ($returnValue === null) { - $returnValue = ($arg == 0) ? 0 : $arg; - } else { - if (($returnValue == 0) || ($arg == 0)) { - $returnValue = 0; - } else { - $returnValue /= $arg; - } - } - } - } - - // Return - return (int) $returnValue; + return MathTrig\Quotient::funcQuotient($numerator, $denominator); } /** - * RAND. + * RAND/RANDBETWEEN. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Random::randBetween() + * Use the randBetween or randBetween method in the MathTrig\Random class instead * * @param int $min Minimal value * @param int $max Maximal value * - * @return int Random number + * @return float|int|string Random number */ public static function RAND($min = 0, $max = 0) { - $min = Functions::flattenSingleValue($min); - $max = Functions::flattenSingleValue($max); - - if ($min == 0 && $max == 0) { - return (mt_rand(0, 10000000)) / 10000000; - } - - return mt_rand($min, $max); + return MathTrig\Random::randBetween($min, $max); } + /** + * ROMAN. + * + * Converts a number to Roman numeral + * + * @Deprecated 1.17.0 + * + * @Ssee MathTrig\Roman::funcRoman() + * Use the funcRoman() method in the MathTrig\Roman class instead + * + * @param mixed $aValue Number to convert + * @param mixed $style Number indicating one of five possible forms + * + * @return string Roman numeral, or a string containing an error + */ public static function ROMAN($aValue, $style = 0) { - $aValue = Functions::flattenSingleValue($aValue); - $style = ($style === null) ? 0 : (int) Functions::flattenSingleValue($style); - if ((!is_numeric($aValue)) || ($aValue < 0) || ($aValue >= 4000)) { - return Functions::VALUE(); - } - $aValue = (int) $aValue; - if ($aValue == 0) { - return ''; - } - - $mill = ['', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM']; - $cent = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM']; - $tens = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC']; - $ones = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX']; - - $roman = ''; - while ($aValue > 5999) { - $roman .= 'M'; - $aValue -= 1000; - } - $m = self::romanCut($aValue, 1000); - $aValue %= 1000; - $c = self::romanCut($aValue, 100); - $aValue %= 100; - $t = self::romanCut($aValue, 10); - $aValue %= 10; - - return $roman . $mill[$m] . $cent[$c] . $tens[$t] . $ones[$aValue]; + return MathTrig\Roman::funcRoman($aValue, $style); } /** @@ -1097,6 +626,11 @@ class MathTrig * * Rounds a number up to a specified number of decimal places * + * @Deprecated 1.17.0 + * + * @See MathTrig\RoundUp::funcRoundUp() + * Use the funcRoundUp() method in the MathTrig\RoundUp class instead + * * @param float $number Number to round * @param int $digits Number of digits to which you want to round $number * @@ -1104,22 +638,7 @@ class MathTrig */ public static function ROUNDUP($number, $digits) { - $number = Functions::flattenSingleValue($number); - $digits = Functions::flattenSingleValue($digits); - - if ((is_numeric($number)) && (is_numeric($digits))) { - if ($number == 0.0) { - return 0.0; - } - - if ($number < 0.0) { - return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); - } - - return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); - } - - return Functions::VALUE(); + return MathTrig\RoundUp::funcRoundUp($number, $digits); } /** @@ -1127,6 +646,11 @@ class MathTrig * * Rounds a number down to a specified number of decimal places * + * @Deprecated 1.17.0 + * + * @See MathTrig\RoundDown::funcRoundDown() + * Use the funcRoundDown() method in the MathTrig\RoundDown class instead + * * @param float $number Number to round * @param int $digits Number of digits to which you want to round $number * @@ -1134,22 +658,7 @@ class MathTrig */ public static function ROUNDDOWN($number, $digits) { - $number = Functions::flattenSingleValue($number); - $digits = Functions::flattenSingleValue($digits); - - if ((is_numeric($number)) && (is_numeric($digits))) { - if ($number == 0.0) { - return 0.0; - } - - if ($number < 0.0) { - return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP); - } - - return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP); - } - - return Functions::VALUE(); + return MathTrig\RoundDown::funcRoundDown($number, $digits); } /** @@ -1157,37 +666,21 @@ class MathTrig * * Returns the sum of a power series * - * @param mixed[] $args An array of mixed values for the Data Series + * @Deprecated 1.18.0 + * + * @See MathTrig\SeriesSum::funcSeriesSum() + * Use the funcSeriesSum method in the MathTrig\SeriesSum class instead + * + * @param mixed $x Input value + * @param mixed $n Initial power + * @param mixed $m Step + * @param mixed[] $args An array of coefficients for the Data Series * * @return float|string The result, or a string containing an error */ - public static function SERIESSUM(...$args) + public static function SERIESSUM($x, $n, $m, ...$args) { - $returnValue = 0; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - - $x = array_shift($aArgs); - $n = array_shift($aArgs); - $m = array_shift($aArgs); - - if ((is_numeric($x)) && (is_numeric($n)) && (is_numeric($m))) { - // Calculate - $i = 0; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $returnValue += $arg * $x ** ($n + ($m * $i++)); - } else { - return Functions::VALUE(); - } - } - - return $returnValue; - } - - return Functions::VALUE(); + return MathTrig\SeriesSum::funcSeriesSum($x, $n, $m, ...$args); } /** @@ -1196,26 +689,31 @@ class MathTrig * Determines the sign of a number. Returns 1 if the number is positive, zero (0) * if the number is 0, and -1 if the number is negative. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Sign::funcSign() + * Use the funcSign method in the MathTrig\Sign class instead + * * @param float $number Number to round * * @return int|string sign value, or a string containing an error */ public static function SIGN($number) { - $number = Functions::flattenSingleValue($number); + return MathTrig\Sign::funcSign($number); + } - if (is_bool($number)) { - return (int) $number; - } - if (is_numeric($number)) { - if ($number == 0.0) { - return 0; - } - - return $number / abs($number); - } - - return Functions::VALUE(); + /** + * returnSign = returns 0/-1/+1. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Helpers::returnSign() + * Use the returnSign method in the MathTrig\Helpers class instead + */ + public static function returnSign(float $number): int + { + return MathTrig\Helpers::returnSign($number); } /** @@ -1223,57 +721,18 @@ class MathTrig * * Returns the square root of (number * pi). * + * @Deprecated 1.18.0 + * + * @See MathTrig\SqrtPi::evaluate() + * Use the evaluate method in the MathTrig\SqrtPi class instead + * * @param float $number Number * * @return float|string Square Root of Number * Pi, or a string containing an error */ public static function SQRTPI($number) { - $number = Functions::flattenSingleValue($number); - - if (is_numeric($number)) { - if ($number < 0) { - return Functions::NAN(); - } - - return sqrt($number * M_PI); - } - - return Functions::VALUE(); - } - - protected static function filterHiddenArgs($cellReference, $args) - { - return array_filter( - $args, - function ($index) use ($cellReference) { - [, $row, $column] = explode('.', $index); - - return $cellReference->getWorksheet()->getRowDimension($row)->getVisible() && - $cellReference->getWorksheet()->getColumnDimension($column)->getVisible(); - }, - ARRAY_FILTER_USE_KEY - ); - } - - protected static function filterFormulaArgs($cellReference, $args) - { - return array_filter( - $args, - function ($index) use ($cellReference) { - [, $row, $column] = explode('.', $index); - if ($cellReference->getWorksheet()->cellExists($column . $row)) { - //take this cell out if it contains the SUBTOTAL or AGGREGATE functions in a formula - $isFormula = $cellReference->getWorksheet()->getCell($column . $row)->isFormula(); - $cellFormula = !preg_match('/^=.*\b(SUBTOTAL|AGGREGATE)\s*\(/i', $cellReference->getWorksheet()->getCell($column . $row)->getValue()); - - return !$isFormula || $cellFormula; - } - - return true; - }, - ARRAY_FILTER_USE_KEY - ); + return MathTrig\SqrtPi::evaluate($number); } /** @@ -1281,6 +740,11 @@ class MathTrig * * Returns a subtotal in a list or database. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Subtotal::funcSubtotal() + * Use the funcSubtotal method in the MathTrig\Subtotal class instead + * * @param int $functionType * A number 1 to 11 that specifies which function to * use in calculating subtotals within a range @@ -1294,45 +758,7 @@ class MathTrig */ public static function SUBTOTAL($functionType, ...$args) { - $cellReference = array_pop($args); - $aArgs = Functions::flattenArrayIndexed($args); - $subtotal = Functions::flattenSingleValue($functionType); - - // Calculate - if ((is_numeric($subtotal)) && (!is_string($subtotal))) { - if ($subtotal > 100) { - $aArgs = self::filterHiddenArgs($cellReference, $aArgs); - $subtotal -= 100; - } - - $aArgs = self::filterFormulaArgs($cellReference, $aArgs); - switch ($subtotal) { - case 1: - return Statistical::AVERAGE($aArgs); - case 2: - return Statistical::COUNT($aArgs); - case 3: - return Statistical::COUNTA($aArgs); - case 4: - return Statistical::MAX($aArgs); - case 5: - return Statistical::MIN($aArgs); - case 6: - return self::PRODUCT($aArgs); - case 7: - return Statistical::STDEV($aArgs); - case 8: - return Statistical::STDEVP($aArgs); - case 9: - return self::SUM($aArgs); - case 10: - return Statistical::VARFunc($aArgs); - case 11: - return Statistical::VARP($aArgs); - } - } - - return Functions::VALUE(); + return MathTrig\Subtotal::funcSubtotal($functionType, ...$args); } /** @@ -1340,131 +766,67 @@ class MathTrig * * SUM computes the sum of all the values and cells referenced in the argument list. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Sum::funcSumNoStrings() + * Use the funcSumNoStrings method in the MathTrig\Sum class instead + * * Excel Function: * SUM(value1[,value2[, ...]]) * * @param mixed ...$args Data values * - * @return float + * @return float|string */ public static function SUM(...$args) { - $returnValue = 0; - - // Loop through the arguments - foreach (Functions::flattenArray($args) as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $returnValue += $arg; - } elseif (Functions::isError($arg)) { - return $arg; - } - } - - return $returnValue; + return MathTrig\Sum::funcSum(...$args); } /** * SUMIF. * - * Counts the number of cells that contain numbers within the list of arguments + * Totals the values of cells that contain numbers within the list of arguments * * Excel Function: - * SUMIF(value1[,value2[, ...]],condition) + * SUMIF(range, criteria, [sum_range]) * - * @param mixed $aArgs Data values - * @param string $condition the criteria that defines which cells will be summed - * @param mixed $sumArgs + * @Deprecated 1.17.0 * - * @return float + * @see Statistical\Conditional::SUMIF() + * Use the SUMIF() method in the Statistical\Conditional class instead + * + * @param mixed $range Data values + * @param string $criteria the criteria that defines which cells will be summed + * @param mixed $sumRange + * + * @return float|string */ - public static function SUMIF($aArgs, $condition, $sumArgs = []) + public static function SUMIF($range, $criteria, $sumRange = []) { - $returnValue = 0; - - $aArgs = Functions::flattenArray($aArgs); - $sumArgs = Functions::flattenArray($sumArgs); - if (empty($sumArgs)) { - $sumArgs = $aArgs; - } - $condition = Functions::ifCondition($condition); - // Loop through arguments - foreach ($aArgs as $key => $arg) { - if (!is_numeric($arg)) { - $arg = str_replace('"', '""', $arg); - $arg = Calculation::wrapResult(strtoupper($arg)); - } - - $testCondition = '=' . $arg . $condition; - $sumValue = array_key_exists($key, $sumArgs) ? $sumArgs[$key] : 0; - - if ( - is_numeric($sumValue) && - Calculation::getInstance()->_calculateFormulaValue($testCondition) - ) { - // Is it a value within our criteria and only numeric can be added to the result - $returnValue += $sumValue; - } - } - - return $returnValue; + return Statistical\Conditional::SUMIF($range, $criteria, $sumRange); } /** * SUMIFS. * - * Counts the number of cells that contain numbers within the list of arguments + * Totals the values of cells that contain numbers within the list of arguments * * Excel Function: - * SUMIFS(value1[,value2[, ...]],condition) + * SUMIFS(sum_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) + * + * @Deprecated 1.17.0 + * + * @see Statistical\Conditional::SUMIFS() + * Use the SUMIFS() method in the Statistical\Conditional class instead * * @param mixed $args Data values * - * @return float + * @return float|string */ public static function SUMIFS(...$args) { - $arrayList = $args; - - // Return value - $returnValue = 0; - - $sumArgs = Functions::flattenArray(array_shift($arrayList)); - $aArgsArray = []; - $conditions = []; - - while (count($arrayList) > 0) { - $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); - $conditions[] = Functions::ifCondition(array_shift($arrayList)); - } - - // Loop through each sum and see if arguments and conditions are true - foreach ($sumArgs as $index => $value) { - $valid = true; - - foreach ($conditions as $cidx => $condition) { - $arg = $aArgsArray[$cidx][$index]; - - // Loop through arguments - if (!is_numeric($arg)) { - $arg = Calculation::wrapResult(strtoupper($arg)); - } - $testCondition = '=' . $arg . $condition; - if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - // Is not a value within our criteria - $valid = false; - - break; // if false found, don't need to check other conditions - } - } - - if ($valid) { - $returnValue += $value; - } - } - - // Return - return $returnValue; + return Statistical\Conditional::SUMIFS(...$args); } /** @@ -1473,39 +835,18 @@ class MathTrig * Excel Function: * SUMPRODUCT(value1[,value2[, ...]]) * + * @Deprecated 1.18.0 + * + * @See MathTrig\SumProduct::funcSumProduct() + * Use the funcSumProduct method in the MathTrig\SumProduct class instead + * * @param mixed ...$args Data values * * @return float|string The result, or a string containing an error */ public static function SUMPRODUCT(...$args) { - $arrayList = $args; - - $wrkArray = Functions::flattenArray(array_shift($arrayList)); - $wrkCellCount = count($wrkArray); - - for ($i = 0; $i < $wrkCellCount; ++$i) { - if ((!is_numeric($wrkArray[$i])) || (is_string($wrkArray[$i]))) { - $wrkArray[$i] = 0; - } - } - - foreach ($arrayList as $matrixData) { - $array2 = Functions::flattenArray($matrixData); - $count = count($array2); - if ($wrkCellCount != $count) { - return Functions::VALUE(); - } - - foreach ($array2 as $i => $val) { - if ((!is_numeric($val)) || (is_string($val))) { - $val = 0; - } - $wrkArray[$i] *= $val; - } - } - - return array_sum($wrkArray); + return MathTrig\SumProduct::funcSumProduct(...$args); } /** @@ -1513,107 +854,75 @@ class MathTrig * * SUMSQ returns the sum of the squares of the arguments * + * @Deprecated 1.18.0 + * + * @See MathTrig\SumSquares::sumSquare() + * Use the sumSquare method in the MathTrig\SumSquares class instead + * * Excel Function: * SUMSQ(value1[,value2[, ...]]) * * @param mixed ...$args Data values * - * @return float + * @return float|string */ public static function SUMSQ(...$args) { - $returnValue = 0; - - // Loop through arguments - foreach (Functions::flattenArray($args) as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $returnValue += ($arg * $arg); - } - } - - return $returnValue; + return MathTrig\SumSquares::sumSquare(...$args); } /** * SUMX2MY2. * + * @Deprecated 1.18.0 + * + * @See MathTrig\SumSquares::sumXSquaredMinusYSquared() + * Use the sumXSquaredMinusYSquared method in the MathTrig\SumSquares class instead + * * @param mixed[] $matrixData1 Matrix #1 * @param mixed[] $matrixData2 Matrix #2 * - * @return float + * @return float|string */ public static function SUMX2MY2($matrixData1, $matrixData2) { - $array1 = Functions::flattenArray($matrixData1); - $array2 = Functions::flattenArray($matrixData2); - $count = min(count($array1), count($array2)); - - $result = 0; - for ($i = 0; $i < $count; ++$i) { - if ( - ((is_numeric($array1[$i])) && (!is_string($array1[$i]))) && - ((is_numeric($array2[$i])) && (!is_string($array2[$i]))) - ) { - $result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]); - } - } - - return $result; + return MathTrig\SumSquares::sumXSquaredMinusYSquared($matrixData1, $matrixData2); } /** * SUMX2PY2. * + * @Deprecated 1.18.0 + * + * @See MathTrig\SumSquares::sumXSquaredPlusYSquared() + * Use the sumXSquaredPlusYSquared method in the MathTrig\SumSquares class instead + * * @param mixed[] $matrixData1 Matrix #1 * @param mixed[] $matrixData2 Matrix #2 * - * @return float + * @return float|string */ public static function SUMX2PY2($matrixData1, $matrixData2) { - $array1 = Functions::flattenArray($matrixData1); - $array2 = Functions::flattenArray($matrixData2); - $count = min(count($array1), count($array2)); - - $result = 0; - for ($i = 0; $i < $count; ++$i) { - if ( - ((is_numeric($array1[$i])) && (!is_string($array1[$i]))) && - ((is_numeric($array2[$i])) && (!is_string($array2[$i]))) - ) { - $result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]); - } - } - - return $result; + return MathTrig\SumSquares::sumXSquaredPlusYSquared($matrixData1, $matrixData2); } /** * SUMXMY2. * + * @Deprecated 1.18.0 + * + * @See MathTrig\SumSquares::sumXMinusYSquared() + * Use the sumXMinusYSquared method in the MathTrig\SumSquares class instead + * * @param mixed[] $matrixData1 Matrix #1 * @param mixed[] $matrixData2 Matrix #2 * - * @return float + * @return float|string */ public static function SUMXMY2($matrixData1, $matrixData2) { - $array1 = Functions::flattenArray($matrixData1); - $array2 = Functions::flattenArray($matrixData2); - $count = min(count($array1), count($array2)); - - $result = 0; - for ($i = 0; $i < $count; ++$i) { - if ( - ((is_numeric($array1[$i])) && (!is_string($array1[$i]))) && - ((is_numeric($array2[$i])) && (!is_string($array2[$i]))) - ) { - $result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]); - } - } - - return $result; + return MathTrig\SumSquares::sumXMinusYSquared($matrixData1, $matrixData2); } /** @@ -1621,6 +930,11 @@ class MathTrig * * Truncates value to the number of fractional digits by number_digits. * + * @Deprecated 1.17.0 + * + * @see MathTrig\Trunc::funcTrunc() + * Use the funcTrunc() method in the MathTrig\Trunc class instead + * * @param float $value * @param int $digits * @@ -1628,23 +942,7 @@ class MathTrig */ public static function TRUNC($value = 0, $digits = 0) { - $value = Functions::flattenSingleValue($value); - $digits = Functions::flattenSingleValue($digits); - - // Validate parameters - if ((!is_numeric($value)) || (!is_numeric($digits))) { - return Functions::VALUE(); - } - $digits = floor($digits); - - // Truncate - $adjust = 10 ** $digits; - - if (($digits > 0) && (rtrim((int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) { - return $value; - } - - return ((int) ($value * $adjust)) / $adjust; + return MathTrig\Trunc::funcTrunc($value, $digits); } /** @@ -1652,21 +950,18 @@ class MathTrig * * Returns the secant of an angle. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Sec::funcSec() + * Use the funcSec method in the MathTrig\Sec class instead + * * @param float $angle Number * * @return float|string The secant of the angle */ public static function SEC($angle) { - $angle = Functions::flattenSingleValue($angle); - - if (!is_numeric($angle)) { - return Functions::VALUE(); - } - - $result = cos($angle); - - return ($result == 0.0) ? Functions::DIV0() : 1 / $result; + return MathTrig\Sec::funcSec($angle); } /** @@ -1674,21 +969,18 @@ class MathTrig * * Returns the hyperbolic secant of an angle. * + * @Deprecated 1.18.0 + * + * @See MathTrig\SecH::funcSech() + * Use the funcSecH method in the MathTrig\Sech class instead + * * @param float $angle Number * * @return float|string The hyperbolic secant of the angle */ public static function SECH($angle) { - $angle = Functions::flattenSingleValue($angle); - - if (!is_numeric($angle)) { - return Functions::VALUE(); - } - - $result = cosh($angle); - - return ($result == 0.0) ? Functions::DIV0() : 1 / $result; + return MathTrig\Sech::funcSech($angle); } /** @@ -1696,21 +988,18 @@ class MathTrig * * Returns the cosecant of an angle. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Csc::funcCsc() + * Use the funcCsc method in the MathTrig\Csc class instead + * * @param float $angle Number * * @return float|string The cosecant of the angle */ public static function CSC($angle) { - $angle = Functions::flattenSingleValue($angle); - - if (!is_numeric($angle)) { - return Functions::VALUE(); - } - - $result = sin($angle); - - return ($result == 0.0) ? Functions::DIV0() : 1 / $result; + return MathTrig\Csc::funcCsc($angle); } /** @@ -1718,21 +1007,18 @@ class MathTrig * * Returns the hyperbolic cosecant of an angle. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Csch::funcCsch() + * Use the funcCsch method in the MathTrig\Csch class instead + * * @param float $angle Number * * @return float|string The hyperbolic cosecant of the angle */ public static function CSCH($angle) { - $angle = Functions::flattenSingleValue($angle); - - if (!is_numeric($angle)) { - return Functions::VALUE(); - } - - $result = sinh($angle); - - return ($result == 0.0) ? Functions::DIV0() : 1 / $result; + return MathTrig\Csch::funcCsch($angle); } /** @@ -1740,21 +1026,18 @@ class MathTrig * * Returns the cotangent of an angle. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Cot::funcCot() + * Use the funcCot method in the MathTrig\Cot class instead + * * @param float $angle Number * * @return float|string The cotangent of the angle */ public static function COT($angle) { - $angle = Functions::flattenSingleValue($angle); - - if (!is_numeric($angle)) { - return Functions::VALUE(); - } - - $result = tan($angle); - - return ($result == 0.0) ? Functions::DIV0() : 1 / $result; + return MathTrig\Cot::funcCot($angle); } /** @@ -1762,21 +1045,18 @@ class MathTrig * * Returns the hyperbolic cotangent of an angle. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Coth::funcCoth() + * Use the funcCoth method in the MathTrig\Coth class instead + * * @param float $angle Number * * @return float|string The hyperbolic cotangent of the angle */ public static function COTH($angle) { - $angle = Functions::flattenSingleValue($angle); - - if (!is_numeric($angle)) { - return Functions::VALUE(); - } - - $result = tanh($angle); - - return ($result == 0.0) ? Functions::DIV0() : 1 / $result; + return MathTrig\Coth::funcCoth($angle); } /** @@ -1784,19 +1064,35 @@ class MathTrig * * Returns the arccotangent of a number. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Acot::funcAcot() + * Use the funcAcot method in the MathTrig\Acot class instead + * * @param float $number Number * * @return float|string The arccotangent of the number */ public static function ACOT($number) { - $number = Functions::flattenSingleValue($number); + return MathTrig\Acot::funcAcot($number); + } - if (!is_numeric($number)) { - return Functions::VALUE(); - } - - return (M_PI / 2) - atan($number); + /** + * Return NAN or value depending on argument. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Helpers::numberOrNan() + * Use the numberOrNan method in the MathTrig\Helpers class instead + * + * @param float $result Number + * + * @return float|string + */ + public static function numberOrNan($result) + { + return MathTrig\Helpers::numberOrNan($result); } /** @@ -1804,20 +1100,418 @@ class MathTrig * * Returns the hyperbolic arccotangent of a number. * + * @Deprecated 1.18.0 + * + * @See MathTrig\Acoth::funcAcoth() + * Use the funcAcoth method in the MathTrig\Acoth class instead + * * @param float $number Number * * @return float|string The hyperbolic arccotangent of the number */ public static function ACOTH($number) + { + return MathTrig\Acoth::funcAcoth($number); + } + + /** + * ROUND. + * + * Returns the result of builtin function round after validating args. + * + * @Deprecated 1.17.0 + * + * @See MathTrig\Round::builtinROUND() + * Use the builtinRound() method in the MathTrig\Round class instead + * + * @param mixed $number Should be numeric + * @param mixed $precision Should be int + * + * @return float|string Rounded number + */ + public static function builtinROUND($number, $precision) + { + return MathTrig\Round::builtinRound($number, $precision); + } + + /** + * ABS. + * + * Returns the result of builtin function abs after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Absolute::evaluate() + * Use the evaluate method in the MathTrig\Absolute class instead + * + * @param mixed $number Should be numeric + * + * @return float|int|string Rounded number + */ + public static function builtinABS($number) + { + return MathTrig\Absolute::evaluate($number); + } + + /** + * ACOS. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Acos::funcAcos() + * Use the funcAcos method in the MathTrig\Acos class instead + * + * Returns the result of builtin function acos after validating args. + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinACOS($number) + { + return MathTrig\Acos::funcAcos($number); + } + + /** + * ACOSH. + * + * Returns the result of builtin function acosh after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Acosh::funcAcosh() + * Use the funcAcosh method in the MathTrig\Acosh class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinACOSH($number) + { + return MathTrig\Acosh::funcAcosh($number); + } + + /** + * ASIN. + * + * Returns the result of builtin function asin after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Asin::funcAsin() + * Use the funcAsin method in the MathTrig\Asin class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinASIN($number) + { + return MathTrig\Asin::funcAsin($number); + } + + /** + * ASINH. + * + * Returns the result of builtin function asinh after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Asinh::funcAsinh() + * Use the funcAsinh method in the MathTrig\Asinh class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinASINH($number) + { + return MathTrig\Asinh::funcAsinh($number); + } + + /** + * ATAN. + * + * Returns the result of builtin function atan after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Atan::funcAtan() + * Use the funcAtan method in the MathTrig\Atan class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinATAN($number) + { + return MathTrig\Atan::funcAtan($number); + } + + /** + * ATANH. + * + * Returns the result of builtin function atanh after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Atanh::funcAtanh() + * Use the funcAtanh method in the MathTrig\Atanh class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinATANH($number) + { + return MathTrig\Atanh::funcAtanh($number); + } + + /** + * COS. + * + * Returns the result of builtin function cos after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Cos::funcCos() + * Use the funcCos method in the MathTrig\Cos class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinCOS($number) + { + return MathTrig\Cos::funcCos($number); + } + + /** + * COSH. + * + * Returns the result of builtin function cos after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Cosh::funcCosh() + * Use the funcCosh method in the MathTrig\Cosh class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinCOSH($number) + { + return MathTrig\Cosh::funcCosh($number); + } + + /** + * DEGREES. + * + * Returns the result of builtin function rad2deg after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Degrees::evaluate() + * Use the evaluate method in the MathTrig\Degrees class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinDEGREES($number) + { + return MathTrig\Degrees::evaluate($number); + } + + /** + * EXP. + * + * Returns the result of builtin function exp after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Exp::evaluate() + * Use the evaluate method in the MathTrig\Exp class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinEXP($number) + { + return MathTrig\Exp::evaluate($number); + } + + /** + * LN. + * + * Returns the result of builtin function log after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Logarithms::natural() + * Use the natural method in the MathTrig\Logarithms class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinLN($number) + { + return MathTrig\Logarithms::natural($number); + } + + /** + * LOG10. + * + * Returns the result of builtin function log after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Logarithms::base10() + * Use the natural method in the MathTrig\Logarithms class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinLOG10($number) + { + return MathTrig\Logarithms::base10($number); + } + + /** + * RADIANS. + * + * Returns the result of builtin function deg2rad after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Radians::evaluate() + * Use the evaluate method in the MathTrig\Radians class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinRADIANS($number) + { + return MathTrig\Radians::evaluate($number); + } + + /** + * SIN. + * + * Returns the result of builtin function sin after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Sin::funcSin() + * Use the funcSin method in the MathTrig\Sin class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinSIN($number) + { + return MathTrig\Sin::funcSin($number); + } + + /** + * SINH. + * + * Returns the result of builtin function sinh after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Sinh::funcSinh() + * Use the funcSinh method in the MathTrig\Sinh class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinSINH($number) + { + return MathTrig\Sinh::funcSinh($number); + } + + /** + * SQRT. + * + * Returns the result of builtin function sqrt after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Sqrt::evaluate() + * Use the evaluate method in the MathTrig\Sqrt class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinSQRT($number) + { + return MathTrig\Sqrt::evaluate($number); + } + + /** + * TAN. + * + * Returns the result of builtin function tan after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Tan::funcTan() + * Use the funcTan method in the MathTrig\Tan class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinTAN($number) + { + return MathTrig\Tan::funcTan($number); + } + + /** + * TANH. + * + * Returns the result of builtin function sinh after validating args. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Tan::funcTan() + * Use the funcTanh method in the MathTrig\Tanh class instead + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function builtinTANH($number) + { + return MathTrig\Tanh::funcTanh($number); + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @Deprecated 1.18.0 + * + * @See MathTrig\Helpers::validateNumericNullBool() + * Use the validateNumericNullBool method in the MathTrig\Helpers class instead + * + * @param mixed $number + */ + public static function nullFalseTrueToNumber(&$number): void { $number = Functions::flattenSingleValue($number); - - if (!is_numeric($number)) { - return Functions::VALUE(); + if ($number === null) { + $number = 0; + } elseif (is_bool($number)) { + $number = (int) $number; } - - $result = log(($number + 1) / ($number - 1)) / 2; - - return is_nan($result) ? Functions::NAN() : $result; } } diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Absolute.php b/src/PhpSpreadsheet/Calculation/MathTrig/Absolute.php new file mode 100644 index 00000000..c2dc579f --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Absolute.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return abs($number); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Acos.php b/src/PhpSpreadsheet/Calculation/MathTrig/Acos.php new file mode 100644 index 00000000..20d645f2 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Acos.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::numberOrNan(acos($number)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Acosh.php b/src/PhpSpreadsheet/Calculation/MathTrig/Acosh.php new file mode 100644 index 00000000..f77d3a09 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Acosh.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::numberOrNan(acosh($number)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Acot.php b/src/PhpSpreadsheet/Calculation/MathTrig/Acot.php new file mode 100644 index 00000000..1024f9f6 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Acot.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return (M_PI / 2) - atan($number); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Acoth.php b/src/PhpSpreadsheet/Calculation/MathTrig/Acoth.php new file mode 100644 index 00000000..42bdc181 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Acoth.php @@ -0,0 +1,30 @@ +getMessage(); + } + + $result = ($number === 1) ? NAN : (log(($number + 1) / ($number - 1)) / 2); + + return Helpers::numberOrNan($result); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php b/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php new file mode 100644 index 00000000..320856b9 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php @@ -0,0 +1,95 @@ + 1000, + 'D' => 500, + 'C' => 100, + 'L' => 50, + 'X' => 10, + 'V' => 5, + 'I' => 1, + ]; + + /** + * Recursively calculate the arabic value of a roman numeral. + * + * @param int $sum + * @param int $subtract + * + * @return int + */ + private static function calculateArabic(array $roman, &$sum = 0, $subtract = 0) + { + $numeral = array_shift($roman); + if (!isset(self::ROMAN_LOOKUP[$numeral])) { + throw new Exception('Invalid character detected'); + } + + $arabic = self::ROMAN_LOOKUP[$numeral]; + if (count($roman) > 0 && isset(self::ROMAN_LOOKUP[$roman[0]]) && $arabic < self::ROMAN_LOOKUP[$roman[0]]) { + $subtract += $arabic; + } else { + $sum += ($arabic - $subtract); + $subtract = 0; + } + + if (count($roman) > 0) { + self::calculateArabic($roman, $sum, $subtract); + } + + return $sum; + } + + private static function strSplit(string $roman): array + { + $rslt = str_split($roman); + + return is_array($rslt) ? $rslt : []; + } + + /** + * ARABIC. + * + * Converts a Roman numeral to an Arabic numeral. + * + * Excel Function: + * ARABIC(text) + * + * @param string $roman + * + * @return int|string the arabic numberal contrived from the roman numeral + */ + public static function evaluate($roman) + { + // An empty string should return 0 + $roman = substr(trim(strtoupper((string) Functions::flattenSingleValue($roman))), 0, 255); + if ($roman === '') { + return 0; + } + + // Convert the roman numeral to an arabic number + $negativeNumber = $roman[0] === '-'; + if ($negativeNumber) { + $roman = substr($roman, 1); + } + + try { + $arabic = self::calculateArabic(self::strSplit($roman)); + } catch (Exception $e) { + return Functions::VALUE(); // Invalid character detected + } + + if ($negativeNumber) { + $arabic *= -1; // The number should be negative + } + + return $arabic; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Asin.php b/src/PhpSpreadsheet/Calculation/MathTrig/Asin.php new file mode 100644 index 00000000..e30ab04c --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Asin.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::numberOrNan(asin($number)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Asinh.php b/src/PhpSpreadsheet/Calculation/MathTrig/Asinh.php new file mode 100644 index 00000000..35a3ae26 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Asinh.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::numberOrNan(asinh($number)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Atan.php b/src/PhpSpreadsheet/Calculation/MathTrig/Atan.php new file mode 100644 index 00000000..3e57f048 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Atan.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::numberOrNan(atan($number)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Atan2.php b/src/PhpSpreadsheet/Calculation/MathTrig/Atan2.php new file mode 100644 index 00000000..2ea975a8 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Atan2.php @@ -0,0 +1,46 @@ +getMessage(); + } + + if (($xCoordinate == 0) && ($yCoordinate == 0)) { + return Functions::DIV0(); + } + + return atan2($yCoordinate, $xCoordinate); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Atanh.php b/src/PhpSpreadsheet/Calculation/MathTrig/Atanh.php new file mode 100644 index 00000000..a9723f16 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Atanh.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::numberOrNan(atanh($number)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Base.php b/src/PhpSpreadsheet/Calculation/MathTrig/Base.php new file mode 100644 index 00000000..35522334 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Base.php @@ -0,0 +1,49 @@ +getMessage(); + } + $minLength = Functions::flattenSingleValue($minLength); + + if ($minLength === null || is_numeric($minLength)) { + if ($number < 0 || $number >= 2 ** 53 || $radix < 2 || $radix > 36) { + return Functions::NAN(); // Numeric range constraints + } + + $outcome = strtoupper((string) base_convert($number, 10, $radix)); + if ($minLength !== null) { + $outcome = str_pad($outcome, (int) $minLength, '0', STR_PAD_LEFT); // String padding + } + + return $outcome; + } + + return Functions::VALUE(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php b/src/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php new file mode 100644 index 00000000..1085158a --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php @@ -0,0 +1,66 @@ +getMessage(); + } + + return self::argumentsOk((float) $number, (float) $significance); + } + + /** + * Avoid Scrutinizer problems concerning complexity. + * + * @return float|string + */ + private static function argumentsOk(float $number, float $significance) + { + if (empty($number * $significance)) { + return 0.0; + } + if (Helpers::returnSign($number) == Helpers::returnSign($significance)) { + return ceil($number / $significance) * $significance; + } + + return Functions::NAN(); + } + + private static function floorCheck1Arg(): void + { + $compatibility = Functions::getCompatibilityMode(); + if ($compatibility === Functions::COMPATIBILITY_EXCEL) { + throw new Exception('Excel requires 2 arguments for CEILING'); + } + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/CeilingMath.php b/src/PhpSpreadsheet/Calculation/MathTrig/CeilingMath.php new file mode 100644 index 00000000..e41e9d09 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/CeilingMath.php @@ -0,0 +1,50 @@ +getMessage(); + } + + if (empty($significance * $number)) { + return 0.0; + } + if (self::ceilingMathTest((float) $significance, (float) $number, (int) $mode)) { + return floor($number / $significance) * $significance; + } + + return ceil($number / $significance) * $significance; + } + + /** + * Let CEILINGMATH complexity pass Scrutinizer. + */ + private static function ceilingMathTest(float $significance, float $number, int $mode): bool + { + return ((float) $significance < 0) || ((float) $number < 0 && !empty($mode)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/CeilingPrecise.php b/src/PhpSpreadsheet/Calculation/MathTrig/CeilingPrecise.php new file mode 100644 index 00000000..1bc4504b --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/CeilingPrecise.php @@ -0,0 +1,38 @@ +getMessage(); + } + + if (!$significance) { + return 0.0; + } + $result = $number / abs($significance); + + return ceil($result) * $significance * (($significance < 0) ? -1 : 1); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php b/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php new file mode 100644 index 00000000..78b18fc6 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php @@ -0,0 +1,74 @@ +getMessage(); + } + + return round(Fact::funcFact($numObjs) / Fact::funcFact($numObjs - $numInSet)) / Fact::funcFact($numInSet); + } + + /** + * COMBIN. + * + * Returns the number of combinations for a given number of items. Use COMBIN to + * determine the total possible number of groups for a given number of items. + * + * Excel Function: + * COMBIN(numObjs,numInSet) + * + * @param mixed $numObjs Number of different objects + * @param mixed $numInSet Number of objects in each combination + * + * @return float|int|string Number of combinations, or a string containing an error + */ + public static function withRepetition($numObjs, $numInSet) + { + try { + $numObjs = Helpers::validateNumericNullSubstitution($numObjs, null); + $numInSet = Helpers::validateNumericNullSubstitution($numInSet, null); + Helpers::validateNotNegative($numInSet); + Helpers::validateNotNegative($numObjs); + $numObjs = (int) $numObjs; + $numInSet = (int) $numInSet; + // Microsoft documentation says following is true, but Excel + // does not enforce this restriction. + //Helpers::validateNotNegative($numObjs - $numInSet); + if ($numObjs === 0) { + Helpers::validateNotNegative(-$numInSet); + + return 1; + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return round(Fact::funcFact($numObjs + $numInSet - 1) / Fact::funcFact($numObjs - 1)) / Fact::funcFact($numInSet); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Cos.php b/src/PhpSpreadsheet/Calculation/MathTrig/Cos.php new file mode 100644 index 00000000..2dfed782 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Cos.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return cos($number); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Cosh.php b/src/PhpSpreadsheet/Calculation/MathTrig/Cosh.php new file mode 100644 index 00000000..3e806cd6 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Cosh.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return cosh($number); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Cot.php b/src/PhpSpreadsheet/Calculation/MathTrig/Cot.php new file mode 100644 index 00000000..1cf5c6b4 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Cot.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(cos($angle), sin($angle)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Coth.php b/src/PhpSpreadsheet/Calculation/MathTrig/Coth.php new file mode 100644 index 00000000..c80ec93e --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Coth.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(1.0, tanh($angle)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Csc.php b/src/PhpSpreadsheet/Calculation/MathTrig/Csc.php new file mode 100644 index 00000000..325637b8 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Csc.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(1.0, sin($angle)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Csch.php b/src/PhpSpreadsheet/Calculation/MathTrig/Csch.php new file mode 100644 index 00000000..8a045203 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Csch.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(1.0, sinh($angle)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Degrees.php b/src/PhpSpreadsheet/Calculation/MathTrig/Degrees.php new file mode 100644 index 00000000..501817be --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Degrees.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return rad2deg($number); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Even.php b/src/PhpSpreadsheet/Calculation/MathTrig/Even.php new file mode 100644 index 00000000..ac79a211 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Even.php @@ -0,0 +1,35 @@ +getMessage(); + } + + return Helpers::getEven($number); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php b/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php new file mode 100644 index 00000000..f3f8af59 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return exp($number); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Fact.php b/src/PhpSpreadsheet/Calculation/MathTrig/Fact.php new file mode 100644 index 00000000..298ccbac --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Fact.php @@ -0,0 +1,47 @@ +getMessage(); + } + + $factLoop = floor($factVal); + if ($factVal > $factLoop) { + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + return Statistical\Distributions\Gamma::gammaValue($factVal + 1); + } + } + + $factorial = 1; + while ($factLoop > 1) { + $factorial *= $factLoop--; + } + + return $factorial; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/FactDouble.php b/src/PhpSpreadsheet/Calculation/MathTrig/FactDouble.php new file mode 100644 index 00000000..4b760144 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/FactDouble.php @@ -0,0 +1,39 @@ +getMessage(); + } + + $factLoop = floor($factVal); + $factorial = 1; + while ($factLoop > 1) { + $factorial *= $factLoop; + $factLoop -= 2; + } + + return $factorial; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php b/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php new file mode 100644 index 00000000..f178b324 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php @@ -0,0 +1,69 @@ +getMessage(); + } + + return self::argumentsOk((float) $number, (float) $significance); + } + + /** + * Avoid Scrutinizer problems concerning complexity. + * + * @return float|string + */ + private static function argumentsOk(float $number, float $significance) + { + if ($significance == 0.0) { + return Functions::DIV0(); + } + if ($number == 0.0) { + return 0.0; + } + if (Helpers::returnSign($significance) == 1) { + return floor($number / $significance) * $significance; + } + if (Helpers::returnSign($number) == -1 && Helpers::returnSign($significance) == -1) { + return floor($number / $significance) * $significance; + } + + return Functions::NAN(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/FloorMath.php b/src/PhpSpreadsheet/Calculation/MathTrig/FloorMath.php new file mode 100644 index 00000000..8b922829 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/FloorMath.php @@ -0,0 +1,64 @@ +getMessage(); + } + + return self::argsOk((float) $number, (float) $significance, (int) $mode); + } + + /** + * Avoid Scrutinizer complexity problems. + * + * @return float|string Rounded Number, or a string containing an error + */ + private static function argsOk(float $number, float $significance, int $mode) + { + if (!$significance) { + return Functions::DIV0(); + } + if (!$number) { + return 0.0; + } + if (self::floorMathTest($number, $significance, $mode)) { + return ceil($number / $significance) * $significance; + } + + return floor($number / $significance) * $significance; + } + + /** + * Let FLOORMATH complexity pass Scrutinizer. + */ + private static function floorMathTest(float $number, float $significance, int $mode): bool + { + return Helpers::returnSign($significance) == -1 || (Helpers::returnSign($number) == -1 && !empty($mode)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/FloorPrecise.php b/src/PhpSpreadsheet/Calculation/MathTrig/FloorPrecise.php new file mode 100644 index 00000000..3ce34dc4 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/FloorPrecise.php @@ -0,0 +1,51 @@ +getMessage(); + } + + return self::argumentsOk((float) $number, (float) $significance); + } + + /** + * Avoid Scrutinizer problems concerning complexity. + * + * @return float|string + */ + private static function argumentsOk(float $number, float $significance) + { + if ($significance == 0.0) { + return Functions::DIV0(); + } + if ($number == 0.0) { + return 0.0; + } + + return floor($number / abs($significance)) * abs($significance); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php b/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php new file mode 100644 index 00000000..21c22699 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php @@ -0,0 +1,69 @@ +getMessage(); + } + + if (count($arrayArgs) <= 0) { + return Functions::VALUE(); + } + $gcd = (int) array_pop($arrayArgs); + do { + $gcd = self::evaluateGCD($gcd, (int) array_pop($arrayArgs)); + } while (!empty($arrayArgs)); + + return $gcd; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php b/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php new file mode 100644 index 00000000..26716467 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php @@ -0,0 +1,129 @@ += 0. + * + * @param float|int $number + */ + public static function validateNotNegative($number, ?string $except = null): void + { + if ($number >= 0) { + return; + } + + throw new Exception($except ?? Functions::NAN()); + } + + /** + * Confirm number > 0. + * + * @param float|int $number + */ + public static function validatePositive($number, ?string $except = null): void + { + if ($number > 0) { + return; + } + + throw new Exception($except ?? Functions::NAN()); + } + + /** + * Confirm number != 0. + * + * @param float|int $number + */ + public static function validateNotZero($number): void + { + if ($number) { + return; + } + + throw new Exception(Functions::DIV0()); + } + + public static function returnSign(float $number): int + { + return $number ? (($number > 0) ? 1 : -1) : 0; + } + + public static function getEven(float $number): float + { + $significance = 2 * self::returnSign($number); + + return $significance ? (ceil($number / $significance) * $significance) : 0; + } + + /** + * Return NAN or value depending on argument. + * + * @param float $result Number + * + * @return float|string + */ + public static function numberOrNan($result) + { + return is_nan($result) ? Functions::NAN() : $result; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php b/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php new file mode 100644 index 00000000..e43fe65c --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php @@ -0,0 +1,31 @@ +getMessage(); + } + + return (int) floor($number); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php b/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php new file mode 100644 index 00000000..38d9b620 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php @@ -0,0 +1,110 @@ + 1; --$i) { + if (($value % $i) == 0) { + $factorArray = array_merge($factorArray, self::factors($value / $i)); + $factorArray = array_merge($factorArray, self::factors($i)); + if ($i <= sqrt($value)) { + break; + } + } + } + if (!empty($factorArray)) { + rsort($factorArray); + + return $factorArray; + } + + return [(int) $value]; + } + + /** + * LCM. + * + * Returns the lowest common multiplier of a series of numbers + * The least common multiple is the smallest positive integer that is a multiple + * of all integer arguments number1, number2, and so on. Use LCM to add fractions + * with different denominators. + * + * Excel Function: + * LCM(number1[,number2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return int|string Lowest Common Multiplier, or a string containing an error + */ + public static function funcLcm(...$args) + { + try { + $arrayArgs = []; + $anyZeros = 0; + $anyNonNulls = 0; + foreach (Functions::flattenArray($args) as $value1) { + $anyNonNulls += (int) ($value1 !== null); + $value = Helpers::validateNumericNullSubstitution($value1, 1); + Helpers::validateNotNegative($value); + $arrayArgs[] = (int) $value; + $anyZeros += (int) !((bool) $value); + } + self::testNonNulls($anyNonNulls); + if ($anyZeros) { + return 0; + } + } catch (Exception $e) { + return $e->getMessage(); + } + + $returnValue = 1; + $allPoweredFactors = []; + // Loop through arguments + foreach ($arrayArgs as $value) { + $myFactors = self::factors(floor($value)); + $myCountedFactors = array_count_values($myFactors); + $myPoweredFactors = []; + foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) { + $myPoweredFactors[$myCountedFactor] = $myCountedFactor ** $myCountedPower; + } + self::processPoweredFactors($allPoweredFactors, $myPoweredFactors); + } + foreach ($allPoweredFactors as $allPoweredFactor) { + $returnValue *= (int) $allPoweredFactor; + } + + return $returnValue; + } + + private static function processPoweredFactors(array &$allPoweredFactors, array &$myPoweredFactors): void + { + foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) { + if (isset($allPoweredFactors[$myPoweredValue])) { + if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) { + $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; + } + } else { + $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; + } + } + } + + private static function testNonNulls(int $anyNonNulls): void + { + if (!$anyNonNulls) { + throw new Exception(Functions::VALUE()); + } + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php b/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php new file mode 100644 index 00000000..169a74b0 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php @@ -0,0 +1,77 @@ +getMessage(); + } + + return log($number, $base); + } + + /** + * LOG10. + * + * Returns the result of builtin function log after validating args. + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function base10($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + Helpers::validatePositive($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return log10($number); + } + + /** + * LN. + * + * Returns the result of builtin function log after validating args. + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function natural($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + Helpers::validatePositive($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return log($number); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php b/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php new file mode 100644 index 00000000..f0eea049 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php @@ -0,0 +1,138 @@ +determinant(); + } catch (MatrixException $ex) { + return Functions::VALUE(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + + /** + * MINVERSE. + * + * Returns the inverse matrix for the matrix stored in an array. + * + * Excel Function: + * MINVERSE(array) + * + * @param mixed $matrixValues A matrix of values + * + * @return array|string The result, or a string containing an error + */ + public static function inverse($matrixValues) + { + try { + $matrix = self::getMatrix($matrixValues); + + return $matrix->inverse()->toArray(); + } catch (MatrixException $e) { + return (strpos($e->getMessage(), 'determinant') === false) ? Functions::VALUE() : Functions::NAN(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + + /** + * MMULT. + * + * @param mixed $matrixData1 A matrix of values + * @param mixed $matrixData2 A matrix of values + * + * @return array|string The result, or a string containing an error + */ + public static function multiply($matrixData1, $matrixData2) + { + try { + $matrixA = self::getMatrix($matrixData1); + $matrixB = self::getMatrix($matrixData2); + + return $matrixA->multiply($matrixB)->toArray(); + } catch (MatrixException $ex) { + return Functions::VALUE(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + + /** + * MUnit. + * + * @param mixed $dimension Number of rows and columns + * + * @return array|string The result, or a string containing an error + */ + public static function funcMUnit($dimension) + { + try { + $dimension = (int) Helpers::validateNumericNullBool($dimension); + Helpers::validatePositive($dimension, Functions::VALUE()); + $matrix = Builder::createFilledMatrix(0, $dimension)->toArray(); + for ($x = 0; $x < $dimension; ++$x) { + $matrix[$x][$x] = 1; + } + + return $matrix; + } catch (Exception $e) { + return $e->getMessage(); + } + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Mod.php b/src/PhpSpreadsheet/Calculation/MathTrig/Mod.php new file mode 100644 index 00000000..04267ee9 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Mod.php @@ -0,0 +1,36 @@ +getMessage(); + } + + if (($dividend < 0.0) && ($divisor > 0.0)) { + return $divisor - fmod(abs($dividend), $divisor); + } + if (($dividend > 0.0) && ($divisor < 0.0)) { + return $divisor + fmod($dividend, abs($divisor)); + } + + return fmod($dividend, $divisor); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Mround.php b/src/PhpSpreadsheet/Calculation/MathTrig/Mround.php new file mode 100644 index 00000000..d1b32aa7 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Mround.php @@ -0,0 +1,40 @@ +getMessage(); + } + + if ($number == 0 || $multiple == 0) { + return 0; + } + if ((Helpers::returnSign($number)) == (Helpers::returnSign($multiple))) { + $multiplier = 1 / $multiple; + + return round($number * $multiplier) / $multiplier; + } + + return Functions::NAN(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Multinomial.php b/src/PhpSpreadsheet/Calculation/MathTrig/Multinomial.php new file mode 100644 index 00000000..5ebecb97 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Multinomial.php @@ -0,0 +1,41 @@ +getMessage(); + } + + $summer = Fact::funcFact($summer); + + return $summer / $divisor; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Odd.php b/src/PhpSpreadsheet/Calculation/MathTrig/Odd.php new file mode 100644 index 00000000..b8ef3dd0 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Odd.php @@ -0,0 +1,38 @@ +getMessage(); + } + + $significance = Helpers::returnSign($number); + if ($significance == 0) { + return 1; + } + + $result = ceil($number / $significance) * $significance; + if ($result == Helpers::getEven($result)) { + $result += $significance; + } + + return $result; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Power.php b/src/PhpSpreadsheet/Calculation/MathTrig/Power.php new file mode 100644 index 00000000..70d177cd --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Power.php @@ -0,0 +1,42 @@ +getMessage(); + } + + // Validate parameters + if (!$x && !$y) { + return Functions::NAN(); + } + if (!$x && $y < 0.0) { + return Functions::DIV0(); + } + + // Return + $result = $x ** $y; + + return Helpers::numberOrNan($result); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Product.php b/src/PhpSpreadsheet/Calculation/MathTrig/Product.php new file mode 100644 index 00000000..254b7b79 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Product.php @@ -0,0 +1,47 @@ +getMessage(); + } + + return (int) ($numerator / $denominator); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Radians.php b/src/PhpSpreadsheet/Calculation/MathTrig/Radians.php new file mode 100644 index 00000000..15e97011 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Radians.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return deg2rad($number); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Random.php b/src/PhpSpreadsheet/Calculation/MathTrig/Random.php new file mode 100644 index 00000000..1a384fe9 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Random.php @@ -0,0 +1,39 @@ +getMessage(); + } + + return mt_rand($min, $max); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php b/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php new file mode 100644 index 00000000..05ecb531 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php @@ -0,0 +1,839 @@ + ['VL'], + 46 => ['VLI'], + 47 => ['VLII'], + 48 => ['VLIII'], + 49 => ['VLIV', 'IL'], + 95 => ['VC'], + 96 => ['VCI'], + 97 => ['VCII'], + 98 => ['VCIII'], + 99 => ['VCIV', 'IC'], + 145 => ['CVL'], + 146 => ['CVLI'], + 147 => ['CVLII'], + 148 => ['CVLIII'], + 149 => ['CVLIV', 'CIL'], + 195 => ['CVC'], + 196 => ['CVCI'], + 197 => ['CVCII'], + 198 => ['CVCIII'], + 199 => ['CVCIV', 'CIC'], + 245 => ['CCVL'], + 246 => ['CCVLI'], + 247 => ['CCVLII'], + 248 => ['CCVLIII'], + 249 => ['CCVLIV', 'CCIL'], + 295 => ['CCVC'], + 296 => ['CCVCI'], + 297 => ['CCVCII'], + 298 => ['CCVCIII'], + 299 => ['CCVCIV', 'CCIC'], + 345 => ['CCCVL'], + 346 => ['CCCVLI'], + 347 => ['CCCVLII'], + 348 => ['CCCVLIII'], + 349 => ['CCCVLIV', 'CCCIL'], + 395 => ['CCCVC'], + 396 => ['CCCVCI'], + 397 => ['CCCVCII'], + 398 => ['CCCVCIII'], + 399 => ['CCCVCIV', 'CCCIC'], + 445 => ['CDVL'], + 446 => ['CDVLI'], + 447 => ['CDVLII'], + 448 => ['CDVLIII'], + 449 => ['CDVLIV', 'CDIL'], + 450 => ['LD'], + 451 => ['LDI'], + 452 => ['LDII'], + 453 => ['LDIII'], + 454 => ['LDIV'], + 455 => ['LDV'], + 456 => ['LDVI'], + 457 => ['LDVII'], + 458 => ['LDVIII'], + 459 => ['LDIX'], + 460 => ['LDX'], + 461 => ['LDXI'], + 462 => ['LDXII'], + 463 => ['LDXIII'], + 464 => ['LDXIV'], + 465 => ['LDXV'], + 466 => ['LDXVI'], + 467 => ['LDXVII'], + 468 => ['LDXVIII'], + 469 => ['LDXIX'], + 470 => ['LDXX'], + 471 => ['LDXXI'], + 472 => ['LDXXII'], + 473 => ['LDXXIII'], + 474 => ['LDXXIV'], + 475 => ['LDXXV'], + 476 => ['LDXXVI'], + 477 => ['LDXXVII'], + 478 => ['LDXXVIII'], + 479 => ['LDXXIX'], + 480 => ['LDXXX'], + 481 => ['LDXXXI'], + 482 => ['LDXXXII'], + 483 => ['LDXXXIII'], + 484 => ['LDXXXIV'], + 485 => ['LDXXXV'], + 486 => ['LDXXXVI'], + 487 => ['LDXXXVII'], + 488 => ['LDXXXVIII'], + 489 => ['LDXXXIX'], + 490 => ['LDXL', 'XD'], + 491 => ['LDXLI', 'XDI'], + 492 => ['LDXLII', 'XDII'], + 493 => ['LDXLIII', 'XDIII'], + 494 => ['LDXLIV', 'XDIV'], + 495 => ['LDVL', 'XDV', 'VD'], + 496 => ['LDVLI', 'XDVI', 'VDI'], + 497 => ['LDVLII', 'XDVII', 'VDII'], + 498 => ['LDVLIII', 'XDVIII', 'VDIII'], + 499 => ['LDVLIV', 'XDIX', 'VDIV', 'ID'], + 545 => ['DVL'], + 546 => ['DVLI'], + 547 => ['DVLII'], + 548 => ['DVLIII'], + 549 => ['DVLIV', 'DIL'], + 595 => ['DVC'], + 596 => ['DVCI'], + 597 => ['DVCII'], + 598 => ['DVCIII'], + 599 => ['DVCIV', 'DIC'], + 645 => ['DCVL'], + 646 => ['DCVLI'], + 647 => ['DCVLII'], + 648 => ['DCVLIII'], + 649 => ['DCVLIV', 'DCIL'], + 695 => ['DCVC'], + 696 => ['DCVCI'], + 697 => ['DCVCII'], + 698 => ['DCVCIII'], + 699 => ['DCVCIV', 'DCIC'], + 745 => ['DCCVL'], + 746 => ['DCCVLI'], + 747 => ['DCCVLII'], + 748 => ['DCCVLIII'], + 749 => ['DCCVLIV', 'DCCIL'], + 795 => ['DCCVC'], + 796 => ['DCCVCI'], + 797 => ['DCCVCII'], + 798 => ['DCCVCIII'], + 799 => ['DCCVCIV', 'DCCIC'], + 845 => ['DCCCVL'], + 846 => ['DCCCVLI'], + 847 => ['DCCCVLII'], + 848 => ['DCCCVLIII'], + 849 => ['DCCCVLIV', 'DCCCIL'], + 895 => ['DCCCVC'], + 896 => ['DCCCVCI'], + 897 => ['DCCCVCII'], + 898 => ['DCCCVCIII'], + 899 => ['DCCCVCIV', 'DCCCIC'], + 945 => ['CMVL'], + 946 => ['CMVLI'], + 947 => ['CMVLII'], + 948 => ['CMVLIII'], + 949 => ['CMVLIV', 'CMIL'], + 950 => ['LM'], + 951 => ['LMI'], + 952 => ['LMII'], + 953 => ['LMIII'], + 954 => ['LMIV'], + 955 => ['LMV'], + 956 => ['LMVI'], + 957 => ['LMVII'], + 958 => ['LMVIII'], + 959 => ['LMIX'], + 960 => ['LMX'], + 961 => ['LMXI'], + 962 => ['LMXII'], + 963 => ['LMXIII'], + 964 => ['LMXIV'], + 965 => ['LMXV'], + 966 => ['LMXVI'], + 967 => ['LMXVII'], + 968 => ['LMXVIII'], + 969 => ['LMXIX'], + 970 => ['LMXX'], + 971 => ['LMXXI'], + 972 => ['LMXXII'], + 973 => ['LMXXIII'], + 974 => ['LMXXIV'], + 975 => ['LMXXV'], + 976 => ['LMXXVI'], + 977 => ['LMXXVII'], + 978 => ['LMXXVIII'], + 979 => ['LMXXIX'], + 980 => ['LMXXX'], + 981 => ['LMXXXI'], + 982 => ['LMXXXII'], + 983 => ['LMXXXIII'], + 984 => ['LMXXXIV'], + 985 => ['LMXXXV'], + 986 => ['LMXXXVI'], + 987 => ['LMXXXVII'], + 988 => ['LMXXXVIII'], + 989 => ['LMXXXIX'], + 990 => ['LMXL', 'XM'], + 991 => ['LMXLI', 'XMI'], + 992 => ['LMXLII', 'XMII'], + 993 => ['LMXLIII', 'XMIII'], + 994 => ['LMXLIV', 'XMIV'], + 995 => ['LMVL', 'XMV', 'VM'], + 996 => ['LMVLI', 'XMVI', 'VMI'], + 997 => ['LMVLII', 'XMVII', 'VMII'], + 998 => ['LMVLIII', 'XMVIII', 'VMIII'], + 999 => ['LMVLIV', 'XMIX', 'VMIV', 'IM'], + 1045 => ['MVL'], + 1046 => ['MVLI'], + 1047 => ['MVLII'], + 1048 => ['MVLIII'], + 1049 => ['MVLIV', 'MIL'], + 1095 => ['MVC'], + 1096 => ['MVCI'], + 1097 => ['MVCII'], + 1098 => ['MVCIII'], + 1099 => ['MVCIV', 'MIC'], + 1145 => ['MCVL'], + 1146 => ['MCVLI'], + 1147 => ['MCVLII'], + 1148 => ['MCVLIII'], + 1149 => ['MCVLIV', 'MCIL'], + 1195 => ['MCVC'], + 1196 => ['MCVCI'], + 1197 => ['MCVCII'], + 1198 => ['MCVCIII'], + 1199 => ['MCVCIV', 'MCIC'], + 1245 => ['MCCVL'], + 1246 => ['MCCVLI'], + 1247 => ['MCCVLII'], + 1248 => ['MCCVLIII'], + 1249 => ['MCCVLIV', 'MCCIL'], + 1295 => ['MCCVC'], + 1296 => ['MCCVCI'], + 1297 => ['MCCVCII'], + 1298 => ['MCCVCIII'], + 1299 => ['MCCVCIV', 'MCCIC'], + 1345 => ['MCCCVL'], + 1346 => ['MCCCVLI'], + 1347 => ['MCCCVLII'], + 1348 => ['MCCCVLIII'], + 1349 => ['MCCCVLIV', 'MCCCIL'], + 1395 => ['MCCCVC'], + 1396 => ['MCCCVCI'], + 1397 => ['MCCCVCII'], + 1398 => ['MCCCVCIII'], + 1399 => ['MCCCVCIV', 'MCCCIC'], + 1445 => ['MCDVL'], + 1446 => ['MCDVLI'], + 1447 => ['MCDVLII'], + 1448 => ['MCDVLIII'], + 1449 => ['MCDVLIV', 'MCDIL'], + 1450 => ['MLD'], + 1451 => ['MLDI'], + 1452 => ['MLDII'], + 1453 => ['MLDIII'], + 1454 => ['MLDIV'], + 1455 => ['MLDV'], + 1456 => ['MLDVI'], + 1457 => ['MLDVII'], + 1458 => ['MLDVIII'], + 1459 => ['MLDIX'], + 1460 => ['MLDX'], + 1461 => ['MLDXI'], + 1462 => ['MLDXII'], + 1463 => ['MLDXIII'], + 1464 => ['MLDXIV'], + 1465 => ['MLDXV'], + 1466 => ['MLDXVI'], + 1467 => ['MLDXVII'], + 1468 => ['MLDXVIII'], + 1469 => ['MLDXIX'], + 1470 => ['MLDXX'], + 1471 => ['MLDXXI'], + 1472 => ['MLDXXII'], + 1473 => ['MLDXXIII'], + 1474 => ['MLDXXIV'], + 1475 => ['MLDXXV'], + 1476 => ['MLDXXVI'], + 1477 => ['MLDXXVII'], + 1478 => ['MLDXXVIII'], + 1479 => ['MLDXXIX'], + 1480 => ['MLDXXX'], + 1481 => ['MLDXXXI'], + 1482 => ['MLDXXXII'], + 1483 => ['MLDXXXIII'], + 1484 => ['MLDXXXIV'], + 1485 => ['MLDXXXV'], + 1486 => ['MLDXXXVI'], + 1487 => ['MLDXXXVII'], + 1488 => ['MLDXXXVIII'], + 1489 => ['MLDXXXIX'], + 1490 => ['MLDXL', 'MXD'], + 1491 => ['MLDXLI', 'MXDI'], + 1492 => ['MLDXLII', 'MXDII'], + 1493 => ['MLDXLIII', 'MXDIII'], + 1494 => ['MLDXLIV', 'MXDIV'], + 1495 => ['MLDVL', 'MXDV', 'MVD'], + 1496 => ['MLDVLI', 'MXDVI', 'MVDI'], + 1497 => ['MLDVLII', 'MXDVII', 'MVDII'], + 1498 => ['MLDVLIII', 'MXDVIII', 'MVDIII'], + 1499 => ['MLDVLIV', 'MXDIX', 'MVDIV', 'MID'], + 1545 => ['MDVL'], + 1546 => ['MDVLI'], + 1547 => ['MDVLII'], + 1548 => ['MDVLIII'], + 1549 => ['MDVLIV', 'MDIL'], + 1595 => ['MDVC'], + 1596 => ['MDVCI'], + 1597 => ['MDVCII'], + 1598 => ['MDVCIII'], + 1599 => ['MDVCIV', 'MDIC'], + 1645 => ['MDCVL'], + 1646 => ['MDCVLI'], + 1647 => ['MDCVLII'], + 1648 => ['MDCVLIII'], + 1649 => ['MDCVLIV', 'MDCIL'], + 1695 => ['MDCVC'], + 1696 => ['MDCVCI'], + 1697 => ['MDCVCII'], + 1698 => ['MDCVCIII'], + 1699 => ['MDCVCIV', 'MDCIC'], + 1745 => ['MDCCVL'], + 1746 => ['MDCCVLI'], + 1747 => ['MDCCVLII'], + 1748 => ['MDCCVLIII'], + 1749 => ['MDCCVLIV', 'MDCCIL'], + 1795 => ['MDCCVC'], + 1796 => ['MDCCVCI'], + 1797 => ['MDCCVCII'], + 1798 => ['MDCCVCIII'], + 1799 => ['MDCCVCIV', 'MDCCIC'], + 1845 => ['MDCCCVL'], + 1846 => ['MDCCCVLI'], + 1847 => ['MDCCCVLII'], + 1848 => ['MDCCCVLIII'], + 1849 => ['MDCCCVLIV', 'MDCCCIL'], + 1895 => ['MDCCCVC'], + 1896 => ['MDCCCVCI'], + 1897 => ['MDCCCVCII'], + 1898 => ['MDCCCVCIII'], + 1899 => ['MDCCCVCIV', 'MDCCCIC'], + 1945 => ['MCMVL'], + 1946 => ['MCMVLI'], + 1947 => ['MCMVLII'], + 1948 => ['MCMVLIII'], + 1949 => ['MCMVLIV', 'MCMIL'], + 1950 => ['MLM'], + 1951 => ['MLMI'], + 1952 => ['MLMII'], + 1953 => ['MLMIII'], + 1954 => ['MLMIV'], + 1955 => ['MLMV'], + 1956 => ['MLMVI'], + 1957 => ['MLMVII'], + 1958 => ['MLMVIII'], + 1959 => ['MLMIX'], + 1960 => ['MLMX'], + 1961 => ['MLMXI'], + 1962 => ['MLMXII'], + 1963 => ['MLMXIII'], + 1964 => ['MLMXIV'], + 1965 => ['MLMXV'], + 1966 => ['MLMXVI'], + 1967 => ['MLMXVII'], + 1968 => ['MLMXVIII'], + 1969 => ['MLMXIX'], + 1970 => ['MLMXX'], + 1971 => ['MLMXXI'], + 1972 => ['MLMXXII'], + 1973 => ['MLMXXIII'], + 1974 => ['MLMXXIV'], + 1975 => ['MLMXXV'], + 1976 => ['MLMXXVI'], + 1977 => ['MLMXXVII'], + 1978 => ['MLMXXVIII'], + 1979 => ['MLMXXIX'], + 1980 => ['MLMXXX'], + 1981 => ['MLMXXXI'], + 1982 => ['MLMXXXII'], + 1983 => ['MLMXXXIII'], + 1984 => ['MLMXXXIV'], + 1985 => ['MLMXXXV'], + 1986 => ['MLMXXXVI'], + 1987 => ['MLMXXXVII'], + 1988 => ['MLMXXXVIII'], + 1989 => ['MLMXXXIX'], + 1990 => ['MLMXL', 'MXM'], + 1991 => ['MLMXLI', 'MXMI'], + 1992 => ['MLMXLII', 'MXMII'], + 1993 => ['MLMXLIII', 'MXMIII'], + 1994 => ['MLMXLIV', 'MXMIV'], + 1995 => ['MLMVL', 'MXMV', 'MVM'], + 1996 => ['MLMVLI', 'MXMVI', 'MVMI'], + 1997 => ['MLMVLII', 'MXMVII', 'MVMII'], + 1998 => ['MLMVLIII', 'MXMVIII', 'MVMIII'], + 1999 => ['MLMVLIV', 'MXMIX', 'MVMIV', 'MIM'], + 2045 => ['MMVL'], + 2046 => ['MMVLI'], + 2047 => ['MMVLII'], + 2048 => ['MMVLIII'], + 2049 => ['MMVLIV', 'MMIL'], + 2095 => ['MMVC'], + 2096 => ['MMVCI'], + 2097 => ['MMVCII'], + 2098 => ['MMVCIII'], + 2099 => ['MMVCIV', 'MMIC'], + 2145 => ['MMCVL'], + 2146 => ['MMCVLI'], + 2147 => ['MMCVLII'], + 2148 => ['MMCVLIII'], + 2149 => ['MMCVLIV', 'MMCIL'], + 2195 => ['MMCVC'], + 2196 => ['MMCVCI'], + 2197 => ['MMCVCII'], + 2198 => ['MMCVCIII'], + 2199 => ['MMCVCIV', 'MMCIC'], + 2245 => ['MMCCVL'], + 2246 => ['MMCCVLI'], + 2247 => ['MMCCVLII'], + 2248 => ['MMCCVLIII'], + 2249 => ['MMCCVLIV', 'MMCCIL'], + 2295 => ['MMCCVC'], + 2296 => ['MMCCVCI'], + 2297 => ['MMCCVCII'], + 2298 => ['MMCCVCIII'], + 2299 => ['MMCCVCIV', 'MMCCIC'], + 2345 => ['MMCCCVL'], + 2346 => ['MMCCCVLI'], + 2347 => ['MMCCCVLII'], + 2348 => ['MMCCCVLIII'], + 2349 => ['MMCCCVLIV', 'MMCCCIL'], + 2395 => ['MMCCCVC'], + 2396 => ['MMCCCVCI'], + 2397 => ['MMCCCVCII'], + 2398 => ['MMCCCVCIII'], + 2399 => ['MMCCCVCIV', 'MMCCCIC'], + 2445 => ['MMCDVL'], + 2446 => ['MMCDVLI'], + 2447 => ['MMCDVLII'], + 2448 => ['MMCDVLIII'], + 2449 => ['MMCDVLIV', 'MMCDIL'], + 2450 => ['MMLD'], + 2451 => ['MMLDI'], + 2452 => ['MMLDII'], + 2453 => ['MMLDIII'], + 2454 => ['MMLDIV'], + 2455 => ['MMLDV'], + 2456 => ['MMLDVI'], + 2457 => ['MMLDVII'], + 2458 => ['MMLDVIII'], + 2459 => ['MMLDIX'], + 2460 => ['MMLDX'], + 2461 => ['MMLDXI'], + 2462 => ['MMLDXII'], + 2463 => ['MMLDXIII'], + 2464 => ['MMLDXIV'], + 2465 => ['MMLDXV'], + 2466 => ['MMLDXVI'], + 2467 => ['MMLDXVII'], + 2468 => ['MMLDXVIII'], + 2469 => ['MMLDXIX'], + 2470 => ['MMLDXX'], + 2471 => ['MMLDXXI'], + 2472 => ['MMLDXXII'], + 2473 => ['MMLDXXIII'], + 2474 => ['MMLDXXIV'], + 2475 => ['MMLDXXV'], + 2476 => ['MMLDXXVI'], + 2477 => ['MMLDXXVII'], + 2478 => ['MMLDXXVIII'], + 2479 => ['MMLDXXIX'], + 2480 => ['MMLDXXX'], + 2481 => ['MMLDXXXI'], + 2482 => ['MMLDXXXII'], + 2483 => ['MMLDXXXIII'], + 2484 => ['MMLDXXXIV'], + 2485 => ['MMLDXXXV'], + 2486 => ['MMLDXXXVI'], + 2487 => ['MMLDXXXVII'], + 2488 => ['MMLDXXXVIII'], + 2489 => ['MMLDXXXIX'], + 2490 => ['MMLDXL', 'MMXD'], + 2491 => ['MMLDXLI', 'MMXDI'], + 2492 => ['MMLDXLII', 'MMXDII'], + 2493 => ['MMLDXLIII', 'MMXDIII'], + 2494 => ['MMLDXLIV', 'MMXDIV'], + 2495 => ['MMLDVL', 'MMXDV', 'MMVD'], + 2496 => ['MMLDVLI', 'MMXDVI', 'MMVDI'], + 2497 => ['MMLDVLII', 'MMXDVII', 'MMVDII'], + 2498 => ['MMLDVLIII', 'MMXDVIII', 'MMVDIII'], + 2499 => ['MMLDVLIV', 'MMXDIX', 'MMVDIV', 'MMID'], + 2545 => ['MMDVL'], + 2546 => ['MMDVLI'], + 2547 => ['MMDVLII'], + 2548 => ['MMDVLIII'], + 2549 => ['MMDVLIV', 'MMDIL'], + 2595 => ['MMDVC'], + 2596 => ['MMDVCI'], + 2597 => ['MMDVCII'], + 2598 => ['MMDVCIII'], + 2599 => ['MMDVCIV', 'MMDIC'], + 2645 => ['MMDCVL'], + 2646 => ['MMDCVLI'], + 2647 => ['MMDCVLII'], + 2648 => ['MMDCVLIII'], + 2649 => ['MMDCVLIV', 'MMDCIL'], + 2695 => ['MMDCVC'], + 2696 => ['MMDCVCI'], + 2697 => ['MMDCVCII'], + 2698 => ['MMDCVCIII'], + 2699 => ['MMDCVCIV', 'MMDCIC'], + 2745 => ['MMDCCVL'], + 2746 => ['MMDCCVLI'], + 2747 => ['MMDCCVLII'], + 2748 => ['MMDCCVLIII'], + 2749 => ['MMDCCVLIV', 'MMDCCIL'], + 2795 => ['MMDCCVC'], + 2796 => ['MMDCCVCI'], + 2797 => ['MMDCCVCII'], + 2798 => ['MMDCCVCIII'], + 2799 => ['MMDCCVCIV', 'MMDCCIC'], + 2845 => ['MMDCCCVL'], + 2846 => ['MMDCCCVLI'], + 2847 => ['MMDCCCVLII'], + 2848 => ['MMDCCCVLIII'], + 2849 => ['MMDCCCVLIV', 'MMDCCCIL'], + 2895 => ['MMDCCCVC'], + 2896 => ['MMDCCCVCI'], + 2897 => ['MMDCCCVCII'], + 2898 => ['MMDCCCVCIII'], + 2899 => ['MMDCCCVCIV', 'MMDCCCIC'], + 2945 => ['MMCMVL'], + 2946 => ['MMCMVLI'], + 2947 => ['MMCMVLII'], + 2948 => ['MMCMVLIII'], + 2949 => ['MMCMVLIV', 'MMCMIL'], + 2950 => ['MMLM'], + 2951 => ['MMLMI'], + 2952 => ['MMLMII'], + 2953 => ['MMLMIII'], + 2954 => ['MMLMIV'], + 2955 => ['MMLMV'], + 2956 => ['MMLMVI'], + 2957 => ['MMLMVII'], + 2958 => ['MMLMVIII'], + 2959 => ['MMLMIX'], + 2960 => ['MMLMX'], + 2961 => ['MMLMXI'], + 2962 => ['MMLMXII'], + 2963 => ['MMLMXIII'], + 2964 => ['MMLMXIV'], + 2965 => ['MMLMXV'], + 2966 => ['MMLMXVI'], + 2967 => ['MMLMXVII'], + 2968 => ['MMLMXVIII'], + 2969 => ['MMLMXIX'], + 2970 => ['MMLMXX'], + 2971 => ['MMLMXXI'], + 2972 => ['MMLMXXII'], + 2973 => ['MMLMXXIII'], + 2974 => ['MMLMXXIV'], + 2975 => ['MMLMXXV'], + 2976 => ['MMLMXXVI'], + 2977 => ['MMLMXXVII'], + 2978 => ['MMLMXXVIII'], + 2979 => ['MMLMXXIX'], + 2980 => ['MMLMXXX'], + 2981 => ['MMLMXXXI'], + 2982 => ['MMLMXXXII'], + 2983 => ['MMLMXXXIII'], + 2984 => ['MMLMXXXIV'], + 2985 => ['MMLMXXXV'], + 2986 => ['MMLMXXXVI'], + 2987 => ['MMLMXXXVII'], + 2988 => ['MMLMXXXVIII'], + 2989 => ['MMLMXXXIX'], + 2990 => ['MMLMXL', 'MMXM'], + 2991 => ['MMLMXLI', 'MMXMI'], + 2992 => ['MMLMXLII', 'MMXMII'], + 2993 => ['MMLMXLIII', 'MMXMIII'], + 2994 => ['MMLMXLIV', 'MMXMIV'], + 2995 => ['MMLMVL', 'MMXMV', 'MMVM'], + 2996 => ['MMLMVLI', 'MMXMVI', 'MMVMI'], + 2997 => ['MMLMVLII', 'MMXMVII', 'MMVMII'], + 2998 => ['MMLMVLIII', 'MMXMVIII', 'MMVMIII'], + 2999 => ['MMLMVLIV', 'MMXMIX', 'MMVMIV', 'MMIM'], + 3045 => ['MMMVL'], + 3046 => ['MMMVLI'], + 3047 => ['MMMVLII'], + 3048 => ['MMMVLIII'], + 3049 => ['MMMVLIV', 'MMMIL'], + 3095 => ['MMMVC'], + 3096 => ['MMMVCI'], + 3097 => ['MMMVCII'], + 3098 => ['MMMVCIII'], + 3099 => ['MMMVCIV', 'MMMIC'], + 3145 => ['MMMCVL'], + 3146 => ['MMMCVLI'], + 3147 => ['MMMCVLII'], + 3148 => ['MMMCVLIII'], + 3149 => ['MMMCVLIV', 'MMMCIL'], + 3195 => ['MMMCVC'], + 3196 => ['MMMCVCI'], + 3197 => ['MMMCVCII'], + 3198 => ['MMMCVCIII'], + 3199 => ['MMMCVCIV', 'MMMCIC'], + 3245 => ['MMMCCVL'], + 3246 => ['MMMCCVLI'], + 3247 => ['MMMCCVLII'], + 3248 => ['MMMCCVLIII'], + 3249 => ['MMMCCVLIV', 'MMMCCIL'], + 3295 => ['MMMCCVC'], + 3296 => ['MMMCCVCI'], + 3297 => ['MMMCCVCII'], + 3298 => ['MMMCCVCIII'], + 3299 => ['MMMCCVCIV', 'MMMCCIC'], + 3345 => ['MMMCCCVL'], + 3346 => ['MMMCCCVLI'], + 3347 => ['MMMCCCVLII'], + 3348 => ['MMMCCCVLIII'], + 3349 => ['MMMCCCVLIV', 'MMMCCCIL'], + 3395 => ['MMMCCCVC'], + 3396 => ['MMMCCCVCI'], + 3397 => ['MMMCCCVCII'], + 3398 => ['MMMCCCVCIII'], + 3399 => ['MMMCCCVCIV', 'MMMCCCIC'], + 3445 => ['MMMCDVL'], + 3446 => ['MMMCDVLI'], + 3447 => ['MMMCDVLII'], + 3448 => ['MMMCDVLIII'], + 3449 => ['MMMCDVLIV', 'MMMCDIL'], + 3450 => ['MMMLD'], + 3451 => ['MMMLDI'], + 3452 => ['MMMLDII'], + 3453 => ['MMMLDIII'], + 3454 => ['MMMLDIV'], + 3455 => ['MMMLDV'], + 3456 => ['MMMLDVI'], + 3457 => ['MMMLDVII'], + 3458 => ['MMMLDVIII'], + 3459 => ['MMMLDIX'], + 3460 => ['MMMLDX'], + 3461 => ['MMMLDXI'], + 3462 => ['MMMLDXII'], + 3463 => ['MMMLDXIII'], + 3464 => ['MMMLDXIV'], + 3465 => ['MMMLDXV'], + 3466 => ['MMMLDXVI'], + 3467 => ['MMMLDXVII'], + 3468 => ['MMMLDXVIII'], + 3469 => ['MMMLDXIX'], + 3470 => ['MMMLDXX'], + 3471 => ['MMMLDXXI'], + 3472 => ['MMMLDXXII'], + 3473 => ['MMMLDXXIII'], + 3474 => ['MMMLDXXIV'], + 3475 => ['MMMLDXXV'], + 3476 => ['MMMLDXXVI'], + 3477 => ['MMMLDXXVII'], + 3478 => ['MMMLDXXVIII'], + 3479 => ['MMMLDXXIX'], + 3480 => ['MMMLDXXX'], + 3481 => ['MMMLDXXXI'], + 3482 => ['MMMLDXXXII'], + 3483 => ['MMMLDXXXIII'], + 3484 => ['MMMLDXXXIV'], + 3485 => ['MMMLDXXXV'], + 3486 => ['MMMLDXXXVI'], + 3487 => ['MMMLDXXXVII'], + 3488 => ['MMMLDXXXVIII'], + 3489 => ['MMMLDXXXIX'], + 3490 => ['MMMLDXL', 'MMMXD'], + 3491 => ['MMMLDXLI', 'MMMXDI'], + 3492 => ['MMMLDXLII', 'MMMXDII'], + 3493 => ['MMMLDXLIII', 'MMMXDIII'], + 3494 => ['MMMLDXLIV', 'MMMXDIV'], + 3495 => ['MMMLDVL', 'MMMXDV', 'MMMVD'], + 3496 => ['MMMLDVLI', 'MMMXDVI', 'MMMVDI'], + 3497 => ['MMMLDVLII', 'MMMXDVII', 'MMMVDII'], + 3498 => ['MMMLDVLIII', 'MMMXDVIII', 'MMMVDIII'], + 3499 => ['MMMLDVLIV', 'MMMXDIX', 'MMMVDIV', 'MMMID'], + 3545 => ['MMMDVL'], + 3546 => ['MMMDVLI'], + 3547 => ['MMMDVLII'], + 3548 => ['MMMDVLIII'], + 3549 => ['MMMDVLIV', 'MMMDIL'], + 3595 => ['MMMDVC'], + 3596 => ['MMMDVCI'], + 3597 => ['MMMDVCII'], + 3598 => ['MMMDVCIII'], + 3599 => ['MMMDVCIV', 'MMMDIC'], + 3645 => ['MMMDCVL'], + 3646 => ['MMMDCVLI'], + 3647 => ['MMMDCVLII'], + 3648 => ['MMMDCVLIII'], + 3649 => ['MMMDCVLIV', 'MMMDCIL'], + 3695 => ['MMMDCVC'], + 3696 => ['MMMDCVCI'], + 3697 => ['MMMDCVCII'], + 3698 => ['MMMDCVCIII'], + 3699 => ['MMMDCVCIV', 'MMMDCIC'], + 3745 => ['MMMDCCVL'], + 3746 => ['MMMDCCVLI'], + 3747 => ['MMMDCCVLII'], + 3748 => ['MMMDCCVLIII'], + 3749 => ['MMMDCCVLIV', 'MMMDCCIL'], + 3795 => ['MMMDCCVC'], + 3796 => ['MMMDCCVCI'], + 3797 => ['MMMDCCVCII'], + 3798 => ['MMMDCCVCIII'], + 3799 => ['MMMDCCVCIV', 'MMMDCCIC'], + 3845 => ['MMMDCCCVL'], + 3846 => ['MMMDCCCVLI'], + 3847 => ['MMMDCCCVLII'], + 3848 => ['MMMDCCCVLIII'], + 3849 => ['MMMDCCCVLIV', 'MMMDCCCIL'], + 3895 => ['MMMDCCCVC'], + 3896 => ['MMMDCCCVCI'], + 3897 => ['MMMDCCCVCII'], + 3898 => ['MMMDCCCVCIII'], + 3899 => ['MMMDCCCVCIV', 'MMMDCCCIC'], + 3945 => ['MMMCMVL'], + 3946 => ['MMMCMVLI'], + 3947 => ['MMMCMVLII'], + 3948 => ['MMMCMVLIII'], + 3949 => ['MMMCMVLIV', 'MMMCMIL'], + 3950 => ['MMMLM'], + 3951 => ['MMMLMI'], + 3952 => ['MMMLMII'], + 3953 => ['MMMLMIII'], + 3954 => ['MMMLMIV'], + 3955 => ['MMMLMV'], + 3956 => ['MMMLMVI'], + 3957 => ['MMMLMVII'], + 3958 => ['MMMLMVIII'], + 3959 => ['MMMLMIX'], + 3960 => ['MMMLMX'], + 3961 => ['MMMLMXI'], + 3962 => ['MMMLMXII'], + 3963 => ['MMMLMXIII'], + 3964 => ['MMMLMXIV'], + 3965 => ['MMMLMXV'], + 3966 => ['MMMLMXVI'], + 3967 => ['MMMLMXVII'], + 3968 => ['MMMLMXVIII'], + 3969 => ['MMMLMXIX'], + 3970 => ['MMMLMXX'], + 3971 => ['MMMLMXXI'], + 3972 => ['MMMLMXXII'], + 3973 => ['MMMLMXXIII'], + 3974 => ['MMMLMXXIV'], + 3975 => ['MMMLMXXV'], + 3976 => ['MMMLMXXVI'], + 3977 => ['MMMLMXXVII'], + 3978 => ['MMMLMXXVIII'], + 3979 => ['MMMLMXXIX'], + 3980 => ['MMMLMXXX'], + 3981 => ['MMMLMXXXI'], + 3982 => ['MMMLMXXXII'], + 3983 => ['MMMLMXXXIII'], + 3984 => ['MMMLMXXXIV'], + 3985 => ['MMMLMXXXV'], + 3986 => ['MMMLMXXXVI'], + 3987 => ['MMMLMXXXVII'], + 3988 => ['MMMLMXXXVIII'], + 3989 => ['MMMLMXXXIX'], + 3990 => ['MMMLMXL', 'MMMXM'], + 3991 => ['MMMLMXLI', 'MMMXMI'], + 3992 => ['MMMLMXLII', 'MMMXMII'], + 3993 => ['MMMLMXLIII', 'MMMXMIII'], + 3994 => ['MMMLMXLIV', 'MMMXMIV'], + 3995 => ['MMMLMVL', 'MMMXMV', 'MMMVM'], + 3996 => ['MMMLMVLI', 'MMMXMVI', 'MMMVMI'], + 3997 => ['MMMLMVLII', 'MMMXMVII', 'MMMVMII'], + 3998 => ['MMMLMVLIII', 'MMMXMVIII', 'MMMVMIII'], + 3999 => ['MMMLMVLIV', 'MMMXMIX', 'MMMVMIV', 'MMMIM'], + ]; + private const THOUSANDS = ['', 'M', 'MM', 'MMM']; + private const HUNDREDS = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM']; + private const TENS = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC']; + private const ONES = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX']; + const MAX_ROMAN_VALUE = 3999; + const MAX_ROMAN_STYLE = 4; + + private static function romanCut($num, $n) + { + return ($num - ($num % $n)) / $n; + } + + private static function valueOk(int $aValue, int $style): string + { + $origValue = $aValue; + $m = self::romanCut($aValue, 1000); + $aValue %= 1000; + $c = self::romanCut($aValue, 100); + $aValue %= 100; + $t = self::romanCut($aValue, 10); + $aValue %= 10; + $result = self::THOUSANDS[$m] . self::HUNDREDS[$c] . self::TENS[$t] . self::ONES[$aValue]; + if ($style > 0) { + if (array_key_exists($origValue, self::VALUES)) { + $arr = self::VALUES[$origValue]; + $idx = min($style, count($arr)) - 1; + $result = $arr[$idx]; + } + } + + return $result; + } + + private static function styleOk(int $aValue, int $style): string + { + return ($aValue < 0 || $aValue > self::MAX_ROMAN_VALUE) ? Functions::VALUE() : self::valueOk($aValue, $style); + } + + public static function calculateRoman(int $aValue, int $style): string + { + return ($style < 0 || $style > self::MAX_ROMAN_STYLE) ? Functions::VALUE() : self::styleOk($aValue, $style); + } + + /** + * ROMAN. + * + * Converts a number to Roman numeral + * + * @param mixed $aValue Number to convert + * @param mixed $style Number indicating one of five possible forms + * + * @return string Roman numeral, or a string containing an error + */ + public static function funcRoman($aValue, $style = 0) + { + try { + $aValue = Helpers::validateNumericNullBool($aValue); + if (is_bool($style)) { + $style = $style ? 0 : 4; + } + $style = Helpers::validateNumericNullSubstitution($style, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::calculateRoman((int) $aValue, (int) $style); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Round.php b/src/PhpSpreadsheet/Calculation/MathTrig/Round.php new file mode 100644 index 00000000..bc1c6669 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Round.php @@ -0,0 +1,30 @@ +getMessage(); + } + + return round($number, (int) $precision); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/RoundDown.php b/src/PhpSpreadsheet/Calculation/MathTrig/RoundDown.php new file mode 100644 index 00000000..bf19d5d5 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/RoundDown.php @@ -0,0 +1,38 @@ +getMessage(); + } + + if ($number == 0.0) { + return 0.0; + } + + if ($number < 0.0) { + return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP); + } + + return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/RoundUp.php b/src/PhpSpreadsheet/Calculation/MathTrig/RoundUp.php new file mode 100644 index 00000000..a4f00cd3 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/RoundUp.php @@ -0,0 +1,38 @@ +getMessage(); + } + + if ($number == 0.0) { + return 0.0; + } + + if ($number < 0.0) { + return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); + } + + return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Sec.php b/src/PhpSpreadsheet/Calculation/MathTrig/Sec.php new file mode 100644 index 00000000..9bb5a1b7 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Sec.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(1.0, cos($angle)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Sech.php b/src/PhpSpreadsheet/Calculation/MathTrig/Sech.php new file mode 100644 index 00000000..191bea4d --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Sech.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(1.0, cosh($angle)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php b/src/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php new file mode 100644 index 00000000..063593b6 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php @@ -0,0 +1,46 @@ +getMessage(); + } + + return $returnValue; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php b/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php new file mode 100644 index 00000000..84ff523f --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php @@ -0,0 +1,29 @@ +getMessage(); + } + + return Helpers::returnSign($number); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Sin.php b/src/PhpSpreadsheet/Calculation/MathTrig/Sin.php new file mode 100644 index 00000000..f718451c --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Sin.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return sin($angle); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Sinh.php b/src/PhpSpreadsheet/Calculation/MathTrig/Sinh.php new file mode 100644 index 00000000..ce3ef3e5 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Sinh.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return sinh($angle); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php b/src/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php new file mode 100644 index 00000000..aeb38234 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::numberOrNan(sqrt($number)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/SqrtPi.php b/src/PhpSpreadsheet/Calculation/MathTrig/SqrtPi.php new file mode 100644 index 00000000..6ff79203 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/SqrtPi.php @@ -0,0 +1,29 @@ +getMessage(); + } + + return sqrt($number * M_PI); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php b/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php new file mode 100644 index 00000000..3d441fa2 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php @@ -0,0 +1,99 @@ +getWorksheet()->getRowDimension($row)->getVisible(); + }, + ARRAY_FILTER_USE_KEY + ); + } + + protected static function filterFormulaArgs($cellReference, $args) + { + return array_filter( + $args, + function ($index) use ($cellReference) { + [, $row, $column] = explode('.', $index); + $retVal = true; + if ($cellReference->getWorksheet()->cellExists($column . $row)) { + //take this cell out if it contains the SUBTOTAL or AGGREGATE functions in a formula + $isFormula = $cellReference->getWorksheet()->getCell($column . $row)->isFormula(); + $cellFormula = !preg_match('/^=.*\b(SUBTOTAL|AGGREGATE)\s*\(/i', $cellReference->getWorksheet()->getCell($column . $row)->getValue()); + + $retVal = !$isFormula || $cellFormula; + } + + return $retVal; + }, + ARRAY_FILTER_USE_KEY + ); + } + + private const CALL_FUNCTIONS = [ + 1 => [Statistical\Averages::class, 'AVERAGE'], + [Statistical\Counts::class, 'COUNT'], // 2 + [Statistical\Counts::class, 'COUNTA'], // 3 + [Statistical\Maximum::class, 'MAX'], // 4 + [Statistical\Minimum::class, 'MIN'], // 5 + [Product::class, 'funcProduct'], // 6 + [Statistical\StandardDeviations::class, 'STDEV'], // 7 + [Statistical\StandardDeviations::class, 'STDEVP'], // 8 + [Sum::class, 'funcSum'], // 9 + [Statistical\Variances::class, 'VAR'], // 10 + [Statistical\Variances::class, 'VARP'], // 11 + ]; + + /** + * SUBTOTAL. + * + * Returns a subtotal in a list or database. + * + * @param mixed $functionType + * A number 1 to 11 that specifies which function to + * use in calculating subtotals within a range + * list + * Numbers 101 to 111 shadow the functions of 1 to 11 + * but ignore any values in the range that are + * in hidden rows or columns + * @param mixed[] $args A mixed data series of values + * + * @return float|string + */ + public static function funcSubtotal($functionType, ...$args) + { + $cellReference = array_pop($args); + $aArgs = Functions::flattenArrayIndexed($args); + + try { + $subtotal = (int) Helpers::validateNumericNullBool($functionType); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Calculate + if ($subtotal > 100) { + $aArgs = self::filterHiddenArgs($cellReference, $aArgs); + $subtotal -= 100; + } + + $aArgs = self::filterFormulaArgs($cellReference, $aArgs); + if (array_key_exists($subtotal, self::CALL_FUNCTIONS)) { + return call_user_func_array(self::CALL_FUNCTIONS[$subtotal], $aArgs); + } + + return Functions::VALUE(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php b/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php new file mode 100644 index 00000000..cd29248b --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php @@ -0,0 +1,68 @@ + $val) { + if ((!is_numeric($val)) || (is_string($val))) { + $val = 0; + } + $wrkArray[$i] *= $val; + } + } + + return array_sum($wrkArray); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php b/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php new file mode 100644 index 00000000..a750b149 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php @@ -0,0 +1,142 @@ +getMessage(); + } + + return $returnValue; + } + + private static function getCount(array $array1, array $array2): int + { + $count = count($array1); + if ($count !== count($array2)) { + throw new Exception(Functions::NA()); + } + + return $count; + } + + /** + * These functions accept only numeric arguments, not even strings which are numeric. + * + * @param mixed $item + */ + private static function numericNotString($item): bool + { + return is_numeric($item) && !is_string($item); + } + + /** + * SUMX2MY2. + * + * @param mixed[] $matrixData1 Matrix #1 + * @param mixed[] $matrixData2 Matrix #2 + * + * @return float|string + */ + public static function sumXSquaredMinusYSquared($matrixData1, $matrixData2) + { + try { + $array1 = Functions::flattenArray($matrixData1); + $array2 = Functions::flattenArray($matrixData2); + $count = self::getCount($array1, $array2); + + $result = 0; + for ($i = 0; $i < $count; ++$i) { + if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) { + $result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]); + } + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return $result; + } + + /** + * SUMX2PY2. + * + * @param mixed[] $matrixData1 Matrix #1 + * @param mixed[] $matrixData2 Matrix #2 + * + * @return float|string + */ + public static function sumXSquaredPlusYSquared($matrixData1, $matrixData2) + { + try { + $array1 = Functions::flattenArray($matrixData1); + $array2 = Functions::flattenArray($matrixData2); + $count = self::getCount($array1, $array2); + + $result = 0; + for ($i = 0; $i < $count; ++$i) { + if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) { + $result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]); + } + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return $result; + } + + /** + * SUMXMY2. + * + * @param mixed[] $matrixData1 Matrix #1 + * @param mixed[] $matrixData2 Matrix #2 + * + * @return float|string + */ + public static function sumXMinusYSquared($matrixData1, $matrixData2) + { + try { + $array1 = Functions::flattenArray($matrixData1); + $array2 = Functions::flattenArray($matrixData2); + $count = self::getCount($array1, $array2); + + $result = 0; + for ($i = 0; $i < $count; ++$i) { + if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) { + $result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]); + } + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return $result; + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Tan.php b/src/PhpSpreadsheet/Calculation/MathTrig/Tan.php new file mode 100644 index 00000000..82612cb8 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Tan.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(sin($angle), cos($angle)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Tanh.php b/src/PhpSpreadsheet/Calculation/MathTrig/Tanh.php new file mode 100644 index 00000000..29bf82e1 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Tanh.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return tanh($angle); + } +} diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php b/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php new file mode 100644 index 00000000..ba82a000 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php @@ -0,0 +1,39 @@ +getMessage(); + } + + $digits = floor($digits); + + // Truncate + $adjust = 10 ** $digits; + + if (($digits > 0) && (rtrim((int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) { + return $value; + } + + return ((int) ($value * $adjust)) / $adjust; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical.php b/src/PhpSpreadsheet/Calculation/Statistical.php index 19f40f2d..a0f4cd7c 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical.php +++ b/src/PhpSpreadsheet/Calculation/Statistical.php @@ -2,564 +2,25 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; -use PhpOffice\PhpSpreadsheet\Shared\Trend\Trend; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Conditional; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Confidence; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Counts; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Maximum; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Minimum; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Permutations; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Trends; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Variances; class Statistical { const LOG_GAMMA_X_MAX_VALUE = 2.55e305; - const XMININ = 2.23e-308; const EPS = 2.22e-16; const MAX_VALUE = 1.2e308; const MAX_ITERATIONS = 256; const SQRT2PI = 2.5066282746310005024157652848110452530069867406099; - private static function checkTrendArrays(&$array1, &$array2) - { - if (!is_array($array1)) { - $array1 = [$array1]; - } - if (!is_array($array2)) { - $array2 = [$array2]; - } - - $array1 = Functions::flattenArray($array1); - $array2 = Functions::flattenArray($array2); - foreach ($array1 as $key => $value) { - if ((is_bool($value)) || (is_string($value)) || ($value === null)) { - unset($array1[$key], $array2[$key]); - } - } - foreach ($array2 as $key => $value) { - if ((is_bool($value)) || (is_string($value)) || ($value === null)) { - unset($array1[$key], $array2[$key]); - } - } - $array1 = array_merge($array1); - $array2 = array_merge($array2); - - return true; - } - - /** - * Incomplete beta function. - * - * @author Jaco van Kooten - * @author Paul Meagher - * - * The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992). - * - * @param mixed $x require 0<=x<=1 - * @param mixed $p require p>0 - * @param mixed $q require q>0 - * - * @return float 0 if x<0, p<=0, q<=0 or p+q>2.55E305 and 1 if x>1 to avoid errors and over/underflow - */ - private static function incompleteBeta($x, $p, $q) - { - if ($x <= 0.0) { - return 0.0; - } elseif ($x >= 1.0) { - return 1.0; - } elseif (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { - return 0.0; - } - $beta_gam = exp((0 - self::logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x)); - if ($x < ($p + 1.0) / ($p + $q + 2.0)) { - return $beta_gam * self::betaFraction($x, $p, $q) / $p; - } - - return 1.0 - ($beta_gam * self::betaFraction(1 - $x, $q, $p) / $q); - } - - // Function cache for logBeta function - private static $logBetaCacheP = 0.0; - - private static $logBetaCacheQ = 0.0; - - private static $logBetaCacheResult = 0.0; - - /** - * The natural logarithm of the beta function. - * - * @param mixed $p require p>0 - * @param mixed $q require q>0 - * - * @return float 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow - * - * @author Jaco van Kooten - */ - private static function logBeta($p, $q) - { - if ($p != self::$logBetaCacheP || $q != self::$logBetaCacheQ) { - self::$logBetaCacheP = $p; - self::$logBetaCacheQ = $q; - if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { - self::$logBetaCacheResult = 0.0; - } else { - self::$logBetaCacheResult = self::logGamma($p) + self::logGamma($q) - self::logGamma($p + $q); - } - } - - return self::$logBetaCacheResult; - } - - /** - * Evaluates of continued fraction part of incomplete beta function. - * Based on an idea from Numerical Recipes (W.H. Press et al, 1992). - * - * @author Jaco van Kooten - * - * @param mixed $x - * @param mixed $p - * @param mixed $q - * - * @return float - */ - private static function betaFraction($x, $p, $q) - { - $c = 1.0; - $sum_pq = $p + $q; - $p_plus = $p + 1.0; - $p_minus = $p - 1.0; - $h = 1.0 - $sum_pq * $x / $p_plus; - if (abs($h) < self::XMININ) { - $h = self::XMININ; - } - $h = 1.0 / $h; - $frac = $h; - $m = 1; - $delta = 0.0; - while ($m <= self::MAX_ITERATIONS && abs($delta - 1.0) > Functions::PRECISION) { - $m2 = 2 * $m; - // even index for d - $d = $m * ($q - $m) * $x / (($p_minus + $m2) * ($p + $m2)); - $h = 1.0 + $d * $h; - if (abs($h) < self::XMININ) { - $h = self::XMININ; - } - $h = 1.0 / $h; - $c = 1.0 + $d / $c; - if (abs($c) < self::XMININ) { - $c = self::XMININ; - } - $frac *= $h * $c; - // odd index for d - $d = -($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2)); - $h = 1.0 + $d * $h; - if (abs($h) < self::XMININ) { - $h = self::XMININ; - } - $h = 1.0 / $h; - $c = 1.0 + $d / $c; - if (abs($c) < self::XMININ) { - $c = self::XMININ; - } - $delta = $h * $c; - $frac *= $delta; - ++$m; - } - - return $frac; - } - - /** - * logGamma function. - * - * @version 1.1 - * - * @author Jaco van Kooten - * - * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher. - * - * The natural logarithm of the gamma function.
- * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz
- * Applied Mathematics Division
- * Argonne National Laboratory
- * Argonne, IL 60439
- *

- * References: - *

    - *
  1. W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural - * Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.
  2. - *
  3. K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.
  4. - *
  5. Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.
  6. - *
- *

- *

- * From the original documentation: - *

- *

- * This routine calculates the LOG(GAMMA) function for a positive real argument X. - * Computation is based on an algorithm outlined in references 1 and 2. - * The program uses rational functions that theoretically approximate LOG(GAMMA) - * to at least 18 significant decimal digits. The approximation for X > 12 is from - * reference 3, while approximations for X < 12.0 are similar to those in reference - * 1, but are unpublished. The accuracy achieved depends on the arithmetic system, - * the compiler, the intrinsic functions, and proper selection of the - * machine-dependent constants. - *

- *

- * Error returns:
- * The program returns the value XINF for X .LE. 0.0 or when overflow would occur. - * The computation is believed to be free of underflow and overflow. - *

- * - * @return float MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305 - */ - - // Function cache for logGamma - private static $logGammaCacheResult = 0.0; - - private static $logGammaCacheX = 0.0; - - private static function logGamma($x) - { - // Log Gamma related constants - static $lg_d1 = -0.5772156649015328605195174; - static $lg_d2 = 0.4227843350984671393993777; - static $lg_d4 = 1.791759469228055000094023; - - static $lg_p1 = [ - 4.945235359296727046734888, - 201.8112620856775083915565, - 2290.838373831346393026739, - 11319.67205903380828685045, - 28557.24635671635335736389, - 38484.96228443793359990269, - 26377.48787624195437963534, - 7225.813979700288197698961, - ]; - static $lg_p2 = [ - 4.974607845568932035012064, - 542.4138599891070494101986, - 15506.93864978364947665077, - 184793.2904445632425417223, - 1088204.76946882876749847, - 3338152.967987029735917223, - 5106661.678927352456275255, - 3074109.054850539556250927, - ]; - static $lg_p4 = [ - 14745.02166059939948905062, - 2426813.369486704502836312, - 121475557.4045093227939592, - 2663432449.630976949898078, - 29403789566.34553899906876, - 170266573776.5398868392998, - 492612579337.743088758812, - 560625185622.3951465078242, - ]; - static $lg_q1 = [ - 67.48212550303777196073036, - 1113.332393857199323513008, - 7738.757056935398733233834, - 27639.87074403340708898585, - 54993.10206226157329794414, - 61611.22180066002127833352, - 36351.27591501940507276287, - 8785.536302431013170870835, - ]; - static $lg_q2 = [ - 183.0328399370592604055942, - 7765.049321445005871323047, - 133190.3827966074194402448, - 1136705.821321969608938755, - 5267964.117437946917577538, - 13467014.54311101692290052, - 17827365.30353274213975932, - 9533095.591844353613395747, - ]; - static $lg_q4 = [ - 2690.530175870899333379843, - 639388.5654300092398984238, - 41355999.30241388052042842, - 1120872109.61614794137657, - 14886137286.78813811542398, - 101680358627.2438228077304, - 341747634550.7377132798597, - 446315818741.9713286462081, - ]; - static $lg_c = [ - -0.001910444077728, - 8.4171387781295e-4, - -5.952379913043012e-4, - 7.93650793500350248e-4, - -0.002777777777777681622553, - 0.08333333333333333331554247, - 0.0057083835261, - ]; - - // Rough estimate of the fourth root of logGamma_xBig - static $lg_frtbig = 2.25e76; - static $pnt68 = 0.6796875; - - if ($x == self::$logGammaCacheX) { - return self::$logGammaCacheResult; - } - $y = $x; - if ($y > 0.0 && $y <= self::LOG_GAMMA_X_MAX_VALUE) { - if ($y <= self::EPS) { - $res = -log($y); - } elseif ($y <= 1.5) { - // --------------------- - // EPS .LT. X .LE. 1.5 - // --------------------- - if ($y < $pnt68) { - $corr = -log($y); - $xm1 = $y; - } else { - $corr = 0.0; - $xm1 = $y - 1.0; - } - if ($y <= 0.5 || $y >= $pnt68) { - $xden = 1.0; - $xnum = 0.0; - for ($i = 0; $i < 8; ++$i) { - $xnum = $xnum * $xm1 + $lg_p1[$i]; - $xden = $xden * $xm1 + $lg_q1[$i]; - } - $res = $corr + $xm1 * ($lg_d1 + $xm1 * ($xnum / $xden)); - } else { - $xm2 = $y - 1.0; - $xden = 1.0; - $xnum = 0.0; - for ($i = 0; $i < 8; ++$i) { - $xnum = $xnum * $xm2 + $lg_p2[$i]; - $xden = $xden * $xm2 + $lg_q2[$i]; - } - $res = $corr + $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden)); - } - } elseif ($y <= 4.0) { - // --------------------- - // 1.5 .LT. X .LE. 4.0 - // --------------------- - $xm2 = $y - 2.0; - $xden = 1.0; - $xnum = 0.0; - for ($i = 0; $i < 8; ++$i) { - $xnum = $xnum * $xm2 + $lg_p2[$i]; - $xden = $xden * $xm2 + $lg_q2[$i]; - } - $res = $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden)); - } elseif ($y <= 12.0) { - // ---------------------- - // 4.0 .LT. X .LE. 12.0 - // ---------------------- - $xm4 = $y - 4.0; - $xden = -1.0; - $xnum = 0.0; - for ($i = 0; $i < 8; ++$i) { - $xnum = $xnum * $xm4 + $lg_p4[$i]; - $xden = $xden * $xm4 + $lg_q4[$i]; - } - $res = $lg_d4 + $xm4 * ($xnum / $xden); - } else { - // --------------------------------- - // Evaluate for argument .GE. 12.0 - // --------------------------------- - $res = 0.0; - if ($y <= $lg_frtbig) { - $res = $lg_c[6]; - $ysq = $y * $y; - for ($i = 0; $i < 6; ++$i) { - $res = $res / $ysq + $lg_c[$i]; - } - $res /= $y; - $corr = log($y); - $res = $res + log(self::SQRT2PI) - 0.5 * $corr; - $res += $y * ($corr - 1.0); - } - } - } else { - // -------------------------- - // Return for bad arguments - // -------------------------- - $res = self::MAX_VALUE; - } - // ------------------------------ - // Final adjustments and return - // ------------------------------ - self::$logGammaCacheX = $x; - self::$logGammaCacheResult = $res; - - return $res; - } - - // - // Private implementation of the incomplete Gamma function - // - private static function incompleteGamma($a, $x) - { - static $max = 32; - $summer = 0; - for ($n = 0; $n <= $max; ++$n) { - $divisor = $a; - for ($i = 1; $i <= $n; ++$i) { - $divisor *= ($a + $i); - } - $summer += ($x ** $n / $divisor); - } - - return $x ** $a * exp(0 - $x) * $summer; - } - - // - // Private implementation of the Gamma function - // - private static function gamma($data) - { - if ($data == 0.0) { - return 0; - } - - static $p0 = 1.000000000190015; - static $p = [ - 1 => 76.18009172947146, - 2 => -86.50532032941677, - 3 => 24.01409824083091, - 4 => -1.231739572450155, - 5 => 1.208650973866179e-3, - 6 => -5.395239384953e-6, - ]; - - $y = $x = $data; - $tmp = $x + 5.5; - $tmp -= ($x + 0.5) * log($tmp); - - $summer = $p0; - for ($j = 1; $j <= 6; ++$j) { - $summer += ($p[$j] / ++$y); - } - - return exp(0 - $tmp + log(self::SQRT2PI * $summer / $x)); - } - - /* - * inverse_ncdf.php - * ------------------- - * begin : Friday, January 16, 2004 - * copyright : (C) 2004 Michael Nickerson - * email : nickersonm@yahoo.com - * - */ - private static function inverseNcdf($p) - { - // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to - // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as - // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html - // I have not checked the accuracy of this implementation. Be aware that PHP - // will truncate the coeficcients to 14 digits. - - // You have permission to use and distribute this function freely for - // whatever purpose you want, but please show common courtesy and give credit - // where credit is due. - - // Input paramater is $p - probability - where 0 < p < 1. - - // Coefficients in rational approximations - static $a = [ - 1 => -3.969683028665376e+01, - 2 => 2.209460984245205e+02, - 3 => -2.759285104469687e+02, - 4 => 1.383577518672690e+02, - 5 => -3.066479806614716e+01, - 6 => 2.506628277459239e+00, - ]; - - static $b = [ - 1 => -5.447609879822406e+01, - 2 => 1.615858368580409e+02, - 3 => -1.556989798598866e+02, - 4 => 6.680131188771972e+01, - 5 => -1.328068155288572e+01, - ]; - - static $c = [ - 1 => -7.784894002430293e-03, - 2 => -3.223964580411365e-01, - 3 => -2.400758277161838e+00, - 4 => -2.549732539343734e+00, - 5 => 4.374664141464968e+00, - 6 => 2.938163982698783e+00, - ]; - - static $d = [ - 1 => 7.784695709041462e-03, - 2 => 3.224671290700398e-01, - 3 => 2.445134137142996e+00, - 4 => 3.754408661907416e+00, - ]; - - // Define lower and upper region break-points. - $p_low = 0.02425; //Use lower region approx. below this - $p_high = 1 - $p_low; //Use upper region approx. above this - - if (0 < $p && $p < $p_low) { - // Rational approximation for lower region. - $q = sqrt(-2 * log($p)); - - return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / - (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); - } elseif ($p_low <= $p && $p <= $p_high) { - // Rational approximation for central region. - $q = $p - 0.5; - $r = $q * $q; - - return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q / - ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1); - } elseif ($p_high < $p && $p < 1) { - // Rational approximation for upper region. - $q = sqrt(-2 * log(1 - $p)); - - return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / - (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); - } - // If 0 < p < 1, return a null value - return Functions::NULL(); - } - - /** - * MS Excel does not count Booleans if passed as cell values, but they are counted if passed as literals. - * OpenOffice Calc always counts Booleans. - * Gnumeric never counts Booleans. - * - * @param mixed $arg - * @param mixed $k - * - * @return int|mixed - */ - private static function testAcceptedBoolean($arg, $k) - { - if ( - (is_bool($arg)) && - ((!Functions::isCellValue($k) && (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_EXCEL)) || - (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE)) - ) { - $arg = (int) $arg; - } - - return $arg; - } - - /** - * @param mixed $arg - * @param mixed $k - * - * @return bool - */ - private static function isAcceptedCountable($arg, $k) - { - if ( - ((is_numeric($arg)) && (!is_string($arg))) || - ((is_numeric($arg)) && (!Functions::isCellValue($k)) && - (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_GNUMERIC)) - ) { - return true; - } - - return false; - } - /** * AVEDEV. * @@ -569,45 +30,18 @@ class Statistical * Excel Function: * AVEDEV(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Averages::averageDeviations() + * Use the averageDeviations() method in the Statistical\Averages class instead + * * @param mixed ...$args Data values * * @return float|string */ public static function AVEDEV(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - - // Return value - $returnValue = 0; - - $aMean = self::AVERAGE(...$args); - if ($aMean === Functions::DIV0()) { - return Functions::NAN(); - } elseif ($aMean === Functions::VALUE()) { - return Functions::VALUE(); - } - - $aCount = 0; - foreach ($aArgs as $k => $arg) { - $arg = self::testAcceptedBoolean($arg, $k); - // Is it a numeric value? - // Strings containing numeric values are only counted if they are string literals (not cell values) - // and then only in MS Excel and in Open Office, not in Gnumeric - if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { - return Functions::VALUE(); - } - if (self::isAcceptedCountable($arg, $k)) { - $returnValue += abs($arg - $aMean); - ++$aCount; - } - } - - // Return - if ($aCount === 0) { - return Functions::DIV0(); - } - - return $returnValue / $aCount; + return Averages::averageDeviations(...$args); } /** @@ -618,35 +52,18 @@ class Statistical * Excel Function: * AVERAGE(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Averages::average() + * Use the average() method in the Statistical\Averages class instead + * * @param mixed ...$args Data values * * @return float|string */ public static function AVERAGE(...$args) { - $returnValue = $aCount = 0; - - // Loop through arguments - foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { - $arg = self::testAcceptedBoolean($arg, $k); - // Is it a numeric value? - // Strings containing numeric values are only counted if they are string literals (not cell values) - // and then only in MS Excel and in Open Office, not in Gnumeric - if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { - return Functions::VALUE(); - } - if (self::isAcceptedCountable($arg, $k)) { - $returnValue += $arg; - ++$aCount; - } - } - - // Return - if ($aCount > 0) { - return $returnValue / $aCount; - } - - return Functions::DIV0(); + return Averages::average(...$args); } /** @@ -657,39 +74,18 @@ class Statistical * Excel Function: * AVERAGEA(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Averages::averageA() + * Use the averageA() method in the Statistical\Averages class instead + * * @param mixed ...$args Data values * * @return float|string */ public static function AVERAGEA(...$args) { - $returnValue = null; - - $aCount = 0; - // Loop through arguments - foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { - if ( - (is_bool($arg)) && - (!Functions::isMatrixValue($k)) - ) { - } else { - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - $returnValue += $arg; - ++$aCount; - } - } - } - - if ($aCount > 0) { - return $returnValue / $aCount; - } - - return Functions::DIV0(); + return Averages::averageA(...$args); } /** @@ -700,47 +96,20 @@ class Statistical * Excel Function: * AVERAGEIF(value1[,value2[, ...]],condition) * - * @param mixed $aArgs Data values - * @param string $condition the criteria that defines which cells will be checked - * @param mixed[] $averageArgs Data values + * @Deprecated 1.17.0 * - * @return float|string + * @see Statistical\Conditional::AVERAGEIF() + * Use the AVERAGEIF() method in the Statistical\Conditional class instead + * + * @param mixed $range Data values + * @param string $condition the criteria that defines which cells will be checked + * @param mixed[] $averageRange Data values + * + * @return null|float|string */ - public static function AVERAGEIF($aArgs, $condition, $averageArgs = []) + public static function AVERAGEIF($range, $condition, $averageRange = []) { - $returnValue = 0; - - $aArgs = Functions::flattenArray($aArgs); - $averageArgs = Functions::flattenArray($averageArgs); - if (empty($averageArgs)) { - $averageArgs = $aArgs; - } - $condition = Functions::ifCondition($condition); - $conditionIsNumeric = strpos($condition, '"') === false; - - // Loop through arguments - $aCount = 0; - foreach ($aArgs as $key => $arg) { - if (!is_numeric($arg)) { - if ($conditionIsNumeric) { - continue; - } - $arg = Calculation::wrapResult(strtoupper($arg)); - } elseif (!$conditionIsNumeric) { - continue; - } - $testCondition = '=' . $arg . $condition; - if (Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - $returnValue += $averageArgs[$key]; - ++$aCount; - } - } - - if ($aCount > 0) { - return $returnValue / $aCount; - } - - return Functions::DIV0(); + return Conditional::AVERAGEIF($range, $condition, $averageRange); } /** @@ -748,6 +117,11 @@ class Statistical * * Returns the beta distribution. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Beta::distribution() + * Use the distribution() method in the Statistical\Distributions\Beta class instead + * * @param float $value Value at which you want to evaluate the distribution * @param float $alpha Parameter to the distribution * @param float $beta Parameter to the distribution @@ -758,28 +132,7 @@ class Statistical */ public static function BETADIST($value, $alpha, $beta, $rMin = 0, $rMax = 1) { - $value = Functions::flattenSingleValue($value); - $alpha = Functions::flattenSingleValue($alpha); - $beta = Functions::flattenSingleValue($beta); - $rMin = Functions::flattenSingleValue($rMin); - $rMax = Functions::flattenSingleValue($rMax); - - if ((is_numeric($value)) && (is_numeric($alpha)) && (is_numeric($beta)) && (is_numeric($rMin)) && (is_numeric($rMax))) { - if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) { - return Functions::NAN(); - } - if ($rMin > $rMax) { - $tmp = $rMin; - $rMin = $rMax; - $rMax = $tmp; - } - $value -= $rMin; - $value /= ($rMax - $rMin); - - return self::incompleteBeta($value, $alpha, $beta); - } - - return Functions::VALUE(); + return Statistical\Distributions\Beta::distribution($value, $alpha, $beta, $rMin, $rMax); } /** @@ -787,6 +140,11 @@ class Statistical * * Returns the inverse of the Beta distribution. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Beta::inverse() + * Use the inverse() method in the Statistical\Distributions\Beta class instead + * * @param float $probability Probability at which you want to evaluate the distribution * @param float $alpha Parameter to the distribution * @param float $beta Parameter to the distribution @@ -797,44 +155,7 @@ class Statistical */ public static function BETAINV($probability, $alpha, $beta, $rMin = 0, $rMax = 1) { - $probability = Functions::flattenSingleValue($probability); - $alpha = Functions::flattenSingleValue($alpha); - $beta = Functions::flattenSingleValue($beta); - $rMin = Functions::flattenSingleValue($rMin); - $rMax = Functions::flattenSingleValue($rMax); - - if ((is_numeric($probability)) && (is_numeric($alpha)) && (is_numeric($beta)) && (is_numeric($rMin)) && (is_numeric($rMax))) { - if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0) || ($probability > 1)) { - return Functions::NAN(); - } - if ($rMin > $rMax) { - $tmp = $rMin; - $rMin = $rMax; - $rMax = $tmp; - } - $a = 0; - $b = 2; - - $i = 0; - while ((($b - $a) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { - $guess = ($a + $b) / 2; - $result = self::BETADIST($guess, $alpha, $beta); - if (($result == $probability) || ($result == 0)) { - $b = $a; - } elseif ($result > $probability) { - $b = $guess; - } else { - $a = $guess; - } - } - if ($i == self::MAX_ITERATIONS) { - return Functions::NA(); - } - - return round($rMin + $guess * ($rMax - $rMin), 12); - } - - return Functions::VALUE(); + return Statistical\Distributions\Beta::inverse($probability, $alpha, $beta, $rMin, $rMax); } /** @@ -846,43 +167,21 @@ class Statistical * experiment. For example, BINOMDIST can calculate the probability that two of the next three * babies born are male. * - * @param float $value Number of successes in trials - * @param float $trials Number of trials - * @param float $probability Probability of success on each trial - * @param bool $cumulative + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Binomial::distribution() + * Use the distribution() method in the Statistical\Distributions\Binomial class instead + * + * @param mixed $value Number of successes in trials + * @param mixed $trials Number of trials + * @param mixed $probability Probability of success on each trial + * @param mixed $cumulative * * @return float|string */ public static function BINOMDIST($value, $trials, $probability, $cumulative) { - $value = Functions::flattenSingleValue($value); - $trials = Functions::flattenSingleValue($trials); - $probability = Functions::flattenSingleValue($probability); - - if ((is_numeric($value)) && (is_numeric($trials)) && (is_numeric($probability))) { - $value = floor($value); - $trials = floor($trials); - if (($value < 0) || ($value > $trials)) { - return Functions::NAN(); - } - if (($probability < 0) || ($probability > 1)) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - $summer = 0; - for ($i = 0; $i <= $value; ++$i) { - $summer += MathTrig::COMBIN($trials, $i) * $probability ** $i * (1 - $probability) ** ($trials - $i); - } - - return $summer; - } - - return MathTrig::COMBIN($trials, $value) * $probability ** $value * (1 - $probability) ** ($trials - $value); - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Binomial::distribution($value, $trials, $probability, $cumulative); } /** @@ -890,6 +189,11 @@ class Statistical * * Returns the one-tailed probability of the chi-squared distribution. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\ChiSquared::distributionRightTail() + * Use the distributionRightTail() method in the Statistical\Distributions\ChiSquared class instead + * * @param float $value Value for the function * @param float $degrees degrees of freedom * @@ -897,26 +201,7 @@ class Statistical */ public static function CHIDIST($value, $degrees) { - $value = Functions::flattenSingleValue($value); - $degrees = Functions::flattenSingleValue($degrees); - - if ((is_numeric($value)) && (is_numeric($degrees))) { - $degrees = floor($degrees); - if ($degrees < 1) { - return Functions::NAN(); - } - if ($value < 0) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - return 1; - } - - return Functions::NAN(); - } - - return 1 - (self::incompleteGamma($degrees / 2, $value / 2) / self::gamma($degrees / 2)); - } - - return Functions::VALUE(); + return Statistical\Distributions\ChiSquared::distributionRightTail($value, $degrees); } /** @@ -924,6 +209,11 @@ class Statistical * * Returns the one-tailed probability of the chi-squared distribution. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\ChiSquared::inverseRightTail() + * Use the inverseRightTail() method in the Statistical\Distributions\ChiSquared class instead + * * @param float $probability Probability for the function * @param float $degrees degrees of freedom * @@ -931,52 +221,7 @@ class Statistical */ public static function CHIINV($probability, $degrees) { - $probability = Functions::flattenSingleValue($probability); - $degrees = Functions::flattenSingleValue($degrees); - - if ((is_numeric($probability)) && (is_numeric($degrees))) { - $degrees = floor($degrees); - - $xLo = 100; - $xHi = 0; - - $x = $xNew = 1; - $dx = 1; - $i = 0; - - while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { - // Apply Newton-Raphson step - $result = 1 - (self::incompleteGamma($degrees / 2, $x / 2) / self::gamma($degrees / 2)); - $error = $result - $probability; - if ($error == 0.0) { - $dx = 0; - } elseif ($error < 0.0) { - $xLo = $x; - } else { - $xHi = $x; - } - // Avoid division by zero - if ($result != 0.0) { - $dx = $error / $result; - $xNew = $x - $dx; - } - // If the NR fails to converge (which for example may be the - // case if the initial guess is too rough) we apply a bisection - // step to determine a more narrow interval around the root. - if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) { - $xNew = ($xLo + $xHi) / 2; - $dx = $xNew - $x; - } - $x = $xNew; - } - if ($i == self::MAX_ITERATIONS) { - return Functions::NA(); - } - - return round($x, 12); - } - - return Functions::VALUE(); + return Statistical\Distributions\ChiSquared::inverseRightTail($probability, $degrees); } /** @@ -984,6 +229,11 @@ class Statistical * * Returns the confidence interval for a population mean * + * @Deprecated 1.18.0 + * + * @see Statistical\Confidence::CONFIDENCE() + * Use the CONFIDENCE() method in the Statistical\Confidence class instead + * * @param float $alpha * @param float $stdDev Standard Deviation * @param float $size @@ -992,23 +242,7 @@ class Statistical */ public static function CONFIDENCE($alpha, $stdDev, $size) { - $alpha = Functions::flattenSingleValue($alpha); - $stdDev = Functions::flattenSingleValue($stdDev); - $size = Functions::flattenSingleValue($size); - - if ((is_numeric($alpha)) && (is_numeric($stdDev)) && (is_numeric($size))) { - $size = floor($size); - if (($alpha <= 0) || ($alpha >= 1)) { - return Functions::NAN(); - } - if (($stdDev <= 0) || ($size < 1)) { - return Functions::NAN(); - } - - return self::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size); - } - - return Functions::VALUE(); + return Confidence::CONFIDENCE($alpha, $stdDev, $size); } /** @@ -1016,6 +250,11 @@ class Statistical * * Returns covariance, the average of the products of deviations for each data point pair. * + * @Deprecated 1.18.0 + * + * @see Statistical\Trends::CORREL() + * Use the CORREL() method in the Statistical\Trends class instead + * * @param mixed $yValues array of mixed Data Series Y * @param null|mixed $xValues array of mixed Data Series X * @@ -1023,24 +262,7 @@ class Statistical */ public static function CORREL($yValues, $xValues = null) { - if (($xValues === null) || (!is_array($yValues)) || (!is_array($xValues))) { - return Functions::VALUE(); - } - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getCorrelation(); + return Trends::CORREL($xValues, $yValues); } /** @@ -1051,27 +273,18 @@ class Statistical * Excel Function: * COUNT(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Counts::COUNT() + * Use the COUNT() method in the Statistical\Counts class instead + * * @param mixed ...$args Data values * * @return int */ public static function COUNT(...$args) { - $returnValue = 0; - - // Loop through arguments - $aArgs = Functions::flattenArrayIndexed($args); - foreach ($aArgs as $k => $arg) { - $arg = self::testAcceptedBoolean($arg, $k); - // Is it a numeric value? - // Strings containing numeric values are only counted if they are string literals (not cell values) - // and then only in MS Excel and in Open Office, not in Gnumeric - if (self::isAcceptedCountable($arg, $k)) { - ++$returnValue; - } - } - - return $returnValue; + return Counts::COUNT(...$args); } /** @@ -1082,24 +295,18 @@ class Statistical * Excel Function: * COUNTA(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Counts::COUNTA() + * Use the COUNTA() method in the Statistical\Counts class instead + * * @param mixed ...$args Data values * * @return int */ public static function COUNTA(...$args) { - $returnValue = 0; - - // Loop through arguments - $aArgs = Functions::flattenArrayIndexed($args); - foreach ($aArgs as $k => $arg) { - // Nulls are counted if literals, but not if cell values - if ($arg !== null || (!Functions::isCellValue($k))) { - ++$returnValue; - } - } - - return $returnValue; + return Counts::COUNTA(...$args); } /** @@ -1110,24 +317,18 @@ class Statistical * Excel Function: * COUNTBLANK(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Counts::COUNTBLANK() + * Use the COUNTBLANK() method in the Statistical\Counts class instead + * * @param mixed ...$args Data values * * @return int */ public static function COUNTBLANK(...$args) { - $returnValue = 0; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - // Is it a blank cell? - if (($arg === null) || ((is_string($arg)) && ($arg == ''))) { - ++$returnValue; - } - } - - return $returnValue; + return Counts::COUNTBLANK(...$args); } /** @@ -1136,38 +337,21 @@ class Statistical * Counts the number of cells that contain numbers within the list of arguments * * Excel Function: - * COUNTIF(value1[,value2[, ...]],condition) + * COUNTIF(range,condition) * - * @param mixed $aArgs Data values + * @Deprecated 1.17.0 + * + * @see Statistical\Conditional::COUNTIF() + * Use the COUNTIF() method in the Statistical\Conditional class instead + * + * @param mixed $range Data values * @param string $condition the criteria that defines which cells will be counted * * @return int */ - public static function COUNTIF($aArgs, $condition) + public static function COUNTIF($range, $condition) { - $returnValue = 0; - - $aArgs = Functions::flattenArray($aArgs); - $condition = Functions::ifCondition($condition); - $conditionIsNumeric = strpos($condition, '"') === false; - // Loop through arguments - foreach ($aArgs as $arg) { - if (!is_numeric($arg)) { - if ($conditionIsNumeric) { - continue; - } - $arg = Calculation::wrapResult(strtoupper($arg)); - } elseif (!$conditionIsNumeric) { - continue; - } - $testCondition = '=' . $arg . $condition; - if (Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - // Is it a value within our criteria - ++$returnValue; - } - } - - return $returnValue; + return Conditional::COUNTIF($range, $condition); } /** @@ -1178,66 +362,18 @@ class Statistical * Excel Function: * COUNTIFS(criteria_range1, criteria1, [criteria_range2, criteria2]…) * - * @param mixed $args Criterias + * @Deprecated 1.17.0 + * + * @see Statistical\Conditional::COUNTIFS() + * Use the COUNTIFS() method in the Statistical\Conditional class instead + * + * @param mixed $args Pairs of Ranges and Criteria * * @return int */ public static function COUNTIFS(...$args) { - $arrayList = $args; - - // Return value - $returnValue = 0; - - if (empty($arrayList)) { - return $returnValue; - } - - $aArgsArray = []; - $conditions = []; - - while (count($arrayList) > 0) { - $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); - $conditions[] = Functions::ifCondition(array_shift($arrayList)); - } - - // Loop through each arg and see if arguments and conditions are true - foreach (array_keys($aArgsArray[0]) as $index) { - $valid = true; - - foreach ($conditions as $cidx => $condition) { - $conditionIsNumeric = strpos($condition, '"') === false; - $arg = $aArgsArray[$cidx][$index]; - - // Loop through arguments - if (!is_numeric($arg)) { - if ($conditionIsNumeric) { - $valid = false; - - break; // if false found, don't need to check other conditions - } - $arg = Calculation::wrapResult(strtoupper($arg)); - } elseif (!$conditionIsNumeric) { - $valid = false; - - break; // if false found, don't need to check other conditions - } - $testCondition = '=' . $arg . $condition; - if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - // Is not a value within our criteria - $valid = false; - - break; // if false found, don't need to check other conditions - } - } - - if ($valid) { - ++$returnValue; - } - } - - // Return - return $returnValue; + return Conditional::COUNTIFS(...$args); } /** @@ -1245,6 +381,11 @@ class Statistical * * Returns covariance, the average of the products of deviations for each data point pair. * + * @Deprecated 1.18.0 + * + * @see Statistical\Trends::COVAR() + * Use the COVAR() method in the Statistical\Trends class instead + * * @param mixed $yValues array of mixed Data Series Y * @param mixed $xValues array of mixed Data Series X * @@ -1252,21 +393,7 @@ class Statistical */ public static function COVAR($yValues, $xValues) { - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getCovariance(); + return Trends::COVAR($yValues, $xValues); } /** @@ -1277,123 +404,20 @@ class Statistical * * See https://support.microsoft.com/en-us/help/828117/ for details of the algorithm used * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Binomial::inverse() + * Use the inverse() method in the Statistical\Distributions\Binomial class instead + * * @param float $trials number of Bernoulli trials * @param float $probability probability of a success on each trial * @param float $alpha criterion value * * @return int|string - * - * @TODO Warning. This implementation differs from the algorithm detailed on the MS - * web site in that $CumPGuessMinus1 = $CumPGuess - 1 rather than $CumPGuess - $PGuess - * This eliminates a potential endless loop error, but may have an adverse affect on the - * accuracy of the function (although all my tests have so far returned correct results). */ public static function CRITBINOM($trials, $probability, $alpha) { - $trials = floor(Functions::flattenSingleValue($trials)); - $probability = Functions::flattenSingleValue($probability); - $alpha = Functions::flattenSingleValue($alpha); - - if ((is_numeric($trials)) && (is_numeric($probability)) && (is_numeric($alpha))) { - $trials = (int) $trials; - if ($trials < 0) { - return Functions::NAN(); - } elseif (($probability < 0.0) || ($probability > 1.0)) { - return Functions::NAN(); - } elseif (($alpha < 0.0) || ($alpha > 1.0)) { - return Functions::NAN(); - } - - if ($alpha <= 0.5) { - $t = sqrt(log(1 / ($alpha * $alpha))); - $trialsApprox = 0 - ($t + (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t)); - } else { - $t = sqrt(log(1 / (1 - $alpha) ** 2)); - $trialsApprox = $t - (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t); - } - - $Guess = floor($trials * $probability + $trialsApprox * sqrt($trials * $probability * (1 - $probability))); - if ($Guess < 0) { - $Guess = 0; - } elseif ($Guess > $trials) { - $Guess = $trials; - } - - $TotalUnscaledProbability = $UnscaledPGuess = $UnscaledCumPGuess = 0.0; - $EssentiallyZero = 10e-12; - - $m = floor($trials * $probability); - ++$TotalUnscaledProbability; - if ($m == $Guess) { - ++$UnscaledPGuess; - } - if ($m <= $Guess) { - ++$UnscaledCumPGuess; - } - - $PreviousValue = 1; - $Done = false; - $k = $m + 1; - while ((!$Done) && ($k <= $trials)) { - $CurrentValue = $PreviousValue * ($trials - $k + 1) * $probability / ($k * (1 - $probability)); - $TotalUnscaledProbability += $CurrentValue; - if ($k == $Guess) { - $UnscaledPGuess += $CurrentValue; - } - if ($k <= $Guess) { - $UnscaledCumPGuess += $CurrentValue; - } - if ($CurrentValue <= $EssentiallyZero) { - $Done = true; - } - $PreviousValue = $CurrentValue; - ++$k; - } - - $PreviousValue = 1; - $Done = false; - $k = $m - 1; - while ((!$Done) && ($k >= 0)) { - $CurrentValue = $PreviousValue * $k + 1 * (1 - $probability) / (($trials - $k) * $probability); - $TotalUnscaledProbability += $CurrentValue; - if ($k == $Guess) { - $UnscaledPGuess += $CurrentValue; - } - if ($k <= $Guess) { - $UnscaledCumPGuess += $CurrentValue; - } - if ($CurrentValue <= $EssentiallyZero) { - $Done = true; - } - $PreviousValue = $CurrentValue; - --$k; - } - - $PGuess = $UnscaledPGuess / $TotalUnscaledProbability; - $CumPGuess = $UnscaledCumPGuess / $TotalUnscaledProbability; - - $CumPGuessMinus1 = $CumPGuess - 1; - - while (true) { - if (($CumPGuessMinus1 < $alpha) && ($CumPGuess >= $alpha)) { - return $Guess; - } elseif (($CumPGuessMinus1 < $alpha) && ($CumPGuess < $alpha)) { - $PGuessPlus1 = $PGuess * ($trials - $Guess) * $probability / $Guess / (1 - $probability); - $CumPGuessMinus1 = $CumPGuess; - $CumPGuess = $CumPGuess + $PGuessPlus1; - $PGuess = $PGuessPlus1; - ++$Guess; - } elseif (($CumPGuessMinus1 >= $alpha) && ($CumPGuess >= $alpha)) { - $PGuessMinus1 = $PGuess * $Guess * (1 - $probability) / ($trials - $Guess + 1) / $probability; - $CumPGuess = $CumPGuessMinus1; - $CumPGuessMinus1 = $CumPGuessMinus1 - $PGuess; - $PGuess = $PGuessMinus1; - --$Guess; - } - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Binomial::inverse($trials, $probability, $alpha); } /** @@ -1415,7 +439,7 @@ class Statistical // Return value $returnValue = null; - $aMean = self::AVERAGE($aArgs); + $aMean = Averages::average($aArgs); if ($aMean != Functions::DIV0()) { $aCount = -1; foreach ($aArgs as $k => $arg) { @@ -1455,6 +479,11 @@ class Statistical * such as how long an automated bank teller takes to deliver cash. For example, you can * use EXPONDIST to determine the probability that the process takes at most 1 minute. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Exponential::distribution() + * Use the distribution() method in the Statistical\Distributions\Exponential class instead + * * @param float $value Value of the function * @param float $lambda The parameter value * @param bool $cumulative @@ -1463,34 +492,7 @@ class Statistical */ public static function EXPONDIST($value, $lambda, $cumulative) { - $value = Functions::flattenSingleValue($value); - $lambda = Functions::flattenSingleValue($lambda); - $cumulative = Functions::flattenSingleValue($cumulative); - - if ((is_numeric($value)) && (is_numeric($lambda))) { - if (($value < 0) || ($lambda < 0)) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - return 1 - exp(0 - $value * $lambda); - } - - return $lambda * exp(0 - $value * $lambda); - } - } - - return Functions::VALUE(); - } - - private static function betaFunction($a, $b) - { - return (self::gamma($a) * self::gamma($b)) / self::gamma($a + $b); - } - - private static function regularizedIncompleteBeta($value, $a, $b) - { - return self::incompleteBeta($value, $a, $b) / self::betaFunction($a, $b); + return Statistical\Distributions\Exponential::distribution($value, $lambda, $cumulative); } /** @@ -1501,6 +503,11 @@ class Statistical * For example, you can examine the test scores of men and women entering high school, and determine * if the variability in the females is different from that found in the males. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\F::distribution() + * Use the distribution() method in the Statistical\Distributions\Exponential class instead + * * @param float $value Value of the function * @param int $u The numerator degrees of freedom * @param int $v The denominator degrees of freedom @@ -1511,32 +518,7 @@ class Statistical */ public static function FDIST2($value, $u, $v, $cumulative) { - $value = Functions::flattenSingleValue($value); - $u = Functions::flattenSingleValue($u); - $v = Functions::flattenSingleValue($v); - $cumulative = Functions::flattenSingleValue($cumulative); - - if (is_numeric($value) && is_numeric($u) && is_numeric($v)) { - if ($value < 0 || $u < 1 || $v < 1) { - return Functions::NAN(); - } - - $cumulative = (bool) $cumulative; - $u = (int) $u; - $v = (int) $v; - - if ($cumulative) { - $adjustedValue = ($u * $value) / ($u * $value + $v); - - return self::incompleteBeta($adjustedValue, $u / 2, $v / 2); - } - - return (self::gamma(($v + $u) / 2) / (self::gamma($u / 2) * self::gamma($v / 2))) * - (($u / $v) ** ($u / 2)) * - (($value ** (($u - 2) / 2)) / ((1 + ($u / $v) * $value) ** (($u + $v) / 2))); - } - - return Functions::VALUE(); + return Statistical\Distributions\F::distribution($value, $u, $v, $cumulative); } /** @@ -1546,23 +528,18 @@ class Statistical * is normally distributed rather than skewed. Use this function to perform hypothesis * testing on the correlation coefficient. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Fisher::distribution() + * Use the distribution() method in the Statistical\Distributions\Fisher class instead + * * @param float $value * * @return float|string */ public static function FISHER($value) { - $value = Functions::flattenSingleValue($value); - - if (is_numeric($value)) { - if (($value <= -1) || ($value >= 1)) { - return Functions::NAN(); - } - - return 0.5 * log((1 + $value) / (1 - $value)); - } - - return Functions::VALUE(); + return Statistical\Distributions\Fisher::distribution($value); } /** @@ -1572,19 +549,18 @@ class Statistical * analyzing correlations between ranges or arrays of data. If y = FISHER(x), then * FISHERINV(y) = x. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Fisher::inverse() + * Use the inverse() method in the Statistical\Distributions\Fisher class instead + * * @param float $value * * @return float|string */ public static function FISHERINV($value) { - $value = Functions::flattenSingleValue($value); - - if (is_numeric($value)) { - return (exp(2 * $value) - 1) / (exp(2 * $value) + 1); - } - - return Functions::VALUE(); + return Statistical\Distributions\Fisher::inverse($value); } /** @@ -1592,6 +568,11 @@ class Statistical * * Calculates, or predicts, a future value by using existing values. The predicted value is a y-value for a given x-value. * + * @Deprecated 1.18.0 + * + * @see Statistical\Trends::FORECAST() + * Use the FORECAST() method in the Statistical\Trends class instead + * * @param float $xValue Value of X for which we want to find Y * @param mixed $yValues array of mixed Data Series Y * @param mixed $xValues of mixed Data Series X @@ -1600,30 +581,18 @@ class Statistical */ public static function FORECAST($xValue, $yValues, $xValues) { - $xValue = Functions::flattenSingleValue($xValue); - if (!is_numeric($xValue)) { - return Functions::VALUE(); - } elseif (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getValueOfYForX($xValue); + return Trends::FORECAST($xValue, $yValues, $xValues); } /** * GAMMA. * - * Return the gamma function value. + * Returns the gamma function value. + * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Gamma::gamma() + * Use the gamma() method in the Statistical\Distributions\Gamma class instead * * @param float $value * @@ -1631,14 +600,7 @@ class Statistical */ public static function GAMMAFunction($value) { - $value = Functions::flattenSingleValue($value); - if (!is_numeric($value)) { - return Functions::VALUE(); - } elseif ((((int) $value) == ((float) $value)) && $value <= 0.0) { - return Functions::NAN(); - } - - return self::gamma($value); + return Statistical\Distributions\Gamma::gamma($value); } /** @@ -1646,6 +608,11 @@ class Statistical * * Returns the gamma distribution. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Gamma::distribution() + * Use the distribution() method in the Statistical\Distributions\Gamma class instead + * * @param float $value Value at which you want to evaluate the distribution * @param float $a Parameter to the distribution * @param float $b Parameter to the distribution @@ -1655,24 +622,7 @@ class Statistical */ public static function GAMMADIST($value, $a, $b, $cumulative) { - $value = Functions::flattenSingleValue($value); - $a = Functions::flattenSingleValue($a); - $b = Functions::flattenSingleValue($b); - - if ((is_numeric($value)) && (is_numeric($a)) && (is_numeric($b))) { - if (($value < 0) || ($a <= 0) || ($b <= 0)) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - return self::incompleteGamma($a, $value / $b) / self::gamma($a); - } - - return (1 / ($b ** $a * self::gamma($a))) * $value ** ($a - 1) * exp(0 - ($value / $b)); - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Gamma::distribution($value, $a, $b, $cumulative); } /** @@ -1680,6 +630,11 @@ class Statistical * * Returns the inverse of the Gamma distribution. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Gamma::inverse() + * Use the inverse() method in the Statistical\Distributions\Gamma class instead + * * @param float $probability Probability at which you want to evaluate the distribution * @param float $alpha Parameter to the distribution * @param float $beta Parameter to the distribution @@ -1688,53 +643,7 @@ class Statistical */ public static function GAMMAINV($probability, $alpha, $beta) { - $probability = Functions::flattenSingleValue($probability); - $alpha = Functions::flattenSingleValue($alpha); - $beta = Functions::flattenSingleValue($beta); - - if ((is_numeric($probability)) && (is_numeric($alpha)) && (is_numeric($beta))) { - if (($alpha <= 0) || ($beta <= 0) || ($probability < 0) || ($probability > 1)) { - return Functions::NAN(); - } - - $xLo = 0; - $xHi = $alpha * $beta * 5; - - $x = $xNew = 1; - $dx = 1024; - $i = 0; - - while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { - // Apply Newton-Raphson step - $error = self::GAMMADIST($x, $alpha, $beta, true) - $probability; - if ($error < 0.0) { - $xLo = $x; - } else { - $xHi = $x; - } - $pdf = self::GAMMADIST($x, $alpha, $beta, false); - // Avoid division by zero - if ($pdf != 0.0) { - $dx = $error / $pdf; - $xNew = $x - $dx; - } - // If the NR fails to converge (which for example may be the - // case if the initial guess is too rough) we apply a bisection - // step to determine a more narrow interval around the root. - if (($xNew < $xLo) || ($xNew > $xHi) || ($pdf == 0.0)) { - $xNew = ($xLo + $xHi) / 2; - $dx = $xNew - $x; - } - $x = $xNew; - } - if ($i == self::MAX_ITERATIONS) { - return Functions::NA(); - } - - return $x; - } - - return Functions::VALUE(); + return Statistical\Distributions\Gamma::inverse($probability, $alpha, $beta); } /** @@ -1742,23 +651,18 @@ class Statistical * * Returns the natural logarithm of the gamma function. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Gamma::ln() + * Use the ln() method in the Statistical\Distributions\Gamma class instead + * * @param float $value * * @return float|string */ public static function GAMMALN($value) { - $value = Functions::flattenSingleValue($value); - - if (is_numeric($value)) { - if ($value <= 0) { - return Functions::NAN(); - } - - return log(self::gamma($value)); - } - - return Functions::VALUE(); + return Statistical\Distributions\Gamma::ln($value); } /** @@ -1778,7 +682,7 @@ class Statistical return Functions::VALUE(); } - return self::NORMDIST($value, 0, 1, true) - 0.5; + return Statistical\Distributions\Normal::distribution($value, 0, 1, true) - 0.5; } /** @@ -1799,10 +703,10 @@ class Statistical { $aArgs = Functions::flattenArray($args); - $aMean = MathTrig::PRODUCT($aArgs); + $aMean = MathTrig\Product::funcProduct($aArgs); if (is_numeric($aMean) && ($aMean > 0)) { - $aCount = self::COUNT($aArgs); - if (self::MIN($aArgs) > 0) { + $aCount = Counts::COUNT($aArgs); + if (Minimum::MIN($aArgs) > 0) { return $aMean ** (1 / $aCount); } } @@ -1815,31 +719,21 @@ class Statistical * * Returns values along a predicted exponential Trend * + * @Deprecated 1.18.0 + * + * @see Statistical\Trends::GROWTH() + * Use the GROWTH() method in the Statistical\Trends class instead + * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @param mixed[] $newValues Values of X for which we want to find Y * @param bool $const a logical value specifying whether to force the intersect to equal 0 * - * @return array of float + * @return float[] */ public static function GROWTH($yValues, $xValues = [], $newValues = [], $const = true) { - $yValues = Functions::flattenArray($yValues); - $xValues = Functions::flattenArray($xValues); - $newValues = Functions::flattenArray($newValues); - $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); - - $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); - if (empty($newValues)) { - $newValues = $bestFitExponential->getXValues(); - } - - $returnArray = []; - foreach ($newValues as $xValue) { - $returnArray[0][] = $bestFitExponential->getValueOfYForX($xValue); - } - - return $returnArray; + return Trends::GROWTH($yValues, $xValues, $newValues, $const); } /** @@ -1862,7 +756,7 @@ class Statistical // Loop through arguments $aArgs = Functions::flattenArray($args); - if (self::MIN($aArgs) < 0) { + if (Minimum::MIN($aArgs) < 0) { return Functions::NAN(); } $aCount = 0; @@ -1891,42 +785,26 @@ class Statistical * Returns the hypergeometric distribution. HYPGEOMDIST returns the probability of a given number of * sample successes, given the sample size, population successes, and population size. * - * @param float $sampleSuccesses Number of successes in the sample - * @param float $sampleNumber Size of the sample - * @param float $populationSuccesses Number of successes in the population - * @param float $populationNumber Population size + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\HyperGeometric::distribution() + * Use the distribution() method in the Statistical\Distributions\HyperGeometric class instead + * + * @param mixed $sampleSuccesses Number of successes in the sample + * @param mixed $sampleNumber Size of the sample + * @param mixed $populationSuccesses Number of successes in the population + * @param mixed $populationNumber Population size * * @return float|string */ public static function HYPGEOMDIST($sampleSuccesses, $sampleNumber, $populationSuccesses, $populationNumber) { - $sampleSuccesses = Functions::flattenSingleValue($sampleSuccesses); - $sampleNumber = Functions::flattenSingleValue($sampleNumber); - $populationSuccesses = Functions::flattenSingleValue($populationSuccesses); - $populationNumber = Functions::flattenSingleValue($populationNumber); - - if ((is_numeric($sampleSuccesses)) && (is_numeric($sampleNumber)) && (is_numeric($populationSuccesses)) && (is_numeric($populationNumber))) { - $sampleSuccesses = floor($sampleSuccesses); - $sampleNumber = floor($sampleNumber); - $populationSuccesses = floor($populationSuccesses); - $populationNumber = floor($populationNumber); - - if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) { - return Functions::NAN(); - } - if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) { - return Functions::NAN(); - } - if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) { - return Functions::NAN(); - } - - return MathTrig::COMBIN($populationSuccesses, $sampleSuccesses) * - MathTrig::COMBIN($populationNumber - $populationSuccesses, $sampleNumber - $sampleSuccesses) / - MathTrig::COMBIN($populationNumber, $sampleNumber); - } - - return Functions::VALUE(); + return Statistical\Distributions\HyperGeometric::distribution( + $sampleSuccesses, + $sampleNumber, + $populationSuccesses, + $populationNumber + ); } /** @@ -1934,6 +812,11 @@ class Statistical * * Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values. * + * @Deprecated 1.18.0 + * + * @see Statistical\Trends::INTERCEPT() + * Use the INTERCEPT() method in the Statistical\Trends class instead + * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @@ -1941,21 +824,7 @@ class Statistical */ public static function INTERCEPT($yValues, $xValues) { - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getIntersect(); + return Trends::INTERCEPT($yValues, $xValues); } /** @@ -1973,8 +842,8 @@ class Statistical public static function KURT(...$args) { $aArgs = Functions::flattenArrayIndexed($args); - $mean = self::AVERAGE($aArgs); - $stdDev = self::STDEV($aArgs); + $mean = Averages::average($aArgs); + $stdDev = StandardDeviations::STDEV($aArgs); if ($stdDev > 0) { $count = $summer = 0; @@ -2031,7 +900,7 @@ class Statistical $mArgs[] = $arg; } } - $count = self::COUNT($mArgs); + $count = Counts::COUNT($mArgs); --$entry; if (($entry < 0) || ($entry >= $count) || ($count == 0)) { return Functions::NAN(); @@ -2050,6 +919,11 @@ class Statistical * Calculates the statistics for a line by using the "least squares" method to calculate a straight line that best fits your data, * and then returns an array that describes the line. * + * @Deprecated 1.18.0 + * + * @see Statistical\Trends::LINEST() + * Use the LINEST() method in the Statistical\Trends class instead + * * @param mixed[] $yValues Data Series Y * @param null|mixed[] $xValues Data Series X * @param bool $const a logical value specifying whether to force the intersect to equal 0 @@ -2059,48 +933,7 @@ class Statistical */ public static function LINEST($yValues, $xValues = null, $const = true, $stats = false) { - $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); - $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); - if ($xValues === null) { - $xValues = range(1, count(Functions::flattenArray($yValues))); - } - - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return 0; - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); - if ($stats) { - return [ - [ - $bestFitLinear->getSlope(), - $bestFitLinear->getSlopeSE(), - $bestFitLinear->getGoodnessOfFit(), - $bestFitLinear->getF(), - $bestFitLinear->getSSRegression(), - ], - [ - $bestFitLinear->getIntersect(), - $bestFitLinear->getIntersectSE(), - $bestFitLinear->getStdevOfResiduals(), - $bestFitLinear->getDFResiduals(), - $bestFitLinear->getSSResiduals(), - ], - ]; - } - - return [ - $bestFitLinear->getSlope(), - $bestFitLinear->getIntersect(), - ]; + return Trends::LINEST($yValues, $xValues, $const, $stats); } /** @@ -2109,6 +942,11 @@ class Statistical * Calculates an exponential curve that best fits the X and Y data series, * and then returns an array that describes the line. * + * @Deprecated 1.18.0 + * + * @see Statistical\Trends::LOGEST() + * Use the LOGEST() method in the Statistical\Trends class instead + * * @param mixed[] $yValues Data Series Y * @param null|mixed[] $xValues Data Series X * @param bool $const a logical value specifying whether to force the intersect to equal 0 @@ -2118,54 +956,7 @@ class Statistical */ public static function LOGEST($yValues, $xValues = null, $const = true, $stats = false) { - $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); - $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); - if ($xValues === null) { - $xValues = range(1, count(Functions::flattenArray($yValues))); - } - - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - foreach ($yValues as $value) { - if ($value <= 0.0) { - return Functions::NAN(); - } - } - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return 1; - } - - $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); - if ($stats) { - return [ - [ - $bestFitExponential->getSlope(), - $bestFitExponential->getSlopeSE(), - $bestFitExponential->getGoodnessOfFit(), - $bestFitExponential->getF(), - $bestFitExponential->getSSRegression(), - ], - [ - $bestFitExponential->getIntersect(), - $bestFitExponential->getIntersectSE(), - $bestFitExponential->getStdevOfResiduals(), - $bestFitExponential->getDFResiduals(), - $bestFitExponential->getSSResiduals(), - ], - ]; - } - - return [ - $bestFitExponential->getSlope(), - $bestFitExponential->getIntersect(), - ]; + return Trends::LOGEST($yValues, $xValues, $const, $stats); } /** @@ -2173,6 +964,11 @@ class Statistical * * Returns the inverse of the normal cumulative distribution * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\LogNormal::inverse() + * Use the inverse() method in the Statistical\Distributions\LogNormal class instead + * * @param float $probability * @param float $mean * @param float $stdDev @@ -2185,19 +981,7 @@ class Statistical */ public static function LOGINV($probability, $mean, $stdDev) { - $probability = Functions::flattenSingleValue($probability); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if (($probability < 0) || ($probability > 1) || ($stdDev <= 0)) { - return Functions::NAN(); - } - - return exp($mean + $stdDev * self::NORMSINV($probability)); - } - - return Functions::VALUE(); + return Statistical\Distributions\LogNormal::inverse($probability, $mean, $stdDev); } /** @@ -2206,6 +990,11 @@ class Statistical * Returns the cumulative lognormal distribution of x, where ln(x) is normally distributed * with parameters mean and standard_dev. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\LogNormal::cumulative() + * Use the cumulative() method in the Statistical\Distributions\LogNormal class instead + * * @param float $value * @param float $mean * @param float $stdDev @@ -2214,19 +1003,7 @@ class Statistical */ public static function LOGNORMDIST($value, $mean, $stdDev) { - $value = Functions::flattenSingleValue($value); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if (($value <= 0) || ($stdDev <= 0)) { - return Functions::NAN(); - } - - return self::NORMSDIST((log($value) - $mean) / $stdDev); - } - - return Functions::VALUE(); + return Statistical\Distributions\LogNormal::cumulative($value, $mean, $stdDev); } /** @@ -2235,6 +1012,11 @@ class Statistical * Returns the lognormal distribution of x, where ln(x) is normally distributed * with parameters mean and standard_dev. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\LogNormal::distribution() + * Use the distribution() method in the Statistical\Distributions\LogNormal class instead + * * @param float $value * @param float $mean * @param float $stdDev @@ -2244,25 +1026,7 @@ class Statistical */ public static function LOGNORMDIST2($value, $mean, $stdDev, $cumulative = false) { - $value = Functions::flattenSingleValue($value); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - $cumulative = (bool) Functions::flattenSingleValue($cumulative); - - if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if (($value <= 0) || ($stdDev <= 0)) { - return Functions::NAN(); - } - - if ($cumulative === true) { - return self::NORMSDIST2((log($value) - $mean) / $stdDev, true); - } - - return (1 / (sqrt(2 * M_PI) * $stdDev * $value)) * - exp(0 - ((log($value) - $mean) ** 2 / (2 * $stdDev ** 2))); - } - - return Functions::VALUE(); + return Statistical\Distributions\LogNormal::distribution($value, $mean, $stdDev, $cumulative); } /** @@ -2274,30 +1038,18 @@ class Statistical * Excel Function: * MAX(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Maximum::MAX() + * Use the MAX() method in the Statistical\Maximum class instead + * * @param mixed ...$args Data values * * @return float */ public static function MAX(...$args) { - $returnValue = null; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if (($returnValue === null) || ($arg > $returnValue)) { - $returnValue = $arg; - } - } - } - - if ($returnValue === null) { - return 0; - } - - return $returnValue; + return Maximum::MAX(...$args); } /** @@ -2308,35 +1060,18 @@ class Statistical * Excel Function: * MAXA(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Maximum::MAXA() + * Use the MAXA() method in the Statistical\Maximum class instead + * * @param mixed ...$args Data values * * @return float */ public static function MAXA(...$args) { - $returnValue = null; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - if (($returnValue === null) || ($arg > $returnValue)) { - $returnValue = $arg; - } - } - } - - if ($returnValue === null) { - return 0; - } - - return $returnValue; + return Maximum::MAXA(...$args); } /** @@ -2347,53 +1082,18 @@ class Statistical * Excel Function: * MAXIFS(max_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) * + * @Deprecated 1.17.0 + * + * @see Statistical\Conditional::MAXIFS() + * Use the MAXIFS() method in the Statistical\Conditional class instead + * * @param mixed $args Data range and criterias * * @return float */ public static function MAXIFS(...$args) { - $arrayList = $args; - - // Return value - $returnValue = null; - - $maxArgs = Functions::flattenArray(array_shift($arrayList)); - $aArgsArray = []; - $conditions = []; - - while (count($arrayList) > 0) { - $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); - $conditions[] = Functions::ifCondition(array_shift($arrayList)); - } - - // Loop through each arg and see if arguments and conditions are true - foreach ($maxArgs as $index => $value) { - $valid = true; - - foreach ($conditions as $cidx => $condition) { - $arg = $aArgsArray[$cidx][$index]; - - // Loop through arguments - if (!is_numeric($arg)) { - $arg = Calculation::wrapResult(strtoupper($arg)); - } - $testCondition = '=' . $arg . $condition; - if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - // Is not a value within our criteria - $valid = false; - - break; // if false found, don't need to check other conditions - } - } - - if ($valid) { - $returnValue = $returnValue === null ? $value : max($value, $returnValue); - } - } - - // Return - return $returnValue; + return Conditional::MAXIFS(...$args); } /** @@ -2404,37 +1104,18 @@ class Statistical * Excel Function: * MEDIAN(value1[,value2[, ...]]) * + * @Deprecated 1.18.0 + * + * @see Statistical\Averages::median() + * Use the median() method in the Statistical\Averages class instead + * * @param mixed ...$args Data values * * @return float|string The result, or a string containing an error */ public static function MEDIAN(...$args) { - $returnValue = Functions::NAN(); - - $mArgs = []; - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $mArgs[] = $arg; - } - } - - $mValueCount = count($mArgs); - if ($mValueCount > 0) { - sort($mArgs, SORT_NUMERIC); - $mValueCount = $mValueCount / 2; - if ($mValueCount == floor($mValueCount)) { - $returnValue = ($mArgs[$mValueCount--] + $mArgs[$mValueCount]) / 2; - } else { - $mValueCount = floor($mValueCount); - $returnValue = $mArgs[$mValueCount]; - } - } - - return $returnValue; + return Statistical\Averages::median(...$args); } /** @@ -2446,30 +1127,18 @@ class Statistical * Excel Function: * MIN(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Minimum::MIN() + * Use the MIN() method in the Statistical\Minimum class instead + * * @param mixed ...$args Data values * * @return float */ public static function MIN(...$args) { - $returnValue = null; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if (($returnValue === null) || ($arg < $returnValue)) { - $returnValue = $arg; - } - } - } - - if ($returnValue === null) { - return 0; - } - - return $returnValue; + return Minimum::MIN(...$args); } /** @@ -2480,35 +1149,18 @@ class Statistical * Excel Function: * MINA(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Minimum::MINA() + * Use the MINA() method in the Statistical\Minimum class instead + * * @param mixed ...$args Data values * * @return float */ public static function MINA(...$args) { - $returnValue = null; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - if (($returnValue === null) || ($arg < $returnValue)) { - $returnValue = $arg; - } - } - } - - if ($returnValue === null) { - return 0; - } - - return $returnValue; + return Minimum::MINA(...$args); } /** @@ -2519,102 +1171,18 @@ class Statistical * Excel Function: * MINIFS(min_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) * + * @Deprecated 1.17.0 + * + * @see Statistical\Conditional::MINIFS() + * Use the MINIFS() method in the Statistical\Conditional class instead + * * @param mixed $args Data range and criterias * * @return float */ public static function MINIFS(...$args) { - $arrayList = $args; - - // Return value - $returnValue = null; - - $minArgs = Functions::flattenArray(array_shift($arrayList)); - $aArgsArray = []; - $conditions = []; - - while (count($arrayList) > 0) { - $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); - $conditions[] = Functions::ifCondition(array_shift($arrayList)); - } - - // Loop through each arg and see if arguments and conditions are true - foreach ($minArgs as $index => $value) { - $valid = true; - - foreach ($conditions as $cidx => $condition) { - $arg = $aArgsArray[$cidx][$index]; - - // Loop through arguments - if (!is_numeric($arg)) { - $arg = Calculation::wrapResult(strtoupper($arg)); - } - $testCondition = '=' . $arg . $condition; - if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - // Is not a value within our criteria - $valid = false; - - break; // if false found, don't need to check other conditions - } - } - - if ($valid) { - $returnValue = $returnValue === null ? $value : min($value, $returnValue); - } - } - - // Return - return $returnValue; - } - - // - // Special variant of array_count_values that isn't limited to strings and integers, - // but can work with floating point numbers as values - // - private static function modeCalc($data) - { - $frequencyArray = []; - $index = 0; - $maxfreq = 0; - $maxfreqkey = ''; - $maxfreqdatum = ''; - foreach ($data as $datum) { - $found = false; - ++$index; - foreach ($frequencyArray as $key => $value) { - if ((string) $value['value'] == (string) $datum) { - ++$frequencyArray[$key]['frequency']; - $freq = $frequencyArray[$key]['frequency']; - if ($freq > $maxfreq) { - $maxfreq = $freq; - $maxfreqkey = $key; - $maxfreqdatum = $datum; - } elseif ($freq == $maxfreq) { - if ($frequencyArray[$key]['index'] < $frequencyArray[$maxfreqkey]['index']) { - $maxfreqkey = $key; - $maxfreqdatum = $datum; - } - } - $found = true; - - break; - } - } - if (!$found) { - $frequencyArray[] = [ - 'value' => $datum, - 'frequency' => 1, - 'index' => $index, - ]; - } - } - - if ($maxfreq <= 1) { - return Functions::NA(); - } - - return $maxfreqdatum; + return Conditional::MINIFS(...$args); } /** @@ -2625,30 +1193,18 @@ class Statistical * Excel Function: * MODE(value1[,value2[, ...]]) * + * @Deprecated 1.18.0 + * + * @see Statistical\Averages::mode() + * Use the mode() method in the Statistical\Averages class instead + * * @param mixed ...$args Data values * * @return float|string The result, or a string containing an error */ public static function MODE(...$args) { - $returnValue = Functions::NA(); - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - - $mArgs = []; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $mArgs[] = $arg; - } - } - - if (!empty($mArgs)) { - return self::modeCalc($mArgs); - } - - return $returnValue; + return Statistical\Averages::mode(...$args); } /** @@ -2660,34 +1216,20 @@ class Statistical * distribution, except that the number of successes is fixed, and the number of trials is * variable. Like the binomial, trials are assumed to be independent. * - * @param float $failures Number of Failures - * @param float $successes Threshold number of Successes - * @param float $probability Probability of success on each trial + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Binomial::negative() + * Use the negative() method in the Statistical\Distributions\Binomial class instead + * + * @param mixed $failures Number of Failures + * @param mixed $successes Threshold number of Successes + * @param mixed $probability Probability of success on each trial * * @return float|string The result, or a string containing an error */ public static function NEGBINOMDIST($failures, $successes, $probability) { - $failures = floor(Functions::flattenSingleValue($failures)); - $successes = floor(Functions::flattenSingleValue($successes)); - $probability = Functions::flattenSingleValue($probability); - - if ((is_numeric($failures)) && (is_numeric($successes)) && (is_numeric($probability))) { - if (($failures < 0) || ($successes < 1)) { - return Functions::NAN(); - } elseif (($probability < 0) || ($probability > 1)) { - return Functions::NAN(); - } - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - if (($failures + $successes - 1) <= 0) { - return Functions::NAN(); - } - } - - return (MathTrig::COMBIN($failures + $successes - 1, $successes - 1)) * ($probability ** $successes) * ((1 - $probability) ** $failures); - } - - return Functions::VALUE(); + return Statistical\Distributions\Binomial::negative($failures, $successes, $probability); } /** @@ -2697,33 +1239,21 @@ class Statistical * function has a very wide range of applications in statistics, including hypothesis * testing. * - * @param float $value - * @param float $mean Mean Value - * @param float $stdDev Standard Deviation - * @param bool $cumulative + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Normal::distribution() + * Use the distribution() method in the Statistical\Distributions\Normal class instead + * + * @param mixed $value + * @param mixed $mean Mean Value + * @param mixed $stdDev Standard Deviation + * @param mixed $cumulative * * @return float|string The result, or a string containing an error */ public static function NORMDIST($value, $mean, $stdDev, $cumulative) { - $value = Functions::flattenSingleValue($value); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if ($stdDev < 0) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - return 0.5 * (1 + Engineering::erfVal(($value - $mean) / ($stdDev * sqrt(2)))); - } - - return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev)))); - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Normal::distribution($value, $mean, $stdDev, $cumulative); } /** @@ -2731,30 +1261,20 @@ class Statistical * * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation. * - * @param float $probability - * @param float $mean Mean Value - * @param float $stdDev Standard Deviation + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Normal::inverse() + * Use the inverse() method in the Statistical\Distributions\Normal class instead + * + * @param mixed $probability + * @param mixed $mean Mean Value + * @param mixed $stdDev Standard Deviation * * @return float|string The result, or a string containing an error */ public static function NORMINV($probability, $mean, $stdDev) { - $probability = Functions::flattenSingleValue($probability); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if (($probability < 0) || ($probability > 1)) { - return Functions::NAN(); - } - if ($stdDev < 0) { - return Functions::NAN(); - } - - return (self::inverseNcdf($probability) * $stdDev) + $mean; - } - - return Functions::VALUE(); + return Statistical\Distributions\Normal::inverse($probability, $mean, $stdDev); } /** @@ -2764,18 +1284,18 @@ class Statistical * a mean of 0 (zero) and a standard deviation of one. Use this function in place of a * table of standard normal curve areas. * - * @param float $value + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\StandardNormal::cumulative() + * Use the cumulative() method in the Statistical\Distributions\StandardNormal class instead + * + * @param mixed $value * * @return float|string The result, or a string containing an error */ public static function NORMSDIST($value) { - $value = Functions::flattenSingleValue($value); - if (!is_numeric($value)) { - return Functions::VALUE(); - } - - return self::NORMDIST($value, 0, 1, true); + return Statistical\Distributions\StandardNormal::cumulative($value); } /** @@ -2785,20 +1305,19 @@ class Statistical * a mean of 0 (zero) and a standard deviation of one. Use this function in place of a * table of standard normal curve areas. * - * @param float $value - * @param bool $cumulative + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\StandardNormal::distribution() + * Use the distribution() method in the Statistical\Distributions\StandardNormal class instead + * + * @param mixed $value + * @param mixed $cumulative * * @return float|string The result, or a string containing an error */ public static function NORMSDIST2($value, $cumulative) { - $value = Functions::flattenSingleValue($value); - if (!is_numeric($value)) { - return Functions::VALUE(); - } - $cumulative = (bool) Functions::flattenSingleValue($cumulative); - - return self::NORMDIST($value, 0, 1, $cumulative); + return Statistical\Distributions\StandardNormal::distribution($value, $cumulative); } /** @@ -2806,13 +1325,18 @@ class Statistical * * Returns the inverse of the standard normal cumulative distribution * - * @param float $value + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\StandardNormal::inverse() + * Use the inverse() method in the Statistical\Distributions\StandardNormal class instead + * + * @param mixed $value * * @return float|string The result, or a string containing an error */ public static function NORMSINV($value) { - return self::NORMINV($value, 0, 1); + return Statistical\Distributions\StandardNormal::inverse($value); } /** @@ -2823,92 +1347,42 @@ class Statistical * Excel Function: * PERCENTILE(value1[,value2[, ...]],entry) * + * @Deprecated 1.18.0 + * + * @see Statistical\Percentiles::PERCENTILE() + * Use the PERCENTILE() method in the Statistical\Percentiles class instead + * * @param mixed $args Data values * * @return float|string The result, or a string containing an error */ public static function PERCENTILE(...$args) { - $aArgs = Functions::flattenArray($args); - - // Calculate - $entry = array_pop($aArgs); - - if ((is_numeric($entry)) && (!is_string($entry))) { - if (($entry < 0) || ($entry > 1)) { - return Functions::NAN(); - } - $mArgs = []; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $mArgs[] = $arg; - } - } - $mValueCount = count($mArgs); - if ($mValueCount > 0) { - sort($mArgs); - $count = self::COUNT($mArgs); - $index = $entry * ($count - 1); - $iBase = floor($index); - if ($index == $iBase) { - return $mArgs[$index]; - } - $iNext = $iBase + 1; - $iProportion = $index - $iBase; - - return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion); - } - } - - return Functions::VALUE(); + return Statistical\Percentiles::PERCENTILE(...$args); } /** * PERCENTRANK. * * Returns the rank of a value in a data set as a percentage of the data set. + * Note that the returned rank is simply rounded to the appropriate significant digits, + * rather than floored (as MS Excel), so value 3 for a value set of 1, 2, 3, 4 will return + * 0.667 rather than 0.666 * - * @param float[] $valueSet An array of, or a reference to, a list of numbers - * @param int $value the number whose rank you want to find - * @param int $significance the number of significant digits for the returned percentage value + * @Deprecated 1.18.0 + * + * @see Statistical\Percentiles::PERCENTRANK() + * Use the PERCENTRANK() method in the Statistical\Percentiles class instead + * + * @param mixed $valueSet An array of, or a reference to, a list of numbers + * @param mixed $value the number whose rank you want to find + * @param mixed $significance the number of significant digits for the returned percentage value * * @return float|string (string if result is an error) */ public static function PERCENTRANK($valueSet, $value, $significance = 3) { - $valueSet = Functions::flattenArray($valueSet); - $value = Functions::flattenSingleValue($value); - $significance = ($significance === null) ? 3 : (int) Functions::flattenSingleValue($significance); - - foreach ($valueSet as $key => $valueEntry) { - if (!is_numeric($valueEntry)) { - unset($valueSet[$key]); - } - } - sort($valueSet, SORT_NUMERIC); - $valueCount = count($valueSet); - if ($valueCount == 0) { - return Functions::NAN(); - } - - $valueAdjustor = $valueCount - 1; - if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) { - return Functions::NA(); - } - - $pos = array_search($value, $valueSet); - if ($pos === false) { - $pos = 0; - $testValue = $valueSet[0]; - while ($testValue < $value) { - $testValue = $valueSet[++$pos]; - } - --$pos; - $pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos])); - } - - return round($pos / $valueAdjustor, $significance); + return Statistical\Percentiles::PERCENTRANK($valueSet, $value, $significance); } /** @@ -2920,6 +1394,11 @@ class Statistical * combinations, for which the internal order is not significant. Use this function * for lottery-style probability calculations. * + * @Deprecated 1.17.0 + * + * @see Statistical\Permutations::PERMUT() + * Use the PERMUT() method in the Statistical\Permutations class instead + * * @param int $numObjs Number of different objects * @param int $numInSet Number of objects in each permutation * @@ -2927,19 +1406,7 @@ class Statistical */ public static function PERMUT($numObjs, $numInSet) { - $numObjs = Functions::flattenSingleValue($numObjs); - $numInSet = Functions::flattenSingleValue($numInSet); - - if ((is_numeric($numObjs)) && (is_numeric($numInSet))) { - $numInSet = floor($numInSet); - if ($numObjs < $numInSet) { - return Functions::NAN(); - } - - return round(MathTrig::FACT($numObjs) / MathTrig::FACT($numObjs - $numInSet)); - } - - return Functions::VALUE(); + return Permutations::PERMUT($numObjs, $numInSet); } /** @@ -2949,37 +1416,20 @@ class Statistical * is predicting the number of events over a specific time, such as the number of * cars arriving at a toll plaza in 1 minute. * - * @param float $value - * @param float $mean Mean Value - * @param bool $cumulative + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Poisson::distribution() + * Use the distribution() method in the Statistical\Distributions\Poisson class instead + * + * @param mixed $value + * @param mixed $mean Mean Value + * @param mixed $cumulative * * @return float|string The result, or a string containing an error */ public static function POISSON($value, $mean, $cumulative) { - $value = Functions::flattenSingleValue($value); - $mean = Functions::flattenSingleValue($mean); - - if ((is_numeric($value)) && (is_numeric($mean))) { - if (($value < 0) || ($mean <= 0)) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - $summer = 0; - $floor = floor($value); - for ($i = 0; $i <= $floor; ++$i) { - $summer += $mean ** $i / MathTrig::FACT($i); - } - - return exp(0 - $mean) * $summer; - } - - return (exp(0 - $mean) * $mean ** $value) / MathTrig::FACT($value); - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Poisson::distribution($value, $mean, $cumulative); } /** @@ -2990,27 +1440,18 @@ class Statistical * Excel Function: * QUARTILE(value1[,value2[, ...]],entry) * + * @Deprecated 1.18.0 + * + * @see Statistical\Percentiles::QUARTILE() + * Use the QUARTILE() method in the Statistical\Percentiles class instead + * * @param mixed $args Data values * * @return float|string The result, or a string containing an error */ public static function QUARTILE(...$args) { - $aArgs = Functions::flattenArray($args); - - // Calculate - $entry = floor(array_pop($aArgs)); - - if ((is_numeric($entry)) && (!is_string($entry))) { - $entry /= 4; - if (($entry < 0) || ($entry > 1)) { - return Functions::NAN(); - } - - return self::PERCENTILE($aArgs, $entry); - } - - return Functions::VALUE(); + return Statistical\Percentiles::QUARTILE(...$args); } /** @@ -3018,35 +1459,20 @@ class Statistical * * Returns the rank of a number in a list of numbers. * - * @param int $value the number whose rank you want to find - * @param float[] $valueSet An array of, or a reference to, a list of numbers - * @param int $order Order to sort the values in the value set + * @Deprecated 1.18.0 + * + * @see Statistical\Percentiles::RANK() + * Use the RANK() method in the Statistical\Percentiles class instead + * + * @param mixed $value the number whose rank you want to find + * @param mixed $valueSet An array of, or a reference to, a list of numbers + * @param mixed $order Order to sort the values in the value set * * @return float|string The result, or a string containing an error */ public static function RANK($value, $valueSet, $order = 0) { - $value = Functions::flattenSingleValue($value); - $valueSet = Functions::flattenArray($valueSet); - $order = ($order === null) ? 0 : (int) Functions::flattenSingleValue($order); - - foreach ($valueSet as $key => $valueEntry) { - if (!is_numeric($valueEntry)) { - unset($valueSet[$key]); - } - } - - if ($order == 0) { - rsort($valueSet, SORT_NUMERIC); - } else { - sort($valueSet, SORT_NUMERIC); - } - $pos = array_search($value, $valueSet); - if ($pos === false) { - return Functions::NA(); - } - - return ++$pos; + return Statistical\Percentiles::RANK($value, $valueSet, $order); } /** @@ -3054,6 +1480,11 @@ class Statistical * * Returns the square of the Pearson product moment correlation coefficient through data points in known_y's and known_x's. * + * @Deprecated 1.18.0 + * + * @see Statistical\Trends::RSQ() + * Use the RSQ() method in the Statistical\Trends class instead + * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @@ -3061,21 +1492,7 @@ class Statistical */ public static function RSQ($yValues, $xValues) { - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getGoodnessOfFit(); + return Trends::RSQ($yValues, $xValues); } /** @@ -3093,16 +1510,19 @@ class Statistical public static function SKEW(...$args) { $aArgs = Functions::flattenArrayIndexed($args); - $mean = self::AVERAGE($aArgs); - $stdDev = self::STDEV($aArgs); + $mean = Averages::average($aArgs); + $stdDev = StandardDeviations::STDEV($aArgs); + + if ($stdDev === 0.0 || is_string($stdDev)) { + return Functions::DIV0(); + } $count = $summer = 0; // Loop through arguments foreach ($aArgs as $k => $arg) { - if ( - (is_bool($arg)) && - (!Functions::isMatrixValue($k)) - ) { + if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) { + } elseif (!is_numeric($arg)) { + return Functions::VALUE(); } else { // Is it a numeric value? if ((is_numeric($arg)) && (!is_string($arg))) { @@ -3124,6 +1544,11 @@ class Statistical * * Returns the slope of the linear regression line through data points in known_y's and known_x's. * + * @Deprecated 1.18.0 + * + * @see Statistical\Trends::SLOPE() + * Use the SLOPE() method in the Statistical\Trends class instead + * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @@ -3131,21 +1556,7 @@ class Statistical */ public static function SLOPE($yValues, $xValues) { - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getSlope(); + return Trends::SLOPE($yValues, $xValues); } /** @@ -3178,7 +1589,7 @@ class Statistical $mArgs[] = $arg; } } - $count = self::COUNT($mArgs); + $count = Counts::COUNT($mArgs); --$entry; if (($entry < 0) || ($entry >= $count) || ($count == 0)) { return Functions::NAN(); @@ -3228,45 +1639,18 @@ class Statistical * Excel Function: * STDEV(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\StandardDeviations::STDEV() + * Use the STDEV() method in the Statistical\StandardDeviations class instead + * * @param mixed ...$args Data values * * @return float|string The result, or a string containing an error */ public static function STDEV(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - - // Return value - $returnValue = null; - - $aMean = self::AVERAGE($aArgs); - if ($aMean !== null) { - $aCount = -1; - foreach ($aArgs as $k => $arg) { - if ( - (is_bool($arg)) && - ((!Functions::isCellValue($k)) || (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) - ) { - $arg = (int) $arg; - } - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if ($returnValue === null) { - $returnValue = ($arg - $aMean) ** 2; - } else { - $returnValue += ($arg - $aMean) ** 2; - } - ++$aCount; - } - } - - // Return - if (($aCount > 0) && ($returnValue >= 0)) { - return sqrt($returnValue / $aCount); - } - } - - return Functions::DIV0(); + return StandardDeviations::STDEV(...$args); } /** @@ -3277,48 +1661,18 @@ class Statistical * Excel Function: * STDEVA(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\StandardDeviations::STDEVA() + * Use the STDEVA() method in the Statistical\StandardDeviations class instead + * * @param mixed ...$args Data values * * @return float|string */ public static function STDEVA(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - - $returnValue = null; - - $aMean = self::AVERAGEA($aArgs); - if ($aMean !== null) { - $aCount = -1; - foreach ($aArgs as $k => $arg) { - if ( - (is_bool($arg)) && - (!Functions::isMatrixValue($k)) - ) { - } else { - // Is it a numeric value? - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - if ($returnValue === null) { - $returnValue = ($arg - $aMean) ** 2; - } else { - $returnValue += ($arg - $aMean) ** 2; - } - ++$aCount; - } - } - } - - if (($aCount > 0) && ($returnValue >= 0)) { - return sqrt($returnValue / $aCount); - } - } - - return Functions::DIV0(); + return StandardDeviations::STDEVA(...$args); } /** @@ -3329,43 +1683,18 @@ class Statistical * Excel Function: * STDEVP(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\StandardDeviations::STDEVP() + * Use the STDEVP() method in the Statistical\StandardDeviations class instead + * * @param mixed ...$args Data values * * @return float|string */ public static function STDEVP(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - - $returnValue = null; - - $aMean = self::AVERAGE($aArgs); - if ($aMean !== null) { - $aCount = 0; - foreach ($aArgs as $k => $arg) { - if ( - (is_bool($arg)) && - ((!Functions::isCellValue($k)) || (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) - ) { - $arg = (int) $arg; - } - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if ($returnValue === null) { - $returnValue = ($arg - $aMean) ** 2; - } else { - $returnValue += ($arg - $aMean) ** 2; - } - ++$aCount; - } - } - - if (($aCount > 0) && ($returnValue >= 0)) { - return sqrt($returnValue / $aCount); - } - } - - return Functions::DIV0(); + return StandardDeviations::STDEVP(...$args); } /** @@ -3376,53 +1705,28 @@ class Statistical * Excel Function: * STDEVPA(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\StandardDeviations::STDEVPA() + * Use the STDEVPA() method in the Statistical\StandardDeviations class instead + * * @param mixed ...$args Data values * * @return float|string */ public static function STDEVPA(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - - $returnValue = null; - - $aMean = self::AVERAGEA($aArgs); - if ($aMean !== null) { - $aCount = 0; - foreach ($aArgs as $k => $arg) { - if ( - (is_bool($arg)) && - (!Functions::isMatrixValue($k)) - ) { - } else { - // Is it a numeric value? - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - if ($returnValue === null) { - $returnValue = ($arg - $aMean) ** 2; - } else { - $returnValue += ($arg - $aMean) ** 2; - } - ++$aCount; - } - } - } - - if (($aCount > 0) && ($returnValue >= 0)) { - return sqrt($returnValue / $aCount); - } - } - - return Functions::DIV0(); + return StandardDeviations::STDEVPA(...$args); } /** * STEYX. * + * @Deprecated 1.18.0 + * + * @see Statistical\Trends::STEYX() + * Use the STEYX() method in the Statistical\Trends class instead + * * Returns the standard error of the predicted y-value for each x in the regression. * * @param mixed[] $yValues Data Series Y @@ -3432,21 +1736,7 @@ class Statistical */ public static function STEYX($yValues, $xValues) { - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getStdevOfResiduals(); + return Trends::STEYX($yValues, $xValues); } /** @@ -3454,6 +1744,11 @@ class Statistical * * Returns the probability of Student's T distribution. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\StudentT::distribution() + * Use the distribution() method in the Statistical\Distributions\StudentT class instead + * * @param float $value Value for the function * @param float $degrees degrees of freedom * @param float $tails number of tails (1 or 2) @@ -3462,61 +1757,18 @@ class Statistical */ public static function TDIST($value, $degrees, $tails) { - $value = Functions::flattenSingleValue($value); - $degrees = floor(Functions::flattenSingleValue($degrees)); - $tails = floor(Functions::flattenSingleValue($tails)); - - if ((is_numeric($value)) && (is_numeric($degrees)) && (is_numeric($tails))) { - if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) { - return Functions::NAN(); - } - // tdist, which finds the probability that corresponds to a given value - // of t with k degrees of freedom. This algorithm is translated from a - // pascal function on p81 of "Statistical Computing in Pascal" by D - // Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd: - // London). The above Pascal algorithm is itself a translation of the - // fortran algoritm "AS 3" by B E Cooper of the Atlas Computer - // Laboratory as reported in (among other places) "Applied Statistics - // Algorithms", editied by P Griffiths and I D Hill (1985; Ellis - // Horwood Ltd.; W. Sussex, England). - $tterm = $degrees; - $ttheta = atan2($value, sqrt($tterm)); - $tc = cos($ttheta); - $ts = sin($ttheta); - - if (($degrees % 2) == 1) { - $ti = 3; - $tterm = $tc; - } else { - $ti = 2; - $tterm = 1; - } - - $tsum = $tterm; - while ($ti < $degrees) { - $tterm *= $tc * $tc * ($ti - 1) / $ti; - $tsum += $tterm; - $ti += 2; - } - $tsum *= $ts; - if (($degrees % 2) == 1) { - $tsum = Functions::M_2DIVPI * ($tsum + $ttheta); - } - $tValue = 0.5 * (1 + $tsum); - if ($tails == 1) { - return 1 - abs($tValue); - } - - return 1 - abs((1 - $tValue) - $tValue); - } - - return Functions::VALUE(); + return Statistical\Distributions\StudentT::distribution($value, $degrees, $tails); } /** * TINV. * - * Returns the one-tailed probability of the chi-squared distribution. + * Returns the one-tailed probability of the Student-T distribution. + * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\StudentT::inverse() + * Use the inverse() method in the Statistical\Distributions\StudentT class instead * * @param float $probability Probability for the function * @param float $degrees degrees of freedom @@ -3525,50 +1777,7 @@ class Statistical */ public static function TINV($probability, $degrees) { - $probability = Functions::flattenSingleValue($probability); - $degrees = floor(Functions::flattenSingleValue($degrees)); - - if ((is_numeric($probability)) && (is_numeric($degrees))) { - $xLo = 100; - $xHi = 0; - - $x = $xNew = 1; - $dx = 1; - $i = 0; - - while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { - // Apply Newton-Raphson step - $result = self::TDIST($x, $degrees, 2); - $error = $result - $probability; - if ($error == 0.0) { - $dx = 0; - } elseif ($error < 0.0) { - $xLo = $x; - } else { - $xHi = $x; - } - // Avoid division by zero - if ($result != 0.0) { - $dx = $error / $result; - $xNew = $x - $dx; - } - // If the NR fails to converge (which for example may be the - // case if the initial guess is too rough) we apply a bisection - // step to determine a more narrow interval around the root. - if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) { - $xNew = ($xLo + $xHi) / 2; - $dx = $xNew - $x; - } - $x = $xNew; - } - if ($i == self::MAX_ITERATIONS) { - return Functions::NA(); - } - - return round($x, 12); - } - - return Functions::VALUE(); + return Statistical\Distributions\StudentT::inverse($probability, $degrees); } /** @@ -3576,31 +1785,21 @@ class Statistical * * Returns values along a linear Trend * + * @Deprecated 1.18.0 + * + * @see Statistical\Trends::TREND() + * Use the TREND() method in the Statistical\Trends class instead + * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @param mixed[] $newValues Values of X for which we want to find Y * @param bool $const a logical value specifying whether to force the intersect to equal 0 * - * @return array of float + * @return float[] */ public static function TREND($yValues, $xValues = [], $newValues = [], $const = true) { - $yValues = Functions::flattenArray($yValues); - $xValues = Functions::flattenArray($xValues); - $newValues = Functions::flattenArray($newValues); - $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); - if (empty($newValues)) { - $newValues = $bestFitLinear->getXValues(); - } - - $returnArray = []; - foreach ($newValues as $xValue) { - $returnArray[0][] = $bestFitLinear->getValueOfYForX($xValue); - } - - return $returnArray; + return Trends::TREND($yValues, $xValues, $newValues, $const); } /** @@ -3628,6 +1827,7 @@ class Statistical if (($percent < 0) || ($percent > 1)) { return Functions::NAN(); } + $mArgs = []; foreach ($aArgs as $arg) { // Is it a numeric value? @@ -3635,14 +1835,16 @@ class Statistical $mArgs[] = $arg; } } - $discard = floor(self::COUNT($mArgs) * $percent / 2); + + $discard = floor(Counts::COUNT($mArgs) * $percent / 2); sort($mArgs); + for ($i = 0; $i < $discard; ++$i) { array_pop($mArgs); array_shift($mArgs); } - return self::AVERAGE($mArgs); + return Averages::average($mArgs); } return Functions::VALUE(); @@ -3656,38 +1858,18 @@ class Statistical * Excel Function: * VAR(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * * @param mixed ...$args Data values * * @return float|string (string if result is an error) + * + *@see Statistical\Variances::VAR() + * Use the VAR() method in the Statistical\Variances class instead */ public static function VARFunc(...$args) { - $returnValue = Functions::DIV0(); - - $summerA = $summerB = 0; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - $aCount = 0; - foreach ($aArgs as $arg) { - if (is_bool($arg)) { - $arg = (int) $arg; - } - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $summerA += ($arg * $arg); - $summerB += $arg; - ++$aCount; - } - } - - if ($aCount > 1) { - $summerA *= $aCount; - $summerB *= $summerB; - $returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1)); - } - - return $returnValue; + return Variances::VAR(...$args); } /** @@ -3698,51 +1880,18 @@ class Statistical * Excel Function: * VARA(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Variances::VARA() + * Use the VARA() method in the Statistical\Variances class instead + * * @param mixed ...$args Data values * * @return float|string (string if result is an error) */ public static function VARA(...$args) { - $returnValue = Functions::DIV0(); - - $summerA = $summerB = 0; - - // Loop through arguments - $aArgs = Functions::flattenArrayIndexed($args); - $aCount = 0; - foreach ($aArgs as $k => $arg) { - if ( - (is_string($arg)) && - (Functions::isValue($k)) - ) { - return Functions::VALUE(); - } elseif ( - (is_string($arg)) && - (!Functions::isMatrixValue($k)) - ) { - } else { - // Is it a numeric value? - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - $summerA += ($arg * $arg); - $summerB += $arg; - ++$aCount; - } - } - } - - if ($aCount > 1) { - $summerA *= $aCount; - $summerB *= $summerB; - $returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1)); - } - - return $returnValue; + return Variances::VARA(...$args); } /** @@ -3753,39 +1902,18 @@ class Statistical * Excel Function: * VARP(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Variances::VARP() + * Use the VARP() method in the Statistical\Variances class instead + * * @param mixed ...$args Data values * * @return float|string (string if result is an error) */ public static function VARP(...$args) { - // Return value - $returnValue = Functions::DIV0(); - - $summerA = $summerB = 0; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - $aCount = 0; - foreach ($aArgs as $arg) { - if (is_bool($arg)) { - $arg = (int) $arg; - } - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $summerA += ($arg * $arg); - $summerB += $arg; - ++$aCount; - } - } - - if ($aCount > 0) { - $summerA *= $aCount; - $summerB *= $summerB; - $returnValue = ($summerA - $summerB) / ($aCount * $aCount); - } - - return $returnValue; + return Variances::VARP(...$args); } /** @@ -3796,51 +1924,18 @@ class Statistical * Excel Function: * VARPA(value1[,value2[, ...]]) * + * @Deprecated 1.17.0 + * + * @see Statistical\Variances::VARPA() + * Use the VARPA() method in the Statistical\Variances class instead + * * @param mixed ...$args Data values * * @return float|string (string if result is an error) */ public static function VARPA(...$args) { - $returnValue = Functions::DIV0(); - - $summerA = $summerB = 0; - - // Loop through arguments - $aArgs = Functions::flattenArrayIndexed($args); - $aCount = 0; - foreach ($aArgs as $k => $arg) { - if ( - (is_string($arg)) && - (Functions::isValue($k)) - ) { - return Functions::VALUE(); - } elseif ( - (is_string($arg)) && - (!Functions::isMatrixValue($k)) - ) { - } else { - // Is it a numeric value? - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - $summerA += ($arg * $arg); - $summerB += $arg; - ++$aCount; - } - } - } - - if ($aCount > 0) { - $summerA *= $aCount; - $summerB *= $summerB; - $returnValue = ($summerA - $summerB) / ($aCount * $aCount); - } - - return $returnValue; + return Variances::VARPA(...$args); } /** @@ -3849,6 +1944,11 @@ class Statistical * Returns the Weibull distribution. Use this distribution in reliability * analysis, such as calculating a device's mean time to failure. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\Weibull::distribution() + * Use the distribution() method in the Statistical\Distributions\Weibull class instead + * * @param float $value * @param float $alpha Alpha Parameter * @param float $beta Beta Parameter @@ -3858,31 +1958,21 @@ class Statistical */ public static function WEIBULL($value, $alpha, $beta, $cumulative) { - $value = Functions::flattenSingleValue($value); - $alpha = Functions::flattenSingleValue($alpha); - $beta = Functions::flattenSingleValue($beta); - - if ((is_numeric($value)) && (is_numeric($alpha)) && (is_numeric($beta))) { - if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - return 1 - exp(0 - ($value / $beta) ** $alpha); - } - - return ($alpha / $beta ** $alpha) * $value ** ($alpha - 1) * exp(0 - ($value / $beta) ** $alpha); - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Weibull::distribution($value, $alpha, $beta, $cumulative); } /** * ZTEST. * - * Returns the Weibull distribution. Use this distribution in reliability - * analysis, such as calculating a device's mean time to failure. + * Returns the one-tailed P-value of a z-test. + * + * For a given hypothesized population mean, x, Z.TEST returns the probability that the sample mean would be + * greater than the average of observations in the data set (array) — that is, the observed sample mean. + * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\StandardNormal::zTest() + * Use the zTest() method in the Statistical\Distributions\StandardNormal class instead * * @param float $dataSet * @param float $m0 Alpha Parameter @@ -3892,15 +1982,6 @@ class Statistical */ public static function ZTEST($dataSet, $m0, $sigma = null) { - $dataSet = Functions::flattenArrayIndexed($dataSet); - $m0 = Functions::flattenSingleValue($m0); - $sigma = Functions::flattenSingleValue($sigma); - - if ($sigma === null) { - $sigma = self::STDEV($dataSet); - } - $n = count($dataSet); - - return 1 - self::NORMSDIST((self::AVERAGE($dataSet) - $m0) / ($sigma / sqrt($n))); + return Statistical\Distributions\StandardNormal::zTest($dataSet, $m0, $sigma); } } diff --git a/src/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php b/src/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php new file mode 100644 index 00000000..75c012dc --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php @@ -0,0 +1,50 @@ + $arg) { + $arg = self::testAcceptedBoolean($arg, $k); + // Is it a numeric value? + // Strings containing numeric values are only counted if they are string literals (not cell values) + // and then only in MS Excel and in Open Office, not in Gnumeric + if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { + return Functions::VALUE(); + } + if (self::isAcceptedCountable($arg, $k)) { + $returnValue += abs($arg - $aMean); + ++$aCount; + } + } + + // Return + if ($aCount === 0) { + return Functions::DIV0(); + } + + return $returnValue / $aCount; + } + + /** + * AVERAGE. + * + * Returns the average (arithmetic mean) of the arguments + * + * Excel Function: + * AVERAGE(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function average(...$args) + { + $returnValue = $aCount = 0; + + // Loop through arguments + foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { + $arg = self::testAcceptedBoolean($arg, $k); + // Is it a numeric value? + // Strings containing numeric values are only counted if they are string literals (not cell values) + // and then only in MS Excel and in Open Office, not in Gnumeric + if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { + return Functions::VALUE(); + } + if (self::isAcceptedCountable($arg, $k)) { + $returnValue += $arg; + ++$aCount; + } + } + + // Return + if ($aCount > 0) { + return $returnValue / $aCount; + } + + return Functions::DIV0(); + } + + /** + * AVERAGEA. + * + * Returns the average of its arguments, including numbers, text, and logical values + * + * Excel Function: + * AVERAGEA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function averageA(...$args) + { + $returnValue = null; + + $aCount = 0; + // Loop through arguments + foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { + if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) { + } else { + if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { + if (is_bool($arg)) { + $arg = (int) $arg; + } elseif (is_string($arg)) { + $arg = 0; + } + $returnValue += $arg; + ++$aCount; + } + } + } + + if ($aCount > 0) { + return $returnValue / $aCount; + } + + return Functions::DIV0(); + } + + /** + * MEDIAN. + * + * Returns the median of the given numbers. The median is the number in the middle of a set of numbers. + * + * Excel Function: + * MEDIAN(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function median(...$args) + { + $aArgs = Functions::flattenArray($args); + + $returnValue = Functions::NAN(); + + $aArgs = self::filterArguments($aArgs); + $valueCount = count($aArgs); + if ($valueCount > 0) { + sort($aArgs, SORT_NUMERIC); + $valueCount = $valueCount / 2; + if ($valueCount == floor($valueCount)) { + $returnValue = ($aArgs[$valueCount--] + $aArgs[$valueCount]) / 2; + } else { + $valueCount = floor($valueCount); + $returnValue = $aArgs[$valueCount]; + } + } + + return $returnValue; + } + + /** + * MODE. + * + * Returns the most frequently occurring, or repetitive, value in an array or range of data + * + * Excel Function: + * MODE(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function mode(...$args) + { + $returnValue = Functions::NA(); + + // Loop through arguments + $aArgs = Functions::flattenArray($args); + $aArgs = self::filterArguments($aArgs); + + if (!empty($aArgs)) { + return self::modeCalc($aArgs); + } + + return $returnValue; + } + + protected static function filterArguments($args) + { + return array_filter( + $args, + function ($value) { + // Is it a numeric value? + return (is_numeric($value)) && (!is_string($value)); + } + ); + } + + // + // Special variant of array_count_values that isn't limited to strings and integers, + // but can work with floating point numbers as values + // + private static function modeCalc($data) + { + $frequencyArray = []; + $index = 0; + $maxfreq = 0; + $maxfreqkey = ''; + $maxfreqdatum = ''; + foreach ($data as $datum) { + $found = false; + ++$index; + foreach ($frequencyArray as $key => $value) { + if ((string) $value['value'] == (string) $datum) { + ++$frequencyArray[$key]['frequency']; + $freq = $frequencyArray[$key]['frequency']; + if ($freq > $maxfreq) { + $maxfreq = $freq; + $maxfreqkey = $key; + $maxfreqdatum = $datum; + } elseif ($freq == $maxfreq) { + if ($frequencyArray[$key]['index'] < $frequencyArray[$maxfreqkey]['index']) { + $maxfreqkey = $key; + $maxfreqdatum = $datum; + } + } + $found = true; + + break; + } + } + + if ($found === false) { + $frequencyArray[] = [ + 'value' => $datum, + 'frequency' => 1, + 'index' => $index, + ]; + } + } + + if ($maxfreq <= 1) { + return Functions::NA(); + } + + return $maxfreqdatum; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php b/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php new file mode 100644 index 00000000..51e6b004 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php @@ -0,0 +1,304 @@ +getMessage(); + } + + if (($alpha <= 0) || ($alpha >= 1) || ($stdDev <= 0) || ($size < 1)) { + return Functions::NAN(); + } + + return Distributions\StandardNormal::inverse(1 - $alpha / 2) * $stdDev / sqrt($size); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Counts.php b/src/PhpSpreadsheet/Calculation/Statistical/Counts.php new file mode 100644 index 00000000..13e7af79 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Counts.php @@ -0,0 +1,95 @@ + $arg) { + $arg = self::testAcceptedBoolean($arg, $k); + // Is it a numeric value? + // Strings containing numeric values are only counted if they are string literals (not cell values) + // and then only in MS Excel and in Open Office, not in Gnumeric + if (self::isAcceptedCountable($arg, $k)) { + ++$returnValue; + } + } + + return $returnValue; + } + + /** + * COUNTA. + * + * Counts the number of cells that are not empty within the list of arguments + * + * Excel Function: + * COUNTA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return int + */ + public static function COUNTA(...$args) + { + $returnValue = 0; + + // Loop through arguments + $aArgs = Functions::flattenArrayIndexed($args); + foreach ($aArgs as $k => $arg) { + // Nulls are counted if literals, but not if cell values + if ($arg !== null || (!Functions::isCellValue($k))) { + ++$returnValue; + } + } + + return $returnValue; + } + + /** + * COUNTBLANK. + * + * Counts the number of empty cells within the list of arguments + * + * Excel Function: + * COUNTBLANK(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return int + */ + public static function COUNTBLANK(...$args) + { + $returnValue = 0; + + // Loop through arguments + $aArgs = Functions::flattenArray($args); + foreach ($aArgs as $arg) { + // Is it a blank cell? + if (($arg === null) || ((is_string($arg)) && ($arg == ''))) { + ++$returnValue; + } + } + + return $returnValue; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php new file mode 100644 index 00000000..63e6eb4d --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php @@ -0,0 +1,260 @@ +getMessage(); + } + + if ($rMin > $rMax) { + $tmp = $rMin; + $rMin = $rMax; + $rMax = $tmp; + } + if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) { + return Functions::NAN(); + } + + $value -= $rMin; + $value /= ($rMax - $rMin); + + return self::incompleteBeta($value, $alpha, $beta); + } + + /** + * BETAINV. + * + * Returns the inverse of the Beta distribution. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * @param mixed $alpha Parameter to the distribution as a float + * @param mixed $beta Parameter to the distribution as a float + * @param mixed $rMin Minimum value as a float + * @param mixed $rMax Maximum value as a float + * + * @return float|string + */ + public static function inverse($probability, $alpha, $beta, $rMin = 0.0, $rMax = 1.0) + { + $probability = Functions::flattenSingleValue($probability); + $alpha = Functions::flattenSingleValue($alpha); + $beta = Functions::flattenSingleValue($beta); + $rMin = ($rMin === null) ? 0.0 : Functions::flattenSingleValue($rMin); + $rMax = ($rMax === null) ? 1.0 : Functions::flattenSingleValue($rMax); + + try { + $probability = DistributionValidations::validateProbability($probability); + $alpha = DistributionValidations::validateFloat($alpha); + $beta = DistributionValidations::validateFloat($beta); + $rMax = DistributionValidations::validateFloat($rMax); + $rMin = DistributionValidations::validateFloat($rMin); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($rMin > $rMax) { + $tmp = $rMin; + $rMin = $rMax; + $rMax = $tmp; + } + if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0.0)) { + return Functions::NAN(); + } + + return self::calculateInverse($probability, $alpha, $beta, $rMin, $rMax); + } + + /** + * @return float|string + */ + private static function calculateInverse(float $probability, float $alpha, float $beta, float $rMin, float $rMax) + { + $a = 0; + $b = 2; + + $i = 0; + while ((($b - $a) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) { + $guess = ($a + $b) / 2; + $result = self::distribution($guess, $alpha, $beta); + if (($result === $probability) || ($result === 0.0)) { + $b = $a; + } elseif ($result > $probability) { + $b = $guess; + } else { + $a = $guess; + } + } + + if ($i === self::MAX_ITERATIONS) { + return Functions::NA(); + } + + return round($rMin + $guess * ($rMax - $rMin), 12); + } + + /** + * Incomplete beta function. + * + * @author Jaco van Kooten + * @author Paul Meagher + * + * The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992). + * + * @param float $x require 0<=x<=1 + * @param float $p require p>0 + * @param float $q require q>0 + * + * @return float 0 if x<0, p<=0, q<=0 or p+q>2.55E305 and 1 if x>1 to avoid errors and over/underflow + */ + public static function incompleteBeta(float $x, float $p, float $q): float + { + if ($x <= 0.0) { + return 0.0; + } elseif ($x >= 1.0) { + return 1.0; + } elseif (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { + return 0.0; + } + + $beta_gam = exp((0 - self::logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x)); + if ($x < ($p + 1.0) / ($p + $q + 2.0)) { + return $beta_gam * self::betaFraction($x, $p, $q) / $p; + } + + return 1.0 - ($beta_gam * self::betaFraction(1 - $x, $q, $p) / $q); + } + + // Function cache for logBeta function + private static $logBetaCacheP = 0.0; + + private static $logBetaCacheQ = 0.0; + + private static $logBetaCacheResult = 0.0; + + /** + * The natural logarithm of the beta function. + * + * @param float $p require p>0 + * @param float $q require q>0 + * + * @return float 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow + * + * @author Jaco van Kooten + */ + private static function logBeta(float $p, float $q): float + { + if ($p != self::$logBetaCacheP || $q != self::$logBetaCacheQ) { + self::$logBetaCacheP = $p; + self::$logBetaCacheQ = $q; + if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { + self::$logBetaCacheResult = 0.0; + } else { + self::$logBetaCacheResult = Gamma::logGamma($p) + Gamma::logGamma($q) - Gamma::logGamma($p + $q); + } + } + + return self::$logBetaCacheResult; + } + + /** + * Evaluates of continued fraction part of incomplete beta function. + * Based on an idea from Numerical Recipes (W.H. Press et al, 1992). + * + * @author Jaco van Kooten + */ + private static function betaFraction(float $x, float $p, float $q): float + { + $c = 1.0; + $sum_pq = $p + $q; + $p_plus = $p + 1.0; + $p_minus = $p - 1.0; + $h = 1.0 - $sum_pq * $x / $p_plus; + if (abs($h) < self::XMININ) { + $h = self::XMININ; + } + $h = 1.0 / $h; + $frac = $h; + $m = 1; + $delta = 0.0; + while ($m <= self::MAX_ITERATIONS && abs($delta - 1.0) > Functions::PRECISION) { + $m2 = 2 * $m; + // even index for d + $d = $m * ($q - $m) * $x / (($p_minus + $m2) * ($p + $m2)); + $h = 1.0 + $d * $h; + if (abs($h) < self::XMININ) { + $h = self::XMININ; + } + $h = 1.0 / $h; + $c = 1.0 + $d / $c; + if (abs($c) < self::XMININ) { + $c = self::XMININ; + } + $frac *= $h * $c; + // odd index for d + $d = -($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2)); + $h = 1.0 + $d * $h; + if (abs($h) < self::XMININ) { + $h = self::XMININ; + } + $h = 1.0 / $h; + $c = 1.0 + $d / $c; + if (abs($c) < self::XMININ) { + $c = self::XMININ; + } + $delta = $h * $c; + $frac *= $delta; + ++$m; + } + + return $frac; + } + + private static function betaValue(float $a, float $b): float + { + return (Gamma::gammaValue($a) * Gamma::gammaValue($b)) / + Gamma::gammaValue($a + $b); + } + + private static function regularizedIncompleteBeta(float $value, float $a, float $b): float + { + return self::incompleteBeta($value, $a, $b) / self::betaValue($a, $b); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php new file mode 100644 index 00000000..9631236a --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php @@ -0,0 +1,202 @@ +getMessage(); + } + + if (($value < 0) || ($value > $trials)) { + return Functions::NAN(); + } + + if ($cumulative) { + return self::calculateCumulativeBinomial($value, $trials, $probability); + } + + return Combinations::withoutRepetition($trials, $value) * $probability ** $value + * (1 - $probability) ** ($trials - $value); + } + + /** + * BINOM.DIST.RANGE. + * + * Returns returns the Binomial Distribution probability for the number of successes from a specified number + * of trials falling into a specified range. + * + * @param mixed $trials Integer number of trials + * @param mixed $probability Probability of success on each trial as a float + * @param mixed $successes The integer number of successes in trials + * @param mixed $limit Upper limit for successes in trials as null, or an integer + * If null, then this will indicate the same as the number of Successes + * + * @return float|string + */ + public static function range($trials, $probability, $successes, $limit = null) + { + $trials = Functions::flattenSingleValue($trials); + $probability = Functions::flattenSingleValue($probability); + $successes = Functions::flattenSingleValue($successes); + $limit = ($limit === null) ? $successes : Functions::flattenSingleValue($limit); + + try { + $trials = DistributionValidations::validateInt($trials); + $probability = DistributionValidations::validateProbability($probability); + $successes = DistributionValidations::validateInt($successes); + $limit = DistributionValidations::validateInt($limit); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($successes < 0) || ($successes > $trials)) { + return Functions::NAN(); + } + if (($limit < 0) || ($limit > $trials) || $limit < $successes) { + return Functions::NAN(); + } + + $summer = 0; + for ($i = $successes; $i <= $limit; ++$i) { + $summer += Combinations::withoutRepetition($trials, $i) * $probability ** $i + * (1 - $probability) ** ($trials - $i); + } + + return $summer; + } + + /** + * NEGBINOMDIST. + * + * Returns the negative binomial distribution. NEGBINOMDIST returns the probability that + * there will be number_f failures before the number_s-th success, when the constant + * probability of a success is probability_s. This function is similar to the binomial + * distribution, except that the number of successes is fixed, and the number of trials is + * variable. Like the binomial, trials are assumed to be independent. + * + * @param mixed $failures Number of Failures as an integer + * @param mixed $successes Threshold number of Successes as an integer + * @param mixed $probability Probability of success on each trial as a float + * + * @return float|string The result, or a string containing an error + * + * TODO Add support for the cumulative flag not present for NEGBINOMDIST, but introduced for NEGBINOM.DIST + * The cumulative default should be false to reflect the behaviour of NEGBINOMDIST + */ + public static function negative($failures, $successes, $probability) + { + $failures = Functions::flattenSingleValue($failures); + $successes = Functions::flattenSingleValue($successes); + $probability = Functions::flattenSingleValue($probability); + + try { + $failures = DistributionValidations::validateInt($failures); + $successes = DistributionValidations::validateInt($successes); + $probability = DistributionValidations::validateProbability($probability); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($failures < 0) || ($successes < 1)) { + return Functions::NAN(); + } + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + if (($failures + $successes - 1) <= 0) { + return Functions::NAN(); + } + } + + return (Combinations::withoutRepetition($failures + $successes - 1, $successes - 1)) + * ($probability ** $successes) * ((1 - $probability) ** $failures); + } + + /** + * CRITBINOM. + * + * Returns the smallest value for which the cumulative binomial distribution is greater + * than or equal to a criterion value + * + * @param mixed $trials number of Bernoulli trials as an integer + * @param mixed $probability probability of a success on each trial as a float + * @param mixed $alpha criterion value as a float + * + * @return int|string + */ + public static function inverse($trials, $probability, $alpha) + { + $trials = Functions::flattenSingleValue($trials); + $probability = Functions::flattenSingleValue($probability); + $alpha = Functions::flattenSingleValue($alpha); + + try { + $trials = DistributionValidations::validateInt($trials); + $probability = DistributionValidations::validateProbability($probability); + $alpha = DistributionValidations::validateFloat($alpha); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($trials < 0) { + return Functions::NAN(); + } elseif (($alpha < 0.0) || ($alpha > 1.0)) { + return Functions::NAN(); + } + + $successes = 0; + while ($successes <= $trials) { + $result = self::calculateCumulativeBinomial($successes, $trials, $probability); + if ($result >= $alpha) { + break; + } + ++$successes; + } + + return $successes; + } + + /** + * @return float|int + */ + private static function calculateCumulativeBinomial(int $value, int $trials, float $probability) + { + $summer = 0; + for ($i = 0; $i <= $value; ++$i) { + $summer += Combinations::withoutRepetition($trials, $i) * $probability ** $i + * (1 - $probability) ** ($trials - $i); + } + + return $summer; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php new file mode 100644 index 00000000..5165d639 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php @@ -0,0 +1,311 @@ +getMessage(); + } + + if ($degrees < 1) { + return Functions::NAN(); + } + if ($value < 0) { + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + return 1; + } + + return Functions::NAN(); + } + + return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2) / Gamma::gammaValue($degrees / 2)); + } + + /** + * CHIDIST. + * + * Returns the one-tailed probability of the chi-squared distribution. + * + * @param mixed $value Float value for which we want the probability + * @param mixed $degrees Integer degrees of freedom + * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false) + * + * @return float|string + */ + public static function distributionLeftTail($value, $degrees, $cumulative) + { + $value = Functions::flattenSingleValue($value); + $degrees = Functions::flattenSingleValue($degrees); + $cumulative = Functions::flattenSingleValue($cumulative); + + try { + $value = DistributionValidations::validateFloat($value); + $degrees = DistributionValidations::validateInt($degrees); + $cumulative = DistributionValidations::validateBool($cumulative); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees < 1) { + return Functions::NAN(); + } + if ($value < 0) { + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + return 1; + } + + return Functions::NAN(); + } + + if ($cumulative === true) { + return 1 - self::distributionRightTail($value, $degrees); + } + + return (($value ** (($degrees / 2) - 1) * exp(-$value / 2))) / + ((2 ** ($degrees / 2)) * Gamma::gammaValue($degrees / 2)); + } + + /** + * CHIINV. + * + * Returns the inverse of the right-tailed probability of the chi-squared distribution. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * @param mixed $degrees Integer degrees of freedom + * + * @return float|string + */ + public static function inverseRightTail($probability, $degrees) + { + $probability = Functions::flattenSingleValue($probability); + $degrees = Functions::flattenSingleValue($degrees); + + try { + $probability = DistributionValidations::validateProbability($probability); + $degrees = DistributionValidations::validateInt($degrees); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees < 1) { + return Functions::NAN(); + } + + $callback = function ($value) use ($degrees) { + return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2) + / Gamma::gammaValue($degrees / 2)); + }; + + $newtonRaphson = new NewtonRaphson($callback); + + return $newtonRaphson->execute($probability); + } + + /** + * CHIINV. + * + * Returns the inverse of the left-tailed probability of the chi-squared distribution. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * @param mixed $degrees Integer degrees of freedom + * + * @return float|string + */ + public static function inverseLeftTail($probability, $degrees) + { + $probability = Functions::flattenSingleValue($probability); + $degrees = Functions::flattenSingleValue($degrees); + + try { + $probability = DistributionValidations::validateProbability($probability); + $degrees = DistributionValidations::validateInt($degrees); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees < 1) { + return Functions::NAN(); + } + + return self::inverseLeftTailCalculation($probability, $degrees); + } + + /** + * CHITEST. + * + * Uses the chi-square test to calculate the probability that the differences between two supplied data sets + * (of observed and expected frequencies), are likely to be simply due to sampling error, + * or if they are likely to be real. + * + * @param mixed $actual an array of observed frequencies + * @param mixed $expected an array of expected frequencies + * + * @return float|string + */ + public static function test($actual, $expected) + { + $rows = count($actual); + $actual = Functions::flattenArray($actual); + $expected = Functions::flattenArray($expected); + $columns = count($actual) / $rows; + + $countActuals = count($actual); + $countExpected = count($expected); + if ($countActuals !== $countExpected || $countActuals === 1) { + return Functions::NAN(); + } + + $result = 0.0; + for ($i = 0; $i < $countActuals; ++$i) { + if ($expected[$i] == 0.0) { + return Functions::DIV0(); + } elseif ($expected[$i] < 0.0) { + return Functions::NAN(); + } + $result += (($actual[$i] - $expected[$i]) ** 2) / $expected[$i]; + } + + $degrees = self::degrees($rows, $columns); + + $result = self::distributionRightTail($result, $degrees); + + return $result; + } + + protected static function degrees(int $rows, int $columns): int + { + if ($rows === 1) { + return $columns - 1; + } elseif ($columns === 1) { + return $rows - 1; + } + + return ($columns - 1) * ($rows - 1); + } + + private static function inverseLeftTailCalculation(float $probability, int $degrees): float + { + // bracket the root + $min = 0; + $sd = sqrt(2.0 * $degrees); + $max = 2 * $sd; + $s = -1; + + while ($s * self::pchisq($max, $degrees) > $probability * $s) { + $min = $max; + $max += 2 * $sd; + } + + // Find root using bisection + $chi2 = 0.5 * ($min + $max); + + while (($max - $min) > self::EPS * $chi2) { + if ($s * self::pchisq($chi2, $degrees) > $probability * $s) { + $min = $chi2; + } else { + $max = $chi2; + } + $chi2 = 0.5 * ($min + $max); + } + + return $chi2; + } + + private static function pchisq($chi2, $degrees) + { + return self::gammp($degrees, 0.5 * $chi2); + } + + private static function gammp($n, $x) + { + if ($x < 0.5 * $n + 1) { + return self::gser($n, $x); + } + + return 1 - self::gcf($n, $x); + } + + // Return the incomplete gamma function P(n/2,x) evaluated by + // series representation. Algorithm from numerical recipe. + // Assume that n is a positive integer and x>0, won't check arguments. + // Relative error controlled by the eps parameter + private static function gser($n, $x) + { + $gln = Gamma::ln($n / 2); + $a = 0.5 * $n; + $ap = $a; + $sum = 1.0 / $a; + $del = $sum; + for ($i = 1; $i < 101; ++$i) { + ++$ap; + $del = $del * $x / $ap; + $sum += $del; + if ($del < $sum * self::EPS) { + break; + } + } + + return $sum * exp(-$x + $a * log($x) - $gln); + } + + // Return the incomplete gamma function Q(n/2,x) evaluated by + // its continued fraction representation. Algorithm from numerical recipe. + // Assume that n is a postive integer and x>0, won't check arguments. + // Relative error controlled by the eps parameter + private static function gcf($n, $x) + { + $gln = Gamma::ln($n / 2); + $a = 0.5 * $n; + $b = $x + 1 - $a; + $fpmin = 1.e-300; + $c = 1 / $fpmin; + $d = 1 / $b; + $h = $d; + for ($i = 1; $i < 101; ++$i) { + $an = -$i * ($i - $a); + $b += 2; + $d = $an * $d + $b; + if (abs($d) < $fpmin) { + $d = $fpmin; + } + $c = $b + $an / $c; + if (abs($c) < $fpmin) { + $c = $fpmin; + } + $d = 1 / $d; + $del = $d * $c; + $h = $h * $del; + if (abs($del - 1) < self::EPS) { + break; + } + } + + return $h * exp(-$x + $a * log($x) - $gln); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php new file mode 100644 index 00000000..57ef00af --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php @@ -0,0 +1,24 @@ + 1.0) { + throw new Exception(Functions::NAN()); + } + + return $probability; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php new file mode 100644 index 00000000..b3fd9460 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php @@ -0,0 +1,47 @@ +getMessage(); + } + + if (($value < 0) || ($lambda < 0)) { + return Functions::NAN(); + } + + if ($cumulative === true) { + return 1 - exp(0 - $value * $lambda); + } + + return $lambda * exp(0 - $value * $lambda); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php new file mode 100644 index 00000000..54b1950d --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php @@ -0,0 +1,56 @@ +getMessage(); + } + + if ($value < 0 || $u < 1 || $v < 1) { + return Functions::NAN(); + } + + if ($cumulative) { + $adjustedValue = ($u * $value) / ($u * $value + $v); + + return Beta::incompleteBeta($adjustedValue, $u / 2, $v / 2); + } + + return (Gamma::gammaValue(($v + $u) / 2) / + (Gamma::gammaValue($u / 2) * Gamma::gammaValue($v / 2))) * + (($u / $v) ** ($u / 2)) * + (($value ** (($u - 2) / 2)) / ((1 + ($u / $v) * $value) ** (($u + $v) / 2))); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php new file mode 100644 index 00000000..923bf02d --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php @@ -0,0 +1,61 @@ +getMessage(); + } + + if (($value <= -1) || ($value >= 1)) { + return Functions::NAN(); + } + + return 0.5 * log((1 + $value) / (1 - $value)); + } + + /** + * FISHERINV. + * + * Returns the inverse of the Fisher transformation. Use this transformation when + * analyzing correlations between ranges or arrays of data. If y = FISHER(x), then + * FISHERINV(y) = x. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * + * @return float|string + */ + public static function inverse($probability) + { + $probability = Functions::flattenSingleValue($probability); + + try { + DistributionValidations::validateFloat($probability); + } catch (Exception $e) { + return $e->getMessage(); + } + + return (exp(2 * $probability) - 1) / (exp(2 * $probability) + 1); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php new file mode 100644 index 00000000..2c6ed670 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php @@ -0,0 +1,127 @@ +getMessage(); + } + + if ((((int) $value) == ((float) $value)) && $value <= 0.0) { + return Functions::NAN(); + } + + return self::gammaValue($value); + } + + /** + * GAMMADIST. + * + * Returns the gamma distribution. + * + * @param mixed $value Float Value at which you want to evaluate the distribution + * @param mixed $a Parameter to the distribution as a float + * @param mixed $b Parameter to the distribution as a float + * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false) + * + * @return float|string + */ + public static function distribution($value, $a, $b, $cumulative) + { + $value = Functions::flattenSingleValue($value); + $a = Functions::flattenSingleValue($a); + $b = Functions::flattenSingleValue($b); + + try { + $value = DistributionValidations::validateFloat($value); + $a = DistributionValidations::validateFloat($a); + $b = DistributionValidations::validateFloat($b); + $cumulative = DistributionValidations::validateBool($cumulative); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($value < 0) || ($a <= 0) || ($b <= 0)) { + return Functions::NAN(); + } + + return self::calculateDistribution($value, $a, $b, $cumulative); + } + + /** + * GAMMAINV. + * + * Returns the inverse of the Gamma distribution. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * @param mixed $alpha Parameter to the distribution as a float + * @param mixed $beta Parameter to the distribution as a float + * + * @return float|string + */ + public static function inverse($probability, $alpha, $beta) + { + $probability = Functions::flattenSingleValue($probability); + $alpha = Functions::flattenSingleValue($alpha); + $beta = Functions::flattenSingleValue($beta); + + try { + $probability = DistributionValidations::validateProbability($probability); + $alpha = DistributionValidations::validateFloat($alpha); + $beta = DistributionValidations::validateFloat($beta); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($alpha <= 0.0) || ($beta <= 0.0)) { + return Functions::NAN(); + } + + return self::calculateInverse($probability, $alpha, $beta); + } + + /** + * GAMMALN. + * + * Returns the natural logarithm of the gamma function. + * + * @param mixed $value Float Value at which you want to evaluate the distribution + * + * @return float|string + */ + public static function ln($value) + { + $value = Functions::flattenSingleValue($value); + + try { + $value = DistributionValidations::validateFloat($value); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($value <= 0) { + return Functions::NAN(); + } + + return log(self::gammaValue($value)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php new file mode 100644 index 00000000..89170f7c --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php @@ -0,0 +1,381 @@ + Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) { + // Apply Newton-Raphson step + $result = self::calculateDistribution($x, $alpha, $beta, true); + $error = $result - $probability; + + if ($error == 0.0) { + $dx = 0; + } elseif ($error < 0.0) { + $xLo = $x; + } else { + $xHi = $x; + } + + $pdf = self::calculateDistribution($x, $alpha, $beta, false); + // Avoid division by zero + if ($pdf !== 0.0) { + $dx = $error / $pdf; + $xNew = $x - $dx; + } + + // If the NR fails to converge (which for example may be the + // case if the initial guess is too rough) we apply a bisection + // step to determine a more narrow interval around the root. + if (($xNew < $xLo) || ($xNew > $xHi) || ($pdf == 0.0)) { + $xNew = ($xLo + $xHi) / 2; + $dx = $xNew - $x; + } + $x = $xNew; + } + + if ($i === self::MAX_ITERATIONS) { + return Functions::NA(); + } + + return $x; + } + + // + // Implementation of the incomplete Gamma function + // + public static function incompleteGamma(float $a, float $x): float + { + static $max = 32; + $summer = 0; + for ($n = 0; $n <= $max; ++$n) { + $divisor = $a; + for ($i = 1; $i <= $n; ++$i) { + $divisor *= ($a + $i); + } + $summer += ($x ** $n / $divisor); + } + + return $x ** $a * exp(0 - $x) * $summer; + } + + // + // Implementation of the Gamma function + // + public static function gammaValue(float $value): float + { + if ($value == 0.0) { + return 0; + } + + static $p0 = 1.000000000190015; + static $p = [ + 1 => 76.18009172947146, + 2 => -86.50532032941677, + 3 => 24.01409824083091, + 4 => -1.231739572450155, + 5 => 1.208650973866179e-3, + 6 => -5.395239384953e-6, + ]; + + $y = $x = $value; + $tmp = $x + 5.5; + $tmp -= ($x + 0.5) * log($tmp); + + $summer = $p0; + for ($j = 1; $j <= 6; ++$j) { + $summer += ($p[$j] / ++$y); + } + + return exp(0 - $tmp + log(self::SQRT2PI * $summer / $x)); + } + + /** + * logGamma function. + * + * @version 1.1 + * + * @author Jaco van Kooten + * + * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher. + * + * The natural logarithm of the gamma function.
+ * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz
+ * Applied Mathematics Division
+ * Argonne National Laboratory
+ * Argonne, IL 60439
+ *

+ * References: + *

    + *
  1. W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural + * Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.
  2. + *
  3. K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.
  4. + *
  5. Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.
  6. + *
+ *

+ *

+ * From the original documentation: + *

+ *

+ * This routine calculates the LOG(GAMMA) function for a positive real argument X. + * Computation is based on an algorithm outlined in references 1 and 2. + * The program uses rational functions that theoretically approximate LOG(GAMMA) + * to at least 18 significant decimal digits. The approximation for X > 12 is from + * reference 3, while approximations for X < 12.0 are similar to those in reference + * 1, but are unpublished. The accuracy achieved depends on the arithmetic system, + * the compiler, the intrinsic functions, and proper selection of the + * machine-dependent constants. + *

+ *

+ * Error returns:
+ * The program returns the value XINF for X .LE. 0.0 or when overflow would occur. + * The computation is believed to be free of underflow and overflow. + *

+ * + * @return float MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305 + */ + + // Log Gamma related constants + private const LG_D1 = -0.5772156649015328605195174; + + private const LG_D2 = 0.4227843350984671393993777; + + private const LG_D4 = 1.791759469228055000094023; + + private const LG_P1 = [ + 4.945235359296727046734888, + 201.8112620856775083915565, + 2290.838373831346393026739, + 11319.67205903380828685045, + 28557.24635671635335736389, + 38484.96228443793359990269, + 26377.48787624195437963534, + 7225.813979700288197698961, + ]; + + private const LG_P2 = [ + 4.974607845568932035012064, + 542.4138599891070494101986, + 15506.93864978364947665077, + 184793.2904445632425417223, + 1088204.76946882876749847, + 3338152.967987029735917223, + 5106661.678927352456275255, + 3074109.054850539556250927, + ]; + + private const LG_P4 = [ + 14745.02166059939948905062, + 2426813.369486704502836312, + 121475557.4045093227939592, + 2663432449.630976949898078, + 29403789566.34553899906876, + 170266573776.5398868392998, + 492612579337.743088758812, + 560625185622.3951465078242, + ]; + + private const LG_Q1 = [ + 67.48212550303777196073036, + 1113.332393857199323513008, + 7738.757056935398733233834, + 27639.87074403340708898585, + 54993.10206226157329794414, + 61611.22180066002127833352, + 36351.27591501940507276287, + 8785.536302431013170870835, + ]; + + private const LG_Q2 = [ + 183.0328399370592604055942, + 7765.049321445005871323047, + 133190.3827966074194402448, + 1136705.821321969608938755, + 5267964.117437946917577538, + 13467014.54311101692290052, + 17827365.30353274213975932, + 9533095.591844353613395747, + ]; + + private const LG_Q4 = [ + 2690.530175870899333379843, + 639388.5654300092398984238, + 41355999.30241388052042842, + 1120872109.61614794137657, + 14886137286.78813811542398, + 101680358627.2438228077304, + 341747634550.7377132798597, + 446315818741.9713286462081, + ]; + + private const LG_C = [ + -0.001910444077728, + 8.4171387781295e-4, + -5.952379913043012e-4, + 7.93650793500350248e-4, + -0.002777777777777681622553, + 0.08333333333333333331554247, + 0.0057083835261, + ]; + + // Rough estimate of the fourth root of logGamma_xBig + private const LG_FRTBIG = 2.25e76; + + private const PNT68 = 0.6796875; + + // Function cache for logGamma + private static $logGammaCacheResult = 0.0; + + private static $logGammaCacheX = 0.0; + + public static function logGamma(float $x): float + { + if ($x == self::$logGammaCacheX) { + return self::$logGammaCacheResult; + } + + $y = $x; + if ($y > 0.0 && $y <= self::LOG_GAMMA_X_MAX_VALUE) { + if ($y <= self::EPS) { + $res = -log($y); + } elseif ($y <= 1.5) { + $res = self::logGamma1($y); + } elseif ($y <= 4.0) { + $res = self::logGamma2($y); + } elseif ($y <= 12.0) { + $res = self::logGamma3($y); + } else { + $res = self::logGamma4($y); + } + } else { + // -------------------------- + // Return for bad arguments + // -------------------------- + $res = self::MAX_VALUE; + } + + // ------------------------------ + // Final adjustments and return + // ------------------------------ + self::$logGammaCacheX = $x; + self::$logGammaCacheResult = $res; + + return $res; + } + + private static function logGamma1(float $y) + { + // --------------------- + // EPS .LT. X .LE. 1.5 + // --------------------- + if ($y < self::PNT68) { + $corr = -log($y); + $xm1 = $y; + } else { + $corr = 0.0; + $xm1 = $y - 1.0; + } + + $xden = 1.0; + $xnum = 0.0; + if ($y <= 0.5 || $y >= self::PNT68) { + for ($i = 0; $i < 8; ++$i) { + $xnum = $xnum * $xm1 + self::LG_P1[$i]; + $xden = $xden * $xm1 + self::LG_Q1[$i]; + } + + return $corr + $xm1 * (self::LG_D1 + $xm1 * ($xnum / $xden)); + } + + $xm2 = $y - 1.0; + for ($i = 0; $i < 8; ++$i) { + $xnum = $xnum * $xm2 + self::LG_P2[$i]; + $xden = $xden * $xm2 + self::LG_Q2[$i]; + } + + return $corr + $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden)); + } + + private static function logGamma2(float $y) + { + // --------------------- + // 1.5 .LT. X .LE. 4.0 + // --------------------- + $xm2 = $y - 2.0; + $xden = 1.0; + $xnum = 0.0; + for ($i = 0; $i < 8; ++$i) { + $xnum = $xnum * $xm2 + self::LG_P2[$i]; + $xden = $xden * $xm2 + self::LG_Q2[$i]; + } + + return $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden)); + } + + protected static function logGamma3(float $y) + { + // ---------------------- + // 4.0 .LT. X .LE. 12.0 + // ---------------------- + $xm4 = $y - 4.0; + $xden = -1.0; + $xnum = 0.0; + for ($i = 0; $i < 8; ++$i) { + $xnum = $xnum * $xm4 + self::LG_P4[$i]; + $xden = $xden * $xm4 + self::LG_Q4[$i]; + } + + return self::LG_D4 + $xm4 * ($xnum / $xden); + } + + protected static function logGamma4(float $y) + { + // --------------------------------- + // Evaluate for argument .GE. 12.0 + // --------------------------------- + $res = 0.0; + if ($y <= self::LG_FRTBIG) { + $res = self::LG_C[6]; + $ysq = $y * $y; + for ($i = 0; $i < 6; ++$i) { + $res = $res / $ysq + self::LG_C[$i]; + } + $res /= $y; + $corr = log($y); + $res = $res + log(self::SQRT2PI) - 0.5 * $corr; + $res += $y * ($corr - 1.0); + } + + return $res; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php new file mode 100644 index 00000000..fe30c087 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php @@ -0,0 +1,59 @@ +getMessage(); + } + + if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) { + return Functions::NAN(); + } + if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) { + return Functions::NAN(); + } + if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) { + return Functions::NAN(); + } + + $successesPopulationAndSample = (float) Combinations::withoutRepetition($populationSuccesses, $sampleSuccesses); + $numbersPopulationAndSample = (float) Combinations::withoutRepetition($populationNumber, $sampleNumber); + $adjustedPopulationAndSample = (float) Combinations::withoutRepetition( + $populationNumber - $populationSuccesses, + $sampleNumber - $sampleSuccesses + ); + + return $successesPopulationAndSample * $adjustedPopulationAndSample / $numbersPopulationAndSample; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php new file mode 100644 index 00000000..e1523773 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php @@ -0,0 +1,119 @@ +getMessage(); + } + + if (($value <= 0) || ($stdDev <= 0)) { + return Functions::NAN(); + } + + return StandardNormal::cumulative((log($value) - $mean) / $stdDev); + } + + /** + * LOGNORM.DIST. + * + * Returns the lognormal distribution of x, where ln(x) is normally distributed + * with parameters mean and standard_dev. + * + * @param mixed $value Float value for which we want the probability + * @param mixed $mean Mean value as a float + * @param mixed $stdDev Standard Deviation as a float + * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false) + * + * @return float|string The result, or a string containing an error + */ + public static function distribution($value, $mean, $stdDev, $cumulative = false) + { + $value = Functions::flattenSingleValue($value); + $mean = Functions::flattenSingleValue($mean); + $stdDev = Functions::flattenSingleValue($stdDev); + $cumulative = Functions::flattenSingleValue($cumulative); + + try { + $value = DistributionValidations::validateFloat($value); + $mean = DistributionValidations::validateFloat($mean); + $stdDev = DistributionValidations::validateFloat($stdDev); + $cumulative = DistributionValidations::validateBool($cumulative); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($value <= 0) || ($stdDev <= 0)) { + return Functions::NAN(); + } + + if ($cumulative === true) { + return StandardNormal::distribution((log($value) - $mean) / $stdDev, true); + } + + return (1 / (sqrt(2 * M_PI) * $stdDev * $value)) * + exp(0 - ((log($value) - $mean) ** 2 / (2 * $stdDev ** 2))); + } + + /** + * LOGINV. + * + * Returns the inverse of the lognormal cumulative distribution + * + * @param mixed $probability Float probability for which we want the value + * @param mixed $mean Mean Value as a float + * @param mixed $stdDev Standard Deviation as a float + * + * @return float|string The result, or a string containing an error + * + * @TODO Try implementing P J Acklam's refinement algorithm for greater + * accuracy if I can get my head round the mathematics + * (as described at) http://home.online.no/~pjacklam/notes/invnorm/ + */ + public static function inverse($probability, $mean, $stdDev) + { + $probability = Functions::flattenSingleValue($probability); + $mean = Functions::flattenSingleValue($mean); + $stdDev = Functions::flattenSingleValue($stdDev); + + try { + $probability = DistributionValidations::validateProbability($probability); + $mean = DistributionValidations::validateFloat($mean); + $stdDev = DistributionValidations::validateFloat($stdDev); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($stdDev <= 0) { + return Functions::NAN(); + } + + return exp($mean + $stdDev * StandardNormal::inverse($probability)); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php new file mode 100644 index 00000000..26211672 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php @@ -0,0 +1,62 @@ +callback = $callback; + } + + public function execute(float $probability) + { + $xLo = 100; + $xHi = 0; + + $dx = 1; + $x = $xNew = 1; + $i = 0; + + while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { + // Apply Newton-Raphson step + $result = call_user_func($this->callback, $x); + $error = $result - $probability; + + if ($error == 0.0) { + $dx = 0; + } elseif ($error < 0.0) { + $xLo = $x; + } else { + $xHi = $x; + } + + // Avoid division by zero + if ($result != 0.0) { + $dx = $error / $result; + $xNew = $x - $dx; + } + + // If the NR fails to converge (which for example may be the + // case if the initial guess is too rough) we apply a bisection + // step to determine a more narrow interval around the root. + if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) { + $xNew = ($xLo + $xHi) / 2; + $dx = $xNew - $x; + } + $x = $xNew; + } + + if ($i == self::MAX_ITERATIONS) { + return Functions::NA(); + } + + return $x; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php new file mode 100644 index 00000000..4d158b8c --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php @@ -0,0 +1,166 @@ +getMessage(); + } + + if ($stdDev < 0) { + return Functions::NAN(); + } + + if ($cumulative) { + return 0.5 * (1 + Engineering\Erf::erfValue(($value - $mean) / ($stdDev * sqrt(2)))); + } + + return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev)))); + } + + /** + * NORMINV. + * + * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation. + * + * @param mixed $probability Float probability for which we want the value + * @param mixed $mean Mean Value as a float + * @param mixed $stdDev Standard Deviation as a float + * + * @return float|string The result, or a string containing an error + */ + public static function inverse($probability, $mean, $stdDev) + { + $probability = Functions::flattenSingleValue($probability); + $mean = Functions::flattenSingleValue($mean); + $stdDev = Functions::flattenSingleValue($stdDev); + + try { + $probability = DistributionValidations::validateProbability($probability); + $mean = DistributionValidations::validateFloat($mean); + $stdDev = DistributionValidations::validateFloat($stdDev); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($stdDev < 0) { + return Functions::NAN(); + } + + return (self::inverseNcdf($probability) * $stdDev) + $mean; + } + + /* + * inverse_ncdf.php + * ------------------- + * begin : Friday, January 16, 2004 + * copyright : (C) 2004 Michael Nickerson + * email : nickersonm@yahoo.com + * + */ + private static function inverseNcdf($p) + { + // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to + // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as + // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html + // I have not checked the accuracy of this implementation. Be aware that PHP + // will truncate the coeficcients to 14 digits. + + // You have permission to use and distribute this function freely for + // whatever purpose you want, but please show common courtesy and give credit + // where credit is due. + + // Input paramater is $p - probability - where 0 < p < 1. + + // Coefficients in rational approximations + static $a = [ + 1 => -3.969683028665376e+01, + 2 => 2.209460984245205e+02, + 3 => -2.759285104469687e+02, + 4 => 1.383577518672690e+02, + 5 => -3.066479806614716e+01, + 6 => 2.506628277459239e+00, + ]; + + static $b = [ + 1 => -5.447609879822406e+01, + 2 => 1.615858368580409e+02, + 3 => -1.556989798598866e+02, + 4 => 6.680131188771972e+01, + 5 => -1.328068155288572e+01, + ]; + + static $c = [ + 1 => -7.784894002430293e-03, + 2 => -3.223964580411365e-01, + 3 => -2.400758277161838e+00, + 4 => -2.549732539343734e+00, + 5 => 4.374664141464968e+00, + 6 => 2.938163982698783e+00, + ]; + + static $d = [ + 1 => 7.784695709041462e-03, + 2 => 3.224671290700398e-01, + 3 => 2.445134137142996e+00, + 4 => 3.754408661907416e+00, + ]; + + // Define lower and upper region break-points. + $p_low = 0.02425; //Use lower region approx. below this + $p_high = 1 - $p_low; //Use upper region approx. above this + + if (0 < $p && $p < $p_low) { + // Rational approximation for lower region. + $q = sqrt(-2 * log($p)); + + return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / + (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); + } elseif ($p_high < $p && $p < 1) { + // Rational approximation for upper region. + $q = sqrt(-2 * log(1 - $p)); + + return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / + (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); + } + + // Rational approximation for central region. + $q = $p - 0.5; + $r = $q * $q; + + return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q / + ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php new file mode 100644 index 00000000..c25194a7 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php @@ -0,0 +1,53 @@ +getMessage(); + } + + if (($value < 0) || ($mean < 0)) { + return Functions::NAN(); + } + + if ($cumulative) { + $summer = 0; + $floor = floor($value); + for ($i = 0; $i <= $floor; ++$i) { + $summer += $mean ** $i / MathTrig\Fact::funcFact($i); + } + + return exp(0 - $mean) * $summer; + } + + return (exp(0 - $mean) * $mean ** $value) / MathTrig\Fact::funcFact($value); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php new file mode 100644 index 00000000..0dde2006 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php @@ -0,0 +1,90 @@ +getMessage(); + } + + if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) { + return Functions::NAN(); + } + + return self::calculateDistribution($value, $degrees, $tails); + } + + /** + * TINV. + * + * Returns the one-tailed probability of the chi-squared distribution. + * + * @param mixed $probability Float probability for the function + * @param mixed $degrees Integer value for degrees of freedom + * + * @return float|string The result, or a string containing an error + */ + public static function inverse($probability, $degrees) + { + $probability = Functions::flattenSingleValue($probability); + $degrees = Functions::flattenSingleValue($degrees); + + try { + $probability = DistributionValidations::validateProbability($probability); + $degrees = DistributionValidations::validateInt($degrees); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees <= 0) { + return Functions::NAN(); + } + + $callback = function ($value) use ($degrees) { + return self::distribution($value, $degrees, 2); + }; + + $newtonRaphson = new NewtonRaphson($callback); + + return $newtonRaphson->execute($probability); + } + + /** + * @return float + */ + private static function calculateDistribution(float $value, int $degrees, int $tails) + { + // tdist, which finds the probability that corresponds to a given value + // of t with k degrees of freedom. This algorithm is translated from a + // pascal function on p81 of "Statistical Computing in Pascal" by D + // Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd: + // London). The above Pascal algorithm is itself a translation of the + // fortran algoritm "AS 3" by B E Cooper of the Atlas Computer + // Laboratory as reported in (among other places) "Applied Statistics + // Algorithms", editied by P Griffiths and I D Hill (1985; Ellis + // Horwood Ltd.; W. Sussex, England). + $tterm = $degrees; + $ttheta = atan2($value, sqrt($tterm)); + $tc = cos($ttheta); + $ts = sin($ttheta); + + if (($degrees % 2) === 1) { + $ti = 3; + $tterm = $tc; + } else { + $ti = 2; + $tterm = 1; + } + + $tsum = $tterm; + while ($ti < $degrees) { + $tterm *= $tc * $tc * ($ti - 1) / $ti; + $tsum += $tterm; + $ti += 2; + } + + $tsum *= $ts; + if (($degrees % 2) == 1) { + $tsum = Functions::M_2DIVPI * ($tsum + $ttheta); + } + + $tValue = 0.5 * (1 + $tsum); + if ($tails == 1) { + return 1 - abs($tValue); + } + + return 1 - abs((1 - $tValue) - $tValue); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php new file mode 100644 index 00000000..ecec8a85 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php @@ -0,0 +1,49 @@ +getMessage(); + } + + if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) { + return Functions::NAN(); + } + + if ($cumulative) { + return 1 - exp(0 - ($value / $beta) ** $alpha); + } + + return ($alpha / $beta ** $alpha) * $value ** ($alpha - 1) * exp(0 - ($value / $beta) ** $alpha); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php b/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php new file mode 100644 index 00000000..bd17b062 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php @@ -0,0 +1,17 @@ + $returnValue)) { + $returnValue = $arg; + } + } + } + + if ($returnValue === null) { + return 0; + } + + return $returnValue; + } + + /** + * MAXA. + * + * Returns the greatest value in a list of arguments, including numbers, text, and logical values + * + * Excel Function: + * MAXA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float + */ + public static function MAXA(...$args) + { + $returnValue = null; + + // Loop through arguments + $aArgs = Functions::flattenArray($args); + foreach ($aArgs as $arg) { + // Is it a numeric value? + if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { + $arg = self::datatypeAdjustmentAllowStrings($arg); + if (($returnValue === null) || ($arg > $returnValue)) { + $returnValue = $arg; + } + } + } + + if ($returnValue === null) { + return 0; + } + + return $returnValue; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php b/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php new file mode 100644 index 00000000..bd46882e --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php @@ -0,0 +1,78 @@ +getMessage(); + } + + if (($entry < 0) || ($entry > 1)) { + return Functions::NAN(); + } + + $mArgs = self::percentileFilterValues($aArgs); + $mValueCount = count($mArgs); + if ($mValueCount > 0) { + sort($mArgs); + $count = Counts::COUNT($mArgs); + $index = $entry * ($count - 1); + $iBase = floor($index); + if ($index == $iBase) { + return $mArgs[$index]; + } + $iNext = $iBase + 1; + $iProportion = $index - $iBase; + + return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion); + } + + return Functions::NAN(); + } + + /** + * PERCENTRANK. + * + * Returns the rank of a value in a data set as a percentage of the data set. + * Note that the returned rank is simply rounded to the appropriate significant digits, + * rather than floored (as MS Excel), so value 3 for a value set of 1, 2, 3, 4 will return + * 0.667 rather than 0.666 + * + * @param mixed $valueSet An array of (float) values, or a reference to, a list of numbers + * @param mixed $value The number whose rank you want to find + * @param mixed $significance The (integer) number of significant digits for the returned percentage value + * + * @return float|string (string if result is an error) + */ + public static function PERCENTRANK($valueSet, $value, $significance = 3) + { + $valueSet = Functions::flattenArray($valueSet); + $value = Functions::flattenSingleValue($value); + $significance = ($significance === null) ? 3 : Functions::flattenSingleValue($significance); + + try { + $value = StatisticalValidations::validateFloat($value); + $significance = StatisticalValidations::validateInt($significance); + } catch (Exception $e) { + return $e->getMessage(); + } + + $valueSet = self::rankFilterValues($valueSet); + $valueCount = count($valueSet); + if ($valueCount == 0) { + return Functions::NA(); + } + sort($valueSet, SORT_NUMERIC); + + $valueAdjustor = $valueCount - 1; + if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) { + return Functions::NA(); + } + + $pos = array_search($value, $valueSet); + if ($pos === false) { + $pos = 0; + $testValue = $valueSet[0]; + while ($testValue < $value) { + $testValue = $valueSet[++$pos]; + } + --$pos; + $pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos])); + } + + return round($pos / $valueAdjustor, $significance); + } + + /** + * QUARTILE. + * + * Returns the quartile of a data set. + * + * Excel Function: + * QUARTILE(value1[,value2[, ...]],entry) + * + * @param mixed $args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function QUARTILE(...$args) + { + $aArgs = Functions::flattenArray($args); + $entry = array_pop($aArgs); + + try { + $entry = StatisticalValidations::validateFloat($entry); + } catch (Exception $e) { + return $e->getMessage(); + } + + $entry = floor($entry); + $entry /= 4; + if (($entry < 0) || ($entry > 1)) { + return Functions::NAN(); + } + + return self::PERCENTILE($aArgs, $entry); + } + + /** + * RANK. + * + * Returns the rank of a number in a list of numbers. + * + * @param mixed $value The number whose rank you want to find + * @param mixed $valueSet An array of float values, or a reference to, a list of numbers + * @param mixed $order Order to sort the values in the value set + * + * @return float|string The result, or a string containing an error (0 = Descending, 1 = Ascending) + */ + public static function RANK($value, $valueSet, $order = self::RANK_SORT_DESCENDING) + { + $value = Functions::flattenSingleValue($value); + $valueSet = Functions::flattenArray($valueSet); + $order = ($order === null) ? self::RANK_SORT_DESCENDING : Functions::flattenSingleValue($order); + + try { + $value = StatisticalValidations::validateFloat($value); + $order = StatisticalValidations::validateInt($order); + } catch (Exception $e) { + return $e->getMessage(); + } + + $valueSet = self::rankFilterValues($valueSet); + if ($order === self::RANK_SORT_DESCENDING) { + rsort($valueSet, SORT_NUMERIC); + } else { + sort($valueSet, SORT_NUMERIC); + } + + $pos = array_search($value, $valueSet); + if ($pos === false) { + return Functions::NA(); + } + + return ++$pos; + } + + protected static function percentileFilterValues(array $dataSet) + { + return array_filter( + $dataSet, + function ($value): bool { + return is_numeric($value) && !is_string($value); + } + ); + } + + protected static function rankFilterValues(array $dataSet) + { + return array_filter( + $dataSet, + function ($value): bool { + return is_numeric($value); + } + ); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php b/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php new file mode 100644 index 00000000..6330d39f --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php @@ -0,0 +1,73 @@ +getMessage(); + } + + if ($numObjs < $numInSet) { + return Functions::NAN(); + } + + return (int) round(MathTrig\Fact::funcFact($numObjs) / MathTrig\Fact::funcFact($numObjs - $numInSet)); + } + + /** + * PERMUTATIONA. + * + * Returns the number of permutations for a given number of objects (with repetitions) + * that can be selected from the total objects. + * + * @param mixed $numObjs Integer number of different objects + * @param mixed $numInSet Integer number of objects in each permutation + * + * @return int|string Number of permutations, or a string containing an error + */ + public static function PERMUTATIONA($numObjs, $numInSet) + { + $numObjs = Functions::flattenSingleValue($numObjs); + $numInSet = Functions::flattenSingleValue($numInSet); + + try { + $numObjs = StatisticalValidations::validateInt($numObjs); + $numInSet = StatisticalValidations::validateInt($numInSet); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($numObjs < 0 || $numInSet < 0) { + return Functions::NAN(); + } + + return (int) ($numObjs ** $numInSet); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php b/src/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php new file mode 100644 index 00000000..af271205 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php @@ -0,0 +1,95 @@ + $value) { + if ((is_bool($value)) || (is_string($value)) || ($value === null)) { + unset($array1[$key], $array2[$key]); + } + } + } + + private static function checkTrendArrays(&$array1, &$array2): void + { + if (!is_array($array1)) { + $array1 = [$array1]; + } + if (!is_array($array2)) { + $array2 = [$array2]; + } + + $array1 = Functions::flattenArray($array1); + $array2 = Functions::flattenArray($array2); + + self::filterTrendValues($array1, $array2); + self::filterTrendValues($array2, $array1); + + // Reset the array indexes + $array1 = array_merge($array1); + $array2 = array_merge($array2); + } + + protected static function validateTrendArrays(array $yValues, array $xValues): void + { + $yValueCount = count($yValues); + $xValueCount = count($xValues); + + if (($yValueCount === 0) || ($yValueCount !== $xValueCount)) { + throw new Exception(Functions::NA()); + } elseif ($yValueCount === 1) { + throw new Exception(Functions::DIV0()); + } + } + + /** + * CORREL. + * + * Returns covariance, the average of the products of deviations for each data point pair. + * + * @param mixed $yValues array of mixed Data Series Y + * @param null|mixed $xValues array of mixed Data Series X + * + * @return float|string + */ + public static function CORREL($yValues, $xValues = null) + { + if (($xValues === null) || (!is_array($yValues)) || (!is_array($xValues))) { + return Functions::VALUE(); + } + + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getCorrelation(); + } + + /** + * COVAR. + * + * Returns covariance, the average of the products of deviations for each data point pair. + * + * @param mixed $yValues array of mixed Data Series Y + * @param mixed $xValues array of mixed Data Series X + * + * @return float|string + */ + public static function COVAR($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getCovariance(); + } + + /** + * FORECAST. + * + * Calculates, or predicts, a future value by using existing values. + * The predicted value is a y-value for a given x-value. + * + * @param mixed $xValue Float value of X for which we want to find Y + * @param mixed $yValues array of mixed Data Series Y + * @param mixed $xValues of mixed Data Series X + * + * @return bool|float|string + */ + public static function FORECAST($xValue, $yValues, $xValues) + { + $xValue = Functions::flattenSingleValue($xValue); + + try { + $xValue = StatisticalValidations::validateFloat($xValue); + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getValueOfYForX($xValue); + } + + /** + * GROWTH. + * + * Returns values along a predicted exponential Trend + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * @param mixed[] $newValues Values of X for which we want to find Y + * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not + * + * @return float[] + */ + public static function GROWTH($yValues, $xValues = [], $newValues = [], $const = true) + { + $yValues = Functions::flattenArray($yValues); + $xValues = Functions::flattenArray($xValues); + $newValues = Functions::flattenArray($newValues); + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + + $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); + if (empty($newValues)) { + $newValues = $bestFitExponential->getXValues(); + } + + $returnArray = []; + foreach ($newValues as $xValue) { + $returnArray[0][] = [$bestFitExponential->getValueOfYForX($xValue)]; + } + + return $returnArray; + } + + /** + * INTERCEPT. + * + * Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values. + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * + * @return float|string + */ + public static function INTERCEPT($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getIntersect(); + } + + /** + * LINEST. + * + * Calculates the statistics for a line by using the "least squares" method to calculate a straight line + * that best fits your data, and then returns an array that describes the line. + * + * @param mixed[] $yValues Data Series Y + * @param null|mixed[] $xValues Data Series X + * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not + * @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics + * + * @return array|int|string The result, or a string containing an error + */ + public static function LINEST($yValues, $xValues = null, $const = true, $stats = false) + { + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); + if ($xValues === null) { + $xValues = $yValues; + } + + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); + + if ($stats === true) { + return [ + [ + $bestFitLinear->getSlope(), + $bestFitLinear->getIntersect(), + ], + [ + $bestFitLinear->getSlopeSE(), + ($const === false) ? Functions::NA() : $bestFitLinear->getIntersectSE(), + ], + [ + $bestFitLinear->getGoodnessOfFit(), + $bestFitLinear->getStdevOfResiduals(), + ], + [ + $bestFitLinear->getF(), + $bestFitLinear->getDFResiduals(), + ], + [ + $bestFitLinear->getSSRegression(), + $bestFitLinear->getSSResiduals(), + ], + ]; + } + + return [ + $bestFitLinear->getSlope(), + $bestFitLinear->getIntersect(), + ]; + } + + /** + * LOGEST. + * + * Calculates an exponential curve that best fits the X and Y data series, + * and then returns an array that describes the line. + * + * @param mixed[] $yValues Data Series Y + * @param null|mixed[] $xValues Data Series X + * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not + * @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics + * + * @return array|int|string The result, or a string containing an error + */ + public static function LOGEST($yValues, $xValues = null, $const = true, $stats = false) + { + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); + if ($xValues === null) { + $xValues = $yValues; + } + + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + foreach ($yValues as $value) { + if ($value < 0.0) { + return Functions::NAN(); + } + } + + $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); + + if ($stats === true) { + return [ + [ + $bestFitExponential->getSlope(), + $bestFitExponential->getIntersect(), + ], + [ + $bestFitExponential->getSlopeSE(), + ($const === false) ? Functions::NA() : $bestFitExponential->getIntersectSE(), + ], + [ + $bestFitExponential->getGoodnessOfFit(), + $bestFitExponential->getStdevOfResiduals(), + ], + [ + $bestFitExponential->getF(), + $bestFitExponential->getDFResiduals(), + ], + [ + $bestFitExponential->getSSRegression(), + $bestFitExponential->getSSResiduals(), + ], + ]; + } + + return [ + $bestFitExponential->getSlope(), + $bestFitExponential->getIntersect(), + ]; + } + + /** + * RSQ. + * + * Returns the square of the Pearson product moment correlation coefficient through data points + * in known_y's and known_x's. + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * + * @return float|string The result, or a string containing an error + */ + public static function RSQ($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getGoodnessOfFit(); + } + + /** + * SLOPE. + * + * Returns the slope of the linear regression line through data points in known_y's and known_x's. + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * + * @return float|string The result, or a string containing an error + */ + public static function SLOPE($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getSlope(); + } + + /** + * STEYX. + * + * Returns the standard error of the predicted y-value for each x in the regression. + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * + * @return float|string + */ + public static function STEYX($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getStdevOfResiduals(); + } + + /** + * TREND. + * + * Returns values along a linear Trend + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * @param mixed[] $newValues Values of X for which we want to find Y + * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not + * + * @return float[] + */ + public static function TREND($yValues, $xValues = [], $newValues = [], $const = true) + { + $yValues = Functions::flattenArray($yValues); + $xValues = Functions::flattenArray($xValues); + $newValues = Functions::flattenArray($newValues); + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); + if (empty($newValues)) { + $newValues = $bestFitLinear->getXValues(); + } + + $returnArray = []; + foreach ($newValues as $xValue) { + $returnArray[0][] = [$bestFitLinear->getValueOfYForX($xValue)]; + } + + return $returnArray; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php b/src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php new file mode 100644 index 00000000..e5334671 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php @@ -0,0 +1,28 @@ + 1) { + $summerA *= $aCount; + $summerB *= $summerB; + + return ($summerA - $summerB) / ($aCount * ($aCount - 1)); + } + + return $returnValue; + } + + /** + * VARA. + * + * Estimates variance based on a sample, including numbers, text, and logical values + * + * Excel Function: + * VARA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function VARA(...$args) + { + $returnValue = Functions::DIV0(); + + $summerA = $summerB = 0.0; + + // Loop through arguments + $aArgs = Functions::flattenArrayIndexed($args); + $aCount = 0; + foreach ($aArgs as $k => $arg) { + if ((is_string($arg)) && (Functions::isValue($k))) { + return Functions::VALUE(); + } elseif ((is_string($arg)) && (!Functions::isMatrixValue($k))) { + } else { + // Is it a numeric value? + if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { + $arg = self::datatypeAdjustmentAllowStrings($arg); + $summerA += ($arg * $arg); + $summerB += $arg; + ++$aCount; + } + } + } + + if ($aCount > 1) { + $summerA *= $aCount; + $summerB *= $summerB; + + return ($summerA - $summerB) / ($aCount * ($aCount - 1)); + } + + return $returnValue; + } + + /** + * VARP. + * + * Calculates variance based on the entire population + * + * Excel Function: + * VARP(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function VARP(...$args) + { + // Return value + $returnValue = Functions::DIV0(); + + $summerA = $summerB = 0.0; + + // Loop through arguments + $aArgs = Functions::flattenArray($args); + $aCount = 0; + foreach ($aArgs as $arg) { + $arg = self::datatypeAdjustmentBooleans($arg); + + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $summerA += ($arg * $arg); + $summerB += $arg; + ++$aCount; + } + } + + if ($aCount > 0) { + $summerA *= $aCount; + $summerB *= $summerB; + + return ($summerA - $summerB) / ($aCount * $aCount); + } + + return $returnValue; + } + + /** + * VARPA. + * + * Calculates variance based on the entire population, including numbers, text, and logical values + * + * Excel Function: + * VARPA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function VARPA(...$args) + { + $returnValue = Functions::DIV0(); + + $summerA = $summerB = 0.0; + + // Loop through arguments + $aArgs = Functions::flattenArrayIndexed($args); + $aCount = 0; + foreach ($aArgs as $k => $arg) { + if ((is_string($arg)) && (Functions::isValue($k))) { + return Functions::VALUE(); + } elseif ((is_string($arg)) && (!Functions::isMatrixValue($k))) { + } else { + // Is it a numeric value? + if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { + $arg = self::datatypeAdjustmentAllowStrings($arg); + $summerA += ($arg * $arg); + $summerB += $arg; + ++$aCount; + } + } + } + + if ($aCount > 0) { + $summerA *= $aCount; + $summerB *= $summerB; + + return ($summerA - $summerB) / ($aCount * $aCount); + } + + return $returnValue; + } +} diff --git a/src/PhpSpreadsheet/Calculation/TextData.php b/src/PhpSpreadsheet/Calculation/TextData.php index f8974402..0bde3b7f 100644 --- a/src/PhpSpreadsheet/Calculation/TextData.php +++ b/src/PhpSpreadsheet/Calculation/TextData.php @@ -3,141 +3,88 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; use DateTimeInterface; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PhpOffice\PhpSpreadsheet\Shared\StringHelper; -use PhpOffice\PhpSpreadsheet\Style\NumberFormat; +/** + * @deprecated 1.18.0 + */ class TextData { - private static $invalidChars; - - private static function unicodeToOrd($character) - { - return unpack('V', iconv('UTF-8', 'UCS-4LE', $character))[1]; - } - /** * CHARACTER. * + * @Deprecated 1.18.0 + * + * @see Use the character() method in the TextData\CharacterConvert class instead + * * @param string $character Value * * @return string */ public static function CHARACTER($character) { - $character = Functions::flattenSingleValue($character); - - if (!is_numeric($character)) { - return Functions::VALUE(); - } - $character = (int) $character; - if ($character < 1 || $character > 255) { - return Functions::VALUE(); - } - - return iconv('UCS-4LE', 'UTF-8', pack('V', $character)); + return TextData\CharacterConvert::character($character); } /** * TRIMNONPRINTABLE. * + * @Deprecated 1.18.0 + * + * @see Use the nonPrintable() method in the TextData\Trim class instead + * * @param mixed $stringValue Value to check * * @return string */ public static function TRIMNONPRINTABLE($stringValue = '') { - $stringValue = Functions::flattenSingleValue($stringValue); - - if (is_bool($stringValue)) { - return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (self::$invalidChars === null) { - self::$invalidChars = range(chr(0), chr(31)); - } - - if (is_string($stringValue) || is_numeric($stringValue)) { - return str_replace(self::$invalidChars, '', trim($stringValue, "\x00..\x1F")); - } - - return null; + return TextData\Trim::nonPrintable($stringValue); } /** * TRIMSPACES. * + * @Deprecated 1.18.0 + * + * @see Use the spaces() method in the TextData\Trim class instead + * * @param mixed $stringValue Value to check * * @return string */ public static function TRIMSPACES($stringValue = '') { - $stringValue = Functions::flattenSingleValue($stringValue); - if (is_bool($stringValue)) { - return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (is_string($stringValue) || is_numeric($stringValue)) { - return trim(preg_replace('/ +/', ' ', trim($stringValue, ' ')), ' '); - } - - return null; - } - - private static function convertBooleanValue($value) - { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - return (int) $value; - } - - return ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); + return TextData\Trim::spaces($stringValue); } /** * ASCIICODE. * + * @Deprecated 1.18.0 + * + * @see Use the code() method in the TextData\CharacterConvert class instead + * * @param string $characters Value * * @return int|string A string if arguments are invalid */ public static function ASCIICODE($characters) { - if (($characters === null) || ($characters === '')) { - return Functions::VALUE(); - } - $characters = Functions::flattenSingleValue($characters); - if (is_bool($characters)) { - $characters = self::convertBooleanValue($characters); - } - - $character = $characters; - if (mb_strlen($characters, 'UTF-8') > 1) { - $character = mb_substr($characters, 0, 1, 'UTF-8'); - } - - return self::unicodeToOrd($character); + return TextData\CharacterConvert::code($characters); } /** * CONCATENATE. * + * @Deprecated 1.18.0 + * + * @see Use the CONCATENATE() method in the TextData\Concatenate class instead + * * @return string */ public static function CONCATENATE(...$args) { - $returnValue = ''; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - if (is_bool($arg)) { - $arg = self::convertBooleanValue($arg); - } - $returnValue .= $arg; - } - - return $returnValue; + return TextData\Concatenate::CONCATENATE(...$args); } /** @@ -146,6 +93,10 @@ class TextData * This function converts a number to text using currency format, with the decimals rounded to the specified place. * The format used is $#,##0.00_);($#,##0.00).. * + * @Deprecated 1.18.0 + * + * @see Use the DOLLAR() method in the TextData\Format class instead + * * @param float $value The value to format * @param int $decimals The number of digits to display to the right of the decimal point. * If decimals is negative, number is rounded to the left of the decimal point. @@ -155,33 +106,16 @@ class TextData */ public static function DOLLAR($value = 0, $decimals = 2) { - $value = Functions::flattenSingleValue($value); - $decimals = $decimals === null ? 0 : Functions::flattenSingleValue($decimals); - - // Validate parameters - if (!is_numeric($value) || !is_numeric($decimals)) { - return Functions::VALUE(); - } - $decimals = floor($decimals); - - $mask = '$#,##0'; - if ($decimals > 0) { - $mask .= '.' . str_repeat('0', $decimals); - } else { - $round = 10 ** abs($decimals); - if ($value < 0) { - $round = 0 - $round; - } - $value = MathTrig::MROUND($value, $round); - } - $mask = "$mask;($mask)"; - - return NumberFormat::toFormattedString($value, $mask); + return TextData\Format::DOLLAR($value, $decimals); } /** * SEARCHSENSITIVE. * + * @Deprecated 1.18.0 + * + * @see Use the sensitive() method in the TextData\Search class instead + * * @param string $needle The string to look for * @param string $haystack The string in which to look * @param int $offset Offset within $haystack @@ -190,33 +124,16 @@ class TextData */ public static function SEARCHSENSITIVE($needle, $haystack, $offset = 1) { - $needle = Functions::flattenSingleValue($needle); - $haystack = Functions::flattenSingleValue($haystack); - $offset = Functions::flattenSingleValue($offset); - - if (!is_bool($needle)) { - if (is_bool($haystack)) { - $haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) { - if (StringHelper::countCharacters($needle) === 0) { - return $offset; - } - - $pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8'); - if ($pos !== false) { - return ++$pos; - } - } - } - - return Functions::VALUE(); + return TextData\Search::sensitive($needle, $haystack, $offset); } /** * SEARCHINSENSITIVE. * + * @Deprecated 1.18.0 + * + * @see Use the insensitive() method in the TextData\Search class instead + * * @param string $needle The string to look for * @param string $haystack The string in which to look * @param int $offset Offset within $haystack @@ -225,33 +142,16 @@ class TextData */ public static function SEARCHINSENSITIVE($needle, $haystack, $offset = 1) { - $needle = Functions::flattenSingleValue($needle); - $haystack = Functions::flattenSingleValue($haystack); - $offset = Functions::flattenSingleValue($offset); - - if (!is_bool($needle)) { - if (is_bool($haystack)) { - $haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) { - if (StringHelper::countCharacters($needle) === 0) { - return $offset; - } - - $pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8'); - if ($pos !== false) { - return ++$pos; - } - } - } - - return Functions::VALUE(); + return TextData\Search::insensitive($needle, $haystack, $offset); } /** * FIXEDFORMAT. * + * @Deprecated 1.18.0 + * + * @see Use the FIXEDFORMAT() method in the TextData\Format class instead + * * @param mixed $value Value to check * @param int $decimals * @param bool $no_commas @@ -260,35 +160,16 @@ class TextData */ public static function FIXEDFORMAT($value, $decimals = 2, $no_commas = false) { - $value = Functions::flattenSingleValue($value); - $decimals = Functions::flattenSingleValue($decimals); - $no_commas = Functions::flattenSingleValue($no_commas); - - // Validate parameters - if (!is_numeric($value) || !is_numeric($decimals)) { - return Functions::VALUE(); - } - $decimals = (int) floor($decimals); - - $valueResult = round($value, $decimals); - if ($decimals < 0) { - $decimals = 0; - } - if (!$no_commas) { - $valueResult = number_format( - $valueResult, - $decimals, - StringHelper::getDecimalSeparator(), - StringHelper::getThousandsSeparator() - ); - } - - return (string) $valueResult; + return TextData\Format::FIXEDFORMAT($value, $decimals, $no_commas); } /** * LEFT. * + * @Deprecated 1.18.0 + * + * @see Use the left() method in the TextData\Extract class instead + * * @param string $value Value * @param int $chars Number of characters * @@ -296,23 +177,16 @@ class TextData */ public static function LEFT($value = '', $chars = 1) { - $value = Functions::flattenSingleValue($value); - $chars = Functions::flattenSingleValue($chars); - - if ($chars < 0) { - return Functions::VALUE(); - } - - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return mb_substr($value, 0, $chars, 'UTF-8'); + return TextData\Extract::left($value, $chars); } /** * MID. * + * @Deprecated 1.18.0 + * + * @see Use the mid() method in the TextData\Extract class instead + * * @param string $value Value * @param int $start Start character * @param int $chars Number of characters @@ -321,28 +195,16 @@ class TextData */ public static function MID($value = '', $start = 1, $chars = null) { - $value = Functions::flattenSingleValue($value); - $start = Functions::flattenSingleValue($start); - $chars = Functions::flattenSingleValue($chars); - - if (($start < 1) || ($chars < 0)) { - return Functions::VALUE(); - } - - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (empty($chars)) { - return ''; - } - - return mb_substr($value, --$start, $chars, 'UTF-8'); + return TextData\Extract::mid($value, $start, $chars); } /** * RIGHT. * + * @Deprecated 1.18.0 + * + * @see Use the right() method in the TextData\Extract class instead + * * @param string $value Value * @param int $chars Number of characters * @@ -350,36 +212,23 @@ class TextData */ public static function RIGHT($value = '', $chars = 1) { - $value = Functions::flattenSingleValue($value); - $chars = Functions::flattenSingleValue($chars); - - if ($chars < 0) { - return Functions::VALUE(); - } - - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return mb_substr($value, mb_strlen($value, 'UTF-8') - $chars, $chars, 'UTF-8'); + return TextData\Extract::right($value, $chars); } /** * STRINGLENGTH. * + * @Deprecated 1.18.0 + * + * @see Use the length() method in the TextData\Text class instead + * * @param string $value Value * * @return int */ public static function STRINGLENGTH($value = '') { - $value = Functions::flattenSingleValue($value); - - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return mb_strlen($value, 'UTF-8'); + return TextData\Text::length($value); } /** @@ -387,19 +236,17 @@ class TextData * * Converts a string value to upper case. * + * @Deprecated 1.18.0 + * + * @see Use the lower() method in the TextData\CaseConvert class instead + * * @param string $mixedCaseString * * @return string */ public static function LOWERCASE($mixedCaseString) { - $mixedCaseString = Functions::flattenSingleValue($mixedCaseString); - - if (is_bool($mixedCaseString)) { - $mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return StringHelper::strToLower($mixedCaseString); + return TextData\CaseConvert::lower($mixedCaseString); } /** @@ -407,19 +254,17 @@ class TextData * * Converts a string value to upper case. * + * @Deprecated 1.18.0 + * + * @see Use the upper() method in the TextData\CaseConvert class instead + * * @param string $mixedCaseString * * @return string */ public static function UPPERCASE($mixedCaseString) { - $mixedCaseString = Functions::flattenSingleValue($mixedCaseString); - - if (is_bool($mixedCaseString)) { - $mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return StringHelper::strToUpper($mixedCaseString); + return TextData\CaseConvert::upper($mixedCaseString); } /** @@ -427,24 +272,26 @@ class TextData * * Converts a string value to upper case. * + * @Deprecated 1.18.0 + * + * @see Use the proper() method in the TextData\CaseConvert class instead + * * @param string $mixedCaseString * * @return string */ public static function PROPERCASE($mixedCaseString) { - $mixedCaseString = Functions::flattenSingleValue($mixedCaseString); - - if (is_bool($mixedCaseString)) { - $mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return StringHelper::strToTitle($mixedCaseString); + return TextData\CaseConvert::proper($mixedCaseString); } /** * REPLACE. * + * @Deprecated 1.18.0 + * + * @see Use the replace() method in the TextData\Replace class instead + * * @param string $oldText String to modify * @param int $start Start character * @param int $chars Number of characters @@ -454,20 +301,16 @@ class TextData */ public static function REPLACE($oldText, $start, $chars, $newText) { - $oldText = Functions::flattenSingleValue($oldText); - $start = Functions::flattenSingleValue($start); - $chars = Functions::flattenSingleValue($chars); - $newText = Functions::flattenSingleValue($newText); - - $left = self::LEFT($oldText, $start - 1); - $right = self::RIGHT($oldText, self::STRINGLENGTH($oldText) - ($start + $chars) + 1); - - return $left . $newText . $right; + return TextData\Replace::replace($oldText, $start, $chars, $newText); } /** * SUBSTITUTE. * + * @Deprecated 1.18.0 + * + * @see Use the substitute() method in the TextData\Replace class instead + * * @param string $text Value * @param string $fromText From Value * @param string $toText To Value @@ -477,52 +320,32 @@ class TextData */ public static function SUBSTITUTE($text = '', $fromText = '', $toText = '', $instance = 0) { - $text = Functions::flattenSingleValue($text); - $fromText = Functions::flattenSingleValue($fromText); - $toText = Functions::flattenSingleValue($toText); - $instance = floor(Functions::flattenSingleValue($instance)); - - if ($instance == 0) { - return str_replace($fromText, $toText, $text); - } - - $pos = -1; - while ($instance > 0) { - $pos = mb_strpos($text, $fromText, $pos + 1, 'UTF-8'); - if ($pos === false) { - break; - } - --$instance; - } - - if ($pos !== false) { - return self::REPLACE($text, ++$pos, mb_strlen($fromText, 'UTF-8'), $toText); - } - - return $text; + return TextData\Replace::substitute($text, $fromText, $toText, $instance); } /** * RETURNSTRING. * + * @Deprecated 1.18.0 + * + * @see Use the test() method in the TextData\Text class instead + * * @param mixed $testValue Value to check * * @return null|string */ public static function RETURNSTRING($testValue = '') { - $testValue = Functions::flattenSingleValue($testValue); - - if (is_string($testValue)) { - return $testValue; - } - - return null; + return TextData\Text::test($testValue); } /** * TEXTFORMAT. * + * @Deprecated 1.18.0 + * + * @see Use the TEXTFORMAT() method in the TextData\Format class instead + * * @param mixed $value Value to check * @param string $format Format mask to use * @@ -530,65 +353,32 @@ class TextData */ public static function TEXTFORMAT($value, $format) { - $value = Functions::flattenSingleValue($value); - $format = Functions::flattenSingleValue($format); - - if ((is_string($value)) && (!is_numeric($value)) && Date::isDateTimeFormatCode($format)) { - $value = DateTime::DATEVALUE($value); - } - - return (string) NumberFormat::toFormattedString($value, $format); + return TextData\Format::TEXTFORMAT($value, $format); } /** * VALUE. * + * @Deprecated 1.18.0 + * + * @see Use the VALUE() method in the TextData\Format class instead + * * @param mixed $value Value to check * * @return DateTimeInterface|float|int|string A string if arguments are invalid */ public static function VALUE($value = '') { - $value = Functions::flattenSingleValue($value); - - if (!is_numeric($value)) { - $numberValue = str_replace( - StringHelper::getThousandsSeparator(), - '', - trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode()) - ); - if (is_numeric($numberValue)) { - return (float) $numberValue; - } - - $dateSetting = Functions::getReturnDateType(); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - - if (strpos($value, ':') !== false) { - $timeValue = DateTime::TIMEVALUE($value); - if ($timeValue !== Functions::VALUE()) { - Functions::setReturnDateType($dateSetting); - - return $timeValue; - } - } - $dateValue = DateTime::DATEVALUE($value); - if ($dateValue !== Functions::VALUE()) { - Functions::setReturnDateType($dateSetting); - - return $dateValue; - } - Functions::setReturnDateType($dateSetting); - - return Functions::VALUE(); - } - - return (float) $value; + return TextData\Format::VALUE($value); } /** * NUMBERVALUE. * + * @Deprecated 1.18.0 + * + * @see Use the NUMBERVALUE() method in the TextData\Format class instead + * * @param mixed $value Value to check * @param string $decimalSeparator decimal separator, defaults to locale defined value * @param string $groupSeparator group/thosands separator, defaults to locale defined value @@ -597,39 +387,7 @@ class TextData */ public static function NUMBERVALUE($value = '', $decimalSeparator = null, $groupSeparator = null) { - $value = Functions::flattenSingleValue($value); - $decimalSeparator = Functions::flattenSingleValue($decimalSeparator); - $groupSeparator = Functions::flattenSingleValue($groupSeparator); - - if (!is_numeric($value)) { - $decimalSeparator = empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : $decimalSeparator; - $groupSeparator = empty($groupSeparator) ? StringHelper::getThousandsSeparator() : $groupSeparator; - - $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE); - if ($decimalPositions > 1) { - return Functions::VALUE(); - } - $decimalOffset = array_pop($matches[0])[1]; - if (strpos($value, $groupSeparator, $decimalOffset) !== false) { - return Functions::VALUE(); - } - - $value = str_replace([$groupSeparator, $decimalSeparator], ['', '.'], $value); - - // Handle the special case of trailing % signs - $percentageString = rtrim($value, '%'); - if (!is_numeric($percentageString)) { - return Functions::VALUE(); - } - - $percentageAdjustment = strlen($value) - strlen($percentageString); - if ($percentageAdjustment) { - $value = (float) $percentageString; - $value /= 10 ** ($percentageAdjustment * 2); - } - } - - return (float) $value; + return TextData\Format::NUMBERVALUE($value, $decimalSeparator, $groupSeparator); } /** @@ -637,22 +395,27 @@ class TextData * EXACT is case-sensitive but ignores formatting differences. * Use EXACT to test text being entered into a document. * - * @param $value1 - * @param $value2 + * @Deprecated 1.18.0 + * + * @see Use the exact() method in the TextData\Text class instead + * + * @param mixed $value1 + * @param mixed $value2 * * @return bool */ public static function EXACT($value1, $value2) { - $value1 = Functions::flattenSingleValue($value1); - $value2 = Functions::flattenSingleValue($value2); - - return (string) $value2 === (string) $value1; + return TextData\Text::exact($value1, $value2); } /** * TEXTJOIN. * + * @Deprecated 1.18.0 + * + * @see Use the TEXTJOIN() method in the TextData\Concatenate class instead + * * @param mixed $delimiter * @param mixed $ignoreEmpty * @param mixed $args @@ -661,16 +424,25 @@ class TextData */ public static function TEXTJOIN($delimiter, $ignoreEmpty, ...$args) { - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $key => &$arg) { - if ($ignoreEmpty && trim($arg) == '') { - unset($aArgs[$key]); - } elseif (is_bool($arg)) { - $arg = self::convertBooleanValue($arg); - } - } + return TextData\Concatenate::TEXTJOIN($delimiter, $ignoreEmpty, ...$args); + } - return implode($delimiter, $aArgs); + /** + * REPT. + * + * Returns the result of builtin function repeat after validating args. + * + * @Deprecated 1.18.0 + * + * @see Use the builtinREPT() method in the TextData\Concatenate class instead + * + * @param string $str Should be numeric + * @param mixed $number Should be int + * + * @return string + */ + public static function builtinREPT($str, $number) + { + return TextData\Concatenate::builtinREPT($str, $number); } } diff --git a/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php b/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php new file mode 100644 index 00000000..36b5efbd --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php @@ -0,0 +1,64 @@ + 255) { + return Functions::VALUE(); + } + + return iconv('UCS-4LE', 'UTF-8', pack('V', $character)); + } + + /** + * ASCIICODE. + * + * @param mixed $characters String character to convert to its ASCII value + * + * @return int|string A string if arguments are invalid + */ + public static function code($characters) + { + if (($characters === null) || ($characters === '')) { + return Functions::VALUE(); + } + $characters = Functions::flattenSingleValue($characters); + if (is_bool($characters)) { + $characters = self::convertBooleanValue($characters); + } + + $character = $characters; + if (mb_strlen($characters, 'UTF-8') > 1) { + $character = mb_substr($characters, 0, 1, 'UTF-8'); + } + + return self::unicodeToOrd($character); + } + + private static function unicodeToOrd($character) + { + return unpack('V', iconv('UTF-8', 'UCS-4LE', $character))[1]; + } + + private static function convertBooleanValue($value) + { + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { + return (int) $value; + } + + return ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php b/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php new file mode 100644 index 00000000..5780bb6e --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php @@ -0,0 +1,82 @@ + &$arg) { + if ($ignoreEmpty === true && is_string($arg) && trim($arg) === '') { + unset($aArgs[$key]); + } elseif (is_bool($arg)) { + $arg = self::convertBooleanValue($arg); + } + } + + return implode($delimiter, $aArgs); + } + + /** + * REPT. + * + * Returns the result of builtin function round after validating args. + * + * @param mixed $stringValue The value to repeat + * @param mixed $repeatCount The number of times the string value should be repeated + */ + public static function builtinREPT($stringValue, $repeatCount): string + { + $repeatCount = Functions::flattenSingleValue($repeatCount); + + if (!is_numeric($repeatCount) || $repeatCount < 0) { + return Functions::VALUE(); + } + + if (is_bool($stringValue)) { + $stringValue = self::convertBooleanValue($stringValue); + } + + return str_repeat($stringValue, (int) $repeatCount); + } + + private static function convertBooleanValue($value) + { + if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { + return (int) $value; + } + + return ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/TextData/Extract.php b/src/PhpSpreadsheet/Calculation/TextData/Extract.php new file mode 100644 index 00000000..2f994858 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/TextData/Extract.php @@ -0,0 +1,77 @@ + 0) { + $mask .= '.' . str_repeat('0', $decimals); + } else { + $round = 10 ** abs($decimals); + if ($value < 0) { + $round = 0 - $round; + } + $value = MathTrig\Mround::funcMround($value, $round); + } + $mask = "$mask;($mask)"; + + return NumberFormat::toFormattedString($value, $mask); + } + + /** + * FIXEDFORMAT. + * + * @param mixed $value The value to format + * @param mixed $decimals Integer value for the number of decimal places that should be formatted + * @param mixed $noCommas Boolean value indicating whether the value should have thousands separators or not + */ + public static function FIXEDFORMAT($value, $decimals = 2, $noCommas = false): string + { + $value = Functions::flattenSingleValue($value); + $decimals = $decimals === null ? 2 : Functions::flattenSingleValue($decimals); + $noCommas = Functions::flattenSingleValue($noCommas); + + // Validate parameters + if (!is_numeric($value) || !is_numeric($decimals)) { + return Functions::VALUE(); + } + $decimals = (int) floor($decimals); + + $valueResult = round($value, $decimals); + if ($decimals < 0) { + $decimals = 0; + } + if ($noCommas === false) { + $valueResult = number_format( + $valueResult, + $decimals, + StringHelper::getDecimalSeparator(), + StringHelper::getThousandsSeparator() + ); + } + + return (string) $valueResult; + } + + /** + * TEXTFORMAT. + * + * @param mixed $value The value to format + * @param mixed $format A string with the Format mask that should be used + */ + public static function TEXTFORMAT($value, $format): string + { + $value = Functions::flattenSingleValue($value); + $format = Functions::flattenSingleValue($format); + + if ((is_string($value)) && (!is_numeric($value)) && Date::isDateTimeFormatCode($format)) { + $value = DateTimeExcel\DateValue::funcDateValue($value); + } + + return (string) NumberFormat::toFormattedString($value, $format); + } + + /** + * VALUE. + * + * @param mixed $value Value to check + * + * @return DateTimeInterface|float|int|string A string if arguments are invalid + */ + public static function VALUE($value = '') + { + $value = Functions::flattenSingleValue($value); + + if (!is_numeric($value)) { + $numberValue = str_replace( + StringHelper::getThousandsSeparator(), + '', + trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode()) + ); + if (is_numeric($numberValue)) { + return (float) $numberValue; + } + + $dateSetting = Functions::getReturnDateType(); + Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); + + if (strpos($value, ':') !== false) { + $timeValue = DateTimeExcel\TimeValue::funcTimeValue($value); + if ($timeValue !== Functions::VALUE()) { + Functions::setReturnDateType($dateSetting); + + return $timeValue; + } + } + $dateValue = DateTimeExcel\DateValue::funcDateValue($value); + if ($dateValue !== Functions::VALUE()) { + Functions::setReturnDateType($dateSetting); + + return $dateValue; + } + Functions::setReturnDateType($dateSetting); + + return Functions::VALUE(); + } + + return (float) $value; + } + + /** + * NUMBERVALUE. + * + * @param mixed $value The value to format + * @param mixed $decimalSeparator A string with the decimal separator to use, defaults to locale defined value + * @param mixed $groupSeparator A string with the group/thousands separator to use, defaults to locale defined value + * + * @return float|string + */ + public static function NUMBERVALUE($value = '', $decimalSeparator = null, $groupSeparator = null) + { + $value = Functions::flattenSingleValue($value); + $decimalSeparator = Functions::flattenSingleValue($decimalSeparator); + $groupSeparator = Functions::flattenSingleValue($groupSeparator); + + if (!is_numeric($value)) { + $decimalSeparator = empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : $decimalSeparator; + $groupSeparator = empty($groupSeparator) ? StringHelper::getThousandsSeparator() : $groupSeparator; + + $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE); + if ($decimalPositions > 1) { + return Functions::VALUE(); + } + $decimalOffset = array_pop($matches[0])[1]; + if (strpos($value, $groupSeparator, $decimalOffset) !== false) { + return Functions::VALUE(); + } + + $value = str_replace([$groupSeparator, $decimalSeparator], ['', '.'], $value); + + // Handle the special case of trailing % signs + $percentageString = rtrim($value, '%'); + if (!is_numeric($percentageString)) { + return Functions::VALUE(); + } + + $percentageAdjustment = strlen($value) - strlen($percentageString); + if ($percentageAdjustment) { + $value = (float) $percentageString; + $value /= 10 ** ($percentageAdjustment * 2); + } + } + + return (float) $value; + } +} diff --git a/src/PhpSpreadsheet/Calculation/TextData/Replace.php b/src/PhpSpreadsheet/Calculation/TextData/Replace.php new file mode 100644 index 00000000..7ca710ef --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/TextData/Replace.php @@ -0,0 +1,64 @@ + 0) { + $pos = mb_strpos($text, $fromText, $pos + 1, 'UTF-8'); + if ($pos === false) { + break; + } + --$instance; + } + + if ($pos !== false) { + return self::REPLACE($text, ++$pos, mb_strlen($fromText, 'UTF-8'), $toText); + } + + return $text; + } +} diff --git a/src/PhpSpreadsheet/Calculation/TextData/Search.php b/src/PhpSpreadsheet/Calculation/TextData/Search.php new file mode 100644 index 00000000..2da688d8 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/TextData/Search.php @@ -0,0 +1,80 @@ + 0) && (StringHelper::countCharacters($haystack) > $offset)) { + if (StringHelper::countCharacters($needle) === 0) { + return $offset; + } + + $pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8'); + if ($pos !== false) { + return ++$pos; + } + } + } + + return Functions::VALUE(); + } + + /** + * SEARCHINSENSITIVE. + * + * @param mixed $needle The string to look for + * @param mixed $haystack The string in which to look + * @param mixed $offset Integer offset within $haystack to start searching from + * + * @return int|string + */ + public static function insensitive($needle, $haystack, $offset = 1) + { + $needle = Functions::flattenSingleValue($needle); + $haystack = Functions::flattenSingleValue($haystack); + $offset = Functions::flattenSingleValue($offset); + + if (!is_bool($needle)) { + if (is_bool($haystack)) { + $haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE(); + } + + if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) { + if (StringHelper::countCharacters($needle) === 0) { + return $offset; + } + + $pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8'); + if ($pos !== false) { + return ++$pos; + } + } + } + + return Functions::VALUE(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/TextData/Text.php b/src/PhpSpreadsheet/Calculation/TextData/Text.php new file mode 100644 index 00000000..6e408891 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/TextData/Text.php @@ -0,0 +1,59 @@ +setValueExplicit((float) $value, DataType::TYPE_NUMERIC); - - return true; - } - - // Check for fraction + // Check for fractions if (preg_match('/^([+-]?)\s*(\d+)\s?\/\s*(\d+)$/', $value, $matches)) { - // Convert value to number - $value = $matches[2] / $matches[3]; - if ($matches[1] == '-') { - $value = 0 - $value; - } - $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC); - // Set style - $cell->getWorksheet()->getStyle($cell->getCoordinate()) - ->getNumberFormat()->setFormatCode('??/??'); - - return true; + return $this->setProperFraction($matches, $cell); } elseif (preg_match('/^([+-]?)(\d*) +(\d*)\s?\/\s*(\d*)$/', $value, $matches)) { - // Convert value to number - $value = $matches[2] + ($matches[3] / $matches[4]); - if ($matches[1] == '-') { - $value = 0 - $value; - } - $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC); - // Set style - $cell->getWorksheet()->getStyle($cell->getCoordinate()) - ->getNumberFormat()->setFormatCode('# ??/??'); - - return true; + return $this->setImproperFraction($matches, $cell); } // Check for percentage if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $value)) { - // Convert value to number - $value = (float) str_replace('%', '', $value) / 100; - $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); - // Set style - $cell->getWorksheet()->getStyle($cell->getCoordinate()) - ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_PERCENTAGE_00); - - return true; + return $this->setPercentage($value, $cell); } // Check for currency @@ -115,29 +83,12 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder // Check for time without seconds e.g. '9:45', '09:45' if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d$/', $value)) { - // Convert value to number - [$h, $m] = explode(':', $value); - $days = $h / 24 + $m / 1440; - $cell->setValueExplicit($days, DataType::TYPE_NUMERIC); - // Set style - $cell->getWorksheet()->getStyle($cell->getCoordinate()) - ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME3); - - return true; + return $this->setTimeHoursMinutes($value, $cell); } // Check for time with seconds '9:45:59', '09:45:59' if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d:[0-5]\d$/', $value)) { - // Convert value to number - [$h, $m, $s] = explode(':', $value); - $days = $h / 24 + $m / 1440 + $s / 86400; - // Convert value to number - $cell->setValueExplicit($days, DataType::TYPE_NUMERIC); - // Set style - $cell->getWorksheet()->getStyle($cell->getCoordinate()) - ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME4); - - return true; + return $this->setTimeHoursMinutesSeconds($value, $cell); } // Check for datetime, e.g. '2008-12-31', '2008-12-31 15:59', '2008-12-31 15:59:10' @@ -158,7 +109,6 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder // Check for newline character "\n" if (strpos($value, "\n") !== false) { - $value = StringHelper::sanitizeUTF8($value); $cell->setValueExplicit($value, DataType::TYPE_STRING); // Set style $cell->getWorksheet()->getStyle($cell->getCoordinate()) @@ -171,4 +121,85 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder // Not bound yet? Use parent... return parent::bindValue($cell, $value); } + + protected function setImproperFraction(array $matches, Cell $cell): bool + { + // Convert value to number + $value = $matches[2] + ($matches[3] / $matches[4]); + if ($matches[1] === '-') { + $value = 0 - $value; + } + $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC); + + // Build the number format mask based on the size of the matched values + $dividend = str_repeat('?', strlen($matches[3])); + $divisor = str_repeat('?', strlen($matches[4])); + $fractionMask = "# {$dividend}/{$divisor}"; + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode($fractionMask); + + return true; + } + + protected function setProperFraction(array $matches, Cell $cell): bool + { + // Convert value to number + $value = $matches[2] / $matches[3]; + if ($matches[1] === '-') { + $value = 0 - $value; + } + $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC); + + // Build the number format mask based on the size of the matched values + $dividend = str_repeat('?', strlen($matches[2])); + $divisor = str_repeat('?', strlen($matches[3])); + $fractionMask = "{$dividend}/{$divisor}"; + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode($fractionMask); + + return true; + } + + protected function setPercentage(string $value, Cell $cell): bool + { + // Convert value to number + $value = ((float) str_replace('%', '', $value)) / 100; + $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); + + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_PERCENTAGE_00); + + return true; + } + + protected function setTimeHoursMinutes(string $value, Cell $cell): bool + { + // Convert value to number + [$hours, $minutes] = explode(':', $value); + $days = ($hours / 24) + ($minutes / 1440); + $cell->setValueExplicit($days, DataType::TYPE_NUMERIC); + + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME3); + + return true; + } + + protected function setTimeHoursMinutesSeconds(string $value, Cell $cell): bool + { + // Convert value to number + [$hours, $minutes, $seconds] = explode(':', $value); + $days = ($hours / 24) + ($minutes / 1440) + ($seconds / 86400); + $cell->setValueExplicit($days, DataType::TYPE_NUMERIC); + + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME4); + + return true; + } } diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index 5dee411b..89aa32cd 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -78,6 +78,7 @@ class Cell public function detach(): void { + // @phpstan-ignore-next-line $this->parent = null; } @@ -201,7 +202,7 @@ class Cell break; case DataType::TYPE_STRING2: $pDataType = DataType::TYPE_STRING; - // no break + // no break case DataType::TYPE_STRING: // Synonym for string case DataType::TYPE_INLINE: @@ -252,9 +253,11 @@ class Cell if ($this->dataType == DataType::TYPE_FORMULA) { try { $index = $this->getWorksheet()->getParent()->getActiveSheetIndex(); + $selected = $this->getWorksheet()->getSelectedCells(); $result = Calculation::getInstance( $this->getWorksheet()->getParent() )->calculateCellValue($this, $resetLog); + $this->getWorksheet()->setSelectedCells($selected); $this->getWorksheet()->getParent()->setActiveSheetIndex($index); // We don't yet handle array returns if (is_array($result)) { @@ -561,7 +564,7 @@ class Cell // Verify if cell is in range return ($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) && - ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow); + ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow); } /** diff --git a/src/PhpSpreadsheet/Cell/Coordinate.php b/src/PhpSpreadsheet/Cell/Coordinate.php index 2afeebe9..0b3917f2 100644 --- a/src/PhpSpreadsheet/Cell/Coordinate.php +++ b/src/PhpSpreadsheet/Cell/Coordinate.php @@ -25,7 +25,7 @@ abstract class Coordinate * * @param string $pCoordinateString eg: 'A1' * - * @return string[] Array containing column and row (indexes 0 and 1) + * @return array{0: string, 1: string} Array containing column and row (indexes 0 and 1) */ public static function coordinateFromString($pCoordinateString) { @@ -40,6 +40,23 @@ abstract class Coordinate throw new Exception('Invalid cell coordinate ' . $pCoordinateString); } + /** + * Get indexes from a string coordinates. + * + * @param string $coordinates eg: 'A1', '$B$12' + * + * @return array{0: int, 1: int} Array containing column index and row index (indexes 0 and 1) + */ + public static function indexesFromString(string $coordinates): array + { + [$col, $row] = self::coordinateFromString($coordinates); + + return [ + self::columnIndexFromString(ltrim($col, '$')), + (int) ltrim($row, '$'), + ]; + } + /** * Checks if a coordinate represents a range of cells. * @@ -339,7 +356,8 @@ abstract class Coordinate private static function processRangeSetOperators(array $operators, array $cells): array { - for ($offset = 0; $offset < count($operators); ++$offset) { + $operatorCount = count($operators); + for ($offset = 0; $offset < $operatorCount; ++$offset) { $operator = $operators[$offset]; if ($operator !== ' ') { continue; @@ -350,6 +368,7 @@ abstract class Coordinate $operators = array_values($operators); $cells = array_values($cells); --$offset; + --$operatorCount; } return $cells; diff --git a/src/PhpSpreadsheet/Cell/DefaultValueBinder.php b/src/PhpSpreadsheet/Cell/DefaultValueBinder.php index 693446e6..6fae5e76 100644 --- a/src/PhpSpreadsheet/Cell/DefaultValueBinder.php +++ b/src/PhpSpreadsheet/Cell/DefaultValueBinder.php @@ -40,39 +40,39 @@ class DefaultValueBinder implements IValueBinder /** * DataType for value. * - * @param mixed $pValue + * @param mixed $value * * @return string */ - public static function dataTypeForValue($pValue) + public static function dataTypeForValue($value) { // Match the value against a few data types - if ($pValue === null) { + if ($value === null) { return DataType::TYPE_NULL; - } elseif (is_float($pValue) || is_int($pValue)) { + } elseif (is_float($value) || is_int($value)) { return DataType::TYPE_NUMERIC; - } elseif (is_bool($pValue)) { + } elseif (is_bool($value)) { return DataType::TYPE_BOOL; - } elseif ($pValue === '') { + } elseif ($value === '') { return DataType::TYPE_STRING; - } elseif ($pValue instanceof RichText) { + } elseif ($value instanceof RichText) { return DataType::TYPE_INLINE; - } elseif (is_string($pValue) && $pValue[0] === '=' && strlen($pValue) > 1) { + } elseif (is_string($value) && $value[0] === '=' && strlen($value) > 1) { return DataType::TYPE_FORMULA; - } elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $pValue)) { - $tValue = ltrim($pValue, '+-'); - if (is_string($pValue) && $tValue[0] === '0' && strlen($tValue) > 1 && $tValue[1] !== '.') { + } elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) { + $tValue = ltrim($value, '+-'); + if (is_string($value) && $tValue[0] === '0' && strlen($tValue) > 1 && $tValue[1] !== '.') { return DataType::TYPE_STRING; - } elseif ((strpos($pValue, '.') === false) && ($pValue > PHP_INT_MAX)) { + } elseif ((strpos($value, '.') === false) && ($value > PHP_INT_MAX)) { return DataType::TYPE_STRING; - } elseif (!is_numeric($pValue)) { + } elseif (!is_numeric($value)) { return DataType::TYPE_STRING; } return DataType::TYPE_NUMERIC; - } elseif (is_string($pValue)) { + } elseif (is_string($value)) { $errorCodes = DataType::getErrorCodes(); - if (isset($errorCodes[$pValue])) { + if (isset($errorCodes[$value])) { return DataType::TYPE_ERROR; } } diff --git a/src/PhpSpreadsheet/Chart/Axis.php b/src/PhpSpreadsheet/Chart/Axis.php index 7995c3b3..27e61060 100644 --- a/src/PhpSpreadsheet/Chart/Axis.php +++ b/src/PhpSpreadsheet/Chart/Axis.php @@ -13,7 +13,7 @@ class Axis extends Properties /** * Axis Number. * - * @var array of mixed + * @var mixed[] */ private $axisNumber = [ 'format' => self::FORMAT_CODE_GENERAL, @@ -23,7 +23,7 @@ class Axis extends Properties /** * Axis Options. * - * @var array of mixed + * @var mixed[] */ private $axisOptions = [ 'minimum' => null, @@ -41,7 +41,7 @@ class Axis extends Properties /** * Fill Properties. * - * @var array of mixed + * @var mixed[] */ private $fillProperties = [ 'type' => self::EXCEL_COLOR_TYPE_ARGB, @@ -52,7 +52,7 @@ class Axis extends Properties /** * Line Properties. * - * @var array of mixed + * @var mixed[] */ private $lineProperties = [ 'type' => self::EXCEL_COLOR_TYPE_ARGB, @@ -63,7 +63,7 @@ class Axis extends Properties /** * Line Style Properties. * - * @var array of mixed + * @var mixed[] */ private $lineStyleProperties = [ 'width' => '9525', @@ -86,7 +86,7 @@ class Axis extends Properties /** * Shadow Properties. * - * @var array of mixed + * @var mixed[] */ private $shadowProperties = [ 'presets' => self::SHADOW_PRESETS_NOSHADOW, @@ -111,7 +111,7 @@ class Axis extends Properties /** * Glow Properties. * - * @var array of mixed + * @var mixed[] */ private $glowProperties = [ 'size' => null, @@ -125,7 +125,7 @@ class Axis extends Properties /** * Soft Edge Properties. * - * @var array of mixed + * @var mixed[] */ private $softEdges = [ 'size' => null, @@ -135,10 +135,8 @@ class Axis extends Properties * Get Series Data Type. * * @param mixed $format_code - * - * @return string */ - public function setAxisNumberProperties($format_code) + public function setAxisNumberProperties($format_code): void { $this->axisNumber['format'] = (string) $format_code; $this->axisNumber['source_linked'] = 0; @@ -340,9 +338,9 @@ class Axis extends Properties { $this->setShadowPresetsProperties((int) $sh_presets) ->setShadowColor( - $sh_color_value === null ? $this->shadowProperties['color']['value'] : $sh_color_value, - $sh_color_alpha === null ? (int) $this->shadowProperties['color']['alpha'] : $sh_color_alpha, - $sh_color_type === null ? $this->shadowProperties['color']['type'] : $sh_color_type + $sh_color_value ?? $this->shadowProperties['color']['value'], + $sh_color_alpha ?? (int) $this->shadowProperties['color']['alpha'], + $sh_color_type ?? $this->shadowProperties['color']['type'] ) ->setShadowBlur($sh_blur) ->setShadowAngle($sh_angle) @@ -367,7 +365,7 @@ class Axis extends Properties /** * Set Shadow Properties from Mapped Values. * - * @param mixed &$reference + * @param mixed $reference * * @return $this */ @@ -482,9 +480,9 @@ class Axis extends Properties { $this->setGlowSize($size) ->setGlowColor( - $color_value === null ? $this->glowProperties['color']['value'] : $color_value, - $color_alpha === null ? (int) $this->glowProperties['color']['alpha'] : $color_alpha, - $color_type === null ? $this->glowProperties['color']['type'] : $color_type + $color_value ?? $this->glowProperties['color']['value'], + $color_alpha ?? (int) $this->glowProperties['color']['alpha'], + $color_type ?? $this->glowProperties['color']['type'] ); } diff --git a/src/PhpSpreadsheet/Chart/Chart.php b/src/PhpSpreadsheet/Chart/Chart.php index 20eb2aee..4fdff6ff 100644 --- a/src/PhpSpreadsheet/Chart/Chart.php +++ b/src/PhpSpreadsheet/Chart/Chart.php @@ -424,7 +424,7 @@ class Chart /** * Get the top left position of the chart. * - * @return array an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell + * @return array{cell: string, xOffset: int, yOffset: int} an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell */ public function getTopLeftPosition() { diff --git a/src/PhpSpreadsheet/Chart/DataSeries.php b/src/PhpSpreadsheet/Chart/DataSeries.php index 3a44b335..067d30e5 100644 --- a/src/PhpSpreadsheet/Chart/DataSeries.php +++ b/src/PhpSpreadsheet/Chart/DataSeries.php @@ -75,21 +75,21 @@ class DataSeries /** * Order of plots in Series. * - * @var array of integer + * @var int[] */ private $plotOrder = []; /** * Plot Label. * - * @var array of DataSeriesValues + * @var DataSeriesValues[] */ private $plotLabel = []; /** * Plot Category. * - * @var array of DataSeriesValues + * @var DataSeriesValues[] */ private $plotCategory = []; @@ -103,7 +103,7 @@ class DataSeries /** * Plot Values. * - * @var array of DataSeriesValues + * @var DataSeriesValues[] */ private $plotValues = []; @@ -231,7 +231,7 @@ class DataSeries /** * Get Plot Labels. * - * @return array of DataSeriesValues + * @return DataSeriesValues[] */ public function getPlotLabels() { @@ -243,7 +243,7 @@ class DataSeries * * @param mixed $index * - * @return DataSeriesValues + * @return DataSeriesValues|false */ public function getPlotLabelByIndex($index) { @@ -260,7 +260,7 @@ class DataSeries /** * Get Plot Categories. * - * @return array of DataSeriesValues + * @return DataSeriesValues[] */ public function getPlotCategories() { @@ -272,7 +272,7 @@ class DataSeries * * @param mixed $index * - * @return DataSeriesValues + * @return DataSeriesValues|false */ public function getPlotCategoryByIndex($index) { @@ -313,7 +313,7 @@ class DataSeries /** * Get Plot Values. * - * @return array of DataSeriesValues + * @return DataSeriesValues[] */ public function getPlotValues() { @@ -325,7 +325,7 @@ class DataSeries * * @param mixed $index * - * @return DataSeriesValues + * @return DataSeriesValues|false */ public function getPlotValuesByIndex($index) { diff --git a/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/src/PhpSpreadsheet/Chart/DataSeriesValues.php index c1bd973a..88063336 100644 --- a/src/PhpSpreadsheet/Chart/DataSeriesValues.php +++ b/src/PhpSpreadsheet/Chart/DataSeriesValues.php @@ -55,7 +55,7 @@ class DataSeriesValues /** * Data Values. * - * @var array of mixed + * @var mixed[] */ private $dataValues = []; @@ -313,7 +313,7 @@ class DataSeriesValues /** * Get Series Data Values. * - * @return array of mixed + * @return mixed[] */ public function getDataValues() { diff --git a/src/PhpSpreadsheet/Chart/GridLines.php b/src/PhpSpreadsheet/Chart/GridLines.php index 2e424bc2..c388f2c9 100644 --- a/src/PhpSpreadsheet/Chart/GridLines.php +++ b/src/PhpSpreadsheet/Chart/GridLines.php @@ -291,9 +291,9 @@ class GridLines extends Properties $this->activateObject() ->setShadowPresetsProperties((int) $sh_presets) ->setShadowColor( - $sh_color_value === null ? $this->shadowProperties['color']['value'] : $sh_color_value, + $sh_color_value ?? $this->shadowProperties['color']['value'], $sh_color_alpha === null ? (int) $this->shadowProperties['color']['alpha'] : $this->getTrueAlpha($sh_color_alpha), - $sh_color_type === null ? $this->shadowProperties['color']['type'] : $sh_color_type + $sh_color_type ?? $this->shadowProperties['color']['type'] ) ->setShadowBlur($sh_blur) ->setShadowAngle($sh_angle) @@ -318,7 +318,7 @@ class GridLines extends Properties /** * Set Shadow Properties Values. * - * @param mixed &$reference + * @param mixed $reference * * @return $this */ diff --git a/src/PhpSpreadsheet/Chart/Legend.php b/src/PhpSpreadsheet/Chart/Legend.php index fc0ed140..2f003cd8 100644 --- a/src/PhpSpreadsheet/Chart/Legend.php +++ b/src/PhpSpreadsheet/Chart/Legend.php @@ -131,18 +131,10 @@ class Legend * Set allow overlay of other elements? * * @param bool $overlay - * - * @return bool */ - public function setOverlay($overlay) + public function setOverlay($overlay): void { - if (!is_bool($overlay)) { - return false; - } - $this->overlay = $overlay; - - return true; } /** diff --git a/src/PhpSpreadsheet/Chart/PlotArea.php b/src/PhpSpreadsheet/Chart/PlotArea.php index 954777cf..ecb7b6c9 100644 --- a/src/PhpSpreadsheet/Chart/PlotArea.php +++ b/src/PhpSpreadsheet/Chart/PlotArea.php @@ -43,10 +43,8 @@ class PlotArea /** * Get Number of Plot Groups. - * - * @return array of DataSeries */ - public function getPlotGroupCount() + public function getPlotGroupCount(): int { return count($this->plotSeries); } @@ -69,7 +67,7 @@ class PlotArea /** * Get Plot Series. * - * @return array of DataSeries + * @return DataSeries[] */ public function getPlotGroup() { diff --git a/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php b/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php index 02fbfed7..0ab70870 100644 --- a/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php +++ b/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php @@ -301,6 +301,8 @@ class JpGraph implements IRenderer $seriesPlots = []; if ($grouping == 'percentStacked') { $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); + } else { + $sumValues = []; } // Loop through each data series in turn @@ -376,6 +378,8 @@ class JpGraph implements IRenderer $seriesPlots = []; if ($grouping == 'percentStacked') { $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); + } else { + $sumValues = []; } // Loop through each data series in turn diff --git a/src/PhpSpreadsheet/Collection/Cells.php b/src/PhpSpreadsheet/Collection/Cells.php index 48f34f41..c5247090 100644 --- a/src/PhpSpreadsheet/Collection/Cells.php +++ b/src/PhpSpreadsheet/Collection/Cells.php @@ -12,28 +12,28 @@ use Psr\SimpleCache\CacheInterface; class Cells { /** - * @var \Psr\SimpleCache\CacheInterface + * @var CacheInterface */ private $cache; /** * Parent worksheet. * - * @var Worksheet + * @var null|Worksheet */ private $parent; /** * The currently active Cell. * - * @var Cell + * @var null|Cell */ private $currentCell; /** * Coordinate of the currently active Cell. * - * @var string + * @var null|string */ private $currentCoordinate; @@ -405,7 +405,7 @@ class Cells * @param string $pCoord Coordinate of the cell to update * @param Cell $cell Cell to update * - * @return \PhpOffice\PhpSpreadsheet\Cell\Cell + * @return Cell */ public function add($pCoord, Cell $cell) { @@ -426,7 +426,7 @@ class Cells * * @param string $pCoord Coordinate of the cell * - * @return null|\PhpOffice\PhpSpreadsheet\Cell\Cell Cell that was found, or null if not found + * @return null|Cell Cell that was found, or null if not found */ public function get($pCoord) { diff --git a/src/PhpSpreadsheet/Document/Properties.php b/src/PhpSpreadsheet/Document/Properties.php index 0876a9ed..c099bccc 100644 --- a/src/PhpSpreadsheet/Document/Properties.php +++ b/src/PhpSpreadsheet/Document/Properties.php @@ -5,12 +5,20 @@ namespace PhpOffice\PhpSpreadsheet\Document; class Properties { /** constants */ - const PROPERTY_TYPE_BOOLEAN = 'b'; - const PROPERTY_TYPE_INTEGER = 'i'; - const PROPERTY_TYPE_FLOAT = 'f'; - const PROPERTY_TYPE_DATE = 'd'; - const PROPERTY_TYPE_STRING = 's'; - const PROPERTY_TYPE_UNKNOWN = 'u'; + public const PROPERTY_TYPE_BOOLEAN = 'b'; + public const PROPERTY_TYPE_INTEGER = 'i'; + public const PROPERTY_TYPE_FLOAT = 'f'; + public const PROPERTY_TYPE_DATE = 'd'; + public const PROPERTY_TYPE_STRING = 's'; + public const PROPERTY_TYPE_UNKNOWN = 'u'; + + private const VALID_PROPERTY_TYPE_LIST = [ + self::PROPERTY_TYPE_BOOLEAN, + self::PROPERTY_TYPE_INTEGER, + self::PROPERTY_TYPE_FLOAT, + self::PROPERTY_TYPE_DATE, + self::PROPERTY_TYPE_STRING, + ]; /** * Creator. @@ -92,7 +100,7 @@ class Properties /** * Custom Properties. * - * @var string + * @var array{value: mixed, type: string}[] */ private $customProperties = []; @@ -109,10 +117,8 @@ class Properties /** * Get Creator. - * - * @return string */ - public function getCreator() + public function getCreator(): string { return $this->creator; } @@ -120,11 +126,9 @@ class Properties /** * Set Creator. * - * @param string $creator - * * @return $this */ - public function setCreator($creator) + public function setCreator(string $creator): self { $this->creator = $creator; @@ -133,10 +137,8 @@ class Properties /** * Get Last Modified By. - * - * @return string */ - public function getLastModifiedBy() + public function getLastModifiedBy(): string { return $this->lastModifiedBy; } @@ -144,23 +146,19 @@ class Properties /** * Set Last Modified By. * - * @param string $pValue - * * @return $this */ - public function setLastModifiedBy($pValue) + public function setLastModifiedBy(string $modifier): self { - $this->lastModifiedBy = $pValue; + $this->lastModifiedBy = $modifier; return $this; } /** * Get Created. - * - * @return int */ - public function getCreated() + public function getCreated(): int { return $this->created; } @@ -168,33 +166,31 @@ class Properties /** * Set Created. * - * @param int|string $time + * @param null|int|string $timestamp * * @return $this */ - public function setCreated($time) + public function setCreated($timestamp): self { - if ($time === null) { - $time = time(); - } elseif (is_string($time)) { - if (is_numeric($time)) { - $time = (int) $time; + if ($timestamp === null) { + $timestamp = time(); + } elseif (is_string($timestamp)) { + if (is_numeric($timestamp)) { + $timestamp = (int) $timestamp; } else { - $time = strtotime($time); + $timestamp = strtotime($timestamp); } } - $this->created = $time; + $this->created = $timestamp; return $this; } /** * Get Modified. - * - * @return int */ - public function getModified() + public function getModified(): int { return $this->modified; } @@ -202,33 +198,31 @@ class Properties /** * Set Modified. * - * @param int|string $time + * @param null|int|string $timestamp * * @return $this */ - public function setModified($time) + public function setModified($timestamp): self { - if ($time === null) { - $time = time(); - } elseif (is_string($time)) { - if (is_numeric($time)) { - $time = (int) $time; + if ($timestamp === null) { + $timestamp = time(); + } elseif (is_string($timestamp)) { + if (is_numeric($timestamp)) { + $timestamp = (int) $timestamp; } else { - $time = strtotime($time); + $timestamp = strtotime($timestamp); } } - $this->modified = $time; + $this->modified = $timestamp; return $this; } /** * Get Title. - * - * @return string */ - public function getTitle() + public function getTitle(): string { return $this->title; } @@ -236,11 +230,9 @@ class Properties /** * Set Title. * - * @param string $title - * * @return $this */ - public function setTitle($title) + public function setTitle(string $title): self { $this->title = $title; @@ -249,10 +241,8 @@ class Properties /** * Get Description. - * - * @return string */ - public function getDescription() + public function getDescription(): string { return $this->description; } @@ -260,11 +250,9 @@ class Properties /** * Set Description. * - * @param string $description - * * @return $this */ - public function setDescription($description) + public function setDescription(string $description): self { $this->description = $description; @@ -273,10 +261,8 @@ class Properties /** * Get Subject. - * - * @return string */ - public function getSubject() + public function getSubject(): string { return $this->subject; } @@ -284,11 +270,9 @@ class Properties /** * Set Subject. * - * @param string $subject - * * @return $this */ - public function setSubject($subject) + public function setSubject(string $subject): self { $this->subject = $subject; @@ -297,10 +281,8 @@ class Properties /** * Get Keywords. - * - * @return string */ - public function getKeywords() + public function getKeywords(): string { return $this->keywords; } @@ -308,11 +290,9 @@ class Properties /** * Set Keywords. * - * @param string $keywords - * * @return $this */ - public function setKeywords($keywords) + public function setKeywords(string $keywords): self { $this->keywords = $keywords; @@ -321,10 +301,8 @@ class Properties /** * Get Category. - * - * @return string */ - public function getCategory() + public function getCategory(): string { return $this->category; } @@ -332,11 +310,9 @@ class Properties /** * Set Category. * - * @param string $category - * * @return $this */ - public function setCategory($category) + public function setCategory(string $category): self { $this->category = $category; @@ -345,10 +321,8 @@ class Properties /** * Get Company. - * - * @return string */ - public function getCompany() + public function getCompany(): string { return $this->company; } @@ -356,11 +330,9 @@ class Properties /** * Set Company. * - * @param string $company - * * @return $this */ - public function setCompany($company) + public function setCompany(string $company): self { $this->company = $company; @@ -369,10 +341,8 @@ class Properties /** * Get Manager. - * - * @return string */ - public function getManager() + public function getManager(): string { return $this->manager; } @@ -380,11 +350,9 @@ class Properties /** * Set Manager. * - * @param string $manager - * * @return $this */ - public function setManager($manager) + public function setManager(string $manager): self { $this->manager = $manager; @@ -394,33 +362,27 @@ class Properties /** * Get a List of Custom Property Names. * - * @return array of string + * @return string[] */ - public function getCustomProperties() + public function getCustomProperties(): array { return array_keys($this->customProperties); } /** * Check if a Custom Property is defined. - * - * @param string $propertyName - * - * @return bool */ - public function isCustomPropertySet($propertyName) + public function isCustomPropertySet(string $propertyName): bool { - return isset($this->customProperties[$propertyName]); + return array_key_exists($propertyName, $this->customProperties); } /** * Get a Custom Property Value. * - * @param string $propertyName - * * @return mixed */ - public function getCustomPropertyValue($propertyName) + public function getCustomPropertyValue(string $propertyName) { if (isset($this->customProperties[$propertyName])) { return $this->customProperties[$propertyName]['value']; @@ -430,24 +392,34 @@ class Properties /** * Get a Custom Property Type. * - * @param string $propertyName - * - * @return string + * @return null|string */ - public function getCustomPropertyType($propertyName) + public function getCustomPropertyType(string $propertyName) { - if (isset($this->customProperties[$propertyName])) { - return $this->customProperties[$propertyName]['type']; + return $this->customProperties[$propertyName]['type'] ?? null; + } + + private function identifyPropertyType($propertyValue) + { + if ($propertyValue === null) { + return self::PROPERTY_TYPE_STRING; + } elseif (is_float($propertyValue)) { + return self::PROPERTY_TYPE_FLOAT; + } elseif (is_int($propertyValue)) { + return self::PROPERTY_TYPE_INTEGER; + } elseif (is_bool($propertyValue)) { + return self::PROPERTY_TYPE_BOOLEAN; } + + return self::PROPERTY_TYPE_STRING; } /** * Set a Custom Property. * - * @param string $propertyName * @param mixed $propertyValue * @param string $propertyType - * 'i' : Integer + * 'i' : Integer * 'f' : Floating Point * 's' : String * 'd' : Date/Time @@ -455,27 +427,10 @@ class Properties * * @return $this */ - public function setCustomProperty($propertyName, $propertyValue = '', $propertyType = null) + public function setCustomProperty(string $propertyName, $propertyValue = '', $propertyType = null): self { - if ( - ($propertyType === null) || (!in_array($propertyType, [self::PROPERTY_TYPE_INTEGER, - self::PROPERTY_TYPE_FLOAT, - self::PROPERTY_TYPE_STRING, - self::PROPERTY_TYPE_DATE, - self::PROPERTY_TYPE_BOOLEAN, - ])) - ) { - if ($propertyValue === null) { - $propertyType = self::PROPERTY_TYPE_STRING; - } elseif (is_float($propertyValue)) { - $propertyType = self::PROPERTY_TYPE_FLOAT; - } elseif (is_int($propertyValue)) { - $propertyType = self::PROPERTY_TYPE_INTEGER; - } elseif (is_bool($propertyValue)) { - $propertyType = self::PROPERTY_TYPE_BOOLEAN; - } else { - $propertyType = self::PROPERTY_TYPE_STRING; - } + if (($propertyType === null) || (!in_array($propertyType, self::VALID_PROPERTY_TYPE_LIST))) { + $propertyType = $this->identifyPropertyType($propertyValue); } $this->customProperties[$propertyName] = [ @@ -501,54 +456,38 @@ class Properties } } - public static function convertProperty($propertyValue, $propertyType) + public static function convertProperty($propertyValue, string $propertyType) { switch ($propertyType) { case 'empty': // Empty return ''; - - break; case 'null': // Null return null; - - break; case 'i1': // 1-Byte Signed Integer case 'i2': // 2-Byte Signed Integer case 'i4': // 4-Byte Signed Integer case 'i8': // 8-Byte Signed Integer case 'int': // Integer return (int) $propertyValue; - - break; case 'ui1': // 1-Byte Unsigned Integer case 'ui2': // 2-Byte Unsigned Integer case 'ui4': // 4-Byte Unsigned Integer case 'ui8': // 8-Byte Unsigned Integer case 'uint': // Unsigned Integer return abs((int) $propertyValue); - - break; case 'r4': // 4-Byte Real Number case 'r8': // 8-Byte Real Number case 'decimal': // Decimal return (float) $propertyValue; - - break; case 'lpstr': // LPSTR case 'lpwstr': // LPWSTR case 'bstr': // Basic String return $propertyValue; - - break; case 'date': // Date and Time case 'filetime': // File Time return strtotime($propertyValue); - - break; case 'bool': // Boolean return $propertyValue == 'true'; - - break; case 'cy': // Currency case 'error': // Error Status Code case 'vector': // Vector @@ -563,14 +502,12 @@ class Properties case 'clsid': // Class ID case 'cf': // Clipboard Data return $propertyValue; - - break; } return $propertyValue; } - public static function convertPropertyType($propertyType) + public static function convertPropertyType(string $propertyType): string { switch ($propertyType) { case 'i1': // 1-Byte Signed Integer @@ -584,31 +521,21 @@ class Properties case 'ui8': // 8-Byte Unsigned Integer case 'uint': // Unsigned Integer return self::PROPERTY_TYPE_INTEGER; - - break; case 'r4': // 4-Byte Real Number case 'r8': // 8-Byte Real Number case 'decimal': // Decimal return self::PROPERTY_TYPE_FLOAT; - - break; case 'empty': // Empty case 'null': // Null case 'lpstr': // LPSTR case 'lpwstr': // LPWSTR case 'bstr': // Basic String return self::PROPERTY_TYPE_STRING; - - break; case 'date': // Date and Time case 'filetime': // File Time return self::PROPERTY_TYPE_DATE; - - break; case 'bool': // Boolean return self::PROPERTY_TYPE_BOOLEAN; - - break; case 'cy': // Currency case 'error': // Error Status Code case 'vector': // Vector @@ -623,8 +550,6 @@ class Properties case 'clsid': // Class ID case 'cf': // Clipboard Data return self::PROPERTY_TYPE_UNKNOWN; - - break; } return self::PROPERTY_TYPE_UNKNOWN; diff --git a/src/PhpSpreadsheet/HashTable.php b/src/PhpSpreadsheet/HashTable.php index 90ea806b..5d4444e7 100644 --- a/src/PhpSpreadsheet/HashTable.php +++ b/src/PhpSpreadsheet/HashTable.php @@ -2,12 +2,15 @@ namespace PhpOffice\PhpSpreadsheet; +/** + * @template T of IComparable + */ class HashTable { /** * HashTable elements. * - * @var IComparable[] + * @var T[] */ protected $items = []; @@ -21,7 +24,7 @@ class HashTable /** * Create a new \PhpOffice\PhpSpreadsheet\HashTable. * - * @param IComparable[] $pSource Optional source array to create HashTable from + * @param T[] $pSource Optional source array to create HashTable from */ public function __construct($pSource = null) { @@ -34,7 +37,7 @@ class HashTable /** * Add HashTable items from source. * - * @param IComparable[] $pSource Source array to create HashTable from + * @param T[] $pSource Source array to create HashTable from */ public function addFromSource(?array $pSource = null): void { @@ -51,7 +54,7 @@ class HashTable /** * Add HashTable item. * - * @param IComparable $pSource Item to add + * @param T $pSource Item to add */ public function add(IComparable $pSource): void { @@ -65,7 +68,7 @@ class HashTable /** * Remove HashTable item. * - * @param IComparable $pSource Item to remove + * @param T $pSource Item to remove */ public function remove(IComparable $pSource): void { @@ -123,7 +126,7 @@ class HashTable * * @param int $pIndex * - * @return IComparable + * @return null|T */ public function getByIndex($pIndex) { @@ -139,7 +142,7 @@ class HashTable * * @param string $pHashCode * - * @return IComparable + * @return null|T */ public function getByHashCode($pHashCode) { @@ -153,7 +156,7 @@ class HashTable /** * HashTable to array. * - * @return IComparable[] + * @return T[] */ public function toArray() { diff --git a/src/PhpSpreadsheet/Helper/Html.php b/src/PhpSpreadsheet/Helper/Html.php index 6c4cbf9b..f07bc961 100644 --- a/src/PhpSpreadsheet/Helper/Html.php +++ b/src/PhpSpreadsheet/Helper/Html.php @@ -711,7 +711,7 @@ class Html } elseif (strpos(trim($attributeValue), '#') === 0) { $this->$attributeName = ltrim($attributeValue, '#'); } else { - $this->$attributeName = $this->colourNameLookup($attributeValue); + $this->$attributeName = static::colourNameLookup($attributeValue); } } else { $this->$attributeName = $attributeValue; diff --git a/src/PhpSpreadsheet/Helper/Sample.php b/src/PhpSpreadsheet/Helper/Sample.php index a91b195e..c84c3930 100644 --- a/src/PhpSpreadsheet/Helper/Sample.php +++ b/src/PhpSpreadsheet/Helper/Sample.php @@ -71,7 +71,7 @@ class Sample /** * Returns an array of all known samples. * - * @return string[] [$name => $path] + * @return string[][] [$name => $path] */ public function getSamples() { @@ -132,6 +132,11 @@ class Sample $this->logEndingNotes(); } + protected function isDirOrMkdir(string $folder): bool + { + return \is_dir($folder) || \mkdir($folder); + } + /** * Returns the temporary directory and make sure it exists. * @@ -140,10 +145,8 @@ class Sample private function getTemporaryFolder() { $tempFolder = sys_get_temp_dir() . '/phpspreadsheet'; - if (!is_dir($tempFolder)) { - if (!mkdir($tempFolder) && !is_dir($tempFolder)) { - throw new RuntimeException(sprintf('Directory "%s" was not created', $tempFolder)); - } + if (!$this->isDirOrMkdir($tempFolder)) { + throw new RuntimeException(sprintf('Directory "%s" was not created', $tempFolder)); } return $tempFolder; diff --git a/src/PhpSpreadsheet/IOFactory.php b/src/PhpSpreadsheet/IOFactory.php index ab04e969..06006edc 100644 --- a/src/PhpSpreadsheet/IOFactory.php +++ b/src/PhpSpreadsheet/IOFactory.php @@ -120,7 +120,7 @@ abstract class IOFactory $reader = self::createReader($guessedReader); // Let's see if we are lucky - if (isset($reader) && $reader->canRead($filename)) { + if ($reader->canRead($filename)) { return $reader; } } diff --git a/src/PhpSpreadsheet/NamedFormula.php b/src/PhpSpreadsheet/NamedFormula.php index eeddbbcb..500151f0 100644 --- a/src/PhpSpreadsheet/NamedFormula.php +++ b/src/PhpSpreadsheet/NamedFormula.php @@ -17,7 +17,7 @@ class NamedFormula extends DefinedName ?Worksheet $scope = null ) { // Validate data - if (empty($formula)) { + if (!isset($formula)) { throw new Exception('You must specify a Formula value for a Named Formula'); } parent::__construct($name, $worksheet, $formula, $localOnly, $scope); diff --git a/src/PhpSpreadsheet/Reader/BaseReader.php b/src/PhpSpreadsheet/Reader/BaseReader.php index eb0e3ba2..80348132 100644 --- a/src/PhpSpreadsheet/Reader/BaseReader.php +++ b/src/PhpSpreadsheet/Reader/BaseReader.php @@ -38,7 +38,7 @@ abstract class BaseReader implements IReader * Restrict which sheets should be loaded? * This property holds an array of worksheet names to be loaded. If null, then all worksheets will be loaded. * - * @var array of string + * @var null|string[] */ protected $loadSheetsOnly; diff --git a/src/PhpSpreadsheet/Reader/Csv.php b/src/PhpSpreadsheet/Reader/Csv.php index 1495d102..dc746735 100644 --- a/src/PhpSpreadsheet/Reader/Csv.php +++ b/src/PhpSpreadsheet/Reader/Csv.php @@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader; use InvalidArgumentException; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Reader\Csv\Delimiter; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -138,118 +139,26 @@ class Csv extends BaseReader return; } - $potentialDelimiters = [',', ';', "\t", '|', ':', ' ', '~']; - $counts = []; - foreach ($potentialDelimiters as $delimiter) { - $counts[$delimiter] = []; - } - - // Count how many times each of the potential delimiters appears in each line - $numberLines = 0; - while (($line = $this->getNextLine()) !== false && (++$numberLines < 1000)) { - $countLine = []; - for ($i = strlen($line) - 1; $i >= 0; --$i) { - $char = $line[$i]; - if (isset($counts[$char])) { - if (!isset($countLine[$char])) { - $countLine[$char] = 0; - } - ++$countLine[$char]; - } - } - foreach ($potentialDelimiters as $delimiter) { - $counts[$delimiter][] = $countLine[$delimiter] - ?? 0; - } - } + $inferenceEngine = new Delimiter($this->fileHandle, $this->escapeCharacter, $this->enclosure); // If number of lines is 0, nothing to infer : fall back to the default - if ($numberLines === 0) { - $this->delimiter = reset($potentialDelimiters); + if ($inferenceEngine->linesCounted() === 0) { + $this->delimiter = $inferenceEngine->getDefaultDelimiter(); $this->skipBOM(); return; } - // Calculate the mean square deviations for each delimiter (ignoring delimiters that haven't been found consistently) - $meanSquareDeviations = []; - $middleIdx = floor(($numberLines - 1) / 2); - - foreach ($potentialDelimiters as $delimiter) { - $series = $counts[$delimiter]; - sort($series); - - $median = ($numberLines % 2) - ? $series[$middleIdx] - : ($series[$middleIdx] + $series[$middleIdx + 1]) / 2; - - if ($median === 0) { - continue; - } - - $meanSquareDeviations[$delimiter] = array_reduce( - $series, - function ($sum, $value) use ($median) { - return $sum + ($value - $median) ** 2; - } - ) / count($series); - } - - // ... and pick the delimiter with the smallest mean square deviation (in case of ties, the order in potentialDelimiters is respected) - $min = INF; - foreach ($potentialDelimiters as $delimiter) { - if (!isset($meanSquareDeviations[$delimiter])) { - continue; - } - - if ($meanSquareDeviations[$delimiter] < $min) { - $min = $meanSquareDeviations[$delimiter]; - $this->delimiter = $delimiter; - } - } + $this->delimiter = $inferenceEngine->infer(); // If no delimiter could be detected, fall back to the default if ($this->delimiter === null) { - $this->delimiter = reset($potentialDelimiters); + $this->delimiter = $inferenceEngine->getDefaultDelimiter(); } $this->skipBOM(); } - /** - * Get the next full line from the file. - * - * @return false|string - */ - private function getNextLine() - { - $line = ''; - $enclosure = ($this->escapeCharacter === '' ? '' - : ('(?escapeCharacter, '/') . ')')) - . preg_quote($this->enclosure, '/'); - - do { - // Get the next line in the file - $newLine = fgets($this->fileHandle); - - // Return false if there is no next line - if ($newLine === false) { - return false; - } - - // Add the new line to the line passed in - $line = $line . $newLine; - - // Drop everything that is enclosed to avoid counting false positives in enclosures - $line = preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line); - - // See if we have any enclosures left in the line - // if we still have an enclosure then we need to read the next line as well - } while (preg_match('/(' . $enclosure . ')/', $line) > 0); - - return $line; - } - /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * @@ -334,7 +243,7 @@ class Csv extends BaseReader public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet) { $lineEnding = ini_get('auto_detect_line_endings'); - ini_set('auto_detect_line_endings', true); + ini_set('auto_detect_line_endings', '1'); // Open file $this->openFileOrMemory($pFilename); @@ -528,7 +437,8 @@ class Csv extends BaseReader fclose($this->fileHandle); // Trust file extension if any - $extension = strtolower(pathinfo($pFilename, PATHINFO_EXTENSION)); + $extension = pathinfo($pFilename, PATHINFO_EXTENSION); + $extension = is_array($extension) ? '' : strtolower($extension); if (in_array($extension, ['csv', 'tsv'])) { return true; } diff --git a/src/PhpSpreadsheet/Reader/Csv/Delimiter.php b/src/PhpSpreadsheet/Reader/Csv/Delimiter.php new file mode 100644 index 00000000..eb62c9ac --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Csv/Delimiter.php @@ -0,0 +1,144 @@ +fileHandle = $fileHandle; + $this->escapeCharacter = $escapeCharacter; + $this->enclosure = $enclosure; + + $this->countPotentialDelimiters(); + } + + public function getDefaultDelimiter(): string + { + return self::POTENTIAL_DELIMETERS[0]; + } + + public function linesCounted(): int + { + return $this->numberLines; + } + + protected function countPotentialDelimiters(): void + { + $this->counts = array_fill_keys(self::POTENTIAL_DELIMETERS, []); + $delimiterKeys = array_flip(self::POTENTIAL_DELIMETERS); + + // Count how many times each of the potential delimiters appears in each line + $this->numberLines = 0; + while (($line = $this->getNextLine()) !== false && (++$this->numberLines < 1000)) { + $this->countDelimiterValues($line, $delimiterKeys); + } + } + + protected function countDelimiterValues(string $line, array $delimiterKeys): void + { + $splitString = str_split($line, 1); + if (!is_array($splitString)) { + return; + } + + $distribution = array_count_values($splitString); + $countLine = array_intersect_key($distribution, $delimiterKeys); + + foreach (self::POTENTIAL_DELIMETERS as $delimiter) { + $this->counts[$delimiter][] = $countLine[$delimiter] ?? 0; + } + } + + public function infer(): ?string + { + // Calculate the mean square deviations for each delimiter + // (ignoring delimiters that haven't been found consistently) + $meanSquareDeviations = []; + $middleIdx = floor(($this->numberLines - 1) / 2); + + foreach (self::POTENTIAL_DELIMETERS as $delimiter) { + $series = $this->counts[$delimiter]; + sort($series); + + $median = ($this->numberLines % 2) + ? $series[$middleIdx] + : ($series[$middleIdx] + $series[$middleIdx + 1]) / 2; + + if ($median === 0) { + continue; + } + + $meanSquareDeviations[$delimiter] = array_reduce( + $series, + function ($sum, $value) use ($median) { + return $sum + ($value - $median) ** 2; + } + ) / count($series); + } + + // ... and pick the delimiter with the smallest mean square deviation + // (in case of ties, the order in potentialDelimiters is respected) + $min = INF; + foreach (self::POTENTIAL_DELIMETERS as $delimiter) { + if (!isset($meanSquareDeviations[$delimiter])) { + continue; + } + + if ($meanSquareDeviations[$delimiter] < $min) { + $min = $meanSquareDeviations[$delimiter]; + $this->delimiter = $delimiter; + } + } + + return $this->delimiter; + } + + /** + * Get the next full line from the file. + * + * @return false|string + */ + public function getNextLine() + { + $line = ''; + $enclosure = ($this->escapeCharacter === '' ? '' + : ('(?escapeCharacter, '/') . ')')) + . preg_quote($this->enclosure, '/'); + + do { + // Get the next line in the file + $newLine = fgets($this->fileHandle); + + // Return false if there is no next line + if ($newLine === false) { + return false; + } + + // Add the new line to the line passed in + $line = $line . $newLine; + + // Drop everything that is enclosed to avoid counting false positives in enclosures + $line = preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line); + + // See if we have any enclosures left in the line + // if we still have an enclosure then we need to read the next line as well + } while (preg_match('/(' . $enclosure . ')/', $line) > 0); + + return $line; + } +} diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php index dc921c1e..d3cdf1b0 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric.php @@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\PageSetup; +use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\Properties; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\RichText\RichText; @@ -262,148 +263,6 @@ class Gnumeric extends BaseReader return self::$mappings; } - private function docPropertiesOld(SimpleXMLElement $gnmXML): void - { - $docProps = $this->spreadsheet->getProperties(); - foreach ($gnmXML->Summary->Item as $summaryItem) { - $propertyName = $summaryItem->name; - $propertyValue = $summaryItem->{'val-string'}; - switch ($propertyName) { - case 'title': - $docProps->setTitle(trim($propertyValue)); - - break; - case 'comments': - $docProps->setDescription(trim($propertyValue)); - - break; - case 'keywords': - $docProps->setKeywords(trim($propertyValue)); - - break; - case 'category': - $docProps->setCategory(trim($propertyValue)); - - break; - case 'manager': - $docProps->setManager(trim($propertyValue)); - - break; - case 'author': - $docProps->setCreator(trim($propertyValue)); - $docProps->setLastModifiedBy(trim($propertyValue)); - - break; - case 'company': - $docProps->setCompany(trim($propertyValue)); - - break; - } - } - } - - private function docPropertiesDC(SimpleXMLElement $officePropertyDC): void - { - $docProps = $this->spreadsheet->getProperties(); - foreach ($officePropertyDC as $propertyName => $propertyValue) { - $propertyValue = trim((string) $propertyValue); - switch ($propertyName) { - case 'title': - $docProps->setTitle($propertyValue); - - break; - case 'subject': - $docProps->setSubject($propertyValue); - - break; - case 'creator': - $docProps->setCreator($propertyValue); - $docProps->setLastModifiedBy($propertyValue); - - break; - case 'date': - $creationDate = strtotime($propertyValue); - $docProps->setCreated($creationDate); - $docProps->setModified($creationDate); - - break; - case 'description': - $docProps->setDescription($propertyValue); - - break; - } - } - } - - private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta, array $namespacesMeta): void - { - $docProps = $this->spreadsheet->getProperties(); - foreach ($officePropertyMeta as $propertyName => $propertyValue) { - $attributes = $propertyValue->attributes($namespacesMeta['meta']); - $propertyValue = trim((string) $propertyValue); - switch ($propertyName) { - case 'keyword': - $docProps->setKeywords($propertyValue); - - break; - case 'initial-creator': - $docProps->setCreator($propertyValue); - $docProps->setLastModifiedBy($propertyValue); - - break; - case 'creation-date': - $creationDate = strtotime($propertyValue); - $docProps->setCreated($creationDate); - $docProps->setModified($creationDate); - - break; - case 'user-defined': - [, $attrName] = explode(':', $attributes['name']); - switch ($attrName) { - case 'publisher': - $docProps->setCompany($propertyValue); - - break; - case 'category': - $docProps->setCategory($propertyValue); - - break; - case 'manager': - $docProps->setManager($propertyValue); - - break; - } - - break; - } - } - } - - private function docProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML, array $namespacesMeta): void - { - if (isset($namespacesMeta['office'])) { - $officeXML = $xml->children($namespacesMeta['office']); - $officeDocXML = $officeXML->{'document-meta'}; - $officeDocMetaXML = $officeDocXML->meta; - - foreach ($officeDocMetaXML as $officePropertyData) { - $officePropertyDC = []; - if (isset($namespacesMeta['dc'])) { - $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']); - } - $this->docPropertiesDC($officePropertyDC); - - $officePropertyMeta = []; - if (isset($namespacesMeta['meta'])) { - $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']); - } - $this->docPropertiesMeta($officePropertyMeta, $namespacesMeta); - } - } elseif (isset($gnmXML->Summary)) { - $this->docPropertiesOld($gnmXML); - } - } - private function processComments(SimpleXMLElement $sheet): void { if ((!$this->readDataOnly) && (isset($sheet->Objects))) { @@ -450,7 +309,7 @@ class Gnumeric extends BaseReader $this->gnm = array_key_exists('gmr', $namespacesMeta) ? 'gmr' : 'gnm'; $gnmXML = $xml->children($namespacesMeta[$this->gnm]); - $this->docProperties($xml, $gnmXML, $namespacesMeta); + (new Properties($this->spreadsheet))->readProperties($xml, $gnmXML, $namespacesMeta); $worksheetID = 0; foreach ($gnmXML->Sheets->Sheet as $sheet) { @@ -657,7 +516,7 @@ class Gnumeric extends BaseReader $column = $columnAttributes['No']; $columnWidth = ((float) $columnAttributes['Unit']) / 5.4; $hidden = (isset($columnAttributes['Hidden'])) && ((string) $columnAttributes['Hidden'] == '1'); - $columnCount = (isset($columnAttributes['Count'])) ? $columnAttributes['Count'] : 1; + $columnCount = (int) ($columnAttributes['Count'] ?? 1); while ($c < $column) { $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth); ++$c; @@ -696,7 +555,7 @@ class Gnumeric extends BaseReader $row = $rowAttributes['No']; $rowHeight = (float) $rowAttributes['Unit']; $hidden = (isset($rowAttributes['Hidden'])) && ((string) $rowAttributes['Hidden'] == '1'); - $rowCount = (isset($rowAttributes['Count'])) ? $rowAttributes['Count'] : 1; + $rowCount = (int) ($rowAttributes['Count'] ?? 1); while ($r < $row) { ++$r; $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight); diff --git a/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php b/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php new file mode 100644 index 00000000..16d9c2e0 --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php @@ -0,0 +1,167 @@ +spreadsheet = $spreadsheet; + } + + private function docPropertiesOld(SimpleXMLElement $gnmXML): void + { + $docProps = $this->spreadsheet->getProperties(); + foreach ($gnmXML->Summary->Item as $summaryItem) { + $propertyName = $summaryItem->name; + $propertyValue = $summaryItem->{'val-string'}; + switch ($propertyName) { + case 'title': + $docProps->setTitle(trim($propertyValue)); + + break; + case 'comments': + $docProps->setDescription(trim($propertyValue)); + + break; + case 'keywords': + $docProps->setKeywords(trim($propertyValue)); + + break; + case 'category': + $docProps->setCategory(trim($propertyValue)); + + break; + case 'manager': + $docProps->setManager(trim($propertyValue)); + + break; + case 'author': + $docProps->setCreator(trim($propertyValue)); + $docProps->setLastModifiedBy(trim($propertyValue)); + + break; + case 'company': + $docProps->setCompany(trim($propertyValue)); + + break; + } + } + } + + private function docPropertiesDC(SimpleXMLElement $officePropertyDC): void + { + $docProps = $this->spreadsheet->getProperties(); + foreach ($officePropertyDC as $propertyName => $propertyValue) { + $propertyValue = trim((string) $propertyValue); + switch ($propertyName) { + case 'title': + $docProps->setTitle($propertyValue); + + break; + case 'subject': + $docProps->setSubject($propertyValue); + + break; + case 'creator': + $docProps->setCreator($propertyValue); + $docProps->setLastModifiedBy($propertyValue); + + break; + case 'date': + $creationDate = strtotime($propertyValue); + $creationDate = $creationDate === false ? time() : $creationDate; + $docProps->setCreated($creationDate); + $docProps->setModified($creationDate); + + break; + case 'description': + $docProps->setDescription($propertyValue); + + break; + } + } + } + + private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta, array $namespacesMeta): void + { + $docProps = $this->spreadsheet->getProperties(); + foreach ($officePropertyMeta as $propertyName => $propertyValue) { + if ($propertyValue === null) { + continue; + } + + $attributes = $propertyValue->attributes($namespacesMeta['meta']); + $propertyValue = trim((string) $propertyValue); + switch ($propertyName) { + case 'keyword': + $docProps->setKeywords($propertyValue); + + break; + case 'initial-creator': + $docProps->setCreator($propertyValue); + $docProps->setLastModifiedBy($propertyValue); + + break; + case 'creation-date': + $creationDate = strtotime($propertyValue); + $creationDate = $creationDate === false ? time() : $creationDate; + $docProps->setCreated($creationDate); + $docProps->setModified($creationDate); + + break; + case 'user-defined': + [, $attrName] = explode(':', $attributes['name']); + switch ($attrName) { + case 'publisher': + $docProps->setCompany($propertyValue); + + break; + case 'category': + $docProps->setCategory($propertyValue); + + break; + case 'manager': + $docProps->setManager($propertyValue); + + break; + } + + break; + } + } + } + + public function readProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML, array $namespacesMeta): void + { + if (isset($namespacesMeta['office'])) { + $officeXML = $xml->children($namespacesMeta['office']); + $officeDocXML = $officeXML->{'document-meta'}; + $officeDocMetaXML = $officeDocXML->meta; + + foreach ($officeDocMetaXML as $officePropertyData) { + $officePropertyDC = []; + if (isset($namespacesMeta['dc'])) { + $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']); + } + $this->docPropertiesDC($officePropertyDC); + + $officePropertyMeta = []; + if (isset($namespacesMeta['meta'])) { + $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']); + } + $this->docPropertiesMeta($officePropertyMeta, $namespacesMeta); + } + } elseif (isset($gnmXML->Summary)) { + $this->docPropertiesOld($gnmXML); + } + } +} diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php index 73f4591e..f235f9b1 100644 --- a/src/PhpSpreadsheet/Reader/Html.php +++ b/src/PhpSpreadsheet/Reader/Html.php @@ -320,7 +320,7 @@ class Html extends BaseReader { if ($child->nodeName === 'title') { $this->processDomElement($child, $sheet, $row, $column, $cellContent); - $sheet->setTitle($cellContent, true, false); + $sheet->setTitle($cellContent, true, true); $cellContent = ''; } else { $this->processDomElementSpanEtc($sheet, $row, $column, $cellContent, $child, $attributeArray); @@ -650,7 +650,7 @@ class Html extends BaseReader $loaded = false; } if ($loaded === false) { - throw new Exception('Failed to load ' . $pFilename . ' as a DOM Document'); + throw new Exception('Failed to load ' . $pFilename . ' as a DOM Document', 0, $e ?? null); } return $this->loadDocument($dom, $spreadsheet); @@ -672,7 +672,7 @@ class Html extends BaseReader $loaded = false; } if ($loaded === false) { - throw new Exception('Failed to load content as a DOM Document'); + throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null); } return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet()); @@ -910,8 +910,6 @@ class Html extends BaseReader /** * Check if has #, so we can get clean hex. * - * @param $value - * * @return null|string */ public function getStyleColor($value) diff --git a/src/PhpSpreadsheet/Reader/Ods.php b/src/PhpSpreadsheet/Reader/Ods.php index 4ceac653..1a4d7ca3 100644 --- a/src/PhpSpreadsheet/Reader/Ods.php +++ b/src/PhpSpreadsheet/Reader/Ods.php @@ -23,6 +23,7 @@ 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; @@ -379,9 +380,8 @@ class Ods extends BaseReader } $columnID = 'A'; - foreach ($childNode->childNodes as $key => $cellData) { - // @var \DOMElement $cellData - + /** @var DOMElement $cellData */ + foreach ($childNode->childNodes as $cellData) { if ($this->getReadFilter() !== null) { if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) { ++$columnID; @@ -646,10 +646,83 @@ class Ods extends BaseReader $this->readDefinedExpressions($spreadsheet, $workbookData, $tableNs); } $spreadsheet->setActiveSheetIndex(0); + + if ($zip->locateName('settings.xml') !== false) { + $this->processSettings($zip, $spreadsheet); + } // Return return $spreadsheet; } + private function processSettings(ZipArchive $zip, Spreadsheet $spreadsheet): void + { + $dom = new DOMDocument('1.01', 'UTF-8'); + $dom->loadXML( + $this->securityScanner->scan($zip->getFromName('settings.xml')), + Settings::getLibXmlLoaderOptions() + ); + //$xlinkNs = $dom->lookupNamespaceUri('xlink'); + $configNs = $dom->lookupNamespaceUri('config'); + //$oooNs = $dom->lookupNamespaceUri('ooo'); + $officeNs = $dom->lookupNamespaceUri('office'); + $settings = $dom->getElementsByTagNameNS($officeNs, 'settings') + ->item(0); + $this->lookForActiveSheet($settings, $spreadsheet, $configNs); + $this->lookForSelectedCells($settings, $spreadsheet, $configNs); + } + + private function lookForActiveSheet(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void + { + /** @var DOMElement $t */ + foreach ($settings->getElementsByTagNameNS($configNs, 'config-item') as $t) { + if ($t->getAttributeNs($configNs, 'name') === 'ActiveTable') { + try { + $spreadsheet->setActiveSheetIndexByName($t->nodeValue); + } catch (Throwable $e) { + // do nothing + } + + break; + } + } + } + + private function lookForSelectedCells(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void + { + /** @var DOMElement $t */ + foreach ($settings->getElementsByTagNameNS($configNs, 'config-item-map-named') as $t) { + if ($t->getAttributeNs($configNs, 'name') === 'Tables') { + foreach ($t->getElementsByTagNameNS($configNs, 'config-item-map-entry') as $ws) { + $setRow = $setCol = ''; + $wsname = $ws->getAttributeNs($configNs, 'name'); + foreach ($ws->getElementsByTagNameNS($configNs, 'config-item') as $configItem) { + $attrName = $configItem->getAttributeNs($configNs, 'name'); + if ($attrName === 'CursorPositionX') { + $setCol = $configItem->nodeValue; + } + if ($attrName === 'CursorPositionY') { + $setRow = $configItem->nodeValue; + } + } + $this->setSelected($spreadsheet, $wsname, $setCol, $setRow); + } + + break; + } + } + } + + private function setSelected(Spreadsheet $spreadsheet, string $wsname, string $setCol, string $setRow): void + { + if (is_numeric($setCol) && is_numeric($setRow)) { + try { + $spreadsheet->getSheetByName($wsname)->setSelectedCellByColumnAndRow($setCol + 1, $setRow + 1); + } catch (Throwable $e) { + // do nothing + } + } + } + /** * Recursively scan element. * diff --git a/src/PhpSpreadsheet/Reader/Ods/PageSettings.php b/src/PhpSpreadsheet/Reader/Ods/PageSettings.php index 77341aab..8d24fd0c 100644 --- a/src/PhpSpreadsheet/Reader/Ods/PageSettings.php +++ b/src/PhpSpreadsheet/Reader/Ods/PageSettings.php @@ -54,10 +54,10 @@ class PageSettings $marginBottom = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-bottom'); $header = $styleSet->getElementsByTagNameNS($this->stylesNs, 'header-style')[0]; $headerProperties = $header->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0]; - $marginHeader = $headerProperties->getAttributeNS($this->stylesFo, 'min-height'); + $marginHeader = isset($headerProperties) ? $headerProperties->getAttributeNS($this->stylesFo, 'min-height') : null; $footer = $styleSet->getElementsByTagNameNS($this->stylesNs, 'footer-style')[0]; $footerProperties = $footer->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0]; - $marginFooter = $footerProperties->getAttributeNS($this->stylesFo, 'min-height'); + $marginFooter = isset($footerProperties) ? $footerProperties->getAttributeNS($this->stylesFo, 'min-height') : null; $this->pageLayoutStyles[$styleName] = (object) [ 'orientation' => $styleOrientation ?: PageSetup::ORIENTATION_DEFAULT, diff --git a/src/PhpSpreadsheet/Reader/Slk.php b/src/PhpSpreadsheet/Reader/Slk.php index e58ff2f6..c7b6fc82 100644 --- a/src/PhpSpreadsheet/Reader/Slk.php +++ b/src/PhpSpreadsheet/Reader/Slk.php @@ -169,7 +169,7 @@ class Slk extends BaseReader foreach ($rowData as $rowDatum) { switch ($rowDatum[0]) { case 'X': - $columnIndex = substr($rowDatum, 1) - 1; + $columnIndex = (int) substr($rowDatum, 1) - 1; break; case 'Y': @@ -251,7 +251,7 @@ class Slk extends BaseReader } // Bracketed R references are relative to the current row if ($rowReference[0] == '[') { - $rowReference = $row + trim($rowReference, '[]'); + $rowReference = (int) $row + (int) trim($rowReference, '[]'); } $columnReference = $cellReference[4][0]; // Empty C reference is the current column @@ -260,7 +260,7 @@ class Slk extends BaseReader } // Bracketed C references are relative to the current column if ($columnReference[0] == '[') { - $columnReference = $column + trim($columnReference, '[]'); + $columnReference = (int) $column + (int) trim($columnReference, '[]'); } $A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference; @@ -419,14 +419,14 @@ class Slk extends BaseReader if ($columnWidth > '') { if ($startCol == $endCol) { $startCol = Coordinate::stringFromColumnIndex((int) $startCol); - $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth($columnWidth); + $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth((float) $columnWidth); } else { $startCol = Coordinate::stringFromColumnIndex($startCol); $endCol = Coordinate::stringFromColumnIndex($endCol); $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth((float) $columnWidth); do { - $spreadsheet->getActiveSheet()->getColumnDimension(++$startCol)->setWidth($columnWidth); - } while ($startCol != $endCol); + $spreadsheet->getActiveSheet()->getColumnDimension(++$startCol)->setWidth((float) $columnWidth); + } while ($startCol !== $endCol); } } } diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index 023806d6..c389105b 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -224,7 +224,7 @@ class Xls extends BaseReader /** * Shared fonts. * - * @var array + * @var Font[] */ private $objFonts; @@ -374,7 +374,7 @@ class Xls extends BaseReader * * @var int */ - private $encryptionStartPos = false; + private $encryptionStartPos = 0; /** * The current RC4 decryption object. @@ -659,7 +659,7 @@ class Xls extends BaseReader $this->definedname = []; $this->sst = []; $this->drawingGroupData = ''; - $this->xfIndex = ''; + $this->xfIndex = 0; $this->mapCellXfIndex = []; $this->mapCellStyleXfIndex = []; @@ -801,9 +801,10 @@ class Xls extends BaseReader } // treat MSODRAWINGGROUP records, workbook-level Escher + $escherWorkbook = null; if (!$this->readDataOnly && $this->drawingGroupData) { - $escherWorkbook = new Escher(); - $reader = new Xls\Escher($escherWorkbook); + $escher = new Escher(); + $reader = new Xls\Escher($escher); $escherWorkbook = $reader->load($this->drawingGroupData); } @@ -1133,38 +1134,40 @@ class Xls extends BaseReader continue 2; } - $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection(); - $BSE = $BSECollection[$BSEindex - 1]; - $blipType = $BSE->getBlipType(); + if ($escherWorkbook) { + $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection(); + $BSE = $BSECollection[$BSEindex - 1]; + $blipType = $BSE->getBlipType(); - // need check because some blip types are not supported by Escher reader such as EMF - if ($blip = $BSE->getBlip()) { - $ih = imagecreatefromstring($blip->getData()); - $drawing = new MemoryDrawing(); - $drawing->setImageResource($ih); + // need check because some blip types are not supported by Escher reader such as EMF + if ($blip = $BSE->getBlip()) { + $ih = imagecreatefromstring($blip->getData()); + $drawing = new MemoryDrawing(); + $drawing->setImageResource($ih); - // width, height, offsetX, offsetY - $drawing->setResizeProportional(false); - $drawing->setWidth($width); - $drawing->setHeight($height); - $drawing->setOffsetX($offsetX); - $drawing->setOffsetY($offsetY); + // width, height, offsetX, offsetY + $drawing->setResizeProportional(false); + $drawing->setWidth($width); + $drawing->setHeight($height); + $drawing->setOffsetX($offsetX); + $drawing->setOffsetY($offsetY); - switch ($blipType) { - case BSE::BLIPTYPE_JPEG: - $drawing->setRenderingFunction(MemoryDrawing::RENDERING_JPEG); - $drawing->setMimeType(MemoryDrawing::MIMETYPE_JPEG); + switch ($blipType) { + case BSE::BLIPTYPE_JPEG: + $drawing->setRenderingFunction(MemoryDrawing::RENDERING_JPEG); + $drawing->setMimeType(MemoryDrawing::MIMETYPE_JPEG); - break; - case BSE::BLIPTYPE_PNG: - $drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG); - $drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG); + break; + case BSE::BLIPTYPE_PNG: + $drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG); + $drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG); - break; + break; + } + + $drawing->setWorksheet($this->phpSheet); + $drawing->setCoordinates($spContainer->getStartCoordinates()); } - - $drawing->setWorksheet($this->phpSheet); - $drawing->setCoordinates($spContainer->getStartCoordinates()); } break; @@ -1290,10 +1293,10 @@ class Xls extends BaseReader } } // Named Value - // TODO Provide support for named values + // TODO Provide support for named values } } - $this->data = null; + $this->data = ''; return $this->spreadsheet; } @@ -1703,7 +1706,8 @@ class Xls extends BaseReader // max 2048 bytes will probably throw a wobbly. $row = self::getUInt2d($recordData, 0); $extension = true; - $cellAddress = array_pop(array_keys($this->phpSheet->getComments())); + $arrayKeys = array_keys($this->phpSheet->getComments()); + $cellAddress = array_pop($arrayKeys); } $cellAddress = str_replace('$', '', $cellAddress); @@ -2286,8 +2290,8 @@ class Xls extends BaseReader $rotation = $angle; } elseif ($angle <= 180) { $rotation = 90 - $angle; - } elseif ($angle == 255) { - $rotation = -165; + } elseif ($angle == Alignment::TEXTROTATION_STACK_EXCEL) { + $rotation = Alignment::TEXTROTATION_STACK_PHPSPREADSHEET; } $objStyle->getAlignment()->setTextRotation($rotation); @@ -2389,7 +2393,7 @@ class Xls extends BaseReader break; case 1: - $objStyle->getAlignment()->setTextRotation(-165); + $objStyle->getAlignment()->setTextRotation(Alignment::TEXTROTATION_STACK_PHPSPREADSHEET); break; case 2: @@ -2741,6 +2745,7 @@ class Xls extends BaseReader $sheetType = ord($recordData[5]); // offset: 6; size: var; sheet name + $rec_name = null; if ($this->version == self::XLS_BIFF8) { $string = self::readUnicodeStringShort(substr($recordData, 6)); $rec_name = $string['value']; @@ -3017,12 +3022,14 @@ class Xls extends BaseReader // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text $hasRichText = (($optionFlags & 0x08) != 0); + $formattingRuns = 0; if ($hasRichText) { // number of Rich-Text formatting runs $formattingRuns = self::getUInt2d($recordData, $pos); $pos += 2; } + $extendedRunLength = 0; if ($hasAsian) { // size of Asian phonetic setting $extendedRunLength = self::getInt4d($recordData, $pos); @@ -3033,6 +3040,7 @@ class Xls extends BaseReader $len = ($isCompressed) ? $numChars : $numChars * 2; // look up limit position - Check it again to be sure that no error occurs when parsing SST structure + $limitpos = null; foreach ($spliceOffsets as $spliceOffset) { // it can happen that the string is empty, therefore we need // <= and not just < @@ -3097,7 +3105,7 @@ class Xls extends BaseReader $len = min($charsLeft, $limitpos - $pos); for ($j = 0; $j < $len; ++$j) { $retstr .= $recordData[$pos + $j] - . chr(0); + . chr(0); } $charsLeft -= $len; $isCompressed = false; @@ -4384,6 +4392,8 @@ class Xls extends BaseReader // offset: 4; size: 2; index to first visible colum $firstVisibleColumn = self::getUInt2d($recordData, 4); + $zoomscaleInPageBreakPreview = 0; + $zoomscaleInNormalView = 0; if ($this->version === self::XLS_BIFF8) { // offset: 8; size: 2; not used // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%) @@ -7181,6 +7191,7 @@ class Xls extends BaseReader { [$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell); $baseCol = Coordinate::columnIndexFromString($baseCol) - 1; + $baseRow = (int) $baseRow; // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767)) $rowIndex = self::getUInt2d($cellAddressStructure, 0); @@ -7358,8 +7369,8 @@ class Xls extends BaseReader */ private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1') { - [$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell); - $baseCol = Coordinate::columnIndexFromString($baseCol) - 1; + [$baseCol, $baseRow] = Coordinate::indexesFromString($baseCell); + $baseCol = $baseCol - 1; // TODO: if cell range is just a single cell, should this funciton // not just return e.g. 'A1' and not 'A1:A1' ? @@ -7635,6 +7646,8 @@ class Xls extends BaseReader $size = 9; break; + default: + throw new PhpSpreadsheetException('Unsupported BIFF8 constant'); } return [ diff --git a/src/PhpSpreadsheet/Reader/Xls/Color.php b/src/PhpSpreadsheet/Reader/Xls/Color.php index c45f88c7..06c2d0b9 100644 --- a/src/PhpSpreadsheet/Reader/Xls/Color.php +++ b/src/PhpSpreadsheet/Reader/Xls/Color.php @@ -20,7 +20,7 @@ class Color if ($color <= 0x07 || $color >= 0x40) { // special built-in color return Color\BuiltIn::lookup($color); - } elseif (isset($palette, $palette[$color - 8])) { + } elseif (isset($palette[$color - 8])) { // palette color, color index 0x08 maps to pallete index 0 return $palette[$color - 8]; } diff --git a/src/PhpSpreadsheet/Reader/Xls/MD5.php b/src/PhpSpreadsheet/Reader/Xls/MD5.php index c0417ba6..3e15f641 100644 --- a/src/PhpSpreadsheet/Reader/Xls/MD5.php +++ b/src/PhpSpreadsheet/Reader/Xls/MD5.php @@ -5,12 +5,25 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls; class MD5 { // Context + + /** + * @var int + */ private $a; + /** + * @var int + */ private $b; + /** + * @var int + */ private $c; + /** + * @var int + */ private $d; /** @@ -56,7 +69,7 @@ class MD5 * * @param string $data Data to add */ - public function add($data): void + public function add(string $data): void { $words = array_values(unpack('V16', $data)); @@ -148,34 +161,34 @@ class MD5 $this->d = ($this->d + $D) & 0xffffffff; } - private static function f($X, $Y, $Z) + private static function f(int $X, int $Y, int $Z) { return ($X & $Y) | ((~$X) & $Z); // X AND Y OR NOT X AND Z } - private static function g($X, $Y, $Z) + private static function g(int $X, int $Y, int $Z) { return ($X & $Z) | ($Y & (~$Z)); // X AND Z OR Y AND NOT Z } - private static function h($X, $Y, $Z) + private static function h(int $X, int $Y, int $Z) { return $X ^ $Y ^ $Z; // X XOR Y XOR Z } - private static function i($X, $Y, $Z) + private static function i(int $X, int $Y, int $Z) { return $Y ^ ($X | (~$Z)); // Y XOR (X OR NOT Z) } - private static function step($func, &$A, $B, $C, $D, $M, $s, $t): void + private static function step($func, int &$A, int $B, int $C, int $D, int $M, int $s, int $t): void { $A = ($A + call_user_func($func, $B, $C, $D) + $M + $t) & 0xffffffff; $A = self::rotate($A, $s); $A = ($B + $A) & 0xffffffff; } - private static function rotate($decimal, $bits) + private static function rotate(int $decimal, int $bits) { $binary = str_pad(decbin($decimal), 32, '0', STR_PAD_LEFT); diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 0bc29c61..f734c99f 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\Hyperlink; use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; @@ -271,11 +272,11 @@ class Xlsx extends BaseReader if (!isset($sharedFormulas[(string) $c->f['si']])) { $sharedFormulas[$instance] = ['master' => $r, 'formula' => $value]; } else { - $master = Coordinate::coordinateFromString($sharedFormulas[$instance]['master']); - $current = Coordinate::coordinateFromString($r); + $master = Coordinate::indexesFromString($sharedFormulas[$instance]['master']); + $current = Coordinate::indexesFromString($r); $difference = [0, 0]; - $difference[0] = Coordinate::columnIndexFromString($current[0]) - Coordinate::columnIndexFromString($master[0]); + $difference[0] = $current[0] - $master[0]; $difference[1] = $current[1] - $master[1]; $value = $this->referenceHelper->updateFormulaReferences($sharedFormulas[$instance]['formula'], 'A1', $difference[0], $difference[1]); @@ -429,7 +430,7 @@ class Xlsx extends BaseReader 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() ); - if (isset($xmlStrings, $xmlStrings->si)) { + if (isset($xmlStrings->si)) { foreach ($xmlStrings->si as $val) { if (isset($val->t)) { $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t); @@ -488,7 +489,7 @@ class Xlsx extends BaseReader } if (!$this->readDataOnly && $xmlStyles) { foreach ($xmlStyles->cellXfs->xf as $xf) { - $numFmt = NumberFormat::FORMAT_GENERAL; + $numFmt = null; if ($xf['numFmtId']) { if (isset($numFmts)) { @@ -503,19 +504,17 @@ class Xlsx extends BaseReader // But there's a lot of naughty homebrew xlsx writers that do use "reserved" id values that aren't actually used // So we make allowance for them rather than lose formatting masks if ( + $numFmt === null && (int) $xf['numFmtId'] < 164 && NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== '' ) { $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']); } } - $quotePrefix = false; - if (isset($xf['quotePrefix'])) { - $quotePrefix = (bool) $xf['quotePrefix']; - } + $quotePrefix = (bool) ($xf['quotePrefix'] ?? false); $style = (object) [ - 'numFmt' => $numFmt, + 'numFmt' => $numFmt ?? NumberFormat::FORMAT_GENERAL, 'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])], 'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])], 'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])], @@ -531,7 +530,7 @@ class Xlsx extends BaseReader $excel->addCellXf($objStyle); } - foreach (isset($xmlStyles->cellStyleXfs->xf) ? $xmlStyles->cellStyleXfs->xf : [] as $xf) { + foreach ($xmlStyles->cellStyleXfs->xf ?? [] as $xf) { $numFmt = NumberFormat::FORMAT_GENERAL; if ($numFmts && $xf['numFmtId']) { $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]")); @@ -542,6 +541,8 @@ class Xlsx extends BaseReader } } + $quotePrefix = (bool) ($xf['quotePrefix'] ?? false); + $cellStyle = (object) [ 'numFmt' => $numFmt, 'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])], @@ -720,6 +721,10 @@ class Xlsx extends BaseReader } else { // Formula $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToString'); + if (isset($c->f['t'])) { + $attributes = $c->f['t']; + $docSheet->getCell($r)->setFormulaAttributes(['t' => (string) $attributes]); + } } break; @@ -735,6 +740,10 @@ class Xlsx extends BaseReader $cell = $docSheet->getCell($r); // Assign value if ($cellDataType != '') { + // it is possible, that datatype is numeric but with an empty string, which result in an error + if ($cellDataType === DataType::TYPE_NUMERIC && $value === '') { + $cellDataType = DataType::TYPE_STRING; + } $cell->setValueExplicit($value, $cellDataType); } else { $cell->setValue($value); @@ -870,10 +879,11 @@ class Xlsx extends BaseReader // Loop through contents foreach ($commentsFile->commentList->comment as $comment) { + $commentModel = $docSheet->getComment((string) $comment['ref']); if (!empty($comment['authorId'])) { - $docSheet->getComment((string) $comment['ref'])->setAuthor($authors[(string) $comment['authorId']]); + $commentModel->setAuthor($authors[$comment['authorId']]); } - $docSheet->getComment((string) $comment['ref'])->setText($this->parseRichText($comment->text)); + $commentModel->setText($this->parseRichText($comment->text)); } } @@ -1071,6 +1081,7 @@ class Xlsx extends BaseReader } if ($xmlSheet->drawing && !$this->readDataOnly) { $unparsedDrawings = []; + $fileDrawing = null; foreach ($xmlSheet->drawing as $drawing) { $drawingRelId = (string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id'); $fileDrawing = $drawings[$drawingRelId]; @@ -1123,15 +1134,20 @@ class Xlsx extends BaseReader $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); $objDrawing->setName((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')); $objDrawing->setDescription((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr')); - $objDrawing->setPath( - 'zip://' . File::realpath($pFilename) . '#' . - $images[(string) self::getArrayItem( - $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), - 'embed' - )], - false + $imageKey = (string) self::getArrayItem( + $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), + 'embed' ); - $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); + + if (isset($images[$imageKey])) { + $objDrawing->setPath( + 'zip://' . File::realpath($pFilename) . '#' . + $images[$imageKey], + false + ); + } + $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); + $objDrawing->setOffsetX(Drawing::EMUToPixels($oneCellAnchor->from->colOff)); $objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff)); $objDrawing->setResizeProportional(false); @@ -1147,7 +1163,7 @@ class Xlsx extends BaseReader $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist'))); $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir'))); $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn')); - $clr = isset($outerShdw->srgbClr) ? $outerShdw->srgbClr : $outerShdw->prstClr; + $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val')); $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000); } @@ -1155,13 +1171,27 @@ class Xlsx extends BaseReader $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks); $objDrawing->setWorksheet($docSheet); - } else { - // ? Can charts be positioned with a oneCellAnchor ? - $coordinates = Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1); + } elseif ($this->includeCharts && $oneCellAnchor->graphicFrame) { + // Exported XLSX from Google Sheets positions charts with a oneCellAnchor + $coordinates = Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1); $offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff); $offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff); $width = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx')); $height = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy')); + + $graphic = $oneCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic; + /** @var SimpleXMLElement $chartRef */ + $chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart; + $thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + + $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ + 'fromCoordinate' => $coordinates, + 'fromOffsetX' => $offsetX, + 'fromOffsetY' => $offsetY, + 'width' => $width, + 'height' => $height, + 'worksheetTitle' => $docSheet->getTitle(), + ]; } } } @@ -1175,15 +1205,19 @@ class Xlsx extends BaseReader $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); $objDrawing->setName((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')); $objDrawing->setDescription((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr')); - $objDrawing->setPath( - 'zip://' . File::realpath($pFilename) . '#' . - $images[(string) self::getArrayItem( - $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), - 'embed' - )], - false + $imageKey = (string) self::getArrayItem( + $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), + 'embed' ); - $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1)); + if (isset($images[$imageKey])) { + $objDrawing->setPath( + 'zip://' . File::realpath($pFilename) . '#' . + $images[$imageKey], + false + ); + } + $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1)); + $objDrawing->setOffsetX(Drawing::EMUToPixels($twoCellAnchor->from->colOff)); $objDrawing->setOffsetY(Drawing::EMUToPixels($twoCellAnchor->from->rowOff)); $objDrawing->setResizeProportional(false); @@ -1200,7 +1234,7 @@ class Xlsx extends BaseReader $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist'))); $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir'))); $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn')); - $clr = isset($outerShdw->srgbClr) ? $outerShdw->srgbClr : $outerShdw->prstClr; + $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val')); $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000); } @@ -1209,10 +1243,10 @@ class Xlsx extends BaseReader $objDrawing->setWorksheet($docSheet); } elseif (($this->includeCharts) && ($twoCellAnchor->graphicFrame)) { - $fromCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1); + $fromCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1); $fromOffsetX = Drawing::EMUToPixels($twoCellAnchor->from->colOff); $fromOffsetY = Drawing::EMUToPixels($twoCellAnchor->from->rowOff); - $toCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1); + $toCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1); $toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff); $toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff); $graphic = $twoCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic; @@ -1508,7 +1542,10 @@ class Xlsx extends BaseReader $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart); $objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet'])); $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']); - $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']); + if (array_key_exists('toCoordinate', $chartDetails[$chartPositionRef])) { + // For oneCellAnchor positioned charts, toCoordinate is not in the data. Does it need to be calculated? + $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']); + } } } } @@ -1701,7 +1738,7 @@ class Xlsx extends BaseReader * * @return RichText */ - private function parseRichText($is) + private function parseRichText(?SimpleXMLElement $is) { $value = new RichText(); @@ -1709,6 +1746,8 @@ class Xlsx extends BaseReader $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $is->t)); } else { if (is_object($is->r)) { + + /** @var SimpleXMLElement $run */ foreach ($is->r as $run) { if (!isset($run->rPr)) { $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $run->t)); diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index c9a230c2..c9fc2f66 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -61,7 +61,7 @@ class Chart $XaxisLabel = $YaxisLabel = $legend = $title = null; $dispBlanksAs = $plotVisOnly = null; - + $plotArea = null; foreach ($chartElementsC as $chartElementKey => $chartElement) { switch ($chartElementKey) { case 'chart': @@ -90,8 +90,22 @@ class Chart break; case 'valAx': - if (isset($chartDetail->title)) { - $YaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta); + if (isset($chartDetail->title, $chartDetail->axPos)) { + $axisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta); + $axPos = self::getAttribute($chartDetail->axPos, 'val', 'string'); + + switch ($axPos) { + case 't': + case 'b': + $XaxisLabel = $axisLabel; + + break; + case 'r': + case 'l': + $YaxisLabel = $axisLabel; + + break; + } } break; @@ -328,26 +342,51 @@ class Chart { if (isset($seriesDetail->strRef)) { $seriesSource = (string) $seriesDetail->strRef->f; - $seriesData = self::chartDataSeriesValues($seriesDetail->strRef->strCache->children($namespacesChartMeta['c']), 's'); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker); - return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker); + if (isset($seriesDetail->strRef->strCache)) { + $seriesData = self::chartDataSeriesValues($seriesDetail->strRef->strCache->children($namespacesChartMeta['c']), 's'); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } + + return $seriesValues; } elseif (isset($seriesDetail->numRef)) { $seriesSource = (string) $seriesDetail->numRef->f; - $seriesData = self::chartDataSeriesValues($seriesDetail->numRef->numCache->children($namespacesChartMeta['c'])); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, null, null, $marker); + if (isset($seriesDetail->numRef->numCache)) { + $seriesData = self::chartDataSeriesValues($seriesDetail->numRef->numCache->children($namespacesChartMeta['c'])); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } - return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker); + return $seriesValues; } elseif (isset($seriesDetail->multiLvlStrRef)) { $seriesSource = (string) $seriesDetail->multiLvlStrRef->f; - $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($namespacesChartMeta['c']), 's'); - $seriesData['pointCount'] = count($seriesData['dataValues']); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker); - return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker); + if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) { + $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($namespacesChartMeta['c']), 's'); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } + + return $seriesValues; } elseif (isset($seriesDetail->multiLvlNumRef)) { $seriesSource = (string) $seriesDetail->multiLvlNumRef->f; - $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($namespacesChartMeta['c']), 's'); - $seriesData['pointCount'] = count($seriesData['dataValues']); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker); - return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker); + if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) { + $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($namespacesChartMeta['c']), 's'); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } + + return $seriesValues; } return null; @@ -500,7 +539,7 @@ class Chart { $plotAttributes = []; if (isset($chartDetail->dLbls)) { - if (isset($chartDetail->dLbls->howLegendKey)) { + if (isset($chartDetail->dLbls->showLegendKey)) { $plotAttributes['showLegendKey'] = self::getAttribute($chartDetail->dLbls->showLegendKey, 'val', 'string'); } if (isset($chartDetail->dLbls->showVal)) { diff --git a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php index 4aa48e17..7f96956f 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php @@ -2,7 +2,11 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; +use PhpOffice\PhpSpreadsheet\Style\Color; use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; @@ -25,7 +29,8 @@ class ConditionalStyles { $this->setConditionalStyles( $this->worksheet, - $this->readConditionalStyles($this->worksheetXml) + $this->readConditionalStyles($this->worksheetXml), + $this->worksheetXml->extLst ); } @@ -36,14 +41,16 @@ class ConditionalStyles foreach ($conditional->cfRule as $cfRule) { if ( ((string) $cfRule['type'] == Conditional::CONDITION_NONE - || (string) $cfRule['type'] == Conditional::CONDITION_CELLIS - || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSTEXT - || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSBLANKS - || (string) $cfRule['type'] == Conditional::CONDITION_NOTCONTAINSBLANKS - || (string) $cfRule['type'] == Conditional::CONDITION_EXPRESSION) + || (string) $cfRule['type'] == Conditional::CONDITION_CELLIS + || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSTEXT + || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSBLANKS + || (string) $cfRule['type'] == Conditional::CONDITION_NOTCONTAINSBLANKS + || (string) $cfRule['type'] == Conditional::CONDITION_EXPRESSION) && isset($this->dxfs[(int) ($cfRule['dxfId'])]) ) { $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; + } elseif ((string) $cfRule['type'] == Conditional::CONDITION_DATABAR) { + $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; } } } @@ -51,11 +58,11 @@ class ConditionalStyles return $conditionals; } - private function setConditionalStyles(Worksheet $worksheet, array $conditionals): void + private function setConditionalStyles(Worksheet $worksheet, array $conditionals, $xmlExtLst): void { foreach ($conditionals as $ref => $cfRules) { ksort($cfRules); - $conditionalStyles = $this->readStyleRules($cfRules); + $conditionalStyles = $this->readStyleRules($cfRules, $xmlExtLst); // Extract all cell references in $ref $cellBlocks = explode(' ', str_replace('$', '', strtoupper($ref))); @@ -65,8 +72,9 @@ class ConditionalStyles } } - private function readStyleRules($cfRules) + private function readStyleRules($cfRules, $extLst) { + $conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst); $conditionalStyles = []; foreach ($cfRules as $cfRule) { $objConditional = new Conditional(); @@ -88,10 +96,61 @@ class ConditionalStyles } else { $objConditional->addCondition((string) $cfRule->formula); } - $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]); + + if (isset($cfRule->dataBar)) { + $objConditional->setDataBar($this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions)); + } else { + $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]); + } + $conditionalStyles[] = $objConditional; } return $conditionalStyles; } + + private function readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions): ConditionalDataBar + { + $dataBar = new ConditionalDataBar(); + //dataBar attribute + if (isset($cfRule->dataBar['showValue'])) { + $dataBar->setShowValue((bool) $cfRule->dataBar['showValue']); + } + + //dataBar children + //conditionalFormatValueObjects + $cfvoXml = $cfRule->dataBar->cfvo; + $cfvoIndex = 0; + foreach ((count($cfvoXml) > 1 ? $cfvoXml : [$cfvoXml]) as $cfvo) { + if ($cfvoIndex === 0) { + $dataBar->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val'])); + } + if ($cfvoIndex === 1) { + $dataBar->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val'])); + } + ++$cfvoIndex; + } + + //color + if (isset($cfRule->dataBar->color)) { + $dataBar->setColor((string) $cfRule->dataBar->color['rgb']); + } + //extLst + $this->readDataBarExtLstOfConditionalRule($dataBar, $cfRule, $conditionalFormattingRuleExtensions); + + return $dataBar; + } + + private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, $cfRule, $conditionalFormattingRuleExtensions): void + { + if (isset($cfRule->extLst)) { + $ns = $cfRule->extLst->getNamespaces(true); + foreach ((count($cfRule->extLst) > 0 ? $cfRule->extLst->ext : [$cfRule->extLst->ext]) as $ext) { + $extId = (string) $ext->children($ns['x14'])->id; + if (isset($conditionalFormattingRuleExtensions[$extId]) && (string) $ext['uri'] === '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}') { + $dataBar->setConditionalFormattingRuleExt($conditionalFormattingRuleExtensions[$extId]); + } + } + } + } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php b/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php index 106fd44e..a9afce38 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php @@ -29,7 +29,9 @@ class Hyperlinks public function setHyperlinks(SimpleXMLElement $worksheetXml): void { foreach ($worksheetXml->hyperlink as $hyperlink) { - $this->setHyperlink($hyperlink, $this->worksheet); + if ($hyperlink !== null) { + $this->setHyperlink($hyperlink, $this->worksheet); + } } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php index 43de8787..290e8cb7 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php @@ -143,21 +143,21 @@ class Styles extends BaseParserClass private static function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void { - $alignment->setHorizontal((string) $alignmentXml->alignment['horizontal']); - $alignment->setVertical((string) $alignmentXml->alignment['vertical']); + $alignment->setHorizontal((string) $alignmentXml['horizontal']); + $alignment->setVertical((string) $alignmentXml['vertical']); $textRotation = 0; - if ((int) $alignmentXml->alignment['textRotation'] <= 90) { - $textRotation = (int) $alignmentXml->alignment['textRotation']; - } elseif ((int) $alignmentXml->alignment['textRotation'] > 90) { - $textRotation = 90 - (int) $alignmentXml->alignment['textRotation']; + if ((int) $alignmentXml['textRotation'] <= 90) { + $textRotation = (int) $alignmentXml['textRotation']; + } elseif ((int) $alignmentXml['textRotation'] > 90) { + $textRotation = 90 - (int) $alignmentXml['textRotation']; } $alignment->setTextRotation((int) $textRotation); - $alignment->setWrapText(self::boolean((string) $alignmentXml->alignment['wrapText'])); - $alignment->setShrinkToFit(self::boolean((string) $alignmentXml->alignment['shrinkToFit'])); - $alignment->setIndent((int) ((string) $alignmentXml->alignment['indent']) > 0 ? (int) ((string) $alignmentXml->alignment['indent']) : 0); - $alignment->setReadOrder((int) ((string) $alignmentXml->alignment['readingOrder']) > 0 ? (int) ((string) $alignmentXml->alignment['readingOrder']) : 0); + $alignment->setWrapText(self::boolean((string) $alignmentXml['wrapText'])); + $alignment->setShrinkToFit(self::boolean((string) $alignmentXml['shrinkToFit'])); + $alignment->setIndent((int) ((string) $alignmentXml['indent']) > 0 ? (int) ((string) $alignmentXml['indent']) : 0); + $alignment->setReadOrder((int) ((string) $alignmentXml['readingOrder']) > 0 ? (int) ((string) $alignmentXml['readingOrder']) : 0); } private function readStyle(Style $docStyle, $style): void diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Theme.php b/src/PhpSpreadsheet/Reader/Xlsx/Theme.php index c105f3c1..1f2b863c 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Theme.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Theme.php @@ -21,16 +21,16 @@ class Theme /** * Colour Map. * - * @var array of string + * @var string[] */ private $colourMap; /** * Create a new Theme. * - * @param mixed $themeName - * @param mixed $colourSchemeName - * @param mixed $colourMap + * @param string $themeName + * @param string $colourSchemeName + * @param string[] $colourMap */ public function __construct($themeName, $colourSchemeName, $colourMap) { @@ -63,9 +63,9 @@ class Theme /** * Get colour Map Value by Position. * - * @param mixed $index + * @param int $index * - * @return string + * @return null|string */ public function getColourByIndex($index) { diff --git a/src/PhpSpreadsheet/Reader/Xml.php b/src/PhpSpreadsheet/Reader/Xml.php index 11aa1df3..a900ad9b 100644 --- a/src/PhpSpreadsheet/Reader/Xml.php +++ b/src/PhpSpreadsheet/Reader/Xml.php @@ -2,6 +2,8 @@ namespace PhpOffice\PhpSpreadsheet\Reader; +use DateTime; +use DateTimeZone; use PhpOffice\PhpSpreadsheet\Cell\AddressHelper; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; @@ -114,7 +116,7 @@ class Xml extends BaseReader $signature = [ '', + 'xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet', ]; // Open file @@ -191,7 +193,7 @@ class Xml extends BaseReader $xml_ss = $xml->children($namespaces['ss']); foreach ($xml_ss->Worksheet as $worksheet) { - $worksheet_ss = $worksheet->attributes($namespaces['ss']); + $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']); $worksheetNames[] = (string) $worksheet_ss['Name']; } @@ -221,7 +223,7 @@ class Xml extends BaseReader $worksheetID = 1; $xml_ss = $xml->children($namespaces['ss']); foreach ($xml_ss->Worksheet as $worksheet) { - $worksheet_ss = $worksheet->attributes($namespaces['ss']); + $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']); $tmpInfo = []; $tmpInfo['worksheetName'] = ''; @@ -381,13 +383,13 @@ class Xml extends BaseReader } if (isset($xml->CustomDocumentProperties)) { foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) { - $propertyAttributes = $propertyValue->attributes($namespaces['dt']); + $propertyAttributes = self::getAttributes($propertyValue, $namespaces['dt']); $propertyName = preg_replace_callback('/_x([0-9a-f]{4})_/i', ['self', 'hex2str'], $propertyName); $propertyType = Properties::PROPERTY_TYPE_UNKNOWN; switch ((string) $propertyAttributes) { case 'string': $propertyType = Properties::PROPERTY_TYPE_STRING; - $propertyValue = trim($propertyValue); + $propertyValue = trim((string) $propertyValue); break; case 'boolean': @@ -407,7 +409,7 @@ class Xml extends BaseReader break; case 'dateTime.tz': $propertyType = Properties::PROPERTY_TYPE_DATE; - $propertyValue = strtotime(trim($propertyValue)); + $propertyValue = strtotime(trim((string) $propertyValue)); break; } @@ -420,8 +422,10 @@ class Xml extends BaseReader $worksheetID = 0; $xml_ss = $xml->children($namespaces['ss']); - foreach ($xml_ss->Worksheet as $worksheet) { - $worksheet_ss = $worksheet->attributes($namespaces['ss']); + /** @var null|SimpleXMLElement $worksheetx */ + foreach ($xml_ss->Worksheet as $worksheetx) { + $worksheet = $worksheetx ?? new SimpleXMLElement(''); + $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']); if ( (isset($this->loadSheetsOnly)) && (isset($worksheet_ss['Name'])) && @@ -433,6 +437,7 @@ class Xml extends BaseReader // Create new Worksheet $spreadsheet->createSheet(); $spreadsheet->setActiveSheetIndex($worksheetID); + $worksheetName = ''; if (isset($worksheet_ss['Name'])) { $worksheetName = (string) $worksheet_ss['Name']; // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in @@ -444,7 +449,7 @@ class Xml extends BaseReader // locally scoped defined names if (isset($worksheet->Names[0])) { foreach ($worksheet->Names[0] as $definedName) { - $definedName_ss = $definedName->attributes($namespaces['ss']); + $definedName_ss = self::getAttributes($definedName, $namespaces['ss']); $name = (string) $definedName_ss['Name']; $definedValue = (string) $definedName_ss['RefersTo']; $convertedValue = AddressHelper::convertFormulaToA1($definedValue); @@ -458,7 +463,7 @@ class Xml extends BaseReader $columnID = 'A'; if (isset($worksheet->Table->Column)) { foreach ($worksheet->Table->Column as $columnData) { - $columnData_ss = $columnData->attributes($namespaces['ss']); + $columnData_ss = self::getAttributes($columnData, $namespaces['ss']); if (isset($columnData_ss['Index'])) { $columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']); } @@ -475,14 +480,14 @@ class Xml extends BaseReader $additionalMergedCells = 0; foreach ($worksheet->Table->Row as $rowData) { $rowHasData = false; - $row_ss = $rowData->attributes($namespaces['ss']); + $row_ss = self::getAttributes($rowData, $namespaces['ss']); if (isset($row_ss['Index'])) { $rowID = (int) $row_ss['Index']; } $columnID = 'A'; foreach ($rowData->Cell as $cell) { - $cell_ss = $cell->attributes($namespaces['ss']); + $cell_ss = self::getAttributes($cell, $namespaces['ss']); if (isset($cell_ss['Index'])) { $columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']); } @@ -524,7 +529,7 @@ class Xml extends BaseReader $cellData = $cell->Data; $cellValue = (string) $cellData; $type = DataType::TYPE_NULL; - $cellData_ss = $cellData->attributes($namespaces['ss']); + $cellData_ss = self::getAttributes($cellData, $namespaces['ss']); if (isset($cellData_ss['Type'])) { $cellDataType = $cellData_ss['Type']; switch ($cellDataType) { @@ -556,7 +561,8 @@ class Xml extends BaseReader break; case 'DateTime': $type = DataType::TYPE_NUMERIC; - $cellValue = Date::PHPToExcel(strtotime($cellValue . ' UTC')); + $dateTime = new DateTime($cellValue, new DateTimeZone('UTC')); + $cellValue = Date::PHPToExcel($dateTime); break; case 'Error': @@ -587,7 +593,7 @@ class Xml extends BaseReader $author = (string) $commentAttributes->Author; } $node = $cell->Comment->Data->asXML(); - $annotation = strip_tags($node); + $annotation = strip_tags((string) $node); $spreadsheet->getActiveSheet()->getComment($columnID . $rowID)->setAuthor($author)->setText($this->parseRichText($annotation)); } @@ -610,7 +616,7 @@ class Xml extends BaseReader if ($rowHasData) { if (isset($row_ss['Height'])) { $rowHeight = $row_ss['Height']; - $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight($rowHeight); + $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight((float) $rowHeight); } } @@ -631,7 +637,7 @@ class Xml extends BaseReader $activeWorksheet = $spreadsheet->setActiveSheetIndex(0); if (isset($xml->Names[0])) { foreach ($xml->Names[0] as $definedName) { - $definedName_ss = $definedName->attributes($namespaces['ss']); + $definedName_ss = self::getAttributes($definedName, $namespaces['ss']); $name = (string) $definedName_ss['Name']; $definedValue = (string) $definedName_ss['RefersTo']; $convertedValue = AddressHelper::convertFormulaToA1($definedValue); @@ -662,10 +668,11 @@ class Xml extends BaseReader } foreach ($xml->Styles[0] as $style) { - $style_ss = $style->attributes($namespaces['ss']); + $style_ss = self::getAttributes($style, $namespaces['ss']); $styleID = (string) $style_ss['ID']; - $this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : []; - foreach ($style as $styleType => $styleData) { + $this->styles[$styleID] = $this->styles['Default'] ?? []; + foreach ($style as $styleType => $styleDatax) { + $styleData = $styleDatax ?? new SimpleXMLElement(''); $styleAttributes = $styleData->attributes($namespaces['ss']); switch ($styleType) { case 'Alignment': @@ -742,23 +749,21 @@ class Xml extends BaseReader private static $borderPositions = ['top', 'left', 'bottom', 'right']; - /** - * @param $styleID - */ private function parseStyleBorders($styleID, SimpleXMLElement $styleData, array $namespaces): void { $diagonalDirection = ''; $borderPosition = ''; foreach ($styleData->Border as $borderStyle) { - $borderAttributes = $borderStyle->attributes($namespaces['ss']); + $borderAttributes = self::getAttributes($borderStyle, $namespaces['ss']); $thisBorder = []; $style = (string) $borderAttributes->Weight; $style .= strtolower((string) $borderAttributes->LineStyle); $thisBorder['borderStyle'] = self::$mappings['borderStyle'][$style] ?? Border::BORDER_NONE; - foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) { + foreach ($borderAttributes as $borderStyleKey => $borderStyleValuex) { + $borderStyleValue = (string) $borderStyleValuex; switch ($borderStyleKey) { case 'Position': - $borderStyleValue = strtolower((string) $borderStyleValue); + $borderStyleValue = strtolower($borderStyleValue); if (in_array($borderStyleValue, self::$borderPositions)) { $borderPosition = $borderStyleValue; } elseif ($borderStyleValue == 'diagonalleft') { @@ -784,6 +789,11 @@ class Xml extends BaseReader } } + private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement + { + return ($simple === null) ? new SimpleXMLElement('') : ($simple->attributes($node) ?? new SimpleXMLElement('')); + } + private static $underlineStyles = [ Font::UNDERLINE_NONE, Font::UNDERLINE_DOUBLE, @@ -809,9 +819,6 @@ class Xml extends BaseReader } } - /** - * @param $styleID - */ private function parseStyleFont(string $styleID, SimpleXMLElement $styleAttributes): void { foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { @@ -849,12 +856,10 @@ class Xml extends BaseReader } } - /** - * @param $styleID - */ private function parseStyleInterior($styleID, SimpleXMLElement $styleAttributes): void { - foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { + foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValuex) { + $styleAttributeValue = (string) $styleAttributeValuex; switch ($styleAttributeKey) { case 'Color': $this->styles[$styleID]['fill']['endColor']['rgb'] = substr($styleAttributeValue, 1); @@ -874,9 +879,6 @@ class Xml extends BaseReader } } - /** - * @param $styleID - */ private function parseStyleNumberFormat($styleID, SimpleXMLElement $styleAttributes): void { $fromFormats = ['\-', '\ ']; diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index 13f7cf71..d4fced37 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -375,17 +375,16 @@ class ReferenceHelper $allCoordinates = $pSheet->getCoordinates(); // Get coordinate of $pBefore - [$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($pBefore); - $beforeColumnIndex = Coordinate::columnIndexFromString($beforeColumn); + [$beforeColumn, $beforeRow] = Coordinate::indexesFromString($pBefore); // Clear cells if we are removing columns or rows $highestColumn = $pSheet->getHighestColumn(); $highestRow = $pSheet->getHighestRow(); // 1. Clear column strips if we are removing columns - if ($pNumCols < 0 && $beforeColumnIndex - 2 + $pNumCols > 0) { + if ($pNumCols < 0 && $beforeColumn - 2 + $pNumCols > 0) { for ($i = 1; $i <= $highestRow - 1; ++$i) { - for ($j = $beforeColumnIndex - 1 + $pNumCols; $j <= $beforeColumnIndex - 2; ++$j) { + for ($j = $beforeColumn - 1 + $pNumCols; $j <= $beforeColumn - 2; ++$j) { $coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i; $pSheet->removeConditionalStyles($coordinate); if ($pSheet->cellExists($coordinate)) { @@ -398,7 +397,7 @@ class ReferenceHelper // 2. Clear row strips if we are removing rows if ($pNumRows < 0 && $beforeRow - 1 + $pNumRows > 0) { - for ($i = $beforeColumnIndex - 1; $i <= Coordinate::columnIndexFromString($highestColumn) - 1; ++$i) { + for ($i = $beforeColumn - 1; $i <= Coordinate::columnIndexFromString($highestColumn) - 1; ++$i) { for ($j = $beforeRow + $pNumRows; $j <= $beforeRow - 1; ++$j) { $coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j; $pSheet->removeConditionalStyles($coordinate); @@ -427,7 +426,7 @@ class ReferenceHelper $newCoordinate = Coordinate::stringFromColumnIndex($cellIndex + $pNumCols) . ($cell->getRow() + $pNumRows); // Should the cell be updated? Move value and cellXf index from one cell to another. - if (($cellIndex >= $beforeColumnIndex) && ($cell->getRow() >= $beforeRow)) { + if (($cellIndex >= $beforeColumn) && ($cell->getRow() >= $beforeRow)) { // Update cell styles $pSheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex()); @@ -457,15 +456,15 @@ class ReferenceHelper $highestColumn = $pSheet->getHighestColumn(); $highestRow = $pSheet->getHighestRow(); - if ($pNumCols > 0 && $beforeColumnIndex - 2 > 0) { + if ($pNumCols > 0 && $beforeColumn - 2 > 0) { for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) { // Style - $coordinate = Coordinate::stringFromColumnIndex($beforeColumnIndex - 1) . $i; + $coordinate = Coordinate::stringFromColumnIndex($beforeColumn - 1) . $i; if ($pSheet->cellExists($coordinate)) { $xfIndex = $pSheet->getCell($coordinate)->getXfIndex(); $conditionalStyles = $pSheet->conditionalStylesExists($coordinate) ? $pSheet->getConditionalStyles($coordinate) : false; - for ($j = $beforeColumnIndex; $j <= $beforeColumnIndex - 1 + $pNumCols; ++$j) { + for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $pNumCols; ++$j) { $pSheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex); if ($conditionalStyles) { $cloned = []; @@ -480,7 +479,7 @@ class ReferenceHelper } if ($pNumRows > 0 && $beforeRow - 1 > 0) { - for ($i = $beforeColumnIndex; $i <= Coordinate::columnIndexFromString($highestColumn); ++$i) { + for ($i = $beforeColumn; $i <= Coordinate::columnIndexFromString($highestColumn); ++$i) { // Style $coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1); if ($pSheet->cellExists($coordinate)) { @@ -502,28 +501,28 @@ class ReferenceHelper } // Update worksheet: column dimensions - $this->adjustColumnDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustColumnDimensions($pSheet, $pBefore, $beforeColumn, $pNumCols, $beforeRow, $pNumRows); // Update worksheet: row dimensions - $this->adjustRowDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustRowDimensions($pSheet, $pBefore, $beforeColumn, $pNumCols, $beforeRow, $pNumRows); // Update worksheet: page breaks - $this->adjustPageBreaks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustPageBreaks($pSheet, $pBefore, $beforeColumn, $pNumCols, $beforeRow, $pNumRows); // Update worksheet: comments - $this->adjustComments($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustComments($pSheet, $pBefore, $beforeColumn, $pNumCols, $beforeRow, $pNumRows); // Update worksheet: hyperlinks - $this->adjustHyperlinks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustHyperlinks($pSheet, $pBefore, $beforeColumn, $pNumCols, $beforeRow, $pNumRows); // Update worksheet: data validations - $this->adjustDataValidations($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustDataValidations($pSheet, $pBefore, $beforeColumn, $pNumCols, $beforeRow, $pNumRows); // Update worksheet: merge cells - $this->adjustMergeCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustMergeCells($pSheet, $pBefore, $beforeColumn, $pNumCols, $beforeRow, $pNumRows); // Update worksheet: protected cells - $this->adjustProtectedCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustProtectedCells($pSheet, $pBefore, $beforeColumn, $pNumCols, $beforeRow, $pNumRows); // Update worksheet: autofilter $autoFilter = $pSheet->getAutoFilter(); @@ -608,7 +607,7 @@ class ReferenceHelper // Update workbook: define names if (count($pSheet->getParent()->getDefinedNames()) > 0) { foreach ($pSheet->getParent()->getDefinedNames() as $definedName) { - if ($definedName->getWorksheet()->getHashCode() === $pSheet->getHashCode()) { + if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $pSheet->getHashCode()) { $definedName->setValue($this->updateCellReference($definedName->getValue(), $pBefore, $pNumCols, $pNumRows)); } } @@ -654,7 +653,7 @@ class ReferenceHelper $toString .= $modified3 . ':' . $modified4; // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = 100000; - $row = 10000000 + trim($match[3], '$'); + $row = 10000000 + (int) trim($match[3], '$'); $cellIndex = $column . $row; $newCellTokens[$cellIndex] = preg_quote($toString, '/'); @@ -705,7 +704,7 @@ class ReferenceHelper [$column, $row] = Coordinate::coordinateFromString($match[3]); // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000; - $row = trim($row, '$') + 10000000; + $row = (int) trim($row, '$') + 10000000; $cellIndex = $column . $row; $newCellTokens[$cellIndex] = preg_quote($toString, '/'); @@ -731,7 +730,7 @@ class ReferenceHelper [$column, $row] = Coordinate::coordinateFromString($match[3]); // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000; - $row = trim($row, '$') + 10000000; + $row = (int) trim($row, '$') + 10000000; $cellIndex = $row . $column; $newCellTokens[$cellIndex] = preg_quote($toString, '/'); @@ -1021,7 +1020,7 @@ class ReferenceHelper // Create new row reference if ($updateRow) { - $newRow = $newRow + $pNumRows; + $newRow = (int) $newRow + $pNumRows; } // Return new reference diff --git a/src/PhpSpreadsheet/RichText/ITextElement.php b/src/PhpSpreadsheet/RichText/ITextElement.php index 69954676..39b70c86 100644 --- a/src/PhpSpreadsheet/RichText/ITextElement.php +++ b/src/PhpSpreadsheet/RichText/ITextElement.php @@ -14,7 +14,7 @@ interface ITextElement /** * Set text. * - * @param $text string Text + * @param string $text Text * * @return ITextElement */ diff --git a/src/PhpSpreadsheet/RichText/TextElement.php b/src/PhpSpreadsheet/RichText/TextElement.php index f8be5d55..26aebc0e 100644 --- a/src/PhpSpreadsheet/RichText/TextElement.php +++ b/src/PhpSpreadsheet/RichText/TextElement.php @@ -35,7 +35,7 @@ class TextElement implements ITextElement /** * Set text. * - * @param $text string Text + * @param string $text Text * * @return $this */ diff --git a/src/PhpSpreadsheet/Settings.php b/src/PhpSpreadsheet/Settings.php index cfa50573..8fdccbad 100644 --- a/src/PhpSpreadsheet/Settings.php +++ b/src/PhpSpreadsheet/Settings.php @@ -118,7 +118,7 @@ class Settings if (self::$libXmlLoaderOptions === null && defined('LIBXML_DTDLOAD')) { self::setLibXmlLoaderOptions(LIBXML_DTDLOAD | LIBXML_DTDATTR); } elseif (self::$libXmlLoaderOptions === null) { - self::$libXmlLoaderOptions = true; + self::$libXmlLoaderOptions = 0; } return self::$libXmlLoaderOptions; diff --git a/src/PhpSpreadsheet/Shared/Date.php b/src/PhpSpreadsheet/Shared/Date.php index 180a7159..898dd523 100644 --- a/src/PhpSpreadsheet/Shared/Date.php +++ b/src/PhpSpreadsheet/Shared/Date.php @@ -2,9 +2,10 @@ namespace PhpOffice\PhpSpreadsheet\Shared; +use DateTime; use DateTimeInterface; use DateTimeZone; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; @@ -154,26 +155,26 @@ class Date * if you don't want to treat it as a UTC value * Use the default (UST) unless you absolutely need a conversion * - * @return \DateTime PHP date/time object + * @return DateTime PHP date/time object */ public static function excelToDateTimeObject($excelTimestamp, $timeZone = null) { $timeZone = ($timeZone === null) ? self::getDefaultTimezone() : self::validateTimeZone($timeZone); if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { - if ($excelTimestamp < 1.0) { + if ($excelTimestamp < 1 && self::$excelCalendar === self::CALENDAR_WINDOWS_1900) { // Unix timestamp base date - $baseDate = new \DateTime('1970-01-01', $timeZone); + $baseDate = new DateTime('1970-01-01', $timeZone); } else { // MS Excel calendar base dates if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) { // Allow adjustment for 1900 Leap Year in MS Excel - $baseDate = ($excelTimestamp < 60) ? new \DateTime('1899-12-31', $timeZone) : new \DateTime('1899-12-30', $timeZone); + $baseDate = ($excelTimestamp < 60) ? new DateTime('1899-12-31', $timeZone) : new DateTime('1899-12-30', $timeZone); } else { - $baseDate = new \DateTime('1904-01-01', $timeZone); + $baseDate = new DateTime('1904-01-01', $timeZone); } } } else { - $baseDate = new \DateTime('1899-12-30', $timeZone); + $baseDate = new DateTime('1899-12-30', $timeZone); } $days = floor($excelTimestamp); @@ -254,7 +255,7 @@ class Date * * @param int $dateValue Unix Timestamp * - * @return float MS Excel serialized date/time value + * @return false|float MS Excel serialized date/time value */ public static function timestampToExcel($dateValue) { @@ -262,7 +263,7 @@ class Date return false; } - return self::dateTimeToExcel(new \DateTime('@' . $dateValue)); + return self::dateTimeToExcel(new DateTime('@' . $dateValue)); } /** @@ -303,8 +304,8 @@ class Date } // Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0) - $century = substr($year, 0, 2); - $decade = substr($year, 2, 2); + $century = (int) substr($year, 0, 2); + $decade = (int) substr($year, 2, 2); $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myexcelBaseDate + $excel1900isLeapYear; $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400; @@ -436,14 +437,14 @@ class Date return false; } - $dateValueNew = DateTime::DATEVALUE($dateValue); + $dateValueNew = DateTimeExcel\DateValue::funcDateValue($dateValue); if ($dateValueNew === Functions::VALUE()) { return false; } if (strpos($dateValue, ':') !== false) { - $timeValue = DateTime::TIMEVALUE($dateValue); + $timeValue = DateTimeExcel\TimeValue::funcTimeValue($dateValue); if ($timeValue === Functions::VALUE()) { return false; } diff --git a/src/PhpSpreadsheet/Shared/Drawing.php b/src/PhpSpreadsheet/Shared/Drawing.php index f41fb695..ebb87ed1 100644 --- a/src/PhpSpreadsheet/Shared/Drawing.php +++ b/src/PhpSpreadsheet/Shared/Drawing.php @@ -15,7 +15,7 @@ class Drawing */ public static function pixelsToEMU($pValue) { - return round($pValue * 9525); + return $pValue * 9525; } /** @@ -28,7 +28,7 @@ class Drawing public static function EMUToPixels($pValue) { if ($pValue != 0) { - return round($pValue / 9525); + return (int) round($pValue / 9525); } return 0; @@ -141,7 +141,7 @@ class Drawing public static function angleToDegrees($pValue) { if ($pValue != 0) { - return round($pValue / 60000); + return (int) round($pValue / 60000); } return 0; @@ -171,6 +171,8 @@ class Drawing // Process the header // Structure: http://www.fastgraph.com/help/bmp_header_format.html + $width = 0; + $height = 0; if (substr($header, 0, 4) == '424d') { // Cut it in parts of 2 bytes $header_parts = str_split($header, 2); diff --git a/src/PhpSpreadsheet/Shared/File.php b/src/PhpSpreadsheet/Shared/File.php index 7525df8a..7991ed45 100644 --- a/src/PhpSpreadsheet/Shared/File.php +++ b/src/PhpSpreadsheet/Shared/File.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared; use InvalidArgumentException; +use PhpOffice\PhpSpreadsheet\Exception; use ZipArchive; class File @@ -124,6 +125,16 @@ class File return realpath(sys_get_temp_dir()); } + public static function temporaryFilename(): string + { + $filename = tempnam(self::sysGetTempDir(), 'phpspreadsheet'); + if ($filename === false) { + throw new Exception('Could not create temporary file'); + } + + return $filename; + } + /** * Assert that given path is an existing file and is readable, otherwise throw exception. * diff --git a/src/PhpSpreadsheet/Shared/Font.php b/src/PhpSpreadsheet/Shared/Font.php index ee1f8aba..00629e69 100644 --- a/src/PhpSpreadsheet/Shared/Font.php +++ b/src/PhpSpreadsheet/Shared/Font.php @@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\RichText\RichText; +use PhpOffice\PhpSpreadsheet\Style\Alignment; class Font { @@ -231,7 +232,7 @@ class Font } // Special case if there are one or more newline characters ("\n") - if (strpos($cellText, "\n") !== false) { + if (strpos($cellText ?? '', "\n") !== false) { $lineTexts = explode("\n", $cellText); $lineWidths = []; foreach ($lineTexts as $lineText) { @@ -243,6 +244,7 @@ class Font // Try to get the exact text width in pixels $approximate = self::$autoSizeMethod == self::AUTOSIZE_METHOD_APPROX; + $columnWidth = 0; if (!$approximate) { $columnWidthAdjust = ceil(self::getTextWidthPixelsExact('n', $font, 0) * 1.07); @@ -266,19 +268,13 @@ class Font $columnWidth = Drawing::pixelsToCellDimension($columnWidth, $defaultFont); // Return - return round($columnWidth, 6); + return (int) round($columnWidth, 6); } /** * Get GD text width in pixels for a string of text in a certain font at a certain rotation angle. - * - * @param string $text - * @param \PhpOffice\PhpSpreadsheet\Style\Font - * @param int $rotation - * - * @return int */ - public static function getTextWidthPixelsExact($text, \PhpOffice\PhpSpreadsheet\Style\Font $font, $rotation = 0) + public static function getTextWidthPixelsExact(string $text, \PhpOffice\PhpSpreadsheet\Style\Font $font, int $rotation = 0): int { if (!function_exists('imagettfbbox')) { throw new PhpSpreadsheetException('GD library needs to be enabled'); @@ -342,13 +338,13 @@ class Font // Calculate approximate rotated column width if ($rotation !== 0) { - if ($rotation == -165) { + if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) { // stacked text $columnWidth = 4; // approximation } else { // rotated text $columnWidth = $columnWidth * cos(deg2rad($rotation)) - + $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation + + $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation } } @@ -413,35 +409,35 @@ class Font switch ($name) { case 'Arial': $fontFile = ( - $bold ? ($italic ? self::ARIAL_BOLD_ITALIC : self::ARIAL_BOLD) - : ($italic ? self::ARIAL_ITALIC : self::ARIAL) + $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) + $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) + $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 + $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) + $bold ? ($italic ? self::GEORGIA_BOLD_ITALIC : self::GEORGIA_BOLD) + : ($italic ? self::GEORGIA_ITALIC : self::GEORGIA) ); break; @@ -451,8 +447,8 @@ class Font break; case 'Liberation Sans': $fontFile = ( - $bold ? ($italic ? self::LIBERATION_SANS_BOLD_ITALIC : self::LIBERATION_SANS_BOLD) - : ($italic ? self::LIBERATION_SANS_ITALIC : self::LIBERATION_SANS) + $bold ? ($italic ? self::LIBERATION_SANS_BOLD_ITALIC : self::LIBERATION_SANS_BOLD) + : ($italic ? self::LIBERATION_SANS_ITALIC : self::LIBERATION_SANS) ); break; @@ -470,8 +466,8 @@ class Font break; case 'Palatino Linotype': $fontFile = ( - $bold ? ($italic ? self::PALATINO_LINOTYPE_BOLD_ITALIC : self::PALATINO_LINOTYPE_BOLD) - : ($italic ? self::PALATINO_LINOTYPE_ITALIC : self::PALATINO_LINOTYPE) + $bold ? ($italic ? self::PALATINO_LINOTYPE_BOLD_ITALIC : self::PALATINO_LINOTYPE_BOLD) + : ($italic ? self::PALATINO_LINOTYPE_ITALIC : self::PALATINO_LINOTYPE) ); break; @@ -481,28 +477,28 @@ class Font break; case 'Tahoma': $fontFile = ( - $bold ? self::TAHOMA_BOLD : self::TAHOMA + $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) + $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) + $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) + $bold ? ($italic ? self::VERDANA_BOLD_ITALIC : self::VERDANA_BOLD) + : ($italic ? self::VERDANA_ITALIC : self::VERDANA) ); break; @@ -561,13 +557,13 @@ class Font // Exact width can be determined $columnWidth = $pPixels ? self::$defaultColumnWidths[$font->getName()][$font->getSize()]['px'] - : self::$defaultColumnWidths[$font->getName()][$font->getSize()]['width']; + : self::$defaultColumnWidths[$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 = $pPixels ? self::$defaultColumnWidths['Calibri'][11]['px'] - : self::$defaultColumnWidths['Calibri'][11]['width']; + : self::$defaultColumnWidths['Calibri'][11]['width']; $columnWidth = $columnWidth * $font->getSize() / 11; // Round pixels to closest integer diff --git a/src/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php b/src/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php index 2b241d55..27d02176 100644 --- a/src/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php +++ b/src/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php @@ -103,7 +103,7 @@ class CholeskyDecomposition /** * Solve A*X = B. * - * @param $B Row-equal matrix + * @param Matrix $B Row-equal matrix * * @return Matrix L * L' * X = B */ @@ -111,7 +111,7 @@ class CholeskyDecomposition { if ($B->getRowDimension() == $this->m) { if ($this->isspd) { - $X = $B->getArrayCopy(); + $X = $B->getArray(); $nx = $B->getColumnDimension(); for ($k = 0; $k < $this->m; ++$k) { diff --git a/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php b/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php index 4c67c3a9..5c6ccfd3 100644 --- a/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php +++ b/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php @@ -18,9 +18,9 @@ namespace PhpOffice\PhpSpreadsheet\Shared\JAMA; * conditioned, or even singular, so the validity of the equation * A = V*D*inverse(V) depends upon V.cond(). * - * @author Paul Meagher + * @author Paul Meagher * - * @version 1.1 + * @version 1.1 */ class EigenvalueDecomposition { @@ -70,6 +70,11 @@ class EigenvalueDecomposition private $cdivi; + /** + * @var array + */ + private $A; + /** * Symmetric Householder reduction to tridiagonal form. */ @@ -80,6 +85,7 @@ class EigenvalueDecomposition // Auto. Comp., Vol.ii-Linear Algebra, and the corresponding // Fortran subroutine in EISPACK. $this->d = $this->V[$this->n - 1]; + $j = 0; // Householder reduction to tridiagonal form. for ($i = $this->n - 1; $i > 0; --$i) { $i_ = $i - 1; @@ -781,9 +787,9 @@ class EigenvalueDecomposition /** * Constructor: Check for symmetry, then construct the eigenvalue decomposition. * - * @param mixed $Arg A Square matrix + * @param Matrix $Arg A Square matrix */ - public function __construct($Arg) + public function __construct(Matrix $Arg) { $this->A = $Arg->getArray(); $this->n = $Arg->getColumnDimension(); @@ -848,6 +854,7 @@ class EigenvalueDecomposition */ public function getD() { + $D = []; for ($i = 0; $i < $this->n; ++$i) { $D[$i] = array_fill(0, $this->n, 0.0); $D[$i][$i] = $this->d[$i]; diff --git a/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php b/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php index 4aecff73..ecfe42ba 100644 --- a/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php +++ b/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php @@ -135,6 +135,7 @@ class LUDecomposition */ public function getL() { + $L = []; for ($i = 0; $i < $this->m; ++$i) { for ($j = 0; $j < $this->n; ++$j) { if ($i > $j) { @@ -159,6 +160,7 @@ class LUDecomposition */ public function getU() { + $U = []; for ($i = 0; $i < $this->n; ++$i) { for ($j = 0; $j < $this->n; ++$j) { if ($i <= $j) { @@ -219,7 +221,7 @@ class LUDecomposition /** * Count determinants. * - * @return array d matrix deterninat + * @return float */ public function det() { @@ -240,11 +242,11 @@ class LUDecomposition /** * Solve A*X = B. * - * @param mixed $B a Matrix with as many rows as A and any number of columns + * @param Matrix $B a Matrix with as many rows as A and any number of columns * * @return Matrix X so that L*U*X = B(piv,:) */ - public function solve($B) + public function solve(Matrix $B) { if ($B->getRowDimension() == $this->m) { if ($this->isNonsingular()) { diff --git a/src/PhpSpreadsheet/Shared/JAMA/Matrix.php b/src/PhpSpreadsheet/Shared/JAMA/Matrix.php index a5cb6de0..adf399ac 100644 --- a/src/PhpSpreadsheet/Shared/JAMA/Matrix.php +++ b/src/PhpSpreadsheet/Shared/JAMA/Matrix.php @@ -147,7 +147,7 @@ class Matrix * @param int $i Row position * @param int $j Column position * - * @return mixed Element (int/float/double) + * @return float|int */ public function get($i = null, $j = null) { @@ -323,11 +323,9 @@ class Matrix * * @param int $i Row position * @param int $j Column position - * @param mixed $c Int/float/double value - * - * @return mixed Element (int/float/double) + * @param float|int $c value */ - public function set($i = null, $j = null, $c = null) + public function set($i = null, $j = null, $c = null): void { // Optimized set version just has this $this->A[$i][$j] = $c; @@ -456,17 +454,6 @@ class Matrix return $s; } - /** - * uminus. - * - * Unary minus matrix -A - * - * @return Matrix Unary minus matrix - */ - public function uminus() - { - } - /** * plus. * @@ -1164,7 +1151,7 @@ class Matrix * * @return Matrix ... Solution if A is square, least squares solution otherwise */ - public function solve($B) + public function solve(self $B) { if ($this->m == $this->n) { $LU = new LUDecomposition($this); diff --git a/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php b/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php index 3bb8a10e..9b51f413 100644 --- a/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php +++ b/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php @@ -15,9 +15,9 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException; * of simultaneous linear equations. This will fail if isFullRank() * returns false. * - * @author Paul Meagher + * @author Paul Meagher * - * @version 1.1 + * @version 1.1 */ class QRDecomposition { @@ -54,47 +54,43 @@ class QRDecomposition /** * QR Decomposition computed by Householder reflections. * - * @param matrix $A Rectangular matrix + * @param Matrix $A Rectangular matrix */ - public function __construct($A) + public function __construct(Matrix $A) { - if ($A instanceof Matrix) { - // Initialize. - $this->QR = $A->getArray(); - $this->m = $A->getRowDimension(); - $this->n = $A->getColumnDimension(); - // Main loop. - for ($k = 0; $k < $this->n; ++$k) { - // Compute 2-norm of k-th column without under/overflow. - $nrm = 0.0; - for ($i = $k; $i < $this->m; ++$i) { - $nrm = hypo($nrm, $this->QR[$i][$k]); - } - if ($nrm != 0.0) { - // Form k-th Householder vector. - if ($this->QR[$k][$k] < 0) { - $nrm = -$nrm; - } - for ($i = $k; $i < $this->m; ++$i) { - $this->QR[$i][$k] /= $nrm; - } - $this->QR[$k][$k] += 1.0; - // Apply transformation to remaining columns. - for ($j = $k + 1; $j < $this->n; ++$j) { - $s = 0.0; - for ($i = $k; $i < $this->m; ++$i) { - $s += $this->QR[$i][$k] * $this->QR[$i][$j]; - } - $s = -$s / $this->QR[$k][$k]; - for ($i = $k; $i < $this->m; ++$i) { - $this->QR[$i][$j] += $s * $this->QR[$i][$k]; - } - } - } - $this->Rdiag[$k] = -$nrm; + // Initialize. + $this->QR = $A->getArray(); + $this->m = $A->getRowDimension(); + $this->n = $A->getColumnDimension(); + // Main loop. + for ($k = 0; $k < $this->n; ++$k) { + // Compute 2-norm of k-th column without under/overflow. + $nrm = 0.0; + for ($i = $k; $i < $this->m; ++$i) { + $nrm = hypo($nrm, $this->QR[$i][$k]); } - } else { - throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION); + if ($nrm != 0.0) { + // Form k-th Householder vector. + if ($this->QR[$k][$k] < 0) { + $nrm = -$nrm; + } + for ($i = $k; $i < $this->m; ++$i) { + $this->QR[$i][$k] /= $nrm; + } + $this->QR[$k][$k] += 1.0; + // Apply transformation to remaining columns. + for ($j = $k + 1; $j < $this->n; ++$j) { + $s = 0.0; + for ($i = $k; $i < $this->m; ++$i) { + $s += $this->QR[$i][$k] * $this->QR[$i][$j]; + } + $s = -$s / $this->QR[$k][$k]; + for ($i = $k; $i < $this->m; ++$i) { + $this->QR[$i][$j] += $s * $this->QR[$i][$k]; + } + } + } + $this->Rdiag[$k] = -$nrm; } } @@ -205,13 +201,13 @@ class QRDecomposition * * @return Matrix matrix that minimizes the two norm of Q*R*X-B */ - public function solve($B) + public function solve(Matrix $B) { if ($B->getRowDimension() == $this->m) { if ($this->isFullRank()) { // Copy right hand side $nx = $B->getColumnDimension(); - $X = $B->getArrayCopy(); + $X = $B->getArray(); // Compute Y = transpose(Q)*B for ($k = 0; $k < $this->n; ++$k) { for ($j = 0; $j < $nx; ++$j) { diff --git a/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php b/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php index b997fb7c..6c8999d0 100644 --- a/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php +++ b/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php @@ -65,7 +65,7 @@ class SingularValueDecomposition public function __construct($Arg) { // Initialize. - $A = $Arg->getArrayCopy(); + $A = $Arg->getArray(); $this->m = $Arg->getRowDimension(); $this->n = $Arg->getColumnDimension(); $nu = min($this->m, $this->n); @@ -476,6 +476,7 @@ class SingularValueDecomposition */ public function getS() { + $S = []; for ($i = 0; $i < $this->n; ++$i) { for ($j = 0; $j < $this->n; ++$j) { $S[$i][$j] = 0.0; diff --git a/src/PhpSpreadsheet/Shared/OLE.php b/src/PhpSpreadsheet/Shared/OLE.php index d380995c..f65fbca7 100644 --- a/src/PhpSpreadsheet/Shared/OLE.php +++ b/src/PhpSpreadsheet/Shared/OLE.php @@ -21,6 +21,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared; // +----------------------------------------------------------------------+ // +use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; use PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream; use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root; @@ -227,7 +228,8 @@ class OLE // in OLE_ChainedBlockStream::stream_open(). // Object is removed from self::$instances in OLE_Stream::close(). $GLOBALS['_OLE_INSTANCES'][] = $this; - $instanceId = end(array_keys($GLOBALS['_OLE_INSTANCES'])); + $keys = array_keys($GLOBALS['_OLE_INSTANCES']); + $instanceId = end($keys); $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId; if ($blockIdOrPps instanceof OLE\PPS) { @@ -316,7 +318,7 @@ class OLE break; default: - break; + throw new Exception('Unsupported PPS type'); } fseek($fh, 1, SEEK_CUR); $pps->Type = $type; @@ -495,7 +497,7 @@ class OLE */ public static function localDateToOLE($date) { - if (!isset($date)) { + if (!$date) { return "\x00\x00\x00\x00\x00\x00\x00\x00"; } diff --git a/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php b/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php index cee5cd99..43e4804d 100644 --- a/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php +++ b/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php @@ -9,7 +9,7 @@ class ChainedBlockStream /** * The OLE container of the file that is being read. * - * @var OLE + * @var null|OLE */ public $ole; @@ -42,7 +42,7 @@ class ChainedBlockStream * ole-chainedblockstream://oleInstanceId=1 * @param string $mode only "r" is supported * @param int $options mask of STREAM_REPORT_ERRORS and STREAM_USE_PATH - * @param string &$openedPath absolute path of the opened stream (out parameter) + * @param string $openedPath absolute path of the opened stream (out parameter) * * @return bool true on success */ @@ -112,7 +112,7 @@ class ChainedBlockStream * * @param int $count maximum number of bytes to read * - * @return string + * @return false|string */ public function stream_read($count) // @codingStandardsIgnoreLine { diff --git a/src/PhpSpreadsheet/Shared/OLE/PPS.php b/src/PhpSpreadsheet/Shared/OLE/PPS.php index cf764d0b..104b0d6a 100644 --- a/src/PhpSpreadsheet/Shared/OLE/PPS.php +++ b/src/PhpSpreadsheet/Shared/OLE/PPS.php @@ -189,7 +189,7 @@ class PPS . "\x00\x00\x00\x00" // 100 . OLE::localDateToOLE($this->Time1st) // 108 . OLE::localDateToOLE($this->Time2nd) // 116 - . pack('V', isset($this->startBlock) ? $this->startBlock : 0) // 120 + . pack('V', $this->startBlock ?? 0) // 120 . pack('V', $this->Size) // 124 . pack('V', 0); // 128 @@ -200,7 +200,7 @@ class PPS * Updates index and pointers to previous, next and children PPS's for this * PPS. I don't think it'll work with Dir PPS's. * - * @param array &$raList Reference to the array of PPS's for the whole OLE + * @param array $raList Reference to the array of PPS's for the whole OLE * container * @param mixed $to_save * @param mixed $depth diff --git a/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php b/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php index 5466d2bc..2fe41055 100644 --- a/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php +++ b/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php @@ -237,7 +237,7 @@ class Root extends PPS * Saving big data (PPS's with data bigger than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL). * * @param int $iStBlk - * @param array &$raList Reference to array of PPS's + * @param array $raList Reference to array of PPS's */ private function saveBigData($iStBlk, &$raList): void { @@ -267,7 +267,7 @@ class Root extends PPS /** * get small data (PPS's with data smaller than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL). * - * @param array &$raList Reference to array of PPS's + * @param array $raList Reference to array of PPS's * * @return string */ diff --git a/src/PhpSpreadsheet/Shared/OLERead.php b/src/PhpSpreadsheet/Shared/OLERead.php index 7112b090..c4dc572a 100644 --- a/src/PhpSpreadsheet/Shared/OLERead.php +++ b/src/PhpSpreadsheet/Shared/OLERead.php @@ -92,10 +92,8 @@ class OLERead /** * Read the file. - * - * @param $pFilename string Filename */ - public function read($pFilename): void + public function read(string $pFilename): void { File::assertFile($pFilename); @@ -190,7 +188,7 @@ class OLERead * * @param int $stream * - * @return string + * @return null|string */ public function getStream($stream) { diff --git a/src/PhpSpreadsheet/Shared/StringHelper.php b/src/PhpSpreadsheet/Shared/StringHelper.php index 9ae32413..e85ce55d 100644 --- a/src/PhpSpreadsheet/Shared/StringHelper.php +++ b/src/PhpSpreadsheet/Shared/StringHelper.php @@ -464,7 +464,7 @@ class StringHelper */ public static function countCharacters($value, $enc = 'UTF-8') { - return mb_strlen($value, $enc); + return mb_strlen($value ?? '', $enc); } /** @@ -556,7 +556,7 @@ class StringHelper * Identify whether a string contains a fractional numeric value, * and convert it to a numeric if it is. * - * @param string &$operand string value to test + * @param string $operand string value to test * * @return bool */ diff --git a/src/PhpSpreadsheet/Shared/Trend/BestFit.php b/src/PhpSpreadsheet/Shared/Trend/BestFit.php index c9499722..7df48953 100644 --- a/src/PhpSpreadsheet/Shared/Trend/BestFit.php +++ b/src/PhpSpreadsheet/Shared/Trend/BestFit.php @@ -2,7 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared\Trend; -class BestFit +abstract class BestFit { /** * Indicator flag for a calculation error. @@ -96,24 +96,18 @@ class BestFit * * @param float $xValue X-Value * - * @return bool Y-Value + * @return float Y-Value */ - public function getValueOfYForX($xValue) - { - return false; - } + abstract public function getValueOfYForX($xValue); /** * Return the X-Value for a specified value of Y. * * @param float $yValue Y-Value * - * @return bool X-Value + * @return float X-Value */ - public function getValueOfXForY($yValue) - { - return false; - } + abstract public function getValueOfXForY($yValue); /** * Return the original set of X-Values. @@ -130,12 +124,9 @@ class BestFit * * @param int $dp Number of places of decimal precision to display * - * @return bool + * @return string */ - public function getEquation($dp = 0) - { - return false; - } + abstract public function getEquation($dp = 0); /** * Return the Slope of the line. @@ -348,13 +339,13 @@ class BestFit $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue); $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY); - if ($const) { + if ($const === true) { $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY); } else { $SStot += $this->yValues[$xKey] * $this->yValues[$xKey]; } $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY); - if ($const) { + if ($const === true) { $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX); } else { $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey]; @@ -362,7 +353,7 @@ class BestFit } $this->SSResiduals = $SSres; - $this->DFResiduals = $this->valueCount - 1 - $const; + $this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0); if ($this->DFResiduals == 0.0) { $this->stdevOfResiduals = 0.0; @@ -395,27 +386,39 @@ class BestFit } } + private function sumSquares(array $values) + { + return array_sum( + array_map( + function ($value) { + return $value ** 2; + }, + $values + ) + ); + } + /** * @param float[] $yValues * @param float[] $xValues - * @param bool $const */ - protected function leastSquareFit(array $yValues, array $xValues, $const): void + protected function leastSquareFit(array $yValues, array $xValues, bool $const): void { // calculate sums - $x_sum = array_sum($xValues); - $y_sum = array_sum($yValues); - $meanX = $x_sum / $this->valueCount; - $meanY = $y_sum / $this->valueCount; - $mBase = $mDivisor = $xx_sum = $xy_sum = $yy_sum = 0.0; + $sumValuesX = array_sum($xValues); + $sumValuesY = array_sum($yValues); + $meanValueX = $sumValuesX / $this->valueCount; + $meanValueY = $sumValuesY / $this->valueCount; + $sumSquaresX = $this->sumSquares($xValues); + $sumSquaresY = $this->sumSquares($yValues); + $mBase = $mDivisor = 0.0; + $xy_sum = 0.0; for ($i = 0; $i < $this->valueCount; ++$i) { $xy_sum += $xValues[$i] * $yValues[$i]; - $xx_sum += $xValues[$i] * $xValues[$i]; - $yy_sum += $yValues[$i] * $yValues[$i]; - if ($const) { - $mBase += ($xValues[$i] - $meanX) * ($yValues[$i] - $meanY); - $mDivisor += ($xValues[$i] - $meanX) * ($xValues[$i] - $meanX); + if ($const === true) { + $mBase += ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY); + $mDivisor += ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX); } else { $mBase += $xValues[$i] * $yValues[$i]; $mDivisor += $xValues[$i] * $xValues[$i]; @@ -426,13 +429,9 @@ class BestFit $this->slope = $mBase / $mDivisor; // calculate intersect - if ($const) { - $this->intersect = $meanY - ($this->slope * $meanX); - } else { - $this->intersect = 0; - } + $this->intersect = ($const === true) ? $meanValueY - ($this->slope * $meanValueX) : 0.0; - $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, $meanX, $meanY, $const); + $this->calculateGoodnessOfFit($sumValuesX, $sumValuesY, $sumSquaresX, $sumSquaresY, $xy_sum, $meanValueX, $meanValueY, $const); } /** @@ -440,23 +439,22 @@ class BestFit * * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression - * @param bool $const */ - public function __construct($yValues, $xValues = [], $const = true) + public function __construct($yValues, $xValues = []) { // Calculate number of points - $nY = count($yValues); - $nX = count($xValues); + $yValueCount = count($yValues); + $xValueCount = count($xValues); // Define X Values if necessary - if ($nX == 0) { - $xValues = range(1, $nY); - } elseif ($nY != $nX) { + if ($xValueCount === 0) { + $xValues = range(1, $yValueCount); + } elseif ($yValueCount !== $xValueCount) { // Ensure both arrays of points are the same size $this->error = true; } - $this->valueCount = $nY; + $this->valueCount = $yValueCount; $this->xValues = $xValues; $this->yValues = $yValues; } diff --git a/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php b/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php index 82866dee..eb8cd746 100644 --- a/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php +++ b/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php @@ -88,20 +88,17 @@ class ExponentialBestFit extends BestFit * * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression - * @param bool $const */ - private function exponentialRegression($yValues, $xValues, $const): void + private function exponentialRegression(array $yValues, array $xValues, bool $const): void { - foreach ($yValues as &$value) { - if ($value < 0.0) { - $value = 0 - log(abs($value)); - } elseif ($value > 0.0) { - $value = log($value); - } - } - unset($value); + $adjustedYValues = array_map( + function ($value) { + return ($value < 0.0) ? 0 - log(abs($value)) : log($value); + }, + $yValues + ); - $this->leastSquareFit($yValues, $xValues, $const); + $this->leastSquareFit($adjustedYValues, $xValues, $const); } /** @@ -116,7 +113,7 @@ class ExponentialBestFit extends BestFit parent::__construct($yValues, $xValues); if (!$this->error) { - $this->exponentialRegression($yValues, $xValues, $const); + $this->exponentialRegression($yValues, $xValues, (bool) $const); } } } diff --git a/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php b/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php index 26a562c5..65d6b4ff 100644 --- a/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php +++ b/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php @@ -56,9 +56,8 @@ class LinearBestFit extends BestFit * * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression - * @param bool $const */ - private function linearRegression($yValues, $xValues, $const): void + private function linearRegression(array $yValues, array $xValues, bool $const): void { $this->leastSquareFit($yValues, $xValues, $const); } @@ -75,7 +74,7 @@ class LinearBestFit extends BestFit parent::__construct($yValues, $xValues); if (!$this->error) { - $this->linearRegression($yValues, $xValues, $const); + $this->linearRegression($yValues, $xValues, (bool) $const); } } } diff --git a/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php b/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php index c469067d..2366dc63 100644 --- a/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php +++ b/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php @@ -48,7 +48,7 @@ class LogarithmicBestFit extends BestFit $slope = $this->getSlope($dp); $intersect = $this->getIntersect($dp); - return 'Y = ' . $intersect . ' + ' . $slope . ' * log(X)'; + return 'Y = ' . $slope . ' * log(' . $intersect . ' * X)'; } /** @@ -56,20 +56,17 @@ class LogarithmicBestFit extends BestFit * * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression - * @param bool $const */ - private function logarithmicRegression($yValues, $xValues, $const): void + private function logarithmicRegression(array $yValues, array $xValues, bool $const): void { - foreach ($xValues as &$value) { - if ($value < 0.0) { - $value = 0 - log(abs($value)); - } elseif ($value > 0.0) { - $value = log($value); - } - } - unset($value); + $adjustedYValues = array_map( + function ($value) { + return ($value < 0.0) ? 0 - log(abs($value)) : log($value); + }, + $yValues + ); - $this->leastSquareFit($yValues, $xValues, $const); + $this->leastSquareFit($adjustedYValues, $xValues, $const); } /** @@ -84,7 +81,7 @@ class LogarithmicBestFit extends BestFit parent::__construct($yValues, $xValues); if (!$this->error) { - $this->logarithmicRegression($yValues, $xValues, $const); + $this->logarithmicRegression($yValues, $xValues, (bool) $const); } } } diff --git a/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php b/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php index d959eddb..2c8eea5b 100644 --- a/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php +++ b/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php @@ -42,6 +42,7 @@ class PolynomialBestFit extends BestFit { $retVal = $this->getIntersect(); $slope = $this->getSlope(); + // @phpstan-ignore-next-line foreach ($slope as $key => $value) { if ($value != 0.0) { $retVal += $value * $xValue ** ($key + 1); @@ -76,6 +77,7 @@ class PolynomialBestFit extends BestFit $intersect = $this->getIntersect($dp); $equation = 'Y = ' . $intersect; + // @phpstan-ignore-next-line foreach ($slope as $key => $value) { if ($value != 0.0) { $equation .= ' + ' . $value . ' * X'; @@ -93,7 +95,7 @@ class PolynomialBestFit extends BestFit * * @param int $dp Number of places of decimal precision to display * - * @return string + * @return float */ public function getSlope($dp = 0) { @@ -103,6 +105,7 @@ class PolynomialBestFit extends BestFit $coefficients[] = round($coefficient, $dp); } + // @phpstan-ignore-next-line return $coefficients; } @@ -178,9 +181,8 @@ class PolynomialBestFit extends BestFit * @param int $order Order of Polynomial for this regression * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression - * @param bool $const */ - public function __construct($order, $yValues, $xValues = [], $const = true) + public function __construct($order, $yValues, $xValues = []) { parent::__construct($yValues, $xValues); diff --git a/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php b/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php index c53eab63..cafd0115 100644 --- a/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php +++ b/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php @@ -72,28 +72,23 @@ class PowerBestFit extends BestFit * * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression - * @param bool $const */ - private function powerRegression($yValues, $xValues, $const): void + private function powerRegression(array $yValues, array $xValues, bool $const): void { - foreach ($xValues as &$value) { - if ($value < 0.0) { - $value = 0 - log(abs($value)); - } elseif ($value > 0.0) { - $value = log($value); - } - } - unset($value); - foreach ($yValues as &$value) { - if ($value < 0.0) { - $value = 0 - log(abs($value)); - } elseif ($value > 0.0) { - $value = log($value); - } - } - unset($value); + $adjustedYValues = array_map( + function ($value) { + return ($value < 0.0) ? 0 - log(abs($value)) : log($value); + }, + $yValues + ); + $adjustedXValues = array_map( + function ($value) { + return ($value < 0.0) ? 0 - log(abs($value)) : log($value); + }, + $xValues + ); - $this->leastSquareFit($yValues, $xValues, $const); + $this->leastSquareFit($adjustedYValues, $adjustedXValues, $const); } /** @@ -108,7 +103,7 @@ class PowerBestFit extends BestFit parent::__construct($yValues, $xValues); if (!$this->error) { - $this->powerRegression($yValues, $xValues, $const); + $this->powerRegression($yValues, $xValues, (bool) $const); } } } diff --git a/src/PhpSpreadsheet/Shared/Trend/Trend.php b/src/PhpSpreadsheet/Shared/Trend/Trend.php index 1b7b3901..61d1183a 100644 --- a/src/PhpSpreadsheet/Shared/Trend/Trend.php +++ b/src/PhpSpreadsheet/Shared/Trend/Trend.php @@ -44,7 +44,7 @@ class Trend /** * Cached results for each method when trying to identify which provides the best fit. * - * @var bestFit[] + * @var BestFit[] */ private static $trendCache = []; @@ -55,10 +55,9 @@ class Trend $nX = count($xValues); // Define X Values if necessary - if ($nX == 0) { + if ($nX === 0) { $xValues = range(1, $nY); - $nX = $nY; - } elseif ($nY != $nX) { + } elseif ($nY !== $nX) { // Ensure both arrays of points are the same size trigger_error('Trend(): Number of elements in coordinate arrays do not match.', E_USER_ERROR); } @@ -84,7 +83,7 @@ class Trend case self::TREND_POLYNOMIAL_6: if (!isset(self::$trendCache[$key])) { $order = substr($trendType, -1); - self::$trendCache[$key] = new PolynomialBestFit($order, $yValues, $xValues, $const); + self::$trendCache[$key] = new PolynomialBestFit($order, $yValues, $xValues); } return self::$trendCache[$key]; @@ -92,6 +91,8 @@ class Trend case self::TREND_BEST_FIT_NO_POLY: // If the request is to determine the best fit regression, then we test each Trend line in turn // Start by generating an instance of each available Trend method + $bestFit = []; + $bestFitValue = []; foreach (self::$trendTypes as $trendMethod) { $className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit'; $bestFit[$trendMethod] = new $className($yValues, $xValues, $const); @@ -100,7 +101,7 @@ class Trend if ($trendType != self::TREND_BEST_FIT_NO_POLY) { foreach (self::$trendTypePolynomialOrders as $trendMethod) { $order = substr($trendMethod, -1); - $bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues, $const); + $bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues); if ($bestFit[$trendMethod]->getError()) { unset($bestFit[$trendMethod]); } else { diff --git a/src/PhpSpreadsheet/Shared/XMLWriter.php b/src/PhpSpreadsheet/Shared/XMLWriter.php index 4f7a6a06..c35b7820 100644 --- a/src/PhpSpreadsheet/Shared/XMLWriter.php +++ b/src/PhpSpreadsheet/Shared/XMLWriter.php @@ -87,6 +87,6 @@ class XMLWriter extends \XMLWriter $text = implode("\n", $text); } - return $this->writeRaw(htmlspecialchars($text)); + return $this->writeRaw(htmlspecialchars($text ?? '')); } } diff --git a/src/PhpSpreadsheet/Shared/Xls.php b/src/PhpSpreadsheet/Shared/Xls.php index c9eaf378..26035ec6 100644 --- a/src/PhpSpreadsheet/Shared/Xls.php +++ b/src/PhpSpreadsheet/Shared/Xls.php @@ -205,12 +205,11 @@ class Xls * @param int $width Width in pixels * @param int $height Height in pixels * - * @return array + * @return null|array */ public static function oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height) { - [$column, $row] = Coordinate::coordinateFromString($coordinates); - $col_start = Coordinate::columnIndexFromString($column); + [$col_start, $row] = Coordinate::indexesFromString($coordinates); $row_start = $row - 1; $x1 = $offsetX; @@ -246,16 +245,16 @@ class Xls // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell // with zero height or width. if (self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start)) == 0) { - return; + return null; } if (self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end)) == 0) { - return; + return null; } if (self::sizeRow($sheet, $row_start + 1) == 0) { - return; + return null; } if (self::sizeRow($sheet, $row_end + 1) == 0) { - return; + return null; } // Convert the pixel values to the percentage value expected by Excel diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php index c8e8f72c..fb945399 100644 --- a/src/PhpSpreadsheet/Spreadsheet.php +++ b/src/PhpSpreadsheet/Spreadsheet.php @@ -55,7 +55,7 @@ class Spreadsheet /** * Calculation Engine. * - * @var Calculation + * @var null|Calculation */ private $calculationEngine; @@ -69,7 +69,7 @@ class Spreadsheet /** * Named ranges. * - * @var NamedRange[] + * @var DefinedName[] */ private $definedNames = []; @@ -104,21 +104,21 @@ class Spreadsheet /** * macrosCode : all macros code as binary data (the vbaProject.bin file, this include form, code, etc.), null if no macro. * - * @var string + * @var null|string */ private $macrosCode; /** * macrosCertificate : if macros are signed, contains binary data vbaProjectSignature.bin file, null if not signed. * - * @var string + * @var null|string */ private $macrosCertificate; /** * ribbonXMLData : null if workbook is'nt Excel 2007 or not contain a customized UI. * - * @var null|string + * @var null|array{target: string, data: string} */ private $ribbonXMLData; @@ -298,11 +298,9 @@ class Spreadsheet /** * retrieve ribbon XML Data. * - * return string|null|array - * * @param string $what * - * @return string + * @return null|array|string */ public function getRibbonXMLData($what = 'all') //we need some constants here... { @@ -373,7 +371,9 @@ class Spreadsheet */ private function getExtensionOnly($path) { - return pathinfo($path, PATHINFO_EXTENSION); + $extension = pathinfo($path, PATHINFO_EXTENSION); + + return is_array($extension) ? '' : $extension; } /** @@ -453,7 +453,7 @@ class Spreadsheet * * @param string $pName Sheet name * - * @return Worksheet + * @return null|Worksheet */ public function getSheetByCodeName($pName) { @@ -503,8 +503,8 @@ class Spreadsheet */ public function __destruct() { - $this->calculationEngine = null; $this->disconnectWorksheets(); + $this->calculationEngine = null; } /** @@ -513,19 +513,17 @@ class Spreadsheet */ public function disconnectWorksheets(): void { - $worksheet = null; - foreach ($this->workSheetCollection as $k => &$worksheet) { + foreach ($this->workSheetCollection as $worksheet) { $worksheet->disconnectCells(); - $this->workSheetCollection[$k] = null; + unset($worksheet); } - unset($worksheet); $this->workSheetCollection = []; } /** * Return the calculation engine for this worksheet. * - * @return Calculation + * @return null|Calculation */ public function getCalculationEngine() { @@ -874,7 +872,7 @@ class Spreadsheet /** * Get an array of all Named Ranges. * - * @return NamedRange[] + * @return DefinedName[] */ public function getNamedRanges(): array { @@ -889,7 +887,7 @@ class Spreadsheet /** * Get an array of all Named Formulae. * - * @return NamedFormula[] + * @return DefinedName[] */ public function getNamedFormulae(): array { @@ -1122,6 +1120,7 @@ class Spreadsheet */ public function __clone() { + // @phpstan-ignore-next-line foreach ($this as $key => $val) { if (is_object($val) || (is_array($val))) { $this->{$key} = unserialize(serialize($val)); @@ -1341,6 +1340,7 @@ class Spreadsheet // remove cellXfs without references and create mapping so we can update xfIndex // for all cells and columns $countNeededCellXfs = 0; + $map = []; foreach ($this->cellXfCollection as $index => $cellXf) { if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf ++$countNeededCellXfs; diff --git a/src/PhpSpreadsheet/Style/Alignment.php b/src/PhpSpreadsheet/Style/Alignment.php index 04a089fe..e072c524 100644 --- a/src/PhpSpreadsheet/Style/Alignment.php +++ b/src/PhpSpreadsheet/Style/Alignment.php @@ -35,21 +35,21 @@ class Alignment extends Supervisor /** * Horizontal alignment. * - * @var string + * @var null|string */ protected $horizontal = self::HORIZONTAL_GENERAL; /** * Vertical alignment. * - * @var string + * @var null|string */ protected $vertical = self::VERTICAL_BOTTOM; /** * Text rotation. * - * @var int + * @var null|int */ protected $textRotation = 0; @@ -179,7 +179,7 @@ class Alignment extends Supervisor /** * Get Horizontal. * - * @return string + * @return null|string */ public function getHorizontal() { @@ -216,7 +216,7 @@ class Alignment extends Supervisor /** * Get Vertical. * - * @return string + * @return null|string */ public function getVertical() { @@ -253,7 +253,7 @@ class Alignment extends Supervisor /** * Get TextRotation. * - * @return int + * @return null|int */ public function getTextRotation() { @@ -392,7 +392,8 @@ class Alignment extends Supervisor if ( $this->getHorizontal() != self::HORIZONTAL_GENERAL && $this->getHorizontal() != self::HORIZONTAL_LEFT && - $this->getHorizontal() != self::HORIZONTAL_RIGHT + $this->getHorizontal() != self::HORIZONTAL_RIGHT && + $this->getHorizontal() != self::HORIZONTAL_DISTRIBUTED ) { $pValue = 0; // indent not supported } diff --git a/src/PhpSpreadsheet/Style/Border.php b/src/PhpSpreadsheet/Style/Border.php index 1d3096f0..a8af9d94 100644 --- a/src/PhpSpreadsheet/Style/Border.php +++ b/src/PhpSpreadsheet/Style/Border.php @@ -37,7 +37,7 @@ class Border extends Supervisor protected $color; /** - * @var int + * @var null|int */ public $colorIndex; @@ -47,11 +47,8 @@ class Border extends Supervisor * @param bool $isSupervisor Flag indicating if this is a supervisor or not * Leave this value at default unless you understand exactly what * its ramifications are - * @param bool $isConditional Flag indicating if this is a conditional style or not - * Leave this value at default unless you understand exactly what - * its ramifications are */ - public function __construct($isSupervisor = false, $isConditional = false) + public function __construct($isSupervisor = false) { // Supervisor? parent::__construct($isSupervisor); @@ -73,17 +70,19 @@ class Border extends Supervisor */ public function getSharedComponent() { + /** @var Borders $sharedComponent */ + $sharedComponent = $this->parent->getSharedComponent(); switch ($this->parentPropertyName) { case 'bottom': - return $this->parent->getSharedComponent()->getBottom(); + return $sharedComponent->getBottom(); case 'diagonal': - return $this->parent->getSharedComponent()->getDiagonal(); + return $sharedComponent->getDiagonal(); case 'left': - return $this->parent->getSharedComponent()->getLeft(); + return $sharedComponent->getLeft(); case 'right': - return $this->parent->getSharedComponent()->getRight(); + return $sharedComponent->getRight(); case 'top': - return $this->parent->getSharedComponent()->getTop(); + return $sharedComponent->getTop(); } throw new PhpSpreadsheetException('Cannot get shared component for a pseudo-border.'); diff --git a/src/PhpSpreadsheet/Style/Borders.php b/src/PhpSpreadsheet/Style/Borders.php index a1acfdd4..eeb4932a 100644 --- a/src/PhpSpreadsheet/Style/Borders.php +++ b/src/PhpSpreadsheet/Style/Borders.php @@ -95,21 +95,18 @@ class Borders extends Supervisor * @param bool $isSupervisor Flag indicating if this is a supervisor or not * Leave this value at default unless you understand exactly what * its ramifications are - * @param bool $isConditional Flag indicating if this is a conditional style or not - * Leave this value at default unless you understand exactly what - * its ramifications are */ - public function __construct($isSupervisor = false, $isConditional = false) + public function __construct($isSupervisor = false) { // Supervisor? parent::__construct($isSupervisor); // Initialise values - $this->left = new Border($isSupervisor, $isConditional); - $this->right = new Border($isSupervisor, $isConditional); - $this->top = new Border($isSupervisor, $isConditional); - $this->bottom = new Border($isSupervisor, $isConditional); - $this->diagonal = new Border($isSupervisor, $isConditional); + $this->left = new Border($isSupervisor); + $this->right = new Border($isSupervisor); + $this->top = new Border($isSupervisor); + $this->bottom = new Border($isSupervisor); + $this->diagonal = new Border($isSupervisor); $this->diagonalDirection = self::DIAGONAL_NONE; // Specially for supervisor diff --git a/src/PhpSpreadsheet/Style/Color.php b/src/PhpSpreadsheet/Style/Color.php index ad598f11..bf5d093f 100644 --- a/src/PhpSpreadsheet/Style/Color.php +++ b/src/PhpSpreadsheet/Style/Color.php @@ -71,14 +71,16 @@ class Color extends Supervisor */ public function getSharedComponent() { + /** @var Border|Fill $sharedComponent */ + $sharedComponent = $this->parent->getSharedComponent(); if ($this->parentPropertyName === 'endColor') { - return $this->parent->getSharedComponent()->getEndColor(); + return $sharedComponent->getEndColor(); } if ($this->parentPropertyName === 'startColor') { - return $this->parent->getSharedComponent()->getStartColor(); + return $sharedComponent->getStartColor(); } - return $this->parent->getSharedComponent()->getColor(); + return $sharedComponent->getColor(); } /** @@ -167,7 +169,7 @@ class Color extends Supervisor return $this->getSharedComponent()->getRGB(); } - return substr($this->argb, 2); + return substr($this->argb ?? '', 2); } /** @@ -200,7 +202,7 @@ class Color extends Supervisor * @param bool $hex Flag indicating whether the component should be returned as a hex or a * decimal value * - * @return string The extracted colour component + * @return int|string The extracted colour component */ private static function getColourComponent($RGB, $offset, $hex = true) { @@ -216,7 +218,7 @@ class Color extends Supervisor * @param bool $hex Flag indicating whether the component should be returned as a hex or a * decimal value * - * @return string The red colour component + * @return int|string The red colour component */ public static function getRed($RGB, $hex = true) { @@ -230,7 +232,7 @@ class Color extends Supervisor * @param bool $hex Flag indicating whether the component should be returned as a hex or a * decimal value * - * @return string The green colour component + * @return int|string The green colour component */ public static function getGreen($RGB, $hex = true) { @@ -244,7 +246,7 @@ class Color extends Supervisor * @param bool $hex Flag indicating whether the component should be returned as a hex or a * decimal value * - * @return string The blue colour component + * @return int|string The blue colour component */ public static function getBlue($RGB, $hex = true) { @@ -264,8 +266,11 @@ class Color extends Supervisor $rgba = (strlen($hex) === 8); $adjustPercentage = max(-1.0, min(1.0, $adjustPercentage)); + /** @var int $red */ $red = self::getRed($hex, false); + /** @var int $green */ $green = self::getGreen($hex, false); + /** @var int $blue */ $blue = self::getBlue($hex, false); if ($adjustPercentage > 0) { $red += (255 - $red) * $adjustPercentage; diff --git a/src/PhpSpreadsheet/Style/Conditional.php b/src/PhpSpreadsheet/Style/Conditional.php index e4fe0acc..b008c9f2 100644 --- a/src/PhpSpreadsheet/Style/Conditional.php +++ b/src/PhpSpreadsheet/Style/Conditional.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Style; use PhpOffice\PhpSpreadsheet\IComparable; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar; class Conditional implements IComparable { @@ -13,6 +14,7 @@ class Conditional implements IComparable const CONDITION_EXPRESSION = 'expression'; const CONDITION_CONTAINSBLANKS = 'containsBlanks'; const CONDITION_NOTCONTAINSBLANKS = 'notContainsBlanks'; + const CONDITION_DATABAR = 'dataBar'; // Operator types const OPERATOR_NONE = ''; @@ -64,6 +66,11 @@ class Conditional implements IComparable */ private $condition = []; + /** + * @var ConditionalDataBar + */ + private $dataBar; + /** * Style. * @@ -241,6 +248,28 @@ class Conditional implements IComparable return $this; } + /** + * get DataBar. + * + * @return ConditionalDataBar | null + */ + public function getDataBar() + { + return $this->dataBar; + } + + /** + * set DataBar. + * + * @return $this + */ + public function setDataBar(ConditionalDataBar $dataBar) + { + $this->dataBar = $dataBar; + + return $this; + } + /** * Get hash code. * diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php new file mode 100644 index 00000000..54513670 --- /dev/null +++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php @@ -0,0 +1,102 @@ + attribute */ + + /** @var null|bool */ + private $showValue; + + /** children */ + + /** @var ConditionalFormatValueObject */ + private $minimumConditionalFormatValueObject; + + /** @var ConditionalFormatValueObject */ + private $maximumConditionalFormatValueObject; + + /** @var string */ + private $color; + + /** */ + + /** @var ConditionalFormattingRuleExtension */ + private $conditionalFormattingRuleExt; + + /** + * @return null|bool + */ + public function getShowValue() + { + return $this->showValue; + } + + /** + * @param bool $showValue + */ + public function setShowValue($showValue) + { + $this->showValue = $showValue; + + return $this; + } + + /** + * @return ConditionalFormatValueObject + */ + public function getMinimumConditionalFormatValueObject() + { + return $this->minimumConditionalFormatValueObject; + } + + public function setMinimumConditionalFormatValueObject(ConditionalFormatValueObject $minimumConditionalFormatValueObject) + { + $this->minimumConditionalFormatValueObject = $minimumConditionalFormatValueObject; + + return $this; + } + + /** + * @return ConditionalFormatValueObject + */ + public function getMaximumConditionalFormatValueObject() + { + return $this->maximumConditionalFormatValueObject; + } + + public function setMaximumConditionalFormatValueObject(ConditionalFormatValueObject $maximumConditionalFormatValueObject) + { + $this->maximumConditionalFormatValueObject = $maximumConditionalFormatValueObject; + + return $this; + } + + public function getColor(): string + { + return $this->color; + } + + public function setColor(string $color): self + { + $this->color = $color; + + return $this; + } + + /** + * @return ConditionalFormattingRuleExtension + */ + public function getConditionalFormattingRuleExt() + { + return $this->conditionalFormattingRuleExt; + } + + public function setConditionalFormattingRuleExt(ConditionalFormattingRuleExtension $conditionalFormattingRuleExt) + { + $this->conditionalFormattingRuleExt = $conditionalFormattingRuleExt; + + return $this; + } +} diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php new file mode 100644 index 00000000..c709cf3e --- /dev/null +++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php @@ -0,0 +1,290 @@ + attributes */ + + /** @var int */ + private $minLength; + + /** @var int */ + private $maxLength; + + /** @var null|bool */ + private $border; + + /** @var null|bool */ + private $gradient; + + /** @var string */ + private $direction; + + /** @var null|bool */ + private $negativeBarBorderColorSameAsPositive; + + /** @var string */ + private $axisPosition; + + // children + + /** @var ConditionalFormatValueObject */ + private $maximumConditionalFormatValueObject; + + /** @var ConditionalFormatValueObject */ + private $minimumConditionalFormatValueObject; + + /** @var string */ + private $borderColor; + + /** @var string */ + private $negativeFillColor; + + /** @var string */ + private $negativeBorderColor; + + /** @var array */ + private $axisColor = [ + 'rgb' => null, + 'theme' => null, + 'tint' => null, + ]; + + public function getXmlAttributes() + { + $ret = []; + foreach (['minLength', 'maxLength', 'direction', 'axisPosition'] as $attrKey) { + if (null !== $this->{$attrKey}) { + $ret[$attrKey] = $this->{$attrKey}; + } + } + foreach (['border', 'gradient', 'negativeBarBorderColorSameAsPositive'] as $attrKey) { + if (null !== $this->{$attrKey}) { + $ret[$attrKey] = $this->{$attrKey} ? '1' : '0'; + } + } + + return $ret; + } + + public function getXmlElements() + { + $ret = []; + $elms = ['borderColor', 'negativeFillColor', 'negativeBorderColor']; + foreach ($elms as $elmKey) { + if (null !== $this->{$elmKey}) { + $ret[$elmKey] = ['rgb' => $this->{$elmKey}]; + } + } + foreach (array_filter($this->axisColor) as $attrKey => $axisColorAttr) { + if (!isset($ret['axisColor'])) { + $ret['axisColor'] = []; + } + $ret['axisColor'][$attrKey] = $axisColorAttr; + } + + return $ret; + } + + /** + * @return int + */ + public function getMinLength() + { + return $this->minLength; + } + + public function setMinLength(int $minLength): self + { + $this->minLength = $minLength; + + return $this; + } + + /** + * @return int + */ + public function getMaxLength() + { + return $this->maxLength; + } + + public function setMaxLength(int $maxLength): self + { + $this->maxLength = $maxLength; + + return $this; + } + + /** + * @return null|bool + */ + public function getBorder() + { + return $this->border; + } + + public function setBorder(bool $border): self + { + $this->border = $border; + + return $this; + } + + /** + * @return null|bool + */ + public function getGradient() + { + return $this->gradient; + } + + public function setGradient(bool $gradient): self + { + $this->gradient = $gradient; + + return $this; + } + + /** + * @return string + */ + public function getDirection() + { + return $this->direction; + } + + public function setDirection(string $direction): self + { + $this->direction = $direction; + + return $this; + } + + /** + * @return null|bool + */ + public function getNegativeBarBorderColorSameAsPositive() + { + return $this->negativeBarBorderColorSameAsPositive; + } + + public function setNegativeBarBorderColorSameAsPositive(bool $negativeBarBorderColorSameAsPositive): self + { + $this->negativeBarBorderColorSameAsPositive = $negativeBarBorderColorSameAsPositive; + + return $this; + } + + /** + * @return string + */ + public function getAxisPosition() + { + return $this->axisPosition; + } + + public function setAxisPosition(string $axisPosition): self + { + $this->axisPosition = $axisPosition; + + return $this; + } + + /** + * @return ConditionalFormatValueObject + */ + public function getMaximumConditionalFormatValueObject() + { + return $this->maximumConditionalFormatValueObject; + } + + public function setMaximumConditionalFormatValueObject(ConditionalFormatValueObject $maximumConditionalFormatValueObject) + { + $this->maximumConditionalFormatValueObject = $maximumConditionalFormatValueObject; + + return $this; + } + + /** + * @return ConditionalFormatValueObject + */ + public function getMinimumConditionalFormatValueObject() + { + return $this->minimumConditionalFormatValueObject; + } + + public function setMinimumConditionalFormatValueObject(ConditionalFormatValueObject $minimumConditionalFormatValueObject) + { + $this->minimumConditionalFormatValueObject = $minimumConditionalFormatValueObject; + + return $this; + } + + /** + * @return string + */ + public function getBorderColor() + { + return $this->borderColor; + } + + public function setBorderColor(string $borderColor): self + { + $this->borderColor = $borderColor; + + return $this; + } + + /** + * @return string + */ + public function getNegativeFillColor() + { + return $this->negativeFillColor; + } + + public function setNegativeFillColor(string $negativeFillColor): self + { + $this->negativeFillColor = $negativeFillColor; + + return $this; + } + + /** + * @return string + */ + public function getNegativeBorderColor() + { + return $this->negativeBorderColor; + } + + public function setNegativeBorderColor(string $negativeBorderColor): self + { + $this->negativeBorderColor = $negativeBorderColor; + + return $this; + } + + public function getAxisColor(): array + { + return $this->axisColor; + } + + /** + * @param mixed $rgb + * @param null|mixed $theme + * @param null|mixed $tint + */ + public function setAxisColor($rgb, $theme = null, $tint = null): self + { + $this->axisColor = [ + 'rgb' => $rgb, + 'theme' => $theme, + 'tint' => $tint, + ]; + + return $this; + } +} diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php new file mode 100644 index 00000000..107969bf --- /dev/null +++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php @@ -0,0 +1,78 @@ +type = $type; + $this->value = $value; + $this->cellFormula = $cellFormula; + } + + /** + * @return mixed + */ + public function getType() + { + return $this->type; + } + + /** + * @param mixed $type + */ + public function setType($type) + { + $this->type = $type; + + return $this; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * @param mixed $value + */ + public function setValue($value) + { + $this->value = $value; + + return $this; + } + + /** + * @return mixed + */ + public function getCellFormula() + { + return $this->cellFormula; + } + + /** + * @param mixed $cellFormula + */ + public function setCellFormula($cellFormula) + { + $this->cellFormula = $cellFormula; + + return $this; + } +} diff --git a/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php new file mode 100644 index 00000000..899bbe43 --- /dev/null +++ b/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php @@ -0,0 +1,195 @@ + attributes */ + private $id; + + /** @var string Conditional Formatting Rule */ + private $cfRule; + + /** children */ + + /** @var ConditionalDataBarExtension */ + private $dataBar; + + /** @var string Sequence of References */ + private $sqref; + + /** + * ConditionalFormattingRuleExtension constructor. + */ + public function __construct($id = null, string $cfRule = self::CONDITION_EXTENSION_DATABAR) + { + if (null === $id) { + $this->id = '{' . $this->generateUuid() . '}'; + } else { + $this->id = $id; + } + $this->cfRule = $cfRule; + } + + private function generateUuid() + { + $chars = str_split('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'); + + foreach ($chars as $i => $char) { + if ($char === 'x') { + $chars[$i] = dechex(random_int(0, 15)); + } elseif ($char === 'y') { + $chars[$i] = dechex(random_int(8, 11)); + } + } + + return implode('', $chars); + } + + public static function parseExtLstXml($extLstXml) + { + $conditionalFormattingRuleExtensions = []; + $conditionalFormattingRuleExtensionXml = null; + if ($extLstXml instanceof SimpleXMLElement) { + foreach ((count($extLstXml) > 0 ? $extLstXml : [$extLstXml]) as $extLst) { + //this uri is conditionalFormattings + //https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/07d607af-5618-4ca2-b683-6a78dc0d9627 + if (isset($extLst->ext['uri']) && (string) $extLst->ext['uri'] === '{78C0D931-6437-407d-A8EE-F0AAD7539E65}') { + $conditionalFormattingRuleExtensionXml = $extLst->ext; + } + } + if ($conditionalFormattingRuleExtensionXml) { + $ns = $conditionalFormattingRuleExtensionXml->getNamespaces(true); + $extFormattingsXml = $conditionalFormattingRuleExtensionXml->children($ns['x14']); + + foreach ($extFormattingsXml->children($ns['x14']) as $extFormattingXml) { + $extCfRuleXml = $extFormattingXml->cfRule; + $extFormattingRuleObj = new self((string) $extCfRuleXml->attributes()->id); + $extFormattingRuleObj->setSqref((string) $extFormattingXml->children($ns['xm'])->sqref); + $conditionalFormattingRuleExtensions[$extFormattingRuleObj->getId()] = $extFormattingRuleObj; + + $extDataBarObj = new ConditionalDataBarExtension(); + $extFormattingRuleObj->setDataBarExt($extDataBarObj); + + $dataBarXml = $extCfRuleXml->dataBar; + self::parseExtDataBarAttributesFromXml($extDataBarObj, $dataBarXml); + self::parseExtDataBarElementChildrenFromXml($extDataBarObj, $dataBarXml, $ns); + } + } + } + + return $conditionalFormattingRuleExtensions; + } + + private static function parseExtDataBarAttributesFromXml(ConditionalDataBarExtension $extDataBarObj, SimpleXMLElement $dataBarXml): void + { + $dataBarAttribute = $dataBarXml->attributes(); + if ($dataBarAttribute->minLength) { + $extDataBarObj->setMinLength((int) $dataBarAttribute->minLength); + } + if ($dataBarAttribute->maxLength) { + $extDataBarObj->setMaxLength((int) $dataBarAttribute->maxLength); + } + if ($dataBarAttribute->border) { + $extDataBarObj->setBorder((bool) (string) $dataBarAttribute->border); + } + if ($dataBarAttribute->gradient) { + $extDataBarObj->setGradient((bool) (string) $dataBarAttribute->gradient); + } + if ($dataBarAttribute->direction) { + $extDataBarObj->setDirection((string) $dataBarAttribute->direction); + } + if ($dataBarAttribute->negativeBarBorderColorSameAsPositive) { + $extDataBarObj->setNegativeBarBorderColorSameAsPositive((bool) (string) $dataBarAttribute->negativeBarBorderColorSameAsPositive); + } + if ($dataBarAttribute->axisPosition) { + $extDataBarObj->setAxisPosition((string) $dataBarAttribute->axisPosition); + } + } + + private static function parseExtDataBarElementChildrenFromXml(ConditionalDataBarExtension $extDataBarObj, SimpleXMLElement $dataBarXml, $ns): void + { + if ($dataBarXml->borderColor) { + $extDataBarObj->setBorderColor((string) $dataBarXml->borderColor->attributes()['rgb']); + } + if ($dataBarXml->negativeFillColor) { + $extDataBarObj->setNegativeFillColor((string) $dataBarXml->negativeFillColor->attributes()['rgb']); + } + if ($dataBarXml->negativeBorderColor) { + $extDataBarObj->setNegativeBorderColor((string) $dataBarXml->negativeBorderColor->attributes()['rgb']); + } + if ($dataBarXml->axisColor) { + $axisColorAttr = $dataBarXml->axisColor->attributes(); + $extDataBarObj->setAxisColor((string) $axisColorAttr['rgb'], (string) $axisColorAttr['theme'], (string) $axisColorAttr['tint']); + } + $cfvoIndex = 0; + foreach ($dataBarXml->cfvo as $cfvo) { + $f = (string) $cfvo->children($ns['xm'])->f; + if ($cfvoIndex === 0) { + $extDataBarObj->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo->attributes()['type'], null, (empty($f) ? null : $f))); + } + if ($cfvoIndex === 1) { + $extDataBarObj->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo->attributes()['type'], null, (empty($f) ? null : $f))); + } + ++$cfvoIndex; + } + } + + /** + * @return mixed + */ + public function getId() + { + return $this->id; + } + + /** + * @param mixed $id + */ + public function setId($id): self + { + $this->id = $id; + + return $this; + } + + public function getCfRule(): string + { + return $this->cfRule; + } + + public function setCfRule(string $cfRule): self + { + $this->cfRule = $cfRule; + + return $this; + } + + public function getDataBarExt(): ConditionalDataBarExtension + { + return $this->dataBar; + } + + public function setDataBarExt(ConditionalDataBarExtension $dataBar): self + { + $this->dataBar = $dataBar; + + return $this; + } + + public function getSqref(): string + { + return $this->sqref; + } + + public function setSqref(string $sqref): self + { + $this->sqref = $sqref; + + return $this; + } +} diff --git a/src/PhpSpreadsheet/Style/Fill.php b/src/PhpSpreadsheet/Style/Fill.php index 3891bc47..e793f1e7 100644 --- a/src/PhpSpreadsheet/Style/Fill.php +++ b/src/PhpSpreadsheet/Style/Fill.php @@ -28,19 +28,19 @@ class Fill extends Supervisor const FILL_PATTERN_MEDIUMGRAY = 'mediumGray'; /** - * @var int + * @var null|int */ public $startcolorIndex; /** - * @var int + * @var null|int */ public $endcolorIndex; /** * Fill type. * - * @var string + * @var null|string */ protected $fillType = self::FILL_NONE; @@ -168,7 +168,7 @@ class Fill extends Supervisor /** * Get Fill Type. * - * @return string + * @return null|string */ public function getFillType() { diff --git a/src/PhpSpreadsheet/Style/Font.php b/src/PhpSpreadsheet/Style/Font.php index ad405708..473fe1dc 100644 --- a/src/PhpSpreadsheet/Style/Font.php +++ b/src/PhpSpreadsheet/Style/Font.php @@ -14,56 +14,56 @@ class Font extends Supervisor /** * Font Name. * - * @var string + * @var null|string */ protected $name = 'Calibri'; /** * Font Size. * - * @var float + * @var null|float */ protected $size = 11; /** * Bold. * - * @var bool + * @var null|bool */ protected $bold = false; /** * Italic. * - * @var bool + * @var null|bool */ protected $italic = false; /** * Superscript. * - * @var bool + * @var null|bool */ protected $superscript = false; /** * Subscript. * - * @var bool + * @var null|bool */ protected $subscript = false; /** * Underline. * - * @var string + * @var null|string */ protected $underline = self::UNDERLINE_NONE; /** * Strikethrough. * - * @var bool + * @var null|bool */ protected $strikethrough = false; @@ -75,7 +75,7 @@ class Font extends Supervisor protected $color; /** - * @var int + * @var null|int */ public $colorIndex; @@ -199,7 +199,7 @@ class Font extends Supervisor /** * Get Name. * - * @return string + * @return null|string */ public function getName() { @@ -235,7 +235,7 @@ class Font extends Supervisor /** * Get Size. * - * @return float + * @return null|float */ public function getSize() { @@ -271,7 +271,7 @@ class Font extends Supervisor /** * Get Bold. * - * @return bool + * @return null|bool */ public function getBold() { @@ -307,7 +307,7 @@ class Font extends Supervisor /** * Get Italic. * - * @return bool + * @return null|bool */ public function getItalic() { @@ -343,7 +343,7 @@ class Font extends Supervisor /** * Get Superscript. * - * @return bool + * @return null|bool */ public function getSuperscript() { @@ -377,7 +377,7 @@ class Font extends Supervisor /** * Get Subscript. * - * @return bool + * @return null|bool */ public function getSubscript() { @@ -411,7 +411,7 @@ class Font extends Supervisor /** * Get Underline. * - * @return string + * @return null|string */ public function getUnderline() { @@ -451,7 +451,7 @@ class Font extends Supervisor /** * Get Strikethrough. * - * @return bool + * @return null|bool */ public function getStrikethrough() { diff --git a/src/PhpSpreadsheet/Style/NumberFormat.php b/src/PhpSpreadsheet/Style/NumberFormat.php index 0b761bd3..6235d864 100644 --- a/src/PhpSpreadsheet/Style/NumberFormat.php +++ b/src/PhpSpreadsheet/Style/NumberFormat.php @@ -2,10 +2,6 @@ namespace PhpOffice\PhpSpreadsheet\Style; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PhpOffice\PhpSpreadsheet\Shared\StringHelper; - class NumberFormat extends Supervisor { // Pre-defined formats @@ -68,14 +64,14 @@ class NumberFormat extends Supervisor /** * Format Code. * - * @var string + * @var null|string */ protected $formatCode = self::FORMAT_GENERAL; /** * Built-in format Code. * - * @var string + * @var false|int */ protected $builtInFormatCode = 0; @@ -154,7 +150,7 @@ class NumberFormat extends Supervisor /** * Get Format Code. * - * @return string + * @return null|string */ public function getFormatCode() { @@ -194,7 +190,7 @@ class NumberFormat extends Supervisor /** * Get Built-In Format Code. * - * @return int + * @return false|int */ public function getBuiltInFormatCode() { @@ -389,428 +385,6 @@ class NumberFormat extends Supervisor ); } - /** - * Search/replace values to convert Excel date/time format masks to PHP format masks. - * - * @var array - */ - private static $dateFormatReplacements = [ - // first remove escapes related to non-format characters - '\\' => '', - // 12-hour suffix - 'am/pm' => 'A', - // 4-digit year - 'e' => 'Y', - 'yyyy' => 'Y', - // 2-digit year - 'yy' => 'y', - // first letter of month - no php equivalent - 'mmmmm' => 'M', - // full month name - 'mmmm' => 'F', - // short month name - 'mmm' => 'M', - // mm is minutes if time, but can also be month w/leading zero - // so we try to identify times be the inclusion of a : separator in the mask - // It isn't perfect, but the best way I know how - ':mm' => ':i', - 'mm:' => 'i:', - // month leading zero - 'mm' => 'm', - // month no leading zero - 'm' => 'n', - // full day of week name - 'dddd' => 'l', - // short day of week name - 'ddd' => 'D', - // days leading zero - 'dd' => 'd', - // days no leading zero - 'd' => 'j', - // seconds - 'ss' => 's', - // fractional seconds - no php equivalent - '.s' => '', - ]; - - /** - * Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock). - * - * @var array - */ - private static $dateFormatReplacements24 = [ - 'hh' => 'H', - 'h' => 'G', - ]; - - /** - * Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock). - * - * @var array - */ - private static $dateFormatReplacements12 = [ - 'hh' => 'h', - 'h' => 'g', - ]; - - private static function setLowercaseCallback($matches) - { - return mb_strtolower($matches[0]); - } - - private static function escapeQuotesCallback($matches) - { - return '\\' . implode('\\', str_split($matches[1])); - } - - private static function formatAsDate(&$value, &$format): void - { - // strip off first part containing e.g. [$-F800] or [$USD-409] - // general syntax: [$-] - // language info is in hexadecimal - // strip off chinese part like [DBNum1][$-804] - $format = preg_replace('/^(\[[0-9A-Za-z]*\])*(\[\$[A-Z]*-[0-9A-F]*\])/i', '', $format); - - // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case; - // but we don't want to change any quoted strings - $format = preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', ['self', 'setLowercaseCallback'], $format); - - // Only process the non-quoted blocks for date format characters - $blocks = explode('"', $format); - foreach ($blocks as $key => &$block) { - if ($key % 2 == 0) { - $block = strtr($block, self::$dateFormatReplacements); - if (!strpos($block, 'A')) { - // 24-hour time format - // when [h]:mm format, the [h] should replace to the hours of the value * 24 - if (false !== strpos($block, '[h]')) { - $hours = (int) ($value * 24); - $block = str_replace('[h]', $hours, $block); - - continue; - } - $block = strtr($block, self::$dateFormatReplacements24); - } else { - // 12-hour time format - $block = strtr($block, self::$dateFormatReplacements12); - } - } - } - $format = implode('"', $blocks); - - // escape any quoted characters so that DateTime format() will render them correctly - $format = preg_replace_callback('/"(.*)"/U', ['self', 'escapeQuotesCallback'], $format); - - $dateObj = Date::excelToDateTimeObject($value); - $value = $dateObj->format($format); - } - - private static function formatAsPercentage(&$value, &$format): void - { - if ($format === self::FORMAT_PERCENTAGE) { - $value = round((100 * $value), 0) . '%'; - } else { - if (preg_match('/\.[#0]+/', $format, $m)) { - $s = substr($m[0], 0, 1) . (strlen($m[0]) - 1); - $format = str_replace($m[0], $s, $format); - } - if (preg_match('/^[#0]+/', $format, $m)) { - $format = str_replace($m[0], strlen($m[0]), $format); - } - $format = '%' . str_replace('%', 'f%%', $format); - - $value = sprintf($format, 100 * $value); - } - } - - private static function formatAsFraction(&$value, &$format): void - { - $sign = ($value < 0) ? '-' : ''; - - $integerPart = floor(abs($value)); - $decimalPart = trim(fmod(abs($value), 1), '0.'); - $decimalLength = strlen($decimalPart); - $decimalDivisor = 10 ** $decimalLength; - - $GCD = MathTrig::GCD($decimalPart, $decimalDivisor); - - $adjustedDecimalPart = $decimalPart / $GCD; - $adjustedDecimalDivisor = $decimalDivisor / $GCD; - - if ((strpos($format, '0') !== false)) { - $value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor"; - } elseif ((strpos($format, '#') !== false)) { - if ($integerPart == 0) { - $value = "$sign$adjustedDecimalPart/$adjustedDecimalDivisor"; - } else { - $value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor"; - } - } elseif ((substr($format, 0, 3) == '? ?')) { - if ($integerPart == 0) { - $integerPart = ''; - } - $value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor"; - } else { - $adjustedDecimalPart += $integerPart * $adjustedDecimalDivisor; - $value = "$sign$adjustedDecimalPart/$adjustedDecimalDivisor"; - } - } - - private static function mergeComplexNumberFormatMasks($numbers, $masks) - { - $decimalCount = strlen($numbers[1]); - $postDecimalMasks = []; - - do { - $tempMask = array_pop($masks); - $postDecimalMasks[] = $tempMask; - $decimalCount -= strlen($tempMask); - } while ($decimalCount > 0); - - return [ - implode('.', $masks), - implode('.', array_reverse($postDecimalMasks)), - ]; - } - - private static function processComplexNumberFormatMask($number, $mask) - { - $result = $number; - $maskingBlockCount = preg_match_all('/0+/', $mask, $maskingBlocks, PREG_OFFSET_CAPTURE); - - if ($maskingBlockCount > 1) { - $maskingBlocks = array_reverse($maskingBlocks[0]); - - foreach ($maskingBlocks as $block) { - $divisor = 1 . $block[0]; - $size = strlen($block[0]); - $offset = $block[1]; - - $blockValue = sprintf( - '%0' . $size . 'd', - fmod($number, $divisor) - ); - $number = floor($number / $divisor); - $mask = substr_replace($mask, $blockValue, $offset, $size); - } - if ($number > 0) { - $mask = substr_replace($mask, $number, $offset, 0); - } - $result = $mask; - } - - return $result; - } - - private static function complexNumberFormatMask($number, $mask, $splitOnPoint = true) - { - $sign = ($number < 0.0); - $number = abs($number); - - if ($splitOnPoint && strpos($mask, '.') !== false && strpos($number, '.') !== false) { - $numbers = explode('.', $number); - $masks = explode('.', $mask); - if (count($masks) > 2) { - $masks = self::mergeComplexNumberFormatMasks($numbers, $masks); - } - $result1 = self::complexNumberFormatMask($numbers[0], $masks[0], false); - $result2 = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1]), false)); - - return (($sign) ? '-' : '') . $result1 . '.' . $result2; - } - - $result = self::processComplexNumberFormatMask($number, $mask); - - return (($sign) ? '-' : '') . $result; - } - - private static function formatStraightNumericValue($value, $format, array $matches, $useThousands, $number_regex) - { - $left = $matches[1]; - $dec = $matches[2]; - $right = $matches[3]; - - // minimun width of formatted number (including dot) - $minWidth = strlen($left) + strlen($dec) + strlen($right); - if ($useThousands) { - $value = number_format( - $value, - strlen($right), - StringHelper::getDecimalSeparator(), - StringHelper::getThousandsSeparator() - ); - $value = preg_replace($number_regex, $value, $format); - } else { - if (preg_match('/[0#]E[+-]0/i', $format)) { - // Scientific format - $value = sprintf('%5.2E', $value); - } elseif (preg_match('/0([^\d\.]+)0/', $format) || substr_count($format, '.') > 1) { - if ($value == (int) $value && substr_count($format, '.') === 1) { - $value *= 10 ** strlen(explode('.', $format)[1]); - } - $value = self::complexNumberFormatMask($value, $format); - } else { - $sprintf_pattern = "%0$minWidth." . strlen($right) . 'f'; - $value = sprintf($sprintf_pattern, $value); - $value = preg_replace($number_regex, $value, $format); - } - } - - return $value; - } - - private static function formatAsNumber($value, $format) - { - // The "_" in this string has already been stripped out, - // so this test is never true. Furthermore, testing - // on Excel shows this format uses Euro symbol, not "EUR". - //if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) { - // return 'EUR ' . sprintf('%1.2f', $value); - //} - - // Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols - $format = str_replace(['"', '*'], '', $format); - - // Find out if we need thousands separator - // This is indicated by a comma enclosed by a digit placeholder: - // #,# or 0,0 - $useThousands = preg_match('/(#,#|0,0)/', $format); - if ($useThousands) { - $format = preg_replace('/0,0/', '00', $format); - $format = preg_replace('/#,#/', '##', $format); - } - - // Scale thousands, millions,... - // This is indicated by a number of commas after a digit placeholder: - // #, or 0.0,, - $scale = 1; // same as no scale - $matches = []; - if (preg_match('/(#|0)(,+)/', $format, $matches)) { - $scale = 1000 ** strlen($matches[2]); - - // strip the commas - $format = preg_replace('/0,+/', '0', $format); - $format = preg_replace('/#,+/', '#', $format); - } - - if (preg_match('/#?.*\?\/\?/', $format, $m)) { - if ($value != (int) $value) { - self::formatAsFraction($value, $format); - } - } else { - // Handle the number itself - - // scale number - $value = $value / $scale; - // Strip # - $format = preg_replace('/\\#/', '0', $format); - // Remove locale code [$-###] - $format = preg_replace('/\[\$\-.*\]/', '', $format); - - $n = '/\\[[^\\]]+\\]/'; - $m = preg_replace($n, '', $format); - $number_regex = '/(0+)(\\.?)(0*)/'; - if (preg_match($number_regex, $m, $matches)) { - $value = self::formatStraightNumericValue($value, $format, $matches, $useThousands, $number_regex); - } - } - - if (preg_match('/\[\$(.*)\]/u', $format, $m)) { - // Currency or Accounting - $currencyCode = $m[1]; - [$currencyCode] = explode('-', $currencyCode); - if ($currencyCode == '') { - $currencyCode = StringHelper::getCurrencyCode(); - } - $value = preg_replace('/\[\$([^\]]*)\]/u', $currencyCode, $value); - } - - return $value; - } - - private static function splitFormatCompare($value, $cond, $val, $dfcond, $dfval) - { - if (!$cond) { - $cond = $dfcond; - $val = $dfval; - } - switch ($cond) { - case '>': - return $value > $val; - - case '<': - return $value < $val; - - case '<=': - return $value <= $val; - - case '<>': - return $value != $val; - - case '=': - return $value == $val; - } - - return $value >= $val; - } - - private static function splitFormat($sections, $value) - { - // Extract the relevant section depending on whether number is positive, negative, or zero? - // Text not supported yet. - // Here is how the sections apply to various values in Excel: - // 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT] - // 2 sections: [POSITIVE/ZERO/TEXT] [NEGATIVE] - // 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO] - // 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT] - $cnt = count($sections); - $color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . ')\\]/'; - $cond_regex = '/\\[(>|>=|<|<=|=|<>)([+-]?\\d+([.]\\d+)?)\\]/'; - $colors = ['', '', '', '', '']; - $condops = ['', '', '', '', '']; - $condvals = [0, 0, 0, 0, 0]; - for ($idx = 0; $idx < $cnt; ++$idx) { - if (preg_match($color_regex, $sections[$idx], $matches)) { - $colors[$idx] = $matches[0]; - $sections[$idx] = preg_replace($color_regex, '', $sections[$idx]); - } - if (preg_match($cond_regex, $sections[$idx], $matches)) { - $condops[$idx] = $matches[1]; - $condvals[$idx] = $matches[2]; - $sections[$idx] = preg_replace($cond_regex, '', $sections[$idx]); - } - } - $color = $colors[0]; - $format = $sections[0]; - $absval = $value; - switch ($cnt) { - case 2: - $absval = abs($value); - if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>=', 0)) { - $color = $colors[1]; - $format = $sections[1]; - } - - break; - case 3: - case 4: - $absval = abs($value); - if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>', 0)) { - if (self::splitFormatCompare($value, $condops[1], $condvals[1], '<', 0)) { - $color = $colors[1]; - $format = $sections[1]; - } else { - $color = $colors[2]; - $format = $sections[2]; - } - } - - break; - } - - return [$color, $format, $absval]; - } - /** * Convert a value in a pre-defined format to a PHP string. * @@ -822,53 +396,7 @@ class NumberFormat extends Supervisor */ public static function toFormattedString($value, $format, $callBack = null) { - // For now we do not treat strings although section 4 of a format code affects strings - if (!is_numeric($value)) { - return $value; - } - - // For 'General' format code, we just pass the value although this is not entirely the way Excel does it, - // it seems to round numbers to a total of 10 digits. - if (($format === self::FORMAT_GENERAL) || ($format === self::FORMAT_TEXT)) { - return $value; - } - - // Convert any other escaped characters to quoted strings, e.g. (\T to "T") - $format = preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/u', '"${2}"', $format); - - // Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal) - $sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format); - - [$colors, $format, $value] = self::splitFormat($sections, $value); - - // In Excel formats, "_" is used to add spacing, - // The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space - $format = preg_replace('/_./', ' ', $format); - - // Let's begin inspecting the format and converting the value to a formatted string - - // Check for date/time characters (not inside quotes) - if (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format, $matches)) { - // datetime format - self::formatAsDate($value, $format); - } else { - if (substr($format, 0, 1) === '"' && substr($format, -1, 1) === '"') { - $value = substr($format, 1, -1); - } elseif (preg_match('/%$/', $format)) { - // % number format - self::formatAsPercentage($value, $format); - } else { - $value = self::formatAsNumber($value, $format); - } - } - - // Additional formatting provided by callback function - if ($callBack !== null) { - [$writerInstance, $function] = $callBack; - $value = $writerInstance->$function($value, $colors); - } - - return $value; + return NumberFormat\Formatter::toFormattedString($value, $format, $callBack); } protected function exportArray1(): array diff --git a/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php b/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php new file mode 100644 index 00000000..7988143c --- /dev/null +++ b/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php @@ -0,0 +1,12 @@ + '', + // 12-hour suffix + 'am/pm' => 'A', + // 4-digit year + 'e' => 'Y', + 'yyyy' => 'Y', + // 2-digit year + 'yy' => 'y', + // first letter of month - no php equivalent + 'mmmmm' => 'M', + // full month name + 'mmmm' => 'F', + // short month name + 'mmm' => 'M', + // mm is minutes if time, but can also be month w/leading zero + // so we try to identify times be the inclusion of a : separator in the mask + // It isn't perfect, but the best way I know how + ':mm' => ':i', + 'mm:' => 'i:', + // month leading zero + 'mm' => 'm', + // month no leading zero + 'm' => 'n', + // full day of week name + 'dddd' => 'l', + // short day of week name + 'ddd' => 'D', + // days leading zero + 'dd' => 'd', + // days no leading zero + 'd' => 'j', + // seconds + 'ss' => 's', + // fractional seconds - no php equivalent + '.s' => '', + ]; + + /** + * Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock). + * + * @var array + */ + private static $dateFormatReplacements24 = [ + 'hh' => 'H', + 'h' => 'G', + ]; + + /** + * Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock). + * + * @var array + */ + private static $dateFormatReplacements12 = [ + 'hh' => 'h', + 'h' => 'g', + ]; + + public static function format($value, string $format): string + { + // strip off first part containing e.g. [$-F800] or [$USD-409] + // general syntax: [$-] + // language info is in hexadecimal + // strip off chinese part like [DBNum1][$-804] + $format = preg_replace('/^(\[DBNum\d\])*(\[\$[^\]]*\])/i', '', $format); + + // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case; + // but we don't want to change any quoted strings + $format = preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', ['self', 'setLowercaseCallback'], $format); + + // Only process the non-quoted blocks for date format characters + $blocks = explode('"', $format); + foreach ($blocks as $key => &$block) { + if ($key % 2 == 0) { + $block = strtr($block, self::$dateFormatReplacements); + if (!strpos($block, 'A')) { + // 24-hour time format + // when [h]:mm format, the [h] should replace to the hours of the value * 24 + if (false !== strpos($block, '[h]')) { + $hours = (int) ($value * 24); + $block = str_replace('[h]', $hours, $block); + + continue; + } + $block = strtr($block, self::$dateFormatReplacements24); + } else { + // 12-hour time format + $block = strtr($block, self::$dateFormatReplacements12); + } + } + } + $format = implode('"', $blocks); + + // escape any quoted characters so that DateTime format() will render them correctly + $format = preg_replace_callback('/"(.*)"/U', ['self', 'escapeQuotesCallback'], $format); + + $dateObj = Date::excelToDateTimeObject($value); + // If the colon preceding minute had been quoted, as happens in + // Excel 2003 XML formats, m will not have been changed to i above. + // Change it now. + $format = \preg_replace('/\\\\:m/', ':i', $format); + + return $dateObj->format($format); + } + + private static function setLowercaseCallback($matches): string + { + return mb_strtolower($matches[0]); + } + + private static function escapeQuotesCallback($matches): string + { + return '\\' . implode('\\', str_split($matches[1])); + } +} diff --git a/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php b/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php new file mode 100644 index 00000000..ef756d7b --- /dev/null +++ b/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php @@ -0,0 +1,162 @@ +': + return $value > $val; + + case '<': + return $value < $val; + + case '<=': + return $value <= $val; + + case '<>': + return $value != $val; + + case '=': + return $value == $val; + } + + return $value >= $val; + } + + private static function splitFormat($sections, $value) + { + // Extract the relevant section depending on whether number is positive, negative, or zero? + // Text not supported yet. + // Here is how the sections apply to various values in Excel: + // 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT] + // 2 sections: [POSITIVE/ZERO/TEXT] [NEGATIVE] + // 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO] + // 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT] + $cnt = count($sections); + $color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . ')\\]/mui'; + $cond_regex = '/\\[(>|>=|<|<=|=|<>)([+-]?\\d+([.]\\d+)?)\\]/'; + $colors = ['', '', '', '', '']; + $condops = ['', '', '', '', '']; + $condvals = [0, 0, 0, 0, 0]; + for ($idx = 0; $idx < $cnt; ++$idx) { + if (preg_match($color_regex, $sections[$idx], $matches)) { + $colors[$idx] = $matches[0]; + $sections[$idx] = preg_replace($color_regex, '', $sections[$idx]); + } + if (preg_match($cond_regex, $sections[$idx], $matches)) { + $condops[$idx] = $matches[1]; + $condvals[$idx] = $matches[2]; + $sections[$idx] = preg_replace($cond_regex, '', $sections[$idx]); + } + } + $color = $colors[0]; + $format = $sections[0]; + $absval = $value; + switch ($cnt) { + case 2: + $absval = abs($value); + if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>=', 0)) { + $color = $colors[1]; + $format = $sections[1]; + } + + break; + case 3: + case 4: + $absval = abs($value); + if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>', 0)) { + if (self::splitFormatCompare($value, $condops[1], $condvals[1], '<', 0)) { + $color = $colors[1]; + $format = $sections[1]; + } else { + $color = $colors[2]; + $format = $sections[2]; + } + } + + break; + } + + return [$color, $format, $absval]; + } + + /** + * Convert a value in a pre-defined format to a PHP string. + * + * @param mixed $value Value to format + * @param string $format Format code, see = NumberFormat::FORMAT_* + * @param array $callBack Callback function for additional formatting of string + * + * @return string Formatted string + */ + public static function toFormattedString($value, $format, $callBack = null) + { + // For now we do not treat strings although section 4 of a format code affects strings + if (!is_numeric($value)) { + return $value; + } + + // For 'General' format code, we just pass the value although this is not entirely the way Excel does it, + // it seems to round numbers to a total of 10 digits. + if (($format === NumberFormat::FORMAT_GENERAL) || ($format === NumberFormat::FORMAT_TEXT)) { + return $value; + } + + $format = preg_replace_callback( + '/(["])(?:(?=(\\\\?))\\2.)*?\\1/u', + function ($matches) { + return str_replace('.', chr(0x00), $matches[0]); + }, + $format + ); + + // Convert any other escaped characters to quoted strings, e.g. (\T to "T") + $format = preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/ui', '"${2}"', $format); + + // Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal) + $sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format); + + [$colors, $format, $value] = self::splitFormat($sections, $value); + + // In Excel formats, "_" is used to add spacing, + // The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space + $format = preg_replace('/_/ui', ' ', $format); + + // Let's begin inspecting the format and converting the value to a formatted string + + // Check for date/time characters (not inside quotes) + if (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format, $matches)) { + // datetime format + $value = DateFormatter::format($value, $format); + } else { + if (substr($format, 0, 1) === '"' && substr($format, -1, 1) === '"' && substr_count($format, '"') === 2) { + $value = substr($format, 1, -1); + } elseif (preg_match('/[0#, ]%/', $format)) { + // % number format + $value = PercentageFormatter::format($value, $format); + } else { + $value = NumberFormatter::format($value, $format); + } + } + + // Additional formatting provided by callback function + if ($callBack !== null) { + [$writerInstance, $function] = $callBack; + $value = $writerInstance->$function($value, $colors); + } + + $value = str_replace(chr(0x00), '.', $value); + + return $value; + } +} diff --git a/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php b/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php new file mode 100644 index 00000000..48d927f2 --- /dev/null +++ b/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php @@ -0,0 +1,45 @@ + 0); + + return [ + implode('.', $masks), + implode('.', array_reverse($postDecimalMasks)), + ]; + } + + private static function processComplexNumberFormatMask($number, $mask): string + { + $result = $number; + $maskingBlockCount = preg_match_all('/0+/', $mask, $maskingBlocks, PREG_OFFSET_CAPTURE); + + if ($maskingBlockCount > 1) { + $maskingBlocks = array_reverse($maskingBlocks[0]); + + $offset = 0; + foreach ($maskingBlocks as $block) { + $size = strlen($block[0]); + $divisor = 10 ** $size; + $offset = $block[1]; + + $blockValue = sprintf("%0{$size}d", fmod($number, $divisor)); + $number = floor($number / $divisor); + $mask = substr_replace($mask, $blockValue, $offset, $size); + } + if ($number > 0) { + $mask = substr_replace($mask, $number, $offset, 0); + } + $result = $mask; + } + + return $result; + } + + private static function complexNumberFormatMask($number, $mask, $splitOnPoint = true): string + { + $sign = ($number < 0.0) ? '-' : ''; + $number = abs($number); + + if ($splitOnPoint && strpos($mask, '.') !== false && strpos($number, '.') !== false) { + $numbers = explode('.', $number); + $masks = explode('.', $mask); + if (count($masks) > 2) { + $masks = self::mergeComplexNumberFormatMasks($numbers, $masks); + } + $integerPart = self::complexNumberFormatMask($numbers[0], $masks[0], false); + $decimalPart = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1]), false)); + + return "{$sign}{$integerPart}.{$decimalPart}"; + } + + $result = self::processComplexNumberFormatMask($number, $mask); + + return "{$sign}{$result}"; + } + + private static function formatStraightNumericValue($value, $format, array $matches, $useThousands): string + { + $left = $matches[1]; + $dec = $matches[2]; + $right = $matches[3]; + + // minimun width of formatted number (including dot) + $minWidth = strlen($left) + strlen($dec) + strlen($right); + if ($useThousands) { + $value = number_format( + $value, + strlen($right), + StringHelper::getDecimalSeparator(), + StringHelper::getThousandsSeparator() + ); + + return preg_replace(self::NUMBER_REGEX, $value, $format); + } + + if (preg_match('/[0#]E[+-]0/i', $format)) { + // Scientific format + return sprintf('%5.2E', $value); + } elseif (preg_match('/0([^\d\.]+)0/', $format) || substr_count($format, '.') > 1) { + if ($value == (int) $value && substr_count($format, '.') === 1) { + $value *= 10 ** strlen(explode('.', $format)[1]); + } + + return self::complexNumberFormatMask($value, $format); + } + + $sprintf_pattern = "%0$minWidth." . strlen($right) . 'f'; + $value = sprintf($sprintf_pattern, $value); + + return preg_replace(self::NUMBER_REGEX, $value, $format); + } + + public static function format($value, $format): string + { + // The "_" in this string has already been stripped out, + // so this test is never true. Furthermore, testing + // on Excel shows this format uses Euro symbol, not "EUR". + //if ($format === NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE) { + // return 'EUR ' . sprintf('%1.2f', $value); + //} + + // Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols + $format = str_replace(['"', '*'], '', $format); + + // Find out if we need thousands separator + // This is indicated by a comma enclosed by a digit placeholder: + // #,# or 0,0 + $useThousands = preg_match('/(#,#|0,0)/', $format); + if ($useThousands) { + $format = preg_replace('/0,0/', '00', $format); + $format = preg_replace('/#,#/', '##', $format); + } + + // Scale thousands, millions,... + // This is indicated by a number of commas after a digit placeholder: + // #, or 0.0,, + $scale = 1; // same as no scale + $matches = []; + if (preg_match('/(#|0)(,+)/', $format, $matches)) { + $scale = 1000 ** strlen($matches[2]); + + // strip the commas + $format = preg_replace('/0,+/', '0', $format); + $format = preg_replace('/#,+/', '#', $format); + } + + if (preg_match('/#?.*\?\/\?/', $format, $m)) { + if ($value != (int) $value) { + $value = FractionFormatter::format($value, $format); + } + } else { + // Handle the number itself + + // scale number + $value = $value / $scale; + // Strip # + $format = preg_replace('/\\#/', '0', $format); + // Remove locale code [$-###] + $format = preg_replace('/\[\$\-.*\]/', '', $format); + + $n = '/\\[[^\\]]+\\]/'; + $m = preg_replace($n, '', $format); + if (preg_match(self::NUMBER_REGEX, $m, $matches)) { + $value = self::formatStraightNumericValue($value, $format, $matches, $useThousands); + } + } + + if (preg_match('/\[\$(.*)\]/u', $format, $m)) { + // Currency or Accounting + $currencyCode = $m[1]; + [$currencyCode] = explode('-', $currencyCode); + if ($currencyCode == '') { + $currencyCode = StringHelper::getCurrencyCode(); + } + $value = preg_replace('/\[\$([^\]]*)\]/u', $currencyCode, $value); + } + + return $value; + } +} diff --git a/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php b/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php new file mode 100644 index 00000000..cf1731ec --- /dev/null +++ b/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php @@ -0,0 +1,42 @@ +font = new Font($isSupervisor, $isConditional); $this->fill = new Fill($isSupervisor, $isConditional); - $this->borders = new Borders($isSupervisor, $isConditional); + $this->borders = new Borders($isSupervisor); $this->alignment = new Alignment($isSupervisor, $isConditional); $this->numberFormat = new NumberFormat($isSupervisor, $isConditional); $this->protection = new Protection($isSupervisor, $isConditional); @@ -202,18 +202,17 @@ class Style extends Supervisor // Calculate range outer borders $rangeStart = Coordinate::coordinateFromString($rangeA); $rangeEnd = Coordinate::coordinateFromString($rangeB); + $rangeStartIndexes = Coordinate::indexesFromString($rangeA); + $rangeEndIndexes = Coordinate::indexesFromString($rangeB); - // Translate column into index - $rangeStart0 = $rangeStart[0]; - $rangeEnd0 = $rangeEnd[0]; - $rangeStart[0] = Coordinate::columnIndexFromString($rangeStart[0]); - $rangeEnd[0] = Coordinate::columnIndexFromString($rangeEnd[0]); + $columnStart = $rangeStart[0]; + $columnEnd = $rangeEnd[0]; // Make sure we can loop upwards on rows and columns - if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) { - $tmp = $rangeStart; - $rangeStart = $rangeEnd; - $rangeEnd = $tmp; + if ($rangeStartIndexes[0] > $rangeEndIndexes[0] && $rangeStartIndexes[1] > $rangeEndIndexes[1]) { + $tmp = $rangeStartIndexes; + $rangeStartIndexes = $rangeEndIndexes; + $rangeEndIndexes = $tmp; } // ADVANCED MODE: @@ -249,19 +248,19 @@ class Style extends Supervisor unset($pStyles['borders']['inside']); // not needed any more } // width and height characteristics of selection, 1, 2, or 3 (for 3 or more) - $xMax = min($rangeEnd[0] - $rangeStart[0] + 1, 3); - $yMax = min($rangeEnd[1] - $rangeStart[1] + 1, 3); + $xMax = min($rangeEndIndexes[0] - $rangeStartIndexes[0] + 1, 3); + $yMax = min($rangeEndIndexes[1] - $rangeStartIndexes[1] + 1, 3); // loop through up to 3 x 3 = 9 regions for ($x = 1; $x <= $xMax; ++$x) { // start column index for region $colStart = ($x == 3) ? - Coordinate::stringFromColumnIndex($rangeEnd[0]) - : Coordinate::stringFromColumnIndex($rangeStart[0] + $x - 1); + Coordinate::stringFromColumnIndex($rangeEndIndexes[0]) + : Coordinate::stringFromColumnIndex($rangeStartIndexes[0] + $x - 1); // end column index for region $colEnd = ($x == 1) ? - Coordinate::stringFromColumnIndex($rangeStart[0]) - : Coordinate::stringFromColumnIndex($rangeEnd[0] - $xMax + $x); + Coordinate::stringFromColumnIndex($rangeStartIndexes[0]) + : Coordinate::stringFromColumnIndex($rangeEndIndexes[0] - $xMax + $x); for ($y = 1; $y <= $yMax; ++$y) { // which edges are touching the region @@ -285,11 +284,11 @@ class Style extends Supervisor // start row index for region $rowStart = ($y == 3) ? - $rangeEnd[1] : $rangeStart[1] + $y - 1; + $rangeEndIndexes[1] : $rangeStartIndexes[1] + $y - 1; // end row index for region $rowEnd = ($y == 1) ? - $rangeStart[1] : $rangeEnd[1] - $yMax + $y; + $rangeStartIndexes[1] : $rangeEndIndexes[1] - $yMax + $y; // build range for region $range = $colStart . $rowStart . ':' . $colEnd . $rowEnd; @@ -349,52 +348,11 @@ class Style extends Supervisor } // First loop through columns, rows, or cells to find out which styles are affected by this operation - switch ($selectionType) { - case 'COLUMN': - $oldXfIndexes = []; - for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { - $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true; - } - foreach ($this->getActiveSheet()->getColumnIterator($rangeStart0, $rangeEnd0) as $columnIterator) { - $cellIterator = $columnIterator->getCellIterator(); - $cellIterator->setIterateOnlyExistingCells(true); - foreach ($cellIterator as $columnCell) { - $columnCell->getStyle()->applyFromArray($pStyles); - } - } - - break; - case 'ROW': - $oldXfIndexes = []; - for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { - if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() == null) { - $oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style - } else { - $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true; - } - } - foreach ($this->getActiveSheet()->getRowIterator((int) $rangeStart[1], (int) $rangeEnd[1]) as $rowIterator) { - $cellIterator = $rowIterator->getCellIterator(); - $cellIterator->setIterateOnlyExistingCells(true); - foreach ($cellIterator as $rowCell) { - $rowCell->getStyle()->applyFromArray($pStyles); - } - } - - break; - case 'CELL': - $oldXfIndexes = []; - for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { - for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { - $oldXfIndexes[$this->getActiveSheet()->getCellByColumnAndRow($col, $row)->getXfIndex()] = true; - } - } - - break; - } + $oldXfIndexes = $this->getOldXfIndexes($selectionType, $rangeStartIndexes, $rangeEndIndexes, $columnStart, $columnEnd, $pStyles); // clone each of the affected styles, apply the style array, and add the new styles to the workbook $workbook = $this->getActiveSheet()->getParent(); + $newXfIndexes = []; foreach ($oldXfIndexes as $oldXfIndex => $dummy) { $style = $workbook->getCellXfByIndex($oldXfIndex); $newStyle = clone $style; @@ -413,7 +371,7 @@ class Style extends Supervisor // Loop through columns, rows, or cells again and update the XF index switch ($selectionType) { case 'COLUMN': - for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { + for ($col = $rangeStartIndexes[0]; $col <= $rangeEndIndexes[0]; ++$col) { $columnDimension = $this->getActiveSheet()->getColumnDimensionByColumn($col); $oldXfIndex = $columnDimension->getXfIndex(); $columnDimension->setXfIndex($newXfIndexes[$oldXfIndex]); @@ -421,17 +379,17 @@ class Style extends Supervisor break; case 'ROW': - for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { + for ($row = $rangeStartIndexes[1]; $row <= $rangeEndIndexes[1]; ++$row) { $rowDimension = $this->getActiveSheet()->getRowDimension($row); - $oldXfIndex = $rowDimension->getXfIndex() === null ? - 0 : $rowDimension->getXfIndex(); // row without explicit style should be formatted based on default style + // row without explicit style should be formatted based on default style + $oldXfIndex = $rowDimension->getXfIndex() ?? 0; $rowDimension->setXfIndex($newXfIndexes[$oldXfIndex]); } break; case 'CELL': - for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { - for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { + for ($col = $rangeStartIndexes[0]; $col <= $rangeEndIndexes[0]; ++$col) { + for ($row = $rangeStartIndexes[1]; $row <= $rangeEndIndexes[1]; ++$row) { $cell = $this->getActiveSheet()->getCellByColumnAndRow($col, $row); $oldXfIndex = $cell->getXfIndex(); $cell->setXfIndex($newXfIndexes[$oldXfIndex]); @@ -468,6 +426,57 @@ class Style extends Supervisor return $this; } + private function getOldXfIndexes(string $selectionType, array $rangeStart, array $rangeEnd, string $columnStart, string $columnEnd, array $pStyles): array + { + $oldXfIndexes = []; + switch ($selectionType) { + case 'COLUMN': + for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { + $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true; + } + foreach ($this->getActiveSheet()->getColumnIterator($columnStart, $columnEnd) as $columnIterator) { + $cellIterator = $columnIterator->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(true); + foreach ($cellIterator as $columnCell) { + if ($columnCell !== null) { + $columnCell->getStyle()->applyFromArray($pStyles); + } + } + } + + break; + case 'ROW': + for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { + if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() === null) { + $oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style + } else { + $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true; + } + } + foreach ($this->getActiveSheet()->getRowIterator((int) $rangeStart[1], (int) $rangeEnd[1]) as $rowIterator) { + $cellIterator = $rowIterator->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(true); + foreach ($cellIterator as $rowCell) { + if ($rowCell !== null) { + $rowCell->getStyle()->applyFromArray($pStyles); + } + } + } + + break; + case 'CELL': + for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { + for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { + $oldXfIndexes[$this->getActiveSheet()->getCellByColumnAndRow($col, $row)->getXfIndex()] = true; + } + } + + break; + } + + return $oldXfIndexes; + } + /** * Get Fill. * diff --git a/src/PhpSpreadsheet/Worksheet/AutoFilter.php b/src/PhpSpreadsheet/Worksheet/AutoFilter.php index c2ded195..1a710e40 100644 --- a/src/PhpSpreadsheet/Worksheet/AutoFilter.php +++ b/src/PhpSpreadsheet/Worksheet/AutoFilter.php @@ -3,7 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; @@ -14,7 +14,7 @@ class AutoFilter /** * Autofilter Worksheet. * - * @var Worksheet + * @var null|Worksheet */ private $workSheet; @@ -47,7 +47,7 @@ class AutoFilter /** * Get AutoFilter Parent Worksheet. * - * @return Worksheet + * @return null|Worksheet */ public function getParent() { @@ -472,7 +472,7 @@ class AutoFilter $val = $maxVal = null; $ruleValues = []; - $baseDate = DateTime::DATENOW(); + $baseDate = DateTimeExcel\Now::funcNow(); // Calculate start/end dates for the required date range based on current date switch ($dynamicRuleType) { case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK: @@ -595,7 +595,9 @@ class AutoFilter sort($dataValues); } - return array_pop(array_slice($dataValues, 0, $ruleValue)); + $slice = array_slice($dataValues, 0, $ruleValue); + + return array_pop($slice); } /** @@ -777,6 +779,9 @@ class AutoFilter case AutoFilter\Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER: $ruleValues = []; $dataRowCount = $rangeEnd[1] - $rangeStart[1]; + $toptenRuleType = null; + $ruleValue = 0; + $ruleOperator = null; foreach ($rules as $rule) { // We should only ever have one Dynamic Filter Rule anyway $toptenRuleType = $rule->getGrouping(); @@ -786,10 +791,10 @@ class AutoFilter if ($ruleOperator === AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) { $ruleValue = floor($ruleValue * ($dataRowCount / 100)); } - if ($ruleValue < 1) { + if (!is_array($ruleValue) && $ruleValue < 1) { $ruleValue = 1; } - if ($ruleValue > 500) { + if (!is_array($ruleValue) && $ruleValue > 500) { $ruleValue = 500; } diff --git a/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php b/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php index 09584a7a..8ec0ca3b 100644 --- a/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php +++ b/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php @@ -49,7 +49,7 @@ class Column /** * Autofilter. * - * @var AutoFilter + * @var null|AutoFilter */ private $parent; @@ -77,14 +77,14 @@ class Column /** * Autofilter Column Rules. * - * @var array of Column\Rule + * @var Column\Rule[] */ private $ruleset = []; /** * Autofilter Column Dynamic Attributes. * - * @var array of mixed + * @var mixed[] */ private $attributes = []; @@ -133,7 +133,7 @@ class Column /** * Get this Column's AutoFilter Parent. * - * @return AutoFilter + * @return null|AutoFilter */ public function getParent() { @@ -256,7 +256,7 @@ class Column * * @param string $pName Attribute Name * - * @return string + * @return null|string */ public function getAttribute($pName) { diff --git a/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php b/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php index 1aacb0cb..330a1641 100644 --- a/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php +++ b/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php @@ -217,7 +217,7 @@ class Rule /** * Autofilter Rule Value. * - * @var string + * @var string|string[] */ private $value = ''; @@ -276,7 +276,7 @@ class Rule /** * Get AutoFilter Rule Value. * - * @return string + * @return string|string[] */ public function getValue() { diff --git a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php index be2f23df..5829671b 100644 --- a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php +++ b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php @@ -39,7 +39,7 @@ class BaseDrawing implements IComparable /** * Worksheet. * - * @var Worksheet + * @var null|Worksheet */ protected $worksheet; @@ -190,7 +190,7 @@ class BaseDrawing implements IComparable /** * Get Worksheet. * - * @return Worksheet + * @return null|Worksheet */ public function getWorksheet() { @@ -330,7 +330,7 @@ class BaseDrawing implements IComparable // Resize proportional? if ($this->resizeProportional && $pValue != 0) { $ratio = $this->height / ($this->width != 0 ? $this->width : 1); - $this->height = round($ratio * $pValue); + $this->height = (int) round($ratio * $pValue); } // Set width @@ -361,7 +361,7 @@ class BaseDrawing implements IComparable // Resize proportional? if ($this->resizeProportional && $pValue != 0) { $ratio = $this->width / ($this->height != 0 ? $this->height : 1); - $this->width = round($ratio * $pValue); + $this->width = (int) round($ratio * $pValue); } // Set height @@ -392,10 +392,10 @@ class BaseDrawing implements IComparable $yratio = $height / ($this->height != 0 ? $this->height : 1); if ($this->resizeProportional && !($width == 0 || $height == 0)) { if (($xratio * $this->height) < $height) { - $this->height = ceil($xratio * $this->height); + $this->height = (int) ceil($xratio * $this->height); $this->width = $width; } else { - $this->width = ceil($yratio * $this->width); + $this->width = (int) ceil($yratio * $this->width); $this->height = $height; } } else { diff --git a/src/PhpSpreadsheet/Worksheet/CellIterator.php b/src/PhpSpreadsheet/Worksheet/CellIterator.php index 45f76cab..31f6ae65 100644 --- a/src/PhpSpreadsheet/Worksheet/CellIterator.php +++ b/src/PhpSpreadsheet/Worksheet/CellIterator.php @@ -25,15 +25,14 @@ abstract class CellIterator implements Iterator */ public function __destruct() { + // @phpstan-ignore-next-line $this->worksheet = null; } /** * Get loop only existing cells. - * - * @return bool */ - public function getIterateOnlyExistingCells() + public function getIterateOnlyExistingCells(): bool { return $this->onlyExistingCells; } @@ -45,10 +44,8 @@ abstract class CellIterator implements Iterator /** * Set the iterator to loop only existing cells. - * - * @param bool $value */ - public function setIterateOnlyExistingCells($value): void + public function setIterateOnlyExistingCells(bool $value): void { $this->onlyExistingCells = (bool) $value; diff --git a/src/PhpSpreadsheet/Worksheet/Column.php b/src/PhpSpreadsheet/Worksheet/Column.php index 410e8073..8abbabe5 100644 --- a/src/PhpSpreadsheet/Worksheet/Column.php +++ b/src/PhpSpreadsheet/Worksheet/Column.php @@ -36,15 +36,14 @@ class Column */ public function __destruct() { + // @phpstan-ignore-next-line $this->parent = null; } /** * Get column index as string eg: 'A'. - * - * @return string */ - public function getColumnIndex() + public function getColumnIndex(): string { return $this->columnIndex; } diff --git a/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php b/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php index 714ee7ce..65772114 100644 --- a/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php +++ b/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; @@ -17,7 +18,7 @@ class ColumnCellIterator extends CellIterator /** * Column index. * - * @var string + * @var int */ private $columnIndex; @@ -59,7 +60,7 @@ class ColumnCellIterator extends CellIterator * * @return $this */ - public function resetStart($startRow = 1) + public function resetStart(int $startRow = 1) { $this->startRow = $startRow; $this->adjustForExistingOnlyRange(); @@ -77,7 +78,7 @@ class ColumnCellIterator extends CellIterator */ public function resetEnd($endRow = null) { - $this->endRow = ($endRow) ? $endRow : $this->worksheet->getHighestRow(); + $this->endRow = $endRow ?: $this->worksheet->getHighestRow(); $this->adjustForExistingOnlyRange(); return $this; @@ -90,7 +91,7 @@ class ColumnCellIterator extends CellIterator * * @return $this */ - public function seek($row = 1) + public function seek(int $row = 1) { if ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $row))) { throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); @@ -113,20 +114,16 @@ class ColumnCellIterator extends CellIterator /** * Return the current cell in this worksheet column. - * - * @return \PhpOffice\PhpSpreadsheet\Cell\Cell */ - public function current() + public function current(): ?Cell { return $this->worksheet->getCellByColumnAndRow($this->columnIndex, $this->currentRow); } /** * Return the current iterator key. - * - * @return int */ - public function key() + public function key(): int { return $this->currentRow; } @@ -161,10 +158,8 @@ class ColumnCellIterator extends CellIterator /** * Indicate if more rows exist in the worksheet range of rows that we're iterating. - * - * @return bool */ - public function valid() + public function valid(): bool { return $this->currentRow <= $this->endRow && $this->currentRow >= $this->startRow; } diff --git a/src/PhpSpreadsheet/Worksheet/ColumnDimension.php b/src/PhpSpreadsheet/Worksheet/ColumnDimension.php index 4e87a344..12b1efdf 100644 --- a/src/PhpSpreadsheet/Worksheet/ColumnDimension.php +++ b/src/PhpSpreadsheet/Worksheet/ColumnDimension.php @@ -43,10 +43,8 @@ class ColumnDimension extends Dimension /** * Get column index as string eg: 'A'. - * - * @return string */ - public function getColumnIndex() + public function getColumnIndex(): string { return $this->columnIndex; } @@ -54,23 +52,19 @@ class ColumnDimension extends Dimension /** * Set column index as string eg: 'A'. * - * @param string $pValue - * * @return $this */ - public function setColumnIndex($pValue) + public function setColumnIndex(string $index) { - $this->columnIndex = $pValue; + $this->columnIndex = $index; return $this; } /** * Get Width. - * - * @return float */ - public function getWidth() + public function getWidth(): float { return $this->width; } @@ -78,23 +72,19 @@ class ColumnDimension extends Dimension /** * Set Width. * - * @param float $pValue - * * @return $this */ - public function setWidth($pValue) + public function setWidth(float $width) { - $this->width = $pValue; + $this->width = $width; return $this; } /** * Get Auto Size. - * - * @return bool */ - public function getAutoSize() + public function getAutoSize(): bool { return $this->autoSize; } @@ -102,13 +92,11 @@ class ColumnDimension extends Dimension /** * Set Auto Size. * - * @param bool $pValue - * * @return $this */ - public function setAutoSize($pValue) + public function setAutoSize(bool $autosizeEnabled) { - $this->autoSize = $pValue; + $this->autoSize = $autosizeEnabled; return $this; } diff --git a/src/PhpSpreadsheet/Worksheet/ColumnIterator.php b/src/PhpSpreadsheet/Worksheet/ColumnIterator.php index d0bb20cc..0651a7a4 100644 --- a/src/PhpSpreadsheet/Worksheet/ColumnIterator.php +++ b/src/PhpSpreadsheet/Worksheet/ColumnIterator.php @@ -57,6 +57,7 @@ class ColumnIterator implements Iterator */ public function __destruct() { + // @phpstan-ignore-next-line $this->worksheet = null; } @@ -67,11 +68,13 @@ class ColumnIterator implements Iterator * * @return $this */ - public function resetStart($startColumn = 'A') + public function resetStart(string $startColumn = 'A') { $startColumnIndex = Coordinate::columnIndexFromString($startColumn); if ($startColumnIndex > Coordinate::columnIndexFromString($this->worksheet->getHighestColumn())) { - throw new Exception("Start column ({$startColumn}) is beyond highest column ({$this->worksheet->getHighestColumn()})"); + throw new Exception( + "Start column ({$startColumn}) is beyond highest column ({$this->worksheet->getHighestColumn()})" + ); } $this->startColumnIndex = $startColumnIndex; @@ -92,7 +95,7 @@ class ColumnIterator implements Iterator */ public function resetEnd($endColumn = null) { - $endColumn = $endColumn ? $endColumn : $this->worksheet->getHighestColumn(); + $endColumn = $endColumn ?: $this->worksheet->getHighestColumn(); $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn); return $this; @@ -105,11 +108,13 @@ class ColumnIterator implements Iterator * * @return $this */ - public function seek($column = 'A') + public function seek(string $column = 'A') { $column = Coordinate::columnIndexFromString($column); if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) { - throw new PhpSpreadsheetException("Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})"); + throw new PhpSpreadsheetException( + "Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})" + ); } $this->currentColumnIndex = $column; @@ -136,10 +141,8 @@ class ColumnIterator implements Iterator /** * Return the current iterator key. - * - * @return string */ - public function key() + public function key(): string { return Coordinate::stringFromColumnIndex($this->currentColumnIndex); } @@ -162,10 +165,8 @@ class ColumnIterator implements Iterator /** * Indicate if more columns exist in the worksheet range of columns that we're iterating. - * - * @return bool */ - public function valid() + public function valid(): bool { return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex; } diff --git a/src/PhpSpreadsheet/Worksheet/Dimension.php b/src/PhpSpreadsheet/Worksheet/Dimension.php index a27daf09..4b3a0da8 100644 --- a/src/PhpSpreadsheet/Worksheet/Dimension.php +++ b/src/PhpSpreadsheet/Worksheet/Dimension.php @@ -47,10 +47,8 @@ abstract class Dimension /** * Get Visible. - * - * @return bool */ - public function getVisible() + public function getVisible(): bool { return $this->visible; } @@ -58,23 +56,19 @@ abstract class Dimension /** * Set Visible. * - * @param bool $pValue - * * @return $this */ - public function setVisible($pValue) + public function setVisible(bool $visible) { - $this->visible = (bool) $pValue; + $this->visible = $visible; return $this; } /** * Get Outline Level. - * - * @return int */ - public function getOutlineLevel() + public function getOutlineLevel(): int { return $this->outlineLevel; } @@ -83,27 +77,23 @@ abstract class Dimension * Set Outline Level. * Value must be between 0 and 7. * - * @param int $pValue - * * @return $this */ - public function setOutlineLevel($pValue) + public function setOutlineLevel(int $level) { - if ($pValue < 0 || $pValue > 7) { + if ($level < 0 || $level > 7) { throw new PhpSpreadsheetException('Outline level must range between 0 and 7.'); } - $this->outlineLevel = $pValue; + $this->outlineLevel = $level; return $this; } /** * Get Collapsed. - * - * @return bool */ - public function getCollapsed() + public function getCollapsed(): bool { return $this->collapsed; } @@ -111,13 +101,11 @@ abstract class Dimension /** * Set Collapsed. * - * @param bool $pValue - * * @return $this */ - public function setCollapsed($pValue) + public function setCollapsed(bool $collapsed) { - $this->collapsed = (bool) $pValue; + $this->collapsed = $collapsed; return $this; } @@ -127,7 +115,7 @@ abstract class Dimension * * @return int */ - public function getXfIndex() + public function getXfIndex(): ?int { return $this->xfIndex; } @@ -135,11 +123,9 @@ abstract class Dimension /** * Set index to cellXf. * - * @param int $pValue - * * @return $this */ - public function setXfIndex($pValue) + public function setXfIndex(int $pValue) { $this->xfIndex = $pValue; diff --git a/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php b/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php index 01ffed94..0557c83c 100644 --- a/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php +++ b/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php @@ -52,7 +52,7 @@ class Shadow implements IComparable /** * Shadow alignment. * - * @var int + * @var string */ private $alignment; @@ -184,7 +184,7 @@ class Shadow implements IComparable /** * Get Shadow alignment. * - * @return int + * @return string */ public function getAlignment() { @@ -194,7 +194,7 @@ class Shadow implements IComparable /** * Set Shadow alignment. * - * @param int $pValue + * @param string $pValue * * @return $this */ diff --git a/src/PhpSpreadsheet/Worksheet/Iterator.php b/src/PhpSpreadsheet/Worksheet/Iterator.php index 6cfed37a..134b619a 100644 --- a/src/PhpSpreadsheet/Worksheet/Iterator.php +++ b/src/PhpSpreadsheet/Worksheet/Iterator.php @@ -29,14 +29,6 @@ class Iterator implements \Iterator $this->subject = $subject; } - /** - * Destructor. - */ - public function __destruct() - { - $this->subject = null; - } - /** * Rewind iterator. */ @@ -47,20 +39,16 @@ class Iterator implements \Iterator /** * Current Worksheet. - * - * @return Worksheet */ - public function current() + public function current(): Worksheet { return $this->subject->getSheet($this->position); } /** * Current key. - * - * @return int */ - public function key() + public function key(): int { return $this->position; } diff --git a/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php b/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php index fb002114..cde23c96 100644 --- a/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php +++ b/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php @@ -52,7 +52,6 @@ class MemoryDrawing extends BaseDrawing public function __construct() { // Initialise values - $this->imageResource = null; $this->renderingFunction = self::RENDERING_DEFAULT; $this->mimeType = self::MIMETYPE_DEFAULT; $this->uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999)); diff --git a/src/PhpSpreadsheet/Worksheet/PageSetup.php b/src/PhpSpreadsheet/Worksheet/PageSetup.php index d1a22a7b..7f3f71dc 100644 --- a/src/PhpSpreadsheet/Worksheet/PageSetup.php +++ b/src/PhpSpreadsheet/Worksheet/PageSetup.php @@ -238,7 +238,7 @@ class PageSetup /** * Print area. * - * @var string + * @var null|string */ private $printArea; diff --git a/src/PhpSpreadsheet/Worksheet/Row.php b/src/PhpSpreadsheet/Worksheet/Row.php index 4f48a346..01053c39 100644 --- a/src/PhpSpreadsheet/Worksheet/Row.php +++ b/src/PhpSpreadsheet/Worksheet/Row.php @@ -36,15 +36,14 @@ class Row */ public function __destruct() { + // @phpstan-ignore-next-line $this->worksheet = null; } /** * Get row index. - * - * @return int */ - public function getRowIndex() + public function getRowIndex(): int { return $this->rowIndex; } @@ -64,10 +63,8 @@ class Row /** * Returns bound worksheet. - * - * @return Worksheet */ - public function getWorksheet() + public function getWorksheet(): Worksheet { return $this->worksheet; } diff --git a/src/PhpSpreadsheet/Worksheet/RowCellIterator.php b/src/PhpSpreadsheet/Worksheet/RowCellIterator.php index 9b9d54eb..6a96a826 100644 --- a/src/PhpSpreadsheet/Worksheet/RowCellIterator.php +++ b/src/PhpSpreadsheet/Worksheet/RowCellIterator.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; @@ -59,7 +60,7 @@ class RowCellIterator extends CellIterator * * @return $this */ - public function resetStart($startColumn = 'A') + public function resetStart(string $startColumn = 'A') { $this->startColumnIndex = Coordinate::columnIndexFromString($startColumn); $this->adjustForExistingOnlyRange(); @@ -77,7 +78,7 @@ class RowCellIterator extends CellIterator */ public function resetEnd($endColumn = null) { - $endColumn = $endColumn ? $endColumn : $this->worksheet->getHighestColumn(); + $endColumn = $endColumn ?: $this->worksheet->getHighestColumn(); $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn); $this->adjustForExistingOnlyRange(); @@ -91,7 +92,7 @@ class RowCellIterator extends CellIterator * * @return $this */ - public function seek($column = 'A') + public function seek(string $column = 'A') { $columnx = $column; $column = Coordinate::columnIndexFromString($column); @@ -116,20 +117,16 @@ class RowCellIterator extends CellIterator /** * Return the current cell in this worksheet row. - * - * @return \PhpOffice\PhpSpreadsheet\Cell\Cell */ - public function current() + public function current(): ?Cell { return $this->worksheet->getCellByColumnAndRow($this->currentColumnIndex, $this->rowIndex); } /** * Return the current iterator key. - * - * @return string */ - public function key() + public function key(): string { return Coordinate::stringFromColumnIndex($this->currentColumnIndex); } @@ -166,10 +163,8 @@ class RowCellIterator extends CellIterator /** * Return the current iterator position. - * - * @return int */ - public function getCurrentColumnIndex() + public function getCurrentColumnIndex(): int { return $this->currentColumnIndex; } diff --git a/src/PhpSpreadsheet/Worksheet/RowDimension.php b/src/PhpSpreadsheet/Worksheet/RowDimension.php index c4a87bdb..d86dd80f 100644 --- a/src/PhpSpreadsheet/Worksheet/RowDimension.php +++ b/src/PhpSpreadsheet/Worksheet/RowDimension.php @@ -43,10 +43,8 @@ class RowDimension extends Dimension /** * Get Row Index. - * - * @return int */ - public function getRowIndex() + public function getRowIndex(): int { return $this->rowIndex; } @@ -54,13 +52,11 @@ class RowDimension extends Dimension /** * Set Row Index. * - * @param int $pValue - * * @return $this */ - public function setRowIndex($pValue) + public function setRowIndex(int $index) { - $this->rowIndex = $pValue; + $this->rowIndex = $index; return $this; } @@ -78,23 +74,21 @@ class RowDimension extends Dimension /** * Set Row Height. * - * @param float $pValue + * @param float $height * * @return $this */ - public function setRowHeight($pValue) + public function setRowHeight($height) { - $this->height = $pValue; + $this->height = $height; return $this; } /** * Get ZeroHeight. - * - * @return bool */ - public function getZeroHeight() + public function getZeroHeight(): bool { return $this->zeroHeight; } @@ -102,11 +96,9 @@ class RowDimension extends Dimension /** * Set ZeroHeight. * - * @param bool $pValue - * * @return $this */ - public function setZeroHeight($pValue) + public function setZeroHeight(bool $pValue) { $this->zeroHeight = $pValue; diff --git a/src/PhpSpreadsheet/Worksheet/RowIterator.php b/src/PhpSpreadsheet/Worksheet/RowIterator.php index 42542533..7d49f1ac 100644 --- a/src/PhpSpreadsheet/Worksheet/RowIterator.php +++ b/src/PhpSpreadsheet/Worksheet/RowIterator.php @@ -50,14 +50,6 @@ class RowIterator implements Iterator $this->resetStart($startRow); } - /** - * Destructor. - */ - public function __destruct() - { - $this->subject = null; - } - /** * (Re)Set the start row and the current row pointer. * @@ -65,10 +57,12 @@ class RowIterator implements Iterator * * @return $this */ - public function resetStart($startRow = 1) + public function resetStart(int $startRow = 1) { if ($startRow > $this->subject->getHighestRow()) { - throw new PhpSpreadsheetException("Start row ({$startRow}) is beyond highest row ({$this->subject->getHighestRow()})"); + throw new PhpSpreadsheetException( + "Start row ({$startRow}) is beyond highest row ({$this->subject->getHighestRow()})" + ); } $this->startRow = $startRow; @@ -89,7 +83,7 @@ class RowIterator implements Iterator */ public function resetEnd($endRow = null) { - $this->endRow = ($endRow) ? $endRow : $this->subject->getHighestRow(); + $this->endRow = $endRow ?: $this->subject->getHighestRow(); return $this; } @@ -101,7 +95,7 @@ class RowIterator implements Iterator * * @return $this */ - public function seek($row = 1) + public function seek(int $row = 1) { if (($row < $this->startRow) || ($row > $this->endRow)) { throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})"); @@ -131,10 +125,8 @@ class RowIterator implements Iterator /** * Return the current iterator key. - * - * @return int */ - public function key() + public function key(): int { return $this->position; } @@ -157,10 +149,8 @@ class RowIterator implements Iterator /** * Indicate if more rows exist in the worksheet range of rows that we're iterating. - * - * @return bool */ - public function valid() + public function valid(): bool { return $this->position <= $this->endRow && $this->position >= $this->startRow; } diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 19833b71..2ecd210c 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -96,16 +96,16 @@ class Worksheet implements IComparable /** * Collection of drawings. * - * @var BaseDrawing[] + * @var ArrayObject */ private $drawingCollection; /** * Collection of Chart objects. * - * @var Chart[] + * @var ArrayObject */ - private $chartCollection = []; + private $chartCollection; /** * Worksheet title. @@ -180,7 +180,7 @@ class Worksheet implements IComparable /** * Collection of breaks. * - * @var array + * @var int[] */ private $breaks = []; @@ -278,9 +278,9 @@ class Worksheet implements IComparable /** * Cached highest column. * - * @var string + * @var int */ - private $cachedHighestColumn = 'A'; + private $cachedHighestColumn = 1; /** * Cached highest row. @@ -313,7 +313,7 @@ class Worksheet implements IComparable /** * Tab color. * - * @var Color + * @var null|Color */ private $tabColor; @@ -383,9 +383,11 @@ class Worksheet implements IComparable { if ($this->cellCollection !== null) { $this->cellCollection->unsetWorksheetCells(); + // @phpstan-ignore-next-line $this->cellCollection = null; } // detach ourself from the workbook, so that it can then delete this worksheet successfully + // @phpstan-ignore-next-line $this->parent = null; } @@ -534,7 +536,7 @@ class Worksheet implements IComparable /** * Get collection of drawings. * - * @return BaseDrawing[] + * @return ArrayObject */ public function getDrawingCollection() { @@ -544,7 +546,7 @@ class Worksheet implements IComparable /** * Get collection of charts. * - * @return Chart[] + * @return ArrayObject */ public function getChartCollection() { @@ -728,7 +730,7 @@ class Worksheet implements IComparable // loop through all cells in the worksheet foreach ($this->getCoordinates(false) as $coordinate) { - $cell = $this->getCell($coordinate, false); + $cell = $this->getCellOrNull($coordinate); if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) { //Determine if cell is in merge range $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]); @@ -824,7 +826,7 @@ class Worksheet implements IComparable /** * Set title. * - * @param string $pValue String containing the dimension of this worksheet + * @param string $title String containing the dimension of this worksheet * @param bool $updateFormulaCellReferences Flag indicating whether cell references in formulae should * be updated to reflect the new sheet name. * This should be left as the default true, unless you are @@ -835,10 +837,10 @@ class Worksheet implements IComparable * * @return $this */ - public function setTitle($pValue, $updateFormulaCellReferences = true, $validate = true) + public function setTitle($title, $updateFormulaCellReferences = true, $validate = true) { // Is this a 'rename' or not? - if ($this->getTitle() == $pValue) { + if ($this->getTitle() == $title) { return $this; } @@ -847,37 +849,37 @@ class Worksheet implements IComparable if ($validate) { // Syntax check - self::checkSheetTitle($pValue); + self::checkSheetTitle($title); if ($this->parent) { // Is there already such sheet name? - if ($this->parent->sheetNameExists($pValue)) { + if ($this->parent->sheetNameExists($title)) { // Use name, but append with lowest possible integer - if (Shared\StringHelper::countCharacters($pValue) > 29) { - $pValue = Shared\StringHelper::substring($pValue, 0, 29); + if (Shared\StringHelper::countCharacters($title) > 29) { + $title = Shared\StringHelper::substring($title, 0, 29); } $i = 1; - while ($this->parent->sheetNameExists($pValue . ' ' . $i)) { + while ($this->parent->sheetNameExists($title . ' ' . $i)) { ++$i; if ($i == 10) { - if (Shared\StringHelper::countCharacters($pValue) > 28) { - $pValue = Shared\StringHelper::substring($pValue, 0, 28); + if (Shared\StringHelper::countCharacters($title) > 28) { + $title = Shared\StringHelper::substring($title, 0, 28); } } elseif ($i == 100) { - if (Shared\StringHelper::countCharacters($pValue) > 27) { - $pValue = Shared\StringHelper::substring($pValue, 0, 27); + if (Shared\StringHelper::countCharacters($title) > 27) { + $title = Shared\StringHelper::substring($title, 0, 27); } } } - $pValue .= " $i"; + $title .= " $i"; } } } // Set title - $this->title = $pValue; + $this->title = $title; $this->dirty = true; if ($this->parent && $this->parent->getCalculationEngine()) { @@ -1039,7 +1041,7 @@ class Worksheet implements IComparable public function getHighestColumn($row = null) { if ($row == null) { - return $this->cachedHighestColumn; + return Coordinate::stringFromColumnIndex($this->cachedHighestColumn); } return $this->getHighestDataColumn($row); @@ -1166,50 +1168,94 @@ class Worksheet implements IComparable /** * Get cell at a specific coordinate. * - * @param string $pCoordinate Coordinate of the cell, eg: 'A1' - * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't - * already exist, or a null should be returned instead + * @param string $coordinate Coordinate of the cell, eg: 'A1' * - * @return null|Cell Cell that was found/created or null + * @return Cell Cell that was found or created */ - public function getCell($pCoordinate, $createIfNotExists = true) + public function getCell(string $coordinate): Cell { - // Uppercase coordinate - $pCoordinateUpper = strtoupper($pCoordinate); + // Shortcut for increased performance for the vast majority of simple cases + if ($this->cellCollection->has($coordinate)) { + /** @var Cell $cell */ + $cell = $this->cellCollection->get($coordinate); - // Check cell collection - if ($this->cellCollection->has($pCoordinateUpper)) { - return $this->cellCollection->get($pCoordinateUpper); + return $cell; } + /** @var Worksheet $sheet */ + [$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($coordinate); + $cell = $sheet->cellCollection->get($finalCoordinate); + + return $cell ?? $sheet->createNewCell($finalCoordinate); + } + + /** + * Get the correct Worksheet and coordinate from a coordinate that may + * contains reference to another sheet or a named range. + * + * @return array{0: Worksheet, 1: string} + */ + private function getWorksheetAndCoordinate(string $pCoordinate): array + { + $sheet = null; + $finalCoordinate = null; + // Worksheet reference? if (strpos($pCoordinate, '!') !== false) { $worksheetReference = self::extractSheetTitle($pCoordinate, true); - return $this->parent->getSheetByName($worksheetReference[0])->getCell(strtoupper($worksheetReference[1]), $createIfNotExists); - } + $sheet = $this->parent->getSheetByName($worksheetReference[0]); + $finalCoordinate = strtoupper($worksheetReference[1]); - // Named range? - if ( - (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) && - (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/i', $pCoordinate, $matches)) + if (!$sheet) { + throw new Exception('Sheet not found for name: ' . $worksheetReference[0]); + } + } elseif ( + !preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate) && + preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/i', $pCoordinate) ) { - $namedRange = DefinedName::resolveName($pCoordinate, $this); + // Named range? + $namedRange = $this->validateNamedRange($pCoordinate, true); if ($namedRange !== null) { - $pCoordinate = $namedRange->getValue(); + $sheet = $namedRange->getWorksheet(); + if (!$sheet) { + throw new Exception('Sheet not found for named range: ' . $namedRange->getName()); + } - return $namedRange->getWorksheet()->getCell($pCoordinate, $createIfNotExists); + $cellCoordinate = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(), '!')), '!'); + $finalCoordinate = str_replace('$', '', $cellCoordinate); } } - if (Coordinate::coordinateIsRange($pCoordinate)) { - throw new Exception('Cell coordinate can not be a range of cells.'); - } elseif (strpos($pCoordinate, '$') !== false) { + if (!$sheet || !$finalCoordinate) { + $sheet = $this; + $finalCoordinate = strtoupper($pCoordinate); + } + + if (Coordinate::coordinateIsRange($finalCoordinate)) { + throw new Exception('Cell coordinate string can not be a range of cells.'); + } elseif (strpos($finalCoordinate, '$') !== false) { throw new Exception('Cell coordinate must not be absolute.'); } - // Create new cell object, if required - return $createIfNotExists ? $this->createNewCell($pCoordinateUpper) : null; + return [$sheet, $finalCoordinate]; + } + + /** + * Get an existing cell at a specific coordinate, or null. + * + * @param string $coordinate Coordinate of the cell, eg: 'A1' + * + * @return null|Cell Cell that was found or null + */ + private function getCellOrNull($coordinate): ?Cell + { + // Check cell collection + if ($this->cellCollection->has($coordinate)) { + return $this->cellCollection->get($coordinate); + } + + return null; } /** @@ -1250,11 +1296,12 @@ class Worksheet implements IComparable // Coordinates $aCoordinates = Coordinate::coordinateFromString($pCoordinate); - if (Coordinate::columnIndexFromString($this->cachedHighestColumn) < Coordinate::columnIndexFromString($aCoordinates[0])) { - $this->cachedHighestColumn = $aCoordinates[0]; + $aIndexes = Coordinate::indexesFromString($pCoordinate); + if ($this->cachedHighestColumn < $aIndexes[0]) { + $this->cachedHighestColumn = $aIndexes[0]; } - if ($aCoordinates[1] > $this->cachedHighestRow) { - $this->cachedHighestRow = $aCoordinates[1]; + if ($aIndexes[1] > $this->cachedHighestRow) { + $this->cachedHighestRow = $aIndexes[1]; } // Cell needs appropriate xfIndex from dimensions records @@ -1276,50 +1323,16 @@ class Worksheet implements IComparable /** * Does the cell at a specific coordinate exist? * - * @param string $pCoordinate Coordinate of the cell eg: 'A1' + * @param string $coordinate Coordinate of the cell eg: 'A1' * * @return bool */ - public function cellExists($pCoordinate) + public function cellExists($coordinate) { - // Worksheet reference? - if (strpos($pCoordinate, '!') !== false) { - $worksheetReference = self::extractSheetTitle($pCoordinate, true); + /** @var Worksheet $sheet */ + [$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($coordinate); - return $this->parent->getSheetByName($worksheetReference[0])->cellExists(strtoupper($worksheetReference[1])); - } - - // Named range? - if ( - (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) && - (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/i', $pCoordinate, $matches)) - ) { - $namedRange = DefinedName::resolveName($pCoordinate, $this); - if ($namedRange !== null) { - $pCoordinate = $namedRange->getValue(); - if ($this->getHashCode() != $namedRange->getWorksheet()->getHashCode()) { - if (!$namedRange->getLocalOnly()) { - return $namedRange->getWorksheet()->cellExists($pCoordinate); - } - - throw new Exception('Named range ' . $namedRange->getName() . ' is not accessible from within sheet ' . $this->getTitle()); - } - } else { - return false; - } - } - - // Uppercase coordinate - $pCoordinate = strtoupper($pCoordinate); - - if (Coordinate::coordinateIsRange($pCoordinate)) { - throw new Exception('Cell coordinate can not be a range of cells.'); - } elseif (strpos($pCoordinate, '$') !== false) { - throw new Exception('Cell coordinate must not be absolute.'); - } - - // Cell exists? - return $this->cellCollection->has($pCoordinate); + return $sheet->cellCollection->has($finalCoordinate); } /** @@ -1341,7 +1354,7 @@ class Worksheet implements IComparable * @param int $pRow Numeric index of the row * @param bool $create * - * @return RowDimension + * @return null|RowDimension */ public function getRowDimension($pRow, $create = true) { @@ -1367,7 +1380,7 @@ class Worksheet implements IComparable * @param string $pColumn String index of the column eg: 'A' * @param bool $create * - * @return ColumnDimension + * @return null|ColumnDimension */ public function getColumnDimension($pColumn, $create = true) { @@ -1381,8 +1394,9 @@ class Worksheet implements IComparable } $this->columnDimensions[$pColumn] = new ColumnDimension($pColumn); - if (Coordinate::columnIndexFromString($this->cachedHighestColumn) < Coordinate::columnIndexFromString($pColumn)) { - $this->cachedHighestColumn = $pColumn; + $columnIndex = Coordinate::columnIndexFromString($pColumn); + if ($this->cachedHighestColumn < $columnIndex) { + $this->cachedHighestColumn = $columnIndex; } } @@ -1394,7 +1408,7 @@ class Worksheet implements IComparable * * @param int $columnIndex Numeric column coordinate of the cell * - * @return ColumnDimension + * @return null|ColumnDimension */ public function getColumnDimensionByColumn($columnIndex) { @@ -1486,7 +1500,7 @@ class Worksheet implements IComparable * Set conditional styles. * * @param string $pCoordinate eg: 'A1' - * @param $pValue Conditional[] + * @param Conditional[] $pValue * * @return $this */ @@ -1644,7 +1658,7 @@ class Worksheet implements IComparable /** * Get breaks. * - * @return array[] + * @return int[] */ public function getBreaks() { @@ -1991,7 +2005,7 @@ class Worksheet implements IComparable /** * Get the default position of the right bottom pane. * - * @return int + * @return null|string */ public function getTopLeftCell() { @@ -2550,10 +2564,42 @@ class Worksheet implements IComparable return $returnValue; } + private function validateNamedRange(string $definedName, bool $returnNullIfInvalid = false): ?DefinedName + { + $namedRange = DefinedName::resolveName($definedName, $this); + if ($namedRange === null) { + if ($returnNullIfInvalid) { + return null; + } + + throw new Exception('Named Range ' . $definedName . ' does not exist.'); + } + + if ($namedRange->isFormula()) { + if ($returnNullIfInvalid) { + return null; + } + + throw new Exception('Defined Named ' . $definedName . ' is a formula, not a range or cell.'); + } + + if ($namedRange->getLocalOnly() && $this->getHashCode() !== $namedRange->getWorksheet()->getHashCode()) { + if ($returnNullIfInvalid) { + return null; + } + + throw new Exception( + 'Named range ' . $definedName . ' is not accessible from within sheet ' . $this->getTitle() + ); + } + + return $namedRange; + } + /** * Create array from a range of cells. * - * @param string $pNamedRange Name of the Named Range + * @param string $definedName The Named Range that should be returned * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist * @param bool $calculateFormulas Should formulas be calculated? * @param bool $formatData Should formatting be applied to cell values? @@ -2562,17 +2608,14 @@ class Worksheet implements IComparable * * @return array */ - public function namedRangeToArray($pNamedRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false) + public function namedRangeToArray(string $definedName, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false) { - $namedRange = DefinedName::resolveName($pNamedRange, $this); - if ($namedRange !== null) { - $pWorkSheet = $namedRange->getWorksheet(); - $pCellRange = $namedRange->getValue(); + $namedRange = $this->validateNamedRange($definedName); + $workSheet = $namedRange->getWorksheet(); + $cellRange = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(), '!')), '!'); + $cellRange = str_replace('$', '', $cellRange); - return $pWorkSheet->rangeToArray($pCellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef); - } - - throw new Exception('Named Range ' . $pNamedRange . ' does not exist.'); + return $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef); } /** @@ -2652,9 +2695,9 @@ class Worksheet implements IComparable // Cache values if ($highestColumn < 1) { - $this->cachedHighestColumn = 'A'; + $this->cachedHighestColumn = 1; } else { - $this->cachedHighestColumn = Coordinate::stringFromColumnIndex($highestColumn); + $this->cachedHighestColumn = $highestColumn; } $this->cachedHighestRow = $highestRow; @@ -2880,7 +2923,6 @@ class Worksheet implements IComparable public function resetTabColor() { $this->tabColor = null; - $this->tabColor = null; return $this; } @@ -2910,6 +2952,7 @@ class Worksheet implements IComparable */ public function __clone() { + // @phpstan-ignore-next-line foreach ($this as $key => $val) { if ($key == 'parent') { continue; diff --git a/src/PhpSpreadsheet/Writer/Csv.php b/src/PhpSpreadsheet/Writer/Csv.php index 74f28636..188a83a8 100644 --- a/src/PhpSpreadsheet/Writer/Csv.php +++ b/src/PhpSpreadsheet/Writer/Csv.php @@ -64,6 +64,13 @@ class Csv extends BaseWriter */ private $excelCompatibility = false; + /** + * Output encoding. + * + * @var string + */ + private $outputEncoding = ''; + /** * Create a new CSV. * @@ -296,6 +303,30 @@ class Csv extends BaseWriter return $this; } + /** + * Get output encoding. + * + * @return string + */ + public function getOutputEncoding() + { + return $this->outputEncoding; + } + + /** + * Set output encoding. + * + * @param string $pValue Output encoding + * + * @return $this + */ + public function setOutputEncoding($pValue) + { + $this->outputEncoding = $pValue; + + return $this; + } + private $enclosureRequired = true; public function setEnclosureRequired(bool $value): self @@ -347,6 +378,9 @@ class Csv extends BaseWriter $line .= $this->lineEnding; // Write to file + if ($this->outputEncoding != '') { + $line = mb_convert_encoding($line, $this->outputEncoding); + } fwrite($pFileHandle, $line); } } diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index e9de2ce6..4ee94114 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -37,7 +37,7 @@ class Html extends BaseWriter /** * Sheet index to write. * - * @var int + * @var null|int */ private $sheetIndex = 0; @@ -453,10 +453,8 @@ class Html extends BaseWriter // Get worksheet dimension [$min, $max] = explode(':', $sheet->calculateWorksheetDataDimension()); - [$minCol, $minRow] = Coordinate::coordinateFromString($min); - $minCol = Coordinate::columnIndexFromString($minCol); - [$maxCol, $maxRow] = Coordinate::coordinateFromString($max); - $maxCol = Coordinate::columnIndexFromString($maxCol); + [$minCol, $minRow] = Coordinate::indexesFromString($min); + [$maxCol, $maxRow] = Coordinate::indexesFromString($max); [$theadStart, $theadEnd, $tbodyStart] = $this->generateSheetStarts($sheet, $minRow); @@ -737,7 +735,7 @@ class Html extends BaseWriter if ($chartCoordinates['cell'] == $coordinates) { $chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png'; if (!$chart->render($chartFileName)) { - return; + return ''; } $html .= PHP_EOL; @@ -1323,7 +1321,7 @@ class Html extends BaseWriter [$this, 'formatColor'] ); if ($cellData === $origData) { - $cellData = htmlspecialchars($cellData); + $cellData = htmlspecialchars($cellData ?? ''); } if ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) { $cellData = '' . $cellData . ''; @@ -1703,11 +1701,11 @@ class Html extends BaseWriter $first = $cells[0]; $last = $cells[1]; - [$fc, $fr] = Coordinate::coordinateFromString($first); - $fc = Coordinate::columnIndexFromString($fc) - 1; + [$fc, $fr] = Coordinate::indexesFromString($first); + $fc = $fc - 1; - [$lc, $lr] = Coordinate::coordinateFromString($last); - $lc = Coordinate::columnIndexFromString($lc) - 1; + [$lc, $lr] = Coordinate::indexesFromString($last); + $lc = $lc - 1; // loop through the individual cells in the individual merge $r = $fr - 1; diff --git a/src/PhpSpreadsheet/Writer/Ods.php b/src/PhpSpreadsheet/Writer/Ods.php index 36f3e9ca..f07ade9a 100644 --- a/src/PhpSpreadsheet/Writer/Ods.php +++ b/src/PhpSpreadsheet/Writer/Ods.php @@ -2,7 +2,6 @@ namespace PhpOffice\PhpSpreadsheet\Writer; -use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; use PhpOffice\PhpSpreadsheet\Writer\Ods\Content; @@ -18,13 +17,6 @@ use ZipStream\ZipStream; class Ods extends BaseWriter { - /** - * Private writer parts. - * - * @var Ods\WriterPart[] - */ - private $writerParts = []; - /** * Private PhpSpreadsheet. * @@ -32,6 +24,41 @@ class Ods extends BaseWriter */ private $spreadSheet; + /** + * @var Content + */ + private $writerPartContent; + + /** + * @var Meta + */ + private $writerPartMeta; + + /** + * @var MetaInf + */ + private $writerPartMetaInf; + + /** + * @var Mimetype + */ + private $writerPartMimetype; + + /** + * @var Settings + */ + private $writerPartSettings; + + /** + * @var Styles + */ + private $writerPartStyles; + + /** + * @var Thumbnails + */ + private $writerPartThumbnails; + /** * Create a new Ods. */ @@ -39,35 +66,48 @@ class Ods extends BaseWriter { $this->setSpreadsheet($spreadsheet); - $writerPartsArray = [ - 'content' => Content::class, - 'meta' => Meta::class, - 'meta_inf' => MetaInf::class, - 'mimetype' => Mimetype::class, - 'settings' => Settings::class, - 'styles' => Styles::class, - 'thumbnails' => Thumbnails::class, - ]; - - foreach ($writerPartsArray as $writer => $class) { - $this->writerParts[$writer] = new $class($this); - } + $this->writerPartContent = new Content($this); + $this->writerPartMeta = new Meta($this); + $this->writerPartMetaInf = new MetaInf($this); + $this->writerPartMimetype = new Mimetype($this); + $this->writerPartSettings = new Settings($this); + $this->writerPartStyles = new Styles($this); + $this->writerPartThumbnails = new Thumbnails($this); } - /** - * Get writer part. - * - * @param string $pPartName Writer part name - * - * @return null|Ods\WriterPart - */ - public function getWriterPart($pPartName) + public function getWriterPartContent(): Content { - if ($pPartName != '' && isset($this->writerParts[strtolower($pPartName)])) { - return $this->writerParts[strtolower($pPartName)]; - } + return $this->writerPartContent; + } - return null; + public function getWriterPartMeta(): Meta + { + return $this->writerPartMeta; + } + + public function getWriterPartMetaInf(): MetaInf + { + return $this->writerPartMetaInf; + } + + public function getWriterPartMimetype(): Mimetype + { + return $this->writerPartMimetype; + } + + public function getWriterPartSettings(): Settings + { + return $this->writerPartSettings; + } + + public function getWriterPartStyles(): Styles + { + return $this->writerPartStyles; + } + + public function getWriterPartThumbnails(): Thumbnails + { + return $this->writerPartThumbnails; } /** @@ -88,13 +128,13 @@ class Ods extends BaseWriter $zip = $this->createZip(); - $zip->addFile('META-INF/manifest.xml', $this->getWriterPart('meta_inf')->writeManifest()); - $zip->addFile('Thumbnails/thumbnail.png', $this->getWriterPart('thumbnails')->writeThumbnail()); - $zip->addFile('content.xml', $this->getWriterPart('content')->write()); - $zip->addFile('meta.xml', $this->getWriterPart('meta')->write()); - $zip->addFile('mimetype', $this->getWriterPart('mimetype')->write()); - $zip->addFile('settings.xml', $this->getWriterPart('settings')->write()); - $zip->addFile('styles.xml', $this->getWriterPart('styles')->write()); + $zip->addFile('META-INF/manifest.xml', $this->getWriterPartMetaInf()->write()); + $zip->addFile('Thumbnails/thumbnail.png', $this->getWriterPartthumbnails()->write()); + $zip->addFile('content.xml', $this->getWriterPartcontent()->write()); + $zip->addFile('meta.xml', $this->getWriterPartmeta()->write()); + $zip->addFile('mimetype', $this->getWriterPartmimetype()->write()); + $zip->addFile('settings.xml', $this->getWriterPartsettings()->write()); + $zip->addFile('styles.xml', $this->getWriterPartstyles()->write()); // Close file try { diff --git a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php new file mode 100644 index 00000000..f8aae20c --- /dev/null +++ b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php @@ -0,0 +1,178 @@ +writer = $writer; + } + + private function mapHorizontalAlignment(string $horizontalAlignment): string + { + switch ($horizontalAlignment) { + case Alignment::HORIZONTAL_CENTER: + case Alignment::HORIZONTAL_CENTER_CONTINUOUS: + case Alignment::HORIZONTAL_DISTRIBUTED: + return 'center'; + case Alignment::HORIZONTAL_RIGHT: + return 'end'; + case Alignment::HORIZONTAL_FILL: + case Alignment::HORIZONTAL_JUSTIFY: + return 'justify'; + } + + return 'start'; + } + + private function mapVerticalAlignment(string $verticalAlignment): string + { + switch ($verticalAlignment) { + case Alignment::VERTICAL_TOP: + return 'top'; + case Alignment::VERTICAL_CENTER: + return 'middle'; + case Alignment::VERTICAL_DISTRIBUTED: + case Alignment::VERTICAL_JUSTIFY: + return 'automatic'; + } + + return 'bottom'; + } + + private function writeFillStyle(Fill $fill): void + { + switch ($fill->getFillType()) { + case Fill::FILL_SOLID: + $this->writer->writeAttribute('fo:background-color', sprintf( + '#%s', + strtolower($fill->getStartColor()->getRGB()) + )); + + break; + case Fill::FILL_GRADIENT_LINEAR: + case Fill::FILL_GRADIENT_PATH: + /// TODO :: To be implemented + break; + case Fill::FILL_NONE: + default: + } + } + + private function writeCellProperties(CellStyle $style): void + { + // Align + $hAlign = $style->getAlignment()->getHorizontal(); + $vAlign = $style->getAlignment()->getVertical(); + $wrap = $style->getAlignment()->getWrapText(); + + $this->writer->startElement('style:table-cell-properties'); + if (!empty($vAlign) || $wrap) { + if (!empty($vAlign)) { + $vAlign = $this->mapVerticalAlignment($vAlign); + $this->writer->writeAttribute('style:vertical-align', $vAlign); + } + if ($wrap) { + $this->writer->writeAttribute('fo:wrap-option', 'wrap'); + } + } + $this->writer->writeAttribute('style:rotation-align', 'none'); + + // Fill + if ($fill = $style->getFill()) { + $this->writeFillStyle($fill); + } + + $this->writer->endElement(); + + if (!empty($hAlign)) { + $hAlign = $this->mapHorizontalAlignment($hAlign); + $this->writer->startElement('style:paragraph-properties'); + $this->writer->writeAttribute('fo:text-align', $hAlign); + $this->writer->endElement(); + } + } + + protected function mapUnderlineStyle(Font $font): string + { + switch ($font->getUnderline()) { + case Font::UNDERLINE_DOUBLE: + case Font::UNDERLINE_DOUBLEACCOUNTING: + return'double'; + case Font::UNDERLINE_SINGLE: + case Font::UNDERLINE_SINGLEACCOUNTING: + return'single'; + } + + return 'none'; + } + + protected function writeTextProperties(CellStyle $style): void + { + // Font + $this->writer->startElement('style:text-properties'); + + $font = $style->getFont(); + + if ($font->getBold()) { + $this->writer->writeAttribute('fo:font-weight', 'bold'); + $this->writer->writeAttribute('style:font-weight-complex', 'bold'); + $this->writer->writeAttribute('style:font-weight-asian', 'bold'); + } + + if ($font->getItalic()) { + $this->writer->writeAttribute('fo:font-style', 'italic'); + } + + if ($color = $font->getColor()) { + $this->writer->writeAttribute('fo:color', sprintf('#%s', $color->getRGB())); + } + + if ($family = $font->getName()) { + $this->writer->writeAttribute('fo:font-family', $family); + } + + if ($size = $font->getSize()) { + $this->writer->writeAttribute('fo:font-size', sprintf('%.1Fpt', $size)); + } + + if ($font->getUnderline() && $font->getUnderline() !== Font::UNDERLINE_NONE) { + $this->writer->writeAttribute('style:text-underline-style', 'solid'); + $this->writer->writeAttribute('style:text-underline-width', 'auto'); + $this->writer->writeAttribute('style:text-underline-color', 'font-color'); + + $underline = $this->mapUnderlineStyle($font); + $this->writer->writeAttribute('style:text-underline-type', $underline); + } + + $this->writer->endElement(); // Close style:text-properties + } + + public function write(CellStyle $style): void + { + $this->writer->startElement('style:style'); + $this->writer->writeAttribute('style:name', self::CELL_STYLE_PREFIX . $style->getIndex()); + $this->writer->writeAttribute('style:family', 'table-cell'); + $this->writer->writeAttribute('style:parent-style-name', 'Default'); + + // Alignment, fill colour, etc + $this->writeCellProperties($style); + + // style:text-properties + $this->writeTextProperties($style); + + // End + $this->writer->endElement(); // Close style:style + } +} diff --git a/src/PhpSpreadsheet/Writer/Ods/Content.php b/src/PhpSpreadsheet/Writer/Ods/Content.php index 96e66850..e4bd1793 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Content.php +++ b/src/PhpSpreadsheet/Writer/Ods/Content.php @@ -7,13 +7,12 @@ use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PhpOffice\PhpSpreadsheet\Style\Fill; -use PhpOffice\PhpSpreadsheet\Style\Font; use PhpOffice\PhpSpreadsheet\Worksheet\Row; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Writer\Exception; use PhpOffice\PhpSpreadsheet\Writer\Ods; use PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Comment; +use PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Style; /** * @author Alexander Pervakov @@ -22,7 +21,6 @@ class Content extends WriterPart { const NUMBER_COLS_REPEATED_MAX = 1024; const NUMBER_ROWS_REPEATED_MAX = 1048576; - const CELL_STYLE_PREFIX = 'ce'; private $formulaConvertor; @@ -41,7 +39,7 @@ class Content extends WriterPart * * @return string XML Output */ - public function write() + public function write(): string { $objWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { @@ -185,7 +183,7 @@ class Content extends WriterPart // Style XF $style = $cell->getXfIndex(); if ($style !== null) { - $objWriter->writeAttribute('table:style-name', self::CELL_STYLE_PREFIX . $style); + $objWriter->writeAttribute('table:style-name', Style::CELL_STYLE_PREFIX . $style); } switch ($cell->getDataType()) { @@ -196,7 +194,10 @@ class Content extends WriterPart break; case DataType::TYPE_ERROR: - throw new Exception('Writing of error not implemented yet.'); + $objWriter->writeAttribute('table:formula', 'of:=#NULL!'); + $objWriter->writeAttribute('office:value-type', 'string'); + $objWriter->writeAttribute('office:string-value', ''); + $objWriter->writeElement('text:p', '#NULL!'); break; case DataType::TYPE_FORMULA: @@ -217,10 +218,6 @@ class Content extends WriterPart $objWriter->writeAttribute('office:value', $formulaValue); $objWriter->writeElement('text:p', $formulaValue); - break; - case DataType::TYPE_INLINE: - throw new Exception('Writing of inline not implemented yet.'); - break; case DataType::TYPE_NUMERIC: $objWriter->writeAttribute('office:value-type', 'float'); @@ -228,6 +225,8 @@ class Content extends WriterPart $objWriter->writeElement('text:p', $cell->getValue()); break; + case DataType::TYPE_INLINE: + // break intentionally omitted case DataType::TYPE_STRING: $objWriter->writeAttribute('office:value-type', 'string'); $objWriter->writeElement('text:p', $cell->getValue()); @@ -274,89 +273,9 @@ class Content extends WriterPart */ private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): void { + $styleWriter = new Style($writer); foreach ($spreadsheet->getCellXfCollection() as $style) { - $writer->startElement('style:style'); - $writer->writeAttribute('style:name', self::CELL_STYLE_PREFIX . $style->getIndex()); - $writer->writeAttribute('style:family', 'table-cell'); - $writer->writeAttribute('style:parent-style-name', 'Default'); - - // style:text-properties - - // Font - $writer->startElement('style:text-properties'); - - $font = $style->getFont(); - - if ($font->getBold()) { - $writer->writeAttribute('fo:font-weight', 'bold'); - $writer->writeAttribute('style:font-weight-complex', 'bold'); - $writer->writeAttribute('style:font-weight-asian', 'bold'); - } - - if ($font->getItalic()) { - $writer->writeAttribute('fo:font-style', 'italic'); - } - - if ($color = $font->getColor()) { - $writer->writeAttribute('fo:color', sprintf('#%s', $color->getRGB())); - } - - if ($family = $font->getName()) { - $writer->writeAttribute('fo:font-family', $family); - } - - if ($size = $font->getSize()) { - $writer->writeAttribute('fo:font-size', sprintf('%.1Fpt', $size)); - } - - if ($font->getUnderline() && $font->getUnderline() != Font::UNDERLINE_NONE) { - $writer->writeAttribute('style:text-underline-style', 'solid'); - $writer->writeAttribute('style:text-underline-width', 'auto'); - $writer->writeAttribute('style:text-underline-color', 'font-color'); - - switch ($font->getUnderline()) { - case Font::UNDERLINE_DOUBLE: - $writer->writeAttribute('style:text-underline-type', 'double'); - - break; - case Font::UNDERLINE_SINGLE: - $writer->writeAttribute('style:text-underline-type', 'single'); - - break; - } - } - - $writer->endElement(); // Close style:text-properties - - // style:table-cell-properties - - $writer->startElement('style:table-cell-properties'); - $writer->writeAttribute('style:rotation-align', 'none'); - - // Fill - if ($fill = $style->getFill()) { - switch ($fill->getFillType()) { - case Fill::FILL_SOLID: - $writer->writeAttribute('fo:background-color', sprintf( - '#%s', - strtolower($fill->getStartColor()->getRGB()) - )); - - break; - case Fill::FILL_GRADIENT_LINEAR: - case Fill::FILL_GRADIENT_PATH: - /// TODO :: To be implemented - break; - case Fill::FILL_NONE: - default: - } - } - - $writer->endElement(); // Close style:table-cell-properties - - // End - - $writer->endElement(); // Close style:style + $styleWriter->write($style); } } @@ -374,7 +293,7 @@ class Content extends WriterPart $start = Coordinate::coordinateFromString($startCell); $end = Coordinate::coordinateFromString($endCell); $columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1; - $rowSpan = $end[1] - $start[1] + 1; + $rowSpan = ((int) $end[1]) - ((int) $start[1]) + 1; $objWriter->writeAttribute('table:number-columns-spanned', $columnSpan); $objWriter->writeAttribute('table:number-rows-spanned', $rowSpan); diff --git a/src/PhpSpreadsheet/Writer/Ods/Meta.php b/src/PhpSpreadsheet/Writer/Ods/Meta.php index 365221f7..cd3054c0 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Meta.php +++ b/src/PhpSpreadsheet/Writer/Ods/Meta.php @@ -3,22 +3,17 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Ods; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; -use PhpOffice\PhpSpreadsheet\Spreadsheet; class Meta extends WriterPart { /** * Write meta.xml to XML format. * - * @param Spreadsheet $spreadsheet - * * @return string XML Output */ - public function write(?Spreadsheet $spreadsheet = null) + public function write(): string { - if (!$spreadsheet) { - $spreadsheet = $this->getParentWriter()->getSpreadsheet(); - } + $spreadsheet = $this->getParentWriter()->getSpreadsheet(); $objWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { diff --git a/src/PhpSpreadsheet/Writer/Ods/MetaInf.php b/src/PhpSpreadsheet/Writer/Ods/MetaInf.php index c9085cf8..f3f0d5fc 100644 --- a/src/PhpSpreadsheet/Writer/Ods/MetaInf.php +++ b/src/PhpSpreadsheet/Writer/Ods/MetaInf.php @@ -11,7 +11,7 @@ class MetaInf extends WriterPart * * @return string XML Output */ - public function writeManifest() + public function write(): string { $objWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { diff --git a/src/PhpSpreadsheet/Writer/Ods/Mimetype.php b/src/PhpSpreadsheet/Writer/Ods/Mimetype.php index 4aac3685..e109e6e7 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Mimetype.php +++ b/src/PhpSpreadsheet/Writer/Ods/Mimetype.php @@ -2,18 +2,14 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Ods; -use PhpOffice\PhpSpreadsheet\Spreadsheet; - class Mimetype extends WriterPart { /** * Write mimetype to plain text format. * - * @param Spreadsheet $spreadsheet - * * @return string XML Output */ - public function write(?Spreadsheet $spreadsheet = null) + public function write(): string { return 'application/vnd.oasis.opendocument.spreadsheet'; } diff --git a/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php b/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php index 9edc5c64..ae1c4217 100644 --- a/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php +++ b/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php @@ -23,11 +23,13 @@ class NamedExpressions $this->formulaConvertor = $formulaConvertor; } - public function write(): void + public function write(): string { $this->objWriter->startElement('table:named-expressions'); $this->writeExpressions(); $this->objWriter->endElement(); + + return ''; } private function writeExpressions(): void diff --git a/src/PhpSpreadsheet/Writer/Ods/Settings.php b/src/PhpSpreadsheet/Writer/Ods/Settings.php index d458e8c2..047bd410 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Settings.php +++ b/src/PhpSpreadsheet/Writer/Ods/Settings.php @@ -2,21 +2,18 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Ods; +use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; -use PhpOffice\PhpSpreadsheet\Spreadsheet; class Settings extends WriterPart { /** * Write settings.xml to XML format. * - * @param Spreadsheet $spreadsheet - * * @return string XML Output */ - public function write(?Spreadsheet $spreadsheet = null) + public function write(): string { - $objWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); } else { @@ -39,13 +36,52 @@ class Settings extends WriterPart $objWriter->writeAttribute('config:name', 'ooo:view-settings'); $objWriter->startElement('config:config-item-map-indexed'); $objWriter->writeAttribute('config:name', 'Views'); - $objWriter->endElement(); - $objWriter->endElement(); + $objWriter->startElement('config:config-item-map-entry'); + $spreadsheet = $this->getParentWriter()->getSpreadsheet(); + + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', 'ViewId'); + $objWriter->writeAttribute('config:type', 'string'); + $objWriter->text('view1'); + $objWriter->endElement(); // ViewId + $objWriter->startElement('config:config-item-map-named'); + $objWriter->writeAttribute('config:name', 'Tables'); + foreach ($spreadsheet->getWorksheetIterator() as $ws) { + $objWriter->startElement('config:config-item-map-entry'); + $objWriter->writeAttribute('config:name', $ws->getTitle()); + $selected = $ws->getSelectedCells(); + if (preg_match('/^([a-z]+)([0-9]+)/i', $selected, $matches) === 1) { + $colSel = Coordinate::columnIndexFromString($matches[1]) - 1; + $rowSel = (int) $matches[2] - 1; + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', 'CursorPositionX'); + $objWriter->writeAttribute('config:type', 'int'); + $objWriter->text($colSel); + $objWriter->endElement(); + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', 'CursorPositionY'); + $objWriter->writeAttribute('config:type', 'int'); + $objWriter->text($rowSel); + $objWriter->endElement(); + } + $objWriter->endElement(); // config:config-item-map-entry + } + $objWriter->endElement(); // config:config-item-map-named + $wstitle = $spreadsheet->getActiveSheet()->getTitle(); + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', 'ActiveTable'); + $objWriter->writeAttribute('config:type', 'string'); + $objWriter->text($wstitle); + $objWriter->endElement(); // config:config-item ActiveTable + + $objWriter->endElement(); // config:config-item-map-entry + $objWriter->endElement(); // config:config-item-map-indexed Views + $objWriter->endElement(); // config:config-item-set ooo:view-settings $objWriter->startElement('config:config-item-set'); $objWriter->writeAttribute('config:name', 'ooo:configuration-settings'); - $objWriter->endElement(); - $objWriter->endElement(); - $objWriter->endElement(); + $objWriter->endElement(); // config:config-item-set ooo:configuration-settings + $objWriter->endElement(); // office:settings + $objWriter->endElement(); // office:document-settings return $objWriter->getData(); } diff --git a/src/PhpSpreadsheet/Writer/Ods/Styles.php b/src/PhpSpreadsheet/Writer/Ods/Styles.php index 7ba7eba7..448b1eff 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Styles.php +++ b/src/PhpSpreadsheet/Writer/Ods/Styles.php @@ -3,18 +3,15 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Ods; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; -use PhpOffice\PhpSpreadsheet\Spreadsheet; class Styles extends WriterPart { /** * Write styles.xml to XML format. * - * @param Spreadsheet $spreadsheet - * * @return string XML Output */ - public function write(?Spreadsheet $spreadsheet = null) + public function write(): string { $objWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { diff --git a/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php b/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php index dfab0654..db9579d0 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php +++ b/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php @@ -2,18 +2,14 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Ods; -use PhpOffice\PhpSpreadsheet\Spreadsheet; - class Thumbnails extends WriterPart { /** * Write Thumbnails/thumbnail.png to PNG format. * - * @param Spreadsheet $spreadsheet - * * @return string XML Output */ - public function writeThumbnail(?Spreadsheet $spreadsheet = null) + public function write(): string { return ''; } diff --git a/src/PhpSpreadsheet/Writer/Ods/WriterPart.php b/src/PhpSpreadsheet/Writer/Ods/WriterPart.php index 1982c450..17d5d169 100644 --- a/src/PhpSpreadsheet/Writer/Ods/WriterPart.php +++ b/src/PhpSpreadsheet/Writer/Ods/WriterPart.php @@ -30,4 +30,6 @@ abstract class WriterPart { $this->parentWriter = $writer; } + + abstract public function write(): string; } diff --git a/src/PhpSpreadsheet/Writer/Pdf.php b/src/PhpSpreadsheet/Writer/Pdf.php index 87220458..36f3966d 100644 --- a/src/PhpSpreadsheet/Writer/Pdf.php +++ b/src/PhpSpreadsheet/Writer/Pdf.php @@ -165,7 +165,7 @@ abstract class Pdf extends Html /** * Set Paper Size. * - * @param string $pValue Paper size see PageSetup::PAPERSIZE_* + * @param int $pValue Paper size see PageSetup::PAPERSIZE_* * * @return self */ diff --git a/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php b/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php index 9ae2ccee..87e8eeb5 100644 --- a/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php +++ b/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php @@ -59,7 +59,7 @@ class Dompdf extends Pdf // Create PDF $pdf = $this->createExternalWriterInstance(); - $pdf->setPaper(strtolower($paperSize), $orientation); + $pdf->setPaper($paperSize, $orientation); $pdf->loadHtml($this->generateHTMLAll()); $pdf->render(); diff --git a/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php b/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php index 75e0010d..56ac6930 100644 --- a/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php +++ b/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php @@ -64,7 +64,7 @@ class Mpdf extends Pdf $config = ['tempDir' => $this->tempDir . '/mpdf']; $pdf = $this->createExternalWriterInstance($config); $ortmp = $orientation; - $pdf->_setPageSize(strtoupper($paperSize), $ortmp); + $pdf->_setPageSize($paperSize, $ortmp); $pdf->DefOrientation = $orientation; $pdf->AddPageByArray([ 'orientation' => $orientation, diff --git a/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php b/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php index 7530b1ef..56e917e3 100644 --- a/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php +++ b/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php @@ -24,7 +24,7 @@ class Tcpdf extends Pdf * * @param string $orientation Page orientation * @param string $unit Unit measure - * @param string $paperSize Paper size + * @param array|string $paperSize Paper size * * @return \TCPDF implementation */ diff --git a/src/PhpSpreadsheet/Writer/Xls.php b/src/PhpSpreadsheet/Writer/Xls.php index c7c2e7d6..a1b477bf 100644 --- a/src/PhpSpreadsheet/Writer/Xls.php +++ b/src/PhpSpreadsheet/Writer/Xls.php @@ -23,6 +23,9 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; +use PhpOffice\PhpSpreadsheet\Writer\Xls\Parser; +use PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook; +use PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet; class Xls extends BaseWriter { @@ -64,7 +67,7 @@ class Xls extends BaseWriter /** * Formula parser. * - * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser + * @var Parser */ private $parser; @@ -78,24 +81,24 @@ class Xls extends BaseWriter /** * Basic OLE object summary information. * - * @var array + * @var string */ private $summaryInformation; /** * Extended OLE object document summary information. * - * @var array + * @var string */ private $documentSummaryInformation; /** - * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook + * @var Workbook */ private $writerWorkbook; /** - * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet[] + * @var Worksheet[] */ private $writerWorksheets; @@ -388,7 +391,7 @@ class Xls extends BaseWriter } } - private function processMemoryDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing, string $renderingFunctionx): void + private function processMemoryDrawing(BstoreContainer &$bstoreContainer, MemoryDrawing $drawing, string $renderingFunctionx): void { switch ($renderingFunctionx) { case MemoryDrawing::RENDERING_JPEG: @@ -418,8 +421,9 @@ class Xls extends BaseWriter $bstoreContainer->addBSE($BSE); } - private function processDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing): void + private function processDrawing(BstoreContainer &$bstoreContainer, Drawing $drawing): void { + $blipType = null; $blipData = ''; $filename = $drawing->getPath(); @@ -723,6 +727,7 @@ 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)); @@ -740,6 +745,7 @@ 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/BIFFwriter.php b/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php index 84e27d0d..4a7e0656 100644 --- a/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php +++ b/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php @@ -49,7 +49,7 @@ class BIFFwriter /** * The string containing the data of the BIFF stream. * - * @var string + * @var null|string */ public $_data; diff --git a/src/PhpSpreadsheet/Writer/Xls/Escher.php b/src/PhpSpreadsheet/Writer/Xls/Escher.php index 1ee2e904..e42139b3 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Escher.php +++ b/src/PhpSpreadsheet/Writer/Xls/Escher.php @@ -420,8 +420,8 @@ class Escher $recType = 0xF010; // start coordinates - [$column, $row] = Coordinate::coordinateFromString($this->object->getStartCoordinates()); - $c1 = Coordinate::columnIndexFromString($column) - 1; + [$column, $row] = Coordinate::indexesFromString($this->object->getStartCoordinates()); + $c1 = $column - 1; $r1 = $row - 1; // start offsetX @@ -431,8 +431,8 @@ class Escher $startOffsetY = $this->object->getStartOffsetY(); // end coordinates - [$column, $row] = Coordinate::coordinateFromString($this->object->getEndCoordinates()); - $c2 = Coordinate::columnIndexFromString($column) - 1; + [$column, $row] = Coordinate::indexesFromString($this->object->getEndCoordinates()); + $c2 = $column - 1; $r2 = $row - 1; // end offsetX diff --git a/src/PhpSpreadsheet/Writer/Xls/Font.php b/src/PhpSpreadsheet/Writer/Xls/Font.php index 9cb31ead..1266cf24 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Font.php +++ b/src/PhpSpreadsheet/Writer/Xls/Font.php @@ -119,7 +119,7 @@ class Font /** * Map of BIFF2-BIFF8 codes for underline styles. * - * @var array of int + * @var int[] */ private static $mapUnderline = [ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE => 0x00, diff --git a/src/PhpSpreadsheet/Writer/Xls/Parser.php b/src/PhpSpreadsheet/Writer/Xls/Parser.php index f89957a4..d49459b3 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Parser.php +++ b/src/PhpSpreadsheet/Writer/Xls/Parser.php @@ -527,11 +527,11 @@ class Parser } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $token) && $this->spreadsheet->getDefinedName($token) !== null) { return $this->convertDefinedName($token); // commented so argument number can be processed correctly. See toReversePolish(). - /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/", $token)) - { - return($this->convertFunction($token, $this->_func_args)); - }*/ - // if it's an argument, ignore the token (the argument remains) + /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/", $token)) + { + return($this->convertFunction($token, $this->_func_args)); + }*/ + // if it's an argument, ignore the token (the argument remains) } elseif ($token == 'arg') { return ''; } @@ -597,10 +597,9 @@ class Parser if ($args >= 0) { return pack('Cv', $this->ptg['ptgFuncV'], $this->functions[$token][0]); } + // Variable number of args eg. SUM($i, $j, $k, ..). - if ($args == -1) { - return pack('CCv', $this->ptg['ptgFuncVarV'], $num_args, $this->functions[$token][0]); - } + return pack('CCv', $this->ptg['ptgFuncVarV'], $num_args, $this->functions[$token][0]); } /** @@ -747,7 +746,7 @@ class Parser return pack('C', 0xFF); } - private function convertDefinedName(string $name): void + private function convertDefinedName(string $name): string { if (strlen($name) > 255) { throw new WriterException('Defined Name is too long'); @@ -764,7 +763,8 @@ class Parser $ptgRef = pack('Cvxx', $this->ptg['ptgName'], $nameReference); throw new WriterException('Cannot yet write formulae with defined names to Xls'); -// return $ptgRef; + + return $ptgRef; } /** @@ -851,10 +851,10 @@ class Parser * called by the addWorksheet() method of the * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class. * - * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::addWorksheet() - * * @param string $name The name of the worksheet being added * @param int $index The index of the worksheet being added + * + * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::addWorksheet() */ public function setExtSheet($name, $index): void { @@ -968,6 +968,7 @@ class Parser */ private function advance() { + $token = ''; $i = $this->currentCharacter; $formula_length = strlen($this->formula); // eat up white spaces @@ -1229,9 +1230,9 @@ class Parser * This function just introduces a ptgParen element in the tree, so that Excel * doesn't get confused when working with a parenthesized formula afterwards. * - * @see fact() - * * @return array The parsed ptg'd tree + * + * @see fact() */ private function parenthesizedExpression() { @@ -1473,6 +1474,7 @@ class Parser } else { $left_tree = ''; } + // add it's left subtree and return. return $left_tree . $this->convertFunction($tree['value'], $tree['right']); } diff --git a/src/PhpSpreadsheet/Writer/Xls/Workbook.php b/src/PhpSpreadsheet/Writer/Xls/Workbook.php index f752bce6..a917185d 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Workbook.php +++ b/src/PhpSpreadsheet/Writer/Xls/Workbook.php @@ -551,8 +551,8 @@ class Workbook extends BIFFwriter $newRange = ''; if (empty($worksheet)) { if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) { - // We need a worksheet - $worksheet = $pDefinedName->getWorksheet()->getTitle(); + // We should have a worksheet + $worksheet = $pDefinedName->getWorksheet() ? $pDefinedName->getWorksheet()->getTitle() : null; } } else { $worksheet = str_replace("''", "'", trim($worksheet, "'")); @@ -678,13 +678,13 @@ class Workbook extends BIFFwriter $formulaData = ''; for ($j = 0; $j < $countPrintArea; ++$j) { $printAreaRect = $printArea[$j]; // e.g. A3:J6 - $printAreaRect[0] = Coordinate::coordinateFromString($printAreaRect[0]); - $printAreaRect[1] = Coordinate::coordinateFromString($printAreaRect[1]); + $printAreaRect[0] = Coordinate::indexesFromString($printAreaRect[0]); + $printAreaRect[1] = Coordinate::indexesFromString($printAreaRect[1]); $print_rowmin = $printAreaRect[0][1] - 1; $print_rowmax = $printAreaRect[1][1] - 1; - $print_colmin = Coordinate::columnIndexFromString($printAreaRect[0][0]) - 1; - $print_colmax = Coordinate::columnIndexFromString($printAreaRect[1][0]) - 1; + $print_colmin = $printAreaRect[0][0] - 1; + $print_colmax = $printAreaRect[1][0] - 1; // construct formula data manually because parser does not recognize absolute 3d cell references $formulaData .= pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax); @@ -756,8 +756,8 @@ class Workbook extends BIFFwriter * Write a short NAME record. * * @param string $name - * @param string $sheetIndex 1-based sheet index the defined name applies to. 0 = global - * @param integer[][] $rangeBounds range boundaries + * @param int $sheetIndex 1-based sheet index the defined name applies to. 0 = global + * @param int[][] $rangeBounds range boundaries * @param bool $isHidden * * @return string Complete binary record data @@ -839,10 +839,9 @@ class Workbook extends BIFFwriter /** * Writes Excel BIFF BOUNDSHEET record. * - * @param Worksheet $sheet Worksheet name * @param int $offset Location of worksheet BOF */ - private function writeBoundSheet($sheet, $offset): void + private function writeBoundSheet(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $sheet, $offset): void { $sheetname = $sheet->getTitle(); $record = 0x0085; // Record identifier diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index a793128a..7051ab2d 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -95,7 +95,7 @@ class Worksheet extends BIFFwriter /** * Whether to use outline. * - * @var int + * @var bool */ private $outlineOn; @@ -217,8 +217,8 @@ class Worksheet extends BIFFwriter * * @param int $str_total Total number of strings * @param int $str_unique Total number of unique strings - * @param array &$str_table String Table - * @param array &$colors Colour Table + * @param array $str_table String Table + * @param array $colors Colour Table * @param Parser $parser The formula parser created for the Workbook * @param bool $preCalculateFormulas Flag indicating whether formulas should be calculated or just written * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $phpSheet The worksheet to write @@ -244,10 +244,10 @@ class Worksheet extends BIFFwriter $this->printHeaders = 0; - $this->outlineStyle = 0; - $this->outlineBelow = 1; - $this->outlineRight = 1; - $this->outlineOn = 1; + $this->outlineStyle = false; + $this->outlineBelow = true; + $this->outlineRight = true; + $this->outlineOn = true; $this->fontHashIndex = []; @@ -512,7 +512,7 @@ class Worksheet extends BIFFwriter // Hyperlinks foreach ($phpSheet->getHyperLinkCollection() as $coordinate => $hyperlink) { - [$column, $row] = Coordinate::coordinateFromString($coordinate); + [$column, $row] = Coordinate::indexesFromString($coordinate); $url = $hyperlink->getUrl(); @@ -526,7 +526,7 @@ class Worksheet extends BIFFwriter $url = 'external:' . $url; } - $this->writeUrl($row - 1, Coordinate::columnIndexFromString($column) - 1, $url); + $this->writeUrl($row - 1, $column - 1, $url); } $this->writeDataValidity(); @@ -587,22 +587,20 @@ class Worksheet extends BIFFwriter $lastCell = $explodes[1]; } - $firstCellCoordinates = Coordinate::coordinateFromString($firstCell); // e.g. [0, 1] - $lastCellCoordinates = Coordinate::coordinateFromString($lastCell); // e.g. [1, 6] + $firstCellCoordinates = Coordinate::indexesFromString($firstCell); // e.g. [0, 1] + $lastCellCoordinates = Coordinate::indexesFromString($lastCell); // e.g. [1, 6] - return pack('vvvv', $firstCellCoordinates[1] - 1, $lastCellCoordinates[1] - 1, Coordinate::columnIndexFromString($firstCellCoordinates[0]) - 1, Coordinate::columnIndexFromString($lastCellCoordinates[0]) - 1); + return pack('vvvv', $firstCellCoordinates[1] - 1, $lastCellCoordinates[1] - 1, $firstCellCoordinates[0] - 1, $lastCellCoordinates[0] - 1); } /** - * Retrieves data from memory in one chunk, or from disk in $buffer + * Retrieves data from memory in one chunk, or from disk * sized chunks. * * @return string The data */ public function getData() { - $buffer = 4096; - // Return data stored in memory if (isset($this->_data)) { $tmp = $this->_data; @@ -612,7 +610,7 @@ class Worksheet extends BIFFwriter } // No data to return - return false; + return ''; } /** @@ -640,11 +638,6 @@ class Worksheet extends BIFFwriter $this->outlineBelow = $symbols_below; $this->outlineRight = $symbols_right; $this->outlineStyle = $auto_style; - - // Ensure this is a boolean vale for Window2 - if ($this->outlineOn) { - $this->outlineOn = 1; - } } /** @@ -926,20 +919,14 @@ class Worksheet extends BIFFwriter * The hyperlink can be to a http, ftp, mail, internal sheet (not yet), or external * directory url. * - * Returns 0 : normal termination - * -2 : row or column out of range - * -3 : long string truncated to 255 chars - * * @param int $row Row * @param int $col Column * @param string $url URL string - * - * @return int */ - private function writeUrl($row, $col, $url) + private function writeUrl($row, $col, $url): void { // Add start row and col to arg list - return $this->writeUrlRange($row, $col, $row, $col, $url); + $this->writeUrlRange($row, $col, $row, $col, $url); } /** @@ -954,21 +941,19 @@ class Worksheet extends BIFFwriter * @param int $col2 End column * @param string $url URL string * - * @return int - * * @see writeUrl() */ - public function writeUrlRange($row1, $col1, $row2, $col2, $url) + private function writeUrlRange($row1, $col1, $row2, $col2, $url): void { // Check for internal/external sheet links or default to web link if (preg_match('[^internal:]', $url)) { - return $this->writeUrlInternal($row1, $col1, $row2, $col2, $url); + $this->writeUrlInternal($row1, $col1, $row2, $col2, $url); } if (preg_match('[^external:]', $url)) { - return $this->writeUrlExternal($row1, $col1, $row2, $col2, $url); + $this->writeUrlExternal($row1, $col1, $row2, $col2, $url); } - return $this->writeUrlWeb($row1, $col1, $row2, $col2, $url); + $this->writeUrlWeb($row1, $col1, $row2, $col2, $url); } /** @@ -982,14 +967,11 @@ class Worksheet extends BIFFwriter * @param int $col2 End column * @param string $url URL string * - * @return int - * * @see writeUrl() */ - public function writeUrlWeb($row1, $col1, $row2, $col2, $url) + public function writeUrlWeb($row1, $col1, $row2, $col2, $url): void { $record = 0x01B8; // Record identifier - $length = 0x00000; // Bytes to follow // Pack the undocumented parts of the hyperlink stream $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000'); @@ -1014,8 +996,6 @@ class Worksheet extends BIFFwriter // Write the packed data $this->append($header . $data . $unknown1 . $options . $unknown2 . $url_len . $url); - - return 0; } /** @@ -1027,14 +1007,11 @@ class Worksheet extends BIFFwriter * @param int $col2 End column * @param string $url URL string * - * @return int - * * @see writeUrl() */ - public function writeUrlInternal($row1, $col1, $row2, $col2, $url) + private function writeUrlInternal($row1, $col1, $row2, $col2, $url): void { $record = 0x01B8; // Record identifier - $length = 0x00000; // Bytes to follow // Strip URL type $url = preg_replace('/^internal:/', '', $url); @@ -1063,8 +1040,6 @@ class Worksheet extends BIFFwriter // Write the packed data $this->append($header . $data . $unknown1 . $options . $url_len . $url); - - return 0; } /** @@ -1080,16 +1055,14 @@ class Worksheet extends BIFFwriter * @param int $col2 End column * @param string $url URL string * - * @return int - * * @see writeUrl() */ - public function writeUrlExternal($row1, $col1, $row2, $col2, $url) + private function writeUrlExternal($row1, $col1, $row2, $col2, $url): void { // Network drives are different. We will handle them separately // MS/Novell network drives and shares start with \\ if (preg_match('[^external:\\\\]', $url)) { - return; //($this->writeUrlExternal_net($row1, $col1, $row2, $col2, $url, $str, $format)); + return; } $record = 0x01B8; // Record identifier @@ -1165,8 +1138,6 @@ class Worksheet extends BIFFwriter // Write the packed data $this->append($header . $data); - - return 0; } /** @@ -1344,32 +1315,13 @@ class Worksheet extends BIFFwriter */ private function writeColinfo($col_array): void { - if (isset($col_array[0])) { - $colFirst = $col_array[0]; - } - if (isset($col_array[1])) { - $colLast = $col_array[1]; - } - if (isset($col_array[2])) { - $coldx = $col_array[2]; - } else { - $coldx = 8.43; - } - if (isset($col_array[3])) { - $xfIndex = $col_array[3]; - } else { - $xfIndex = 15; - } - if (isset($col_array[4])) { - $grbit = $col_array[4]; - } else { - $grbit = 0; - } - if (isset($col_array[5])) { - $level = $col_array[5]; - } else { - $level = 0; - } + $colFirst = $col_array[0] ?? null; + $colLast = $col_array[1] ?? null; + $coldx = $col_array[2] ?? 8.43; + $xfIndex = $col_array[3] ?? 15; + $grbit = $col_array[4] ?? 0; + $level = $col_array[5] ?? 0; + $record = 0x007D; // Record identifier $length = 0x000C; // Number of bytes to follow @@ -1425,13 +1377,6 @@ class Worksheet extends BIFFwriter $irefAct = 0; // Active cell ref $cref = 1; // Number of refs - if (!isset($rwLast)) { - $rwLast = $rwFirst; // Last row in reference - } - if (!isset($colLast)) { - $colLast = $colFirst; // Last col in reference - } - // Swap last row/col for first row/col as necessary if ($rwFirst > $rwLast) { [$rwFirst, $rwLast] = [$rwLast, $rwFirst]; @@ -1481,10 +1426,10 @@ class Worksheet extends BIFFwriter // extract the row and column indexes $range = Coordinate::splitRange($mergeCell); [$first, $last] = $range[0]; - [$firstColumn, $firstRow] = Coordinate::coordinateFromString($first); - [$lastColumn, $lastRow] = Coordinate::coordinateFromString($last); + [$firstColumn, $firstRow] = Coordinate::indexesFromString($first); + [$lastColumn, $lastRow] = Coordinate::indexesFromString($last); - $recordData .= pack('vvvv', $firstRow - 1, $lastRow - 1, Coordinate::columnIndexFromString($firstColumn) - 1, Coordinate::columnIndexFromString($lastColumn) - 1); + $recordData .= pack('vvvv', $firstRow - 1, $lastRow - 1, $firstColumn - 1, $lastColumn - 1); // flush record if we have reached limit for number of merged cells, or reached final merged cell if ($j == $maxCountMergeCellsPerRecord || $i == $countMergeCells) { @@ -1627,76 +1572,37 @@ class Worksheet extends BIFFwriter */ private function writePanes(): void { - $panes = []; - if ($this->phpSheet->getFreezePane()) { - [$column, $row] = Coordinate::coordinateFromString($this->phpSheet->getFreezePane()); - $panes[0] = Coordinate::columnIndexFromString($column) - 1; - $panes[1] = $row - 1; - - [$leftMostColumn, $topRow] = Coordinate::coordinateFromString($this->phpSheet->getTopLeftCell()); - //Coordinates are zero-based in xls files - $panes[2] = $topRow - 1; - $panes[3] = Coordinate::columnIndexFromString($leftMostColumn) - 1; - } else { + if (!$this->phpSheet->getFreezePane()) { // thaw panes return; } - $x = $panes[0] ?? null; - $y = $panes[1] ?? null; - $rwTop = $panes[2] ?? null; - $colLeft = $panes[3] ?? null; - if (count($panes) > 4) { // if Active pane was received - $pnnAct = $panes[4]; - } else { - $pnnAct = null; - } + [$column, $row] = Coordinate::indexesFromString($this->phpSheet->getFreezePane()); + $x = $column - 1; + $y = $row - 1; + + [$leftMostColumn, $topRow] = Coordinate::indexesFromString($this->phpSheet->getTopLeftCell()); + //Coordinates are zero-based in xls files + $rwTop = $topRow - 1; + $colLeft = $leftMostColumn - 1; + $record = 0x0041; // Record identifier $length = 0x000A; // Number of bytes to follow - // Code specific to frozen or thawed panes. - if ($this->phpSheet->getFreezePane()) { - // Set default values for $rwTop and $colLeft - if (!isset($rwTop)) { - $rwTop = $y; - } - if (!isset($colLeft)) { - $colLeft = $x; - } - } else { - // Set default values for $rwTop and $colLeft - if (!isset($rwTop)) { - $rwTop = 0; - } - if (!isset($colLeft)) { - $colLeft = 0; - } - - // Convert Excel's row and column units to the internal units. - // The default row height is 12.75 - // The default column width is 8.43 - // The following slope and intersection values were interpolated. - // - $y = 20 * $y + 255; - $x = 113.879 * $x + 390; - } - // Determine which pane should be active. There is also the undocumented // option to override this should it be necessary: may be removed later. - // - if (!isset($pnnAct)) { - if ($x != 0 && $y != 0) { - $pnnAct = 0; // Bottom right - } - if ($x != 0 && $y == 0) { - $pnnAct = 1; // Top right - } - if ($x == 0 && $y != 0) { - $pnnAct = 2; // Bottom left - } - if ($x == 0 && $y == 0) { - $pnnAct = 3; // Top left - } + $pnnAct = null; + if ($x != 0 && $y != 0) { + $pnnAct = 0; // Bottom right + } + if ($x != 0 && $y == 0) { + $pnnAct = 1; // Top right + } + if ($x == 0 && $y != 0) { + $pnnAct = 2; // Bottom left + } + if ($x == 0 && $y == 0) { + $pnnAct = 3; // Top left } $this->activePane = $pnnAct; // Used in writeSelection @@ -1715,9 +1621,7 @@ class Worksheet extends BIFFwriter $length = 0x0022; // Number of bytes to follow $iPaperSize = $this->phpSheet->getPageSetup()->getPaperSize(); // Paper size - - $iScale = $this->phpSheet->getPageSetup()->getScale() ? - $this->phpSheet->getPageSetup()->getScale() : 100; // Print scaling factor + $iScale = $this->phpSheet->getPageSetup()->getScale() ?: 100; // Print scaling factor $iPageStart = 0x01; // Starting page number $iFitWidth = (int) $this->phpSheet->getPageSetup()->getFitToWidth(); // Fit to number of pages wide @@ -2976,9 +2880,9 @@ class Worksheet extends BIFFwriter private function writeCFRule(Conditional $conditional): void { $record = 0x01B1; // Record identifier + $type = null; // Type of the CF + $operatorType = null; // Comparison operator - // $type : Type of the CF - // $operatorType : Comparison operator if ($conditional->getConditionType() == Conditional::CONDITION_EXPRESSION) { $type = 0x02; $operatorType = 0x00; @@ -3143,6 +3047,11 @@ class Worksheet extends BIFFwriter // Text direction $flags |= (1 == 0 ? 0x80000000 : 0); + $dataBlockFont = null; + $dataBlockAlign = null; + $dataBlockBorder = null; + $dataBlockFill = null; + // Data Blocks if ($bFormatFont == 1) { // Font Name @@ -4400,15 +4309,6 @@ class Worksheet extends BIFFwriter $dataBlockFill = pack('v', $blockFillPatternStyle); $dataBlockFill .= pack('v', $colorIdxFg | ($colorIdxBg << 7)); } - if ($bFormatProt == 1) { - $dataBlockProtection = 0; - if ($conditional->getStyle()->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED) { - $dataBlockProtection = 1; - } - if ($conditional->getStyle()->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED) { - $dataBlockProtection = 1 << 1; - } - } $data = pack('CCvvVv', $type, $operatorType, $szValue1, $szValue2, $flags, 0x0000); if ($bFormatFont == 1) { // Block Formatting : OK @@ -4424,7 +4324,7 @@ class Worksheet extends BIFFwriter $data .= $dataBlockFill; } if ($bFormatProt == 1) { - $data .= $dataBlockProtection; + $data .= $this->getDataBlockProtection($conditional); } if ($operand1 !== null) { $data .= $operand1; @@ -4459,10 +4359,7 @@ class Worksheet extends BIFFwriter $arrConditional[] = $conditional->getHashCode(); } // Cells - $arrCoord = Coordinate::coordinateFromString($cellCoordinate); - if (!is_numeric($arrCoord[0])) { - $arrCoord[0] = Coordinate::columnIndexFromString($arrCoord[0]); - } + $arrCoord = Coordinate::indexesFromString($cellCoordinate); if ($numColumnMin === null || ($numColumnMin > $arrCoord[0])) { $numColumnMin = $arrCoord[0]; } @@ -4488,4 +4385,17 @@ class Worksheet extends BIFFwriter $data .= $cellRange; $this->append($header . $data); } + + private function getDataBlockProtection(Conditional $conditional): int + { + $dataBlockProtection = 0; + if ($conditional->getStyle()->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED) { + $dataBlockProtection = 1; + } + if ($conditional->getStyle()->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED) { + $dataBlockProtection = 1 << 1; + } + + return $dataBlockProtection; + } } diff --git a/src/PhpSpreadsheet/Writer/Xls/Xf.php b/src/PhpSpreadsheet/Writer/Xls/Xf.php index 3e8169b3..6f1d1cb2 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Xf.php +++ b/src/PhpSpreadsheet/Writer/Xls/Xf.php @@ -115,6 +115,21 @@ class Xf */ private $rightBorderColor; + /** + * @var int + */ + private $diag; + + /** + * @var int + */ + private $diagColor; + + /** + * @var Style + */ + private $style; + /** * Constructor. * @@ -132,14 +147,14 @@ class Xf $this->foregroundColor = 0x40; $this->backgroundColor = 0x41; - $this->_diag = 0; + $this->diag = 0; $this->bottomBorderColor = 0x40; $this->topBorderColor = 0x40; $this->leftBorderColor = 0x40; $this->rightBorderColor = 0x40; - $this->_diag_color = 0x40; - $this->_style = $style; + $this->diagColor = 0x40; + $this->style = $style; } /** @@ -153,39 +168,39 @@ class Xf if ($this->isStyleXf) { $style = 0xFFF5; } else { - $style = self::mapLocked($this->_style->getProtection()->getLocked()); - $style |= self::mapHidden($this->_style->getProtection()->getHidden()) << 1; + $style = self::mapLocked($this->style->getProtection()->getLocked()); + $style |= self::mapHidden($this->style->getProtection()->getHidden()) << 1; } // Flags to indicate if attributes have been set. $atr_num = ($this->numberFormatIndex != 0) ? 1 : 0; $atr_fnt = ($this->fontIndex != 0) ? 1 : 0; - $atr_alc = ((int) $this->_style->getAlignment()->getWrapText()) ? 1 : 0; - $atr_bdr = (self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) || - self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) || - self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()) || - self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle())) ? 1 : 0; - $atr_pat = (($this->foregroundColor != 0x40) || - ($this->backgroundColor != 0x41) || - self::mapFillType($this->_style->getFill()->getFillType())) ? 1 : 0; - $atr_prot = self::mapLocked($this->_style->getProtection()->getLocked()) - | self::mapHidden($this->_style->getProtection()->getHidden()); + $atr_alc = ((int) $this->style->getAlignment()->getWrapText()) ? 1 : 0; + $atr_bdr = (self::mapBorderStyle($this->style->getBorders()->getBottom()->getBorderStyle()) || + self::mapBorderStyle($this->style->getBorders()->getTop()->getBorderStyle()) || + self::mapBorderStyle($this->style->getBorders()->getLeft()->getBorderStyle()) || + self::mapBorderStyle($this->style->getBorders()->getRight()->getBorderStyle())) ? 1 : 0; + $atr_pat = ($this->foregroundColor != 0x40) ? 1 : 0; + $atr_pat = ($this->backgroundColor != 0x41) ? 1 : $atr_pat; + $atr_pat = self::mapFillType($this->style->getFill()->getFillType()) ? 1 : $atr_pat; + $atr_prot = self::mapLocked($this->style->getProtection()->getLocked()) + | self::mapHidden($this->style->getProtection()->getHidden()); // Zero the default border colour if the border has not been set. - if (self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) == 0) { + if (self::mapBorderStyle($this->style->getBorders()->getBottom()->getBorderStyle()) == 0) { $this->bottomBorderColor = 0; } - if (self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) == 0) { + if (self::mapBorderStyle($this->style->getBorders()->getTop()->getBorderStyle()) == 0) { $this->topBorderColor = 0; } - if (self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle()) == 0) { + if (self::mapBorderStyle($this->style->getBorders()->getRight()->getBorderStyle()) == 0) { $this->rightBorderColor = 0; } - if (self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()) == 0) { + if (self::mapBorderStyle($this->style->getBorders()->getLeft()->getBorderStyle()) == 0) { $this->leftBorderColor = 0; } - if (self::mapBorderStyle($this->_style->getBorders()->getDiagonal()->getBorderStyle()) == 0) { - $this->_diag_color = 0; + if (self::mapBorderStyle($this->style->getBorders()->getDiagonal()->getBorderStyle()) == 0) { + $this->diagColor = 0; } $record = 0x00E0; // Record identifier @@ -194,9 +209,9 @@ class Xf $ifnt = $this->fontIndex; // Index to FONT record $ifmt = $this->numberFormatIndex; // Index to FORMAT record - $align = $this->mapHAlign($this->_style->getAlignment()->getHorizontal()); // Alignment - $align |= (int) $this->_style->getAlignment()->getWrapText() << 3; - $align |= self::mapVAlign($this->_style->getAlignment()->getVertical()) << 4; + $align = $this->mapHAlign($this->style->getAlignment()->getHorizontal()); // Alignment + $align |= (int) $this->style->getAlignment()->getWrapText() << 3; + $align |= self::mapVAlign($this->style->getAlignment()->getVertical()) << 4; $align |= $this->textJustLast << 7; $used_attrib = $atr_num << 2; @@ -209,35 +224,35 @@ class Xf $icv = $this->foregroundColor; // fg and bg pattern colors $icv |= $this->backgroundColor << 7; - $border1 = self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()); // Border line style and color - $border1 |= self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle()) << 4; - $border1 |= self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) << 8; - $border1 |= self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) << 12; + $border1 = self::mapBorderStyle($this->style->getBorders()->getLeft()->getBorderStyle()); // Border line style and color + $border1 |= self::mapBorderStyle($this->style->getBorders()->getRight()->getBorderStyle()) << 4; + $border1 |= self::mapBorderStyle($this->style->getBorders()->getTop()->getBorderStyle()) << 8; + $border1 |= self::mapBorderStyle($this->style->getBorders()->getBottom()->getBorderStyle()) << 12; $border1 |= $this->leftBorderColor << 16; $border1 |= $this->rightBorderColor << 23; - $diagonalDirection = $this->_style->getBorders()->getDiagonalDirection(); + $diagonalDirection = $this->style->getBorders()->getDiagonalDirection(); $diag_tl_to_rb = $diagonalDirection == Borders::DIAGONAL_BOTH - || $diagonalDirection == Borders::DIAGONAL_DOWN; + || $diagonalDirection == Borders::DIAGONAL_DOWN; $diag_tr_to_lb = $diagonalDirection == Borders::DIAGONAL_BOTH - || $diagonalDirection == Borders::DIAGONAL_UP; + || $diagonalDirection == Borders::DIAGONAL_UP; $border1 |= $diag_tl_to_rb << 30; $border1 |= $diag_tr_to_lb << 31; $border2 = $this->topBorderColor; // Border color $border2 |= $this->bottomBorderColor << 7; - $border2 |= $this->_diag_color << 14; - $border2 |= self::mapBorderStyle($this->_style->getBorders()->getDiagonal()->getBorderStyle()) << 21; - $border2 |= self::mapFillType($this->_style->getFill()->getFillType()) << 26; + $border2 |= $this->diagColor << 14; + $border2 |= self::mapBorderStyle($this->style->getBorders()->getDiagonal()->getBorderStyle()) << 21; + $border2 |= self::mapFillType($this->style->getFill()->getFillType()) << 26; $header = pack('vv', $record, $length); //BIFF8 options: identation, shrinkToFit and text direction - $biff8_options = $this->_style->getAlignment()->getIndent(); - $biff8_options |= (int) $this->_style->getAlignment()->getShrinkToFit() << 4; + $biff8_options = $this->style->getAlignment()->getIndent(); + $biff8_options |= (int) $this->style->getAlignment()->getShrinkToFit() << 4; $data = pack('vvvC', $ifnt, $ifmt, $style, $align); - $data .= pack('CCC', self::mapTextRotation($this->_style->getAlignment()->getTextRotation()), $biff8_options, $used_attrib); + $data .= pack('CCC', self::mapTextRotation($this->style->getAlignment()->getTextRotation()), $biff8_options, $used_attrib); $data .= pack('VVv', $border1, $border2, $icv); return $header . $data; @@ -300,7 +315,7 @@ class Xf */ public function setDiagColor($colorIndex): void { - $this->_diag_color = $colorIndex; + $this->diagColor = $colorIndex; } /** @@ -347,7 +362,7 @@ class Xf /** * Map of BIFF2-BIFF8 codes for border styles. * - * @var array of int + * @var int[] */ private static $mapBorderStyles = [ Border::BORDER_NONE => 0x00, @@ -375,17 +390,13 @@ class Xf */ private static function mapBorderStyle($borderStyle) { - if (isset(self::$mapBorderStyles[$borderStyle])) { - return self::$mapBorderStyles[$borderStyle]; - } - - return 0x00; + return self::$mapBorderStyles[$borderStyle] ?? 0; } /** * Map of BIFF2-BIFF8 codes for fill types. * - * @var array of int + * @var int[] */ private static $mapFillTypes = [ Fill::FILL_NONE => 0x00, @@ -420,17 +431,13 @@ class Xf */ private static function mapFillType($fillType) { - if (isset(self::$mapFillTypes[$fillType])) { - return self::$mapFillTypes[$fillType]; - } - - return 0x00; + return self::$mapFillTypes[$fillType] ?? 0; } /** * Map of BIFF2-BIFF8 codes for horizontal alignment. * - * @var array of int + * @var int[] */ private static $mapHAlignments = [ Alignment::HORIZONTAL_GENERAL => 0, @@ -451,17 +458,13 @@ class Xf */ private function mapHAlign($hAlign) { - if (isset(self::$mapHAlignments[$hAlign])) { - return self::$mapHAlignments[$hAlign]; - } - - return 0; + return self::$mapHAlignments[$hAlign] ?? 0; } /** * Map of BIFF2-BIFF8 codes for vertical alignment. * - * @var array of int + * @var int[] */ private static $mapVAlignments = [ Alignment::VERTICAL_TOP => 0, @@ -479,11 +482,7 @@ class Xf */ private static function mapVAlign($vAlign) { - if (isset(self::$mapVAlignments[$vAlign])) { - return self::$mapVAlignments[$vAlign]; - } - - return 2; + return self::$mapVAlignments[$vAlign] ?? 2; } /** @@ -497,15 +496,22 @@ class Xf { if ($textRotation >= 0) { return $textRotation; - } elseif ($textRotation == -165) { - return 255; - } elseif ($textRotation < 0) { - return 90 - $textRotation; } + if ($textRotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) { + return Alignment::TEXTROTATION_STACK_EXCEL; + } + + return 90 - $textRotation; } + private const LOCK_ARRAY = [ + Protection::PROTECTION_INHERIT => 1, + Protection::PROTECTION_PROTECTED => 1, + Protection::PROTECTION_UNPROTECTED => 0, + ]; + /** - * Map locked. + * Map locked values. * * @param string $locked * @@ -513,18 +519,15 @@ class Xf */ private static function mapLocked($locked) { - switch ($locked) { - case Protection::PROTECTION_INHERIT: - return 1; - case Protection::PROTECTION_PROTECTED: - return 1; - case Protection::PROTECTION_UNPROTECTED: - return 0; - default: - return 1; - } + return array_key_exists($locked, self::LOCK_ARRAY) ? self::LOCK_ARRAY[$locked] : 1; } + private const HIDDEN_ARRAY = [ + Protection::PROTECTION_INHERIT => 0, + Protection::PROTECTION_PROTECTED => 1, + Protection::PROTECTION_UNPROTECTED => 0, + ]; + /** * Map hidden. * @@ -534,15 +537,6 @@ class Xf */ private static function mapHidden($hidden) { - switch ($hidden) { - case Protection::PROTECTION_INHERIT: - return 0; - case Protection::PROTECTION_PROTECTED: - return 1; - case Protection::PROTECTION_UNPROTECTED: - return 0; - default: - return 0; - } + return array_key_exists($hidden, self::HIDDEN_ARRAY) ? self::HIDDEN_ARRAY[$hidden] : 0; } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index d71541c8..27ceb559 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -5,8 +5,13 @@ namespace PhpOffice\PhpSpreadsheet\Writer; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\HashTable; -use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Borders; +use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PhpOffice\PhpSpreadsheet\Style\Fill; +use PhpOffice\PhpSpreadsheet\Style\Font; +use PhpOffice\PhpSpreadsheet\Style\NumberFormat; +use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing as WorksheetDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; @@ -37,13 +42,6 @@ class Xlsx extends BaseWriter */ private $office2003compatibility = false; - /** - * Private writer parts. - * - * @var Xlsx\WriterPart[] - */ - private $writerParts = []; - /** * Private Spreadsheet. * @@ -61,49 +59,49 @@ class Xlsx extends BaseWriter /** * Private unique Conditional HashTable. * - * @var HashTable + * @var HashTable */ private $stylesConditionalHashTable; /** * Private unique Style HashTable. * - * @var HashTable + * @var HashTable<\PhpOffice\PhpSpreadsheet\Style\Style> */ private $styleHashTable; /** * Private unique Fill HashTable. * - * @var HashTable + * @var HashTable */ private $fillHashTable; /** * Private unique \PhpOffice\PhpSpreadsheet\Style\Font HashTable. * - * @var HashTable + * @var HashTable */ private $fontHashTable; /** * Private unique Borders HashTable. * - * @var HashTable + * @var HashTable */ private $bordersHashTable; /** * Private unique NumberFormat HashTable. * - * @var HashTable + * @var HashTable */ private $numFmtHashTable; /** * Private unique \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable. * - * @var HashTable + * @var HashTable */ private $drawingHashTable; @@ -114,6 +112,71 @@ class Xlsx extends BaseWriter */ private $zip; + /** + * @var Chart + */ + private $writerPartChart; + + /** + * @var Comments + */ + private $writerPartComments; + + /** + * @var ContentTypes + */ + private $writerPartContentTypes; + + /** + * @var DocProps + */ + private $writerPartDocProps; + + /** + * @var Drawing + */ + private $writerPartDrawing; + + /** + * @var Rels + */ + private $writerPartRels; + + /** + * @var RelsRibbon + */ + private $writerPartRelsRibbon; + + /** + * @var RelsVBA + */ + private $writerPartRelsVBA; + + /** + * @var StringTable + */ + private $writerPartStringTable; + + /** + * @var Style + */ + private $writerPartStyle; + + /** + * @var Theme + */ + private $writerPartTheme; + + /** + * @var Workbook + */ + private $writerPartWorkbook; + + /** + * @var Worksheet + */ + private $writerPartWorksheet; + /** * Create a new Xlsx Writer. */ @@ -122,53 +185,100 @@ class Xlsx extends BaseWriter // Assign PhpSpreadsheet $this->setSpreadsheet($spreadsheet); - $writerPartsArray = [ - 'stringtable' => StringTable::class, - 'contenttypes' => ContentTypes::class, - 'docprops' => DocProps::class, - 'rels' => Rels::class, - 'theme' => Theme::class, - 'style' => Style::class, - 'workbook' => Workbook::class, - 'worksheet' => Worksheet::class, - 'drawing' => Drawing::class, - 'comments' => Comments::class, - 'chart' => Chart::class, - 'relsvba' => RelsVBA::class, - 'relsribbonobjects' => RelsRibbon::class, - ]; - - // Initialise writer parts - // and Assign their parent IWriters - foreach ($writerPartsArray as $writer => $class) { - $this->writerParts[$writer] = new $class($this); - } - - $hashTablesArray = ['stylesConditionalHashTable', 'fillHashTable', 'fontHashTable', - 'bordersHashTable', 'numFmtHashTable', 'drawingHashTable', - 'styleHashTable', - ]; + $this->writerPartChart = new Chart($this); + $this->writerPartComments = new Comments($this); + $this->writerPartContentTypes = new ContentTypes($this); + $this->writerPartDocProps = new DocProps($this); + $this->writerPartDrawing = new Drawing($this); + $this->writerPartRels = new Rels($this); + $this->writerPartRelsRibbon = new RelsRibbon($this); + $this->writerPartRelsVBA = new RelsVBA($this); + $this->writerPartStringTable = new StringTable($this); + $this->writerPartStyle = new Style($this); + $this->writerPartTheme = new Theme($this); + $this->writerPartWorkbook = new Workbook($this); + $this->writerPartWorksheet = new Worksheet($this); // Set HashTable variables - foreach ($hashTablesArray as $tableName) { - $this->$tableName = new HashTable(); - } + // @phpstan-ignore-next-line + $this->bordersHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->drawingHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->fillHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->fontHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->numFmtHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->styleHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->stylesConditionalHashTable = new HashTable(); } - /** - * Get writer part. - * - * @param string $pPartName Writer part name - * - * @return \PhpOffice\PhpSpreadsheet\Writer\Xlsx\WriterPart - */ - public function getWriterPart($pPartName) + public function getWriterPartChart(): Chart { - if ($pPartName != '' && isset($this->writerParts[strtolower($pPartName)])) { - return $this->writerParts[strtolower($pPartName)]; - } + return $this->writerPartChart; + } - return null; + public function getWriterPartComments(): Comments + { + return $this->writerPartComments; + } + + public function getWriterPartContentTypes(): ContentTypes + { + return $this->writerPartContentTypes; + } + + public function getWriterPartDocProps(): DocProps + { + return $this->writerPartDocProps; + } + + public function getWriterPartDrawing(): Drawing + { + return $this->writerPartDrawing; + } + + public function getWriterPartRels(): Rels + { + return $this->writerPartRels; + } + + public function getWriterPartRelsRibbon(): RelsRibbon + { + return $this->writerPartRelsRibbon; + } + + public function getWriterPartRelsVBA(): RelsVBA + { + return $this->writerPartRelsVBA; + } + + public function getWriterPartStringTable(): StringTable + { + return $this->writerPartStringTable; + } + + public function getWriterPartStyle(): Style + { + return $this->writerPartStyle; + } + + public function getWriterPartTheme(): Theme + { + return $this->writerPartTheme; + } + + public function getWriterPartWorkbook(): Workbook + { + return $this->writerPartWorkbook; + } + + public function getWriterPartWorksheet(): Worksheet + { + return $this->writerPartWorksheet; } /** @@ -182,8 +292,6 @@ class Xlsx extends BaseWriter $this->pathNames = []; $this->spreadSheet->garbageCollect(); - $this->openFileHandle($pFilename); - $saveDebugLog = Calculation::getInstance($this->spreadSheet)->getDebugLog()->getWriteDebugLog(); Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog(false); $saveDateReturnType = Functions::getReturnDateType(); @@ -192,91 +300,86 @@ class Xlsx extends BaseWriter // Create string lookup table $this->stringTable = []; for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { - $this->stringTable = $this->getWriterPart('StringTable')->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable); + $this->stringTable = $this->getWriterPartStringTable()->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable); } // Create styles dictionaries - $this->styleHashTable->addFromSource($this->getWriterPart('Style')->allStyles($this->spreadSheet)); - $this->stylesConditionalHashTable->addFromSource($this->getWriterPart('Style')->allConditionalStyles($this->spreadSheet)); - $this->fillHashTable->addFromSource($this->getWriterPart('Style')->allFills($this->spreadSheet)); - $this->fontHashTable->addFromSource($this->getWriterPart('Style')->allFonts($this->spreadSheet)); - $this->bordersHashTable->addFromSource($this->getWriterPart('Style')->allBorders($this->spreadSheet)); - $this->numFmtHashTable->addFromSource($this->getWriterPart('Style')->allNumberFormats($this->spreadSheet)); + $this->styleHashTable->addFromSource($this->getWriterPartStyle()->allStyles($this->spreadSheet)); + $this->stylesConditionalHashTable->addFromSource($this->getWriterPartStyle()->allConditionalStyles($this->spreadSheet)); + $this->fillHashTable->addFromSource($this->getWriterPartStyle()->allFills($this->spreadSheet)); + $this->fontHashTable->addFromSource($this->getWriterPartStyle()->allFonts($this->spreadSheet)); + $this->bordersHashTable->addFromSource($this->getWriterPartStyle()->allBorders($this->spreadSheet)); + $this->numFmtHashTable->addFromSource($this->getWriterPartStyle()->allNumberFormats($this->spreadSheet)); // Create drawing dictionary - $this->drawingHashTable->addFromSource($this->getWriterPart('Drawing')->allDrawings($this->spreadSheet)); - - $options = new Archive(); - $options->setEnableZip64(false); - $options->setOutputStream($this->fileHandle); - - $this->zip = new ZipStream(null, $options); + $this->drawingHashTable->addFromSource($this->getWriterPartDrawing()->allDrawings($this->spreadSheet)); + $zipContent = []; // Add [Content_Types].xml to ZIP file - $this->addZipFile('[Content_Types].xml', $this->getWriterPart('ContentTypes')->writeContentTypes($this->spreadSheet, $this->includeCharts)); + $zipContent['[Content_Types].xml'] = $this->getWriterPartContentTypes()->writeContentTypes($this->spreadSheet, $this->includeCharts); //if hasMacros, add the vbaProject.bin file, Certificate file(if exists) if ($this->spreadSheet->hasMacros()) { $macrosCode = $this->spreadSheet->getMacrosCode(); if ($macrosCode !== null) { // we have the code ? - $this->addZipFile('xl/vbaProject.bin', $macrosCode); //allways in 'xl', allways named vbaProject.bin + $zipContent['xl/vbaProject.bin'] = $macrosCode; //allways in 'xl', allways named vbaProject.bin if ($this->spreadSheet->hasMacrosCertificate()) { //signed macros ? // Yes : add the certificate file and the related rels file - $this->addZipFile('xl/vbaProjectSignature.bin', $this->spreadSheet->getMacrosCertificate()); - $this->addZipFile('xl/_rels/vbaProject.bin.rels', $this->getWriterPart('RelsVBA')->writeVBARelationships($this->spreadSheet)); + $zipContent['xl/vbaProjectSignature.bin'] = $this->spreadSheet->getMacrosCertificate(); + $zipContent['xl/_rels/vbaProject.bin.rels'] = $this->getWriterPartRelsVBA()->writeVBARelationships($this->spreadSheet); } } } //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'); - $this->addZipFile($tmpRibbonTarget, $this->spreadSheet->getRibbonXMLData('data')); + $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) { - $this->addZipFile($tmpRootPath . $aPath, $aContent); + $zipContent[$tmpRootPath . $aPath] = $aContent; } //the rels for files - $this->addZipFile($tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels', $this->getWriterPart('RelsRibbonObjects')->writeRibbonRelationships($this->spreadSheet)); + $zipContent[$tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels'] = $this->getWriterPartRelsRibbon()->writeRibbonRelationships($this->spreadSheet); } } // Add relationships to ZIP file - $this->addZipFile('_rels/.rels', $this->getWriterPart('Rels')->writeRelationships($this->spreadSheet)); - $this->addZipFile('xl/_rels/workbook.xml.rels', $this->getWriterPart('Rels')->writeWorkbookRelationships($this->spreadSheet)); + $zipContent['_rels/.rels'] = $this->getWriterPartRels()->writeRelationships($this->spreadSheet); + $zipContent['xl/_rels/workbook.xml.rels'] = $this->getWriterPartRels()->writeWorkbookRelationships($this->spreadSheet); // Add document properties to ZIP file - $this->addZipFile('docProps/app.xml', $this->getWriterPart('DocProps')->writeDocPropsApp($this->spreadSheet)); - $this->addZipFile('docProps/core.xml', $this->getWriterPart('DocProps')->writeDocPropsCore($this->spreadSheet)); - $customPropertiesPart = $this->getWriterPart('DocProps')->writeDocPropsCustom($this->spreadSheet); + $zipContent['docProps/app.xml'] = $this->getWriterPartDocProps()->writeDocPropsApp($this->spreadSheet); + $zipContent['docProps/core.xml'] = $this->getWriterPartDocProps()->writeDocPropsCore($this->spreadSheet); + $customPropertiesPart = $this->getWriterPartDocProps()->writeDocPropsCustom($this->spreadSheet); if ($customPropertiesPart !== null) { - $this->addZipFile('docProps/custom.xml', $customPropertiesPart); + $zipContent['docProps/custom.xml'] = $customPropertiesPart; } // Add theme to ZIP file - $this->addZipFile('xl/theme/theme1.xml', $this->getWriterPart('Theme')->writeTheme($this->spreadSheet)); + $zipContent['xl/theme/theme1.xml'] = $this->getWriterPartTheme()->writeTheme($this->spreadSheet); // Add string table to ZIP file - $this->addZipFile('xl/sharedStrings.xml', $this->getWriterPart('StringTable')->writeStringTable($this->stringTable)); + $zipContent['xl/sharedStrings.xml'] = $this->getWriterPartStringTable()->writeStringTable($this->stringTable); // Add styles to ZIP file - $this->addZipFile('xl/styles.xml', $this->getWriterPart('Style')->writeStyles($this->spreadSheet)); + $zipContent['xl/styles.xml'] = $this->getWriterPartStyle()->writeStyles($this->spreadSheet); // Add workbook to ZIP file - $this->addZipFile('xl/workbook.xml', $this->getWriterPart('Workbook')->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas)); + $zipContent['xl/workbook.xml'] = $this->getWriterPartWorkbook()->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas); $chartCount = 0; // Add worksheets for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { - $this->addZipFile('xl/worksheets/sheet' . ($i + 1) . '.xml', $this->getWriterPart('Worksheet')->writeWorksheet($this->spreadSheet->getSheet($i), $this->stringTable, $this->includeCharts)); + $zipContent['xl/worksheets/sheet' . ($i + 1) . '.xml'] = $this->getWriterPartWorksheet()->writeWorksheet($this->spreadSheet->getSheet($i), $this->stringTable, $this->includeCharts); if ($this->includeCharts) { $charts = $this->spreadSheet->getSheet($i)->getChartCollection(); if (count($charts) > 0) { foreach ($charts as $chart) { - $this->addZipFile('xl/charts/chart' . ($chartCount + 1) . '.xml', $this->getWriterPart('Chart')->writeChart($chart, $this->preCalculateFormulas)); + $zipContent['xl/charts/chart' . ($chartCount + 1) . '.xml'] = $this->getWriterPartChart()->writeChart($chart, $this->preCalculateFormulas); ++$chartCount; } } @@ -287,19 +390,19 @@ class Xlsx extends BaseWriter // Add worksheet relationships (drawings, ...) for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { // Add relationships - $this->addZipFile('xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts)); + $zipContent['xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts); // Add unparsedLoadedData $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName(); $unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData(); if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) { foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) { - $this->addZipFile($ctrlProp['filePath'], $ctrlProp['content']); + $zipContent[$ctrlProp['filePath']] = $ctrlProp['content']; } } if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) { foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) { - $this->addZipFile($ctrlProp['filePath'], $ctrlProp['content']); + $zipContent[$ctrlProp['filePath']] = $ctrlProp['content']; } } @@ -312,13 +415,13 @@ class Xlsx extends BaseWriter // Add drawing and image relationship parts if (($drawingCount > 0) || ($chartCount > 0)) { // Drawing relationships - $this->addZipFile('xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts)); + $zipContent['xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts); // Drawings - $this->addZipFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); + $zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = $this->getWriterPartDrawing()->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts); } elseif (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) { // Drawings - $this->addZipFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); + $zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = $this->getWriterPartDrawing()->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts); } // Add unparsed drawings @@ -327,7 +430,7 @@ class Xlsx extends BaseWriter $drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']); if ($drawingFile !== false) { $drawingFile = ltrim($drawingFile, '.'); - $this->addZipFile('xl' . $drawingFile, $drawingXml); + $zipContent['xl' . $drawingFile] = $drawingXml; } } } @@ -335,30 +438,30 @@ class Xlsx extends BaseWriter // Add comment relationship parts if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) { // VML Comments - $this->addZipFile('xl/drawings/vmlDrawing' . ($i + 1) . '.vml', $this->getWriterPart('Comments')->writeVMLComments($this->spreadSheet->getSheet($i))); + $zipContent['xl/drawings/vmlDrawing' . ($i + 1) . '.vml'] = $this->getWriterPartComments()->writeVMLComments($this->spreadSheet->getSheet($i)); // Comments - $this->addZipFile('xl/comments' . ($i + 1) . '.xml', $this->getWriterPart('Comments')->writeComments($this->spreadSheet->getSheet($i))); + $zipContent['xl/comments' . ($i + 1) . '.xml'] = $this->getWriterPartComments()->writeComments($this->spreadSheet->getSheet($i)); } // Add unparsed relationship parts if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) { foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) { - $this->addZipFile($vmlDrawing['filePath'], $vmlDrawing['content']); + $zipContent[$vmlDrawing['filePath']] = $vmlDrawing['content']; } } // Add header/footer relationship parts if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) { // VML Drawings - $this->addZipFile('xl/drawings/vmlDrawingHF' . ($i + 1) . '.vml', $this->getWriterPart('Drawing')->writeVMLHeaderFooterImages($this->spreadSheet->getSheet($i))); + $zipContent['xl/drawings/vmlDrawingHF' . ($i + 1) . '.vml'] = $this->getWriterPartDrawing()->writeVMLHeaderFooterImages($this->spreadSheet->getSheet($i)); // VML Drawing relationships - $this->addZipFile('xl/drawings/_rels/vmlDrawingHF' . ($i + 1) . '.vml.rels', $this->getWriterPart('Rels')->writeHeaderFooterDrawingRelationships($this->spreadSheet->getSheet($i))); + $zipContent['xl/drawings/_rels/vmlDrawingHF' . ($i + 1) . '.vml.rels'] = $this->getWriterPartRels()->writeHeaderFooterDrawingRelationships($this->spreadSheet->getSheet($i)); // Media foreach ($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages() as $image) { - $this->addZipFile('xl/media/' . $image->getIndexedFilename(), file_get_contents($image->getPath())); + $zipContent['xl/media/' . $image->getIndexedFilename()] = file_get_contents($image->getPath()); } } } @@ -381,7 +484,7 @@ class Xlsx extends BaseWriter $imageContents = file_get_contents($imagePath); } - $this->addZipFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents); + $zipContent['xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename())] = $imageContents; } elseif ($this->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) { ob_start(); call_user_func( @@ -391,13 +494,23 @@ class Xlsx extends BaseWriter $imageContents = ob_get_contents(); ob_end_clean(); - $this->addZipFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents); + $zipContent['xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename())] = $imageContents; } } Functions::setReturnDateType($saveDateReturnType); Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); + $this->openFileHandle($pFilename); + + $options = new Archive(); + $options->setEnableZip64(false); + $options->setOutputStream($this->fileHandle); + + $this->zip = new ZipStream(null, $options); + + $this->addZipFiles($zipContent); + // Close file try { $this->zip->finish(); @@ -445,7 +558,7 @@ class Xlsx extends BaseWriter /** * Get Style HashTable. * - * @return HashTable + * @return HashTable<\PhpOffice\PhpSpreadsheet\Style\Style> */ public function getStyleHashTable() { @@ -455,7 +568,7 @@ class Xlsx extends BaseWriter /** * Get Conditional HashTable. * - * @return HashTable + * @return HashTable */ public function getStylesConditionalHashTable() { @@ -465,7 +578,7 @@ class Xlsx extends BaseWriter /** * Get Fill HashTable. * - * @return HashTable + * @return HashTable */ public function getFillHashTable() { @@ -475,7 +588,7 @@ class Xlsx extends BaseWriter /** * Get \PhpOffice\PhpSpreadsheet\Style\Font HashTable. * - * @return HashTable + * @return HashTable */ public function getFontHashTable() { @@ -485,7 +598,7 @@ class Xlsx extends BaseWriter /** * Get Borders HashTable. * - * @return HashTable + * @return HashTable */ public function getBordersHashTable() { @@ -495,7 +608,7 @@ class Xlsx extends BaseWriter /** * Get NumberFormat HashTable. * - * @return HashTable + * @return HashTable */ public function getNumFmtHashTable() { @@ -505,7 +618,7 @@ class Xlsx extends BaseWriter /** * Get \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable. * - * @return HashTable + * @return HashTable */ public function getDrawingHashTable() { @@ -545,4 +658,11 @@ class Xlsx extends BaseWriter $this->zip->addFile($path, $content); } } + + private function addZipFiles(array $zipContent): void + { + foreach ($zipContent as $path => $content) { + $this->addZipFile($path, $content); + } + } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index 583b262c..eefae529 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -129,7 +129,7 @@ class Chart extends WriterPart if ((is_array($caption)) && (count($caption) > 0)) { $caption = $caption[0]; } - $this->getParentWriter()->getWriterPart('stringtable')->writeRichTextForCharts($objWriter, $caption, 'a'); + $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a'); $objWriter->endElement(); $objWriter->endElement(); @@ -219,10 +219,12 @@ class Chart extends WriterPart $chartTypes = self::getChartType($plotArea); $catIsMultiLevelSeries = $valIsMultiLevelSeries = false; $plotGroupingType = ''; + $chartType = null; foreach ($chartTypes as $chartType) { $objWriter->startElement('c:' . $chartType); $groupCount = $plotArea->getPlotGroupCount(); + $plotGroup = null; for ($i = 0; $i < $groupCount; ++$i) { $plotGroup = $plotArea->getPlotGroupByIndex($i); $groupType = $plotGroup->getPlotType(); @@ -244,7 +246,7 @@ class Chart extends WriterPart $this->writeDataLabels($objWriter, $layout); - if ($chartType === DataSeries::TYPE_LINECHART) { + if ($chartType === DataSeries::TYPE_LINECHART && $plotGroup) { // Line only, Line3D can't be smoothed $objWriter->startElement('c:smooth'); $objWriter->writeAttribute('val', (int) $plotGroup->getSmoothLine()); @@ -1038,9 +1040,9 @@ class Chart extends WriterPart * @param DataSeries $plotGroup * @param string $groupType Type of plot for dataseries * @param XMLWriter $objWriter XML Writer - * @param bool &$catIsMultiLevelSeries Is category a multi-series category - * @param bool &$valIsMultiLevelSeries Is value set a multi-series set - * @param string &$plotGroupingType Type of grouping for multi-series values + * @param bool $catIsMultiLevelSeries Is category a multi-series category + * @param bool $valIsMultiLevelSeries Is value set a multi-series set + * @param string $plotGroupingType Type of grouping for multi-series values */ private function writePlotGroup($plotGroup, $groupType, $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType): void { @@ -1079,6 +1081,7 @@ class Chart extends WriterPart } } + $plotSeriesIdx = 0; foreach ($plotSeriesOrder as $plotSeriesIdx => $plotSeriesRef) { $objWriter->startElement('c:ser'); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Comments.php b/src/PhpSpreadsheet/Writer/Xlsx/Comments.php index 73c4308b..51f4248c 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Comments.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Comments.php @@ -79,7 +79,7 @@ class Comments extends WriterPart // text $objWriter->startElement('text'); - $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $pComment->getText()); + $this->getParentWriter()->getWriterPartstringtable()->writeRichText($objWriter, $pComment->getText()); $objWriter->endElement(); $objWriter->endElement(); @@ -165,8 +165,7 @@ class Comments extends WriterPart private function writeVMLComment(XMLWriter $objWriter, $pCellReference, Comment $pComment): void { // Metadata - [$column, $row] = Coordinate::coordinateFromString($pCellReference); - $column = Coordinate::columnIndexFromString($column); + [$column, $row] = Coordinate::indexesFromString($pCellReference); $id = 1024 + $column + $row; $id = substr($id, 0, 4); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php b/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php index 8c3da827..4c0929db 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php @@ -69,58 +69,13 @@ class DefinedNames $this->objWriter->startElement('definedName'); $this->objWriter->writeAttribute('name', $pDefinedName->getName()); if ($pDefinedName->getLocalOnly() && $pDefinedName->getScope() !== null) { - $this->objWriter->writeAttribute('localSheetId', $pDefinedName->getScope()->getParent()->getIndex($pDefinedName->getScope())); + $this->objWriter->writeAttribute( + 'localSheetId', + $pDefinedName->getScope()->getParent()->getIndex($pDefinedName->getScope()) + ); } - $definedRange = $pDefinedName->getValue(); - $splitCount = preg_match_all( - '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui', - $definedRange, - $splitRanges, - PREG_OFFSET_CAPTURE - ); - - $lengths = array_map('strlen', array_column($splitRanges[0], 0)); - $offsets = array_column($splitRanges[0], 1); - - $worksheets = $splitRanges[2]; - $columns = $splitRanges[6]; - $rows = $splitRanges[7]; - - while ($splitCount > 0) { - --$splitCount; - $length = $lengths[$splitCount]; - $offset = $offsets[$splitCount]; - $worksheet = $worksheets[$splitCount][0]; - $column = $columns[$splitCount][0]; - $row = $rows[$splitCount][0]; - - $newRange = ''; - if (empty($worksheet)) { - if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) { - // We should have a worksheet - $worksheet = $pDefinedName->getWorksheet()->getTitle(); - } - } else { - $worksheet = str_replace("''", "'", trim($worksheet, "'")); - } - if (!empty($worksheet)) { - $newRange = "'" . str_replace("'", "''", $worksheet) . "'!"; - } - - if (!empty($column)) { - $newRange .= $column; - } - if (!empty($row)) { - $newRange .= $row; - } - - $definedRange = substr($definedRange, 0, $offset) . $newRange . substr($definedRange, $offset + $length); - } - - if (substr($definedRange, 0, 1) === '=') { - $definedRange = substr($definedRange, 1); - } + $definedRange = $this->getDefinedRange($pDefinedName); $this->objWriter->writeRawData($definedRange); @@ -144,7 +99,7 @@ class DefinedNames $range = Coordinate::splitRange($autoFilterRange); $range = $range[0]; // Strip any worksheet ref so we can make the cell ref absolute - [$ws, $range[0]] = Worksheet::extractSheetTitle($range[0], true); + [, $range[0]] = Worksheet::extractSheetTitle($range[0], true); $range[0] = Coordinate::absoluteCoordinate($range[0]); $range[1] = Coordinate::absoluteCoordinate($range[1]); @@ -220,4 +175,54 @@ class DefinedNames $this->objWriter->endElement(); } } + + private function getDefinedRange(DefinedName $pDefinedName): string + { + $definedRange = $pDefinedName->getValue(); + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui', + $definedRange, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $lengths = array_map('strlen', array_column($splitRanges[0], 0)); + $offsets = array_column($splitRanges[0], 1); + + $worksheets = $splitRanges[2]; + $columns = $splitRanges[6]; + $rows = $splitRanges[7]; + + while ($splitCount > 0) { + --$splitCount; + $length = $lengths[$splitCount]; + $offset = $offsets[$splitCount]; + $worksheet = $worksheets[$splitCount][0]; + $column = $columns[$splitCount][0]; + $row = $rows[$splitCount][0]; + + $newRange = ''; + if (empty($worksheet)) { + if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) { + // We should have a worksheet + $worksheet = $pDefinedName->getWorksheet() ? $pDefinedName->getWorksheet()->getTitle() : null; + } + } else { + $worksheet = str_replace("''", "'", trim($worksheet, "'")); + } + + if (!empty($worksheet)) { + $newRange = "'" . str_replace("'", "''", $worksheet) . "'!"; + } + $newRange = "{$newRange}{$column}{$row}"; + + $definedRange = substr($definedRange, 0, $offset) . $newRange . substr($definedRange, $offset + $length); + } + + if (substr($definedRange, 0, 1) === '=') { + $definedRange = substr($definedRange, 1); + } + + return $definedRange; + } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php b/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php index bcbc2379..6332a34d 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php @@ -170,13 +170,13 @@ class DocProps extends WriterPart /** * Write docProps/custom.xml to XML format. * - * @return string XML Output + * @return null|string XML Output */ public function writeDocPropsCustom(Spreadsheet $spreadsheet) { $customPropertyList = $spreadsheet->getProperties()->getCustomProperties(); if (empty($customPropertyList)) { - return; + return null; } // Create XML writer diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php index 1713b982..a4b09d39 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php @@ -84,22 +84,22 @@ class Drawing extends WriterPart public function writeChart(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Chart\Chart $pChart, $pRelationId = -1): void { $tl = $pChart->getTopLeftPosition(); - $tl['colRow'] = Coordinate::coordinateFromString($tl['cell']); + $tlColRow = Coordinate::indexesFromString($tl['cell']); $br = $pChart->getBottomRightPosition(); - $br['colRow'] = Coordinate::coordinateFromString($br['cell']); + $brColRow = Coordinate::indexesFromString($br['cell']); $objWriter->startElement('xdr:twoCellAnchor'); $objWriter->startElement('xdr:from'); - $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($tl['colRow'][0]) - 1); + $objWriter->writeElement('xdr:col', $tlColRow[0] - 1); $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['xOffset'])); - $objWriter->writeElement('xdr:row', $tl['colRow'][1] - 1); + $objWriter->writeElement('xdr:row', $tlColRow[1] - 1); $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['yOffset'])); $objWriter->endElement(); $objWriter->startElement('xdr:to'); - $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($br['colRow'][0]) - 1); + $objWriter->writeElement('xdr:col', $brColRow[0] - 1); $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['xOffset'])); - $objWriter->writeElement('xdr:row', $br['colRow'][1] - 1); + $objWriter->writeElement('xdr:row', $brColRow[1] - 1); $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['yOffset'])); $objWriter->endElement(); @@ -158,8 +158,7 @@ class Drawing extends WriterPart // xdr:oneCellAnchor $objWriter->startElement('xdr:oneCellAnchor'); // Image location - $aCoordinates = Coordinate::coordinateFromString($pDrawing->getCoordinates()); - $aCoordinates[0] = Coordinate::columnIndexFromString($aCoordinates[0]); + $aCoordinates = Coordinate::indexesFromString($pDrawing->getCoordinates()); // xdr:from $objWriter->startElement('xdr:from'); @@ -433,7 +432,7 @@ class Drawing extends WriterPart { // Calculate object id preg_match('{(\d+)}', md5($pReference), $m); - $id = 1500 + (substr($m[1], 0, 2) * 1); + $id = 1500 + ((int) substr($m[1], 0, 2) * 1); // Calculate offset $width = $pImage->getWidth(); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php index 79841404..a67d3ef8 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php @@ -291,7 +291,7 @@ class Rels extends WriterPart /** * Write drawing relationships to XML format. * - * @param int &$chartRef Chart ID + * @param int $chartRef Chart ID * @param bool $includeCharts Flag indicating if we should write charts * * @return string XML Output @@ -425,9 +425,7 @@ class Rels extends WriterPart } /** - * @param $objWriter * @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing - * @param $i * * @return int */ diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Theme.php b/src/PhpSpreadsheet/Writer/Xlsx/Theme.php index 3a47be7f..99177292 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Theme.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Theme.php @@ -10,7 +10,7 @@ class Theme extends WriterPart /** * Map of Major fonts to write. * - * @var array of string + * @var string[] */ private static $majorFonts = [ 'Jpan' => 'MS Pゴシック', @@ -48,7 +48,7 @@ class Theme extends WriterPart /** * Map of Minor fonts to write. * - * @var array of string + * @var string[] */ private static $minorFonts = [ 'Jpan' => 'MS Pゴシック', @@ -86,7 +86,7 @@ class Theme extends WriterPart /** * Map of core colours. * - * @var array of string + * @var string[] */ private static $colourScheme = [ 'dk2' => '1F497D', @@ -784,13 +784,9 @@ class Theme extends WriterPart /** * Write fonts to XML format. * - * @param XMLWriter $objWriter - * @param string $latinFont - * @param array of string $fontSet - * - * @return string XML Output + * @param string[] $fontSet */ - private function writeFonts($objWriter, $latinFont, $fontSet) + private function writeFonts(XMLWriter $objWriter, string $latinFont, array $fontSet): void { // a:latin $objWriter->startElement('a:latin'); @@ -817,12 +813,8 @@ class Theme extends WriterPart /** * Write colour scheme to XML format. - * - * @param XMLWriter $objWriter - * - * @return string XML Output */ - private function writeColourScheme($objWriter) + private function writeColourScheme(XMLWriter $objWriter): void { foreach (self::$colourScheme as $colourName => $colourValue) { $objWriter->startElement('a:' . $colourName); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 8faa7ae2..3978eb6f 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -8,6 +8,8 @@ use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension; use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column; use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule; use PhpOffice\PhpSpreadsheet\Worksheet\SheetView; @@ -44,6 +46,7 @@ class Worksheet extends WriterPart $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); $objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main'); + $objWriter->writeAttribute('xmlns:xm', 'http://schemas.microsoft.com/office/excel/2006/main'); $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); $objWriter->writeAttribute('mc:Ignorable', 'x14ac'); $objWriter->writeAttribute('xmlns:x14ac', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac'); @@ -114,6 +117,10 @@ class Worksheet extends WriterPart // AlternateContent $this->writeAlternateContent($objWriter, $pSheet); + // ConditionalFormattingRuleExtensionList + // (Must be inserted last. Not insert last, an Excel parse error will occur) + $this->writeExtLst($objWriter, $pSheet); + $objWriter->endElement(); // Return @@ -503,6 +510,94 @@ class Worksheet extends WriterPart } } + private static function writeExtConditionalFormattingElements(XMLWriter $objWriter, ConditionalFormattingRuleExtension $ruleExtension): void + { + $prefix = 'x14'; + $objWriter->startElementNs($prefix, 'conditionalFormatting', null); + + $objWriter->startElementNs($prefix, 'cfRule', null); + $objWriter->writeAttribute('type', $ruleExtension->getCfRule()); + $objWriter->writeAttribute('id', $ruleExtension->getId()); + $objWriter->startElementNs($prefix, 'dataBar', null); + $dataBar = $ruleExtension->getDataBarExt(); + foreach ($dataBar->getXmlAttributes() as $attrKey => $val) { + $objWriter->writeAttribute($attrKey, $val); + } + $minCfvo = $dataBar->getMinimumConditionalFormatValueObject(); + if ($minCfvo) { + $objWriter->startElementNs($prefix, 'cfvo', null); + $objWriter->writeAttribute('type', $minCfvo->getType()); + if ($minCfvo->getCellFormula()) { + $objWriter->writeElement('xm:f', $minCfvo->getCellFormula()); + } + $objWriter->endElement(); //end cfvo + } + + $maxCfvo = $dataBar->getMaximumConditionalFormatValueObject(); + if ($maxCfvo) { + $objWriter->startElementNs($prefix, 'cfvo', null); + $objWriter->writeAttribute('type', $maxCfvo->getType()); + if ($maxCfvo->getCellFormula()) { + $objWriter->writeElement('xm:f', $maxCfvo->getCellFormula()); + } + $objWriter->endElement(); //end cfvo + } + + foreach ($dataBar->getXmlElements() as $elmKey => $elmAttr) { + $objWriter->startElementNs($prefix, $elmKey, null); + foreach ($elmAttr as $attrKey => $attrVal) { + $objWriter->writeAttribute($attrKey, $attrVal); + } + $objWriter->endElement(); //end elmKey + } + $objWriter->endElement(); //end dataBar + $objWriter->endElement(); //end cfRule + $objWriter->writeElement('xm:sqref', $ruleExtension->getSqref()); + $objWriter->endElement(); //end conditionalFormatting + } + + private static function writeDataBarElements(XMLWriter $objWriter, $dataBar): void + { + /** @var ConditionalDataBar $dataBar */ + if ($dataBar) { + $objWriter->startElement('dataBar'); + self::writeAttributeIf($objWriter, null !== $dataBar->getShowValue(), 'showValue', $dataBar->getShowValue() ? '1' : '0'); + + $minCfvo = $dataBar->getMinimumConditionalFormatValueObject(); + if ($minCfvo) { + $objWriter->startElement('cfvo'); + self::writeAttributeIf($objWriter, $minCfvo->getType(), 'type', (string) $minCfvo->getType()); + self::writeAttributeIf($objWriter, $minCfvo->getValue(), 'val', (string) $minCfvo->getValue()); + $objWriter->endElement(); + } + $maxCfvo = $dataBar->getMaximumConditionalFormatValueObject(); + if ($maxCfvo) { + $objWriter->startElement('cfvo'); + self::writeAttributeIf($objWriter, $maxCfvo->getType(), 'type', (string) $maxCfvo->getType()); + self::writeAttributeIf($objWriter, $maxCfvo->getValue(), 'val', (string) $maxCfvo->getValue()); + $objWriter->endElement(); + } + if ($dataBar->getColor()) { + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $dataBar->getColor()); + $objWriter->endElement(); + } + $objWriter->endElement(); // end dataBar + + if ($dataBar->getConditionalFormattingRuleExt()) { + $objWriter->startElement('extLst'); + $extension = $dataBar->getConditionalFormattingRuleExt(); + $objWriter->startElement('ext'); + $objWriter->writeAttribute('uri', '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}'); + $objWriter->startElementNs('x14', 'id', null); + $objWriter->text($extension->getId()); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); //end extLst + } + } + } + /** * Write ConditionalFormatting. * @@ -529,7 +624,12 @@ class Worksheet extends WriterPart // cfRule $objWriter->startElement('cfRule'); $objWriter->writeAttribute('type', $conditional->getConditionType()); - $objWriter->writeAttribute('dxfId', $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode())); + self::writeAttributeIf( + $objWriter, + ($conditional->getConditionType() != Conditional::CONDITION_DATABAR), + 'dxfId', + $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) + ); $objWriter->writeAttribute('priority', $id++); self::writeAttributeif( @@ -548,7 +648,10 @@ class Worksheet extends WriterPart self::writeOtherCondElements($objWriter, $conditional, $cellCoordinate); } - $objWriter->endElement(); + // + self::writeDataBarElements($objWriter, $conditional->getDataBar()); + + $objWriter->endElement(); //end cfRule $objWriter->endElement(); } @@ -981,7 +1084,7 @@ class Worksheet extends WriterPart private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, array $pStringTable): void { // Flipped stringtable, for faster index searching - $aFlippedStringTable = $this->getParentWriter()->getWriterPart('stringtable')->flipStringTable($pStringTable); + $aFlippedStringTable = $this->getParentWriter()->getWriterPartstringtable()->flipStringTable($pStringTable); // sheetData $objWriter->startElement('sheetData'); @@ -1066,7 +1169,7 @@ class Worksheet extends WriterPart $objWriter->writeElement('t', StringHelper::controlCharacterPHP2OOXML(htmlspecialchars($cellValue))); } elseif ($cellValue instanceof RichText) { $objWriter->startElement('is'); - $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $cellValue); + $this->getParentWriter()->getWriterPartstringtable()->writeRichText($objWriter, $cellValue); $objWriter->endElement(); } } @@ -1279,4 +1382,38 @@ class Worksheet extends WriterPart $objWriter->writeRaw($alternateContent); } } + + /** + * write + * only implementation conditionalFormattings. + * + * @url https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/07d607af-5618-4ca2-b683-6a78dc0d9627 + */ + private function writeExtLst(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void + { + $conditionalFormattingRuleExtList = []; + foreach ($pSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) { + /** @var Conditional $conditional */ + foreach ($conditionalStyles as $conditional) { + $dataBar = $conditional->getDataBar(); + if ($dataBar && $dataBar->getConditionalFormattingRuleExt()) { + $conditionalFormattingRuleExtList[] = $dataBar->getConditionalFormattingRuleExt(); + } + } + } + + if (count($conditionalFormattingRuleExtList) > 0) { + $conditionalFormattingRuleExtNsPrefix = 'x14'; + $objWriter->startElement('extLst'); + $objWriter->startElement('ext'); + $objWriter->writeAttribute('uri', '{78C0D931-6437-407d-A8EE-F0AAD7539E65}'); + $objWriter->startElementNs($conditionalFormattingRuleExtNsPrefix, 'conditionalFormattings', null); + foreach ($conditionalFormattingRuleExtList as $extension) { + self::writeExtConditionalFormattingElements($objWriter, $extension); + } + $objWriter->endElement(); //end conditionalFormattings + $objWriter->endElement(); //end ext + $objWriter->endElement(); //end extLst + } + } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php b/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php index 8f7c07e8..c88ef245 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php @@ -10,6 +10,7 @@ class Xlfn . '|beta[.]inv' . '|binom[.]dist' . '|binom[.]inv' + . '|ceiling[.]precise' . '|chisq[.]dist' . '|chisq[.]dist[.]rt' . '|chisq[.]inv' @@ -27,6 +28,7 @@ class Xlfn . '|f[.]inv' . '|f[.]inv[.]rt' . '|f[.]test' + . '|floor[.]precise' . '|gamma[.]dist' . '|gamma[.]inv' . '|gammaln[.]precise' @@ -138,6 +140,11 @@ class Xlfn . '|unique' . '|xlookup' . '|xmatch' + . '|arraytotext' + . '|call' + . '|let' + . '|register[.]id' + . '|valuetotext' . ')(?=\\s*[(])/i'; /** diff --git a/tests/PhpSpreadsheetTests/Calculation/CalculationTest.php b/tests/PhpSpreadsheetTests/Calculation/CalculationTest.php index 8e339207..433d2be5 100644 --- a/tests/PhpSpreadsheetTests/Calculation/CalculationTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/CalculationTest.php @@ -4,13 +4,20 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class CalculationTest extends TestCase { + /** + * @var string + */ private $compatibilityMode; + /** + * @var string + */ private $locale; protected function setUp(): void @@ -46,7 +53,7 @@ class CalculationTest extends TestCase self::assertEquals($expectedResultOpenOffice, $resultOpenOffice, 'should be OpenOffice compatible'); } - public function providerBinaryComparisonOperation() + public function providerBinaryComparisonOperation(): array { return require 'tests/data/CalculationBinaryComparisonOperation.php'; } @@ -63,7 +70,7 @@ class CalculationTest extends TestCase self::assertIsCallable($functionCall); } - public function providerGetFunctions() + public function providerGetFunctions(): array { return Calculation::getInstance()->getFunctions(); } @@ -88,7 +95,7 @@ class CalculationTest extends TestCase self::assertTrue($calculation->setLocale($locale)); } - public function providerCanLoadAllSupportedLocales() + public function providerCanLoadAllSupportedLocales(): array { return [ ['bg'], @@ -117,11 +124,13 @@ class CalculationTest extends TestCase $calculation = Calculation::getInstance(); $tree = $calculation->parseFormula('=_xlfn.ISFORMULA(A1)'); + self::assertIsArray($tree); self::assertCount(3, $tree); $function = $tree[2]; self::assertEquals('Function', $function['type']); $tree = $calculation->parseFormula('=_xlfn.STDEV.S(A1:B2)'); + self::assertIsArray($tree); self::assertCount(5, $tree); $function = $tree[4]; self::assertEquals('Function', $function['type']); @@ -159,6 +168,14 @@ class CalculationTest extends TestCase $cell->getStyle()->setQuotePrefix(true); self::assertEquals("=cmd|'/C calc'!A0", $cell->getCalculatedValue()); + + $cell2 = $workSheet->getCell('A2'); + $cell2->setValueExplicit('ABC', DataType::TYPE_FORMULA); + self::assertEquals('ABC', $cell2->getCalculatedValue()); + + $cell3 = $workSheet->getCell('A3'); + $cell3->setValueExplicit('=', DataType::TYPE_FORMULA); + self::assertEquals('', $cell3->getCalculatedValue()); } public function testCellWithDdeExpresion(): void @@ -196,6 +213,7 @@ class CalculationTest extends TestCase // Very simple formula $formula = '=IF(A1="please +",B1)'; $tokens = $calculation->parseFormula($formula); + self::assertIsArray($tokens); $foundEqualAssociatedToStoreKey = false; $foundConditionalOnB1 = false; @@ -225,6 +243,7 @@ class CalculationTest extends TestCase // Internal operation $formula = '=IF(A1="please +",SUM(B1:B3))+IF(A2="please *",PRODUCT(C1:C3), C1)'; $tokens = $calculation->parseFormula($formula); + self::assertIsArray($tokens); $plusGotTagged = false; $productFunctionCorrectlyTagged = false; @@ -254,6 +273,7 @@ class CalculationTest extends TestCase $formula = '=IF(A1="please +",SUM(B1:B3),1+IF(NOT(A2="please *"),C2-C1,PRODUCT(C1:C3)))'; $tokens = $calculation->parseFormula($formula); + self::assertIsArray($tokens); $plusCorrectlyTagged = false; $productFunctionCorrectlyTagged = false; @@ -298,6 +318,8 @@ class CalculationTest extends TestCase $formula = '=IF(A1="flag",IF(A2<10, 0) + IF(A3<10000, 0))'; $tokens = $calculation->parseFormula($formula); + self::assertIsArray($tokens); + $properlyTaggedPlus = false; foreach ($tokens as $token) { $isPlus = $token['value'] === '+'; @@ -310,8 +332,8 @@ class CalculationTest extends TestCase } /** - * @param $expectedResult - * @param $dataArray + * @param mixed $expectedResult + * @param mixed $dataArray * @param string $formula * @param string $cellCoordinates where to put the formula * @param string[] $shouldBeSetInCacheCells coordinates of cells that must @@ -358,7 +380,7 @@ class CalculationTest extends TestCase self::assertEquals($expectedResult, $calculated); } - public function dataProviderBranchPruningFullExecution() + public function dataProviderBranchPruningFullExecution(): array { return require 'tests/data/Calculation/Calculation.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/DefinedNameConfusedForCellTest.php b/tests/PhpSpreadsheetTests/Calculation/DefinedNameConfusedForCellTest.php index 76886c23..6f97f9c8 100644 --- a/tests/PhpSpreadsheetTests/Calculation/DefinedNameConfusedForCellTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/DefinedNameConfusedForCellTest.php @@ -2,20 +2,23 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation; +use PhpOffice\PhpSpreadsheet\IOFactory; +use PhpOffice\PhpSpreadsheet\NamedRange; use PhpOffice\PhpSpreadsheet\Shared\File; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class DefinedNameConfusedForCellTest extends TestCase { public function testDefinedName(): void { - $obj = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $obj = new Spreadsheet(); $sheet0 = $obj->setActiveSheetIndex(0); $sheet0->setCellValue('A1', 2); - $obj->addNamedRange(new \PhpOffice\PhpSpreadsheet\NamedRange('A1A', $sheet0, 'A1')); + $obj->addNamedRange(new NamedRange('A1A', $sheet0, 'A1')); $sheet0->setCellValue('B1', '=2*A1A'); - $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($obj, 'Xlsx'); - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $writer = IOFactory::createWriter($obj, 'Xlsx'); + $filename = File::temporaryFilename(); $writer->save($filename); self::assertTrue(true); unlink($filename); diff --git a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php index 4f1ff397..c27d16af 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php @@ -9,7 +9,10 @@ use PHPUnit\Framework\TestCase; class RangeTest extends TestCase { - protected $spreadSheet; + /** + * @var Spreadsheet + */ + private $spreadSheet; protected function setUp(): void { @@ -40,7 +43,7 @@ class RangeTest extends TestCase self::assertSame($expectedResult, $actualRresult); } - public function providerRangeEvaluation() + public function providerRangeEvaluation(): array { return[ ['=SUM(A1:B3,A1:C2)', 48], @@ -88,7 +91,7 @@ class RangeTest extends TestCase self::assertSame($expectedResult, $sumRresult); } - public function providerNamedRangeEvaluation() + public function providerNamedRangeEvaluation(): array { return[ ['$A$1:$B$3', '$A$1:$C$2', '=SUM(GROUP1,GROUP2)', 48], @@ -123,7 +126,7 @@ class RangeTest extends TestCase self::assertSame($expectedResult, $sumRresult); } - public function providerUTF8NamedRangeEvaluation() + public function providerUTF8NamedRangeEvaluation(): array { return[ [['Γειά', 'σου', 'Κόσμε'], ['$A$1', '$B$1:$B$2', '$C$1:$C$3'], '=SUM(Γειά,σου,Κόσμε)', 26], @@ -151,7 +154,7 @@ class RangeTest extends TestCase self::assertSame($expectedCount, $actualCount); } - public function providerCompositeNamedRangeEvaluation() + public function providerCompositeNamedRangeEvaluation(): array { return[ // Calculation engine doesn't yet handle union ranges with overlap diff --git a/tests/PhpSpreadsheetTests/Calculation/FinancialTest.php b/tests/PhpSpreadsheetTests/Calculation/FinancialTest.php deleted file mode 100644 index e80ef35b..00000000 --- a/tests/PhpSpreadsheetTests/Calculation/FinancialTest.php +++ /dev/null @@ -1,628 +0,0 @@ - 0.999999 && $frac < 1.000001) { - $result = $expectedResult; - } - } - } - self::assertEquals($expectedResult, $result, $message); - } - - public function providerXIRR() - { - return require 'tests/data/Calculation/Financial/XIRR.php'; - } - - /** - * @dataProvider providerXNPV - * - * @param mixed $expectedResult - * @param mixed $message - */ - public function testXNPV($expectedResult, $message, ...$args): void - { - $result = Financial::XNPV(...$args); - if (is_numeric($result) && is_numeric($expectedResult)) { - if ($expectedResult != 0) { - $frac = $result / $expectedResult; - if ($frac > 0.999999 && $frac < 1.000001) { - $result = $expectedResult; - } - } - } - self::assertEquals($expectedResult, $result, $message); - } - - public function providerXNPV() - { - return require 'tests/data/Calculation/Financial/XNPV.php'; - } - - /** - * @dataProvider providerPDURATION - * - * @param mixed $expectedResult - */ - public function testPDURATION($expectedResult, array $args): void - { - $result = Financial::PDURATION(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); - } - - public function providerPDURATION() - { - return require 'tests/data/Calculation/Financial/PDURATION.php'; - } - - /** - * @dataProvider providerRRI - * - * @param mixed $expectedResult - */ - public function testRRI($expectedResult, array $args): void - { - $result = Financial::RRI(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); - } - - public function providerRRI() - { - return require 'tests/data/Calculation/Financial/RRI.php'; - } - - /** - * @dataProvider providerSLN - * - * @param mixed $expectedResult - */ - public function testSLN($expectedResult, array $args): void - { - $result = Financial::SLN(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); - } - - public function providerSLN() - { - return require 'tests/data/Calculation/Financial/SLN.php'; - } - - /** - * @dataProvider providerSYD - * - * @param mixed $expectedResult - */ - public function testSYD($expectedResult, array $args): void - { - $result = Financial::SYD(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); - } - - public function providerSYD() - { - return require 'tests/data/Calculation/Financial/SYD.php'; - } -} diff --git a/tests/PhpSpreadsheetTests/Calculation/FormulaAsStringTest.php b/tests/PhpSpreadsheetTests/Calculation/FormulaAsStringTest.php index 9afe5570..27c746b4 100644 --- a/tests/PhpSpreadsheetTests/Calculation/FormulaAsStringTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/FormulaAsStringTest.php @@ -39,7 +39,7 @@ class FormulaAsStringTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerFunctionsAsString() + public function providerFunctionsAsString(): array { return require 'tests/data/Calculation/FunctionsAsString.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DAverageTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DAverageTest.php new file mode 100644 index 00000000..2d8fb9f9 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DAverageTest.php @@ -0,0 +1,110 @@ +database1(), + 'Yield', + [ + ['Tree', 'Height'], + ['=Apple', '>10'], + ], + ], + [ + 13, + $this->database1(), + 3, + $this->database1(), + ], + [ + 268333.333333333333, + $this->database2(), + 'Sales', + [ + ['Quarter', 'Sales Rep.'], + ['>1', 'Tina'], + ], + ], + [ + 372500, + $this->database2(), + 'Sales', + [ + ['Quarter', 'Area'], + ['1', 'South'], + ], + ], + [ + null, + $this->database1(), + null, + $this->database1(), + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DCountATest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DCountATest.php new file mode 100644 index 00000000..2f7cad14 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DCountATest.php @@ -0,0 +1,103 @@ +database1(), + 'Profit', + [ + ['Tree', 'Height', 'Height'], + ['=Apple', '>10', '<16'], + ], + ], + [ + 2, + $this->database2(), + 'Score', + [ + ['Subject', 'Gender'], + ['Science', 'Male'], + ], + ], + [ + 1, + $this->database2(), + 'Score', + [ + ['Subject', 'Gender'], + ['Math', 'Female'], + ], + ], + [ + 3, + $this->database2(), + 'Score', + [ + ['Subject', 'Score'], + ['English', '>60%'], + ], + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DCountTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DCountTest.php new file mode 100644 index 00000000..3f9d9966 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DCountTest.php @@ -0,0 +1,136 @@ +database1(), + 'Age', + [ + ['Tree', 'Height', 'Height'], + ['=Apple', '>10', '<16'], + ], + ], + [ + 1, + $this->database2(), + 'Score', + [ + ['Subject', 'Gender'], + ['Science', 'Male'], + ], + ], + [ + 1, + $this->database2(), + 'Score', + [ + ['Subject', 'Gender'], + ['Math', 'Female'], + ], + ], + [ + 3, + $this->database2(), + null, + [ + ['Subject', 'Score'], + ['English', '>63%'], + ], + ], + [ + 3, + $this->database3(), + 'Value', + [ + ['Status'], + [true], + ], + ], + [ + 5, + $this->database3(), + 'Value', + [ + ['Status'], + ['<>true'], + ], + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DGetTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DGetTest.php new file mode 100644 index 00000000..7853e0b6 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DGetTest.php @@ -0,0 +1,115 @@ +database1(), + 'Yield', + [ + ['Tree'], + ['=Apple'], + ['=Pear'], + ], + ], + [ + 10, + $this->database1(), + 'Yield', + [ + ['Tree', 'Height', 'Height'], + ['=Apple', '>10', '<16'], + ['=Pear', '>12', null], + ], + ], + [ + 188000, + $this->database2(), + 'Sales', + [ + ['Sales Rep.', 'Quarter'], + ['Tina', 4], + ], + ], + [ + Functions::NAN(), + $this->database2(), + 'Sales', + [ + ['Area', 'Quarter'], + ['South', 4], + ], + ], + [ + null, + $this->database1(), + null, + $this->database1(), + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DMaxTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DMaxTest.php new file mode 100644 index 00000000..94ac0425 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DMaxTest.php @@ -0,0 +1,105 @@ +database1(), + 'Profit', + [ + ['Tree', 'Height', 'Height'], + ['=Apple', '>10', '<16'], + ['=Pear', null, null], + ], + ], + [ + 340000, + $this->database2(), + 'Sales', + [ + ['Quarter', 'Area'], + [2, 'North'], + ], + ], + [ + 460000, + $this->database2(), + 'Sales', + [ + ['Sales Rep.', 'Quarter'], + ['Carol', '>1'], + ], + ], + [ + null, + $this->database1(), + null, + $this->database1(), + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DMinTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DMinTest.php new file mode 100644 index 00000000..a02ad2e1 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DMinTest.php @@ -0,0 +1,101 @@ +database1(), + 'Profit', + [ + ['Tree', 'Height', 'Height'], + ['=Apple', '>10', '<16'], + ['=Pear', '>12', null], + ], + ], + [ + 0.48, + $this->database2(), + 'Score', + [ + ['Subject', 'Age'], + ['Science', '>8'], + ], + ], + [ + 0.55, + $this->database2(), + 'Score', + [ + ['Subject', 'Gender'], + ['Math', 'Male'], + ], + ], + [ + null, + $this->database1(), + null, + $this->database1(), + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DProductTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DProductTest.php new file mode 100644 index 00000000..14962558 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DProductTest.php @@ -0,0 +1,102 @@ +database1(), + 'Yield', + [ + ['Tree', 'Height', 'Height'], + ['=Apple', '>10', '<16'], + ['=Pear', null, null], + ], + ], + [ + 36, + $this->database2(), + 'Score', + [ + ['Name', 'Date'], + ['Gary', '05-Jan-2017'], + ], + ], + [ + 8, + $this->database2(), + 'Score', + [ + ['Test', 'Date'], + ['Test1', '<05-Jan-2017'], + ], + ], + [ + null, + $this->database1(), + null, + $this->database1(), + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DStDevPTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DStDevPTest.php new file mode 100644 index 00000000..669a694b --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DStDevPTest.php @@ -0,0 +1,101 @@ +database1(), + 'Yield', + [ + ['Tree'], + ['=Apple'], + ['=Pear'], + ], + ], + [ + 0.085244745684, + $this->database2(), + 'Score', + [ + ['Subject', 'Gender'], + ['English', 'Male'], + ], + ], + [ + 0.160623784042, + $this->database2(), + 'Score', + [ + ['Subject', 'Age'], + ['Math', '>8'], + ], + ], + [ + null, + $this->database1(), + null, + $this->database1(), + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DStDevTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DStDevTest.php new file mode 100644 index 00000000..a7975a11 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DStDevTest.php @@ -0,0 +1,101 @@ +database1(), + 'Yield', + [ + ['Tree'], + ['=Apple'], + ['=Pear'], + ], + ], + [ + 0.104403065089, + $this->database2(), + 'Score', + [ + ['Subject', 'Gender'], + ['English', 'Male'], + ], + ], + [ + 0.196723155729, + $this->database2(), + 'Score', + [ + ['Subject', 'Age'], + ['Math', '>8'], + ], + ], + [ + null, + $this->database1(), + null, + $this->database1(), + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DSumTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DSumTest.php new file mode 100644 index 00000000..55edffe0 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DSumTest.php @@ -0,0 +1,123 @@ +database1(), + 'Profit', + [ + ['Tree'], + ['=Apple'], + ], + ], + [ + 248, + $this->database1(), + 'Profit', + [ + ['Tree', 'Height', 'Height'], + ['=Apple', '>10', '<16'], + ['=Pear', null, null], + ], + ], + [ + 1210000, + $this->database2(), + 'Sales', + [ + ['Quarter', 'Area'], + ['>2', 'North'], + ], + ], + [ + 710000, + $this->database2(), + 'Sales', + [ + ['Quarter', 'Sales Rep.'], + ['3', 'C*'], + ], + ], + [ + 705000, + $this->database2(), + 'Sales', + [ + ['Quarter', 'Sales Rep.'], + ['3', '<>C*'], + ], + ], + [ + null, + $this->database1(), + null, + $this->database1(), + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DVarPTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DVarPTest.php new file mode 100644 index 00000000..60612db5 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DVarPTest.php @@ -0,0 +1,101 @@ +database1(), + 'Yield', + [ + ['Tree'], + ['=Apple'], + ['=Pear'], + ], + ], + [ + 0.025622222222, + $this->database2(), + 'Score', + [ + ['Subject', 'Gender'], + ['Math', 'Male'], + ], + ], + [ + 0.011622222222, + $this->database2(), + 'Score', + [ + ['Subject', 'Age'], + ['Science', '>8'], + ], + ], + [ + null, + $this->database1(), + null, + $this->database1(), + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DVarTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DVarTest.php new file mode 100644 index 00000000..0015b7fb --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Database/DVarTest.php @@ -0,0 +1,101 @@ +database1(), + 'Yield', + [ + ['Tree'], + ['=Apple'], + ['=Pear'], + ], + ], + [ + 0.038433333333, + $this->database2(), + 'Score', + [ + ['Subject', 'Gender'], + ['Math', 'Male'], + ], + ], + [ + 0.017433333333, + $this->database2(), + 'Score', + [ + ['Subject', 'Age'], + ['Science', '>8'], + ], + ], + [ + null, + $this->database1(), + null, + $this->database1(), + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/AllSetupTeardown.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/AllSetupTeardown.php new file mode 100644 index 00000000..414670c4 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/AllSetupTeardown.php @@ -0,0 +1,85 @@ +compatibilityMode = Functions::getCompatibilityMode(); + $this->excelCalendar = Date::getExcelCalendar(); + $this->returnDateType = Functions::getReturnDateType(); + $this->spreadsheet = new Spreadsheet(); + $this->sheet = $this->spreadsheet->getActiveSheet(); + } + + protected function tearDown(): void + { + Date::setExcelCalendar($this->excelCalendar); + Functions::setCompatibilityMode($this->compatibilityMode); + Functions::setReturnDateType($this->returnDateType); + $this->spreadsheet->disconnectWorksheets(); + } + + protected static function setMac1904(): void + { + Date::setExcelCalendar(Date::CALENDAR_MAC_1904); + } + + protected static function setUnixReturn(): void + { + Functions::setReturnDateType(Functions::RETURNDATE_UNIX_TIMESTAMP); + } + + protected static function setObjectReturn(): void + { + Functions::setReturnDateType(Functions::RETURNDATE_PHP_DATETIME_OBJECT); + } + + protected static function setOpenOffice(): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + } + + /** + * @param mixed $expectedResult + */ + protected function mightHaveException($expectedResult): void + { + if ($expectedResult === 'exception') { + $this->expectException(CalcException::class); + } + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateDifTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateDifTest.php index db8e29a1..3aa9446d 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateDifTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateDifTest.php @@ -2,35 +2,23 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class DateDifTest extends TestCase +class DateDifTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerDATEDIF * * @param mixed $expectedResult - * @param $startDate - * @param $endDate - * @param $unit */ - public function testDATEDIF($expectedResult, $startDate, $endDate, $unit): void + public function testDATEDIF($expectedResult, string $formula): void { - $result = DateTime::DATEDIF($startDate, $endDate, $unit); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('A1')->setValue("=DATEDIF($formula)"); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerDATEDIF() + public function providerDATEDIF(): array { return require 'tests/data/Calculation/DateTime/DATEDIF.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateTest.php index 48f7cfd7..d790777b 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateTest.php @@ -2,53 +2,42 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Datefunc; -class DateTest extends TestCase +class DateTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerDATE * * @param mixed $expectedResult - * @param $year - * @param $month - * @param $day */ - public function testDATE($expectedResult, $year, $month, $day): void + public function testDATE($expectedResult, string $formula): void { - $result = DateTime::DATE($year, $month, $day); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('A1')->setValue("=DATE($formula)"); + self::assertEquals($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerDATE() + public function providerDATE(): array { return require 'tests/data/Calculation/DateTime/DATE.php'; } public function testDATEtoUnixTimestamp(): void { - Functions::setReturnDateType(Functions::RETURNDATE_UNIX_TIMESTAMP); + self::setUnixReturn(); - $result = DateTime::DATE(2012, 1, 31); + $result = Datefunc::funcDate(2012, 1, 31); // 32-bit safe self::assertEquals(1327968000, $result); - self::assertEqualsWithDelta(1327968000, $result, 1E-8); } public function testDATEtoDateTimeObject(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_DATETIME_OBJECT); + self::setObjectReturn(); - $result = DateTime::DATE(2012, 1, 31); + $result = Datefunc::funcDate(2012, 1, 31); // Must return an object... self::assertIsObject($result); // ... of the correct type @@ -59,17 +48,12 @@ class DateTest extends TestCase public function testDATEwith1904Calendar(): void { - Date::setExcelCalendar(Date::CALENDAR_MAC_1904); + self::setMac1904(); - $result = DateTime::DATE(1918, 11, 11); + $result = Datefunc::funcDate(1918, 11, 11); self::assertEquals($result, 5428); - } - public function testDATEwith1904CalendarError(): void - { - Date::setExcelCalendar(Date::CALENDAR_MAC_1904); - - $result = DateTime::DATE(1901, 1, 31); + $result = Datefunc::funcDate(1901, 1, 31); self::assertEquals($result, '#NUM!'); } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateValueTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateValueTest.php index 51e4f7c0..2d422e0a 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateValueTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateValueTest.php @@ -2,52 +2,60 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; +use DateTimeImmutable; use DateTimeInterface; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\DateValue; -class DateValueTest extends TestCase +class DateValueTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerDATEVALUE * * @param mixed $expectedResult - * @param $dateValue */ - public function testDATEVALUE($expectedResult, $dateValue): void + public function testDATEVALUE($expectedResult, string $dateValue): void { - $result = DateTime::DATEVALUE($dateValue); + $this->sheet->getCell('B1')->setValue('1954-07-20'); + // Loop to avoid extraordinarily rare edge case where first calculation + // and second do not take place on same day. + $row = 0; + do { + ++$row; + $dtStart = new DateTimeImmutable(); + $startDay = $dtStart->format('d'); + if (is_string($expectedResult)) { + $replYMD = str_replace('Y', date('Y'), $expectedResult); + if ($replYMD !== $expectedResult) { + $expectedResult = DateValue::funcDateValue($replYMD); + } + } + $this->sheet->getCell("A$row")->setValue("=DATEVALUE($dateValue)"); + $result = $this->sheet->getCell("A$row")->getCalculatedValue(); + $dtEnd = new DateTimeImmutable(); + $endDay = $dtEnd->format('d'); + } while ($startDay !== $endDay); self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerDATEVALUE() + public function providerDATEVALUE(): array { return require 'tests/data/Calculation/DateTime/DATEVALUE.php'; } public function testDATEVALUEtoUnixTimestamp(): void { - Functions::setReturnDateType(Functions::RETURNDATE_UNIX_TIMESTAMP); + self::setUnixReturn(); - $result = DateTime::DATEVALUE('2012-1-31'); + $result = DateValue::funcDateValue('2012-1-31'); self::assertEquals(1327968000, $result); self::assertEqualsWithDelta(1327968000, $result, 1E-8); } public function testDATEVALUEtoDateTimeObject(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_DATETIME_OBJECT); + self::setObjectReturn(); - $result = DateTime::DATEVALUE('2012-1-31'); + $result = DateValue::funcDateValue('2012-1-31'); // Must return an object... self::assertIsObject($result); // ... of the correct type @@ -55,4 +63,13 @@ class DateValueTest extends TestCase // ... with the correct value self::assertEquals($result->format('d-M-Y'), '31-Jan-2012'); } + + public function testDATEVALUEwith1904Calendar(): void + { + self::setMac1904(); + self::assertEquals(5428, DateValue::funcDateValue('1918-11-11')); + self::assertEquals(0, DateValue::funcDateValue('1904-01-01')); + self::assertEquals('#VALUE!', DateValue::funcDateValue('1903-12-31')); + self::assertEquals('#VALUE!', DateValue::funcDateValue('1900-02-29')); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DayTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DayTest.php index 482e068d..dc8adf11 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DayTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DayTest.php @@ -2,56 +2,43 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class DayTest extends TestCase +class DayTest extends AllSetupTeardown { - private $compatibilityMode; - - private $returnDateType; - - private $excelCalendar; - - protected function setUp(): void - { - $this->compatibilityMode = Functions::getCompatibilityMode(); - $this->returnDateType = Functions::getReturnDateType(); - $this->excelCalendar = Date::getExcelCalendar(); - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - - protected function tearDown(): void - { - Functions::setCompatibilityMode($this->compatibilityMode); - Functions::setReturnDateType($this->returnDateType); - Date::setExcelCalendar($this->excelCalendar); - } - /** * @dataProvider providerDAY * * @param mixed $expectedResultExcel - * @param mixed $expectedResultOpenOffice - * @param $dateTimeValue */ - public function testDAY($expectedResultExcel, $expectedResultOpenOffice, $dateTimeValue): void + public function testDAY($expectedResultExcel, string $dateTimeValue): void { - $resultExcel = DateTime::DAYOFMONTH($dateTimeValue); - self::assertEqualsWithDelta($expectedResultExcel, $resultExcel, 1E-8); - - Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); - - $resultOpenOffice = DateTime::DAYOFMONTH($dateTimeValue); - self::assertEqualsWithDelta($expectedResultOpenOffice, $resultOpenOffice, 1E-8); + $this->mightHaveException($expectedResultExcel); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('A1')->setValue("=DAY($dateTimeValue)"); + self::assertSame($expectedResultExcel, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerDAY() + public function providerDAY(): array { return require 'tests/data/Calculation/DateTime/DAY.php'; } + + /** + * @dataProvider providerDAYOpenOffice + * + * @param mixed $expectedResultOpenOffice + */ + public function testDAYOpenOffice($expectedResultOpenOffice, string $dateTimeValue): void + { + self::setOpenOffice(); + $this->mightHaveException($expectedResultOpenOffice); + $sheet = $this->sheet; + $sheet->getCell('A2')->setValue("=DAY($dateTimeValue)"); + self::assertSame($expectedResultOpenOffice, $sheet->getCell('A2')->getCalculatedValue()); + } + + public function providerDAYOpenOffice(): array + { + return require 'tests/data/Calculation/DateTime/DAYOpenOffice.php'; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/Days360Test.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/Days360Test.php index 47449e0d..bd3a0283 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/Days360Test.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/Days360Test.php @@ -2,35 +2,24 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class Days360Test extends TestCase +class Days360Test extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerDAYS360 * * @param mixed $expectedResult - * @param $startDate - * @param $endDate - * @param $method */ - public function testDAYS360($expectedResult, $startDate, $endDate, $method): void + public function testDAYS360($expectedResult, string $formula): void { - $result = DateTime::DAYS360($startDate, $endDate, $method); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('2000-02-29'); + $sheet->getCell('C1')->setValue('2000-03-31'); + $sheet->getCell('A1')->setValue("=DAYS360($formula)"); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerDAYS360() + public function providerDAYS360(): array { return require 'tests/data/Calculation/DateTime/DAYS360.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DaysTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DaysTest.php index fe31dfcc..8c65622a 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DaysTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DaysTest.php @@ -2,35 +2,44 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use DateTime; +use DateTimeImmutable; +use Exception; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Days; -class DaysTest extends TestCase +class DaysTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerDAYS * * @param mixed $expectedResult - * @param $endDate - * @param $startDate */ - public function testDAYS($expectedResult, $endDate, $startDate): void + public function testDAYS($expectedResult, string $formula): void { - $result = DateTime::DAYS($endDate, $startDate); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('C1')->setValue('1954-11-30'); + $sheet->getCell('A1')->setValue("=DAYS($formula)"); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerDAYS() + public function providerDAYS(): array { return require 'tests/data/Calculation/DateTime/DAYS.php'; } + + public function testObject(): void + { + $obj1 = new DateTime('2000-3-31'); + $obj2 = new DateTimeImmutable('2000-2-29'); + self::assertSame(31, Days::funcDays($obj1, $obj2)); + } + + public function testNonDateObject(): void + { + $obj1 = new Exception(); + $obj2 = new DateTimeImmutable('2000-2-29'); + self::assertSame('#VALUE!', Days::funcDays($obj1, $obj2)); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EDateTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EDateTest.php index a887ba5b..efb34d0d 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EDateTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EDateTest.php @@ -2,52 +2,43 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\EDate; -class EDateTest extends TestCase +class EDateTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerEDATE * * @param mixed $expectedResult - * @param $dateValue - * @param $adjustmentMonths */ - public function testEDATE($expectedResult, $dateValue, $adjustmentMonths): void + public function testEDATE($expectedResult, string $formula): void { - $result = DateTime::EDATE($dateValue, $adjustmentMonths); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=EDATE($formula)"); + $sheet->getCell('B1')->setValue('1954-11-23'); + self::assertEquals($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerEDATE() + public function providerEDATE(): array { return require 'tests/data/Calculation/DateTime/EDATE.php'; } public function testEDATEtoUnixTimestamp(): void { - Functions::setReturnDateType(Functions::RETURNDATE_UNIX_TIMESTAMP); + self::setUnixReturn(); - $result = DateTime::EDATE('2012-1-26', -1); + $result = EDate::funcEDate('2012-1-26', -1); self::assertEquals(1324857600, $result); self::assertEqualsWithDelta(1324857600, $result, 1E-8); } public function testEDATEtoDateTimeObject(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_DATETIME_OBJECT); + self::setObjectReturn(); - $result = DateTime::EDATE('2012-1-26', -1); + $result = EDate::funcEDate('2012-1-26', -1); // Must return an object... self::assertIsObject($result); // ... of the correct type diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EoMonthTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EoMonthTest.php index f9c54039..6db76f58 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EoMonthTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EoMonthTest.php @@ -2,57 +2,47 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\EoMonth; -class EoMonthTest extends TestCase +class EoMonthTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerEOMONTH * * @param mixed $expectedResult - * @param $dateValue - * @param $adjustmentMonths */ - public function testEOMONTH($expectedResult, $dateValue, $adjustmentMonths): void + public function testEOMONTH($expectedResult, string $formula): void { - $result = DateTime::EOMONTH($dateValue, $adjustmentMonths); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=EOMONTH($formula)"); + $sheet->getCell('B1')->setValue('1954-11-23'); + self::assertEquals($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerEOMONTH() + public function providerEOMONTH(): array { return require 'tests/data/Calculation/DateTime/EOMONTH.php'; } public function testEOMONTHtoUnixTimestamp(): void { - Functions::setReturnDateType(Functions::RETURNDATE_UNIX_TIMESTAMP); + self::setUnixReturn(); - $result = DateTime::EOMONTH('2012-1-26', -1); + $result = EoMonth::funcEomonth('2012-1-26', -1); self::assertEquals(1325289600, $result); - self::assertEqualsWithDelta(1325289600, $result, 1E-8); } public function testEOMONTHtoDateTimeObject(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_DATETIME_OBJECT); + self::setObjectReturn(); - $result = DateTime::EOMONTH('2012-1-26', -1); + $result = EoMonth::funcEomonth('2012-1-26', -1); // Must return an object... self::assertIsObject($result); // ... of the correct type self::assertTrue(is_a($result, 'DateTimeInterface')); // ... with the correct value - self::assertEquals($result->format('d-M-Y'), '31-Dec-2011'); + self::assertSame($result->format('d-M-Y'), '31-Dec-2011'); } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/HourTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/HourTest.php index 2d0cd5d1..93afbb5d 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/HourTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/HourTest.php @@ -2,33 +2,23 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class HourTest extends TestCase +class HourTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerHOUR * * @param mixed $expectedResult - * @param $dateTimeValue */ - public function testHOUR($expectedResult, $dateTimeValue): void + public function testHOUR($expectedResult, string $dateTimeValue): void { - $result = DateTime::HOUROFDAY($dateTimeValue); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=HOUR($dateTimeValue)"); + $sheet->getCell('B1')->setValue('1954-11-23 2:23:46'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerHOUR() + public function providerHOUR(): array { return require 'tests/data/Calculation/DateTime/HOUR.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/IsoWeekNumTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/IsoWeekNumTest.php index 1ef0080a..6be2e1af 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/IsoWeekNumTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/IsoWeekNumTest.php @@ -2,34 +2,46 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class IsoWeekNumTest extends TestCase +class IsoWeekNumTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerISOWEEKNUM * * @param mixed $expectedResult - * @param mixed $dateValue + * @param string $dateValue */ public function testISOWEEKNUM($expectedResult, $dateValue): void { - $result = DateTime::ISOWEEKNUM($dateValue); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=ISOWEEKNUM($dateValue)"); + $sheet->getCell('B1')->setValue('1954-11-23'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerISOWEEKNUM() + public function providerISOWEEKNUM(): array { return require 'tests/data/Calculation/DateTime/ISOWEEKNUM.php'; } + + /** + * @dataProvider providerISOWEEKNUM1904 + * + * @param mixed $expectedResult + * @param string $dateValue + */ + public function testISOWEEKNUM1904($expectedResult, $dateValue): void + { + $this->mightHaveException($expectedResult); + self::setMac1904(); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=ISOWEEKNUM($dateValue)"); + $sheet->getCell('B1')->setValue('1954-11-23'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); + } + + public function providerISOWEEKNUM1904(): array + { + return require 'tests/data/Calculation/DateTime/ISOWEEKNUM1904.php'; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MinuteTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MinuteTest.php index 8472c6de..57d7a77e 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MinuteTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MinuteTest.php @@ -2,33 +2,23 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class MinuteTest extends TestCase +class MinuteTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerMINUTE * * @param mixed $expectedResult - * @param $dateTimeValue */ - public function testMINUTE($expectedResult, $dateTimeValue): void + public function testMINUTE($expectedResult, string $dateTimeValue): void { - $result = DateTime::MINUTE($dateTimeValue); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=MINUTE($dateTimeValue)"); + $sheet->getCell('B1')->setValue('1954-11-23 2:23:46'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerMINUTE() + public function providerMINUTE(): array { return require 'tests/data/Calculation/DateTime/MINUTE.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MonthTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MonthTest.php index 62513702..ed09a993 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MonthTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MonthTest.php @@ -2,33 +2,23 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class MonthTest extends TestCase +class MonthTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerMONTH * * @param mixed $expectedResult - * @param $dateTimeValue */ - public function testMONTH($expectedResult, $dateTimeValue): void + public function testMONTH($expectedResult, string $dateTimeValue): void { - $result = DateTime::MONTHOFYEAR($dateTimeValue); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=MONTH($dateTimeValue)"); + $sheet->getCell('B1')->setValue('1954-11-23'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerMONTH() + public function providerMONTH(): array { return require 'tests/data/Calculation/DateTime/MONTH.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MovedFunctionsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MovedFunctionsTest.php new file mode 100644 index 00000000..d14f7d7d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MovedFunctionsTest.php @@ -0,0 +1,63 @@ +format('s'); + $nowResult = DateTime::DATETIMENOW(); + $todayResult = DateTime::DATENOW(); + $dtEnd = new DateTimeImmutable(); + $endSecond = $dtEnd->format('s'); + } while ($startSecond !== $endSecond); + self::assertSame(DateTime::DAYOFMONTH($nowResult), DateTime::DAYOFMONTH($todayResult)); + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NetworkDaysTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NetworkDaysTest.php index e366c44e..b121f7bf 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NetworkDaysTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NetworkDaysTest.php @@ -2,32 +2,50 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class NetworkDaysTest extends TestCase +class NetworkDaysTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerNETWORKDAYS * * @param mixed $expectedResult + * @param mixed $arg1 + * @param mixed $arg2 */ - public function testNETWORKDAYS($expectedResult, ...$args): void + public function testNETWORKDAYS($expectedResult, $arg1 = 'omitted', $arg2 = 'omitted', ?array $arg3 = null): void { - $result = DateTime::NETWORKDAYS(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($arg1 !== null) { + $sheet->getCell('A1')->setValue($arg1); + } + if ($arg2 !== null) { + $sheet->getCell('A2')->setValue($arg2); + } + $dateArray = []; + if (is_array($arg3)) { + if (array_key_exists(0, $arg3) && is_array($arg3[0])) { + $dateArray = $arg3[0]; + } else { + $dateArray = $arg3; + } + } + $dateIndex = 0; + foreach ($dateArray as $date) { + ++$dateIndex; + $sheet->getCell("C$dateIndex")->setValue($date); + } + $arrayArg = $dateIndex ? ", C1:C$dateIndex" : ''; + if ($arg1 === 'omitted') { + $sheet->getCell('B1')->setValue('=NETWORKDAYS()'); + } elseif ($arg2 === 'omitted') { + $sheet->getCell('B1')->setValue('=NETWORKDAYS(A1)'); + } else { + $sheet->getCell('B1')->setValue("=NETWORKDAYS(A1, A2$arrayArg)"); + } + self::assertEquals($expectedResult, $sheet->getCell('B1')->getCalculatedValue()); } - public function providerNETWORKDAYS() + public function providerNETWORKDAYS(): array { return require 'tests/data/Calculation/DateTime/NETWORKDAYS.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NowTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NowTest.php new file mode 100644 index 00000000..e0f68c24 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NowTest.php @@ -0,0 +1,34 @@ +sheet; + // Loop to avoid rare edge case where first calculation + // and second do not take place in same second. + do { + $dtStart = new DateTimeImmutable(); + $startSecond = $dtStart->format('s'); + $sheet->setCellValue('A1', '=NOW()'); + $dtEnd = new DateTimeImmutable(); + $endSecond = $dtEnd->format('s'); + } while ($startSecond !== $endSecond); + $sheet->setCellValue('B1', '=YEAR(A1)'); + $sheet->setCellValue('C1', '=MONTH(A1)'); + $sheet->setCellValue('D1', '=DAY(A1)'); + $sheet->setCellValue('E1', '=HOUR(A1)'); + $sheet->setCellValue('F1', '=MINUTE(A1)'); + $sheet->setCellValue('G1', '=SECOND(A1)'); + self::assertEquals($dtStart->format('Y'), $sheet->getCell('B1')->getCalculatedValue()); + self::assertEquals($dtStart->format('m'), $sheet->getCell('C1')->getCalculatedValue()); + self::assertEquals($dtStart->format('d'), $sheet->getCell('D1')->getCalculatedValue()); + self::assertEquals($dtStart->format('H'), $sheet->getCell('E1')->getCalculatedValue()); + self::assertEquals($dtStart->format('i'), $sheet->getCell('F1')->getCalculatedValue()); + self::assertEquals($dtStart->format('s'), $sheet->getCell('G1')->getCalculatedValue()); + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/SecondTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/SecondTest.php index bc2b0752..6c264a57 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/SecondTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/SecondTest.php @@ -2,33 +2,23 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class SecondTest extends TestCase +class SecondTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerSECOND * * @param mixed $expectedResult - * @param $dateTimeValue */ - public function testSECOND($expectedResult, $dateTimeValue): void + public function testSECOND($expectedResult, string $dateTimeValue): void { - $result = DateTime::SECOND($dateTimeValue); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=SECOND($dateTimeValue)"); + $sheet->getCell('B1')->setValue('1954-11-23 2:23:46'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerSECOND() + public function providerSECOND(): array { return require 'tests/data/Calculation/DateTime/SECOND.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeTest.php index 344061d4..d6910024 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeTest.php @@ -2,49 +2,44 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Time; -class TimeTest extends TestCase +class TimeTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerTIME * * @param mixed $expectedResult */ - public function testTIME($expectedResult, ...$args): void + public function testTIME($expectedResult, string $formula): void { - $result = DateTime::TIME(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('15'); + $sheet->getCell('B2')->setValue('32'); + $sheet->getCell('B3')->setValue('50'); + $sheet->getCell('A1')->setValue("=TIME($formula)"); + self::assertEqualsWithDelta($expectedResult, $sheet->getCell('A1')->getCalculatedValue(), 1E-8); } - public function providerTIME() + public function providerTIME(): array { return require 'tests/data/Calculation/DateTime/TIME.php'; } public function testTIMEtoUnixTimestamp(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_NUMERIC); + self::setUnixReturn(); - $result = DateTime::TIME(7, 30, 20); + $result = Time::funcTime(7, 30, 20); self::assertEqualsWithDelta(27020, $result, 1E-8); } public function testTIMEtoDateTimeObject(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_OBJECT); + self::setObjectReturn(); - $result = DateTime::TIME(7, 30, 20); + $result = Time::funcTime(7, 30, 20); // Must return an object... self::assertIsObject($result); // ... of the correct type @@ -52,4 +47,17 @@ class TimeTest extends TestCase // ... with the correct value self::assertEquals($result->format('H:i:s'), '07:30:20'); } + + public function testTIME1904(): void + { + self::setMac1904(); + $result = Time::funcTime(0, 0, 0); + self::assertEquals(0, $result); + } + + public function testTIME1900(): void + { + $result = Time::funcTime(0, 0, 0); + self::assertEquals(0, $result); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeValueTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeValueTest.php index 04b8c058..ac4d3dbd 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeValueTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeValueTest.php @@ -2,51 +2,45 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\TimeValue; -class TimeValueTest extends TestCase +class TimeValueTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerTIMEVALUE * * @param mixed $expectedResult - * @param $timeValue + * @param mixed $timeValue */ public function testTIMEVALUE($expectedResult, $timeValue): void { - $result = DateTime::TIMEVALUE($timeValue); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('03:45:52'); + $sheet->getCell('A1')->setValue("=TIMEVALUE($timeValue)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerTIMEVALUE() + public function providerTIMEVALUE(): array { return require 'tests/data/Calculation/DateTime/TIMEVALUE.php'; } public function testTIMEVALUEtoUnixTimestamp(): void { - Functions::setReturnDateType(Functions::RETURNDATE_UNIX_TIMESTAMP); + self::setUnixReturn(); - $result = DateTime::TIMEVALUE('7:30:20'); + $result = TimeValue::funcTimeValue('7:30:20'); self::assertEquals(23420, $result); self::assertEqualsWithDelta(23420, $result, 1E-8); } public function testTIMEVALUEtoDateTimeObject(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_DATETIME_OBJECT); + self::setObjectReturn(); - $result = DateTime::TIMEVALUE('7:30:20'); + $result = TimeValue::funcTimeValue('7:30:20'); // Must return an object... self::assertIsObject($result); // ... of the correct type diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TodayTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TodayTest.php new file mode 100644 index 00000000..6ce82bfd --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TodayTest.php @@ -0,0 +1,34 @@ +sheet; + // Loop to avoid rare edge case where first calculation + // and second do not take place in same second. + do { + $dtStart = new DateTimeImmutable(); + $startSecond = $dtStart->format('s'); + $sheet->setCellValue('A1', '=TODAY()'); + $dtEnd = new DateTimeImmutable(); + $endSecond = $dtEnd->format('s'); + } while ($startSecond !== $endSecond); + $sheet->setCellValue('B1', '=YEAR(A1)'); + $sheet->setCellValue('C1', '=MONTH(A1)'); + $sheet->setCellValue('D1', '=DAY(A1)'); + $sheet->setCellValue('E1', '=HOUR(A1)'); + $sheet->setCellValue('F1', '=MINUTE(A1)'); + $sheet->setCellValue('G1', '=SECOND(A1)'); + self::assertSame((int) $dtStart->format('Y'), $sheet->getCell('B1')->getCalculatedValue()); + self::assertSame((int) $dtStart->format('m'), $sheet->getCell('C1')->getCalculatedValue()); + self::assertSame((int) $dtStart->format('d'), $sheet->getCell('D1')->getCalculatedValue()); + self::assertSame(0, $sheet->getCell('E1')->getCalculatedValue()); + self::assertSame(0, $sheet->getCell('F1')->getCalculatedValue()); + self::assertSame(0, $sheet->getCell('G1')->getCalculatedValue()); + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekDayTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekDayTest.php index c5b89e01..c95ce9cc 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekDayTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekDayTest.php @@ -2,33 +2,34 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\WeekDay; -class WeekDayTest extends TestCase +class WeekDayTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerWEEKDAY * * @param mixed $expectedResult */ - public function testWEEKDAY($expectedResult, ...$args): void + public function testWEEKDAY($expectedResult, string $formula): void { - $result = DateTime::WEEKDAY(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('A1')->setValue("=WEEKDAY($formula)"); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerWEEKDAY() + public function providerWEEKDAY(): array { return require 'tests/data/Calculation/DateTime/WEEKDAY.php'; } + + public function testWEEKDAYwith1904Calendar(): void + { + self::setMac1904(); + self::assertEquals(7, WeekDay::funcWeekDay('1904-01-02')); + self::assertEquals(6, WeekDay::funcWeekDay('1904-01-01')); + self::assertEquals(6, WeekDay::funcWeekDay(null)); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekNumTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekNumTest.php index 9d8e1eb2..cf8ac65b 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekNumTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekNumTest.php @@ -2,33 +2,44 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class WeekNumTest extends TestCase +class WeekNumTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerWEEKNUM * * @param mixed $expectedResult */ - public function testWEEKNUM($expectedResult, ...$args): void + public function testWEEKNUM($expectedResult, string $formula): void { - $result = DateTime::WEEKNUM(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('A1')->setValue("=WEEKNUM($formula)"); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerWEEKNUM() + public function providerWEEKNUM(): array { return require 'tests/data/Calculation/DateTime/WEEKNUM.php'; } + + /** + * @dataProvider providerWEEKNUM1904 + * + * @param mixed $expectedResult + */ + public function testWEEKNUM1904($expectedResult, string $formula): void + { + $this->mightHaveException($expectedResult); + self::setMac1904(); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('A1')->setValue("=WEEKNUM($formula)"); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); + } + + public function providerWEEKNUM1904(): array + { + return require 'tests/data/Calculation/DateTime/WEEKNUM1904.php'; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WorkDayTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WorkDayTest.php index 4784e463..80829699 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WorkDayTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WorkDayTest.php @@ -2,32 +2,50 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class WorkDayTest extends TestCase +class WorkDayTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerWORKDAY * * @param mixed $expectedResult + * @param mixed $arg1 + * @param mixed $arg2 */ - public function testWORKDAY($expectedResult, ...$args): void + public function testWORKDAY($expectedResult, $arg1 = 'omitted', $arg2 = 'omitted', ?array $arg3 = null): void { - $result = DateTime::WORKDAY(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($arg1 !== null) { + $sheet->getCell('A1')->setValue($arg1); + } + if ($arg2 !== null) { + $sheet->getCell('A2')->setValue($arg2); + } + $dateArray = []; + if (is_array($arg3)) { + if (array_key_exists(0, $arg3) && is_array($arg3[0])) { + $dateArray = $arg3[0]; + } else { + $dateArray = $arg3; + } + } + $dateIndex = 0; + foreach ($dateArray as $date) { + ++$dateIndex; + $sheet->getCell("C$dateIndex")->setValue($date); + } + $arrayArg = $dateIndex ? ", C1:C$dateIndex" : ''; + if ($arg1 === 'omitted') { + $sheet->getCell('B1')->setValue('=WORKDAY()'); + } elseif ($arg2 === 'omitted') { + $sheet->getCell('B1')->setValue('=WORKDAY(A1)'); + } else { + $sheet->getCell('B1')->setValue("=WORKDAY(A1, A2$arrayArg)"); + } + self::assertEquals($expectedResult, $sheet->getCell('B1')->getCalculatedValue()); } - public function providerWORKDAY() + public function providerWORKDAY(): array { return require 'tests/data/Calculation/DateTime/WORKDAY.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearFracTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearFracTest.php index 05f11310..e16ce697 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearFracTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearFracTest.php @@ -2,32 +2,42 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class YearFracTest extends TestCase +class YearFracTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerYEARFRAC * * @param mixed $expectedResult + * @param mixed $arg1 + * @param mixed $arg2 + * @param mixed $arg3 */ - public function testYEARFRAC($expectedResult, ...$args): void + public function testYEARFRAC($expectedResult, $arg1 = 'omitted', $arg2 = 'omitted', $arg3 = 'omitted'): void { - $result = DateTime::YEARFRAC(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($arg1 !== null) { + $sheet->getCell('A1')->setValue($arg1); + } + if ($arg2 !== null) { + $sheet->getCell('A2')->setValue($arg2); + } + if ($arg3 !== null) { + $sheet->getCell('A3')->setValue($arg3); + } + if ($arg1 === 'omitted') { + $sheet->getCell('B1')->setValue('=YEARFRAC()'); + } elseif ($arg2 === 'omitted') { + $sheet->getCell('B1')->setValue('=YEARFRAC(A1)'); + } elseif ($arg3 === 'omitted') { + $sheet->getCell('B1')->setValue('=YEARFRAC(A1, A2)'); + } else { + $sheet->getCell('B1')->setValue('=YEARFRAC(A1, A2, A3)'); + } + self::assertEqualswithDelta($expectedResult, $sheet->getCell('B1')->getCalculatedValue(), 1E-6); } - public function providerYEARFRAC() + public function providerYEARFRAC(): array { return require 'tests/data/Calculation/DateTime/YEARFRAC.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearTest.php index bbdaf92a..74b3bed0 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearTest.php @@ -2,33 +2,23 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class YearTest extends TestCase +class YearTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerYEAR * * @param mixed $expectedResult - * @param $dateTimeValue */ - public function testYEAR($expectedResult, $dateTimeValue): void + public function testYEAR($expectedResult, string $dateTimeValue): void { - $result = DateTime::YEAR($dateTimeValue); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=YEAR($dateTimeValue)"); + $sheet->getCell('B1')->setValue('1954-11-23'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerYEAR() + public function providerYEAR(): array { return require 'tests/data/Calculation/DateTime/YEAR.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselITest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselITest.php index 8fff98af..d24a0208 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselITest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselITest.php @@ -8,7 +8,7 @@ use PHPUnit\Framework\TestCase; class BesselITest extends TestCase { - const BESSEL_PRECISION = 1E-8; + const BESSEL_PRECISION = 1E-9; protected function setUp(): void { @@ -26,7 +26,7 @@ class BesselITest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, self::BESSEL_PRECISION); } - public function providerBESSELI() + public function providerBESSELI(): array { return require 'tests/data/Calculation/Engineering/BESSELI.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselJTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselJTest.php index d10f028f..325a0d64 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselJTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselJTest.php @@ -26,7 +26,7 @@ class BesselJTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, self::BESSEL_PRECISION); } - public function providerBESSEJ() + public function providerBESSEJ(): array { return require 'tests/data/Calculation/Engineering/BESSELJ.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselKTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselKTest.php index 27123a26..51725d38 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselKTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselKTest.php @@ -8,7 +8,7 @@ use PHPUnit\Framework\TestCase; class BesselKTest extends TestCase { - const BESSEL_PRECISION = 1E-8; + const BESSEL_PRECISION = 1E-12; protected function setUp(): void { @@ -26,7 +26,7 @@ class BesselKTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, self::BESSEL_PRECISION); } - public function providerBESSELK() + public function providerBESSELK(): array { return require 'tests/data/Calculation/Engineering/BESSELK.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselYTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselYTest.php index ab55f0ac..1e8d863f 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselYTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BesselYTest.php @@ -8,7 +8,7 @@ use PHPUnit\Framework\TestCase; class BesselYTest extends TestCase { - const BESSEL_PRECISION = 1E-8; + const BESSEL_PRECISION = 1E-12; protected function setUp(): void { @@ -26,7 +26,7 @@ class BesselYTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, self::BESSEL_PRECISION); } - public function providerBESSELY() + public function providerBESSELY(): array { return require 'tests/data/Calculation/Engineering/BESSELY.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2DecTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2DecTest.php index faba3de8..c0923d1a 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2DecTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2DecTest.php @@ -2,30 +2,92 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class Bin2DecTest extends TestCase { + /** + * @var string + */ + private $compatibilityMode; + protected function setUp(): void { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $this->compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); } /** * @dataProvider providerBIN2DEC * * @param mixed $expectedResult + * @param mixed $formula */ - public function testBIN2DEC($expectedResult, ...$args): void + public function testBin2Dec($expectedResult, $formula): void { - $result = Engineering::BINTODEC(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 101); + $sheet->getCell('A1')->setValue("=BIN2DEC($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerBIN2DEC() + public function providerBIN2DEC(): array { return require 'tests/data/Calculation/Engineering/BIN2DEC.php'; } + + /** + * @dataProvider providerBIN2DEC + * + * @param mixed $expectedResult + * @param mixed $formula + */ + public function testBIN2DECOds($expectedResult, $formula): void + { + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + if ($formula === 'true') { + $expectedResult = 1; + } elseif ($formula === 'false') { + $expectedResult = 0; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 101); + $sheet->getCell('A1')->setValue("=BIN2DEC($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function testBIN2DECFrac(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + $cell = 'G1'; + $sheet->setCellValue($cell, '=BIN2DEC(101.1)'); + self::assertEquals(5, $sheet->getCell($cell)->getCalculatedValue(), 'Gnumeric'); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + $cell = 'O1'; + $sheet->setCellValue($cell, '=BIN2DEC(101.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue(), 'Ods'); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $cell = 'E1'; + $sheet->setCellValue($cell, '=BIN2DEC(101.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue(), 'Excel'); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2HexTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2HexTest.php index 2a16d5ac..c95c375d 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2HexTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2HexTest.php @@ -2,30 +2,92 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class Bin2HexTest extends TestCase { + /** + * @var string + */ + private $compatibilityMode; + protected function setUp(): void { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $this->compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); } /** * @dataProvider providerBIN2HEX * * @param mixed $expectedResult + * @param mixed $formula */ - public function testBIN2HEX($expectedResult, ...$args): void + public function testBin2Hex($expectedResult, $formula): void { - $result = Engineering::BINTOHEX(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 101); + $sheet->getCell('A1')->setValue("=BIN2HEX($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerBIN2HEX() + public function providerBIN2HEX(): array { return require 'tests/data/Calculation/Engineering/BIN2HEX.php'; } + + /** + * @dataProvider providerBIN2HEX + * + * @param mixed $expectedResult + * @param mixed $formula + */ + public function testBIN2HEXOds($expectedResult, $formula): void + { + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + if ($formula === 'true') { + $expectedResult = 1; + } elseif ($formula === 'false') { + $expectedResult = 0; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 101); + $sheet->getCell('A1')->setValue("=BIN2HEX($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function testBIN2HEXFrac(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + $cell = 'G1'; + $sheet->setCellValue($cell, '=BIN2HEX(101.1)'); + self::assertEquals(5, $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + $cell = 'O1'; + $sheet->setCellValue($cell, '=BIN2HEX(101.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $cell = 'E1'; + $sheet->setCellValue($cell, '=BIN2HEX(101.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2OctTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2OctTest.php index 78db6a6e..ee54063c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2OctTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2OctTest.php @@ -2,30 +2,92 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class Bin2OctTest extends TestCase { + /** + * @var string + */ + private $compatibilityMode; + protected function setUp(): void { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $this->compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); } /** * @dataProvider providerBIN2OCT * * @param mixed $expectedResult + * @param mixed $formula */ - public function testBIN2OCT($expectedResult, ...$args): void + public function testBin2Oct($expectedResult, $formula): void { - $result = Engineering::BINTOOCT(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 101); + $sheet->getCell('A1')->setValue("=BIN2OCT($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerBIN2OCT() + public function providerBIN2OCT(): array { return require 'tests/data/Calculation/Engineering/BIN2OCT.php'; } + + /** + * @dataProvider providerBIN2OCT + * + * @param mixed $expectedResult + * @param mixed $formula + */ + public function testBIN2OCTOds($expectedResult, $formula): void + { + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + if ($formula === 'true') { + $expectedResult = 1; + } elseif ($formula === 'false') { + $expectedResult = 0; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 101); + $sheet->getCell('A1')->setValue("=BIN2OCT($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function testBIN2OCTFrac(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + $cell = 'G1'; + $sheet->setCellValue($cell, '=BIN2OCT(101.1)'); + self::assertEquals(5, $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + $cell = 'O1'; + $sheet->setCellValue($cell, '=BIN2OCT(101.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $cell = 'E1'; + $sheet->setCellValue($cell, '=BIN2OCT(101.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitAndTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitAndTest.php index e73efccc..23682908 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitAndTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitAndTest.php @@ -2,30 +2,31 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class BitAndTest extends TestCase { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerBITAND * * @param mixed $expectedResult - * @param mixed[] $args */ - public function testBITAND($expectedResult, array $args): void + public function testBITAND($expectedResult, string $formula): void { - $result = Engineering::BITAND(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 24); + $sheet->getCell('A1')->setValue("=BITAND($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerBITAND() + public function providerBITAND(): array { return require 'tests/data/Calculation/Engineering/BITAND.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitLShiftTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitLShiftTest.php index 61aa89b4..ee408497 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitLShiftTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitLShiftTest.php @@ -2,30 +2,31 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class BitLShiftTest extends TestCase { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerBITLSHIFT * * @param mixed $expectedResult - * @param mixed[] $args */ - public function testBITLSHIFT($expectedResult, array $args): void + public function testBITLSHIFT($expectedResult, string $formula): void { - $result = Engineering::BITLSHIFT(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 8); + $sheet->getCell('A1')->setValue("=BITLSHIFT($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerBITLSHIFT() + public function providerBITLSHIFT(): array { return require 'tests/data/Calculation/Engineering/BITLSHIFT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitOrTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitOrTest.php index 857c7466..3cc1f4bc 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitOrTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitOrTest.php @@ -2,30 +2,31 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class BitOrTest extends TestCase { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerBITOR * * @param mixed $expectedResult - * @param mixed[] $args */ - public function testBITOR($expectedResult, array $args): void + public function testBITOR($expectedResult, string $formula): void { - $result = Engineering::BITOR(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 8); + $sheet->getCell('A1')->setValue("=BITOR($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerBITOR() + public function providerBITOR(): array { return require 'tests/data/Calculation/Engineering/BITOR.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitRShiftTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitRShiftTest.php index 26b13d07..f58d6149 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitRShiftTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitRShiftTest.php @@ -2,30 +2,31 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class BitRShiftTest extends TestCase { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerBITRSHIFT * * @param mixed $expectedResult - * @param mixed[] $args */ - public function testBITRSHIFT($expectedResult, array $args): void + public function testBITRSHIFT($expectedResult, string $formula): void { - $result = Engineering::BITRSHIFT(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 8); + $sheet->getCell('A1')->setValue("=BITRSHIFT($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerBITRSHIFT() + public function providerBITRSHIFT(): array { return require 'tests/data/Calculation/Engineering/BITRSHIFT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitXorTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitXorTest.php index 4415f6da..4fa302af 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitXorTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/BitXorTest.php @@ -2,30 +2,31 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class BitXorTest extends TestCase { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerBITXOR * * @param mixed $expectedResult - * @param mixed[] $args */ - public function testBITXOR($expectedResult, array $args): void + public function testBITXOR($expectedResult, string $formula): void { - $result = Engineering::BITXOR(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 8); + $sheet->getCell('A1')->setValue("=BITXOR($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerBITXOR() + public function providerBITXOR(): array { return require 'tests/data/Calculation/Engineering/BITXOR.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ComplexTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ComplexTest.php index 4b857e2d..f60315dc 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ComplexTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ComplexTest.php @@ -24,7 +24,7 @@ class ComplexTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerCOMPLEX() + public function providerCOMPLEX(): array { return require 'tests/data/Calculation/Engineering/COMPLEX.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ConvertUoMTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ConvertUoMTest.php index 7a18067f..87198edb 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ConvertUoMTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ConvertUoMTest.php @@ -37,6 +37,12 @@ class ConvertUoMTest extends TestCase self::assertIsArray($result); } + public function testGetBinaryConversionMultipliers(): void + { + $result = Engineering::getBinaryConversionMultipliers(); + self::assertIsArray($result); + } + /** * @dataProvider providerCONVERTUOM * @@ -48,7 +54,7 @@ class ConvertUoMTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerCONVERTUOM() + public function providerCONVERTUOM(): array { return require 'tests/data/Calculation/Engineering/CONVERTUOM.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2BinTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2BinTest.php index 3626ac6b..dfed3478 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2BinTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2BinTest.php @@ -2,30 +2,92 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class Dec2BinTest extends TestCase { + /** + * @var string + */ + private $compatibilityMode; + protected function setUp(): void { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $this->compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); } /** * @dataProvider providerDEC2BIN * * @param mixed $expectedResult + * @param mixed $formula */ - public function testDEC2BIN($expectedResult, ...$args): void + public function testDEC2BIN($expectedResult, $formula): void { - $result = Engineering::DECTOBIN(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 5); + $sheet->getCell('A1')->setValue("=DEC2BIN($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerDEC2BIN() + public function providerDEC2BIN(): array { return require 'tests/data/Calculation/Engineering/DEC2BIN.php'; } + + /** + * @dataProvider providerDEC2BIN + * + * @param mixed $expectedResult + * @param mixed $formula + */ + public function testDEC2BINOds($expectedResult, $formula): void + { + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + if ($formula === 'true') { + $expectedResult = 1; + } elseif ($formula === 'false') { + $expectedResult = 0; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 5); + $sheet->getCell('A1')->setValue("=DEC2BIN($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function testDEC2BINFrac(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + $cell = 'G1'; + $sheet->setCellValue($cell, '=DEC2BIN(5.1)'); + self::assertEquals(101, $sheet->getCell($cell)->getCalculatedValue(), 'Gnumeric'); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + $cell = 'O1'; + $sheet->setCellValue($cell, '=DEC2BIN(5.1)'); + self::assertEquals(101, $sheet->getCell($cell)->getCalculatedValue(), 'Ods'); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $cell = 'E1'; + $sheet->setCellValue($cell, '=DEC2BIN(5.1)'); + self::assertEquals(101, $sheet->getCell($cell)->getCalculatedValue(), 'Excel'); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2HexTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2HexTest.php index d191f620..ebe49464 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2HexTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2HexTest.php @@ -3,29 +3,99 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; use PhpOffice\PhpSpreadsheet\Calculation\Engineering; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class Dec2HexTest extends TestCase { + /** + * @var string + */ + private $compatibilityMode; + protected function setUp(): void { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $this->compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); } /** * @dataProvider providerDEC2HEX * * @param mixed $expectedResult + * @param mixed $formula */ - public function testDEC2HEX($expectedResult, ...$args): void + public function testDEC2HEX($expectedResult, $formula): void { - $result = Engineering::DECTOHEX(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 17); + $sheet->getCell('A1')->setValue("=DEC2HEX($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerDEC2HEX() + public function providerDEC2HEX(): array { return require 'tests/data/Calculation/Engineering/DEC2HEX.php'; } + + /** + * @dataProvider providerDEC2HEX + * + * @param mixed $expectedResult + * @param mixed $formula + */ + public function testDEC2HEXOds($expectedResult, $formula): void + { + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + if ($formula === 'true') { + $expectedResult = 1; + } elseif ($formula === 'false') { + $expectedResult = 0; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 17); + $sheet->getCell('A1')->setValue("=DEC2HEX($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function testDEC2HEXFrac(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + $cell = 'G1'; + $sheet->setCellValue($cell, '=DEC2HEX(17.1)'); + self::assertEquals(11, $sheet->getCell($cell)->getCalculatedValue(), 'Gnumeric'); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + $cell = 'O1'; + $sheet->setCellValue($cell, '=DEC2HEX(17.1)'); + self::assertEquals(11, $sheet->getCell($cell)->getCalculatedValue(), 'Ods'); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $cell = 'E1'; + $sheet->setCellValue($cell, '=DEC2HEX(17.1)'); + self::assertEquals(11, $sheet->getCell($cell)->getCalculatedValue(), 'Excel'); + } + + public function test32bitHex(): void + { + self::assertEquals('A2DE246000', Engineering\ConvertDecimal::hex32bit(-400000000000, 'DE246000', true)); + self::assertEquals('7FFFFFFFFF', Engineering\ConvertDecimal::hex32bit(549755813887, 'FFFFFFFF', true)); + self::assertEquals('FFFFFFFFFF', Engineering\ConvertDecimal::hex32bit(-1, 'FFFFFFFF', true)); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2OctTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2OctTest.php index 61eb3dbb..093f17bc 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2OctTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2OctTest.php @@ -2,30 +2,92 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class Dec2OctTest extends TestCase { + /** + * @var string + */ + private $compatibilityMode; + protected function setUp(): void { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $this->compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); } /** * @dataProvider providerDEC2OCT * * @param mixed $expectedResult + * @param mixed $formula */ - public function testDEC2OCT($expectedResult, ...$args): void + public function testDEC2OCT($expectedResult, $formula): void { - $result = Engineering::DECTOOCT(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 17); + $sheet->getCell('A1')->setValue("=DEC2OCT($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerDEC2OCT() + public function providerDEC2OCT(): array { return require 'tests/data/Calculation/Engineering/DEC2OCT.php'; } + + /** + * @dataProvider providerDEC2OCT + * + * @param mixed $expectedResult + * @param mixed $formula + */ + public function testDEC2OCTOds($expectedResult, $formula): void + { + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + if ($formula === 'true') { + $expectedResult = 1; + } elseif ($formula === 'false') { + $expectedResult = 0; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 17); + $sheet->getCell('A1')->setValue("=DEC2OCT($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function testDEC2OCTFrac(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + $cell = 'G1'; + $sheet->setCellValue($cell, '=DEC2OCT(17.1)'); + self::assertEquals(21, $sheet->getCell($cell)->getCalculatedValue(), 'Gnumeric'); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + $cell = 'O1'; + $sheet->setCellValue($cell, '=DEC2OCT(17.1)'); + self::assertEquals(21, $sheet->getCell($cell)->getCalculatedValue(), 'Ods'); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $cell = 'E1'; + $sheet->setCellValue($cell, '=DEC2OCT(17.1)'); + self::assertEquals(21, $sheet->getCell($cell)->getCalculatedValue(), 'Excel'); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/DeltaTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/DeltaTest.php index a93d2ea6..749b33c2 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/DeltaTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/DeltaTest.php @@ -24,7 +24,7 @@ class DeltaTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerDELTA() + public function providerDELTA(): array { return require 'tests/data/Calculation/Engineering/DELTA.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfCTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfCTest.php index 09bf448e..45d5b4c8 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfCTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfCTest.php @@ -27,7 +27,7 @@ class ErfCTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, self::ERF_PRECISION); } - public function providerERFC() + public function providerERFC(): array { return require 'tests/data/Calculation/Engineering/ERFC.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfPreciseTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfPreciseTest.php index eb26ae98..952b2560 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfPreciseTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfPreciseTest.php @@ -27,7 +27,7 @@ class ErfPreciseTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, self::ERF_PRECISION); } - public function providerERFPRECISE() + public function providerERFPRECISE(): array { return require 'tests/data/Calculation/Engineering/ERFPRECISE.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfTest.php index 8201edbc..9866024f 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ErfTest.php @@ -27,7 +27,7 @@ class ErfTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, self::ERF_PRECISION); } - public function providerERF() + public function providerERF(): array { return require 'tests/data/Calculation/Engineering/ERF.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/GeStepTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/GeStepTest.php index 370c1a82..07e3a48c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/GeStepTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/GeStepTest.php @@ -24,7 +24,7 @@ class GeStepTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerGESTEP() + public function providerGESTEP(): array { return require 'tests/data/Calculation/Engineering/GESTEP.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2BinTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2BinTest.php index 44d8908d..ad76716c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2BinTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2BinTest.php @@ -2,30 +2,95 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class Hex2BinTest extends TestCase { + /** + * @var string + */ + private $compatibilityMode; + protected function setUp(): void { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $this->compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); } /** * @dataProvider providerHEX2BIN * * @param mixed $expectedResult + * @param mixed $formula */ - public function testHEX2BIN($expectedResult, ...$args): void + public function testHEX2BIN($expectedResult, $formula): void { - $result = Engineering::HEXTOBIN(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 'B'); + $sheet->getCell('A1')->setValue("=HEX2BIN($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerHEX2BIN() + public function providerHEX2BIN(): array { return require 'tests/data/Calculation/Engineering/HEX2BIN.php'; } + + /** + * @dataProvider providerHEX2BIN + * + * @param mixed $expectedResult + * @param mixed $formula + */ + public function testHEX2BINOds($expectedResult, $formula): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + if ($formula === 'true') { + $expectedResult = 1; + } elseif ($formula === 'false') { + $expectedResult = 0; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 'B'); + $sheet->getCell('A1')->setValue("=HEX2BIN($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function testHEX2BINFrac(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + $cell = 'G1'; + $sheet->setCellValue($cell, '=HEX2BIN(10.1)'); + self::assertEquals('10000', $sheet->getCell($cell)->getCalculatedValue()); + $cell = 'F21'; + $sheet->setCellValue($cell, '=HEX2BIN("A.1")'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + $cell = 'O1'; + $sheet->setCellValue($cell, '=HEX2BIN(10.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $cell = 'E1'; + $sheet->setCellValue($cell, '=HEX2BIN(10.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2DecTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2DecTest.php index b388b2b7..806ba44d 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2DecTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2DecTest.php @@ -2,30 +2,95 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class Hex2DecTest extends TestCase { + /** + * @var string + */ + private $compatibilityMode; + protected function setUp(): void { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $this->compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); } /** * @dataProvider providerHEX2DEC * * @param mixed $expectedResult + * @param mixed $formula */ - public function testHEX2DEC($expectedResult, ...$args): void + public function testHEX2DEC($expectedResult, $formula): void { - $result = Engineering::HEXTODEC(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 'B'); + $sheet->getCell('A1')->setValue("=HEX2DEC($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerHEX2DEC() + public function providerHEX2DEC(): array { return require 'tests/data/Calculation/Engineering/HEX2DEC.php'; } + + /** + * @dataProvider providerHEX2DEC + * + * @param mixed $expectedResult + * @param mixed $formula + */ + public function testHEX2DECOds($expectedResult, $formula): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + if ($formula === 'true') { + $expectedResult = 1; + } elseif ($formula === 'false') { + $expectedResult = 0; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 'B'); + $sheet->getCell('A1')->setValue("=HEX2DEC($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function testHEX2DECFrac(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + $cell = 'G1'; + $sheet->setCellValue($cell, '=HEX2DEC(10.1)'); + self::assertEquals(16, $sheet->getCell($cell)->getCalculatedValue()); + $cell = 'F21'; + $sheet->setCellValue($cell, '=HEX2DEC("A.1")'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + $cell = 'O1'; + $sheet->setCellValue($cell, '=HEX2DEC(10.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $cell = 'E1'; + $sheet->setCellValue($cell, '=HEX2DEC(10.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue(), 'Excel'); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2OctTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2OctTest.php index bc0a5cb7..41f1a8f7 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2OctTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2OctTest.php @@ -2,30 +2,92 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class Hex2OctTest extends TestCase { + /** + * @var string + */ + private $compatibilityMode; + protected function setUp(): void { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $this->compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); } /** * @dataProvider providerHEX2OCT * * @param mixed $expectedResult + * @param mixed $formula */ - public function testHEX2OCT($expectedResult, ...$args): void + public function testHEX2OCT($expectedResult, $formula): void { - $result = Engineering::HEXTOOCT(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 'B'); + $sheet->getCell('A1')->setValue("=HEX2OCT($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerHEX2OCT() + public function providerHEX2OCT(): array { return require 'tests/data/Calculation/Engineering/HEX2OCT.php'; } + + /** + * @dataProvider providerHEX2OCT + * + * @param mixed $expectedResult + * @param mixed $formula + */ + public function testHEX2OCTOds($expectedResult, $formula): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + if ($formula === 'true') { + $expectedResult = 1; + } elseif ($formula === 'false') { + $expectedResult = 0; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 'B'); + $sheet->getCell('A1')->setValue("=HEX2OCT($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function testHEX2OCTFrac(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + $cell = 'G1'; + $sheet->setCellValue($cell, '=HEX2OCT(10.1)'); + self::assertEquals(20, $sheet->getCell($cell)->getCalculatedValue()); + $cell = 'F21'; + $sheet->setCellValue($cell, '=HEX2OCT("A.1")'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + $cell = 'O1'; + $sheet->setCellValue($cell, '=HEX2OCT(10.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $cell = 'E1'; + $sheet->setCellValue($cell, '=HEX2OCT(10.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue(), 'Excel'); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImAbsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImAbsTest.php index 1f1ee9dd..367cfb33 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImAbsTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImAbsTest.php @@ -27,7 +27,7 @@ class ImAbsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, self::COMPLEX_PRECISION); } - public function providerIMABS() + public function providerIMABS(): array { return require 'tests/data/Calculation/Engineering/IMABS.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImArgumentTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImArgumentTest.php index 6f1a6485..9ff8209f 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImArgumentTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImArgumentTest.php @@ -27,7 +27,7 @@ class ImArgumentTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, self::COMPLEX_PRECISION); } - public function providerIMARGUMENT() + public function providerIMARGUMENT(): array { return require 'tests/data/Calculation/Engineering/IMARGUMENT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImConjugateTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImConjugateTest.php index bc3a3918..d952f5df 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImConjugateTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImConjugateTest.php @@ -14,7 +14,7 @@ class ImConjugateTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImConjugateTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMCONJUGATE * @@ -42,7 +37,7 @@ class ImConjugateTest extends TestCase ); } - public function providerIMCONJUGATE() + public function providerIMCONJUGATE(): array { return require 'tests/data/Calculation/Engineering/IMCONJUGATE.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCosTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCosTest.php index 693f0bab..b6cb8dc2 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCosTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCosTest.php @@ -14,7 +14,7 @@ class ImCosTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImCosTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMCOS * @@ -42,7 +37,7 @@ class ImCosTest extends TestCase ); } - public function providerIMCOS() + public function providerIMCOS(): array { return require 'tests/data/Calculation/Engineering/IMCOS.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCoshTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCoshTest.php index ae035fef..0d544084 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCoshTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCoshTest.php @@ -14,7 +14,7 @@ class ImCoshTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImCoshTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMCOSH * @@ -42,7 +37,7 @@ class ImCoshTest extends TestCase ); } - public function providerIMCOSH() + public function providerIMCOSH(): array { return require 'tests/data/Calculation/Engineering/IMCOSH.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCotTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCotTest.php index 8b888b3f..69bd02ff 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCotTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCotTest.php @@ -14,7 +14,7 @@ class ImCotTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImCotTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMCOT * @@ -42,7 +37,7 @@ class ImCotTest extends TestCase ); } - public function providerIMCOT() + public function providerIMCOT(): array { return require 'tests/data/Calculation/Engineering/IMCOT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCscTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCscTest.php index 5a08c0b6..51843f89 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCscTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCscTest.php @@ -14,7 +14,7 @@ class ImCscTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImCscTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMCSC * @@ -42,7 +37,7 @@ class ImCscTest extends TestCase ); } - public function providerIMCSC() + public function providerIMCSC(): array { return require 'tests/data/Calculation/Engineering/IMCSC.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCschTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCschTest.php index a95a4eaf..d2d7511d 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCschTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCschTest.php @@ -14,7 +14,7 @@ class ImCschTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImCschTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMCSCH * @@ -42,7 +37,7 @@ class ImCschTest extends TestCase ); } - public function providerIMCSCH() + public function providerIMCSCH(): array { return require 'tests/data/Calculation/Engineering/IMCSCH.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImDivTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImDivTest.php index 2bc91619..652f5ca2 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImDivTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImDivTest.php @@ -14,7 +14,7 @@ class ImDivTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImDivTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMDIV * @@ -41,7 +36,7 @@ class ImDivTest extends TestCase ); } - public function providerIMDIV() + public function providerIMDIV(): array { return require 'tests/data/Calculation/Engineering/IMDIV.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImExpTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImExpTest.php index 7debbc9c..d7be573f 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImExpTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImExpTest.php @@ -14,7 +14,7 @@ class ImExpTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImExpTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMEXP * @@ -42,7 +37,7 @@ class ImExpTest extends TestCase ); } - public function providerIMEXP() + public function providerIMEXP(): array { return require 'tests/data/Calculation/Engineering/IMEXP.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLnTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLnTest.php index 3e270975..4566607b 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLnTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLnTest.php @@ -14,7 +14,7 @@ class ImLnTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImLnTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMLN * @@ -42,7 +37,7 @@ class ImLnTest extends TestCase ); } - public function providerIMLN() + public function providerIMLN(): array { return require 'tests/data/Calculation/Engineering/IMLN.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLog10Test.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLog10Test.php index 2a4db7fb..0d818686 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLog10Test.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLog10Test.php @@ -14,7 +14,7 @@ class ImLog10Test extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImLog10Test extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMLOG10 * @@ -42,7 +37,7 @@ class ImLog10Test extends TestCase ); } - public function providerIMLOG10() + public function providerIMLOG10(): array { return require 'tests/data/Calculation/Engineering/IMLOG10.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLog2Test.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLog2Test.php index 53b302dd..02583448 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLog2Test.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLog2Test.php @@ -14,7 +14,7 @@ class ImLog2Test extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImLog2Test extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMLOG2 * @@ -42,7 +37,7 @@ class ImLog2Test extends TestCase ); } - public function providerIMLOG2() + public function providerIMLOG2(): array { return require 'tests/data/Calculation/Engineering/IMLOG2.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImPowerTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImPowerTest.php index 41e52878..18e92791 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImPowerTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImPowerTest.php @@ -14,7 +14,7 @@ class ImPowerTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImPowerTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMPOWER * @@ -41,7 +36,7 @@ class ImPowerTest extends TestCase ); } - public function providerIMPOWER() + public function providerIMPOWER(): array { return require 'tests/data/Calculation/Engineering/IMPOWER.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImProductTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImProductTest.php index 43495739..845d436f 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImProductTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImProductTest.php @@ -14,7 +14,7 @@ class ImProductTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImProductTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMPRODUCT * @@ -41,7 +36,7 @@ class ImProductTest extends TestCase ); } - public function providerIMPRODUCT() + public function providerIMPRODUCT(): array { return require 'tests/data/Calculation/Engineering/IMPRODUCT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImRealTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImRealTest.php index 08d2feb1..790487c1 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImRealTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImRealTest.php @@ -27,7 +27,7 @@ class ImRealTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, self::COMPLEX_PRECISION); } - public function providerIMREAL() + public function providerIMREAL(): array { return require 'tests/data/Calculation/Engineering/IMREAL.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSecTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSecTest.php index e785d3ac..b750796d 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSecTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSecTest.php @@ -14,7 +14,7 @@ class ImSecTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImSecTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMSEC * @@ -42,7 +37,7 @@ class ImSecTest extends TestCase ); } - public function providerIMSEC() + public function providerIMSEC(): array { return require 'tests/data/Calculation/Engineering/IMSEC.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSechTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSechTest.php index 22cb6fae..c6477c1c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSechTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSechTest.php @@ -14,7 +14,7 @@ class ImSechTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImSechTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMSECH * @@ -42,7 +37,7 @@ class ImSechTest extends TestCase ); } - public function providerIMSECH() + public function providerIMSECH(): array { return require 'tests/data/Calculation/Engineering/IMSECH.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSinTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSinTest.php index dd797a35..df8cb020 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSinTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSinTest.php @@ -14,7 +14,7 @@ class ImSinTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImSinTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMSIN * @@ -42,7 +37,7 @@ class ImSinTest extends TestCase ); } - public function providerIMSIN() + public function providerIMSIN(): array { return require 'tests/data/Calculation/Engineering/IMSIN.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSinhTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSinhTest.php index 4174d1c0..f8dbafb9 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSinhTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSinhTest.php @@ -14,7 +14,7 @@ class ImSinhTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImSinhTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMSINH * @@ -42,7 +37,7 @@ class ImSinhTest extends TestCase ); } - public function providerIMSINH() + public function providerIMSINH(): array { return require 'tests/data/Calculation/Engineering/IMSINH.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSqrtTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSqrtTest.php index 091b11c5..0d536f94 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSqrtTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSqrtTest.php @@ -14,7 +14,7 @@ class ImSqrtTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImSqrtTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMSQRT * @@ -42,7 +37,7 @@ class ImSqrtTest extends TestCase ); } - public function providerIMSQRT() + public function providerIMSQRT(): array { return require 'tests/data/Calculation/Engineering/IMSQRT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSubTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSubTest.php index 79286120..edb413d5 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSubTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSubTest.php @@ -14,7 +14,7 @@ class ImSubTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImSubTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMSUB * @@ -41,7 +36,7 @@ class ImSubTest extends TestCase ); } - public function providerIMSUB() + public function providerIMSUB(): array { return require 'tests/data/Calculation/Engineering/IMSUB.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSumTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSumTest.php index 8abc3638..0620c684 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSumTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSumTest.php @@ -14,7 +14,7 @@ class ImSumTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImSumTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMSUM * @@ -41,7 +36,7 @@ class ImSumTest extends TestCase ); } - public function providerIMSUM() + public function providerIMSUM(): array { return require 'tests/data/Calculation/Engineering/IMSUM.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImTanTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImTanTest.php index 57b23815..f1e4037e 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImTanTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImTanTest.php @@ -14,7 +14,7 @@ class ImTanTest extends TestCase /** * @var ComplexAssert */ - protected $complexAssert; + private $complexAssert; protected function setUp(): void { @@ -22,11 +22,6 @@ class ImTanTest extends TestCase $this->complexAssert = new ComplexAssert(); } - protected function tearDown(): void - { - $this->complexAssert = null; - } - /** * @dataProvider providerIMTAN * @@ -42,7 +37,7 @@ class ImTanTest extends TestCase ); } - public function providerIMTAN() + public function providerIMTAN(): array { return require 'tests/data/Calculation/Engineering/IMTAN.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImaginaryTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImaginaryTest.php index 6ad72287..1c976b05 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImaginaryTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImaginaryTest.php @@ -27,7 +27,7 @@ class ImaginaryTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, self::COMPLEX_PRECISION); } - public function providerIMAGINARY() + public function providerIMAGINARY(): array { return require 'tests/data/Calculation/Engineering/IMAGINARY.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/MovedBitwiseTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/MovedBitwiseTest.php new file mode 100644 index 00000000..457db0d3 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/MovedBitwiseTest.php @@ -0,0 +1,24 @@ +compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); } /** * @dataProvider providerOCT2BIN * * @param mixed $expectedResult + * @param mixed $formula */ - public function testOCT2BIN($expectedResult, ...$args): void + public function testOCT2BIN($expectedResult, $formula): void { - $result = Engineering::OCTTOBIN(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 101); + $sheet->getCell('A1')->setValue("=OCT2BIN($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerOCT2BIN() + public function providerOCT2BIN(): array { return require 'tests/data/Calculation/Engineering/OCT2BIN.php'; } + + /** + * @dataProvider providerOCT2BIN + * + * @param mixed $expectedResult + * @param mixed $formula + */ + public function testOCT2BINOds($expectedResult, $formula): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + if ($formula === 'true') { + $expectedResult = 1; + } elseif ($formula === 'false') { + $expectedResult = 0; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 101); + $sheet->getCell('A1')->setValue("=OCT2BIN($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function testOCT2BINFrac(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + $cell = 'G1'; + $sheet->setCellValue($cell, '=HEX2BIN(10.1)'); + self::assertEquals('10000', $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + $cell = 'O1'; + $sheet->setCellValue($cell, '=OCT2BIN(10.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $cell = 'E1'; + $sheet->setCellValue($cell, '=OCT2BIN(10.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Oct2DecTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Oct2DecTest.php index 87e213ef..2c354046 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Oct2DecTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Oct2DecTest.php @@ -2,30 +2,92 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class Oct2DecTest extends TestCase { + /** + * @var string + */ + private $compatibilityMode; + protected function setUp(): void { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $this->compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); } /** * @dataProvider providerOCT2DEC * * @param mixed $expectedResult + * @param mixed $formula */ - public function testOCT2DEC($expectedResult, ...$args): void + public function testOCT2DEC($expectedResult, $formula): void { - $result = Engineering::OCTTODEC(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 101); + $sheet->getCell('A1')->setValue("=OCT2DEC($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerOCT2DEC() + public function providerOCT2DEC(): array { return require 'tests/data/Calculation/Engineering/OCT2DEC.php'; } + + /** + * @dataProvider providerOCT2DEC + * + * @param mixed $expectedResult + * @param mixed $formula + */ + public function testOCT2DECOds($expectedResult, $formula): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + if ($formula === 'true') { + $expectedResult = 1; + } elseif ($formula === 'false') { + $expectedResult = 0; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 101); + $sheet->getCell('A1')->setValue("=OCT2DEC($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function testOCT2DECFrac(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + $cell = 'G1'; + $sheet->setCellValue($cell, '=OCT2DEC(10.1)'); + self::assertEquals(8, $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + $cell = 'O1'; + $sheet->setCellValue($cell, '=OCT2DEC(10.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $cell = 'E1'; + $sheet->setCellValue($cell, '=OCT2DEC(10.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Oct2HexTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Oct2HexTest.php index e2d75a78..3f721a48 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Oct2HexTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Oct2HexTest.php @@ -2,30 +2,92 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering; -use PhpOffice\PhpSpreadsheet\Calculation\Engineering; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class Oct2HexTest extends TestCase { + /** + * @var string + */ + private $compatibilityMode; + protected function setUp(): void { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $this->compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); } /** * @dataProvider providerOCT2HEX * * @param mixed $expectedResult + * @param mixed $formula */ - public function testOCT2HEX($expectedResult, ...$args): void + public function testOCT2HEX($expectedResult, $formula): void { - $result = Engineering::OCTTOHEX(...$args); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 101); + $sheet->getCell('A1')->setValue("=OCT2HEX($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerOCT2HEX() + public function providerOCT2HEX(): array { return require 'tests/data/Calculation/Engineering/OCT2HEX.php'; } + + /** + * @dataProvider providerOCT2HEX + * + * @param mixed $expectedResult + * @param mixed $formula + */ + public function testOCT2HEXOds($expectedResult, $formula): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + if ($expectedResult === 'exception') { + $this->expectException(CalcExp::class); + } + if ($formula === 'true') { + $expectedResult = 1; + } elseif ($formula === 'false') { + $expectedResult = 0; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A2', 101); + $sheet->getCell('A1')->setValue("=OCT2HEX($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function testOCT2HEXFrac(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + $cell = 'G1'; + $sheet->setCellValue($cell, '=OCT2HEX(20.1)'); + self::assertEquals(10, $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + $cell = 'O1'; + $sheet->setCellValue($cell, '=OCT2HEX(20.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $cell = 'E1'; + $sheet->setCellValue($cell, '=OCT2HEX(20.1)'); + self::assertEquals('#NUM!', $sheet->getCell($cell)->getCalculatedValue()); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/AccrintMTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/AccrintMTest.php index 597db5c2..fbc7a61c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/AccrintMTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/AccrintMTest.php @@ -21,10 +21,10 @@ class AccrintMTest extends TestCase public function testACCRINTM($expectedResult, ...$args): void { $result = Financial::ACCRINTM(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerACCRINTM() + public function providerACCRINTM(): array { return require 'tests/data/Calculation/Financial/ACCRINTM.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/AccrintTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/AccrintTest.php index edb79230..74444061 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/AccrintTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/AccrintTest.php @@ -21,10 +21,10 @@ class AccrintTest extends TestCase public function testACCRINT($expectedResult, ...$args): void { $result = Financial::ACCRINT(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerACCRINT() + public function providerACCRINT(): array { return require 'tests/data/Calculation/Financial/ACCRINT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/AmorDegRcTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/AmorDegRcTest.php new file mode 100644 index 00000000..6f69e7fb --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/AmorDegRcTest.php @@ -0,0 +1,31 @@ + 0.999999 && $frac < 1.000001) { + $result = $expectedResult; + } + } + } + self::assertEquals($expectedResult, $result, $message); + } + + public function providerXNPV(): array + { + return require 'tests/data/Calculation/Financial/XNPV.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/XirrTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/XirrTest.php new file mode 100644 index 00000000..a6677f3f --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/XirrTest.php @@ -0,0 +1,40 @@ + 0.999999 && $frac < 1.000001) { + $result = $expectedResult; + } + } + } + self::assertEquals($expectedResult, $result, $message); + } + + public function providerXIRR(): array + { + return require 'tests/data/Calculation/Financial/XIRR.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/YieldDiscTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/YieldDiscTest.php new file mode 100644 index 00000000..1c966d87 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Financial/YieldDiscTest.php @@ -0,0 +1,31 @@ +getMockBuilder(Cell::class) + ->onlyMethods(['getColumn']) + ->disableOriginalConstructor() + ->getMock(); + $cell->method('getColumn') + ->willReturn('D'); + + $result = LookupRef::COLUMN(null, $cell); + self::assertSame(4, $result); + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnsTest.php index a7908241..e14023cd 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnsTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnsTest.php @@ -24,7 +24,7 @@ class ColumnsTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerCOLUMNS() + public function providerCOLUMNS(): array { return require 'tests/data/Calculation/LookupRef/COLUMNS.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/HLookupTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/HLookupTest.php index 767b6de8..7484c47b 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/HLookupTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/HLookupTest.php @@ -24,7 +24,7 @@ class HLookupTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerHLOOKUP() + public function providerHLOOKUP(): array { return require 'tests/data/Calculation/LookupRef/HLOOKUP.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php index 8ff66931..4de661ed 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php @@ -21,10 +21,11 @@ class IndexTest extends TestCase public function testINDEX($expectedResult, ...$args): void { $result = LookupRef::INDEX(...$args); +// var_dump($result); self::assertEquals($expectedResult, $result); } - public function providerINDEX() + public function providerINDEX(): array { return require 'tests/data/Calculation/LookupRef/INDEX.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php new file mode 100644 index 00000000..b11fce68 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php @@ -0,0 +1,55 @@ +getMockBuilder(Calculation::class) +// ->setMethods(['getInstance', 'extractCellRange']) +// ->disableOriginalConstructor() +// ->getMock(); +// $calculation->method('getInstance') +// ->willReturn($calculation); +// $calculation->method('extractCellRange') +// ->willReturn([]); +// +// $worksheet = $this->getMockBuilder(Cell::class) +// ->setMethods(['getParent']) +// ->disableOriginalConstructor() +// ->getMock(); +// +// $cell = $this->getMockBuilder(Cell::class) +// ->setMethods(['getWorksheet']) +// ->disableOriginalConstructor() +// ->getMock(); +// $cell->method('getWorksheet') +// ->willReturn($worksheet); + + $result = LookupRef::INDIRECT($cellReference); + self::assertSame($expectedResult, $result); + } + + public function providerINDIRECT(): array + { + return require 'tests/data/Calculation/LookupRef/INDIRECT.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/LookupTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/LookupTest.php index d1b36e4a..73dffd3d 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/LookupTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/LookupTest.php @@ -24,7 +24,7 @@ class LookupTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerLOOKUP() + public function providerLOOKUP(): array { return require 'tests/data/Calculation/LookupRef/LOOKUP.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/MatchTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/MatchTest.php index e020d3ba..ba2dce29 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/MatchTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/MatchTest.php @@ -24,7 +24,7 @@ class MatchTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerMATCH() + public function providerMATCH(): array { return require 'tests/data/Calculation/LookupRef/MATCH.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php new file mode 100644 index 00000000..31a9703a --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php @@ -0,0 +1,32 @@ +getMockBuilder(Cell::class) + ->onlyMethods(['getRow']) + ->disableOriginalConstructor() + ->getMock(); + $cell->method('getRow') + ->willReturn(3); + + $result = LookupRef::ROW(null, $cell); + self::assertSame(3, $result); + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/RowsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/RowsTest.php index 62a06626..ec9b33d9 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/RowsTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/RowsTest.php @@ -24,7 +24,7 @@ class RowsTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerROWS() + public function providerROWS(): array { return require 'tests/data/Calculation/LookupRef/ROWS.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/TransposeTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/TransposeTest.php new file mode 100644 index 00000000..7654f1bd --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/TransposeTest.php @@ -0,0 +1,32 @@ +sheet; + $this->mightHaveException($expectedResult); + $this->setCell('A1', $number); + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=ABS()'); + } else { + $sheet->getCell('B1')->setValue('=ABS(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertSame($expectedResult, $result); + } + + public function providerAbs(): array + { + return require 'tests/data/Calculation/MathTrig/ABS.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcosTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcosTest.php new file mode 100644 index 00000000..c6372ddb --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcosTest.php @@ -0,0 +1,26 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A2')->setValue(0.5); + $sheet->getCell('A1')->setValue("=ACOS($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerAcos(): array + { + return require 'tests/data/Calculation/MathTrig/ACOS.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcoshTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcoshTest.php new file mode 100644 index 00000000..557da135 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcoshTest.php @@ -0,0 +1,26 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A2')->setValue('1.5'); + $sheet->getCell('A1')->setValue("=ACOSH($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerAcosh(): array + { + return require 'tests/data/Calculation/MathTrig/ACOSH.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcotTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcotTest.php index d81c3b9d..d94d4dec 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcotTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcotTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class AcotTest extends TestCase +class AcotTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerACOT * @@ -21,11 +12,18 @@ class AcotTest extends TestCase */ public function testACOT($expectedResult, $number): void { - $result = MathTrig::ACOT($number); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5); + $sheet->getCell('A1')->setValue("=ACOT($number)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-9); } - public function providerACOT() + public function providerACOT(): array { return require 'tests/data/Calculation/MathTrig/ACOT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcothTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcothTest.php index 0a3864cc..a065b496 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcothTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcothTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class AcothTest extends TestCase +class AcothTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerACOTH * @@ -21,11 +12,18 @@ class AcothTest extends TestCase */ public function testACOTH($expectedResult, $number): void { - $result = MathTrig::ACOTH($number); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -10); + $sheet->getCell('A1')->setValue("=ACOTH($number)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-9); } - public function providerACOTH() + public function providerACOTH(): array { return require 'tests/data/Calculation/MathTrig/ACOTH.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AllSetupTeardown.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AllSetupTeardown.php new file mode 100644 index 00000000..a8630727 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AllSetupTeardown.php @@ -0,0 +1,75 @@ +compatibilityMode = Functions::getCompatibilityMode(); + $this->spreadsheet = new Spreadsheet(); + $this->sheet = $this->spreadsheet->getActiveSheet(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); + $this->spreadsheet->disconnectWorksheets(); + } + + protected static function setOpenOffice(): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + } + + protected static function setGnumeric(): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + } + + /** + * @param mixed $expectedResult + */ + protected function mightHaveException($expectedResult): void + { + if ($expectedResult === 'exception') { + $this->expectException(CalcException::class); + } + } + + /** + * @param mixed $value + */ + protected function setCell(string $cell, $value): void + { + if ($value !== null) { + if (is_string($value) && is_numeric($value)) { + $this->sheet->getCell($cell)->setValueExplicit($value, DataType::TYPE_STRING); + } else { + $this->sheet->getCell($cell)->setValue($value); + } + } + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ArabicTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ArabicTest.php index 7b3a5e15..c9dbbcce 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ArabicTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ArabicTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class ArabicTest extends TestCase +class ArabicTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerARABIC * @@ -21,11 +12,15 @@ class ArabicTest extends TestCase */ public function testARABIC($expectedResult, $romanNumeral): void { - $result = MathTrig::ARABIC($romanNumeral); - self::assertEquals($expectedResult, $result); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue($romanNumeral); + $sheet->getCell('B1')->setValue('=ARABIC(A1)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertSame($expectedResult, $result); } - public function providerARABIC() + public function providerARABIC(): array { return require 'tests/data/Calculation/MathTrig/ARABIC.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AsinTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AsinTest.php new file mode 100644 index 00000000..b3612d01 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AsinTest.php @@ -0,0 +1,26 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A2')->setValue(0.5); + $sheet->getCell('A1')->setValue("=ASIN($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerAsin(): array + { + return require 'tests/data/Calculation/MathTrig/ASIN.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AsinhTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AsinhTest.php new file mode 100644 index 00000000..e8f21142 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AsinhTest.php @@ -0,0 +1,26 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A2')->setValue(0.5); + $sheet->getCell('A1')->setValue("=ASINH($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerAsinh(): array + { + return require 'tests/data/Calculation/MathTrig/ASINH.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/Atan2Test.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/Atan2Test.php index 4edec4cb..a22d6ad3 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/Atan2Test.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/Atan2Test.php @@ -2,31 +2,25 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class Atan2Test extends TestCase +class Atan2Test extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerATAN2 * * @param mixed $expectedResult - * @param mixed $x - * @param mixed $y */ - public function testATAN2($expectedResult, $x, $y): void + public function testATAN2($expectedResult, string $formula): void { - $result = MathTrig::ATAN2($x, $y); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A2')->setValue(5); + $sheet->getCell('A3')->setValue(6); + $sheet->getCell('A1')->setValue("=ATAN2($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-9); } - public function providerATAN2() + public function providerATAN2(): array { return require 'tests/data/Calculation/MathTrig/ATAN2.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AtanTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AtanTest.php new file mode 100644 index 00000000..69f4ed53 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AtanTest.php @@ -0,0 +1,26 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A2')->setValue(5); + $sheet->getCell('A1')->setValue("=ATAN($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerAtan(): array + { + return require 'tests/data/Calculation/MathTrig/ATAN.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AtanhTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AtanhTest.php new file mode 100644 index 00000000..20a3e9c0 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AtanhTest.php @@ -0,0 +1,26 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A2')->setValue(0.8); + $sheet->getCell('A1')->setValue("=ATANH($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerAtanh(): array + { + return require 'tests/data/Calculation/MathTrig/ATANH.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/BaseTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/BaseTest.php index 72b52559..8bf0f4b2 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/BaseTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/BaseTest.php @@ -2,29 +2,43 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class BaseTest extends TestCase +class BaseTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerBASE * * @param mixed $expectedResult + * @param mixed $arg1 + * @param mixed $arg2 + * @param mixed $arg3 */ - public function testBASE($expectedResult, ...$args): void + public function testBASE($expectedResult, $arg1 = 'omitted', $arg2 = 'omitted', $arg3 = 'omitted'): void { - $result = MathTrig::BASE(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($arg1 !== null) { + $sheet->getCell('A1')->setValue($arg1); + } + if ($arg2 !== null) { + $sheet->getCell('A2')->setValue($arg2); + } + if ($arg3 !== null) { + $sheet->getCell('A3')->setValue($arg3); + } + if ($arg1 === 'omitted') { + $sheet->getCell('B1')->setValue('=BASE()'); + } elseif ($arg2 === 'omitted') { + $sheet->getCell('B1')->setValue('=BASE(A1)'); + } elseif ($arg3 === 'omitted') { + $sheet->getCell('B1')->setValue('=BASE(A1, A2)'); + } else { + $sheet->getCell('B1')->setValue('=BASE(A1, A2, A3)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerBASE() + public function providerBASE(): array { return require 'tests/data/Calculation/MathTrig/BASE.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CeilingMathTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CeilingMathTest.php new file mode 100644 index 00000000..f4039458 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CeilingMathTest.php @@ -0,0 +1,30 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=CEILING.MATH($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + } + + public function providerCEILINGMATH(): array + { + return require 'tests/data/Calculation/MathTrig/CEILINGMATH.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CeilingPreciseTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CeilingPreciseTest.php new file mode 100644 index 00000000..c859646b --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CeilingPreciseTest.php @@ -0,0 +1,30 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=CEILING.PRECISE($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + } + + public function providerFLOORPRECISE(): array + { + return require 'tests/data/Calculation/MathTrig/CEILINGPRECISE.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CeilingTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CeilingTest.php index b60d7c30..d65ac6e9 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CeilingTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CeilingTest.php @@ -2,30 +2,56 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class CeilingTest extends TestCase +class CeilingTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerCEILING * * @param mixed $expectedResult + * @param string $formula */ - public function testCEILING($expectedResult, ...$args): void + public function testCEILING($expectedResult, $formula): void { - $result = MathTrig::CEILING(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=CEILING($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerCEILING() + public function providerCEILING(): array { return require 'tests/data/Calculation/MathTrig/CEILING.php'; } + + public function testCEILINGGnumeric1Arg(): void + { + self::setGnumeric(); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue('=CEILING(5.1)'); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta(6, $result, 1E-12); + } + + public function testCELINGOpenOffice1Arg(): void + { + self::setOpenOffice(); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue('=CEILING(5.1)'); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta(6, $result, 1E-12); + } + + public function testCEILINGExcel1Arg(): void + { + $this->mightHaveException('exception'); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue('=CEILING(5.1)'); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta(6, $result, 1E-12); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinATest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinATest.php new file mode 100644 index 00000000..45ad8b29 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinATest.php @@ -0,0 +1,33 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($numObjs !== null) { + $sheet->getCell('A1')->setValue($numObjs); + } + if ($numInSet !== null) { + $sheet->getCell('A2')->setValue($numInSet); + } + $sheet->getCell('B1')->setValue('=COMBINA(A1,A2)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function providerCOMBINA(): array + { + return require 'tests/data/Calculation/MathTrig/COMBINA.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinTest.php index d9156339..408b60fe 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CombinTest.php @@ -2,29 +2,31 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class CombinTest extends TestCase +class CombinTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerCOMBIN * * @param mixed $expectedResult + * @param mixed $numObjs + * @param mixed $numInSet */ - public function testCOMBIN($expectedResult, ...$args): void + public function testCOMBIN($expectedResult, $numObjs, $numInSet): void { - $result = MathTrig::COMBIN(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($numObjs !== null) { + $sheet->getCell('A1')->setValue($numObjs); + } + if ($numInSet !== null) { + $sheet->getCell('A2')->setValue($numInSet); + } + $sheet->getCell('B1')->setValue('=COMBIN(A1,A2)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); } - public function providerCOMBIN() + public function providerCOMBIN(): array { return require 'tests/data/Calculation/MathTrig/COMBIN.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CosTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CosTest.php new file mode 100644 index 00000000..5bb3427e --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CosTest.php @@ -0,0 +1,26 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 2); + $sheet->getCell('A1')->setValue("=COS($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerCos(): array + { + return require 'tests/data/Calculation/MathTrig/COS.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CoshTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CoshTest.php new file mode 100644 index 00000000..c408338f --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CoshTest.php @@ -0,0 +1,26 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 2); + $sheet->getCell('A1')->setValue("=COSH($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerCosh(): array + { + return require 'tests/data/Calculation/MathTrig/COSH.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CotTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CotTest.php index 3fee6901..d096d0da 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CotTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CotTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class CotTest extends TestCase +class CotTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerCOT * @@ -21,11 +12,18 @@ class CotTest extends TestCase */ public function testCOT($expectedResult, $angle): void { - $result = MathTrig::COT($angle); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=COT($angle)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-9); } - public function providerCOT() + public function providerCOT(): array { return require 'tests/data/Calculation/MathTrig/COT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CothTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CothTest.php index e3db23d5..11949c43 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CothTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CothTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class CothTest extends TestCase +class CothTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerCOTH * @@ -21,11 +12,18 @@ class CothTest extends TestCase */ public function testCOTH($expectedResult, $angle): void { - $result = MathTrig::COTH($angle); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=COTH($angle)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-9); } - public function providerCOTH() + public function providerCOTH(): array { return require 'tests/data/Calculation/MathTrig/COTH.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CscTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CscTest.php index 675ebf57..ea5a74bf 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CscTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CscTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class CscTest extends TestCase +class CscTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerCSC * @@ -21,11 +12,18 @@ class CscTest extends TestCase */ public function testCSC($expectedResult, $angle): void { - $result = MathTrig::CSC($angle); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=CSC($angle)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-9); } - public function providerCSC() + public function providerCSC(): array { return require 'tests/data/Calculation/MathTrig/CSC.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CschTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CschTest.php index c630be2f..071f7497 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CschTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CschTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class CschTest extends TestCase +class CschTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerCSCH * @@ -21,11 +12,18 @@ class CschTest extends TestCase */ public function testCSCH($expectedResult, $angle): void { - $result = MathTrig::CSCH($angle); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=CSCH($angle)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-9); } - public function providerCSCH() + public function providerCSCH(): array { return require 'tests/data/Calculation/MathTrig/CSCH.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/DegreesTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/DegreesTest.php new file mode 100644 index 00000000..10eb3b10 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/DegreesTest.php @@ -0,0 +1,31 @@ +sheet; + $this->mightHaveException($expectedResult); + $this->setCell('A1', $number); + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=DEGREES()'); + } else { + $sheet->getCell('B1')->setValue('=DEGREES(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + } + + public function providerDegrees(): array + { + return require 'tests/data/Calculation/MathTrig/DEGREES.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/EvenTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/EvenTest.php index 96c0b046..12bca051 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/EvenTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/EvenTest.php @@ -2,30 +2,24 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class EvenTest extends TestCase +class EvenTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerEVEN * * @param mixed $expectedResult - * @param $value + * @param mixed $value */ public function testEVEN($expectedResult, $value): void { - $result = MathTrig::EVEN($value); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=EVEN($value)"); + $sheet->getCell('A2')->setValue(3.7); + self::assertEquals($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerEVEN() + public function providerEVEN(): array { return require 'tests/data/Calculation/MathTrig/EVEN.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ExpTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ExpTest.php new file mode 100644 index 00000000..70e7c650 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ExpTest.php @@ -0,0 +1,33 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($number !== null) { + $sheet->getCell('A1')->setValue($number); + } + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=EXP()'); + } else { + $sheet->getCell('B1')->setValue('=EXP(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + } + + public function providerEXP(): array + { + return require 'tests/data/Calculation/MathTrig/EXP.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactDoubleTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactDoubleTest.php index f0b6b146..70f06f11 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactDoubleTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactDoubleTest.php @@ -2,30 +2,25 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class FactDoubleTest extends TestCase +class FactDoubleTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerFACTDOUBLE * * @param mixed $expectedResult - * @param $value + * @param mixed $value */ public function testFACTDOUBLE($expectedResult, $value): void { - $result = MathTrig::FACTDOUBLE($value); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue($value); + $sheet->getCell('B1')->setValue('=FACTDOUBLE(A1)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); } - public function providerFACTDOUBLE() + public function providerFACTDOUBLE(): array { return require 'tests/data/Calculation/MathTrig/FACTDOUBLE.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactTest.php index f6092896..379b382c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FactTest.php @@ -2,31 +2,60 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class FactTest extends TestCase +class FactTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerFACT * * @param mixed $expectedResult - * @param $value + * @param mixed $arg1 */ - public function testFACT($expectedResult, $value): void + public function testFACT($expectedResult, $arg1): void { - $result = MathTrig::FACT($value); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($arg1 !== null) { + $sheet->getCell('A1')->setValue($arg1); + } + if ($arg1 === 'omitted') { + $sheet->getCell('B1')->setValue('=FACT()'); + } else { + $sheet->getCell('B1')->setValue('=FACT(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); } - public function providerFACT() + public function providerFACT(): array { return require 'tests/data/Calculation/MathTrig/FACT.php'; } + + /** + * @dataProvider providerFACTGnumeric + * + * @param mixed $expectedResult + * @param mixed $arg1 + */ + public function testFACTGnumeric($expectedResult, $arg1): void + { + $this->mightHaveException($expectedResult); + self::setGnumeric(); + $sheet = $this->sheet; + if ($arg1 !== null) { + $sheet->getCell('A1')->setValue($arg1); + } + if ($arg1 === 'omitted') { + $sheet->getCell('B1')->setValue('=FACT()'); + } else { + $sheet->getCell('B1')->setValue('=FACT(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function providerFACTGnumeric(): array + { + return require 'tests/data/Calculation/MathTrig/FACTGNUMERIC.php'; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FloorMathTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FloorMathTest.php index d7d51b59..ecf4c73a 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FloorMathTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FloorMathTest.php @@ -2,29 +2,28 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class FloorMathTest extends TestCase +class FloorMathTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerFLOORMATH * * @param mixed $expectedResult + * @param string $formula */ - public function testFLOORMATH($expectedResult, ...$args): void + public function testFLOORMATH($expectedResult, $formula): void { - $result = MathTrig::FLOORMATH(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=FLOOR.MATH($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerFLOORMATH() + public function providerFLOORMATH(): array { return require 'tests/data/Calculation/MathTrig/FLOORMATH.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FloorPreciseTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FloorPreciseTest.php index ae5a3199..22dd72ad 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FloorPreciseTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FloorPreciseTest.php @@ -2,29 +2,28 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class FloorPreciseTest extends TestCase +class FloorPreciseTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerFLOORPRECISE * * @param mixed $expectedResult + * @param string $formula */ - public function testFLOOR($expectedResult, ...$args): void + public function testFLOORPRECISE($expectedResult, $formula): void { - $result = MathTrig::FLOORPRECISE(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=FLOOR.PRECISE($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerFLOORPRECISE() + public function providerFLOORPRECISE(): array { return require 'tests/data/Calculation/MathTrig/FLOORPRECISE.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FloorTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FloorTest.php index e66d97ae..b981b26a 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FloorTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/FloorTest.php @@ -2,30 +2,56 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class FloorTest extends TestCase +class FloorTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerFLOOR * * @param mixed $expectedResult + * @param string $formula */ - public function testFLOOR($expectedResult, ...$args): void + public function testFLOOR($expectedResult, $formula): void { - $result = MathTrig::FLOOR(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=FLOOR($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerFLOOR() + public function providerFLOOR(): array { return require 'tests/data/Calculation/MathTrig/FLOOR.php'; } + + public function testFLOORGnumeric1Arg(): void + { + self::setGnumeric(); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue('=FLOOR(5.1)'); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta(5, $result, 1E-12); + } + + public function testFLOOROpenOffice1Arg(): void + { + self::setOpenOffice(); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue('=FLOOR(5.1)'); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta(5, $result, 1E-12); + } + + public function testFLOORExcel1Arg(): void + { + $this->mightHaveException('exception'); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue('=FLOOR(5.1)'); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta(5, $result, 1E-12); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/GcdTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/GcdTest.php index ce1aec3f..aaa641d7 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/GcdTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/GcdTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class GcdTest extends TestCase +class GcdTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerGCD * @@ -20,11 +11,25 @@ class GcdTest extends TestCase */ public function testGCD($expectedResult, ...$args): void { - $result = MathTrig::GCD(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $row = 0; + foreach ($args as $arg) { + ++$row; + if ($arg !== null) { + $sheet->getCell("A$row")->setValue($arg); + } + } + if ($row < 1) { + $sheet->getCell('B1')->setValue('=GCD()'); + } else { + $sheet->getCell('B1')->setValue("=GCD(A1:A$row)"); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerGCD() + public function providerGCD(): array { return require 'tests/data/Calculation/MathTrig/GCD.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/IntTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/IntTest.php index f400a7fe..29abbf41 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/IntTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/IntTest.php @@ -2,30 +2,28 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class IntTest extends TestCase +class IntTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerINT * * @param mixed $expectedResult - * @param $value + * @param string $formula */ - public function testINT($expectedResult, $value): void + public function testINT($expectedResult, $formula): void { - $result = MathTrig::INT($value); - self::assertEquals($expectedResult, $result); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=INT($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerINT() + public function providerINT(): array { return require 'tests/data/Calculation/MathTrig/INT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LcmTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LcmTest.php index 57b4a67f..5d0ebb58 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LcmTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LcmTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class LcmTest extends TestCase +class LcmTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerLCM * @@ -20,11 +11,18 @@ class LcmTest extends TestCase */ public function testLCM($expectedResult, ...$args): void { - $result = MathTrig::LCM(...$args); + $sheet = $this->sheet; + $row = 0; + foreach ($args as $arg) { + ++$row; + $sheet->getCell("A$row")->setValue($arg); + } + $sheet->getCell('B1')->setValue("=LCM(A1:A$row)"); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerLCM() + public function providerLCM(): array { return require 'tests/data/Calculation/MathTrig/LCM.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LnTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LnTest.php new file mode 100644 index 00000000..3fe35a3c --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LnTest.php @@ -0,0 +1,33 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($number !== null) { + $sheet->getCell('A1')->setValue($number); + } + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=LN()'); + } else { + $sheet->getCell('B1')->setValue('=LN(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerLN(): array + { + return require 'tests/data/Calculation/MathTrig/LN.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/Log10Test.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/Log10Test.php new file mode 100644 index 00000000..61cade1d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/Log10Test.php @@ -0,0 +1,33 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($number !== null) { + $sheet->getCell('A1')->setValue($number); + } + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=LOG10()'); + } else { + $sheet->getCell('B1')->setValue('=LOG10(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerLN(): array + { + return require 'tests/data/Calculation/MathTrig/LOG10.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LogTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LogTest.php index 184d83e6..8c45a840 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LogTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/LogTest.php @@ -2,29 +2,37 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class LogTest extends TestCase +class LogTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerLOG * * @param mixed $expectedResult + * @param mixed $number + * @param mixed $base */ - public function testLOG($expectedResult, ...$args): void + public function testLOG($expectedResult, $number = 'omitted', $base = 'omitted'): void { - $result = MathTrig::logBase(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($number !== null) { + $sheet->getCell('A1')->setValue($number); + } + if ($base !== null) { + $sheet->getCell('A2')->setValue($base); + } + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=LOG()'); + } elseif ($base === 'omitted') { + $sheet->getCell('B1')->setValue('=LOG(A1)'); + } else { + $sheet->getCell('B1')->setValue('=LOG(A1,A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerLOG() + public function providerLOG(): array { return require 'tests/data/Calculation/MathTrig/LOG.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MInverseTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MInverseTest.php index a500c3f6..b629d466 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MInverseTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MInverseTest.php @@ -2,30 +2,31 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; -class MInverseTest extends TestCase +class MInverseTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerMINVERSE * * @param mixed $expectedResult */ - public function testMINVERSE($expectedResult, ...$args): void + public function testMINVERSE($expectedResult, array $args): void { - $result = MathTrig::MINVERSE(...$args); + $result = MathTrig\MatrixFunctions::inverse($args); self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerMINVERSE() + public function providerMINVERSE(): array { return require 'tests/data/Calculation/MathTrig/MINVERSE.php'; } + + public function testOnSpreadsheet(): void + { + // very limited ability to test this in the absence of dynamic arrays + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue('=MINVERSE({1,2,3})'); // not square + self::assertSame('#VALUE!', $sheet->getCell('A1')->getCalculatedValue()); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MMultTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MMultTest.php index 66fa80db..e27922c2 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MMultTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MMultTest.php @@ -2,17 +2,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; -class MMultTest extends TestCase +class MMultTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerMMULT * @@ -20,12 +13,31 @@ class MMultTest extends TestCase */ public function testMMULT($expectedResult, ...$args): void { - $result = MathTrig::MMULT(...$args); + $result = MathTrig\MatrixFunctions::multiply(...$args); self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerMMULT() + public function providerMMULT(): array { return require 'tests/data/Calculation/MathTrig/MMULT.php'; } + + public function testOnSpreadsheet(): void + { + // very limited ability to test this in the absence of dynamic arrays + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue('=MMULT({1,2,3}, {1,2,3})'); // incompatible dimensions + self::assertSame('#VALUE!', $sheet->getCell('A1')->getCalculatedValue()); + + $sheet->getCell('A11')->setValue('=MMULT({1, 2, 3, 4}, {5; 6; 7; 8})'); + self::assertEquals(70, $sheet->getCell('A11')->getCalculatedValue()); + $sheet->getCell('A2')->setValue(1); + $sheet->getCell('B2')->setValue(2); + $sheet->getCell('C2')->setValue(3); + $sheet->getCell('D2')->setValue(4); + $sheet->getCell('D3')->setValue(5); + $sheet->getCell('D4')->setValue(6); + $sheet->getCell('A12')->setValue('=MMULT(A2:C2,D2:D4)'); + self::assertEquals(32, $sheet->getCell('A12')->getCalculatedValue()); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MRoundTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MRoundTest.php index 32c9c355..e7aa0f41 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MRoundTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MRoundTest.php @@ -2,29 +2,28 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class MRoundTest extends TestCase +class MRoundTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerMROUND * * @param mixed $expectedResult + * @param mixed $formula */ - public function testMROUND($expectedResult, ...$args): void + public function testMROUND($expectedResult, $formula): void { - $result = MathTrig::MROUND(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=MROUND($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerMROUND() + public function providerMROUND(): array { return require 'tests/data/Calculation/MathTrig/MROUND.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MUnitTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MUnitTest.php new file mode 100644 index 00000000..1035dac7 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MUnitTest.php @@ -0,0 +1,23 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + if (is_array($matrix)) { + $sheet->fromArray($matrix, null, 'A1', true); + $maxCol = $sheet->getHighestColumn(); + $maxRow = $sheet->getHighestRow(); + $sheet->getCell('Z1')->setValue("=MDETERM(A1:$maxCol$maxRow)"); + } else { + $sheet->getCell('Z1')->setValue("=MDETERM($matrix)"); + } + $result = $sheet->getCell('Z1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerMDETERM() + public function providerMDETERM(): array { return require 'tests/data/Calculation/MathTrig/MDETERM.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ModTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ModTest.php index 930708f5..ff35657f 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ModTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ModTest.php @@ -2,29 +2,37 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class ModTest extends TestCase +class ModTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerMOD * * @param mixed $expectedResult + * @param mixed $dividend + * @param mixed $divisor */ - public function testMOD($expectedResult, ...$args): void + public function testMOD($expectedResult, $dividend = 'omitted', $divisor = 'omitted'): void { - $result = MathTrig::MOD(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($dividend !== null) { + $sheet->getCell('A1')->setValue($dividend); + } + if ($divisor !== null) { + $sheet->getCell('A2')->setValue($divisor); + } + if ($dividend === 'omitted') { + $sheet->getCell('B1')->setValue('=MOD()'); + } elseif ($divisor === 'omitted') { + $sheet->getCell('B1')->setValue('=MOD(A1)'); + } else { + $sheet->getCell('B1')->setValue('=MOD(A1,A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerMOD() + public function providerMOD(): array { return require 'tests/data/Calculation/MathTrig/MOD.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MovedFunctionsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MovedFunctionsTest.php new file mode 100644 index 00000000..2cca8f36 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MovedFunctionsTest.php @@ -0,0 +1,111 @@ +2')); + self::assertEquals(2, MathTrig::SUMIFS( + [[1], [1], [1]], + [['Y'], ['Y'], ['N']], + '=Y', + [['H'], ['H'], ['H']], + '=H' + )); + self::assertEquals(17, MathTrig::SUMPRODUCT([1, 2, 3], [5, 0, 4])); + self::assertEquals(21, MathTrig::SUMSQ(1, 2, 4)); + self::assertEquals(-20, MathTrig::SUMX2MY2([1, 2], [3, 4])); + self::assertEquals(30, MathTrig::SUMX2PY2([1, 2], [3, 4])); + self::assertEquals(8, MathTrig::SUMXMY2([1, 2], [3, 4])); + self::assertEquals(0, MathTrig::builtinTAN(0)); + self::assertEquals(0, MathTrig::builtinTANH(0)); + self::assertEquals(70, MathTrig::TRUNC(79.2, -1)); + self::assertEquals(1, MathTrig::returnSign(79.2)); + self::assertEquals(80, MathTrig::getEven(79.2)); + $nullVal = null; + MathTrig::nullFalseTrueToNumber($nullVal); + self::assertSame(0, $nullVal); + $nullVal = true; + MathTrig::nullFalseTrueToNumber($nullVal); + self::assertSame(1, $nullVal); + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MultinomialTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MultinomialTest.php index 93735ba9..3fea7651 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MultinomialTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MultinomialTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class MultinomialTest extends TestCase +class MultinomialTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerMULTINOMIAL * @@ -20,11 +11,23 @@ class MultinomialTest extends TestCase */ public function testMULTINOMIAL($expectedResult, ...$args): void { - $result = MathTrig::MULTINOMIAL(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $row = 0; + $excelArg = ''; + foreach ($args as $arg) { + ++$row; + $excelArg = "A1:A$row"; + if ($arg !== null) { + $sheet->getCell("A$row")->setValue($arg); + } + } + $sheet->getCell('B1')->setValue("=MULTINOMIAL($excelArg)"); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerMULTINOMIAL() + public function providerMULTINOMIAL(): array { return require 'tests/data/Calculation/MathTrig/MULTINOMIAL.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/OddTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/OddTest.php index 6c5758c6..37042743 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/OddTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/OddTest.php @@ -2,30 +2,24 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class OddTest extends TestCase +class OddTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerODD * * @param mixed $expectedResult - * @param $value + * @param mixed $value */ public function testODD($expectedResult, $value): void { - $result = MathTrig::ODD($value); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=ODD($value)"); + $sheet->getCell('A2')->setValue(3.7); + self::assertEquals($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } - public function providerODD() + public function providerODD(): array { return require 'tests/data/Calculation/MathTrig/ODD.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/PowerTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/PowerTest.php index 6749b14a..4d6ba7e2 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/PowerTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/PowerTest.php @@ -2,29 +2,37 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class PowerTest extends TestCase +class PowerTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerPOWER * * @param mixed $expectedResult + * @param mixed $base + * @param mixed $exponent */ - public function testPOWER($expectedResult, ...$args): void + public function testPOWER($expectedResult, $base = 'omitted', $exponent = 'omitted'): void { - $result = MathTrig::POWER(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($base !== null) { + $sheet->getCell('A1')->setValue($base); + } + if ($exponent !== null) { + $sheet->getCell('A2')->setValue($exponent); + } + if ($base === 'omitted') { + $sheet->getCell('B1')->setValue('=POWER()'); + } elseif ($exponent === 'omitted') { + $sheet->getCell('B1')->setValue('=POWER(A1)'); + } else { + $sheet->getCell('B1')->setValue('=POWER(A1,A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerPOWER() + public function providerPOWER(): array { return require 'tests/data/Calculation/MathTrig/POWER.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ProductTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ProductTest.php index 251b783b..4e63ae91 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ProductTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/ProductTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class ProductTest extends TestCase +class ProductTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerPRODUCT * @@ -20,11 +11,18 @@ class ProductTest extends TestCase */ public function testPRODUCT($expectedResult, ...$args): void { - $result = MathTrig::PRODUCT(...$args); + $sheet = $this->sheet; + $row = 0; + foreach ($args as $arg) { + ++$row; + $sheet->getCell("A$row")->setValue($arg); + } + $sheet->getCell('B1')->setValue("=PRODUCT(A1:A$row)"); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerPRODUCT() + public function providerPRODUCT(): array { return require 'tests/data/Calculation/MathTrig/PRODUCT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/QuotientTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/QuotientTest.php index 4232729a..c8cd13b7 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/QuotientTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/QuotientTest.php @@ -2,29 +2,37 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class QuotientTest extends TestCase +class QuotientTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerQUOTIENT * * @param mixed $expectedResult + * @param mixed $arg1 + * @param mixed $arg2 */ - public function testQUOTIENT($expectedResult, ...$args): void + public function testQUOTIENT($expectedResult, $arg1 = 'omitted', $arg2 = 'omitted'): void { - $result = MathTrig::QUOTIENT(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($arg1 !== null) { + $sheet->getCell('A1')->setValue($arg1); + } + if ($arg2 !== null) { + $sheet->getCell('A2')->setValue($arg2); + } + if ($arg1 === 'omitted') { + $sheet->getCell('B1')->setValue('=QUOTIENT()'); + } elseif ($arg2 === 'omitted') { + $sheet->getCell('B1')->setValue('=QUOTIENT(A1)'); + } else { + $sheet->getCell('B1')->setValue('=QUOTIENT(A1, A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertSame($expectedResult, $result); } - public function providerQUOTIENT() + public function providerQUOTIENT(): array { return require 'tests/data/Calculation/MathTrig/QUOTIENT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RadiansTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RadiansTest.php new file mode 100644 index 00000000..03cceaac --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RadiansTest.php @@ -0,0 +1,31 @@ +sheet; + $this->mightHaveException($expectedResult); + $this->setCell('A1', $number); + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=RADIANS()'); + } else { + $sheet->getCell('B1')->setValue('=RADIANS(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-9); + } + + public function providerRADIANS(): array + { + return require 'tests/data/Calculation/MathTrig/RADIANS.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandBetweenTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandBetweenTest.php new file mode 100644 index 00000000..2e1ba676 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandBetweenTest.php @@ -0,0 +1,46 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $lower = (int) $min; + $upper = (int) $max; + if ($min !== null) { + $sheet->getCell('A1')->setValue($min); + } + if ($max !== null) { + $sheet->getCell('A2')->setValue($max); + } + if ($min === 'omitted') { + $sheet->getCell('B1')->setValue('=RANDBETWEEN()'); + } elseif ($max === 'omitted') { + $sheet->getCell('B1')->setValue('=RANDBETWEEN(A1)'); + } else { + $sheet->getCell('B1')->setValue('=RANDBETWEEN(A1,A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + if (is_numeric($expectedResult)) { + self::assertGreaterThanOrEqual($lower, $result); + self::assertLessThanOrEqual($upper, $result); + } else { + self::assertSame($expectedResult, $result); + } + } + + public function providerRANDBETWEEN(): array + { + return require 'tests/data/Calculation/MathTrig/RANDBETWEEN.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandTest.php new file mode 100644 index 00000000..e5e2e107 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandTest.php @@ -0,0 +1,24 @@ +sheet; + $sheet->getCell('B1')->setValue('=RAND()'); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertGreaterThanOrEqual(0, $result); + self::assertLessThanOrEqual(1, $result); + } + + public function testRandException(): void + { + $this->mightHaveException('exception'); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('=RAND(A1)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals(0, $result); + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RomanTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RomanTest.php index c74daa32..417656c0 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RomanTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RomanTest.php @@ -2,29 +2,25 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class RomanTest extends TestCase +class RomanTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerROMAN * * @param mixed $expectedResult + * @param mixed $formula */ - public function testROMAN($expectedResult, ...$args): void + public function testROMAN($expectedResult, $formula): void { - $result = MathTrig::ROMAN(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A3', 49); + $sheet->getCell('A1')->setValue("=ROMAN($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } - public function providerROMAN() + public function providerROMAN(): array { return require 'tests/data/Calculation/MathTrig/ROMAN.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundDownTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundDownTest.php index 2fc211f3..97058102 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundDownTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundDownTest.php @@ -2,29 +2,28 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class RoundDownTest extends TestCase +class RoundDownTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** - * @dataProvider providerROUNDDOWN + * @dataProvider providerRoundDown * * @param mixed $expectedResult + * @param mixed $formula */ - public function testROUNDDOWN($expectedResult, ...$args): void + public function testRoundDown($expectedResult, $formula): void { - $result = MathTrig::ROUNDDOWN(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=ROUNDDOWN($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerROUNDDOWN() + public function providerRoundDown(): array { return require 'tests/data/Calculation/MathTrig/ROUNDDOWN.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundTest.php new file mode 100644 index 00000000..960f4740 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundTest.php @@ -0,0 +1,30 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=ROUND($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + } + + public function providerRound(): array + { + return require 'tests/data/Calculation/MathTrig/ROUND.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundUpTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundUpTest.php index 825fe419..f9ba44e0 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundUpTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundUpTest.php @@ -2,29 +2,28 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class RoundUpTest extends TestCase +class RoundUpTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** - * @dataProvider providerROUNDUP + * @dataProvider providerRoundUp * * @param mixed $expectedResult + * @param mixed $formula */ - public function testROUNDUP($expectedResult, ...$args): void + public function testRoundUp($expectedResult, $formula): void { - $result = MathTrig::ROUNDUP(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=ROUNDUP($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerROUNDUP() + public function providerRoundUp(): array { return require 'tests/data/Calculation/MathTrig/ROUNDUP.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SecTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SecTest.php index ad4b196c..ad52cee2 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SecTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SecTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class SecTest extends TestCase +class SecTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerSEC * @@ -21,11 +12,18 @@ class SecTest extends TestCase */ public function testSEC($expectedResult, $angle): void { - $result = MathTrig::SEC($angle); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=SEC($angle)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-9); } - public function providerSEC() + public function providerSEC(): array { return require 'tests/data/Calculation/MathTrig/SEC.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SechTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SechTest.php index b9488bda..7dabf291 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SechTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SechTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class SechTest extends TestCase +class SechTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerSECH * @@ -21,11 +12,18 @@ class SechTest extends TestCase */ public function testSECH($expectedResult, $angle): void { - $result = MathTrig::SECH($angle); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=SECH($angle)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-9); } - public function providerSECH() + public function providerSECH(): array { return require 'tests/data/Calculation/MathTrig/SECH.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SeriesSumTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SeriesSumTest.php index 689336a3..9070f60b 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SeriesSumTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SeriesSumTest.php @@ -3,28 +3,43 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; -class SeriesSumTest extends TestCase +class SeriesSumTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerSERIESSUM * * @param mixed $expectedResult + * @param mixed $arg1 + * @param mixed $arg2 + * @param mixed $arg3 */ - public function testSERIESSUM($expectedResult, ...$args): void + public function testSERIESSUM($expectedResult, $arg1, $arg2, $arg3, ...$args): void { - $result = MathTrig::SERIESSUM(...$args); + $sheet = $this->sheet; + if ($arg1 !== null) { + $sheet->getCell('C1')->setValue($arg1); + } + if ($arg2 !== null) { + $sheet->getCell('C2')->setValue($arg2); + } + if ($arg3 !== null) { + $sheet->getCell('C3')->setValue($arg3); + } + $row = 0; + $aArgs = Functions::flattenArray($args); + foreach ($aArgs as $arg) { + ++$row; + if ($arg !== null) { + $sheet->getCell("A$row")->setValue($arg); + } + } + $sheet->getCell('B1')->setValue("=SERIESSUM(C1, C2, C3, A1:A$row)"); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerSERIESSUM() + public function providerSERIESSUM(): array { return require 'tests/data/Calculation/MathTrig/SERIESSUM.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SignTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SignTest.php index 68f5acb9..fd1a3e6c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SignTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SignTest.php @@ -2,30 +2,27 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class SignTest extends TestCase +class SignTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerSIGN * * @param mixed $expectedResult - * @param $value + * @param mixed $value */ public function testSIGN($expectedResult, $value): void { - $result = MathTrig::SIGN($value); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 0); + $sheet->setCellValue('A4', -3.8); + $sheet->getCell('A1')->setValue("=SIGN($value)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); } - public function providerSIGN() + public function providerSIGN(): array { return require 'tests/data/Calculation/MathTrig/SIGN.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SinTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SinTest.php new file mode 100644 index 00000000..c2d0afe5 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SinTest.php @@ -0,0 +1,26 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 2); + $sheet->getCell('A1')->setValue("=SIN($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerSin(): array + { + return require 'tests/data/Calculation/MathTrig/SIN.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SinhTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SinhTest.php new file mode 100644 index 00000000..bb680f08 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SinhTest.php @@ -0,0 +1,26 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 2); + $sheet->getCell('A1')->setValue("=SINH($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerCosh(): array + { + return require 'tests/data/Calculation/MathTrig/SINH.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SqrtPiTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SqrtPiTest.php index bb4bba4b..1a879056 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SqrtPiTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SqrtPiTest.php @@ -2,30 +2,31 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class SqrtPiTest extends TestCase +class SqrtPiTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerSQRTPI * * @param mixed $expectedResult - * @param $value + * @param mixed $number */ - public function testSQRTPI($expectedResult, $value): void + public function testSQRTPI($expectedResult, $number): void { - $result = MathTrig::SQRTPI($value); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($number !== null) { + $sheet->getCell('A1')->setValue($number); + } + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=SQRTPI()'); + } else { + $sheet->getCell('B1')->setValue('=SQRTPI(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerSQRTPI() + public function providerSQRTPI(): array { return require 'tests/data/Calculation/MathTrig/SQRTPI.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SqrtTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SqrtTest.php new file mode 100644 index 00000000..8ce557bd --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SqrtTest.php @@ -0,0 +1,31 @@ +sheet; + $this->mightHaveException($expectedResult); + $this->setCell('A1', $number); + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=SQRT()'); + } else { + $sheet->getCell('B1')->setValue('=SQRT(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerSqrt(): array + { + return require 'tests/data/Calculation/MathTrig/SQRT.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SubTotalTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SubTotalTest.php index 14865673..fde1d16e 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SubTotalTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SubTotalTest.php @@ -2,197 +2,127 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PhpOffice\PhpSpreadsheet\Cell\Cell; -use PhpOffice\PhpSpreadsheet\Worksheet\ColumnDimension; -use PhpOffice\PhpSpreadsheet\Worksheet\RowDimension; -use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; -use PHPUnit\Framework\TestCase; - -class SubTotalTest extends TestCase +class SubTotalTest extends AllSetupTeardown { - protected function setUp(): void + /** + * @dataProvider providerSUBTOTAL + * + * @param mixed $expectedResult + * @param mixed $type expect an integer + */ + public function testSubtotal($expectedResult, $type): void { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->fromArray([[0], [1], [1], [2], [3], [5], [8], [13], [21], [34], [55], [89]], null, 'A1', true); + $maxCol = $sheet->getHighestColumn(); + $maxRow = $sheet->getHighestRow(); + $sheet->getCell('D2')->setValue("=SUBTOTAL($type, A1:$maxCol$maxRow)"); + $result = $sheet->getCell('D2')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + } + + public function providerSUBTOTAL(): array + { + return require 'tests/data/Calculation/MathTrig/SUBTOTAL.php'; } /** * @dataProvider providerSUBTOTAL * * @param mixed $expectedResult + * @param mixed $type expect an integer */ - public function testSUBTOTAL($expectedResult, ...$args): void + public function testSubtotalColumnHidden($expectedResult, $type): void { - $cell = $this->getMockBuilder(Cell::class) - ->setMethods(['getValue', 'isFormula']) - ->disableOriginalConstructor() - ->getMock(); - $cell->method('getValue') - ->willReturn(null); - $cell->method('getValue') - ->willReturn(false); - $worksheet = $this->getMockBuilder(Worksheet::class) - ->setMethods(['cellExists', 'getCell']) - ->disableOriginalConstructor() - ->getMock(); - $worksheet->method('cellExists') - ->willReturn(true); - $worksheet->method('getCell') - ->willReturn($cell); - $cellReference = $this->getMockBuilder(Cell::class) - ->setMethods(['getWorksheet']) - ->disableOriginalConstructor() - ->getMock(); - $cellReference->method('getWorksheet') - ->willReturn($worksheet); - - array_push($args, $cellReference); - $result = MathTrig::SUBTOTAL(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); - } - - public function providerSUBTOTAL() - { - return require 'tests/data/Calculation/MathTrig/SUBTOTAL.php'; - } - - protected function rowVisibility() - { - $data = [1 => false, 2 => true, 3 => false, 4 => true, 5 => false, 6 => false, 7 => false, 8 => true, 9 => false, 10 => true, 11 => true]; - foreach ($data as $k => $v) { - yield $k => $v; + // Hidden columns don't affect calculation, only hidden rows + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->fromArray([0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89], null, 'A1', true); + $maxCol = $sheet->getHighestColumn(); + $maxRow = $sheet->getHighestRow(); + $hiddenColumns = [ + 'A' => false, + 'B' => true, + 'C' => false, + 'D' => true, + 'E' => false, + 'F' => false, + 'G' => false, + 'H' => true, + 'I' => false, + 'J' => true, + 'K' => true, + 'L' => false, + ]; + foreach ($hiddenColumns as $col => $hidden) { + $sheet->getColumnDimension($col)->setVisible($hidden); } + $sheet->getCell('D2')->setValue("=SUBTOTAL($type, A1:$maxCol$maxRow)"); + $result = $sheet->getCell('D2')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } /** - * @dataProvider providerHiddenSUBTOTAL + * @dataProvider providerSUBTOTALHIDDEN * * @param mixed $expectedResult + * @param mixed $type expect an integer */ - public function testHiddenSUBTOTAL($expectedResult, ...$args): void + public function testSubtotalRowHidden($expectedResult, $type): void { - $visibilityGenerator = $this->rowVisibility(); - - $rowDimension = $this->getMockBuilder(RowDimension::class) - ->setMethods(['getVisible']) - ->disableOriginalConstructor() - ->getMock(); - $rowDimension->method('getVisible') - ->willReturnCallback(function () use ($visibilityGenerator) { - $result = $visibilityGenerator->current(); - $visibilityGenerator->next(); - - return $result; - }); - $columnDimension = $this->getMockBuilder(ColumnDimension::class) - ->setMethods(['getVisible']) - ->disableOriginalConstructor() - ->getMock(); - $columnDimension->method('getVisible') - ->willReturn(true); - $cell = $this->getMockBuilder(Cell::class) - ->setMethods(['getValue', 'isFormula']) - ->disableOriginalConstructor() - ->getMock(); - $cell->method('getValue') - ->willReturn(''); - $cell->method('getValue') - ->willReturn(false); - $worksheet = $this->getMockBuilder(Worksheet::class) - ->setMethods(['cellExists', 'getCell', 'getRowDimension', 'getColumnDimension']) - ->disableOriginalConstructor() - ->getMock(); - $worksheet->method('cellExists') - ->willReturn(true); - $worksheet->method('getCell') - ->willReturn($cell); - $worksheet->method('getRowDimension') - ->willReturn($rowDimension); - $worksheet->method('getColumnDimension') - ->willReturn($columnDimension); - $cellReference = $this->getMockBuilder(Cell::class) - ->setMethods(['getWorksheet']) - ->disableOriginalConstructor() - ->getMock(); - $cellReference->method('getWorksheet') - ->willReturn($worksheet); - - array_push($args, $cellReference); - $result = MathTrig::SUBTOTAL(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->fromArray([[0], [1], [1], [2], [3], [5], [8], [13], [21], [34], [55], [89]], null, 'A1', true); + $maxCol = $sheet->getHighestColumn(); + $maxRow = $sheet->getHighestRow(); + $visibleRows = [ + '1' => false, + '2' => true, + '3' => false, + '4' => true, + '5' => false, + '6' => false, + '7' => false, + '8' => true, + '9' => false, + '10' => true, + '11' => true, + '12' => false, + ]; + foreach ($visibleRows as $row => $visible) { + $sheet->getRowDimension($row)->setVisible($visible); + } + $sheet->getCell('D2')->setValue("=SUBTOTAL($type, A1:$maxCol$maxRow)"); + $result = $sheet->getCell('D2')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerHiddenSUBTOTAL() + public function providerSUBTOTALHIDDEN(): array { return require 'tests/data/Calculation/MathTrig/SUBTOTALHIDDEN.php'; } - protected function cellValues(array $cellValues) + public function testSubtotalNested(): void { - foreach ($cellValues as $k => $v) { - yield $k => $v; - } - } - - protected function cellIsFormula(array $cellValues) - { - foreach ($cellValues as $cellValue) { - yield is_string($cellValue) && $cellValue[0] === '='; - } - } - - /** - * @dataProvider providerNestedSUBTOTAL - * - * @param mixed $expectedResult - */ - public function testNestedSUBTOTAL($expectedResult, ...$args): void - { - $cellValueGenerator = $this->cellValues(Functions::flattenArray(array_slice($args, 1))); - $cellIsFormulaGenerator = $this->cellIsFormula(Functions::flattenArray(array_slice($args, 1))); - - $cell = $this->getMockBuilder(Cell::class) - ->setMethods(['getValue', 'isFormula']) - ->disableOriginalConstructor() - ->getMock(); - $cell->method('getValue') - ->willReturnCallback(function () use ($cellValueGenerator) { - $result = $cellValueGenerator->current(); - $cellValueGenerator->next(); - - return $result; - }); - $cell->method('isFormula') - ->willReturnCallback(function () use ($cellIsFormulaGenerator) { - $result = $cellIsFormulaGenerator->current(); - $cellIsFormulaGenerator->next(); - - return $result; - }); - $worksheet = $this->getMockBuilder(Worksheet::class) - ->setMethods(['cellExists', 'getCell']) - ->disableOriginalConstructor() - ->getMock(); - $worksheet->method('cellExists') - ->willReturn(true); - $worksheet->method('getCell') - ->willReturn($cell); - $cellReference = $this->getMockBuilder(Cell::class) - ->setMethods(['getWorksheet']) - ->disableOriginalConstructor() - ->getMock(); - $cellReference->method('getWorksheet') - ->willReturn($worksheet); - - array_push($args, $cellReference); - - $result = MathTrig::SUBTOTAL(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-12); - } - - public function providerNestedSUBTOTAL() - { - return require 'tests/data/Calculation/MathTrig/SUBTOTALNESTED.php'; + $sheet = $this->sheet; + $sheet->fromArray( + [ + [123], + [234], + ['=SUBTOTAL(1,A1:A2)'], + ['=ROMAN(SUBTOTAL(1, A1:A2))'], + ['This is text containing "=" and "SUBTOTAL("'], + ['=AGGREGATE(1, 0, A1:A2)'], + ['=SUM(2, 3)'], + ], + null, + 'A1', + true + ); + $maxCol = $sheet->getHighestColumn(); + $maxRow = $sheet->getHighestRow(); + $sheet->getCell('H1')->setValue("=SUBTOTAL(9, A1:$maxCol$maxRow)"); + self::assertEquals(362, $sheet->getCell('H1')->getCalculatedValue()); } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumIfTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumIfTest.php index f7ff928f..ad560423 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumIfTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumIfTest.php @@ -2,29 +2,40 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class SumIfTest extends TestCase +class SumIfTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerSUMIF * * @param mixed $expectedResult + * @param mixed $condition */ - public function testSUMIF($expectedResult, ...$args): void + public function testSUMIF2($expectedResult, array $array1, $condition, ?array $array2 = null): void { - $result = MathTrig::SUMIF(...$args); + $this->mightHaveException($expectedResult); + if ($expectedResult === 'incomplete') { + self::markTestIncomplete('Raises formula error - researching solution'); + } + $sheet = $this->sheet; + $sheet->fromArray($array1, null, 'A1', true); + $maxARow = count($array1); + $firstArg = "A1:A$maxARow"; + //$secondArg = is_string($condition) ? "\"$condition\"" : $condition; + $sheet->getCell('B1')->setValue($condition); + $secondArg = 'B1'; + if (empty($array2)) { + $sheet->getCell('D1')->setValue("=SUMIF($firstArg, $secondArg)"); + } else { + $sheet->fromArray($array2, null, 'C1', true); + $maxCRow = count($array2); + $thirdArg = "C1:C$maxCRow"; + $sheet->getCell('D1')->setValue("=SUMIF($firstArg, $secondArg, $thirdArg)"); + } + $result = $sheet->getCell('D1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerSUMIF() + public function providerSUMIF(): array { return require 'tests/data/Calculation/MathTrig/SUMIF.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumIfsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumIfsTest.php index b7be17c9..1ae6e231 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumIfsTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumIfsTest.php @@ -2,17 +2,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical; -class SumIfsTest extends TestCase +class SumIfsTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerSUMIFS * @@ -20,11 +13,11 @@ class SumIfsTest extends TestCase */ public function testSUMIFS($expectedResult, ...$args): void { - $result = MathTrig::SUMIFS(...$args); + $result = Statistical\Conditional::SUMIFS(...$args); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerSUMIFS() + public function providerSUMIFS(): array { return require 'tests/data/Calculation/MathTrig/SUMIFS.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumProductTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumProductTest.php index b34036e5..b2cdb379 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumProductTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumProductTest.php @@ -3,16 +3,9 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; -class SumProductTest extends TestCase +class SumProductTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerSUMPRODUCT * @@ -20,11 +13,28 @@ class SumProductTest extends TestCase */ public function testSUMPRODUCT($expectedResult, ...$args): void { - $result = MathTrig::SUMPRODUCT(...$args); + $sheet = $this->sheet; + $row = 0; + $arrayArg = ''; + foreach ($args as $arr) { + $arr2 = Functions::flattenArray($arr); + $startRow = 0; + foreach ($arr2 as $arr3) { + ++$row; + if (!$startRow) { + $startRow = $row; + } + $sheet->getCell("A$row")->setValue($arr3); + } + $arrayArg .= "A$startRow:A$row,"; + } + $arrayArg = substr($arrayArg, 0, -1); // strip trailing comma + $sheet->getCell('B1')->setValue("=SUMPRODUCT($arrayArg)"); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerSUMPRODUCT() + public function providerSUMPRODUCT(): array { return require 'tests/data/Calculation/MathTrig/SUMPRODUCT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumSqTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumSqTest.php index f1165e7b..162c578a 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumSqTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumSqTest.php @@ -2,17 +2,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class SumSqTest extends TestCase +class SumSqTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerSUMSQ * @@ -20,11 +11,23 @@ class SumSqTest extends TestCase */ public function testSUMSQ($expectedResult, ...$args): void { - $result = MathTrig::SUMSQ(...$args); + $this->mightHaveException($expectedResult); + $maxRow = 0; + $funcArg = ''; + $sheet = $this->sheet; + foreach ($args as $arg) { + ++$maxRow; + $funcArg = "A1:A$maxRow"; + if ($arg !== null) { + $sheet->getCell("A$maxRow")->setValue($arg); + } + } + $sheet->getCell('B1')->setValue("=SUMSQ($funcArg)"); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerSUMSQ() + public function providerSUMSQ(): array { return require 'tests/data/Calculation/MathTrig/SUMSQ.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumTest.php new file mode 100644 index 00000000..a9ea7f29 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumTest.php @@ -0,0 +1,29 @@ +sheet; + $row = 0; + foreach ($args as $arg) { + ++$row; + $sheet->getCell("A$row")->setValue($arg); + } + $sheet->getCell('B1')->setValue("=SUM(A1:A$row)"); + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-12); + } + + public function providerSUM(): array + { + return require 'tests/data/Calculation/MathTrig/SUM.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumX2MY2Test.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumX2MY2Test.php index 3bf2785b..427e5c85 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumX2MY2Test.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumX2MY2Test.php @@ -3,28 +3,38 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; -class SumX2MY2Test extends TestCase +class SumX2MY2Test extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerSUMX2MY2 * * @param mixed $expectedResult */ - public function testSUMX2MY2($expectedResult, ...$args): void + public function testSUMX2MY2($expectedResult, array $matrixData1, array $matrixData2): void { - $result = MathTrig::SUMX2MY2(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $maxRow = 0; + $funcArg1 = ''; + foreach (Functions::flattenArray($matrixData1) as $arg) { + ++$maxRow; + $funcArg1 = "A1:A$maxRow"; + $this->setCell("A$maxRow", $arg); + } + $maxRow = 0; + $funcArg2 = ''; + foreach (Functions::flattenArray($matrixData2) as $arg) { + ++$maxRow; + $funcArg2 = "C1:C$maxRow"; + $this->setCell("C$maxRow", $arg); + } + $sheet->getCell('B1')->setValue("=SUMX2MY2($funcArg1, $funcArg2)"); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerSUMX2MY2() + public function providerSUMX2MY2(): array { return require 'tests/data/Calculation/MathTrig/SUMX2MY2.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumX2PY2Test.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumX2PY2Test.php index a370d79b..100b8566 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumX2PY2Test.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumX2PY2Test.php @@ -3,28 +3,38 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; -class SumX2PY2Test extends TestCase +class SumX2PY2Test extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerSUMX2PY2 * * @param mixed $expectedResult */ - public function testSUMX2PY2($expectedResult, ...$args): void + public function testSUMX2PY2($expectedResult, array $matrixData1, array $matrixData2): void { - $result = MathTrig::SUMX2PY2(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $maxRow = 0; + $funcArg1 = ''; + foreach (Functions::flattenArray($matrixData1) as $arg) { + ++$maxRow; + $funcArg1 = "A1:A$maxRow"; + $this->setCell("A$maxRow", $arg); + } + $maxRow = 0; + $funcArg2 = ''; + foreach (Functions::flattenArray($matrixData2) as $arg) { + ++$maxRow; + $funcArg2 = "C1:C$maxRow"; + $this->setCell("C$maxRow", $arg); + } + $sheet->getCell('B1')->setValue("=SUMX2PY2($funcArg1, $funcArg2)"); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerSUMX2PY2() + public function providerSUMX2PY2(): array { return require 'tests/data/Calculation/MathTrig/SUMX2PY2.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumXMY2Test.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumXMY2Test.php index 1f64523b..43acd4a0 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumXMY2Test.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumXMY2Test.php @@ -3,28 +3,38 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; -class SumXMY2Test extends TestCase +class SumXMY2Test extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerSUMXMY2 * * @param mixed $expectedResult */ - public function testSUMXMY2($expectedResult, ...$args): void + public function testSUMXMY2($expectedResult, array $matrixData1, array $matrixData2): void { - $result = MathTrig::SUMXMY2(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $maxRow = 0; + $funcArg1 = ''; + foreach (Functions::flattenArray($matrixData1) as $arg) { + ++$maxRow; + $funcArg1 = "A1:A$maxRow"; + $this->setCell("A$maxRow", $arg); + } + $maxRow = 0; + $funcArg2 = ''; + foreach (Functions::flattenArray($matrixData2) as $arg) { + ++$maxRow; + $funcArg2 = "C1:C$maxRow"; + $this->setCell("C$maxRow", $arg); + } + $sheet->getCell('B1')->setValue("=SUMXMY2($funcArg1, $funcArg2)"); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerSUMXMY2() + public function providerSUMXMY2(): array { return require 'tests/data/Calculation/MathTrig/SUMXMY2.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/TanTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/TanTest.php new file mode 100644 index 00000000..80dd7eff --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/TanTest.php @@ -0,0 +1,26 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1); + $sheet->getCell('A1')->setValue("=TAN($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerTan(): array + { + return require 'tests/data/Calculation/MathTrig/TAN.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/TanhTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/TanhTest.php new file mode 100644 index 00000000..9a55e9b4 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/TanhTest.php @@ -0,0 +1,26 @@ +mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1); + $sheet->getCell('A1')->setValue("=TANH($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEqualsWithDelta($expectedResult, $result, 1E-6); + } + + public function providerTanh(): array + { + return require 'tests/data/Calculation/MathTrig/TANH.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/TruncTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/TruncTest.php index 5fc248cc..7d7a4b55 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/TruncTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/TruncTest.php @@ -2,29 +2,28 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PHPUnit\Framework\TestCase; - -class TruncTest extends TestCase +class TruncTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - } - /** * @dataProvider providerTRUNC * * @param mixed $expectedResult + * @param string $formula */ - public function testTRUNC($expectedResult, ...$args): void + public function testTRUNC($expectedResult, $formula): void { - $result = MathTrig::TRUNC(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->setCellValue('A2', 1.3); + $sheet->setCellValue('A3', 2.7); + $sheet->setCellValue('A4', -3.8); + $sheet->setCellValue('A5', -5.2); + $sheet->getCell('A1')->setValue("=TRUNC($formula)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerTRUNC() + public function providerTRUNC(): array { return require 'tests/data/Calculation/MathTrig/TRUNC.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AveDevTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AveDevTest.php index 571c06c3..ea3df6bb 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AveDevTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AveDevTest.php @@ -24,7 +24,7 @@ class AveDevTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerAVEDEV() + public function providerAVEDEV(): array { return require 'tests/data/Calculation/Statistical/AVEDEV.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AverageATest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AverageATest.php index 1af96dfc..fec65c28 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AverageATest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AverageATest.php @@ -24,7 +24,7 @@ class AverageATest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerAVERAGEA() + public function providerAVERAGEA(): array { return require 'tests/data/Calculation/Statistical/AVERAGEA.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AverageIfTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AverageIfTest.php index 69dcfb87..c9648d43 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AverageIfTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AverageIfTest.php @@ -24,7 +24,7 @@ class AverageIfTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerAVERAGEIF() + public function providerAVERAGEIF(): array { return require 'tests/data/Calculation/Statistical/AVERAGEIF.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AverageIfsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AverageIfsTest.php new file mode 100644 index 00000000..de593764 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/AverageIfsTest.php @@ -0,0 +1,31 @@ +expectException(CalcExp::class); + $formula = '=REPT()'; + } elseif ($rpt === null) { + $this->expectException(CalcExp::class); + $formula = "=REPT($val)"; + } else { + if (is_bool($val)) { + $val = ($val) ? Calculation::getTRUE() : Calculation::getFALSE(); + } + $formula = "=REPT($val, $rpt)"; + } + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getCell('A1')->setValue($formula); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function providerREPT(): array + { + return require 'tests/data/Calculation/TextData/REPT.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/RightTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/RightTest.php index 50fc86dc..a2ab3a4c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/RightTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/RightTest.php @@ -3,10 +3,16 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; use PhpOffice\PhpSpreadsheet\Calculation\TextData; +use PhpOffice\PhpSpreadsheet\Settings; use PHPUnit\Framework\TestCase; class RightTest extends TestCase { + protected function tearDown(): void + { + Settings::setLocale('en_US'); + } + /** * @dataProvider providerRIGHT * @@ -18,8 +24,44 @@ class RightTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerRIGHT() + public function providerRIGHT(): array { return require 'tests/data/Calculation/TextData/RIGHT.php'; } + + /** + * @dataProvider providerLocaleRIGHT + * + * @param string $expectedResult + * @param mixed $value + * @param mixed $locale + * @param mixed $characters + */ + public function testLowerWithLocaleBoolean($expectedResult, $locale, $value, $characters): void + { + $newLocale = Settings::setLocale($locale); + if ($newLocale === false) { + Settings::setLocale('en_US'); + self::markTestSkipped('Unable to set locale for locale-specific test'); + } + + $result = TextData::RIGHT($value, $characters); + self::assertEquals($expectedResult, $result); + + Settings::setLocale('en_US'); + } + + public function providerLocaleRIGHT(): array + { + return [ + ['RAI', 'fr_FR', true, 3], + ['AAR', 'nl_NL', true, 3], + ['OSI', 'fi', true, 3], + ['ИНА', 'bg', true, 3], + ['UX', 'fr_FR', false, 2], + ['WAAR', 'nl_NL', false, 4], + ['ÄTOSI', 'fi', false, 5], + ['ЖЬ', 'bg', false, 2], + ]; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SearchTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SearchTest.php index 7bb92e83..14f5735b 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SearchTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SearchTest.php @@ -18,7 +18,7 @@ class SearchTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerSEARCH() + public function providerSEARCH(): array { return require 'tests/data/Calculation/TextData/SEARCH.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SubstituteTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SubstituteTest.php index 2a9d1012..792e1a15 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SubstituteTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SubstituteTest.php @@ -18,7 +18,7 @@ class SubstituteTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerSUBSTITUTE() + public function providerSUBSTITUTE(): array { return require 'tests/data/Calculation/TextData/SUBSTITUTE.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TTest.php index c7606c05..9ca47f91 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TTest.php @@ -11,7 +11,7 @@ class TTest extends TestCase * @dataProvider providerT * * @param mixed $expectedResult - * @param $value + * @param mixed $value */ public function testT($expectedResult, $value): void { @@ -19,7 +19,7 @@ class TTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerT() + public function providerT(): array { return require 'tests/data/Calculation/TextData/T.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextJoinTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextJoinTest.php index e8fb404d..78e72f96 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextJoinTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextJoinTest.php @@ -18,7 +18,7 @@ class TextJoinTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerTEXTJOIN() + public function providerTEXTJOIN(): array { return require 'tests/data/Calculation/TextData/TEXTJOIN.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextTest.php index 8d7b238b..91418065 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextTest.php @@ -18,7 +18,7 @@ class TextTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerTEXT() + public function providerTEXT(): array { return require 'tests/data/Calculation/TextData/TEXT.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TrimTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TrimTest.php index 91890ded..302dce4f 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TrimTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TrimTest.php @@ -11,7 +11,7 @@ class TrimTest extends TestCase * @dataProvider providerTRIM * * @param mixed $expectedResult - * @param $character + * @param mixed $character */ public function testTRIM($expectedResult, $character): void { @@ -19,7 +19,7 @@ class TrimTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerTRIM() + public function providerTRIM(): array { return require 'tests/data/Calculation/TextData/TRIM.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UpperTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UpperTest.php index 13fb0b86..45f7aea8 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UpperTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UpperTest.php @@ -3,15 +3,21 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; use PhpOffice\PhpSpreadsheet\Calculation\TextData; +use PhpOffice\PhpSpreadsheet\Settings; use PHPUnit\Framework\TestCase; class UpperTest extends TestCase { + protected function tearDown(): void + { + Settings::setLocale('en_US'); + } + /** * @dataProvider providerUPPER * * @param mixed $expectedResult - * @param $value + * @param mixed $value */ public function testUPPER($expectedResult, $value): void { @@ -19,8 +25,43 @@ class UpperTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerUPPER() + public function providerUPPER(): array { return require 'tests/data/Calculation/TextData/UPPER.php'; } + + /** + * @dataProvider providerLocaleLOWER + * + * @param string $expectedResult + * @param mixed $value + * @param mixed $locale + */ + public function testLowerWithLocaleBoolean($expectedResult, $locale, $value): void + { + $newLocale = Settings::setLocale($locale); + if ($newLocale === false) { + Settings::setLocale('en_US'); + self::markTestSkipped('Unable to set locale for locale-specific test'); + } + + $result = TextData::UPPERCASE($value); + self::assertEquals($expectedResult, $result); + + Settings::setLocale('en_US'); + } + + public function providerLocaleLOWER(): array + { + return [ + ['VRAI', 'fr_FR', true], + ['WAAR', 'nl_NL', true], + ['TOSI', 'fi', true], + ['ИСТИНА', 'bg', true], + ['FAUX', 'fr_FR', false], + ['ONWAAR', 'nl_NL', false], + ['EPÄTOSI', 'fi', false], + ['ЛОЖЬ', 'bg', false], + ]; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ValueTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ValueTest.php index 355193de..b3ba1246 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ValueTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ValueTest.php @@ -8,10 +8,19 @@ use PHPUnit\Framework\TestCase; class ValueTest extends TestCase { + /** + * @var string + */ private $currencyCode; + /** + * @var string + */ private $decimalSeparator; + /** + * @var string + */ private $thousandsSeparator; protected function setUp(): void @@ -32,7 +41,7 @@ class ValueTest extends TestCase * @dataProvider providerVALUE * * @param mixed $expectedResult - * @param $value + * @param mixed $value */ public function testVALUE($expectedResult, $value): void { @@ -44,7 +53,7 @@ class ValueTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerVALUE() + public function providerVALUE(): array { return require 'tests/data/Calculation/TextData/VALUE.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/FunctionsTest.php b/tests/PhpSpreadsheetTests/Calculation/FunctionsTest.php index dfa01822..4e8335f1 100644 --- a/tests/PhpSpreadsheetTests/Calculation/FunctionsTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/FunctionsTest.php @@ -10,8 +10,14 @@ use PHPUnit\Framework\TestCase; class FunctionsTest extends TestCase { + /** + * @var string + */ private $compatibilityMode; + /** + * @var string + */ private $returnDate; protected function setUp(): void @@ -123,7 +129,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerIsBlank() + public function providerIsBlank(): array { return require 'tests/data/Calculation/Functions/IS_BLANK.php'; } @@ -139,7 +145,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerIsErr() + public function providerIsErr(): array { return require 'tests/data/Calculation/Functions/IS_ERR.php'; } @@ -155,7 +161,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerIsError() + public function providerIsError(): array { return require 'tests/data/Calculation/Functions/IS_ERROR.php'; } @@ -171,7 +177,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerErrorType() + public function providerErrorType(): array { return require 'tests/data/Calculation/Functions/ERROR_TYPE.php'; } @@ -187,7 +193,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerIsLogical() + public function providerIsLogical(): array { return require 'tests/data/Calculation/Functions/IS_LOGICAL.php'; } @@ -203,7 +209,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerIsNa() + public function providerIsNa(): array { return require 'tests/data/Calculation/Functions/IS_NA.php'; } @@ -219,7 +225,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerIsNumber() + public function providerIsNumber(): array { return require 'tests/data/Calculation/Functions/IS_NUMBER.php'; } @@ -235,7 +241,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerIsText() + public function providerIsText(): array { return require 'tests/data/Calculation/Functions/IS_TEXT.php'; } @@ -251,7 +257,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerIsNonText() + public function providerIsNonText(): array { return require 'tests/data/Calculation/Functions/IS_NONTEXT.php'; } @@ -267,7 +273,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerIsEven() + public function providerIsEven(): array { return require 'tests/data/Calculation/Functions/IS_EVEN.php'; } @@ -283,7 +289,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerIsOdd() + public function providerIsOdd(): array { return require 'tests/data/Calculation/Functions/IS_ODD.php'; } @@ -299,7 +305,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerTYPE() + public function providerTYPE(): array { return require 'tests/data/Calculation/Functions/TYPE.php'; } @@ -315,7 +321,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerN() + public function providerN(): array { return require 'tests/data/Calculation/Functions/N.php'; } @@ -368,7 +374,7 @@ class FunctionsTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerIsFormula() + public function providerIsFormula(): array { return require 'tests/data/Calculation/Functions/ISFORMULA.php'; } @@ -384,7 +390,7 @@ class FunctionsTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerIfCondition() + public function providerIfCondition(): array { return require 'tests/data/Calculation/Functions/IF_CONDITION.php'; } diff --git a/tests/PhpSpreadsheetTests/Calculation/LookupRefTest.php b/tests/PhpSpreadsheetTests/Calculation/LookupRefTest.php index 04dc0a32..af8b8079 100644 --- a/tests/PhpSpreadsheetTests/Calculation/LookupRefTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/LookupRefTest.php @@ -69,8 +69,14 @@ class LookupRefTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } - public function providerFormulaText() + public function providerFormulaText(): array { return require 'tests/data/Calculation/LookupRef/FORMULATEXT.php'; } + + public function testFormulaTextWithoutCell(): void + { + $result = LookupRef::FORMULATEXT('A1'); + self::assertEquals(Functions::REF(), $result); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php b/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php new file mode 100644 index 00000000..e2384460 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php @@ -0,0 +1,57 @@ +compatibilityMode = Functions::getCompatibilityMode(); + $this->returnDate = Functions::getReturnDateType(); + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); + Functions::setReturnDateType($this->returnDate); + } + + /** + * @dataProvider providerTranslations + */ + public function testTranslation(string $expectedResult, string $locale, string $formula): void + { + $validLocale = Settings::setLocale($locale); + if (!$validLocale) { + self::markTestSkipped("Unable to set locale to {$locale}"); + } + + $translatedFormula = Calculation::getInstance()->_translateFormulaToLocale($formula); + self::assertSame($expectedResult, $translatedFormula); + + $restoredFormula = Calculation::getInstance()->_translateFormulaToEnglish($translatedFormula); + self::assertSame($formula, $restoredFormula); + } + + public function providerTranslations(): array + { + return require 'tests/data/Calculation/Translations.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/XlfnFunctionsTest.php b/tests/PhpSpreadsheetTests/Calculation/XlfnFunctionsTest.php index ea20fbf3..f8f02f0e 100644 --- a/tests/PhpSpreadsheetTests/Calculation/XlfnFunctionsTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/XlfnFunctionsTest.php @@ -75,7 +75,7 @@ class XlfnFunctionsTest extends \PHPUnit\Framework\TestCase $sheet->setSelectedCell('B1'); $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($workbook, 'Xlsx'); - $oufil = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $oufil = File::temporaryFilename(); $writer->save($oufil); $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx'); diff --git a/tests/PhpSpreadsheetTests/Cell/AddressHelperTest.php b/tests/PhpSpreadsheetTests/Cell/AddressHelperTest.php index 9b21767b..3f9c8302 100644 --- a/tests/PhpSpreadsheetTests/Cell/AddressHelperTest.php +++ b/tests/PhpSpreadsheetTests/Cell/AddressHelperTest.php @@ -18,7 +18,7 @@ class AddressHelperTest extends TestCase self::assertSame($expectedValue, $actualValue); } - public function providerR1C1ConversionToA1Absolute() + public function providerR1C1ConversionToA1Absolute(): array { return require 'tests/data/Cell/R1C1ConversionToA1Absolute.php'; } @@ -26,9 +26,13 @@ class AddressHelperTest extends TestCase /** * @dataProvider providerR1C1ConversionToA1Relative */ - public function testR1C1ConversionToA1Relative(string $expectedValue, string $address, ?int $row = null, ?int $column = null): void - { - $args = [$address]; + public function testR1C1ConversionToA1Relative( + string $expectedValue, + string $address, + ?int $row = null, + ?int $column = null + ): void { + $args = []; if ($row !== null) { $args[] = $row; } @@ -36,12 +40,12 @@ class AddressHelperTest extends TestCase $args[] = $column; } - $actualValue = AddressHelper::convertToA1(...$args); + $actualValue = AddressHelper::convertToA1($address, ...$args); self::assertSame($expectedValue, $actualValue); } - public function providerR1C1ConversionToA1Relative() + public function providerR1C1ConversionToA1Relative(): array { return require 'tests/data/Cell/R1C1ConversionToA1Relative.php'; } @@ -56,7 +60,7 @@ class AddressHelperTest extends TestCase AddressHelper::convertToA1($address); } - public function providerR1C1ConversionToA1Exception() + public function providerR1C1ConversionToA1Exception(): array { return require 'tests/data/Cell/R1C1ConversionToA1Exception.php'; } @@ -71,7 +75,7 @@ class AddressHelperTest extends TestCase self::assertSame($expectedValue, $actualValue); } - public function providerA1ConversionToR1C1Absolute() + public function providerA1ConversionToR1C1Absolute(): array { return require 'tests/data/Cell/A1ConversionToR1C1Absolute.php'; } @@ -86,7 +90,7 @@ class AddressHelperTest extends TestCase self::assertSame($expectedValue, $actualValue); } - public function providerA1ConversionToR1C1Relative() + public function providerA1ConversionToR1C1Relative(): array { return require 'tests/data/Cell/A1ConversionToR1C1Relative.php'; } @@ -101,7 +105,7 @@ class AddressHelperTest extends TestCase AddressHelper::convertToR1C1($address); } - public function providerA1ConversionToR1C1Exception() + public function providerA1ConversionToR1C1Exception(): array { return require 'tests/data/Cell/A1ConversionToR1C1Exception.php'; } diff --git a/tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php b/tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php index 630a2944..7b4bbb42 100644 --- a/tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php +++ b/tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php @@ -13,10 +13,19 @@ use PHPUnit\Framework\TestCase; class AdvancedValueBinderTest extends TestCase { + /** + * @var string + */ private $currencyCode; + /** + * @var string + */ private $decimalSeparator; + /** + * @var string + */ private $thousandsSeparator; protected function setUp(): void @@ -33,25 +42,8 @@ class AdvancedValueBinderTest extends TestCase StringHelper::setThousandsSeparator($this->thousandsSeparator); } - public function provider() - { - $currencyUSD = NumberFormat::FORMAT_CURRENCY_USD_SIMPLE; - $currencyEURO = str_replace('$', '€', NumberFormat::FORMAT_CURRENCY_USD_SIMPLE); - - return [ - ['10%', 0.1, NumberFormat::FORMAT_PERCENTAGE_00, ',', '.', '$'], - ['$10.11', 10.11, $currencyUSD, ',', '.', '$'], - ['$1,010.12', 1010.12, $currencyUSD, ',', '.', '$'], - ['$20,20', 20.2, $currencyUSD, '.', ',', '$'], - ['$2.020,20', 2020.2, $currencyUSD, '.', ',', '$'], - ['€2.020,20', 2020.2, $currencyEURO, '.', ',', '€'], - ['€ 2.020,20', 2020.2, $currencyEURO, '.', ',', '€'], - ['€2,020.22', 2020.22, $currencyEURO, ',', '.', '€'], - ]; - } - /** - * @dataProvider provider + * @dataProvider currencyProvider * * @param mixed $value * @param mixed $valueBinded @@ -63,7 +55,8 @@ class AdvancedValueBinderTest extends TestCase public function testCurrency($value, $valueBinded, $format, $thousandsSeparator, $decimalSeparator, $currencyCode): void { $sheet = $this->getMockBuilder(Worksheet::class) - ->setMethods(['getStyle', 'getNumberFormat', 'setFormatCode', 'getCellCollection']) + ->onlyMethods(['getStyle', 'getCellCollection']) + ->addMethods(['getNumberFormat', 'setFormatCode']) ->getMock(); $cellCollection = $this->getMockBuilder(Cells::class) ->disableOriginalConstructor() @@ -75,9 +68,11 @@ class AdvancedValueBinderTest extends TestCase $sheet->expects(self::once()) ->method('getStyle') ->willReturnSelf(); + // @phpstan-ignore-next-line $sheet->expects(self::once()) ->method('getNumberFormat') ->willReturnSelf(); + // @phpstan-ignore-next-line $sheet->expects(self::once()) ->method('setFormatCode') ->with($format) @@ -96,4 +91,235 @@ class AdvancedValueBinderTest extends TestCase $binder->bindValue($cell, $value); self::assertEquals($valueBinded, $cell->getValue()); } + + public function currencyProvider(): array + { + $currencyUSD = NumberFormat::FORMAT_CURRENCY_USD_SIMPLE; + $currencyEURO = str_replace('$', '€', NumberFormat::FORMAT_CURRENCY_USD_SIMPLE); + + return [ + ['$10.11', 10.11, $currencyUSD, ',', '.', '$'], + ['$1,010.12', 1010.12, $currencyUSD, ',', '.', '$'], + ['$20,20', 20.2, $currencyUSD, '.', ',', '$'], + ['$2.020,20', 2020.2, $currencyUSD, '.', ',', '$'], + ['€2.020,20', 2020.2, $currencyEURO, '.', ',', '€'], + ['€ 2.020,20', 2020.2, $currencyEURO, '.', ',', '€'], + ['€2,020.22', 2020.22, $currencyEURO, ',', '.', '€'], + ]; + } + + /** + * @dataProvider fractionProvider + * + * @param mixed $value + * @param mixed $valueBinded + * @param mixed $format + */ + public function testFractions($value, $valueBinded, $format): void + { + $sheet = $this->getMockBuilder(Worksheet::class) + ->onlyMethods(['getStyle', 'getCellCollection']) + ->addMethods(['getNumberFormat', 'setFormatCode']) + ->getMock(); + + $cellCollection = $this->getMockBuilder(Cells::class) + ->disableOriginalConstructor() + ->getMock(); + $cellCollection->expects(self::any()) + ->method('getParent') + ->willReturn($sheet); + + $sheet->expects(self::once()) + ->method('getStyle') + ->willReturnSelf(); + // @phpstan-ignore-next-line + $sheet->expects(self::once()) + ->method('getNumberFormat') + ->willReturnSelf(); + // @phpstan-ignore-next-line + $sheet->expects(self::once()) + ->method('setFormatCode') + ->with($format) + ->willReturnSelf(); + $sheet->expects(self::any()) + ->method('getCellCollection') + ->willReturn($cellCollection); + + $cell = new Cell(null, DataType::TYPE_STRING, $sheet); + + $binder = new AdvancedValueBinder(); + $binder->bindValue($cell, $value); + self::assertEquals($valueBinded, $cell->getValue()); + } + + public function fractionProvider(): array + { + return [ + ['1/5', 0.2, '?/?'], + ['-1/5', -0.2, '?/?'], + ['12/5', 2.4, '??/?'], + ['2/100', 0.02, '?/???'], + ['15/12', 1.25, '??/??'], + ['20/100', 0.2, '??/???'], + ['1 3/5', 1.6, '# ?/?'], + ['-1 3/5', -1.6, '# ?/?'], + ['1 4/20', 1.2, '# ?/??'], + ['1 16/20', 1.8, '# ??/??'], + ['12 20/100', 12.2, '# ??/???'], + ]; + } + + /** + * @dataProvider percentageProvider + * + * @param mixed $value + * @param mixed $valueBinded + * @param mixed $format + */ + public function testPercentages($value, $valueBinded, $format): void + { + $sheet = $this->getMockBuilder(Worksheet::class) + ->onlyMethods(['getStyle', 'getCellCollection']) + ->addMethods(['getNumberFormat', 'setFormatCode']) + ->getMock(); + $cellCollection = $this->getMockBuilder(Cells::class) + ->disableOriginalConstructor() + ->getMock(); + $cellCollection->expects(self::any()) + ->method('getParent') + ->willReturn($sheet); + + $sheet->expects(self::once()) + ->method('getStyle') + ->willReturnSelf(); + // @phpstan-ignore-next-line + $sheet->expects(self::once()) + ->method('getNumberFormat') + ->willReturnSelf(); + // @phpstan-ignore-next-line + $sheet->expects(self::once()) + ->method('setFormatCode') + ->with($format) + ->willReturnSelf(); + $sheet->expects(self::any()) + ->method('getCellCollection') + ->willReturn($cellCollection); + + $cell = new Cell(null, DataType::TYPE_STRING, $sheet); + + $binder = new AdvancedValueBinder(); + $binder->bindValue($cell, $value); + self::assertEquals($valueBinded, $cell->getValue()); + } + + public function percentageProvider(): array + { + return [ + ['10%', 0.1, NumberFormat::FORMAT_PERCENTAGE_00], + ['-12%', -0.12, NumberFormat::FORMAT_PERCENTAGE_00], + ['120%', 1.2, NumberFormat::FORMAT_PERCENTAGE_00], + ['12.5%', 0.125, NumberFormat::FORMAT_PERCENTAGE_00], + ]; + } + + /** + * @dataProvider timeProvider + * + * @param mixed $value + * @param mixed $valueBinded + * @param mixed $format + */ + public function testTimes($value, $valueBinded, $format): void + { + $sheet = $this->getMockBuilder(Worksheet::class) + ->onlyMethods(['getStyle', 'getCellCollection']) + ->addMethods(['getNumberFormat', 'setFormatCode']) + ->getMock(); + + $cellCollection = $this->getMockBuilder(Cells::class) + ->disableOriginalConstructor() + ->getMock(); + $cellCollection->expects(self::any()) + ->method('getParent') + ->willReturn($sheet); + + $sheet->expects(self::once()) + ->method('getStyle') + ->willReturnSelf(); + // @phpstan-ignore-next-line + $sheet->expects(self::once()) + ->method('getNumberFormat') + ->willReturnSelf(); + // @phpstan-ignore-next-line + $sheet->expects(self::once()) + ->method('setFormatCode') + ->with($format) + ->willReturnSelf(); + $sheet->expects(self::any()) + ->method('getCellCollection') + ->willReturn($cellCollection); + + $cell = new Cell(null, DataType::TYPE_STRING, $sheet); + + $binder = new AdvancedValueBinder(); + $binder->bindValue($cell, $value); + self::assertEquals($valueBinded, $cell->getValue()); + } + + public function timeProvider(): array + { + return [ + ['1:20', 0.05555555556, NumberFormat::FORMAT_DATE_TIME3], + ['09:17', 0.386805555556, NumberFormat::FORMAT_DATE_TIME3], + ['15:00', 0.625, NumberFormat::FORMAT_DATE_TIME3], + ['17:12:35', 0.71707175926, NumberFormat::FORMAT_DATE_TIME4], + ['23:58:20', 0.99884259259, NumberFormat::FORMAT_DATE_TIME4], + ]; + } + + /** + * @dataProvider stringProvider + */ + public function testStringWrapping(string $value, bool $wrapped): void + { + $sheet = $this->getMockBuilder(Worksheet::class) + ->onlyMethods(['getStyle', 'getCellCollection']) + ->addMethods(['getAlignment', 'setWrapText']) + ->getMock(); + $cellCollection = $this->getMockBuilder(Cells::class) + ->disableOriginalConstructor() + ->getMock(); + $cellCollection->expects(self::any()) + ->method('getParent') + ->willReturn($sheet); + + $sheet->expects($wrapped ? self::once() : self::never()) + ->method('getStyle') + ->willReturnSelf(); + // @phpstan-ignore-next-line + $sheet->expects($wrapped ? self::once() : self::never()) + ->method('getAlignment') + ->willReturnSelf(); + // @phpstan-ignore-next-line + $sheet->expects($wrapped ? self::once() : self::never()) + ->method('setWrapText') + ->with($wrapped) + ->willReturnSelf(); + $sheet->expects(self::any()) + ->method('getCellCollection') + ->willReturn($cellCollection); + + $cell = new Cell(null, DataType::TYPE_STRING, $sheet); + + $binder = new AdvancedValueBinder(); + $binder->bindValue($cell, $value); + } + + public function stringProvider(): array + { + return [ + ['Hello World', false], + ["Hello\nWorld", true], + ]; + } } diff --git a/tests/PhpSpreadsheetTests/Cell/CellTest.php b/tests/PhpSpreadsheetTests/Cell/CellTest.php index 0d9ce337..980f0170 100644 --- a/tests/PhpSpreadsheetTests/Cell/CellTest.php +++ b/tests/PhpSpreadsheetTests/Cell/CellTest.php @@ -23,7 +23,7 @@ class CellTest extends TestCase self::assertSame($expected, $cell->getValue()); } - public function providerSetValueExplicit() + public function providerSetValueExplicit(): array { return require 'tests/data/Cell/SetValueExplicit.php'; } @@ -42,7 +42,7 @@ class CellTest extends TestCase $cell->setValueExplicit($value, $dataType); } - public function providerSetValueExplicitException() + public function providerSetValueExplicitException(): array { return require 'tests/data/Cell/SetValueExplicitException.php'; } diff --git a/tests/PhpSpreadsheetTests/Cell/CoordinateTest.php b/tests/PhpSpreadsheetTests/Cell/CoordinateTest.php index 8e0e98a9..9a0a2c21 100644 --- a/tests/PhpSpreadsheetTests/Cell/CoordinateTest.php +++ b/tests/PhpSpreadsheetTests/Cell/CoordinateTest.php @@ -24,7 +24,7 @@ class CoordinateTest extends TestCase self::assertEquals($stringBack, $string, 'should be able to get the original input with opposite method'); } - public function providerColumnString() + public function providerColumnString(): array { return require 'tests/data/ColumnString.php'; } @@ -74,7 +74,7 @@ class CoordinateTest extends TestCase self::assertEquals($columnIndexBack, $columnIndex, 'should be able to get the original input with opposite method'); } - public function providerColumnIndex() + public function providerColumnIndex(): array { return require 'tests/data/ColumnIndex.php'; } @@ -91,11 +91,25 @@ class CoordinateTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerCoordinates() + public function providerCoordinates(): array { return require 'tests/data/CellCoordinates.php'; } + /** + * @dataProvider providerIndexesFromString + */ + public function testIndexesFromString(array $expectedResult, string $rangeSet): void + { + $result = Coordinate::indexesFromString($rangeSet); + self::assertSame($expectedResult, $result); + } + + public function providerIndexesFromString(): array + { + return require 'tests/data/Cell/IndexesFromString.php'; + } + public function testCoordinateFromStringWithRangeAddress(): void { $cellAddress = 'A1:AI2012'; @@ -153,7 +167,7 @@ class CoordinateTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerAbsoluteCoordinates() + public function providerAbsoluteCoordinates(): array { return require 'tests/data/CellAbsoluteCoordinate.php'; } @@ -185,7 +199,7 @@ class CoordinateTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerAbsoluteReferences() + public function providerAbsoluteReferences(): array { return require 'tests/data/CellAbsoluteReference.php'; } @@ -223,7 +237,7 @@ class CoordinateTest extends TestCase } } - public function providerSplitRange() + public function providerSplitRange(): array { return require 'tests/data/CellSplitRange.php'; } @@ -232,14 +246,15 @@ class CoordinateTest extends TestCase * @dataProvider providerBuildRange * * @param mixed $expectedResult + * @param mixed $rangeSets */ - public function testBuildRange($expectedResult, ...$args): void + public function testBuildRange($expectedResult, $rangeSets): void { - $result = Coordinate::buildRange(...$args); + $result = Coordinate::buildRange($rangeSets); self::assertEquals($expectedResult, $result); } - public function providerBuildRange() + public function providerBuildRange(): array { return require 'tests/data/CellBuildRange.php'; } @@ -248,7 +263,17 @@ class CoordinateTest extends TestCase { $this->expectException(TypeError::class); - $cellRange = ''; + $cellRange = null; + // @phpstan-ignore-next-line + Coordinate::buildRange($cellRange); + } + + public function testBuildRangeInvalid2(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Range does not contain any information'); + + $cellRange = []; Coordinate::buildRange($cellRange); } @@ -264,7 +289,7 @@ class CoordinateTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerRangeBoundaries() + public function providerRangeBoundaries(): array { return require 'tests/data/CellRangeBoundaries.php'; } @@ -281,7 +306,7 @@ class CoordinateTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerRangeDimension() + public function providerRangeDimension(): array { return require 'tests/data/CellRangeDimension.php'; } @@ -298,7 +323,7 @@ class CoordinateTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerGetRangeBoundaries() + public function providerGetRangeBoundaries(): array { return require 'tests/data/CellGetRangeBoundaries.php'; } @@ -315,7 +340,7 @@ class CoordinateTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerExtractAllCellReferencesInRange() + public function providerExtractAllCellReferencesInRange(): array { return require 'tests/data/CellExtractAllCellReferencesInRange.php'; } @@ -333,7 +358,7 @@ class CoordinateTest extends TestCase Coordinate::extractAllCellReferencesInRange($range); } - public function providerInvalidRange() + public function providerInvalidRange(): array { return [['Z1:A1'], ['A4:A1'], ['B1:A1'], ['AA1:Z1']]; } @@ -342,14 +367,15 @@ class CoordinateTest extends TestCase * @dataProvider providerMergeRangesInCollection * * @param mixed $expectedResult + * @param mixed $rangeSets */ - public function testMergeRangesInCollection($expectedResult, ...$args): void + public function testMergeRangesInCollection($expectedResult, $rangeSets): void { - $result = Coordinate::mergeRangesInCollection(...$args); + $result = Coordinate::mergeRangesInCollection($rangeSets); self::assertEquals($expectedResult, $result); } - public function providerMergeRangesInCollection() + public function providerMergeRangesInCollection(): array { return require 'tests/data/CellMergeRangesInCollection.php'; } @@ -366,7 +392,7 @@ class CoordinateTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerCoordinateIsRange() + public function providerCoordinateIsRange(): array { return require 'tests/data/CoordinateIsRange.php'; } diff --git a/tests/PhpSpreadsheetTests/Cell/DataTypeTest.php b/tests/PhpSpreadsheetTests/Cell/DataTypeTest.php index 95454c16..176dd6ac 100644 --- a/tests/PhpSpreadsheetTests/Cell/DataTypeTest.php +++ b/tests/PhpSpreadsheetTests/Cell/DataTypeTest.php @@ -25,6 +25,7 @@ class DataTypeTest extends TestCase $stringLimit = 32767; $randString = $this->randr($stringLimit + 10); $result2 = DataType::checkString($randString); + self::assertIsString($result2); self::assertSame($stringLimit, strlen($result2)); $dirtyString = "bla bla\r\n bla\r test\n"; diff --git a/tests/PhpSpreadsheetTests/Cell/DefaultValueBinderTest.php b/tests/PhpSpreadsheetTests/Cell/DefaultValueBinderTest.php index e13cd942..cc9098bf 100644 --- a/tests/PhpSpreadsheetTests/Cell/DefaultValueBinderTest.php +++ b/tests/PhpSpreadsheetTests/Cell/DefaultValueBinderTest.php @@ -8,14 +8,15 @@ use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder; use PhpOffice\PhpSpreadsheet\RichText\RichText; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class DefaultValueBinderTest extends TestCase { - private function createCellStub() + private function createCellStub(): Cell { // Create a stub for the Cell class. - /** @var Cell $cellStub */ + /** @var Cell&MockObject $cellStub */ $cellStub = $this->getMockBuilder(Cell::class) ->disableOriginalConstructor() ->getMock(); @@ -41,7 +42,7 @@ class DefaultValueBinderTest extends TestCase self::assertTrue($result); } - public function binderProvider() + public function binderProvider(): array { return [ [null], @@ -65,14 +66,15 @@ class DefaultValueBinderTest extends TestCase * @dataProvider providerDataTypeForValue * * @param mixed $expectedResult + * @param mixed $value */ - public function testDataTypeForValue($expectedResult, ...$args): void + public function testDataTypeForValue($expectedResult, $value): void { - $result = DefaultValueBinder::dataTypeForValue(...$args); + $result = DefaultValueBinder::dataTypeForValue($value); self::assertEquals($expectedResult, $result); } - public function providerDataTypeForValue() + public function providerDataTypeForValue(): array { return require 'tests/data/Cell/DefaultValueBinder.php'; } diff --git a/tests/PhpSpreadsheetTests/Cell/HyperlinkTest.php b/tests/PhpSpreadsheetTests/Cell/HyperlinkTest.php index 9c09aa75..e92fbaf3 100644 --- a/tests/PhpSpreadsheetTests/Cell/HyperlinkTest.php +++ b/tests/PhpSpreadsheetTests/Cell/HyperlinkTest.php @@ -34,7 +34,7 @@ class HyperlinkTest extends TestCase { $tooltipValue = 'PhpSpreadsheet Web Site'; - $testInstance = new Hyperlink(null, $tooltipValue); + $testInstance = new Hyperlink('', $tooltipValue); $result = $testInstance->getTooltip(); self::assertEquals($tooltipValue, $result); @@ -45,7 +45,7 @@ class HyperlinkTest extends TestCase $initialTooltipValue = 'PhpSpreadsheet Web Site'; $newTooltipValue = 'PhpSpreadsheet Repository on Github'; - $testInstance = new Hyperlink(null, $initialTooltipValue); + $testInstance = new Hyperlink('', $initialTooltipValue); $result = $testInstance->setTooltip($newTooltipValue); self::assertInstanceOf(Hyperlink::class, $result); diff --git a/tests/PhpSpreadsheetTests/Cell/ValueBinderWithOverriddenDataTypeForValue.php b/tests/PhpSpreadsheetTests/Cell/ValueBinderWithOverriddenDataTypeForValue.php index d1b025a9..e2ddc602 100644 --- a/tests/PhpSpreadsheetTests/Cell/ValueBinderWithOverriddenDataTypeForValue.php +++ b/tests/PhpSpreadsheetTests/Cell/ValueBinderWithOverriddenDataTypeForValue.php @@ -6,12 +6,15 @@ use PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder; class ValueBinderWithOverriddenDataTypeForValue extends DefaultValueBinder { + /** + * @var bool + */ public static $called = false; - public static function dataTypeForValue($pValue) + public static function dataTypeForValue($value) { self::$called = true; - return parent::dataTypeForValue($pValue); + return parent::dataTypeForValue($value); } } diff --git a/tests/PhpSpreadsheetTests/Chart/LegendTest.php b/tests/PhpSpreadsheetTests/Chart/LegendTest.php index 30715365..b12658a4 100644 --- a/tests/PhpSpreadsheetTests/Chart/LegendTest.php +++ b/tests/PhpSpreadsheetTests/Chart/LegendTest.php @@ -98,22 +98,11 @@ class LegendTest extends TestCase $testInstance = new Legend(); foreach ($overlayValues as $overlayValue) { - $result = $testInstance->setOverlay($overlayValue); - self::assertTrue($result); + $testInstance->setOverlay($overlayValue); + self::assertSame($overlayValue, $testInstance->getOverlay()); } } - public function testSetInvalidOverlayReturnsFalse(): void - { - $testInstance = new Legend(); - - $result = $testInstance->setOverlay('INVALID'); - self::assertFalse($result); - - $result = $testInstance->getOverlay(); - self::assertFalse($result); - } - public function testGetOverlay(): void { $OverlayValue = true; diff --git a/tests/PhpSpreadsheetTests/Collection/CellsTest.php b/tests/PhpSpreadsheetTests/Collection/CellsTest.php index 539d0232..5e656cf5 100644 --- a/tests/PhpSpreadsheetTests/Collection/CellsTest.php +++ b/tests/PhpSpreadsheetTests/Collection/CellsTest.php @@ -91,7 +91,7 @@ class CellsTest extends TestCase $collection = $this->getMockBuilder(Cells::class) ->setConstructorArgs([new Worksheet(), new Memory()]) - ->setMethods(['has']) + ->onlyMethods(['has']) ->getMock(); $collection->method('has') diff --git a/tests/PhpSpreadsheetTests/Custom/ComplexAssert.php b/tests/PhpSpreadsheetTests/Custom/ComplexAssert.php index 4d1025d2..828329ac 100644 --- a/tests/PhpSpreadsheetTests/Custom/ComplexAssert.php +++ b/tests/PhpSpreadsheetTests/Custom/ComplexAssert.php @@ -6,9 +6,16 @@ use Complex\Complex; class ComplexAssert { + /** + * @var string + */ private $errorMessage = ''; - private function testExpectedExceptions($expected, $actual) + /** + * @param mixed $expected + * @param mixed $actual + */ + private function testExpectedExceptions($expected, $actual): bool { // Expecting an error, so we do a straight string comparison if ($expected === $actual) { @@ -21,7 +28,7 @@ class ComplexAssert return false; } - private function adjustDelta($expected, $actual, $delta) + private function adjustDelta(float $expected, float $actual, float $delta): float { $adjustedDelta = $delta; @@ -33,7 +40,11 @@ class ComplexAssert return $adjustedDelta > 1.0 ? 1.0 : $adjustedDelta; } - public function assertComplexEquals($expected, $actual, $delta = 0) + /** + * @param mixed $expected + * @param mixed $actual + */ + public function assertComplexEquals($expected, $actual, float $delta = 0): bool { if ($expected === INF || (is_string($expected) && $expected[0] === '#')) { return $this->testExpectedExceptions($expected, $actual); @@ -42,16 +53,6 @@ class ComplexAssert $expectedComplex = new Complex($expected); $actualComplex = new Complex($actual); - if (!is_numeric($actualComplex->getReal()) || !is_numeric($expectedComplex->getReal())) { - if ($actualComplex->getReal() !== $expectedComplex->getReal()) { - $this->errorMessage = 'Mismatched String: ' . $actualComplex->getReal() . ' !== ' . $expectedComplex->getReal(); - - return false; - } - - return true; - } - $adjustedDelta = $this->adjustDelta($expectedComplex->getReal(), $actualComplex->getReal(), $delta); if (abs($actualComplex->getReal() - $expectedComplex->getReal()) > $adjustedDelta) { $this->errorMessage = 'Mismatched Real part: ' . $actualComplex->getReal() . ' != ' . $expectedComplex->getReal(); @@ -75,7 +76,7 @@ class ComplexAssert return true; } - public function getErrorMessage() + public function getErrorMessage(): string { return $this->errorMessage; } diff --git a/tests/PhpSpreadsheetTests/DefinedNameFormulaTest.php b/tests/PhpSpreadsheetTests/DefinedNameFormulaTest.php index 14843091..4b17230e 100644 --- a/tests/PhpSpreadsheetTests/DefinedNameFormulaTest.php +++ b/tests/PhpSpreadsheetTests/DefinedNameFormulaTest.php @@ -31,7 +31,7 @@ class DefinedNameFormulaTest extends TestCase } $allDefinedNames = $spreadSheet->getDefinedNames(); - self::assertSame(count($definedNamesForTest), count($allDefinedNames)); + self::assertCount(count($definedNamesForTest), $allDefinedNames); } public function testGetNamedRanges(): void @@ -49,7 +49,7 @@ class DefinedNameFormulaTest extends TestCase } $allNamedRanges = $spreadSheet->getNamedRanges(); - self::assertSame(count(array_filter($rangeOrFormula)), count($allNamedRanges)); + self::assertCount(count(array_filter($rangeOrFormula)), $allNamedRanges); } public function testGetScopedNamedRange(): void @@ -65,6 +65,7 @@ class DefinedNameFormulaTest extends TestCase $spreadSheet->addDefinedName(DefinedName::createInstance($rangeName, $workSheet, $localRangeValue, true)); $localScopedRange = $spreadSheet->getNamedRange($rangeName, $workSheet); + self::assertNotNull($localScopedRange); self::assertSame($localRangeValue, $localScopedRange->getValue()); } @@ -83,6 +84,7 @@ class DefinedNameFormulaTest extends TestCase $spreadSheet->addDefinedName(DefinedName::createInstance($rangeName, $workSheet1, $localRangeValue, true)); $localScopedRange = $spreadSheet->getNamedRange($rangeName, $workSheet2); + self::assertNotNull($localScopedRange); self::assertSame($globalRangeValue, $localScopedRange->getValue()); } @@ -101,7 +103,7 @@ class DefinedNameFormulaTest extends TestCase } $allNamedFormulae = $spreadSheet->getNamedFormulae(); - self::assertSame(count(array_filter($rangeOrFormula)), count($allNamedFormulae)); + self::assertCount(count(array_filter($rangeOrFormula)), $allNamedFormulae); } public function testGetScopedNamedFormula(): void @@ -117,6 +119,7 @@ class DefinedNameFormulaTest extends TestCase $spreadSheet->addDefinedName(DefinedName::createInstance($formulaName, $workSheet, $localFormulaValue, true)); $localScopedFormula = $spreadSheet->getNamedFormula($formulaName, $workSheet); + self::assertNotNull($localScopedFormula); self::assertSame($localFormulaValue, $localScopedFormula->getValue()); } @@ -135,6 +138,7 @@ class DefinedNameFormulaTest extends TestCase $spreadSheet->addDefinedName(DefinedName::createInstance($formulaName, $workSheet1, $localFormulaValue, true)); $localScopedFormula = $spreadSheet->getNamedFormula($formulaName, $workSheet2); + self::assertNotNull($localScopedFormula); self::assertSame($globalFormulaValue, $localScopedFormula->getValue()); } diff --git a/tests/PhpSpreadsheetTests/DefinedNameTest.php b/tests/PhpSpreadsheetTests/DefinedNameTest.php index 8a411775..43eddc8a 100644 --- a/tests/PhpSpreadsheetTests/DefinedNameTest.php +++ b/tests/PhpSpreadsheetTests/DefinedNameTest.php @@ -47,10 +47,9 @@ class DefinedNameTest extends TestCase ); self::assertCount(1, $this->spreadsheet->getDefinedNames()); - self::assertSame( - '=B1', - $this->spreadsheet->getDefinedName('foo', $this->spreadsheet->getActiveSheet())->getValue() - ); + $definedName = $this->spreadsheet->getDefinedName('foo', $this->spreadsheet->getActiveSheet()); + self::assertNotNull($definedName); + self::assertSame('=B1', $definedName->getValue()); } public function testAddScopedDefinedNameWithSameName(): void @@ -63,14 +62,13 @@ class DefinedNameTest extends TestCase ); self::assertCount(2, $this->spreadsheet->getDefinedNames()); - self::assertSame( - '=A1', - $this->spreadsheet->getDefinedName('foo', $this->spreadsheet->getActiveSheet())->getValue() - ); - self::assertSame( - '=B1', - $this->spreadsheet->getDefinedName('foo', $this->spreadsheet->getSheetByName('Sheet #2'))->getValue() - ); + $definedName1 = $this->spreadsheet->getDefinedName('foo', $this->spreadsheet->getActiveSheet()); + self::assertNotNull($definedName1); + self::assertSame('=A1', $definedName1->getValue()); + + $definedName2 = $this->spreadsheet->getDefinedName('foo', $this->spreadsheet->getSheetByName('Sheet #2')); + self::assertNotNull($definedName2); + self::assertSame('=B1', $definedName2->getValue()); } public function testRemoveDefinedName(): void @@ -99,10 +97,9 @@ class DefinedNameTest extends TestCase $this->spreadsheet->removeDefinedName('Foo', $this->spreadsheet->getActiveSheet()); self::assertCount(1, $this->spreadsheet->getDefinedNames()); - self::assertSame( - '=B1', - $this->spreadsheet->getDefinedName('foo', $this->spreadsheet->getSheetByName('Sheet #2'))->getValue() - ); + $definedName = $this->spreadsheet->getDefinedName('foo', $this->spreadsheet->getSheetByName('Sheet #2')); + self::assertNotNull($definedName); + self::assertSame('=B1', $definedName->getValue()); } public function testRemoveScopedDefinedNameWhenDuplicateNames(): void @@ -117,10 +114,9 @@ class DefinedNameTest extends TestCase $this->spreadsheet->removeDefinedName('Foo', $this->spreadsheet->getSheetByName('Sheet #2')); self::assertCount(1, $this->spreadsheet->getDefinedNames()); - self::assertSame( - '=A1', - $this->spreadsheet->getDefinedName('foo')->getValue() - ); + $definedName = $this->spreadsheet->getDefinedName('foo'); + self::assertNotNull($definedName); + self::assertSame('=A1', $definedName->getValue()); } public function testDefinedNameNoWorksheetNoScope(): void @@ -135,6 +131,7 @@ class DefinedNameTest extends TestCase DefinedName::createInstance('xyz', $this->spreadsheet->getActiveSheet(), 'A1') ); + /** @var NamedRange $namedRange */ $namedRange = $this->spreadsheet->getDefinedName('XYZ'); self::assertInstanceOf(NamedRange::class, $namedRange); self::assertEquals('A1', $namedRange->getRange()); @@ -147,6 +144,9 @@ class DefinedNameTest extends TestCase { $sheet1 = $this->spreadsheet->getSheetByName('Sheet #1'); $sheet2 = $this->spreadsheet->getSheetByName('Sheet #2'); + self::assertNotNull($sheet1); + self::assertNotNull($sheet2); + $sheet1->getCell('A1')->setValue(1); $sheet2->getCell('A1')->setValue(2); $namedRange = new NamedRange('xyz', $sheet2, '$A$1'); @@ -162,6 +162,9 @@ class DefinedNameTest extends TestCase { $sheet1 = $this->spreadsheet->getSheetByName('Sheet #1'); $sheet2 = $this->spreadsheet->getSheetByName('Sheet #2'); + self::assertNotNull($sheet1); + self::assertNotNull($sheet2); + $sheet1->getCell('A1')->setValue(1); $sheet2->getCell('A1')->setValue(2); $namedRange = new NamedRange('abc', $sheet2, '$A$1'); @@ -177,6 +180,9 @@ class DefinedNameTest extends TestCase { $sheet1 = $this->spreadsheet->getSheetByName('Sheet #1'); $sheet2 = $this->spreadsheet->getSheetByName('Sheet #2'); + self::assertNotNull($sheet1); + self::assertNotNull($sheet2); + $sheet1->getCell('A1')->setValue(1); $sheet2->getCell('A1')->setValue(2); $namedRange = new NamedRange('abc', $sheet2, '$A$1'); @@ -192,12 +198,17 @@ class DefinedNameTest extends TestCase { $sheet1 = $this->spreadsheet->getSheetByName('Sheet #1'); $sheet2 = $this->spreadsheet->getSheetByName('Sheet #2'); + self::assertNotNull($sheet1); + self::assertNotNull($sheet2); + $sheet1->getCell('A1')->setValue(1); $sheet2->getCell('A1')->setValue(2); $namedRange = new NamedRange('abc', $sheet2, '$A$1'); $namedRangeClone = clone $namedRange; $ss1 = $namedRange->getWorksheet(); $ss2 = $namedRangeClone->getWorksheet(); + self::assertNotNull($ss1); + self::assertNotNull($ss2); self::assertNotSame($ss1, $ss2); self::assertEquals($ss1->getTitle(), $ss2->getTitle()); } diff --git a/tests/PhpSpreadsheetTests/Document/PropertiesTest.php b/tests/PhpSpreadsheetTests/Document/PropertiesTest.php new file mode 100644 index 00000000..54932bb8 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Document/PropertiesTest.php @@ -0,0 +1,186 @@ +properties = new Properties(); + } + + public function testNewInstance(): void + { + $createdTime = $modifiedTime = time(); + self::assertSame('Unknown Creator', $this->properties->getCreator()); + self::assertSame('Unknown Creator', $this->properties->getLastModifiedBy()); + self::assertSame('Untitled Spreadsheet', $this->properties->getTitle()); + self::assertSame('Microsoft Corporation', $this->properties->getCompany()); + self::assertSame($createdTime, $this->properties->getCreated()); + self::assertSame($modifiedTime, $this->properties->getModified()); + } + + public function testSetCreator(): void + { + $creator = 'Mark Baker'; + + $this->properties->setCreator($creator); + self::assertSame($creator, $this->properties->getCreator()); + } + + /** + * @dataProvider providerCreationTime + * + * @param mixed $expectedCreationTime + * @param mixed $created + */ + public function testSetCreated($expectedCreationTime, $created): void + { + $expectedCreationTime = $expectedCreationTime ?? time(); + + $this->properties->setCreated($created); + self::assertSame($expectedCreationTime, $this->properties->getCreated()); + } + + public function providerCreationTime(): array + { + return [ + [null, null], + [1615980600, 1615980600], + [1615980600, '1615980600'], + [1615980600, '2021-03-17 11:30:00Z'], + ]; + } + + public function testSetModifier(): void + { + $creator = 'Mark Baker'; + + $this->properties->setLastModifiedBy($creator); + self::assertSame($creator, $this->properties->getLastModifiedBy()); + } + + /** + * @dataProvider providerModifiedTime + * + * @param mixed $expectedModifiedTime + * @param mixed $modified + */ + public function testSetModified($expectedModifiedTime, $modified): void + { + $expectedModifiedTime = $expectedModifiedTime ?? time(); + + $this->properties->setModified($modified); + self::assertSame($expectedModifiedTime, $this->properties->getModified()); + } + + public function providerModifiedTime(): array + { + return [ + [null, null], + [1615980600, 1615980600], + [1615980600, '1615980600'], + [1615980600, '2021-03-17 11:30:00Z'], + ]; + } + + public function testSetTitle(): void + { + $title = 'My spreadsheet title test'; + + $this->properties->setTitle($title); + self::assertSame($title, $this->properties->getTitle()); + } + + public function testSetDescription(): void + { + $description = 'A test for spreadsheet description'; + + $this->properties->setDescription($description); + self::assertSame($description, $this->properties->getDescription()); + } + + public function testSetSubject(): void + { + $subject = 'Test spreadsheet'; + + $this->properties->setSubject($subject); + self::assertSame($subject, $this->properties->getSubject()); + } + + public function testSetKeywords(): void + { + $keywords = 'Test PHPSpreadsheet Spreadsheet Excel LibreOffice Gnumeric OpenSpreadsheetML OASIS'; + + $this->properties->setKeywords($keywords); + self::assertSame($keywords, $this->properties->getKeywords()); + } + + public function testSetCategory(): void + { + $category = 'Testing'; + + $this->properties->setCategory($category); + self::assertSame($category, $this->properties->getCategory()); + } + + public function testSetCompany(): void + { + $company = 'PHPOffice Suite'; + + $this->properties->setCompany($company); + self::assertSame($company, $this->properties->getCompany()); + } + + public function testSetManager(): void + { + $manager = 'Mark Baker'; + + $this->properties->setManager($manager); + self::assertSame($manager, $this->properties->getManager()); + } + + /** + * @dataProvider providerCustomProperties + * + * @param mixed $expectedType + * @param mixed $expectedValue + * @param mixed $propertyName + */ + public function testSetCustomProperties($expectedType, $expectedValue, $propertyName, ...$args): void + { + $this->properties->setCustomProperty($propertyName, ...$args); + self::assertTrue($this->properties->isCustomPropertySet($propertyName)); + self::assertSame($expectedValue, $this->properties->getCustomPropertyValue($propertyName)); + self::assertSame($expectedType, $this->properties->getCustomPropertyType($propertyName)); + } + + public function providerCustomProperties(): array + { + return [ + [Properties::PROPERTY_TYPE_STRING, null, 'Editor', null], + [Properties::PROPERTY_TYPE_STRING, 'Mark Baker', 'Editor', 'Mark Baker'], + [Properties::PROPERTY_TYPE_FLOAT, 1.17, 'Version', 1.17], + [Properties::PROPERTY_TYPE_INTEGER, 2, 'Revision', 2], + [Properties::PROPERTY_TYPE_BOOLEAN, true, 'Tested', true], + [Properties::PROPERTY_TYPE_DATE, '2021-03-17', 'Test Date', '2021-03-17', Properties::PROPERTY_TYPE_DATE], + ]; + } + + public function testGetUnknownCustomProperties(): void + { + $propertyName = 'I DONT EXIST'; + + self::assertFalse($this->properties->isCustomPropertySet($propertyName)); + self::assertNull($this->properties->getCustomPropertyValue($propertyName)); + self::assertNull($this->properties->getCustomPropertyType($propertyName)); + } +} diff --git a/tests/PhpSpreadsheetTests/Functional/AbstractFunctional.php b/tests/PhpSpreadsheetTests/Functional/AbstractFunctional.php index f242c698..da821532 100644 --- a/tests/PhpSpreadsheetTests/Functional/AbstractFunctional.php +++ b/tests/PhpSpreadsheetTests/Functional/AbstractFunctional.php @@ -21,7 +21,7 @@ abstract class AbstractFunctional extends TestCase */ protected function writeAndReload(Spreadsheet $spreadsheet, $format, ?callable $readerCustomizer = null) { - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $filename = File::temporaryFilename(); $writer = IOFactory::createWriter($spreadsheet, $format); $writer->save($filename); diff --git a/tests/PhpSpreadsheetTests/Functional/ActiveSheetTest.php b/tests/PhpSpreadsheetTests/Functional/ActiveSheetTest.php index 56682b34..024185c6 100644 --- a/tests/PhpSpreadsheetTests/Functional/ActiveSheetTest.php +++ b/tests/PhpSpreadsheetTests/Functional/ActiveSheetTest.php @@ -3,10 +3,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Functional; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Protection; class ActiveSheetTest extends AbstractFunctional { - public function providerFormats() + public function providerFormats(): array { return [ ['Xls'], @@ -46,6 +47,12 @@ class ActiveSheetTest extends AbstractFunctional $spreadsheet->createSheet(2); + $spreadsheet->setActiveSheetIndex(2) + ->setCellValue('F1', 2) + ->getCell('F1') + ->getStyle() + ->getProtection() + ->setHidden(Protection::PROTECTION_PROTECTED); $spreadsheet->setActiveSheetIndex(2) ->setTitle('Test3') ->setCellValue('A1', 4) diff --git a/tests/PhpSpreadsheetTests/Functional/ColumnWidthTest.php b/tests/PhpSpreadsheetTests/Functional/ColumnWidthTest.php index 5cd0aec7..38117059 100644 --- a/tests/PhpSpreadsheetTests/Functional/ColumnWidthTest.php +++ b/tests/PhpSpreadsheetTests/Functional/ColumnWidthTest.php @@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; class ColumnWidthTest extends AbstractFunctional { - public function providerFormats() + public function providerFormats(): array { return [ ['Xlsx'], @@ -15,8 +15,6 @@ class ColumnWidthTest extends AbstractFunctional /** * @dataProvider providerFormats - * - * @param $format */ public function testReadColumnWidth($format): void { diff --git a/tests/PhpSpreadsheetTests/Functional/CommentsTest.php b/tests/PhpSpreadsheetTests/Functional/CommentsTest.php index 5ba4e7c8..d82f7f96 100644 --- a/tests/PhpSpreadsheetTests/Functional/CommentsTest.php +++ b/tests/PhpSpreadsheetTests/Functional/CommentsTest.php @@ -7,7 +7,7 @@ use PhpOffice\PhpSpreadsheet\Style\Alignment; class CommentsTest extends AbstractFunctional { - public function providerFormats() + public function providerFormats(): array { return [ ['Html'], @@ -21,8 +21,6 @@ class CommentsTest extends AbstractFunctional * count of comments in correct coords. * * @dataProvider providerFormats - * - * @param $format */ public function testComments($format): void { diff --git a/tests/PhpSpreadsheetTests/Functional/ConditionalStopIfTrueTest.php b/tests/PhpSpreadsheetTests/Functional/ConditionalStopIfTrueTest.php index 3183450f..5ee0b1f5 100644 --- a/tests/PhpSpreadsheetTests/Functional/ConditionalStopIfTrueTest.php +++ b/tests/PhpSpreadsheetTests/Functional/ConditionalStopIfTrueTest.php @@ -9,7 +9,7 @@ class ConditionalStopIfTrueTest extends AbstractFunctional const COLOR_GREEN = 'FF99FF66'; const COLOR_RED = 'FFFF5050'; - public function providerFormats() + public function providerFormats(): array { return [ ['Xlsx'], diff --git a/tests/PhpSpreadsheetTests/Functional/EnclosureTest.php b/tests/PhpSpreadsheetTests/Functional/EnclosureTest.php index 1f1cb7eb..2ddecfd2 100644 --- a/tests/PhpSpreadsheetTests/Functional/EnclosureTest.php +++ b/tests/PhpSpreadsheetTests/Functional/EnclosureTest.php @@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; class EnclosureTest extends AbstractFunctional { - public function providerFormats() + public function providerFormats(): array { return [ ['Html'], diff --git a/tests/PhpSpreadsheetTests/Functional/FreezePaneTest.php b/tests/PhpSpreadsheetTests/Functional/FreezePaneTest.php index 4e725d03..67083949 100644 --- a/tests/PhpSpreadsheetTests/Functional/FreezePaneTest.php +++ b/tests/PhpSpreadsheetTests/Functional/FreezePaneTest.php @@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; class FreezePaneTest extends AbstractFunctional { - public function providerFormats() + public function providerFormats(): array { return [ ['Xls'], diff --git a/tests/PhpSpreadsheetTests/Functional/MergedCellsTest.php b/tests/PhpSpreadsheetTests/Functional/MergedCellsTest.php index 39865817..d2e9234f 100644 --- a/tests/PhpSpreadsheetTests/Functional/MergedCellsTest.php +++ b/tests/PhpSpreadsheetTests/Functional/MergedCellsTest.php @@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; class MergedCellsTest extends AbstractFunctional { - public function providerFormats() + public function providerFormats(): array { return [ ['Html'], diff --git a/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php b/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php index 584f6dd5..93753762 100644 --- a/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php +++ b/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php @@ -7,7 +7,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; class PrintAreaTest extends AbstractFunctional { - public function providerFormats() + public function providerFormats(): array { return [ ['Xls'], diff --git a/tests/PhpSpreadsheetTests/Functional/ReadBlankCellsTest.php b/tests/PhpSpreadsheetTests/Functional/ReadBlankCellsTest.php index 9dac3437..585871dc 100644 --- a/tests/PhpSpreadsheetTests/Functional/ReadBlankCellsTest.php +++ b/tests/PhpSpreadsheetTests/Functional/ReadBlankCellsTest.php @@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; class ReadBlankCellsTest extends AbstractFunctional { - public function providerSheetFormat() + public function providerSheetFormat(): array { return [ ['Xlsx'], diff --git a/tests/PhpSpreadsheetTests/Functional/ReadFilterTest.php b/tests/PhpSpreadsheetTests/Functional/ReadFilterTest.php index 9ab86baa..930288a7 100644 --- a/tests/PhpSpreadsheetTests/Functional/ReadFilterTest.php +++ b/tests/PhpSpreadsheetTests/Functional/ReadFilterTest.php @@ -7,7 +7,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; class ReadFilterTest extends AbstractFunctional { - public function providerCellsValues() + public function providerCellsValues(): array { $cellValues = [ // one argument as a multidimensional array diff --git a/tests/PhpSpreadsheetTests/Functional/SelectedCellsTest.php b/tests/PhpSpreadsheetTests/Functional/SelectedCellsTest.php index 625f2428..a1866b09 100644 --- a/tests/PhpSpreadsheetTests/Functional/SelectedCellsTest.php +++ b/tests/PhpSpreadsheetTests/Functional/SelectedCellsTest.php @@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; class SelectedCellsTest extends AbstractFunctional { - public function providerFormats() + public function providerFormats(): array { return [ ['Xls'], diff --git a/tests/PhpSpreadsheetTests/Functional/TypeAttributePreservationTest.php b/tests/PhpSpreadsheetTests/Functional/TypeAttributePreservationTest.php index e6d6377b..bebf0589 100644 --- a/tests/PhpSpreadsheetTests/Functional/TypeAttributePreservationTest.php +++ b/tests/PhpSpreadsheetTests/Functional/TypeAttributePreservationTest.php @@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; class TypeAttributePreservationTest extends AbstractFunctional { - public function providerFormulae() + public function providerFormulae(): array { $formats = ['Xlsx']; $data = require 'tests/data/Functional/TypeAttributePreservation/Formula.php'; diff --git a/tests/PhpSpreadsheetTests/Functional/WorkbookViewAttributesTest.php b/tests/PhpSpreadsheetTests/Functional/WorkbookViewAttributesTest.php index f97ad9cf..8d44c4d2 100644 --- a/tests/PhpSpreadsheetTests/Functional/WorkbookViewAttributesTest.php +++ b/tests/PhpSpreadsheetTests/Functional/WorkbookViewAttributesTest.php @@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; class WorkbookViewAttributesTest extends AbstractFunctional { - public function providerFormats() + public function providerFormats(): array { return [ ['Xlsx'], diff --git a/tests/PhpSpreadsheetTests/Helper/HtmlTest.php b/tests/PhpSpreadsheetTests/Helper/HtmlTest.php index d47c2f64..b2fadf42 100644 --- a/tests/PhpSpreadsheetTests/Helper/HtmlTest.php +++ b/tests/PhpSpreadsheetTests/Helper/HtmlTest.php @@ -21,7 +21,7 @@ class HtmlTest extends TestCase self::assertSame($expected, $actual->getPlainText()); } - public function providerUtf8EncodingSupport() + public function providerUtf8EncodingSupport(): array { return [ ['foo', 'foo'], diff --git a/tests/PhpSpreadsheetTests/Helper/SampleCoverageTest.php b/tests/PhpSpreadsheetTests/Helper/SampleCoverageTest.php new file mode 100644 index 00000000..f3f616fe --- /dev/null +++ b/tests/PhpSpreadsheetTests/Helper/SampleCoverageTest.php @@ -0,0 +1,39 @@ +getSamples(); + self::assertArrayHasKey('Basic', $samples); + $basic = $samples['Basic']; + self::assertArrayHasKey('02 Types', $basic); + self::assertSame('Basic/02_Types.php', $basic['02 Types']); + self::assertSame('phpunit', $helper->getPageTitle()); + self::assertSame('

phpunit

', $helper->getPageHeading()); + } + + public function testDirectoryFail(): void + { + $this->expectException(RuntimeException::class); + + $helper = $this->getMockBuilder(Sample::class) + ->onlyMethods(['isDirOrMkdir']) + ->getMock(); + $helper->expects(self::once()) + ->method('isDirOrMkdir') + ->with(self::isType('string')) + ->willReturn(false); + self::assertSame('', $helper->getFilename('a.xlsx')); + } +} diff --git a/tests/PhpSpreadsheetTests/Helper/SampleTest.php b/tests/PhpSpreadsheetTests/Helper/SampleTest.php index 8fd6bb47..0817f1d5 100644 --- a/tests/PhpSpreadsheetTests/Helper/SampleTest.php +++ b/tests/PhpSpreadsheetTests/Helper/SampleTest.php @@ -25,7 +25,7 @@ class SampleTest extends TestCase self::assertTrue(true); } - public function providerSample() + 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 @@ -38,6 +38,7 @@ class SampleTest extends TestCase [ 'Pdf/21_Pdf_Domdf.php', 'Pdf/21_Pdf_TCPDF.php', + 'Chart/35_Chart_render.php', // idem ] ); } @@ -57,6 +58,9 @@ class SampleTest extends TestCase $result = []; foreach ($helper->getSamples() as $samples) { foreach ($samples as $sample) { +// if (array_pop(explode('/', $sample)) !== 'DGET.php') { +// continue; +// } if (!in_array($sample, $skipped)) { $file = 'samples/' . $sample; $result[] = [$file]; diff --git a/tests/PhpSpreadsheetTests/IOFactoryTest.php b/tests/PhpSpreadsheetTests/IOFactoryTest.php index 886fcb36..4837d6cf 100644 --- a/tests/PhpSpreadsheetTests/IOFactoryTest.php +++ b/tests/PhpSpreadsheetTests/IOFactoryTest.php @@ -24,7 +24,7 @@ class IOFactoryTest extends TestCase self::assertInstanceOf($expected, $actual); } - public function providerCreateWriter() + public function providerCreateWriter(): array { return [ ['Xls', Writer\Xls::class], @@ -58,7 +58,7 @@ class IOFactoryTest extends TestCase self::assertInstanceOf($expected, $actual); } - public function providerCreateReader() + public function providerCreateReader(): array { return [ ['Xls', Reader\Xls::class], @@ -118,7 +118,7 @@ class IOFactoryTest extends TestCase self::assertInstanceOf(Spreadsheet::class, $actual); } - public function providerIdentify() + public function providerIdentify(): array { return [ ['samples/templates/26template.xlsx', 'Xlsx', Reader\Xlsx::class], diff --git a/tests/PhpSpreadsheetTests/Reader/CsvContiguousFilter.php b/tests/PhpSpreadsheetTests/Reader/CsvContiguousFilter.php index 9bc16ae0..1abe9940 100644 --- a/tests/PhpSpreadsheetTests/Reader/CsvContiguousFilter.php +++ b/tests/PhpSpreadsheetTests/Reader/CsvContiguousFilter.php @@ -7,10 +7,19 @@ use PhpOffice\PhpSpreadsheet\Reader\IReadFilter; /** Define a Read Filter class implementing IReadFilter */ class CsvContiguousFilter implements IReadFilter { + /** + * @var int + */ private $startRow = 0; + /** + * @var int + */ private $endRow = 0; + /** + * @var int + */ private $filterType = 0; /** diff --git a/tests/PhpSpreadsheetTests/Reader/CsvContiguousTest.php b/tests/PhpSpreadsheetTests/Reader/CsvContiguousTest.php index 3a417791..82f960e4 100644 --- a/tests/PhpSpreadsheetTests/Reader/CsvContiguousTest.php +++ b/tests/PhpSpreadsheetTests/Reader/CsvContiguousTest.php @@ -8,6 +8,9 @@ use PHPUnit\Framework\TestCase; class CsvContiguousTest extends TestCase { + /** + * @var string + */ private $inputFileName = 'samples/Reader/sampleData/example2.csv'; public function testContiguous(): void @@ -23,8 +26,8 @@ class CsvContiguousTest extends TestCase // Tell the Reader that we want to use the Read Filter that we've Instantiated // and that we want to store it in contiguous rows/columns self::assertFalse($reader->getContiguous()); - $reader->setReadFilter($chunkFilter) - ->setContiguous(true); + $reader->setReadFilter($chunkFilter); + $reader->setContiguous(true); // Instantiate a new PhpSpreadsheet object manually $spreadsheet = new Spreadsheet(); @@ -65,8 +68,8 @@ class CsvContiguousTest extends TestCase // Tell the Reader that we want to use the Read Filter that we've Instantiated // and that we want to store it in contiguous rows/columns - $reader->setReadFilter($chunkFilter) - ->setContiguous(true); + $reader->setReadFilter($chunkFilter); + $reader->setContiguous(true); // Instantiate a new PhpSpreadsheet object manually $spreadsheet = new Spreadsheet(); diff --git a/tests/PhpSpreadsheetTests/Reader/CsvTest.php b/tests/PhpSpreadsheetTests/Reader/CsvTest.php index e543ff48..73c281ec 100644 --- a/tests/PhpSpreadsheetTests/Reader/CsvTest.php +++ b/tests/PhpSpreadsheetTests/Reader/CsvTest.php @@ -29,7 +29,7 @@ class CsvTest extends TestCase self::assertSame($expectedValue, $actual, 'should be able to retrieve correct value'); } - public function providerDelimiterDetection() + public function providerDelimiterDetection(): array { return [ [ @@ -101,7 +101,7 @@ class CsvTest extends TestCase self::assertSame($expected, $reader->canRead($filename)); } - public function providerCanLoad() + public function providerCanLoad(): array { return [ [false, 'tests/data/Reader/Ods/data.ods'], @@ -172,7 +172,7 @@ class CsvTest extends TestCase self::assertEquals(2, $info[0]['totalColumns']); } - public function providerEncodings() + public function providerEncodings(): array { return [ ['tests/data/Reader/CSV/encoding.iso88591.csv', 'ISO-8859-1'], @@ -288,7 +288,7 @@ EOF; self::assertEquals($delimiter, $reader->getDelimiter()); } - public function providerEscapes() + public function providerEscapes(): array { return [ ['\\', ';'], @@ -310,7 +310,7 @@ EOF; self::assertEquals('sixième', $sheet->getCell('C2')->getValue()); } - public function providerGuessEncoding() + public function providerGuessEncoding(): array { return [ ['tests/data/Reader/CSV/premiere.utf8.csv'], diff --git a/tests/PhpSpreadsheetTests/Reader/Html/HtmlHelper.php b/tests/PhpSpreadsheetTests/Reader/Html/HtmlHelper.php index c09902ff..a6be40c9 100644 --- a/tests/PhpSpreadsheetTests/Reader/Html/HtmlHelper.php +++ b/tests/PhpSpreadsheetTests/Reader/Html/HtmlHelper.php @@ -3,13 +3,14 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader\Html; use PhpOffice\PhpSpreadsheet\Reader\Html; +use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; class HtmlHelper { public static function createHtml(string $html): string { - $filename = tempnam(sys_get_temp_dir(), 'html'); + $filename = File::temporaryFilename(); file_put_contents($filename, $html); return $filename; diff --git a/tests/PhpSpreadsheetTests/Reader/Html/HtmlLoadStringTest.php b/tests/PhpSpreadsheetTests/Reader/Html/HtmlLoadStringTest.php index e1041507..bc4c30ff 100644 --- a/tests/PhpSpreadsheetTests/Reader/Html/HtmlLoadStringTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Html/HtmlLoadStringTest.php @@ -89,4 +89,33 @@ class HtmlLoadStringTest extends TestCase $spreadsheet = $reader->loadFromString($html, $spreadsheet); self::assertEquals(2, $spreadsheet->getSheetCount()); } + + public function testCanLoadDuplicateTitle(): void + { + $html = <<<'EOF' + + +Sheet + + +
1
+ + +EOF; + $reader = new \PhpOffice\PhpSpreadsheet\Reader\Html(); + $spreadsheet = $reader->loadFromString($html); + $reader->setSheetIndex(1); + $reader->loadFromString($html, $spreadsheet); + $reader->setSheetIndex(2); + $reader->loadFromString($html, $spreadsheet); + $sheet = $spreadsheet->getSheet(0); + self::assertEquals(1, $sheet->getCell('A1')->getValue()); + self::assertEquals('Sheet', $sheet->getTitle()); + $sheet = $spreadsheet->getSheet(1); + self::assertEquals(1, $sheet->getCell('A1')->getValue()); + self::assertEquals('Sheet 1', $sheet->getTitle()); + $sheet = $spreadsheet->getSheet(2); + self::assertEquals(1, $sheet->getCell('A1')->getValue()); + self::assertEquals('Sheet 2', $sheet->getTitle()); + } } diff --git a/tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php b/tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php index 14bdb39a..7acc5527 100644 --- a/tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php @@ -20,22 +20,22 @@ class HtmlTest extends TestCase public function testBadHtml(): void { - $this->expectException(ReaderException::class); $filename = 'tests/data/Reader/HTML/badhtml.html'; $reader = new Html(); self::assertTrue($reader->canRead($filename)); + + $this->expectException(ReaderException::class); $reader->load($filename); - self::assertTrue(false); } public function testNonHtml(): void { - $this->expectException(ReaderException::class); $filename = __FILE__; $reader = new Html(); self::assertFalse($reader->canRead($filename)); + + $this->expectException(ReaderException::class); $reader->load($filename); - self::assertTrue(false); } public function testInvalidFilename(): void @@ -45,7 +45,7 @@ class HtmlTest extends TestCase self::assertFalse($reader->canRead('')); } - public function providerCanReadVerySmallFile() + public function providerCanReadVerySmallFile(): array { $padding = str_repeat('a', 2048); diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/OdsTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/OdsTest.php index 8be1aa7c..10910207 100644 --- a/tests/PhpSpreadsheetTests/Reader/Ods/OdsTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Ods/OdsTest.php @@ -15,6 +15,22 @@ use PHPUnit\Framework\TestCase; */ class OdsTest extends TestCase { + /** + * @var string + */ + private $timeZone; + + protected function setUp(): void + { + $this->timeZone = date_default_timezone_get(); + date_default_timezone_set('UTC'); + } + + protected function tearDown(): void + { + date_default_timezone_set($this->timeZone); + } + /** * @var Spreadsheet */ @@ -30,7 +46,7 @@ class OdsTest extends TestCase */ private function loadOdsTestFile() { - if (!$this->spreadsheetOdsTest) { + if (!isset($this->spreadsheetOdsTest)) { $filename = 'samples/templates/OOCalcTest.ods'; // Load into this instance @@ -46,7 +62,7 @@ class OdsTest extends TestCase */ protected function loadDataFile() { - if (!$this->spreadsheetData) { + if (!isset($this->spreadsheetData)) { $filename = 'tests/data/Reader/Ods/data.ods'; // Load into this instance @@ -88,6 +104,20 @@ class OdsTest extends TestCase self::assertEquals('Sheet1', $spreadsheet->getSheet(0)->getTitle()); } + public function testLoadOneWorksheetNotActive(): void + { + $filename = 'tests/data/Reader/Ods/data.ods'; + + // Load into this instance + $reader = new Ods(); + $reader->setLoadSheetsOnly(['Second Sheet']); + $spreadsheet = $reader->load($filename); + + self::assertEquals(1, $spreadsheet->getSheetCount()); + + self::assertEquals('Second Sheet', $spreadsheet->getSheet(0)->getTitle()); + } + public function testLoadBadFile(): void { $this->expectException(ReaderException::class); @@ -153,13 +183,13 @@ class OdsTest extends TestCase self::assertEquals(0, $firstSheet->getCell('G10')->getValue()); self::assertEquals(DataType::TYPE_NUMERIC, $firstSheet->getCell('A10')->getDataType()); // Date - self::assertEquals(22269.0, $firstSheet->getCell('A10')->getValue()); + self::assertEquals('19-Dec-60', $firstSheet->getCell('A10')->getFormattedValue()); self::assertEquals(DataType::TYPE_NUMERIC, $firstSheet->getCell('A13')->getDataType()); // Time - self::assertEquals(25569.0625, $firstSheet->getCell('A13')->getValue()); + self::assertEquals('2:30:00', $firstSheet->getCell('A13')->getFormattedValue()); self::assertEquals(DataType::TYPE_NUMERIC, $firstSheet->getCell('A15')->getDataType()); // Date + Time - self::assertEquals(22269.0625, $firstSheet->getCell('A15')->getValue()); + self::assertEquals('19-Dec-60 1:30:00', $firstSheet->getCell('A15')->getFormattedValue()); self::assertEquals(DataType::TYPE_NUMERIC, $firstSheet->getCell('A11')->getDataType()); // Fraction diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/PageSetupBug1772Test.php b/tests/PhpSpreadsheetTests/Reader/Ods/PageSetupBug1772Test.php new file mode 100644 index 00000000..883664fc --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Ods/PageSetupBug1772Test.php @@ -0,0 +1,98 @@ +spreadsheet = $reader->load($filename); + } + + public function testPageSetup(): void + { + $assertions = $this->pageSetupAssertions(); + + foreach ($this->spreadsheet->getAllSheets() as $worksheet) { + if (!array_key_exists($worksheet->getTitle(), $assertions)) { + continue; + } + + $sheetAssertions = $assertions[$worksheet->getTitle()]; + foreach ($sheetAssertions as $test => $expectedResult) { + $testMethodName = 'get' . ucfirst($test); + $actualResult = $worksheet->getPageSetup()->$testMethodName(); + self::assertSame( + $expectedResult, + $actualResult, + "Failed assertion for Worksheet '{$worksheet->getTitle()}' {$test}" + ); + } + } + } + + public function testPageMargins(): void + { + $assertions = $this->pageMarginAssertions(); + + foreach ($this->spreadsheet->getAllSheets() as $worksheet) { + if (!array_key_exists($worksheet->getTitle(), $assertions)) { + continue; + } + + $sheetAssertions = $assertions[$worksheet->getTitle()]; + foreach ($sheetAssertions as $test => $expectedResult) { + $testMethodName = 'get' . ucfirst($test); + $actualResult = $worksheet->getPageMargins()->$testMethodName(); + self::assertEqualsWithDelta( + $expectedResult, + $actualResult, + self::MARGIN_PRECISION, + "Failed assertion for Worksheet '{$worksheet->getTitle()}' {$test} margin" + ); + } + } + } + + private function pageSetupAssertions(): array + { + return [ + 'Employee update template' => [ + 'orientation' => PageSetup::ORIENTATION_DEFAULT, + 'scale' => 100, + 'horizontalCentered' => false, + 'verticalCentered' => false, + 'pageOrder' => PageSetup::PAGEORDER_DOWN_THEN_OVER, + ], + ]; + } + + private function pageMarginAssertions(): array + { + return [ + 'Employee update template' => [ + // Here the values are in cm + 'top' => 0.0, + 'header' => 0.2953, + 'left' => 0.0, + 'right' => 0.0, + 'bottom' => 0.0, + 'footer' => 0.2953, + ], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php b/tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php index f98ff7e1..39bc864c 100644 --- a/tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php @@ -23,7 +23,6 @@ class XmlScannerTest extends TestCase * * @param mixed $filename * @param mixed $expectedResult - * @param $libxmlDisableEntityLoader */ public function testValidXML($filename, $expectedResult, $libxmlDisableEntityLoader): void { @@ -37,12 +36,12 @@ class XmlScannerTest extends TestCase self::assertEquals($expectedResult, $result); // php 8.+ deprecated libxml_disable_entity_loader() - It's on by default - if (\PHP_VERSION_ID < 80000) { + if (isset($oldDisableEntityLoaderState)) { libxml_disable_entity_loader($oldDisableEntityLoaderState); } } - public function providerValidXML() + public function providerValidXML(): array { $tests = []; foreach (glob('tests/data/Reader/Xml/XEETestValid*.xml') as $file) { @@ -59,7 +58,6 @@ class XmlScannerTest extends TestCase * @dataProvider providerInvalidXML * * @param mixed $filename - * @param $libxmlDisableEntityLoader */ public function testInvalidXML($filename, $libxmlDisableEntityLoader): void { @@ -80,7 +78,7 @@ class XmlScannerTest extends TestCase } } - public function providerInvalidXML() + public function providerInvalidXML(): array { $tests = []; foreach (glob('tests/data/Reader/Xml/XEETestInvalidUTF*.xml') as $file) { @@ -127,7 +125,7 @@ class XmlScannerTest extends TestCase self::assertEquals(strrev($expectedResult), $xml); } - public function providerValidXMLForCallback() + public function providerValidXMLForCallback(): array { $tests = []; foreach (glob('tests/data/Reader/Xml/SecurityScannerWithCallback*.xml') as $file) { diff --git a/tests/PhpSpreadsheetTests/Reader/SheetsXlsxChartTest.php b/tests/PhpSpreadsheetTests/Reader/SheetsXlsxChartTest.php new file mode 100644 index 00000000..c4dc328d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/SheetsXlsxChartTest.php @@ -0,0 +1,51 @@ +setIncludeCharts(true); + $spreadsheet = $reader->load($filename); + $worksheet = $spreadsheet->getActiveSheet(); + + $charts = $worksheet->getChartCollection(); + self::assertEquals(2, $worksheet->getChartCount()); + self::assertCount(2, $charts); + + $chart1 = $charts[0]; + $pa1 = $chart1->getPlotArea(); + self::assertEquals(2, $pa1->getPlotSeriesCount()); + + $pg1 = $pa1->getPlotGroup()[0]; + + self::assertEquals(DataSeries::TYPE_LINECHART, $pg1->getPlotType()); + self::assertCount(2, $pg1->getPlotLabels()); + self::assertCount(2, $pg1->getPlotValues()); + self::assertCount(2, $pg1->getPlotCategories()); + + $chart2 = $charts[1]; + $pa1 = $chart2->getPlotArea(); + self::assertEquals(2, $pa1->getPlotSeriesCount()); + + $pg1 = $pa1->getPlotGroupByIndex(0); + //Before a refresh, data values are empty + foreach ($pg1->getPlotValues() as $dv) { + self::assertEmpty($dv->getPointCount()); + } + $pg1->refresh($worksheet); + foreach ($pg1->getPlotValues() as $dv) { + self::assertEquals(9, $dv->getPointCount()); + } + self::assertEquals(DataSeries::TYPE_SCATTERCHART, $pg1->getPlotType()); + self::assertCount(2, $pg1->getPlotLabels()); + self::assertCount(2, $pg1->getPlotValues()); + self::assertCount(2, $pg1->getPlotCategories()); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/SlkTest.php b/tests/PhpSpreadsheetTests/Reader/SlkTest.php index e461557e..6881648d 100644 --- a/tests/PhpSpreadsheetTests/Reader/SlkTest.php +++ b/tests/PhpSpreadsheetTests/Reader/SlkTest.php @@ -11,8 +11,14 @@ use PhpOffice\PhpSpreadsheet\Style\Font; class SlkTest extends \PHPUnit\Framework\TestCase { + /** + * @var string + */ private static $testbook = __DIR__ . '/../../../samples/templates/SylkTest.slk'; + /** + * @var string + */ private $filename = ''; protected function teardown(): void diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php index 63da2d05..7287b711 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php @@ -36,7 +36,7 @@ class AutoFilterTest extends TestCase return $instance; } - public function loadDataProvider() + public function loadDataProvider(): array { return [ ['$B3$E8', 0, 'B3E8'], diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php new file mode 100644 index 00000000..5e171139 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php @@ -0,0 +1,64 @@ +getCaption()) { + return null; + } + + return implode("\n", array_map(function ($rt) { + return $rt->getPlainText(); + }, $title->getCaption())); +} + +class ChartsTitleTest extends TestCase +{ + public function testChartTitles(): void + { + $filename = 'tests/data/Reader/XLSX/excelChartsTest.xlsx'; + $reader = IOFactory::createReader('Xlsx')->setIncludeCharts(true); + $spreadsheet = $reader->load($filename); + $worksheet = $spreadsheet->getActiveSheet(); + + $charts = $worksheet->getChartCollection(); + self::assertEquals(5, $worksheet->getChartCount()); + self::assertCount(5, $charts); + + // No title or axis labels + $chart1 = $charts[0]; + $title = getTitleText($chart1->getTitle()); + self::assertEmpty($title); + self::assertEmpty(getTitleText($chart1->getXAxisLabel())); + self::assertEmpty(getTitleText($chart1->getYAxisLabel())); + + // Title, no axis labels + $chart2 = $charts[1]; + + self::assertEquals('Chart with Title and no Axis Labels', getTitleText($chart2->getTitle())); + self::assertEmpty(getTitleText($chart2->getXAxisLabel())); + self::assertEmpty(getTitleText($chart2->getYAxisLabel())); + + // No title, only horizontal axis label + $chart3 = $charts[2]; + self::assertEmpty(getTitleText($chart3->getTitle())); + self::assertEquals('Horizontal Axis Title Only', getTitleText($chart3->getXAxisLabel())); + self::assertEmpty(getTitleText($chart3->getYAxisLabel())); + + // No title, only vertical axis label + $chart4 = $charts[3]; + self::assertEmpty(getTitleText($chart4->getTitle())); + self::assertEquals('Vertical Axis Title Only', getTitleText($chart4->getYAxisLabel())); + self::assertEmpty(getTitleText($chart4->getXAxisLabel())); + + // Title and both axis labels + $chart5 = $charts[4]; + self::assertEquals('Complete Annotations', getTitleText($chart5->getTitle())); + self::assertEquals('Horizontal Axis Title', getTitleText($chart5->getXAxisLabel())); + self::assertEquals('Vertical Axis Title', getTitleText($chart5->getYAxisLabel())); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php new file mode 100644 index 00000000..57fbdad2 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php @@ -0,0 +1,325 @@ +load($filename); + $worksheet = $spreadsheet->getActiveSheet(); + + $this->pattern1Assertion($worksheet); + $this->pattern2Assertion($worksheet); + $this->pattern3Assertion($worksheet); + $this->pattern4Assertion($worksheet); + } + + public function testReloadXlsxConditionalFormattingDataBar(): void + { + // Make sure conditionals from existing file are maintained across save + $filename = 'tests/data/Reader/XLSX/conditionalFormattingDataBarTest.xlsx'; + $outfile = File::temporaryFilename(); + $reader = IOFactory::createReader('Xlsx'); + $spreadshee1 = $reader->load($filename); + $writer = IOFactory::createWriter($spreadshee1, 'Xlsx'); + $writer->save($outfile); + $spreadsheet = $reader->load($outfile); + unlink($outfile); + $worksheet = $spreadsheet->getActiveSheet(); + + $this->pattern1Assertion($worksheet); + $this->pattern2Assertion($worksheet); + $this->pattern3Assertion($worksheet); + $this->pattern4Assertion($worksheet); + } + + public function testNewXlsxConditionalFormattingDataBar(): void + { + // Make sure blanks/non-blanks added by PhpSpreadsheet are handled correctly + $outfile = File::temporaryFilename(); + $spreadshee1 = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $sheet = $spreadshee1->getActiveSheet(); + $sheet->setCellValue('A1', 1); + $sheet->setCellValue('A2', 2); + $sheet->setCellValue('A3', 3); + $sheet->setCellValue('A4', 4); + $sheet->setCellValue('A5', 5); + $cond1 = new Conditional(); + $cond1->setConditionType(Conditional::CONDITION_DATABAR); + $cond1->setDataBar(new ConditionalDataBar()); + $cond1->getDataBar() + ->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject('min')) + ->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject('max')) + ->setColor(Color::COLOR_GREEN); + $cond = [$cond1]; + $sheet->getStyle('A1:A5')->setConditionalStyles($cond); + $writer = IOFactory::createWriter($spreadshee1, 'Xlsx'); + $writer->save($outfile); + $reader = IOFactory::createReader('Xlsx'); + $spreadsheet = $reader->load($outfile); + unlink($outfile); + $worksheet = $spreadsheet->getActiveSheet(); + + $conditionalStyle = $worksheet->getConditionalStyles('A1:A5'); + self::assertNotEmpty($conditionalStyle); + /** @var Conditional $conditionalRule */ + $conditionalRule = $conditionalStyle[0]; + $conditions = $conditionalRule->getConditions(); + self::assertNotEmpty($conditions); + self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); + self::assertNotEmpty($conditionalRule->getDataBar()); + + $dataBar = $conditionalRule->getDataBar(); + self::assertNotEmpty($dataBar->getMinimumConditionalFormatValueObject()); + self::assertNotEmpty($dataBar->getMaximumConditionalFormatValueObject()); + self::assertEquals('min', $dataBar->getMinimumConditionalFormatValueObject()->getType()); + self::assertEquals('max', $dataBar->getMaximumConditionalFormatValueObject()->getType()); + self::assertEquals(Color::COLOR_GREEN, $dataBar->getColor()); + } + + private function pattern1Assertion(Worksheet $worksheet): void + { + self::assertEquals( + "Type: Automatic, Automatic\nDirection: Automatic\nFills: Gradient\nAxis Position: Automatic", + $worksheet->getCell('A2')->getValue() + ); + + $conditionalStyle = $worksheet->getConditionalStyles('A3:A23'); + self::assertNotEmpty($conditionalStyle); + /** @var Conditional $conditionalRule */ + $conditionalRule = $conditionalStyle[0]; + $dataBar = $conditionalRule->getDataBar(); + + self::assertNotEmpty($dataBar); + self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); + self::assertNotEmpty($dataBar->getMinimumConditionalFormatValueObject()); + self::assertNotEmpty($dataBar->getMaximumConditionalFormatValueObject()); + self::assertEquals('min', $dataBar->getMinimumConditionalFormatValueObject()->getType()); + self::assertEquals('max', $dataBar->getMaximumConditionalFormatValueObject()->getType()); + + self::assertEquals('FF638EC6', $dataBar->getColor()); + self::assertNotEmpty($dataBar->getConditionalFormattingRuleExt()); + //ext + $rule1ext = $dataBar->getConditionalFormattingRuleExt(); + self::assertEquals('{72C64AE0-5CD9-164F-83D1-AB720F263E79}', $rule1ext->getId()); + self::assertEquals('dataBar', $rule1ext->getCfRule()); + self::assertEquals('A3:A23', $rule1ext->getSqref()); + $extDataBar = $rule1ext->getDataBarExt(); + self::assertNotEmpty($extDataBar); + $pattern1 = [ + 'minLength' => 0, + 'maxLength' => 100, + 'border' => true, + 'gradient' => null, + 'direction' => null, + 'axisPosition' => null, + 'negativeBarBorderColorSameAsPositive' => false, + 'borderColor' => 'FF638EC6', + 'negativeFillColor' => 'FFFF0000', + 'negativeBorderColor' => 'FFFF0000', + ]; + foreach ($pattern1 as $key => $value) { + $funcName = 'get' . ucwords($key); + self::assertEquals($value, $extDataBar->$funcName(), __METHOD__ . '::' . $funcName . ' function patten'); + } + + self::assertNotEmpty($extDataBar->getMinimumConditionalFormatValueObject()); + self::assertNotEmpty($extDataBar->getMaximumConditionalFormatValueObject()); + self::assertEquals('autoMin', $extDataBar->getMinimumConditionalFormatValueObject()->getType()); + self::assertEquals('autoMax', $extDataBar->getMaximumConditionalFormatValueObject()->getType()); + + self::assertArrayHasKey('rgb', $extDataBar->getAxisColor()); + self::assertEquals('FF000000', $extDataBar->getAxisColor()['rgb']); + } + + private function pattern2Assertion(Worksheet $worksheet): void + { + self::assertEquals( + "Type: Number, Number\nValue: -5, 5\nDirection: Automatic\nFills: Solid\nAxis Position: Automatic", + $worksheet->getCell('B2')->getValue() + ); + + $conditionalStyle = $worksheet->getConditionalStyles('B3:B23'); + self::assertNotEmpty($conditionalStyle); + /** @var Conditional $conditionalRule */ + $conditionalRule = $conditionalStyle[0]; + $dataBar = $conditionalRule->getDataBar(); + + self::assertNotEmpty($dataBar); + self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); + self::assertNotEmpty($dataBar->getMinimumConditionalFormatValueObject()); + self::assertNotEmpty($dataBar->getMaximumConditionalFormatValueObject()); + self::assertEquals('num', $dataBar->getMinimumConditionalFormatValueObject()->getType()); + self::assertEquals('num', $dataBar->getMaximumConditionalFormatValueObject()->getType()); + self::assertEquals('-5', $dataBar->getMinimumConditionalFormatValueObject()->getValue()); + self::assertEquals('5', $dataBar->getMaximumConditionalFormatValueObject()->getValue()); + self::assertEquals('FF63C384', $dataBar->getColor()); + self::assertNotEmpty($dataBar->getConditionalFormattingRuleExt()); + //ext + $rule1ext = $dataBar->getConditionalFormattingRuleExt(); + self::assertEquals('{98904F60-57F0-DF47-B480-691B20D325E3}', $rule1ext->getId()); + self::assertEquals('dataBar', $rule1ext->getCfRule()); + self::assertEquals('B3:B23', $rule1ext->getSqref()); + $extDataBar = $rule1ext->getDataBarExt(); + self::assertNotEmpty($extDataBar); + $pattern1 = [ + 'minLength' => 0, + 'maxLength' => 100, + 'border' => null, + 'gradient' => false, + 'direction' => null, + 'axisPosition' => null, + 'negativeBarBorderColorSameAsPositive' => null, + 'borderColor' => null, + 'negativeFillColor' => 'FFFF0000', + 'negativeBorderColor' => null, + ]; + foreach ($pattern1 as $key => $value) { + $funcName = 'get' . ucwords($key); + self::assertEquals($value, $extDataBar->$funcName(), $funcName . ' function patten'); + } + + self::assertNotEmpty($extDataBar->getMinimumConditionalFormatValueObject()); + self::assertNotEmpty($extDataBar->getMaximumConditionalFormatValueObject()); + self::assertEquals('num', $extDataBar->getMinimumConditionalFormatValueObject()->getType()); + self::assertEquals('num', $extDataBar->getMaximumConditionalFormatValueObject()->getType()); + self::assertEquals('-5', $extDataBar->getMinimumConditionalFormatValueObject()->getCellFormula()); + self::assertEquals('5', $extDataBar->getMaximumConditionalFormatValueObject()->getCellFormula()); + + self::assertArrayHasKey('rgb', $extDataBar->getAxisColor()); + self::assertEquals('FF000000', $extDataBar->getAxisColor()['rgb']); + } + + private function pattern3Assertion(Worksheet $worksheet): void + { + self::assertEquals( + "Type: Automatic, Automatic\nDirection: rightToLeft\nFills: Solid\nAxis Position: None", + $worksheet->getCell('C2')->getValue() + ); + + $conditionalStyle = $worksheet->getConditionalStyles('C3:C23'); + self::assertNotEmpty($conditionalStyle); + /** @var Conditional $conditionalRule */ + $conditionalRule = $conditionalStyle[0]; + $dataBar = $conditionalRule->getDataBar(); + + self::assertNotEmpty($dataBar); + self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); + self::assertNotEmpty($dataBar->getMinimumConditionalFormatValueObject()); + self::assertNotEmpty($dataBar->getMaximumConditionalFormatValueObject()); + self::assertEquals('min', $dataBar->getMinimumConditionalFormatValueObject()->getType()); + self::assertEquals('max', $dataBar->getMaximumConditionalFormatValueObject()->getType()); + self::assertEmpty($dataBar->getMinimumConditionalFormatValueObject()->getValue()); + self::assertEmpty($dataBar->getMaximumConditionalFormatValueObject()->getValue()); + self::assertEquals('FFFF555A', $dataBar->getColor()); + self::assertNotEmpty($dataBar->getConditionalFormattingRuleExt()); + + //ext + $rule1ext = $dataBar->getConditionalFormattingRuleExt(); + self::assertEquals('{453C04BA-7ABD-8548-8A17-D9CFD2BDABE9}', $rule1ext->getId()); + self::assertEquals('dataBar', $rule1ext->getCfRule()); + self::assertEquals('C3:C23', $rule1ext->getSqref()); + $extDataBar = $rule1ext->getDataBarExt(); + self::assertNotEmpty($extDataBar); + $pattern1 = [ + 'minLength' => 0, + 'maxLength' => 100, + 'border' => null, + 'gradient' => false, + 'direction' => 'rightToLeft', + 'axisPosition' => 'none', + 'negativeBarBorderColorSameAsPositive' => null, + 'borderColor' => null, + 'negativeFillColor' => 'FFFF0000', + 'negativeBorderColor' => null, + ]; + foreach ($pattern1 as $key => $value) { + $funcName = 'get' . ucwords($key); + self::assertEquals($value, $extDataBar->$funcName(), $funcName . ' function patten'); + } + + self::assertNotEmpty($extDataBar->getMinimumConditionalFormatValueObject()); + self::assertNotEmpty($extDataBar->getMaximumConditionalFormatValueObject()); + self::assertEquals('autoMin', $extDataBar->getMinimumConditionalFormatValueObject()->getType()); + self::assertEquals('autoMax', $extDataBar->getMaximumConditionalFormatValueObject()->getType()); + self::assertEmpty($extDataBar->getMinimumConditionalFormatValueObject()->getCellFormula()); + self::assertEmpty($extDataBar->getMaximumConditionalFormatValueObject()->getCellFormula()); + + self::assertArrayHasKey('rgb', $extDataBar->getAxisColor()); + self::assertEmpty($extDataBar->getAxisColor()['rgb']); + } + + private function pattern4Assertion(Worksheet $worksheet): void + { + self::assertEquals( + "type: formula, formula\nValue: =2+3, =10+10\nDirection: leftToRight\nShowDataBarOnly\nFills: Solid\nBorder: Solid\nAxis Position: Midpoint", + $worksheet->getCell('D2')->getValue() + ); + + $conditionalStyle = $worksheet->getConditionalStyles('D3:D23'); + self::assertNotEmpty($conditionalStyle); + /** @var Conditional $conditionalRule */ + $conditionalRule = $conditionalStyle[0]; + $dataBar = $conditionalRule->getDataBar(); + + self::assertNotEmpty($dataBar); + self::assertEquals(Conditional::CONDITION_DATABAR, $conditionalRule->getConditionType()); + + self::assertTrue($dataBar->getShowValue()); + self::assertNotEmpty($dataBar->getMinimumConditionalFormatValueObject()); + self::assertNotEmpty($dataBar->getMaximumConditionalFormatValueObject()); + self::assertEquals('formula', $dataBar->getMinimumConditionalFormatValueObject()->getType()); + self::assertEquals('formula', $dataBar->getMaximumConditionalFormatValueObject()->getType()); + self::assertEquals('3+2', $dataBar->getMinimumConditionalFormatValueObject()->getValue()); + self::assertEquals('10+10', $dataBar->getMaximumConditionalFormatValueObject()->getValue()); + self::assertEquals('FFFF555A', $dataBar->getColor()); + self::assertNotEmpty($dataBar->getConditionalFormattingRuleExt()); + + //ext + $rule1ext = $dataBar->getConditionalFormattingRuleExt(); + self::assertEquals('{6C1E066A-E240-3D4A-98F8-8CC218B0DFD2}', $rule1ext->getId()); + self::assertEquals('dataBar', $rule1ext->getCfRule()); + self::assertEquals('D3:D23', $rule1ext->getSqref()); + $extDataBar = $rule1ext->getDataBarExt(); + self::assertNotEmpty($extDataBar); + $pattern1 = [ + 'minLength' => 0, + 'maxLength' => 100, + 'border' => true, + 'gradient' => false, + 'direction' => 'leftToRight', + 'axisPosition' => 'middle', + 'negativeBarBorderColorSameAsPositive' => null, + 'borderColor' => 'FF000000', + 'negativeFillColor' => 'FFFF0000', + 'negativeBorderColor' => null, + ]; + foreach ($pattern1 as $key => $value) { + $funcName = 'get' . ucwords($key); + self::assertEquals($value, $extDataBar->$funcName(), $funcName . ' function patten'); + } + + self::assertNotEmpty($extDataBar->getMaximumConditionalFormatValueObject()); + self::assertNotEmpty($extDataBar->getMinimumConditionalFormatValueObject()); + self::assertEquals('formula', $extDataBar->getMinimumConditionalFormatValueObject()->getType()); + self::assertEquals('formula', $extDataBar->getMaximumConditionalFormatValueObject()->getType()); + self::assertEquals('3+2', $extDataBar->getMinimumConditionalFormatValueObject()->getCellFormula()); + self::assertEquals('10+10', $extDataBar->getMaximumConditionalFormatValueObject()->getCellFormula()); + + self::assertArrayHasKey('rgb', $extDataBar->getAxisColor()); + self::assertEquals('FF000000', $extDataBar->getAxisColor()['rgb']); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx2Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx2Test.php index 1220c378..cef72121 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx2Test.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx2Test.php @@ -49,7 +49,7 @@ class Xlsx2Test extends TestCase { // Make sure conditionals from existing file are maintained across save $filename = 'tests/data/Reader/XLSX/conditionalFormatting2Test.xlsx'; - $outfile = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $outfile = File::temporaryFilename(); $reader = IOFactory::createReader('Xlsx'); $spreadshee1 = $reader->load($filename); $writer = IOFactory::createWriter($spreadshee1, 'Xlsx'); @@ -87,7 +87,7 @@ class Xlsx2Test extends TestCase public function testNewXlsxConditionalFormatting2(): void { // Make sure blanks/non-blanks added by PhpSpreadsheet are handled correctly - $outfile = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $outfile = File::temporaryFilename(); $spreadshee1 = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $sheet = $spreadshee1->getActiveSheet(); $sheet->setCellValue('A2', 'a2'); diff --git a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php index b326c142..738a25b9 100644 --- a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php +++ b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php @@ -55,6 +55,25 @@ class XlsxTest extends TestCase } } + public function testListWorksheetInfo(): void + { + $filename = 'tests/data/Reader/XLSX/rowColumnAttributeTest.xlsx'; + $reader = new Xlsx(); + $actual = $reader->listWorksheetInfo($filename); + + $expected = [ + [ + 'worksheetName' => 'Sheet1', + 'lastColumnLetter' => 'F', + 'lastColumnIndex' => 5, + 'totalRows' => '6', + 'totalColumns' => 6, + ], + ]; + + self::assertEquals($expected, $actual); + } + public function testLoadXlsxRowColumnAttributes(): void { $filename = 'tests/data/Reader/XLSX/rowColumnAttributeTest.xlsx'; @@ -218,7 +237,7 @@ class XlsxTest extends TestCase $filename = 'tests/data/Reader/XLSX/empty_drawing.xlsx'; $reader = new Xlsx(); $excel = $reader->load($filename); - $resultFilename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $resultFilename = File::temporaryFilename(); $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($excel); $writer->save($resultFilename); $excel = $reader->load($resultFilename); @@ -231,7 +250,6 @@ class XlsxTest extends TestCase * Test if all whitespace is removed from a style definition string. * This is needed to parse it into properties with the correct keys. * - * @param $string * @dataProvider providerStripsWhiteSpaceFromStyleString */ public function testStripsWhiteSpaceFromStyleString($string): void @@ -240,7 +258,7 @@ class XlsxTest extends TestCase self::assertEquals(preg_match('/\s/', $string), 0); } - public function providerStripsWhiteSpaceFromStyleString() + public function providerStripsWhiteSpaceFromStyleString(): array { return [ ['position:absolute;margin-left:424.5pt;margin-top:169.5pt;width:67.5pt; diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/XmlLoadTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/XmlLoadTest.php index 969571f2..3783c1a9 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xml/XmlLoadTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xml/XmlLoadTest.php @@ -36,6 +36,10 @@ class XmlLoadTest extends TestCase self::assertEquals('# ?0/??0', $sheet->getCell('A11')->getStyle()->getNumberFormat()->getFormatCode()); // Same pattern, same value, different display in Gnumeric vs Excel //self::assertEquals('1 1/2', $sheet->getCell('A11')->getFormattedValue()); + self::assertEquals('hh":"mm":"ss', $sheet->getCell('A13')->getStyle()->getNumberFormat()->getFormatCode()); + self::assertEquals('02:30:00', $sheet->getCell('A13')->getFormattedValue()); + self::assertEquals('d/m/yy hh":"mm', $sheet->getCell('A15')->getStyle()->getNumberFormat()->getFormatCode()); + self::assertEquals('19/12/60 01:30', $sheet->getCell('A15')->getFormattedValue()); self::assertEquals('=B1+C1', $sheet->getCell('H1')->getValue()); self::assertEquals('=E2&F2', $sheet->getCell('J2')->getValue()); diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/XmlOddTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/XmlOddTest.php index e0b43113..8b9e05ff 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xml/XmlOddTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xml/XmlOddTest.php @@ -8,6 +8,9 @@ use PHPUnit\Framework\TestCase; class XmlOddTest extends TestCase { + /** + * @var string + */ private $filename = ''; protected function teardown(): void @@ -53,7 +56,7 @@ class XmlOddTest extends TestCase EOT; - $this->filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $this->filename = File::temporaryFilename(); file_put_contents($this->filename, $xmldata); $reader = new Xml(); $spreadsheet = $reader->load($this->filename); diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/XmlTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/XmlTest.php index 2b66c7b4..bcc108c1 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xml/XmlTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xml/XmlTest.php @@ -10,8 +10,6 @@ class XmlTest extends TestCase { /** * @dataProvider providerInvalidSimpleXML - * - * @param $filename */ public function testInvalidSimpleXML($filename): void { @@ -21,7 +19,7 @@ class XmlTest extends TestCase $xmlReader->trySimpleXMLLoadString($filename); } - public function providerInvalidSimpleXML() + public function providerInvalidSimpleXML(): array { $tests = []; foreach (glob('tests/data/Reader/Xml/XEETestInvalidSimpleXML*.xml') as $file) { diff --git a/tests/PhpSpreadsheetTests/SettingsTest.php b/tests/PhpSpreadsheetTests/SettingsTest.php index 92f3bb1f..11b93ae6 100644 --- a/tests/PhpSpreadsheetTests/SettingsTest.php +++ b/tests/PhpSpreadsheetTests/SettingsTest.php @@ -8,9 +8,9 @@ use PHPUnit\Framework\TestCase; class SettingsTest extends TestCase { /** - * @var string + * @var bool */ - protected $prevValue; + private $prevValue; protected function setUp(): void { diff --git a/tests/PhpSpreadsheetTests/Shared/CodePageTest.php b/tests/PhpSpreadsheetTests/Shared/CodePageTest.php index 2bdbda72..e7dc7469 100644 --- a/tests/PhpSpreadsheetTests/Shared/CodePageTest.php +++ b/tests/PhpSpreadsheetTests/Shared/CodePageTest.php @@ -12,14 +12,15 @@ class CodePageTest extends TestCase * @dataProvider providerCodePage * * @param mixed $expectedResult + * @param mixed $codePageIndex */ - public function testCodePageNumberToName($expectedResult, ...$args): void + public function testCodePageNumberToName($expectedResult, $codePageIndex): void { - $result = CodePage::numberToName(...$args); + $result = CodePage::numberToName($codePageIndex); self::assertEquals($expectedResult, $result); } - public function providerCodePage() + public function providerCodePage(): array { return require 'tests/data/Shared/CodePage.php'; } diff --git a/tests/PhpSpreadsheetTests/Shared/DateTest.php b/tests/PhpSpreadsheetTests/Shared/DateTest.php index 7254635e..1ca4c674 100644 --- a/tests/PhpSpreadsheetTests/Shared/DateTest.php +++ b/tests/PhpSpreadsheetTests/Shared/DateTest.php @@ -2,22 +2,33 @@ namespace PhpOffice\PhpSpreadsheetTests\Shared; +use DateTimeZone; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PHPUnit\Framework\TestCase; class DateTest extends TestCase { + /** + * @var int + */ + private $excelCalendar; + + /** + * @var null|DateTimeZone + */ private $dttimezone; protected function setUp(): void { $this->dttimezone = Date::getDefaultTimeZone(); + $this->excelCalendar = Date::getExcelCalendar(); } protected function tearDown(): void { Date::setDefaultTimeZone($this->dttimezone); + Date::setExcelCalendar($this->excelCalendar); } public function testSetExcelCalendar(): void @@ -35,7 +46,7 @@ class DateTest extends TestCase public function testSetExcelCalendarWithInvalidValue(): void { - $unsupportedCalendar = '2012'; + $unsupportedCalendar = 2012; $result = Date::setExcelCalendar($unsupportedCalendar); self::assertFalse($result); } @@ -44,16 +55,20 @@ class DateTest extends TestCase * @dataProvider providerDateTimeExcelToTimestamp1900 * * @param mixed $expectedResult + * @param mixed $excelDateTimeValue */ - public function testDateTimeExcelToTimestamp1900($expectedResult, ...$args): void + public function testDateTimeExcelToTimestamp1900($expectedResult, $excelDateTimeValue): void { + if (is_numeric($expectedResult) && ($expectedResult > PHP_INT_MAX || $expectedResult < PHP_INT_MIN)) { + self::markTestSkipped('Test invalid on 32-bit system.'); + } Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - $result = Date::excelToTimestamp(...$args); + $result = Date::excelToTimestamp($excelDateTimeValue); self::assertEquals($expectedResult, $result); } - public function providerDateTimeExcelToTimestamp1900() + public function providerDateTimeExcelToTimestamp1900(): array { return require 'tests/data/Shared/Date/ExcelToTimestamp1900.php'; } @@ -62,16 +77,17 @@ class DateTest extends TestCase * @dataProvider providerDateTimeTimestampToExcel1900 * * @param mixed $expectedResult + * @param mixed $unixTimestamp */ - public function testDateTimeTimestampToExcel1900($expectedResult, ...$args): void + public function testDateTimeTimestampToExcel1900($expectedResult, $unixTimestamp): void { Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - $result = Date::timestampToExcel(...$args); + $result = Date::timestampToExcel($unixTimestamp); self::assertEqualsWithDelta($expectedResult, $result, 1E-5); } - public function providerDateTimeTimestampToExcel1900() + public function providerDateTimeTimestampToExcel1900(): array { return require 'tests/data/Shared/Date/TimestampToExcel1900.php'; } @@ -80,16 +96,17 @@ class DateTest extends TestCase * @dataProvider providerDateTimeDateTimeToExcel * * @param mixed $expectedResult + * @param mixed $dateTimeObject */ - public function testDateTimeDateTimeToExcel($expectedResult, ...$args): void + public function testDateTimeDateTimeToExcel($expectedResult, $dateTimeObject): void { Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - $result = Date::dateTimeToExcel(...$args); + $result = Date::dateTimeToExcel($dateTimeObject); self::assertEqualsWithDelta($expectedResult, $result, 1E-5); } - public function providerDateTimeDateTimeToExcel() + public function providerDateTimeDateTimeToExcel(): array { return require 'tests/data/Shared/Date/DateTimeToExcel.php'; } @@ -107,7 +124,7 @@ class DateTest extends TestCase self::assertEqualsWithDelta($expectedResult, $result, 1E-5); } - public function providerDateTimeFormattedPHPToExcel1900() + public function providerDateTimeFormattedPHPToExcel1900(): array { return require 'tests/data/Shared/Date/FormattedPHPToExcel1900.php'; } @@ -116,16 +133,20 @@ class DateTest extends TestCase * @dataProvider providerDateTimeExcelToTimestamp1904 * * @param mixed $expectedResult + * @param mixed $excelDateTimeValue */ - public function testDateTimeExcelToTimestamp1904($expectedResult, ...$args): void + public function testDateTimeExcelToTimestamp1904($expectedResult, $excelDateTimeValue): void { + if (is_numeric($expectedResult) && ($expectedResult > PHP_INT_MAX || $expectedResult < PHP_INT_MIN)) { + self::markTestSkipped('Test invalid on 32-bit system.'); + } Date::setExcelCalendar(Date::CALENDAR_MAC_1904); - $result = Date::excelToTimestamp(...$args); + $result = Date::excelToTimestamp($excelDateTimeValue); self::assertEquals($expectedResult, $result); } - public function providerDateTimeExcelToTimestamp1904() + public function providerDateTimeExcelToTimestamp1904(): array { return require 'tests/data/Shared/Date/ExcelToTimestamp1904.php'; } @@ -134,16 +155,17 @@ class DateTest extends TestCase * @dataProvider providerDateTimeTimestampToExcel1904 * * @param mixed $expectedResult + * @param mixed $unixTimestamp */ - public function testDateTimeTimestampToExcel1904($expectedResult, ...$args): void + public function testDateTimeTimestampToExcel1904($expectedResult, $unixTimestamp): void { Date::setExcelCalendar(Date::CALENDAR_MAC_1904); - $result = Date::timestampToExcel(...$args); + $result = Date::timestampToExcel($unixTimestamp); self::assertEqualsWithDelta($expectedResult, $result, 1E-5); } - public function providerDateTimeTimestampToExcel1904() + public function providerDateTimeTimestampToExcel1904(): array { return require 'tests/data/Shared/Date/TimestampToExcel1904.php'; } @@ -159,7 +181,7 @@ class DateTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerIsDateTimeFormatCode() + public function providerIsDateTimeFormatCode(): array { return require 'tests/data/Shared/Date/FormatCodes.php'; } @@ -168,16 +190,21 @@ class DateTest extends TestCase * @dataProvider providerDateTimeExcelToTimestamp1900Timezone * * @param mixed $expectedResult + * @param mixed $excelDateTimeValue + * @param mixed $timezone */ - public function testDateTimeExcelToTimestamp1900Timezone($expectedResult, ...$args): void + public function testDateTimeExcelToTimestamp1900Timezone($expectedResult, $excelDateTimeValue, $timezone): void { + if (is_numeric($expectedResult) && ($expectedResult > PHP_INT_MAX || $expectedResult < PHP_INT_MIN)) { + self::markTestSkipped('Test invalid on 32-bit system.'); + } Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - $result = Date::excelToTimestamp(...$args); + $result = Date::excelToTimestamp($excelDateTimeValue, $timezone); self::assertEquals($expectedResult, $result); } - public function providerDateTimeExcelToTimestamp1900Timezone() + public function providerDateTimeExcelToTimestamp1900Timezone(): array { return require 'tests/data/Shared/Date/ExcelToTimestamp1900Timezone.php'; } @@ -189,29 +216,37 @@ class DateTest extends TestCase self::assertTrue((bool) Date::stringToExcel('2019-02-28')); self::assertTrue((bool) Date::stringToExcel('2019-02-28 11:18')); self::assertFalse(Date::stringToExcel('2019-02-28 11:71')); + $date = Date::PHPToExcel('2020-01-01'); self::assertEquals(43831.0, $date); + $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setCellValue('B1', 'x'); $val = $sheet->getCell('B1')->getValue(); self::assertFalse(Date::timestampToExcel($val)); + $cell = $sheet->getCell('A1'); self::assertNotNull($cell); + $cell->setValue($date); $sheet->getStyle('A1') ->getNumberFormat() ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME); self::assertTrue(null !== $cell && Date::isDateTime($cell)); + $cella2 = $sheet->getCell('A2'); self::assertNotNull($cella2); + $cella2->setValue('=A1+2'); $sheet->getStyle('A2') ->getNumberFormat() ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME); self::assertTrue(null !== $cella2 && Date::isDateTime($cella2)); + $cella3 = $sheet->getCell('A3'); self::assertNotNull($cella3); + $cella3->setValue('=A1+4'); $sheet->getStyle('A3') ->getNumberFormat() diff --git a/tests/PhpSpreadsheetTests/Shared/FontTest.php b/tests/PhpSpreadsheetTests/Shared/FontTest.php index 5eb70852..9bc1d18a 100644 --- a/tests/PhpSpreadsheetTests/Shared/FontTest.php +++ b/tests/PhpSpreadsheetTests/Shared/FontTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Shared; use PhpOffice\PhpSpreadsheet\Shared\Font; +use PhpOffice\PhpSpreadsheet\Style\Font as StyleFont; use PHPUnit\Framework\TestCase; class FontTest extends TestCase @@ -40,14 +41,15 @@ class FontTest extends TestCase * @dataProvider providerFontSizeToPixels * * @param mixed $expectedResult + * @param mixed $size */ - public function testFontSizeToPixels($expectedResult, ...$args): void + public function testFontSizeToPixels($expectedResult, $size): void { - $result = Font::fontSizeToPixels(...$args); + $result = Font::fontSizeToPixels($size); self::assertEquals($expectedResult, $result); } - public function providerFontSizeToPixels() + public function providerFontSizeToPixels(): array { return require 'tests/data/Shared/FontSizeToPixels.php'; } @@ -56,14 +58,15 @@ class FontTest extends TestCase * @dataProvider providerInchSizeToPixels * * @param mixed $expectedResult + * @param mixed $size */ - public function testInchSizeToPixels($expectedResult, ...$args): void + public function testInchSizeToPixels($expectedResult, $size): void { - $result = Font::inchSizeToPixels(...$args); + $result = Font::inchSizeToPixels($size); self::assertEquals($expectedResult, $result); } - public function providerInchSizeToPixels() + public function providerInchSizeToPixels(): array { return require 'tests/data/Shared/InchSizeToPixels.php'; } @@ -72,15 +75,28 @@ class FontTest extends TestCase * @dataProvider providerCentimeterSizeToPixels * * @param mixed $expectedResult + * @param mixed $size */ - public function testCentimeterSizeToPixels($expectedResult, ...$args): void + public function testCentimeterSizeToPixels($expectedResult, $size): void { - $result = Font::centimeterSizeToPixels(...$args); + $result = Font::centimeterSizeToPixels($size); self::assertEquals($expectedResult, $result); } - public function providerCentimeterSizeToPixels() + public function providerCentimeterSizeToPixels(): array { return require 'tests/data/Shared/CentimeterSizeToPixels.php'; } + + public function testVerdanaRotation(): void + { + $font = new StyleFont(); + $font->setName('Verdana')->setSize(10); + $width = Font::getTextWidthPixelsApprox('n', $font, 0); + self::assertEquals(8, $width); + $width = Font::getTextWidthPixelsApprox('n', $font, 45); + self::assertEquals(7, $width); + $width = Font::getTextWidthPixelsApprox('n', $font, -165); + self::assertEquals(4, $width); + } } diff --git a/tests/PhpSpreadsheetTests/Shared/PasswordHasherTest.php b/tests/PhpSpreadsheetTests/Shared/PasswordHasherTest.php index 0d286725..e85b9fa3 100644 --- a/tests/PhpSpreadsheetTests/Shared/PasswordHasherTest.php +++ b/tests/PhpSpreadsheetTests/Shared/PasswordHasherTest.php @@ -18,7 +18,7 @@ class PasswordHasherTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerHashPassword() + public function providerHashPassword(): array { return require 'tests/data/Shared/PasswordHashes.php'; } diff --git a/tests/PhpSpreadsheetTests/Shared/StringHelperTest.php b/tests/PhpSpreadsheetTests/Shared/StringHelperTest.php index 41ed0b21..92f197a2 100644 --- a/tests/PhpSpreadsheetTests/Shared/StringHelperTest.php +++ b/tests/PhpSpreadsheetTests/Shared/StringHelperTest.php @@ -7,10 +7,19 @@ use PHPUnit\Framework\TestCase; class StringHelperTest extends TestCase { + /** + * @var string + */ private $currencyCode; + /** + * @var string + */ private $decimalSeparator; + /** + * @var string + */ private $thousandsSeparator; protected function setUp(): void diff --git a/tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php b/tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php index ff38badf..edad6e6b 100644 --- a/tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php +++ b/tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php @@ -3,14 +3,21 @@ namespace PhpOffice\PhpSpreadsheetTests\Shared; use DateTime; +use DateTimeZone; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\TimeZone; use PHPUnit\Framework\TestCase; class TimeZoneTest extends TestCase { + /** + * @var string + */ private $tztimezone; + /** + * @var null|DateTimeZone + */ private $dttimezone; protected function setUp(): void diff --git a/tests/PhpSpreadsheetTests/Shared/Trend/ExponentialBestFitTest.php b/tests/PhpSpreadsheetTests/Shared/Trend/ExponentialBestFitTest.php new file mode 100644 index 00000000..a12a438c --- /dev/null +++ b/tests/PhpSpreadsheetTests/Shared/Trend/ExponentialBestFitTest.php @@ -0,0 +1,49 @@ +getSlope(1); + self::assertEquals($expectedSlope[0], $slope); + $slope = $bestFit->getSlope(); + self::assertEquals($expectedSlope[1], $slope); + $intersect = $bestFit->getIntersect(1); + self::assertEquals($expectedIntersect[0], $intersect); + $intersect = $bestFit->getIntersect(); + self::assertEquals($expectedIntersect[1], $intersect); + + $equation = $bestFit->getEquation(2); + self::assertEquals($expectedEquation, $equation); + + self::assertSame($expectedGoodnessOfFit[0], $bestFit->getGoodnessOfFit(6)); + self::assertSame($expectedGoodnessOfFit[1], $bestFit->getGoodnessOfFit()); + } + + public function providerExponentialBestFit(): array + { + return require 'tests/data/Shared/Trend/ExponentialBestFit.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Shared/Trend/LinearBestFitTest.php b/tests/PhpSpreadsheetTests/Shared/Trend/LinearBestFitTest.php new file mode 100644 index 00000000..9ada87a5 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Shared/Trend/LinearBestFitTest.php @@ -0,0 +1,49 @@ +getSlope(1); + self::assertEquals($expectedSlope[0], $slope); + $slope = $bestFit->getSlope(); + self::assertEquals($expectedSlope[1], $slope); + $intersect = $bestFit->getIntersect(1); + self::assertEquals($expectedIntersect[0], $intersect); + $intersect = $bestFit->getIntersect(); + self::assertEquals($expectedIntersect[1], $intersect); + + $equation = $bestFit->getEquation(2); + self::assertEquals($expectedEquation, $equation); + + self::assertSame($expectedGoodnessOfFit[0], $bestFit->getGoodnessOfFit(6)); + self::assertSame($expectedGoodnessOfFit[1], $bestFit->getGoodnessOfFit()); + } + + public function providerLinearBestFit(): array + { + return require 'tests/data/Shared/Trend/LinearBestFit.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/SpreadsheetTest.php b/tests/PhpSpreadsheetTests/SpreadsheetTest.php index 129bea7c..7f159d67 100644 --- a/tests/PhpSpreadsheetTests/SpreadsheetTest.php +++ b/tests/PhpSpreadsheetTests/SpreadsheetTest.php @@ -26,10 +26,7 @@ class SpreadsheetTest extends TestCase $this->object->addSheet($sheet); } - /** - * @return array - */ - public function dataProviderForSheetNames() + public function dataProviderForSheetNames(): array { $array = [ [0, 'someSheet1'], @@ -44,9 +41,6 @@ class SpreadsheetTest extends TestCase } /** - * @param $index - * @param $sheetName - * * @dataProvider dataProviderForSheetNames */ public function testGetSheetByName($index, $sheetName): void diff --git a/tests/PhpSpreadsheetTests/Style/ColorTest.php b/tests/PhpSpreadsheetTests/Style/ColorTest.php index 04028f87..3a66192d 100644 --- a/tests/PhpSpreadsheetTests/Style/ColorTest.php +++ b/tests/PhpSpreadsheetTests/Style/ColorTest.php @@ -11,14 +11,15 @@ class ColorTest extends TestCase * @dataProvider providerColorGetRed * * @param mixed $expectedResult + * @param mixed $color */ - public function testGetRed($expectedResult, ...$args): void + public function testGetRed($expectedResult, $color, ...$args): void { - $result = Color::getRed(...$args); + $result = Color::getRed($color, ...$args); self::assertEquals($expectedResult, $result); } - public function providerColorGetRed() + public function providerColorGetRed(): array { return require 'tests/data/Style/ColorGetRed.php'; } @@ -27,14 +28,15 @@ class ColorTest extends TestCase * @dataProvider providerColorGetGreen * * @param mixed $expectedResult + * @param mixed $color */ - public function testGetGreen($expectedResult, ...$args): void + public function testGetGreen($expectedResult, $color, ...$args): void { - $result = Color::getGreen(...$args); + $result = Color::getGreen($color, ...$args); self::assertEquals($expectedResult, $result); } - public function providerColorGetGreen() + public function providerColorGetGreen(): array { return require 'tests/data/Style/ColorGetGreen.php'; } @@ -43,14 +45,15 @@ class ColorTest extends TestCase * @dataProvider providerColorGetBlue * * @param mixed $expectedResult + * @param mixed $color */ - public function testGetBlue($expectedResult, ...$args): void + public function testGetBlue($expectedResult, $color, ...$args): void { - $result = Color::getBlue(...$args); + $result = Color::getBlue($color, ...$args); self::assertEquals($expectedResult, $result); } - public function providerColorGetBlue() + public function providerColorGetBlue(): array { return require 'tests/data/Style/ColorGetBlue.php'; } @@ -66,7 +69,7 @@ class ColorTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerColorChangeBrightness() + public function providerColorChangeBrightness(): array { return require 'tests/data/Style/ColorChangeBrightness.php'; } diff --git a/tests/PhpSpreadsheetTests/Style/NumberFormatTest.php b/tests/PhpSpreadsheetTests/Style/NumberFormatTest.php index 6bf7db04..e386b292 100644 --- a/tests/PhpSpreadsheetTests/Style/NumberFormatTest.php +++ b/tests/PhpSpreadsheetTests/Style/NumberFormatTest.php @@ -8,10 +8,19 @@ use PHPUnit\Framework\TestCase; class NumberFormatTest extends TestCase { + /** + * @var string + */ private $currencyCode; + /** + * @var string + */ private $decimalSeparator; + /** + * @var string + */ private $thousandsSeparator; protected function setUp(): void @@ -41,7 +50,7 @@ class NumberFormatTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerNumberFormat() + public function providerNumberFormat(): array { return require 'tests/data/Style/NumberFormat.php'; } @@ -57,7 +66,7 @@ class NumberFormatTest extends TestCase self::assertEquals($expectedResult, $result); } - public function providerNumberFormatDates() + public function providerNumberFormatDates(): array { return require 'tests/data/Style/NumberFormatDates.php'; } diff --git a/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/Column/RuleTest.php b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/Column/RuleTest.php index 156a95de..276836c9 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/Column/RuleTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/Column/RuleTest.php @@ -3,12 +3,20 @@ namespace PhpOffice\PhpSpreadsheetTests\Worksheet\AutoFilter\Column; use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column; +use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class RuleTest extends TestCase { + /** + * @var Rule + */ private $testAutoFilterRuleObject; + /** + * @var Column&MockObject + */ private $mockAutoFilterColumnObject; protected function setUp(): void @@ -17,7 +25,7 @@ class RuleTest extends TestCase ->disableOriginalConstructor() ->getMock(); - $this->testAutoFilterRuleObject = new Column\Rule( + $this->testAutoFilterRuleObject = new Rule( $this->mockAutoFilterColumnObject ); } @@ -25,16 +33,16 @@ class RuleTest extends TestCase public function testGetRuleType(): void { $result = $this->testAutoFilterRuleObject->getRuleType(); - self::assertEquals(Column\Rule::AUTOFILTER_RULETYPE_FILTER, $result); + self::assertEquals(Rule::AUTOFILTER_RULETYPE_FILTER, $result); } public function testSetRuleType(): void { - $expectedResult = Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP; + $expectedResult = Rule::AUTOFILTER_RULETYPE_DATEGROUP; // Setters return the instance to implement the fluent interface $result = $this->testAutoFilterRuleObject->setRuleType($expectedResult); - self::assertInstanceOf(Column\Rule::class, $result); + self::assertInstanceOf(Rule::class, $result); $result = $this->testAutoFilterRuleObject->getRuleType(); self::assertEquals($expectedResult, $result); @@ -46,7 +54,7 @@ class RuleTest extends TestCase // Setters return the instance to implement the fluent interface $result = $this->testAutoFilterRuleObject->setValue($expectedResult); - self::assertInstanceOf(Column\Rule::class, $result); + self::assertInstanceOf(Rule::class, $result); $result = $this->testAutoFilterRuleObject->getValue(); self::assertEquals($expectedResult, $result); @@ -55,16 +63,16 @@ class RuleTest extends TestCase public function testGetOperator(): void { $result = $this->testAutoFilterRuleObject->getOperator(); - self::assertEquals(Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, $result); + self::assertEquals(Rule::AUTOFILTER_COLUMN_RULE_EQUAL, $result); } public function testSetOperator(): void { - $expectedResult = Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN; + $expectedResult = Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN; // Setters return the instance to implement the fluent interface $result = $this->testAutoFilterRuleObject->setOperator($expectedResult); - self::assertInstanceOf(Column\Rule::class, $result); + self::assertInstanceOf(Rule::class, $result); $result = $this->testAutoFilterRuleObject->getOperator(); self::assertEquals($expectedResult, $result); @@ -72,11 +80,11 @@ class RuleTest extends TestCase public function testSetGrouping(): void { - $expectedResult = Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH; + $expectedResult = Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH; // Setters return the instance to implement the fluent interface $result = $this->testAutoFilterRuleObject->setGrouping($expectedResult); - self::assertInstanceOf(Column\Rule::class, $result); + self::assertInstanceOf(Rule::class, $result); $result = $this->testAutoFilterRuleObject->getGrouping(); self::assertEquals($expectedResult, $result); @@ -92,12 +100,12 @@ class RuleTest extends TestCase { // Setters return the instance to implement the fluent interface $result = $this->testAutoFilterRuleObject->setParent($this->mockAutoFilterColumnObject); - self::assertInstanceOf(Column\Rule::class, $result); + self::assertInstanceOf(Rule::class, $result); } public function testClone(): void { $result = clone $this->testAutoFilterRuleObject; - self::assertInstanceOf(Column\Rule::class, $result); + self::assertInstanceOf(Rule::class, $result); } } diff --git a/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/ColumnTest.php b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/ColumnTest.php index ef67b05d..93369626 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/ColumnTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/ColumnTest.php @@ -3,14 +3,25 @@ namespace PhpOffice\PhpSpreadsheetTests\Worksheet\AutoFilter; use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter; +use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class ColumnTest extends TestCase { + /** + * @var string + */ private $testInitialColumn = 'H'; + /** + * @var Column + */ private $testAutoFilterColumnObject; + /** + * @var AutoFilter&MockObject + */ private $mockAutoFilterObject; protected function setUp(): void @@ -23,7 +34,7 @@ class ColumnTest extends TestCase ->method('testColumnInRange') ->willReturn(3); - $this->testAutoFilterColumnObject = new AutoFilter\Column($this->testInitialColumn, $this->mockAutoFilterObject); + $this->testAutoFilterColumnObject = new Column($this->testInitialColumn, $this->mockAutoFilterObject); } public function testGetColumnIndex(): void @@ -38,7 +49,7 @@ class ColumnTest extends TestCase // Setters return the instance to implement the fluent interface $result = $this->testAutoFilterColumnObject->setColumnIndex($expectedResult); - self::assertInstanceOf(AutoFilter\Column::class, $result); + self::assertInstanceOf(Column::class, $result); $result = $this->testAutoFilterColumnObject->getColumnIndex(); self::assertEquals($expectedResult, $result); @@ -54,22 +65,22 @@ class ColumnTest extends TestCase { // Setters return the instance to implement the fluent interface $result = $this->testAutoFilterColumnObject->setParent($this->mockAutoFilterObject); - self::assertInstanceOf(AutoFilter\Column::class, $result); + self::assertInstanceOf(Column::class, $result); } public function testGetFilterType(): void { $result = $this->testAutoFilterColumnObject->getFilterType(); - self::assertEquals(AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER, $result); + self::assertEquals(Column::AUTOFILTER_FILTERTYPE_FILTER, $result); } public function testSetFilterType(): void { - $result = $this->testAutoFilterColumnObject->setFilterType(AutoFilter\Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); - self::assertInstanceOf(AutoFilter\Column::class, $result); + $result = $this->testAutoFilterColumnObject->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); + self::assertInstanceOf(Column::class, $result); $result = $this->testAutoFilterColumnObject->getFilterType(); - self::assertEquals(AutoFilter\Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER, $result); + self::assertEquals(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER, $result); } public function testSetInvalidFilterTypeThrowsException(): void @@ -84,16 +95,16 @@ class ColumnTest extends TestCase public function testGetJoin(): void { $result = $this->testAutoFilterColumnObject->getJoin(); - self::assertEquals(AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR, $result); + self::assertEquals(Column::AUTOFILTER_COLUMN_JOIN_OR, $result); } public function testSetJoin(): void { - $result = $this->testAutoFilterColumnObject->setJoin(AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND); - self::assertInstanceOf(AutoFilter\Column::class, $result); + $result = $this->testAutoFilterColumnObject->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND); + self::assertInstanceOf(Column::class, $result); $result = $this->testAutoFilterColumnObject->getJoin(); - self::assertEquals(AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND, $result); + self::assertEquals(Column::AUTOFILTER_COLUMN_JOIN_AND, $result); } public function testSetInvalidJoinThrowsException(): void @@ -114,7 +125,7 @@ class ColumnTest extends TestCase // Setters return the instance to implement the fluent interface $result = $this->testAutoFilterColumnObject->setAttributes($attributeSet); - self::assertInstanceOf(AutoFilter\Column::class, $result); + self::assertInstanceOf(Column::class, $result); } public function testGetAttributes(): void @@ -141,7 +152,7 @@ class ColumnTest extends TestCase foreach ($attributeSet as $attributeName => $attributeValue) { // Setters return the instance to implement the fluent interface $result = $this->testAutoFilterColumnObject->setAttribute($attributeName, $attributeValue); - self::assertInstanceOf(AutoFilter\Column::class, $result); + self::assertInstanceOf(Column::class, $result); } } @@ -166,7 +177,7 @@ class ColumnTest extends TestCase { $originalRule = $this->testAutoFilterColumnObject->createRule(); $result = clone $this->testAutoFilterColumnObject; - self::assertInstanceOf(AutoFilter\Column::class, $result); + self::assertInstanceOf(Column::class, $result); self::assertCount(1, $result->getRules()); self::assertContainsOnlyInstancesOf(AutoFilter\Column\Rule::class, $result->getRules()); $clonedRule = $result->getRules()[0]; diff --git a/tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php b/tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php index 17b4c022..7f218b54 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php @@ -6,10 +6,14 @@ use PhpOffice\PhpSpreadsheet\Collection\Cells; use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter; use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class AutoFilterTest extends TestCase { + /** + * @var string + */ private $testInitialRange = 'H2:O256'; /** @@ -17,8 +21,14 @@ class AutoFilterTest extends TestCase */ private $testAutoFilterObject; + /** + * @var Worksheet&MockObject + */ private $mockWorksheetObject; + /** + * @var Cells&MockObject + */ private $cellCollection; protected function setUp(): void diff --git a/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIterator2Test.php b/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIterator2Test.php index c542d89e..90280bd2 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIterator2Test.php +++ b/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIterator2Test.php @@ -25,9 +25,11 @@ class ColumnCellIterator2Test extends TestCase $lastCoordinate = ''; $firstCoordinate = ''; foreach ($iterator as $cell) { - $lastCoordinate = $cell->getCoordinate(); - if (!$firstCoordinate) { - $firstCoordinate = $lastCoordinate; + if ($cell !== null) { + $lastCoordinate = $cell->getCoordinate(); + if (!$firstCoordinate) { + $firstCoordinate = $lastCoordinate; + } } } self::assertEquals($expectedResultFirst, $firstCoordinate); diff --git a/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIteratorTest.php b/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIteratorTest.php index 1fa25330..9d5f4977 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIteratorTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIteratorTest.php @@ -5,13 +5,20 @@ namespace PhpOffice\PhpSpreadsheetTests\Worksheet; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Worksheet\ColumnCellIterator; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class ColumnCellIteratorTest extends TestCase { - public $mockWorksheet; + /** + * @var Worksheet&MockObject + */ + private $mockWorksheet; - public $mockCell; + /** + * @var Cell&MockObject + */ + private $mockCell; protected function setUp(): void { diff --git a/tests/PhpSpreadsheetTests/Worksheet/ColumnDimensionTest.php b/tests/PhpSpreadsheetTests/Worksheet/ColumnDimensionTest.php index b10875c2..8062a24c 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/ColumnDimensionTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/ColumnDimensionTest.php @@ -40,6 +40,6 @@ class ColumnDimensionTest extends TestCase $columnDimension = new ColumnDimension(); $columnDimension->setAutoSize($expected); $result = $columnDimension->getAutoSize(); - self::assertSame($expected, $result); + self::assertTrue($result); } } diff --git a/tests/PhpSpreadsheetTests/Worksheet/ColumnIteratorTest.php b/tests/PhpSpreadsheetTests/Worksheet/ColumnIteratorTest.php index de985cee..98a402fe 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/ColumnIteratorTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/ColumnIteratorTest.php @@ -5,20 +5,18 @@ namespace PhpOffice\PhpSpreadsheetTests\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Column; use PhpOffice\PhpSpreadsheet\Worksheet\ColumnIterator; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class ColumnIteratorTest extends TestCase { - public $mockWorksheet; - - public $mockColumn; + /** + * @var Worksheet&MockObject + */ + private $mockWorksheet; protected function setUp(): void { - $this->mockColumn = $this->getMockBuilder(Column::class) - ->disableOriginalConstructor() - ->getMock(); - $this->mockWorksheet = $this->getMockBuilder(Worksheet::class) ->disableOriginalConstructor() ->getMock(); @@ -67,6 +65,22 @@ class ColumnIteratorTest extends TestCase } } + public function testIteratorResetStart(): void + { + $iterator = new ColumnIterator($this->mockWorksheet, 'B', 'D'); + $iterator->resetStart('E'); + + $key = $iterator->key(); + self::assertSame('E', $key); + + $lastColumn = $iterator->key(); + while ($iterator->valid() !== false) { + $iterator->next(); + $lastColumn = $iterator->key(); + } + self::assertSame('F', $lastColumn); + } + public function testSeekOutOfRange(): void { $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); @@ -81,4 +95,12 @@ class ColumnIteratorTest extends TestCase $iterator->prev(); self::assertFalse($iterator->valid()); } + + public function testResetStartOutOfRange(): void + { + $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); + + $iterator = new ColumnIterator($this->mockWorksheet, 'B', 'D'); + $iterator->resetStart('H'); + } } diff --git a/tests/PhpSpreadsheetTests/Worksheet/ColumnTest.php b/tests/PhpSpreadsheetTests/Worksheet/ColumnTest.php index 0abff0ec..7795b5ae 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/ColumnTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/ColumnTest.php @@ -5,13 +5,15 @@ namespace PhpOffice\PhpSpreadsheetTests\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Column; use PhpOffice\PhpSpreadsheet\Worksheet\ColumnCellIterator; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class ColumnTest extends TestCase { - public $mockWorksheet; - - public $mockColumn; + /** + * @var Worksheet&MockObject + */ + private $mockWorksheet; protected function setUp(): void { diff --git a/tests/PhpSpreadsheetTests/Worksheet/RowCellIterator2Test.php b/tests/PhpSpreadsheetTests/Worksheet/RowCellIterator2Test.php index 20d10da9..52183168 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/RowCellIterator2Test.php +++ b/tests/PhpSpreadsheetTests/Worksheet/RowCellIterator2Test.php @@ -25,9 +25,11 @@ class RowCellIterator2Test extends TestCase $lastCoordinate = ''; $firstCoordinate = ''; foreach ($iterator as $cell) { - $lastCoordinate = $cell->getCoordinate(); - if (!$firstCoordinate) { - $firstCoordinate = $lastCoordinate; + if ($cell !== null) { + $lastCoordinate = $cell->getCoordinate(); + if (!$firstCoordinate) { + $firstCoordinate = $lastCoordinate; + } } } self::assertEquals($expectedResultFirst, $firstCoordinate); diff --git a/tests/PhpSpreadsheetTests/Worksheet/RowCellIteratorTest.php b/tests/PhpSpreadsheetTests/Worksheet/RowCellIteratorTest.php index 4105c91c..85191746 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/RowCellIteratorTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/RowCellIteratorTest.php @@ -5,13 +5,20 @@ namespace PhpOffice\PhpSpreadsheetTests\Worksheet; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Worksheet\RowCellIterator; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class RowCellIteratorTest extends TestCase { - public $mockWorksheet; + /** + * @var Worksheet&MockObject + */ + private $mockWorksheet; - public $mockCell; + /** + * @var Cell&MockObject + */ + private $mockCell; protected function setUp(): void { diff --git a/tests/PhpSpreadsheetTests/Worksheet/RowIteratorTest.php b/tests/PhpSpreadsheetTests/Worksheet/RowIteratorTest.php index 919da9ef..6228a320 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/RowIteratorTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/RowIteratorTest.php @@ -5,20 +5,18 @@ namespace PhpOffice\PhpSpreadsheetTests\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Row; use PhpOffice\PhpSpreadsheet\Worksheet\RowIterator; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class RowIteratorTest extends TestCase { - public $mockWorksheet; - - public $mockRow; + /** + * @var Worksheet&MockObject + */ + private $mockWorksheet; protected function setUp(): void { - $this->mockRow = $this->getMockBuilder(Row::class) - ->disableOriginalConstructor() - ->getMock(); - $this->mockWorksheet = $this->getMockBuilder(Worksheet::class) ->disableOriginalConstructor() ->getMock(); @@ -65,6 +63,22 @@ class RowIteratorTest extends TestCase } } + public function testIteratorResetStart(): void + { + $iterator = new RowIterator($this->mockWorksheet, 2, 4); + $iterator->resetStart(5); + + $key = $iterator->key(); + self::assertSame(5, $key); + + $lastRow = $iterator->key(); + while ($iterator->valid() !== false) { + $iterator->next(); + $lastRow = $iterator->key(); + } + self::assertSame(6, $lastRow); + } + public function testSeekOutOfRange(): void { $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); @@ -79,4 +93,12 @@ class RowIteratorTest extends TestCase $iterator->prev(); self::assertFalse($iterator->valid()); } + + public function testResetStartOutOfRange(): void + { + $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); + + $iterator = new RowIterator($this->mockWorksheet, 2, 4); + $iterator->resetStart(10); + } } diff --git a/tests/PhpSpreadsheetTests/Worksheet/RowTest.php b/tests/PhpSpreadsheetTests/Worksheet/RowTest.php index 93ff589c..9dea36aa 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/RowTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/RowTest.php @@ -5,13 +5,15 @@ namespace PhpOffice\PhpSpreadsheetTests\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Row; use PhpOffice\PhpSpreadsheet\Worksheet\RowCellIterator; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class RowTest extends TestCase { - public $mockWorksheet; - - public $mockRow; + /** + * @var Worksheet&MockObject + */ + private $mockWorksheet; protected function setUp(): void { diff --git a/tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php b/tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php new file mode 100644 index 00000000..1560f1ed --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/WorksheetNamedRangesTest.php @@ -0,0 +1,147 @@ +spreadsheet = $reader->load('tests/data/Worksheet/namedRangeTest.xlsx'); + } + + public function testCellExists(): void + { + $namedCell = 'GREETING'; + + $worksheet = $this->spreadsheet->getActiveSheet(); + $cellExists = $worksheet->cellExists($namedCell); + self::assertTrue($cellExists); + } + + public function testCellNotExists(): void + { + $namedCell = 'GOODBYE'; + + $worksheet = $this->spreadsheet->getActiveSheet(); + $cellExists = $worksheet->cellExists($namedCell); + self::assertFalse($cellExists); + } + + public function testCellExistsInvalidScope(): void + { + $namedCell = 'Result'; + + $worksheet = $this->spreadsheet->getActiveSheet(); + $cellExists = $worksheet->cellExists($namedCell); + self::assertFalse($cellExists); + } + + public function testCellExistsRange(): void + { + $namedRange = 'Range1'; + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Cell coordinate string can not be a range of cells'); + + $worksheet = $this->spreadsheet->getActiveSheet(); + $worksheet->cellExists($namedRange); + } + + public function testGetCell(): void + { + $namedCell = 'GREETING'; + + $worksheet = $this->spreadsheet->getActiveSheet(); + $cell = $worksheet->getCell($namedCell); + self::assertSame('Hello', $cell->getValue()); + } + + public function testGetCellNotExists(): void + { + $namedCell = 'GOODBYE'; + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Invalid cell coordinate {$namedCell}"); + + $worksheet = $this->spreadsheet->getActiveSheet(); + $worksheet->getCell($namedCell); + } + + public function testGetCellInvalidScope(): void + { + $namedCell = 'Result'; + $ucNamedCell = strtoupper($namedCell); + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Invalid cell coordinate {$ucNamedCell}"); + + $worksheet = $this->spreadsheet->getActiveSheet(); + $worksheet->getCell($namedCell); + } + + public function testGetCellLocalScoped(): void + { + $namedCell = 'Result'; + + $this->spreadsheet->setActiveSheetIndexByName('Sheet2'); + $worksheet = $this->spreadsheet->getActiveSheet(); + $cell = $worksheet->getCell($namedCell); + self::assertSame(8, $cell->getCalculatedValue()); + } + + public function testGetCellNamedFormula(): void + { + $namedCell = 'Result'; + + $this->spreadsheet->setActiveSheetIndexByName('Sheet2'); + $worksheet = $this->spreadsheet->getActiveSheet(); + $cell = $worksheet->getCell($namedCell); + self::assertSame(8, $cell->getCalculatedValue()); + } + + public function testGetCellWithNamedRange(): void + { + $namedCell = 'Range1'; + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Cell coordinate string can not be a range of cells'); + + $worksheet = $this->spreadsheet->getActiveSheet(); + $worksheet->getCell($namedCell); + } + + public function testNamedRangeToArray(): void + { + $namedRange = 'Range1'; + + $worksheet = $this->spreadsheet->getActiveSheet(); + $rangeData = $worksheet->namedRangeToArray($namedRange); + self::assertSame([[1, 2, 3]], $rangeData); + } + + public function testInvalidNamedRangeToArray(): void + { + $namedRange = 'Range2'; + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Named Range {$namedRange} does not exist"); + + $worksheet = $this->spreadsheet->getActiveSheet(); + $rangeData = $worksheet->namedRangeToArray($namedRange); + self::assertSame([[1, 2, 3]], $rangeData); + } +} diff --git a/tests/PhpSpreadsheetTests/Worksheet/WorksheetTest.php b/tests/PhpSpreadsheetTests/Worksheet/WorksheetTest.php index 46c848ba..5377444d 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/WorksheetTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/WorksheetTest.php @@ -18,7 +18,7 @@ class WorksheetTest extends TestCase self::assertSame($testTitle, $worksheet->getTitle()); } - public function setTitleInvalidProvider() + public function setTitleInvalidProvider(): array { return [ [str_repeat('a', 32), 'Maximum 31 characters allowed in sheet title.'], @@ -76,7 +76,7 @@ class WorksheetTest extends TestCase self::assertSame($testCodeName, $worksheet->getCodeName()); } - public function setCodeNameInvalidProvider() + public function setCodeNameInvalidProvider(): array { return [ [str_repeat('a', 32), 'Maximum 31 characters allowed in sheet code name.'], @@ -132,7 +132,7 @@ class WorksheetTest extends TestCase self::assertSame('B2', $worksheet->getTopLeftCell()); } - public function extractSheetTitleProvider() + public function extractSheetTitleProvider(): array { return [ ['B2', '', '', 'B2'], @@ -274,7 +274,7 @@ class WorksheetTest extends TestCase self::assertSame($expectedData, $worksheet->toArray()); } - public function removeRowsProvider() + public function removeRowsProvider(): array { return [ 'Remove all rows except first one' => [ diff --git a/tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php b/tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php index d048183c..716a3dcc 100644 --- a/tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Csv/CsvEnclosureTest.php @@ -33,7 +33,7 @@ class CsvEnclosureTest extends Functional\AbstractFunctional $writer = new CsvWriter($spreadsheet); $writer->setDelimiter($delimiter); $writer->setEnclosure($enclosure); - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $filename = File::temporaryFilename(); $writer->save($filename); $filedata = file_get_contents($filename); $filedata = preg_replace('/\\r?\\n/', $delimiter, $filedata); @@ -64,7 +64,7 @@ class CsvEnclosureTest extends Functional\AbstractFunctional $writer->setDelimiter($delimiter); $writer->setEnclosure($enclosure); self::assertEquals('', $writer->getEnclosure()); - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $filename = File::temporaryFilename(); $writer->save($filename); $filedata = file_get_contents($filename); $filedata = preg_replace('/\\r?\\n/', $delimiter, $filedata); @@ -95,7 +95,7 @@ class CsvEnclosureTest extends Functional\AbstractFunctional $writer = new CsvWriter($spreadsheet); self::assertTrue($writer->getEnclosureRequired()); $writer->setEnclosureRequired(false)->setDelimiter($delimiter)->setEnclosure($enclosure); - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $filename = File::temporaryFilename(); $writer->save($filename); $filedata = file_get_contents($filename); $filedata = preg_replace('/\\r?\\n/', $delimiter, $filedata); @@ -149,7 +149,7 @@ class CsvEnclosureTest extends Functional\AbstractFunctional $writer = new CsvWriter($spreadsheet); self::assertTrue($writer->getEnclosureRequired()); $writer->setEnclosureRequired(false)->setDelimiter($delimiter)->setEnclosure($enclosure); - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $filename = File::temporaryFilename(); $writer->save($filename); $filedata = file_get_contents($filename); $filedata = preg_replace('/\\r/', '', $filedata); @@ -176,7 +176,7 @@ class CsvEnclosureTest extends Functional\AbstractFunctional $sheet->setCellValue('C1', '4'); $writer = new CsvWriter($spreadsheet); $writer->setEnclosureRequired(false)->setDelimiter($delimiter)->setEnclosure($enclosure); - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $filename = File::temporaryFilename(); $writer->save($filename); $reader = new CsvReader(); $reader->setDelimiter($delimiter); @@ -200,7 +200,7 @@ class CsvEnclosureTest extends Functional\AbstractFunctional $sheet->setCellValue('C1', '4'); $writer = new CsvWriter($spreadsheet); $writer->setDelimiter($delimiter)->setEnclosure($enclosure); - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $filename = File::temporaryFilename(); $writer->save($filename); $reader = new CsvReader(); $reader->setDelimiter($delimiter); diff --git a/tests/PhpSpreadsheetTests/Writer/Csv/CsvOutputEncodingTest.php b/tests/PhpSpreadsheetTests/Writer/Csv/CsvOutputEncodingTest.php new file mode 100644 index 00000000..81e4428e --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Csv/CsvOutputEncodingTest.php @@ -0,0 +1,31 @@ +getActiveSheet(); + $sheet->setCellValue('A1', 'こんにちは!'); + $sheet->setCellValue('B1', 'Hello!'); + + $writer = new CsvWriter($spreadsheet); + + $filename = File::temporaryFilename(); + $writer->setUseBOM(false); + $writer->setOutputEncoding('SJIS-win'); + $writer->save($filename); + $contents = file_get_contents($filename); + unlink($filename); + + // self::assertStringContainsString(mb_convert_encoding('こんにちは!', 'SJIS-win'), $contents); + self::assertStringContainsString("\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd\x81\x49", $contents); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Csv/CsvWriteTest.php b/tests/PhpSpreadsheetTests/Writer/Csv/CsvWriteTest.php index 7fe1902b..aa9a8fc3 100644 --- a/tests/PhpSpreadsheetTests/Writer/Csv/CsvWriteTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Csv/CsvWriteTest.php @@ -23,7 +23,7 @@ class CsvWriteTest extends Functional\AbstractFunctional $writer = new CsvWriter($spreadsheet); $writer->setSheetIndex(1); self::assertEquals(1, $writer->getSheetIndex()); - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $filename = File::temporaryFilename(); $writer->save($filename); $reader = new CsvReader(); $newspreadsheet = $reader->load($filename); diff --git a/tests/PhpSpreadsheetTests/Writer/Html/CallbackTest.php b/tests/PhpSpreadsheetTests/Writer/Html/CallbackTest.php index 94c201a7..ae71ca55 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/CallbackTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/CallbackTest.php @@ -42,7 +42,7 @@ EOF; self::assertEquals($html3, $html1); $writer->setEditHtmlCallback([$this, 'yellowBody']); - $oufil = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $oufil = File::temporaryFilename(); $writer->save($oufil); $html4 = file_get_contents($oufil); unlink($oufil); diff --git a/tests/PhpSpreadsheetTests/Writer/Html/HtmlCommentsTest.php b/tests/PhpSpreadsheetTests/Writer/Html/HtmlCommentsTest.php index 0d43d7eb..a4f474fa 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/HtmlCommentsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/HtmlCommentsTest.php @@ -8,9 +8,12 @@ use PhpOffice\PhpSpreadsheetTests\Functional; class HtmlCommentsTest extends Functional\AbstractFunctional { + /** + * @var Spreadsheet + */ private $spreadsheet; - public function providerCommentRichText() + public function providerCommentRichText(): array { $valueSingle = 'I am comment.'; $valueMulti = 'I am ' . PHP_EOL . 'multi-line' . PHP_EOL . 'comment.'; diff --git a/tests/PhpSpreadsheetTests/Writer/Html/HtmlNumberFormatTest.php b/tests/PhpSpreadsheetTests/Writer/Html/HtmlNumberFormatTest.php index 340e820b..199ab529 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/HtmlNumberFormatTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/HtmlNumberFormatTest.php @@ -10,10 +10,19 @@ use PhpOffice\PhpSpreadsheetTests\Functional; class HtmlNumberFormatTest extends Functional\AbstractFunctional { + /** + * @var string + */ private $currency; + /** + * @var string + */ private $decsep; + /** + * @var string + */ private $thosep; protected function setUp(): void @@ -172,7 +181,7 @@ class HtmlNumberFormatTest extends Functional\AbstractFunctional $this->writeAndReload($spreadsheet, 'Html'); } - public function providerNumberFormat() + public function providerNumberFormat(): array { return require __DIR__ . '/../../../data/Style/NumberFormat.php'; } @@ -208,7 +217,7 @@ class HtmlNumberFormatTest extends Functional\AbstractFunctional $this->writeAndReload($spreadsheet, 'Html'); } - public function providerNumberFormatDates() + public function providerNumberFormatDates(): array { return require __DIR__ . '/../../../data/Style/NumberFormatDates.php'; } diff --git a/tests/PhpSpreadsheetTests/Writer/Html/ImagesRootTest.php b/tests/PhpSpreadsheetTests/Writer/Html/ImagesRootTest.php index 6cc7f18f..40099177 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/ImagesRootTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/ImagesRootTest.php @@ -9,6 +9,9 @@ use PhpOffice\PhpSpreadsheetTests\Functional; class ImagesRootTest extends Functional\AbstractFunctional { + /** + * @var false|string + */ private $curdir; protected function setUp(): void diff --git a/tests/PhpSpreadsheetTests/Writer/Html/XssVulnerabilityTest.php b/tests/PhpSpreadsheetTests/Writer/Html/XssVulnerabilityTest.php index 48aced02..4d8b03a5 100644 --- a/tests/PhpSpreadsheetTests/Writer/Html/XssVulnerabilityTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Html/XssVulnerabilityTest.php @@ -10,7 +10,7 @@ use PhpOffice\PhpSpreadsheetTests\Functional; class XssVulnerabilityTest extends Functional\AbstractFunctional { - public function providerAcceptableMarkupRichText() + public function providerAcceptableMarkupRichText(): array { return [ 'basic text' => ['Hello, I am safely viewing your site', 'Hello, I am safely viewing your site'], @@ -37,17 +37,18 @@ class XssVulnerabilityTest extends Functional\AbstractFunctional ->getComment('A1') ->setText($richText); - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $filename = File::temporaryFilename(); $writer = IOFactory::createWriter($spreadsheet, 'Html'); $writer->save($filename); $verify = file_get_contents($filename); + unlink($filename); // Ensure that executable js has been stripped from the comments self::assertStringContainsString($adjustedTextString, $verify); } - public function providerXssRichText() + public function providerXssRichText(): array { return [ 'script tag' => ["Hello, I am trying to your site"], @@ -58,8 +59,6 @@ class XssVulnerabilityTest extends Functional\AbstractFunctional ]; } - private static $counter = 0; - /** * @dataProvider providerXssRichText * @@ -78,14 +77,13 @@ class XssVulnerabilityTest extends Functional\AbstractFunctional ->getComment('A1') ->setText($richText); - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $filename = File::temporaryFilename(); $writer = IOFactory::createWriter($spreadsheet, 'Html'); $writer->save($filename); $verify = file_get_contents($filename); - $counter = self::$counter++; - file_put_contents("verify{$counter}.html", $verify); + unlink($filename); // Ensure that executable js has been stripped from the comments self::assertStringNotContainsString($xssTextString, $verify); } diff --git a/tests/PhpSpreadsheetTests/Writer/Ods/ContentTest.php b/tests/PhpSpreadsheetTests/Writer/Ods/ContentTest.php index 4086914d..917a0410 100644 --- a/tests/PhpSpreadsheetTests/Writer/Ods/ContentTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Ods/ContentTest.php @@ -16,8 +16,15 @@ use PHPUnit\Framework\TestCase; class ContentTest extends TestCase { + /** + * @var string + */ private $samplesPath = 'tests/data/Writer/Ods'; + /** + * @var string + */ + /** * @var string */ @@ -59,6 +66,7 @@ class ContentTest extends TestCase $worksheet1->setCellValue('A2', true); // Boolean $worksheet1->setCellValue('B2', false); // Boolean + $worksheet1->setCellValueExplicit( 'C2', '=IF(A3, CONCATENATE(A1, " ", A2), CONCATENATE(A2, " ", A1))', @@ -70,6 +78,9 @@ class ContentTest extends TestCase ->getNumberFormat() ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME); + $worksheet1->setCellValueExplicit('F1', null, DataType::TYPE_ERROR); + $worksheet1->setCellValueExplicit('G1', 'Lorem ipsum', DataType::TYPE_INLINE); + // Styles $worksheet1->getStyle('A1')->getFont()->setBold(true); $worksheet1->getStyle('B1')->getFont()->setItalic(true); diff --git a/tests/PhpSpreadsheetTests/Writer/RetainSelectedCellsTest.php b/tests/PhpSpreadsheetTests/Writer/RetainSelectedCellsTest.php new file mode 100644 index 00000000..59f480fd --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/RetainSelectedCellsTest.php @@ -0,0 +1,77 @@ +getActiveSheet(); + $sheet->setCellValue('A1', '=SIN(1)') + ->setCellValue('A2', '=SIN(2)') + ->setCellValue('A3', '=SIN(3)') + ->setCellValue('B1', '=SIN(4)') + ->setCellValue('B2', '=SIN(5)') + ->setCellValue('B3', '=SIN(6)') + ->setCellValue('C1', '=SIN(7)') + ->setCellValue('C2', '=SIN(8)') + ->setCellValue('C3', '=SIN(9)'); + $sheet->setSelectedCell('A3'); + $sheet = $spreadsheet->createSheet(); + $sheet->setCellValue('A1', '=SIN(1)') + ->setCellValue('A2', '=SIN(2)') + ->setCellValue('A3', '=SIN(3)') + ->setCellValue('B1', '=SIN(4)') + ->setCellValue('B2', '=SIN(5)') + ->setCellValue('B3', '=SIN(6)') + ->setCellValue('C1', '=SIN(7)') + ->setCellValue('C2', '=SIN(8)') + ->setCellValue('C3', '=SIN(9)'); + $sheet->setSelectedCell('B1'); + $sheet = $spreadsheet->createSheet(); + $sheet->setCellValue('A1', '=SIN(1)') + ->setCellValue('A2', '=SIN(2)') + ->setCellValue('A3', '=SIN(3)') + ->setCellValue('B1', '=SIN(4)') + ->setCellValue('B2', '=SIN(5)') + ->setCellValue('B3', '=SIN(6)') + ->setCellValue('C1', '=SIN(7)') + ->setCellValue('C2', '=SIN(8)') + ->setCellValue('C3', '=SIN(9)'); + $sheet->setSelectedCell('C2'); + $spreadsheet->setActiveSheetIndex(1); + + $reloaded = $this->writeAndReload($spreadsheet, $format); + self::assertEquals('A3', $spreadsheet->getSheet(0)->getSelectedCells()); + self::assertEquals('B1', $spreadsheet->getSheet(1)->getSelectedCells()); + self::assertEquals('C2', $spreadsheet->getSheet(2)->getSelectedCells()); + self::assertEquals(1, $spreadsheet->getActiveSheetIndex()); + // SelectedCells and ActiveSheet don't make sense for Html, Csv. + if ($format === 'Xlsx' || $format === 'Xls' || $format === 'Ods') { + self::assertEquals('A3', $reloaded->getSheet(0)->getSelectedCells()); + self::assertEquals('B1', $reloaded->getSheet(1)->getSelectedCells()); + self::assertEquals('C2', $reloaded->getSheet(2)->getSelectedCells()); + self::assertEquals(1, $reloaded->getActiveSheetIndex()); + } + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/FormulaErrTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/FormulaErrTest.php index bbb00d89..3965db62 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xls/FormulaErrTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xls/FormulaErrTest.php @@ -19,7 +19,7 @@ class FormulaErrTest extends TestCase $sheet0->setCellValue('C1', '=DEFNAM=2'); $sheet0->setCellValue('D1', '=CONCAT("X",DEFNAM)'); $writer = IOFactory::createWriter($obj, 'Xls'); - $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $filename = File::temporaryFilename(); $writer->save($filename); $reader = IOFactory::createReader('Xls'); $robj = $reader->load($filename); diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php index 5ebe645f..160a665a 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php @@ -47,7 +47,7 @@ class WorkbookTest extends TestCase self::assertEquals($expectedResult, $palette); } - public function providerAddColor() + public function providerAddColor(): array { $this->setUp(); diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php index ceba7e54..03f201be 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php @@ -9,6 +9,9 @@ use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional; class XlsGifBmpTest extends AbstractFunctional { + /** + * @var string + */ private $filename = ''; protected function tearDown(): void diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php index d6ad77c6..88c63306 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php @@ -2,8 +2,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Settings; +use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional; class DrawingsTest extends AbstractFunctional @@ -11,7 +13,7 @@ class DrawingsTest extends AbstractFunctional /** * @var int */ - protected $prevValue; + private $prevValue; protected function setUp(): void { @@ -42,4 +44,35 @@ class DrawingsTest extends AbstractFunctional // Fake assert. The only thing we need is to ensure the file is loaded without exception self::assertNotNull($reloadedSpreadsheet); } + + /** + * Test save and load XLSX file with drawing with the same file name. + */ + public function testSaveLoadWithDrawingWithSamePath(): void + { + // Read spreadsheet from file + $originalFileName = 'tests/data/Writer/XLSX/saving_drawing_with_same_path.xlsx'; + + $originalFile = file_get_contents($originalFileName); + + $tempFileName = File::sysGetTempDir() . '/saving_drawing_with_same_path'; + + file_put_contents($tempFileName, $originalFile); + + $reader = new Xlsx(); + $spreadsheet = $reader->load($tempFileName); + + $spreadsheet->getActiveSheet()->setCellValue('D5', 'foo'); + // Save spreadsheet to file to the same path. Success test case won't + // throw exception here + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $writer->save($tempFileName); + + $reloadedSpreadsheet = $reader->load($tempFileName); + + unlink($tempFileName); + + // Fake assert. The only thing we need is to ensure the file is loaded without exception + self::assertNotNull($reloadedSpreadsheet); + } } diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php index 746b9846..22f3284b 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php @@ -18,7 +18,7 @@ class FloatsRetainedTest extends TestCase */ public function testIntyFloatsRetainedByWriter($value): void { - $outputFilename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $outputFilename = File::temporaryFilename(); Settings::setLibXmlLoaderOptions(null); $sheet = new Spreadsheet(); $sheet->getActiveSheet()->getCell('A1')->setValue($value); @@ -33,7 +33,7 @@ class FloatsRetainedTest extends TestCase self::assertSame($value, $sheet->getActiveSheet()->getCell('A1')->getValue()); } - public function providerIntyFloatsRetainedByWriter() + public function providerIntyFloatsRetainedByWriter(): array { return [ [-1.0], diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php index 373ef37f..3a9fb1cc 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php @@ -6,9 +6,15 @@ use PHPUnit\Framework\TestCase; class LocaleFloatsTest extends TestCase { - protected $localeAdjusted; + /** + * @var bool + */ + private $localeAdjusted; - protected $currentLocale; + /** + * @var false|string + */ + private $currentLocale; protected function setUp(): void { diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/StartsWithHashTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/StartsWithHashTest.php index d4fe5b22..826c482d 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/StartsWithHashTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/StartsWithHashTest.php @@ -15,7 +15,7 @@ class StartsWithHashTest extends TestCase { public function testStartWithHash(): void { - $outputFilename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $outputFilename = File::temporaryFilename(); Settings::setLibXmlLoaderOptions(null); $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); @@ -40,7 +40,7 @@ class StartsWithHashTest extends TestCase public function testStartWithHashReadRaw(): void { // Make sure raw data indicates A3 is an error, but A2 isn't. - $outputFilename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $outputFilename = File::temporaryFilename(); Settings::setLibXmlLoaderOptions(null); $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php index 660e40fe..def6f70e 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php @@ -15,7 +15,7 @@ class UnparsedDataCloneTest extends TestCase public function testLoadSaveXlsxWithUnparsedDataClone(): void { $sampleFilename = 'tests/data/Writer/XLSX/drawing_on_2nd_page.xlsx'; - $resultFilename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $resultFilename = File::temporaryFilename(); Settings::setLibXmlLoaderOptions(null); // reset to default options $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $spreadsheet = $reader->load($sampleFilename); @@ -60,8 +60,8 @@ class UnparsedDataCloneTest extends TestCase public function testSaveTwice(): void { $sampleFilename = 'tests/data/Writer/XLSX/drawing_on_2nd_page.xlsx'; - $resultFilename1 = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test1'); - $resultFilename2 = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test2'); + $resultFilename1 = File::temporaryFilename(); + $resultFilename2 = File::temporaryFilename(); self::assertNotEquals($resultFilename1, $resultFilename2); Settings::setLibXmlLoaderOptions(null); // reset to default options $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php index 4ea6f955..a2f21aef 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php @@ -16,7 +16,7 @@ class UnparsedDataTest extends TestCase public function testLoadSaveXlsxWithUnparsedData(): void { $sampleFilename = 'tests/data/Writer/XLSX/form_pass_print.xlsm'; - $resultFilename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $resultFilename = File::temporaryFilename(); Settings::setLibXmlLoaderOptions(null); // reset to default options $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $excel = $reader->load($sampleFilename); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 77cd5228..9ebd3f26 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -3,4 +3,4 @@ setlocale(LC_ALL, 'en_US.utf8'); // PHP 5.3 Compat -date_default_timezone_set('Europe/London'); +//date_default_timezone_set('Europe/London'); diff --git a/tests/data/Calculation/Calculation.php b/tests/data/Calculation/Calculation.php index 2f9e0a0c..e49bed9f 100644 --- a/tests/data/Calculation/Calculation.php +++ b/tests/data/Calculation/Calculation.php @@ -1,6 +1,6 @@ [ - 6890, // '11th November 1918' - 18, 11, 11, - ], - 'Excel 1900 Calendar Base Date' => [ - 1, - 1900, 1, 1, - ], - 'Day before Excel mythical 1900 leap day' => [ - 59, - 1900, 2, 28, - ], - 'Excel mythical 1900 leap day' => [ - 60, - 1900, 2, 29, - ], - 'Day after Excel mythical 1900 leap day' => [ - 61, - 1900, 3, 1, - ], - 'Day after Excel actual 1904 leap day' => [ - 713, - 1901, 12, 13, - ], - 'signed 32-bit Unix Timestamp Earliest Date' => [ - 714, - 1901, 12, 14, - ], - 'Day before Excel 1904 Calendar Base Date' => [ - 1461, - 1903, 12, 31, - ], - 'Excel 1904 Calendar Base Date' => [ - 1462, - 1904, 1, 1, - ], - 'Day after Excel 1904 Calendar Base Date' => [ - 1463, - 1904, 1, 2, - ], - [ - 22269, - 1960, 12, 19, - ], - 'Unix Timestamp Base Date' => [ - 25569, - 1970, 1, 1, - ], - [ - 30292, - 1982, 12, 7, - ], - [ - 39611, - 2008, 6, 12, - ], - '32-bit signed Unix Timestamp Latest Date' => [ - 50424, - 2038, 1, 19, - ], - 'Day after 32-bit signed Unix Timestamp Latest Date' => [ - 50425, - 2038, 1, 20, - ], - [ - 39448, - 2008, 1, 1, - ], - [ - 39447, - 2008, 1, null, - ], - [ - 39446, - 2008, 1, -1, - ], - [ - 39417, - 2008, 1, -30, - ], - [ - 39416, - 2008, 1, -31, - ], - [ - 39082, - 2008, 1, -365, - ], - [ - 39508, - 2008, 3, 1, - ], - [ - 39507, - 2008, 3, null, - ], - [ - 39506, - 2008, 3, -1, - ], - [ - 39142, - 2008, 3, -365, - ], - [ - 39417, - 2008, null, 1, - ], - [ - 39387, - 2008, -1, 1, - ], - [ - 39083, - 2008, -11, 1, - ], - [ - 39052, - 2008, -12, 1, - ], - [ - 39022, - 2008, -13, 1, - ], - [ - 39051, - 2008, -13, 30, - ], - [ - 39021, - 2008, -13, null, - ], - [ - 38991, - 2008, -13, -30, - ], - [ - 38990, - 2008, -13, -31, - ], - [ - 39814, - 2008, 13, 1, - ], - [ - 39507, - 2007, 15, null, - ], - [ - 40210, - 2008, 26, 1, - ], - [ - 40199, - 2008, 26, -10, - ], - [ - 38686, - 2008, -26, 61, - ], - [ - 39641, - 2010, -15, -50, - ], - [ - 39741, - 2010, -15, 50, - ], - [ - 40552, - 2010, 15, -50, - ], - [ - 40652, - 2010, 15, 50, - ], - [ - 40179, - 2010, 1.5, 1, - ], - [ - 40178, - 2010, 1.5, 0, - ], - [ - 40148, - 2010, 0, 1.5, - ], - [ - 40179, - 2010, 1, 1.5, - ], - [ - 41075, - 2012, 6, 15, - ], - [ - 41060, - 2012, 6, null, - ], - [ - 40892, - 2012, null, 15, - ], - [ - 167, - null, 6, 15, - ], - [ - 3819, - 10, 6, 15, - ], - [ - 3622, - 10, null, null, - ], - [ - 274, - null, 10, null, - ], - [ - '#NUM!', - null, null, 10, - ], - [ - '#NUM!', - -20, null, null, - ], - [ - '#NUM!', - -20, 6, 15, - ], - 'Excel Maximum Date' => [ - 2958465, - 9999, 12, 31, - ], - 'Exceeded Excel Maximum Date' => [ - '#NUM!', - 10000, 1, 1, - ], - [ - 39670, - 2008, 8, 10, - ], - [ - 39813, - 2008, 12, 31, - ], - [ - 39692, - 2008, 8, 32, - ], - [ - 39844, - 2008, 13, 31, - ], - [ - 39813, - 2009, 1, 0, - ], - [ - 39812, - 2009, 1, -1, - ], - [ - 39782, - 2009, 0, 0, - ], - [ - 39781, - 2009, 0, -1, - ], - [ - 39752, - 2009, -1, 0, - ], - [ - 39751, - 2009, -1, -1, - ], - [ - 40146, - 2010, 0, -1, - ], - [ - 40329, - 2010, 5, 31, - ], + [6890, '18, 11, 11'], // year without centure + [1, '1900, 1, 1'], // Excel 1900 Calendar BaseDate + [59, '1900, 2, 28'], // Day before Excel mythical 1900 leap day + [60, '1900, 2, 29'], // Excel mythical 1900 leap day + [61, '1900, 3, 1'], // Day after Excel mythical 1900 leap day + [713, '1901, 12, 13'], // Day after actual 1904 leap day + [714, '1901, 12, 14'], // signed 32-bit Unix Timestamp Earliest Date + [1461, '1903, 12, 31'], // Day before Excel 1904 Calendar Base Date + [1462, '1904, 1, 1'], // Excel 1904 Calendar Base Date + [1463, '1904, 1, 2'], // Day after Excel 1904 Calendar Base Date + [22269, '1960, 12, 19'], + [25569, '1970, 1, 1'], // Unix Timestamp Base Date + [30292, '1982, 12, 7'], + [39611, '2008, 6, 12'], + [50424, '2038, 1, 19'], // 32-bit signed Unix Timestamp Latest Date + [50425, '2038, 1, 20'], // Day after 32-bit signed Unix Timestamp Latest Date + [39448, '2008, 1, 1'], + [39447, '2008, 1, Q15'], + [39446, '2008, 1, -1'], + [39417, '2008, 1, -30'], + [39416, '2008, 1, -31'], + [39082, '2008, 1, -365'], + [39508, '2008, 3, 1'], + [39507, '2008, 3, Q15'], + [39506, '2008, 3, -1'], + [39142, '2008, 3, -365'], + [39417, '2008, Q15, 1'], + [39387, '2008, -1, 1'], + [39083, '2008, -11, 1'], + [39052, '2008, -12, 1'], + [39022, '2008, -13, 1'], + [39051, '2008, -13, 30'], + [39021, '2008, -13, Q15'], + [38991, '2008, -13, -30'], + [38990, '2008, -13, -31'], + [39814, '2008, 13, 1'], + [39507, '2007, 15, Q15'], + [40210, '2008, 26, 1'], + [40199, '2008, 26, -10'], + [38686, '2008, -26, 61'], + [39641, '2010, -15, -50'], + [39741, '2010, -15, 50'], + [40552, '2010, 15, -50'], + [40652, '2010, 15, 50'], + [40179, '2010, 1.5, 1'], + [40178, '2010, 1.5, 0'], + [40148, '2010, 0, 1.5'], + [40179, '2010, 1, 1.5'], + [41075, '2012, 6, 15'], + [41060, '2012, 6, Q15'], + [40892, '2012, Q15, 15'], + [167, 'Q15, 6, 15'], + [3819, '10, 6, 15'], + [3622, '10, Q15, Q16'], + [274, 'Q14, 10, Q15'], + ['#NUM!', 'Q14, Q15, 10'], + ['#NUM!', '-20, Q14, Q15'], + ['#NUM!', '-20, 6, 15'], + [2958465, '9999, 12, 31'], // Excel maximum date + ['#NUM!', '10000, 1, 1'], // Exceeded Excel maximum date + [39670, '2008, 8, 10'], + [39813, '2008, 12, 31'], + [39692, '2008, 8, 32'], + [39844, '2008, 13, 31'], + [39813, '2009, 1, 0'], + [39812, '2009, 1, -1'], + [39782, '2009, 0, 0'], + [39781, '2009, 0, -1'], + [39752, '2009, -1, 0'], + [39751, '2009, -1, -1'], + [40146, '2010, 0, -1'], + [40329, '2010, 5, 31'], + [40199, '2010, 1, "21st"'], // Excel can't parse ordinal, PhpSpreadsheet can + [40258, '2010, "March", "21st"'], // ordinal and month name // MS Excel will fail with a #VALUE return, but PhpSpreadsheet can parse this date - [ - 40199, - 2010, 1, '21st', - ], - // MS Excel will fail with a #VALUE return, but PhpSpreadsheet can parse this date - [ - 40258, - 2010, 'March', '21st', - ], - // MS Excel will fail with a #VALUE return, but PhpSpreadsheet can parse this date - [ - 40258, - 2010, 'March', 21, - ], - [ - '#VALUE!', - 'ABC', 1, 21, - ], - [ - '#VALUE!', - 2010, 'DEF', 21, - ], - [ - '#VALUE!', - 2010, 3, 'GHI', - ], + [40258, '2010, "March", 21'], // Excel can't parse month name, PhpSpreadsheet can + ['#VALUE!', '"ABC", 1, 21'], + ['#VALUE!', '2010, "DEF", 21'], + ['#VALUE!', '2010, 3, "GHI"'], + ['exception', '2010, 3'], ]; diff --git a/tests/data/Calculation/DateTime/DATEDIF.php b/tests/data/Calculation/DateTime/DATEDIF.php index a6d2d761..5ba0bd3c 100644 --- a/tests/data/Calculation/DateTime/DATEDIF.php +++ b/tests/data/Calculation/DateTime/DATEDIF.php @@ -1,424 +1,112 @@ = 2**48 + ['#NUM!', '1, power(2, 50)'], // argument >= 2**48 + ['#NUM!', '-2, 1'], // negative argument + ['#NUM!', '2, -1'], // negative argument + ['#NUM!', '-2, -1'], // negative argument + ['#NUM!', '3.1, 1'], // non-integer argument + ['#NUM!', '3, 1.1'], // non-integer argument + [0, '4, Q15'], + [0, '4, null'], + [0, '4, false'], + [1, '3, true'], + ['exception', ''], + ['exception', '2'], + [0, ', 4'], + [0, 'Q15, 4'], + [0, 'false, 4'], + [1, 'true, 5'], + [8, 'A2, 9'], ]; diff --git a/tests/data/Calculation/Engineering/BITLSHIFT.php b/tests/data/Calculation/Engineering/BITLSHIFT.php index ceaf9a33..fee922e4 100644 --- a/tests/data/Calculation/Engineering/BITLSHIFT.php +++ b/tests/data/Calculation/Engineering/BITLSHIFT.php @@ -1,20 +1,34 @@ 2**32 + [16000000000, '8000000000, 1'], // argument > 2**32 + ['#NUM!', 'power(2,50), 1'], // argument >= 2**48 + ['1', 'power(2, 47), -47'], ]; diff --git a/tests/data/Calculation/Engineering/BITOR.php b/tests/data/Calculation/Engineering/BITOR.php index 01640c9f..a98ce380 100644 --- a/tests/data/Calculation/Engineering/BITOR.php +++ b/tests/data/Calculation/Engineering/BITOR.php @@ -1,24 +1,34 @@ = 2**48 + ['#NUM!', '1, power(2, 50)'], // argument >= 2**48 + ['#NUM!', '-2, 1'], // negative argument + ['#NUM!', '2, -1'], // negative argument + ['#NUM!', '-2, -1'], // negative argument + ['#NUM!', '3.1, 1'], // non-integer argument + ['#NUM!', '3, 1.1'], // non-integer argument + [4, '4, Q15'], + [4, '4, null'], + [4, '4, false'], + [5, '4, true'], + ['exception', ''], + ['exception', '2'], + [4, ', 4'], + [4, 'Q15, 4'], + [4, 'false, 4'], + [5, 'true, 4'], + [9, 'A2, 1'], ]; diff --git a/tests/data/Calculation/Engineering/BITRSHIFT.php b/tests/data/Calculation/Engineering/BITRSHIFT.php index 78cacf37..343ccb5c 100644 --- a/tests/data/Calculation/Engineering/BITRSHIFT.php +++ b/tests/data/Calculation/Engineering/BITRSHIFT.php @@ -1,16 +1,35 @@ 2**32 + [8000000000, '16000000000, 1'], // argument > 2**32 + ['#NUM!', 'power(2,50), 1'], // argument >= 2**48 + ['1', 'power(2, 47), 47'], ]; diff --git a/tests/data/Calculation/Engineering/BITXOR.php b/tests/data/Calculation/Engineering/BITXOR.php index 40972c1c..836f327a 100644 --- a/tests/data/Calculation/Engineering/BITXOR.php +++ b/tests/data/Calculation/Engineering/BITXOR.php @@ -1,24 +1,32 @@ = 2**48 + ['#NUM!', '1, power(2, 50)'], // argument >= 2**48 + ['#NUM!', '-2, 1'], // negative argument + ['#NUM!', '2, -1'], // negative argument + ['#NUM!', '-2, -1'], // negative argument + ['#NUM!', '3.1, 1'], // non-integer argument + ['#NUM!', '3, 1.1'], // non-integer argument + [4, '4, Q15'], + [4, '4, null'], + [4, '4, false'], + [5, '4, true'], + ['exception', ''], + ['exception', '2'], + [4, ', 4'], + [4, 'Q15, 4'], + [4, 'false, 4'], + [5, 'true, 4'], + [9, 'A2, 1'], ]; diff --git a/tests/data/Calculation/Engineering/COMPLEX.php b/tests/data/Calculation/Engineering/COMPLEX.php index 6b3e137c..22e21a8f 100644 --- a/tests/data/Calculation/Engineering/COMPLEX.php +++ b/tests/data/Calculation/Engineering/COMPLEX.php @@ -1,5 +1,7 @@ [ + 1.942559385723E-03, + 1.0, + 'ozm', + 'sg', + ], + 'Same prefixed metric UoM' => [ + 5.0, + 5.0, + 'kg', + 'kg', + ], + 'Imperial to prefixed metric' => [ + 4.5359237E-01, 1.0, 'lbm', 'kg', ], - [ - 123.45, - 123.45, - 'kg', + 'Prefixed metric to prefixed metric, same unit' => [ + 0.2, + 2.0, + 'hg', 'kg', ], + 'Unprefixed metric to prefixed metric, same unit' => [ + 12.345000000000001, + 12345, + 'm', + 'km', + ], + 'Prefixed metric to unprefixed metric, same unit' => [ + 12345, + 12.345000000000001, + 'km', + 'm', + ], + 'Prefixed metric to imperial' => [ + 0.62137119223732995, + 1, + 'km', + 'mi', + ], + 'Prefixed metric to alternative metric' => [ + 1.23450000000000E+05, + 12.345, + 'um', + 'ang', + ], + 'Prefixed metric to alternative prefixed metric' => [ + 1.23450000000000E+02, + 12.345, + 'um', + 'kang', + ], + 'Prefixed metric to 2-character prefixed metric, same unit' => [ + 1000.0, + 100.0, + 'hl', + 'dal', + ], + 'Imperial to Imperial (distance)' => [ + 1.0, + 3.0, + 'ft', + 'yd', + ], [ 20, 68, @@ -38,111 +92,171 @@ return [ 'F', ], [ - 295.14999999999998, + -273.15, + 0, + 'K', + 'C', + ], + [ + -459.67, + 0, + 'K', + 'F', + ], + [ + 295.15, 22, 'C', 'K', ], [ 22.5, - 295.64999999999998, + 295.65, 'K', 'C', ], - [ - '#N/A', - 2.5, - 'ft', - 'sec', + 'Melting Point of Titanium (K to C)' => [ + 1667.85, + 1941, + 'K', + 'C', ], - [ - 12.345000000000001, - 12345, - 'm', - 'km', + 'Melting Point of Titanium (K to F)' => [ + 3034.13, + 1941, + 'K', + 'F', ], - [ - 12345, - 12.345000000000001, - 'km', - 'm', + 'Melting Point of Titanium (K to Rankine)' => [ + 3493.8, + 1941, + 'K', + 'Rank', ], - [ - 0.62137119223732995, - 1, - 'km', - 'mi', + 'Melting Point of Titanium (K to Réaumur)' => [ + 1334.28, + 1941, + 'K', + 'Reau', ], - [ - '#VALUE!', - 'three', - 'ft', - 'yds', + 'Melting Point of Titanium (Rankine to K)' => [ + 1941, + 3493.8, + 'Rank', + 'K', ], - [ + 'Melting Point of Titanium (Réaumur to K)' => [ + 1941, + 1334.28, + 'Reau', + 'K', + ], + 'Temperature synonyms (K)' => [ 123.45, 123.45, 'K', 'kel', ], - [ + 'Temperature synonyms (C)' => [ 123.45, 123.45, 'C', 'cel', ], - [ + 'Temperature synonyms (F)' => [ 123.45, 123.45, 'F', 'fah', ], - [ + 'Invalid value to convert' => [ + '#VALUE!', + 'three', + 'ft', + 'yds', + ], + 'Imperial to 2-character prefixed imperial, same unit' => [ + '#N/A', + 100.0, + 'pt', + 'dapt', + ], + 'Prefixed metric to binary prefixed metric' => [ + '#N/A', + 12.345, + 'um', + 'kiang', + ], + 'Mismatched categories' => [ '#N/A', 1, 'ft', 'day', ], - [ - 123.45, - 123.45, - 'm', - 'm', - ], - [ - 234.56, - 234.56, - 'km', - 'km', - ], - [ + 'From prefixed Imperial (Invalid)' => [ '#N/A', 234.56, 'kpt', 'lt', ], - [ - '#N/A', - 234.56, - 'sm', - 'm', - ], - [ + 'To prefixed Imperial (Invalid)' => [ '#N/A', 234.56, 'lt', 'kpt', ], - [ + 'From binary prefixed Imperial (Invalid)' => [ + '#N/A', + 234.56, + 'kiqt', + 'pt', + ], + 'To binary prefixed Imperial (Invalid)' => [ + '#N/A', + 234.56, + 'pt', + 'kiqt', + ], + 'From prefixed Imperial 2 (Invalid)' => [ + '#N/A', + 12345.6, + 'baton', + 'cwt', + ], + 'To prefixed Imperial 2 (Invalid)' => [ + '#N/A', + 12345.6, + 'cwt', + 'baton', + ], + 'Invalid from unit' => [ + '#N/A', + 234.56, + 'xxxx', + 'm', + ], + 'Invalid to unit' => [ '#N/A', 234.56, 'm', - 'sm', + 'xxxx', ], - [ - 12345000, - 12.345000000000001, - 'km', - 'mm', + 'Basic Information conversion' => [ + 2, + 16, + 'bit', + 'byte', + ], + 'Information with standard metric prefix' => [ + 1000, + 1, + 'kbyte', + 'byte', + ], + 'Information with binary prefix' => [ + 1024, + 1, + 'kibyte', + 'byte', ], ]; diff --git a/tests/data/Calculation/Engineering/DEC2BIN.php b/tests/data/Calculation/Engineering/DEC2BIN.php index a88b69a8..317c9b5b 100644 --- a/tests/data/Calculation/Engineering/DEC2BIN.php +++ b/tests/data/Calculation/Engineering/DEC2BIN.php @@ -1,91 +1,36 @@ [ '#VALUE!', - '2008-03-01', - '2008-08-31', - '2008-05-01', - 0.10000000000000001, - 1000, - 2, - 'ABC', + '2008-03-01', '2008-08-31', '2008-05-01', 'NaN', 1000, 2, 0, + ], + 'Invalid Rate' => [ + '#NUM!', + '2008-03-01', '2008-08-31', '2008-05-01', -0.10, 1000, 2, 0, + ], + 'Non-numeric Par Value' => [ + '#VALUE!', + '2008-03-01', '2008-08-31', '2008-05-01', 0.10, 'NaN', 2, 0, + ], + 'Invalid Par Value' => [ + '#NUM!', + '2008-03-01', '2008-08-31', '2008-05-01', 0.10, -1000, 2, 0, + ], + 'Non-numeric Frequency' => [ + '#VALUE!', + '2008-03-01', '2008-08-31', '2008-05-01', 0.10, 1000, 'NaN', 0, + ], + 'Invalid Frequency' => [ + '#NUM!', + '2008-03-01', '2008-08-31', '2008-05-01', 0.10, -1000, 3, 0, + ], + 'Non-numeric Basis' => [ + '#VALUE!', + '2008-03-01', '2008-08-31', '2008-05-01', 0.10, 1000, 2, 'ABC', + ], + 'Invalid Basis' => [ + '#NUM!', + '2008-03-01', '2008-08-31', '2008-05-01', 0.10, 1000, 2, -2, ], ]; diff --git a/tests/data/Calculation/Financial/ACCRINTM.php b/tests/data/Calculation/Financial/ACCRINTM.php index 7949b1ad..444e41f9 100644 --- a/tests/data/Calculation/Financial/ACCRINTM.php +++ b/tests/data/Calculation/Financial/ACCRINTM.php @@ -5,41 +5,54 @@ return [ [ 20.547945205478999, - '2008-04-01', - '2008-06-15', - 0.10000000000000001, - 1000, - 3, + '2008-04-01', '2008-06-15', 0.10, 1000, 3, ], [ 800, - '2010-01-01', - '2010-12-31', - 0.080000000000000002, - 10000, + '2010-01-01', '2010-12-31', 0.08, 10000, + ], + [ + 800, + '2010-01-01', '2010-12-31', 0.08, 10000, null, + ], + [ + 365.958904109589, + '2012-01-01', '2013-02-15', 0.065, 5000, 3, + ], + [ + 73.1917808219178, + '2012-01-01', '2013-02-15', 0.065, 1000, 3, ], [ '#NUM!', - '2008-03-05', - '2008-08-31', - -0.10000000000000001, - 1000, - 2, + '2008-03-05', '2008-08-31', -0.10, 1000, 2, ], [ '#VALUE!', - 'Invalid Date', - '2008-08-31', - 0.10000000000000001, - 1000, - 2, + 'Invalid Date', '2008-08-31', 0.10, 1000, 2, ], - [ + 'Non-numeric Rate' => [ '#VALUE!', - '2008-03-01', - '2008-08-31', - 'ABC', - 1000, - 2, + '2008-03-01', '2008-08-31', 'NaN', 1000, 2, + ], + 'Invalid Rate' => [ + '#NUM!', + '2008-03-01', '2008-08-31', -0.10, 1000, 2, + ], + 'Non-numeric Par Value' => [ + '#VALUE!', + '2008-03-01', '2008-08-31', 0.10, 'NaN', 2, + ], + 'Invalid Par Value' => [ + '#NUM!', + '2008-03-01', '2008-08-31', 0.10, -1000, 2, + ], + 'Non-numeric Basis' => [ + '#VALUE!', + '2008-03-01', '2008-08-31', 0.10, 1000, 'NaN', + ], + 'Invalid Basis' => [ + '#NUM!', + '2008-03-01', '2008-08-31', 0.10, 1000, 99, ], ]; diff --git a/tests/data/Calculation/Financial/AMORDEGRC.php b/tests/data/Calculation/Financial/AMORDEGRC.php index 1e959141..fa546c8c 100644 --- a/tests/data/Calculation/Financial/AMORDEGRC.php +++ b/tests/data/Calculation/Financial/AMORDEGRC.php @@ -5,22 +5,94 @@ return [ [ 776, - 2400, - '2008-08-19', - '2008-12-31', - 300, - 1, - 0.14999999999999999, - 1, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.15, 1, + ], + [ + 776, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.15, 0, + ], + [ + 776, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.15, null, + ], + [ + 820, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.2, 1, + ], + [ + 492, + 2400, '2008-08-19', '2008-12-31', 300, 2, 0.2, 1, + ], + [ + 886, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.22, 1, + ], + [ + 949, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.24, 1, + ], + [ + 494, + 2400, '2008-08-19', '2008-12-31', 300, 2, 0.24, 1, + ], + [ + 902, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.3, 1, ], [ 42, - 150, - '2011-01-01', - '2011-09-30', - 20, - 1, - 0.20000000000000001, - 4, + 150, '2011-01-01', '2011-09-30', 20, 1, 0.2, 4, + ], + [ + 25, + 150, '2011-01-01', '2011-09-30', 20, 2, 0.2, 4, + ], + [ + 16, + 150, '2011-01-01', '2011-09-30', 20, 3, 0.15, 4, + ], + [ + 42, + 150, '2011-01-01', '2011-09-30', 20, 1, 0.4, 4, + ], + [ + 2813, + 10000, '2012-03-01', '2012-12-31', 1500, 1, 0.3, 1, + ], + [ + 0.0, + 500, '2012-03-01', '2012-12-31', 500, 3, 0.3, 1, + ], + [ + '#VALUE!', + 'NaN', '2012-03-01', '2020-12-25', 20, 1, 0.2, 4, + ], + [ + '#VALUE!', + 550, 'notADate', '2020-12-25', 20, 1, 0.2, 4, + ], + [ + '#VALUE!', + 550, '2011-01-01', 'notADate', 20, 1, 0.2, 4, + ], + [ + '#VALUE!', + 550, '2012-03-01', '2020-12-25', 'NaN', 1, 0.2, 4, + ], + [ + '#VALUE!', + 550, '2012-03-01', '2020-12-25', 20, 'NaN', 0.2, 4, + ], + [ + '#VALUE!', + 550, '2012-03-01', '2020-12-25', 20, 1, 'NaN', 4, + ], + [ + '#VALUE!', + 550, '2012-03-01', '2020-12-25', 20, 1, 0.2, 'NaN', + ], + [ + '#NUM!', + 550, '2012-03-01', '2020-12-25', 20, 1, 0.2, 99, ], ]; diff --git a/tests/data/Calculation/Financial/AMORLINC.php b/tests/data/Calculation/Financial/AMORLINC.php index 92adbe13..c0175536 100644 --- a/tests/data/Calculation/Financial/AMORLINC.php +++ b/tests/data/Calculation/Financial/AMORLINC.php @@ -5,22 +5,66 @@ return [ [ 360, - 2400, - '2008-08-19', - '2008-12-31', - 300, - 1, - 0.14999999999999999, - 1, + 2400, '2008-08-19', '2008-12-31', 300, 1, 0.15, 1, + ], + [ + 576, + 2400, '2008-08-19', '2008-12-31', 300, 2, 0.24, 1, + ], + [ + 576, + 2400, '2008-08-19', '2008-12-31', 300, 2, 0.24, 0, + ], + [ + 576, + 2400, '2008-08-19', '2008-12-31', 300, 2, 0.24, null, ], [ 30, - 150, - '2011-01-01', - '2011-09-30', - 20, - 1, - 0.20000000000000001, - 4, + 150, '2011-01-01', '2011-09-30', 20, 1, 0.2, 4, + ], + [ + 22.41666667, + 150, '2011-01-01', '2011-09-30', 20, 0, 0.2, 4, + ], + [ + 17.58333333, + 150, '2011-01-01', '2011-09-30', 20, 4, 0.2, 4, + ], + [ + 0.0, + 150, '2011-01-01', '2011-09-30', 20, 5, 0.2, 4, + ], + [ + '#VALUE!', + 'NaN', '2011-01-01', '2011-09-30', 20, 1, 0.2, 4, + ], + [ + '#VALUE!', + 150, '2011-01-01', 'notADate', 20, 1, 0.2, 4, + ], + [ + '#VALUE!', + 150, 'notADate', '2011-09-30', 20, 1, 0.2, 4, + ], + [ + '#VALUE!', + 150, '2011-01-01', '2011-09-30', 'NaN', 1, 0.2, 4, + ], + [ + '#VALUE!', + 150, '2011-01-01', '2011-09-30', 20, 'NaN', 0.2, 4, + ], + [ + '#VALUE!', + 150, '2011-01-01', '2011-09-30', 20, 1, 'NaN', 4, + ], + [ + '#VALUE!', + 150, '2011-01-01', '2011-09-30', 20, 1, 0.2, 'NaN', + ], + [ + '#NUM!', + 550, '2012-03-01', '2020-12-25', 20, 1, 0.2, 99, ], ]; diff --git a/tests/data/Calculation/Financial/COUPDAYBS.php b/tests/data/Calculation/Financial/COUPDAYBS.php index ea93d427..260783fa 100644 --- a/tests/data/Calculation/Financial/COUPDAYBS.php +++ b/tests/data/Calculation/Financial/COUPDAYBS.php @@ -16,6 +16,20 @@ return [ '2012-10-25', 4, ], + [ + 66, + '2011-01-01', + '2012-10-25', + 4, + null, + ], + [ + 71, + '2011-01-25', + '2011-11-15', + 2, + 1, + ], [ '#VALUE!', 'Invalid Date', @@ -30,11 +44,165 @@ return [ 2, 1, ], - [ + 'Invalid Frequency' => [ '#NUM!', '25-Jan-2007', '15-Nov-2008', 3, 1, ], + 'Non-Numeric Frequency' => [ + '#VALUE!', + '25-Jan-2007', + '15-Nov-2008', + 'NaN', + 1, + ], + 'Invalid Basis' => [ + '#NUM!', + '25-Jan-2007', + '15-Nov-2008', + 4, + -1, + ], + 'Non-Numeric Basis' => [ + '#VALUE!', + '25-Jan-2007', + '15-Nov-2008', + 4, + 'NaN', + ], + 'Same Date' => [ + '#NUM!', + '24-Dec-2000', + '24-Dec-2000', + 4, + 0, + ], + [ + 311, + '31-Jan-2021', + '20-Mar-2021', + 1, + 0, + ], + [ + 317, + '31-Jan-2021', + '20-Mar-2021', + 1, + 1, + ], + [ + 317, + '31-Jan-2020', + '20-Mar-2021', + 1, + 1, + ], + [ + 317, + '31-Jan-2021', + '20-Mar-2021', + 1, + 2, + ], + [ + 317, + '31-Jan-2021', + '20-Mar-2021', + 1, + 3, + ], + [ + 310, + '31-Jan-2021', + '20-Mar-2021', + 1, + 4, + ], + [ + 131, + '31-Jan-2021', + '20-Mar-2021', + 2, + 0, + ], + [ + 133, + '31-Jan-2021', + '20-Mar-2021', + 2, + 1, + ], + [ + 133, + '31-Jan-2020', + '20-Mar-2021', + 2, + 1, + ], + [ + 133, + '31-Jan-2021', + '20-Mar-2021', + 2, + 2, + ], + [ + 133, + '31-Jan-2021', + '20-Mar-2021', + 2, + 3, + ], + [ + 130, + '31-Jan-2021', + '20-Mar-2021', + 2, + 4, + ], + [ + 41, + '31-Jan-2021', + '20-Mar-2021', + 4, + 0, + ], + [ + 42, + '31-Jan-2021', + '20-Mar-2021', + 4, + 1, + ], + [ + 42, + '31-Jan-2020', + '20-Mar-2021', + 4, + 1, + ], + [ + 42, + '31-Jan-2021', + '20-Mar-2021', + 4, + 2, + ], + [ + 42, + '31-Jan-2021', + '20-Mar-2021', + 4, + 3, + ], + [ + 40, + '31-Jan-2021', + '20-Mar-2021', + 4, + 4, + ], ]; diff --git a/tests/data/Calculation/Financial/COUPDAYS.php b/tests/data/Calculation/Financial/COUPDAYS.php index 384df0f1..c72d26c1 100644 --- a/tests/data/Calculation/Financial/COUPDAYS.php +++ b/tests/data/Calculation/Financial/COUPDAYS.php @@ -16,6 +16,13 @@ return [ '2012-10-25', 4, ], + [ + 90, + '2011-01-01', + '2012-10-25', + 4, + null, + ], [ 182.5, '25-Jan-2007', @@ -51,11 +58,165 @@ return [ 2, 1, ], - [ + 'Invalid Frequency' => [ '#NUM!', '25-Jan-2007', '15-Nov-2008', 3, 1, ], + 'Non-Numeric Frequency' => [ + '#VALUE!', + '25-Jan-2007', + '15-Nov-2008', + 'NaN', + 1, + ], + 'Invalid Basis' => [ + '#NUM!', + '25-Jan-2007', + '15-Nov-2008', + 4, + -1, + ], + 'Non-Numeric Basis' => [ + '#VALUE!', + '25-Jan-2007', + '15-Nov-2008', + 4, + 'NaN', + ], + 'Same Date' => [ + '#NUM!', + '24-Dec-2000', + '24-Dec-2000', + 4, + 0, + ], + [ + 360, + '31-Jan-2021', + '20-Mar-2021', + 1, + 0, + ], + [ + 365, + '31-Jan-2021', + '20-Mar-2021', + 1, + 1, + ], + [ + 366, + '31-Jan-2020', + '20-Mar-2021', + 1, + 1, + ], + [ + 360, + '31-Jan-2021', + '20-Mar-2021', + 1, + 2, + ], + [ + 365, + '31-Jan-2021', + '20-Mar-2021', + 1, + 3, + ], + [ + 360, + '31-Jan-2021', + '20-Mar-2021', + 1, + 4, + ], + [ + 180, + '31-Jan-2021', + '20-Mar-2021', + 2, + 0, + ], + [ + 181, + '31-Jan-2021', + '20-Mar-2021', + 2, + 1, + ], + [ + 182, + '31-Jan-2020', + '20-Mar-2021', + 2, + 1, + ], + [ + 180, + '31-Jan-2021', + '20-Mar-2021', + 2, + 2, + ], + [ + 182.5, + '31-Jan-2021', + '20-Mar-2021', + 2, + 3, + ], + [ + 180, + '31-Jan-2021', + '20-Mar-2021', + 2, + 4, + ], + [ + 90, + '31-Jan-2021', + '20-Mar-2021', + 4, + 0, + ], + [ + 90, + '31-Jan-2021', + '20-Mar-2021', + 4, + 1, + ], + [ + 91, + '31-Jan-2020', + '20-Mar-2021', + 4, + 1, + ], + [ + 90, + '31-Jan-2021', + '20-Mar-2021', + 4, + 2, + ], + [ + 91.25, + '31-Jan-2021', + '20-Mar-2021', + 4, + 3, + ], + [ + 90, + '31-Jan-2021', + '20-Mar-2021', + 4, + 4, + ], ]; diff --git a/tests/data/Calculation/Financial/COUPDAYSNC.php b/tests/data/Calculation/Financial/COUPDAYSNC.php index 2b249cb8..ca611475 100644 --- a/tests/data/Calculation/Financial/COUPDAYSNC.php +++ b/tests/data/Calculation/Financial/COUPDAYSNC.php @@ -16,6 +16,13 @@ return [ '2012-10-25', 4, ], + [ + 24, + '2011-01-01', + '2012-10-25', + 4, + null, + ], [ '#VALUE!', 'Invalid Date', @@ -30,11 +37,186 @@ return [ 2, 1, ], - [ + 'Invalid Frequency' => [ '#NUM!', '25-Jan-2007', '15-Nov-2008', 3, 1, ], + 'Non-Numeric Frequency' => [ + '#VALUE!', + '25-Jan-2007', + '15-Nov-2008', + 'NaN', + 1, + ], + 'Invalid Basis' => [ + '#NUM!', + '25-Jan-2007', + '15-Nov-2008', + 4, + -1, + ], + 'Non-Numeric Basis' => [ + '#VALUE!', + '25-Jan-2007', + '15-Nov-2008', + 4, + 'NaN', + ], + 'Same Date' => [ + '#NUM!', + '24-Dec-2000', + '24-Dec-2000', + 4, + 0, + ], + [ + 49, + '31-Jan-2021', + '20-Mar-2021', + 1, + 0, + ], + [ + 49, + '01-Feb-2021', + '20-Mar-2021', + 1, + 0, + ], + [ + 48, + '31-Jan-2021', + '20-Mar-2021', + 1, + 1, + ], + [ + 49, + '31-Jan-2020', + '20-Mar-2021', + 1, + 1, + ], + [ + 48, + '31-Jan-2021', + '20-Mar-2021', + 1, + 2, + ], + [ + 48, + '31-Jan-2021', + '20-Mar-2021', + 1, + 3, + ], + [ + 50, + '31-Jan-2021', + '20-Mar-2021', + 1, + 4, + ], + [ + 49, + '31-Jan-2021', + '20-Mar-2021', + 2, + 0, + ], + [ + 49, + '01-Feb-2021', + '20-Mar-2021', + 2, + 0, + ], + [ + 48, + '31-Jan-2021', + '20-Mar-2021', + 2, + 1, + ], + [ + 49, + '31-Jan-2020', + '20-Mar-2021', + 2, + 1, + ], + [ + 48, + '31-Jan-2021', + '20-Mar-2021', + 2, + 2, + ], + [ + 48, + '31-Jan-2021', + '20-Mar-2021', + 2, + 3, + ], + [ + 50, + '31-Jan-2021', + '20-Mar-2021', + 2, + 4, + ], + [ + 49, + '31-Jan-2021', + '20-Mar-2021', + 4, + 0, + ], + [ + 49, + '01-Feb-2021', + '20-Mar-2021', + 4, + 0, + ], + [ + 48, + '31-Jan-2021', + '20-Mar-2021', + 4, + 1, + ], + [ + 49, + '31-Jan-2020', + '20-Mar-2021', + 4, + 1, + ], + [ + 48, + '31-Jan-2021', + '20-Mar-2021', + 4, + 2, + ], + [ + 48, + '31-Jan-2021', + '20-Mar-2021', + 4, + 3, + ], + [ + 50, + '31-Jan-2021', + '20-Mar-2021', + 4, + 4, + ], ]; diff --git a/tests/data/Calculation/Financial/COUPNCD.php b/tests/data/Calculation/Financial/COUPNCD.php index eea6ad13..8dee4c0a 100644 --- a/tests/data/Calculation/Financial/COUPNCD.php +++ b/tests/data/Calculation/Financial/COUPNCD.php @@ -16,6 +16,13 @@ return [ '2012-10-25', 4, ], + [ + 40568, + '2011-01-01', + '2012-10-25', + 4, + null, + ], [ '#VALUE!', 'Invalid Date', @@ -30,14 +37,35 @@ return [ 2, 1, ], - [ + 'Invalid Frequency' => [ '#NUM!', '25-Jan-2007', '15-Nov-2008', 3, 1, ], - [ + 'Non-Numeric Frequency' => [ + '#VALUE!', + '25-Jan-2007', + '15-Nov-2008', + 'NaN', + 1, + ], + 'Invalid Basis' => [ + '#NUM!', + '25-Jan-2007', + '15-Nov-2008', + 4, + -1, + ], + 'Non-Numeric Basis' => [ + '#VALUE!', + '25-Jan-2007', + '15-Nov-2008', + 4, + 'NaN', + ], + 'Same Date' => [ '#NUM!', '24-Dec-2000', '24-Dec-2000', @@ -65,4 +93,130 @@ return [ 4, 0, ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 1, + 0, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 1, + 1, + ], + [ + 43910, + '31-Jan-2020', + '20-Mar-2021', + 1, + 1, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 1, + 2, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 1, + 3, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 1, + 4, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 2, + 0, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 2, + 1, + ], + [ + 43910, + '31-Jan-2020', + '20-Mar-2021', + 2, + 1, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 2, + 2, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 2, + 3, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 2, + 4, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 4, + 0, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 4, + 1, + ], + [ + 43910, + '31-Jan-2020', + '20-Mar-2021', + 4, + 1, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 4, + 2, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 4, + 3, + ], + [ + 44275, + '31-Jan-2021', + '20-Mar-2021', + 4, + 4, + ], ]; diff --git a/tests/data/Calculation/Financial/COUPNUM.php b/tests/data/Calculation/Financial/COUPNUM.php index 719ad733..4cf0cc29 100644 --- a/tests/data/Calculation/Financial/COUPNUM.php +++ b/tests/data/Calculation/Financial/COUPNUM.php @@ -17,6 +17,13 @@ return [ 4, 0, ], + [ + 8, + '2011-01-01', + '2012-10-25', + 4, + null, + ], [ '#VALUE!', 'Invalid Date', @@ -31,13 +38,41 @@ return [ 2, 1, ], - [ + 'Invalid Frequency' => [ '#NUM!', '25-Jan-2007', '15-Nov-2008', 3, 1, ], + 'Non-Numeric Frequency' => [ + '#VALUE!', + '25-Jan-2007', + '15-Nov-2008', + 'NaN', + 1, + ], + 'Invalid Basis' => [ + '#NUM!', + '25-Jan-2007', + '15-Nov-2008', + 4, + -1, + ], + 'Non-Numeric Basis' => [ + '#VALUE!', + '25-Jan-2007', + '15-Nov-2008', + 4, + 'NaN', + ], + 'Same Date' => [ + '#NUM!', + '24-Dec-2000', + '24-Dec-2000', + 4, + 0, + ], [ 5, '01-Jan-2008', @@ -108,4 +143,130 @@ return [ 2, 4, ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 1, + 0, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 1, + 1, + ], + [ + 2, + '31-Jan-2020', + '20-Mar-2021', + 1, + 1, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 1, + 2, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 1, + 3, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 1, + 4, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 2, + 0, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 2, + 1, + ], + [ + 3, + '31-Jan-2020', + '20-Mar-2021', + 2, + 1, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 2, + 2, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 2, + 3, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 2, + 4, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 4, + 0, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 4, + 1, + ], + [ + 5, + '31-Jan-2020', + '20-Mar-2021', + 4, + 1, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 4, + 2, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 4, + 3, + ], + [ + 1, + '31-Jan-2021', + '20-Mar-2021', + 4, + 4, + ], ]; diff --git a/tests/data/Calculation/Financial/COUPPCD.php b/tests/data/Calculation/Financial/COUPPCD.php index 911637d6..e906f147 100644 --- a/tests/data/Calculation/Financial/COUPPCD.php +++ b/tests/data/Calculation/Financial/COUPPCD.php @@ -16,6 +16,13 @@ return [ '2012-10-25', 4, ], + [ + 40476, + '2011-01-01', + '2012-10-25', + 4, + null, + ], [ '#VALUE!', 'Invalid Date', @@ -30,14 +37,35 @@ return [ 2, 1, ], - [ + 'Invalid Frequency' => [ '#NUM!', '25-Jan-2007', '15-Nov-2008', 3, 1, ], - [ + 'Non-Numeric Frequency' => [ + '#VALUE!', + '25-Jan-2007', + '15-Nov-2008', + 'NaN', + 1, + ], + 'Invalid Basis' => [ + '#NUM!', + '25-Jan-2007', + '15-Nov-2008', + 4, + -1, + ], + 'Non-Numeric Basis' => [ + '#VALUE!', + '25-Jan-2007', + '15-Nov-2008', + 4, + 'NaN', + ], + 'Same Date' => [ '#NUM!', '24-Dec-2000', '24-Dec-2000', @@ -65,4 +93,130 @@ return [ 4, 0, ], + [ + 43910, + '31-Jan-2021', + '20-Mar-2021', + 1, + 0, + ], + [ + 43910, + '31-Jan-2021', + '20-Mar-2021', + 1, + 1, + ], + [ + 43544, + '31-Jan-2020', + '20-Mar-2021', + 1, + 1, + ], + [ + 43910, + '31-Jan-2021', + '20-Mar-2021', + 1, + 2, + ], + [ + 43910, + '31-Jan-2021', + '20-Mar-2021', + 1, + 3, + ], + [ + 43910, + '31-Jan-2021', + '20-Mar-2021', + 1, + 4, + ], + [ + 44094, + '31-Jan-2021', + '20-Mar-2021', + 2, + 0, + ], + [ + 44094, + '31-Jan-2021', + '20-Mar-2021', + 2, + 1, + ], + [ + 43728, + '31-Jan-2020', + '20-Mar-2021', + 2, + 1, + ], + [ + 44094, + '31-Jan-2021', + '20-Mar-2021', + 2, + 2, + ], + [ + 44094, + '31-Jan-2021', + '20-Mar-2021', + 2, + 3, + ], + [ + 44094, + '31-Jan-2021', + '20-Mar-2021', + 2, + 4, + ], + [ + 44185, + '31-Jan-2021', + '20-Mar-2021', + 4, + 0, + ], + [ + 44185, + '31-Jan-2021', + '20-Mar-2021', + 4, + 1, + ], + [ + 43819, + '31-Jan-2020', + '20-Mar-2021', + 4, + 1, + ], + [ + 44185, + '31-Jan-2021', + '20-Mar-2021', + 4, + 2, + ], + [ + 44185, + '31-Jan-2021', + '20-Mar-2021', + 4, + 3, + ], + [ + 44185, + '31-Jan-2021', + '20-Mar-2021', + 4, + 4, + ], ]; diff --git a/tests/data/Calculation/Financial/CUMIPMT.php b/tests/data/Calculation/Financial/CUMIPMT.php index bc14d92a..2ccc5ff2 100644 --- a/tests/data/Calculation/Financial/CUMIPMT.php +++ b/tests/data/Calculation/Financial/CUMIPMT.php @@ -5,7 +5,7 @@ return [ [ -11135.232130750999, - 0.0074999999999999997, + 0.0075, 360, 125000, 13, @@ -14,7 +14,7 @@ return [ ], [ -937.5, - 0.0074999999999999997, + 0.0075, 360, 125000, 1, @@ -22,8 +22,17 @@ return [ 0, ], [ - -2294.9775375120998, - 0.0041666666669999998, + -937.5, + 0.0075, + 360, + 125000, + 1, + 1, + null, + ], + [ + -2299.6141712553544, + 0.004175, 60, 50000, 1, @@ -31,8 +40,8 @@ return [ 0, ], [ - -1833.1000667254, - 0.0041666666669999998, + -1836.8883999349455, + 0.004175, 60, 50000, 13, @@ -40,8 +49,8 @@ return [ 0, ], [ - -1347.5920679425001, - 0.0041666666669999998, + -1350.4402595996685, + 0.004175, 60, 50000, 25, @@ -49,8 +58,8 @@ return [ 0, ], [ - -837.24455850309005, - 0.0041666666669999998, + -839.0535854569902, + 0.004175, 60, 50000, 37, @@ -58,8 +67,8 @@ return [ 0, ], [ - -300.78670189938998, - 0.0041666666669999998, + -301.4498641014851, + 0.004175, 60, 50000, 49, @@ -68,7 +77,7 @@ return [ ], [ '#VALUE!', - 0.0074999999999999997, + 'NaN', 360, 125000, 24, @@ -76,12 +85,66 @@ return [ 0, ], [ - '#NUM!', - 0.0074999999999999997, + '#VALUE!', + 0.0075, + 'NaN', + 125000, + 24, + 13, + 0, + ], + [ + '#VALUE!', + 0.0075, + 360, + 'NaN', + 24, + 13, + 0, + ], + [ + '#VALUE!', + 0.0075, + 360, + 125000, + 'NaN', + 13, + 0, + ], + [ + '#VALUE!', + 0.0075, + 360, + 125000, + 24, + 'NaN', + 0, + ], + [ + '#VALUE!', + 0.0075, 360, 125000, 24, 13, + 'NaN', + ], + [ + '#NUM!', + 0.0075, + 360, + 125000, + 13, + 24, 2, ], + [ + '#NUM!', + 0.0075, + 10, + 125000, + 24, + 13, + 0, + ], ]; diff --git a/tests/data/Calculation/Financial/CUMPRINC.php b/tests/data/Calculation/Financial/CUMPRINC.php index 63392c52..e14d5adc 100644 --- a/tests/data/Calculation/Financial/CUMPRINC.php +++ b/tests/data/Calculation/Financial/CUMPRINC.php @@ -5,7 +5,7 @@ return [ [ -934.10712342088004, - 0.0074999999999999997, + 0.0075, 360, 125000, 13, @@ -14,7 +14,7 @@ return [ ], [ -68.278271180977001, - 0.0074999999999999997, + 0.0075, 360, 125000, 1, @@ -22,8 +22,17 @@ return [ 0, ], [ - -9027.7626490046005, - 0.0041666666669999998, + -68.278271180977001, + 0.0075, + 360, + 125000, + 1, + 1, + null, + ], + [ + -9025.875084814226, + 0.004175, 60, 50000, 1, @@ -31,8 +40,8 @@ return [ 0, ], [ - -9489.6401197913001, - 0.0041666666669999998, + -9488.600856134633, + 0.004175, 60, 50000, 13, @@ -40,8 +49,8 @@ return [ 0, ], [ - -9975.1481185740995, - 0.0041666666669999998, + -9975.04899646991, + 0.004175, 60, 50000, 25, @@ -49,8 +58,8 @@ return [ 0, ], [ - -10485.495628014, - 0.0041666666669999998, + -10486.435670612591, + 0.004175, 60, 50000, 37, @@ -58,8 +67,8 @@ return [ 0, ], [ - -11021.953484617001, - 0.0041666666669999998, + -11024.039391968092, + 0.004175, 60, 50000, 49, @@ -68,7 +77,61 @@ return [ ], [ '#VALUE!', - 0.0074999999999999997, + 'NaN', + 360, + 125000, + 13, + 24, + 0, + ], + [ + '#VALUE!', + 0.0075, + 'NaN', + 125000, + 13, + 24, + 0, + ], + [ + '#VALUE!', + 0.0075, + 360, + 'NaN', + 13, + 24, + 0, + ], + [ + '#VALUE!', + 0.0075, + 360, + 125000, + 'NaN', + 24, + 0, + ], + [ + '#VALUE!', + 0.0075, + 360, + 125000, + 13, + 'NaN', + 0, + ], + [ + '#VALUE!', + 0.0075, + 360, + 125000, + 13, + 24, + 'NaN', + ], + [ + '#VALUE!', + 0.0075, 360, 125000, 24, @@ -77,11 +140,20 @@ return [ ], [ '#NUM!', - 0.0074999999999999997, + 0.0075, 360, 125000, 24, 13, 2, ], + [ + '#NUM!', + 0.0075, + 10, + 125000, + 13, + 24, + 0, + ], ]; diff --git a/tests/data/Calculation/Financial/DB.php b/tests/data/Calculation/Financial/DB.php index 7f8fc6fa..89bd22f2 100644 --- a/tests/data/Calculation/Financial/DB.php +++ b/tests/data/Calculation/Financial/DB.php @@ -115,6 +115,47 @@ return [ 6, 6, ], + [ + 4651.199, + 12000, + 2000, + 3.5, + 2, + 1, + ], + [ + 3521.399, + 12000, + 2000, + 5, + 2.5, + 1, + ], + [ + 3521.399, + 12000, + 2000, + 5, + 2.5, + 1.2, + ], + // Period value between 0 and 1 not yet handled in code + // [ + // 301.0, + // 12000, + // 2000, + // 5, + // 0.5, + // 1, + // ], + [ + -554.116, + 12000, + 15000, + 5, + 2, + 1, + ], [ '#NUM!', -1000, @@ -125,10 +166,82 @@ return [ ], [ '#VALUE!', - 'ABC', - 100, + 'Invalid', + 1000, 5, - 6, - 6, + 2, + 1, + ], + [ + '#VALUE!', + 12000, + 'Invalid', + 5, + 2, + 1, + ], + [ + '#VALUE!', + 12000, + 1000, + 'Invalid', + 2, + 1, + ], + [ + '#VALUE!', + 12000, + 1000, + 5, + 'Invalid', + 1, + ], + [ + '#VALUE!', + 12000, + 1000, + 5, + 2, + 'Invalid', + ], + [ + '#NUM!', + -12000, + 1000, + 5, + 2, + 1, + ], + [ + '#NUM!', + 12000, + -1000, + 5, + 2, + 1, + ], + [ + '#NUM!', + 12000, + 1000, + 5, + 0, + 1, + ], + [ + '#NUM!', + 12000, + 1000, + 5, + -2, + 1, + ], + [ + '#NUM!', + 12000, + 1000, + 5, + 2, + 0, ], ]; diff --git a/tests/data/Calculation/Financial/DDB.php b/tests/data/Calculation/Financial/DDB.php index 879224ca..67962ea2 100644 --- a/tests/data/Calculation/Financial/DDB.php +++ b/tests/data/Calculation/Financial/DDB.php @@ -98,6 +98,47 @@ return [ 5, 5, ], + [ + 972.0, + 12000, + 1000, + 5, + 3, + 0.5, + ], + [ + 1259.4752186588921, + 12000, + 1000, + 3.5, + 3, + 0.5, + ], + [ + 1080.00, + 12000, + 1000, + 5, + 2, + 0.5, + ], + [ + 0.0, + 12000, + 15000, + 5, + 2, + 0.5, + ], + // Code does not yet handle fractional period values for DDB, only integer + // [ + // 1024.58, + // 12000, + // 1000, + // 5, + // 2.5, + // 0.5, + // ], [ '#NUM!', -2400, @@ -112,4 +153,76 @@ return [ 36500, 1, ], + [ + '#VALUE!', + 12000, + 'INVALID', + 5, + 3, + 0.5, + ], + [ + '#VALUE!', + 12000, + 1000, + 'INVALID', + 3, + 0.5, + ], + [ + '#VALUE!', + 12000, + 1000, + 5, + 'INVALID', + 0.5, + ], + [ + '#VALUE!', + 12000, + 1000, + 5, + 3, + 'INVALID', + ], + [ + '#NUM!', + 12000, + -1000, + 5, + 3, + 0.5, + ], + [ + '#NUM!', + 12000, + 1000, + 5, + -3, + 0.5, + ], + [ + '#NUM!', + 12000, + 1000, + 5, + 3, + -0.5, + ], + [ + '#NUM!', + 12000, + 1000, + 5, + 0, + 0.5, + ], + [ + '#NUM!', + 12000, + 1000, + 2, + 3, + 0.5, + ], ]; diff --git a/tests/data/Calculation/Financial/DISC.php b/tests/data/Calculation/Financial/DISC.php index d77b0a81..f33ed21b 100644 --- a/tests/data/Calculation/Financial/DISC.php +++ b/tests/data/Calculation/Financial/DISC.php @@ -18,6 +18,14 @@ return [ 95, 100, ], + [ + 0.01, + '2010-04-01', + '2015-03-31', + 95, + 100, + null, + ], [ '#NUM!', '2010-04-01', diff --git a/tests/data/Calculation/Financial/DaysPerYear.php b/tests/data/Calculation/Financial/DaysPerYear.php new file mode 100644 index 00000000..d8adb1c5 --- /dev/null +++ b/tests/data/Calculation/Financial/DaysPerYear.php @@ -0,0 +1,15 @@ + 0, 2 > 0, + true, true, ], [ true, @@ -15,23 +15,23 @@ return [ ], [ true, - 1 > 0, 0 > 1, + true, false, ], [ true, - 0 > 1, 2 > 0, + false, true, ], [ false, - 0 > 1, 0 > 2, + false, false, ], [ false, - 1 > 0, 2 > 0, 0 > 1, 0 > 2, + true, true, false, false, ], [ true, - 1 > 0, 2 > 0, 3 > 0, 0 > 1, + true, true, true, false, ], [ false, diff --git a/tests/data/Calculation/LookupRef/ADDRESS.php b/tests/data/Calculation/LookupRef/ADDRESS.php new file mode 100644 index 00000000..0e2baea6 --- /dev/null +++ b/tests/data/Calculation/LookupRef/ADDRESS.php @@ -0,0 +1,56 @@ + 'B5', 'C' => 'C5', 'D' => 'D5'], + ], + [ + [2, 3, 4], + 'B2:D3', + ], + [ + [2, 3, 4], + 'Sheet1!B2:D2', + ], + [ + [2, 3, 4], + '"WorkSheet #1"!B2:D2', + ], +]; diff --git a/tests/data/Calculation/LookupRef/HLOOKUP.php b/tests/data/Calculation/LookupRef/HLOOKUP.php index b880f247..d2a8a446 100644 --- a/tests/data/Calculation/LookupRef/HLOOKUP.php +++ b/tests/data/Calculation/LookupRef/HLOOKUP.php @@ -308,4 +308,24 @@ return [ 2, false, ], + [ + '#VALUE!', + 'B', + [ + ['Selection column', 'C', 'B', 'A'], + ['Value to retrieve', 3, 2, 1], + ], + 'Nan', + false, + ], + [ + '#REF!', + 'B', + [ + 'Selection column', + 'Value to retrieve', + ], + 2, + false, + ], ]; diff --git a/tests/data/Calculation/LookupRef/INDEX.php b/tests/data/Calculation/LookupRef/INDEX.php index 55206270..157794ab 100644 --- a/tests/data/Calculation/LookupRef/INDEX.php +++ b/tests/data/Calculation/LookupRef/INDEX.php @@ -54,7 +54,7 @@ return [ -1, ], [ - '#VALUE!', // Expected + '#REF!', // Expected // Input [ '20' => ['R' => 1, 'S' => 3], @@ -63,6 +63,16 @@ return [ 2, 10, ], + [ + '#REF!', // Expected + // Input + [ + '20' => ['R' => 1, 'S' => 3], + '21' => ['R' => 2, 'S' => 4], + ], + 10, + 2, + ], [ 4, // Expected // Input @@ -87,4 +97,64 @@ return [ '21' => ['R' => 2], ], ], + [ + 'Pears', + [ + ['Apples', 'Lemons'], + ['Bananas', 'Pears'], + ], + 2, + 2, + ], + [ + 'Bananas', + [ + ['Apples', 'Lemons'], + ['Bananas', 'Pears'], + ], + 2, + 1, + ], + [ + 3, + [ + [4, 6], + [5, 3], + [6, 9], + [7, 5], + [8, 3], + ], + 5, + 2, + ], + [ + [4 => [8, 3]], + [ + [4, 6], + [5, 3], + [6, 9], + [7, 5], + [8, 3], + ], + 5, + 0, + ], + [ + [ + [6], + [3], + [9], + [5], + [3], + ], + [ + [4, 6], + [5, 3], + [6, 9], + [7, 5], + [8, 3], + ], + 0, + 2, + ], ]; diff --git a/tests/data/Calculation/LookupRef/INDIRECT.php b/tests/data/Calculation/LookupRef/INDIRECT.php new file mode 100644 index 00000000..3ff44707 --- /dev/null +++ b/tests/data/Calculation/LookupRef/INDIRECT.php @@ -0,0 +1,16 @@ + ['B' => 'B5', 'C' => 'C5', 'D' => 'D5'], + 5 => ['B' => 'B5', 'C' => 'C5', 'D' => 'D5'], + ], + ], + [ + [[10], [11], [12]], + 'Sheet1!C10:C12', + ], + [ + [[10], [11], [12]], + '"WorkSheet #1"!C10:C12', + ], +]; diff --git a/tests/data/Calculation/LookupRef/TRANSPOSE.php b/tests/data/Calculation/LookupRef/TRANSPOSE.php new file mode 100644 index 00000000..a5ca45f4 --- /dev/null +++ b/tests/data/Calculation/LookupRef/TRANSPOSE.php @@ -0,0 +1,32 @@ + 36 + ['#NUM!', 2 ** 54, 16], // number > 2 ** 53 + ['00000120', 15, 3, 8.1], + ['exception'], + ['exception', 1], ]; diff --git a/tests/data/Calculation/MathTrig/CEILING.php b/tests/data/Calculation/MathTrig/CEILING.php index 4005f12b..a4af7df1 100644 --- a/tests/data/Calculation/MathTrig/CEILING.php +++ b/tests/data/Calculation/MathTrig/CEILING.php @@ -1,104 +1,34 @@ ['A' => 0], - 2 => ['A' => 1], - 3 => ['A' => 1], - 4 => ['A' => 2], - 5 => ['A' => 3], - 6 => ['A' => 5], - 7 => ['A' => 8], - 8 => ['A' => 13], - 9 => ['A' => 21], - 10 => ['A' => 34], - 11 => ['A' => 55], - 12 => ['A' => 89], -]; - return [ - [ - 19.3333333333333, - 1, - $baseTestData, - ], - [ - 12, - 2, - $baseTestData, - ], - [ - 12, - 3, - $baseTestData, - ], - [ - 89, - 4, - $baseTestData, - ], - [ - 0, - 5, - $baseTestData, - ], - [ - 0, - 6, - $baseTestData, - ], - [ - 27.5196899207337, - 7, - $baseTestData, - ], - [ - 26.3480971271593, - 8, - $baseTestData, - ], - [ - 232, - 9, - $baseTestData, - ], - [ - 757.3333333333330, - 10, - $baseTestData, - ], - [ - 694.2222222222220, - 11, - $baseTestData, - ], + [19.3333333333333, 1], + [12, 2], + [12, 3], + [89, 4], + [0, 5], + [0, 6], + [27.5196899207337, 7], + [26.3480971271593, 8], + [232, 9], + [757.3333333333330, '10'], + [694.2222222222220, 11.1], + ['#VALUE!', 0], + ['#VALUE!', -1], + ['#VALUE!', 12], + ['#VALUE!', '"X"'], ]; diff --git a/tests/data/Calculation/MathTrig/SUBTOTALHIDDEN.php b/tests/data/Calculation/MathTrig/SUBTOTALHIDDEN.php index 001531f8..df6375dc 100644 --- a/tests/data/Calculation/MathTrig/SUBTOTALHIDDEN.php +++ b/tests/data/Calculation/MathTrig/SUBTOTALHIDDEN.php @@ -1,74 +1,15 @@ ['A' => 0], - 2 => ['A' => 1], - 3 => ['A' => 1], - 4 => ['A' => 2], - 5 => ['A' => 3], - 6 => ['A' => 5], - 7 => ['A' => 8], - 8 => ['A' => 13], - 9 => ['A' => 21], - 10 => ['A' => 34], - 11 => ['A' => 55], - 12 => ['A' => 89], -]; - return [ - [ - 21, - 101, - $baseTestData, - ], - [ - 5, - 102, - $baseTestData, - ], - [ - 5, - 103, - $baseTestData, - ], - [ - 55, - 104, - $baseTestData, - ], - [ - 1, - 105, - $baseTestData, - ], - [ - 48620, - 106, - $baseTestData, - ], - [ - 23.1840462387393, - 107, - $baseTestData, - ], - [ - 20.7364413533277, - 108, - $baseTestData, - ], - [ - 105, - 109, - $baseTestData, - ], - [ - 537.5, - 110, - $baseTestData, - ], - [ - 430, - 111, - $baseTestData, - ], + [21, 101], + [5, 102], + [5, 103], + [55, 104], + [1, 105], + [48620, 106], + [23.1840462387393, 107], + [20.7364413533277, 108], + [105, 109], + [537.5, 110], + [430, 111], ]; diff --git a/tests/data/Calculation/MathTrig/SUBTOTALNESTED.php b/tests/data/Calculation/MathTrig/SUBTOTALNESTED.php deleted file mode 100644 index e1ae38f8..00000000 --- a/tests/data/Calculation/MathTrig/SUBTOTALNESTED.php +++ /dev/null @@ -1,18 +0,0 @@ - ['A' => 123], - 2 => ['A' => 234], - 3 => ['A' => '=SUBTOTAL(1, A1:A2)'], - 4 => ['A' => '=ROMAN(SUBTOTAL(1, A1:A2))'], - 5 => ['A' => 'This is text containing "=" and "SUBTOTAL("'], - 6 => ['A' => '=AGGREGATE(1, A1:A2)'], -]; - -return [ - [ - 357, - 9, - $baseTestData, - ], -]; diff --git a/tests/data/Calculation/MathTrig/SUM.php b/tests/data/Calculation/MathTrig/SUM.php new file mode 100644 index 00000000..a8219076 --- /dev/null +++ b/tests/data/Calculation/MathTrig/SUM.php @@ -0,0 +1,8 @@ +2', + ['Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol'], + 'Jeff', + ], ]; diff --git a/tests/data/Calculation/MathTrig/SUMPRODUCT.php b/tests/data/Calculation/MathTrig/SUMPRODUCT.php index 3a75e033..14ea8b39 100644 --- a/tests/data/Calculation/MathTrig/SUMPRODUCT.php +++ b/tests/data/Calculation/MathTrig/SUMPRODUCT.php @@ -16,4 +16,14 @@ return [ [[1, 2], [3, 4]], [[5, 6], [7, 8]], ], + ['#VALUE!', [1, 2], [5, 6, 4]], // mismatched dimensions + [17, [1, 2, 3], [5, 'y', 4]], + [17, [1, 2, 3], [5, 0, 4]], + [19, [1, 2, 3], [5, 1, 4]], + [145, [1, 2, 3], [5, 1, 4], [9, 8, 7]], + [61, [1, 2, 3], [5, 1, 4], [9, 8, '="7"']], // string treated as 0 + [100, ['="1"', 2, 3], [5, 1, 4], [9, 8, 7]], // string treated as 0 + [100, [null, 2, 3], [5, 1, 4], [9, 8, 7]], // null treated as 0 + [100, [true, 2, 3], [5, 1, 4], [9, 8, 7]], // true treated as 0 + [61, [1, 2, 3], [5, 1, 4], [9, 8, true]], // true treated as 0 ]; diff --git a/tests/data/Calculation/MathTrig/SUMSQ.php b/tests/data/Calculation/MathTrig/SUMSQ.php index 5661f01f..f62e802a 100644 --- a/tests/data/Calculation/MathTrig/SUMSQ.php +++ b/tests/data/Calculation/MathTrig/SUMSQ.php @@ -44,4 +44,13 @@ return [ 2, 3, ], + [14, 1, '2', 3], + [14, 1, '=2', 3], + [14, 1, '="2"', 3], + ['#VALUE!', 1, 'X', 3], + ['#VALUE!', 1, '', 3], + ['#VALUE!', false, 2, 3], + ['#VALUE!', 1, 2, true], + [5, 1, 2, null], + [10, 1, null, 3, null], ]; diff --git a/tests/data/Calculation/MathTrig/SUMX2MY2.php b/tests/data/Calculation/MathTrig/SUMX2MY2.php index 9ed17ffd..725910f1 100644 --- a/tests/data/Calculation/MathTrig/SUMX2MY2.php +++ b/tests/data/Calculation/MathTrig/SUMX2MY2.php @@ -16,4 +16,14 @@ return [ [[1, 2], [3, 4]], [[5, 6], [7, 8]], ], + [-20, [1, 2], [3, 4]], + [-20, [1, '=2'], [3, 4]], + [-8, [1, '2'], [3, 4]], + [-8, [1, '="2"'], [3, 4]], + [-8, [1, 'X'], [3, 4]], + [-8, [1, false], [3, 4]], + [-12, [1, 2], ['', 4]], + [-12, [1, 2], [null, 4]], + [-12, [1, 2], [true, 4]], + ['#N/A', [1, 2], [3, 4, 5]], // different dimensions ]; diff --git a/tests/data/Calculation/MathTrig/SUMX2PY2.php b/tests/data/Calculation/MathTrig/SUMX2PY2.php index 1d2a4b13..68d0ebf7 100644 --- a/tests/data/Calculation/MathTrig/SUMX2PY2.php +++ b/tests/data/Calculation/MathTrig/SUMX2PY2.php @@ -16,4 +16,14 @@ return [ [[1, 2], [3, 4]], [[5, 6], [7, 8]], ], + [30, [1, 2], [3, 4]], + [30, [1, '=2'], [3, 4]], + [10, [1, ''], [3, 4]], + [10, [1, '2'], [3, 4]], + [10, [1, '="2"'], [3, 4]], + [10, [1, 'X'], [3, 4]], + [10, [1, false], [3, 4]], + [20, [1, 2], [null, 4]], + [20, [1, 2], [true, 4]], + ['#N/A', [1, 2], [3, 4, 5]], // different dimensions ]; diff --git a/tests/data/Calculation/MathTrig/SUMXMY2.php b/tests/data/Calculation/MathTrig/SUMXMY2.php index f6b355d8..0a7724db 100644 --- a/tests/data/Calculation/MathTrig/SUMXMY2.php +++ b/tests/data/Calculation/MathTrig/SUMXMY2.php @@ -16,4 +16,14 @@ return [ [[1, 2], [3, 4]], [[5, 6], [7, 8]], ], + [8, [1, 2], [3, 4]], + [8, [1, '=2'], [3, 4]], + [4, [1, ''], [3, 4]], + [4, [1, '2'], [3, 4]], + [4, [1, '="2"'], [3, 4]], + [4, [1, 'X'], [3, 4]], + [4, [1, false], [3, 4]], + [4, [1, 2], [null, 4]], + [4, [1, 2], [true, 4]], + ['#N/A', [1, 2], [3, 4, 5]], // different dimensions ]; diff --git a/tests/data/Calculation/MathTrig/TAN.php b/tests/data/Calculation/MathTrig/TAN.php new file mode 100644 index 00000000..9b5e4f74 --- /dev/null +++ b/tests/data/Calculation/MathTrig/TAN.php @@ -0,0 +1,17 @@ +true', + [2, 4, 6, 8, 10], + ], + [ + (1 + 2 + 5 + 6) / 4, + ['North', 'South', 'East', 'West', 'North', 'South', 'East', 'West'], + '???th', + [1, 2, 3, 4, 5, 6, 7, 8], + ], + [ + 16733.5, + ['East', 'West', 'North', 'South (New Office)', 'Midwest'], + '=*West', + [45678, 23789, -4789, 0, 9678], + ], + [ + 18589, + ['East', 'West', 'North', 'South (New Office)', 'Midwest'], + '<>*(New Office)', + [45678, 23789, -4789, 0, 9678], ], ]; diff --git a/tests/data/Calculation/Statistical/AVERAGEIFS.php b/tests/data/Calculation/Statistical/AVERAGEIFS.php new file mode 100644 index 00000000..d5bc6dad --- /dev/null +++ b/tests/data/Calculation/Statistical/AVERAGEIFS.php @@ -0,0 +1,45 @@ +70', + [75, 94, 86, 'incomplete'], + '<90', + ], + [ + '#DIV/0!', + [85, 80, 93, 75], + [85, 80, 93, 75], + '>95', + ], + [ + 87.5, + [87, 88, 'incomplete', 75], + [87, 88, 'incomplete', 75], + '<>incomplete', + [87, 88, 'incomplete', 75], + '>80', + ], + [ + 174000, + [223000, 125000, 456000, 322000, 340000, 198000, 310000, 250000, 460000, 261000, 389000, 305000], + [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4], + 1, + ['North', 'North', 'South', 'North', 'North', 'South', 'North', 'North', 'South', 'North', 'North', 'South'], + 'North', + ], + [ + 285500, + [223000, 125000, 456000, 322000, 340000, 198000, 310000, 250000, 460000, 261000, 389000, 305000], + [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4], + '>2', + ['Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol'], + 'Jeff', + ], +]; diff --git a/tests/data/Calculation/Statistical/BETADIST.php b/tests/data/Calculation/Statistical/BETADIST.php index f489429c..ef89575f 100644 --- a/tests/data/Calculation/Statistical/BETADIST.php +++ b/tests/data/Calculation/Statistical/BETADIST.php @@ -13,6 +13,10 @@ return [ 0.960370937542, 3, 7.5, 9, 1, 4, ], + [ + 0.960370937542, + 3, 7.5, 9, 4, 1, + ], [ 0.598190307617, 7.5, 8, 9, 5, 10, @@ -21,12 +25,60 @@ return [ 0.685470581054, 2, 8, 10, 1, 3, ], + [ + 0.4059136, + 0.4, 4, 5, + ], + [ + 0.4059136, + 0.4, 4, 5, null, null, + ], + [ + '#VALUE!', + 'NAN', 8, 10, 1, 3, + ], [ '#VALUE!', 2, 'NAN', 10, 1, 3, ], [ + '#VALUE!', + 2, 8, 'NAN', 1, 3, + ], + [ + '#VALUE!', + 2, 8, 10, 'NAN', 3, + ], + [ + '#VALUE!', + 2, 8, 10, 1, 'NAN', + ], + 'alpha < 0' => [ '#NUM!', 2, -8, 10, 1, 3, ], + 'alpha = 0' => [ + '#NUM!', + 2, 0, 10, 1, 3, + ], + 'beta < 0' => [ + '#NUM!', + 2, 8, -10, 1, 3, + ], + 'beta = 0' => [ + '#NUM!', + 2, 8, 0, 1, 3, + ], + 'value < Min' => [ + '#NUM!', + 0.5, 8, 10, 1, 3, + ], + 'value > Max' => [ + '#NUM!', + 3.5, 8, 10, 1, 3, + ], + 'Min = Max' => [ + '#NUM!', + 2, 8, 10, 2, 2, + ], ]; diff --git a/tests/data/Calculation/Statistical/BETAINV.php b/tests/data/Calculation/Statistical/BETAINV.php index d62c1c35..609c2804 100644 --- a/tests/data/Calculation/Statistical/BETAINV.php +++ b/tests/data/Calculation/Statistical/BETAINV.php @@ -9,6 +9,10 @@ return [ 2.164759759129, 0.3, 7.5, 9, 1, 4, ], + [ + 2.164759759129, + 0.3, 7.5, 9, 4, 1, + ], [ 7.761240188783, 0.75, 8, 9, 5, 10, @@ -21,12 +25,60 @@ return [ 0.303225844664, 0.2, 4, 5, 0, 1, ], + [ + 0.303225844664, + 0.2, 4, 5, null, null, + ], + [ + '#VALUE!', + 'NAN', 4, 5, 0, 1, + ], [ '#VALUE!', 0.2, 'NAN', 5, 0, 1, ], [ + '#VALUE!', + 0.2, 4, 'NAN', 0, 1, + ], + [ + '#VALUE!', + 0.2, 4, 5, 'NAN', 1, + ], + [ + '#VALUE!', + 0.2, 4, 5, 0, 'NAN', + ], + 'alpha < 0' => [ '#NUM!', 0.2, -4, 5, 0, 1, ], + 'alpha = 0' => [ + '#NUM!', + 0.2, 0, 5, 0, 1, + ], + 'beta < 0' => [ + '#NUM!', + 0.2, 4, -5, 0, 1, + ], + 'beta = 0' => [ + '#NUM!', + 0.2, 4, 0, 0, 1, + ], + 'Probability < 0' => [ + '#NUM!', + -0.5, 4, 5, 1, 3, + ], + 'Probability = 0' => [ + '#NUM!', + 0.0, 4, 5, 1, 3, + ], + 'Probability > 1' => [ + '#NUM!', + 1.5, 4, 5, 1, 3, + ], + 'Min = Max' => [ + '#NUM!', + 1, 4, 5, 1, 1, + ], ]; diff --git a/tests/data/Calculation/Statistical/BINOMDIST.php b/tests/data/Calculation/Statistical/BINOMDIST.php index 47e8a3f3..3606bced 100644 --- a/tests/data/Calculation/Statistical/BINOMDIST.php +++ b/tests/data/Calculation/Statistical/BINOMDIST.php @@ -49,12 +49,32 @@ return [ '#VALUE!', 'NAN', 100, 0.5, true, ], + [ + '#VALUE!', + 65, 'NAN', 0.5, true, + ], + [ + '#VALUE!', + 65, 100, 'NAN', true, + ], + [ + '#VALUE!', + 65, 100, 0.5, 'NAN', + ], [ '#NUM!', -5, 100, 0.5, true, ], [ '#NUM!', - 5, 100, 1.5, true, + 105, 100, 0.5, true, + ], + [ + '#NUM!', + 65, 100, -0.5, true, + ], + [ + '#NUM!', + 65, 100, 1.5, true, ], ]; diff --git a/tests/data/Calculation/Statistical/BINOMDISTRANGE.php b/tests/data/Calculation/Statistical/BINOMDISTRANGE.php new file mode 100644 index 00000000..153706d1 --- /dev/null +++ b/tests/data/Calculation/Statistical/BINOMDISTRANGE.php @@ -0,0 +1,76 @@ + [ + '#NUM!', + -8, 3, true, + ], + 'Degrees < 1' => [ + '#NUM!', + 8, 0, true, + ], +]; diff --git a/tests/data/Calculation/Statistical/CHIDIST.php b/tests/data/Calculation/Statistical/CHIDISTRightTail.php similarity index 84% rename from tests/data/Calculation/Statistical/CHIDIST.php rename to tests/data/Calculation/Statistical/CHIDISTRightTail.php index 5cfdc664..24ddab9f 100644 --- a/tests/data/Calculation/Statistical/CHIDIST.php +++ b/tests/data/Calculation/Statistical/CHIDISTRightTail.php @@ -35,14 +35,18 @@ return [ ], [ '#VALUE!', - 'NAN', 3, + 'NaN', 3, ], [ - '#NUM!', - 8, 0, + '#VALUE!', + 8, 'NaN', ], - [ + 'Value < 0' => [ '#NUM!', -8, 3, ], + 'Degrees < 1' => [ + '#NUM!', + 8, 0, + ], ]; diff --git a/tests/data/Calculation/Statistical/CHIINVLeftTail.php b/tests/data/Calculation/Statistical/CHIINVLeftTail.php new file mode 100644 index 00000000..bc64721e --- /dev/null +++ b/tests/data/Calculation/Statistical/CHIINVLeftTail.php @@ -0,0 +1,64 @@ + [ + '#NUM!', + -0.1, 3, + ], + 'Probability > 1' => [ + '#NUM!', + 1.1, 3, + ], + 'Freedom > 1' => [ + '#NUM!', + 0.1, 0.5, + ], +]; diff --git a/tests/data/Calculation/Statistical/CHIINV.php b/tests/data/Calculation/Statistical/CHIINVRightTail.php similarity index 51% rename from tests/data/Calculation/Statistical/CHIINV.php rename to tests/data/Calculation/Statistical/CHIINVRightTail.php index 2384cda6..58b317e5 100644 --- a/tests/data/Calculation/Statistical/CHIINV.php +++ b/tests/data/Calculation/Statistical/CHIINVRightTail.php @@ -10,13 +10,21 @@ return [ 0.75, 10, ], [ - 18.30697345702, - 0.050001, 10, + 0.007716715545, + 0.93, 1, + ], + [ + 1.021651247532, + 0.6, 2, ], [ 0.45493642312, 0.5, 1, ], + [ + 4.351460191096, + 0.5, 5, + ], [ 0.101531044268, 0.75, 1, @@ -35,6 +43,22 @@ return [ ], [ '#VALUE!', - 0.25, 'NAN', + 'NaN', 3, + ], + [ + '#VALUE!', + 0.25, 'NaN', + ], + 'Probability < 0' => [ + '#NUM!', + -0.1, 3, + ], + 'Probability > 1' => [ + '#NUM!', + 1.1, 3, + ], + 'Freedom > 1' => [ + '#NUM!', + 0.1, 0.5, ], ]; diff --git a/tests/data/Calculation/Statistical/CHITEST.php b/tests/data/Calculation/Statistical/CHITEST.php new file mode 100644 index 00000000..6f5cd05c --- /dev/null +++ b/tests/data/Calculation/Statistical/CHITEST.php @@ -0,0 +1,39 @@ +true', + ], + [ + 4, + ['apples', 'oranges', 'peaches', 'apples'], + '*', + ], + [ + 3, + ['apples', 'oranges', 'peaches', 'apples'], + '*p*s*', + ], + [ + 4, + [ + ['apples', 'oranges', 'peaches', 'apples'], + ['bananas', 'mangoes', 'grapes', 'cherries'], + ], + '*p*e*', + ], + [ + 2, + ['apples', 'oranges', 'peaches', 'apples'], + '?????es', + ], + [ + 2, + ['great * ratings', 'bad * ratings', 'films * wars', 'films * trek', 'music * radio'], + '*~* ra*s', + ], ]; diff --git a/tests/data/Calculation/Statistical/COUNTIFS.php b/tests/data/Calculation/Statistical/COUNTIFS.php index 32f64d71..3ed1634a 100644 --- a/tests/data/Calculation/Statistical/COUNTIFS.php +++ b/tests/data/Calculation/Statistical/COUNTIFS.php @@ -1,6 +1,9 @@ 60%', + ], [ 2, - [1, 2, 3, 'B', '', false], - '<=2', + ['Maths', 'English', 'Science', 'Maths', 'English', 'Science', 'Maths', 'English', 'Science', 'Maths', 'English', 'Science'], + 'Science', + [0.63, 0.78, 0.39, 0.55, 0.71, 0.51, 0.78, 0.81, 0.49, 0.35, 0.69, 0.65], + '<50%', ], ]; diff --git a/tests/data/Calculation/Statistical/COVAR.php b/tests/data/Calculation/Statistical/COVAR.php index d361a352..b1cd2908 100644 --- a/tests/data/Calculation/Statistical/COVAR.php +++ b/tests/data/Calculation/Statistical/COVAR.php @@ -16,4 +16,14 @@ return [ [2, 7, 8, 3, 4, 1, 6, 5], [22.9, 33.49, 34.5, 27.61, 19.5, 10.11, 37.9, 31.08], ], + [ + '#N/A', + [1, 2, 3], + [4, 5], + ], + [ + '#DIV/0!', + [1, 2, 3], + [4, null, null], + ], ]; diff --git a/tests/data/Calculation/Statistical/CRITBINOM.php b/tests/data/Calculation/Statistical/CRITBINOM.php deleted file mode 100644 index 8e59bf51..00000000 --- a/tests/data/Calculation/Statistical/CRITBINOM.php +++ /dev/null @@ -1,24 +0,0 @@ - ['#NUM!', 0.0], + 'Negative integer value' => ['#NUM!', -1], ['#VALUE!', 'NAN'], ]; diff --git a/tests/data/Calculation/Statistical/GAMMADIST.php b/tests/data/Calculation/Statistical/GAMMADIST.php index e79b3869..2a1bfa14 100644 --- a/tests/data/Calculation/Statistical/GAMMADIST.php +++ b/tests/data/Calculation/Statistical/GAMMADIST.php @@ -17,12 +17,44 @@ return [ 0.576809918873, 6, 3, 2, true, ], + 'Boolean as numeric' => [ + 0.576809918873, + 6, 3, 2, 1, + ], + [ + '#VALUE!', + 'NAN', 3, 2, true, + ], [ '#VALUE!', 6, 'NAN', 2, true, ], [ + '#VALUE!', + 6, 3, 'NAN', true, + ], + [ + '#VALUE!', + 6, 3, 2, 'NAN', + ], + 'Value < 0' => [ '#NUM!', -6, 3, 2, true, ], + 'A < 0' => [ + '#NUM!', + 6, -3, 2, true, + ], + 'A = 0' => [ + '#NUM!', + 6, 0, 2, true, + ], + 'B < 0' => [ + '#NUM!', + 6, 3, -2, true, + ], + 'B = 0' => [ + '#NUM!', + 6, 3, 0, true, + ], ]; diff --git a/tests/data/Calculation/Statistical/GAMMAINV.php b/tests/data/Calculation/Statistical/GAMMAINV.php index 3b3604b4..c35c4219 100644 --- a/tests/data/Calculation/Statistical/GAMMAINV.php +++ b/tests/data/Calculation/Statistical/GAMMAINV.php @@ -11,10 +11,38 @@ return [ ], [ '#VALUE!', - 'NAN', 3, 2, + 'NaN', 3, 2, ], [ + '#VALUE!', + 0.5, 'NaN', 2, + ], + [ + '#VALUE!', + 0.5, 3, 'NaN', + ], + 'Probability < 0' => [ '#NUM!', -0.5, 3, 2, ], + 'Probability > 1' => [ + '#NUM!', + 1.5, 3, 2, + ], + 'Alpha < 0' => [ + '#NUM!', + 0.5, -3, 2, + ], + 'Alpha = 0' => [ + '#NUM!', + 0.5, 0, 2, + ], + 'Beta < 0' => [ + '#NUM!', + 0.5, 3, -2, + ], + 'Beta = 0' => [ + '#NUM!', + 0.5, 3, 0, + ], ]; diff --git a/tests/data/Calculation/Statistical/GAMMALN.php b/tests/data/Calculation/Statistical/GAMMALN.php index a415f559..7b43eea8 100644 --- a/tests/data/Calculation/Statistical/GAMMALN.php +++ b/tests/data/Calculation/Statistical/GAMMALN.php @@ -13,8 +13,12 @@ return [ '#VALUE!', 'NAN', ], - [ + 'Value < 0' => [ '#NUM!', -4.5, ], + 'Value = 0' => [ + '#NUM!', + 0.0, + ], ]; diff --git a/tests/data/Calculation/Statistical/GROWTH.php b/tests/data/Calculation/Statistical/GROWTH.php new file mode 100644 index 00000000..25596e30 --- /dev/null +++ b/tests/data/Calculation/Statistical/GROWTH.php @@ -0,0 +1,25 @@ + [ + // [ + // [-234.2371645, 2553.21066, 12529.76817, 27.64138737, 52317.83051], + // [13.26801148, 530.6691519, 400.0668382, 5.429374042, 12237.3616], + // [0.996747993, 970.5784629, '#N/A', '#N/A', '#N/A'], + // [459.7536742, 6, '#N/A', '#N/A', '#N/A'], + // [1732393319, 5652135.316, '#N/A', '#N/A', '#N/A'], + // ], + // [142000, 144000, 151000, 150000, 139000, 169000, 126000, 142900, 163000, 169000, 149000], + // [ + // [2310, 2, 2, 20], + // [2333, 2, 2, 12], + // [2356, 3, 1.5, 33], + // [2379, 3, 2, 43], + // [2402, 2, 3, 53], + // [2425, 4, 2, 23], + // [2448, 2, 1.5, 99], + // [2471, 2, 2, 34], + // [2494, 3, 3, 23], + // [2517, 4, 4, 55], + // [2540, 2, 3, 22], + // ], + // true, + // true, + // ], ]; diff --git a/tests/data/Calculation/Statistical/LOGEST.php b/tests/data/Calculation/Statistical/LOGEST.php index e74db005..bba7487b 100644 --- a/tests/data/Calculation/Statistical/LOGEST.php +++ b/tests/data/Calculation/Statistical/LOGEST.php @@ -1,6 +1,20 @@ 2', + ['Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol'], + 'Jeff', + ], ]; diff --git a/tests/data/Calculation/Statistical/MINIFS.php b/tests/data/Calculation/Statistical/MINIFS.php index a00f25b7..5a1de173 100644 --- a/tests/data/Calculation/Statistical/MINIFS.php +++ b/tests/data/Calculation/Statistical/MINIFS.php @@ -1,6 +1,9 @@ 2', + ['Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol'], + 'Jeff', + ], ]; diff --git a/tests/data/Calculation/Statistical/NEGBINOMDIST.php b/tests/data/Calculation/Statistical/NEGBINOMDIST.php new file mode 100644 index 00000000..fd983cc1 --- /dev/null +++ b/tests/data/Calculation/Statistical/NEGBINOMDIST.php @@ -0,0 +1,52 @@ + [ + '#NUM!', + -35, 40, true, + ], + 'Mean < 0' => [ + '#NUM!', + 35, -40, true, + ], +]; diff --git a/tests/data/Calculation/Statistical/QUARTILE.php b/tests/data/Calculation/Statistical/QUARTILE.php new file mode 100644 index 00000000..26a7902f --- /dev/null +++ b/tests/data/Calculation/Statistical/QUARTILE.php @@ -0,0 +1,52 @@ + [ + '#VALUE!', + true, + 'Mark Baker', + ], ]; diff --git a/tests/data/Calculation/TextData/LEFT.php b/tests/data/Calculation/TextData/LEFT.php index 914d16be..d524dc36 100644 --- a/tests/data/Calculation/TextData/LEFT.php +++ b/tests/data/Calculation/TextData/LEFT.php @@ -11,16 +11,46 @@ return [ '', 1, ], + [ + '', + 'ABC', + 0, + ], [ '#VALUE!', 'QWERTYUIOP', -1, ], + [ + '#VALUE!', + 'QWERTYUIOP', + 'NaN', + ], + [ + '#VALUE!', + 'QWERTYUIOP', + null, + ], [ 'ABC', 'ABCDEFGHI', 3, ], + [ + 'Ενα', + 'Ενα δύο τρία τέσσερα πέντε', + 3, + ], + [ + 'Ενα δύο', + 'Ενα δύο τρία τέσσερα πέντε', + 7, + ], + [ + 'Ενα δύο τρία', + 'Ενα δύο τρία τέσσερα πέντε', + 12, + ], [ 'TR', true, diff --git a/tests/data/Calculation/TextData/LOWER.php b/tests/data/Calculation/TextData/LOWER.php index 2a4064bf..c5360b95 100644 --- a/tests/data/Calculation/TextData/LOWER.php +++ b/tests/data/Calculation/TextData/LOWER.php @@ -9,6 +9,18 @@ return [ 'mark baker', 'MARK BAKER', ], + [ + 'buenos días', + 'BUENOS DÍAS', + ], + [ + 'καλημερα', + 'ΚΑΛΗΜΕΡΑ', + ], + [ + 'доброе утро', + 'ДОБРОЕ УТРО', + ], [ 'true', true, diff --git a/tests/data/Calculation/TextData/MID.php b/tests/data/Calculation/TextData/MID.php index 2a59373b..b434f670 100644 --- a/tests/data/Calculation/TextData/MID.php +++ b/tests/data/Calculation/TextData/MID.php @@ -16,7 +16,7 @@ return [ [ '#VALUE!', 'QWERTYUIOP', - -1, + 0, 1, ], [ @@ -26,7 +26,19 @@ return [ -1, ], [ - '', + '#VALUE!', + 'QWERTYUIOP', + 'NaN', + 1, + ], + [ + '#VALUE!', + 'QWERTYUIOP', + 2, + 'NaN', + ], + [ + '#VALUE!', 'QWERTYUIOP', 5, ], @@ -36,12 +48,36 @@ return [ 8, 20, ], + [ + '', + 'QWERTYUIOP', + 999, + 2, + ], [ 'DEF', 'ABCDEFGHI', 4, 3, ], + [ + 'δύο', + 'Ενα δύο τρία τέσσερα πέντε', + 5, + 3, + ], + [ + 'δύο τρία', + 'Ενα δύο τρία τέσσερα πέντε', + 5, + 8, + ], + [ + 'τρία τέσσερα', + 'Ενα δύο τρία τέσσερα πέντε', + 9, + 12, + ], [ 'R', true, diff --git a/tests/data/Calculation/TextData/PROPER.php b/tests/data/Calculation/TextData/PROPER.php index 84c29096..8bbf0e5c 100644 --- a/tests/data/Calculation/TextData/PROPER.php +++ b/tests/data/Calculation/TextData/PROPER.php @@ -5,6 +5,18 @@ return [ 'Mark Baker', 'MARK BAKER', ], + [ + 'Buenos Días', + 'BUENOS DÍAS', + ], + [ + 'Καλημερα', + 'ΚΑΛΗΜΕΡΑ', + ], + [ + 'Доброе Утро', + 'ДОБРОЕ УТРО', + ], [ 'True', true, diff --git a/tests/data/Calculation/TextData/REPLACE.php b/tests/data/Calculation/TextData/REPLACE.php index 086d1290..09e22968 100644 --- a/tests/data/Calculation/TextData/REPLACE.php +++ b/tests/data/Calculation/TextData/REPLACE.php @@ -29,4 +29,32 @@ return [ 0, 'DFG', ], + [ + 'Ενα δύοτρίατέσσεραπέντε', + 'Εναδύοτρίατέσσεραπέντε', + 4, + 0, + ' ', + ], + [ + 'Ενα δύο τρίατέσσεραπέντε', + 'Ενα δύοτρίατέσσεραπέντε', + 8, + 0, + ' ', + ], + [ + 'Ενα δύο τρία τέσσεραπέντε', + 'Ενα δύο τρίατέσσεραπέντε', + 13, + 0, + ' ', + ], + [ + 'Ενα δύο τρία τέσσερα πέντε', + 'Ενα δύο τρία τέσσεραπέντε', + 21, + 0, + ' ', + ], ]; diff --git a/tests/data/Calculation/TextData/REPT.php b/tests/data/Calculation/TextData/REPT.php new file mode 100644 index 00000000..2c8d1c0d --- /dev/null +++ b/tests/data/Calculation/TextData/REPT.php @@ -0,0 +1,14 @@ + [ + '#VALUE!', + true, + 'Mark Baker', + ], ]; diff --git a/tests/data/Calculation/TextData/SUBSTITUTE.php b/tests/data/Calculation/TextData/SUBSTITUTE.php index 23f66a18..97cb8d0f 100644 --- a/tests/data/Calculation/TextData/SUBSTITUTE.php +++ b/tests/data/Calculation/TextData/SUBSTITUTE.php @@ -20,6 +20,20 @@ return [ 'x', 1, ], + [ + 'Mark Bxker', + 'Mark Baker', + 'a', + 'x', + 2, + ], + [ + 'Mark Bakker', + 'Mark Baker', + 'k', + 'kk', + 2, + ], [ 'Mark Baker', 'Mark Baker', @@ -27,6 +41,26 @@ return [ 'a', 1, ], + [ + 'Ενα δύο αρία αέσσερα πέναε', + 'Ενα δύο τρία τέσσερα πέντε', + 'τ', + 'α', + ], + [ + 'Ενα δύο τρία αέσσερα πέντε', + 'Ενα δύο τρία τέσσερα πέντε', + 'τ', + 'α', + 2, + ], + [ + 'Ενα δύο τρία ατέσσερα πέντε', + 'Ενα δύο τρία τέσσερα πέντε', + 'τ', + 'ατ', + 2, + ], 'Unicode equivalence is not supported' => [ "\u{0061}\u{030A}", "\u{0061}\u{030A}", diff --git a/tests/data/Calculation/TextData/TEXTJOIN.php b/tests/data/Calculation/TextData/TEXTJOIN.php index 9ad85e94..9c6b4246 100644 --- a/tests/data/Calculation/TextData/TEXTJOIN.php +++ b/tests/data/Calculation/TextData/TEXTJOIN.php @@ -5,10 +5,22 @@ return [ 'ABCDE,FGHIJ', [',', true, 'ABCDE', 'FGHIJ'], ], + [ + 'ABCDEFGHIJ', + ['', true, 'ABCDE', 'FGHIJ'], + ], [ '1-2-3', ['-', true, 1, 2, 3], ], + [ + '<<::>>', + ['::', true, '<<', '>>'], + ], + [ + 'Καλό απόγευμα', + [' ', true, 'Καλό', 'απόγευμα'], + ], [ 'Boolean-TRUE', ['-', true, 'Boolean', '', true], diff --git a/tests/data/Calculation/TextData/UPPER.php b/tests/data/Calculation/TextData/UPPER.php index b43163be..e5d2f18e 100644 --- a/tests/data/Calculation/TextData/UPPER.php +++ b/tests/data/Calculation/TextData/UPPER.php @@ -9,6 +9,18 @@ return [ 'MARK BAKER', 'mark baker', ], + [ + 'BUENOS DÍAS', + 'buenos días', + ], + [ + 'ΚΑΛΗΜΕΡΑ', + 'Καλημερα', + ], + [ + 'ДОБРОЕ УТРО', + 'доброе утро', + ], [ 'TRUE', true, diff --git a/tests/data/Calculation/Translations.php b/tests/data/Calculation/Translations.php new file mode 100644 index 00000000..d470a05c --- /dev/null +++ b/tests/data/Calculation/Translations.php @@ -0,0 +1,48 @@ + [0.8, 0.813512072856517], + 'intersect' => [20.7, 20.671878197177865], + 'goodnessOfFit' => [0.904868, 0.9048681877346413], + 'equation' => 'Y = 20.67 * 0.81^X', + [3, 10, 3, 6, 8, 12, 1, 4, 9, 14], + [8, 2, 11, 6, 5, 4, 12, 9, 6, 1], + ], +]; diff --git a/tests/data/Shared/Trend/LinearBestFit.php b/tests/data/Shared/Trend/LinearBestFit.php new file mode 100644 index 00000000..b1be2f9a --- /dev/null +++ b/tests/data/Shared/Trend/LinearBestFit.php @@ -0,0 +1,20 @@ + [-1.1, -1.1064189189190], + 'intersect' => [14.1, 14.081081081081], + 'goodnessOfFit' => [0.873138, 0.8731378215564962], + 'equation' => 'Y = 14.08 + -1.11 * X', + [3, 10, 3, 6, 8, 12, 1, 4, 9, 14], + [8, 2, 11, 6, 5, 4, 12, 9, 6, 1], + ], + [ + 'slope' => [1.0, 1.0], + 'intersect' => [-2.0, -2.0], + 'goodnessOfFit' => [1.0, 1.0], + 'equation' => 'Y = -2 + 1 * X', + [1, 2, 3, 4, 5], + [3, 4, 5, 6, 7], + ], +]; diff --git a/tests/data/Style/NumberFormat.php b/tests/data/Style/NumberFormat.php index 5038d6da..aee357a7 100644 --- a/tests/data/Style/NumberFormat.php +++ b/tests/data/Style/NumberFormat.php @@ -88,6 +88,26 @@ return [ 12345.678900000001, '#,##0.000', ], + [ + '12.34 kg', + 12.34, + '0.00 "kg"', + ], + [ + 'kg 12.34', + 12.34, + '"kg" 0.00', + ], + [ + '12.34 kg.', + 12.34, + '0.00 "kg."', + ], + [ + 'kg. 12.34', + 12.34, + '"kg." 0.00', + ], [ '£ 12,345.68', 12345.678900000001, @@ -108,6 +128,11 @@ return [ 12345.678900000001, '#,##0.000\ [$]', ], + 'Spacing Character' => [ + '826.00 €', + 826, + '#,##0.00 _€', + ], [ '5.68', 5.6788999999999996, @@ -165,6 +190,81 @@ return [ -125.73999999999999, '$0.00" Surplus";$0.00" Shortage"', ], + [ + '12%', + 0.123, + '0%', + ], + [ + '10%', + 0.1, + '0%', + ], + [ + '10.0%', + 0.1, + '0.0%', + ], + [ + '-12%', + -0.123, + '0%', + ], + [ + '12.3 %', + 0.123, + '0.?? %', + ], + [ + '12.35 %', + 0.12345, + '0.?? %', + ], + [ + '12.345 %', + 0.12345, + '0.00?? %', + ], + [ + '12.3457 %', + 0.123456789, + '0.00?? %', + ], + [ + '-12.3 %', + -0.123, + '0.?? %', + ], + [ + '12.30 %age', + 0.123, + '0.00 %"age"', + ], + [ + '-12.30 %age', + -0.123, + '0.00 %"age"', + ], + [ + '12.30%', + 0.123, + '0.00%;(0.00%)', + ], + [ + '(12.30%)', + -0.123, + '0.00%;(0.00%)', + ], + [ + '12.30% ', + 0.123, + '0.00%_;( 0.00% )', + ], + [ + '( 12.30% )', + -0.123, + '_0.00%_;( 0.00% )', + ], // Fraction [ '5 1/4', @@ -274,12 +374,12 @@ return [ '[$-1010409]#,##0.00;-#,##0.00', ], [ - ' $ 23.06 ', + ' ($ 23.06 )', 23.0597, '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)', ], [ - ' € 13.03 ', + ' (€ 13.03 )', 13.0316, '_("€"* #,##0.00_);_("€"* \(#,##0.00\);_("€"* "-"??_);_(@_)', ], @@ -290,6 +390,11 @@ return [ 12345, '[Green]General', ], + [ + '12345', + 12345, + '[GrEeN]General', + ], [ '-70', -70, @@ -304,14 +409,24 @@ return [ [ '12345', 12345, - '[Blue]0;[Red]0', + '[Blue]0;[Red]0-', ], + [ + '12345-', + -12345, + '[BLUE]0;[red]0-', + ], + [ + '12345-', + -12345, + '[blue]0;[RED]0-', + ], + // Multiple colors with text substitution [ 'Positive', 12, '[Green]"Positive";[Red]"Negative";[Blue]"Zero"', ], - // Multiple colors with text substitution [ 'Zero', 0, @@ -322,6 +437,7 @@ return [ -2, '[Green]"Positive";[Red]"Negative";[Blue]"Zero"', ], + // Value break points [ '<=3500 red', 3500, @@ -342,4 +458,35 @@ return [ 25, '[Green][<>25]"<>25 green";[Red]"else red"', ], + // Leading/trailing quotes in mask + [ + '$12.34 ', + 12.34, + '$#,##0.00_;[RED]"($"#,##0.00")"', + ], + [ + '($12.34)', + -12.34, + '$#,##0.00_;[RED]"($"#,##0.00")"', + ], + [ + 'pfx. 25.00', + 25, + '"pfx." 0.00;"pfx." -0.00;"pfx." 0.00;', + ], + [ + 'pfx. 25.20', + 25.2, + '"pfx." 0.00;"pfx." -0.00;"pfx." 0.00;', + ], + [ + 'pfx. -25.20', + -25.2, + '"pfx." 0.00;"pfx." -0.00;"pfx." 0.00;', + ], + [ + 'pfx. 25.26', + 25.255555555555555, + '"pfx." 0.00;"pfx." -0.00;"pfx." 0.00;', + ], ]; diff --git a/tests/data/Style/NumberFormatDates.php b/tests/data/Style/NumberFormatDates.php index 9e476042..331a080c 100644 --- a/tests/data/Style/NumberFormatDates.php +++ b/tests/data/Style/NumberFormatDates.php @@ -36,22 +36,6 @@ return [ 22269.0625, '"y-m-d "yyyy-mm-dd" h:m:s "hh:mm:ss', ], - // Chinese date format - [ - '1960年12月19日', - 22269.0625, - '[DBNum1][$-804]yyyy"年"m"月"d"日";@', - ], - [ - '1960年12月', - 22269.0625, - '[DBNum1][$-804]yyyy"年"m"月";@', - ], - [ - '12月19日', - 22269.0625, - '[DBNum1][$-804]m"月"d"日";@', - ], [ '07:35:00 AM', 43270.315972222, @@ -77,4 +61,15 @@ return [ 1.1354166666667, '[h]:mm', ], + [ + '19331018', + 12345.6789, + '[DBNum4][$-804]yyyymmdd;@', + ], + // Technically should be 19331018 + [ + '19331018', + 12345.6789, + '[DBNum3][$-zh-CN]yyyymmdd;@', + ], ]; diff --git a/tests/data/Worksheet/namedRangeTest.xlsx b/tests/data/Worksheet/namedRangeTest.xlsx new file mode 100644 index 00000000..a4383bb0 Binary files /dev/null and b/tests/data/Worksheet/namedRangeTest.xlsx differ diff --git a/tests/data/Writer/Ods/content-empty.xml b/tests/data/Writer/Ods/content-empty.xml index 87f756db..c9620060 100644 --- a/tests/data/Writer/Ods/content-empty.xml +++ b/tests/data/Writer/Ods/content-empty.xml @@ -4,8 +4,9 @@ + + - diff --git a/tests/data/Writer/Ods/content-with-data.xml b/tests/data/Writer/Ods/content-with-data.xml index a9048047..a707d197 100644 --- a/tests/data/Writer/Ods/content-with-data.xml +++ b/tests/data/Writer/Ods/content-with-data.xml @@ -1,105 +1,124 @@ - - - - + + + + - - - + + + + - - - + + + + - - - + + + + - - - + + + + - - - + + + + - - - + + + + - - - + + + + - - - + + + + - - - + + + + - - - + + + + - - - + + + + - + - - + + - + 1 - + 12345.6789 - + 1 - + 01234 - + Lorem ipsum - + + #NULL! + + + Lorem ipsum + + - + 1 - - + + - + 1 1 - + 42798.572060185 - - + + + + - - + + - + 2 - + - + - \ No newline at end of file + diff --git a/tests/data/Writer/XLSX/saving_drawing_with_same_path.xlsx b/tests/data/Writer/XLSX/saving_drawing_with_same_path.xlsx new file mode 100644 index 00000000..a88d43d3 Binary files /dev/null and b/tests/data/Writer/XLSX/saving_drawing_with_same_path.xlsx differ