Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into

develop
This commit is contained in:
Laurent Destailleur 2023-03-28 23:30:26 +02:00
parent 764ce03d10
commit 2663c8f2f2
131 changed files with 8905 additions and 1298 deletions

5
composer.json Normal file
View File

@ -0,0 +1,5 @@
{
"require": {
"phpoffice/phpspreadsheet": "^1.12"
}
}

339
composer.lock generated Normal file
View File

@ -0,0 +1,339 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a68ce168cd747e78b793f2d2d0bb89b9",
"packages": [
{
"name": "markbaker/complex",
"version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPComplex.git",
"reference": "c3131244e29c08d44fefb49e0dd35021e9e39dd2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/c3131244e29c08d44fefb49e0dd35021e9e39dd2",
"reference": "c3131244e29c08d44fefb49e0dd35021e9e39dd2",
"shasum": ""
},
"require": {
"php": "^5.6.0|^7.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
"phpcompatibility/php-compatibility": "^9.0",
"phpdocumentor/phpdocumentor": "2.*",
"phploc/phploc": "^4.0|^5.0|^6.0|^7.0",
"phpmd/phpmd": "2.*",
"phpunit/phpunit": "^4.8.35|^5.0|^6.0|^7.0",
"sebastian/phpcpd": "2.*",
"squizlabs/php_codesniffer": "^3.4.0"
},
"type": "library",
"autoload": {
"files": [
"classes/src/functions/abs.php",
"classes/src/functions/acos.php",
"classes/src/functions/acosh.php",
"classes/src/functions/acot.php",
"classes/src/functions/acoth.php",
"classes/src/functions/acsc.php",
"classes/src/functions/acsch.php",
"classes/src/functions/argument.php",
"classes/src/functions/asec.php",
"classes/src/functions/asech.php",
"classes/src/functions/asin.php",
"classes/src/functions/asinh.php",
"classes/src/functions/atan.php",
"classes/src/functions/atanh.php",
"classes/src/functions/conjugate.php",
"classes/src/functions/cos.php",
"classes/src/functions/cosh.php",
"classes/src/functions/cot.php",
"classes/src/functions/coth.php",
"classes/src/functions/csc.php",
"classes/src/functions/csch.php",
"classes/src/functions/exp.php",
"classes/src/functions/inverse.php",
"classes/src/functions/ln.php",
"classes/src/functions/log2.php",
"classes/src/functions/log10.php",
"classes/src/functions/negative.php",
"classes/src/functions/pow.php",
"classes/src/functions/rho.php",
"classes/src/functions/sec.php",
"classes/src/functions/sech.php",
"classes/src/functions/sin.php",
"classes/src/functions/sinh.php",
"classes/src/functions/sqrt.php",
"classes/src/functions/tan.php",
"classes/src/functions/tanh.php",
"classes/src/functions/theta.php",
"classes/src/operations/add.php",
"classes/src/operations/subtract.php",
"classes/src/operations/multiply.php",
"classes/src/operations/divideby.php",
"classes/src/operations/divideinto.php"
],
"psr-4": {
"Complex\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@lange.demon.co.uk"
}
],
"description": "PHP Class for working with complex numbers",
"homepage": "https://github.com/MarkBaker/PHPComplex",
"keywords": [
"complex",
"mathematics"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
"source": "https://github.com/MarkBaker/PHPComplex/tree/1.5.0"
},
"time": "2020-08-26T19:47:57+00:00"
},
{
"name": "markbaker/matrix",
"version": "1.2.3",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPMatrix.git",
"reference": "44bb1ab01811116f01fe216ab37d921dccc6c10d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/44bb1ab01811116f01fe216ab37d921dccc6c10d",
"reference": "44bb1ab01811116f01fe216ab37d921dccc6c10d",
"shasum": ""
},
"require": {
"php": "^5.6.0|^7.0.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "dev-master",
"phploc/phploc": "^4",
"phpmd/phpmd": "dev-master",
"phpunit/phpunit": "^5.7|^6.0|7.0",
"sebastian/phpcpd": "^3.0",
"squizlabs/php_codesniffer": "^3.0@dev"
},
"type": "library",
"autoload": {
"files": [
"classes/src/Functions/adjoint.php",
"classes/src/Functions/antidiagonal.php",
"classes/src/Functions/cofactors.php",
"classes/src/Functions/determinant.php",
"classes/src/Functions/diagonal.php",
"classes/src/Functions/identity.php",
"classes/src/Functions/inverse.php",
"classes/src/Functions/minors.php",
"classes/src/Functions/trace.php",
"classes/src/Functions/transpose.php",
"classes/src/Operations/add.php",
"classes/src/Operations/directsum.php",
"classes/src/Operations/subtract.php",
"classes/src/Operations/multiply.php",
"classes/src/Operations/divideby.php",
"classes/src/Operations/divideinto.php"
],
"psr-4": {
"Matrix\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@lange.demon.co.uk"
}
],
"description": "PHP Class for working with matrices",
"homepage": "https://github.com/MarkBaker/PHPMatrix",
"keywords": [
"mathematics",
"matrix",
"vector"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
"source": "https://github.com/MarkBaker/PHPMatrix/tree/1.2.3"
},
"time": "2021-01-26T14:36:01+00:00"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "1.12.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "f79611d6dc1f6b7e8e30b738fc371b392001dbfd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/f79611d6dc1f6b7e8e30b738fc371b392001dbfd",
"reference": "f79611d6dc1f6b7e8e30b738fc371b392001dbfd",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"markbaker/complex": "^1.4",
"markbaker/matrix": "^1.2",
"php": "^7.1",
"psr/simple-cache": "^1.0"
},
"require-dev": {
"dompdf/dompdf": "^0.8.3",
"friendsofphp/php-cs-fixer": "^2.16",
"jpgraph/jpgraph": "^4.0",
"mpdf/mpdf": "^8.0",
"phpcompatibility/php-compatibility": "^9.3",
"phpunit/phpunit": "^7.5",
"squizlabs/php_codesniffer": "^3.5",
"tecnickcom/tcpdf": "^6.3"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"jpgraph/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"
},
"type": "library",
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "https://blog.maartenballiauw.be"
},
{
"name": "Mark Baker",
"homepage": "https://markbakeruk.net"
},
{
"name": "Franck Lefevre",
"homepage": "https://rootslabs.net"
},
{
"name": "Erik Tilt"
},
{
"name": "Adrien Crivelli"
}
],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"keywords": [
"OpenXML",
"excel",
"gnumeric",
"ods",
"php",
"spreadsheet",
"xls",
"xlsx"
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.12.0"
},
"time": "2020-04-27T08:12:48+00:00"
},
{
"name": "psr/simple-cache",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/simple-cache.git",
"reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
"reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\SimpleCache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interfaces for simple caching",
"keywords": [
"cache",
"caching",
"psr",
"psr-16",
"simple-cache"
],
"support": {
"source": "https://github.com/php-fig/simple-cache/tree/master"
},
"time": "2017-10-23T01:57:42+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.0.0"
}

View File

@ -92,7 +92,7 @@ class ExportExcel2007 extends ModeleExports
if (empty($this->disabled)) {
require_once PHPEXCELNEW_PATH.'Spreadsheet.php';
$this->label_lib = 'PhpSpreadSheet';
$this->version_lib = '1.6.0'; // No way to get info from library
$this->version_lib = '1.12.0'; // No way to get info from library
}
$this->row = 0;

View File

@ -41,7 +41,7 @@ class modFournisseur extends DolibarrModules
*/
public function __construct($db)
{
global $conf, $user;
global $conf, $langs, $user;
$this->db = $db;
$this->numero = 40;
@ -309,6 +309,8 @@ class modFournisseur extends DolibarrModules
//--------
$r = 0;
$langs->loadLangs(array("suppliers", "multicurrency"));
$r++;
$this->export_code[$r] = $this->rights_class.'_'.$r;
$this->export_label[$r] = 'Vendor invoices and lines of invoices';

View File

@ -765,7 +765,7 @@ class modProduct extends DolibarrModules
'sp.quantity' => "QtyMin*",
'sp.tva_tx' => 'VATRate',
'sp.default_vat_code' => 'VATCode',
'sp.delivery_time_days' => 'DeliveryDelay',
'sp.delivery_time_days' => 'NbDaysToDelivery',
'sp.supplier_reputation' => 'SupplierReputation',
'sp.status' => 'Status'
);

View File

@ -722,7 +722,7 @@ class modService extends DolibarrModules
'sp.quantity' => "QtyMin*",
'sp.tva_tx' => 'VATRate',
'sp.default_vat_code' => 'VATCode',
'sp.delivery_time_days' => 'DeliveryDelay',
'sp.delivery_time_days' => 'NbDaysToDelivery',
'sp.supplier_reputation' => 'SupplierReputation'
);
if (is_object($mysoc) && $usenpr) {

View File

@ -36,7 +36,6 @@ include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php';
*/
class modSupplierProposal extends DolibarrModules
{
/**
* Constructor. Define names, constants, directories, boxes, permissions
*

View File

@ -1180,7 +1180,8 @@ if ($step == 5 && $datatoexport) {
print '<br>';
// List of available export formats
$htmltabloflibs = '<table class="noborder centpercent">';
$htmltabloflibs = '<!-- Table with available export formats --><br>';
$htmltabloflibs .= '<table class="noborder centpercent nomarginbottom">';
$htmltabloflibs .= '<tr class="liste_titre">';
$htmltabloflibs .= '<td>'.$langs->trans("AvailableFormats").'</td>';
$htmltabloflibs .= '<td>'.$langs->trans("LibraryUsed").'</td>';
@ -1204,7 +1205,7 @@ if ($step == 5 && $datatoexport) {
$htmltabloflibs .= '<td class="right">'.$objmodelexport->getLibVersionForKey($key).'</td>';
$htmltabloflibs .= '</tr>'."\n";
}
$htmltabloflibs .= '</table>';
$htmltabloflibs .= '</table><br>';
print '<span class="opacitymedium">'.$form->textwithpicto($langs->trans("NowClickToGenerateToBuildExportFile"), $htmltabloflibs, 1, 'help', '', 0, 2, 'helphonformat').'</span>';
//print $htmltabloflibs;

View File

@ -0,0 +1,4 @@
/tests export-ignore
README.md export-ignore
*.min.js binary
/.github export-ignore

View File

@ -0,0 +1,10 @@
/tests/codeCoverage
/analysis
/vendor/
/phpunit.xml
## IDE support
*.buildpath
*.project
/.settings
/.idea

View File

@ -0,0 +1,183 @@
<?php
$finder = PhpCsFixer\Finder::create()
->exclude(['vendor', 'tests/data/Calculation'])
->in('samples')
->in('src')
->in('tests/PhpSpreadsheetTests')
;
return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setFinder($finder)
->setCacheFile(sys_get_temp_dir() . '/php-cs-fixer' . preg_replace('~\W~', '-', __DIR__))
->setRules([
'align_multiline_comment' => true,
'array_syntax' => ['syntax' => 'short'],
'backtick_to_shell_exec' => true,
'binary_operator_spaces' => true,
'blank_line_after_namespace' => true,
'blank_line_after_opening_tag' => true,
'blank_line_before_statement' => true,
'braces' => true,
'cast_spaces' => true,
'class_attributes_separation' => ['elements' => ['method', 'property']], // const are often grouped with other related const
'class_definition' => true,
'class_keyword_remove' => false, // ::class keyword gives us beter support in IDE
'combine_consecutive_issets' => true,
'combine_consecutive_unsets' => true,
'compact_nullable_typehint' => true,
'concat_space' => ['spacing' => 'one'],
'declare_equal_normalize' => true,
'declare_strict_types' => false, // Too early to adopt strict types
'dir_constant' => true,
'doctrine_annotation_array_assignment' => true,
'doctrine_annotation_braces' => true,
'doctrine_annotation_indentation' => true,
'doctrine_annotation_spaces' => true,
'elseif' => true,
'encoding' => true,
'ereg_to_preg' => true,
'escape_implicit_backslashes' => true,
'explicit_indirect_variable' => false, // I feel it makes the code actually harder to read
'explicit_string_variable' => false, // I feel it makes the code actually harder to read
'final_internal_class' => true,
'full_opening_tag' => true,
'function_declaration' => true,
'function_to_constant' => true,
'function_typehint_space' => true,
'general_phpdoc_annotation_remove' => false, // No use for that
'hash_to_slash_comment' => true,
'header_comment' => false, // We don't use common header in all our files
'heredoc_to_nowdoc' => false, // Not sure about this one
'include' => true,
'increment_style' => true,
'indentation_type' => true,
'is_null' => ['use_yoda_style' => false],
'linebreak_after_opening_tag' => true,
'line_ending' => true,
'list_syntax' => ['syntax' => 'short'],
'lowercase_cast' => true,
'lowercase_constants' => true,
'lowercase_keywords' => true,
'magic_constant_casing' => true,
'mb_str_functions' => false, // No, too dangerous to change that
'method_argument_space' => true,
'method_chaining_indentation' => true,
'method_separation' => true,
'modernize_types_casting' => true,
'multiline_comment_opening_closing' => true,
'native_function_casing' => true,
'native_function_invocation' => false, // This is risky and seems to be micro-optimization that make code uglier so not worth it, at least for now
'new_with_braces' => true,
'no_alias_functions' => true,
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_blank_lines_before_namespace' => false, // we want 1 blank line before namespace
'no_break_comment' => true,
'no_closing_tag' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_extra_blank_lines' => true,
'no_homoglyph_names' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_mixed_echo_print' => true,
'no_multiline_whitespace_around_double_arrow' => true,
'no_multiline_whitespace_before_semicolons' => true,
'non_printable_character' => true,
'no_null_property_initialization' => true,
'no_php4_constructor' => true,
'normalize_index_brace' => true,
'no_short_bool_cast' => true,
'no_short_echo_tag' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_spaces_after_function_name' => true,
'no_spaces_around_offset' => true,
'no_spaces_inside_parenthesis' => true,
'no_superfluous_elseif' => false, // Might be risky on a huge code base
'not_operator_with_space' => false, // No we prefer to keep '!' without spaces
'not_operator_with_successor_space' => false, // idem
'no_trailing_comma_in_list_call' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_trailing_whitespace_in_comment' => true,
'no_trailing_whitespace' => true,
'no_unneeded_control_parentheses' => true,
'no_unneeded_curly_braces' => true,
'no_unneeded_final_method' => true,
'no_unreachable_default_argument_value' => true,
'no_unused_imports' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'no_whitespace_before_comma_in_array' => true,
'no_whitespace_in_blank_line' => true,
'object_operator_without_whitespace' => true,
'ordered_class_elements' => false, // We prefer to keep some freedom
'ordered_imports' => true,
'phpdoc_add_missing_param_annotation' => true,
'phpdoc_align' => false, // Waste of time
'phpdoc_annotation_without_dot' => true,
'phpdoc_indent' => true,
'phpdoc_inline_tag' => true,
'phpdoc_no_access' => true,
'phpdoc_no_alias_tag' => true,
'phpdoc_no_empty_return' => true,
'phpdoc_no_package' => true,
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_order' => true,
'phpdoc_return_self_reference' => true,
'phpdoc_scalar' => true,
'phpdoc_separation' => true,
'phpdoc_single_line_var_spacing' => true,
'phpdoc_summary' => true,
'phpdoc_to_comment' => true,
'phpdoc_trim' => true,
'phpdoc_types_order' => true,
'phpdoc_types' => true,
'phpdoc_var_without_name' => true,
'php_unit_construct' => true,
'php_unit_dedicate_assert' => true,
'php_unit_expectation' => true,
'php_unit_fqcn_annotation' => true,
'php_unit_mock' => true,
'php_unit_namespaced' => true,
'php_unit_no_expectation_annotation' => true,
'php_unit_strict' => false, // We sometime actually need assertEquals
'php_unit_test_annotation' => true,
'php_unit_test_class_requires_covers' => false, // We don't care as much as we should about coverage
'pow_to_exponentiation' => false,
'protected_to_private' => true,
'psr0' => true,
'psr4' => true,
'random_api_migration' => false, // This breaks our unit tests
'return_type_declaration' => true,
'self_accessor' => true,
'semicolon_after_instruction' => false, // Buggy in `samples/index.php`
'short_scalar_cast' => true,
'silenced_deprecation_error' => true,
'simplified_null_return' => false, // While technically correct we prefer to be explicit when returning null
'single_blank_line_at_eof' => true,
'single_blank_line_before_namespace' => true,
'single_class_element_per_statement' => true,
'single_import_per_statement' => true,
'single_line_after_imports' => true,
'single_line_comment_style' => true,
'single_quote' => true,
'space_after_semicolon' => true,
'standardize_not_equals' => true,
'static_lambda' => false, // Risky if we can't guarantee nobody use `bindTo()`
'strict_comparison' => false, // No, too dangerous to change that
'strict_param' => false, // No, too dangerous to change that
'switch_case_semicolon_to_colon' => true,
'switch_case_space' => true,
'ternary_operator_spaces' => true,
'ternary_to_null_coalescing' => true,
'trailing_comma_in_multiline_array' => true,
'trim_array_spaces' => true,
'unary_operator_spaces' => true,
'visibility_required' => true,
'void_return' => false, // Cannot use that with PHP 5.6
'whitespace_after_comma_in_array' => true,
'yoda_style' => false,
]);

View File

@ -0,0 +1,27 @@
checks:
php: true
coding_style:
php:
spaces:
before_parentheses:
closure_definition: true
around_operators:
concatenation: true
build:
nodes:
analysis:
tests:
override:
- php-scrutinizer-run
tools:
external_code_coverage:
timeout: 3600
build_failure_conditions:
- 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed
- 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity
- 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection
- 'patches.label("Unused Use Statements").new.exists' # No new unused imports patches allowed

View File

@ -0,0 +1,57 @@
language: php
dist: bionic
php:
- 7.1
- 7.2
- 7.3
- 7.4
cache:
directories:
- vendor
- $HOME/.composer/cache
before_script:
# Deactivate xdebug
- phpenv config-rm xdebug.ini
- composer install --ignore-platform-reqs
script:
- ./vendor/bin/phpunit
jobs:
include:
- stage: Code style
php: 7.2
script:
- ./vendor/bin/php-cs-fixer fix --diff --verbose --dry-run
- ./vendor/bin/phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PSR2 -n
- stage: Coverage
php: 7.2
script:
- pecl install pcov
- composer require pcov/clobber --dev
- ./vendor/bin/pcov clobber
- ./vendor/bin/phpunit --coverage-clover coverage-clover.xml
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover tests/coverage-clover.xml
- stage: API documentations
if: tag is present
php: 7.4
before_script:
- curl -O https://github.com/phpDocumentor/phpDocumentor/releases/download/v3.0.0-rc/phpDocumentor.phar
script:
- php phpDocumentor.phar --directory src/ --target docs/api
deploy:
provider: pages
skip-cleanup: true
local-dir: docs/api
github-token: $GITHUB_TOKEN
on:
all_branches: true
condition: $TRAVIS_BRANCH =~ ^master$

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,471 @@
# Changelog
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.12.0] - 2020-04-27
### Added
- Improved the ARABIC function to also handle short-hand roman numerals
- Added support for the FLOOR.MATH and FLOOR.PRECISE functions [#1351](https://github.com/PHPOffice/PhpSpreadsheet/pull/1351)
### Fixed
- Fix ROUNDUP and ROUNDDOWN for floating-point rounding error [#1404](https://github.com/PHPOffice/PhpSpreadsheet/pull/1404)
- Fix ROUNDUP and ROUNDDOWN for negative number [#1417](https://github.com/PHPOffice/PhpSpreadsheet/pull/1417)
- Fix loading styles from vmlDrawings when containing whitespace [#1347](https://github.com/PHPOffice/PhpSpreadsheet/issues/1347)
- Fix incorrect behavior when removing last row [#1365](https://github.com/PHPOffice/PhpSpreadsheet/pull/1365)
- MATCH with a static array should return the position of the found value based on the values submitted [#1332](https://github.com/PHPOffice/PhpSpreadsheet/pull/1332)
- Fix Xlsx Reader's handling of undefined fill color [#1353](https://github.com/PHPOffice/PhpSpreadsheet/pull/1353)
## [1.11.0] - 2020-03-02
### Added
- Added support for the BASE function
- Added support for the ARABIC function
- Conditionals - Extend Support for (NOT)CONTAINSBLANKS [#1278](https://github.com/PHPOffice/PhpSpreadsheet/pull/1278)
### Fixed
- Handle Error in Formula Processing Better for Xls [#1267](https://github.com/PHPOffice/PhpSpreadsheet/pull/1267)
- Handle ConditionalStyle NumberFormat When Reading Xlsx File [#1296](https://github.com/PHPOffice/PhpSpreadsheet/pull/1296)
- Fix Xlsx Writer's handling of decimal commas [#1282](https://github.com/PHPOffice/PhpSpreadsheet/pull/1282)
- Fix for issue by removing test code mistakenly left in [#1328](https://github.com/PHPOffice/PhpSpreadsheet/pull/1328)
- Fix for Xls writer wrong selected cells and active sheet [#1256](https://github.com/PHPOffice/PhpSpreadsheet/pull/1256)
- Fix active cell when freeze pane is used [#1323](https://github.com/PHPOffice/PhpSpreadsheet/pull/1323)
- Fix XLSX file loading with autofilter containing '$' [#1326](https://github.com/PHPOffice/PhpSpreadsheet/pull/1326)
- PHPDoc - Use `@return $this` for fluent methods [#1362](https://github.com/PHPOffice/PhpSpreadsheet/pull/1362)
## [1.10.1] - 2019-12-02
### Changed
- PHP 7.4 compatibility
### Fixed
- FLOOR() function accept negative number and negative significance [#1245](https://github.com/PHPOffice/PhpSpreadsheet/pull/1245)
- Correct column style even when using rowspan [#1249](https://github.com/PHPOffice/PhpSpreadsheet/pull/1249)
- Do not confuse defined names and cell refs [#1263](https://github.com/PHPOffice/PhpSpreadsheet/pull/1263)
- XLSX reader/writer keep decimal for floats with a zero decimal part [#1262](https://github.com/PHPOffice/PhpSpreadsheet/pull/1262)
- ODS writer prevent invalid numeric value if locale decimal separator is comma [#1268](https://github.com/PHPOffice/PhpSpreadsheet/pull/1268)
- Xlsx writer actually writes plotVisOnly and dispBlanksAs from chart properties [#1266](https://github.com/PHPOffice/PhpSpreadsheet/pull/1266)
## [1.10.0] - 2019-11-18
### Changed
- Change license from LGPL 2.1 to MIT [#140](https://github.com/PHPOffice/PhpSpreadsheet/issues/140)
### Added
- Implementation of IFNA() logical function
- Support "showZeros" worksheet option to change how Excel shows and handles "null" values returned from a calculation
- Allow HTML Reader to accept HTML as a string into an existing spreadsheet [#1212](https://github.com/PHPOffice/PhpSpreadsheet/pull/1212)
### Fixed
- IF implementation properly handles the value `#N/A` [#1165](https://github.com/PHPOffice/PhpSpreadsheet/pull/1165)
- Formula Parser: Wrong line count for stuff like "MyOtherSheet!A:D" [#1215](https://github.com/PHPOffice/PhpSpreadsheet/issues/1215)
- Call garbage collector after removing a column to prevent stale cached values
- Trying to remove a column that doesn't exist deletes the latest column
- Keep big integer as integer instead of lossely casting to float [#874](https://github.com/PHPOffice/PhpSpreadsheet/pull/874)
- Fix branch pruning handling of non boolean conditions [#1167](https://github.com/PHPOffice/PhpSpreadsheet/pull/1167)
- Fix ODS Reader when no DC namespace are defined [#1182](https://github.com/PHPOffice/PhpSpreadsheet/pull/1182)
- Fixed Functions->ifCondition for allowing <> and empty condition [#1206](https://github.com/PHPOffice/PhpSpreadsheet/pull/1206)
- Validate XIRR inputs and return correct error values [#1120](https://github.com/PHPOffice/PhpSpreadsheet/issues/1120)
- Allow to read xlsx files with exotic workbook names like "workbook2.xml" [#1183](https://github.com/PHPOffice/PhpSpreadsheet/pull/1183)
## [1.9.0] - 2019-08-17
### Changed
- Drop support for PHP 5.6 and 7.0, according to https://phpspreadsheet.readthedocs.io/en/latest/#php-version-support
### Added
- When &lt;br&gt; appears in a table cell, set the cell to wrap [#1071](https://github.com/PHPOffice/PhpSpreadsheet/issues/1071) and [#1070](https://github.com/PHPOffice/PhpSpreadsheet/pull/1070)
- Add MAXIFS, MINIFS, COUNTIFS and Remove MINIF, MAXIF [#1056](https://github.com/PHPOffice/PhpSpreadsheet/issues/1056)
- HLookup needs an ordered list even if range_lookup is set to false [#1055](https://github.com/PHPOffice/PhpSpreadsheet/issues/1055) and [#1076](https://github.com/PHPOffice/PhpSpreadsheet/pull/1076)
- Improve performance of IF function calls via ranch pruning to avoid resolution of every branches [#844](https://github.com/PHPOffice/PhpSpreadsheet/pull/844)
- MATCH function supports `*?~` Excel functionality, when match_type=0 [#1116](https://github.com/PHPOffice/PhpSpreadsheet/issues/1116)
- Allow HTML Reader to accept HTML as a string [#1136](https://github.com/PHPOffice/PhpSpreadsheet/pull/1136)
### Fixed
- Fix to AVERAGEIF() function when called with a third argument
- Eliminate duplicate fill none style entries [#1066](https://github.com/PHPOffice/PhpSpreadsheet/issues/1066)
- Fix number format masks containing literal (non-decimal point) dots [#1079](https://github.com/PHPOffice/PhpSpreadsheet/issues/1079)
- Fix number format masks containing named colours that were being misinterpreted as date formats; and add support for masks that fully replace the value with a full text string [#1009](https://github.com/PHPOffice/PhpSpreadsheet/issues/1009)
- Stricter-typed comparison testing in COUNTIF() and COUNTIFS() evaluation [#1046](https://github.com/PHPOffice/PhpSpreadsheet/issues/1046)
- COUPNUM should not return zero when settlement is in the last period [#1020](https://github.com/PHPOffice/PhpSpreadsheet/issues/1020) and [#1021](https://github.com/PHPOffice/PhpSpreadsheet/pull/1021)
- Fix handling of named ranges referencing sheets with spaces or "!" in their title
- Cover `getSheetByName()` with tests for name with quote and spaces [#739](https://github.com/PHPOffice/PhpSpreadsheet/issues/739)
- Best effort to support invalid colspan values in HTML reader - [#878](https://github.com/PHPOffice/PhpSpreadsheet/pull/878)
- Fixes incorrect rows deletion [#868](https://github.com/PHPOffice/PhpSpreadsheet/issues/868)
- MATCH function fix (value search by type, stop search when match_type=-1 and unordered element encountered) [#1116](https://github.com/PHPOffice/PhpSpreadsheet/issues/1116)
- Fix `getCalculatedValue()` error with more than two INDIRECT [#1115](https://github.com/PHPOffice/PhpSpreadsheet/pull/1115)
- Writer\Html did not hide columns [#985](https://github.com/PHPOffice/PhpSpreadsheet/pull/985)
## [1.8.2] - 2019-07-08
### Fixed
- Uncaught error when opening ods file and properties aren't defined [#1047](https://github.com/PHPOffice/PhpSpreadsheet/issues/1047)
- Xlsx Reader Cell datavalidations bug [#1052](https://github.com/PHPOffice/PhpSpreadsheet/pull/1052)
## [1.8.1] - 2019-07-02
### Fixed
- Allow nullable theme for Xlsx Style Reader class [#1043](https://github.com/PHPOffice/PhpSpreadsheet/issues/1043)
## [1.8.0] - 2019-07-01
### Security Fix (CVE-2019-12331)
- Detect double-encoded xml in the Security scanner, and reject as suspicious.
- This change also broadens the scope of the `libxml_disable_entity_loader` setting when reading XML-based formats, so that it is enabled while the xml is being parsed and not simply while it is loaded.
On some versions of PHP, this can cause problems because it is not thread-safe, and can affect other PHP scripts running on the same server. This flag is set to true when instantiating a loader, and back to its original setting when the Reader is no longer in scope, or manually unset.
- Provide a check to identify whether libxml_disable_entity_loader is thread-safe or not.
`XmlScanner::threadSafeLibxmlDisableEntityLoaderAvailability()`
- Provide an option to disable the libxml_disable_entity_loader call through settings. This is not recommended as it reduces the security of the XML-based readers, and should only be used if you understand the consequences and have no other choice.
### Added
- Added support for the SWITCH function [#963](https://github.com/PHPOffice/PhpSpreadsheet/issues/963) and [#983](https://github.com/PHPOffice/PhpSpreadsheet/pull/983)
- Add accounting number format style [#974](https://github.com/PHPOffice/PhpSpreadsheet/pull/974)
### Fixed
- Whitelist `tsv` extension when opening CSV files [#429](https://github.com/PHPOffice/PhpSpreadsheet/issues/429)
- Fix a SUMIF warning with some versions of PHP when having different length of arrays provided as input [#873](https://github.com/PHPOffice/PhpSpreadsheet/pull/873)
- Fix incorrectly handled backslash-escaped space characters in number format
## [1.7.0] - 2019-05-26
- Added support for inline styles in Html reader (borders, alignment, width, height)
- QuotedText cells no longer treated as formulae if the content begins with a `=`
- Clean handling for DDE in formulae
### Fixed
- Fix handling for escaped enclosures and new lines in CSV Separator Inference
- Fix MATCH an error was appearing when comparing strings against 0 (always true)
- Fix wrong calculation of highest column with specified row [#700](https://github.com/PHPOffice/PhpSpreadsheet/issues/700)
- Fix VLOOKUP
- Fix return type hint
## [1.6.0] - 2019-01-02
### Added
- Refactored Matrix Functions to use external Matrix library
- Possibility to specify custom colors of values for pie and donut charts [#768](https://github.com/PHPOffice/PhpSpreadsheet/pull/768)
### Fixed
- Improve XLSX parsing speed if no readFilter is applied [#772](https://github.com/PHPOffice/PhpSpreadsheet/issues/772)
- Fix column names if read filter calls in XLSX reader skip columns [#777](https://github.com/PHPOffice/PhpSpreadsheet/pull/777)
- XLSX reader can now ignore blank cells, using the setReadEmptyCells(false) method. [#810](https://github.com/PHPOffice/PhpSpreadsheet/issues/810)
- Fix LOOKUP function which was breaking on edge cases [#796](https://github.com/PHPOffice/PhpSpreadsheet/issues/796)
- Fix VLOOKUP with exact matches [#809](https://github.com/PHPOffice/PhpSpreadsheet/pull/809)
- Support COUNTIFS multiple arguments [#830](https://github.com/PHPOffice/PhpSpreadsheet/pull/830)
- Change `libxml_disable_entity_loader()` as shortly as possible [#819](https://github.com/PHPOffice/PhpSpreadsheet/pull/819)
- Improved memory usage and performance when loading large spreadsheets [#822](https://github.com/PHPOffice/PhpSpreadsheet/pull/822)
- Improved performance when loading large spreadsheets [#825](https://github.com/PHPOffice/PhpSpreadsheet/pull/825)
- Improved performance when loading large spreadsheets [#824](https://github.com/PHPOffice/PhpSpreadsheet/pull/824)
- Fix color from CSS when reading from HTML [#831](https://github.com/PHPOffice/PhpSpreadsheet/pull/831)
- Fix infinite loop when reading invalid ODS files [#832](https://github.com/PHPOffice/PhpSpreadsheet/pull/832)
- Fix time format for duration is incorrect [#666](https://github.com/PHPOffice/PhpSpreadsheet/pull/666)
- Fix iconv unsupported `//IGNORE//TRANSLIT` on IBM i [#791](https://github.com/PHPOffice/PhpSpreadsheet/issues/791)
### Changed
- `master` is the new default branch, `develop` does not exist anymore
## [1.5.2] - 2018-11-25
### Security
- Improvements to the design of the XML Security Scanner [#771](https://github.com/PHPOffice/PhpSpreadsheet/issues/771)
## [1.5.1] - 2018-11-20
### Security
- Fix and improve XXE security scanning for XML-based and HTML Readers [#771](https://github.com/PHPOffice/PhpSpreadsheet/issues/771)
### Added
- Support page margin in mPDF [#750](https://github.com/PHPOffice/PhpSpreadsheet/issues/750)
### Fixed
- Support numeric condition in SUMIF, SUMIFS, AVERAGEIF, COUNTIF, MAXIF and MINIF [#683](https://github.com/PHPOffice/PhpSpreadsheet/issues/683)
- SUMIFS containing multiple conditions [#704](https://github.com/PHPOffice/PhpSpreadsheet/issues/704)
- Csv reader avoid notice when the file is empty [#743](https://github.com/PHPOffice/PhpSpreadsheet/pull/743)
- Fix print area parser for XLSX reader [#734](https://github.com/PHPOffice/PhpSpreadsheet/pull/734)
- Support overriding `DefaultValueBinder::dataTypeForValue()` without overriding `DefaultValueBinder::bindValue()` [#735](https://github.com/PHPOffice/PhpSpreadsheet/pull/735)
- Mpdf export can exceed pcre.backtrack_limit [#637](https://github.com/PHPOffice/PhpSpreadsheet/issues/637)
- Fix index overflow on data values array [#748](https://github.com/PHPOffice/PhpSpreadsheet/pull/748)
## [1.5.0] - 2018-10-21
### Added
- PHP 7.3 support
- Add the DAYS() function [#594](https://github.com/PHPOffice/PhpSpreadsheet/pull/594)
### Fixed
- Sheet title can contain exclamation mark [#325](https://github.com/PHPOffice/PhpSpreadsheet/issues/325)
- Xls file cause the exception during open by Xls reader [#402](https://github.com/PHPOffice/PhpSpreadsheet/issues/402)
- Skip non numeric value in SUMIF [#618](https://github.com/PHPOffice/PhpSpreadsheet/pull/618)
- OFFSET should allow omitted height and width [#561](https://github.com/PHPOffice/PhpSpreadsheet/issues/561)
- Correctly determine delimiter when CSV contains line breaks inside enclosures [#716](https://github.com/PHPOffice/PhpSpreadsheet/issues/716)
## [1.4.1] - 2018-09-30
### Fixed
- Remove locale from formatting string [#644](https://github.com/PHPOffice/PhpSpreadsheet/pull/644)
- Allow iterators to go out of bounds with prev [#587](https://github.com/PHPOffice/PhpSpreadsheet/issues/587)
- Fix warning when reading xlsx without styles [#631](https://github.com/PHPOffice/PhpSpreadsheet/pull/631)
- Fix broken sample links on windows due to $baseDir having backslash [#653](https://github.com/PHPOffice/PhpSpreadsheet/pull/653)
## [1.4.0] - 2018-08-06
### Added
- Add excel function EXACT(value1, value2) support [#595](https://github.com/PHPOffice/PhpSpreadsheet/pull/595)
- Support workbook view attributes for Xlsx format [#523](https://github.com/PHPOffice/PhpSpreadsheet/issues/523)
- Read and write hyperlink for drawing image [#490](https://github.com/PHPOffice/PhpSpreadsheet/pull/490)
- Added calculation engine support for the new bitwise functions that were added in MS Excel 2013
- BITAND() Returns a Bitwise 'And' of two numbers
- BITOR() Returns a Bitwise 'Or' of two number
- BITXOR() Returns a Bitwise 'Exclusive Or' of two numbers
- BITLSHIFT() Returns a number shifted left by a specified number of bits
- BITRSHIFT() Returns a number shifted right by a specified number of bits
- Added calculation engine support for other new functions that were added in MS Excel 2013 and MS Excel 2016
- Text Functions
- CONCAT() Synonym for CONCATENATE()
- NUMBERVALUE() Converts text to a number, in a locale-independent way
- UNICHAR() Synonym for CHAR() in PHPSpreadsheet, which has always used UTF-8 internally
- UNIORD() Synonym for ORD() in PHPSpreadsheet, which has always used UTF-8 internally
- TEXTJOIN() Joins together two or more text strings, separated by a delimiter
- Logical Functions
- XOR() Returns a logical Exclusive Or of all arguments
- Date/Time Functions
- ISOWEEKNUM() Returns the ISO 8601 week number of the year for a given date
- Lookup and Reference Functions
- FORMULATEXT() Returns a formula as a string
- Financial Functions
- PDURATION() Calculates the number of periods required for an investment to reach a specified value
- RRI() Calculates the interest rate required for an investment to grow to a specified future value
- Engineering Functions
- ERF.PRECISE() Returns the error function integrated between 0 and a supplied limit
- ERFC.PRECISE() Synonym for ERFC
- Math and Trig Functions
- SEC() Returns the secant of an angle
- SECH() Returns the hyperbolic secant of an angle
- CSC() Returns the cosecant of an angle
- CSCH() Returns the hyperbolic cosecant of an angle
- COT() Returns the cotangent of an angle
- COTH() Returns the hyperbolic cotangent of an angle
- ACOT() Returns the cotangent of an angle
- ACOTH() Returns the hyperbolic cotangent of an angle
- Refactored Complex Engineering Functions to use external complex number library
- Added calculation engine support for the new complex number functions that were added in MS Excel 2013
- IMCOSH() Returns the hyperbolic cosine of a complex number
- IMCOT() Returns the cotangent of a complex number
- IMCSC() Returns the cosecant of a complex number
- IMCSCH() Returns the hyperbolic cosecant of a complex number
- IMSEC() Returns the secant of a complex number
- IMSECH() Returns the hyperbolic secant of a complex number
- IMSINH() Returns the hyperbolic sine of a complex number
- IMTAN() Returns the tangent of a complex number
### Fixed
- Fix ISFORMULA() function to work with a cell reference to another worksheet
- Xlsx reader crashed when reading a file with workbook protection [#553](https://github.com/PHPOffice/PhpSpreadsheet/pull/553)
- Cell formats with escaped spaces were causing incorrect date formatting [#557](https://github.com/PHPOffice/PhpSpreadsheet/issues/557)
- Could not open CSV file containing HTML fragment [#564](https://github.com/PHPOffice/PhpSpreadsheet/issues/564)
- Exclude the vendor folder in migration [#481](https://github.com/PHPOffice/PhpSpreadsheet/issues/481)
- Chained operations on cell ranges involving borders operated on last cell only [#428](https://github.com/PHPOffice/PhpSpreadsheet/issues/428)
- Avoid memory exhaustion when cloning worksheet with a drawing [#437](https://github.com/PHPOffice/PhpSpreadsheet/issues/437)
- Migration tool keep variables containing $PHPExcel untouched [#598](https://github.com/PHPOffice/PhpSpreadsheet/issues/598)
- Rowspans/colspans were incorrect when adding worksheet using loadIntoExisting [#619](https://github.com/PHPOffice/PhpSpreadsheet/issues/619)
## [1.3.1] - 2018-06-12
### Fixed
- Ranges across Z and AA columns incorrectly threw an exception [#545](https://github.com/PHPOffice/PhpSpreadsheet/issues/545)
## [1.3.0] - 2018-06-10
### Added
- Support to read Xlsm templates with form elements, macros, printer settings, protected elements and back compatibility drawing, and save result without losing important elements of document [#435](https://github.com/PHPOffice/PhpSpreadsheet/issues/435)
- Expose sheet title maximum length as `Worksheet::SHEET_TITLE_MAXIMUM_LENGTH` [#482](https://github.com/PHPOffice/PhpSpreadsheet/issues/482)
- Allow escape character to be set in CSV reader [#492](https://github.com/PHPOffice/PhpSpreadsheet/issues/492)
### Fixed
- Subtotal 9 in a group that has other subtotals 9 exclude the totals of the other subtotals in the range [#332](https://github.com/PHPOffice/PhpSpreadsheet/issues/332)
- `Helper\Html` support UTF-8 HTML input [#444](https://github.com/PHPOffice/PhpSpreadsheet/issues/444)
- Xlsx loaded an extra empty comment for each real comment [#375](https://github.com/PHPOffice/PhpSpreadsheet/issues/375)
- Xlsx reader do not read rows and columns filtered out in readFilter at all [#370](https://github.com/PHPOffice/PhpSpreadsheet/issues/370)
- Make newer Excel versions properly recalculate formulas on document open [#456](https://github.com/PHPOffice/PhpSpreadsheet/issues/456)
- `Coordinate::extractAllCellReferencesInRange()` throws an exception for an invalid range [#519](https://github.com/PHPOffice/PhpSpreadsheet/issues/519)
- Fixed parsing of conditionals in COUNTIF functions [#526](https://github.com/PHPOffice/PhpSpreadsheet/issues/526)
- Corruption errors for saved Xlsx docs with frozen panes [#532](https://github.com/PHPOffice/PhpSpreadsheet/issues/532)
## [1.2.1] - 2018-04-10
### Fixed
- Plain text and richtext mixed in same cell can be read [#442](https://github.com/PHPOffice/PhpSpreadsheet/issues/442)
## [1.2.0] - 2018-03-04
### Added
- HTML writer creates a generator meta tag [#312](https://github.com/PHPOffice/PhpSpreadsheet/issues/312)
- Support invalid zoom value in XLSX format [#350](https://github.com/PHPOffice/PhpSpreadsheet/pull/350)
- Support for `_xlfn.` prefixed functions and `ISFORMULA`, `MODE.SNGL`, `STDEV.S`, `STDEV.P` [#390](https://github.com/PHPOffice/PhpSpreadsheet/pull/390)
### Fixed
- Avoid potentially unsupported PSR-16 cache keys [#354](https://github.com/PHPOffice/PhpSpreadsheet/issues/354)
- Check for MIME type to know if CSV reader can read a file [#167](https://github.com/PHPOffice/PhpSpreadsheet/issues/167)
- Use proper € symbol for currency format [#379](https://github.com/PHPOffice/PhpSpreadsheet/pull/379)
- Read printing area correctly when skipping some sheets [#371](https://github.com/PHPOffice/PhpSpreadsheet/issues/371)
- Avoid incorrectly overwriting calculated value type [#394](https://github.com/PHPOffice/PhpSpreadsheet/issues/394)
- Select correct cell when calling freezePane [#389](https://github.com/PHPOffice/PhpSpreadsheet/issues/389)
- `setStrikethrough()` did not set the font [#403](https://github.com/PHPOffice/PhpSpreadsheet/issues/403)
## [1.1.0] - 2018-01-28
### Added
- Support for PHP 7.2
- Support cell comments in HTML writer and reader [#308](https://github.com/PHPOffice/PhpSpreadsheet/issues/308)
- Option to stop at a conditional styling, if it matches (only XLSX format) [#292](https://github.com/PHPOffice/PhpSpreadsheet/pull/292)
- Support for line width for data series when rendering Xlsx [#329](https://github.com/PHPOffice/PhpSpreadsheet/pull/329)
### Fixed
- Better auto-detection of CSV separators [#305](https://github.com/PHPOffice/PhpSpreadsheet/issues/305)
- Support for shape style ending with `;` [#304](https://github.com/PHPOffice/PhpSpreadsheet/issues/304)
- Freeze Panes takes wrong coordinates for XLSX [#322](https://github.com/PHPOffice/PhpSpreadsheet/issues/322)
- `COLUMNS` and `ROWS` functions crashed in some cases [#336](https://github.com/PHPOffice/PhpSpreadsheet/issues/336)
- Support XML file without styles [#331](https://github.com/PHPOffice/PhpSpreadsheet/pull/331)
- Cell coordinates which are already a range cause an exception [#319](https://github.com/PHPOffice/PhpSpreadsheet/issues/319)
## [1.0.0] - 2017-12-25
### Added
- Support to write merged cells in ODS format [#287](https://github.com/PHPOffice/PhpSpreadsheet/issues/287)
- Able to set the `topLeftCell` in freeze panes [#261](https://github.com/PHPOffice/PhpSpreadsheet/pull/261)
- Support `DateTimeImmutable` as cell value
- Support migration of prefixed classes
### Fixed
- Can read very small HTML files [#194](https://github.com/PHPOffice/PhpSpreadsheet/issues/194)
- Written DataValidation was corrupted [#290](https://github.com/PHPOffice/PhpSpreadsheet/issues/290)
- Date format compatible with both LibreOffice and Excel [#298](https://github.com/PHPOffice/PhpSpreadsheet/issues/298)
### BREAKING CHANGE
- Constant `TYPE_DOUGHTNUTCHART` is now `TYPE_DOUGHNUTCHART`.
## [1.0.0-beta2] - 2017-11-26
### Added
- Support for chart fill color - @CrazyBite [#158](https://github.com/PHPOffice/PhpSpreadsheet/pull/158)
- Support for read Hyperlink for xml - @GreatHumorist [#223](https://github.com/PHPOffice/PhpSpreadsheet/pull/223)
- Support for cell value validation according to data validation rules - @SailorMax [#257](https://github.com/PHPOffice/PhpSpreadsheet/pull/257)
- Support for custom implementation, or configuration, of PDF libraries - @SailorMax [#266](https://github.com/PHPOffice/PhpSpreadsheet/pull/266)
### Changed
- Merge data-validations to reduce written worksheet size - @billblume [#131](https://github.com/PHPOffice/PhpSpreadSheet/issues/131)
- Throws exception if a XML file is invalid - @GreatHumorist [#222](https://github.com/PHPOffice/PhpSpreadsheet/pull/222)
- Upgrade to mPDF 7.0+ [#144](https://github.com/PHPOffice/PhpSpreadsheet/issues/144)
### Fixed
- Control characters in cell values are automatically escaped [#212](https://github.com/PHPOffice/PhpSpreadsheet/issues/212)
- Prevent color changing when copy/pasting xls files written by PhpSpreadsheet to another file - @al-lala [#218](https://github.com/PHPOffice/PhpSpreadsheet/issues/218)
- Add cell reference automatic when there is no cell reference('r' attribute) in Xlsx file. - @GreatHumorist [#225](https://github.com/PHPOffice/PhpSpreadsheet/pull/225) Refer to [#201](https://github.com/PHPOffice/PhpSpreadsheet/issues/201)
- `Reader\Xlsx::getFromZipArchive()` function return false if the zip entry could not be located. - @anton-harvey [#268](https://github.com/PHPOffice/PhpSpreadsheet/pull/268)
### BREAKING CHANGE
- Extracted coordinate method to dedicate class [migration guide](./docs/topics/migration-from-PHPExcel.md).
- Column indexes are based on 1, see the [migration guide](./docs/topics/migration-from-PHPExcel.md).
- Standardization of array keys used for style, see the [migration guide](./docs/topics/migration-from-PHPExcel.md).
- Easier usage of PDF writers, and other custom readers and writers, see the [migration guide](./docs/topics/migration-from-PHPExcel.md).
- Easier usage of chart renderers, see the [migration guide](./docs/topics/migration-from-PHPExcel.md).
- Rename a few more classes to keep them in their related namespaces:
- `CalcEngine` => `Calculation\Engine`
- `PhpSpreadsheet\Calculation` => `PhpSpreadsheet\Calculation\Calculation`
- `PhpSpreadsheet\Cell` => `PhpSpreadsheet\Cell\Cell`
- `PhpSpreadsheet\Chart` => `PhpSpreadsheet\Chart\Chart`
- `PhpSpreadsheet\RichText` => `PhpSpreadsheet\RichText\RichText`
- `PhpSpreadsheet\Style` => `PhpSpreadsheet\Style\Style`
- `PhpSpreadsheet\Worksheet` => `PhpSpreadsheet\Worksheet\Worksheet`
## [1.0.0-beta] - 2017-08-17
### Added
- Initial implementation of SUMIFS() function
- Additional codepages
- MemoryDrawing not working in HTML writer [#808](https://github.com/PHPOffice/PHPExcel/issues/808)
- CSV Reader can auto-detect the separator used in file [#141](https://github.com/PHPOffice/PhpSpreadsheet/pull/141)
- HTML Reader supports some basic inline styles [#180](https://github.com/PHPOffice/PhpSpreadsheet/pull/180)
### Changed
- Start following [SemVer](https://semver.org) properly.
### Fixed
- Fix to getCell() method when cell reference includes a worksheet reference - @MarkBaker
- Ignore inlineStr type if formula element exists - @ncrypthic [#570](https://github.com/PHPOffice/PHPExcel/issues/570)
- Excel 2007 Reader freezes because of conditional formatting - @rentalhost [#575](https://github.com/PHPOffice/PHPExcel/issues/575)
- Readers will now parse files containing worksheet titles over 31 characters [#176](https://github.com/PHPOffice/PhpSpreadsheet/pull/176)
### General
- Whitespace after toRichTextObject() - @MarkBaker [#554](https://github.com/PHPOffice/PHPExcel/issues/554)
- Optimize vlookup() sort - @umpirsky [#548](https://github.com/PHPOffice/PHPExcel/issues/548)
- c:max and c:min elements shall NOT be inside c:orientation elements - @vitalyrepin [#869](https://github.com/PHPOffice/PHPExcel/pull/869)
- Implement actual timezone adjustment into PHPExcel_Shared_Date::PHPToExcel - @sim642 [#489](https://github.com/PHPOffice/PHPExcel/pull/489)
### BREAKING CHANGE
- Introduction of namespaces for all classes, eg: `PHPExcel_Calculation_Functions` becomes `PhpOffice\PhpSpreadsheet\Calculation\Functions`
- Some classes were renamed for clarity and/or consistency:
For a comprehensive list of all class changes, and a semi-automated migration path, read the [migration guide](./docs/topics/migration-from-PHPExcel.md).
- Dropped `PHPExcel_Calculation_Functions::VERSION()`. Composer or git should be used to know the version.
- Dropped `PHPExcel_Settings::setPdfRenderer()` and `PHPExcel_Settings::setPdfRenderer()`. Composer should be used to autoload PDF libs.
- Dropped support for HHVM
## Previous versions of PHPExcel
The changelog for the project when it was called PHPExcel is [still available](./CHANGELOG.PHPExcel.md).

View File

@ -0,0 +1,11 @@
# Want to contribute?
If you would like to contribute, here are some notes and guidelines:
- All new development happens on feature/fix branches, and are then merged to the `master` branch once stable; so the `master` branch is always the most up-to-date, working code
- Tagged releases are made from the `master` branch
- If you are going to be submitting a pull request, please fork from `master`, and submit your pull request back as a fix/feature branch referencing the GitHub issue number
- Code style might be automatically fixed by `composer fix`
- All code changes must be validated by `composer check`
- [Helpful article about forking](https://help.github.com/articles/fork-a-repo/ "Forking a GitHub repository")
- [Helpful article about pull requests](https://help.github.com/articles/using-pull-requests/ "Pull Requests")

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 PhpSpreadsheet Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,86 @@
{
"name": "phpoffice/phpspreadsheet",
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"keywords": ["PHP", "OpenXML", "Excel", "xlsx", "xls", "ods", "gnumeric", "spreadsheet"],
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "https://blog.maartenballiauw.be"
},
{
"name": "Mark Baker",
"homepage": "https://markbakeruk.net"
},
{
"name": "Franck Lefevre",
"homepage": "https://rootslabs.net"
},
{
"name": "Erik Tilt"
},
{
"name": "Adrien Crivelli"
}
],
"scripts": {
"check": [
"php-cs-fixer fix --ansi --dry-run --diff",
"phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PSR2 -n",
"phpunit --color=always"
],
"fix": [
"php-cs-fixer fix --ansi"
],
"versions": [
"phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PHPCompatibility --runtime-set testVersion 7.1- -n"
]
},
"require": {
"php": "^7.1",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-fileinfo": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-SimpleXML": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"markbaker/complex": "^1.4",
"markbaker/matrix": "^1.2",
"psr/simple-cache": "^1.0"
},
"require-dev": {
"dompdf/dompdf": "^0.8.3",
"friendsofphp/php-cs-fixer": "^2.16",
"jpgraph/jpgraph": "^4.0",
"mpdf/mpdf": "^8.0",
"phpcompatibility/php-compatibility": "^9.3",
"phpunit/phpunit": "^7.5",
"squizlabs/php_codesniffer": "^3.5",
"tecnickcom/tcpdf": "^6.3"
},
"suggest": {
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer",
"jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers"
},
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"autoload-dev": {
"psr-4": {
"PhpOffice\\PhpSpreadsheetTests\\": "tests/PhpSpreadsheetTests"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
site_name: PhpSpreadsheet Documentation
repo_url: https://github.com/PHPOffice/phpspreadsheet
edit_uri: edit/master/docs/
theme: readthedocs
extra_css:
- extra/extra.css

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.5/phpunit.xsd"
bootstrap="./tests/bootstrap.php"
backupGlobals="true"
colors="true">
<php>
<ini name="memory_limit" value="2048M"/>
</php>
<testsuite name="PhpSpreadsheet Unit Test Suite">
<directory suffix="Test.php">./tests/PhpSpreadsheetTests</directory>
</testsuite>
<filter>
<whitelist>
<directory suffix=".php">./src</directory>
<exclude>
<directory>./src/PhpSpreadsheet/Shared/JAMA</directory>
<directory>./src/PhpSpreadsheet/Writer/PDF</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,22 @@
<?php
/**
* Bootstrap for PhpSpreadsheet classes.
*/
// This sucks, but we have to try to find the composer autoloader
$paths = [
__DIR__ . '/../vendor/autoload.php', // In case PhpSpreadsheet is cloned directly
__DIR__ . '/../../../autoload.php', // In case PhpSpreadsheet is a composer dependency.
];
foreach ($paths as $path) {
if (file_exists($path)) {
require_once $path;
return;
}
}
throw new \Exception('Composer autoloader could not be found. Install dependencies with `composer install` and try again.');

View File

@ -25,7 +25,7 @@ class Calculation
// Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it)
const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?([A-Z][A-Z0-9\.]*)[\s]*\(';
// Cell reference (cell or range of cells, with or without a sheet reference)
const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?([a-z]{1,3})\$?(\d{1,7})';
const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.])';
// Named Range of cells
const CALCULATION_REGEXP_NAMEDRANGE = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?([_A-Z][_A-Z0-9\.]*)';
// Error
@ -66,6 +66,15 @@ class Calculation
*/
private $calculationCacheEnabled = true;
/**
* Used to generate unique store keys.
*
* @var int
*/
private $branchStoreKeyCounter = 0;
private $branchPruningEnabled = true;
/**
* List of operators that can be used within formulae
* The true/false value indicates whether it is a binary operator or a unary operator.
@ -254,6 +263,11 @@ class Calculation
'functionCall' => [Logical::class, 'logicalAnd'],
'argumentCount' => '1+',
],
'ARABIC' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig::class, 'ARABIC'],
'argumentCount' => '1',
],
'AREAS' => [
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
'functionCall' => [Functions::class, 'DUMMY'],
@ -319,6 +333,11 @@ class Calculation
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '1',
],
'BASE' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig::class, 'BASE'],
'argumentCount' => '2,3',
],
'BESSELI' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'BESSELI'],
@ -527,7 +546,7 @@ class Calculation
],
'COUNTIFS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'functionCall' => [Statistical::class, 'COUNTIFS'],
'argumentCount' => '2+',
],
'COUPDAYBS' => [
@ -895,6 +914,16 @@ class Calculation
'functionCall' => [MathTrig::class, 'FLOOR'],
'argumentCount' => '2',
],
'FLOOR.MATH' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig::class, 'FLOORMATH'],
'argumentCount' => '3',
],
'FLOOR.PRECISE' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
'functionCall' => [MathTrig::class, 'FLOORPRECISE'],
'argumentCount' => '2',
],
'FORECAST' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical::class, 'FORECAST'],
@ -1018,6 +1047,16 @@ class Calculation
'functionCall' => [Logical::class, 'IFERROR'],
'argumentCount' => '2',
],
'IFNA' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical::class, 'IFNA'],
'argumentCount' => '2',
],
'IFS' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2+',
],
'IMABS' => [
'category' => Category::CATEGORY_ENGINEERING,
'functionCall' => [Engineering::class, 'IMABS'],
@ -1356,10 +1395,10 @@ class Calculation
'functionCall' => [Statistical::class, 'MAXA'],
'argumentCount' => '1+',
],
'MAXIF' => [
'MAXIFS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical::class, 'MAXIF'],
'argumentCount' => '2+',
'functionCall' => [Statistical::class, 'MAXIFS'],
'argumentCount' => '3+',
],
'MDETERM' => [
'category' => Category::CATEGORY_MATH_AND_TRIG,
@ -1401,10 +1440,10 @@ class Calculation
'functionCall' => [Statistical::class, 'MINA'],
'argumentCount' => '1+',
],
'MINIF' => [
'MINIFS' => [
'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical::class, 'MINIF'],
'argumentCount' => '2+',
'functionCall' => [Statistical::class, 'MINIFS'],
'argumentCount' => '3+',
],
'MINUTE' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
@ -1474,7 +1513,12 @@ class Calculation
'NETWORKDAYS' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTime::class, 'NETWORKDAYS'],
'argumentCount' => '2+',
'argumentCount' => '2-3',
],
'NETWORKDAYS.INTL' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2-4',
],
'NOMINAL' => [
'category' => Category::CATEGORY_FINANCIAL,
@ -2143,7 +2187,12 @@ class Calculation
'WORKDAY' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [DateTime::class, 'WORKDAY'],
'argumentCount' => '2+',
'argumentCount' => '2-3',
],
'WORKDAY.INTL' => [
'category' => Category::CATEGORY_DATE_AND_TIME,
'functionCall' => [Functions::class, 'DUMMY'],
'argumentCount' => '2-4',
],
'XIRR' => [
'category' => Category::CATEGORY_FINANCIAL,
@ -2196,7 +2245,7 @@ class Calculation
private static $controlFunctions = [
'MKMATRIX' => [
'argumentCount' => '*',
'functionCall' => 'self::mkMatrix',
'functionCall' => [__CLASS__, 'mkMatrix'],
],
];
@ -2251,6 +2300,7 @@ class Calculation
public function flushInstance()
{
$this->clearCalculationCache();
$this->clearBranchStore();
}
/**
@ -2394,6 +2444,32 @@ class Calculation
}
}
/**
* Enable/disable calculation cache.
*
* @param bool $pValue
* @param mixed $enabled
*/
public function setBranchPruningEnabled($enabled)
{
$this->branchPruningEnabled = $enabled;
}
public function enableBranchPruning()
{
$this->setBranchPruningEnabled(true);
}
public function disableBranchPruning()
{
$this->setBranchPruningEnabled(false);
}
public function clearBranchStore()
{
$this->branchStoreKeyCounter = 0;
}
/**
* Get the currently defined locale code.
*
@ -2416,7 +2492,7 @@ class Calculation
// Identify our locale and language
$language = $locale = strtolower($locale);
if (strpos($locale, '_') !== false) {
list($language) = explode('_', $locale);
[$language] = explode('_', $locale);
}
if (count(self::$validLocaleLanguages) == 1) {
self::loadLocales();
@ -2441,9 +2517,9 @@ class Calculation
// Retrieve the list of locale or language specific function names
$localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($localeFunctions as $localeFunction) {
list($localeFunction) = explode('##', $localeFunction); // Strip out comments
[$localeFunction] = explode('##', $localeFunction); // Strip out comments
if (strpos($localeFunction, '=') !== false) {
list($fName, $lfName) = explode('=', $localeFunction);
[$fName, $lfName] = explode('=', $localeFunction);
$fName = trim($fName);
$lfName = trim($lfName);
if ((isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) {
@ -2466,9 +2542,9 @@ class Calculation
if (file_exists($configFile)) {
$localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($localeSettings as $localeSetting) {
list($localeSetting) = explode('##', $localeSetting); // Strip out comments
[$localeSetting] = explode('##', $localeSetting); // Strip out comments
if (strpos($localeSetting, '=') !== false) {
list($settingName, $settingValue) = explode('=', $localeSetting);
[$settingName, $settingValue] = explode('=', $localeSetting);
$settingName = strtoupper(trim($settingName));
switch ($settingName) {
case 'ARGUMENTSEPARATOR':
@ -2772,7 +2848,7 @@ class Calculation
}
self::$returnArrayAsType = $returnArrayAsType;
if ($result === null) {
if ($result === null && $pCell->getWorksheet()->getSheetView()->getShowZeros()) {
return 0;
} elseif ((is_float($result)) && ((is_nan($result)) || (is_infinite($result)))) {
return Functions::NAN();
@ -2823,13 +2899,13 @@ class Calculation
$this->debugLog->clearLog();
$this->cyclicReferenceStack->clear();
$resetCache = $this->getCalculationCacheEnabled();
if ($this->spreadsheet !== null && $cellID === null && $pCell === null) {
$cellID = 'A1';
$pCell = $this->spreadsheet->getActiveSheet()->getCell($cellID);
} else {
// Disable calculation cacheing because it only applies to cell calculations, not straight formulae
// But don't actually flush any cache
$resetCache = $this->getCalculationCacheEnabled();
$this->calculationCacheEnabled = false;
}
@ -2862,6 +2938,7 @@ class Calculation
if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) {
$this->debugLog->writeDebugLog('Retrieving value for cell ', $cellReference, ' from cache');
// Return the cached result
$cellValue = $this->calculationCache[$cellReference];
return true;
@ -2977,17 +3054,17 @@ class Calculation
// Examine each of the two operands, and turn them into an array if they aren't one already
// Note that this function should only be called if one or both of the operand is already an array
if (!is_array($operand1)) {
list($matrixRows, $matrixColumns) = self::getMatrixDimensions($operand2);
[$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand2);
$operand1 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand1));
$resize = 0;
} elseif (!is_array($operand2)) {
list($matrixRows, $matrixColumns) = self::getMatrixDimensions($operand1);
[$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand1);
$operand2 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand2));
$resize = 0;
}
list($matrix1Rows, $matrix1Columns) = self::getMatrixDimensions($operand1);
list($matrix2Rows, $matrix2Columns) = self::getMatrixDimensions($operand2);
[$matrix1Rows, $matrix1Columns] = self::getMatrixDimensions($operand1);
[$matrix2Rows, $matrix2Columns] = self::getMatrixDimensions($operand2);
if (($matrix1Rows == $matrix2Columns) && ($matrix2Rows == $matrix1Columns)) {
$resize = 1;
}
@ -3197,7 +3274,7 @@ class Calculation
/**
* @param string $formula
*
* @return string
* @return false|string False indicates an error
*/
private function convertMatrixReferences($formula)
{
@ -3321,9 +3398,53 @@ class Calculation
// - is a negation or + is a positive operator rather than an operation
$expectingOperand = false; // We use this test in syntax-checking the expression to determine whether an operand
// should be null in a function call
// IF branch pruning
// currently pending storeKey (last item of the storeKeysStack
$pendingStoreKey = null;
// stores a list of storeKeys (string[])
$pendingStoreKeysStack = [];
$expectingConditionMap = []; // ['storeKey' => true, ...]
$expectingThenMap = []; // ['storeKey' => true, ...]
$expectingElseMap = []; // ['storeKey' => true, ...]
$parenthesisDepthMap = []; // ['storeKey' => 4, ...]
// The guts of the lexical parser
// Loop through the formula extracting each operator and operand in turn
while (true) {
// Branch pruning: we adapt the output item to the context (it will
// be used to limit its computation)
$currentCondition = null;
$currentOnlyIf = null;
$currentOnlyIfNot = null;
$previousStoreKey = null;
$pendingStoreKey = end($pendingStoreKeysStack);
if ($this->branchPruningEnabled) {
// this is a condition ?
if (isset($expectingConditionMap[$pendingStoreKey]) && $expectingConditionMap[$pendingStoreKey]) {
$currentCondition = $pendingStoreKey;
$stackDepth = count($pendingStoreKeysStack);
if ($stackDepth > 1) { // nested if
$previousStoreKey = $pendingStoreKeysStack[$stackDepth - 2];
}
}
if (isset($expectingThenMap[$pendingStoreKey]) && $expectingThenMap[$pendingStoreKey]) {
$currentOnlyIf = $pendingStoreKey;
} elseif (isset($previousStoreKey)) {
if (isset($expectingThenMap[$previousStoreKey]) && $expectingThenMap[$previousStoreKey]) {
$currentOnlyIf = $previousStoreKey;
}
}
if (isset($expectingElseMap[$pendingStoreKey]) && $expectingElseMap[$pendingStoreKey]) {
$currentOnlyIfNot = $pendingStoreKey;
} elseif (isset($previousStoreKey)) {
if (isset($expectingElseMap[$previousStoreKey]) && $expectingElseMap[$previousStoreKey]) {
$currentOnlyIfNot = $previousStoreKey;
}
}
}
$opCharacter = $formula[$index]; // Get the first character of the value at the current index position
if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$comparisonOperators[$formula[$index + 1]]))) {
$opCharacter .= $formula[++$index];
@ -3333,10 +3454,12 @@ class Calculation
$isOperandOrFunction = preg_match($regexpMatchString, substr($formula, $index), $match);
if ($opCharacter == '-' && !$expectingOperator) { // Is it a negation instead of a minus?
$stack->push('Unary Operator', '~'); // Put a negation on the stack
// Put a negation on the stack
$stack->push('Unary Operator', '~', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
++$index; // and drop the negation symbol
} elseif ($opCharacter == '%' && $expectingOperator) {
$stack->push('Unary Operator', '%'); // Put a percentage on the stack
// Put a percentage on the stack
$stack->push('Unary Operator', '%', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
++$index;
} elseif ($opCharacter == '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded?
++$index; // Drop the redundant plus symbol
@ -3349,7 +3472,10 @@ class Calculation
@(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])) {
$output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
}
$stack->push('Binary Operator', $opCharacter); // Finally put our current operator onto the stack
// Finally put our current operator onto the stack
$stack->push('Binary Operator', $opCharacter, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
++$index;
$expectingOperator = false;
} elseif ($opCharacter == ')' && $expectingOperator) { // Are we expecting to close a parenthesis?
@ -3361,7 +3487,29 @@ class Calculation
$output[] = $o2;
}
$d = $stack->last(2);
if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) { // Did this parenthesis just close a function?
// Branch pruning we decrease the depth whether is it a function
// call or a parenthesis
if (!empty($pendingStoreKey)) {
$parenthesisDepthMap[$pendingStoreKey] -= 1;
}
if (is_array($d) && preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) { // Did this parenthesis just close a function?
if (!empty($pendingStoreKey) && $parenthesisDepthMap[$pendingStoreKey] == -1) {
// we are closing an IF(
if ($d['value'] != 'IF(') {
return $this->raiseFormulaError('Parser bug we should be in an "IF("');
}
if ($expectingConditionMap[$pendingStoreKey]) {
return $this->raiseFormulaError('We should not be expecting a condition');
}
$expectingThenMap[$pendingStoreKey] = false;
$expectingElseMap[$pendingStoreKey] = false;
$parenthesisDepthMap[$pendingStoreKey] -= 1;
array_pop($pendingStoreKeysStack);
unset($pendingStoreKey);
}
$functionName = $matches[1]; // Get the function name
$d = $stack->pop();
$argumentCount = $d['value']; // See how many arguments there were (argument count is the next value stored on the stack)
@ -3422,6 +3570,20 @@ class Calculation
}
++$index;
} elseif ($opCharacter == ',') { // Is this the separator for function arguments?
if (!empty($pendingStoreKey) &&
$parenthesisDepthMap[$pendingStoreKey] == 0
) {
// We must go to the IF next argument
if ($expectingConditionMap[$pendingStoreKey]) {
$expectingConditionMap[$pendingStoreKey] = false;
$expectingThenMap[$pendingStoreKey] = true;
} elseif ($expectingThenMap[$pendingStoreKey]) {
$expectingThenMap[$pendingStoreKey] = false;
$expectingElseMap[$pendingStoreKey] = true;
} elseif ($expectingElseMap[$pendingStoreKey]) {
return $this->raiseFormulaError('Reaching fourth argument of an IF');
}
}
while (($o2 = $stack->pop()) && $o2['value'] != '(') { // Pop off the stack back to the last (
if ($o2 === null) {
return $this->raiseFormulaError('Formula Error: Unexpected ,');
@ -3439,13 +3601,19 @@ class Calculation
return $this->raiseFormulaError('Formula Error: Unexpected ,');
}
$d = $stack->pop();
$stack->push($d['type'], ++$d['value'], $d['reference']); // increment the argument count
$stack->push('Brace', '('); // put the ( back on, we'll need to pop back to it again
$itemStoreKey = $d['storeKey'] ?? null;
$itemOnlyIf = $d['onlyIf'] ?? null;
$itemOnlyIfNot = $d['onlyIfNot'] ?? null;
$stack->push($d['type'], ++$d['value'], $d['reference'], $itemStoreKey, $itemOnlyIf, $itemOnlyIfNot); // increment the argument count
$stack->push('Brace', '(', null, $itemStoreKey, $itemOnlyIf, $itemOnlyIfNot); // put the ( back on, we'll need to pop back to it again
$expectingOperator = false;
$expectingOperand = true;
++$index;
} elseif ($opCharacter == '(' && !$expectingOperator) {
$stack->push('Brace', '(');
if (!empty($pendingStoreKey)) { // Branch pruning: we go deeper
$parenthesisDepthMap[$pendingStoreKey] += 1;
}
$stack->push('Brace', '(', null, $currentCondition, $currentOnlyIf, $currentOnlyIf);
++$index;
} elseif ($isOperandOrFunction && !$expectingOperator) { // do we now have a function/variable/number?
$expectingOperator = true;
@ -3456,13 +3624,28 @@ class Calculation
if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $val, $matches)) {
$val = preg_replace('/\s/u', '', $val);
if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) { // it's a function
$stack->push('Function', strtoupper($val));
$valToUpper = strtoupper($val);
// here $matches[1] will contain values like "IF"
// and $val "IF("
if ($this->branchPruningEnabled && ($valToUpper == 'IF(')) { // we handle a new if
$pendingStoreKey = $this->getUnusedBranchStoreKey();
$pendingStoreKeysStack[] = $pendingStoreKey;
$expectingConditionMap[$pendingStoreKey] = true;
$parenthesisDepthMap[$pendingStoreKey] = 0;
} else { // this is not a if but we good deeper
if (!empty($pendingStoreKey) && array_key_exists($pendingStoreKey, $parenthesisDepthMap)) {
$parenthesisDepthMap[$pendingStoreKey] += 1;
}
}
$stack->push('Function', $valToUpper, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
// tests if the function is closed right after opening
$ax = preg_match('/^\s*(\s*\))/ui', substr($formula, $index + $length), $amatch);
if ($ax) {
$stack->push('Operand Count for Function ' . strtoupper($val) . ')', 0);
$stack->push('Operand Count for Function ' . $valToUpper . ')', 0, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
$expectingOperator = true;
} else {
$stack->push('Operand Count for Function ' . strtoupper($val) . ')', 1);
$stack->push('Operand Count for Function ' . $valToUpper . ')', 1, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
$expectingOperator = false;
}
$stack->push('Brace', '(');
@ -3475,7 +3658,7 @@ class Calculation
// If the last entry on the stack was a : operator, then we have a cell range reference
$testPrevOp = $stack->last(1);
if ($testPrevOp['value'] == ':') {
if ($testPrevOp !== null && $testPrevOp['value'] == ':') {
// If we have a worksheet reference, then we're playing with a 3D reference
if ($matches[2] == '') {
// Otherwise, we 'inherit' the worksheet reference from the start cell reference
@ -3490,32 +3673,39 @@ class Calculation
}
}
$output[] = ['type' => 'Cell Reference', 'value' => $val, 'reference' => $val];
$outputItem = $stack->getStackItem('Cell Reference', $val, $val, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
$output[] = $outputItem;
} else { // it's a variable, constant, string, number or boolean
// If the last entry on the stack was a : operator, then we may have a row or column range reference
$testPrevOp = $stack->last(1);
if ($testPrevOp['value'] == ':') {
if ($testPrevOp !== null && $testPrevOp['value'] === ':') {
$startRowColRef = $output[count($output) - 1]['value'];
list($rangeWS1, $startRowColRef) = Worksheet::extractSheetTitle($startRowColRef, true);
[$rangeWS1, $startRowColRef] = Worksheet::extractSheetTitle($startRowColRef, true);
$rangeSheetRef = $rangeWS1;
if ($rangeWS1 != '') {
$rangeWS1 .= '!';
}
list($rangeWS2, $val) = Worksheet::extractSheetTitle($val, true);
[$rangeWS2, $val] = Worksheet::extractSheetTitle($val, true);
if ($rangeWS2 != '') {
$rangeWS2 .= '!';
} else {
$rangeWS2 = $rangeWS1;
}
$refSheet = $pCellParent;
if ($pCellParent !== null && $rangeSheetRef !== $pCellParent->getTitle()) {
$refSheet = $pCellParent->getParent()->getSheetByName($rangeSheetRef);
}
if ((is_int($startRowColRef)) && (ctype_digit($val)) &&
($startRowColRef <= 1048576) && ($val <= 1048576)) {
// Row range
$endRowColRef = ($pCellParent !== null) ? $pCellParent->getHighestColumn() : 'XFD'; // Max 16,384 columns for Excel2007
$endRowColRef = ($refSheet !== null) ? $refSheet->getHighestColumn() : 'XFD'; // Max 16,384 columns for Excel2007
$output[count($output) - 1]['value'] = $rangeWS1 . 'A' . $startRowColRef;
$val = $rangeWS2 . $endRowColRef . $val;
} elseif ((ctype_alpha($startRowColRef)) && (ctype_alpha($val)) &&
(strlen($startRowColRef) <= 3) && (strlen($val) <= 3)) {
// Column range
$endRowColRef = ($pCellParent !== null) ? $pCellParent->getHighestRow() : 1048576; // Max 1,048,576 rows for Excel2007
$endRowColRef = ($refSheet !== null) ? $refSheet->getHighestRow() : 1048576; // Max 1,048,576 rows for Excel2007
$output[count($output) - 1]['value'] = $rangeWS1 . strtoupper($startRowColRef) . '1';
$val = $rangeWS2 . $val . $endRowColRef;
}
@ -3537,7 +3727,7 @@ class Calculation
} elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) {
$val = self::$excelConstants[$localeConstant];
}
$details = ['type' => 'Value', 'value' => $val, 'reference' => null];
$details = $stack->getStackItem('Value', $val, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
if ($localeConstant) {
$details['localeValue'] = $localeConstant;
}
@ -3557,7 +3747,7 @@ class Calculation
} elseif (isset(self::$operators[$opCharacter]) && !$expectingOperator) {
return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'");
} else { // I don't even want to know what you did to get here
return $this->raiseFormulaError('Formula Error: An unexpected error occured');
return $this->raiseFormulaError('Formula Error: An unexpected error occurred');
}
// Test for end of formula string
if ($index == strlen($formula)) {
@ -3640,9 +3830,85 @@ class Calculation
$pCellParent = ($pCell !== null) ? $pCell->getParent() : null;
$stack = new Stack();
// Stores branches that have been pruned
$fakedForBranchPruning = [];
// help us to know when pruning ['branchTestId' => true/false]
$branchStore = [];
// Loop through each token in turn
foreach ($tokens as $tokenData) {
$token = $tokenData['value'];
// Branch pruning: skip useless resolutions
$storeKey = $tokenData['storeKey'] ?? null;
if ($this->branchPruningEnabled && isset($tokenData['onlyIf'])) {
$onlyIfStoreKey = $tokenData['onlyIf'];
$storeValue = $branchStore[$onlyIfStoreKey] ?? null;
$storeValueAsBool = ($storeValue === null) ?
true : (bool) Functions::flattenSingleValue($storeValue);
if (is_array($storeValue)) {
$wrappedItem = end($storeValue);
$storeValue = end($wrappedItem);
}
if (isset($storeValue)
&& (
!$storeValueAsBool
|| Functions::isError($storeValue)
|| ($storeValue === 'Pruned branch')
)
) {
// If branching value is not true, we don't need to compute
if (!isset($fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey])) {
$stack->push('Value', 'Pruned branch (only if ' . $onlyIfStoreKey . ') ' . $token);
$fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey] = true;
}
if (isset($storeKey)) {
// We are processing an if condition
// We cascade the pruning to the depending branches
$branchStore[$storeKey] = 'Pruned branch';
$fakedForBranchPruning['onlyIfNot-' . $storeKey] = true;
$fakedForBranchPruning['onlyIf-' . $storeKey] = true;
}
continue;
}
}
if ($this->branchPruningEnabled && isset($tokenData['onlyIfNot'])) {
$onlyIfNotStoreKey = $tokenData['onlyIfNot'];
$storeValue = $branchStore[$onlyIfNotStoreKey] ?? null;
$storeValueAsBool = ($storeValue === null) ?
true : (bool) Functions::flattenSingleValue($storeValue);
if (is_array($storeValue)) {
$wrappedItem = end($storeValue);
$storeValue = end($wrappedItem);
}
if (isset($storeValue)
&& (
$storeValueAsBool
|| Functions::isError($storeValue)
|| ($storeValue === 'Pruned branch'))
) {
// If branching value is true, we don't need to compute
if (!isset($fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey])) {
$stack->push('Value', 'Pruned branch (only if not ' . $onlyIfNotStoreKey . ') ' . $token);
$fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey] = true;
}
if (isset($storeKey)) {
// We are processing an if condition
// We cascade the pruning to the depending branches
$branchStore[$storeKey] = 'Pruned branch';
$fakedForBranchPruning['onlyIfNot-' . $storeKey] = true;
$fakedForBranchPruning['onlyIf-' . $storeKey] = true;
}
continue;
}
}
// if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
if (isset(self::$binaryOperators[$token])) {
// We must have two operands, error if we don't
@ -3672,18 +3938,21 @@ class Calculation
case '<=': // Less than or Equal to
case '=': // Equality
case '<>': // Inequality
$this->executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack);
$result = $this->executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
break;
// Binary Operators
case ':': // Range
if (strpos($operand1Data['reference'], '!') !== false) {
list($sheet1, $operand1Data['reference']) = Worksheet::extractSheetTitle($operand1Data['reference'], true);
[$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true);
} else {
$sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : '';
}
list($sheet2, $operand2Data['reference']) = Worksheet::extractSheetTitle($operand2Data['reference'], true);
[$sheet2, $operand2Data['reference']] = Worksheet::extractSheetTitle($operand2Data['reference'], true);
if (empty($sheet2)) {
$sheet2 = $sheet1;
}
@ -3728,23 +3997,38 @@ class Calculation
break;
case '+': // Addition
$this->executeNumericBinaryOperation($operand1, $operand2, $token, 'plusEquals', $stack);
$result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'plusEquals', $stack);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
break;
case '-': // Subtraction
$this->executeNumericBinaryOperation($operand1, $operand2, $token, 'minusEquals', $stack);
$result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'minusEquals', $stack);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
break;
case '*': // Multiplication
$this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayTimesEquals', $stack);
$result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayTimesEquals', $stack);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
break;
case '/': // Division
$this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayRightDivide', $stack);
$result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayRightDivide', $stack);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
break;
case '^': // Exponential
$this->executeNumericBinaryOperation($operand1, $operand2, $token, 'power', $stack);
$result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'power', $stack);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
break;
case '&': // Concatenation
@ -3777,6 +4061,10 @@ class Calculation
$this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
$stack->push('Value', $result);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
break;
case '|': // Intersect
$rowIntersect = array_intersect_key($operand1, $operand2);
@ -3821,6 +4109,9 @@ class Calculation
}
$this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
$stack->push('Value', $result);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
} else {
$this->executeNumericBinaryOperation($multiplier, $arg, '*', 'arrayTimesEquals', $stack);
}
@ -3894,9 +4185,16 @@ class Calculation
}
}
$stack->push('Value', $cellValue, $cellRef);
if (isset($storeKey)) {
$branchStore[$storeKey] = $cellValue;
}
// if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
// if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
} elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $token, $matches)) {
if ($pCellParent) {
$pCell->attach($pCellParent);
}
$functionName = $matches[1];
$argCount = $stack->pop();
$argCount = $argCount['value'];
@ -3939,6 +4237,7 @@ class Calculation
}
}
}
// Reverse the order of the arguments
krsort($args);
@ -3963,22 +4262,32 @@ class Calculation
}
unset($arg);
}
$result = call_user_func_array($functionCall, $args);
if ($functionName != 'MKMATRIX') {
$this->debugLog->writeDebugLog('Evaluation Result for ', self::localeFunc($functionName), '() function call is ', $this->showTypeDetails($result));
}
$stack->push('Value', self::wrapResult($result));
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
}
} else {
// if the token is a number, boolean, string or an Excel error, push it onto the stack
if (isset(self::$excelConstants[strtoupper($token)])) {
$excelConstant = strtoupper($token);
$stack->push('Constant Value', self::$excelConstants[$excelConstant]);
if (isset($storeKey)) {
$branchStore[$storeKey] = self::$excelConstants[$excelConstant];
}
$this->debugLog->writeDebugLog('Evaluating Constant ', $excelConstant, ' as ', $this->showTypeDetails(self::$excelConstants[$excelConstant]));
} elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == '"') || ($token[0] == '#')) {
$stack->push('Value', $token);
// if the token is a named range, push the named range name onto the stack
if (isset($storeKey)) {
$branchStore[$storeKey] = $token;
}
// if the token is a named range, push the named range name onto the stack
} elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $token, $matches)) {
$namedRange = $matches[6];
$this->debugLog->writeDebugLog('Evaluating Named Range ', $namedRange);
@ -3987,6 +4296,9 @@ class Calculation
$pCell->attach($pCellParent);
$this->debugLog->writeDebugLog('Evaluation Result for named range ', $namedRange, ' is ', $this->showTypeDetails($cellValue));
$stack->push('Named Range', $cellValue, $namedRange);
if (isset($storeKey)) {
$branchStore[$storeKey] = $cellValue;
}
} else {
return $this->raiseFormulaError("undefined variable '$token'");
}
@ -4048,7 +4360,7 @@ class Calculation
* @param Stack $stack
* @param bool $recursingArrays
*
* @return bool
* @return mixed
*/
private function executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, Stack &$stack, $recursingArrays = false)
{
@ -4085,7 +4397,7 @@ class Calculation
// And push the result onto the stack
$stack->push('Array', $result);
return true;
return $result;
}
// Simple validate the two operands if they are string values
@ -4175,7 +4487,7 @@ class Calculation
// And push the result onto the stack
$stack->push('Value', $result);
return true;
return $result;
}
/**
@ -4201,7 +4513,7 @@ class Calculation
* @param string $matrixFunction
* @param mixed $stack
*
* @return bool
* @return bool|mixed
*/
private function executeNumericBinaryOperation($operand1, $operand2, $operation, $matrixFunction, &$stack)
{
@ -4279,7 +4591,7 @@ class Calculation
// And push the result onto the stack
$stack->push('Value', $result);
return true;
return $result;
}
// trigger an error, but nicely, if need be
@ -4312,7 +4624,7 @@ class Calculation
if ($pSheet !== null) {
$pSheetName = $pSheet->getTitle();
if (strpos($pRange, '!') !== false) {
list($pSheetName, $pRange) = Worksheet::extractSheetTitle($pRange, true);
[$pSheetName, $pRange] = Worksheet::extractSheetTitle($pRange, true);
$pSheet = $this->spreadsheet->getSheetByName($pSheetName);
}
@ -4365,7 +4677,7 @@ class Calculation
if ($pSheet !== null) {
$pSheetName = $pSheet->getTitle();
if (strpos($pRange, '!') !== false) {
list($pSheetName, $pRange) = Worksheet::extractSheetTitle($pRange, true);
[$pSheetName, $pRange] = Worksheet::extractSheetTitle($pRange, true);
$pSheet = $this->spreadsheet->getSheetByName($pSheetName);
}
@ -4389,7 +4701,7 @@ class Calculation
$aReferences = Coordinate::extractAllCellReferencesInRange($pRange);
if (!isset($aReferences[1])) {
// Single cell (or single column or row) in range
list($currentCol, $currentRow) = Coordinate::coordinateFromString($aReferences[0]);
[$currentCol, $currentRow] = Coordinate::coordinateFromString($aReferences[0]);
if ($pSheet->cellExists($aReferences[0])) {
$returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
} else {
@ -4399,7 +4711,7 @@ class Calculation
// Extract cell data for all cells in the range
foreach ($aReferences as $reference) {
// Extract range
list($currentCol, $currentRow) = Coordinate::coordinateFromString($reference);
[$currentCol, $currentRow] = Coordinate::coordinateFromString($reference);
if ($pSheet->cellExists($reference)) {
$returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
} else {
@ -4483,4 +4795,26 @@ class Calculation
return $args;
}
private function getUnusedBranchStoreKey()
{
$storeKeyValue = 'storeKey-' . $this->branchStoreKeyCounter;
++$this->branchStoreKeyCounter;
return $storeKeyValue;
}
private function getTokensAsString($tokens)
{
$tokensStr = array_map(function ($token) {
$value = $token['value'] ?? 'no value';
while (is_array($value)) {
$value = array_pop($value);
}
return $value;
}, $tokens);
return '[ ' . implode(' | ', $tokensStr) . ' ]';
}
}

View File

@ -147,7 +147,7 @@ class Database
* the column label in which you specify a condition for the
* column.
*
* @return float
* @return float|string
*/
public static function DAVERAGE($database, $field, $criteria)
{
@ -452,7 +452,7 @@ class Database
* the column label in which you specify a condition for the
* column.
*
* @return float
* @return float|string
*/
public static function DSTDEV($database, $field, $criteria)
{
@ -493,7 +493,7 @@ class Database
* the column label in which you specify a condition for the
* column.
*
* @return float
* @return float|string
*/
public static function DSTDEVP($database, $field, $criteria)
{

View File

@ -16,7 +16,7 @@ class DateTime
*/
public static function isLeapYear($year)
{
return (($year % 4) == 0) && (($year % 100) != 0) || (($year % 400) == 0);
return (($year % 4) === 0) && (($year % 100) !== 0) || (($year % 400) === 0);
}
/**
@ -156,11 +156,11 @@ class DateTime
$retValue = (float) Date::PHPToExcel(time());
break;
case Functions::RETURNDATE_PHP_NUMERIC:
case Functions::RETURNDATE_UNIX_TIMESTAMP:
$retValue = (int) time();
break;
case Functions::RETURNDATE_PHP_OBJECT:
case Functions::RETURNDATE_PHP_DATETIME_OBJECT:
$retValue = new \DateTime();
break;
@ -200,11 +200,11 @@ class DateTime
$retValue = (float) $excelDateTime;
break;
case Functions::RETURNDATE_PHP_NUMERIC:
case Functions::RETURNDATE_UNIX_TIMESTAMP:
$retValue = (int) Date::excelToTimestamp($excelDateTime);
break;
case Functions::RETURNDATE_PHP_OBJECT:
case Functions::RETURNDATE_PHP_DATETIME_OBJECT:
$retValue = Date::excelToDateTimeObject($excelDateTime);
break;
@ -325,9 +325,9 @@ class DateTime
switch (Functions::getReturnDateType()) {
case Functions::RETURNDATE_EXCEL:
return (float) $excelDateValue;
case Functions::RETURNDATE_PHP_NUMERIC:
case Functions::RETURNDATE_UNIX_TIMESTAMP:
return (int) Date::excelToTimestamp($excelDateValue);
case Functions::RETURNDATE_PHP_OBJECT:
case Functions::RETURNDATE_PHP_DATETIME_OBJECT:
return Date::excelToDateTimeObject($excelDateValue);
}
}
@ -420,9 +420,9 @@ class DateTime
}
return (float) Date::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second);
case Functions::RETURNDATE_PHP_NUMERIC:
case Functions::RETURNDATE_UNIX_TIMESTAMP:
return (int) Date::excelToTimestamp(Date::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600
case Functions::RETURNDATE_PHP_OBJECT:
case Functions::RETURNDATE_PHP_DATETIME_OBJECT:
$dayAdjust = 0;
if ($hour < 0) {
$dayAdjust = floor($hour / 24);
@ -472,7 +472,6 @@ class DateTime
*/
public static function DATEVALUE($dateValue = 1)
{
$dateValueOrig = $dateValue;
$dateValue = trim(Functions::flattenSingleValue($dateValue), '"');
// Strip any ordinals because they're allowed in Excel (English only)
$dateValue = preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue);
@ -492,7 +491,7 @@ class DateTime
$yearFound = true;
}
}
if ((count($t1) == 1) && (strpos($t, ':') != false)) {
if ((count($t1) == 1) && (strpos($t, ':') !== false)) {
// We've been fed a time value without any date
return 0.0;
} elseif (count($t1) == 2) {
@ -569,9 +568,9 @@ class DateTime
switch (Functions::getReturnDateType()) {
case Functions::RETURNDATE_EXCEL:
return (float) $excelDateValue;
case Functions::RETURNDATE_PHP_NUMERIC:
case Functions::RETURNDATE_UNIX_TIMESTAMP:
return (int) Date::excelToTimestamp($excelDateValue);
case Functions::RETURNDATE_PHP_OBJECT:
case Functions::RETURNDATE_PHP_DATETIME_OBJECT:
return new \DateTime($PHPDateArray['year'] . '-' . $PHPDateArray['month'] . '-' . $PHPDateArray['day'] . ' 00:00:00');
}
}
@ -631,9 +630,9 @@ class DateTime
switch (Functions::getReturnDateType()) {
case Functions::RETURNDATE_EXCEL:
return (float) $excelDateValue;
case Functions::RETURNDATE_PHP_NUMERIC:
case Functions::RETURNDATE_UNIX_TIMESTAMP:
return (int) $phpDateValue = Date::excelToTimestamp($excelDateValue + 25569) - 3600;
case Functions::RETURNDATE_PHP_OBJECT:
case Functions::RETURNDATE_PHP_DATETIME_OBJECT:
return new \DateTime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']);
}
}
@ -683,7 +682,6 @@ class DateTime
$endMonths = $PHPEndDateObject->format('n');
$endYears = $PHPEndDateObject->format('Y');
$retVal = Functions::NAN();
switch ($unit) {
case 'D':
$retVal = (int) $difference;
@ -880,6 +878,8 @@ class DateTime
*
* Excel Function:
* YEARFRAC(startDate,endDate[,method])
* See https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html
* for description of algorithm used in Excel
*
* @category Date/Time Functions
*
@ -894,7 +894,7 @@ class DateTime
* 3 Actual/365
* 4 European 30/360
*
* @return float fraction of the year
* @return float|string fraction of the year, or a string containing an error
*/
public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0)
{
@ -908,6 +908,11 @@ class DateTime
if (is_string($endDate = self::getDateValue($endDate))) {
return Functions::VALUE();
}
if ($startDate > $endDate) {
$temp = $startDate;
$startDate = $endDate;
$endDate = $temp;
}
if (((is_numeric($method)) && (!is_string($method))) || ($method == '')) {
switch ($method) {
@ -918,46 +923,43 @@ class DateTime
$startYear = self::YEAR($startDate);
$endYear = self::YEAR($endDate);
$years = $endYear - $startYear + 1;
$leapDays = 0;
$startMonth = self::MONTHOFYEAR($startDate);
$startDay = self::DAYOFMONTH($startDate);
$endMonth = self::MONTHOFYEAR($endDate);
$endDay = self::DAYOFMONTH($endDate);
$startMonthDay = 100 * $startMonth + $startDay;
$endMonthDay = 100 * $endMonth + $endDay;
if ($years == 1) {
if (self::isLeapYear($endYear)) {
$startMonth = self::MONTHOFYEAR($startDate);
$endMonth = self::MONTHOFYEAR($endDate);
$endDay = self::DAYOFMONTH($endDate);
if (($startMonth < 3) ||
(($endMonth * 100 + $endDay) >= (2 * 100 + 29))) {
$leapDays += 1;
$tmpCalcAnnualBasis = 366;
} else {
$tmpCalcAnnualBasis = 365;
}
} elseif ($years == 2 && $startMonthDay >= $endMonthDay) {
if (self::isLeapYear($startYear)) {
if ($startMonthDay <= 229) {
$tmpCalcAnnualBasis = 366;
} else {
$tmpCalcAnnualBasis = 365;
}
} elseif (self::isLeapYear($endYear)) {
if ($endMonthDay >= 229) {
$tmpCalcAnnualBasis = 366;
} else {
$tmpCalcAnnualBasis = 365;
}
} else {
$tmpCalcAnnualBasis = 365;
}
} else {
$tmpCalcAnnualBasis = 0;
for ($year = $startYear; $year <= $endYear; ++$year) {
if ($year == $startYear) {
$startMonth = self::MONTHOFYEAR($startDate);
$startDay = self::DAYOFMONTH($startDate);
if ($startMonth < 3) {
$leapDays += (self::isLeapYear($year)) ? 1 : 0;
}
} elseif ($year == $endYear) {
$endMonth = self::MONTHOFYEAR($endDate);
$endDay = self::DAYOFMONTH($endDate);
if (($endMonth * 100 + $endDay) >= (2 * 100 + 29)) {
$leapDays += (self::isLeapYear($year)) ? 1 : 0;
}
} else {
$leapDays += (self::isLeapYear($year)) ? 1 : 0;
}
$tmpCalcAnnualBasis += self::isLeapYear($year) ? 366 : 365;
}
if ($years == 2) {
if (($leapDays == 0) && (self::isLeapYear($startYear)) && ($days > 365)) {
$leapDays = 1;
} elseif ($days < 366) {
$years = 1;
}
}
$leapDays /= $years;
$tmpCalcAnnualBasis /= $years;
}
return $days / (365 + $leapDays);
return $days / $tmpCalcAnnualBasis;
case 2:
return self::DATEDIF($startDate, $endDate) / 360;
case 3:
@ -1154,9 +1156,9 @@ class DateTime
switch (Functions::getReturnDateType()) {
case Functions::RETURNDATE_EXCEL:
return (float) $endDate;
case Functions::RETURNDATE_PHP_NUMERIC:
case Functions::RETURNDATE_UNIX_TIMESTAMP:
return (int) Date::excelToTimestamp($endDate);
case Functions::RETURNDATE_PHP_OBJECT:
case Functions::RETURNDATE_PHP_DATETIME_OBJECT:
return Date::excelToDateTimeObject($endDate);
}
}
@ -1239,7 +1241,7 @@ class DateTime
// Execute function
$PHPDateObject = Date::excelToDateTimeObject($dateValue);
$DoW = $PHPDateObject->format('w');
$DoW = (int) $PHPDateObject->format('w');
$firstDay = 1;
switch ($style) {
@ -1248,13 +1250,13 @@ class DateTime
break;
case 2:
if ($DoW == 0) {
if ($DoW === 0) {
$DoW = 7;
}
break;
case 3:
if ($DoW == 0) {
if ($DoW === 0) {
$DoW = 7;
}
$firstDay = 0;
@ -1272,9 +1274,39 @@ class DateTime
}
}
return (int) $DoW;
return $DoW;
}
const STARTWEEK_SUNDAY = 1;
const STARTWEEK_MONDAY = 2;
const STARTWEEK_MONDAY_ALT = 11;
const STARTWEEK_TUESDAY = 12;
const STARTWEEK_WEDNESDAY = 13;
const STARTWEEK_THURSDAY = 14;
const STARTWEEK_FRIDAY = 15;
const STARTWEEK_SATURDAY = 16;
const STARTWEEK_SUNDAY_ALT = 17;
const DOW_SUNDAY = 1;
const DOW_MONDAY = 2;
const DOW_TUESDAY = 3;
const DOW_WEDNESDAY = 4;
const DOW_THURSDAY = 5;
const DOW_FRIDAY = 6;
const DOW_SATURDAY = 7;
const STARTWEEK_MONDAY_ISO = 21;
const METHODARR = [
self::STARTWEEK_SUNDAY => self::DOW_SUNDAY,
self::DOW_MONDAY,
self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY,
self::DOW_TUESDAY,
self::DOW_WEDNESDAY,
self::DOW_THURSDAY,
self::DOW_FRIDAY,
self::DOW_SATURDAY,
self::DOW_SUNDAY,
self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO,
];
/**
* WEEKNUM.
*
@ -1293,41 +1325,51 @@ class DateTime
* @param 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.
* 12 Week begins on Tuesday.
* 13 Week begins on Wednesday.
* 14 Week begins on Thursday.
* 15 Week begins on Friday.
* 16 Week begins on Saturday.
* 17 Week begins on Sunday.
* 21 ISO (Jan. 4 is week 1, begins on Monday).
*
* @return int|string Week Number
*/
public static function WEEKNUM($dateValue = 1, $method = 1)
public static function WEEKNUM($dateValue = 1, $method = self::STARTWEEK_SUNDAY)
{
$dateValue = Functions::flattenSingleValue($dateValue);
$method = Functions::flattenSingleValue($method);
if (!is_numeric($method)) {
return Functions::VALUE();
} elseif (($method < 1) || ($method > 2)) {
return Functions::NAN();
}
$method = floor($method);
$method = (int) $method;
if (!array_key_exists($method, self::METHODARR)) {
return Functions::NaN();
}
$method = self::METHODARR[$method];
if ($dateValue === null) {
$dateValue = 1;
} elseif (is_string($dateValue = self::getDateValue($dateValue))) {
$dateValue = self::getDateValue($dateValue);
if (is_string($dateValue)) {
return Functions::VALUE();
} elseif ($dateValue < 0.0) {
}
if ($dateValue < 0.0) {
return Functions::NAN();
}
// Execute function
$PHPDateObject = Date::excelToDateTimeObject($dateValue);
if ($method == self::STARTWEEK_MONDAY_ISO) {
return (int) $PHPDateObject->format('W');
}
$dayOfYear = $PHPDateObject->format('z');
$PHPDateObject->modify('-' . $dayOfYear . ' days');
$firstDayOfFirstWeek = $PHPDateObject->format('w');
$daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7;
$interval = $dayOfYear - $daysInFirstWeek;
$weekOfYear = floor($interval / 7) + 1;
if ($daysInFirstWeek) {
++$weekOfYear;
}
$daysInFirstWeek += 7 * !$daysInFirstWeek;
$endFirstWeek = $daysInFirstWeek - 1;
$weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7);
return (int) $weekOfYear;
}
@ -1591,9 +1633,9 @@ class DateTime
switch (Functions::getReturnDateType()) {
case Functions::RETURNDATE_EXCEL:
return (float) Date::PHPToExcel($PHPDateObject);
case Functions::RETURNDATE_PHP_NUMERIC:
case Functions::RETURNDATE_UNIX_TIMESTAMP:
return (int) Date::excelToTimestamp(Date::PHPToExcel($PHPDateObject));
case Functions::RETURNDATE_PHP_OBJECT:
case Functions::RETURNDATE_PHP_DATETIME_OBJECT:
return $PHPDateObject;
}
}
@ -1640,9 +1682,9 @@ class DateTime
switch (Functions::getReturnDateType()) {
case Functions::RETURNDATE_EXCEL:
return (float) Date::PHPToExcel($PHPDateObject);
case Functions::RETURNDATE_PHP_NUMERIC:
case Functions::RETURNDATE_UNIX_TIMESTAMP:
return (int) Date::excelToTimestamp(Date::PHPToExcel($PHPDateObject));
case Functions::RETURNDATE_PHP_OBJECT:
case Functions::RETURNDATE_PHP_DATETIME_OBJECT:
return $PHPDateObject;
}
}

View File

@ -785,7 +785,7 @@ class Engineering
* If $ord is nonnumeric, BESSELI returns the #VALUE! error value.
* If $ord < 0, BESSELI returns the #NUM! error value.
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function BESSELI($x, $ord)
{
@ -839,7 +839,7 @@ class Engineering
* If $ord is nonnumeric, BESSELJ returns the #VALUE! error value.
* If $ord < 0, BESSELJ returns the #NUM! error value.
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function BESSELJ($x, $ord)
{
@ -932,7 +932,7 @@ class Engineering
* If $ord is nonnumeric, BESSELK returns the #VALUE! error value.
* If $ord < 0, BESSELK returns the #NUM! error value.
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function BESSELK($x, $ord)
{
@ -1021,7 +1021,7 @@ class Engineering
* If $ord is nonnumeric, BESSELK returns the #VALUE! error value.
* If $ord < 0, BESSELK returns the #NUM! error value.
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function BESSELY($x, $ord)
{
@ -2713,7 +2713,7 @@ class Engineering
* @param string $fromUOM the units for value
* @param string $toUOM the units for the result
*
* @return float
* @return float|string
*/
public static function CONVERTUOM($value, $fromUOM, $toUOM)
{

View File

@ -71,7 +71,7 @@ class Financial
* 3 365
* 4 European 360
*
* @return int
* @return int|string Result, or a string containing an error
*/
private static function daysPerYear($year, $basis = 0)
{
@ -144,7 +144,7 @@ class Financial
* 3 Actual/365
* 4 European 30/360
*
* @return float|string
* @return float|string Result, or a string containing an error
*/
public static function ACCRINT($issue, $firstinterest, $settlement, $rate, $par = 1000, $frequency = 1, $basis = 0)
{
@ -197,7 +197,7 @@ class Financial
* 3 Actual/365
* 4 European 30/360
*
* @return float|string
* @return float|string Result, or a string containing an error
*/
public static function ACCRINTM($issue, $settlement, $rate, $par = 1000, $basis = 0)
{
@ -417,7 +417,7 @@ class Financial
return Functions::VALUE();
}
if (($settlement > $maturity) ||
if (($settlement >= $maturity) ||
(!self::isValidFrequency($frequency)) ||
(($basis < 0) || ($basis > 4))) {
return Functions::NAN();
@ -476,7 +476,7 @@ class Financial
return Functions::VALUE();
}
if (($settlement > $maturity) ||
if (($settlement >= $maturity) ||
(!self::isValidFrequency($frequency)) ||
(($basis < 0) || ($basis > 4))) {
return Functions::NAN();
@ -550,7 +550,7 @@ class Financial
return Functions::VALUE();
}
if (($settlement > $maturity) ||
if (($settlement >= $maturity) ||
(!self::isValidFrequency($frequency)) ||
(($basis < 0) || ($basis > 4))) {
return Functions::NAN();
@ -610,7 +610,7 @@ class Financial
return Functions::VALUE();
}
if (($settlement > $maturity) ||
if (($settlement >= $maturity) ||
(!self::isValidFrequency($frequency)) ||
(($basis < 0) || ($basis > 4))) {
return Functions::NAN();
@ -667,26 +667,22 @@ class Financial
return Functions::VALUE();
}
if (($settlement > $maturity) ||
if (($settlement >= $maturity) ||
(!self::isValidFrequency($frequency)) ||
(($basis < 0) || ($basis > 4))) {
return Functions::NAN();
}
$settlement = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
$daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis) * 365;
$daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
$daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis) * $daysPerYear;
switch ($frequency) {
case 1: // annual payments
return ceil($daysBetweenSettlementAndMaturity / 360);
case 2: // half-yearly
return ceil($daysBetweenSettlementAndMaturity / 180);
case 4: // quarterly
return ceil($daysBetweenSettlementAndMaturity / 90);
case 6: // bimonthly
return ceil($daysBetweenSettlementAndMaturity / 60);
case 12: // monthly
return ceil($daysBetweenSettlementAndMaturity / 30);
return ceil($daysBetweenSettlementAndMaturity / $daysPerYear * $frequency);
}
return Functions::VALUE();
@ -740,7 +736,7 @@ class Financial
return Functions::VALUE();
}
if (($settlement > $maturity) ||
if (($settlement >= $maturity) ||
(!self::isValidFrequency($frequency)) ||
(($basis < 0) || ($basis > 4))) {
return Functions::NAN();
@ -1428,7 +1424,7 @@ class Financial
* @param float $finance_rate The interest rate you pay on the money used in the cash flows
* @param float $reinvestment_rate The interest rate you receive on the cash flows as you reinvest them
*
* @return float|string
* @return float|string Result, or a string containing an error
*/
public static function MIRR($values, $finance_rate, $reinvestment_rate)
{
@ -1470,7 +1466,7 @@ class Financial
* @param float $effect_rate Effective interest rate
* @param int $npery Number of compounding payments per year
*
* @return float|string
* @return float|string Result, or a string containing an error
*/
public static function NOMINAL($effect_rate = 0, $npery = 0)
{
@ -1497,7 +1493,7 @@ class Financial
* @param float $fv Future Value
* @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
*
* @return float|string
* @return float|string Result, or a string containing an error
*/
public static function NPER($rate = 0, $pmt = 0, $pv = 0, $fv = 0, $type = 0)
{
@ -1565,7 +1561,7 @@ class Financial
* @param float $pv Present Value
* @param float $fv Future Value
*
* @return float|string
* @return float|string Result, or a string containing an error
*/
public static function PDURATION($rate = 0, $pv = 0, $fv = 0)
{
@ -1594,7 +1590,7 @@ class Financial
* @param float $fv Future Value
* @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function PMT($rate = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0)
{
@ -1629,7 +1625,7 @@ class Financial
* @param float $fv Future Value
* @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function PPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0)
{
@ -1713,7 +1709,7 @@ class Financial
* 3 Actual/365
* 4 European 30/360
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function PRICEDISC($settlement, $maturity, $discount, $redemption, $basis = 0)
{
@ -1759,7 +1755,7 @@ class Financial
* 3 Actual/365
* 4 European 30/360
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function PRICEMAT($settlement, $maturity, $issue, $rate, $yield, $basis = 0)
{
@ -1817,7 +1813,7 @@ class Financial
* @param float $fv Future Value
* @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function PV($rate = 0, $nper = 0, $pmt = 0, $fv = 0, $type = 0)
{
@ -1933,7 +1929,7 @@ class Financial
* 3 Actual/365
* 4 European 30/360
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function RECEIVED($settlement, $maturity, $investment, $discount, $basis = 0)
{
@ -1969,7 +1965,7 @@ class Financial
* @param float $pv Present Value
* @param float $fv Future Value
*
* @return float|string
* @return float|string Result, or a string containing an error
*/
public static function RRI($nper = 0, $pv = 0, $fv = 0)
{
@ -1996,7 +1992,7 @@ class Financial
* @param mixed $salvage Value at the end of the depreciation
* @param mixed $life Number of periods over which the asset is depreciated
*
* @return float|string
* @return float|string Result, or a string containing an error
*/
public static function SLN($cost, $salvage, $life)
{
@ -2026,7 +2022,7 @@ class Financial
* @param mixed $life Number of periods over which the asset is depreciated
* @param mixed $period Period
*
* @return float|string
* @return float|string Result, or a string containing an error
*/
public static function SYD($cost, $salvage, $life, $period)
{
@ -2058,7 +2054,7 @@ class Financial
* The maturity date is the date when the Treasury bill expires.
* @param int $discount The Treasury bill's discount rate
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function TBILLEQ($settlement, $maturity, $discount)
{
@ -2097,7 +2093,7 @@ class Financial
* The maturity date is the date when the Treasury bill expires.
* @param int $discount The Treasury bill's discount rate
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function TBILLPRICE($settlement, $maturity, $discount)
{
@ -2152,7 +2148,7 @@ class Financial
* The maturity date is the date when the Treasury bill expires.
* @param int $price The Treasury bill's price per $100 face value
*
* @return float
* @return float|mixed|string
*/
public static function TBILLYIELD($settlement, $maturity, $price)
{
@ -2187,6 +2183,23 @@ class Financial
return Functions::VALUE();
}
/**
* XIRR.
*
* Returns the internal rate of return for a schedule of cash flows that is not necessarily periodic.
*
* Excel Function:
* =XIRR(values,dates,guess)
*
* @param float[] $values A series of cash flow payments
* The series of values must contain at least one positive value & one negative value
* @param mixed[] $dates A series of payment dates
* 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
*
* @return float|mixed|string
*/
public static function XIRR($values, $dates, $guess = 0.1)
{
if ((!is_array($values)) && (!is_array($dates))) {
@ -2199,11 +2212,28 @@ class Financial
return Functions::NAN();
}
$datesCount = count($dates);
for ($i = 0; $i < $datesCount; ++$i) {
$dates[$i] = DateTime::getDateValue($dates[$i]);
if (!is_numeric($dates[$i])) {
return Functions::VALUE();
}
}
if (min($dates) != $dates[0]) {
return Functions::NAN();
}
// create an initial range, with a root somewhere between 0 and guess
$x1 = 0.0;
$x2 = $guess;
$f1 = self::XNPV($x1, $values, $dates);
if (!is_numeric($f1)) {
return $f1;
}
$f2 = self::XNPV($x2, $values, $dates);
if (!is_numeric($f2)) {
return $f2;
}
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
if (($f1 * $f2) < 0.0) {
break;
@ -2214,7 +2244,7 @@ class Financial
}
}
if (($f1 * $f2) > 0.0) {
return Functions::VALUE();
return Functions::NAN();
}
$f = self::XNPV($x1, $values, $dates);
@ -2251,15 +2281,15 @@ class Financial
* =XNPV(rate,values,dates)
*
* @param float $rate the discount rate to apply to the cash flows
* @param array of float $values A series of cash flows that corresponds to a schedule of payments in dates.
* @param float[] $values A series of cash flows that corresponds to a schedule of payments in dates.
* The first payment is optional and corresponds to a cost or payment that occurs at the beginning of the investment.
* If the first value is a cost or payment, it must be a negative value. All succeeding payments are discounted based on a 365-day year.
* The series of values must contain at least one positive value and one negative value.
* @param array of mixed $dates A schedule of payment dates that corresponds to the cash flow payments.
* @param mixed[] $dates A schedule of payment dates that corresponds to the cash flow payments.
* The first payment date indicates the beginning of the schedule of payments.
* All other dates must be later than this date, but they may occur in any order.
*
* @return float
* @return float|mixed|string
*/
public static function XNPV($rate, $values, $dates)
{
@ -2277,7 +2307,7 @@ class Financial
return Functions::NAN();
}
if ((min($values) > 0) || (max($values) < 0)) {
return Functions::VALUE();
return Functions::NAN();
}
$xnpv = 0.0;
@ -2309,7 +2339,7 @@ class Financial
* 3 Actual/365
* 4 European 30/360
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function YIELDDISC($settlement, $maturity, $price, $redemption, $basis = 0)
{
@ -2360,7 +2390,7 @@ class Financial
* 3 Actual/365
* 4 European 30/360
*
* @return float
* @return float|string Result, or a string containing an error
*/
public static function YIELDMAT($settlement, $maturity, $issue, $rate, $price, $basis = 0)
{

View File

@ -17,8 +17,11 @@ class Functions
const COMPATIBILITY_EXCEL = 'Excel';
const COMPATIBILITY_GNUMERIC = 'Gnumeric';
const COMPATIBILITY_OPENOFFICE = 'OpenOfficeCalc';
const RETURNDATE_PHP_NUMERIC = 'P';
const RETURNDATE_UNIX_TIMESTAMP = 'P';
const RETURNDATE_PHP_OBJECT = 'O';
const RETURNDATE_PHP_DATETIME_OBJECT = 'O';
const RETURNDATE_EXCEL = 'E';
/**
@ -101,16 +104,16 @@ class Functions
*
* @param string $returnDateType Return Date Format
* Permitted values are:
* Functions::RETURNDATE_PHP_NUMERIC 'P'
* Functions::RETURNDATE_PHP_OBJECT 'O'
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P'
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O'
* Functions::RETURNDATE_EXCEL 'E'
*
* @return bool Success or failure
*/
public static function setReturnDateType($returnDateType)
{
if (($returnDateType == self::RETURNDATE_PHP_NUMERIC) ||
($returnDateType == self::RETURNDATE_PHP_OBJECT) ||
if (($returnDateType == self::RETURNDATE_UNIX_TIMESTAMP) ||
($returnDateType == self::RETURNDATE_PHP_DATETIME_OBJECT) ||
($returnDateType == self::RETURNDATE_EXCEL)
) {
self::$returnDateType = $returnDateType;
@ -128,8 +131,8 @@ class Functions
*
* @return string Return Date Format
* Possible Return values are:
* Functions::RETURNDATE_PHP_NUMERIC 'P'
* Functions::RETURNDATE_PHP_OBJECT 'O'
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P'
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O'
* Functions::RETURNDATE_EXCEL 'E'
*/
public static function getReturnDateType()
@ -267,25 +270,29 @@ class Functions
public static function ifCondition($condition)
{
$condition = self::flattenSingleValue($condition);
if (!isset($condition[0]) && !is_numeric($condition)) {
if ($condition === '') {
$condition = '=""';
}
if (!in_array($condition[0], ['>', '<', '='])) {
if (!is_string($condition) || !in_array($condition[0], ['>', '<', '='])) {
if (!is_numeric($condition)) {
$condition = Calculation::wrapResult(strtoupper($condition));
}
return '=' . $condition;
return str_replace('""""', '""', '=' . $condition);
}
preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches);
list(, $operator, $operand) = $matches;
[, $operator, $operand] = $matches;
if (!is_numeric($operand)) {
if (is_numeric(trim($operand, '"'))) {
$operand = trim($operand, '"');
} elseif (!is_numeric($operand)) {
$operand = str_replace('"', '""', $operand);
$operand = Calculation::wrapResult(strtoupper($operand));
}
return $operator . $operand;
return str_replace('""""', '""', $operator . $operand);
}
/**
@ -639,7 +646,7 @@ class Functions
public static function flattenSingleValue($value = '')
{
while (is_array($value)) {
$value = array_pop($value);
$value = array_shift($value);
}
return $value;

View File

@ -266,6 +266,10 @@ class Logical
*/
public static function statementIf($condition = true, $returnIfTrue = 0, $returnIfFalse = false)
{
if (Functions::isError($condition)) {
return $condition;
}
$condition = ($condition === null) ? true : (bool) Functions::flattenSingleValue($condition);
$returnIfTrue = ($returnIfTrue === null) ? 0 : Functions::flattenSingleValue($returnIfTrue);
$returnIfFalse = ($returnIfFalse === null) ? false : Functions::flattenSingleValue($returnIfFalse);
@ -347,4 +351,25 @@ class Logical
return self::statementIf(Functions::isError($testValue), $errorpart, $testValue);
}
/**
* IFNA.
*
* Excel Function:
* =IFNA(testValue,napart)
*
* @category Logical Functions
*
* @param mixed $testValue Value to check, is also the value returned when not an NA
* @param mixed $napart Value to return when testValue is an NA condition
*
* @return mixed The value of errorpart or testValue determined by error condition
*/
public static function IFNA($testValue = '', $napart = '')
{
$testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue);
$napart = ($napart === null) ? '' : Functions::flattenSingleValue($napart);
return self::statementIf(Functions::isNa($testValue), $napart, $testValue);
}
}

View File

@ -98,9 +98,9 @@ class LookupRef
return (int) Coordinate::columnIndexFromString($columnKey);
}
} else {
list($sheet, $cellAddress) = Worksheet::extractSheetTitle($cellAddress, true);
[$sheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
if (strpos($cellAddress, ':') !== false) {
list($startAddress, $endAddress) = explode(':', $cellAddress);
[$startAddress, $endAddress] = explode(':', $cellAddress);
$startAddress = preg_replace('/[^a-z]/i', '', $startAddress);
$endAddress = preg_replace('/[^a-z]/i', '', $endAddress);
$returnValue = [];
@ -138,7 +138,7 @@ class LookupRef
reset($cellAddress);
$isMatrix = (is_numeric(key($cellAddress)));
list($columns, $rows) = Calculation::getMatrixDimensions($cellAddress);
[$columns, $rows] = Calculation::getMatrixDimensions($cellAddress);
if ($isMatrix) {
return $rows;
@ -175,9 +175,9 @@ class LookupRef
}
}
} else {
list($sheet, $cellAddress) = Worksheet::extractSheetTitle($cellAddress, true);
[$sheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
if (strpos($cellAddress, ':') !== false) {
list($startAddress, $endAddress) = explode(':', $cellAddress);
[$startAddress, $endAddress] = explode(':', $cellAddress);
$startAddress = preg_replace('/\D/', '', $startAddress);
$endAddress = preg_replace('/\D/', '', $endAddress);
$returnValue = [];
@ -187,7 +187,7 @@ class LookupRef
return $returnValue;
}
list($cellAddress) = explode(':', $cellAddress);
[$cellAddress] = explode(':', $cellAddress);
return (int) preg_replace('/\D/', '', $cellAddress);
}
@ -215,7 +215,7 @@ class LookupRef
reset($cellAddress);
$isMatrix = (is_numeric(key($cellAddress)));
list($columns, $rows) = Calculation::getMatrixDimensions($cellAddress);
[$columns, $rows] = Calculation::getMatrixDimensions($cellAddress);
if ($isMatrix) {
return $columns;
@ -285,7 +285,7 @@ class LookupRef
$cellAddress1 = $cellAddress;
$cellAddress2 = null;
if (strpos($cellAddress, ':') !== false) {
list($cellAddress1, $cellAddress2) = explode(':', $cellAddress);
[$cellAddress1, $cellAddress2] = explode(':', $cellAddress);
}
if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress1, $matches)) ||
@ -295,7 +295,7 @@ class LookupRef
}
if (strpos($cellAddress, '!') !== false) {
list($sheetName, $cellAddress) = Worksheet::extractSheetTitle($cellAddress, true);
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
$sheetName = trim($sheetName, "'");
$pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
} else {
@ -306,7 +306,7 @@ class LookupRef
}
if (strpos($cellAddress, '!') !== false) {
list($sheetName, $cellAddress) = Worksheet::extractSheetTitle($cellAddress, true);
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
$sheetName = trim($sheetName, "'");
$pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
} else {
@ -359,16 +359,16 @@ class LookupRef
$sheetName = null;
if (strpos($cellAddress, '!')) {
list($sheetName, $cellAddress) = Worksheet::extractSheetTitle($cellAddress, true);
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
$sheetName = trim($sheetName, "'");
}
if (strpos($cellAddress, ':')) {
list($startCell, $endCell) = explode(':', $cellAddress);
[$startCell, $endCell] = explode(':', $cellAddress);
} else {
$startCell = $endCell = $cellAddress;
}
list($startCellColumn, $startCellRow) = Coordinate::coordinateFromString($startCell);
list($endCellColumn, $endCellRow) = Coordinate::coordinateFromString($endCell);
[$startCellColumn, $startCellRow] = Coordinate::coordinateFromString($startCell);
[$endCellColumn, $endCellRow] = Coordinate::coordinateFromString($endCell);
$startCellRow += $rows;
$startCellColumn = Coordinate::columnIndexFromString($startCellColumn) - 1;
@ -464,9 +464,10 @@ class LookupRef
*
* @param mixed $lookupValue The value that you want to match in lookup_array
* @param mixed $lookupArray The range of cells being searched
* @param mixed $matchType The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below. If match_type is 1 or -1, the list has to be ordered.
* @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 The relative position of the found item
* @return int|string The relative position of the found item
*/
public static function MATCH($lookupValue, $lookupArray, $matchType = 1)
{
@ -474,9 +475,10 @@ class LookupRef
$lookupValue = Functions::flattenSingleValue($lookupValue);
$matchType = ($matchType === null) ? 1 : (int) Functions::flattenSingleValue($matchType);
$initialLookupValue = $lookupValue;
// MATCH is not case sensitive
$lookupValue = StringHelper::strToLower($lookupValue);
// MATCH is not case sensitive, so we convert lookup value to be lower cased in case it's string type.
if (is_string($lookupValue)) {
$lookupValue = StringHelper::strToLower($lookupValue);
}
// Lookup_value type has to be number, text, or logical values
if ((!is_numeric($lookupValue)) && (!is_string($lookupValue)) && (!is_bool($lookupValue))) {
@ -522,16 +524,54 @@ class LookupRef
// find the match
// **
if ($matchType == 0 || $matchType == 1) {
if ($matchType === 0 || $matchType === 1) {
foreach ($lookupArray as $i => $lookupArrayValue) {
$onlyNumeric = is_numeric($lookupArrayValue) && is_numeric($lookupValue);
$onlyNumericExactMatch = $onlyNumeric && $lookupArrayValue == $lookupValue;
$nonOnlyNumericExactMatch = !$onlyNumeric && $lookupArrayValue === $lookupValue;
$exactMatch = $onlyNumericExactMatch || $nonOnlyNumericExactMatch;
if (($matchType == 0) && $exactMatch) {
// exact match
return $i + 1;
} elseif (($matchType == 1) && ($lookupArrayValue <= $lookupValue)) {
$typeMatch = gettype($lookupValue) === gettype($lookupArrayValue);
$exactTypeMatch = $typeMatch && $lookupArrayValue === $lookupValue;
$nonOnlyNumericExactMatch = !$typeMatch && $lookupArrayValue === $lookupValue;
$exactMatch = $exactTypeMatch || $nonOnlyNumericExactMatch;
if ($matchType === 0) {
if ($typeMatch && is_string($lookupValue) && (bool) preg_match('/([\?\*])/', $lookupValue)) {
$splitString = $lookupValue;
$chars = array_map(function ($i) use ($splitString) {
return mb_substr($splitString, $i, 1);
}, range(0, mb_strlen($splitString) - 1));
$length = count($chars);
$pattern = '/^';
for ($j = 0; $j < $length; ++$j) {
if ($chars[$j] === '~') {
if (isset($chars[$j + 1])) {
if ($chars[$j + 1] === '*') {
$pattern .= preg_quote($chars[$j + 1], '/');
++$j;
} elseif ($chars[$j + 1] === '?') {
$pattern .= preg_quote($chars[$j + 1], '/');
++$j;
}
} else {
$pattern .= preg_quote($chars[$j], '/');
}
} elseif ($chars[$j] === '*') {
$pattern .= '.*';
} elseif ($chars[$j] === '?') {
$pattern .= '.{1}';
} else {
$pattern .= preg_quote($chars[$j], '/');
}
}
$pattern .= '$/';
if ((bool) preg_match($pattern, $lookupArrayValue)) {
// exact match
return $i + 1;
}
} elseif ($exactMatch) {
// exact match
return $i + 1;
}
} elseif (($matchType === 1) && $typeMatch && ($lookupArrayValue <= $lookupValue)) {
$i = array_search($i, $keySet);
// The current value is the (first) match
@ -539,26 +579,26 @@ class LookupRef
}
}
} else {
// matchType = -1
// "Special" case: since the array it's supposed to be ordered in descending order, the
// Excel algorithm gives up immediately if the first element is smaller than the searched value
if ($lookupArray[0] < $lookupValue) {
return Functions::NA();
}
$maxValueKey = null;
// The basic algorithm is:
// Iterate and keep the highest match until the next element is smaller than the searched value.
// Return immediately if perfect match is found
foreach ($lookupArray as $i => $lookupArrayValue) {
if ($lookupArrayValue == $lookupValue) {
$typeMatch = gettype($lookupValue) === gettype($lookupArrayValue);
$exactTypeMatch = $typeMatch && $lookupArrayValue === $lookupValue;
$nonOnlyNumericExactMatch = !$typeMatch && $lookupArrayValue === $lookupValue;
$exactMatch = $exactTypeMatch || $nonOnlyNumericExactMatch;
if ($exactMatch) {
// Another "special" case. If a perfect match is found,
// the algorithm gives up immediately
return $i + 1;
} elseif ($lookupArrayValue >= $lookupValue) {
} elseif ($typeMatch & $lookupArrayValue >= $lookupValue) {
$maxValueKey = $i + 1;
} elseif ($typeMatch & $lookupArrayValue < $lookupValue) {
//Excel algorithm gives up immediately if the first element is smaller than the searched value
break;
}
}
@ -794,8 +834,10 @@ class LookupRef
$lookupLower = StringHelper::strToLower($lookup_value);
$rowDataLower = StringHelper::strToLower($rowData);
if (($bothNumeric && $rowData > $lookup_value) ||
($bothNotNumeric && $rowDataLower > $lookupLower)) {
if ($not_exact_match && (
($bothNumeric && $rowData > $lookup_value) ||
($bothNotNumeric && $rowDataLower > $lookupLower)
)) {
break;
}

View File

@ -38,6 +38,88 @@ class MathTrig
return ($num - ($num % $n)) / $n;
}
/**
* ARABIC.
*
* Converts a Roman numeral to an Arabic numeral.
*
* Excel Function:
* ARABIC(text)
*
* @category Mathematical and Trigonometric Functions
*
* @param string $roman
*
* @return int|string the arabic numberal contrived from the roman numeral
*/
public static function ARABIC($roman)
{
// An empty string should return 0
$roman = substr(trim(strtoupper((string) Functions::flattenSingleValue($roman))), 0, 255);
if ($roman === '') {
return 0;
}
// Convert the roman numeral to an arabic number
$negativeNumber = $roman[0] === '-';
if ($negativeNumber) {
$roman = substr($roman, 1);
}
try {
$arabic = self::calculateArabic(str_split($roman));
} catch (\Exception $e) {
return Functions::VALUE(); // Invalid character detected
}
if ($negativeNumber) {
$arabic *= -1; // The number should be negative
}
return $arabic;
}
/**
* Recursively calculate the arabic value of a roman numeral.
*
* @param array $roman
* @param int $sum
* @param int $subtract
*
* @return int
*/
protected static function calculateArabic(array $roman, &$sum = 0, $subtract = 0)
{
$lookup = [
'M' => 1000,
'D' => 500,
'C' => 100,
'L' => 50,
'X' => 10,
'V' => 5,
'I' => 1,
];
$numeral = array_shift($roman);
if (!isset($lookup[$numeral])) {
throw new \Exception('Invalid character detected');
}
$arabic = $lookup[$numeral];
if (count($roman) > 0 && isset($lookup[$roman[0]]) && $arabic < $lookup[$roman[0]]) {
$subtract += $arabic;
} else {
$sum += ($arabic - $subtract);
$subtract = 0;
}
if (count($roman) > 0) {
self::calculateArabic($roman, $sum, $subtract);
}
return $sum;
}
/**
* ATAN2.
*
@ -59,7 +141,7 @@ class MathTrig
* @param float $xCoordinate the x-coordinate of the point
* @param float $yCoordinate the y-coordinate of the point
*
* @return float the inverse tangent of the specified x- and y-coordinates
* @return float|string the inverse tangent of the specified x- and y-coordinates, or a string containing an error
*/
public static function ATAN2($xCoordinate = null, $yCoordinate = null)
{
@ -84,6 +166,49 @@ class MathTrig
return Functions::VALUE();
}
/**
* BASE.
*
* Converts a number into a text representation with the given radix (base).
*
* Excel Function:
* BASE(Number, Radix [Min_length])
*
* @category Mathematical and Trigonometric Functions
*
* @param float $number
* @param float $radix
* @param int $minLength
*
* @return string the text representation with the given radix (base)
*/
public static function BASE($number, $radix, $minLength = null)
{
$number = Functions::flattenSingleValue($number);
$radix = Functions::flattenSingleValue($radix);
$minLength = Functions::flattenSingleValue($minLength);
if (is_numeric($number) && is_numeric($radix) && ($minLength === null || is_numeric($minLength))) {
// Truncate to an integer
$number = (int) $number;
$radix = (int) $radix;
$minLength = (int) $minLength;
if ($number < 0 || $number >= 2 ** 53 || $radix < 2 || $radix > 36) {
return Functions::NAN(); // Numeric range constraints
}
$outcome = strtoupper((string) base_convert($number, 10, $radix));
if ($minLength !== null) {
$outcome = str_pad($outcome, $minLength, '0', STR_PAD_LEFT); // String padding
}
return $outcome;
}
return Functions::VALUE();
}
/**
* CEILING.
*
@ -100,7 +225,7 @@ class MathTrig
* @param float $number the number you want to round
* @param float $significance the multiple to which you want to round
*
* @return float Rounded Number
* @return float|string Rounded Number, or a string containing an error
*/
public static function CEILING($number, $significance = null)
{
@ -139,7 +264,7 @@ class MathTrig
* @param int $numObjs Number of different objects
* @param int $numInSet Number of objects in each combination
*
* @return int Number of combinations
* @return int|string Number of combinations, or a string containing an error
*/
public static function COMBIN($numObjs, $numInSet)
{
@ -175,7 +300,7 @@ class MathTrig
*
* @param float $number Number to round
*
* @return int Rounded Number
* @return int|string Rounded Number, or a string containing an error
*/
public static function EVEN($number)
{
@ -209,7 +334,7 @@ class MathTrig
*
* @param float $factVal Factorial Value
*
* @return int Factorial
* @return int|string Factorial, or a string containing an error
*/
public static function FACT($factVal)
{
@ -220,10 +345,9 @@ class MathTrig
return Functions::NAN();
}
$factLoop = floor($factVal);
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
if ($factVal > $factLoop) {
return Functions::NAN();
}
if ((Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) &&
($factVal > $factLoop)) {
return Functions::NAN();
}
$factorial = 1;
@ -249,7 +373,7 @@ class MathTrig
*
* @param float $factVal Factorial Value
*
* @return int Double Factorial
* @return int|string Double Factorial, or a string containing an error
*/
public static function FACTDOUBLE($factVal)
{
@ -285,7 +409,7 @@ class MathTrig
* @param float $number Number to round
* @param float $significance Significance
*
* @return float Rounded Number
* @return float|string Rounded Number, or a string containing an error
*/
public static function FLOOR($number, $significance = null)
{
@ -302,7 +426,9 @@ class MathTrig
return Functions::DIV0();
} elseif ($number == 0.0) {
return 0.0;
} elseif (self::SIGN($number) == self::SIGN($significance)) {
} elseif (self::SIGN($significance) == 1) {
return floor($number / $significance) * $significance;
} elseif (self::SIGN($number) == -1 && self::SIGN($significance) == -1) {
return floor($number / $significance) * $significance;
}
@ -312,6 +438,80 @@ class MathTrig
return Functions::VALUE();
}
/**
* FLOOR.MATH.
*
* Round a number down to the nearest integer or to the nearest multiple of significance.
*
* Excel Function:
* FLOOR.MATH(number[,significance[,mode]])
*
* @category Mathematical and Trigonometric Functions
*
* @param float $number Number to round
* @param float $significance Significance
* @param int $mode direction to round negative numbers
*
* @return float|string Rounded Number, or a string containing an error
*/
public static function FLOORMATH($number, $significance = null, $mode = 0)
{
$number = Functions::flattenSingleValue($number);
$significance = Functions::flattenSingleValue($significance);
$mode = Functions::flattenSingleValue($mode);
if (is_numeric($number) && $significance === null) {
$significance = $number / abs($number);
}
if (is_numeric($number) && is_numeric($significance) && is_numeric($mode)) {
if ($significance == 0.0) {
return Functions::DIV0();
} elseif ($number == 0.0) {
return 0.0;
} elseif (self::SIGN($significance) == -1 || (self::SIGN($number) == -1 && !empty($mode))) {
return ceil($number / $significance) * $significance;
}
return floor($number / $significance) * $significance;
}
return Functions::VALUE();
}
/**
* FLOOR.PRECISE.
*
* Rounds number down, toward zero, to the nearest multiple of significance.
*
* Excel Function:
* FLOOR.PRECISE(number[,significance])
*
* @category Mathematical and Trigonometric Functions
*
* @param float $number Number to round
* @param float $significance Significance
*
* @return float|string Rounded Number, or a string containing an error
*/
public static function FLOORPRECISE($number, $significance = 1)
{
$number = Functions::flattenSingleValue($number);
$significance = Functions::flattenSingleValue($significance);
if ((is_numeric($number)) && (is_numeric($significance))) {
if ($significance == 0.0) {
return Functions::DIV0();
} elseif ($number == 0.0) {
return 0.0;
}
return floor($number / abs($significance)) * abs($significance);
}
return Functions::VALUE();
}
private static function evaluateGCD($a, $b)
{
return $b ? self::evaluateGCD($b, $a % $b) : $a;
@ -331,7 +531,7 @@ class MathTrig
*
* @param mixed ...$args Data values
*
* @return int Greatest Common Divisor
* @return int|mixed|string Greatest Common Divisor, or a string containing an error
*/
public static function GCD(...$args)
{
@ -365,7 +565,7 @@ class MathTrig
*
* @param float $number Number to cast to an integer
*
* @return int Integer value
* @return int|string Integer value, or a string containing an error
*/
public static function INT($number)
{
@ -398,7 +598,7 @@ class MathTrig
*
* @param mixed ...$args Data values
*
* @return int Lowest Common Multiplier
* @return int|string Lowest Common Multiplier, or a string containing an error
*/
public static function LCM(...$args)
{
@ -450,7 +650,7 @@ class MathTrig
* @param float $number The positive real number for which you want the logarithm
* @param float $base The base of the logarithm. If base is omitted, it is assumed to be 10.
*
* @return float
* @return float|string The result, or a string containing an error
*/
public static function logBase($number = null, $base = 10)
{
@ -479,7 +679,7 @@ class MathTrig
*
* @param array $matrixValues A matrix of values
*
* @return float
* @return float|string The result, or a string containing an error
*/
public static function MDETERM($matrixValues)
{
@ -531,7 +731,7 @@ class MathTrig
*
* @param array $matrixValues A matrix of values
*
* @return array
* @return array|string The result, or a string containing an error
*/
public static function MINVERSE($matrixValues)
{
@ -581,7 +781,7 @@ class MathTrig
* @param array $matrixData1 A matrix of values
* @param array $matrixData2 A matrix of values
*
* @return array
* @return array|string The result, or a string containing an error
*/
public static function MMULT($matrixData1, $matrixData2)
{
@ -643,7 +843,7 @@ class MathTrig
* @param int $a Dividend
* @param int $b Divisor
*
* @return int Remainder
* @return int|string Remainder, or a string containing an error
*/
public static function MOD($a = 1, $b = 1)
{
@ -669,7 +869,7 @@ class MathTrig
* @param float $number Number to round
* @param int $multiple Multiple to which you want to round $number
*
* @return float Rounded Number
* @return float|string Rounded Number, or a string containing an error
*/
public static function MROUND($number, $multiple)
{
@ -699,7 +899,7 @@ class MathTrig
*
* @param array of mixed Data Series
*
* @return float
* @return float|string The result, or a string containing an error
*/
public static function MULTINOMIAL(...$args)
{
@ -736,7 +936,7 @@ class MathTrig
*
* @param float $number Number to round
*
* @return int Rounded Number
* @return int|string Rounded Number, or a string containing an error
*/
public static function ODD($number)
{
@ -771,7 +971,7 @@ class MathTrig
* @param float $x
* @param float $y
*
* @return float
* @return float|string The result, or a string containing an error
*/
public static function POWER($x = 0, $y = 2)
{
@ -930,7 +1130,7 @@ class MathTrig
* @param float $number Number to round
* @param int $digits Number of digits to which you want to round $number
*
* @return float Rounded Number
* @return float|string Rounded Number, or a string containing an error
*/
public static function ROUNDUP($number, $digits)
{
@ -938,12 +1138,11 @@ class MathTrig
$digits = Functions::flattenSingleValue($digits);
if ((is_numeric($number)) && (is_numeric($digits))) {
$significance = pow(10, (int) $digits);
if ($number < 0.0) {
return floor($number * $significance) / $significance;
return round($number - 0.5 * pow(0.1, $digits), $digits, PHP_ROUND_HALF_DOWN);
}
return ceil($number * $significance) / $significance;
return round($number + 0.5 * pow(0.1, $digits), $digits, PHP_ROUND_HALF_DOWN);
}
return Functions::VALUE();
@ -957,7 +1156,7 @@ class MathTrig
* @param float $number Number to round
* @param int $digits Number of digits to which you want to round $number
*
* @return float Rounded Number
* @return float|string Rounded Number, or a string containing an error
*/
public static function ROUNDDOWN($number, $digits)
{
@ -965,12 +1164,11 @@ class MathTrig
$digits = Functions::flattenSingleValue($digits);
if ((is_numeric($number)) && (is_numeric($digits))) {
$significance = pow(10, (int) $digits);
if ($number < 0.0) {
return ceil($number * $significance) / $significance;
return round($number + 0.5 * pow(0.1, $digits), $digits, PHP_ROUND_HALF_UP);
}
return floor($number * $significance) / $significance;
return round($number - 0.5 * pow(0.1, $digits), $digits, PHP_ROUND_HALF_UP);
}
return Functions::VALUE();
@ -986,7 +1184,7 @@ class MathTrig
* @param float $m Step by which to increase $n for each term in the series
* @param array of mixed Data Series
*
* @return float
* @return float|string The result, or a string containing an error
*/
public static function SERIESSUM(...$args)
{
@ -1025,7 +1223,7 @@ class MathTrig
*
* @param float $number Number to round
*
* @return int sign value
* @return int|string sign value, or a string containing an error
*/
public static function SIGN($number)
{
@ -1052,7 +1250,7 @@ class MathTrig
*
* @param float $number Number
*
* @return float Square Root of Number * Pi
* @return float|string Square Root of Number * Pi, or a string containing an error
*/
public static function SQRTPI($number)
{
@ -1074,7 +1272,7 @@ class MathTrig
return array_filter(
$args,
function ($index) use ($cellReference) {
list(, $row, $column) = explode('.', $index);
[, $row, $column] = explode('.', $index);
return $cellReference->getWorksheet()->getRowDimension($row)->getVisible() &&
$cellReference->getWorksheet()->getColumnDimension($column)->getVisible();
@ -1088,7 +1286,7 @@ class MathTrig
return array_filter(
$args,
function ($index) use ($cellReference) {
list(, $row, $column) = explode('.', $index);
[, $row, $column] = explode('.', $index);
if ($cellReference->getWorksheet()->cellExists($column . $row)) {
//take this cell out if it contains the SUBTOTAL or AGGREGATE functions in a formula
$isFormula = $cellReference->getWorksheet()->getCell($column . $row)->isFormula();
@ -1116,7 +1314,7 @@ class MathTrig
* in hidden rows or columns
* @param array of mixed Data Series
*
* @return float
* @return float|string
*/
public static function SUBTOTAL(...$args)
{
@ -1306,7 +1504,7 @@ class MathTrig
*
* @param mixed ...$args Data values
*
* @return float
* @return float|string The result, or a string containing an error
*/
public static function SUMPRODUCT(...$args)
{
@ -1451,7 +1649,7 @@ class MathTrig
* @param float $value
* @param int $digits
*
* @return float Truncated value
* @return float|string Truncated value, or a string containing an error
*/
public static function TRUNC($value = 0, $digits = 0)
{

View File

@ -52,7 +52,7 @@ class TextData
return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE();
}
if (self::$invalidChars == null) {
if (self::$invalidChars === null) {
self::$invalidChars = range(chr(0), chr(31));
}
@ -84,6 +84,15 @@ class TextData
return null;
}
private static function convertBooleanValue($value)
{
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
return (int) $value;
}
return ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
}
/**
* ASCIICODE.
*
@ -98,11 +107,7 @@ class TextData
}
$characters = Functions::flattenSingleValue($characters);
if (is_bool($characters)) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
$characters = (int) $characters;
} else {
$characters = ($characters) ? Calculation::getTRUE() : Calculation::getFALSE();
}
$characters = self::convertBooleanValue($characters);
}
$character = $characters;
@ -126,11 +131,7 @@ class TextData
$aArgs = Functions::flattenArray($args);
foreach ($aArgs as $arg) {
if (is_bool($arg)) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
$arg = (int) $arg;
} else {
$arg = ($arg) ? Calculation::getTRUE() : Calculation::getFALSE();
}
$arg = self::convertBooleanValue($arg);
}
$returnValue .= $arg;
}
@ -197,7 +198,7 @@ class TextData
}
if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) {
if (StringHelper::countCharacters($needle) == 0) {
if (StringHelper::countCharacters($needle) === 0) {
return $offset;
}
@ -232,7 +233,7 @@ class TextData
}
if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) {
if (StringHelper::countCharacters($needle) == 0) {
if (StringHelper::countCharacters($needle) === 0) {
return $offset;
}
@ -265,14 +266,19 @@ class TextData
if (!is_numeric($value) || !is_numeric($decimals)) {
return Functions::NAN();
}
$decimals = floor($decimals);
$decimals = (int) floor($decimals);
$valueResult = round($value, $decimals);
if ($decimals < 0) {
$decimals = 0;
}
if (!$no_commas) {
$valueResult = number_format($valueResult, $decimals);
$valueResult = number_format(
$valueResult,
$decimals,
StringHelper::getDecimalSeparator(),
StringHelper::getThousandsSeparator()
);
}
return (string) $valueResult;
@ -659,11 +665,7 @@ class TextData
if ($ignoreEmpty && trim($arg) == '') {
unset($aArgs[$key]);
} elseif (is_bool($arg)) {
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
$arg = (int) $arg;
} else {
$arg = ($arg) ? Calculation::getTRUE() : Calculation::getFALSE();
}
$arg = self::convertBooleanValue($arg);
}
}

View File

@ -36,14 +36,24 @@ class Stack
* @param mixed $type
* @param mixed $value
* @param mixed $reference
* @param null|string $storeKey will store the result under this alias
* @param null|string $onlyIf will only run computation if the matching
* store key is true
* @param null|string $onlyIfNot will only run computation if the matching
* store key is false
*/
public function push($type, $value, $reference = null)
{
$this->stack[$this->count++] = [
'type' => $type,
'value' => $value,
'reference' => $reference,
];
public function push(
$type,
$value,
$reference = null,
$storeKey = null,
$onlyIf = null,
$onlyIfNot = null
) {
$stackItem = $this->getStackItem($type, $value, $reference, $storeKey, $onlyIf, $onlyIfNot);
$this->stack[$this->count++] = $stackItem;
if ($type == 'Function') {
$localeFunction = Calculation::localeFunc($value);
if ($localeFunction != $value) {
@ -52,6 +62,35 @@ class Stack
}
}
public function getStackItem(
$type,
$value,
$reference = null,
$storeKey = null,
$onlyIf = null,
$onlyIfNot = null
) {
$stackItem = [
'type' => $type,
'value' => $value,
'reference' => $reference,
];
if (isset($storeKey)) {
$stackItem['storeKey'] = $storeKey;
}
if (isset($onlyIf)) {
$stackItem['onlyIf'] = $onlyIf;
}
if (isset($onlyIfNot)) {
$stackItem['onlyIfNot'] = $onlyIfNot;
}
return $stackItem;
}
/**
* Pop the last entry from the stack.
*
@ -90,4 +129,21 @@ class Stack
$this->stack = [];
$this->count = 0;
}
public function __toString()
{
$str = 'Stack: ';
foreach ($this->stack as $index => $item) {
if ($index > $this->count - 1) {
break;
}
$value = $item['value'] ?? 'no value';
while (is_array($value)) {
$value = array_pop($value);
}
$str .= $value . ' |> ';
}
return $str;
}
}

View File

@ -9,6 +9,7 @@ ADDRESS
AMORDEGRC
AMORLINC
AND
ARABIC
AREAS
ASC
ASIN
@ -22,6 +23,7 @@ AVERAGEA
AVERAGEIF
AVERAGEIFS
BAHTTEXT
BASE
BESSELI
BESSELJ
BESSELK
@ -137,6 +139,8 @@ FISHER
FISHERINV
FIXED
FLOOR
FLOOR.MATH
FLOOR.PRECISE
FORECAST
FREQUENCY
FTEST
@ -224,6 +228,7 @@ LOWER
MATCH
MAX
MAXA
MAXIFS
MDETERM
MDURATION
MEDIAN
@ -231,6 +236,7 @@ MID
MIDB
MIN
MINA
MINIFS
MINUTE
MINVERSE
MIRR

View File

@ -118,7 +118,7 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
// Check for time without seconds e.g. '9:45', '09:45'
if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d$/', $value)) {
// Convert value to number
list($h, $m) = explode(':', $value);
[$h, $m] = explode(':', $value);
$days = $h / 24 + $m / 1440;
$cell->setValueExplicit($days, DataType::TYPE_NUMERIC);
// Set style
@ -131,7 +131,7 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
// Check for time with seconds '9:45:59', '09:45:59'
if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d:[0-5]\d$/', $value)) {
// Convert value to number
list($h, $m, $s) = explode(':', $value);
[$h, $m, $s] = explode(':', $value);
$days = $h / 24 + $m / 1440 + $s / 86400;
// Convert value to number
$cell->setValueExplicit($days, DataType::TYPE_NUMERIC);

View File

@ -67,7 +67,7 @@ class Cell
/**
* Update the cell into the cell collection.
*
* @return self
* @return $this
*/
public function updateInCollection()
{
@ -177,7 +177,7 @@ class Cell
*
* @throws Exception
*
* @return Cell
* @return $this
*/
public function setValue($pValue)
{
@ -217,7 +217,10 @@ class Cell
break;
case DataType::TYPE_NUMERIC:
$this->value = (float) $pValue;
if (is_string($pValue) && !is_numeric($pValue)) {
throw new Exception('Invalid numeric value for datatype Numeric');
}
$this->value = 0 + $pValue;
break;
case DataType::TYPE_FORMULA:
@ -263,7 +266,7 @@ class Cell
// We don't yet handle array returns
if (is_array($result)) {
while (is_array($result)) {
$result = array_pop($result);
$result = array_shift($result);
}
}
} catch (Exception $ex) {
@ -511,7 +514,7 @@ class Cell
{
if ($mergeRange = $this->getMergeRange()) {
$mergeRange = Coordinate::splitRange($mergeRange);
list($startCell) = $mergeRange[0];
[$startCell] = $mergeRange[0];
if ($this->getCoordinate() === $startCell) {
return true;
}
@ -569,7 +572,7 @@ class Cell
*/
public function isInRange($pRange)
{
list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($pRange);
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange);
// Translate properties
$myColumn = Coordinate::columnIndexFromString($this->getColumn());
@ -669,7 +672,7 @@ class Cell
*
* @param mixed $pAttributes
*
* @return Cell
* @return $this
*/
public function setFormulaAttributes($pAttributes)
{

View File

@ -71,7 +71,7 @@ abstract class Coordinate
}
// Split out any worksheet name from the reference
list($worksheet, $pCoordinateString) = Worksheet::extractSheetTitle($pCoordinateString, true);
[$worksheet, $pCoordinateString] = Worksheet::extractSheetTitle($pCoordinateString, true);
if ($worksheet > '') {
$worksheet .= '!';
}
@ -102,13 +102,13 @@ abstract class Coordinate
}
// Split out any worksheet name from the coordinate
list($worksheet, $pCoordinateString) = Worksheet::extractSheetTitle($pCoordinateString, true);
[$worksheet, $pCoordinateString] = Worksheet::extractSheetTitle($pCoordinateString, true);
if ($worksheet > '') {
$worksheet .= '!';
}
// Create absolute coordinate
list($column, $row) = self::coordinateFromString($pCoordinateString);
[$column, $row] = self::coordinateFromString($pCoordinateString);
$column = ltrim($column, '$');
$row = ltrim($row, '$');
@ -187,7 +187,7 @@ abstract class Coordinate
if (strpos($pRange, ':') === false) {
$rangeA = $rangeB = $pRange;
} else {
list($rangeA, $rangeB) = explode(':', $pRange);
[$rangeA, $rangeB] = explode(':', $pRange);
}
// Calculate range outer borders
@ -211,7 +211,7 @@ abstract class Coordinate
public static function rangeDimension($pRange)
{
// Calculate range outer borders
list($rangeStart, $rangeEnd) = self::rangeBoundaries($pRange);
[$rangeStart, $rangeEnd] = self::rangeBoundaries($pRange);
return [($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1)];
}
@ -238,7 +238,7 @@ abstract class Coordinate
if (strpos($pRange, ':') === false) {
$rangeA = $rangeB = $pRange;
} else {
list($rangeA, $rangeB) = explode(':', $pRange);
[$rangeA, $rangeB] = explode(':', $pRange);
}
return [self::coordinateFromString($rangeA), self::coordinateFromString($rangeB)];
@ -376,9 +376,9 @@ abstract class Coordinate
}
// Range...
list($rangeStart, $rangeEnd) = $range;
list($startColumn, $startRow) = self::coordinateFromString($rangeStart);
list($endColumn, $endRow) = self::coordinateFromString($rangeEnd);
[$rangeStart, $rangeEnd] = $range;
[$startColumn, $startRow] = self::coordinateFromString($rangeStart);
[$endColumn, $endRow] = self::coordinateFromString($rangeEnd);
$startColumnIndex = self::columnIndexFromString($startColumn);
$endColumnIndex = self::columnIndexFromString($endColumn);
++$endColumnIndex;
@ -432,7 +432,7 @@ abstract class Coordinate
continue;
}
list($column, $row) = self::coordinateFromString($coord);
[$column, $row] = self::coordinateFromString($coord);
$row = (int) (ltrim($row, '$'));
$hashCode = $column . '-' . (is_object($value) ? $value->getHashCode() : $value);

View File

@ -142,7 +142,7 @@ class DataValidation
*
* @param string $value
*
* @return DataValidation
* @return $this
*/
public function setFormula1($value)
{
@ -166,7 +166,7 @@ class DataValidation
*
* @param string $value
*
* @return DataValidation
* @return $this
*/
public function setFormula2($value)
{
@ -190,7 +190,7 @@ class DataValidation
*
* @param string $value
*
* @return DataValidation
* @return $this
*/
public function setType($value)
{
@ -214,7 +214,7 @@ class DataValidation
*
* @param string $value see self::STYLE_*
*
* @return DataValidation
* @return $this
*/
public function setErrorStyle($value)
{
@ -238,7 +238,7 @@ class DataValidation
*
* @param string $value
*
* @return DataValidation
* @return $this
*/
public function setOperator($value)
{
@ -262,7 +262,7 @@ class DataValidation
*
* @param bool $value
*
* @return DataValidation
* @return $this
*/
public function setAllowBlank($value)
{
@ -286,7 +286,7 @@ class DataValidation
*
* @param bool $value
*
* @return DataValidation
* @return $this
*/
public function setShowDropDown($value)
{
@ -310,7 +310,7 @@ class DataValidation
*
* @param bool $value
*
* @return DataValidation
* @return $this
*/
public function setShowInputMessage($value)
{
@ -334,7 +334,7 @@ class DataValidation
*
* @param bool $value
*
* @return DataValidation
* @return $this
*/
public function setShowErrorMessage($value)
{
@ -358,7 +358,7 @@ class DataValidation
*
* @param string $value
*
* @return DataValidation
* @return $this
*/
public function setErrorTitle($value)
{
@ -382,7 +382,7 @@ class DataValidation
*
* @param string $value
*
* @return DataValidation
* @return $this
*/
public function setError($value)
{
@ -406,7 +406,7 @@ class DataValidation
*
* @param string $value
*
* @return DataValidation
* @return $this
*/
public function setPromptTitle($value)
{
@ -430,7 +430,7 @@ class DataValidation
*
* @param string $value
*
* @return DataValidation
* @return $this
*/
public function setPrompt($value)
{

View File

@ -51,16 +51,16 @@ class DefaultValueBinder implements IValueBinder
// Match the value against a few data types
if ($pValue === null) {
return DataType::TYPE_NULL;
} elseif (is_float($pValue) || is_int($pValue)) {
return DataType::TYPE_NUMERIC;
} elseif (is_bool($pValue)) {
return DataType::TYPE_BOOL;
} elseif ($pValue === '') {
return DataType::TYPE_STRING;
} elseif ($pValue instanceof RichText) {
return DataType::TYPE_INLINE;
} elseif ($pValue[0] === '=' && strlen($pValue) > 1) {
} elseif (is_string($pValue) && $pValue[0] === '=' && strlen($pValue) > 1) {
return DataType::TYPE_FORMULA;
} elseif (is_bool($pValue)) {
return DataType::TYPE_BOOL;
} elseif (is_float($pValue) || is_int($pValue)) {
return DataType::TYPE_NUMERIC;
} elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $pValue)) {
$tValue = ltrim($pValue, '+-');
if (is_string($pValue) && $tValue[0] === '0' && strlen($tValue) > 1 && $tValue[1] !== '.') {

View File

@ -46,7 +46,7 @@ class Hyperlink
*
* @param string $value
*
* @return Hyperlink
* @return $this
*/
public function setUrl($value)
{
@ -70,7 +70,7 @@ class Hyperlink
*
* @param string $value
*
* @return Hyperlink
* @return $this
*/
public function setTooltip($value)
{

View File

@ -354,7 +354,7 @@ class Axis extends Properties
*
* @param int $shadow_presets
*
* @return Axis
* @return $this
*/
private function setShadowPresetsProperties($shadow_presets)
{
@ -370,7 +370,7 @@ class Axis extends Properties
* @param array $properties_map
* @param mixed &$reference
*
* @return Axis
* @return $this
*/
private function setShadowProperiesMapValues(array $properties_map, &$reference = null)
{
@ -402,7 +402,7 @@ class Axis extends Properties
* @param int $alpha
* @param string $type
*
* @return Axis
* @return $this
*/
private function setShadowColor($color, $alpha, $type)
{
@ -416,7 +416,7 @@ class Axis extends Properties
*
* @param float $blur
*
* @return Axis
* @return $this
*/
private function setShadowBlur($blur)
{
@ -432,7 +432,7 @@ class Axis extends Properties
*
* @param int $angle
*
* @return Axis
* @return $this
*/
private function setShadowAngle($angle)
{
@ -448,7 +448,7 @@ class Axis extends Properties
*
* @param float $distance
*
* @return Axis
* @return $this
*/
private function setShadowDistance($distance)
{
@ -506,7 +506,7 @@ class Axis extends Properties
*
* @param float $size
*
* @return Axis
* @return $this
*/
private function setGlowSize($size)
{
@ -524,7 +524,7 @@ class Axis extends Properties
* @param int $alpha
* @param string $type
*
* @return Axis
* @return $this
*/
private function setGlowColor($color, $alpha, $type)
{

View File

@ -156,7 +156,7 @@ class Chart
* @param null|GridLines $majorGridlines
* @param null|GridLines $minorGridlines
*/
public function __construct($name, Title $title = null, Legend $legend = null, PlotArea $plotArea = null, $plotVisibleOnly = true, $displayBlanksAs = '0', Title $xAxisLabel = null, Title $yAxisLabel = null, Axis $xAxis = null, Axis $yAxis = null, GridLines $majorGridlines = null, GridLines $minorGridlines = null)
public function __construct($name, Title $title = null, Legend $legend = null, PlotArea $plotArea = null, $plotVisibleOnly = true, $displayBlanksAs = 'gap', Title $xAxisLabel = null, Title $yAxisLabel = null, Axis $xAxis = null, Axis $yAxis = null, GridLines $majorGridlines = null, GridLines $minorGridlines = null)
{
$this->name = $name;
$this->title = $title;
@ -197,7 +197,7 @@ class Chart
*
* @param Worksheet $pValue
*
* @return Chart
* @return $this
*/
public function setWorksheet(Worksheet $pValue = null)
{
@ -221,7 +221,7 @@ class Chart
*
* @param Title $title
*
* @return Chart
* @return $this
*/
public function setTitle(Title $title)
{
@ -245,7 +245,7 @@ class Chart
*
* @param Legend $legend
*
* @return Chart
* @return $this
*/
public function setLegend(Legend $legend)
{
@ -269,7 +269,7 @@ class Chart
*
* @param Title $label
*
* @return Chart
* @return $this
*/
public function setXAxisLabel(Title $label)
{
@ -293,7 +293,7 @@ class Chart
*
* @param Title $label
*
* @return Chart
* @return $this
*/
public function setYAxisLabel(Title $label)
{
@ -327,7 +327,7 @@ class Chart
*
* @param bool $plotVisibleOnly
*
* @return Chart
* @return $this
*/
public function setPlotVisibleOnly($plotVisibleOnly)
{
@ -351,7 +351,7 @@ class Chart
*
* @param string $displayBlanksAs
*
* @return Chart
* @return $this
*/
public function setDisplayBlanksAs($displayBlanksAs)
{
@ -423,7 +423,7 @@ class Chart
* @param int $xOffset
* @param int $yOffset
*
* @return Chart
* @return $this
*/
public function setTopLeftPosition($cell, $xOffset = null, $yOffset = null)
{
@ -467,7 +467,7 @@ class Chart
*
* @param string $cell
*
* @return Chart
* @return $this
*/
public function setTopLeftCell($cell)
{
@ -482,7 +482,7 @@ class Chart
* @param int $xOffset
* @param int $yOffset
*
* @return Chart
* @return $this
*/
public function setTopLeftOffset($xOffset, $yOffset)
{
@ -541,7 +541,7 @@ class Chart
* @param int $xOffset
* @param int $yOffset
*
* @return Chart
* @return $this
*/
public function setBottomRightPosition($cell, $xOffset = null, $yOffset = null)
{
@ -593,7 +593,7 @@ class Chart
* @param int $xOffset
* @param int $yOffset
*
* @return Chart
* @return $this
*/
public function setBottomRightOffset($xOffset, $yOffset)
{

View File

@ -157,7 +157,7 @@ class DataSeries
*
* @param string $plotType
*
* @return DataSeries
* @return $this
*/
public function setPlotType($plotType)
{
@ -181,7 +181,7 @@ class DataSeries
*
* @param string $groupingType
*
* @return DataSeries
* @return $this
*/
public function setPlotGrouping($groupingType)
{
@ -205,7 +205,7 @@ class DataSeries
*
* @param string $plotDirection
*
* @return DataSeries
* @return $this
*/
public function setPlotDirection($plotDirection)
{
@ -297,7 +297,7 @@ class DataSeries
*
* @param null|string $plotStyle
*
* @return DataSeries
* @return $this
*/
public function setPlotStyle($plotStyle)
{
@ -360,7 +360,7 @@ class DataSeries
*
* @param bool $smoothLine
*
* @return DataSeries
* @return $this
*/
public function setSmoothLine($smoothLine)
{

View File

@ -117,7 +117,7 @@ class DataSeriesValues
*
* @throws Exception
*
* @return DataSeriesValues
* @return $this
*/
public function setDataType($dataType)
{
@ -144,7 +144,7 @@ class DataSeriesValues
*
* @param string $dataSource
*
* @return DataSeriesValues
* @return $this
*/
public function setDataSource($dataSource)
{
@ -168,7 +168,7 @@ class DataSeriesValues
*
* @param string $marker
*
* @return DataSeriesValues
* @return $this
*/
public function setPointMarker($marker)
{
@ -192,7 +192,7 @@ class DataSeriesValues
*
* @param string $formatCode
*
* @return DataSeriesValues
* @return $this
*/
public function setFormatCode($formatCode)
{
@ -275,7 +275,7 @@ class DataSeriesValues
*
* @param int $width
*
* @return DataSeriesValues
* @return $this
*/
public function setLineWidth($width)
{
@ -346,7 +346,7 @@ class DataSeriesValues
*
* @param array $dataValues
*
* @return DataSeriesValues
* @return $this
*/
public function setDataValues($dataValues)
{
@ -370,13 +370,13 @@ class DataSeriesValues
if ($flatten) {
$this->dataValues = Functions::flattenArray($newDataValues);
foreach ($this->dataValues as &$dataValue) {
if ((!empty($dataValue)) && ($dataValue[0] == '#')) {
if (is_string($dataValue) && !empty($dataValue) && $dataValue[0] == '#') {
$dataValue = 0.0;
}
}
unset($dataValue);
} else {
list($worksheet, $cellRange) = Worksheet::extractSheetTitle($this->dataSource, true);
[$worksheet, $cellRange] = Worksheet::extractSheetTitle($this->dataSource, true);
$dimensions = Coordinate::rangeDimension(str_replace('$', '', $cellRange));
if (($dimensions[0] == 1) || ($dimensions[1] == 1)) {
$this->dataValues = Functions::flattenArray($newDataValues);

View File

@ -91,7 +91,7 @@ class GridLines extends Properties
/**
* Change Object State to True.
*
* @return GridLines
* @return $this
*/
private function activateObject()
{
@ -229,7 +229,7 @@ class GridLines extends Properties
*
* @param float $size
*
* @return GridLines
* @return $this
*/
private function setGlowSize($size)
{
@ -245,7 +245,7 @@ class GridLines extends Properties
* @param int $alpha
* @param string $type
*
* @return GridLines
* @return $this
*/
private function setGlowColor($color, $alpha, $type)
{
@ -305,7 +305,7 @@ class GridLines extends Properties
*
* @param int $shadow_presets
*
* @return GridLines
* @return $this
*/
private function setShadowPresetsProperties($shadow_presets)
{
@ -321,7 +321,7 @@ class GridLines extends Properties
* @param array $properties_map
* @param mixed &$reference
*
* @return GridLines
* @return $this
*/
private function setShadowProperiesMapValues(array $properties_map, &$reference = null)
{
@ -353,7 +353,7 @@ class GridLines extends Properties
* @param int $alpha
* @param string $type
*
* @return GridLines
* @return $this
*/
private function setShadowColor($color, $alpha, $type)
{
@ -375,7 +375,7 @@ class GridLines extends Properties
*
* @param float $blur
*
* @return GridLines
* @return $this
*/
private function setShadowBlur($blur)
{
@ -391,7 +391,7 @@ class GridLines extends Properties
*
* @param int $angle
*
* @return GridLines
* @return $this
*/
private function setShadowAngle($angle)
{
@ -407,7 +407,7 @@ class GridLines extends Properties
*
* @param float $distance
*
* @return GridLines
* @return $this
*/
private function setShadowDistance($distance)
{

View File

@ -153,7 +153,7 @@ class Layout
*
* @param string $value
*
* @return Layout
* @return $this
*/
public function setLayoutTarget($value)
{
@ -177,7 +177,7 @@ class Layout
*
* @param string $value
*
* @return Layout
* @return $this
*/
public function setXMode($value)
{
@ -201,7 +201,7 @@ class Layout
*
* @param string $value
*
* @return Layout
* @return $this
*/
public function setYMode($value)
{
@ -225,7 +225,7 @@ class Layout
*
* @param float $value
*
* @return Layout
* @return $this
*/
public function setXPosition($value)
{
@ -249,7 +249,7 @@ class Layout
*
* @param float $value
*
* @return Layout
* @return $this
*/
public function setYPosition($value)
{
@ -273,7 +273,7 @@ class Layout
*
* @param float $value
*
* @return Layout
* @return $this
*/
public function setWidth($value)
{
@ -297,7 +297,7 @@ class Layout
*
* @param float $value
*
* @return Layout
* @return $this
*/
public function setHeight($value)
{
@ -322,7 +322,7 @@ class Layout
*
* @param bool $value Show legend key
*
* @return Layout
* @return $this
*/
public function setShowLegendKey($value)
{
@ -347,7 +347,7 @@ class Layout
*
* @param bool $value Show val
*
* @return Layout
* @return $this
*/
public function setShowVal($value)
{
@ -372,7 +372,7 @@ class Layout
*
* @param bool $value Show cat name
*
* @return Layout
* @return $this
*/
public function setShowCatName($value)
{
@ -397,7 +397,7 @@ class Layout
*
* @param bool $value Show series name
*
* @return Layout
* @return $this
*/
public function setShowSerName($value)
{
@ -422,7 +422,7 @@ class Layout
*
* @param bool $value Show percentage
*
* @return Layout
* @return $this
*/
public function setShowPercent($value)
{
@ -447,7 +447,7 @@ class Layout
*
* @param bool $value Show bubble size
*
* @return Layout
* @return $this
*/
public function setShowBubbleSize($value)
{
@ -472,7 +472,7 @@ class Layout
*
* @param bool $value Show leader lines
*
* @return Layout
* @return $this
*/
public function setShowLeaderLines($value)
{

View File

@ -94,7 +94,7 @@ class PlotArea
*
* @param DataSeries[] $plotSeries
*
* @return PlotArea
* @return $this
*/
public function setPlotSeries(array $plotSeries)
{

View File

@ -442,7 +442,7 @@ class JpGraph implements IRenderer
$seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]);
} elseif ($scatterStyle == 'smoothMarker') {
$spline = new \Spline($dataValuesY, $dataValuesX);
list($splineDataY, $splineDataX) = $spline->Get(count($dataValuesX) * self::$width / 20);
[$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20);
$lplot = new \LinePlot($splineDataX, $splineDataY);
$lplot->SetColor(self::$colourSet[self::$plotColour]);

View File

@ -45,7 +45,7 @@ class Title
*
* @param string $caption
*
* @return Title
* @return $this
*/
public function setCaption($caption)
{

View File

@ -16,8 +16,6 @@ abstract class CellsFactory
* */
public static function getInstance(Worksheet $parent)
{
$instance = new Cells($parent, Settings::getCache());
return $instance;
return new Cells($parent, Settings::getCache());
}
}

View File

@ -96,7 +96,7 @@ class Comment implements IComparable
*
* @param string $author
*
* @return Comment
* @return $this
*/
public function setAuthor($author)
{
@ -120,7 +120,7 @@ class Comment implements IComparable
*
* @param RichText $pValue
*
* @return Comment
* @return $this
*/
public function setText(RichText $pValue)
{
@ -144,7 +144,7 @@ class Comment implements IComparable
*
* @param string $width
*
* @return Comment
* @return $this
*/
public function setWidth($width)
{
@ -168,7 +168,7 @@ class Comment implements IComparable
*
* @param string $value
*
* @return Comment
* @return $this
*/
public function setHeight($value)
{
@ -192,7 +192,7 @@ class Comment implements IComparable
*
* @param string $value
*
* @return Comment
* @return $this
*/
public function setMarginLeft($value)
{
@ -216,7 +216,7 @@ class Comment implements IComparable
*
* @param string $value
*
* @return Comment
* @return $this
*/
public function setMarginTop($value)
{
@ -240,7 +240,7 @@ class Comment implements IComparable
*
* @param bool $value
*
* @return Comment
* @return $this
*/
public function setVisible($value)
{
@ -264,7 +264,7 @@ class Comment implements IComparable
*
* @param string $alignment see Style\Alignment::HORIZONTAL_*
*
* @return Comment
* @return $this
*/
public function setAlignment($alignment)
{

View File

@ -122,7 +122,7 @@ class Properties
*
* @param string $creator
*
* @return Properties
* @return $this
*/
public function setCreator($creator)
{
@ -146,7 +146,7 @@ class Properties
*
* @param string $pValue
*
* @return Properties
* @return $this
*/
public function setLastModifiedBy($pValue)
{
@ -170,7 +170,7 @@ class Properties
*
* @param int|string $time
*
* @return Properties
* @return $this
*/
public function setCreated($time)
{
@ -204,7 +204,7 @@ class Properties
*
* @param int|string $time
*
* @return Properties
* @return $this
*/
public function setModified($time)
{
@ -238,7 +238,7 @@ class Properties
*
* @param string $title
*
* @return Properties
* @return $this
*/
public function setTitle($title)
{
@ -262,7 +262,7 @@ class Properties
*
* @param string $description
*
* @return Properties
* @return $this
*/
public function setDescription($description)
{
@ -286,7 +286,7 @@ class Properties
*
* @param string $subject
*
* @return Properties
* @return $this
*/
public function setSubject($subject)
{
@ -310,7 +310,7 @@ class Properties
*
* @param string $keywords
*
* @return Properties
* @return $this
*/
public function setKeywords($keywords)
{
@ -334,7 +334,7 @@ class Properties
*
* @param string $category
*
* @return Properties
* @return $this
*/
public function setCategory($category)
{
@ -358,7 +358,7 @@ class Properties
*
* @param string $company
*
* @return Properties
* @return $this
*/
public function setCompany($company)
{
@ -382,7 +382,7 @@ class Properties
*
* @param string $manager
*
* @return Properties
* @return $this
*/
public function setManager($manager)
{
@ -453,7 +453,7 @@ class Properties
* 'd' : Date/Time
* 'b' : Boolean
*
* @return Properties
* @return $this
*/
public function setCustomProperty($propertyName, $propertyValue = '', $propertyType = null)
{

View File

@ -75,7 +75,7 @@ class Security
*
* @param bool $pValue
*
* @return Security
* @return $this
*/
public function setLockRevision($pValue)
{
@ -99,7 +99,7 @@ class Security
*
* @param bool $pValue
*
* @return Security
* @return $this
*/
public function setLockStructure($pValue)
{
@ -123,7 +123,7 @@ class Security
*
* @param bool $pValue
*
* @return Security
* @return $this
*/
public function setLockWindows($pValue)
{
@ -148,7 +148,7 @@ class Security
* @param string $pValue
* @param bool $pAlreadyHashed If the password has already been hashed, set this to true
*
* @return Security
* @return $this
*/
public function setRevisionsPassword($pValue, $pAlreadyHashed = false)
{
@ -176,7 +176,7 @@ class Security
* @param string $pValue
* @param bool $pAlreadyHashed If the password has already been hashed, set this to true
*
* @return Security
* @return $this
*/
public function setWorkbookPassword($pValue, $pAlreadyHashed = false)
{

View File

@ -0,0 +1,111 @@
<?php
namespace PhpOffice\PhpSpreadsheet;
use PhpOffice\PhpSpreadsheet\Calculation\Category;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use ReflectionClass;
use ReflectionException;
use UnexpectedValueException;
class DocumentGenerator
{
/**
* @param array[] $phpSpreadsheetFunctions
*
* @throws ReflectionException
*
* @return string
*/
public static function generateFunctionListByCategory(array $phpSpreadsheetFunctions): string
{
$result = "# Function list by category\n";
foreach (self::getCategories() as $categoryConstant => $category) {
$result .= "\n";
$result .= "## {$categoryConstant}\n";
$result .= "\n";
$lengths = [20, 42];
$result .= self::tableRow($lengths, ['Excel Function', 'PhpSpreadsheet Function']) . "\n";
$result .= self::tableRow($lengths, null) . "\n";
foreach ($phpSpreadsheetFunctions as $excelFunction => $functionInfo) {
if ($category === $functionInfo['category']) {
$phpFunction = self::getPhpSpreadsheetFunctionText($functionInfo['functionCall']);
$result .= self::tableRow($lengths, [$excelFunction, $phpFunction]) . "\n";
}
}
}
return $result;
}
/**
* @throws ReflectionException
*
* @return array
*/
private static function getCategories(): array
{
return (new ReflectionClass(Category::class))->getConstants();
}
private static function tableRow(array $lengths, array $values = null): string
{
$result = '';
foreach (array_map(null, $lengths, $values ?? []) as $i => [$length, $value]) {
$pad = $value === null ? '-' : ' ';
if ($i > 0) {
$result .= '|' . $pad;
}
$result .= str_pad($value ?? '', $length, $pad);
}
return rtrim($result, ' ');
}
private static function getPhpSpreadsheetFunctionText($functionCall): string
{
if (is_string($functionCall)) {
return $functionCall;
}
if ($functionCall === [Functions::class, 'DUMMY']) {
return '**Not yet Implemented**';
}
if (is_array($functionCall)) {
return "\\{$functionCall[0]}::{$functionCall[1]}";
}
throw new UnexpectedValueException(
'$functionCall is of type ' . gettype($functionCall) . '. string or array expected'
);
}
/**
* @param array[] $phpSpreadsheetFunctions
*
* @throws ReflectionException
*
* @return string
*/
public static function generateFunctionListByName(array $phpSpreadsheetFunctions): string
{
$categoryConstants = array_flip(self::getCategories());
$result = "# Function list by name\n";
$lastAlphabet = null;
foreach ($phpSpreadsheetFunctions as $excelFunction => $functionInfo) {
$lengths = [20, 31, 42];
if ($lastAlphabet !== $excelFunction[0]) {
$lastAlphabet = $excelFunction[0];
$result .= "\n";
$result .= "## {$lastAlphabet}\n";
$result .= "\n";
$result .= self::tableRow($lengths, ['Excel Function', 'Category', 'PhpSpreadsheet Function']) . "\n";
$result .= self::tableRow($lengths, null) . "\n";
}
$category = $categoryConstants[$functionInfo['category']];
$phpFunction = self::getPhpSpreadsheetFunctionText($functionInfo['functionCall']);
$result .= self::tableRow($lengths, [$excelFunction, $category, $phpFunction]) . "\n";
}
return $result;
}
}

View File

@ -52,9 +52,8 @@ abstract class IOFactory
// Instantiate writer
$className = self::$writers[$writerType];
$writer = new $className($spreadsheet);
return $writer;
return new $className($spreadsheet);
}
/**
@ -74,9 +73,8 @@ abstract class IOFactory
// Instantiate reader
$className = self::$readers[$readerType];
$reader = new $className();
return $reader;
return new $className();
}
/**

View File

@ -82,7 +82,7 @@ class NamedRange
*
* @param string $value
*
* @return NamedRange
* @return $this
*/
public function setName($value)
{
@ -123,7 +123,7 @@ class NamedRange
*
* @param Worksheet $value
*
* @return NamedRange
* @return $this
*/
public function setWorksheet(Worksheet $value = null)
{
@ -149,7 +149,7 @@ class NamedRange
*
* @param string $value
*
* @return NamedRange
* @return $this
*/
public function setRange($value)
{
@ -175,7 +175,7 @@ class NamedRange
*
* @param bool $value
*
* @return NamedRange
* @return $this
*/
public function setLocalOnly($value)
{
@ -200,7 +200,7 @@ class NamedRange
*
* @param null|Worksheet $value
*
* @return NamedRange
* @return $this
*/
public function setScope(Worksheet $value = null)
{

View File

@ -70,7 +70,7 @@ class Csv extends BaseReader
*
* @param string $pValue Input encoding, eg: 'UTF-8'
*
* @return Csv
* @return $this
*/
public function setInputEncoding($pValue)
{
@ -175,9 +175,8 @@ class Csv extends BaseReader
}
}
foreach ($potentialDelimiters as $delimiter) {
$counts[$delimiter][] = isset($countLine[$delimiter])
? $countLine[$delimiter]
: 0;
$counts[$delimiter][] = $countLine[$delimiter]
?? 0;
}
}
@ -416,7 +415,7 @@ class Csv extends BaseReader
*
* @param string $delimiter Delimiter, eg: ','
*
* @return CSV
* @return $this
*/
public function setDelimiter($delimiter)
{
@ -440,7 +439,7 @@ class Csv extends BaseReader
*
* @param string $enclosure Enclosure, defaults to "
*
* @return CSV
* @return $this
*/
public function setEnclosure($enclosure)
{
@ -467,7 +466,7 @@ class Csv extends BaseReader
*
* @param int $pValue Sheet index
*
* @return CSV
* @return $this
*/
public function setSheetIndex($pValue)
{
@ -481,7 +480,7 @@ class Csv extends BaseReader
*
* @param bool $contiguous
*
* @return Csv
* @return $this
*/
public function setContiguous($contiguous)
{

View File

@ -267,7 +267,7 @@ class Gnumeric extends BaseReader
break;
case 'user-defined':
list(, $attrName) = explode(':', $attributes['name']);
[, $attrName] = explode(':', $attributes['name']);
switch ($attrName) {
case 'publisher':
$docProps->setCompany(trim($propertyValue));
@ -879,7 +879,7 @@ class Gnumeric extends BaseReader
private static function parseGnumericColour($gnmColour)
{
list($gnmR, $gnmG, $gnmB) = explode(':', $gnmColour);
[$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour);
$gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2);
$gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2);
$gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2);

View File

@ -223,7 +223,7 @@ class Html extends BaseReader
*
* @param string $pValue Input encoding, eg: 'ANSI'
*
* @return Html
* @return $this
*/
public function setInputEncoding($pValue)
{
@ -374,8 +374,9 @@ class Html extends BaseReader
// no break
case 'br':
if ($this->tableLevel > 0) {
// If we're inside a table, replace with a \n
// If we're inside a table, replace with a \n and set the cell to wrap
$cellContent .= "\n";
$sheet->getStyle($column . $row)->getAlignment()->setWrapText(true);
} else {
// Otherwise flush our existing content and move the row cursor on
$this->flushCell($sheet, $column, $row, $cellContent);
@ -489,22 +490,22 @@ class Html extends BaseReader
case 'td':
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
// apply inline style
$this->applyInlineStyle($sheet, $row, $column, $attributeArray);
while (isset($this->rowspan[$column . $row])) {
++$column;
}
// apply inline style
$this->applyInlineStyle($sheet, $row, $column, $attributeArray);
$this->flushCell($sheet, $column, $row, $cellContent);
if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
//create merging rowspan and colspan
$columnTo = $column;
for ($i = 0; $i < $attributeArray['colspan'] - 1; ++$i) {
for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
++$columnTo;
}
$range = $column . $row . ':' . $columnTo . ($row + $attributeArray['rowspan'] - 1);
$range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1);
foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
$this->rowspan[$value] = true;
}
@ -512,7 +513,7 @@ class Html extends BaseReader
$column = $columnTo;
} elseif (isset($attributeArray['rowspan'])) {
//create merging rowspan
$range = $column . $row . ':' . $column . ($row + $attributeArray['rowspan'] - 1);
$range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1);
foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
$this->rowspan[$value] = true;
}
@ -520,7 +521,7 @@ class Html extends BaseReader
} elseif (isset($attributeArray['colspan'])) {
//create merging colspan
$columnTo = $column;
for ($i = 0; $i < $attributeArray['colspan'] - 1; ++$i) {
for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
++$columnTo;
}
$sheet->mergeCells($column . $row . ':' . $columnTo . $row);
@ -591,28 +592,63 @@ class Html extends BaseReader
throw new Exception($pFilename . ' is an Invalid HTML file.');
}
// Create new sheet
while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
$spreadsheet->createSheet();
}
$spreadsheet->setActiveSheetIndex($this->sheetIndex);
// Create a new DOM object
// Create a new DOM object
$dom = new DOMDocument();
// Reload the HTML file into the DOM object
// Reload the HTML file into the DOM object
$loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scanFile($pFilename), 'HTML-ENTITIES', 'UTF-8'));
if ($loaded === false) {
throw new Exception('Failed to load ' . $pFilename . ' as a DOM Document');
}
// Discard white space
$dom->preserveWhiteSpace = false;
return $this->loadDocument($dom, $spreadsheet);
}
/**
* Spreadsheet from content.
*
* @param string $content
* @param null|Spreadsheet $spreadsheet
*
* @return Spreadsheet
*/
public function loadFromString($content, ?Spreadsheet $spreadsheet = null): Spreadsheet
{
// Create a new DOM object
$dom = new DOMDocument();
// Reload the HTML file into the DOM object
$loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scan($content), 'HTML-ENTITIES', 'UTF-8'));
if ($loaded === false) {
throw new Exception('Failed to load content as a DOM Document');
}
return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet());
}
/**
* Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance.
*
* @param DOMDocument $document
* @param Spreadsheet $spreadsheet
*
* @throws \PhpOffice\PhpSpreadsheet\Exception
*
* @return Spreadsheet
*/
private function loadDocument(DOMDocument $document, Spreadsheet $spreadsheet): Spreadsheet
{
while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
$spreadsheet->createSheet();
}
$spreadsheet->setActiveSheetIndex($this->sheetIndex);
// Discard white space
$document->preserveWhiteSpace = false;
$row = 0;
$column = 'A';
$content = '';
$this->rowspan = [];
$this->processDomElement($dom, $spreadsheet->getActiveSheet(), $row, $column, $content);
$this->processDomElement($document, $spreadsheet->getActiveSheet(), $row, $column, $content);
// Return
return $spreadsheet;
@ -633,7 +669,7 @@ class Html extends BaseReader
*
* @param int $pValue Sheet index
*
* @return HTML
* @return $this
*/
public function setSheetIndex($pValue)
{
@ -919,7 +955,7 @@ class Html extends BaseReader
*/
private function setBorderStyle(Style $cellStyle, $styleValue, $type)
{
list(, $borderStyle, $color) = explode(' ', $styleValue);
[, $borderStyle, $color] = explode(' ', $styleValue);
$cellStyle->applyFromArray([
'borders' => [

View File

@ -490,7 +490,7 @@ class Ods extends BaseReader
$dateObj = new DateTime($value, $GMT);
$dateObj->setTimeZone($timezoneObj);
list($year, $month, $day, $hour, $minute, $second) = explode(
[$year, $month, $day, $hour, $minute, $second] = explode(
' ',
$dateObj->format('Y m d H i s')
);

View File

@ -19,12 +19,11 @@ class Properties
$docProps = $this->spreadsheet->getProperties();
$officeProperty = $xml->children($namespacesMeta['office']);
foreach ($officeProperty as $officePropertyData) {
/** @var \SimpleXMLElement $officePropertyData */
$officePropertiesDC = (object) [];
// @var \SimpleXMLElement $officePropertyData
if (isset($namespacesMeta['dc'])) {
$officePropertiesDC = $officePropertyData->children($namespacesMeta['dc']);
$this->setCoreProperties($docProps, $officePropertiesDC);
}
$this->setCoreProperties($docProps, $officePropertiesDC);
$officePropertyMeta = (object) [];
if (isset($namespacesMeta['dc'])) {

View File

@ -83,7 +83,7 @@ class Slk extends BaseReader
*
* @param string $pValue Input encoding, eg: 'ANSI'
*
* @return Slk
* @return $this
*/
public function setInputEncoding($pValue)
{
@ -384,7 +384,7 @@ class Slk extends BaseReader
break;
case 'W':
list($startCol, $endCol, $columnWidth) = explode(' ', substr($rowDatum, 1));
[$startCol, $endCol, $columnWidth] = explode(' ', substr($rowDatum, 1));
break;
case 'S':
@ -485,7 +485,7 @@ class Slk extends BaseReader
*
* @param int $pValue Sheet index
*
* @return Slk
* @return $this
*/
public function setSheetIndex($pValue)
{

View File

@ -1089,8 +1089,8 @@ class Xls extends BaseReader
}
// calculate the width and height of the shape
list($startColumn, $startRow) = Coordinate::coordinateFromString($spContainer->getStartCoordinates());
list($endColumn, $endRow) = Coordinate::coordinateFromString($spContainer->getEndCoordinates());
[$startColumn, $startRow] = Coordinate::coordinateFromString($spContainer->getStartCoordinates());
[$endColumn, $endRow] = Coordinate::coordinateFromString($spContainer->getEndCoordinates());
$startOffsetX = $spContainer->getStartOffsetX();
$startOffsetY = $spContainer->getStartOffsetY();
@ -1175,7 +1175,7 @@ class Xls extends BaseReader
// treat SHAREDFMLA records
if ($this->version == self::XLS_BIFF8) {
foreach ($this->sharedFormulaParts as $cell => $baseCell) {
list($column, $row) = Coordinate::coordinateFromString($cell);
[$column, $row] = Coordinate::coordinateFromString($cell);
if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
$formula = $this->getFormulaFromStructure($this->sharedFormulas[$baseCell], $cell);
$this->phpSheet->getCell($cell)->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA);
@ -1251,8 +1251,8 @@ class Xls extends BaseReader
$coordinateStrings = explode(':', $extractedRange);
if (count($coordinateStrings) == 2) {
list($firstColumn, $firstRow) = Coordinate::coordinateFromString($coordinateStrings[0]);
list($lastColumn, $lastRow) = Coordinate::coordinateFromString($coordinateStrings[1]);
[$firstColumn, $firstRow] = Coordinate::coordinateFromString($coordinateStrings[0]);
[$lastColumn, $lastRow] = Coordinate::coordinateFromString($coordinateStrings[1]);
if ($firstColumn == 'A' and $lastColumn == 'IV') {
// then we have repeating rows
@ -3825,7 +3825,7 @@ class Xls extends BaseReader
}
}
if (!$this->readDataOnly && !$emptyCell) {
if (!$this->readDataOnly && !$emptyCell && isset($this->mapCellXfIndex[$xfIndex])) {
// add style information
$cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
}
@ -5279,12 +5279,10 @@ class Xls extends BaseReader
$nextIdentifier = self::getUInt2d($this->data, $this->pos);
} while ($nextIdentifier == self::XLS_TYPE_CONTINUE);
$splicedData = [
return [
'recordData' => $data,
'spliceOffsets' => $spliceOffsets,
];
return $splicedData;
}
/**
@ -5355,12 +5353,12 @@ class Xls extends BaseReader
$formulaStrings = [];
foreach ($tokens as $token) {
// initialize spaces
$space0 = isset($space0) ? $space0 : ''; // spaces before next token, not tParen
$space1 = isset($space1) ? $space1 : ''; // carriage returns before next token, not tParen
$space2 = isset($space2) ? $space2 : ''; // spaces before opening parenthesis
$space3 = isset($space3) ? $space3 : ''; // carriage returns before opening parenthesis
$space4 = isset($space4) ? $space4 : ''; // spaces before closing parenthesis
$space5 = isset($space5) ? $space5 : ''; // carriage returns before closing parenthesis
$space0 = $space0 ?? ''; // spaces before next token, not tParen
$space1 = $space1 ?? ''; // carriage returns before next token, not tParen
$space2 = $space2 ?? ''; // spaces before opening parenthesis
$space3 = $space3 ?? ''; // carriage returns before opening parenthesis
$space4 = $space4 ?? ''; // spaces before closing parenthesis
$space5 = $space5 ?? ''; // carriage returns before closing parenthesis
switch ($token['name']) {
case 'tAdd': // addition
@ -7145,7 +7143,7 @@ class Xls extends BaseReader
*/
private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1')
{
list($baseCol, $baseRow) = Coordinate::coordinateFromString($baseCell);
[$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell);
$baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
// offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
@ -7328,7 +7326,7 @@ class Xls extends BaseReader
*/
private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1')
{
list($baseCol, $baseRow) = Coordinate::coordinateFromString($baseCell);
[$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell);
$baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
// TODO: if cell range is just a single cell, should this funciton

View File

@ -77,34 +77,17 @@ class Xlsx extends BaseReader
{
File::assertFile($pFilename);
$xl = false;
// Load file
$result = false;
$zip = new ZipArchive();
if ($zip->open($pFilename) === true) {
// check if it is an OOXML archive
$rels = simplexml_load_string(
$this->securityScanner->scan(
$this->getFromZipArchive($zip, '_rels/.rels')
),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
if ($rels !== false) {
foreach ($rels->Relationship as $rel) {
switch ($rel['Type']) {
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
if (basename($rel['Target']) == 'workbook.xml') {
$xl = true;
}
break;
}
}
}
if ($zip->open($pFilename) === true) {
$workbookBasename = $this->getWorkbookBaseName($zip);
$result = !empty($workbookBasename);
$zip->close();
}
return $xl;
return $result;
}
/**
@ -357,8 +340,9 @@ class Xlsx extends BaseReader
// Read the theme first, because we need the colour scheme when reading the styles
//~ http://schemas.openxmlformats.org/package/2006/relationships"
$workbookBasename = $this->getWorkbookBaseName($zip);
$wbRels = simplexml_load_string(
$this->securityScanner->scan($this->getFromZipArchive($zip, 'xl/_rels/workbook.xml.rels')),
$this->securityScanner->scan($this->getFromZipArchive($zip, "xl/_rels/${workbookBasename}.rels")),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
@ -445,18 +429,20 @@ class Xlsx extends BaseReader
$sharedStrings = [];
$xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings']"));
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
$xmlStrings = simplexml_load_string(
$this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
if (isset($xmlStrings, $xmlStrings->si)) {
foreach ($xmlStrings->si as $val) {
if (isset($val->t)) {
$sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t);
} elseif (isset($val->r)) {
$sharedStrings[] = $this->parseRichText($val);
if ($xpath) {
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
$xmlStrings = simplexml_load_string(
$this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
if (isset($xmlStrings, $xmlStrings->si)) {
foreach ($xmlStrings->si as $val) {
if (isset($val->t)) {
$sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t);
} elseif (isset($val->r)) {
$sharedStrings[] = $this->parseRichText($val);
}
}
}
}
@ -743,15 +729,6 @@ class Xlsx extends BaseReader
// read empty cells or the cells are not empty
if ($this->readEmptyCells || ($value !== null && $value !== '')) {
// Check for numeric values
if (is_numeric($value) && $cellDataType != 's') {
if ($value == (int) $value) {
$value = (int) $value;
} elseif ($value == (float) $value) {
$value = (float) $value;
}
}
// Rich text?
if ($value instanceof RichText && $this->readDataOnly) {
$value = $value->getPlainText();
@ -1352,7 +1329,7 @@ class Xlsx extends BaseReader
$rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$newRangeSets = [];
foreach ($rangeSets as $rangeSet) {
list($sheetName, $rangeSet) = Worksheet::extractSheetTitle($rangeSet, true);
[$sheetName, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true);
if (strpos($rangeSet, ':') === false) {
$rangeSet = $rangeSet . ':' . $rangeSet;
}
@ -1426,7 +1403,7 @@ class Xlsx extends BaseReader
$locatedSheet = $excel->getSheetByName($extractedSheetName);
// Modify range
list($worksheetName, $extractedRange) = Worksheet::extractSheetTitle($extractedRange, true);
[$worksheetName, $extractedRange] = Worksheet::extractSheetTitle($extractedRange, true);
}
if ($locatedSheet !== null) {
@ -1437,7 +1414,7 @@ class Xlsx extends BaseReader
}
}
if ((!$this->readDataOnly) || (!empty($this->loadSheetsOnly))) {
if ((!$this->readDataOnly || !empty($this->loadSheetsOnly)) && isset($xmlWorkbook->bookViews->workbookView)) {
$workbookView = $xmlWorkbook->bookViews->workbookView;
// active sheet index
@ -1643,8 +1620,6 @@ class Xlsx extends BaseReader
$docStyle->getFill()->setFillType($patternType);
if ($style->fill->patternFill->fgColor) {
$docStyle->getFill()->getStartColor()->setARGB(self::readColor($style->fill->patternFill->fgColor, true));
} else {
$docStyle->getFill()->getStartColor()->setARGB('FF000000');
}
if ($style->fill->patternFill->bgColor) {
$docStyle->getFill()->getEndColor()->setARGB(self::readColor($style->fill->patternFill->bgColor, true));
@ -1841,7 +1816,7 @@ class Xlsx extends BaseReader
private static function getArrayItem($array, $key = 0)
{
return isset($array[$key]) ? $array[$key] : null;
return $array[$key] ?? null;
}
private static function dirAdd($base, $add)
@ -1851,7 +1826,7 @@ class Xlsx extends BaseReader
private static function toCSSArray($style)
{
$style = trim(str_replace(["\r", "\n"], '', $style), ';');
$style = self::stripWhiteSpaceFromStyleString($style);
$temp = explode(';', $style);
$style = [];
@ -1880,6 +1855,11 @@ class Xlsx extends BaseReader
return $style;
}
public static function stripWhiteSpaceFromStyleString($string)
{
return trim(str_replace(["\r", "\n", ' '], '', $string), ';');
}
private static function boolean($value)
{
if (is_object($value)) {
@ -2026,4 +2006,38 @@ class Xlsx extends BaseReader
return (bool) $xsdBoolean;
}
/**
* @param ZipArchive $zip Opened zip archive
*
* @return string basename of the used excel workbook
*/
private function getWorkbookBaseName(ZipArchive $zip)
{
$workbookBasename = '';
// check if it is an OOXML archive
$rels = simplexml_load_string(
$this->securityScanner->scan(
$this->getFromZipArchive($zip, '_rels/.rels')
),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
if ($rels !== false) {
foreach ($rels->Relationship as $rel) {
switch ($rel['Type']) {
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
$basename = basename($rel['Target']);
if (preg_match('/workbook.*\.xml/', $basename)) {
$workbookBasename = $basename;
}
break;
}
}
}
return $workbookBasename;
}
}

View File

@ -20,7 +20,8 @@ class AutoFilter
public function load()
{
$autoFilterRange = (string) $this->worksheetXml->autoFilter['ref'];
// Remove all "$" in the auto filter range
$autoFilterRange = preg_replace('/\$/', '', $this->worksheetXml->autoFilter['ref']);
if (strpos($autoFilterRange, ':') !== false) {
$this->readAutoFilter($autoFilterRange, $this->worksheetXml);
}

View File

@ -36,6 +36,8 @@ class ConditionalStyles
if (((string) $cfRule['type'] == Conditional::CONDITION_NONE
|| (string) $cfRule['type'] == Conditional::CONDITION_CELLIS
|| (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSTEXT
|| (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSBLANKS
|| (string) $cfRule['type'] == Conditional::CONDITION_NOTCONTAINSBLANKS
|| (string) $cfRule['type'] == Conditional::CONDITION_EXPRESSION)
&& isset($this->dxfs[(int) ($cfRule['dxfId'])])) {
$conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule;

View File

@ -86,6 +86,6 @@ class Properties
private static function getArrayItem(array $array, $key = 0)
{
return isset($array[$key]) ? $array[$key] : null;
return $array[$key] ?? null;
}
}

View File

@ -24,6 +24,7 @@ class SheetViews extends BaseParserClass
$this->gridLines();
$this->headers();
$this->direction();
$this->showZeros();
if (isset($this->sheetViewXml->pane)) {
$this->pane();
@ -92,6 +93,15 @@ class SheetViews extends BaseParserClass
}
}
private function showZeros()
{
if (isset($this->sheetViewXml['showZeros'])) {
$this->worksheet->getSheetView()->setShowZeros(
self::boolean((string) $this->sheetViewXml['showZeros'])
);
}
}
private function pane()
{
$xSplit = 0;

View File

@ -8,6 +8,7 @@ use PhpOffice\PhpSpreadsheet\Style\Borders;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Style\Protection;
use PhpOffice\PhpSpreadsheet\Style\Style;
@ -71,6 +72,17 @@ class Styles extends BaseParserClass
}
}
private static function readNumberFormat(NumberFormat $numfmtStyle, \SimpleXMLElement $numfmtStyleXml)
{
if ($numfmtStyleXml->count() === 0) {
return;
}
$numfmt = $numfmtStyleXml->attributes();
if ($numfmt->count() > 0 && isset($numfmt['formatCode'])) {
$numfmtStyle->setFormatCode((string) $numfmt['formatCode']);
}
}
private static function readFillStyle(Fill $fillStyle, \SimpleXMLElement $fillStyleXml)
{
if ($fillStyleXml->gradientFill) {
@ -149,7 +161,11 @@ class Styles extends BaseParserClass
private function readStyle(Style $docStyle, $style)
{
$docStyle->getNumberFormat()->setFormatCode($style->numFmt);
if ($style->numFmt instanceof \SimpleXMLElement) {
self::readNumberFormat($docStyle->getNumberFormat(), $style->numFmt);
} else {
$docStyle->getNumberFormat()->setFormatCode($style->numFmt);
}
if (isset($style->font)) {
self::readFontStyle($docStyle->getFont(), $style->font);
@ -163,7 +179,7 @@ class Styles extends BaseParserClass
self::readBorderStyle($docStyle->getBorders(), $style->border);
}
if (isset($style->alignment)) {
if (isset($style->alignment->alignment)) {
self::readAlignmentStyle($docStyle->getAlignment(), $style->alignment);
}
@ -260,6 +276,6 @@ class Styles extends BaseParserClass
private static function getArrayItem($array, $key = 0)
{
return isset($array[$key]) ? $array[$key] : null;
return $array[$key] ?? null;
}
}

View File

@ -285,9 +285,8 @@ class Xml extends BaseReader
{
$pixels = ($widthUnits / 256) * 7;
$offsetWidthUnits = $widthUnits % 256;
$pixels += round($offsetWidthUnits / (256 / 7));
return $pixels;
return $pixels + round($offsetWidthUnits / (256 / 7));
}
protected static function hex2str($hex)

View File

@ -82,11 +82,8 @@ class ReferenceHelper
*/
public static function cellSort($a, $b)
{
// TODO Scrutinizer doesn't like sscanf($a, '%[A-Z]%d', $ac, $ar), and we can't use short list() syntax
// [$ac, $ar] = sscanf($a, '%[A-Z]%d') while retaining PHP 5.6 support.
// Switch when we drop support for 5.6
list($ac, $ar) = sscanf($a, '%[A-Z]%d');
list($bc, $br) = sscanf($b, '%[A-Z]%d');
[$ac, $ar] = sscanf($a, '%[A-Z]%d');
[$bc, $br] = sscanf($b, '%[A-Z]%d');
if ($ar === $br) {
return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
@ -106,11 +103,8 @@ class ReferenceHelper
*/
public static function cellReverseSort($a, $b)
{
// TODO Scrutinizer doesn't like sscanf($a, '%[A-Z]%d', $ac, $ar), and we can't use short list() syntax
// [$ac, $ar] = sscanf($a, '%[A-Z]%d') while retaining PHP 5.6 support.
// Switch when we drop support for 5.6
list($ac, $ar) = sscanf($a, '%[A-Z]%d');
list($bc, $br) = sscanf($b, '%[A-Z]%d');
[$ac, $ar] = sscanf($a, '%[A-Z]%d');
[$bc, $br] = sscanf($b, '%[A-Z]%d');
if ($ar === $br) {
return 1 - strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
@ -132,7 +126,7 @@ class ReferenceHelper
*/
private static function cellAddressInDeleteRange($cellAddress, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols)
{
list($cellColumn, $cellRow) = Coordinate::coordinateFromString($cellAddress);
[$cellColumn, $cellRow] = Coordinate::coordinateFromString($cellAddress);
$cellColumnIndex = Coordinate::columnIndexFromString($cellColumn);
// Is cell within the range of rows/columns if we're deleting
if ($pNumRows < 0 &&
@ -319,7 +313,7 @@ class ReferenceHelper
if (!empty($aColumnDimensions)) {
foreach ($aColumnDimensions as $objColumnDimension) {
$newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1', $pBefore, $pNumCols, $pNumRows);
list($newReference) = Coordinate::coordinateFromString($newReference);
[$newReference] = Coordinate::coordinateFromString($newReference);
if ($objColumnDimension->getColumnIndex() != $newReference) {
$objColumnDimension->setColumnIndex($newReference);
}
@ -344,7 +338,7 @@ class ReferenceHelper
if (!empty($aRowDimensions)) {
foreach ($aRowDimensions as $objRowDimension) {
$newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex(), $pBefore, $pNumCols, $pNumRows);
list(, $newReference) = Coordinate::coordinateFromString($newReference);
[, $newReference] = Coordinate::coordinateFromString($newReference);
if ($objRowDimension->getRowIndex() != $newReference) {
$objRowDimension->setRowIndex($newReference);
}
@ -378,7 +372,7 @@ class ReferenceHelper
$allCoordinates = $pSheet->getCoordinates();
// Get coordinate of $pBefore
list($beforeColumn, $beforeRow) = Coordinate::coordinateFromString($pBefore);
[$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($pBefore);
$beforeColumnIndex = Coordinate::columnIndexFromString($beforeColumn);
// Clear cells if we are removing columns or rows
@ -539,7 +533,7 @@ class ReferenceHelper
$row = 0;
sscanf($pBefore, '%[A-Z]%d', $column, $row);
$columnIndex = Coordinate::columnIndexFromString($column);
list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($autoFilterRange);
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange);
if ($columnIndex <= $rangeEnd[0]) {
if ($pNumCols < 0) {
// If we're actually deleting any columns that fall within the autofilter range,
@ -707,7 +701,7 @@ class ReferenceHelper
if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
$toString = ($match[2] > '') ? $match[2] . '!' : '';
$toString .= $modified3 . ':' . $modified4;
list($column, $row) = Coordinate::coordinateFromString($match[3]);
[$column, $row] = Coordinate::coordinateFromString($match[3]);
// Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
$column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
$row = trim($row, '$') + 10000000;
@ -733,7 +727,7 @@ class ReferenceHelper
if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
$toString = ($match[2] > '') ? $match[2] . '!' : '';
$toString .= $modified3;
list($column, $row) = Coordinate::coordinateFromString($match[3]);
[$column, $row] = Coordinate::coordinateFromString($match[3]);
// Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
$column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
$row = trim($row, '$') + 10000000;
@ -881,10 +875,10 @@ class ReferenceHelper
}
// Get coordinate of $pBefore
list($beforeColumn, $beforeRow) = Coordinate::coordinateFromString($pBefore);
[$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($pBefore);
// Get coordinate of $pCellReference
list($newColumn, $newRow) = Coordinate::coordinateFromString($pCellReference);
[$newColumn, $newRow] = Coordinate::coordinateFromString($pCellReference);
// Verify which parts should be updated
$updateColumn = (($newColumn[0] != '$') && ($beforeColumn[0] != '$') && (Coordinate::columnIndexFromString($newColumn) >= Coordinate::columnIndexFromString($beforeColumn)));

View File

@ -47,7 +47,7 @@ class RichText implements IComparable
*
* @param ITextElement $pText Rich text element
*
* @return RichText
* @return $this
*/
public function addText(ITextElement $pText)
{
@ -133,7 +133,7 @@ class RichText implements IComparable
*
* @param ITextElement[] $textElements Array of elements
*
* @return RichText
* @return $this
*/
public function setRichTextElements(array $textElements)
{

View File

@ -40,7 +40,7 @@ class Run extends TextElement implements ITextElement
*
* @param Font $pFont Font
*
* @return ITextElement
* @return $this
*/
public function setFont(Font $pFont = null)
{

View File

@ -37,7 +37,7 @@ class TextElement implements ITextElement
*
* @param $text string Text
*
* @return ITextElement
* @return $this
*/
public function setText($text)
{

View File

@ -30,7 +30,6 @@ class Settings
* 7.2 < 7.2.1
* 7.1 < 7.1.13
* 7.0 < 7.0.27
* 5.6 ANY
* then you may need to disable this check to prevent unwanted behaviour in other threads
* SECURITY WARNING: Changing this flag is not recommended.
*
@ -122,7 +121,6 @@ class Settings
* 7.2 < 7.2.1
* 7.1 < 7.1.13
* 7.0 < 7.0.27
* 5.6 ANY
* then you may need to disable this check to prevent unwanted behaviour in other threads
* SECURITY WARNING: Changing this flag to false is not recommended.
*

View File

@ -298,9 +298,7 @@ class Font
$upperLeftCornerX = $textBox[6];
// Consider the rotation when calculating the width
$textWidth = max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX);
return $textWidth;
return max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX);
}
/**

View File

@ -174,7 +174,7 @@ class Matrix
switch ($match) {
//A($i0...; $j0...)
case 'integer,integer':
list($i0, $j0) = $args;
[$i0, $j0] = $args;
if ($i0 >= 0) {
$m = $this->m - $i0;
} else {
@ -197,7 +197,7 @@ class Matrix
break;
//A($i0...$iF; $j0...$jF)
case 'integer,integer,integer,integer':
list($i0, $iF, $j0, $jF) = $args;
[$i0, $iF, $j0, $jF] = $args;
if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
$m = $iF - $i0;
} else {
@ -220,7 +220,7 @@ class Matrix
break;
//$R = array of row indices; $C = array of column indices
case 'array,array':
list($RL, $CL) = $args;
[$RL, $CL] = $args;
if (count($RL) > 0) {
$m = count($RL);
} else {
@ -243,7 +243,7 @@ class Matrix
break;
//A($i0...$iF); $CL = array of column indices
case 'integer,integer,array':
list($i0, $iF, $CL) = $args;
[$i0, $iF, $CL] = $args;
if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
$m = $iF - $i0;
} else {
@ -266,7 +266,7 @@ class Matrix
break;
//$RL = array of row indices
case 'array,integer,integer':
list($RL, $j0, $jF) = $args;
[$RL, $j0, $jF] = $args;
if (count($RL) > 0) {
$m = count($RL);
} else {
@ -524,7 +524,7 @@ class Matrix
*
* @param mixed $B Matrix/Array
*
* @return Matrix Sum
* @return $this
*/
public function plusEquals(...$args)
{
@ -628,7 +628,7 @@ class Matrix
*
* @param mixed $B Matrix/Array
*
* @return Matrix Sum
* @return $this
*/
public function minusEquals(...$args)
{
@ -734,7 +734,7 @@ class Matrix
*
* @param mixed $B Matrix/Array
*
* @return Matrix Matrix Aij
* @return $this
*/
public function arrayTimesEquals(...$args)
{
@ -1091,7 +1091,7 @@ class Matrix
*
* @param mixed $B Matrix/Array
*
* @return Matrix Sum
* @return $this
*/
public function power(...$args)
{
@ -1150,7 +1150,7 @@ class Matrix
*
* @param mixed $B Matrix/Array
*
* @return Matrix Sum
* @return $this
*/
public function concat(...$args)
{

View File

@ -60,7 +60,7 @@ class QRDecomposition
{
if ($A instanceof Matrix) {
// Initialize.
$this->QR = $A->getArrayCopy();
$this->QR = $A->getArray();
$this->m = $A->getRowDimension();
$this->n = $A->getColumnDimension();
// Main loop.

View File

@ -253,7 +253,7 @@ class OLE
*/
private static function _readInt1($fh)
{
list(, $tmp) = unpack('c', fread($fh, 1));
[, $tmp] = unpack('c', fread($fh, 1));
return $tmp;
}
@ -267,7 +267,7 @@ class OLE
*/
private static function _readInt2($fh)
{
list(, $tmp) = unpack('v', fread($fh, 2));
[, $tmp] = unpack('v', fread($fh, 2));
return $tmp;
}
@ -281,7 +281,7 @@ class OLE
*/
private static function _readInt4($fh)
{
list(, $tmp) = unpack('V', fread($fh, 4));
[, $tmp] = unpack('V', fread($fh, 4));
return $tmp;
}

View File

@ -118,7 +118,7 @@ class Root extends PPS
$aList = [];
PPS::_savePpsSetPnt($aList, [$this]);
// calculate values for header
list($iSBDcnt, $iBBcnt, $iPPScnt) = $this->_calcSize($aList); //, $rhInfo);
[$iSBDcnt, $iBBcnt, $iPPScnt] = $this->_calcSize($aList); //, $rhInfo);
// Save Header
$this->_saveHeader($iSBDcnt, $iBBcnt, $iPPScnt);
@ -149,7 +149,7 @@ class Root extends PPS
public function _calcSize(&$raList)
{
// Calculate Basic Setting
list($iSBDcnt, $iBBcnt, $iPPScnt) = [0, 0, 0];
[$iSBDcnt, $iBBcnt, $iPPScnt] = [0, 0, 0];
$iSmallLen = 0;
$iSBcnt = 0;
$iCount = count($raList);

View File

@ -430,9 +430,7 @@ class StringHelper
// characters
$chars = self::convertEncoding($value, 'UTF-16LE', 'UTF-8');
$data = pack('vC', $ln, 0x0001) . $chars;
return $data;
return pack('vC', $ln, 0x0001) . $chars;
}
/**

View File

@ -211,7 +211,7 @@ class Xls
*/
public static function oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height)
{
list($column, $row) = Coordinate::coordinateFromString($coordinates);
[$column, $row] = Coordinate::coordinateFromString($coordinates);
$col_start = Coordinate::columnIndexFromString($column);
$row_start = $row - 1;
@ -269,7 +269,7 @@ class Xls
$startCoordinates = Coordinate::stringFromColumnIndex($col_start) . ($row_start + 1);
$endCoordinates = Coordinate::stringFromColumnIndex($col_end) . ($row_end + 1);
$twoAnchor = [
return [
'startCoordinates' => $startCoordinates,
'startOffsetX' => $x1,
'startOffsetY' => $y1,
@ -277,7 +277,5 @@ class Xls
'endOffsetX' => $x2,
'endOffsetY' => $y2,
];
return $twoAnchor;
}
}

View File

@ -721,7 +721,7 @@ class Spreadsheet
{
$worksheetCount = count($this->workSheetCollection);
for ($i = 0; $i < $worksheetCount; ++$i) {
if ($this->workSheetCollection[$i]->getTitle() === $pName) {
if ($this->workSheetCollection[$i]->getTitle() === trim($pName, "'")) {
return $this->workSheetCollection[$i];
}
}
@ -955,7 +955,7 @@ class Spreadsheet
* @param string $namedRange
* @param null|Worksheet $pSheet scope: use null for global scope
*
* @return Spreadsheet
* @return $this
*/
public function removeNamedRange($namedRange, Worksheet $pSheet = null)
{

View File

@ -140,7 +140,7 @@ class Alignment extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return Alignment
* @return $this
*/
public function applyFromArray(array $pStyles)
{
@ -193,7 +193,7 @@ class Alignment extends Supervisor
*
* @param string $pValue see self::HORIZONTAL_*
*
* @return Alignment
* @return $this
*/
public function setHorizontal($pValue)
{
@ -230,7 +230,7 @@ class Alignment extends Supervisor
*
* @param string $pValue see self::VERTICAL_*
*
* @return Alignment
* @return $this
*/
public function setVertical($pValue)
{
@ -269,7 +269,7 @@ class Alignment extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return Alignment
* @return $this
*/
public function setTextRotation($pValue)
{
@ -312,7 +312,7 @@ class Alignment extends Supervisor
*
* @param bool $pValue
*
* @return Alignment
* @return $this
*/
public function setWrapText($pValue)
{
@ -348,7 +348,7 @@ class Alignment extends Supervisor
*
* @param bool $pValue
*
* @return Alignment
* @return $this
*/
public function setShrinkToFit($pValue)
{
@ -384,7 +384,7 @@ class Alignment extends Supervisor
*
* @param int $pValue
*
* @return Alignment
* @return $this
*/
public function setIndent($pValue)
{
@ -424,7 +424,7 @@ class Alignment extends Supervisor
*
* @param int $pValue
*
* @return Alignment
* @return $this
*/
public function setReadOrder($pValue)
{

View File

@ -127,7 +127,7 @@ class Border extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return Border
* @return $this
*/
public function applyFromArray(array $pStyles)
{
@ -166,7 +166,7 @@ class Border extends Supervisor
* When passing a boolean, FALSE equates Border::BORDER_NONE
* and TRUE to Border::BORDER_MEDIUM
*
* @return Border
* @return $this
*/
public function setBorderStyle($pValue)
{
@ -202,7 +202,7 @@ class Border extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return Border
* @return $this
*/
public function setColor(Color $pValue)
{

View File

@ -197,7 +197,7 @@ class Borders extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return Borders
* @return $this
*/
public function applyFromArray(array $pStyles)
{
@ -382,7 +382,7 @@ class Borders extends Supervisor
*
* @param int $pValue see self::DIAGONAL_*
*
* @return Borders
* @return $this
*/
public function setDiagonalDirection($pValue)
{

View File

@ -6,6 +6,17 @@ use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class Color extends Supervisor
{
const NAMED_COLORS = [
'Black',
'White',
'Red',
'Green',
'Blue',
'Yellow',
'Magenta',
'Cyan',
];
// Colors
const COLOR_BLACK = 'FF000000';
const COLOR_WHITE = 'FFFFFFFF';
@ -95,7 +106,7 @@ class Color extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return Color
* @return $this
*/
public function applyFromArray(array $pStyles)
{
@ -132,7 +143,7 @@ class Color extends Supervisor
*
* @param string $pValue see self::COLOR_*
*
* @return Color
* @return $this
*/
public function setARGB($pValue)
{
@ -168,7 +179,7 @@ class Color extends Supervisor
*
* @param string $pValue RGB value
*
* @return Color
* @return $this
*/
public function setRGB($pValue)
{
@ -198,11 +209,8 @@ class Color extends Supervisor
private static function getColourComponent($RGB, $offset, $hex = true)
{
$colour = substr($RGB, $offset, 2);
if (!$hex) {
$colour = hexdec($colour);
}
return $colour;
return ($hex) ? $colour : hexdec($colour);
}
/**
@ -257,7 +265,7 @@ class Color extends Supervisor
*/
public static function changeBrightness($hex, $adjustPercentage)
{
$rgba = (strlen($hex) == 8);
$rgba = (strlen($hex) === 8);
$red = self::getRed($hex, false);
$green = self::getGreen($hex, false);
@ -289,9 +297,9 @@ class Color extends Supervisor
}
$rgb = strtoupper(
str_pad(dechex($red), 2, '0', 0) .
str_pad(dechex($green), 2, '0', 0) .
str_pad(dechex($blue), 2, '0', 0)
str_pad(dechex((int) $red), 2, '0', 0) .
str_pad(dechex((int) $green), 2, '0', 0) .
str_pad(dechex((int) $blue), 2, '0', 0)
);
return (($rgba) ? 'FF' : '') . $rgb;
@ -304,7 +312,7 @@ class Color extends Supervisor
* @param bool $background Flag to indicate whether default background or foreground colour
* should be returned if the indexed colour doesn't exist
*
* @return Color
* @return self
*/
public static function indexedColor($pIndex, $background = false)
{

View File

@ -12,6 +12,7 @@ class Conditional implements IComparable
const CONDITION_CONTAINSTEXT = 'containsText';
const CONDITION_EXPRESSION = 'expression';
const CONDITION_CONTAINSBLANKS = 'containsBlanks';
const CONDITION_NOTCONTAINSBLANKS = 'notContainsBlanks';
// Operator types
const OPERATOR_NONE = '';
@ -93,7 +94,7 @@ class Conditional implements IComparable
*
* @param string $pValue Condition type, see self::CONDITION_*
*
* @return Conditional
* @return $this
*/
public function setConditionType($pValue)
{
@ -117,7 +118,7 @@ class Conditional implements IComparable
*
* @param string $pValue Conditional operator type, see self::OPERATOR_*
*
* @return Conditional
* @return $this
*/
public function setOperatorType($pValue)
{
@ -141,7 +142,7 @@ class Conditional implements IComparable
*
* @param string $value
*
* @return Conditional
* @return $this
*/
public function setText($value)
{
@ -165,7 +166,7 @@ class Conditional implements IComparable
*
* @param bool $value
*
* @return Conditional
* @return $this
*/
public function setStopIfTrue($value)
{
@ -189,7 +190,7 @@ class Conditional implements IComparable
*
* @param string[] $pValue Condition
*
* @return Conditional
* @return $this
*/
public function setConditions($pValue)
{
@ -206,7 +207,7 @@ class Conditional implements IComparable
*
* @param string $pValue Condition
*
* @return Conditional
* @return $this
*/
public function addCondition($pValue)
{
@ -230,7 +231,7 @@ class Conditional implements IComparable
*
* @param Style $pValue
*
* @return Conditional
* @return $this
*/
public function setStyle(Style $pValue = null)
{

View File

@ -141,7 +141,7 @@ class Fill extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return Fill
* @return $this
*/
public function applyFromArray(array $pStyles)
{
@ -188,7 +188,7 @@ class Fill extends Supervisor
*
* @param string $pValue Fill type, see self::FILL_*
*
* @return Fill
* @return $this
*/
public function setFillType($pValue)
{
@ -221,7 +221,7 @@ class Fill extends Supervisor
*
* @param float $pValue
*
* @return Fill
* @return $this
*/
public function setRotation($pValue)
{
@ -252,7 +252,7 @@ class Fill extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return Fill
* @return $this
*/
public function setStartColor(Color $pValue)
{
@ -286,7 +286,7 @@ class Fill extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return Fill
* @return $this
*/
public function setEndColor(Color $pValue)
{
@ -313,12 +313,13 @@ class Fill extends Supervisor
if ($this->isSupervisor) {
return $this->getSharedComponent()->getHashCode();
}
// Note that we don't care about colours for fill type NONE, but could have duplicate NONEs with
// different hashes if we don't explicitly prevent this
return md5(
$this->getFillType() .
$this->getRotation() .
$this->getStartColor()->getHashCode() .
$this->getEndColor()->getHashCode() .
($this->getFillType() !== self::FILL_NONE ? $this->getStartColor()->getHashCode() : '') .
($this->getFillType() !== self::FILL_NONE ? $this->getEndColor()->getHashCode() : '') .
__CLASS__
);
}

View File

@ -161,7 +161,7 @@ class Font extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return Font
* @return $this
*/
public function applyFromArray(array $pStyles)
{
@ -219,7 +219,7 @@ class Font extends Supervisor
*
* @param string $pValue
*
* @return Font
* @return $this
*/
public function setName($pValue)
{
@ -255,7 +255,7 @@ class Font extends Supervisor
*
* @param float $pValue
*
* @return Font
* @return $this
*/
public function setSize($pValue)
{
@ -291,7 +291,7 @@ class Font extends Supervisor
*
* @param bool $pValue
*
* @return Font
* @return $this
*/
public function setBold($pValue)
{
@ -327,7 +327,7 @@ class Font extends Supervisor
*
* @param bool $pValue
*
* @return Font
* @return $this
*/
public function setItalic($pValue)
{
@ -363,7 +363,7 @@ class Font extends Supervisor
*
* @param bool $pValue
*
* @return Font
* @return $this
*/
public function setSuperscript($pValue)
{
@ -400,7 +400,7 @@ class Font extends Supervisor
*
* @param bool $pValue
*
* @return Font
* @return $this
*/
public function setSubscript($pValue)
{
@ -439,7 +439,7 @@ class Font extends Supervisor
* If a boolean is passed, then TRUE equates to UNDERLINE_SINGLE,
* false equates to UNDERLINE_NONE
*
* @return Font
* @return $this
*/
public function setUnderline($pValue)
{
@ -477,7 +477,7 @@ class Font extends Supervisor
*
* @param bool $pValue
*
* @return Font
* @return $this
*/
public function setStrikethrough($pValue)
{
@ -512,7 +512,7 @@ class Font extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return Font
* @return $this
*/
public function setColor(Color $pValue)
{

View File

@ -23,8 +23,8 @@ class NumberFormat extends Supervisor
const FORMAT_PERCENTAGE_00 = '0.00%';
const FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd';
const FORMAT_DATE_YYYYMMDD = 'yy-mm-dd';
const FORMAT_DATE_DDMMYYYY = 'dd/mm/yy';
const FORMAT_DATE_YYYYMMDD = 'yyyy-mm-dd';
const FORMAT_DATE_DDMMYYYY = 'dd/mm/yyyy';
const FORMAT_DATE_DMYSLASH = 'd/m/yy';
const FORMAT_DATE_DMYMINUS = 'd-m-yy';
const FORMAT_DATE_DMMINUS = 'd-m';
@ -43,7 +43,7 @@ class NumberFormat extends Supervisor
const FORMAT_DATE_TIME6 = 'h:mm:ss';
const FORMAT_DATE_TIME7 = 'i:s.S';
const FORMAT_DATE_TIME8 = 'h:mm:ss;@';
const FORMAT_DATE_YYYYMMDDSLASH = 'yy/mm/dd;@';
const FORMAT_DATE_YYYYMMDDSLASH = 'yyyy/mm/dd;@';
const FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-';
const FORMAT_CURRENCY_USD = '$#,##0_-';
@ -139,7 +139,7 @@ class NumberFormat extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return NumberFormat
* @return $this
*/
public function applyFromArray(array $pStyles)
{
@ -176,7 +176,7 @@ class NumberFormat extends Supervisor
*
* @param string $pValue see self::FORMAT_*
*
* @return NumberFormat
* @return $this
*/
public function setFormatCode($pValue)
{
@ -213,7 +213,7 @@ class NumberFormat extends Supervisor
*
* @param int $pValue
*
* @return NumberFormat
* @return $this
*/
public function setBuiltInFormatCode($pValue)
{
@ -367,7 +367,7 @@ class NumberFormat extends Supervisor
self::fillBuiltInFormatCodes();
// Lookup format code
if (isset(self::$flippedBuiltInFormats[$formatCode])) {
if (array_key_exists($formatCode, self::$flippedBuiltInFormats)) {
return self::$flippedBuiltInFormats[$formatCode];
}
@ -551,24 +551,32 @@ class NumberFormat extends Supervisor
}
}
private static function complexNumberFormatMask($number, $mask)
private static function mergeComplexNumberFormatMasks($numbers, $masks)
{
$sign = ($number < 0.0);
$number = abs($number);
if (strpos($mask, '.') !== false) {
$numbers = explode('.', $number . '.0');
$masks = explode('.', $mask . '.0');
$result1 = self::complexNumberFormatMask($numbers[0], $masks[0]);
$result2 = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1])));
$decimalCount = strlen($numbers[1]);
$postDecimalMasks = [];
return (($sign) ? '-' : '') . $result1 . '.' . $result2;
}
do {
$tempMask = array_pop($masks);
$postDecimalMasks[] = $tempMask;
$decimalCount -= strlen($tempMask);
} while ($decimalCount > 0);
$r = preg_match_all('/0+/', $mask, $result, PREG_OFFSET_CAPTURE);
if ($r > 1) {
$result = array_reverse($result[0]);
return [
implode('.', $masks),
implode('.', array_reverse($postDecimalMasks)),
];
}
foreach ($result as $block) {
private static function processComplexNumberFormatMask($number, $mask)
{
$result = $number;
$maskingBlockCount = preg_match_all('/0+/', $mask, $maskingBlocks, PREG_OFFSET_CAPTURE);
if ($maskingBlockCount > 1) {
$maskingBlocks = array_reverse($maskingBlocks[0]);
foreach ($maskingBlocks as $block) {
$divisor = 1 . $block[0];
$size = strlen($block[0]);
$offset = $block[1];
@ -584,13 +592,134 @@ class NumberFormat extends Supervisor
$mask = substr_replace($mask, $number, $offset, 0);
}
$result = $mask;
} else {
$result = $number;
}
return $result;
}
private static function complexNumberFormatMask($number, $mask, $splitOnPoint = true)
{
$sign = ($number < 0.0);
$number = abs($number);
if ($splitOnPoint && strpos($mask, '.') !== false && strpos($number, '.') !== false) {
$numbers = explode('.', $number);
$masks = explode('.', $mask);
if (count($masks) > 2) {
$masks = self::mergeComplexNumberFormatMasks($numbers, $masks);
}
$result1 = self::complexNumberFormatMask($numbers[0], $masks[0], false);
$result2 = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1]), false));
return (($sign) ? '-' : '') . $result1 . '.' . $result2;
}
$result = self::processComplexNumberFormatMask($number, $mask);
return (($sign) ? '-' : '') . $result;
}
private static function formatStraightNumericValue($value, $format, array $matches, $useThousands, $number_regex)
{
$left = $matches[1];
$dec = $matches[2];
$right = $matches[3];
// minimun width of formatted number (including dot)
$minWidth = strlen($left) + strlen($dec) + strlen($right);
if ($useThousands) {
$value = number_format(
$value,
strlen($right),
StringHelper::getDecimalSeparator(),
StringHelper::getThousandsSeparator()
);
$value = preg_replace($number_regex, $value, $format);
} else {
if (preg_match('/[0#]E[+-]0/i', $format)) {
// Scientific format
$value = sprintf('%5.2E', $value);
} elseif (preg_match('/0([^\d\.]+)0/', $format) || substr_count($format, '.') > 1) {
if ($value == (int) $value && substr_count($format, '.') === 1) {
$value *= 10 ** strlen(explode('.', $format)[1]);
}
$value = self::complexNumberFormatMask($value, $format);
} else {
$sprintf_pattern = "%0$minWidth." . strlen($right) . 'f';
$value = sprintf($sprintf_pattern, $value);
$value = preg_replace($number_regex, $value, $format);
}
}
return $value;
}
private static function formatAsNumber($value, $format)
{
if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) {
return 'EUR ' . sprintf('%1.2f', $value);
}
// Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols
$format = str_replace(['"', '*'], '', $format);
// Find out if we need thousands separator
// This is indicated by a comma enclosed by a digit placeholder:
// #,# or 0,0
$useThousands = preg_match('/(#,#|0,0)/', $format);
if ($useThousands) {
$format = preg_replace('/0,0/', '00', $format);
$format = preg_replace('/#,#/', '##', $format);
}
// Scale thousands, millions,...
// This is indicated by a number of commas after a digit placeholder:
// #, or 0.0,,
$scale = 1; // same as no scale
$matches = [];
if (preg_match('/(#|0)(,+)/', $format, $matches)) {
$scale = pow(1000, strlen($matches[2]));
// strip the commas
$format = preg_replace('/0,+/', '0', $format);
$format = preg_replace('/#,+/', '#', $format);
}
if (preg_match('/#?.*\?\/\?/', $format, $m)) {
if ($value != (int) $value) {
self::formatAsFraction($value, $format);
}
} else {
// Handle the number itself
// scale number
$value = $value / $scale;
// Strip #
$format = preg_replace('/\\#/', '0', $format);
// Remove locale code [$-###]
$format = preg_replace('/\[\$\-.*\]/', '', $format);
$n = '/\\[[^\\]]+\\]/';
$m = preg_replace($n, '', $format);
$number_regex = '/(0+)(\\.?)(0*)/';
if (preg_match($number_regex, $m, $matches)) {
$value = self::formatStraightNumericValue($value, $format, $matches, $useThousands, $number_regex);
}
}
if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
// Currency or Accounting
$currencyCode = $m[1];
[$currencyCode] = explode('-', $currencyCode);
if ($currencyCode == '') {
$currencyCode = StringHelper::getCurrencyCode();
}
$value = preg_replace('/\[\$([^\]]*)\]/u', $currencyCode, $value);
}
return $value;
}
/**
* Convert a value in a pre-defined format to a PHP string.
*
@ -660,7 +789,9 @@ class NumberFormat extends Supervisor
// Save format with color information for later use below
$formatColor = $format;
// Strip colour information
$color_regex = '/\[(' . implode('|', Color::NAMED_COLORS) . ')\]/';
$format = preg_replace($color_regex, '', $format);
// Let's begin inspecting the format and converting the value to a formatted string
// Check for date/time characters (not inside quotes)
@ -668,105 +799,19 @@ class NumberFormat extends Supervisor
// datetime format
self::formatAsDate($value, $format);
} else {
// Strip color information
$color_regex = '/^\\[[a-zA-Z]+\\]/';
$format = preg_replace($color_regex, '', $format);
if (preg_match('/%$/', $format)) {
if (substr($format, 0, 1) === '"' && substr($format, -1, 1) === '"') {
$value = substr($format, 1, -1);
} elseif (preg_match('/%$/', $format)) {
// % number format
self::formatAsPercentage($value, $format);
} else {
if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) {
$value = 'EUR ' . sprintf('%1.2f', $value);
} else {
// Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols
$format = str_replace(['"', '*'], '', $format);
// Find out if we need thousands separator
// This is indicated by a comma enclosed by a digit placeholder:
// #,# or 0,0
$useThousands = preg_match('/(#,#|0,0)/', $format);
if ($useThousands) {
$format = preg_replace('/0,0/', '00', $format);
$format = preg_replace('/#,#/', '##', $format);
}
// Scale thousands, millions,...
// This is indicated by a number of commas after a digit placeholder:
// #, or 0.0,,
$scale = 1; // same as no scale
$matches = [];
if (preg_match('/(#|0)(,+)/', $format, $matches)) {
$scale = pow(1000, strlen($matches[2]));
// strip the commas
$format = preg_replace('/0,+/', '0', $format);
$format = preg_replace('/#,+/', '#', $format);
}
if (preg_match('/#?.*\?\/\?/', $format, $m)) {
if ($value != (int) $value) {
self::formatAsFraction($value, $format);
}
} else {
// Handle the number itself
// scale number
$value = $value / $scale;
// Strip #
$format = preg_replace('/\\#/', '0', $format);
// Remove locale code [$-###]
$format = preg_replace('/\[\$\-.*\]/', '', $format);
$n = '/\\[[^\\]]+\\]/';
$m = preg_replace($n, '', $format);
$number_regex = '/(0+)(\\.?)(0*)/';
if (preg_match($number_regex, $m, $matches)) {
$left = $matches[1];
$dec = $matches[2];
$right = $matches[3];
// minimun width of formatted number (including dot)
$minWidth = strlen($left) + strlen($dec) + strlen($right);
if ($useThousands) {
$value = number_format(
$value,
strlen($right),
StringHelper::getDecimalSeparator(),
StringHelper::getThousandsSeparator()
);
$value = preg_replace($number_regex, $value, $format);
} else {
if (preg_match('/[0#]E[+-]0/i', $format)) {
// Scientific format
$value = sprintf('%5.2E', $value);
} elseif (preg_match('/0([^\d\.]+)0/', $format)) {
$value = self::complexNumberFormatMask($value, $format);
} else {
$sprintf_pattern = "%0$minWidth." . strlen($right) . 'f';
$value = sprintf($sprintf_pattern, $value);
$value = preg_replace($number_regex, $value, $format);
}
}
}
}
if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
// Currency or Accounting
$currencyCode = $m[1];
list($currencyCode) = explode('-', $currencyCode);
if ($currencyCode == '') {
$currencyCode = StringHelper::getCurrencyCode();
}
$value = preg_replace('/\[\$([^\]]*)\]/u', $currencyCode, $value);
}
}
$value = self::formatAsNumber($value, $format);
}
}
// Additional formatting provided by callback function
if ($callBack !== null) {
list($writerInstance, $function) = $callBack;
[$writerInstance, $function] = $callBack;
$value = $writerInstance->$function($value, $formatColor);
}

View File

@ -86,7 +86,7 @@ class Protection extends Supervisor
*
* @throws PhpSpreadsheetException
*
* @return Protection
* @return $this
*/
public function applyFromArray(array $pStyles)
{
@ -123,7 +123,7 @@ class Protection extends Supervisor
*
* @param string $pValue see self::PROTECTION_*
*
* @return Protection
* @return $this
*/
public function setLocked($pValue)
{
@ -156,7 +156,7 @@ class Protection extends Supervisor
*
* @param string $pValue see self::PROTECTION_*
*
* @return Protection
* @return $this
*/
public function setHidden($pValue)
{

View File

@ -189,7 +189,7 @@ class Style extends Supervisor
* @param array $pStyles Array containing style information
* @param bool $pAdvanced advanced mode for setting borders
*
* @return Style
* @return $this
*/
public function applyFromArray(array $pStyles, $pAdvanced = true)
{
@ -204,7 +204,7 @@ class Style extends Supervisor
$rangeA = $pRange;
$rangeB = $pRange;
} else {
list($rangeA, $rangeB) = explode(':', $pRange);
[$rangeA, $rangeB] = explode(':', $pRange);
}
// Calculate range outer borders
@ -485,7 +485,7 @@ class Style extends Supervisor
*
* @param Font $font
*
* @return Style
* @return $this
*/
public function setFont(Font $font)
{
@ -539,7 +539,7 @@ class Style extends Supervisor
*
* @param Conditional[] $pValue Array of conditional styles
*
* @return Style
* @return $this
*/
public function setConditionalStyles(array $pValue)
{
@ -577,7 +577,7 @@ class Style extends Supervisor
*
* @param bool $pValue
*
* @return Style
* @return $this
*/
public function setQuotePrefix($pValue)
{

View File

@ -48,7 +48,7 @@ abstract class Supervisor implements IComparable
* @param Spreadsheet|Style $parent
* @param null|string $parentPropertyName
*
* @return Supervisor
* @return $this
*/
public function bindParent($parent, $parentPropertyName = null)
{

View File

@ -59,7 +59,7 @@ class AutoFilter
*
* @param Worksheet $pSheet
*
* @return AutoFilter
* @return $this
*/
public function setParent(Worksheet $pSheet = null)
{
@ -85,12 +85,12 @@ class AutoFilter
*
* @throws PhpSpreadsheetException
*
* @return AutoFilter
* @return $this
*/
public function setRange($pRange)
{
// extract coordinate
list($worksheet, $pRange) = Worksheet::extractSheetTitle($pRange, true);
[$worksheet, $pRange] = Worksheet::extractSheetTitle($pRange, true);
if (strpos($pRange, ':') !== false) {
$this->range = $pRange;
@ -105,7 +105,7 @@ class AutoFilter
$this->columns = [];
} else {
// Discard any column rules that are no longer valid within this range
list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($this->range);
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
foreach ($this->columns as $key => $value) {
$colIndex = Coordinate::columnIndexFromString($key);
if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) {
@ -143,7 +143,7 @@ class AutoFilter
}
$columnIndex = Coordinate::columnIndexFromString($column);
list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($this->range);
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) {
throw new PhpSpreadsheetException('Column is outside of current autofilter range.');
}
@ -196,7 +196,7 @@ class AutoFilter
*/
public function getColumnByOffset($pColumnOffset)
{
list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($this->range);
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
$pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $pColumnOffset);
return $this->getColumn($pColumn);
@ -210,7 +210,7 @@ class AutoFilter
*
* @throws PhpSpreadsheetException
*
* @return AutoFilter
* @return $this
*/
public function setColumn($pColumn)
{
@ -241,7 +241,7 @@ class AutoFilter
*
* @throws PhpSpreadsheetException
*
* @return AutoFilter
* @return $this
*/
public function clearColumn($pColumn)
{
@ -264,7 +264,7 @@ class AutoFilter
* @param string $fromColumn Column name (e.g. A)
* @param string $toColumn Column name (e.g. B)
*
* @return AutoFilter
* @return $this
*/
public function shiftColumn($fromColumn, $toColumn)
{
@ -357,7 +357,7 @@ class AutoFilter
{
$dataSet = $ruleSet['filterRules'];
$join = $ruleSet['join'];
$customRuleForBlanks = isset($ruleSet['customRuleForBlanks']) ? $ruleSet['customRuleForBlanks'] : false;
$customRuleForBlanks = $ruleSet['customRuleForBlanks'] ?? false;
if (!$customRuleForBlanks) {
// Blank cells are always ignored, so return a FALSE
@ -617,11 +617,11 @@ class AutoFilter
*
* @throws PhpSpreadsheetException
*
* @return AutoFilter
* @return $this
*/
public function showHideRows()
{
list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($this->range);
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
// The heading row should always be visible
$this->workSheet->getRowDimension($rangeStart[1])->setVisible(true);

View File

@ -101,7 +101,7 @@ class Column
}
/**
* Get AutoFilter Column Index.
* Get AutoFilter column index as string eg: 'A'.
*
* @return string
*/
@ -111,13 +111,13 @@ class Column
}
/**
* Set AutoFilter Column Index.
* Set AutoFilter column index as string eg: 'A'.
*
* @param string $pColumn Column (e.g. A)
*
* @throws PhpSpreadsheetException
*
* @return Column
* @return $this
*/
public function setColumnIndex($pColumn)
{
@ -147,7 +147,7 @@ class Column
*
* @param AutoFilter $pParent
*
* @return Column
* @return $this
*/
public function setParent(AutoFilter $pParent = null)
{
@ -173,7 +173,7 @@ class Column
*
* @throws PhpSpreadsheetException
*
* @return Column
* @return $this
*/
public function setFilterType($pFilterType)
{
@ -203,7 +203,7 @@ class Column
*
* @throws PhpSpreadsheetException
*
* @return Column
* @return $this
*/
public function setJoin($pJoin)
{
@ -223,7 +223,7 @@ class Column
*
* @param string[] $attributes
*
* @return Column
* @return $this
*/
public function setAttributes(array $attributes)
{
@ -238,7 +238,7 @@ class Column
* @param string $pName Attribute Name
* @param string $pValue Attribute Value
*
* @return Column
* @return $this
*/
public function setAttribute($pName, $pValue)
{
@ -316,7 +316,7 @@ class Column
*
* @param Column\Rule $pRule
*
* @return Column
* @return $this
*/
public function addRule(Column\Rule $pRule)
{
@ -332,7 +332,7 @@ class Column
*
* @param int $pIndex Rule index in the ruleset array
*
* @return Column
* @return $this
*/
public function deleteRule($pIndex)
{
@ -350,7 +350,7 @@ class Column
/**
* Delete all AutoFilter Column Rules.
*
* @return Column
* @return $this
*/
public function clearRules()
{

View File

@ -262,7 +262,7 @@ class Rule
*
* @throws PhpSpreadsheetException
*
* @return Rule
* @return $this
*/
public function setRuleType($pRuleType)
{
@ -292,7 +292,7 @@ class Rule
*
* @throws PhpSpreadsheetException
*
* @return Rule
* @return $this
*/
public function setValue($pValue)
{
@ -336,7 +336,7 @@ class Rule
*
* @throws PhpSpreadsheetException
*
* @return Rule
* @return $this
*/
public function setOperator($pOperator)
{
@ -369,7 +369,7 @@ class Rule
*
* @throws PhpSpreadsheetException
*
* @return Rule
* @return $this
*/
public function setGrouping($pGrouping)
{
@ -393,7 +393,7 @@ class Rule
*
* @throws PhpSpreadsheetException
*
* @return Rule
* @return $this
*/
public function setRule($pOperator, $pValue, $pGrouping = null)
{
@ -424,7 +424,7 @@ class Rule
*
* @param Column $pParent
*
* @return Rule
* @return $this
*/
public function setParent(Column $pParent = null)
{

View File

@ -154,7 +154,7 @@ class BaseDrawing implements IComparable
*
* @param string $pValue
*
* @return BaseDrawing
* @return $this
*/
public function setName($pValue)
{
@ -178,7 +178,7 @@ class BaseDrawing implements IComparable
*
* @param string $description
*
* @return BaseDrawing
* @return $this
*/
public function setDescription($description)
{
@ -205,7 +205,7 @@ class BaseDrawing implements IComparable
*
* @throws PhpSpreadsheetException
*
* @return BaseDrawing
* @return $this
*/
public function setWorksheet(Worksheet $pValue = null, $pOverrideOld = false)
{
@ -253,7 +253,7 @@ class BaseDrawing implements IComparable
*
* @param string $pValue eg: 'A1'
*
* @return BaseDrawing
* @return $this
*/
public function setCoordinates($pValue)
{
@ -277,7 +277,7 @@ class BaseDrawing implements IComparable
*
* @param int $pValue
*
* @return BaseDrawing
* @return $this
*/
public function setOffsetX($pValue)
{
@ -301,7 +301,7 @@ class BaseDrawing implements IComparable
*
* @param int $pValue
*
* @return BaseDrawing
* @return $this
*/
public function setOffsetY($pValue)
{
@ -325,7 +325,7 @@ class BaseDrawing implements IComparable
*
* @param int $pValue
*
* @return BaseDrawing
* @return $this
*/
public function setWidth($pValue)
{
@ -356,7 +356,7 @@ class BaseDrawing implements IComparable
*
* @param int $pValue
*
* @return BaseDrawing
* @return $this
*/
public function setHeight($pValue)
{
@ -386,7 +386,7 @@ class BaseDrawing implements IComparable
* @param int $width
* @param int $height
*
* @return BaseDrawing
* @return $this
*/
public function setWidthAndHeight($width, $height)
{
@ -423,7 +423,7 @@ class BaseDrawing implements IComparable
*
* @param bool $pValue
*
* @return BaseDrawing
* @return $this
*/
public function setResizeProportional($pValue)
{
@ -447,7 +447,7 @@ class BaseDrawing implements IComparable
*
* @param int $pValue
*
* @return BaseDrawing
* @return $this
*/
public function setRotation($pValue)
{
@ -471,7 +471,7 @@ class BaseDrawing implements IComparable
*
* @param Drawing\Shadow $pValue
*
* @return BaseDrawing
* @return $this
*/
public function setShadow(Drawing\Shadow $pValue = null)
{

View File

@ -40,7 +40,7 @@ class Column
}
/**
* Get column index.
* Get column index as string eg: 'A'.
*
* @return string
*/

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