MDL-75484 lib: PHPSpreadSheet upgraded to 1.25.2

This commit is contained in:
David Woloszyn 2022-10-07 17:01:12 +11:00
parent e4c5a12a1c
commit efc63be039
351 changed files with 20962 additions and 8620 deletions

View File

@ -2,6 +2,11 @@
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
exit(1);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit47a82a2b792e78d18b5f54d474d822dc::getLoader();
return ComposerAutoloaderInite0d802f9161d0d84fba97a5520240273::getLoader();

View File

@ -21,12 +21,14 @@ use Composer\Semver\VersionParser;
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
@ -37,7 +39,7 @@ class InstalledVersions
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
@ -241,7 +243,7 @@ class InstalledVersions
/**
* @return array
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
@ -255,7 +257,7 @@ class InstalledVersions
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@ -278,7 +280,7 @@ class InstalledVersions
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
@ -301,7 +303,7 @@ class InstalledVersions
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
@ -311,7 +313,7 @@ class InstalledVersions
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{

View File

@ -2,7 +2,7 @@
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(

View File

@ -2,7 +2,7 @@
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(

View File

@ -2,7 +2,7 @@
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(

View File

@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit47a82a2b792e78d18b5f54d474d822dc
class ComposerAutoloaderInite0d802f9161d0d84fba97a5520240273
{
private static $loader;
@ -24,31 +24,12 @@ class ComposerAutoloaderInit47a82a2b792e78d18b5f54d474d822dc
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit47a82a2b792e78d18b5f54d474d822dc', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInit47a82a2b792e78d18b5f54d474d822dc', 'loadClassLoader'));
spl_autoload_register(array('ComposerAutoloaderInite0d802f9161d0d84fba97a5520240273', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInite0d802f9161d0d84fba97a5520240273', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit47a82a2b792e78d18b5f54d474d822dc::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInite0d802f9161d0d84fba97a5520240273::getInitializer($loader));
$loader->register(true);

View File

@ -4,7 +4,7 @@
namespace Composer\Autoload;
class ComposerStaticInit47a82a2b792e78d18b5f54d474d822dc
class ComposerStaticInite0d802f9161d0d84fba97a5520240273
{
public static $prefixLengthsPsr4 = array (
'P' =>
@ -59,9 +59,9 @@ class ComposerStaticInit47a82a2b792e78d18b5f54d474d822dc
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit47a82a2b792e78d18b5f54d474d822dc::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit47a82a2b792e78d18b5f54d474d822dc::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit47a82a2b792e78d18b5f54d474d822dc::$classMap;
$loader->prefixLengthsPsr4 = ComposerStaticInite0d802f9161d0d84fba97a5520240273::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInite0d802f9161d0d84fba97a5520240273::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInite0d802f9161d0d84fba97a5520240273::$classMap;
}, null, ClassLoader::class);
}

View File

@ -115,17 +115,17 @@
},
{
"name": "phpoffice/phpspreadsheet",
"version": "1.21.0",
"version_normalized": "1.21.0.0",
"version": "1.25.2",
"version_normalized": "1.25.2.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "1a359d2ccbb89c05f5dffb32711a95f4afc67964"
"reference": "a317a09e7def49852400a4b3eca4a4b0790ceeb5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/1a359d2ccbb89c05f5dffb32711a95f4afc67964",
"reference": "1a359d2ccbb89c05f5dffb32711a95f4afc67964",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/a317a09e7def49852400a4b3eca4a4b0790ceeb5",
"reference": "a317a09e7def49852400a4b3eca4a4b0790ceeb5",
"shasum": ""
},
"require": {
@ -142,35 +142,36 @@
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"ezyang/htmlpurifier": "^4.13",
"ezyang/htmlpurifier": "^4.15",
"maennchen/zipstream-php": "^2.1",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"php": "^7.3 || ^8.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/simple-cache": "^1.0"
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"dompdf/dompdf": "^1.0",
"dompdf/dompdf": "^1.0 || ^2.0",
"friendsofphp/php-cs-fixer": "^3.2",
"jpgraph/jpgraph": "^4.0",
"mpdf/mpdf": "^8.0",
"mitoteam/jpgraph": "10.2.4",
"mpdf/mpdf": "8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.0",
"squizlabs/php_codesniffer": "^3.6",
"tecnickcom/tcpdf": "^6.4"
"squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "6.5"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)",
"jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-intl": "PHP Internationalization Functions",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)"
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
},
"time": "2022-01-06T11:10:08+00:00",
"time": "2022-09-25T17:21:01+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -216,7 +217,7 @@
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.21.0"
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.25.2"
},
"install-path": "../phpoffice/phpspreadsheet"
},

View File

@ -1,22 +1,22 @@
<?php return array(
'root' => array(
'name' => '__root__',
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => NULL,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => NULL,
'name' => '__root__',
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => NULL,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => NULL,
'dev_requirement' => false,
),
'ezyang/htmlpurifier' => array(
@ -34,19 +34,19 @@
'markbaker/complex' => array(
'pretty_version' => '3.0.1',
'version' => '3.0.1.0',
'reference' => 'ab8bc271e404909db09ff2d5ffa1e538085c0f22',
'type' => 'library',
'install_path' => __DIR__ . '/../markbaker/complex',
'aliases' => array(),
'reference' => 'ab8bc271e404909db09ff2d5ffa1e538085c0f22',
'dev_requirement' => false,
),
'markbaker/matrix' => array(
'pretty_version' => '3.0.0',
'version' => '3.0.0.0',
'reference' => 'c66aefcafb4f6c269510e9ac46b82619a904c576',
'type' => 'library',
'install_path' => __DIR__ . '/../markbaker/matrix',
'aliases' => array(),
'reference' => 'c66aefcafb4f6c269510e9ac46b82619a904c576',
'dev_requirement' => false,
),
'myclabs/php-enum' => array(
@ -56,48 +56,48 @@
),
),
'phpoffice/phpspreadsheet' => array(
'pretty_version' => '1.21.0',
'version' => '1.21.0.0',
'pretty_version' => '1.25.2',
'version' => '1.25.2.0',
'reference' => 'a317a09e7def49852400a4b3eca4a4b0790ceeb5',
'type' => 'library',
'install_path' => __DIR__ . '/../phpoffice/phpspreadsheet',
'aliases' => array(),
'reference' => '1a359d2ccbb89c05f5dffb32711a95f4afc67964',
'dev_requirement' => false,
),
'psr/http-client' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-client',
'aliases' => array(),
'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621',
'dev_requirement' => false,
),
'psr/http-factory' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-factory',
'aliases' => array(),
'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be',
'dev_requirement' => false,
),
'psr/http-message' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-message',
'aliases' => array(),
'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363',
'dev_requirement' => false,
),
'psr/simple-cache' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/simple-cache',
'aliases' => array(),
'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b',
'dev_requirement' => false,
),
'symfony/polyfill-mbstring' => array(

View File

@ -5,6 +5,328 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com)
and this project adheres to [Semantic Versioning](https://semver.org).
## 1.25.2 - 2022-09-25
### Added
- Nothing
### Changed
- Nothing
### Deprecated
- Nothing
### Removed
- Nothing
### Fixed
- Composer dependency clash with ezyang/htmlpurifier
## 1.25.0 - 2022-09-25
### Added
- Implementation of the new `TEXTBEFORE()`, `TEXTAFTER()` and `TEXTSPLIT()` Excel Functions
- Implementation of the `ARRAYTOTEXT()` and `VALUETOTEXT()` Excel Functions
- Support for [mitoteam/jpgraph](https://packagist.org/packages/mitoteam/jpgraph) implementation of
JpGraph library to render charts added.
- Charts: Add Gradients, Transparency, Hidden Axes, Rounded Corners, Trendlines, Date Axes.
### Changed
- Allow variant behaviour when merging cells [Issue #3065](https://github.com/PHPOffice/PhpSpreadsheet/issues/3065)
- Merge methods now allow an additional `$behaviour` argument. Permitted values are:
- Worksheet::MERGE_CELL_CONTENT_EMPTY - Empty the content of the hidden cells (the default behaviour)
- Worksheet::MERGE_CELL_CONTENT_HIDE - Keep the content of the hidden cells
- Worksheet::MERGE_CELL_CONTENT_MERGE - Move the content of the hidden cells into the first cell
### Deprecated
- Axis getLineProperty deprecated in favor of getLineColorProperty.
- Moved majorGridlines and minorGridlines from Chart to Axis. Setting either in Chart constructor or through Chart methods, or getting either using Chart methods is deprecated.
- Chart::EXCEL_COLOR_TYPE_* copied from Properties to ChartColor; use in Properties is deprecated.
- ChartColor::EXCEL_COLOR_TYPE_ARGB deprecated in favor of EXCEL_COLOR_TYPE_RGB ("A" component was never allowed).
- Misspelled Properties::LINE_STYLE_DASH_SQUERE_DOT deprecated in favor of LINE_STYLE_DASH_SQUARE_DOT.
- Clone not permitted for Spreadsheet. Spreadsheet->copy() can be used instead.
### Removed
- Nothing
### Fixed
- Fix update to defined names when inserting/deleting rows/columns [Issue #3076](https://github.com/PHPOffice/PhpSpreadsheet/issues/3076) [PR #3077](https://github.com/PHPOffice/PhpSpreadsheet/pull/3077)
- Fix DataValidation sqRef when inserting/deleting rows/columns [Issue #3056](https://github.com/PHPOffice/PhpSpreadsheet/issues/3056) [PR #3074](https://github.com/PHPOffice/PhpSpreadsheet/pull/3074)
- Named ranges not usable as anchors in OFFSET function [Issue #3013](https://github.com/PHPOffice/PhpSpreadsheet/issues/3013)
- Fully flatten an array [Issue #2955](https://github.com/PHPOffice/PhpSpreadsheet/issues/2955) [PR #2956](https://github.com/PHPOffice/PhpSpreadsheet/pull/2956)
- cellExists() and getCell() methods should support UTF-8 named cells [Issue #2987](https://github.com/PHPOffice/PhpSpreadsheet/issues/2987) [PR #2988](https://github.com/PHPOffice/PhpSpreadsheet/pull/2988)
- Spreadsheet copy fixed, clone disabled. [PR #2951](https://github.com/PHPOffice/PhpSpreadsheet/pull/2951)
- Fix PDF problems with text rotation and paper size. [Issue #1747](https://github.com/PHPOffice/PhpSpreadsheet/issues/1747) [Issue #1713](https://github.com/PHPOffice/PhpSpreadsheet/issues/1713) [PR #2960](https://github.com/PHPOffice/PhpSpreadsheet/pull/2960)
- Limited support for chart titles as formulas [Issue #2965](https://github.com/PHPOffice/PhpSpreadsheet/issues/2965) [Issue #749](https://github.com/PHPOffice/PhpSpreadsheet/issues/749) [PR #2971](https://github.com/PHPOffice/PhpSpreadsheet/pull/2971)
- Add Gradients, Transparency, and Hidden Axes to Chart [Issue #2257](https://github.com/PHPOffice/PhpSpreadsheet/issues/2257) [Issue #2229](https://github.com/PHPOffice/PhpSpreadsheet/issues/2929) [Issue #2935](https://github.com/PHPOffice/PhpSpreadsheet/issues/2935) [PR #2950](https://github.com/PHPOffice/PhpSpreadsheet/pull/2950)
- Chart Support for Rounded Corners and Trendlines [Issue #2968](https://github.com/PHPOffice/PhpSpreadsheet/issues/2968) [Issue #2815](https://github.com/PHPOffice/PhpSpreadsheet/issues/2815) [PR #2976](https://github.com/PHPOffice/PhpSpreadsheet/pull/2976)
- Add setName Method for Chart [Issue #2991](https://github.com/PHPOffice/PhpSpreadsheet/issues/2991) [PR #3001](https://github.com/PHPOffice/PhpSpreadsheet/pull/3001)
- Eliminate partial dependency on php-intl in StringHelper [Issue #2982](https://github.com/PHPOffice/PhpSpreadsheet/issues/2982) [PR #2994](https://github.com/PHPOffice/PhpSpreadsheet/pull/2994)
- Minor changes for Pdf [Issue #2999](https://github.com/PHPOffice/PhpSpreadsheet/issues/2999) [PR #3002](https://github.com/PHPOffice/PhpSpreadsheet/pull/3002) [PR #3006](https://github.com/PHPOffice/PhpSpreadsheet/pull/3006)
- Html/Pdf Do net set background color for cells using (default) nofill [PR #3016](https://github.com/PHPOffice/PhpSpreadsheet/pull/3016)
- Add support for Date Axis to Chart [Issue #2967](https://github.com/PHPOffice/PhpSpreadsheet/issues/2967) [PR #3018](https://github.com/PHPOffice/PhpSpreadsheet/pull/3018)
- Reconcile Differences Between Css and Excel for Cell Alignment [PR #3048](https://github.com/PHPOffice/PhpSpreadsheet/pull/3048)
- R1C1 Format Internationalization and Better Support for Relative Offsets [Issue #1704](https://github.com/PHPOffice/PhpSpreadsheet/issues/1704) [PR #3052](https://github.com/PHPOffice/PhpSpreadsheet/pull/3052)
- Minor Fix for Percentage Formatting [Issue #1929](https://github.com/PHPOffice/PhpSpreadsheet/issues/1929) [PR #3053](https://github.com/PHPOffice/PhpSpreadsheet/pull/3053)
## 1.24.1 - 2022-07-18
### Added
- Support for SimpleCache Interface versions 1.0, 2.0 and 3.0
- Add Chart Axis Option textRotation [Issue #2705](https://github.com/PHPOffice/PhpSpreadsheet/issues/2705) [PR #2940](https://github.com/PHPOffice/PhpSpreadsheet/pull/2940)
### Changed
- Nothing
### Deprecated
- Nothing
### Removed
- Nothing
### Fixed
- Fix Encoding issue with Html reader (PHP 8.2 deprecation for mb_convert_encoding) [Issue #2942](https://github.com/PHPOffice/PhpSpreadsheet/issues/2942) [PR #2943](https://github.com/PHPOffice/PhpSpreadsheet/pull/2943)
- Additional Chart fixes
- Pie chart with part separated unwantedly [Issue #2506](https://github.com/PHPOffice/PhpSpreadsheet/issues/2506) [PR #2928](https://github.com/PHPOffice/PhpSpreadsheet/pull/2928)
- Chart styling is lost on simple load / save process [Issue #1797](https://github.com/PHPOffice/PhpSpreadsheet/issues/1797) [Issue #2077](https://github.com/PHPOffice/PhpSpreadsheet/issues/2077) [PR #2930](https://github.com/PHPOffice/PhpSpreadsheet/pull/2930)
- Can't create contour chart (surface 2d) [Issue #2931](https://github.com/PHPOffice/PhpSpreadsheet/issues/2931) [PR #2933](https://github.com/PHPOffice/PhpSpreadsheet/pull/2933)
- VLOOKUP Breaks When Array Contains Null Cells [Issue #2934](https://github.com/PHPOffice/PhpSpreadsheet/issues/2934) [PR #2939](https://github.com/PHPOffice/PhpSpreadsheet/pull/2939)
## 1.24.0 - 2022-07-09
Note that this will be the last 1.x branch release before the 2.x release. We will maintain both branches in parallel for a time; but users are requested to update to version 2.0 once that is fully available.
### Added
- Added `removeComment()` method for Worksheet [PR #2875](https://github.com/PHPOffice/PhpSpreadsheet/pull/2875/files)
- Add point size option for scatter charts [Issue #2298](https://github.com/PHPOffice/PhpSpreadsheet/issues/2298) [PR #2801](https://github.com/PHPOffice/PhpSpreadsheet/pull/2801)
- Basic support for Xlsx reading/writing Chart Sheets [PR #2830](https://github.com/PHPOffice/PhpSpreadsheet/pull/2830)
Note that a ChartSheet is still only written as a normal Worksheet containing a single chart, not as an actual ChartSheet.
- Added Worksheet visibility in Ods Reader [PR #2851](https://github.com/PHPOffice/PhpSpreadsheet/pull/2851) and Gnumeric Reader [PR #2853](https://github.com/PHPOffice/PhpSpreadsheet/pull/2853)
- Added Worksheet visibility in Ods Writer [PR #2850](https://github.com/PHPOffice/PhpSpreadsheet/pull/2850)
- Allow Csv Reader to treat string as contents of file [Issue #1285](https://github.com/PHPOffice/PhpSpreadsheet/issues/1285) [PR #2792](https://github.com/PHPOffice/PhpSpreadsheet/pull/2792)
- Allow Csv Reader to store null string rather than leave cell empty [Issue #2840](https://github.com/PHPOffice/PhpSpreadsheet/issues/2840) [PR #2842](https://github.com/PHPOffice/PhpSpreadsheet/pull/2842)
- Provide new Worksheet methods to identify if a row or column is "empty", making allowance for different definitions of "empty":
- Treat rows/columns containing no cell records as empty (default)
- Treat cells containing a null value as empty
- Treat cells containing an empty string as empty
### Changed
- Modify `rangeBoundaries()`, `rangeDimension()` and `getRangeBoundaries()` Coordinate methods to work with row/column ranges as well as with cell ranges and cells [PR #2926](https://github.com/PHPOffice/PhpSpreadsheet/pull/2926)
- Better enforcement of value modification to match specified datatype when using `setValueExplicit()`
- Relax validation of merge cells to allow merge for a single cell reference [Issue #2776](https://github.com/PHPOffice/PhpSpreadsheet/issues/2776)
- Memory and speed improvements, particularly for the Cell Collection, and the Writers.
See [the Discussion section on github](https://github.com/PHPOffice/PhpSpreadsheet/discussions/2821) for details of performance across versions
- Improved performance for removing rows/columns from a worksheet
### Deprecated
- Nothing
### Removed
- Nothing
### Fixed
- Xls Reader resolving absolute named ranges to relative ranges [Issue #2826](https://github.com/PHPOffice/PhpSpreadsheet/issues/2826) [PR #2827](https://github.com/PHPOffice/PhpSpreadsheet/pull/2827)
- Null value handling in the Excel Math/Trig PRODUCT() function [Issue #2833](https://github.com/PHPOffice/PhpSpreadsheet/issues/2833) [PR #2834](https://github.com/PHPOffice/PhpSpreadsheet/pull/2834)
- Invalid Print Area defined in Xlsx corrupts internal storage of print area [Issue #2848](https://github.com/PHPOffice/PhpSpreadsheet/issues/2848) [PR #2849](https://github.com/PHPOffice/PhpSpreadsheet/pull/2849)
- Time interval formatting [Issue #2768](https://github.com/PHPOffice/PhpSpreadsheet/issues/2768) [PR #2772](https://github.com/PHPOffice/PhpSpreadsheet/pull/2772)
- Copy from Xls(x) to Html/Pdf loses drawings [PR #2788](https://github.com/PHPOffice/PhpSpreadsheet/pull/2788)
- Html Reader converting cell containing 0 to null string [Issue #2810](https://github.com/PHPOffice/PhpSpreadsheet/issues/2810) [PR #2813](https://github.com/PHPOffice/PhpSpreadsheet/pull/2813)
- Many fixes for Charts, especially, but not limited to, Scatter, Bubble, and Surface charts. [Issue #2762](https://github.com/PHPOffice/PhpSpreadsheet/issues/2762) [Issue #2299](https://github.com/PHPOffice/PhpSpreadsheet/issues/2299) [Issue #2700](https://github.com/PHPOffice/PhpSpreadsheet/issues/2700) [Issue #2817](https://github.com/PHPOffice/PhpSpreadsheet/issues/2817) [Issue #2763](https://github.com/PHPOffice/PhpSpreadsheet/issues/2763) [Issue #2219](https://github.com/PHPOffice/PhpSpreadsheet/issues/2219) [Issue #2863](https://github.com/PHPOffice/PhpSpreadsheet/issues/2863) [PR #2828](https://github.com/PHPOffice/PhpSpreadsheet/pull/2828) [PR #2841](https://github.com/PHPOffice/PhpSpreadsheet/pull/2841) [PR #2846](https://github.com/PHPOffice/PhpSpreadsheet/pull/2846) [PR #2852](https://github.com/PHPOffice/PhpSpreadsheet/pull/2852) [PR #2856](https://github.com/PHPOffice/PhpSpreadsheet/pull/2856) [PR #2865](https://github.com/PHPOffice/PhpSpreadsheet/pull/2865) [PR #2872](https://github.com/PHPOffice/PhpSpreadsheet/pull/2872) [PR #2879](https://github.com/PHPOffice/PhpSpreadsheet/pull/2879) [PR #2898](https://github.com/PHPOffice/PhpSpreadsheet/pull/2898) [PR #2906](https://github.com/PHPOffice/PhpSpreadsheet/pull/2906) [PR #2922](https://github.com/PHPOffice/PhpSpreadsheet/pull/2922) [PR #2923](https://github.com/PHPOffice/PhpSpreadsheet/pull/2923)
- Adjust both coordinates for two-cell anchors when rows/columns are added/deleted. [Issue #2908](https://github.com/PHPOffice/PhpSpreadsheet/issues/2908) [PR #2909](https://github.com/PHPOffice/PhpSpreadsheet/pull/2909)
- Keep calculated string results below 32K. [PR #2921](https://github.com/PHPOffice/PhpSpreadsheet/pull/2921)
- Filter out illegal Unicode char values FFFE/FFFF. [Issue #2897](https://github.com/PHPOffice/PhpSpreadsheet/issues/2897) [PR #2910](https://github.com/PHPOffice/PhpSpreadsheet/pull/2910)
- Better handling of REF errors and propagation of all errors in Calculation engine. [PR #2902](https://github.com/PHPOffice/PhpSpreadsheet/pull/2902)
- Calculating Engine regexp for Column/Row references when there are multiple quoted worksheet references in the formula [Issue #2874](https://github.com/PHPOffice/PhpSpreadsheet/issues/2874) [PR #2899](https://github.com/PHPOffice/PhpSpreadsheet/pull/2899)
## 1.23.0 - 2022-04-24
### Added
- Ods Writer support for Freeze Pane [Issue #2013](https://github.com/PHPOffice/PhpSpreadsheet/issues/2013) [PR #2755](https://github.com/PHPOffice/PhpSpreadsheet/pull/2755)
- Ods Writer support for setting column width/row height (including the use of AutoSize) [Issue #2346](https://github.com/PHPOffice/PhpSpreadsheet/issues/2346) [PR #2753](https://github.com/PHPOffice/PhpSpreadsheet/pull/2753)
- Introduced CellAddress, CellRange, RowRange and ColumnRange value objects that can be used as an alternative to a string value (e.g. `'C5'`, `'B2:D4'`, `'2:2'` or `'B:C'`) in appropriate contexts.
- Implementation of the FILTER(), SORT(), SORTBY() and UNIQUE() Lookup/Reference (array) functions.
- Implementation of the ISREF() Information function.
- Added support for reading "formatted" numeric values from Csv files; although default behaviour of reading these values as strings is preserved.
(i.e a value of "12,345.67" can be read as numeric `12345.67`, not simply as a string `"12,345.67"`, if the `castFormattedNumberToNumeric()` setting is enabled.
This functionality is locale-aware, using the server's locale settings to identify the thousands and decimal separators.
- Support for two cell anchor drawing of images. [#2532](https://github.com/PHPOffice/PhpSpreadsheet/pull/2532) [#2674](https://github.com/PHPOffice/PhpSpreadsheet/pull/2674)
- Limited support for Xls Reader to handle Conditional Formatting:
Ranges and Rules are read, but style is currently limited to font size, weight and color; and to fill style and color.
- Add ability to suppress Mac line ending check for CSV [#2623](https://github.com/PHPOffice/PhpSpreadsheet/pull/2623)
- Initial support for creating and writing Tables (Xlsx Writer only) [PR #2671](https://github.com/PHPOffice/PhpSpreadsheet/pull/2671)
See `/samples/Table` for examples of use.
Note that PreCalculateFormulas needs to be disabled when saving spreadsheets containing tables with formulae (totals or column formulae).
### Changed
- Gnumeric Reader now loads number formatting for cells.
- Gnumeric Reader now correctly identifies selected worksheet and selected cells in a worksheet.
- Some Refactoring of the Ods Reader, moving all formula and address translation from Ods to Excel into a separate class to eliminate code duplication and ensure consistency.
- Make Boolean Conversion in Csv Reader locale-aware when using the String Value Binder.
This is determined by the Calculation Engine locale setting.
(i.e. `"Vrai"` wil be converted to a boolean `true` if the Locale is set to `fr`.)
- Allow `psr/simple-cache` 2.x
### Deprecated
- All Excel Function implementations in `Calculation\Functions` (including the Error functions) have been moved to dedicated classes for groups of related functions. See the docblocks against all the deprecated methods for details of the new methods to call instead. At some point, these old classes will be deleted.
- Worksheet methods that reference cells "byColumnandRow". All such methods have an equivalent that references the cell by its address (e.g. '`E3'` rather than `5, 3`).
These functions now accept either a cell address string (`'E3')` or an array with columnId and rowId (`[5, 3]`) or a new `CellAddress` object as their `cellAddress`/`coordinate` argument.
This includes the methods:
- `setCellValueByColumnAndRow()` use the equivalent `setCellValue()`
- `setCellValueExplicitByColumnAndRow()` use the equivalent `setCellValueExplicit()`
- `getCellByColumnAndRow()` use the equivalent `getCell()`
- `cellExistsByColumnAndRow()` use the equivalent `cellExists()`
- `getStyleByColumnAndRow()` use the equivalent `getStyle()`
- `setBreakByColumnAndRow()` use the equivalent `setBreak()`
- `mergeCellsByColumnAndRow()` use the equivalent `mergeCells()`
- `unmergeCellsByColumnAndRow()` use the equivalent `unmergeCells()`
- `protectCellsByColumnAndRow()` use the equivalent `protectCells()`
- `unprotectCellsByColumnAndRow()` use the equivalent `unprotectCells()`
- `setAutoFilterByColumnAndRow()` use the equivalent `setAutoFilter()`
- `freezePaneByColumnAndRow()` use the equivalent `freezePane()`
- `getCommentByColumnAndRow()` use the equivalent `getComment()`
- `setSelectedCellByColumnAndRow()` use the equivalent `setSelectedCells()`
This change provides more consistency in the methods (not every "by cell address" method has an equivalent "byColumnAndRow" method);
and the "by cell address" methods often provide more flexibility, such as allowing a range of cells, or referencing them by passing the defined name of a named range as the argument.
### Removed
- Nothing
### Fixed
- Make allowance for the AutoFilter dropdown icon in the first row of an Autofilter range when using Autosize columns. [Issue #2413](https://github.com/PHPOffice/PhpSpreadsheet/issues/2413) [PR #2754](https://github.com/PHPOffice/PhpSpreadsheet/pull/2754)
- Support for "chained" ranges (e.g. `A5:C10:C20:F1`) in the Calculation Engine; and also support for using named ranges with the Range operator (e.g. `NamedRange1:NamedRange2`) [Issue #2730](https://github.com/PHPOffice/PhpSpreadsheet/issues/2730) [PR #2746](https://github.com/PHPOffice/PhpSpreadsheet/pull/2746)
- Update Conditional Formatting ranges and rule conditions when inserting/deleting rows/columns [Issue #2678](https://github.com/PHPOffice/PhpSpreadsheet/issues/2678) [PR #2689](https://github.com/PHPOffice/PhpSpreadsheet/pull/2689)
- Allow `INDIRECT()` to accept row/column ranges as well as cell ranges [PR #2687](https://github.com/PHPOffice/PhpSpreadsheet/pull/2687)
- Fix bug when deleting cells with hyperlinks, where the hyperlink was then being "inherited" by whatever cell moved to that cell address.
- Fix bug in Conditional Formatting in the Xls Writer that resulted in a broken file when there were multiple conditional ranges in a worksheet.
- Fix Conditional Formatting in the Xls Writer to work with rules that contain string literals, cell references and formulae.
- Fix for setting Active Sheet to the first loaded worksheet when bookViews element isn't defined [Issue #2666](https://github.com/PHPOffice/PhpSpreadsheet/issues/2666) [PR #2669](https://github.com/PHPOffice/PhpSpreadsheet/pull/2669)
- Fixed behaviour of XLSX font style vertical align settings [PR #2619](https://github.com/PHPOffice/PhpSpreadsheet/pull/2619)
- Resolved formula translations to handle separators (row and column) for array functions as well as for function argument separators; and cleanly handle nesting levels.
Note that this method is used when translating Excel functions between `en_us` and other locale languages, as well as when converting formulae between different spreadsheet formats (e.g. Ods to Excel).
Nor is this a perfect solution, as there may still be issues when function calls have array arguments that themselves contain function calls; but it's still better than the current logic.
- Fix for escaping double quotes within a formula [Issue #1971](https://github.com/PHPOffice/PhpSpreadsheet/issues/1971) [PR #2651](https://github.com/PHPOffice/PhpSpreadsheet/pull/2651)
- Change open mode for output from `wb+` to `wb` [Issue #2372](https://github.com/PHPOffice/PhpSpreadsheet/issues/2372) [PR #2657](https://github.com/PHPOffice/PhpSpreadsheet/pull/2657)
- Use color palette if supplied [Issue #2499](https://github.com/PHPOffice/PhpSpreadsheet/issues/2499) [PR #2595](https://github.com/PHPOffice/PhpSpreadsheet/pull/2595)
- Xls reader treat drawing offsets as int rather than float [PR #2648](https://github.com/PHPOffice/PhpSpreadsheet/pull/2648)
- Handle booleans in conditional styles properly [PR #2654](https://github.com/PHPOffice/PhpSpreadsheet/pull/2654)
- Fix for reading files in the root directory of a ZipFile, which should not be prefixed by relative paths ("./") as dirname($filename) does by default.
- Fix invalid style of cells in empty columns with columnDimensions and rows with rowDimensions in added external sheet. [PR #2739](https://github.com/PHPOffice/PhpSpreadsheet/pull/2739)
- Time Interval Formatting [Issue #2768](https://github.com/PHPOffice/PhpSpreadsheet/issues/2768) [PR #2772](https://github.com/PHPOffice/PhpSpreadsheet/pull/2772)
## 1.22.0 - 2022-02-18
### Added
- Namespacing phase 2 - styles.
[PR #2471](https://github.com/PHPOffice/PhpSpreadsheet/pull/2471)
- Improved support for passing of array arguments to Excel function implementations to return array results (where appropriate). [Issue #2551](https://github.com/PHPOffice/PhpSpreadsheet/issues/2551)
This is the first stage in an ongoing process of adding array support to all appropriate function implementations,
- Support for the Excel365 Math/Trig SEQUENCE() function [PR #2536](https://github.com/PHPOffice/PhpSpreadsheet/pull/2536)
- Support for the Excel365 Math/Trig RANDARRAY() function [PR #2540](https://github.com/PHPOffice/PhpSpreadsheet/pull/2540)
Note that the Spill Operator is not yet supported in the Calculation Engine; but this can still be useful for defining array constants.
- Improved support for Conditional Formatting Rules [PR #2491](https://github.com/PHPOffice/PhpSpreadsheet/pull/2491)
- Provide support for a wider range of Conditional Formatting Rules for Xlsx Reader/Writer:
- Cells Containing (cellIs)
- Specific Text (containing, notContaining, beginsWith, endsWith)
- Dates Occurring (all supported timePeriods)
- Blanks/NoBlanks
- Errors/NoErrors
- Duplicates/Unique
- Expression
- Provision of CF Wizards (for all the above listed rule types) to help create/modify CF Rules without having to manage all the combinations of types/operators, and the complexities of formula expressions, or the text/timePeriod attributes.
See [documentation](https://phpspreadsheet.readthedocs.io/en/latest/topics/conditional-formatting/) for details
- Full support of the above CF Rules for the Xlsx Reader and Writer; even when the file being loaded has CF rules listed in the `<extLst><ext><ConditionalFormattings>` element for the worksheet rather than the `<ConditionalFormatting>` element.
- Provision of a CellMatcher to identify if rules are matched for a cell, and which matching style will be applied.
- Improved documentation and examples, covering all supported CF rule types.
- Add support for one digit decimals (FORMAT_NUMBER_0, FORMAT_PERCENTAGE_0). [PR #2525](https://github.com/PHPOffice/PhpSpreadsheet/pull/2525)
- Initial work enabling Excel function implementations for handling arrays as arguments when used in "array formulae" [#2562](https://github.com/PHPOffice/PhpSpreadsheet/issues/2562)
- Enable most of the Date/Time functions to accept array arguments [#2573](https://github.com/PHPOffice/PhpSpreadsheet/issues/2573)
- Array ready functions - Text, Math/Trig, Statistical, Engineering and Logical [#2580](https://github.com/PHPOffice/PhpSpreadsheet/issues/2580)
### Changed
- Additional Russian translations for Excel Functions (courtesy of aleks-samurai).
- Improved code coverage for NumberFormat. [PR #2556](https://github.com/PHPOffice/PhpSpreadsheet/pull/2556)
- Extract some methods from the Calculation Engine into dedicated classes [#2537](https://github.com/PHPOffice/PhpSpreadsheet/issues/2537)
- Eliminate calls to `flattenSingleValue()` that are no longer required when we're checking for array values as arguments [#2590](https://github.com/PHPOffice/PhpSpreadsheet/issues/2590)
### Deprecated
- Nothing
### Removed
- Nothing
### Fixed
- Fixed `ReferenceHelper@insertNewBefore` behavior when removing column before last column with null value [PR #2541](https://github.com/PHPOffice/PhpSpreadsheet/pull/2541)
- Fix bug with `DOLLARDE()` and `DOLLARFR()` functions when the dollar value is negative [Issue #2578](https://github.com/PHPOffice/PhpSpreadsheet/issues/2578) [PR #2579](https://github.com/PHPOffice/PhpSpreadsheet/pull/2579)
- Fix partial function name matching when translating formulae from Russian to English [Issue #2533](https://github.com/PHPOffice/PhpSpreadsheet/issues/2533) [PR #2534](https://github.com/PHPOffice/PhpSpreadsheet/pull/2534)
- Various bugs related to Conditional Formatting Rules, and errors in the Xlsx Writer for Conditional Formatting [PR #2491](https://github.com/PHPOffice/PhpSpreadsheet/pull/2491)
- Xlsx Reader merge range fixes. [Issue #2501](https://github.com/PHPOffice/PhpSpreadsheet/issues/2501) [PR #2504](https://github.com/PHPOffice/PhpSpreadsheet/pull/2504)
- Handle explicit "date" type for Cell in Xlsx Reader. [Issue #2373](https://github.com/PHPOffice/PhpSpreadsheet/issues/2373) [PR #2485](https://github.com/PHPOffice/PhpSpreadsheet/pull/2485)
- Recalibrate Row/Column Dimensions after removeRow/Column. [Issue #2442](https://github.com/PHPOffice/PhpSpreadsheet/issues/2442) [PR #2486](https://github.com/PHPOffice/PhpSpreadsheet/pull/2486)
- Refinement for XIRR. [Issue #2469](https://github.com/PHPOffice/PhpSpreadsheet/issues/2469) [PR #2487](https://github.com/PHPOffice/PhpSpreadsheet/pull/2487)
- Xlsx Reader handle cell with non-null explicit type but null value. [Issue #2488](https://github.com/PHPOffice/PhpSpreadsheet/issues/2488) [PR #2489](https://github.com/PHPOffice/PhpSpreadsheet/pull/2489)
- Xlsx Reader fix height and width for oneCellAnchorDrawings. [PR #2492](https://github.com/PHPOffice/PhpSpreadsheet/pull/2492)
- Fix rounding error in NumberFormat::NUMBER_PERCENTAGE, NumberFormat::NUMBER_PERCENTAGE_00. [PR #2555](https://github.com/PHPOffice/PhpSpreadsheet/pull/2555)
- Don't treat thumbnail file as xml. [Issue #2516](https://github.com/PHPOffice/PhpSpreadsheet/issues/2516) [PR #2517](https://github.com/PHPOffice/PhpSpreadsheet/pull/2517)
- Eliminating Xlsx Reader warning when no sz tag for RichText. [Issue #2542](https://github.com/PHPOffice/PhpSpreadsheet/issues/2542) [PR #2550](https://github.com/PHPOffice/PhpSpreadsheet/pull/2550)
- Fix Xlsx/Xls Writer handling of inline strings. [Issue #353](https://github.com/PHPOffice/PhpSpreadsheet/issues/353) [PR #2569](https://github.com/PHPOffice/PhpSpreadsheet/pull/2569)
- Richtext colors were not being read correctly after namespace change [#2458](https://github.com/PHPOffice/PhpSpreadsheet/issues/2458)
- Fix discrepancy between the way markdown tables are rendered in ReadTheDocs and in PHPStorm [#2520](https://github.com/PHPOffice/PhpSpreadsheet/issues/2520)
- Update Russian Functions Text File [#2557](https://github.com/PHPOffice/PhpSpreadsheet/issues/2557)
- Fix documentation, instantiation example [#2564](https://github.com/PHPOffice/PhpSpreadsheet/issues/2564)
## 1.21.0 - 2022-01-06
### Added

View File

@ -11,6 +11,80 @@
PhpSpreadsheet is a library written in pure PHP and offers a set of classes that
allow you to read and write various spreadsheet file formats such as Excel and LibreOffice Calc.
## PHP version support
LTS: Support for PHP versions will only be maintained for a period of six months beyond the
[end of life of that PHP version](https://www.php.net/eol.php).
Currently the required PHP minimum version is PHP __7.3__.
See the `composer.json` for other requirements.
## Installation
Use [composer](https://getcomposer.org) to install PhpSpreadsheet into your project:
```sh
composer require phpoffice/phpspreadsheet
```
If you are building your installation on a development machine that is on a different PHP version to the server where it will be deployed, or if your PHP CLI version is not the same as your run-time such as `php-fpm` or Apache's `mod_php`, then you might want to add the following to your `composer.json` before installing:
```json
{
"require": {
"phpoffice/phpspreadsheet": "^1.23"
},
"config": {
"platform": {
"php": "7.3"
}
}
}
```
and then run
```sh
composer install
```
to ensure that the correct dependencies are retrieved to match your deployment environment.
See [CLI vs Application run-time](https://php.watch/articles/composer-platform-check) for more details.
### Additional Installation Options
If you want to write to PDF, or to include Charts when you write to HTML or PDF, then you will need to install additional libraries:
#### PDF
For PDF Generation, you can install any of the following, and then configure PhpSpreadsheet to indicate which library you are going to use:
- mpdf/mpdf
- dompdf/dompdf
- tecnickcom/tcpdf
and configure PhpSpreadsheet using:
```php
// Dompdf, Mpdf or Tcpdf (as appropriate)
$className = \PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf::class;
IOFactory::registerWriter('Pdf', $className);
```
or the appropriate PDF Writer wrapper for the library that you have chosen to install.
#### Chart Export
For Chart export, we support following packages, which you will also need to install yourself using `composer require`
- [jpgraph/jpgraph](https://packagist.org/packages/jpgraph/jpgraph) (this package was abandoned at version 4.0.
You can manually download the latest version that supports PHP 8 and above from [jpgraph.net](https://jpgraph.net/))
- [mitoteam/jpgraph](https://packagist.org/packages/mitoteam/jpgraph) (fork with php 8.1 support)
and then configure PhpSpreadsheet using:
```php
Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class); // to use jpgraph/jpgraph
//or
Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class); // to use mitoteam/jpgraph
```
One or the other of these libraries is necessary if you want to generate HTML or PDF files that include charts.
## Documentation
Read more about it, including install instructions, in the [official documentation](https://phpspreadsheet.readthedocs.io). Or check out the [API documentation](https://phpoffice.github.io/PhpSpreadsheet).

View File

@ -12,7 +12,10 @@
"spreadsheet"
],
"config": {
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
},
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"type": "library",
@ -66,32 +69,33 @@
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"ezyang/htmlpurifier": "^4.13",
"ezyang/htmlpurifier": "^4.15",
"maennchen/zipstream-php": "^2.1",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/simple-cache": "^1.0"
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"dompdf/dompdf": "^1.0",
"dompdf/dompdf": "^1.0 || ^2.0",
"friendsofphp/php-cs-fixer": "^3.2",
"jpgraph/jpgraph": "^4.0",
"mpdf/mpdf": "^8.0",
"mitoteam/jpgraph": "10.2.4",
"mpdf/mpdf": "8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.0",
"squizlabs/php_codesniffer": "^3.6",
"tecnickcom/tcpdf": "^6.4"
"squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "6.5"
},
"suggest": {
"ext-intl": "PHP Internationalization Functions",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)",
"jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers"
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers"
},
"autoload": {
"psr-4": {

View File

@ -0,0 +1,133 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentHelper;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentProcessor;
trait ArrayEnabled
{
/**
* @var ArrayArgumentHelper
*/
private static $arrayArgumentHelper;
/**
* @param array|false $arguments Can be changed to array for Php8.1+
*/
private static function initialiseHelper($arguments): void
{
if (self::$arrayArgumentHelper === null) {
self::$arrayArgumentHelper = new ArrayArgumentHelper();
}
self::$arrayArgumentHelper->initialise(($arguments === false) ? [] : $arguments);
}
/**
* Handles array argument processing when the function accepts a single argument that can be an array argument.
* Example use for:
* DAYOFMONTH() or FACT().
*/
protected static function evaluateSingleArgumentArray(callable $method, array $values): array
{
$result = [];
foreach ($values as $value) {
$result[] = $method($value);
}
return $result;
}
/**
* Handles array argument processing when the function accepts multiple arguments,
* and any of them can be an array argument.
* Example use for:
* ROUND() or DATE().
*
* @param mixed ...$arguments
*/
protected static function evaluateArrayArguments(callable $method, ...$arguments): array
{
self::initialiseHelper($arguments);
$arguments = self::$arrayArgumentHelper->arguments();
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
}
/**
* Handles array argument processing when the function accepts multiple arguments,
* but only the first few (up to limit) can be an array arguments.
* Example use for:
* NETWORKDAYS() or CONCATENATE(), where the last argument is a matrix (or a series of values) that need
* to be treated as a such rather than as an array arguments.
*
* @param mixed ...$arguments
*/
protected static function evaluateArrayArgumentsSubset(callable $method, int $limit, ...$arguments): array
{
self::initialiseHelper(array_slice($arguments, 0, $limit));
$trailingArguments = array_slice($arguments, $limit);
$arguments = self::$arrayArgumentHelper->arguments();
$arguments = array_merge($arguments, $trailingArguments);
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
}
/**
* @param mixed $value
*/
private static function testFalse($value): bool
{
return $value === false;
}
/**
* Handles array argument processing when the function accepts multiple arguments,
* but only the last few (from start) can be an array arguments.
* Example use for:
* Z.TEST() or INDEX(), where the first argument 1 is a matrix that needs to be treated as a dataset
* rather than as an array argument.
*
* @param mixed ...$arguments
*/
protected static function evaluateArrayArgumentsSubsetFrom(callable $method, int $start, ...$arguments): array
{
$arrayArgumentsSubset = array_combine(
range($start, count($arguments) - $start),
array_slice($arguments, $start)
);
if (self::testFalse($arrayArgumentsSubset)) {
return ['#VALUE!'];
}
self::initialiseHelper($arrayArgumentsSubset);
$leadingArguments = array_slice($arguments, 0, $start);
$arguments = self::$arrayArgumentHelper->arguments();
$arguments = array_merge($leadingArguments, $arguments);
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
}
/**
* Handles array argument processing when the function accepts multiple arguments,
* and any of them can be an array argument except for the one specified by ignore.
* Example use for:
* HLOOKUP() and VLOOKUP(), where argument 1 is a matrix that needs to be treated as a database
* rather than as an array argument.
*
* @param mixed ...$arguments
*/
protected static function evaluateArrayArgumentsIgnore(callable $method, int $ignore, ...$arguments): array
{
$leadingArguments = array_slice($arguments, 0, $ignore);
$ignoreArgument = array_slice($arguments, $ignore, 1);
$trailingArguments = array_slice($arguments, $ignore + 1);
self::initialiseHelper(array_merge($leadingArguments, [[null]], $trailingArguments));
$arguments = self::$arrayArgumentHelper->arguments();
array_splice($arguments, $ignore, 1, $ignoreArgument);
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
}
}

View File

@ -0,0 +1,181 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class BinaryComparison
{
/**
* Epsilon Precision used for comparisons in calculations.
*/
private const DELTA = 0.1e-12;
/**
* Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters.
*
* @param null|string $str1 First string value for the comparison
* @param null|string $str2 Second string value for the comparison
*/
private static function strcmpLowercaseFirst($str1, $str2): int
{
$inversedStr1 = StringHelper::strCaseReverse($str1 ?? '');
$inversedStr2 = StringHelper::strCaseReverse($str2 ?? '');
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
*/
private static function strcmpAllowNull($str1, $str2): int
{
return strcmp($str1 ?? '', $str2 ?? '');
}
/**
* @param mixed $operand1
* @param mixed $operand2
*/
public static function compare($operand1, $operand2, string $operator): bool
{
// Simple validate the two operands if they are string values
if (is_string($operand1) && $operand1 > '' && $operand1[0] == Calculation::FORMULA_STRING_QUOTE) {
$operand1 = Calculation::unwrapResult($operand1);
}
if (is_string($operand2) && $operand2 > '' && $operand2[0] == Calculation::FORMULA_STRING_QUOTE) {
$operand2 = Calculation::unwrapResult($operand2);
}
// Use case insensitive comparaison if not OpenOffice mode
if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) {
if (is_string($operand1)) {
$operand1 = StringHelper::strToUpper($operand1);
}
if (is_string($operand2)) {
$operand2 = StringHelper::strToUpper($operand2);
}
}
$useLowercaseFirstComparison = is_string($operand1) &&
is_string($operand2) &&
Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE;
return self::evaluateComparison($operand1, $operand2, $operator, $useLowercaseFirstComparison);
}
/**
* @param mixed $operand1
* @param mixed $operand2
*/
private static function evaluateComparison($operand1, $operand2, string $operator, bool $useLowercaseFirstComparison): bool
{
switch ($operator) {
// Equality
case '=':
return self::equal($operand1, $operand2);
// Greater than
case '>':
return self::greaterThan($operand1, $operand2, $useLowercaseFirstComparison);
// Less than
case '<':
return self::lessThan($operand1, $operand2, $useLowercaseFirstComparison);
// Greater than or equal
case '>=':
return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison);
// Less than or equal
case '<=':
return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison);
// Inequality
case '<>':
return self::notEqual($operand1, $operand2);
default:
throw new Exception('Unsupported binary comparison operator');
}
}
/**
* @param mixed $operand1
* @param mixed $operand2
*/
private static function equal($operand1, $operand2): bool
{
if (is_numeric($operand1) && is_numeric($operand2)) {
$result = (abs($operand1 - $operand2) < self::DELTA);
} elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
$result = $operand1 == $operand2;
} else {
$result = self::strcmpAllowNull($operand1, $operand2) == 0;
}
return $result;
}
/**
* @param mixed $operand1
* @param mixed $operand2
*/
private static function greaterThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool
{
if (is_numeric($operand1) && is_numeric($operand2)) {
$result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 > $operand2));
} elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
$result = $operand1 >= $operand2;
} elseif ($useLowercaseFirstComparison) {
$result = self::strcmpLowercaseFirst($operand1, $operand2) >= 0;
} else {
$result = self::strcmpAllowNull($operand1, $operand2) >= 0;
}
return $result;
}
/**
* @param mixed $operand1
* @param mixed $operand2
*/
private static function lessThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool
{
if (is_numeric($operand1) && is_numeric($operand2)) {
$result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 < $operand2));
} elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
$result = $operand1 <= $operand2;
} elseif ($useLowercaseFirstComparison) {
$result = self::strcmpLowercaseFirst($operand1, $operand2) <= 0;
} else {
$result = self::strcmpAllowNull($operand1, $operand2) <= 0;
}
return $result;
}
/**
* @param mixed $operand1
* @param mixed $operand2
*/
private static function greaterThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool
{
return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true;
}
/**
* @param mixed $operand1
* @param mixed $operand2
*/
private static function lessThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool
{
return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true;
}
/**
* @param mixed $operand1
* @param mixed $operand2
*/
private static function notEqual($operand1, $operand2): bool
{
return self::equal($operand1, $operand2) !== true;
}
}

View File

@ -2,7 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class DGet extends DatabaseAbstract
{
@ -41,7 +41,7 @@ class DGet extends DatabaseAbstract
$columnData = self::getFilteredColumn($database, $field, $criteria);
if (count($columnData) > 1) {
return Functions::NAN();
return ExcelError::NAN();
}
$row = array_pop($columnData);

View File

@ -278,9 +278,9 @@ class DateTime
* or a standard date string
* @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object
* or a standard date string
* @param string $unit
* @param array|string $unit
*
* @return int|string Interval between the dates
* @return array|int|string Interval between the dates
*/
public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D')
{
@ -300,12 +300,12 @@ class DateTime
* @See DateTimeExcel\Days::between()
* Use the between method in the DateTimeExcel\Days class instead
*
* @param DateTimeInterface|float|int|string $endDate Excel date serial value (float),
* @param array|DateTimeInterface|float|int|string $endDate Excel date serial value (float),
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
* @param DateTimeInterface|float|int|string $startDate Excel date serial value (float),
* @param array|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
* @return array|int|string Number of days between start date and end date or an error
*/
public static function DAYS($endDate = 0, $startDate = 0)
{
@ -331,7 +331,7 @@ class DateTime
* PHP DateTime object, or a standard date string
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param bool $method US or European Method
* @param array|bool $method US or European Method
* FALSE or omitted: U.S. (NASD) method. If the starting date is
* the last day of a month, it becomes equal to the 30th of the
* same month. If the ending date is the last day of a month and
@ -343,7 +343,7 @@ class DateTime
* occur on the 31st of a month become equal to the 30th of the
* same month.
*
* @return int|string Number of days between start date and end date
* @return array|int|string Number of days between start date and end date
*/
public static function DAYS360($startDate = 0, $endDate = 0, $method = false)
{
@ -373,14 +373,14 @@ class DateTime
* PHP DateTime object, or a standard date string
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param int $method Method used for the calculation
* @param array|int $method Method used for the calculation
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string fraction of the year, or a string containing an error
* @return array|float|string fraction of the year, or a string containing an error
*/
public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0)
{
@ -409,7 +409,7 @@ class DateTime
* PHP DateTime object, or a standard date string
* @param mixed $dateArgs
*
* @return int|string Interval between the dates
* @return array|int|string Interval between the dates
*/
public static function NETWORKDAYS($startDate, $endDate, ...$dateArgs)
{
@ -464,7 +464,7 @@ class DateTime
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
*
* @return int|string Day of the month
* @return array|int|string Day of the month
*/
public static function DAYOFMONTH($dateValue = 1)
{
@ -492,7 +492,7 @@ class DateTime
* 2 Numbers 1 (Monday) through 7 (Sunday).
* 3 Numbers 0 (Monday) through 6 (Sunday).
*
* @return int|string Day of the week value
* @return array|int|string Day of the week value
*/
public static function WEEKDAY($dateValue = 1, $style = 1)
{
@ -704,7 +704,7 @@ class DateTime
* 17 Week begins on Sunday.
* 21 ISO (Jan. 4 is week 1, begins on Monday).
*
* @return int|string Week Number
* @return array|int|string Week Number
*/
public static function WEEKNUM($dateValue = 1, $method = self::STARTWEEK_SUNDAY)
{
@ -727,7 +727,7 @@ class DateTime
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
*
* @return int|string Week Number
* @return array|int|string Week Number
*/
public static function ISOWEEKNUM($dateValue = 1)
{
@ -751,7 +751,7 @@ class DateTime
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
*
* @return int|string Month of the year
* @return array|int|string Month of the year
*/
public static function MONTHOFYEAR($dateValue = 1)
{
@ -775,7 +775,7 @@ class DateTime
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
*
* @return int|string Year
* @return array|int|string Year
*/
public static function YEAR($dateValue = 1)
{
@ -799,7 +799,7 @@ class DateTime
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard time string
*
* @return int|string Hour
* @return array|int|string Hour
*/
public static function HOUROFDAY($timeValue = 0)
{
@ -823,7 +823,7 @@ class DateTime
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard time string
*
* @return int|string Minute
* @return array|int|string Minute
*/
public static function MINUTE($timeValue = 0)
{
@ -847,7 +847,7 @@ class DateTime
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard time string
*
* @return int|string Second
* @return array|int|string Second
*/
public static function SECOND($timeValue = 0)
{

View File

@ -3,7 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTimeImmutable;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Current
{
@ -29,7 +29,7 @@ class Current
$dti = new DateTimeImmutable();
$dateArray = Helpers::dateParse($dti->format('c'));
return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray, true) : Functions::VALUE();
return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray, true) : ExcelError::VALUE();
}
/**
@ -54,6 +54,6 @@ class Current
$dti = new DateTimeImmutable();
$dateArray = Helpers::dateParse($dti->format('c'));
return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray) : Functions::VALUE();
return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray) : ExcelError::VALUE();
}
}

View File

@ -2,13 +2,16 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Date
{
use ArrayEnabled;
/**
* DATE.
*
@ -24,7 +27,7 @@ class Date
* A Month name or abbreviation (English only at this point) such as 'January' or 'Jan' will still be accepted,
* as will a day value with a suffix (e.g. '21st' rather than simply 21); again only English language.
*
* @param int $year The value of the year argument can include one to four digits.
* @param array|int $year The value of the year argument can include one to four digits.
* Excel interprets the year argument according to the configured
* date system: 1900 or 1904.
* If year is between 0 (zero) and 1899 (inclusive), Excel adds that
@ -35,7 +38,7 @@ class Date
* 2008.
* If year is less than 0 or is 10000 or greater, Excel returns the
* #NUM! error value.
* @param int $month A positive or negative integer representing the month of the year
* @param array|int $month A positive or negative integer representing the month of the year
* from 1 to 12 (January to December).
* If month is greater than 12, month adds that number of months to
* the first month in the year specified. For example, DATE(2008,14,2)
@ -44,7 +47,7 @@ class Date
* number of months, plus 1, from the first month in the year
* specified. For example, DATE(2008,-3,2) returns the serial number
* representing September 2, 2007.
* @param int $day A positive or negative integer representing the day of the month
* @param array|int $day A positive or negative integer representing the day of the month
* from 1 to 31.
* If day is greater than the number of days in the month specified,
* day adds that number of days to the first day in the month. For
@ -57,9 +60,15 @@ class Date
*
* @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
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function fromYMD($year, $month, $day)
{
if (is_array($year) || is_array($month) || is_array($day)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $year, $month, $day);
}
$baseYear = SharedDateHelper::getExcelCalendar();
try {
@ -84,18 +93,17 @@ class Date
*/
private static function getYear($year, int $baseYear): int
{
$year = Functions::flattenSingleValue($year);
$year = ($year !== null) ? StringHelper::testStringAsNumeric((string) $year) : 0;
if (!is_numeric($year)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
$year = (int) $year;
if ($year < ($baseYear - 1900)) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
if ((($baseYear - 1900) !== 0) && ($year < $baseYear) && ($year >= 1900)) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
if (($year < $baseYear) && ($year >= ($baseYear - 1900))) {
@ -112,15 +120,13 @@ class Date
*/
private static function getMonth($month): int
{
$month = Functions::flattenSingleValue($month);
if (($month !== null) && (!is_numeric($month))) {
$month = SharedDateHelper::monthStringToNumber($month);
}
$month = ($month !== null) ? StringHelper::testStringAsNumeric((string) $month) : 0;
if (!is_numeric($month)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
return (int) $month;
@ -133,15 +139,13 @@ class Date
*/
private static function getDay($day): int
{
$day = Functions::flattenSingleValue($day);
if (($day !== null) && (!is_numeric($day))) {
$day = SharedDateHelper::dayStringToNumber($day);
}
$day = ($day !== null) ? StringHelper::testStringAsNumeric((string) $day) : 0;
if (!is_numeric($day)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
return (int) $day;
@ -162,7 +166,7 @@ class Date
// Re-validate the year parameter after adjustments
if (($year < $baseYear) || ($year >= 10000)) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
}
}

View File

@ -2,12 +2,15 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class DateParts
{
use ArrayEnabled;
/**
* DAYOFMONTH.
*
@ -19,11 +22,18 @@ class DateParts
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return int|string Day of the month
* @return array|int|string Day of the month
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function day($dateValue)
{
if (is_array($dateValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
}
$weirdResult = self::weirdCondition($dateValue);
if ($weirdResult >= 0) {
return $weirdResult;
@ -52,11 +62,18 @@ class DateParts
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return int|string Month of the year
* @return array|int|string Month of the year
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function month($dateValue)
{
if (is_array($dateValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
}
try {
$dateValue = Helpers::getDateValue($dateValue);
} catch (Exception $e) {
@ -83,11 +100,18 @@ class DateParts
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return int|string Year
* @return array|int|string Year
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function year($dateValue)
{
if (is_array($dateValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
}
try {
$dateValue = Helpers::getDateValue($dateValue);
} catch (Exception $e) {

View File

@ -3,11 +3,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTimeImmutable;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class DateValue
{
use ArrayEnabled;
/**
* DATEVALUE.
*
@ -21,7 +24,7 @@ class DateValue
* Excel Function:
* DATEVALUE(dateValue)
*
* @param string $dateValue Text that represents a date in a Microsoft Excel date format.
* @param array|string $dateValue Text that represents a date in a Microsoft Excel date format.
* For example, "1/30/2008" or "30-Jan-2008" are text strings within
* quotation marks that represent dates. Using the default date
* system in Excel for Windows, date_text must represent a date from
@ -29,17 +32,24 @@ class DateValue
* system in Excel for the Macintosh, date_text must represent a date
* from January 1, 1904, to December 31, 9999. DATEVALUE returns the
* #VALUE! error value if date_text is out of this range.
* Or can be an array of date values
*
* @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
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function fromString($dateValue)
{
if (is_array($dateValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
}
$dti = new DateTimeImmutable();
$baseYear = SharedDateHelper::getExcelCalendar();
$dateValue = trim(Functions::flattenSingleValue($dateValue ?? ''), '"');
$dateValue = trim($dateValue ?? '', '"');
// Strip any ordinals because they're allowed in Excel (English only)
$dateValue = preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue) ?? '';
$dateValue = (string) preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue);
// Convert separators (/ . or space) to hyphens (should also handle dot used for ordinals in some countries, e.g. Denmark, Germany)
$dateValue = str_replace(['/', '.', '-', ' '], ' ', $dateValue);
@ -49,7 +59,7 @@ class DateValue
foreach ($t1 as &$t) {
if ((is_numeric($t)) && ($t > 31)) {
if ($yearFound) {
return Functions::VALUE();
return ExcelError::VALUE();
}
if ($t < 100) {
$t += 1900;
@ -59,7 +69,7 @@ class DateValue
}
if (count($t1) === 1) {
// We've been fed a time value without any date
return ((strpos((string) $t, ':') === false)) ? Functions::Value() : 0.0;
return ((strpos((string) $t, ':') === false)) ? ExcelError::Value() : 0.0;
}
unset($t);
@ -121,12 +131,12 @@ class DateValue
*/
private static function finalResults(array $PHPDateArray, DateTimeImmutable $dti, int $baseYear)
{
$retValue = Functions::Value();
$retValue = ExcelError::Value();
if (Helpers::dateParseSucceeded($PHPDateArray)) {
// Execute function
Helpers::replaceIfEmpty($PHPDateArray['year'], $dti->format('Y'));
if ($PHPDateArray['year'] < $baseYear) {
return Functions::VALUE();
return ExcelError::VALUE();
}
Helpers::replaceIfEmpty($PHPDateArray['month'], $dti->format('m'));
Helpers::replaceIfEmpty($PHPDateArray['day'], $dti->format('d'));
@ -137,7 +147,7 @@ class DateValue
$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();
return ($year === 1900 && $month === 2 && $day === 29) ? Helpers::returnIn3FormatsFloat(60.0) : ExcelError::VALUE();
}
$retValue = Helpers::returnIn3FormatsArray($PHPDateArray, true);
}

View File

@ -3,12 +3,15 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTimeInterface;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Days
{
use ArrayEnabled;
/**
* DAYS.
*
@ -17,15 +20,23 @@ class Days
* Excel Function:
* DAYS(endDate, startDate)
*
* @param DateTimeInterface|float|int|string $endDate Excel date serial value (float),
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
* @param DateTimeInterface|float|int|string $startDate Excel date serial value (float),
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
* @param array|DateTimeInterface|float|int|string $endDate Excel date serial value (float),
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|DateTimeInterface|float|int|string $startDate Excel date serial value (float),
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return int|string Number of days between start date and end date or an error
* @return array|int|string Number of days between start date and end date or an error
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function between($endDate, $startDate)
{
if (is_array($endDate) || is_array($startDate)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $endDate, $startDate);
}
try {
$startDate = Helpers::getDateValue($startDate);
$endDate = Helpers::getDateValue($endDate);
@ -37,7 +48,7 @@ class Days
$PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate);
$PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate);
$days = Functions::VALUE();
$days = ExcelError::VALUE();
$diff = $PHPStartDateObject->diff($PHPEndDateObject);
if ($diff !== false && !is_bool($diff->days)) {
$days = $diff->days;

View File

@ -2,12 +2,15 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Days360
{
use ArrayEnabled;
/**
* DAYS360.
*
@ -18,11 +21,13 @@ class Days360
* Excel Function:
* DAYS360(startDate,endDate[,method])
*
* @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
* @param array|mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* Or can be an array of date values
* @param array|mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param mixed $method US or European Method as a bool
* Or can be an array of date values
* @param array|mixed $method US or European Method as a bool
* FALSE or omitted: U.S. (NASD) method. If the starting date is
* the last day of a month, it becomes equal to the 30th of the
* same month. If the ending date is the last day of a month and
@ -33,11 +38,18 @@ class Days360
* TRUE: European method. Starting dates and ending dates that
* occur on the 31st of a month become equal to the 30th of the
* same month.
* Or can be an array of methods
*
* @return int|string Number of days between start date and end date
* @return array|int|string Number of days between start date and end date
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function between($startDate = 0, $endDate = 0, $method = false)
{
if (is_array($startDate) || is_array($endDate) || is_array($method)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $method);
}
try {
$startDate = Helpers::getDateValue($startDate);
$endDate = Helpers::getDateValue($endDate);
@ -46,7 +58,7 @@ class Days360
}
if (!is_bool($method)) {
return Functions::VALUE();
return ExcelError::VALUE();
}
// Execute function

View File

@ -4,30 +4,42 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateInterval;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Difference
{
use ArrayEnabled;
/**
* DATEDIF.
*
* @param mixed $startDate Excel date serial value, PHP date/time stamp, PHP DateTime object
* or a standard date string
* Or can be an array of date values
* @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object
* or a standard date string
* @param string $unit
* Or can be an array of date values
* @param array|string $unit
* Or can be an array of unit values
*
* @return int|string Interval between the dates
* @return array|int|string Interval between the dates
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function interval($startDate, $endDate, $unit = 'D')
{
if (is_array($startDate) || is_array($endDate) || is_array($unit)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $unit);
}
try {
$startDate = Helpers::getDateValue($startDate);
$endDate = Helpers::getDateValue($endDate);
$difference = self::initialDiff($startDate, $endDate);
$unit = strtoupper(Functions::flattenSingleValue($unit));
$unit = strtoupper($unit);
} catch (Exception $e) {
return $e->getMessage();
}
@ -53,14 +65,14 @@ class Difference
$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;
return is_bool($retVal) ? ExcelError::VALUE() : $retVal;
}
private static function initialDiff(float $startDate, float $endDate): float
{
// Validate parameters
if ($startDate > $endDate) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $endDate - $startDate;

View File

@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Helpers
@ -33,7 +34,7 @@ class Helpers
if (is_object($dateValue)) {
$retval = SharedDateHelper::PHPToExcel($dateValue);
if (is_bool($retval)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
return $retval;
@ -46,11 +47,11 @@ class Helpers
$dateValue = DateValue::fromString($dateValue);
Functions::setReturnDateType($saveReturnDateType);
if (!is_numeric($dateValue)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
}
if ($dateValue < 0 && Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return (float) $dateValue;
@ -253,7 +254,7 @@ class Helpers
return (float) $number;
}
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
/**
@ -266,13 +267,13 @@ class Helpers
public static function validateNotNegative($number)
{
if (!is_numeric($number)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
if ($number >= 0) {
return (float) $number;
}
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void

View File

@ -2,10 +2,13 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Month
{
use ArrayEnabled;
/**
* EDATE.
*
@ -19,15 +22,23 @@ class Month
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param int $adjustmentMonths The number of months before or after start_date.
* Or can be an array of date values
* @param array|int $adjustmentMonths The number of months before or after start_date.
* A positive value for months yields a future date;
* a negative value yields a past date.
* Or can be an array of adjustment values
*
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array|mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function adjust($dateValue, $adjustmentMonths)
{
if (is_array($dateValue) || is_array($adjustmentMonths)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $adjustmentMonths);
}
try {
$dateValue = Helpers::getDateValue($dateValue, false);
$adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths);
@ -54,15 +65,23 @@ class Month
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param int $adjustmentMonths The number of months before or after start_date.
* Or can be an array of date values
* @param array|int $adjustmentMonths The number of months before or after start_date.
* A positive value for months yields a future date;
* a negative value yields a past date.
* Or can be an array of adjustment values
*
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array|mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function lastDay($dateValue, $adjustmentMonths)
{
if (is_array($dateValue) || is_array($adjustmentMonths)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $adjustmentMonths);
}
try {
$dateValue = Helpers::getDateValue($dateValue, false);
$adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths);

View File

@ -2,11 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class NetworkDays
{
use ArrayEnabled;
/**
* NETWORKDAYS.
*
@ -20,14 +23,28 @@ class NetworkDays
*
* @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param mixed $dateArgs
* Or can be an array of date values
* @param mixed $dateArgs An array of dates (such as holidays) to exclude from the calculation
*
* @return int|string Interval between the dates
* @return array|int|string Interval between the dates
* If an array of values is passed for the $startDate or $endDate arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function count($startDate, $endDate, ...$dateArgs)
{
if (is_array($startDate) || is_array($endDate)) {
return self::evaluateArrayArgumentsSubset(
[self::class, __FUNCTION__],
2,
$startDate,
$endDate,
...$dateArgs
);
}
try {
// Retrieve the mandatory start and end date that are referenced in the function definition
$sDate = Helpers::getDateValue($startDate);

View File

@ -3,12 +3,16 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Time
{
use ArrayEnabled;
/**
* TIME.
*
@ -20,23 +24,31 @@ class Time
* Excel Function:
* TIME(hour,minute,second)
*
* @param mixed $hour A number from 0 (zero) to 32767 representing the hour.
* @param array|int $hour A number from 0 (zero) to 32767 representing the hour.
* Any value greater than 23 will be divided by 24 and the remainder
* will be treated as the hour value. For example, TIME(27,0,0) =
* TIME(3,0,0) = .125 or 3:00 AM.
* @param mixed $minute A number from 0 to 32767 representing the minute.
* @param array|int $minute A number from 0 to 32767 representing the minute.
* Any value greater than 59 will be converted to hours and minutes.
* For example, TIME(0,750,0) = TIME(12,30,0) = .520833 or 12:30 PM.
* @param mixed $second A number from 0 to 32767 representing the second.
* @param array|int $second A number from 0 to 32767 representing the second.
* Any value greater than 59 will be converted to hours, minutes,
* and seconds. For example, TIME(0,0,2000) = TIME(0,33,22) = .023148
* or 12:33:20 AM
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array|mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function fromHMS($hour, $minute, $second)
{
if (is_array($hour) || is_array($minute) || is_array($second)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $hour, $minute, $second);
}
try {
$hour = self::toIntWithNullBool($hour);
$minute = self::toIntWithNullBool($minute);
@ -51,7 +63,7 @@ class Time
if ($hour > 23) {
$hour = $hour % 24;
} elseif ($hour < 0) {
return Functions::NAN();
return ExcelError::NAN();
}
// Execute function
@ -105,13 +117,12 @@ class Time
*/
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());
throw new Exception(ExcelError::VALUE());
}
return (int) $value;

View File

@ -2,12 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class TimeParts
{
use ArrayEnabled;
/**
* HOUROFDAY.
*
@ -19,13 +21,19 @@ class TimeParts
*
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard time string
* Or can be an array of date/time values
*
* @return int|string Hour
* @return array|int|string Hour
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function hour($timeValue)
{
if (is_array($timeValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
}
try {
$timeValue = Functions::flattenSingleValue($timeValue);
Helpers::nullFalseTrueToNumber($timeValue);
if (!is_numeric($timeValue)) {
$timeValue = Helpers::getTimeValue($timeValue);
@ -53,13 +61,19 @@ class TimeParts
*
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard time string
* Or can be an array of date/time values
*
* @return int|string Minute
* @return array|int|string Minute
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function minute($timeValue)
{
if (is_array($timeValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
}
try {
$timeValue = Functions::flattenSingleValue($timeValue);
Helpers::nullFalseTrueToNumber($timeValue);
if (!is_numeric($timeValue)) {
$timeValue = Helpers::getTimeValue($timeValue);
@ -87,13 +101,19 @@ class TimeParts
*
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard time string
* Or can be an array of date/time values
*
* @return int|string Second
* @return array|int|string Second
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function second($timeValue)
{
if (is_array($timeValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
}
try {
$timeValue = Functions::flattenSingleValue($timeValue);
Helpers::nullFalseTrueToNumber($timeValue);
if (!is_numeric($timeValue)) {
$timeValue = Helpers::getTimeValue($timeValue);

View File

@ -3,11 +3,15 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use Datetime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class TimeValue
{
use ArrayEnabled;
/**
* TIMEVALUE.
*
@ -21,17 +25,24 @@ class TimeValue
* Excel Function:
* TIMEVALUE(timeValue)
*
* @param string $timeValue A text string that represents a time in any one of the Microsoft
* @param array|string $timeValue A text string that represents a time in any one of the Microsoft
* Excel time formats; for example, "6:45 PM" and "18:45" text strings
* within quotation marks that represent time.
* Date information in time_text is ignored.
* Or can be an array of date/time values
*
* @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
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function fromString($timeValue)
{
$timeValue = trim(Functions::flattenSingleValue($timeValue ?? ''), '"');
if (is_array($timeValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
}
$timeValue = trim($timeValue ?? '', '"');
$timeValue = str_replace(['/', '.'], '-', $timeValue);
$arraySplit = preg_split('/[\/:\-\s]/', $timeValue) ?: [];
@ -41,7 +52,7 @@ class TimeValue
}
$PHPDateArray = Helpers::dateParse($timeValue);
$retValue = Functions::VALUE();
$retValue = ExcelError::VALUE();
if (Helpers::dateParseSucceeded($PHPDateArray)) {
/** @var int */
$hour = $PHPDateArray['hour'];

View File

@ -3,12 +3,15 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Week
{
use ArrayEnabled;
/**
* WEEKNUM.
*
@ -24,7 +27,8 @@ class Week
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param int $method Week begins on Sunday or Monday
* Or can be an array of date values
* @param array|int $method Week begins on Sunday or Monday
* 1 or omitted Week begins on Sunday.
* 2 Week begins on Monday.
* 11 Week begins on Monday.
@ -35,11 +39,18 @@ class Week
* 16 Week begins on Saturday.
* 17 Week begins on Sunday.
* 21 ISO (Jan. 4 is week 1, begins on Monday).
* Or can be an array of methods
*
* @return int|string Week Number
* @return array|int|string Week Number
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function number($dateValue, $method = Constants::STARTWEEK_SUNDAY)
{
if (is_array($dateValue) || is_array($method)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $method);
}
$origDateValueNull = empty($dateValue);
try {
@ -88,11 +99,18 @@ class Week
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return int|string Week Number
* @return array|int|string Week Number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isoWeekNumber($dateValue)
{
if (is_array($dateValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
}
if (self::apparentBug($dateValue)) {
return 52;
}
@ -119,17 +137,25 @@ class Week
* Excel Function:
* WEEKDAY(dateValue[,style])
*
* @param null|float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer),
* @param null|array|float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param mixed $style A number that determines the type of return value
* 1 or omitted Numbers 1 (Sunday) through 7 (Saturday).
* 2 Numbers 1 (Monday) through 7 (Sunday).
* 3 Numbers 0 (Monday) through 6 (Sunday).
* Or can be an array of styles
*
* @return int|string Day of the week value
* @return array|int|string Day of the week value
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function day($dateValue, $style = 1)
{
if (is_array($dateValue) || is_array($style)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $style);
}
try {
$dateValue = Helpers::getDateValue($dateValue);
$style = self::validateStyle($style);
@ -165,14 +191,12 @@ class Week
*/
private static function validateStyle($style): int
{
$style = Functions::flattenSingleValue($style);
if (!is_numeric($style)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
$style = (int) $style;
if (($style < 1) || ($style > 3)) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $style;
@ -209,7 +233,7 @@ class Week
private static function validateDateValue($dateValue): float
{
if (is_bool($dateValue)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
return Helpers::getDateValue($dateValue);
@ -225,14 +249,14 @@ class Week
if ($method === null) {
$method = Constants::STARTWEEK_SUNDAY;
}
$method = Functions::flattenSingleValue($method);
if (!is_numeric($method)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
$method = (int) $method;
if (!array_key_exists($method, Constants::METHODARR)) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
$method = Constants::METHODARR[$method];

View File

@ -2,11 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class WorkDay
{
use ArrayEnabled;
/**
* WORKDAY.
*
@ -18,27 +21,37 @@ class WorkDay
* Excel Function:
* WORKDAY(startDate,endDays[,holidays[,holiday[,...]]])
*
* @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
* @param array|mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param int $endDays The number of nonweekend and nonholiday days before or after
* Or can be an array of date values
* @param array|int $endDays The number of nonweekend and nonholiday days before or after
* startDate. A positive value for days yields a future date; a
* negative value yields a past date.
* @param mixed $dateArgs
* Or can be an array of int values
* @param null|mixed $dateArgs An array of dates (such as holidays) to exclude from the calculation
*
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array|mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function date($startDate, $endDays, ...$dateArgs)
{
if (is_array($startDate) || is_array($endDays)) {
return self::evaluateArrayArgumentsSubset(
[self::class, __FUNCTION__],
2,
$startDate,
$endDays,
...$dateArgs
);
}
// Retrieve the mandatory start date and days that are referenced in the function definition
try {
$startDate = Helpers::getDateValue($startDate);
$endDays = Helpers::validateNumericNull($endDays);
$dateArgs = Functions::flattenArray($dateArgs);
$holidayArray = [];
foreach ($dateArgs as $holidayDate) {
$holidayArray[] = Helpers::getDateValue($holidayDate);
}
$holidayArray = array_map([Helpers::class, 'getDateValue'], Functions::flattenArray($dateArgs));
} catch (Exception $e) {
return $e->getMessage();
}
@ -64,9 +77,8 @@ class WorkDay
private static function incrementing(float $startDate, int $endDays, array $holidayArray)
{
// Adjust the start date if it falls over a weekend
$startDoW = self::getWeekDay($startDate, 3);
if (self::getWeekDay($startDate, 3) >= 5) {
if ($startDoW >= 5) {
$startDate += 7 - $startDoW;
--$endDays;
}
@ -126,9 +138,8 @@ class WorkDay
private static function decrementing(float $startDate, int $endDays, array $holidayArray)
{
// Adjust the start date if it falls over a weekend
$startDoW = self::getWeekDay($startDate, 3);
if (self::getWeekDay($startDate, 3) >= 5) {
if ($startDoW >= 5) {
$startDate += -$startDoW + 4;
++$endDays;
}
@ -172,6 +183,7 @@ class WorkDay
}
// Adjust the calculated end date if it falls over a weekend
$endDoW = self::getWeekDay($endDate, 3);
/** int $endDoW */
if ($endDoW >= 5) {
$endDate += -$endDoW + 4;
}
@ -182,8 +194,8 @@ class WorkDay
private static function getWeekDay(float $date, int $wd): int
{
$result = Week::day($date, $wd);
$result = Functions::scalar(Week::day($date, $wd));
return is_string($result) ? -1 : $result;
return is_int($result) ? $result : -1;
}
}

View File

@ -2,12 +2,16 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class YearFrac
{
use ArrayEnabled;
/**
* YEARFRAC.
*
@ -23,19 +27,28 @@ class YearFrac
*
* @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of values
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param int $method Method used for the calculation
* Or can be an array of methods
* @param array|int $method Method used for the calculation
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
* Or can be an array of methods
*
* @return float|string fraction of the year, or a string containing an error
* @return array|float|string fraction of the year, or a string containing an error
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function fraction($startDate, $endDate, $method = 0)
{
if (is_array($startDate) || is_array($endDate) || is_array($method)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $method);
}
try {
$method = (int) Helpers::validateNumericNull($method);
$sDate = Helpers::getDateValue($startDate);
@ -50,18 +63,18 @@ class YearFrac
switch ($method) {
case 0:
return Days360::between($startDate, $endDate) / 360;
return Functions::scalar(Days360::between($startDate, $endDate)) / 360;
case 1:
return self::method1($startDate, $endDate);
case 2:
return Difference::interval($startDate, $endDate) / 360;
return Functions::scalar(Difference::interval($startDate, $endDate)) / 360;
case 3:
return Difference::interval($startDate, $endDate) / 365;
return Functions::scalar(Difference::interval($startDate, $endDate)) / 365;
case 4:
return Days360::between($startDate, $endDate, true) / 360;
return Functions::scalar(Days360::between($startDate, $endDate, true)) / 360;
}
return Functions::NAN();
return ExcelError::NAN();
}
/**
@ -87,7 +100,7 @@ class YearFrac
private static function method1(float $startDate, float $endDate): float
{
$days = Difference::interval($startDate, $endDate);
$days = Functions::scalar(Difference::interval($startDate, $endDate));
$startYear = (int) DateParts::year($startDate);
$endYear = (int) DateParts::year($endDate);
$years = $endYear - $startYear + 1;

View File

@ -0,0 +1,209 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class ArrayArgumentHelper
{
/**
* @var int
*/
protected $indexStart = 0;
/**
* @var array
*/
protected $arguments;
/**
* @var int
*/
protected $argumentCount;
/**
* @var array
*/
protected $rows;
/**
* @var array
*/
protected $columns;
public function initialise(array $arguments): void
{
$keys = array_keys($arguments);
$this->indexStart = (int) array_shift($keys);
$this->rows = $this->rows($arguments);
$this->columns = $this->columns($arguments);
$this->argumentCount = count($arguments);
$this->arguments = $this->flattenSingleCellArrays($arguments, $this->rows, $this->columns);
$this->rows = $this->rows($arguments);
$this->columns = $this->columns($arguments);
if ($this->arrayArguments() > 2) {
throw new Exception('Formulae with more than two array arguments are not supported');
}
}
public function arguments(): array
{
return $this->arguments;
}
public function hasArrayArgument(): bool
{
return $this->arrayArguments() > 0;
}
public function getFirstArrayArgumentNumber(): int
{
$rowArrays = $this->filterArray($this->rows);
$columnArrays = $this->filterArray($this->columns);
for ($index = $this->indexStart; $index < $this->argumentCount; ++$index) {
if (isset($rowArrays[$index]) || isset($columnArrays[$index])) {
return ++$index;
}
}
return 0;
}
public function getSingleRowVector(): ?int
{
$rowVectors = $this->getRowVectors();
return count($rowVectors) === 1 ? array_pop($rowVectors) : null;
}
private function getRowVectors(): array
{
$rowVectors = [];
for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) {
if ($this->rows[$index] === 1 && $this->columns[$index] > 1) {
$rowVectors[] = $index;
}
}
return $rowVectors;
}
public function getSingleColumnVector(): ?int
{
$columnVectors = $this->getColumnVectors();
return count($columnVectors) === 1 ? array_pop($columnVectors) : null;
}
private function getColumnVectors(): array
{
$columnVectors = [];
for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) {
if ($this->rows[$index] > 1 && $this->columns[$index] === 1) {
$columnVectors[] = $index;
}
}
return $columnVectors;
}
public function getMatrixPair(): array
{
for ($i = $this->indexStart; $i < ($this->indexStart + $this->argumentCount - 1); ++$i) {
for ($j = $i + 1; $j < $this->argumentCount; ++$j) {
if (isset($this->rows[$i], $this->rows[$j])) {
return [$i, $j];
}
}
}
return [];
}
public function isVector(int $argument): bool
{
return $this->rows[$argument] === 1 || $this->columns[$argument] === 1;
}
public function isRowVector(int $argument): bool
{
return $this->rows[$argument] === 1;
}
public function isColumnVector(int $argument): bool
{
return $this->columns[$argument] === 1;
}
public function rowCount(int $argument): int
{
return $this->rows[$argument];
}
public function columnCount(int $argument): int
{
return $this->columns[$argument];
}
private function rows(array $arguments): array
{
return array_map(
function ($argument) {
return is_countable($argument) ? count($argument) : 1;
},
$arguments
);
}
private function columns(array $arguments): array
{
return array_map(
function ($argument) {
return is_array($argument) && is_array($argument[array_keys($argument)[0]])
? count($argument[array_keys($argument)[0]])
: 1;
},
$arguments
);
}
public function arrayArguments(): int
{
$count = 0;
foreach (array_keys($this->arguments) as $argument) {
if ($this->rows[$argument] > 1 || $this->columns[$argument] > 1) {
++$count;
}
}
return $count;
}
private function flattenSingleCellArrays(array $arguments, array $rows, array $columns): array
{
foreach ($arguments as $index => $argument) {
if ($rows[$index] === 1 && $columns[$index] === 1) {
while (is_array($argument)) {
$argument = array_pop($argument);
}
$arguments[$index] = $argument;
}
}
return $arguments;
}
private function filterArray(array $array): array
{
return array_filter(
$array,
function ($value) {
return $value > 1;
}
);
}
}

View File

@ -0,0 +1,175 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class ArrayArgumentProcessor
{
/**
* @var ArrayArgumentHelper
*/
private static $arrayArgumentHelper;
/**
* @param mixed ...$arguments
*/
public static function processArguments(
ArrayArgumentHelper $arrayArgumentHelper,
callable $method,
...$arguments
): array {
self::$arrayArgumentHelper = $arrayArgumentHelper;
if (self::$arrayArgumentHelper->hasArrayArgument() === false) {
return [$method(...$arguments)];
}
if (self::$arrayArgumentHelper->arrayArguments() === 1) {
$nthArgument = self::$arrayArgumentHelper->getFirstArrayArgumentNumber();
return self::evaluateNthArgumentAsArray($method, $nthArgument, ...$arguments);
}
$singleRowVectorIndex = self::$arrayArgumentHelper->getSingleRowVector();
$singleColumnVectorIndex = self::$arrayArgumentHelper->getSingleColumnVector();
if ($singleRowVectorIndex !== null && $singleColumnVectorIndex !== null) {
// Basic logic for a single row vector and a single column vector
return self::evaluateVectorPair($method, $singleRowVectorIndex, $singleColumnVectorIndex, ...$arguments);
}
$matrixPair = self::$arrayArgumentHelper->getMatrixPair();
if ($matrixPair !== []) {
if (
(self::$arrayArgumentHelper->isVector($matrixPair[0]) === true &&
self::$arrayArgumentHelper->isVector($matrixPair[1]) === false) ||
(self::$arrayArgumentHelper->isVector($matrixPair[0]) === false &&
self::$arrayArgumentHelper->isVector($matrixPair[1]) === true)
) {
// Logic for a matrix and a vector (row or column)
return self::evaluateVectorMatrixPair($method, $matrixPair, ...$arguments);
}
// Logic for matrix/matrix, column vector/column vector or row vector/row vector
return self::evaluateMatrixPair($method, $matrixPair, ...$arguments);
}
// Still need to work out the logic for more than two array arguments,
// For the moment, we're throwing an Exception when we initialise the ArrayArgumentHelper
return ['#VALUE!'];
}
/**
* @param mixed ...$arguments
*/
private static function evaluateVectorMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array
{
$matrix2 = array_pop($matrixIndexes);
/** @var array $matrixValues2 */
$matrixValues2 = $arguments[$matrix2];
$matrix1 = array_pop($matrixIndexes);
/** @var array $matrixValues1 */
$matrixValues1 = $arguments[$matrix1];
$rows = min(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
$columns = min(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
if ($rows === 1) {
$rows = max(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
}
if ($columns === 1) {
$columns = max(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
}
$result = [];
for ($rowIndex = 0; $rowIndex < $rows; ++$rowIndex) {
for ($columnIndex = 0; $columnIndex < $columns; ++$columnIndex) {
$rowIndex1 = self::$arrayArgumentHelper->isRowVector($matrix1) ? 0 : $rowIndex;
$columnIndex1 = self::$arrayArgumentHelper->isColumnVector($matrix1) ? 0 : $columnIndex;
$value1 = $matrixValues1[$rowIndex1][$columnIndex1];
$rowIndex2 = self::$arrayArgumentHelper->isRowVector($matrix2) ? 0 : $rowIndex;
$columnIndex2 = self::$arrayArgumentHelper->isColumnVector($matrix2) ? 0 : $columnIndex;
$value2 = $matrixValues2[$rowIndex2][$columnIndex2];
$arguments[$matrix1] = $value1;
$arguments[$matrix2] = $value2;
$result[$rowIndex][$columnIndex] = $method(...$arguments);
}
}
return $result;
}
/**
* @param mixed ...$arguments
*/
private static function evaluateMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array
{
$matrix2 = array_pop($matrixIndexes);
/** @var array $matrixValues2 */
$matrixValues2 = $arguments[$matrix2];
$matrix1 = array_pop($matrixIndexes);
/** @var array $matrixValues1 */
$matrixValues1 = $arguments[$matrix1];
$result = [];
foreach ($matrixValues1 as $rowIndex => $row) {
foreach ($row as $columnIndex => $value1) {
if (isset($matrixValues2[$rowIndex][$columnIndex]) === false) {
continue;
}
$value2 = $matrixValues2[$rowIndex][$columnIndex];
$arguments[$matrix1] = $value1;
$arguments[$matrix2] = $value2;
$result[$rowIndex][$columnIndex] = $method(...$arguments);
}
}
return $result;
}
/**
* @param mixed ...$arguments
*/
private static function evaluateVectorPair(callable $method, int $rowIndex, int $columnIndex, ...$arguments): array
{
$rowVector = Functions::flattenArray($arguments[$rowIndex]);
$columnVector = Functions::flattenArray($arguments[$columnIndex]);
$result = [];
foreach ($columnVector as $column) {
$rowResults = [];
foreach ($rowVector as $row) {
$arguments[$rowIndex] = $row;
$arguments[$columnIndex] = $column;
$rowResults[] = $method(...$arguments);
}
$result[] = $rowResults;
}
return $result;
}
/**
* Note, offset is from 1 (for the first argument) rather than from 0.
*
* @param mixed ...$arguments
*/
private static function evaluateNthArgumentAsArray(callable $method, int $nthArgument, ...$arguments): array
{
$values = array_slice($arguments, $nthArgument - 1, 1);
/** @var array $values */
$values = array_pop($values);
$result = [];
foreach ($values as $value) {
$arguments[$nthArgument - 1] = $value;
$result[] = $method(...$arguments);
}
return $result;
}
}

View File

@ -0,0 +1,223 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class BranchPruner
{
/**
* @var bool
*/
protected $branchPruningEnabled = true;
/**
* Used to generate unique store keys.
*
* @var int
*/
private $branchStoreKeyCounter = 0;
/**
* currently pending storeKey (last item of the storeKeysStack.
*
* @var ?string
*/
protected $pendingStoreKey;
/**
* @var string[]
*/
protected $storeKeysStack = [];
/**
* @var bool[]
*/
protected $conditionMap = [];
/**
* @var bool[]
*/
protected $thenMap = [];
/**
* @var bool[]
*/
protected $elseMap = [];
/**
* @var int[]
*/
protected $braceDepthMap = [];
/**
* @var null|string
*/
protected $currentCondition;
/**
* @var null|string
*/
protected $currentOnlyIf;
/**
* @var null|string
*/
protected $currentOnlyIfNot;
/**
* @var null|string
*/
protected $previousStoreKey;
public function __construct(bool $branchPruningEnabled)
{
$this->branchPruningEnabled = $branchPruningEnabled;
}
public function clearBranchStore(): void
{
$this->branchStoreKeyCounter = 0;
}
public function initialiseForLoop(): void
{
$this->currentCondition = null;
$this->currentOnlyIf = null;
$this->currentOnlyIfNot = null;
$this->previousStoreKey = null;
$this->pendingStoreKey = empty($this->storeKeysStack) ? null : end($this->storeKeysStack);
if ($this->branchPruningEnabled) {
$this->initialiseCondition();
$this->initialiseThen();
$this->initialiseElse();
}
}
private function initialiseCondition(): void
{
if (isset($this->conditionMap[$this->pendingStoreKey]) && $this->conditionMap[$this->pendingStoreKey]) {
$this->currentCondition = $this->pendingStoreKey;
$stackDepth = count($this->storeKeysStack);
if ($stackDepth > 1) {
// nested if
$this->previousStoreKey = $this->storeKeysStack[$stackDepth - 2];
}
}
}
private function initialiseThen(): void
{
if (isset($this->thenMap[$this->pendingStoreKey]) && $this->thenMap[$this->pendingStoreKey]) {
$this->currentOnlyIf = $this->pendingStoreKey;
} elseif (
isset($this->previousStoreKey, $this->thenMap[$this->previousStoreKey])
&& $this->thenMap[$this->previousStoreKey]
) {
$this->currentOnlyIf = $this->previousStoreKey;
}
}
private function initialiseElse(): void
{
if (isset($this->elseMap[$this->pendingStoreKey]) && $this->elseMap[$this->pendingStoreKey]) {
$this->currentOnlyIfNot = $this->pendingStoreKey;
} elseif (
isset($this->previousStoreKey, $this->elseMap[$this->previousStoreKey])
&& $this->elseMap[$this->previousStoreKey]
) {
$this->currentOnlyIfNot = $this->previousStoreKey;
}
}
public function decrementDepth(): void
{
if (!empty($this->pendingStoreKey)) {
--$this->braceDepthMap[$this->pendingStoreKey];
}
}
public function incrementDepth(): void
{
if (!empty($this->pendingStoreKey)) {
++$this->braceDepthMap[$this->pendingStoreKey];
}
}
public function functionCall(string $functionName): void
{
if ($this->branchPruningEnabled && ($functionName === 'IF(')) {
// we handle a new if
$this->pendingStoreKey = $this->getUnusedBranchStoreKey();
$this->storeKeysStack[] = $this->pendingStoreKey;
$this->conditionMap[$this->pendingStoreKey] = true;
$this->braceDepthMap[$this->pendingStoreKey] = 0;
} elseif (!empty($this->pendingStoreKey) && array_key_exists($this->pendingStoreKey, $this->braceDepthMap)) {
// this is not an if but we go deeper
++$this->braceDepthMap[$this->pendingStoreKey];
}
}
public function argumentSeparator(): void
{
if (!empty($this->pendingStoreKey) && $this->braceDepthMap[$this->pendingStoreKey] === 0) {
// We must go to the IF next argument
if ($this->conditionMap[$this->pendingStoreKey]) {
$this->conditionMap[$this->pendingStoreKey] = false;
$this->thenMap[$this->pendingStoreKey] = true;
} elseif ($this->thenMap[$this->pendingStoreKey]) {
$this->thenMap[$this->pendingStoreKey] = false;
$this->elseMap[$this->pendingStoreKey] = true;
} elseif ($this->elseMap[$this->pendingStoreKey]) {
throw new Exception('Reaching fourth argument of an IF');
}
}
}
/**
* @param mixed $value
*/
public function closingBrace($value): void
{
if (!empty($this->pendingStoreKey) && $this->braceDepthMap[$this->pendingStoreKey] === -1) {
// we are closing an IF(
if ($value !== 'IF(') {
throw new Exception('Parser bug we should be in an "IF("');
}
if ($this->conditionMap[$this->pendingStoreKey]) {
throw new Exception('We should not be expecting a condition');
}
$this->thenMap[$this->pendingStoreKey] = false;
$this->elseMap[$this->pendingStoreKey] = false;
--$this->braceDepthMap[$this->pendingStoreKey];
array_pop($this->storeKeysStack);
$this->pendingStoreKey = null;
}
}
public function currentCondition(): ?string
{
return $this->currentCondition;
}
public function currentOnlyIf(): ?string
{
return $this->currentOnlyIf;
}
public function currentOnlyIfNot(): ?string
{
return $this->currentOnlyIfNot;
}
private function getUnusedBranchStoreKey(): string
{
$storeKeyValue = 'storeKey-' . $this->branchStoreKeyCounter;
++$this->branchStoreKeyCounter;
return $storeKeyValue;
}
}

View File

@ -87,18 +87,20 @@ class Logger
/**
* Write an entry to the calculation engine debug log.
*
* @param mixed $args
*/
public function writeDebugLog(...$args): void
public function writeDebugLog(string $message, ...$args): void
{
// Only write the debug log if logging is enabled
if ($this->writeDebugLog) {
$message = implode('', $args);
$message = sprintf($message, ...$args);
$cellReference = implode(' -> ', $this->cellStack->showStack());
if ($this->echoDebugLog) {
echo $cellReference,
($this->cellStack->count() > 0 ? ' => ' : ''),
$message,
PHP_EOL;
($this->cellStack->count() > 0 ? ' => ' : ''),
$message,
PHP_EOL;
}
$this->debugLog[] = $cellReference .
($this->cellStack->count() > 0 ? ' => ' : '') .

View File

@ -61,7 +61,7 @@ class Engineering
* If $ord is nonnumeric, BESSELI returns the #VALUE! error value.
* If $ord < 0, BESSELI returns the #NUM! error value.
*
* @return float|string Result, or a string containing an error
* @return array|float|string Result, or a string containing an error
*/
public static function BESSELI($x, $ord)
{
@ -86,7 +86,7 @@ class Engineering
* 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
* @return array|float|string Result, or a string containing an error
*/
public static function BESSELJ($x, $ord)
{
@ -112,7 +112,7 @@ class Engineering
* If $ord is nonnumeric, BESSELK returns the #VALUE! error value.
* If $ord < 0, BESSELK returns the #NUM! error value.
*
* @return float|string Result, or a string containing an error
* @return array|float|string Result, or a string containing an error
*/
public static function BESSELK($x, $ord)
{
@ -137,7 +137,7 @@ class Engineering
* 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
* @return array|float|string Result, or a string containing an error
*/
public static function BESSELY($x, $ord)
{
@ -163,7 +163,7 @@ class Engineering
* If number is not a valid binary number, or if number contains more than
* 10 characters (10 bits), BIN2DEC returns the #NUM! error value.
*
* @return string
* @return array|string
*/
public static function BINTODEC($x)
{
@ -195,7 +195,7 @@ class Engineering
* If places is nonnumeric, BIN2HEX returns the #VALUE! error value.
* If places is negative, BIN2HEX returns the #NUM! error value.
*
* @return string
* @return array|string
*/
public static function BINTOHEX($x, $places = null)
{
@ -227,7 +227,7 @@ class Engineering
* If places is nonnumeric, BIN2OCT returns the #VALUE! error value.
* If places is negative, BIN2OCT returns the #NUM! error value.
*
* @return string
* @return array|string
*/
public static function BINTOOCT($x, $places = null)
{
@ -263,7 +263,7 @@ class Engineering
* If places is nonnumeric, DEC2BIN returns the #VALUE! error value.
* If places is zero or negative, DEC2BIN returns the #NUM! error value.
*
* @return string
* @return array|string
*/
public static function DECTOBIN($x, $places = null)
{
@ -299,7 +299,7 @@ class Engineering
* If places is nonnumeric, DEC2HEX returns the #VALUE! error value.
* If places is zero or negative, DEC2HEX returns the #NUM! error value.
*
* @return string
* @return array|string
*/
public static function DECTOHEX($x, $places = null)
{
@ -335,7 +335,7 @@ class Engineering
* If places is nonnumeric, DEC2OCT returns the #VALUE! error value.
* If places is zero or negative, DEC2OCT returns the #NUM! error value.
*
* @return string
* @return array|string
*/
public static function DECTOOCT($x, $places = null)
{
@ -371,7 +371,7 @@ class Engineering
* If places is nonnumeric, HEX2BIN returns the #VALUE! error value.
* If places is negative, HEX2BIN returns the #NUM! error value.
*
* @return string
* @return array|string
*/
public static function HEXTOBIN($x, $places = null)
{
@ -398,7 +398,7 @@ class Engineering
* If number is not a valid hexadecimal number, HEX2DEC returns the
* #NUM! error value.
*
* @return string
* @return array|string
*/
public static function HEXTODEC($x)
{
@ -438,7 +438,7 @@ class Engineering
* value.
* If places is negative, HEX2OCT returns the #NUM! error value.
*
* @return string
* @return array|string
*/
public static function HEXTOOCT($x, $places = null)
{
@ -480,7 +480,7 @@ class Engineering
* If places is negative, OCT2BIN returns the #NUM! error
* value.
*
* @return string
* @return array|string
*/
public static function OCTTOBIN($x, $places = null)
{
@ -507,7 +507,7 @@ class Engineering
* If number is not a valid octal number, OCT2DEC returns the
* #NUM! error value.
*
* @return string
* @return array|string
*/
public static function OCTTODEC($x)
{
@ -544,7 +544,7 @@ class Engineering
* If places is nonnumeric, OCT2HEX returns the #VALUE! error value.
* If places is negative, OCT2HEX returns the #NUM! error value.
*
* @return string
* @return array|string
*/
public static function OCTTOHEX($x, $places = null)
{
@ -563,12 +563,12 @@ class Engineering
*
* @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.
* @param array|float $realNumber the real coefficient of the complex number
* @param array|float $imaginary the imaginary coefficient of the complex number
* @param array|string $suffix The suffix for the imaginary component of the complex number.
* If omitted, the suffix is assumed to be "i".
*
* @return string
* @return array|string
*/
public static function COMPLEX($realNumber = 0.0, $imaginary = 0.0, $suffix = 'i')
{
@ -590,7 +590,7 @@ class Engineering
* @param string $complexNumber the complex number for which you want the imaginary
* coefficient
*
* @return float|string
* @return array|float|string
*/
public static function IMAGINARY($complexNumber)
{
@ -611,7 +611,7 @@ class Engineering
*
* @param string $complexNumber the complex number for which you want the real coefficient
*
* @return float|string
* @return array|float|string
*/
public static function IMREAL($complexNumber)
{
@ -632,7 +632,7 @@ class Engineering
*
* @param string $complexNumber the complex number for which you want the absolute value
*
* @return float|string
* @return array|float|string
*/
public static function IMABS($complexNumber)
{
@ -652,9 +652,9 @@ class Engineering
*
* @see Use the IMARGUMENT() method in the Engineering\ComplexFunctions class instead
*
* @param string $complexNumber the complex number for which you want the argument theta
* @param array|string $complexNumber the complex number for which you want the argument theta
*
* @return float|string
* @return array|float|string
*/
public static function IMARGUMENT($complexNumber)
{
@ -673,9 +673,9 @@ class Engineering
*
* @see Use the IMARGUMENT() method in the Engineering\ComplexFunctions class instead
*
* @param string $complexNumber the complex number for which you want the conjugate
* @param array|string $complexNumber the complex number for which you want the conjugate
*
* @return string
* @return array|string
*/
public static function IMCONJUGATE($complexNumber)
{
@ -694,9 +694,9 @@ class Engineering
*
* @see Use the IMCOS() method in the Engineering\ComplexFunctions class instead
*
* @param string $complexNumber the complex number for which you want the cosine
* @param array|string $complexNumber the complex number for which you want the cosine
*
* @return float|string
* @return array|float|string
*/
public static function IMCOS($complexNumber)
{
@ -715,9 +715,9 @@ class Engineering
*
* @see Use the IMCOSH() method in the Engineering\ComplexFunctions class instead
*
* @param string $complexNumber the complex number for which you want the hyperbolic cosine
* @param array|string $complexNumber the complex number for which you want the hyperbolic cosine
*
* @return float|string
* @return array|float|string
*/
public static function IMCOSH($complexNumber)
{
@ -736,9 +736,9 @@ class Engineering
*
* @see Use the IMCOT() method in the Engineering\ComplexFunctions class instead
*
* @param string $complexNumber the complex number for which you want the cotangent
* @param array|string $complexNumber the complex number for which you want the cotangent
*
* @return float|string
* @return array|float|string
*/
public static function IMCOT($complexNumber)
{
@ -757,9 +757,9 @@ class Engineering
*
* @see Use the IMCSC() method in the Engineering\ComplexFunctions class instead
*
* @param string $complexNumber the complex number for which you want the cosecant
* @param array|string $complexNumber the complex number for which you want the cosecant
*
* @return float|string
* @return array|float|string
*/
public static function IMCSC($complexNumber)
{
@ -778,9 +778,9 @@ class Engineering
*
* @see Use the IMCSCH() method in the Engineering\ComplexFunctions class instead
*
* @param string $complexNumber the complex number for which you want the hyperbolic cosecant
* @param array|string $complexNumber the complex number for which you want the hyperbolic cosecant
*
* @return float|string
* @return array|float|string
*/
public static function IMCSCH($complexNumber)
{
@ -801,7 +801,7 @@ class Engineering
*
* @param string $complexNumber the complex number for which you want the sine
*
* @return float|string
* @return array|float|string
*/
public static function IMSIN($complexNumber)
{
@ -822,7 +822,7 @@ class Engineering
*
* @param string $complexNumber the complex number for which you want the hyperbolic sine
*
* @return float|string
* @return array|float|string
*/
public static function IMSINH($complexNumber)
{
@ -843,7 +843,7 @@ class Engineering
*
* @param string $complexNumber the complex number for which you want the secant
*
* @return float|string
* @return array|float|string
*/
public static function IMSEC($complexNumber)
{
@ -864,7 +864,7 @@ class Engineering
*
* @param string $complexNumber the complex number for which you want the hyperbolic secant
*
* @return float|string
* @return array|float|string
*/
public static function IMSECH($complexNumber)
{
@ -885,7 +885,7 @@ class Engineering
*
* @param string $complexNumber the complex number for which you want the tangent
*
* @return float|string
* @return array|float|string
*/
public static function IMTAN($complexNumber)
{
@ -906,7 +906,7 @@ class Engineering
*
* @param string $complexNumber the complex number for which you want the square root
*
* @return string
* @return array|string
*/
public static function IMSQRT($complexNumber)
{
@ -927,7 +927,7 @@ class Engineering
*
* @param string $complexNumber the complex number for which you want the natural logarithm
*
* @return string
* @return array|string
*/
public static function IMLN($complexNumber)
{
@ -948,7 +948,7 @@ class Engineering
*
* @param string $complexNumber the complex number for which you want the common logarithm
*
* @return string
* @return array|string
*/
public static function IMLOG10($complexNumber)
{
@ -969,7 +969,7 @@ class Engineering
*
* @param string $complexNumber the complex number for which you want the base-2 logarithm
*
* @return string
* @return array|string
*/
public static function IMLOG2($complexNumber)
{
@ -990,7 +990,7 @@ class Engineering
*
* @param string $complexNumber the complex number for which you want the exponential
*
* @return string
* @return array|string
*/
public static function IMEXP($complexNumber)
{
@ -1012,7 +1012,7 @@ class Engineering
* @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
* @return array|string
*/
public static function IMPOWER($complexNumber, $realNumber)
{
@ -1034,7 +1034,7 @@ class Engineering
* @param string $complexDividend the complex numerator or dividend
* @param string $complexDivisor the complex denominator or divisor
*
* @return string
* @return array|string
*/
public static function IMDIV($complexDividend, $complexDivisor)
{
@ -1056,7 +1056,7 @@ class Engineering
* @param string $complexNumber1 the complex number from which to subtract complexNumber2
* @param string $complexNumber2 the complex number to subtract from complexNumber1
*
* @return string
* @return array|string
*/
public static function IMSUB($complexNumber1, $complexNumber2)
{
@ -1123,7 +1123,7 @@ class Engineering
* @param float $a the first number
* @param float $b The second number. If omitted, b is assumed to be zero.
*
* @return int|string (string in the event of an error)
* @return array|int|string (string in the event of an error)
*/
public static function DELTA($a, $b = 0)
{
@ -1147,7 +1147,7 @@ class Engineering
* @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)
* @return array|int|string (string in the event of an error)
*/
public static function GESTEP($number, $step = 0)
{
@ -1169,7 +1169,7 @@ class Engineering
* @param int $number1
* @param int $number2
*
* @return int|string
* @return array|int|string
*/
public static function BITAND($number1, $number2)
{
@ -1191,7 +1191,7 @@ class Engineering
* @param int $number1
* @param int $number2
*
* @return int|string
* @return array|int|string
*/
public static function BITOR($number1, $number2)
{
@ -1213,7 +1213,7 @@ class Engineering
* @param int $number1
* @param int $number2
*
* @return int|string
* @return array|int|string
*/
public static function BITXOR($number1, $number2)
{
@ -1235,7 +1235,7 @@ class Engineering
* @param int $number
* @param int $shiftAmount
*
* @return int|string
* @return array|float|int|string
*/
public static function BITLSHIFT($number, $shiftAmount)
{
@ -1257,7 +1257,7 @@ class Engineering
* @param int $number
* @param int $shiftAmount
*
* @return int|string
* @return array|float|int|string
*/
public static function BITRSHIFT($number, $shiftAmount)
{
@ -1285,7 +1285,7 @@ class Engineering
* @param float $upper upper bound for integrating ERF.
* If omitted, ERF integrates between zero and lower_limit
*
* @return float|string
* @return array|float|string
*/
public static function ERF($lower, $upper = null)
{
@ -1306,7 +1306,7 @@ class Engineering
*
* @param float $limit bound for integrating ERF
*
* @return float|string
* @return array|float|string
*/
public static function ERFPRECISE($limit)
{
@ -1332,7 +1332,7 @@ class Engineering
*
* @param float $x The lower bound for integrating ERFC
*
* @return float|string
* @return array|float|string
*/
public static function ERFC($x)
{
@ -1437,7 +1437,7 @@ class Engineering
* @param string $fromUOM the units for value
* @param string $toUOM the units for the result
*
* @return float|string
* @return array|float|string
*/
public static function CONVERTUOM($value, $fromUOM, $toUOM)
{

View File

@ -2,11 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BesselI
{
use ArrayEnabled;
/**
* BESSELI.
*
@ -21,17 +24,22 @@ class BesselI
*
* @param mixed $x A float value at which to evaluate the function.
* If x is nonnumeric, BESSELI returns the #VALUE! error value.
* Or can be an array of values
* @param mixed $ord The integer order of the Bessel function.
* If ord is not an integer, it is truncated.
* If $ord is nonnumeric, BESSELI returns the #VALUE! error value.
* If $ord < 0, BESSELI returns the #NUM! error value.
* Or can be an array of values
*
* @return float|string Result, or a string containing an error
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BESSELI($x, $ord)
{
$x = Functions::flattenSingleValue($x);
$ord = Functions::flattenSingleValue($ord);
if (is_array($x) || is_array($ord)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
}
try {
$x = EngineeringValidations::validateFloat($x);
@ -41,12 +49,12 @@ class BesselI
}
if ($ord < 0) {
return Functions::NAN();
return ExcelError::NAN();
}
$fResult = self::calculate($x, $ord);
return (is_nan($fResult)) ? Functions::NAN() : $fResult;
return (is_nan($fResult)) ? ExcelError::NAN() : $fResult;
}
private static function calculate(float $x, int $ord): float

View File

@ -2,11 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BesselJ
{
use ArrayEnabled;
/**
* BESSELJ.
*
@ -20,17 +23,22 @@ class BesselJ
*
* @param mixed $x A float value at which to evaluate the function.
* If x is nonnumeric, BESSELJ returns the #VALUE! error value.
* Or can be an array of values
* @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.
* Or can be an array of values
*
* @return float|string Result, or a string containing an error
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BESSELJ($x, $ord)
{
$x = Functions::flattenSingleValue($x);
$ord = Functions::flattenSingleValue($ord);
if (is_array($x) || is_array($ord)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
}
try {
$x = EngineeringValidations::validateFloat($x);
@ -40,12 +48,12 @@ class BesselJ
}
if ($ord < 0) {
return Functions::NAN();
return ExcelError::NAN();
}
$fResult = self::calculate($x, $ord);
return (is_nan($fResult)) ? Functions::NAN() : $fResult;
return (is_nan($fResult)) ? ExcelError::NAN() : $fResult;
}
private static function calculate(float $x, int $ord): float

View File

@ -2,11 +2,15 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BesselK
{
use ArrayEnabled;
/**
* BESSELK.
*
@ -18,17 +22,22 @@ class BesselK
*
* @param mixed $x A float value at which to evaluate the function.
* If x is nonnumeric, BESSELK returns the #VALUE! error value.
* Or can be an array of values
* @param mixed $ord The integer order of the Bessel function.
* If ord is not an integer, it is truncated.
* If $ord is nonnumeric, BESSELK returns the #VALUE! error value.
* If $ord < 0, BESSELKI returns the #NUM! error value.
* Or can be an array of values
*
* @return float|string Result, or a string containing an error
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BESSELK($x, $ord)
{
$x = Functions::flattenSingleValue($x);
$ord = Functions::flattenSingleValue($ord);
if (is_array($x) || is_array($ord)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
}
try {
$x = EngineeringValidations::validateFloat($x);
@ -38,12 +47,12 @@ class BesselK
}
if (($ord < 0) || ($x <= 0.0)) {
return Functions::NAN();
return ExcelError::NAN();
}
$fBk = self::calculate($x, $ord);
return (is_nan($fBk)) ? Functions::NAN() : $fBk;
return (is_nan($fBk)) ? ExcelError::NAN() : $fBk;
}
private static function calculate(float $x, int $ord): float
@ -59,13 +68,28 @@ class BesselK
return self::besselK2($x, $ord);
}
/**
* Mollify Phpstan.
*
* @codeCoverageIgnore
*/
private static function callBesselI(float $x, int $ord): float
{
$rslt = BesselI::BESSELI($x, $ord);
if (!is_float($rslt)) {
throw new Exception('Unexpected array or string');
}
return $rslt;
}
private static function besselK0(float $x): float
{
if ($x <= 2) {
$fNum2 = $x * 0.5;
$y = ($fNum2 * $fNum2);
return -log($fNum2) * BesselI::BESSELI($x, 0) +
return -log($fNum2) * self::callBesselI($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))))));
}
@ -83,7 +107,7 @@ class BesselK
$fNum2 = $x * 0.5;
$y = ($fNum2 * $fNum2);
return log($fNum2) * BesselI::BESSELI($x, 1) +
return log($fNum2) * self::callBesselI($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;
}
@ -95,7 +119,7 @@ class BesselK
(0.325614e-2 + $y * (-0.68245e-3)))))));
}
private static function besselK2(float $x, int $ord)
private static function besselK2(float $x, int $ord): float
{
$fTox = 2 / $x;
$fBkm = self::besselK0($x);

View File

@ -2,11 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BesselY
{
use ArrayEnabled;
/**
* BESSELY.
*
@ -17,17 +20,22 @@ class BesselY
*
* @param mixed $x A float value at which to evaluate the function.
* If x is nonnumeric, BESSELY returns the #VALUE! error value.
* Or can be an array of values
* @param mixed $ord The integer order of the Bessel function.
* If ord is not an integer, it is truncated.
* If $ord is nonnumeric, BESSELY returns the #VALUE! error value.
* If $ord < 0, BESSELY returns the #NUM! error value.
* Or can be an array of values
*
* @return float|string Result, or a string containing an error
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BESSELY($x, $ord)
{
$x = Functions::flattenSingleValue($x);
$ord = Functions::flattenSingleValue($ord);
if (is_array($x) || is_array($ord)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
}
try {
$x = EngineeringValidations::validateFloat($x);
@ -37,12 +45,12 @@ class BesselY
}
if (($ord < 0) || ($x <= 0.0)) {
return Functions::NAN();
return ExcelError::NAN();
}
$fBy = self::calculate($x, $ord);
return (is_nan($fBy)) ? Functions::NAN() : $fBy;
return (is_nan($fBy)) ? ExcelError::NAN() : $fBy;
}
private static function calculate(float $x, int $ord): float
@ -58,6 +66,21 @@ class BesselY
return self::besselY2($x, $ord);
}
/**
* Mollify Phpstan.
*
* @codeCoverageIgnore
*/
private static function callBesselJ(float $x, int $ord): float
{
$rslt = BesselJ::BESSELJ($x, $ord);
if (!is_float($rslt)) {
throw new Exception('Unexpected array or string');
}
return $rslt;
}
private static function besselY0(float $x): float
{
if ($x < 8.0) {
@ -67,7 +90,7 @@ class BesselY
$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);
return $ans1 / $ans2 + 0.636619772 * self::callBesselJ($x, 0) * log($x);
}
$z = 8.0 / $x;
@ -89,7 +112,7 @@ class BesselY
$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);
return ($ans1 / $ans2) + 0.636619772 * (self::callBesselJ($x, 1) * log($x) - 1 / $x);
}
$z = 8.0 / $x;

View File

@ -2,11 +2,15 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class BitWise
{
use ArrayEnabled;
const SPLIT_DIVISOR = 2 ** 24;
/**
@ -27,13 +31,21 @@ class BitWise
* Excel Function:
* BITAND(number1, number2)
*
* @param int $number1
* @param int $number2
* @param array|int $number1
* Or can be an array of values
* @param array|int $number2
* Or can be an array of values
*
* @return int|string
* @return array|int|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITAND($number1, $number2)
{
if (is_array($number1) || is_array($number2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2);
}
try {
$number1 = self::validateBitwiseArgument($number1);
$number2 = self::validateBitwiseArgument($number2);
@ -54,13 +66,21 @@ class BitWise
* Excel Function:
* BITOR(number1, number2)
*
* @param int $number1
* @param int $number2
* @param array|int $number1
* Or can be an array of values
* @param array|int $number2
* Or can be an array of values
*
* @return int|string
* @return array|int|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITOR($number1, $number2)
{
if (is_array($number1) || is_array($number2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2);
}
try {
$number1 = self::validateBitwiseArgument($number1);
$number2 = self::validateBitwiseArgument($number2);
@ -82,13 +102,21 @@ class BitWise
* Excel Function:
* BITXOR(number1, number2)
*
* @param int $number1
* @param int $number2
* @param array|int $number1
* Or can be an array of values
* @param array|int $number2
* Or can be an array of values
*
* @return int|string
* @return array|int|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITXOR($number1, $number2)
{
if (is_array($number1) || is_array($number2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2);
}
try {
$number1 = self::validateBitwiseArgument($number1);
$number2 = self::validateBitwiseArgument($number2);
@ -110,13 +138,21 @@ class BitWise
* Excel Function:
* BITLSHIFT(number, shift_amount)
*
* @param int $number
* @param int $shiftAmount
* @param array|int $number
* Or can be an array of values
* @param array|int $shiftAmount
* Or can be an array of values
*
* @return float|int|string
* @return array|float|int|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITLSHIFT($number, $shiftAmount)
{
if (is_array($number) || is_array($shiftAmount)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $shiftAmount);
}
try {
$number = self::validateBitwiseArgument($number);
$shiftAmount = self::validateShiftAmount($shiftAmount);
@ -126,7 +162,7 @@ class BitWise
$result = floor($number * (2 ** $shiftAmount));
if ($result > 2 ** 48 - 1) {
return Functions::NAN();
return ExcelError::NAN();
}
return $result;
@ -140,13 +176,21 @@ class BitWise
* Excel Function:
* BITRSHIFT(number, shift_amount)
*
* @param int $number
* @param int $shiftAmount
* @param array|int $number
* Or can be an array of values
* @param array|int $shiftAmount
* Or can be an array of values
*
* @return float|int|string
* @return array|float|int|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITRSHIFT($number, $shiftAmount)
{
if (is_array($number) || is_array($shiftAmount)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $shiftAmount);
}
try {
$number = self::validateBitwiseArgument($number);
$shiftAmount = self::validateShiftAmount($shiftAmount);
@ -156,7 +200,7 @@ class BitWise
$result = floor($number / (2 ** $shiftAmount));
if ($result > 2 ** 48 - 1) { // possible because shiftAmount can be negative
return Functions::NAN();
return ExcelError::NAN();
}
return $result;
@ -167,25 +211,26 @@ class BitWise
*
* @param mixed $value
*
* @return float|int
* @return float
*/
private static function validateBitwiseArgument($value)
{
self::nullFalseTrueToNumber($value);
$value = self::nullFalseTrueToNumber($value);
if (is_numeric($value)) {
$value = (float) $value;
if ($value == floor($value)) {
if (($value > 2 ** 48 - 1) || ($value < 0)) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return floor($value);
}
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
/**
@ -197,31 +242,34 @@ class BitWise
*/
private static function validateShiftAmount($value)
{
self::nullFalseTrueToNumber($value);
$value = self::nullFalseTrueToNumber($value);
if (is_numeric($value)) {
if (abs($value) > 53) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return (int) $value;
}
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
/**
* Many functions accept null/false/true argument treated as 0/0/1.
*
* @param mixed $number
*
* @return mixed
*/
public static function nullFalseTrueToNumber(&$number): void
private static function nullFalseTrueToNumber(&$number)
{
$number = Functions::flattenSingleValue($number);
if ($number === null) {
$number = 0;
} elseif (is_bool($number)) {
$number = (int) $number;
}
return $number;
}
}

View File

@ -2,11 +2,13 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Compare
{
use ArrayEnabled;
/**
* DELTA.
*
@ -18,15 +20,20 @@ class Compare
* functions you calculate the count of equal pairs. This function is also known as the
* Kronecker Delta function.
*
* @param float $a the first number
* @param float $b The second number. If omitted, b is assumed to be zero.
* @param array|float $a the first number
* Or can be an array of values
* @param array|float $b The second number. If omitted, b is assumed to be zero.
* Or can be an array of values
*
* @return int|string (string in the event of an error)
* @return array|int|string (string in the event of an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function DELTA($a, $b = 0)
public static function DELTA($a, $b = 0.0)
{
$a = Functions::flattenSingleValue($a);
$b = Functions::flattenSingleValue($b);
if (is_array($a) || is_array($b)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $a, $b);
}
try {
$a = EngineeringValidations::validateFloat($a);
@ -35,7 +42,7 @@ class Compare
return $e->getMessage();
}
return (int) ($a == $b);
return (int) (abs($a - $b) < 1.0e-15);
}
/**
@ -48,19 +55,24 @@ class Compare
* 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.
* @param array|float $number the value to test against step
* Or can be an array of values
* @param null|array|float $step The threshold value. If you omit a value for step, GESTEP uses zero.
* Or can be an array of values
*
* @return int|string (string in the event of an error)
* @return array|int|string (string in the event of an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function GESTEP($number, $step = 0)
public static function GESTEP($number, $step = 0.0)
{
$number = Functions::flattenSingleValue($number);
$step = Functions::flattenSingleValue($step);
if (is_array($number) || is_array($step)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $step);
}
try {
$number = EngineeringValidations::validateFloat($number);
$step = EngineeringValidations::validateFloat($step);
$step = EngineeringValidations::validateFloat($step ?? 0.0);
} catch (Exception $e) {
return $e->getMessage();
}

View File

@ -4,11 +4,14 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use Complex\Complex as ComplexObject;
use Complex\Exception as ComplexException;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Complex
{
use ArrayEnabled;
/**
* COMPLEX.
*
@ -18,17 +21,26 @@ class Complex
* COMPLEX(realNumber,imaginary[,suffix])
*
* @param mixed $realNumber the real float coefficient of the complex number
* Or can be an array of values
* @param mixed $imaginary the imaginary float coefficient of the complex number
* Or can be an array of values
* @param mixed $suffix The character suffix for the imaginary component of the complex number.
* If omitted, the suffix is assumed to be "i".
* Or can be an array of values
*
* @return string
* @return array|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
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_array($realNumber) || is_array($imaginary) || is_array($suffix)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $realNumber, $imaginary, $suffix);
}
$realNumber = $realNumber ?? 0.0;
$imaginary = $imaginary ?? 0.0;
$suffix = $suffix ?? 'i';
try {
$realNumber = EngineeringValidations::validateFloat($realNumber);
@ -43,7 +55,7 @@ class Complex
return (string) $complex;
}
return Functions::VALUE();
return ExcelError::VALUE();
}
/**
@ -54,19 +66,24 @@ class Complex
* Excel Function:
* IMAGINARY(complexNumber)
*
* @param string $complexNumber the complex number for which you want the imaginary
* @param array|string $complexNumber the complex number for which you want the imaginary
* coefficient
* Or can be an array of values
*
* @return float|string
* @return array|float|string (string if an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMAGINARY($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return $complex->getImaginary();
@ -80,18 +97,23 @@ class Complex
* Excel Function:
* IMREAL(complexNumber)
*
* @param string $complexNumber the complex number for which you want the real coefficient
* @param array|string $complexNumber the complex number for which you want the real coefficient
* Or can be an array of values
*
* @return float|string
* @return array|float|string (string if an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMREAL($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return $complex->getReal();

View File

@ -4,10 +4,13 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use Complex\Complex as ComplexObject;
use Complex\Exception as ComplexException;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ComplexFunctions
{
use ArrayEnabled;
/**
* IMABS.
*
@ -16,18 +19,23 @@ class ComplexFunctions
* Excel Function:
* IMABS(complexNumber)
*
* @param string $complexNumber the complex number for which you want the absolute value
* @param array|string $complexNumber the complex number for which you want the absolute value
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMABS($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return $complex->abs();
@ -42,22 +50,27 @@ class ComplexFunctions
* Excel Function:
* IMARGUMENT(complexNumber)
*
* @param string $complexNumber the complex number for which you want the argument theta
* @param array|string $complexNumber the complex number for which you want the argument theta
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMARGUMENT($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
return Functions::DIV0();
return ExcelError::DIV0();
}
return $complex->argument();
@ -71,18 +84,23 @@ class ComplexFunctions
* Excel Function:
* IMCONJUGATE(complexNumber)
*
* @param string $complexNumber the complex number for which you want the conjugate
* @param array|string $complexNumber the complex number for which you want the conjugate
* Or can be an array of values
*
* @return string
* @return array|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCONJUGATE($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->conjugate();
@ -96,18 +114,23 @@ class ComplexFunctions
* Excel Function:
* IMCOS(complexNumber)
*
* @param string $complexNumber the complex number for which you want the cosine
* @param array|string $complexNumber the complex number for which you want the cosine
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCOS($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->cos();
@ -121,18 +144,23 @@ class ComplexFunctions
* Excel Function:
* IMCOSH(complexNumber)
*
* @param string $complexNumber the complex number for which you want the hyperbolic cosine
* @param array|string $complexNumber the complex number for which you want the hyperbolic cosine
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCOSH($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->cosh();
@ -146,18 +174,23 @@ class ComplexFunctions
* Excel Function:
* IMCOT(complexNumber)
*
* @param string $complexNumber the complex number for which you want the cotangent
* @param array|string $complexNumber the complex number for which you want the cotangent
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCOT($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->cot();
@ -171,18 +204,23 @@ class ComplexFunctions
* Excel Function:
* IMCSC(complexNumber)
*
* @param string $complexNumber the complex number for which you want the cosecant
* @param array|string $complexNumber the complex number for which you want the cosecant
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCSC($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->csc();
@ -196,18 +234,23 @@ class ComplexFunctions
* Excel Function:
* IMCSCH(complexNumber)
*
* @param string $complexNumber the complex number for which you want the hyperbolic cosecant
* @param array|string $complexNumber the complex number for which you want the hyperbolic cosecant
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCSCH($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->csch();
@ -221,18 +264,23 @@ class ComplexFunctions
* Excel Function:
* IMSIN(complexNumber)
*
* @param string $complexNumber the complex number for which you want the sine
* @param array|string $complexNumber the complex number for which you want the sine
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSIN($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->sin();
@ -246,18 +294,23 @@ class ComplexFunctions
* Excel Function:
* IMSINH(complexNumber)
*
* @param string $complexNumber the complex number for which you want the hyperbolic sine
* @param array|string $complexNumber the complex number for which you want the hyperbolic sine
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSINH($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->sinh();
@ -271,18 +324,23 @@ class ComplexFunctions
* Excel Function:
* IMSEC(complexNumber)
*
* @param string $complexNumber the complex number for which you want the secant
* @param array|string $complexNumber the complex number for which you want the secant
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSEC($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->sec();
@ -296,18 +354,23 @@ class ComplexFunctions
* Excel Function:
* IMSECH(complexNumber)
*
* @param string $complexNumber the complex number for which you want the hyperbolic secant
* @param array|string $complexNumber the complex number for which you want the hyperbolic secant
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSECH($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->sech();
@ -321,18 +384,23 @@ class ComplexFunctions
* Excel Function:
* IMTAN(complexNumber)
*
* @param string $complexNumber the complex number for which you want the tangent
* @param array|string $complexNumber the complex number for which you want the tangent
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMTAN($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->tan();
@ -346,22 +414,27 @@ class ComplexFunctions
* Excel Function:
* IMSQRT(complexNumber)
*
* @param string $complexNumber the complex number for which you want the square root
* @param array|string $complexNumber the complex number for which you want the square root
* Or can be an array of values
*
* @return string
* @return array|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSQRT($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
$theta = self::IMARGUMENT($complexNumber);
if ($theta === Functions::DIV0()) {
if ($theta === ExcelError::DIV0()) {
return '0';
}
@ -376,22 +449,27 @@ class ComplexFunctions
* Excel Function:
* IMLN(complexNumber)
*
* @param string $complexNumber the complex number for which you want the natural logarithm
* @param array|string $complexNumber the complex number for which you want the natural logarithm
* Or can be an array of values
*
* @return string
* @return array|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMLN($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->ln();
@ -405,22 +483,27 @@ class ComplexFunctions
* Excel Function:
* IMLOG10(complexNumber)
*
* @param string $complexNumber the complex number for which you want the common logarithm
* @param array|string $complexNumber the complex number for which you want the common logarithm
* Or can be an array of values
*
* @return string
* @return array|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMLOG10($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->log10();
@ -434,22 +517,27 @@ class ComplexFunctions
* Excel Function:
* IMLOG2(complexNumber)
*
* @param string $complexNumber the complex number for which you want the base-2 logarithm
* @param array|string $complexNumber the complex number for which you want the base-2 logarithm
* Or can be an array of values
*
* @return string
* @return array|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMLOG2($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->log2();
@ -463,18 +551,23 @@ class ComplexFunctions
* Excel Function:
* IMEXP(complexNumber)
*
* @param string $complexNumber the complex number for which you want the exponential
* @param array|string $complexNumber the complex number for which you want the exponential
* Or can be an array of values
*
* @return string
* @return array|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMEXP($complexNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
if (is_array($complexNumber)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $complex->exp();
@ -488,26 +581,31 @@ class ComplexFunctions
* 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
* @param array|string $complexNumber the complex number you want to raise to a power
* Or can be an array of values
* @param array|float|int|string $realNumber the power to which you want to raise the complex number
* Or can be an array of values
*
* @return string
* @return array|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMPOWER($complexNumber, $realNumber)
{
$complexNumber = Functions::flattenSingleValue($complexNumber);
$realNumber = Functions::flattenSingleValue($realNumber);
if (is_array($complexNumber) || is_array($realNumber)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber, $realNumber);
}
try {
$complex = new ComplexObject($complexNumber);
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
if (!is_numeric($realNumber)) {
return Functions::VALUE();
return ExcelError::VALUE();
}
return (string) $complex->pow($realNumber);
return (string) $complex->pow((float) $realNumber);
}
}

View File

@ -4,10 +4,14 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use Complex\Complex as ComplexObject;
use Complex\Exception as ComplexException;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ComplexOperations
{
use ArrayEnabled;
/**
* IMDIV.
*
@ -16,20 +20,25 @@ class ComplexOperations
* Excel Function:
* IMDIV(complexDividend,complexDivisor)
*
* @param string $complexDividend the complex numerator or dividend
* @param string $complexDivisor the complex denominator or divisor
* @param array|string $complexDividend the complex numerator or dividend
* Or can be an array of values
* @param array|string $complexDivisor the complex denominator or divisor
* Or can be an array of values
*
* @return string
* @return array|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMDIV($complexDividend, $complexDivisor)
{
$complexDividend = Functions::flattenSingleValue($complexDividend);
$complexDivisor = Functions::flattenSingleValue($complexDivisor);
if (is_array($complexDividend) || is_array($complexDivisor)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexDividend, $complexDivisor);
}
try {
return (string) (new ComplexObject($complexDividend))->divideby(new ComplexObject($complexDivisor));
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
}
@ -41,20 +50,25 @@ class ComplexOperations
* 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
* @param array|string $complexNumber1 the complex number from which to subtract complexNumber2
* Or can be an array of values
* @param array|string $complexNumber2 the complex number to subtract from complexNumber1
* Or can be an array of values
*
* @return string
* @return array|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSUB($complexNumber1, $complexNumber2)
{
$complexNumber1 = Functions::flattenSingleValue($complexNumber1);
$complexNumber2 = Functions::flattenSingleValue($complexNumber2);
if (is_array($complexNumber1) || is_array($complexNumber2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber1, $complexNumber2);
}
try {
return (string) (new ComplexObject($complexNumber1))->subtract(new ComplexObject($complexNumber2));
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
}
@ -82,7 +96,7 @@ class ComplexOperations
$returnValue = $returnValue->add(new ComplexObject($complex));
}
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $returnValue;
@ -112,7 +126,7 @@ class ComplexOperations
$returnValue = $returnValue->multiply(new ComplexObject($complex));
}
} catch (ComplexException $e) {
return Functions::NAN();
return ExcelError::NAN();
}
return (string) $returnValue;

View File

@ -2,16 +2,20 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertBase
abstract class ConvertBase
{
use ArrayEnabled;
protected static function validateValue($value): string
{
if (is_bool($value)) {
if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
$value = (int) $value;
}
@ -33,13 +37,13 @@ class ConvertBase
if (is_numeric($places)) {
if ($places < 0 || $places > 10) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return (int) $places;
}
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
/**
@ -57,7 +61,7 @@ class ConvertBase
return substr(str_pad($value, $places, '0', STR_PAD_LEFT), -10);
}
return Functions::NAN();
return ExcelError::NAN();
}
return substr($value, -10);

View File

@ -3,7 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertBinary extends ConvertBase
{
@ -15,17 +15,26 @@ class ConvertBinary extends ConvertBase
* Excel Function:
* BIN2DEC(x)
*
* @param string $value The binary number (as a string) that you want to convert. The number
* @param array|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), BIN2DEC returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toDecimal($value): string
public static function toDecimal($value)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateValue($value);
$value = self::validateBinary($value);
} catch (Exception $e) {
return $e->getMessage();
@ -49,25 +58,35 @@ class ConvertBinary extends ConvertBase
* Excel Function:
* BIN2HEX(x[,places])
*
* @param string $value The binary number (as a string) that you want to convert. The number
* @param array|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
* Or can be an array of values
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toHex($value, $places = null): string
public static function toHex($value, $places = null)
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateValue($value);
$value = self::validateBinary($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
@ -92,25 +111,35 @@ class ConvertBinary extends ConvertBase
* Excel Function:
* BIN2OCT(x[,places])
*
* @param string $value The binary number (as a string) that you want to convert. The number
* @param array|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
* Or can be an array of values
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toOctal($value, $places = null): string
public static function toOctal($value, $places = null)
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateValue($value);
$value = self::validateBinary($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
@ -126,7 +155,7 @@ class ConvertBinary extends ConvertBase
protected static function validateBinary(string $value): string
{
if ((strlen($value) > preg_match_all('/[01]/', $value)) || (strlen($value) > 10)) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $value;

View File

@ -3,7 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertDecimal extends ConvertBase
{
@ -22,7 +22,7 @@ class ConvertDecimal extends ConvertBase
* Excel Function:
* DEC2BIN(x[,places])
*
* @param string $value The decimal integer you want to convert. If number is negative,
* @param array|string $value 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
@ -32,26 +32,36 @@ class ConvertDecimal extends ConvertBase
* 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
* Or can be an array of values
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toBinary($value, $places = null): string
public static function toBinary($value, $places = null)
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateValue($value);
$value = self::validateDecimal($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
$places = self::validatePlaces($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();
return ExcelError::NAN();
}
$r = decbin($value);
@ -69,7 +79,7 @@ class ConvertDecimal extends ConvertBase
* Excel Function:
* DEC2HEX(x[,places])
*
* @param string $value The decimal integer you want to convert. If number is negative,
* @param array|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
@ -79,26 +89,36 @@ class ConvertDecimal extends ConvertBase
* 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
* Or can be an array of values
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toHex($value, $places = null): string
public static function toHex($value, $places = null)
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateValue($value);
$value = self::validateDecimal($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
$places = self::validatePlaces($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();
return ExcelError::NAN();
}
$r = strtoupper(dechex((int) $value));
$r = self::hex32bit($value, $r);
@ -135,7 +155,7 @@ class ConvertDecimal extends ConvertBase
* Excel Function:
* DEC2OCT(x[,places])
*
* @param string $value The decimal integer you want to convert. If number is negative,
* @param array|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
@ -145,26 +165,36 @@ class ConvertDecimal extends ConvertBase
* 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
* Or can be an array of values
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toOctal($value, $places = null): string
public static function toOctal($value, $places = null)
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateValue($value);
$value = self::validateDecimal($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
$places = self::validatePlaces($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();
return ExcelError::NAN();
}
$r = decoct($value);
$r = substr($r, -10);
@ -175,7 +205,7 @@ class ConvertDecimal extends ConvertBase
protected static function validateDecimal(string $value): string
{
if (strlen($value) > preg_match_all('/[-0123456789.]/', $value)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
return $value;

View File

@ -3,7 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertHex extends ConvertBase
{
@ -15,7 +15,7 @@ class ConvertHex extends ConvertBase
* Excel Function:
* HEX2BIN(x[,places])
*
* @param string $value The hexadecimal number you want to convert.
* @param array|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 (40th bit from the right).
* The remaining 9 bits are magnitude bits.
@ -25,19 +25,29 @@ class ConvertHex extends ConvertBase
* 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,
* Or can be an array of values
* @param array|int $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.
* If places is nonnumeric, HEX2BIN returns the #VALUE! error value.
* If places is negative, HEX2BIN returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toBinary($value, $places = null): string
public static function toBinary($value, $places = null)
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateValue($value);
$value = self::validateHex($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
@ -55,25 +65,34 @@ class ConvertHex extends ConvertBase
* Excel Function:
* HEX2DEC(x)
*
* @param string $value The hexadecimal number you want to convert. This number cannot
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toDecimal($value): string
public static function toDecimal($value)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateValue($value);
$value = self::validateHex($value);
} catch (Exception $e) {
return $e->getMessage();
}
if (strlen($value) > 10) {
return Functions::NAN();
return ExcelError::NAN();
}
$binX = '';
@ -99,7 +118,7 @@ class ConvertHex extends ConvertBase
* Excel Function:
* HEX2OCT(x[,places])
*
* @param string $value The hexadecimal number you want to convert. Number cannot
* @param array|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
@ -112,20 +131,30 @@ class ConvertHex extends ConvertBase
* 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
* Or can be an array of values
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toOctal($value, $places = null): string
public static function toOctal($value, $places = null)
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateValue($value);
$value = self::validateHex($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
@ -138,7 +167,7 @@ class ConvertHex extends ConvertBase
protected static function validateHex(string $value): string
{
if (strlen($value) > preg_match_all('/[0123456789ABCDEF]/', $value)) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $value;

View File

@ -3,7 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertOctal extends ConvertBase
{
@ -15,7 +15,7 @@ class ConvertOctal extends ConvertBase
* Excel Function:
* OCT2BIN(x[,places])
*
* @param string $value The octal number you want to convert. Number may not
* @param array|string $value 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
@ -28,7 +28,8 @@ class ConvertOctal extends ConvertBase
* 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,
* Or can be an array of values
* @param array|int $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).
@ -37,13 +38,22 @@ class ConvertOctal extends ConvertBase
* error value.
* If places is negative, OCT2BIN returns the #NUM! error
* value.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toBinary($value, $places = null): string
public static function toBinary($value, $places = null)
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateValue($value);
$value = self::validateOctal($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
$places = self::validatePlaces($places);
} catch (Exception $e) {
return $e->getMessage();
}
@ -59,18 +69,27 @@ class ConvertOctal extends ConvertBase
* Excel Function:
* OCT2DEC(x)
*
* @param string $value The octal number you want to convert. Number may not contain
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toDecimal($value): string
public static function toDecimal($value)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateValue($value);
$value = self::validateOctal($value);
} catch (Exception $e) {
return $e->getMessage();
@ -99,7 +118,7 @@ class ConvertOctal extends ConvertBase
* Excel Function:
* OCT2HEX(x[,places])
*
* @param string $value The octal number you want to convert. Number may not contain
* @param array|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
@ -110,25 +129,35 @@ class ConvertOctal extends ConvertBase
* #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
* Or can be an array of values
* @param array|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.
* Or can be an array of values
*
* @return array|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function toHex($value, $places = null): string
public static function toHex($value, $places = null)
{
if (is_array($value) || is_array($places)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
}
try {
$value = self::validateValue(Functions::flattenSingleValue($value));
$value = self::validateValue($value);
$value = self::validateOctal($value);
$places = self::validatePlaces(Functions::flattenSingleValue($places));
$places = self::validatePlaces($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;
$hexVal = (PHP_INT_SIZE === 4 && strlen($value) === 10 && $value[0] >= '4') ? "FF{$hexVal}" : $hexVal;
return self::nbrConversionFormat($hexVal, $places);
}
@ -137,7 +166,7 @@ class ConvertOctal extends ConvertBase
{
$numDigits = (int) preg_match_all('/[01234567]/', $value);
if (strlen($value) > $numDigits || $numDigits > 10) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $value;

View File

@ -2,11 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ConvertUOM
{
use ArrayEnabled;
public const CATEGORY_WEIGHT_AND_MASS = 'Weight and Mass';
public const CATEGORY_DISTANCE = 'Distance';
public const CATEGORY_TIME = 'Time';
@ -103,6 +106,7 @@ class ConvertUOM
'W' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true],
'w' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true],
'PS' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Pferdestärke', 'AllowPrefix' => false],
// Magnetism
'T' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Tesla', 'AllowPrefix' => true],
'ga' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Gauss', 'AllowPrefix' => true],
// Temperature
@ -518,33 +522,39 @@ class ConvertUOM
* 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
* @param array|float|int|string $value the value in fromUOM to convert
* Or can be an array of values
* @param array|string $fromUOM the units for value
* Or can be an array of values
* @param array|string $toUOM the units for the result
* Or can be an array of values
*
* @return float|string
* @return array|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function CONVERT($value, $fromUOM, $toUOM)
{
$value = Functions::flattenSingleValue($value);
$fromUOM = Functions::flattenSingleValue($fromUOM);
$toUOM = Functions::flattenSingleValue($toUOM);
if (is_array($value) || is_array($fromUOM) || is_array($toUOM)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $fromUOM, $toUOM);
}
if (!is_numeric($value)) {
return Functions::VALUE();
return ExcelError::VALUE();
}
try {
[$fromUOM, $fromCategory, $fromMultiplier] = self::getUOMDetails($fromUOM);
[$toUOM, $toCategory, $toMultiplier] = self::getUOMDetails($toUOM);
} catch (Exception $e) {
return Functions::NA();
return ExcelError::NA();
}
if ($fromCategory !== $toCategory) {
return Functions::NA();
return ExcelError::NA();
}
// @var float $value
$value *= $fromMultiplier;
if (($fromUOM === $toUOM) && ($fromMultiplier === $toMultiplier)) {

View File

@ -3,7 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class EngineeringValidations
{
@ -13,7 +13,7 @@ class EngineeringValidations
public static function validateFloat($value): float
{
if (!is_numeric($value)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
return (float) $value;
@ -25,7 +25,7 @@ class EngineeringValidations
public static function validateInt($value): int
{
if (!is_numeric($value)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
return (int) floor((float) $value);

View File

@ -2,10 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Erf
{
use ArrayEnabled;
private static $twoSqrtPi = 1.128379167095512574;
/**
@ -22,15 +26,20 @@ class Erf
* ERF(lower[,upper])
*
* @param mixed $lower Lower bound float for integrating ERF
* Or can be an array of values
* @param mixed $upper Upper bound float for integrating ERF.
* If omitted, ERF integrates between zero and lower_limit
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ERF($lower, $upper = null)
{
$lower = Functions::flattenSingleValue($lower);
$upper = Functions::flattenSingleValue($upper);
if (is_array($lower) || is_array($upper)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $lower, $upper);
}
if (is_numeric($lower)) {
if ($upper === null) {
@ -41,7 +50,7 @@ class Erf
}
}
return Functions::VALUE();
return ExcelError::VALUE();
}
/**
@ -53,12 +62,17 @@ class Erf
* ERF.PRECISE(limit)
*
* @param mixed $limit Float bound for integrating ERF, other bound is zero
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ERFPRECISE($limit)
{
$limit = Functions::flattenSingleValue($limit);
if (is_array($limit)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $limit);
}
return self::ERF($limit);
}

View File

@ -2,10 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class ErfC
{
use ArrayEnabled;
/**
* ERFC.
*
@ -20,18 +24,23 @@ class ErfC
* ERFC(x)
*
* @param mixed $value The float lower bound for integrating ERFC
* Or can be an array of values
*
* @return float|string
* @return array|float|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ERFC($value)
{
$value = Functions::flattenSingleValue($value);
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
if (is_numeric($value)) {
return self::erfcValue($value);
}
return Functions::VALUE();
return ExcelError::VALUE();
}
//
@ -45,7 +54,7 @@ class ErfC
return 1 - Erf::erfValue($value);
}
if ($value < 0) {
return 2 - self::ERFC(-$value);
return 2 - self::erfcValue(-$value);
}
$a = $n = 1;
$b = $c = $value;

View File

@ -9,7 +9,9 @@ class ExceptionHandler
*/
public function __construct()
{
set_error_handler([Exception::class, 'errorHandlerCallback'], E_ALL);
/** @var callable */
$callable = [Exception::class, 'errorHandlerCallback'];
set_error_handler($callable, E_ALL);
}
/**

View File

@ -587,10 +587,10 @@ class Financial
* @see Financial\Dollar::decimal()
* Use the decimal() method in the Financial\Dollar class instead
*
* @param float $fractional_dollar Fractional Dollar
* @param int $fraction Fraction
* @param array|float $fractional_dollar Fractional Dollar
* @param array|int $fraction Fraction
*
* @return float|string
* @return array|float|string
*/
public static function DOLLARDE($fractional_dollar = null, $fraction = 0)
{
@ -612,10 +612,10 @@ class Financial
* @see Financial\Dollar::fractional()
* Use the fractional() method in the Financial\Dollar class instead
*
* @param float $decimal_dollar Decimal Dollar
* @param int $fraction Fraction
* @param array|float $decimal_dollar Decimal Dollar
* @param array|int $fraction Fraction
*
* @return float|string
* @return array|float|string
*/
public static function DOLLARFR($decimal_dollar = null, $fraction = 0)
{

View File

@ -70,10 +70,12 @@ class Amortization
return $e->getMessage();
}
$yearFrac = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis);
if (is_string($yearFrac)) {
return $yearFrac;
$yearFracx = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis);
if (is_string($yearFracx)) {
return $yearFracx;
}
/** @var float */
$yearFrac = $yearFracx;
$amortiseCoeff = self::getAmortizationCoefficient($rate);
@ -161,14 +163,16 @@ class Amortization
$fCostDelta = $cost - $salvage;
// Note, quirky variation for leap years on the YEARFRAC for this function
$purchasedYear = DateTimeExcel\DateParts::year($purchased);
$yearFrac = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis);
if (is_string($yearFrac)) {
return $yearFrac;
$yearFracx = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis);
if (is_string($yearFracx)) {
return $yearFracx;
}
/** @var float */
$yearFrac = $yearFracx;
if (
($basis == FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) &&
($yearFrac < 1) && (DateTimeExcel\Helpers::isLeapYear($purchasedYear))
($yearFrac < 1) && (Functions::scalar(DateTimeExcel\Helpers::isLeapYear($purchasedYear)))
) {
$yearFrac *= 365 / 366;
}

View File

@ -5,7 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\FinancialValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class CashFlowValidations extends FinancialValidations
{
@ -29,7 +29,7 @@ class CashFlowValidations extends FinancialValidations
$type !== FinancialConstants::PAYMENT_END_OF_PERIOD &&
$type !== FinancialConstants::PAYMENT_BEGINNING_OF_PERIOD
) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $rate;

View File

@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Periodic
{
@ -94,7 +95,7 @@ class Periodic
// Validate parameters
if ($numberOfPeriods < 0) {
return Functions::NAN();
return ExcelError::NAN();
}
return self::calculatePresentValue($rate, $numberOfPeriods, $payment, $futureValue, $type);
@ -138,7 +139,7 @@ class Periodic
// Validate parameters
if ($payment == 0.0) {
return Functions::NAN();
return ExcelError::NAN();
}
return self::calculatePeriods($rate, $payment, $presentValue, $futureValue, $type);
@ -187,7 +188,7 @@ class Periodic
) {
if ($rate != 0.0) {
if ($presentValue == 0.0) {
return Functions::NAN();
return ExcelError::NAN();
}
return log(($payment * (1 + $rate * $type) / $rate - $futureValue) /

View File

@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Cumulative
{
@ -57,7 +58,7 @@ class Cumulative
// Validate parameters
if ($start < 1 || $start > $end) {
return Functions::NAN();
return ExcelError::NAN();
}
// Calculate
@ -122,7 +123,7 @@ class Cumulative
// Validate parameters
if ($start < 1 || $start > $end) {
return Functions::VALUE();
return ExcelError::VALUE();
}
// Calculate

View File

@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Interest
{
@ -59,7 +60,7 @@ class Interest
// Validate parameters
if ($period <= 0 || $period > $numberOfPeriods) {
return Functions::NAN();
return ExcelError::NAN();
}
// Calculate
@ -106,7 +107,7 @@ class Interest
// Validate parameters
if ($period <= 0 || $period > $numberOfPeriods) {
return Functions::NAN();
return ExcelError::NAN();
}
// Return value
@ -193,13 +194,13 @@ class Interest
$rate = $rate1;
}
return $close ? $rate : Functions::NAN();
return $close ? $rate : ExcelError::NAN();
}
private static function rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type)
{
if ($rate == 0.0) {
return Functions::NAN();
return ExcelError::NAN();
}
$tt1 = ($rate + 1) ** $numberOfPeriods;
$tt2 = ($rate + 1) ** ($numberOfPeriods - 1);
@ -208,7 +209,7 @@ class Interest
* ($rate * $type + 1) / ($rate * $rate) + $numberOfPeriods
* $payment * $tt2 * ($rate * $type + 1) / $rate + $payment * ($tt1 - 1) * $type / $rate;
if ($denominator == 0) {
return Functions::NAN();
return ExcelError::NAN();
}
return $numerator / $denominator;

View File

@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Payments
{
@ -97,7 +98,7 @@ class Payments
// Validate parameters
if ($period <= 0 || $period > $numberOfPeriods) {
return Functions::NAN();
return ExcelError::NAN();
}
// Calculate

View File

@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Single
{
@ -67,7 +68,7 @@ class Single
// Validate parameters
if ($rate <= 0.0 || $presentValue <= 0.0 || $futureValue <= 0.0) {
return Functions::NAN();
return ExcelError::NAN();
}
return (log($futureValue) - log($presentValue)) / log(1 + $rate);
@ -100,7 +101,7 @@ class Single
// Validate parameters
if ($periods <= 0.0 || $presentValue <= 0.0 || $futureValue < 0.0) {
return Functions::NAN();
return ExcelError::NAN();
}
return ($futureValue / $presentValue) ** (1 / $periods) - 1;

View File

@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class NonPeriodic
{
@ -12,6 +13,8 @@ class NonPeriodic
const FINANCIAL_PRECISION = 1.0e-08;
const DEFAULT_GUESS = 0.1;
/**
* XIRR.
*
@ -25,11 +28,11 @@ class NonPeriodic
* @param mixed[] $dates A series of payment dates
* 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 $guess An optional guess at the expected answer
* @param mixed $guess An optional guess at the expected answer
*
* @return float|string
*/
public static function rate($values, $dates, $guess = 0.1)
public static function rate($values, $dates, $guess = self::DEFAULT_GUESS)
{
$rslt = self::xirrPart1($values, $dates);
if ($rslt !== '') {
@ -37,9 +40,13 @@ class NonPeriodic
}
// create an initial range, with a root somewhere between 0 and guess
$guess = Functions::flattenSingleValue($guess);
$guess = Functions::flattenSingleValue($guess) ?? self::DEFAULT_GUESS;
if (!is_numeric($guess)) {
return ExcelError::VALUE();
}
$guess = ($guess + 0.0) ?: self::DEFAULT_GUESS;
$x1 = 0.0;
$x2 = $guess ?: 0.1;
$x2 = $guess + 0.0;
$f1 = self::xnpvOrdered($x1, $values, $dates, false);
$f2 = self::xnpvOrdered($x2, $values, $dates, false);
$found = false;
@ -54,13 +61,15 @@ class NonPeriodic
break;
} elseif (abs($f1) < abs($f2)) {
$f1 = self::xnpvOrdered($x1 += 1.6 * ($x1 - $x2), $values, $dates, false);
$x1 += 1.6 * ($x1 - $x2);
$f1 = self::xnpvOrdered($x1, $values, $dates, false);
} else {
$f2 = self::xnpvOrdered($x2 += 1.6 * ($x2 - $x1), $values, $dates, false);
$x2 += 1.6 * ($x2 - $x1);
$f2 = self::xnpvOrdered($x2, $values, $dates, false);
}
}
if (!$found) {
return Functions::NAN();
return ExcelError::NAN();
}
return self::xirrPart3($values, $dates, $x1, $x2);
@ -104,13 +113,15 @@ class NonPeriodic
*/
private static function xirrPart1(&$values, &$dates): string
{
if (!is_array($values) && !is_array($dates)) {
return Functions::NA();
}
$values = Functions::flattenArray($values);
$dates = Functions::flattenArray($dates);
$valuesIsArray = count($values) > 1;
$datesIsArray = count($dates) > 1;
if (!$valuesIsArray && !$datesIsArray) {
return ExcelError::NA();
}
if (count($values) != count($dates)) {
return Functions::NAN();
return ExcelError::NAN();
}
$datesCount = count($dates);
@ -133,7 +144,7 @@ class NonPeriodic
for ($i = 0; $i < $valCount; ++$i) {
$fld = $values[$i];
if (!is_numeric($fld)) {
return Functions::VALUE();
return ExcelError::VALUE();
} elseif ($fld > 0) {
$foundpos = true;
} elseif ($fld < 0) {
@ -141,7 +152,7 @@ class NonPeriodic
}
}
if (!self::bothNegAndPos($foundneg, $foundpos)) {
return Functions::NAN();
return ExcelError::NAN();
}
return '';
@ -161,7 +172,7 @@ class NonPeriodic
$dx = $x1 - $x2;
}
$rslt = Functions::VALUE();
$rslt = ExcelError::VALUE();
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
$dx *= 0.5;
$x_mid = $rtb + $dx;
@ -203,7 +214,7 @@ class NonPeriodic
$xnpv = 0.0;
for ($i = 0; $i < $valCount; ++$i) {
if (!is_numeric($values[$i])) {
return Functions::VALUE();
return ExcelError::VALUE();
}
try {
@ -212,17 +223,21 @@ class NonPeriodic
return $e->getMessage();
}
if ($date0 > $datei) {
$dif = $ordered ? Functions::NAN() : -((int) DateTimeExcel\Difference::interval($datei, $date0, 'd'));
$dif = $ordered ? ExcelError::NAN() : -((int) DateTimeExcel\Difference::interval($datei, $date0, 'd'));
} else {
$dif = DateTimeExcel\Difference::interval($date0, $datei, 'd');
}
if (!is_numeric($dif)) {
return $dif;
}
$xnpv += $values[$i] / (1 + $rate) ** ($dif / 365);
if ($rate <= -1.0) {
$xnpv += -abs($values[$i]) / (-1 - $rate) ** ($dif / 365);
} else {
$xnpv += $values[$i] / (1 + $rate) ** ($dif / 365);
}
}
return is_finite($xnpv) ? $xnpv : Functions::VALUE();
return is_finite($xnpv) ? $xnpv : ExcelError::VALUE();
}
/**
@ -231,14 +246,14 @@ class NonPeriodic
private static function validateXnpv($rate, array $values, array $dates): void
{
if (!is_numeric($rate)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
$valCount = count($values);
if ($valCount != count($dates)) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
}
}

View File

@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Periodic
{
@ -33,7 +34,7 @@ class Periodic
public static function rate($values, $guess = 0.1)
{
if (!is_array($values)) {
return Functions::VALUE();
return ExcelError::VALUE();
}
$values = Functions::flattenArray($values);
$guess = Functions::flattenSingleValue($guess);
@ -54,7 +55,7 @@ class Periodic
}
}
if (($f1 * $f2) > 0.0) {
return Functions::VALUE();
return ExcelError::VALUE();
}
$f = self::presentValue($x1, $values);
@ -78,7 +79,7 @@ class Periodic
}
}
return Functions::VALUE();
return ExcelError::VALUE();
}
/**
@ -101,7 +102,7 @@ class Periodic
public static function modifiedRate($values, $financeRate, $reinvestmentRate)
{
if (!is_array($values)) {
return Functions::VALUE();
return ExcelError::VALUE();
}
$values = Functions::flattenArray($values);
$financeRate = Functions::flattenSingleValue($financeRate);
@ -121,13 +122,13 @@ class Periodic
}
if (($npvNeg === 0.0) || ($npvPos === 0.0) || ($reinvestmentRate <= -1.0)) {
return Functions::VALUE();
return ExcelError::VALUE();
}
$mirr = ((-$npvPos * $rr ** $n)
/ ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0;
return is_finite($mirr) ? $mirr : Functions::VALUE();
return is_finite($mirr) ? $mirr : ExcelError::VALUE();
}
/**

View File

@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\Date;
class Coupons
@ -64,9 +65,9 @@ class Coupons
return $e->getMessage();
}
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis);
$daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
if (is_string($daysPerYear)) {
return Functions::VALUE();
return ExcelError::VALUE();
}
$prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
@ -134,7 +135,7 @@ class Coupons
case FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL:
// Actual/actual
if ($frequency == FinancialConstants::FREQUENCY_ANNUAL) {
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis);
$daysPerYear = (int) Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
return $daysPerYear / $frequency;
}
@ -198,6 +199,7 @@ class Coupons
return $e->getMessage();
}
/** @var int */
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis);
$next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
@ -409,7 +411,7 @@ class Coupons
private static function validateCouponPeriod(float $settlement, float $maturity): void
{
if ($settlement >= $maturity) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
}
}

View File

@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Depreciation
{
@ -117,7 +118,7 @@ class Depreciation
}
if ($period > $life) {
return Functions::NAN();
return ExcelError::NAN();
}
// Loop through each period calculating the depreciation
@ -161,7 +162,7 @@ class Depreciation
}
if ($life === 0.0) {
return Functions::DIV0();
return ExcelError::DIV0();
}
return ($cost - $salvage) / $life;
@ -196,7 +197,7 @@ class Depreciation
}
if ($period > $life) {
return Functions::NAN();
return ExcelError::NAN();
}
$syd = (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
@ -208,7 +209,7 @@ class Depreciation
{
$cost = FinancialValidations::validateFloat($cost);
if ($cost < 0.0 && $negativeValueAllowed === false) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $cost;
@ -218,7 +219,7 @@ class Depreciation
{
$salvage = FinancialValidations::validateFloat($salvage);
if ($salvage < 0.0 && $negativeValueAllowed === false) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $salvage;
@ -228,7 +229,7 @@ class Depreciation
{
$life = FinancialValidations::validateFloat($life);
if ($life < 0.0 && $negativeValueAllowed === false) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $life;
@ -238,7 +239,7 @@ class Depreciation
{
$period = FinancialValidations::validateFloat($period);
if ($period <= 0.0 && $negativeValueAllowed === false) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $period;
@ -248,7 +249,7 @@ class Depreciation
{
$month = FinancialValidations::validateInt($month);
if ($month < 1) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $month;
@ -258,7 +259,7 @@ class Depreciation
{
$factor = FinancialValidations::validateFloat($factor);
if ($factor <= 0.0) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $factor;

View File

@ -2,23 +2,34 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\TextData\Format;
class Dollar
{
use ArrayEnabled;
/**
* DOLLAR.
*
* 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)..
*
* @param mixed $number The value to format
* @param mixed $number The value to format, or can be an array of numbers
* Or can be an array of values
* @param mixed $precision The number of digits to display to the right of the decimal point (as an integer).
* If precision is negative, number is rounded to the left of the decimal point.
* If you omit precision, it is assumed to be 2
* Or can be an array of precision values
*
* @return array|string
* If an array of values is passed for either of the arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function format($number, $precision = 2): string
public static function format($number, $precision = 2)
{
return Format::DOLLAR($number, $precision);
}
@ -34,25 +45,37 @@ class Dollar
* DOLLARDE(fractional_dollar,fraction)
*
* @param mixed $fractionalDollar Fractional Dollar
* Or can be an array of values
* @param mixed $fraction Fraction
* Or can be an array of values
*
* @return float|string
* @return array|float|string
*/
public static function decimal($fractionalDollar = null, $fraction = 0)
{
$fractionalDollar = Functions::flattenSingleValue($fractionalDollar);
$fraction = (int) Functions::flattenSingleValue($fraction);
if (is_array($fractionalDollar) || is_array($fraction)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $fractionalDollar, $fraction);
}
// Validate parameters
if ($fractionalDollar === null || $fraction < 0) {
return Functions::NAN();
try {
$fractionalDollar = FinancialValidations::validateFloat(
Functions::flattenSingleValue($fractionalDollar) ?? 0.0
);
$fraction = FinancialValidations::validateInt(Functions::flattenSingleValue($fraction));
} catch (Exception $e) {
return $e->getMessage();
}
// Additional parameter validations
if ($fraction < 0) {
return ExcelError::NAN();
}
if ($fraction == 0) {
return Functions::DIV0();
return ExcelError::DIV0();
}
$dollars = floor($fractionalDollar);
$cents = fmod($fractionalDollar, 1);
$dollars = ($fractionalDollar < 0) ? ceil($fractionalDollar) : floor($fractionalDollar);
$cents = fmod($fractionalDollar, 1.0);
$cents /= $fraction;
$cents *= 10 ** ceil(log10($fraction));
@ -70,24 +93,36 @@ class Dollar
* DOLLARFR(decimal_dollar,fraction)
*
* @param mixed $decimalDollar Decimal Dollar
* Or can be an array of values
* @param mixed $fraction Fraction
* Or can be an array of values
*
* @return float|string
* @return array|float|string
*/
public static function fractional($decimalDollar = null, $fraction = 0)
{
$decimalDollar = Functions::flattenSingleValue($decimalDollar);
$fraction = (int) Functions::flattenSingleValue($fraction);
if (is_array($decimalDollar) || is_array($fraction)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $decimalDollar, $fraction);
}
// Validate parameters
if ($decimalDollar === null || $fraction < 0) {
return Functions::NAN();
try {
$decimalDollar = FinancialValidations::validateFloat(
Functions::flattenSingleValue($decimalDollar) ?? 0.0
);
$fraction = FinancialValidations::validateInt(Functions::flattenSingleValue($fraction));
} catch (Exception $e) {
return $e->getMessage();
}
// Additional parameter validations
if ($fraction < 0) {
return ExcelError::NAN();
}
if ($fraction == 0) {
return Functions::DIV0();
return ExcelError::DIV0();
}
$dollars = floor($decimalDollar);
$dollars = ($decimalDollar < 0.0) ? ceil($decimalDollar) : floor($decimalDollar);
$cents = fmod($decimalDollar, 1);
$cents *= $fraction;
$cents *= 10 ** (-ceil(log10($fraction)));

View File

@ -5,7 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class FinancialValidations
{
@ -39,7 +39,7 @@ class FinancialValidations
public static function validateFloat($value): float
{
if (!is_numeric($value)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
return (float) $value;
@ -51,7 +51,7 @@ class FinancialValidations
public static function validateInt($value): int
{
if (!is_numeric($value)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
return (int) floor((float) $value);
@ -64,7 +64,7 @@ class FinancialValidations
{
$rate = self::validateFloat($rate);
if ($rate < 0.0) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $rate;
@ -81,7 +81,7 @@ class FinancialValidations
($frequency !== FinancialConstants::FREQUENCY_SEMI_ANNUAL) &&
($frequency !== FinancialConstants::FREQUENCY_QUARTERLY)
) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $frequency;
@ -93,12 +93,12 @@ class FinancialValidations
public static function validateBasis($basis): int
{
if (!is_numeric($basis)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
$basis = (int) $basis;
if (($basis < 0) || ($basis > 4)) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $basis;
@ -111,7 +111,7 @@ class FinancialValidations
{
$price = self::validateFloat($price);
if ($price < 0.0) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $price;
@ -124,7 +124,7 @@ class FinancialValidations
{
$parValue = self::validateFloat($parValue);
if ($parValue < 0.0) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $parValue;
@ -137,7 +137,7 @@ class FinancialValidations
{
$yield = self::validateFloat($yield);
if ($yield < 0.0) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $yield;
@ -150,7 +150,7 @@ class FinancialValidations
{
$discount = self::validateFloat($discount);
if ($discount <= 0.0) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $discount;

View File

@ -5,7 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use DateTimeInterface;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Helpers
{
@ -27,7 +27,7 @@ class Helpers
public static function daysPerYear($year, $basis = 0)
{
if (!is_numeric($basis)) {
return Functions::NAN();
return ExcelError::NAN();
}
switch ($basis) {
@ -41,7 +41,7 @@ class Helpers
return (DateTimeExcel\Helpers::isLeapYear($year)) ? 366 : 365;
}
return Functions::NAN();
return ExcelError::NAN();
}
/**

View File

@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class InterestRate
{
@ -34,7 +35,7 @@ class InterestRate
}
if ($nominalRate <= 0 || $periodsPerYear < 1) {
return Functions::NAN();
return ExcelError::NAN();
}
return ((1 + $nominalRate / $periodsPerYear) ** $periodsPerYear) - 1;
@ -63,7 +64,7 @@ class InterestRate
}
if ($effectiveRate <= 0 || $periodsPerYear < 1) {
return Functions::NAN();
return ExcelError::NAN();
}
// Calculate

View File

@ -78,12 +78,12 @@ class AccruedInterest
return $e->getMessage();
}
$daysBetweenIssueAndSettlement = YearFrac::fraction($issue, $settlement, $basis);
$daysBetweenIssueAndSettlement = Functions::scalar(YearFrac::fraction($issue, $settlement, $basis));
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
}
$daysBetweenFirstInterestAndSettlement = YearFrac::fraction($firstInterest, $settlement, $basis);
$daysBetweenFirstInterestAndSettlement = Functions::scalar(YearFrac::fraction($firstInterest, $settlement, $basis));
if (!is_numeric($daysBetweenFirstInterestAndSettlement)) {
// return date error
return $daysBetweenFirstInterestAndSettlement;
@ -140,7 +140,7 @@ class AccruedInterest
return $e->getMessage();
}
$daysBetweenIssueAndSettlement = YearFrac::fraction($issue, $settlement, $basis);
$daysBetweenIssueAndSettlement = Functions::scalar(YearFrac::fraction($issue, $settlement, $basis));
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;

View File

@ -8,6 +8,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstan
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Price
{
@ -134,7 +135,7 @@ class Price
return $e->getMessage();
}
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis);
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
@ -194,23 +195,23 @@ class Price
return $e->getMessage();
}
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis);
$daysPerYear = Functions::scalar(Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis));
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}
$daysBetweenIssueAndSettlement = DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis);
$daysBetweenIssueAndSettlement = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis));
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
}
$daysBetweenIssueAndSettlement *= $daysPerYear;
$daysBetweenIssueAndMaturity = DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis);
$daysBetweenIssueAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis));
if (!is_numeric($daysBetweenIssueAndMaturity)) {
// return date error
return $daysBetweenIssueAndMaturity;
}
$daysBetweenIssueAndMaturity *= $daysPerYear;
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis);
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
@ -270,7 +271,7 @@ class Price
}
if ($investment <= 0) {
return Functions::NAN();
return ExcelError::NAN();
}
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis);
if (!is_numeric($daysBetweenSettlementAndMaturity)) {

View File

@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Rates
{
@ -60,10 +61,10 @@ class Rates
}
if ($price <= 0.0) {
return Functions::NAN();
return ExcelError::NAN();
}
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis);
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
@ -123,10 +124,10 @@ class Rates
}
if ($investment <= 0) {
return Functions::NAN();
return ExcelError::NAN();
}
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis);
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;

View File

@ -4,7 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\FinancialValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class SecurityValidations extends FinancialValidations
{
@ -23,7 +23,7 @@ class SecurityValidations extends FinancialValidations
public static function validateSecurityPeriod($settlement, $maturity): void
{
if ($settlement >= $maturity) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
}
@ -34,7 +34,7 @@ class SecurityValidations extends FinancialValidations
{
$redemption = self::validateFloat($redemption);
if ($redemption <= 0.0) {
throw new Exception(Functions::NAN());
throw new Exception(ExcelError::NAN());
}
return $redemption;

View File

@ -61,7 +61,7 @@ class Yields
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis);
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
@ -126,19 +126,19 @@ class Yields
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}
$daysBetweenIssueAndSettlement = DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis);
$daysBetweenIssueAndSettlement = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis));
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
}
$daysBetweenIssueAndSettlement *= $daysPerYear;
$daysBetweenIssueAndMaturity = DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis);
$daysBetweenIssueAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis));
if (!is_numeric($daysBetweenIssueAndMaturity)) {
// return date error
return $daysBetweenIssueAndMaturity;
}
$daysBetweenIssueAndMaturity *= $daysPerYear;
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis);
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;

View File

@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class TreasuryBill
{
@ -38,17 +39,17 @@ class TreasuryBill
}
if ($discount <= 0) {
return Functions::NAN();
return ExcelError::NAN();
}
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
$daysPerYear = Helpers::daysPerYear(
DateTimeExcel\DateParts::year($maturity),
Functions::scalar(DateTimeExcel\DateParts::year($maturity)),
FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
);
if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) {
return Functions::NAN();
return ExcelError::NAN();
}
return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity);
@ -83,22 +84,22 @@ class TreasuryBill
}
if ($discount <= 0) {
return Functions::NAN();
return ExcelError::NAN();
}
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
$daysPerYear = Helpers::daysPerYear(
DateTimeExcel\DateParts::year($maturity),
Functions::scalar(DateTimeExcel\DateParts::year($maturity)),
FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
);
if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) {
return Functions::NAN();
return ExcelError::NAN();
}
$price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360));
if ($price < 0.0) {
return Functions::NAN();
return ExcelError::NAN();
}
return $price;
@ -134,12 +135,12 @@ class TreasuryBill
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
$daysPerYear = Helpers::daysPerYear(
DateTimeExcel\DateParts::year($maturity),
Functions::scalar(DateTimeExcel\DateParts::year($maturity)),
FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
);
if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) {
return Functions::NAN();
return ExcelError::NAN();
}
return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity);

View File

@ -61,7 +61,7 @@ class FormulaParser
/**
* Create a new FormulaParser.
*
* @param string $formula Formula to parse
* @param ?string $formula Formula to parse
*/
public function __construct($formula = '')
{

View File

@ -4,7 +4,6 @@ namespace PhpOffice\PhpSpreadsheet\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Functions
{
@ -41,30 +40,14 @@ class Functions
*/
protected static $returnDateType = self::RETURNDATE_EXCEL;
/**
* List of error codes.
*
* @var array
*/
protected static $errorCodes = [
'null' => '#NULL!',
'divisionbyzero' => '#DIV/0!',
'value' => '#VALUE!',
'reference' => '#REF!',
'name' => '#NAME?',
'num' => '#NUM!',
'na' => '#N/A',
'gettingdata' => '#GETTING_DATA',
];
/**
* Set the Compatibility Mode.
*
* @param string $compatibilityMode Compatibility Mode
* Permitted values are:
* Functions::COMPATIBILITY_EXCEL 'Excel'
* Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
* Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
* Permitted values are:
* Functions::COMPATIBILITY_EXCEL 'Excel'
* Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
* Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
*
* @return bool (Success or Failure)
*/
@ -87,10 +70,10 @@ class Functions
* Return the current Compatibility Mode.
*
* @return string Compatibility Mode
* Possible Return values are:
* Functions::COMPATIBILITY_EXCEL 'Excel'
* Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
* Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
* Possible Return values are:
* Functions::COMPATIBILITY_EXCEL 'Excel'
* Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
* Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
*/
public static function getCompatibilityMode()
{
@ -98,13 +81,13 @@ class Functions
}
/**
* Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object).
* Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP DateTime Object).
*
* @param string $returnDateType Return Date Format
* Permitted values are:
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P'
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O'
* Functions::RETURNDATE_EXCEL 'E'
* Permitted values are:
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P'
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O'
* Functions::RETURNDATE_EXCEL 'E'
*
* @return bool Success or failure
*/
@ -127,10 +110,10 @@ class Functions
* Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object).
*
* @return string Return Date Format
* Possible Return values are:
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P'
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O'
* Functions::RETURNDATE_EXCEL 'E'
* Possible Return values are:
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P'
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O'
* Functions::RETURNDATE_EXCEL ' 'E'
*/
public static function getReturnDateType()
{
@ -147,92 +130,6 @@ class Functions
return '#Not Yet Implemented';
}
/**
* DIV0.
*
* @return string #Not Yet Implemented
*/
public static function DIV0()
{
return self::$errorCodes['divisionbyzero'];
}
/**
* NA.
*
* Excel Function:
* =NA()
*
* Returns the error value #N/A
* #N/A is the error value that means "no value is available."
*
* @return string #N/A!
*/
public static function NA()
{
return self::$errorCodes['na'];
}
/**
* NaN.
*
* Returns the error value #NUM!
*
* @return string #NUM!
*/
public static function NAN()
{
return self::$errorCodes['num'];
}
/**
* NAME.
*
* Returns the error value #NAME?
*
* @return string #NAME?
*/
public static function NAME()
{
return self::$errorCodes['name'];
}
/**
* REF.
*
* Returns the error value #REF!
*
* @return string #REF!
*/
public static function REF()
{
return self::$errorCodes['reference'];
}
/**
* NULL.
*
* Returns the error value #NULL!
*
* @return string #NULL!
*/
public static function null()
{
return self::$errorCodes['null'];
}
/**
* VALUE.
*
* Returns the error value #VALUE!
*
* @return string #VALUE!
*/
public static function VALUE()
{
return self::$errorCodes['value'];
}
public static function isMatrixValue($idx)
{
return (substr_count($idx, '.') <= 1) || (preg_match('/\.[A-Z]/', $idx) > 0);
@ -240,7 +137,7 @@ class Functions
public static function isValue($idx)
{
return substr_count($idx, '.') == 0;
return substr_count($idx, '.') === 0;
}
public static function isCellValue($idx)
@ -255,11 +152,15 @@ class Functions
if ($condition === '') {
return '=""';
}
if (!is_string($condition) || !in_array($condition[0], ['>', '<', '='])) {
if (!is_string($condition) || !in_array($condition[0], ['>', '<', '='], true)) {
$condition = self::operandSpecialHandling($condition);
if (is_bool($condition)) {
return '=' . ($condition ? 'TRUE' : 'FALSE');
} elseif (!is_numeric($condition)) {
if ($condition !== '""') { // Not an empty string
// Escape any quotes in the string value
$condition = (string) preg_replace('/"/ui', '""', $condition);
}
$condition = Calculation::wrapResult(strtoupper($condition));
}
@ -300,26 +201,142 @@ class Functions
return $operand;
}
/**
* NULL.
*
* Returns the error value #NULL!
*
* @Deprecated 1.23.0
*
* @return string #NULL!
*
*@see Information\ExcelError::null()
* Use the null() method in the Information\Error class instead
*/
public static function null()
{
return Information\ExcelError::null();
}
/**
* NaN.
*
* Returns the error value #NUM!
*
* @Deprecated 1.23.0
*
* @return string #NUM!
*
* @see Information\ExcelError::NAN()
* Use the NAN() method in the Information\Error class instead
*/
public static function NAN()
{
return Information\ExcelError::NAN();
}
/**
* REF.
*
* Returns the error value #REF!
*
* @Deprecated 1.23.0
*
* @return string #REF!
*
* @see Information\ExcelError::REF()
* Use the REF() method in the Information\Error class instead
*/
public static function REF()
{
return Information\ExcelError::REF();
}
/**
* NA.
*
* Excel Function:
* =NA()
*
* Returns the error value #N/A
* #N/A is the error value that means "no value is available."
*
* @Deprecated 1.23.0
*
* @return string #N/A!
*
* @see Information\ExcelError::NA()
* Use the NA() method in the Information\Error class instead
*/
public static function NA()
{
return Information\ExcelError::NA();
}
/**
* VALUE.
*
* Returns the error value #VALUE!
*
* @Deprecated 1.23.0
*
* @return string #VALUE!
*
* @see Information\ExcelError::VALUE()
* Use the VALUE() method in the Information\Error class instead
*/
public static function VALUE()
{
return Information\ExcelError::VALUE();
}
/**
* NAME.
*
* Returns the error value #NAME?
*
* @Deprecated 1.23.0
*
* @return string #NAME?
*
* @see Information\ExcelError::NAME()
* Use the NAME() method in the Information\Error class instead
*/
public static function NAME()
{
return Information\ExcelError::NAME();
}
/**
* DIV0.
*
* @Deprecated 1.23.0
*
* @return string #Not Yet Implemented
*
*@see Information\ExcelError::DIV0()
* Use the DIV0() method in the Information\Error class instead
*/
public static function DIV0()
{
return Information\ExcelError::DIV0();
}
/**
* ERROR_TYPE.
*
* @param mixed $value Value to check
*
* @return int|string
* @Deprecated 1.23.0
*
* @return array|int|string
*
* @see Information\ExcelError::type()
* Use the type() method in the Information\Error class instead
*/
public static function errorType($value = '')
{
$value = self::flattenSingleValue($value);
$i = 1;
foreach (self::$errorCodes as $errorCode) {
if ($value === $errorCode) {
return $i;
}
++$i;
}
return self::NA();
return Information\ExcelError::type($value);
}
/**
@ -327,15 +344,16 @@ class Functions
*
* @param mixed $value Value to check
*
* @return bool
* @Deprecated 1.23.0
*
* @see Information\Value::isBlank()
* Use the isBlank() method in the Information\Value class instead
*
* @return array|bool
*/
public static function isBlank($value = null)
{
if ($value !== null) {
$value = self::flattenSingleValue($value);
}
return $value === null;
return Information\Value::isBlank($value);
}
/**
@ -343,13 +361,16 @@ class Functions
*
* @param mixed $value Value to check
*
* @return bool
* @Deprecated 1.23.0
*
* @see Information\Value::isErr()
* Use the isErr() method in the Information\Value class instead
*
* @return array|bool
*/
public static function isErr($value = '')
{
$value = self::flattenSingleValue($value);
return self::isError($value) && (!self::isNa(($value)));
return Information\ErrorValue::isErr($value);
}
/**
@ -357,17 +378,16 @@ class Functions
*
* @param mixed $value Value to check
*
* @return bool
* @Deprecated 1.23.0
*
* @see Information\Value::isError()
* Use the isError() method in the Information\Value class instead
*
* @return array|bool
*/
public static function isError($value = '')
{
$value = self::flattenSingleValue($value);
if (!is_string($value)) {
return false;
}
return in_array($value, self::$errorCodes);
return Information\ErrorValue::isError($value);
}
/**
@ -375,13 +395,16 @@ class Functions
*
* @param mixed $value Value to check
*
* @return bool
* @Deprecated 1.23.0
*
* @see Information\Value::isNa()
* Use the isNa() method in the Information\Value class instead
*
* @return array|bool
*/
public static function isNa($value = '')
{
$value = self::flattenSingleValue($value);
return $value === self::NA();
return Information\ErrorValue::isNa($value);
}
/**
@ -389,19 +412,16 @@ class Functions
*
* @param mixed $value Value to check
*
* @return bool|string
* @Deprecated 1.23.0
*
* @see Information\Value::isEven()
* Use the isEven() method in the Information\Value class instead
*
* @return array|bool|string
*/
public static function isEven($value = null)
{
$value = self::flattenSingleValue($value);
if ($value === null) {
return self::NAME();
} elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
return self::VALUE();
}
return $value % 2 == 0;
return Information\Value::isEven($value);
}
/**
@ -409,19 +429,16 @@ class Functions
*
* @param mixed $value Value to check
*
* @return bool|string
* @Deprecated 1.23.0
*
* @see Information\Value::isOdd()
* Use the isOdd() method in the Information\Value class instead
*
* @return array|bool|string
*/
public static function isOdd($value = null)
{
$value = self::flattenSingleValue($value);
if ($value === null) {
return self::NAME();
} elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
return self::VALUE();
}
return abs($value) % 2 == 1;
return Information\Value::isOdd($value);
}
/**
@ -429,17 +446,16 @@ class Functions
*
* @param mixed $value Value to check
*
* @return bool
* @Deprecated 1.23.0
*
* @see Information\Value::isNumber()
* Use the isNumber() method in the Information\Value class instead
*
* @return array|bool
*/
public static function isNumber($value = null)
{
$value = self::flattenSingleValue($value);
if (is_string($value)) {
return false;
}
return is_numeric($value);
return Information\Value::isNumber($value);
}
/**
@ -447,13 +463,16 @@ class Functions
*
* @param mixed $value Value to check
*
* @return bool
* @Deprecated 1.23.0
*
* @see Information\Value::isLogical()
* Use the isLogical() method in the Information\Value class instead
*
* @return array|bool
*/
public static function isLogical($value = null)
{
$value = self::flattenSingleValue($value);
return is_bool($value);
return Information\Value::isLogical($value);
}
/**
@ -461,13 +480,16 @@ class Functions
*
* @param mixed $value Value to check
*
* @return bool
* @Deprecated 1.23.0
*
* @see Information\Value::isText()
* Use the isText() method in the Information\Value class instead
*
* @return array|bool
*/
public static function isText($value = null)
{
$value = self::flattenSingleValue($value);
return is_string($value) && !self::isError($value);
return Information\Value::isText($value);
}
/**
@ -475,11 +497,16 @@ class Functions
*
* @param mixed $value Value to check
*
* @return bool
* @Deprecated 1.23.0
*
* @see Information\Value::isNonText()
* Use the isNonText() method in the Information\Value class instead
*
* @return array|bool
*/
public static function isNonText($value = null)
{
return !self::isText($value);
return Information\Value::isNonText($value);
}
/**
@ -487,9 +514,14 @@ class Functions
*
* Returns a value converted to a number
*
* @Deprecated 1.23.0
*
* @see Information\Value::asNumber()
* Use the asNumber() method in the Information\Value class instead
*
* @param null|mixed $value The value you want converted
*
* @return number N converts values listed in the following table
* @return number|string N converts values listed in the following table
* If value is or refers to N returns
* A number That number
* A date The serial number of that date
@ -500,27 +532,7 @@ class Functions
*/
public static function n($value = null)
{
while (is_array($value)) {
$value = array_shift($value);
}
switch (gettype($value)) {
case 'double':
case 'float':
case 'integer':
return $value;
case 'boolean':
return (int) $value;
case 'string':
// Errors
if ((strlen($value) > 0) && ($value[0] == '#')) {
return $value;
}
break;
}
return 0;
return Information\Value::asNumber($value);
}
/**
@ -528,6 +540,11 @@ class Functions
*
* Returns a number that identifies the type of a value
*
* @Deprecated 1.23.0
*
* @see Information\Value::type()
* Use the type() method in the Information\Value class instead
*
* @param null|mixed $value The value you want tested
*
* @return number N converts values listed in the following table
@ -540,39 +557,7 @@ class Functions
*/
public static function TYPE($value = null)
{
$value = self::flattenArrayIndexed($value);
if (is_array($value) && (count($value) > 1)) {
end($value);
$a = key($value);
// Range of cells is an error
if (self::isCellValue($a)) {
return 16;
// Test for Matrix
} elseif (self::isMatrixValue($a)) {
return 64;
}
} elseif (empty($value)) {
// Empty Cell
return 1;
}
$value = self::flattenSingleValue($value);
if (($value === null) || (is_float($value)) || (is_int($value))) {
return 1;
} elseif (is_bool($value)) {
return 4;
} elseif (is_array($value)) {
return 64;
} elseif (is_string($value)) {
// Errors
if ((strlen($value) > 0) && ($value[0] == '#')) {
return 16;
}
return 2;
}
return 0;
return Information\Value::type($value);
}
/**
@ -588,24 +573,38 @@ class Functions
return (array) $array;
}
$arrayValues = [];
foreach ($array as $value) {
$flattened = [];
$stack = array_values($array);
while ($stack) {
$value = array_shift($stack);
if (is_array($value)) {
foreach ($value as $val) {
if (is_array($val)) {
foreach ($val as $v) {
$arrayValues[] = $v;
}
} else {
$arrayValues[] = $val;
}
}
array_unshift($stack, ...array_values($value));
} else {
$arrayValues[] = $value;
$flattened[] = $value;
}
}
return $arrayValues;
return $flattened;
}
/**
* @param mixed $value
*
* @return null|mixed
*/
public static function scalar($value)
{
if (!is_array($value)) {
return $value;
}
do {
$value = array_pop($value);
} while (is_array($value));
return $value;
}
/**
@ -660,29 +659,19 @@ class Functions
/**
* ISFORMULA.
*
* @Deprecated 1.23.0
*
* @see Information\Value::isFormula()
* Use the isFormula() method in the Information\Value class instead
*
* @param mixed $cellReference The cell to check
* @param ?Cell $cell The current cell (containing this formula)
*
* @return bool|string
* @return array|bool|string
*/
public static function isFormula($cellReference = '', ?Cell $cell = null)
{
if ($cell === null) {
return self::REF();
}
$cellReference = self::expandDefinedName((string) $cellReference, $cell);
$cellReference = self::trimTrailingRange($cellReference);
preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellReference, $matches);
$cellReference = $matches[6] . $matches[7];
$worksheetName = str_replace("''", "'", trim($matches[2], "'"));
$worksheet = (!empty($worksheetName))
? $cell->getWorksheet()->getParent()->getSheetByName($worksheetName)
: $cell->getWorksheet();
return $worksheet->getCell($cellReference)->isFormula();
return Information\Value::isFormula($cellReference, $cell);
}
public static function expandDefinedName(string $coordinate, Cell $cell): string
@ -692,12 +681,13 @@ class Functions
// Uppercase coordinate
$pCoordinatex = strtoupper($coordinate);
// Eliminate leading equal sign
$pCoordinatex = Worksheet::pregReplace('/^=/', '', $pCoordinatex);
$pCoordinatex = (string) preg_replace('/^=/', '', $pCoordinatex);
$defined = $spreadsheet->getDefinedName($pCoordinatex, $worksheet);
if ($defined !== null) {
$worksheet2 = $defined->getWorkSheet();
if (!$defined->isFormula() && $worksheet2 !== null) {
$coordinate = "'" . $worksheet2->getTitle() . "'!" . Worksheet::pregReplace('/^=/', '', $defined->getValue());
$coordinate = "'" . $worksheet2->getTitle() . "'!" .
(string) preg_replace('/^=/', '', str_replace('$', '', $defined->getValue()));
}
}
@ -706,6 +696,15 @@ class Functions
public static function trimTrailingRange(string $coordinate): string
{
return Worksheet::pregReplace('/:[\\w\$]+$/', '', $coordinate);
return (string) preg_replace('/:[\\w\$]+$/', '', $coordinate);
}
public static function trimSheetFromCellReference(string $coordinate): string
{
if (strpos($coordinate, '!') !== false) {
$coordinate = substr($coordinate, strrpos($coordinate, '!') + 1);
}
return $coordinate;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Information;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
class ErrorValue
{
use ArrayEnabled;
/**
* IS_ERR.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isErr($value = '')
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
return self::isError($value) && (!self::isNa(($value)));
}
/**
* IS_ERROR.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isError($value = '')
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
if (!is_string($value)) {
return false;
}
return in_array($value, ExcelError::ERROR_CODES, true);
}
/**
* IS_NA.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isNa($value = '')
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
return $value === ExcelError::NA();
}
}

View File

@ -0,0 +1,171 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Information;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
class ExcelError
{
use ArrayEnabled;
/**
* List of error codes.
*
* @var array<string, string>
*/
public const ERROR_CODES = [
'null' => '#NULL!', // 1
'divisionbyzero' => '#DIV/0!', // 2
'value' => '#VALUE!', // 3
'reference' => '#REF!', // 4
'name' => '#NAME?', // 5
'num' => '#NUM!', // 6
'na' => '#N/A', // 7
'gettingdata' => '#GETTING_DATA', // 8
'spill' => '#SPILL!', // 9
'connect' => '#CONNECT!', //10
'blocked' => '#BLOCKED!', //11
'unknown' => '#UNKNOWN!', //12
'field' => '#FIELD!', //13
'calculation' => '#CALC!', //14
];
/**
* List of error codes. Replaced by constant;
* previously it was public and updateable, allowing
* user to make inappropriate alterations.
*
* @deprecated 1.25.0 Use ERROR_CODES constant instead.
*
* @var array<string, string>
*/
public static $errorCodes = self::ERROR_CODES;
/**
* @param mixed $value
*/
public static function throwError($value): string
{
return in_array($value, self::ERROR_CODES, true) ? $value : self::ERROR_CODES['value'];
}
/**
* ERROR_TYPE.
*
* @param mixed $value Value to check
*
* @return array|int|string
*/
public static function type($value = '')
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
$i = 1;
foreach (self::ERROR_CODES as $errorCode) {
if ($value === $errorCode) {
return $i;
}
++$i;
}
return self::NA();
}
/**
* NULL.
*
* Returns the error value #NULL!
*
* @return string #NULL!
*/
public static function null(): string
{
return self::ERROR_CODES['null'];
}
/**
* NaN.
*
* Returns the error value #NUM!
*
* @return string #NUM!
*/
public static function NAN(): string
{
return self::ERROR_CODES['num'];
}
/**
* REF.
*
* Returns the error value #REF!
*
* @return string #REF!
*/
public static function REF(): string
{
return self::ERROR_CODES['reference'];
}
/**
* NA.
*
* Excel Function:
* =NA()
*
* Returns the error value #N/A
* #N/A is the error value that means "no value is available."
*
* @return string #N/A!
*/
public static function NA(): string
{
return self::ERROR_CODES['na'];
}
/**
* VALUE.
*
* Returns the error value #VALUE!
*
* @return string #VALUE!
*/
public static function VALUE(): string
{
return self::ERROR_CODES['value'];
}
/**
* NAME.
*
* Returns the error value #NAME?
*
* @return string #NAME?
*/
public static function NAME(): string
{
return self::ERROR_CODES['name'];
}
/**
* DIV0.
*
* @return string #DIV/0!
*/
public static function DIV0(): string
{
return self::ERROR_CODES['divisionbyzero'];
}
/**
* CALC.
*
* @return string #CALC!
*/
public static function CALC(): string
{
return self::ERROR_CODES['calculation'];
}
}

View File

@ -0,0 +1,328 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Information;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\NamedRange;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Value
{
use ArrayEnabled;
/**
* IS_BLANK.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isBlank($value = null)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
return $value === null;
}
/**
* IS_REF.
*
* @param mixed $value Value to check
*
* @return bool
*/
public static function isRef($value, ?Cell $cell = null)
{
if ($cell === null || $value === $cell->getCoordinate()) {
return false;
}
$cellValue = Functions::trimTrailingRange($value);
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/ui', $cellValue) === 1) {
[$worksheet, $cellValue] = Worksheet::extractSheetTitle($cellValue, true);
if (!empty($worksheet) && $cell->getWorksheet()->getParent()->getSheetByName($worksheet) === null) {
return false;
}
[$column, $row] = Coordinate::indexesFromString($cellValue);
if ($column > 16384 || $row > 1048576) {
return false;
}
return true;
}
$namedRange = $cell->getWorksheet()->getParent()->getNamedRange($value);
return $namedRange instanceof NamedRange;
}
/**
* IS_EVEN.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isEven($value = null)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
if ($value === null) {
return ExcelError::NAME();
} elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
return ExcelError::VALUE();
}
return ((int) fmod($value, 2)) === 0;
}
/**
* IS_ODD.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool|string
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isOdd($value = null)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
if ($value === null) {
return ExcelError::NAME();
} elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
return ExcelError::VALUE();
}
return ((int) fmod($value, 2)) !== 0;
}
/**
* IS_NUMBER.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isNumber($value = null)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
if (is_string($value)) {
return false;
}
return is_numeric($value);
}
/**
* IS_LOGICAL.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isLogical($value = null)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
return is_bool($value);
}
/**
* IS_TEXT.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isText($value = null)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
return is_string($value) && !ErrorValue::isError($value);
}
/**
* IS_NONTEXT.
*
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isNonText($value = null)
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
return !self::isText($value);
}
/**
* ISFORMULA.
*
* @param mixed $cellReference The cell to check
* @param ?Cell $cell The current cell (containing this formula)
*
* @return array|bool|string
*/
public static function isFormula($cellReference = '', ?Cell $cell = null)
{
if ($cell === null) {
return ExcelError::REF();
}
$fullCellReference = Functions::expandDefinedName((string) $cellReference, $cell);
if (strpos($cellReference, '!') !== false) {
$cellReference = Functions::trimSheetFromCellReference($cellReference);
$cellReferences = Coordinate::extractAllCellReferencesInRange($cellReference);
if (count($cellReferences) > 1) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $cellReferences, $cell);
}
}
$fullCellReference = Functions::trimTrailingRange($fullCellReference);
preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $fullCellReference, $matches);
$fullCellReference = $matches[6] . $matches[7];
$worksheetName = str_replace("''", "'", trim($matches[2], "'"));
$worksheet = (!empty($worksheetName))
? $cell->getWorksheet()->getParent()->getSheetByName($worksheetName)
: $cell->getWorksheet();
return ($worksheet !== null) ? $worksheet->getCell($fullCellReference)->isFormula() : ExcelError::REF();
}
/**
* N.
*
* Returns a value converted to a number
*
* @param null|mixed $value The value you want converted
*
* @return number|string N converts values listed in the following table
* If value is or refers to N returns
* A number That number value
* A date The Excel serialized number of that date
* TRUE 1
* FALSE 0
* An error value The error value
* Anything else 0
*/
public static function asNumber($value = null)
{
while (is_array($value)) {
$value = array_shift($value);
}
switch (gettype($value)) {
case 'double':
case 'float':
case 'integer':
return $value;
case 'boolean':
return (int) $value;
case 'string':
// Errors
if ((strlen($value) > 0) && ($value[0] == '#')) {
return $value;
}
break;
}
return 0;
}
/**
* TYPE.
*
* Returns a number that identifies the type of a value
*
* @param null|mixed $value The value you want tested
*
* @return number N converts values listed in the following table
* If value is or refers to N returns
* A number 1
* Text 2
* Logical Value 4
* An error value 16
* Array or Matrix 64
*/
public static function type($value = null)
{
$value = Functions::flattenArrayIndexed($value);
if (is_array($value) && (count($value) > 1)) {
end($value);
$a = key($value);
// Range of cells is an error
if (Functions::isCellValue($a)) {
return 16;
// Test for Matrix
} elseif (Functions::isMatrixValue($a)) {
return 64;
}
} elseif (empty($value)) {
// Empty Cell
return 1;
}
$value = Functions::flattenSingleValue($value);
if (($value === null) || (is_float($value)) || (is_int($value))) {
return 1;
} elseif (is_bool($value)) {
return 4;
} elseif (is_array($value)) {
return 64;
} elseif (is_string($value)) {
// Errors
if ((strlen($value) > 0) && ($value[0] == '#')) {
return 16;
}
return 2;
}
return 0;
}
}

View File

@ -163,7 +163,7 @@ class Logical
*
* @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE
*
* @return bool|string the boolean inverse of the argument
* @return array|bool|string the boolean inverse of the argument
*/
public static function NOT($logical = false)
{

View File

@ -2,11 +2,17 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Information\Value;
class Conditional
{
use ArrayEnabled;
/**
* STATEMENT_IF.
*
@ -34,21 +40,24 @@ class Conditional
*
* @param mixed $condition Condition to evaluate
* @param mixed $returnIfTrue Value to return when condition is true
* Note that this can be an array value
* @param mixed $returnIfFalse Optional value to return when condition is false
* Note that this can be an array value
*
* @return mixed The value of returnIfTrue or returnIfFalse determined by condition
*/
public static function statementIf($condition = true, $returnIfTrue = 0, $returnIfFalse = false)
{
if (Functions::isError($condition)) {
$condition = ($condition === null) ? true : Functions::flattenSingleValue($condition);
if (ErrorValue::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);
$returnIfTrue = $returnIfTrue ?? 0;
$returnIfFalse = $returnIfFalse ?? false;
return ($condition) ? $returnIfTrue : $returnIfFalse;
return ((bool) $condition) ? $returnIfTrue : $returnIfFalse;
}
/**
@ -67,9 +76,11 @@ class Conditional
* result1, result2, ... result_n
* A list of results. The SWITCH function returns the corresponding result when a value
* matches expression.
* Note that these can be array values to be returned
* default
* Optional. It is the default to return if expression does not match any of the values
* (value1, value2, ... value_n).
* Note that this can be an array value to be returned
*
* @param mixed $arguments Statement arguments
*
@ -77,7 +88,7 @@ class Conditional
*/
public static function statementSwitch(...$arguments)
{
$result = Functions::VALUE();
$result = ExcelError::VALUE();
if (count($arguments) > 0) {
$targetValue = Functions::flattenSingleValue($arguments[0]);
@ -99,7 +110,7 @@ class Conditional
}
if ($switchSatisfied !== true) {
$result = $hasDefaultClause ? $defaultClause : Functions::NA();
$result = $hasDefaultClause ? $defaultClause : ExcelError::NA();
}
}
@ -113,16 +124,23 @@ class Conditional
* =IFERROR(testValue,errorpart)
*
* @param mixed $testValue Value to check, is also the value returned when no error
* Or can be an array of values
* @param mixed $errorpart Value to return when testValue is an error condition
* Note that this can be an array value to be returned
*
* @return mixed The value of errorpart or testValue determined by error condition
* If an array of values is passed as the $testValue argument, then the returned result will also be
* an array with the same dimensions
*/
public static function IFERROR($testValue = '', $errorpart = '')
{
$testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue);
$errorpart = ($errorpart === null) ? '' : Functions::flattenSingleValue($errorpart);
if (is_array($testValue)) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $testValue, $errorpart);
}
return self::statementIf(Functions::isError($testValue), $errorpart, $testValue);
$errorpart = $errorpart ?? '';
return self::statementIf(ErrorValue::isError($testValue), $errorpart, $testValue);
}
/**
@ -132,16 +150,23 @@ class Conditional
* =IFNA(testValue,napart)
*
* @param mixed $testValue Value to check, is also the value returned when not an NA
* Or can be an array of values
* @param mixed $napart Value to return when testValue is an NA condition
* Note that this can be an array value to be returned
*
* @return mixed The value of errorpart or testValue determined by error condition
* If an array of values is passed as the $testValue argument, then the returned result will also be
* an array with the same dimensions
*/
public static function IFNA($testValue = '', $napart = '')
{
$testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue);
$napart = ($napart === null) ? '' : Functions::flattenSingleValue($napart);
if (is_array($testValue)) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $testValue, $napart);
}
return self::statementIf(Functions::isNa($testValue), $napart, $testValue);
$napart = $napart ?? '';
return self::statementIf(ErrorValue::isNa($testValue), $napart, $testValue);
}
/**
@ -156,6 +181,7 @@ class Conditional
* Value returned if corresponding testValue (nth) was true
*
* @param mixed ...$arguments Statement arguments
* Note that this can be an array value to be returned
*
* @return mixed|string The value of returnIfTrue_n, if testValue_n was true. #N/A if none of testValues was true
*/
@ -164,13 +190,13 @@ class Conditional
$argumentCount = count($arguments);
if ($argumentCount % 2 != 0) {
return Functions::NA();
return ExcelError::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]);
$returnIfTrue = ($arguments[$i + 1] === null) ? '' : $arguments[$i + 1];
$result = self::statementIf($testValue, $returnIfTrue, $falseValueException);
if ($result !== $falseValueException) {
@ -178,6 +204,6 @@ class Conditional
}
}
return Functions::NA();
return ExcelError::NA();
}
}

View File

@ -2,11 +2,15 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Logical;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Operations
{
use ArrayEnabled;
/**
* LOGICAL_AND.
*
@ -32,7 +36,7 @@ class Operations
$args = Functions::flattenArray($args);
if (count($args) == 0) {
return Functions::VALUE();
return ExcelError::VALUE();
}
$args = array_filter($args, function ($value) {
@ -73,7 +77,7 @@ class Operations
$args = Functions::flattenArray($args);
if (count($args) == 0) {
return Functions::VALUE();
return ExcelError::VALUE();
}
$args = array_filter($args, function ($value) {
@ -115,7 +119,7 @@ class Operations
$args = Functions::flattenArray($args);
if (count($args) == 0) {
return Functions::VALUE();
return ExcelError::VALUE();
}
$args = array_filter($args, function ($value) {
@ -146,12 +150,17 @@ class Operations
* 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
* Or can be an array of values
*
* @return bool|string the boolean inverse of the argument
* @return array|bool|string the boolean inverse of the argument
* If an array of values is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function NOT($logical = false)
{
$logical = Functions::flattenSingleValue($logical);
if (is_array($logical)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $logical);
}
if (is_string($logical)) {
$logical = mb_strtoupper($logical, 'UTF-8');
@ -161,7 +170,7 @@ class Operations
return true;
}
return Functions::VALUE();
return ExcelError::VALUE();
}
return !$logical;
@ -187,7 +196,7 @@ class Operations
} elseif (($arg == 'FALSE') || ($arg == Calculation::getFALSE())) {
$arg = false;
} else {
return Functions::VALUE();
return ExcelError::VALUE();
}
$trueValueCount += ($arg != 0);
}

View File

@ -41,9 +41,9 @@ class LookupRef
* @param bool $referenceStyle A logical value that specifies the A1 or R1C1 reference style.
* 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
* @param array|string $sheetText Optional Name of worksheet to use
*
* @return string
* @return array|string
*/
public static function cellAddress($row, $column, $relativity = 1, $referenceStyle = true, $sheetText = '')
{
@ -277,7 +277,7 @@ class LookupRef
* @param mixed $matchType The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below.
* If match_type is 1 or -1, the list has to be ordered.
*
* @return int|string The relative position of the found item
* @return array|int|string The relative position of the found item
*/
public static function MATCH($lookupValue, $lookupArray, $matchType = 1)
{

View File

@ -2,11 +2,15 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
class Address
{
use ArrayEnabled;
public const ADDRESS_ABSOLUTE = 1;
public const ADDRESS_COLUMN_RELATIVE = 2;
public const ADDRESS_ROW_RELATIVE = 3;
@ -24,33 +28,54 @@ class Address
* =ADDRESS(row, column, [relativity], [referenceStyle], [sheetText])
*
* @param mixed $row Row number (integer) to use in the cell reference
* Or can be an array of values
* @param mixed $column Column number (integer) to use in the cell reference
* Or can be an array of values
* @param mixed $relativity Integer flag indicating the type of reference to return
* 1 or omitted Absolute
* 2 Absolute row; relative column
* 3 Relative row; absolute column
* 4 Relative
* Or can be an array of values
* @param mixed $referenceStyle A logical (boolean) value that specifies the A1 or R1C1 reference style.
* TRUE or omitted ADDRESS returns an A1-style reference
* FALSE ADDRESS returns an R1C1-style reference
* Or can be an array of values
* @param mixed $sheetName Optional Name of worksheet to use
* Or can be an array of values
*
* @return string
* @return array|string
* If an array of values is passed as the $testValue argument, then the returned result will also be
* an array with the same dimensions
*/
public static function cell($row, $column, $relativity = 1, $referenceStyle = true, $sheetName = '')
{
$row = Functions::flattenSingleValue($row);
$column = Functions::flattenSingleValue($column);
$relativity = ($relativity === null) ? 1 : Functions::flattenSingleValue($relativity);
$referenceStyle = ($referenceStyle === null) ? true : Functions::flattenSingleValue($referenceStyle);
$sheetName = Functions::flattenSingleValue($sheetName);
if (
is_array($row) || is_array($column) ||
is_array($relativity) || is_array($referenceStyle) || is_array($sheetName)
) {
return self::evaluateArrayArguments(
[self::class, __FUNCTION__],
$row,
$column,
$relativity,
$referenceStyle,
$sheetName
);
}
$relativity = $relativity ?? 1;
$referenceStyle = $referenceStyle ?? true;
if (($row < 1) || ($column < 1)) {
return Functions::VALUE();
return ExcelError::VALUE();
}
$sheetName = self::sheetName($sheetName);
if (is_int($referenceStyle)) {
$referenceStyle = (bool) $referenceStyle;
}
if ((!is_bool($referenceStyle)) || $referenceStyle === self::REFERENCE_STYLE_A1) {
return self::formatAsA1($row, $column, $relativity, $sheetName);
}
@ -92,7 +117,8 @@ class Address
if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) {
$row = "[{$row}]";
}
[$rowChar, $colChar] = AddressHelper::getRowAndColumnChars();
return "{$sheetName}R{$row}C{$column}";
return "{$sheetName}$rowChar{$row}$colChar{$column}";
}
}

View File

@ -2,13 +2,17 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class ExcelMatch
{
use ArrayEnabled;
public const MATCHTYPE_SMALLEST_VALUE = -1;
public const MATCHTYPE_FIRST_VALUE = 0;
public const MATCHTYPE_LARGEST_VALUE = 1;
@ -26,15 +30,16 @@ class ExcelMatch
* @param mixed $matchType The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below.
* If match_type is 1 or -1, the list has to be ordered.
*
* @return int|string The relative position of the found item
* @return array|int|string The relative position of the found item
*/
public static function MATCH($lookupValue, $lookupArray, $matchType = self::MATCHTYPE_LARGEST_VALUE)
{
if (is_array($lookupValue)) {
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $matchType);
}
$lookupArray = Functions::flattenArray($lookupArray);
$lookupValue = Functions::flattenSingleValue($lookupValue);
$matchType = ($matchType === null)
? self::MATCHTYPE_LARGEST_VALUE
: (int) Functions::flattenSingleValue($matchType);
$matchType = (int) ($matchType ?? self::MATCHTYPE_LARGEST_VALUE);
try {
// Input validation
@ -79,7 +84,7 @@ class ExcelMatch
}
// Unsuccessful in finding a match, return #N/A error value
return Functions::NA();
return ExcelError::NA();
}
private static function matchFirstValue($lookupArray, $lookupValue)
@ -149,7 +154,7 @@ class ExcelMatch
{
// 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());
throw new Exception(ExcelError::NA());
}
}
@ -160,7 +165,7 @@ class ExcelMatch
($matchType !== self::MATCHTYPE_FIRST_VALUE) &&
($matchType !== self::MATCHTYPE_LARGEST_VALUE) && ($matchType !== self::MATCHTYPE_SMALLEST_VALUE)
) {
throw new Exception(Functions::NA());
throw new Exception(ExcelError::NA());
}
}
@ -169,7 +174,7 @@ class ExcelMatch
// Lookup_array should not be empty
$lookupArraySize = count($lookupArray);
if ($lookupArraySize <= 0) {
throw new Exception(Functions::NA());
throw new Exception(ExcelError::NA());
}
}
@ -179,7 +184,7 @@ class ExcelMatch
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());
throw new Exception(ExcelError::NA());
}
// Convert strings to lowercase for case-insensitive testing
if (is_string($value)) {

View File

@ -0,0 +1,81 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Filter
{
/**
* @param mixed $lookupArray
* @param mixed $matchArray
* @param mixed $ifEmpty
*
* @return mixed
*/
public static function filter($lookupArray, $matchArray, $ifEmpty = null)
{
if (!is_array($matchArray)) {
return ExcelError::VALUE();
}
$matchArray = self::enumerateArrayKeys($matchArray);
$result = (Matrix::isColumnVector($matchArray))
? self::filterByRow($lookupArray, $matchArray)
: self::filterByColumn($lookupArray, $matchArray);
if (empty($result)) {
return $ifEmpty ?? ExcelError::CALC();
}
return array_values(array_map('array_values', $result));
}
private static function enumerateArrayKeys(array $sortArray): array
{
array_walk(
$sortArray,
function (&$columns): void {
if (is_array($columns)) {
$columns = array_values($columns);
}
}
);
return array_values($sortArray);
}
private static function filterByRow(array $lookupArray, array $matchArray): array
{
$matchArray = array_values(array_column($matchArray, 0));
return array_filter(
array_values($lookupArray),
function ($index) use ($matchArray): bool {
return (bool) $matchArray[$index];
},
ARRAY_FILTER_USE_KEY
);
}
private static function filterByColumn(array $lookupArray, array $matchArray): array
{
$lookupArray = Matrix::transpose($lookupArray);
if (count($matchArray) === 1) {
$matchArray = array_pop($matchArray);
}
array_walk(
$matchArray,
function (&$value): void {
$value = [$value];
}
);
$result = self::filterByRow($lookupArray, $matchArray);
return Matrix::transpose($result);
}
}

View File

@ -3,7 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
class Formula
@ -19,7 +19,7 @@ class Formula
public static function text($cellReference = '', ?Cell $cell = null)
{
if ($cell === null) {
return Functions::REF();
return ExcelError::REF();
}
preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellReference, $matches);
@ -35,7 +35,7 @@ class Formula
!$worksheet->cellExists($cellReference) ||
!$worksheet->getCell($cellReference)->isFormula()
) {
return Functions::NA();
return ExcelError::NA();
}
return $worksheet->getCell($cellReference)->getValue();

View File

@ -2,13 +2,16 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class HLookup extends LookupBase
{
use ArrayEnabled;
/**
* HLOOKUP
* The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value
@ -24,12 +27,15 @@ class HLookup extends LookupBase
*/
public static function lookup($lookupValue, $lookupArray, $indexNumber, $notExactMatch = true)
{
$lookupValue = Functions::flattenSingleValue($lookupValue);
$indexNumber = Functions::flattenSingleValue($indexNumber);
$notExactMatch = ($notExactMatch === null) ? true : Functions::flattenSingleValue($notExactMatch);
$lookupArray = self::convertLiteralArray($lookupArray);
if (is_array($lookupValue)) {
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $indexNumber, $notExactMatch);
}
$notExactMatch = (bool) ($notExactMatch ?? true);
try {
self::validateLookupArray($lookupArray);
$lookupArray = self::convertLiteralArray($lookupArray);
$indexNumber = self::validateIndexLookup($lookupArray, $indexNumber);
} catch (Exception $e) {
return $e->getMessage();
@ -38,12 +44,12 @@ class HLookup extends LookupBase
$f = array_keys($lookupArray);
$firstRow = reset($f);
if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray))) {
return Functions::REF();
return ExcelError::REF();
}
$firstkey = $f[0] - 1;
$returnColumn = $firstkey + $indexNumber;
$firstColumn = array_shift($f);
$firstColumn = array_shift($f) ?? 1;
$rowNumber = self::hLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch);
if ($rowNumber !== null) {
@ -51,24 +57,23 @@ class HLookup extends LookupBase
return $lookupArray[$returnColumn][Coordinate::stringFromColumnIndex($rowNumber)];
}
return Functions::NA();
return ExcelError::NA();
}
/**
* @param mixed $lookupValue The value that you want to match in lookup_array
* @param mixed $column The column to look up
* @param mixed $notExactMatch determines if you are looking for an exact match based on lookup_value
* @param int|string $column
*/
private static function hLookupSearch($lookupValue, array $lookupArray, $column, $notExactMatch): ?int
private static function hLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int
{
$lookupLower = StringHelper::strToLower($lookupValue);
$lookupLower = StringHelper::strToLower((string) $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);
$cellDataLower = StringHelper::strToLower((string) $rowData);
if (
$notExactMatch &&

View File

@ -13,12 +13,12 @@ class Helpers
public const CELLADDRESS_USE_R1C1 = false;
private static function convertR1C1(string &$cellAddress1, ?string &$cellAddress2, bool $a1): string
private static function convertR1C1(string &$cellAddress1, ?string &$cellAddress2, bool $a1, ?int $baseRow = null, ?int $baseCol = null): string
{
if ($a1 === self::CELLADDRESS_USE_R1C1) {
$cellAddress1 = AddressHelper::convertToA1($cellAddress1);
$cellAddress1 = AddressHelper::convertToA1($cellAddress1, $baseRow ?? 1, $baseCol ?? 1);
if ($cellAddress2) {
$cellAddress2 = AddressHelper::convertToA1($cellAddress2);
$cellAddress2 = AddressHelper::convertToA1($cellAddress2, $baseRow ?? 1, $baseCol ?? 1);
}
}
@ -35,7 +35,7 @@ class Helpers
}
}
public static function extractCellAddresses(string $cellAddress, bool $a1, Worksheet $sheet, string $sheetName = ''): array
public static function extractCellAddresses(string $cellAddress, bool $a1, Worksheet $sheet, string $sheetName = '', ?int $baseRow = null, ?int $baseCol = null): array
{
$cellAddress1 = $cellAddress;
$cellAddress2 = null;
@ -43,7 +43,7 @@ class Helpers
if ($namedRange !== null) {
$workSheet = $namedRange->getWorkSheet();
$sheetTitle = ($workSheet === null) ? '' : $workSheet->getTitle();
$value = preg_replace('/^=/', '', $namedRange->getValue());
$value = (string) preg_replace('/^=/', '', $namedRange->getValue());
self::adjustSheetTitle($sheetTitle, $value);
$cellAddress1 = $sheetTitle . $value;
$cellAddress = $cellAddress1;
@ -52,7 +52,7 @@ class Helpers
if (strpos($cellAddress, ':') !== false) {
[$cellAddress1, $cellAddress2] = explode(':', $cellAddress);
}
$cellAddress = self::convertR1C1($cellAddress1, $cellAddress2, $a1);
$cellAddress = self::convertR1C1($cellAddress1, $cellAddress2, $a1, $baseRow, $baseCol);
return [$cellAddress1, $cellAddress2, $cellAddress];
}

View File

@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
class Hyperlink
@ -25,7 +26,7 @@ class Hyperlink
$displayName = ($displayName === null) ? '' : Functions::flattenSingleValue($displayName);
if ((!is_object($cell)) || (trim($linkURL) == '')) {
return Functions::REF();
return ExcelError::REF();
}
if ((is_object($displayName)) || trim($displayName) == '') {

View File

@ -5,7 +5,9 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Indirect
@ -23,7 +25,7 @@ class Indirect
return Helpers::CELLADDRESS_USE_A1;
}
if (is_string($a1fmt)) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
return (bool) $a1fmt;
@ -38,7 +40,7 @@ class Indirect
{
$cellAddress = Functions::flattenSingleValue($cellAddress);
if (!is_string($cellAddress) || !$cellAddress) {
throw new Exception(Functions::REF());
throw new Exception(ExcelError::REF());
}
return $cellAddress;
@ -62,6 +64,8 @@ class Indirect
*/
public static function INDIRECT($cellAddress, $a1fmt, Cell $cell)
{
[$baseCol, $baseRow] = Coordinate::indexesFromString($cell->getCoordinate());
try {
$a1 = self::a1Format($a1fmt);
$cellAddress = self::validateAddress($cellAddress);
@ -71,13 +75,23 @@ class Indirect
[$cellAddress, $worksheet, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell);
[$cellAddress1, $cellAddress2, $cellAddress] = Helpers::extractCellAddresses($cellAddress, $a1, $cell->getWorkSheet(), $sheetName);
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) {
$cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress));
} elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) {
$cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress));
}
try {
[$cellAddress1, $cellAddress2, $cellAddress] = Helpers::extractCellAddresses($cellAddress, $a1, $cell->getWorkSheet(), $sheetName, $baseRow, $baseCol);
} catch (Exception $e) {
return ExcelError::REF();
}
if (
(!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress1, $matches)) ||
(($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress2, $matches)))
(!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress1, $matches)) ||
(($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress2, $matches)))
) {
return Functions::REF();
return ExcelError::REF();
}
return self::extractRequiredCells($worksheet, $cellAddress);
@ -94,4 +108,22 @@ class Indirect
return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null)
->extractCellRange($cellAddress, $worksheet, false);
}
private static function handleRowColumnRanges(?Worksheet $worksheet, string $start, string $end): string
{
// Being lazy, we're only checking a single row/column to get the max
if (ctype_digit($start) && $start <= 1048576) {
// Max 16,384 columns for Excel2007
$endColRef = ($worksheet !== null) ? $worksheet->getHighestDataColumn((int) $start) : 'XFD';
return "A{$start}:{$endColRef}{$end}";
} elseif (ctype_alpha($start) && strlen($start) <= 3) {
// Max 1,048,576 rows for Excel2007
$endRowRef = ($worksheet !== null) ? $worksheet->getHighestDataRow($start) : 1048576;
return "{$start}1:{$end}{$endRowRef}";
}
return "{$start}:{$end}";
}
}

View File

@ -2,11 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
class Lookup
{
use ArrayEnabled;
/**
* LOOKUP
* The LOOKUP function searches for value either from a one-row or one-column range or from an array.
@ -19,10 +22,12 @@ class Lookup
*/
public static function lookup($lookupValue, $lookupVector, $resultVector = null)
{
$lookupValue = Functions::flattenSingleValue($lookupValue);
if (is_array($lookupValue)) {
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $lookupValue, $lookupVector, $resultVector);
}
if (!is_array($lookupVector)) {
return Functions::NA();
return ExcelError::NA();
}
$hasResultVector = isset($resultVector);
$lookupRows = self::rowCount($lookupVector);

View File

@ -3,20 +3,38 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
abstract class LookupBase
{
protected static function validateIndexLookup($lookup_array, $index_number)
/**
* @param mixed $lookup_array
*/
protected static function validateLookupArray($lookup_array): void
{
// index_number must be a number greater than or equal to 1
if (!is_numeric($index_number) || $index_number < 1) {
throw new Exception(Functions::VALUE());
if (!is_array($lookup_array)) {
throw new Exception(ExcelError::REF());
}
}
protected static function validateIndexLookup(array $lookup_array, $index_number): int
{
// index_number must be a number greater than or equal to 1.
// Excel results are inconsistent when index is non-numeric.
// VLOOKUP(whatever, whatever, SQRT(-1)) yields NUM error, but
// VLOOKUP(whatever, whatever, cellref) yields REF error
// when cellref is '=SQRT(-1)'. So just try our best here.
// Similar results if string (literal yields VALUE, cellRef REF).
if (!is_numeric($index_number)) {
throw new Exception(ExcelError::throwError($index_number));
}
if ($index_number < 1) {
throw new Exception(ExcelError::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))) {
throw new Exception(Functions::REF());
throw new Exception(ExcelError::REF());
}
return (int) $index_number;
@ -25,7 +43,7 @@ abstract class LookupBase
protected static function checkMatch(
bool $bothNumeric,
bool $bothNotNumeric,
$notExactMatch,
bool $notExactMatch,
int $rowKey,
string $cellDataLower,
string $lookupLower,

View File

@ -3,7 +3,8 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class LookupRefValidations
{
@ -13,11 +14,11 @@ class LookupRefValidations
public static function validateInt($value): int
{
if (!is_numeric($value)) {
if (Functions::isError($value)) {
if (ErrorValue::isError($value)) {
throw new Exception($value);
}
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
return (int) floor((float) $value);
@ -31,7 +32,7 @@ class LookupRefValidations
$value = self::validateInt($value);
if (($allowZero === false && $value <= 0) || $value < 0) {
throw new Exception(Functions::VALUE());
throw new Exception(ExcelError::VALUE());
}
return $value;

View File

@ -2,11 +2,31 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Matrix
{
use ArrayEnabled;
/**
* Helper function; NOT an implementation of any Excel Function.
*/
public static function isColumnVector(array $values): bool
{
return count($values, COUNT_RECURSIVE) === (count($values, COUNT_NORMAL) * 2);
}
/**
* Helper function; NOT an implementation of any Excel Function.
*/
public static function isRowVector(array $values): bool
{
return count($values, COUNT_RECURSIVE) > 1 &&
(count($values, COUNT_NORMAL) === 1 || count($values, COUNT_RECURSIVE) === count($values, COUNT_NORMAL));
}
/**
* TRANSPOSE.
*
@ -45,17 +65,25 @@ class Matrix
* @param mixed $matrix A range of cells or an array constant
* @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.
* Or can be an array of values
* @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.
* Or can be an array of values
*
* TODO Provide support for area_num, currently not supported
*
* @return mixed the value of a specified cell or array of cells
* If an array of values is passed as the $rowNum and/or $columnNum arguments, then the returned result
* will also be an array with the same dimensions
*/
public static function index($matrix, $rowNum = 0, $columnNum = 0)
{
$rowNum = ($rowNum === null) ? 0 : Functions::flattenSingleValue($rowNum);
$columnNum = ($columnNum === null) ? 0 : Functions::flattenSingleValue($columnNum);
if (is_array($rowNum) || is_array($columnNum)) {
return self::evaluateArrayArgumentsSubsetFrom([self::class, __FUNCTION__], 1, $matrix, $rowNum, $columnNum);
}
$rowNum = $rowNum ?? 0;
$columnNum = $columnNum ?? 0;
try {
$rowNum = LookupRefValidations::validatePositiveInt($rowNum);
@ -65,14 +93,14 @@ class Matrix
}
if (!is_array($matrix) || ($rowNum > count($matrix))) {
return Functions::REF();
return ExcelError::REF();
}
$rowKeys = array_keys($matrix);
$columnKeys = @array_keys($matrix[$rowKeys[0]]);
if ($columnNum > count($columnKeys)) {
return Functions::REF();
return ExcelError::REF();
}
if ($columnNum === 0) {

Some files were not shown because too many files have changed in this diff Show More