mirror of
https://github.com/moodle/moodle.git
synced 2025-01-19 06:18:28 +01:00
Merge branch 'MDL-79210-master' of https://github.com/meirzamoodle/moodle
This commit is contained in:
commit
73c8675623
@ -4,7 +4,7 @@ Last release package can be found in https://github.com/PHPOffice/PhpSpreadsheet
|
||||
|
||||
NOTICE:
|
||||
* Before running composer command, make sure you have the composer version updated.
|
||||
* Composer version 2.5.1 2022-12-22 15:33:54
|
||||
* Composer version 2.5.5 2023-03-21 11:50:05
|
||||
|
||||
STEPS:
|
||||
* Create a temporary folder outside your moodle installation
|
||||
|
2
lib/phpspreadsheet/vendor/autoload.php
vendored
2
lib/phpspreadsheet/vendor/autoload.php
vendored
@ -22,4 +22,4 @@ if (PHP_VERSION_ID < 50600) {
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInitdb9a9022baf20d73f1edf8437bc648d7::getLoader();
|
||||
return ComposerAutoloaderInitf14832faa9ea8f0ad137e596f5daa06a::getLoader();
|
||||
|
@ -429,7 +429,8 @@ class ClassLoader
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
(self::$includeFile)($file);
|
||||
$includeFile = self::$includeFile;
|
||||
$includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -560,7 +561,10 @@ class ClassLoader
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function initializeIncludeClosure(): void
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private static function initializeIncludeClosure()
|
||||
{
|
||||
if (self::$includeFile !== null) {
|
||||
return;
|
||||
@ -574,8 +578,8 @@ class ClassLoader
|
||||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
self::$includeFile = static function($file) {
|
||||
self::$includeFile = \Closure::bind(static function($file) {
|
||||
include $file;
|
||||
};
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ class InstalledVersions
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (isset($installed['versions'][$packageName])) {
|
||||
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
|
||||
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ class InstalledVersions
|
||||
*/
|
||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||
{
|
||||
$constraint = $parser->parseConstraints($constraint);
|
||||
$constraint = $parser->parseConstraints((string) $constraint);
|
||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||
|
||||
return $provided->matches($constraint);
|
||||
@ -328,7 +328,9 @@ class InstalledVersions
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
$installed[] = self::$installedByVendor[$vendorDir] = $required;
|
||||
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $installed[count($installed) - 1];
|
||||
}
|
||||
@ -340,12 +342,17 @@ class InstalledVersions
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
self::$installed = require __DIR__ . '/installed.php';
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require __DIR__ . '/installed.php';
|
||||
self::$installed = $required;
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array()) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInitdb9a9022baf20d73f1edf8437bc648d7
|
||||
class ComposerAutoloaderInitf14832faa9ea8f0ad137e596f5daa06a
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
@ -24,12 +24,12 @@ class ComposerAutoloaderInitdb9a9022baf20d73f1edf8437bc648d7
|
||||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInitdb9a9022baf20d73f1edf8437bc648d7', 'loadClassLoader'), true, true);
|
||||
spl_autoload_register(array('ComposerAutoloaderInitf14832faa9ea8f0ad137e596f5daa06a', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInitdb9a9022baf20d73f1edf8437bc648d7', 'loadClassLoader'));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInitf14832faa9ea8f0ad137e596f5daa06a', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInitdb9a9022baf20d73f1edf8437bc648d7::getInitializer($loader));
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInitf14832faa9ea8f0ad137e596f5daa06a::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInitdb9a9022baf20d73f1edf8437bc648d7
|
||||
class ComposerStaticInitf14832faa9ea8f0ad137e596f5daa06a
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'P' =>
|
||||
@ -59,9 +59,9 @@ class ComposerStaticInitdb9a9022baf20d73f1edf8437bc648d7
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInitdb9a9022baf20d73f1edf8437bc648d7::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInitdb9a9022baf20d73f1edf8437bc648d7::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInitdb9a9022baf20d73f1edf8437bc648d7::$classMap;
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInitf14832faa9ea8f0ad137e596f5daa06a::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInitf14832faa9ea8f0ad137e596f5daa06a::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInitf14832faa9ea8f0ad137e596f5daa06a::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
|
@ -115,17 +115,17 @@
|
||||
},
|
||||
{
|
||||
"name": "phpoffice/phpspreadsheet",
|
||||
"version": "1.28.0",
|
||||
"version_normalized": "1.28.0.0",
|
||||
"version": "1.29.0",
|
||||
"version_normalized": "1.29.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
||||
"reference": "6e81cf39bbd93ebc3a4e8150444c41e8aa9b769a"
|
||||
"reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/6e81cf39bbd93ebc3a4e8150444c41e8aa9b769a",
|
||||
"reference": "6e81cf39bbd93ebc3a4e8150444c41e8aa9b769a",
|
||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0",
|
||||
"reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -143,7 +143,7 @@
|
||||
"ext-zip": "*",
|
||||
"ext-zlib": "*",
|
||||
"ezyang/htmlpurifier": "^4.15",
|
||||
"maennchen/zipstream-php": "^2.1",
|
||||
"maennchen/zipstream-php": "^2.1 || ^3.0",
|
||||
"markbaker/complex": "^3.0",
|
||||
"markbaker/matrix": "^3.0",
|
||||
"php": "^7.4 || ^8.0",
|
||||
@ -155,12 +155,12 @@
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
||||
"dompdf/dompdf": "^1.0 || ^2.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"mitoteam/jpgraph": "^10.2.4",
|
||||
"mitoteam/jpgraph": "^10.3",
|
||||
"mpdf/mpdf": "^8.1.1",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpstan/phpstan": "^1.1",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^8.5 || ^9.0",
|
||||
"phpunit/phpunit": "^8.5 || ^9.0 || ^10.0",
|
||||
"squizlabs/php_codesniffer": "^3.7",
|
||||
"tecnickcom/tcpdf": "^6.5"
|
||||
},
|
||||
@ -171,7 +171,7 @@
|
||||
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
|
||||
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
|
||||
},
|
||||
"time": "2023-02-25T12:24:49+00:00",
|
||||
"time": "2023-06-14T22:48:31+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
@ -217,30 +217,30 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
||||
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.28.0"
|
||||
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0"
|
||||
},
|
||||
"install-path": "../phpoffice/phpspreadsheet"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-client",
|
||||
"version": "1.0.1",
|
||||
"version_normalized": "1.0.1.0",
|
||||
"version": "1.0.2",
|
||||
"version_normalized": "1.0.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-client.git",
|
||||
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
|
||||
"reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
|
||||
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
|
||||
"url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31",
|
||||
"reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.0 || ^8.0",
|
||||
"psr/http-message": "^1.0"
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"time": "2020-06-29T06:28:15+00:00",
|
||||
"time": "2023-04-10T20:12:12+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
@ -260,7 +260,7 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP clients",
|
||||
@ -272,30 +272,30 @@
|
||||
"psr-18"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-client/tree/master"
|
||||
"source": "https://github.com/php-fig/http-client/tree/1.0.2"
|
||||
},
|
||||
"install-path": "../psr/http-client"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
"version": "1.0.1",
|
||||
"version_normalized": "1.0.1.0",
|
||||
"version": "1.0.2",
|
||||
"version_normalized": "1.0.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-factory.git",
|
||||
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
|
||||
"reference": "e616d01114759c4c489f93b099585439f795fe35"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
|
||||
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
|
||||
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
|
||||
"reference": "e616d01114759c4c489f93b099585439f795fe35",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0.0",
|
||||
"psr/http-message": "^1.0"
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"time": "2019-04-30T12:38:16+00:00",
|
||||
"time": "2023-04-10T20:10:41+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
@ -315,7 +315,7 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interfaces for PSR-7 HTTP message factories",
|
||||
@ -330,33 +330,33 @@
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-factory/tree/master"
|
||||
"source": "https://github.com/php-fig/http-factory/tree/1.0.2"
|
||||
},
|
||||
"install-path": "../psr/http-factory"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"version": "1.0.1",
|
||||
"version_normalized": "1.0.1.0",
|
||||
"version": "2.0",
|
||||
"version_normalized": "2.0.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-message.git",
|
||||
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
|
||||
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"time": "2016-08-06T14:39:51+00:00",
|
||||
"time": "2023-04-04T09:54:51+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
@ -372,7 +372,7 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP messages",
|
||||
@ -386,7 +386,7 @@
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-message/tree/master"
|
||||
"source": "https://github.com/php-fig/http-message/tree/2.0"
|
||||
},
|
||||
"install-path": "../psr/http-message"
|
||||
},
|
||||
|
24
lib/phpspreadsheet/vendor/composer/installed.php
vendored
24
lib/phpspreadsheet/vendor/composer/installed.php
vendored
@ -56,36 +56,36 @@
|
||||
),
|
||||
),
|
||||
'phpoffice/phpspreadsheet' => array(
|
||||
'pretty_version' => '1.28.0',
|
||||
'version' => '1.28.0.0',
|
||||
'reference' => '6e81cf39bbd93ebc3a4e8150444c41e8aa9b769a',
|
||||
'pretty_version' => '1.29.0',
|
||||
'version' => '1.29.0.0',
|
||||
'reference' => 'fde2ccf55eaef7e86021ff1acce26479160a0fa0',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpoffice/phpspreadsheet',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/http-client' => array(
|
||||
'pretty_version' => '1.0.1',
|
||||
'version' => '1.0.1.0',
|
||||
'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621',
|
||||
'pretty_version' => '1.0.2',
|
||||
'version' => '1.0.2.0',
|
||||
'reference' => '0955afe48220520692d2d09f7ab7e0f93ffd6a31',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-client',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/http-factory' => array(
|
||||
'pretty_version' => '1.0.1',
|
||||
'version' => '1.0.1.0',
|
||||
'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be',
|
||||
'pretty_version' => '1.0.2',
|
||||
'version' => '1.0.2.0',
|
||||
'reference' => 'e616d01114759c4c489f93b099585439f795fe35',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-factory',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/http-message' => array(
|
||||
'pretty_version' => '1.0.1',
|
||||
'version' => '1.0.1.0',
|
||||
'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363',
|
||||
'pretty_version' => '2.0',
|
||||
'version' => '2.0.0.0',
|
||||
'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-message',
|
||||
'aliases' => array(),
|
||||
|
@ -5,6 +5,67 @@ 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.29.0 - 2023-06-15
|
||||
|
||||
### Added
|
||||
|
||||
- Wizards for defining Number Format masks for Dates and Times, including Durations/Intervals. [PR #3458](https://github.com/PHPOffice/PhpSpreadsheet/pull/3458)
|
||||
- Specify data type in html tags. [Issue #3444](https://github.com/PHPOffice/PhpSpreadsheet/issues/3444) [PR #3445](https://github.com/PHPOffice/PhpSpreadsheet/pull/3445)
|
||||
- Provide option to ignore hidden rows/columns in `toArray()` methods. [PR #3494](https://github.com/PHPOffice/PhpSpreadsheet/pull/3494)
|
||||
- Font/Effects/Theme support for Chart Data Labels and Axis. [PR #3476](https://github.com/PHPOffice/PhpSpreadsheet/pull/3476)
|
||||
- Font Themes support. [PR #3486](https://github.com/PHPOffice/PhpSpreadsheet/pull/3486)
|
||||
- Ability to Ignore Cell Errors in Excel. [Issue #1141](https://github.com/PHPOffice/PhpSpreadsheet/issues/1141) [PR #3508](https://github.com/PHPOffice/PhpSpreadsheet/pull/3508)
|
||||
- Unzipped Gnumeric file [PR #3591](https://github.com/PHPOffice/PhpSpreadsheet/pull/3591)
|
||||
|
||||
### Changed
|
||||
|
||||
- Xlsx Color schemes read in will be written out (previously Excel 2007-2010 Color scheme was always written); manipulation of those schemes before write, including restoring prior behavior, is provided [PR #3476](https://github.com/PHPOffice/PhpSpreadsheet/pull/3476)
|
||||
- Memory and speed optimisations for Read Filters with Xlsx Files and Shared Formulae. [PR #3474](https://github.com/PHPOffice/PhpSpreadsheet/pull/3474)
|
||||
- Allow `CellRange` and `CellAddress` objects for the `range` argument in the `rangeToArray()` method. [PR #3494](https://github.com/PHPOffice/PhpSpreadsheet/pull/3494)
|
||||
- Stock charts will now read and reproduce `upDownBars` and subsidiary tags; these were previously ignored on read and hard-coded on write. [PR #3515](https://github.com/PHPOffice/PhpSpreadsheet/pull/3515)
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing
|
||||
|
||||
### Fixed
|
||||
|
||||
- Updates Cell formula absolute ranges/references, and Defined Name absolute ranges/references when inserting/deleting rows/columns. [Issue #3368](https://github.com/PHPOffice/PhpSpreadsheet/issues/3368) [PR #3402](https://github.com/PHPOffice/PhpSpreadsheet/pull/3402)
|
||||
- EOMONTH() and EDATE() Functions should round date value before evaluation. [Issue #3436](https://github.com/PHPOffice/PhpSpreadsheet/issues/3436) [PR #3437](https://github.com/PHPOffice/PhpSpreadsheet/pull/3437)
|
||||
- NETWORKDAYS function erroneously being converted to NETWORK_xlfn.DAYS in Xlsx Writer. [Issue #3461](https://github.com/PHPOffice/PhpSpreadsheet/issues/3461) [PR #3463](https://github.com/PHPOffice/PhpSpreadsheet/pull/3463)
|
||||
- Getting a style for a CellAddress instance fails if the worksheet is set in the CellAddress instance. [Issue #3439](https://github.com/PHPOffice/PhpSpreadsheet/issues/3439) [PR #3469](https://github.com/PHPOffice/PhpSpreadsheet/pull/3469)
|
||||
- Shared Formulae outside the filter range when reading with a filter are not always being identified. [Issue #3473](https://github.com/PHPOffice/PhpSpreadsheet/issues/3473) [PR #3474](https://github.com/PHPOffice/PhpSpreadsheet/pull/3474)
|
||||
- Xls Reader Conditional Styles. [PR #3400](https://github.com/PHPOffice/PhpSpreadsheet/pull/3400)
|
||||
- Allow use of # and 0 digit placeholders in fraction masks. [PR #3401](https://github.com/PHPOffice/PhpSpreadsheet/pull/3401)
|
||||
- Modify Date/Time check in the NumberFormatter for decimal/fractional times. [PR #3413](https://github.com/PHPOffice/PhpSpreadsheet/pull/3413)
|
||||
- Misplaced Xml Writing Chart Label FillColor. [Issue #3397](https://github.com/PHPOffice/PhpSpreadsheet/issues/3397) [PR #3404](https://github.com/PHPOffice/PhpSpreadsheet/pull/3404)
|
||||
- TEXT function ignores Time in DateTimeStamp. [Issue #3409](https://github.com/PHPOffice/PhpSpreadsheet/issues/3409) [PR #3411](https://github.com/PHPOffice/PhpSpreadsheet/pull/3411)
|
||||
- Xlsx Column Autosize Approximate for CJK. [Issue #3405](https://github.com/PHPOffice/PhpSpreadsheet/issues/3405) [PR #3416](https://github.com/PHPOffice/PhpSpreadsheet/pull/3416)
|
||||
- Correct Xlsx Parsing of quotePrefix="0". [Issue #3435](https://github.com/PHPOffice/PhpSpreadsheet/issues/3435) [PR #3438](https://github.com/PHPOffice/PhpSpreadsheet/pull/3438)
|
||||
- More Display Options for Chart Axis and Legend. [Issue #3414](https://github.com/PHPOffice/PhpSpreadsheet/issues/3414) [PR #3434](https://github.com/PHPOffice/PhpSpreadsheet/pull/3434)
|
||||
- Apply strict type checking to Complex suffix. [PR #3452](https://github.com/PHPOffice/PhpSpreadsheet/pull/3452)
|
||||
- Incorrect Font Color Read Xlsx Rich Text Indexed Color Custom Palette. [Issue #3464](https://github.com/PHPOffice/PhpSpreadsheet/issues/3464) [PR #3465](https://github.com/PHPOffice/PhpSpreadsheet/pull/3465)
|
||||
- Xlsx Writer Honor Alignment in Default Font. [Issue #3443](https://github.com/PHPOffice/PhpSpreadsheet/issues/3443) [PR #3459](https://github.com/PHPOffice/PhpSpreadsheet/pull/3459)
|
||||
- Support Border for Charts. [PR #3462](https://github.com/PHPOffice/PhpSpreadsheet/pull/3462)
|
||||
- Error in "this row" structured reference calculation (cached result from first row when using a range) [Issue #3504](https://github.com/PHPOffice/PhpSpreadsheet/issues/3504) [PR #3505](https://github.com/PHPOffice/PhpSpreadsheet/pull/3505)
|
||||
- Allow colour palette index references in Number Format masks [Issue #3511](https://github.com/PHPOffice/PhpSpreadsheet/issues/3511) [PR #3512](https://github.com/PHPOffice/PhpSpreadsheet/pull/3512)
|
||||
- Xlsx Reader formula with quotePrefix [Issue #3495](https://github.com/PHPOffice/PhpSpreadsheet/issues/3495) [PR #3497](https://github.com/PHPOffice/PhpSpreadsheet/pull/3497)
|
||||
- Handle REF error as part of range [Issue #3453](https://github.com/PHPOffice/PhpSpreadsheet/issues/3453) [PR #3467](https://github.com/PHPOffice/PhpSpreadsheet/pull/3467)
|
||||
- Handle Absolute Pathnames in Rels File [Issue #3553](https://github.com/PHPOffice/PhpSpreadsheet/issues/3553) [PR #3554](https://github.com/PHPOffice/PhpSpreadsheet/pull/3554)
|
||||
- Return Page Breaks in Order [Issue #3552](https://github.com/PHPOffice/PhpSpreadsheet/issues/3552) [PR #3555](https://github.com/PHPOffice/PhpSpreadsheet/pull/3555)
|
||||
- Add position attribute for MemoryDrawing in Html [Issue #3529](https://github.com/PHPOffice/PhpSpreadsheet/issues/3529 [PR #3535](https://github.com/PHPOffice/PhpSpreadsheet/pull/3535)
|
||||
- Allow Index_number as Array for VLOOKUP/HLOOKUP [Issue #3561](https://github.com/PHPOffice/PhpSpreadsheet/issues/3561 [PR #3570](https://github.com/PHPOffice/PhpSpreadsheet/pull/3570)
|
||||
- Add Unsupported Options in Xml Spreadsheet [Issue #3566](https://github.com/PHPOffice/PhpSpreadsheet/issues/3566 [Issue #3568](https://github.com/PHPOffice/PhpSpreadsheet/issues/3568 [Issue #3569](https://github.com/PHPOffice/PhpSpreadsheet/issues/3569 [PR #3567](https://github.com/PHPOffice/PhpSpreadsheet/pull/3567)
|
||||
- Changes to NUMBERVALUE, VALUE, DATEVALUE, TIMEVALUE [Issue #3574](https://github.com/PHPOffice/PhpSpreadsheet/issues/3574 [PR #3575](https://github.com/PHPOffice/PhpSpreadsheet/pull/3575)
|
||||
- Redo calculation of color tinting [Issue #3550](https://github.com/PHPOffice/PhpSpreadsheet/issues/3550) [PR #3580](https://github.com/PHPOffice/PhpSpreadsheet/pull/3580)
|
||||
- Accommodate Slash with preg_quote [PR #3582](https://github.com/PHPOffice/PhpSpreadsheet/pull/3582) [PR #3583](https://github.com/PHPOffice/PhpSpreadsheet/pull/3583) [PR #3584](https://github.com/PHPOffice/PhpSpreadsheet/pull/3584)
|
||||
- HyperlinkBase Property and Html Handling of Properties [Issue #3573](https://github.com/PHPOffice/PhpSpreadsheet/issues/3573) [PR #3589](https://github.com/PHPOffice/PhpSpreadsheet/pull/3589)
|
||||
- Improvements for Data Validation [Issue #3592](https://github.com/PHPOffice/PhpSpreadsheet/issues/3592) [Issue #3594](https://github.com/PHPOffice/PhpSpreadsheet/issues/3594) [PR #3605](https://github.com/PHPOffice/PhpSpreadsheet/pull/3605)
|
||||
|
||||
## 1.28.0 - 2023-02-25
|
||||
|
||||
### Added
|
||||
|
@ -2,19 +2,44 @@
|
||||
|
||||
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`
|
||||
- All new development should be on feature/fix branches, which are then merged to the `master` branch once stable and approved; so the `master` branch is always the most up-to-date, working code
|
||||
- If you are going to submit a pull request, please fork from `master`, and submit your pull request back as a fix/feature branch referencing the GitHub issue number
|
||||
- The code must work with all PHP versions that we support (currently PHP 7.4 to PHP 8.2).
|
||||
- You can call `composer versions` to test version compatibility.
|
||||
- Code style should be maintained.
|
||||
- `composer style` will identify any issues with Coding Style`.
|
||||
- `composer fix` will fix most issues with Coding Style.
|
||||
- All code changes must be validated by `composer check`.
|
||||
- Please include Unit Tests to verify that a bug exists, and that this PR fixes it.
|
||||
- Please include Unit Tests to show that a new Feature works as expected.
|
||||
- Please don't "bundle" several changes into a single PR; submit a PR for each discrete change/fix.
|
||||
- Remember to update documentation if necessary.
|
||||
|
||||
- [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")
|
||||
|
||||
## Unit Tests
|
||||
|
||||
When writing Unit Tests, please
|
||||
- Always try to write Unit Tests for both the happy and unhappy paths.
|
||||
- Put all assertions in the Test itself, not in an abstract class that the Test extends (even if this means code duplication between tests).
|
||||
- Include any necessary `setup()` and `tearDown()` in the Test itself.
|
||||
- If you change any global settings (such as system locale, or Compatibility Mode for Excel Function tests), make sure that you reset to the default in the `tearDown()`.
|
||||
- Use the `ExcelError` functions in assertions for Excel Error values in Excel Function implementations.
|
||||
<br />Not only does it reduce the risk of typos; but at some point in the future, ExcelError values will be an object rather than a string, and we won't then need to update all the tests.
|
||||
- Don't over-complicate test code by testing happy and unhappy paths in the same test.
|
||||
|
||||
This makes it easier to see exactly what is being tested when reviewing the PR. I want to be able to see it in the PR, not have to hunt in other unchanged classes to see what the test is doing.
|
||||
|
||||
## How to release
|
||||
|
||||
1. Complete CHANGELOG.md and commit
|
||||
2. Create an annotated tag
|
||||
1. `git tag -a 1.2.3`
|
||||
2. Tag subject must be the version number, eg: `1.2.3`
|
||||
3. Tag body must be a copy-paste of the changelog entries
|
||||
3. Push tag with `git push --tags`, GitHub Actions will create a GitHub release automatically
|
||||
3. Tag body must be a copy-paste of the changelog entries.
|
||||
3. Push the tag with `git push --tags`, GitHub Actions will create a GitHub release automatically, and the release details will automatically be sent to packagist.
|
||||
4. Github seems to remove markdown headings in the Release Notes, so you should edit to restore these.
|
||||
|
||||
> **Note:** Tagged releases are made from the `master` branch. Only in an emergency should a tagged release be made from the `release` branch. (i.e. cherry-picked hot-fixes.)
|
||||
|
||||
|
@ -32,7 +32,7 @@ If you are building your installation on a development machine that is on a diff
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"phpoffice/phpspreadsheet": "^1.23"
|
||||
"phpoffice/phpspreadsheet": "^1.28"
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
@ -74,16 +74,20 @@ or the appropriate PDF Writer wrapper for the library that you have chosen to in
|
||||
For Chart export, we support following packages, which you will also need to install yourself using `composer require`
|
||||
- [jpgraph/jpgraph](https://packagist.org/packages/jpgraph/jpgraph) (this package was abandoned at version 4.0.
|
||||
You can manually download the latest version that supports PHP 8 and above from [jpgraph.net](https://jpgraph.net/))
|
||||
- [mitoteam/jpgraph](https://packagist.org/packages/mitoteam/jpgraph) (fork with php 8.1 support)
|
||||
- [mitoteam/jpgraph](https://packagist.org/packages/mitoteam/jpgraph) - up to date fork with modern PHP versions support and some bugs fixed.
|
||||
|
||||
and then configure PhpSpreadsheet using:
|
||||
```php
|
||||
Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class); // to use jpgraph/jpgraph
|
||||
// to use jpgraph/jpgraph
|
||||
Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class);
|
||||
//or
|
||||
Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class); // to use mitoteam/jpgraph
|
||||
// to use mitoteam/jpgraph
|
||||
Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class);
|
||||
```
|
||||
|
||||
One or the other of these libraries is necessary if you want to generate HTML or PDF files that include charts.
|
||||
One or the other of these libraries is necessary if you want to generate HTML or PDF files that include charts; or to render a Chart to an Image format from within your code.
|
||||
They are not necessary to define charts for writing to `Xlsx` files.
|
||||
Other file formats don't support writing Charts.
|
||||
|
||||
## Documentation
|
||||
|
||||
@ -103,10 +107,15 @@ Posts already available to Patreon supporters:
|
||||
- Looping the Loop
|
||||
- Advice on Iterating through the rows and cells in a worksheet.
|
||||
|
||||
The next post (currently being written) will be:
|
||||
And for Patrons at levels actively using PhpSpreadsheet:
|
||||
- Behind the Mask
|
||||
- A look at Number Format Masks.
|
||||
|
||||
The Next Article (currently Work in Progress):
|
||||
- Formula for Success
|
||||
- How to debug formulae that don't produce the expected result.
|
||||
|
||||
|
||||
My aim is to post at least one article each month, taking a detailed look at some feature of MS Excel and how to use that feature in PhpSpreadsheet, or on how to perform different activities in PhpSpreadsheet.
|
||||
|
||||
Planned posts for the future include topics like:
|
||||
@ -116,8 +125,9 @@ Planned posts for the future include topics like:
|
||||
- Array Formulae
|
||||
- Conditional Formatting
|
||||
- Data Validation
|
||||
- Formula Debugging
|
||||
- Value Binders
|
||||
- Images
|
||||
- Charts
|
||||
|
||||
After a period of six months exclusive to Patreon supporters, articles will be incorporated into the public documentation for the library.
|
||||
|
||||
|
@ -42,13 +42,19 @@
|
||||
],
|
||||
"scripts": {
|
||||
"check": [
|
||||
"phpcs src/ tests/ --report=checkstyle",
|
||||
"phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PHPCompatibility --runtime-set testVersion 7.4- -n",
|
||||
"php-cs-fixer fix --ansi --dry-run --diff",
|
||||
"phpcs",
|
||||
"phpunit --color=always",
|
||||
"phpstan analyse --ansi"
|
||||
"phpstan analyse --ansi --memory-limit=2048M"
|
||||
],
|
||||
"style": [
|
||||
"phpcs src/ tests/ --report=checkstyle",
|
||||
"php-cs-fixer fix --ansi --dry-run --diff"
|
||||
],
|
||||
"fix": [
|
||||
"php-cs-fixer fix --ansi"
|
||||
"phpcbf src/ tests/ --report=checkstyle",
|
||||
"php-cs-fixer fix"
|
||||
],
|
||||
"versions": [
|
||||
"phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PHPCompatibility --runtime-set testVersion 7.4- -n"
|
||||
@ -70,7 +76,7 @@
|
||||
"ext-zip": "*",
|
||||
"ext-zlib": "*",
|
||||
"ezyang/htmlpurifier": "^4.15",
|
||||
"maennchen/zipstream-php": "^2.1",
|
||||
"maennchen/zipstream-php": "^2.1 || ^3.0",
|
||||
"markbaker/complex": "^3.0",
|
||||
"markbaker/matrix": "^3.0",
|
||||
"psr/http-client": "^1.0",
|
||||
@ -81,12 +87,12 @@
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
||||
"dompdf/dompdf": "^1.0 || ^2.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"mitoteam/jpgraph": "^10.2.4",
|
||||
"mitoteam/jpgraph": "^10.3",
|
||||
"mpdf/mpdf": "^8.1.1",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpstan/phpstan": "^1.1",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^8.5 || ^9.0",
|
||||
"phpunit/phpunit": "^8.5 || ^9.0 || ^10.0",
|
||||
"squizlabs/php_codesniffer": "^3.7",
|
||||
"tecnickcom/tcpdf": "^6.5"
|
||||
},
|
||||
|
15
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/phpunit10.xml.dist
vendored
Normal file
15
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/phpunit10.xml.dist
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd" bootstrap="./tests/bootstrap.php" backupGlobals="true" colors="true" cacheResultFile="/tmp/.phpspreadsheet.phpunit.result.cache">
|
||||
<coverage/>
|
||||
<php>
|
||||
<ini name="memory_limit" value="2048M"/>
|
||||
</php>
|
||||
<testsuite name="PhpSpreadsheet Unit Test Suite">
|
||||
<directory>./tests/PhpSpreadsheetTests</directory>
|
||||
</testsuite>
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">./src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
@ -19,6 +19,7 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use ReflectionClassConstant;
|
||||
use ReflectionMethod;
|
||||
use ReflectionParameter;
|
||||
use Throwable;
|
||||
|
||||
class Calculation
|
||||
{
|
||||
@ -3556,7 +3557,7 @@ class Calculation
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($e->getMessage());
|
||||
throw new Exception($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
if ((is_array($result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
|
||||
@ -4210,7 +4211,7 @@ class Calculation
|
||||
try {
|
||||
$this->branchPruner->closingBrace($d['value']);
|
||||
} catch (Exception $e) {
|
||||
return $this->raiseFormulaError($e->getMessage());
|
||||
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$functionName = $matches[1]; // Get the function name
|
||||
@ -4249,7 +4250,7 @@ class Calculation
|
||||
} elseif ($expectedArgumentCount != '*') {
|
||||
$isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch);
|
||||
self::doNothing($isOperandOrFunction);
|
||||
switch ($argMatch[2]) {
|
||||
switch ($argMatch[2] ?? '') {
|
||||
case '+':
|
||||
if ($argumentCount < $argMatch[1]) {
|
||||
$argumentCountError = true;
|
||||
@ -4282,7 +4283,7 @@ class Calculation
|
||||
try {
|
||||
$this->branchPruner->argumentSeparator();
|
||||
} catch (Exception $e) {
|
||||
return $this->raiseFormulaError($e->getMessage());
|
||||
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
while (($o2 = $stack->pop()) && $o2['value'] !== '(') { // Pop off the stack back to the last (
|
||||
@ -4364,9 +4365,13 @@ class Calculation
|
||||
$rangeStartCellRef = $output[count($output) - 2]['value'] ?? '';
|
||||
}
|
||||
preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $rangeStartCellRef, $rangeStartMatches);
|
||||
if (array_key_exists(2, $rangeStartMatches)) {
|
||||
if ($rangeStartMatches[2] > '') {
|
||||
$val = $rangeStartMatches[2] . '!' . $val;
|
||||
}
|
||||
} else {
|
||||
$val = Information\ExcelError::REF();
|
||||
}
|
||||
} else {
|
||||
$rangeStartCellRef = $output[count($output) - 1]['value'] ?? '';
|
||||
if ($rangeStartCellRef === ':') {
|
||||
@ -4391,7 +4396,7 @@ class Calculation
|
||||
try {
|
||||
$structuredReference = Operands\StructuredReference::fromParser($formula, $index, $matches);
|
||||
} catch (Exception $e) {
|
||||
return $this->raiseFormulaError($e->getMessage());
|
||||
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$val = $structuredReference->value();
|
||||
@ -4434,6 +4439,8 @@ class Calculation
|
||||
}
|
||||
$val = $address;
|
||||
}
|
||||
} elseif ($val === Information\ExcelError::REF()) {
|
||||
$stackItemReference = $val;
|
||||
} else {
|
||||
$startRowColRef = $output[count($output) - 1]['value'] ?? '';
|
||||
[$rangeWS1, $startRowColRef] = Worksheet::extractSheetTitle($startRowColRef, true);
|
||||
@ -4731,7 +4738,7 @@ class Calculation
|
||||
$cellRange = $token->parse($cell);
|
||||
if (strpos($cellRange, ':') !== false) {
|
||||
$this->debugLog->writeDebugLog('Evaluating Structured Reference %s as Cell Range %s', $token->value(), $cellRange);
|
||||
$rangeValue = self::getInstance($cell->getWorksheet()->getParent())->_calculateFormulaValue("={$cellRange}", $token->value(), $cell);
|
||||
$rangeValue = self::getInstance($cell->getWorksheet()->getParent())->_calculateFormulaValue("={$cellRange}", $cellRange, $cell);
|
||||
$stack->push('Value', $rangeValue);
|
||||
$this->debugLog->writeDebugLog('Evaluated Structured Reference %s as value %s', $token->value(), $this->showValue($rangeValue));
|
||||
} else {
|
||||
@ -4745,7 +4752,7 @@ class Calculation
|
||||
$stack->push('Error', Information\ExcelError::REF(), null);
|
||||
$this->debugLog->writeDebugLog('Evaluated Structured Reference %s as error value %s', $token->value(), Information\ExcelError::REF());
|
||||
} else {
|
||||
return $this->raiseFormulaError($e->getMessage());
|
||||
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
} elseif (!is_numeric($token) && !is_object($token) && isset(self::BINARY_OPERATORS[$token])) {
|
||||
@ -4793,7 +4800,7 @@ class Calculation
|
||||
}
|
||||
}
|
||||
}
|
||||
if (strpos($operand1Data['reference'], '!') !== false) {
|
||||
if (strpos($operand1Data['reference'] ?? '', '!') !== false) {
|
||||
[$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true);
|
||||
} else {
|
||||
$sheet1 = ($pCellWorksheet !== null) ? $pCellWorksheet->getTitle() : '';
|
||||
@ -4830,10 +4837,21 @@ class Calculation
|
||||
|
||||
$oData = array_merge(explode(':', $operand1Data['reference']), explode(':', $operand2Data['reference']));
|
||||
$oCol = $oRow = [];
|
||||
$breakNeeded = false;
|
||||
foreach ($oData as $oDatum) {
|
||||
try {
|
||||
$oCR = Coordinate::coordinateFromString($oDatum);
|
||||
$oCol[] = Coordinate::columnIndexFromString($oCR[0]) - 1;
|
||||
$oRow[] = $oCR[1];
|
||||
} catch (\Exception $e) {
|
||||
$stack->push('Error', Information\ExcelError::REF(), null);
|
||||
$breakNeeded = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($breakNeeded) {
|
||||
break;
|
||||
}
|
||||
$cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
|
||||
if ($pCellParent !== null && $this->spreadsheet !== null) {
|
||||
@ -4842,8 +4860,10 @@ class Calculation
|
||||
return $this->raiseFormulaError('Unable to access Cell Reference');
|
||||
}
|
||||
|
||||
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellValue));
|
||||
$stack->push('Cell Reference', $cellValue, $cellRef);
|
||||
} else {
|
||||
$this->debugLog->writeDebugLog('Evaluation Result is a #REF! Error');
|
||||
$stack->push('Error', Information\ExcelError::REF(), null);
|
||||
}
|
||||
|
||||
@ -5434,13 +5454,13 @@ class Calculation
|
||||
*
|
||||
* @return false
|
||||
*/
|
||||
protected function raiseFormulaError(string $errorMessage)
|
||||
protected function raiseFormulaError(string $errorMessage, int $code = 0, ?Throwable $exception = null)
|
||||
{
|
||||
$this->formulaError = $errorMessage;
|
||||
$this->cyclicReferenceStack->clear();
|
||||
$suppress = /** @scrutinizer ignore-deprecated */ $this->suppressFormulaErrors ?? $this->suppressFormulaErrorsNew;
|
||||
if (!$suppress) {
|
||||
throw new Exception($errorMessage);
|
||||
throw new Exception($errorMessage, $code, $exception);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -45,6 +45,11 @@ class DateValue
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
|
||||
}
|
||||
|
||||
// try to parse as date iff there is at least one digit
|
||||
if (is_string($dateValue) && preg_match('/\\d/', $dateValue) !== 1) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
$dti = new DateTimeImmutable();
|
||||
$baseYear = SharedDateHelper::getExcelCalendar();
|
||||
$dateValue = trim($dateValue ?? '', '"');
|
||||
|
@ -45,6 +45,7 @@ class Month
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
$dateValue = floor($dateValue);
|
||||
$adjustmentMonths = floor($adjustmentMonths);
|
||||
|
||||
// Execute function
|
||||
@ -88,6 +89,7 @@ class Month
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
$dateValue = floor($dateValue);
|
||||
$adjustmentMonths = floor($adjustmentMonths);
|
||||
|
||||
// Execute function
|
||||
|
@ -42,6 +42,11 @@ class TimeValue
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
|
||||
}
|
||||
|
||||
// try to parse as time iff there is at least one digit
|
||||
if (is_string($timeValue) && preg_match('/\\d/', $timeValue) !== 1) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
$timeValue = trim($timeValue ?? '', '"');
|
||||
$timeValue = str_replace(['/', '.'], '-', $timeValue);
|
||||
|
||||
|
@ -48,9 +48,9 @@ class FormattedNumber
|
||||
*/
|
||||
public static function convertToNumberIfNumeric(string &$operand): bool
|
||||
{
|
||||
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator());
|
||||
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
|
||||
$value = preg_replace(['/(\d)' . $thousandsSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1$2', '$1$2'], trim($operand));
|
||||
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator());
|
||||
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
|
||||
$value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? '');
|
||||
|
||||
if (is_numeric($value)) {
|
||||
@ -90,9 +90,9 @@ class FormattedNumber
|
||||
*/
|
||||
public static function convertToNumberIfPercent(string &$operand): bool
|
||||
{
|
||||
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator());
|
||||
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
|
||||
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', trim($operand));
|
||||
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator());
|
||||
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
|
||||
$value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? '');
|
||||
|
||||
$match = [];
|
||||
@ -116,26 +116,31 @@ class FormattedNumber
|
||||
public static function convertToNumberIfCurrency(string &$operand): bool
|
||||
{
|
||||
$currencyRegexp = self::currencyMatcherRegexp();
|
||||
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator());
|
||||
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
|
||||
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $operand);
|
||||
|
||||
$match = [];
|
||||
if ($value !== null && preg_match($currencyRegexp, $value, $match, PREG_UNMATCHED_AS_NULL)) {
|
||||
//Determine the sign
|
||||
$sign = ($match['PrefixedSign'] ?? $match['PrefixedSign2'] ?? $match['PostfixedSign']) ?? '';
|
||||
$decimalSeparator = StringHelper::getDecimalSeparator();
|
||||
//Cast to a float
|
||||
$operand = (float) ($sign . ($match['PostfixedValue'] ?? $match['PrefixedValue']));
|
||||
$intermediate = (string) ($match['PostfixedValue'] ?? $match['PrefixedValue']);
|
||||
$intermediate = str_replace($decimalSeparator, '.', $intermediate);
|
||||
if (is_numeric($intermediate)) {
|
||||
$operand = (float) ($sign . str_replace($decimalSeparator, '.', $intermediate));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function currencyMatcherRegexp(): string
|
||||
{
|
||||
$currencyCodes = sprintf(self::CURRENCY_CONVERSION_LIST, preg_quote(StringHelper::getCurrencyCode()));
|
||||
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator());
|
||||
$currencyCodes = sprintf(self::CURRENCY_CONVERSION_LIST, preg_quote(StringHelper::getCurrencyCode(), '/'));
|
||||
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
|
||||
|
||||
return '~^(?:(?: *(?<PrefixedSign>[-+])? *(?<PrefixedCurrency>[' . $currencyCodes . ']) *(?<PrefixedSign2>[-+])? *(?<PrefixedValue>[0-9]+[' . $decimalSeparator . ']?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?<PostfixedSign>[-+])? *(?<PostfixedValue>[0-9]+' . $decimalSeparator . '?[0-9]*(?:E[-+]?[0-9]*)?) *(?<PostfixedCurrency>[' . $currencyCodes . ']) *))$~ui';
|
||||
}
|
||||
|
@ -190,8 +190,8 @@ final class StructuredReference implements Operand
|
||||
{
|
||||
if ($columnName !== '') {
|
||||
$cellReference = $columnId . $cell->getRow();
|
||||
$pattern1 = '/\[' . preg_quote($columnName) . '\]/miu';
|
||||
$pattern2 = '/@' . preg_quote($columnName) . '/miu';
|
||||
$pattern1 = '/\[' . preg_quote($columnName, '/') . '\]/miu';
|
||||
$pattern2 = '/@' . preg_quote($columnName, '/') . '/miu';
|
||||
if (preg_match($pattern1, $reference) === 1) {
|
||||
$reference = preg_replace($pattern1, $cellReference, $reference);
|
||||
} elseif (preg_match($pattern2, $reference) === 1) {
|
||||
@ -328,7 +328,7 @@ final class StructuredReference implements Operand
|
||||
$cellFrom = "{$columnId}{$startRow}";
|
||||
$cellTo = "{$columnId}{$endRow}";
|
||||
$cellReference = ($cellFrom === $cellTo) ? $cellFrom : "{$cellFrom}:{$cellTo}";
|
||||
$pattern = '/\[' . preg_quote($columnName) . '\]/mui';
|
||||
$pattern = '/\[' . preg_quote($columnName, '/') . '\]/mui';
|
||||
if (preg_match($pattern, $reference) === 1) {
|
||||
$columnsSelected = true;
|
||||
$reference = preg_replace($pattern, $cellReference, $reference);
|
||||
|
@ -20,28 +20,6 @@ class Engineering
|
||||
*/
|
||||
public const EULER = 2.71828182845904523536;
|
||||
|
||||
/**
|
||||
* parseComplex.
|
||||
*
|
||||
* Parses a complex number into its real and imaginary parts, and an I or J suffix
|
||||
*
|
||||
* @deprecated 1.12.0 No longer used by internal code. Please use the \Complex\Complex class instead
|
||||
*
|
||||
* @param string $complexNumber The complex number
|
||||
*
|
||||
* @return mixed[] Indexed on "real", "imaginary" and "suffix"
|
||||
*/
|
||||
public static function parseComplex($complexNumber)
|
||||
{
|
||||
$complex = new Complex($complexNumber);
|
||||
|
||||
return [
|
||||
'real' => $complex->getReal(),
|
||||
'imaginary' => $complex->getImaginary(),
|
||||
'suffix' => $complex->getSuffix(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* BESSELI.
|
||||
*
|
||||
|
@ -49,7 +49,7 @@ class Complex
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) {
|
||||
if (($suffix === 'i') || ($suffix === 'j') || ($suffix === '')) {
|
||||
$complex = new ComplexObject($realNumber, $imaginary, $suffix);
|
||||
|
||||
return (string) $complex;
|
||||
|
@ -40,7 +40,7 @@ class ConvertBinary extends ConvertBase
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (strlen($value) == 10) {
|
||||
if (strlen($value) == 10 && $value[0] === '1') {
|
||||
// Two's Complement
|
||||
$value = substr($value, -9);
|
||||
|
||||
@ -91,7 +91,7 @@ class ConvertBinary extends ConvertBase
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (strlen($value) == 10) {
|
||||
if (strlen($value) == 10 && $value[0] === '1') {
|
||||
$high2 = substr($value, 0, 2);
|
||||
$low8 = substr($value, 2);
|
||||
$xarr = ['00' => '00000000', '01' => '00000001', '10' => 'FFFFFFFE', '11' => 'FFFFFFFF'];
|
||||
@ -144,7 +144,7 @@ class ConvertBinary extends ConvertBase
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (strlen($value) == 10 && substr($value, 0, 1) === '1') { // Two's Complement
|
||||
if (strlen($value) == 10 && $value[0] === '1') { // Two's Complement
|
||||
return str_repeat('7', 6) . strtoupper(decoct((int) bindec("11$value")));
|
||||
}
|
||||
$octVal = (string) decoct((int) bindec($value));
|
||||
|
@ -27,7 +27,7 @@ class HLookup extends LookupBase
|
||||
*/
|
||||
public static function lookup($lookupValue, $lookupArray, $indexNumber, $notExactMatch = true)
|
||||
{
|
||||
if (is_array($lookupValue)) {
|
||||
if (is_array($lookupValue) || is_array($indexNumber)) {
|
||||
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $indexNumber, $notExactMatch);
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ class VLookup extends LookupBase
|
||||
*/
|
||||
public static function lookup($lookupValue, $lookupArray, $indexNumber, $notExactMatch = true)
|
||||
{
|
||||
if (is_array($lookupValue)) {
|
||||
if (is_array($lookupValue) || is_array($indexNumber)) {
|
||||
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $indexNumber, $notExactMatch);
|
||||
}
|
||||
|
||||
|
@ -66,8 +66,8 @@ class Sum
|
||||
$returnValue += (int) $arg;
|
||||
} elseif (ErrorValue::isError($arg)) {
|
||||
return $arg;
|
||||
// ignore non-numerics from cell, but fail as literals (except null)
|
||||
} elseif ($arg !== null && !Functions::isCellValue($k)) {
|
||||
// ignore non-numerics from cell, but fail as literals (except null)
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
}
|
||||
|
@ -261,7 +261,7 @@ class Extract
|
||||
$delimiter = Functions::flattenArray($delimiter);
|
||||
$quotedDelimiters = array_map(
|
||||
function ($delimiter) {
|
||||
return preg_quote($delimiter ?? '');
|
||||
return preg_quote($delimiter ?? '', '/');
|
||||
},
|
||||
$delimiter
|
||||
);
|
||||
@ -270,7 +270,7 @@ class Extract
|
||||
return '(' . $delimiters . ')';
|
||||
}
|
||||
|
||||
return '(' . preg_quote($delimiter ?? '') . ')';
|
||||
return '(' . preg_quote($delimiter ?? '', '/') . ')';
|
||||
}
|
||||
|
||||
private static function matchFlags(int $matchMode): string
|
||||
|
@ -129,7 +129,7 @@ class Format
|
||||
$format = Helpers::extractString($format);
|
||||
|
||||
if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) {
|
||||
$value = DateTimeExcel\DateValue::fromString($value);
|
||||
$value = DateTimeExcel\DateValue::fromString($value) + DateTimeExcel\TimeValue::fromString($value);
|
||||
}
|
||||
|
||||
return (string) NumberFormat::toFormattedString($value, $format);
|
||||
@ -140,7 +140,7 @@ class Format
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function convertValue($value)
|
||||
private static function convertValue($value, bool $spacesMeanZero = false)
|
||||
{
|
||||
$value = $value ?? 0;
|
||||
if (is_bool($value)) {
|
||||
@ -150,6 +150,12 @@ class Format
|
||||
throw new CalcExp(ExcelError::VALUE());
|
||||
}
|
||||
}
|
||||
if (is_string($value)) {
|
||||
$value = trim($value);
|
||||
if ($spacesMeanZero && $value === '') {
|
||||
$value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
@ -181,6 +187,9 @@ class Format
|
||||
'',
|
||||
trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode())
|
||||
);
|
||||
if ($numberValue === '') {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
if (is_numeric($numberValue)) {
|
||||
return (float) $numberValue;
|
||||
}
|
||||
@ -277,7 +286,7 @@ class Format
|
||||
}
|
||||
|
||||
try {
|
||||
$value = self::convertValue($value);
|
||||
$value = self::convertValue($value, true);
|
||||
$decimalSeparator = self::getDecimalSeparator($decimalSeparator);
|
||||
$groupSeparator = self::getGroupSeparator($groupSeparator);
|
||||
} catch (CalcExp $e) {
|
||||
@ -285,12 +294,12 @@ class Format
|
||||
}
|
||||
|
||||
if (!is_numeric($value)) {
|
||||
$decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE);
|
||||
$decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator, '/') . '/', $value, $matches, PREG_OFFSET_CAPTURE);
|
||||
if ($decimalPositions > 1) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
$decimalOffset = array_pop($matches[0])[1]; // @phpstan-ignore-line
|
||||
if (strpos($value, $groupSeparator, $decimalOffset) !== false) {
|
||||
$decimalOffset = array_pop($matches[0])[1] ?? null;
|
||||
if ($decimalOffset === null || strpos($value, $groupSeparator, $decimalOffset) !== false) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
|
@ -193,7 +193,7 @@ class Text
|
||||
if (is_array($delimiter) && count($valueSet) > 1) {
|
||||
$quotedDelimiters = array_map(
|
||||
function ($delimiter) {
|
||||
return preg_quote($delimiter ?? '');
|
||||
return preg_quote($delimiter ?? '', '/');
|
||||
},
|
||||
$valueSet
|
||||
);
|
||||
@ -202,7 +202,7 @@ class Text
|
||||
return '(' . $delimiters . ')';
|
||||
}
|
||||
|
||||
return '(' . preg_quote(/** @scrutinizer ignore-type */ Functions::flattenSingleValue($delimiter)) . ')';
|
||||
return '(' . preg_quote(/** @scrutinizer ignore-type */ Functions::flattenSingleValue($delimiter), '/') . ')';
|
||||
}
|
||||
|
||||
private static function matchFlags(bool $matchMode): string
|
||||
|
@ -51,8 +51,9 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
|
||||
return $this->setImproperFraction($matches, $cell);
|
||||
}
|
||||
|
||||
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator());
|
||||
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator());
|
||||
$decimalSeparatorNoPreg = StringHelper::getDecimalSeparator();
|
||||
$decimalSeparator = preg_quote($decimalSeparatorNoPreg, '/');
|
||||
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
|
||||
|
||||
// Check for percentage
|
||||
if (preg_match('/^\-?\d*' . $decimalSeparator . '?\d*\s?\%$/', preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value))) {
|
||||
@ -64,7 +65,7 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
|
||||
// Convert value to number
|
||||
$sign = ($matches['PrefixedSign'] ?? $matches['PrefixedSign2'] ?? $matches['PostfixedSign']) ?? null;
|
||||
$currencyCode = $matches['PrefixedCurrency'] ?? $matches['PostfixedCurrency'];
|
||||
$value = (float) ($sign . trim(str_replace([$decimalSeparator, $currencyCode, ' ', '-'], ['.', '', '', ''], preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value)))); // @phpstan-ignore-line
|
||||
$value = (float) ($sign . trim(str_replace([$decimalSeparatorNoPreg, $currencyCode, ' ', '-'], ['.', '', '', ''], preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value)))); // @phpstan-ignore-line
|
||||
|
||||
return $this->setCurrency($value, $cell, $currencyCode); // @phpstan-ignore-line
|
||||
}
|
||||
|
@ -71,6 +71,9 @@ class Cell
|
||||
*/
|
||||
private $formulaAttributes;
|
||||
|
||||
/** @var IgnoredErrors */
|
||||
private $ignoredErrors;
|
||||
|
||||
/**
|
||||
* Update the cell into the cell collection.
|
||||
*
|
||||
@ -119,6 +122,7 @@ class Cell
|
||||
} elseif (self::getValueBinder()->bindValue($this, $value) === false) {
|
||||
throw new Exception('Value could not be bound to cell.');
|
||||
}
|
||||
$this->ignoredErrors = new IgnoredErrors();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -391,7 +395,9 @@ class Cell
|
||||
}
|
||||
|
||||
throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception(
|
||||
$this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage()
|
||||
$this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage(),
|
||||
$ex->getCode(),
|
||||
$ex
|
||||
);
|
||||
}
|
||||
|
||||
@ -794,4 +800,9 @@ class Cell
|
||||
{
|
||||
return (string) $this->getValue();
|
||||
}
|
||||
|
||||
public function getIgnoredErrors(): IgnoredErrors
|
||||
{
|
||||
return $this->ignoredErrors;
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ class DataValidator
|
||||
*/
|
||||
public function isValid(Cell $cell)
|
||||
{
|
||||
if (!$cell->hasDataValidation()) {
|
||||
if (!$cell->hasDataValidation() || $cell->getDataValidation()->getType() === DataValidation::TYPE_NONE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -31,13 +31,55 @@ class DataValidator
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: write check on all cases
|
||||
switch ($dataValidation->getType()) {
|
||||
case DataValidation::TYPE_LIST:
|
||||
return $this->isValueInList($cell);
|
||||
$returnValue = false;
|
||||
$type = $dataValidation->getType();
|
||||
if ($type === DataValidation::TYPE_LIST) {
|
||||
$returnValue = $this->isValueInList($cell);
|
||||
} elseif ($type === DataValidation::TYPE_WHOLE) {
|
||||
if (!is_numeric($cellValue) || fmod((float) $cellValue, 1) != 0) {
|
||||
$returnValue = false;
|
||||
} else {
|
||||
$returnValue = $this->numericOperator($dataValidation, (int) $cellValue);
|
||||
}
|
||||
} elseif ($type === DataValidation::TYPE_DECIMAL || $type === DataValidation::TYPE_DATE || $type === DataValidation::TYPE_TIME) {
|
||||
if (!is_numeric($cellValue)) {
|
||||
$returnValue = false;
|
||||
} else {
|
||||
$returnValue = $this->numericOperator($dataValidation, (float) $cellValue);
|
||||
}
|
||||
} elseif ($type === DataValidation::TYPE_TEXTLENGTH) {
|
||||
$returnValue = $this->numericOperator($dataValidation, mb_strlen((string) $cellValue));
|
||||
}
|
||||
|
||||
return false;
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/** @param float|int $cellValue */
|
||||
private function numericOperator(DataValidation $dataValidation, $cellValue): bool
|
||||
{
|
||||
$operator = $dataValidation->getOperator();
|
||||
$formula1 = $dataValidation->getFormula1();
|
||||
$formula2 = $dataValidation->getFormula2();
|
||||
$returnValue = false;
|
||||
if ($operator === DataValidation::OPERATOR_BETWEEN) {
|
||||
$returnValue = $cellValue >= $formula1 && $cellValue <= $formula2;
|
||||
} elseif ($operator === DataValidation::OPERATOR_NOTBETWEEN) {
|
||||
$returnValue = $cellValue < $formula1 || $cellValue > $formula2;
|
||||
} elseif ($operator === DataValidation::OPERATOR_EQUAL) {
|
||||
$returnValue = $cellValue == $formula1;
|
||||
} elseif ($operator === DataValidation::OPERATOR_NOTEQUAL) {
|
||||
$returnValue = $cellValue != $formula1;
|
||||
} elseif ($operator === DataValidation::OPERATOR_LESSTHAN) {
|
||||
$returnValue = $cellValue < $formula1;
|
||||
} elseif ($operator === DataValidation::OPERATOR_LESSTHANOREQUAL) {
|
||||
$returnValue = $cellValue <= $formula1;
|
||||
} elseif ($operator === DataValidation::OPERATOR_GREATERTHAN) {
|
||||
$returnValue = $cellValue > $formula1;
|
||||
} elseif ($operator === DataValidation::OPERATOR_GREATERTHANOREQUAL) {
|
||||
$returnValue = $cellValue >= $formula1;
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
66
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php
vendored
Normal file
66
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Cell;
|
||||
|
||||
class IgnoredErrors
|
||||
{
|
||||
/** @var bool */
|
||||
private $numberStoredAsText = false;
|
||||
|
||||
/** @var bool */
|
||||
private $formula = false;
|
||||
|
||||
/** @var bool */
|
||||
private $twoDigitTextYear = false;
|
||||
|
||||
/** @var bool */
|
||||
private $evalError = false;
|
||||
|
||||
public function setNumberStoredAsText(bool $value): self
|
||||
{
|
||||
$this->numberStoredAsText = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNumberStoredAsText(): bool
|
||||
{
|
||||
return $this->numberStoredAsText;
|
||||
}
|
||||
|
||||
public function setFormula(bool $value): self
|
||||
{
|
||||
$this->formula = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFormula(): bool
|
||||
{
|
||||
return $this->formula;
|
||||
}
|
||||
|
||||
public function setTwoDigitTextYear(bool $value): self
|
||||
{
|
||||
$this->twoDigitTextYear = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTwoDigitTextYear(): bool
|
||||
{
|
||||
return $this->twoDigitTextYear;
|
||||
}
|
||||
|
||||
public function setEvalError(bool $value): self
|
||||
{
|
||||
$this->evalError = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEvalError(): bool
|
||||
{
|
||||
return $this->evalError;
|
||||
}
|
||||
}
|
@ -118,7 +118,7 @@ class CellReferenceHelper
|
||||
{
|
||||
$newColumn = Coordinate::stringFromColumnIndex(min($newColumnIndex + $this->numberOfColumns, AddressRange::MAX_COLUMN_INT));
|
||||
|
||||
return $absoluteColumn . $newColumn;
|
||||
return "{$absoluteColumn}{$newColumn}";
|
||||
}
|
||||
|
||||
protected function updateRowReference(int $newRowIndex, string $absoluteRow): string
|
||||
@ -126,6 +126,6 @@ class CellReferenceHelper
|
||||
$newRow = $newRowIndex + $this->numberOfRows;
|
||||
$newRow = ($newRow > AddressRange::MAX_ROW) ? AddressRange::MAX_ROW : $newRow;
|
||||
|
||||
return $absoluteRow . (string) $newRow;
|
||||
return "{$absoluteRow}{$newRow}";
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,9 @@ class Axis extends Properties
|
||||
/** @var string */
|
||||
private $axisType = '';
|
||||
|
||||
/** @var ?AxisText */
|
||||
private $axisText;
|
||||
|
||||
/**
|
||||
* Axis Options.
|
||||
*
|
||||
@ -88,6 +91,9 @@ class Axis extends Properties
|
||||
Properties::FORMAT_CODE_DATE_ISO8601,
|
||||
];
|
||||
|
||||
/** @var bool */
|
||||
private $noFill = false;
|
||||
|
||||
/**
|
||||
* Get Series Data Type.
|
||||
*
|
||||
@ -183,6 +189,14 @@ class Axis extends Properties
|
||||
*/
|
||||
public function getAxisOptionsProperty($property)
|
||||
{
|
||||
if ($property === 'textRotation') {
|
||||
if ($this->axisText !== null) {
|
||||
if ($this->axisText->getRotation() !== null) {
|
||||
return (string) $this->axisText->getRotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->axisOptions[$property];
|
||||
}
|
||||
|
||||
@ -295,4 +309,28 @@ class Axis extends Properties
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAxisText(): ?AxisText
|
||||
{
|
||||
return $this->axisText;
|
||||
}
|
||||
|
||||
public function setAxisText(?AxisText $axisText): self
|
||||
{
|
||||
$this->axisText = $axisText;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setNoFill(bool $noFill): self
|
||||
{
|
||||
$this->noFill = $noFill;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNoFill(): bool
|
||||
{
|
||||
return $this->noFill;
|
||||
}
|
||||
}
|
||||
|
56
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php
vendored
Normal file
56
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||
|
||||
class AxisText extends Properties
|
||||
{
|
||||
/** @var ?int */
|
||||
private $rotation;
|
||||
|
||||
/** @var Font */
|
||||
private $font;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->font = new Font();
|
||||
$this->font->setSize(null, true);
|
||||
}
|
||||
|
||||
public function setRotation(?int $rotation): self
|
||||
{
|
||||
$this->rotation = $rotation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRotation(): ?int
|
||||
{
|
||||
return $this->rotation;
|
||||
}
|
||||
|
||||
public function getFillColorObject(): ChartColor
|
||||
{
|
||||
$fillColor = $this->font->getChartColor();
|
||||
if ($fillColor === null) {
|
||||
$fillColor = new ChartColor();
|
||||
$this->font->setChartColorFromObject($fillColor);
|
||||
}
|
||||
|
||||
return $fillColor;
|
||||
}
|
||||
|
||||
public function getFont(): Font
|
||||
{
|
||||
return $this->font;
|
||||
}
|
||||
|
||||
public function setFont(Font $font): self
|
||||
{
|
||||
$this->font = $font;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -150,6 +150,12 @@ class Chart
|
||||
/** @var bool */
|
||||
private $roundedCorners = false;
|
||||
|
||||
/** @var GridLines */
|
||||
private $borderLines;
|
||||
|
||||
/** @var ChartColor */
|
||||
private $fillColor;
|
||||
|
||||
/**
|
||||
* Create a new Chart.
|
||||
* majorGridlines and minorGridlines are deprecated, moved to Axis.
|
||||
@ -176,6 +182,8 @@ class Chart
|
||||
if ($minorGridlines !== null) {
|
||||
$this->yAxis->setMinorGridlines($minorGridlines);
|
||||
}
|
||||
$this->fillColor = new ChartColor();
|
||||
$this->borderLines = new GridLines();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -786,4 +794,21 @@ class Chart
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBorderLines(): GridLines
|
||||
{
|
||||
return $this->borderLines;
|
||||
}
|
||||
|
||||
public function setBorderLines(GridLines $borderLines): self
|
||||
{
|
||||
$this->borderLines = $borderLines;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFillColor(): ChartColor
|
||||
{
|
||||
return $this->fillColor;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||
|
||||
class Layout
|
||||
{
|
||||
/**
|
||||
@ -127,8 +129,11 @@ class Layout
|
||||
/** @var ?ChartColor */
|
||||
private $labelBorderColor;
|
||||
|
||||
/** @var ?ChartColor */
|
||||
private $labelFontColor;
|
||||
/** @var ?Font */
|
||||
private $labelFont;
|
||||
|
||||
/** @var Properties */
|
||||
private $labelEffects;
|
||||
|
||||
/**
|
||||
* Create a new Layout.
|
||||
@ -172,7 +177,18 @@ class Layout
|
||||
$this->initBoolean($layout, 'numFmtLinked');
|
||||
$this->initColor($layout, 'labelFillColor');
|
||||
$this->initColor($layout, 'labelBorderColor');
|
||||
$this->initColor($layout, 'labelFontColor');
|
||||
$labelFont = $layout['labelFont'] ?? null;
|
||||
if ($labelFont instanceof Font) {
|
||||
$this->labelFont = $labelFont;
|
||||
}
|
||||
$labelFontColor = $layout['labelFontColor'] ?? null;
|
||||
if ($labelFontColor instanceof ChartColor) {
|
||||
$this->setLabelFontColor($labelFontColor);
|
||||
}
|
||||
$labelEffects = $layout['labelEffects'] ?? null;
|
||||
if ($labelEffects instanceof Properties) {
|
||||
$this->labelEffects = $labelEffects;
|
||||
}
|
||||
}
|
||||
|
||||
private function initBoolean(array $layout, string $name): void
|
||||
@ -493,14 +509,32 @@ class Layout
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLabelFont(): ?Font
|
||||
{
|
||||
return $this->labelFont;
|
||||
}
|
||||
|
||||
public function getLabelEffects(): ?Properties
|
||||
{
|
||||
return $this->labelEffects;
|
||||
}
|
||||
|
||||
public function getLabelFontColor(): ?ChartColor
|
||||
{
|
||||
return $this->labelFontColor;
|
||||
if ($this->labelFont === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->labelFont->getChartColor();
|
||||
}
|
||||
|
||||
public function setLabelFontColor(?ChartColor $chartColor): self
|
||||
{
|
||||
$this->labelFontColor = $chartColor;
|
||||
if ($this->labelFont === null) {
|
||||
$this->labelFont = new Font();
|
||||
$this->labelFont->setSize(null, true);
|
||||
}
|
||||
$this->labelFont->setChartColorFromObject($chartColor);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -48,6 +48,15 @@ class Legend
|
||||
*/
|
||||
private $layout;
|
||||
|
||||
/** @var GridLines */
|
||||
private $borderLines;
|
||||
|
||||
/** @var ChartColor */
|
||||
private $fillColor;
|
||||
|
||||
/** @var ?AxisText */
|
||||
private $legendText;
|
||||
|
||||
/**
|
||||
* Create a new Legend.
|
||||
*
|
||||
@ -60,6 +69,13 @@ class Legend
|
||||
$this->setPosition($position);
|
||||
$this->layout = $layout;
|
||||
$this->setOverlay($overlay);
|
||||
$this->borderLines = new GridLines();
|
||||
$this->fillColor = new ChartColor();
|
||||
}
|
||||
|
||||
public function getFillColor(): ChartColor
|
||||
{
|
||||
return $this->fillColor;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -148,4 +164,28 @@ class Legend
|
||||
{
|
||||
return $this->layout;
|
||||
}
|
||||
|
||||
public function getLegendText(): ?AxisText
|
||||
{
|
||||
return $this->legendText;
|
||||
}
|
||||
|
||||
public function setLegendText(?AxisText $legendText): self
|
||||
{
|
||||
$this->legendText = $legendText;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBorderLines(): GridLines
|
||||
{
|
||||
return $this->borderLines;
|
||||
}
|
||||
|
||||
public function setBorderLines(GridLines $borderLines): self
|
||||
{
|
||||
$this->borderLines = $borderLines;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -163,4 +163,49 @@ class PlotArea
|
||||
{
|
||||
return $this->gradientFillStops;
|
||||
}
|
||||
|
||||
/** @var ?int */
|
||||
private $gapWidth;
|
||||
|
||||
/** @var bool */
|
||||
private $useUpBars = false;
|
||||
|
||||
/** @var bool */
|
||||
private $useDownBars = false;
|
||||
|
||||
public function getGapWidth(): ?int
|
||||
{
|
||||
return $this->gapWidth;
|
||||
}
|
||||
|
||||
public function setGapWidth(?int $gapWidth): self
|
||||
{
|
||||
$this->gapWidth = $gapWidth;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUseUpBars(): bool
|
||||
{
|
||||
return $this->useUpBars;
|
||||
}
|
||||
|
||||
public function setUseUpBars(bool $useUpBars): self
|
||||
{
|
||||
$this->useUpBars = $useUpBars;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUseDownBars(): bool
|
||||
{
|
||||
return $this->useDownBars;
|
||||
}
|
||||
|
||||
public function setUseDownBars(bool $useDownBars): self
|
||||
{
|
||||
$this->useDownBars = $useDownBars;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -434,12 +434,33 @@ abstract class JpGraphRendererBase implements IRenderer
|
||||
|
||||
// Loop through each data series in turn
|
||||
for ($i = 0; $i < $seriesCount; ++$i) {
|
||||
$dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
|
||||
$plotCategoryByIndex = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i);
|
||||
if ($plotCategoryByIndex === false) {
|
||||
$plotCategoryByIndex = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0);
|
||||
}
|
||||
$dataValuesY = $plotCategoryByIndex->getDataValues();
|
||||
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
|
||||
|
||||
$redoDataValuesY = true;
|
||||
if ($bubble) {
|
||||
if (!$bubbleSize) {
|
||||
$bubbleSize = '10';
|
||||
}
|
||||
$redoDataValuesY = false;
|
||||
foreach ($dataValuesY as $dataValueY) {
|
||||
if (!is_int($dataValueY) && !is_float($dataValueY)) {
|
||||
$redoDataValuesY = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($redoDataValuesY) {
|
||||
foreach ($dataValuesY as $k => $dataValueY) {
|
||||
$dataValuesY[$k] = $k;
|
||||
}
|
||||
}
|
||||
//var_dump($dataValuesY, $dataValuesX, $bubbleSize);
|
||||
|
||||
$seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY);
|
||||
if ($scatterStyle == 'lineMarker') {
|
||||
@ -483,7 +504,7 @@ abstract class JpGraphRendererBase implements IRenderer
|
||||
|
||||
$dataValues = [];
|
||||
foreach ($dataValuesY as $k => $dataValueY) {
|
||||
$dataValues[$k] = implode(' ', array_reverse($dataValueY));
|
||||
$dataValues[$k] = is_array($dataValueY) ? implode(' ', array_reverse($dataValueY)) : $dataValueY;
|
||||
}
|
||||
$tmp = array_shift($dataValues);
|
||||
$dataValues[] = $tmp;
|
||||
|
@ -3,12 +3,12 @@
|
||||
namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
|
||||
|
||||
/**
|
||||
* Jpgraph is not oficially maintained in Composer.
|
||||
* Jpgraph is not officially maintained by Composer at packagist.org.
|
||||
*
|
||||
* This renderer implementation uses package
|
||||
* https://packagist.org/packages/mitoteam/jpgraph
|
||||
*
|
||||
* This package is up to date for August 2022 and has PHP 8.1 support.
|
||||
* This package is up to date for June 2023 and has PHP 8.2 support.
|
||||
*/
|
||||
class MtJpGraphRenderer extends JpGraphRendererBase
|
||||
{
|
||||
@ -29,7 +29,7 @@ class MtJpGraphRenderer extends JpGraphRendererBase
|
||||
'regstat',
|
||||
'scatter',
|
||||
'stock',
|
||||
]);
|
||||
], true); // enable Extended mode
|
||||
|
||||
$loaded = true;
|
||||
}
|
||||
|
@ -107,6 +107,8 @@ class Properties
|
||||
*/
|
||||
private $customProperties = [];
|
||||
|
||||
private string $hyperlinkBase = '';
|
||||
|
||||
/**
|
||||
* Create a new Document Properties instance.
|
||||
*/
|
||||
@ -534,4 +536,16 @@ class Properties
|
||||
{
|
||||
return self::PROPERTY_TYPE_ARRAY[$propertyType] ?? self::PROPERTY_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
public function getHyperlinkBase(): string
|
||||
{
|
||||
return $this->hyperlinkBase;
|
||||
}
|
||||
|
||||
public function setHyperlinkBase(string $hyperlinkBase): self
|
||||
{
|
||||
$this->hyperlinkBase = $hyperlinkBase;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
89
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php
vendored
Normal file
89
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Helper;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Exception;
|
||||
|
||||
class Downloader
|
||||
{
|
||||
protected string $filepath;
|
||||
|
||||
protected string $filename;
|
||||
|
||||
protected string $filetype;
|
||||
|
||||
protected const CONTENT_TYPES = [
|
||||
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'xls' => 'application/vnd.ms-excel',
|
||||
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'csv' => 'text/csv',
|
||||
'html' => 'text/html',
|
||||
'pdf' => 'application/pdf',
|
||||
];
|
||||
|
||||
public function __construct(string $folder, string $filename, ?string $filetype = null)
|
||||
{
|
||||
if ((is_dir($folder) === false) || (is_readable($folder) === false)) {
|
||||
throw new Exception("Folder {$folder} is not accessable");
|
||||
}
|
||||
$filepath = "{$folder}/{$filename}";
|
||||
$this->filepath = (string) realpath($filepath);
|
||||
$this->filename = basename($filepath);
|
||||
if ((file_exists($this->filepath) === false) || (is_readable($this->filepath) === false)) {
|
||||
throw new Exception("{$this->filename} not found, or cannot be read");
|
||||
}
|
||||
|
||||
$filetype ??= pathinfo($filename, PATHINFO_EXTENSION);
|
||||
if (array_key_exists(strtolower($filetype), self::CONTENT_TYPES) === false) {
|
||||
throw new Exception("Invalid filetype: {$filetype} cannot be downloaded");
|
||||
}
|
||||
$this->filetype = strtolower($filetype);
|
||||
}
|
||||
|
||||
public function download(): void
|
||||
{
|
||||
$this->headers();
|
||||
|
||||
readfile($this->filepath);
|
||||
}
|
||||
|
||||
public function headers(): void
|
||||
{
|
||||
ob_clean();
|
||||
|
||||
$this->contentType();
|
||||
$this->contentDisposition();
|
||||
$this->cacheHeaders();
|
||||
$this->fileSize();
|
||||
|
||||
flush();
|
||||
}
|
||||
|
||||
protected function contentType(): void
|
||||
{
|
||||
header('Content-Type: ' . self::CONTENT_TYPES[$this->filetype]);
|
||||
}
|
||||
|
||||
protected function contentDisposition(): void
|
||||
{
|
||||
header('Content-Disposition: attachment;filename="' . $this->filename . '"');
|
||||
}
|
||||
|
||||
protected function cacheHeaders(): void
|
||||
{
|
||||
header('Cache-Control: max-age=0');
|
||||
// If you're serving to IE 9, then the following may be needed
|
||||
header('Cache-Control: max-age=1');
|
||||
|
||||
// If you're serving to IE over SSL, then the following may be needed
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified
|
||||
header('Cache-Control: cache, must-revalidate'); // HTTP/1.1
|
||||
header('Pragma: public'); // HTTP/1.0
|
||||
}
|
||||
|
||||
protected function fileSize(): void
|
||||
{
|
||||
header('Content-Length: ' . filesize($this->filepath));
|
||||
}
|
||||
}
|
46
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php
vendored
Normal file
46
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Helper;
|
||||
|
||||
class Handler
|
||||
{
|
||||
/** @var string */
|
||||
private static $invalidHex = 'Y';
|
||||
|
||||
// A bunch of methods to show that we continue
|
||||
// to capture messages even using PhpUnit 10.
|
||||
public static function suppressed(): bool
|
||||
{
|
||||
return @trigger_error('hello');
|
||||
}
|
||||
|
||||
public static function deprecated(): string
|
||||
{
|
||||
return (string) hexdec(self::$invalidHex);
|
||||
}
|
||||
|
||||
public static function notice(string $value): void
|
||||
{
|
||||
date_default_timezone_set($value);
|
||||
}
|
||||
|
||||
public static function warning(): bool
|
||||
{
|
||||
return file_get_contents(__FILE__ . 'noexist') !== false;
|
||||
}
|
||||
|
||||
public static function userDeprecated(): bool
|
||||
{
|
||||
return trigger_error('hello', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
public static function userNotice(): bool
|
||||
{
|
||||
return trigger_error('userNotice', E_USER_NOTICE);
|
||||
}
|
||||
|
||||
public static function userWarning(): bool
|
||||
{
|
||||
return trigger_error('userWarning', E_USER_WARNING);
|
||||
}
|
||||
}
|
@ -714,7 +714,7 @@ class Html
|
||||
return self::COLOUR_MAP[$colorName] ?? '';
|
||||
}
|
||||
|
||||
private function startFontTag(DOMElement $tag): void
|
||||
protected function startFontTag(DOMElement $tag): void
|
||||
{
|
||||
$attrs = $tag->attributes;
|
||||
if ($attrs !== null) {
|
||||
@ -737,72 +737,72 @@ class Html
|
||||
}
|
||||
}
|
||||
|
||||
private function endFontTag(): void
|
||||
protected function endFontTag(): void
|
||||
{
|
||||
$this->face = $this->size = $this->color = null;
|
||||
}
|
||||
|
||||
private function startBoldTag(): void
|
||||
protected function startBoldTag(): void
|
||||
{
|
||||
$this->bold = true;
|
||||
}
|
||||
|
||||
private function endBoldTag(): void
|
||||
protected function endBoldTag(): void
|
||||
{
|
||||
$this->bold = false;
|
||||
}
|
||||
|
||||
private function startItalicTag(): void
|
||||
protected function startItalicTag(): void
|
||||
{
|
||||
$this->italic = true;
|
||||
}
|
||||
|
||||
private function endItalicTag(): void
|
||||
protected function endItalicTag(): void
|
||||
{
|
||||
$this->italic = false;
|
||||
}
|
||||
|
||||
private function startUnderlineTag(): void
|
||||
protected function startUnderlineTag(): void
|
||||
{
|
||||
$this->underline = true;
|
||||
}
|
||||
|
||||
private function endUnderlineTag(): void
|
||||
protected function endUnderlineTag(): void
|
||||
{
|
||||
$this->underline = false;
|
||||
}
|
||||
|
||||
private function startSubscriptTag(): void
|
||||
protected function startSubscriptTag(): void
|
||||
{
|
||||
$this->subscript = true;
|
||||
}
|
||||
|
||||
private function endSubscriptTag(): void
|
||||
protected function endSubscriptTag(): void
|
||||
{
|
||||
$this->subscript = false;
|
||||
}
|
||||
|
||||
private function startSuperscriptTag(): void
|
||||
protected function startSuperscriptTag(): void
|
||||
{
|
||||
$this->superscript = true;
|
||||
}
|
||||
|
||||
private function endSuperscriptTag(): void
|
||||
protected function endSuperscriptTag(): void
|
||||
{
|
||||
$this->superscript = false;
|
||||
}
|
||||
|
||||
private function startStrikethruTag(): void
|
||||
protected function startStrikethruTag(): void
|
||||
{
|
||||
$this->strikethrough = true;
|
||||
}
|
||||
|
||||
private function endStrikethruTag(): void
|
||||
protected function endStrikethruTag(): void
|
||||
{
|
||||
$this->strikethrough = false;
|
||||
}
|
||||
|
||||
private function breakTag(): void
|
||||
protected function breakTag(): void
|
||||
{
|
||||
$this->stringData .= "\n";
|
||||
}
|
||||
@ -826,8 +826,9 @@ class Html
|
||||
if (isset($callbacks[$callbackTag])) {
|
||||
$elementHandler = $callbacks[$callbackTag];
|
||||
if (method_exists($this, $elementHandler)) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
call_user_func([$this, $elementHandler], $element);
|
||||
/** @var callable */
|
||||
$callable = [$this, $elementHandler];
|
||||
call_user_func($callable, $element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Helper;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Settings;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\IWriter;
|
||||
@ -12,6 +14,7 @@ use RecursiveRegexIterator;
|
||||
use ReflectionClass;
|
||||
use RegexIterator;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Helper class to be used in sample code.
|
||||
@ -120,7 +123,7 @@ class Sample
|
||||
* @param string $filename
|
||||
* @param string[] $writers
|
||||
*/
|
||||
public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls']): void
|
||||
public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls'], bool $withCharts = false, ?callable $writerCallback = null): void
|
||||
{
|
||||
// Set active sheet index to the first sheet, so Excel opens this as the first sheet
|
||||
$spreadsheet->setActiveSheetIndex(0);
|
||||
@ -129,9 +132,16 @@ class Sample
|
||||
foreach ($writers as $writerType) {
|
||||
$path = $this->getFilename($filename, mb_strtolower($writerType));
|
||||
$writer = IOFactory::createWriter($spreadsheet, $writerType);
|
||||
$writer->setIncludeCharts($withCharts);
|
||||
if ($writerCallback !== null) {
|
||||
$writerCallback($writer);
|
||||
}
|
||||
$callStartTime = microtime(true);
|
||||
$writer->save($path);
|
||||
$this->logWrite($writer, $path, /** @scrutinizer ignore-type */ $callStartTime);
|
||||
if ($this->isCli() === false) {
|
||||
echo '<a href="/download.php?type=' . pathinfo($path, PATHINFO_EXTENSION) . '&name=' . basename($path) . '">Download ' . basename($path) . '</a><br />';
|
||||
}
|
||||
}
|
||||
|
||||
$this->logEndingNotes();
|
||||
@ -147,7 +157,7 @@ class Sample
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getTemporaryFolder()
|
||||
public function getTemporaryFolder()
|
||||
{
|
||||
$tempFolder = sys_get_temp_dir() . '/phpspreadsheet';
|
||||
if (!$this->isDirOrMkdir($tempFolder)) {
|
||||
@ -162,10 +172,8 @@ class Sample
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $extension
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFilename($filename, $extension = 'xlsx')
|
||||
public function getFilename($filename, $extension = 'xlsx'): string
|
||||
{
|
||||
$originalExtension = pathinfo($filename, PATHINFO_EXTENSION);
|
||||
|
||||
@ -195,7 +203,29 @@ class Sample
|
||||
public function log(string $message): void
|
||||
{
|
||||
$eol = $this->isCli() ? PHP_EOL : '<br />';
|
||||
echo date('H:i:s ') . $message . $eol;
|
||||
echo($this->isCli() ? date('H:i:s ') : '') . $message . $eol;
|
||||
}
|
||||
|
||||
public function renderChart(Chart $chart, string $fileName): void
|
||||
{
|
||||
if ($this->isCli() === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class);
|
||||
|
||||
$fileName = $this->getFilename($fileName, 'png');
|
||||
|
||||
try {
|
||||
$chart->render($fileName);
|
||||
$this->log('Rendered image: ' . $fileName);
|
||||
$imageData = file_get_contents($fileName);
|
||||
if ($imageData !== false) {
|
||||
echo '<div><img src="data:image/gif;base64,' . base64_encode($imageData) . '" /></div>';
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->log('Error rendering chart: ' . $e->getMessage() . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
public function titles(string $category, string $functionName, ?string $description = null): void
|
||||
@ -246,7 +276,10 @@ class Sample
|
||||
$callTime = $callEndTime - $callStartTime;
|
||||
$reflection = new ReflectionClass($writer);
|
||||
$format = $reflection->getShortName();
|
||||
$message = "Write {$format} format to <code>{$path}</code> in " . sprintf('%.4f', $callTime) . ' seconds';
|
||||
|
||||
$message = ($this->isCli() === true)
|
||||
? "Write {$format} format to {$path} in " . sprintf('%.4f', $callTime) . ' seconds'
|
||||
: "Write {$format} format to <code>{$path}</code> in " . sprintf('%.4f', $callTime) . ' seconds';
|
||||
|
||||
$this->log($message);
|
||||
}
|
||||
|
@ -48,17 +48,17 @@ class TextGrid
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$this->gridDisplay = $this->isCli ? '' : '<code>';
|
||||
$this->gridDisplay = $this->isCli ? '' : '<pre>';
|
||||
|
||||
$maxRow = max($this->rows);
|
||||
$maxRowLength = strlen((string) $maxRow) + 1;
|
||||
$maxRowLength = mb_strlen((string) $maxRow) + 1;
|
||||
$columnWidths = $this->getColumnWidths();
|
||||
|
||||
$this->renderColumnHeader($maxRowLength, $columnWidths);
|
||||
$this->renderRows($maxRowLength, $columnWidths);
|
||||
$this->renderFooter($maxRowLength, $columnWidths);
|
||||
|
||||
$this->gridDisplay .= $this->isCli ? '' : '</code>';
|
||||
$this->gridDisplay .= $this->isCli ? '' : '</pre>';
|
||||
|
||||
return $this->gridDisplay;
|
||||
}
|
||||
@ -75,9 +75,9 @@ class TextGrid
|
||||
private function renderCells(array $rowData, array $columnWidths): void
|
||||
{
|
||||
foreach ($rowData as $column => $cell) {
|
||||
$cell = ($this->isCli) ? (string) $cell : htmlentities((string) $cell);
|
||||
$displayCell = ($this->isCli) ? (string) $cell : htmlentities((string) $cell);
|
||||
$this->gridDisplay .= '| ';
|
||||
$this->gridDisplay .= str_pad($cell, $columnWidths[$column] + 1, ' ');
|
||||
$this->gridDisplay .= $displayCell . str_repeat(' ', $columnWidths[$column] - mb_strlen($cell ?? '') + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,12 +126,12 @@ class TextGrid
|
||||
|
||||
foreach ($columnData as $columnValue) {
|
||||
if (is_string($columnValue)) {
|
||||
$columnWidth = max($columnWidth, strlen($columnValue));
|
||||
$columnWidth = max($columnWidth, mb_strlen($columnValue));
|
||||
} elseif (is_bool($columnValue)) {
|
||||
$columnWidth = max($columnWidth, strlen($columnValue ? 'TRUE' : 'FALSE'));
|
||||
$columnWidth = max($columnWidth, mb_strlen($columnValue ? 'TRUE' : 'FALSE'));
|
||||
}
|
||||
|
||||
$columnWidth = max($columnWidth, strlen((string) $columnWidth));
|
||||
$columnWidth = max($columnWidth, mb_strlen((string) $columnWidth));
|
||||
}
|
||||
|
||||
return $columnWidth;
|
||||
|
@ -80,17 +80,15 @@ class Gnumeric extends BaseReader
|
||||
*/
|
||||
public function canRead(string $filename): bool
|
||||
{
|
||||
// Check if gzlib functions are available
|
||||
if (File::testFileNoThrow($filename) && function_exists('gzread')) {
|
||||
// Read signature data (first 3 bytes)
|
||||
$fh = fopen($filename, 'rb');
|
||||
if ($fh !== false) {
|
||||
$data = fread($fh, 2);
|
||||
fclose($fh);
|
||||
$data = null;
|
||||
if (File::testFileNoThrow($filename)) {
|
||||
$data = $this->gzfileGetContents($filename);
|
||||
if (strpos($data, self::NAMESPACE_GNM) === false) {
|
||||
$data = '';
|
||||
}
|
||||
}
|
||||
|
||||
return isset($data) && $data === chr(0x1F) . chr(0x8B);
|
||||
return !empty($data);
|
||||
}
|
||||
|
||||
private static function matchXml(XMLReader $xml, string $expectedLocalName): bool
|
||||
@ -110,9 +108,13 @@ class Gnumeric extends BaseReader
|
||||
public function listWorksheetNames($filename)
|
||||
{
|
||||
File::assertFile($filename);
|
||||
if (!$this->canRead($filename)) {
|
||||
throw new Exception($filename . ' is an invalid Gnumeric file.');
|
||||
}
|
||||
|
||||
$xml = new XMLReader();
|
||||
$xml->xml($this->getSecurityScannerOrThrow()->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions());
|
||||
$contents = $this->gzfileGetContents($filename);
|
||||
$xml->xml($contents, null, Settings::getLibXmlLoaderOptions());
|
||||
$xml->setParserProperty(2, true);
|
||||
|
||||
$worksheetNames = [];
|
||||
@ -139,9 +141,13 @@ class Gnumeric extends BaseReader
|
||||
public function listWorksheetInfo($filename)
|
||||
{
|
||||
File::assertFile($filename);
|
||||
if (!$this->canRead($filename)) {
|
||||
throw new Exception($filename . ' is an invalid Gnumeric file.');
|
||||
}
|
||||
|
||||
$xml = new XMLReader();
|
||||
$xml->xml($this->getSecurityScannerOrThrow()->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions());
|
||||
$contents = $this->gzfileGetContents($filename);
|
||||
$xml->xml($contents, null, Settings::getLibXmlLoaderOptions());
|
||||
$xml->setParserProperty(2, true);
|
||||
|
||||
$worksheetInfo = [];
|
||||
@ -185,13 +191,23 @@ class Gnumeric extends BaseReader
|
||||
*/
|
||||
private function gzfileGetContents($filename)
|
||||
{
|
||||
$file = @gzopen($filename, 'rb');
|
||||
$data = '';
|
||||
if ($file !== false) {
|
||||
while (!gzeof($file)) {
|
||||
$data .= gzread($file, 1024);
|
||||
$contents = @file_get_contents($filename);
|
||||
if ($contents !== false) {
|
||||
if (substr($contents, 0, 2) === "\x1f\x8b") {
|
||||
// Check if gzlib functions are available
|
||||
if (function_exists('gzdecode')) {
|
||||
$contents = @gzdecode($contents);
|
||||
if ($contents !== false) {
|
||||
$data = $contents;
|
||||
}
|
||||
gzclose($file);
|
||||
}
|
||||
} else {
|
||||
$data = $contents;
|
||||
}
|
||||
}
|
||||
if ($data !== '') {
|
||||
$data = $this->getSecurityScannerOrThrow()->scan($data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
@ -245,10 +261,13 @@ class Gnumeric extends BaseReader
|
||||
{
|
||||
$this->spreadsheet = $spreadsheet;
|
||||
File::assertFile($filename);
|
||||
if (!$this->canRead($filename)) {
|
||||
throw new Exception($filename . ' is an invalid Gnumeric file.');
|
||||
}
|
||||
|
||||
$gFileData = $this->gzfileGetContents($filename);
|
||||
|
||||
$xml2 = simplexml_load_string($this->getSecurityScannerOrThrow()->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions());
|
||||
$xml2 = simplexml_load_string($gFileData, 'SimpleXMLElement', Settings::getLibXmlLoaderOptions());
|
||||
$xml = self::testSimpleXml($xml2);
|
||||
|
||||
$gnmXML = $xml->children(self::NAMESPACE_GNM);
|
||||
|
@ -7,6 +7,8 @@ use DOMElement;
|
||||
use DOMNode;
|
||||
use DOMText;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Document\Properties;
|
||||
use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
@ -283,15 +285,35 @@ class Html extends BaseReader
|
||||
* @param int|string $row
|
||||
* @param mixed $cellContent
|
||||
*/
|
||||
protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent): void
|
||||
protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent, array $attributeArray): void
|
||||
{
|
||||
if (is_string($cellContent)) {
|
||||
// Simple String content
|
||||
if (trim($cellContent) > '') {
|
||||
// Only actually write it if there's content in the string
|
||||
// Write to worksheet to be done here...
|
||||
// ... we return the cell so we can mess about with styles more easily
|
||||
// ... we return the cell, so we can mess about with styles more easily
|
||||
|
||||
// Set cell value explicitly if there is data-type attribute
|
||||
if (isset($attributeArray['data-type'])) {
|
||||
$datatype = $attributeArray['data-type'];
|
||||
if (in_array($datatype, [DataType::TYPE_STRING, DataType::TYPE_STRING2, DataType::TYPE_INLINE])) {
|
||||
//Prevent to Excel treat string with beginning equal sign or convert big numbers to scientific number
|
||||
if (substr($cellContent, 0, 1) === '=') {
|
||||
$sheet->getCell($column . $row)
|
||||
->getStyle()
|
||||
->setQuotePrefix(true);
|
||||
}
|
||||
}
|
||||
//catching the Exception and ignoring the invalid data types
|
||||
try {
|
||||
$sheet->setCellValueExplicit($column . $row, $cellContent, $attributeArray['data-type']);
|
||||
} catch (\PhpOffice\PhpSpreadsheet\Exception $exception) {
|
||||
$sheet->setCellValue($column . $row, $cellContent);
|
||||
}
|
||||
} else {
|
||||
$sheet->setCellValue($column . $row, $cellContent);
|
||||
}
|
||||
$this->dataArray[$row][$column] = $cellContent;
|
||||
}
|
||||
} else {
|
||||
@ -305,7 +327,7 @@ class Html extends BaseReader
|
||||
private function processDomElementBody(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child): void
|
||||
{
|
||||
$attributeArray = [];
|
||||
foreach (($child->attributes ?? []) as $attribute) {
|
||||
foreach ($child->attributes as $attribute) {
|
||||
$attributeArray[$attribute->name] = $attribute->value;
|
||||
}
|
||||
|
||||
@ -355,7 +377,7 @@ class Html extends BaseReader
|
||||
private function processDomElementHr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
|
||||
{
|
||||
if ($child->nodeName === 'hr') {
|
||||
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
|
||||
++$row;
|
||||
if (isset($this->formats[$child->nodeName])) {
|
||||
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
|
||||
@ -375,7 +397,7 @@ class Html extends BaseReader
|
||||
$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);
|
||||
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
|
||||
++$row;
|
||||
}
|
||||
} else {
|
||||
@ -421,11 +443,11 @@ class Html extends BaseReader
|
||||
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||
} else {
|
||||
if ($cellContent > '') {
|
||||
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
|
||||
++$row;
|
||||
}
|
||||
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
|
||||
|
||||
if (isset($this->formats[$child->nodeName])) {
|
||||
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
|
||||
@ -448,11 +470,11 @@ class Html extends BaseReader
|
||||
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||
} else {
|
||||
if ($cellContent > '') {
|
||||
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
|
||||
}
|
||||
++$row;
|
||||
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
|
||||
$column = 'A';
|
||||
}
|
||||
} else {
|
||||
@ -469,10 +491,13 @@ class Html extends BaseReader
|
||||
}
|
||||
}
|
||||
|
||||
private string $currentColumn = 'A';
|
||||
|
||||
private function processDomElementTable(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
|
||||
{
|
||||
if ($child->nodeName === 'table') {
|
||||
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||
$this->currentColumn = 'A';
|
||||
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
|
||||
$column = $this->setTableStartColumn($column);
|
||||
if ($this->tableLevel > 1 && $row > 1) {
|
||||
--$row;
|
||||
@ -491,7 +516,10 @@ class Html extends BaseReader
|
||||
|
||||
private function processDomElementTr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
|
||||
{
|
||||
if ($child->nodeName === 'tr') {
|
||||
if ($child->nodeName === 'col') {
|
||||
$this->applyInlineStyle($sheet, -1, $this->currentColumn, $attributeArray);
|
||||
++$this->currentColumn;
|
||||
} elseif ($child->nodeName === 'tr') {
|
||||
$column = $this->getTableStartColumn();
|
||||
$cellContent = '';
|
||||
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||
@ -574,7 +602,7 @@ class Html extends BaseReader
|
||||
// apply inline style
|
||||
$this->applyInlineStyle($sheet, $row, $column, $attributeArray);
|
||||
|
||||
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
|
||||
|
||||
$this->processDomElementBgcolor($sheet, $row, $column, $attributeArray);
|
||||
$this->processDomElementWidth($sheet, $column, $attributeArray);
|
||||
@ -664,10 +692,94 @@ class Html extends BaseReader
|
||||
if ($loaded === false) {
|
||||
throw new Exception('Failed to load ' . $filename . ' as a DOM Document', 0, $e ?? null);
|
||||
}
|
||||
self::loadProperties($dom, $spreadsheet);
|
||||
|
||||
return $this->loadDocument($dom, $spreadsheet);
|
||||
}
|
||||
|
||||
private static function loadProperties(DOMDocument $dom, Spreadsheet $spreadsheet): void
|
||||
{
|
||||
$properties = $spreadsheet->getProperties();
|
||||
foreach ($dom->getElementsByTagName('meta') as $meta) {
|
||||
$metaContent = (string) $meta->getAttribute('content');
|
||||
if ($metaContent !== '') {
|
||||
$metaName = (string) $meta->getAttribute('name');
|
||||
switch ($metaName) {
|
||||
case 'author':
|
||||
$properties->setCreator($metaContent);
|
||||
|
||||
break;
|
||||
case 'category':
|
||||
$properties->setCategory($metaContent);
|
||||
|
||||
break;
|
||||
case 'company':
|
||||
$properties->setCompany($metaContent);
|
||||
|
||||
break;
|
||||
case 'created':
|
||||
$properties->setCreated($metaContent);
|
||||
|
||||
break;
|
||||
case 'description':
|
||||
$properties->setDescription($metaContent);
|
||||
|
||||
break;
|
||||
case 'keywords':
|
||||
$properties->setKeywords($metaContent);
|
||||
|
||||
break;
|
||||
case 'lastModifiedBy':
|
||||
$properties->setLastModifiedBy($metaContent);
|
||||
|
||||
break;
|
||||
case 'manager':
|
||||
$properties->setManager($metaContent);
|
||||
|
||||
break;
|
||||
case 'modified':
|
||||
$properties->setModified($metaContent);
|
||||
|
||||
break;
|
||||
case 'subject':
|
||||
$properties->setSubject($metaContent);
|
||||
|
||||
break;
|
||||
case 'title':
|
||||
$properties->setTitle($metaContent);
|
||||
|
||||
break;
|
||||
default:
|
||||
if (preg_match('/^custom[.](bool|date|float|int|string)[.](.+)$/', $metaName, $matches) === 1) {
|
||||
switch ($matches[1]) {
|
||||
case 'bool':
|
||||
$properties->setCustomProperty($matches[2], (bool) $metaContent, Properties::PROPERTY_TYPE_BOOLEAN);
|
||||
|
||||
break;
|
||||
case 'float':
|
||||
$properties->setCustomProperty($matches[2], (float) $metaContent, Properties::PROPERTY_TYPE_FLOAT);
|
||||
|
||||
break;
|
||||
case 'int':
|
||||
$properties->setCustomProperty($matches[2], (int) $metaContent, Properties::PROPERTY_TYPE_INTEGER);
|
||||
|
||||
break;
|
||||
case 'date':
|
||||
$properties->setCustomProperty($matches[2], $metaContent, Properties::PROPERTY_TYPE_DATE);
|
||||
|
||||
break;
|
||||
default: // string
|
||||
$properties->setCustomProperty($matches[2], $metaContent, Properties::PROPERTY_TYPE_STRING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($dom->baseURI)) {
|
||||
$properties->setHyperlinkBase($dom->baseURI);
|
||||
}
|
||||
}
|
||||
|
||||
private static function replaceNonAscii(array $matches): string
|
||||
{
|
||||
return '&#' . mb_ord($matches[0], 'UTF-8') . ';';
|
||||
@ -698,8 +810,10 @@ class Html extends BaseReader
|
||||
if ($loaded === false) {
|
||||
throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null);
|
||||
}
|
||||
$spreadsheet = $spreadsheet ?? new Spreadsheet();
|
||||
self::loadProperties($dom, $spreadsheet);
|
||||
|
||||
return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet());
|
||||
return $this->loadDocument($dom, $spreadsheet);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -769,7 +883,9 @@ class Html extends BaseReader
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
|
||||
if ($row <= 0 || $column === '') {
|
||||
$cellStyle = new Style();
|
||||
} elseif (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
|
||||
$columnTo = $column;
|
||||
for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
|
||||
++$columnTo;
|
||||
@ -901,16 +1017,20 @@ class Html extends BaseReader
|
||||
break;
|
||||
|
||||
case 'width':
|
||||
if ($column !== '') {
|
||||
$sheet->getColumnDimension($column)->setWidth(
|
||||
(new CssDimension($styleValue ?? ''))->width()
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'height':
|
||||
if ($row > 0) {
|
||||
$sheet->getRowDimension($row)->setRowHeight(
|
||||
(new CssDimension($styleValue ?? ''))->height()
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
|
@ -8,6 +8,7 @@ use DOMElement;
|
||||
use DOMNode;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Helper\Dimension as HelperDimension;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Ods\AutoFilter;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Ods\DefinedNames;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Ods\FormulaTranslator;
|
||||
@ -295,11 +296,29 @@ class Ods extends BaseReader
|
||||
$tableNs = $dom->lookupNamespaceUri('table');
|
||||
$textNs = $dom->lookupNamespaceUri('text');
|
||||
$xlinkNs = $dom->lookupNamespaceUri('xlink');
|
||||
$styleNs = $dom->lookupNamespaceUri('style');
|
||||
|
||||
$pageSettings->readStyleCrossReferences($dom);
|
||||
|
||||
$autoFilterReader = new AutoFilter($spreadsheet, $tableNs);
|
||||
$definedNameReader = new DefinedNames($spreadsheet, $tableNs);
|
||||
$columnWidths = [];
|
||||
$automaticStyle0 = $dom->getElementsByTagNameNS($officeNs, 'automatic-styles')->item(0);
|
||||
$automaticStyles = ($automaticStyle0 === null) ? [] : $automaticStyle0->getElementsByTagNameNS($styleNs, 'style');
|
||||
foreach ($automaticStyles as $automaticStyle) {
|
||||
$styleName = $automaticStyle->getAttributeNS($styleNs, 'name');
|
||||
$styleFamily = $automaticStyle->getAttributeNS($styleNs, 'family');
|
||||
if ($styleFamily === 'table-column') {
|
||||
$tcprops = $automaticStyle->getElementsByTagNameNS($styleNs, 'table-column-properties');
|
||||
if ($tcprops !== null) {
|
||||
$tcprop = $tcprops->item(0);
|
||||
if ($tcprop !== null) {
|
||||
$columnWidth = $tcprop->getAttributeNs($styleNs, 'column-width');
|
||||
$columnWidths[$styleName] = $columnWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
$item0 = $dom->getElementsByTagNameNS($officeNs, 'body')->item(0);
|
||||
@ -340,6 +359,7 @@ class Ods extends BaseReader
|
||||
|
||||
// Go through every child of table element
|
||||
$rowID = 1;
|
||||
$tableColumnIndex = 1;
|
||||
foreach ($worksheetDataSet->childNodes as $childNode) {
|
||||
/** @var DOMElement $childNode */
|
||||
|
||||
@ -366,6 +386,26 @@ class Ods extends BaseReader
|
||||
// $rowData = $cellData;
|
||||
// break;
|
||||
// }
|
||||
break;
|
||||
case 'table-column':
|
||||
if ($childNode->hasAttributeNS($tableNs, 'number-columns-repeated')) {
|
||||
$rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-columns-repeated');
|
||||
} else {
|
||||
$rowRepeats = 1;
|
||||
}
|
||||
$tableStyleName = $childNode->getAttributeNS($tableNs, 'style-name');
|
||||
if (isset($columnWidths[$tableStyleName])) {
|
||||
$columnWidth = new HelperDimension($columnWidths[$tableStyleName]);
|
||||
$tableColumnString = Coordinate::stringFromColumnIndex($tableColumnIndex);
|
||||
for ($rowRepeats2 = $rowRepeats; $rowRepeats2 > 0; --$rowRepeats2) {
|
||||
$spreadsheet->getActiveSheet()
|
||||
->getColumnDimension($tableColumnString)
|
||||
->setWidth($columnWidth->toUnit('cm'), 'cm');
|
||||
++$tableColumnString;
|
||||
}
|
||||
}
|
||||
$tableColumnIndex += $rowRepeats;
|
||||
|
||||
break;
|
||||
case 'table-row':
|
||||
if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) {
|
||||
|
@ -151,7 +151,7 @@ class XmlScanner
|
||||
throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks');
|
||||
}
|
||||
|
||||
if ($this->callback !== null && is_callable($this->callback)) {
|
||||
if ($this->callback !== null) {
|
||||
$xml = call_user_func($this->callback, $xml);
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SharedFormula;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles;
|
||||
@ -61,6 +62,11 @@ class Xlsx extends BaseReader
|
||||
/** @var Styles */
|
||||
private $styleReader;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $sharedFormulae = [];
|
||||
|
||||
/**
|
||||
* Create a new Xlsx Reader instance.
|
||||
*/
|
||||
@ -128,7 +134,7 @@ class Xlsx extends BaseReader
|
||||
if ($replaceUnclosedBr) {
|
||||
$contents = str_replace('<br>', '<br/>', $contents);
|
||||
}
|
||||
$rels = simplexml_load_string(
|
||||
$rels = @simplexml_load_string(
|
||||
$this->getSecurityScannerOrThrow()->scan($contents),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions(),
|
||||
@ -246,6 +252,7 @@ class Xlsx extends BaseReader
|
||||
$xmlWorkbook = $this->loadZip($relTarget, $mainNS);
|
||||
if ($xmlWorkbook->sheets) {
|
||||
$dir = dirname($relTarget);
|
||||
|
||||
/** @var SimpleXMLElement $eleSheet */
|
||||
foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
|
||||
$tmpInfo = [
|
||||
@ -261,8 +268,8 @@ class Xlsx extends BaseReader
|
||||
|
||||
$xml = new XMLReader();
|
||||
$xml->xml(
|
||||
$this->getSecurityScannerOrThrow()->scanFile(
|
||||
'zip://' . File::realpath($filename) . '#' . $fileWorksheetPath
|
||||
$this->getSecurityScannerOrThrow()->scan(
|
||||
$this->getFromZipArchive($this->zip, $fileWorksheetPath)
|
||||
),
|
||||
null,
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
@ -324,13 +331,13 @@ class Xlsx extends BaseReader
|
||||
* @param mixed $value
|
||||
* @param mixed $calculatedValue
|
||||
*/
|
||||
private function castToFormula(?SimpleXMLElement $c, string $r, string &$cellDataType, &$value, &$calculatedValue, array &$sharedFormulas, string $castBaseType): void
|
||||
private function castToFormula(?SimpleXMLElement $c, string $r, string &$cellDataType, &$value, &$calculatedValue, string $castBaseType, bool $updateSharedCells = true): void
|
||||
{
|
||||
if ($c === null) {
|
||||
return;
|
||||
}
|
||||
$attr = $c->f->attributes();
|
||||
$cellDataType = 'f';
|
||||
$cellDataType = DataType::TYPE_FORMULA;
|
||||
$value = "={$c->f}";
|
||||
$calculatedValue = self::$castBaseType($c);
|
||||
|
||||
@ -338,17 +345,19 @@ class Xlsx extends BaseReader
|
||||
if (isset($attr['t']) && strtolower((string) $attr['t']) == 'shared') {
|
||||
$instance = (string) $attr['si'];
|
||||
|
||||
if (!isset($sharedFormulas[(string) $attr['si']])) {
|
||||
$sharedFormulas[$instance] = ['master' => $r, 'formula' => $value];
|
||||
} else {
|
||||
$master = Coordinate::indexesFromString($sharedFormulas[$instance]['master']);
|
||||
if (!isset($this->sharedFormulae[(string) $attr['si']])) {
|
||||
$this->sharedFormulae[$instance] = new SharedFormula($r, $value);
|
||||
} elseif ($updateSharedCells === true) {
|
||||
// It's only worth the overhead of adjusting the shared formula for this cell if we're actually loading
|
||||
// the cell, which may not be the case if we're using a read filter.
|
||||
$master = Coordinate::indexesFromString($this->sharedFormulae[$instance]->master());
|
||||
$current = Coordinate::indexesFromString($r);
|
||||
|
||||
$difference = [0, 0];
|
||||
$difference[0] = $current[0] - $master[0];
|
||||
$difference[1] = $current[1] - $master[1];
|
||||
|
||||
$value = $this->referenceHelper->updateFormulaReferences($sharedFormulas[$instance]['formula'], 'A1', $difference[0], $difference[1]);
|
||||
$value = $this->referenceHelper->updateFormulaReferences($this->sharedFormulae[$instance]->formula(), 'A1', $difference[0], $difference[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -395,12 +404,18 @@ class Xlsx extends BaseReader
|
||||
// Sadly, some 3rd party xlsx generators don't use consistent case for filenaming
|
||||
// so we need to load case-insensitively from the zip file
|
||||
|
||||
// Apache POI fixes
|
||||
$contents = $archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE);
|
||||
|
||||
// Apache POI fixes
|
||||
if ($contents === false) {
|
||||
$contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE);
|
||||
}
|
||||
|
||||
// Has the file been saved with Windoze directory separators rather than unix?
|
||||
if ($contents === false) {
|
||||
$contents = $archive->getFromName(str_replace('/', '\\', $fileName), 0, ZipArchive::FL_NOCASE);
|
||||
}
|
||||
|
||||
return ($contents === false) ? '' : $contents;
|
||||
}
|
||||
|
||||
@ -447,6 +462,7 @@ class Xlsx extends BaseReader
|
||||
|
||||
$colourScheme = self::getAttributes($xmlTheme->themeElements->clrScheme);
|
||||
$colourSchemeName = (string) $colourScheme['name'];
|
||||
$excel->getTheme()->setThemeColorName($colourSchemeName);
|
||||
$colourScheme = $xmlTheme->themeElements->clrScheme->children($drawingNS);
|
||||
|
||||
$themeColours = [];
|
||||
@ -458,14 +474,46 @@ class Xlsx extends BaseReader
|
||||
if (isset($xmlColour->sysClr)) {
|
||||
$xmlColourData = self::getAttributes($xmlColour->sysClr);
|
||||
$themeColours[$themePos] = (string) $xmlColourData['lastClr'];
|
||||
$excel->getTheme()->setThemeColor($k, (string) $xmlColourData['lastClr']);
|
||||
} elseif (isset($xmlColour->srgbClr)) {
|
||||
$xmlColourData = self::getAttributes($xmlColour->srgbClr);
|
||||
$themeColours[$themePos] = (string) $xmlColourData['val'];
|
||||
$excel->getTheme()->setThemeColor($k, (string) $xmlColourData['val']);
|
||||
}
|
||||
}
|
||||
$theme = new Theme($themeName, $colourSchemeName, $themeColours);
|
||||
$this->styleReader->setTheme($theme);
|
||||
|
||||
$fontScheme = self::getAttributes($xmlTheme->themeElements->fontScheme);
|
||||
$fontSchemeName = (string) $fontScheme['name'];
|
||||
$excel->getTheme()->setThemeFontName($fontSchemeName);
|
||||
$majorFonts = [];
|
||||
$minorFonts = [];
|
||||
$fontScheme = $xmlTheme->themeElements->fontScheme->children($drawingNS);
|
||||
$majorLatin = self::getAttributes($fontScheme->majorFont->latin)['typeface'] ?? '';
|
||||
$majorEastAsian = self::getAttributes($fontScheme->majorFont->ea)['typeface'] ?? '';
|
||||
$majorComplexScript = self::getAttributes($fontScheme->majorFont->cs)['typeface'] ?? '';
|
||||
$minorLatin = self::getAttributes($fontScheme->minorFont->latin)['typeface'] ?? '';
|
||||
$minorEastAsian = self::getAttributes($fontScheme->minorFont->ea)['typeface'] ?? '';
|
||||
$minorComplexScript = self::getAttributes($fontScheme->minorFont->cs)['typeface'] ?? '';
|
||||
|
||||
foreach ($fontScheme->majorFont->font as $xmlFont) {
|
||||
$fontAttributes = self::getAttributes($xmlFont);
|
||||
$script = (string) ($fontAttributes['script'] ?? '');
|
||||
if (!empty($script)) {
|
||||
$majorFonts[$script] = (string) ($fontAttributes['typeface'] ?? '');
|
||||
}
|
||||
}
|
||||
foreach ($fontScheme->minorFont->font as $xmlFont) {
|
||||
$fontAttributes = self::getAttributes($xmlFont);
|
||||
$script = (string) ($fontAttributes['script'] ?? '');
|
||||
if (!empty($script)) {
|
||||
$minorFonts[$script] = (string) ($fontAttributes['typeface'] ?? '');
|
||||
}
|
||||
}
|
||||
$excel->getTheme()->setMajorFontValues($majorLatin, $majorEastAsian, $majorComplexScript, $majorFonts);
|
||||
$excel->getTheme()->setMinorFontValues($minorLatin, $minorEastAsian, $minorComplexScript, $minorFonts);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -477,6 +525,10 @@ class Xlsx extends BaseReader
|
||||
foreach ($rels->Relationship as $relx) {
|
||||
$rel = self::getAttributes($relx);
|
||||
$relTarget = (string) $rel['Target'];
|
||||
// issue 3553
|
||||
if ($relTarget[0] === '/') {
|
||||
$relTarget = substr($relTarget, 1);
|
||||
}
|
||||
$relType = (string) $rel['Type'];
|
||||
$mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN;
|
||||
switch ($relType) {
|
||||
@ -507,26 +559,6 @@ class Xlsx extends BaseReader
|
||||
$relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', '');
|
||||
$relsWorkbook->registerXPathNamespace('rel', Namespaces::RELATIONSHIPS);
|
||||
|
||||
$sharedStrings = [];
|
||||
$relType = "rel:Relationship[@Type='"
|
||||
//. Namespaces::SHARED_STRINGS
|
||||
. "$xmlNamespaceBase/sharedStrings"
|
||||
. "']";
|
||||
$xpath = self::getArrayItem($relsWorkbook->xpath($relType));
|
||||
|
||||
if ($xpath) {
|
||||
$xmlStrings = $this->loadZip("$dir/$xpath[Target]", $mainNS);
|
||||
if (isset($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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$worksheets = [];
|
||||
$macros = $customUI = null;
|
||||
foreach ($relsWorkbook->Relationship as $elex) {
|
||||
@ -618,7 +650,7 @@ class Xlsx extends BaseReader
|
||||
$numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
|
||||
}
|
||||
}
|
||||
$quotePrefix = (bool) ($xf['quotePrefix'] ?? false);
|
||||
$quotePrefix = (bool) (string) ($xf['quotePrefix'] ?? '');
|
||||
|
||||
$style = (object) [
|
||||
'numFmt' => $numFmt ?? NumberFormat::FORMAT_GENERAL,
|
||||
@ -653,7 +685,7 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
|
||||
$quotePrefix = (bool) ($xf['quotePrefix'] ?? false);
|
||||
$quotePrefix = (bool) (string) ($xf['quotePrefix'] ?? '');
|
||||
|
||||
$cellStyle = (object) [
|
||||
'numFmt' => $numFmt,
|
||||
@ -682,6 +714,27 @@ class Xlsx extends BaseReader
|
||||
$dxfs = $this->styleReader->dxfs($this->readDataOnly);
|
||||
$styles = $this->styleReader->styles();
|
||||
|
||||
// Read content after setting the styles
|
||||
$sharedStrings = [];
|
||||
$relType = "rel:Relationship[@Type='"
|
||||
//. Namespaces::SHARED_STRINGS
|
||||
. "$xmlNamespaceBase/sharedStrings"
|
||||
. "']";
|
||||
$xpath = self::getArrayItem($relsWorkbook->xpath($relType));
|
||||
|
||||
if ($xpath) {
|
||||
$xmlStrings = $this->loadZip("$dir/$xpath[Target]", $mainNS);
|
||||
if (isset($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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$xmlWorkbook = $this->loadZipNoNamespace($relTarget, $mainNS);
|
||||
$xmlWorkbookNS = $this->loadZip($relTarget, $mainNS);
|
||||
|
||||
@ -743,7 +796,8 @@ class Xlsx extends BaseReader
|
||||
$xmlSheet = $this->loadZipNoNamespace("$dir/$fileWorksheet", $mainNS);
|
||||
$xmlSheetNS = $this->loadZip("$dir/$fileWorksheet", $mainNS);
|
||||
|
||||
$sharedFormulas = [];
|
||||
// Shared Formula table is unique to each Worksheet, so we need to reset it here
|
||||
$this->sharedFormulae = [];
|
||||
|
||||
if (isset($eleSheetAttr['state']) && (string) $eleSheetAttr['state'] != '') {
|
||||
$docSheet->setSheetState((string) $eleSheetAttr['state']);
|
||||
@ -789,8 +843,12 @@ class Xlsx extends BaseReader
|
||||
$coordinates = Coordinate::coordinateFromString($r);
|
||||
|
||||
if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) {
|
||||
if (isset($cAttr->f)) {
|
||||
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
|
||||
// Normally, just testing for the f attribute should identify this cell as containing a formula
|
||||
// that we need to read, even though it is outside of the filter range, in case it is a shared formula.
|
||||
// But in some cases, this attribute isn't set; so we need to delve a level deeper and look at
|
||||
// whether or not the cell has a child formula element that is shared.
|
||||
if (isset($cAttr->f) || (isset($c->f, $c->f->attributes()['t']) && strtolower((string) $c->f->attributes()['t']) === 'shared')) {
|
||||
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError', false);
|
||||
}
|
||||
++$rowIndex;
|
||||
|
||||
@ -822,7 +880,7 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
} else {
|
||||
// Formula
|
||||
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToBoolean');
|
||||
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToBoolean');
|
||||
if (isset($c->f['t'])) {
|
||||
$att = $c->f;
|
||||
$docSheet->getCell($r)->setFormulaAttributes($att);
|
||||
@ -832,7 +890,7 @@ class Xlsx extends BaseReader
|
||||
break;
|
||||
case 'inlineStr':
|
||||
if (isset($c->f)) {
|
||||
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
|
||||
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError');
|
||||
} else {
|
||||
$value = $this->parseRichText($c->is);
|
||||
}
|
||||
@ -843,7 +901,7 @@ class Xlsx extends BaseReader
|
||||
$value = self::castToError($c);
|
||||
} else {
|
||||
// Formula
|
||||
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
|
||||
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError');
|
||||
}
|
||||
|
||||
break;
|
||||
@ -852,7 +910,7 @@ class Xlsx extends BaseReader
|
||||
$value = self::castToString($c);
|
||||
} else {
|
||||
// Formula
|
||||
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToString');
|
||||
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToString');
|
||||
if (isset($c->f['t'])) {
|
||||
$attributes = $c->f['t'];
|
||||
$docSheet->getCell($r)->setFormulaAttributes(['t' => (string) $attributes]);
|
||||
@ -891,6 +949,10 @@ class Xlsx extends BaseReader
|
||||
// no style index means 0, it seems
|
||||
$cell->setXfIndex(isset($styles[(int) ($cAttr['s'])]) ?
|
||||
(int) ($cAttr['s']) : 0);
|
||||
// issue 3495
|
||||
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
|
||||
$cell->getStyle()->setQuotePrefix(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
++$rowIndex;
|
||||
@ -898,6 +960,12 @@ class Xlsx extends BaseReader
|
||||
++$cIndex;
|
||||
}
|
||||
}
|
||||
if ($xmlSheetNS && $xmlSheetNS->ignoredErrors) {
|
||||
foreach ($xmlSheetNS->ignoredErrors->ignoredError as $ignoredErrorx) {
|
||||
$ignoredError = self::testSimpleXml($ignoredErrorx);
|
||||
$this->processIgnoredErrors($ignoredError, $docSheet);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->readDataOnly && $xmlSheetNS && $xmlSheetNS->sheetProtection) {
|
||||
$protAttr = $xmlSheetNS->sheetProtection->attributes() ?? [];
|
||||
@ -2205,4 +2273,48 @@ class Xlsx extends BaseReader
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function processIgnoredErrors(SimpleXMLElement $xml, Worksheet $sheet): void
|
||||
{
|
||||
$attributes = self::getAttributes($xml);
|
||||
$sqref = (string) ($attributes['sqref'] ?? '');
|
||||
$numberStoredAsText = (string) ($attributes['numberStoredAsText'] ?? '');
|
||||
$formula = (string) ($attributes['formula'] ?? '');
|
||||
$twoDigitTextYear = (string) ($attributes['twoDigitTextYear'] ?? '');
|
||||
$evalError = (string) ($attributes['evalError'] ?? '');
|
||||
if (!empty($sqref)) {
|
||||
$explodedSqref = explode(' ', $sqref);
|
||||
$pattern1 = '/^([A-Z]{1,3})([0-9]{1,7})(:([A-Z]{1,3})([0-9]{1,7}))?$/';
|
||||
foreach ($explodedSqref as $sqref1) {
|
||||
if (preg_match($pattern1, $sqref1, $matches) === 1) {
|
||||
$firstRow = $matches[2];
|
||||
$firstCol = $matches[1];
|
||||
if (array_key_exists(3, $matches)) {
|
||||
$lastCol = $matches[4];
|
||||
$lastRow = $matches[5];
|
||||
} else {
|
||||
$lastCol = $firstCol;
|
||||
$lastRow = $firstRow;
|
||||
}
|
||||
++$lastCol;
|
||||
for ($row = $firstRow; $row <= $lastRow; ++$row) {
|
||||
for ($col = $firstCol; $col !== $lastCol; ++$col) {
|
||||
if ($numberStoredAsText === '1') {
|
||||
$sheet->getCell("$col$row")->getIgnoredErrors()->setNumberStoredAsText(true);
|
||||
}
|
||||
if ($formula === '1') {
|
||||
$sheet->getCell("$col$row")->getIgnoredErrors()->setFormula(true);
|
||||
}
|
||||
if ($twoDigitTextYear === '1') {
|
||||
$sheet->getCell("$col$row")->getIgnoredErrors()->setTwoDigitTextYear(true);
|
||||
}
|
||||
if ($evalError === '1') {
|
||||
$sheet->getCell("$col$row")->getIgnoredErrors()->setEvalError(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Axis;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\AxisText;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
@ -76,16 +77,28 @@ class Chart
|
||||
$yAxis = new Axis();
|
||||
$autoTitleDeleted = null;
|
||||
$chartNoFill = false;
|
||||
$chartBorderLines = null;
|
||||
$chartFillColor = null;
|
||||
$gradientArray = [];
|
||||
$gradientLin = null;
|
||||
$roundedCorners = false;
|
||||
$gapWidth = null;
|
||||
$useUpBars = null;
|
||||
$useDownBars = null;
|
||||
foreach ($chartElementsC as $chartElementKey => $chartElement) {
|
||||
switch ($chartElementKey) {
|
||||
case 'spPr':
|
||||
$possibleNoFill = $chartElementsC->spPr->children($this->aNamespace);
|
||||
if (isset($possibleNoFill->noFill)) {
|
||||
$children = $chartElementsC->spPr->children($this->aNamespace);
|
||||
if (isset($children->noFill)) {
|
||||
$chartNoFill = true;
|
||||
}
|
||||
if (isset($children->solidFill)) {
|
||||
$chartFillColor = $this->readColor($children->solidFill);
|
||||
}
|
||||
if (isset($children->ln)) {
|
||||
$chartBorderLines = new GridLines();
|
||||
$this->readLineStyle($chartElementsC, $chartBorderLines);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'roundedCorners':
|
||||
@ -157,6 +170,9 @@ class Chart
|
||||
$axisColorArray = $this->readColor($sppr->solidFill);
|
||||
$xAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
|
||||
}
|
||||
if (isset($chartDetail->spPr->ln->noFill)) {
|
||||
$xAxis->setNoFill(true);
|
||||
}
|
||||
}
|
||||
if (isset($chartDetail->majorGridlines)) {
|
||||
$majorGridlines = new GridLines();
|
||||
@ -227,6 +243,9 @@ class Chart
|
||||
$axisColorArray = $this->readColor($sppr->solidFill);
|
||||
$whichAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
|
||||
}
|
||||
if (isset($sppr->ln->noFill)) {
|
||||
$whichAxis->setNoFill(true);
|
||||
}
|
||||
}
|
||||
if ($whichAxis !== null && isset($chartDetail->majorGridlines)) {
|
||||
$majorGridlines = new GridLines();
|
||||
@ -316,6 +335,15 @@ class Chart
|
||||
break;
|
||||
case 'stockChart':
|
||||
$plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
|
||||
if (isset($chartDetail->upDownBars->gapWidth)) {
|
||||
$gapWidth = self::getAttribute($chartDetail->upDownBars->gapWidth, 'val', 'integer');
|
||||
}
|
||||
if (isset($chartDetail->upDownBars->upBars)) {
|
||||
$useUpBars = true;
|
||||
}
|
||||
if (isset($chartDetail->upDownBars->downBars)) {
|
||||
$useDownBars = true;
|
||||
}
|
||||
$plotAttributes = $this->readChartAttributes($chartDetail);
|
||||
|
||||
break;
|
||||
@ -332,6 +360,15 @@ class Chart
|
||||
if (!empty($gradientArray)) {
|
||||
$plotArea->setGradientFillProperties($gradientArray, $gradientLin);
|
||||
}
|
||||
if (is_int($gapWidth)) {
|
||||
$plotArea->setGapWidth($gapWidth);
|
||||
}
|
||||
if ($useUpBars === true) {
|
||||
$plotArea->setUseUpBars(true);
|
||||
}
|
||||
if ($useDownBars === true) {
|
||||
$plotArea->setUseDownBars(true);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'plotVisOnly':
|
||||
@ -350,6 +387,10 @@ class Chart
|
||||
$legendPos = 'r';
|
||||
$legendLayout = null;
|
||||
$legendOverlay = false;
|
||||
$legendBorderLines = null;
|
||||
$legendFillColor = null;
|
||||
$legendText = null;
|
||||
$addLegendText = false;
|
||||
foreach ($chartDetails as $chartDetailKey => $chartDetail) {
|
||||
$chartDetail = Xlsx::testSimpleXml($chartDetail);
|
||||
switch ($chartDetailKey) {
|
||||
@ -364,10 +405,45 @@ class Chart
|
||||
case 'layout':
|
||||
$legendLayout = $this->chartLayoutDetails($chartDetail);
|
||||
|
||||
break;
|
||||
case 'spPr':
|
||||
$children = $chartDetails->spPr->children($this->aNamespace);
|
||||
if (isset($children->solidFill)) {
|
||||
$legendFillColor = $this->readColor($children->solidFill);
|
||||
}
|
||||
if (isset($children->ln)) {
|
||||
$legendBorderLines = new GridLines();
|
||||
$this->readLineStyle($chartDetails, $legendBorderLines);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'txPr':
|
||||
$children = $chartDetails->txPr->children($this->aNamespace);
|
||||
$addLegendText = false;
|
||||
$legendText = new AxisText();
|
||||
if (isset($children->p->pPr->defRPr->solidFill)) {
|
||||
$colorArray = $this->readColor($children->p->pPr->defRPr->solidFill);
|
||||
$legendText->getFillColorObject()->setColorPropertiesArray($colorArray);
|
||||
$addLegendText = true;
|
||||
}
|
||||
if (isset($children->p->pPr->defRPr->effectLst)) {
|
||||
$this->readEffects($children->p->pPr->defRPr, $legendText, false);
|
||||
$addLegendText = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
$legend = new Legend("$legendPos", $legendLayout, (bool) $legendOverlay);
|
||||
if ($legendFillColor !== null) {
|
||||
$legend->getFillColor()->setColorPropertiesArray($legendFillColor);
|
||||
}
|
||||
if ($legendBorderLines !== null) {
|
||||
$legend->setBorderLines($legendBorderLines);
|
||||
}
|
||||
if ($addLegendText) {
|
||||
$legend->setLegendText($legendText);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@ -378,6 +454,12 @@ class Chart
|
||||
if ($chartNoFill) {
|
||||
$chart->setNoFill(true);
|
||||
}
|
||||
if ($chartFillColor !== null) {
|
||||
$chart->getFillColor()->setColorPropertiesArray($chartFillColor);
|
||||
}
|
||||
if ($chartBorderLines !== null) {
|
||||
$chart->setBorderLines($chartBorderLines);
|
||||
}
|
||||
$chart->setRoundedCorners($roundedCorners);
|
||||
if (is_bool($autoTitleDeleted)) {
|
||||
$chart->setAutoTitleDeleted($autoTitleDeleted);
|
||||
@ -1082,6 +1164,37 @@ class Chart
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function parseFont(SimpleXMLElement $titleDetailPart): ?Font
|
||||
{
|
||||
if (!isset($titleDetailPart->pPr->defRPr)) {
|
||||
return null;
|
||||
}
|
||||
$fontArray = [];
|
||||
$fontArray['size'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer');
|
||||
$fontArray['bold'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'b', 'boolean');
|
||||
$fontArray['italic'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'i', 'boolean');
|
||||
$fontArray['underscore'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'u', 'string');
|
||||
$fontArray['strikethrough'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string');
|
||||
|
||||
if (isset($titleDetailPart->pPr->defRPr->latin)) {
|
||||
$fontArray['latin'] = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string');
|
||||
}
|
||||
if (isset($titleDetailPart->pPr->defRPr->ea)) {
|
||||
$fontArray['eastAsian'] = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string');
|
||||
}
|
||||
if (isset($titleDetailPart->pPr->defRPr->cs)) {
|
||||
$fontArray['complexScript'] = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string');
|
||||
}
|
||||
if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
|
||||
$fontArray['chartColor'] = new ChartColor($this->readColor($titleDetailPart->pPr->defRPr->solidFill));
|
||||
}
|
||||
$font = new Font();
|
||||
$font->setSize(null, true);
|
||||
$font->applyFromArray($fontArray);
|
||||
|
||||
return $font;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?SimpleXMLElement $chartDetail
|
||||
*/
|
||||
@ -1128,8 +1241,13 @@ class Chart
|
||||
}
|
||||
if (isset($chartDetail->dLbls->txPr)) {
|
||||
$txpr = $chartDetail->dLbls->txPr->children($this->aNamespace);
|
||||
if (isset($txpr->p->pPr->defRPr->solidFill)) {
|
||||
$plotAttributes['labelFontColor'] = new ChartColor($this->readColor($txpr->p->pPr->defRPr->solidFill));
|
||||
if (isset($txpr->p)) {
|
||||
$plotAttributes['labelFont'] = $this->parseFont($txpr->p);
|
||||
if (isset($txpr->p->pPr->defRPr->effectLst)) {
|
||||
$labelEffects = new GridLines();
|
||||
$this->readEffects($txpr->p->pPr->defRPr, $labelEffects, false);
|
||||
$plotAttributes['labelEffects'] = $labelEffects;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1176,13 +1294,19 @@ class Chart
|
||||
}
|
||||
}
|
||||
|
||||
private function readEffects(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void
|
||||
private function readEffects(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject, bool $getSppr = true): void
|
||||
{
|
||||
if (!isset($chartObject, $chartDetail->spPr)) {
|
||||
if (!isset($chartObject)) {
|
||||
return;
|
||||
}
|
||||
if ($getSppr) {
|
||||
if (!isset($chartDetail->spPr)) {
|
||||
return;
|
||||
}
|
||||
$sppr = $chartDetail->spPr->children($this->aNamespace);
|
||||
|
||||
} else {
|
||||
$sppr = $chartDetail;
|
||||
}
|
||||
if (isset($sppr->effectLst->glow)) {
|
||||
$axisGlowSize = (float) self::getAttribute($sppr->effectLst->glow, 'rad', 'integer') / ChartProperties::POINTS_WIDTH_MULTIPLIER;
|
||||
if ($axisGlowSize != 0.0) {
|
||||
@ -1412,12 +1536,29 @@ class Chart
|
||||
}
|
||||
if (isset($chartDetail->txPr)) {
|
||||
$children = $chartDetail->txPr->children($this->aNamespace);
|
||||
$addAxisText = false;
|
||||
$axisText = new AxisText();
|
||||
if (isset($children->bodyPr)) {
|
||||
/** @var string */
|
||||
$textRotation = self::getAttribute($children->bodyPr, 'rot', 'string');
|
||||
if (is_numeric($textRotation)) {
|
||||
$whichAxis->setAxisOption('textRotation', (string) ChartProperties::xmlToAngle($textRotation));
|
||||
}
|
||||
$axisText->setRotation((int) ChartProperties::xmlToAngle($textRotation));
|
||||
$addAxisText = true;
|
||||
}
|
||||
}
|
||||
if (isset($children->p->pPr->defRPr)) {
|
||||
$font = $this->parseFont($children->p);
|
||||
if ($font !== null) {
|
||||
$axisText->setFont($font);
|
||||
$addAxisText = true;
|
||||
}
|
||||
}
|
||||
if (isset($children->p->pPr->defRPr->effectLst)) {
|
||||
$this->readEffects($children->p->pPr->defRPr, $axisText, false);
|
||||
$addAxisText = true;
|
||||
}
|
||||
if ($addAxisText) {
|
||||
$whichAxis->setAxisText($axisText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,18 @@ class DataValidations
|
||||
|
||||
public function load(): void
|
||||
{
|
||||
foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) {
|
||||
// Uppercase coordinate
|
||||
$range = strtoupper((string) $dataValidation['sqref']);
|
||||
$rangeSet = explode(' ', $range);
|
||||
foreach ($rangeSet as $range) {
|
||||
if (preg_match('/^[A-Z]{1,3}\\d{1,7}/', $range, $matches) === 1) {
|
||||
// Ensure left/top row of range exists, thereby
|
||||
// adjusting high row/column.
|
||||
$this->worksheet->getCell($matches[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) {
|
||||
// Uppercase coordinate
|
||||
$range = strtoupper((string) $dataValidation['sqref']);
|
||||
|
@ -73,6 +73,9 @@ class Properties
|
||||
if (isset($xmlCore->Manager)) {
|
||||
$this->docProps->setManager((string) $xmlCore->Manager);
|
||||
}
|
||||
if (isset($xmlCore->HyperlinkBase)) {
|
||||
$this->docProps->setHyperlinkBase((string) $xmlCore->HyperlinkBase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
class SharedFormula
|
||||
{
|
||||
private string $master;
|
||||
|
||||
private string $formula;
|
||||
|
||||
public function __construct(string $master, string $formula)
|
||||
{
|
||||
$this->master = $master;
|
||||
$this->formula = $formula;
|
||||
}
|
||||
|
||||
public function master(): string
|
||||
{
|
||||
return $this->master;
|
||||
}
|
||||
|
||||
public function formula(): string
|
||||
{
|
||||
return $this->formula;
|
||||
}
|
||||
}
|
@ -136,6 +136,10 @@ class Styles extends BaseParserClass
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($fontStyleXml->scheme)) {
|
||||
$attr = $this->getStyleAttributes($fontStyleXml->scheme);
|
||||
$fontStyle->setScheme((string) $attr['val']);
|
||||
}
|
||||
}
|
||||
|
||||
private function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void
|
||||
@ -253,10 +257,14 @@ class Styles extends BaseParserClass
|
||||
|
||||
public function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void
|
||||
{
|
||||
$horizontal = $this->getAttribute($alignmentXml, 'horizontal');
|
||||
$horizontal = (string) $this->getAttribute($alignmentXml, 'horizontal');
|
||||
if ($horizontal !== '') {
|
||||
$alignment->setHorizontal($horizontal);
|
||||
$vertical = $this->getAttribute($alignmentXml, 'vertical');
|
||||
$alignment->setVertical((string) $vertical);
|
||||
}
|
||||
$vertical = (string) $this->getAttribute($alignmentXml, 'vertical');
|
||||
if ($vertical !== '') {
|
||||
$alignment->setVertical($vertical);
|
||||
}
|
||||
|
||||
$textRotation = (int) $this->getAttribute($alignmentXml, 'textRotation');
|
||||
if ($textRotation > 90) {
|
||||
|
@ -9,6 +9,7 @@ use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\DefinedName;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xml\PageSettings;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xml\Properties;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
|
||||
@ -26,6 +27,8 @@ use SimpleXMLElement;
|
||||
*/
|
||||
class Xml extends BaseReader
|
||||
{
|
||||
public const NAMESPACES_SS = 'urn:schemas-microsoft-com:office:spreadsheet';
|
||||
|
||||
/**
|
||||
* Formats.
|
||||
*
|
||||
@ -146,11 +149,9 @@ class Xml extends BaseReader
|
||||
throw new Exception("Problem reading {$filename}");
|
||||
}
|
||||
|
||||
$namespaces = $xml->getNamespaces(true);
|
||||
|
||||
$xml_ss = $xml->children($namespaces['ss']);
|
||||
$xml_ss = $xml->children(self::NAMESPACES_SS);
|
||||
foreach ($xml_ss->Worksheet as $worksheet) {
|
||||
$worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']);
|
||||
$worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS);
|
||||
$worksheetNames[] = (string) $worksheet_ss['Name'];
|
||||
}
|
||||
|
||||
@ -178,12 +179,10 @@ class Xml extends BaseReader
|
||||
throw new Exception("Problem reading {$filename}");
|
||||
}
|
||||
|
||||
$namespaces = $xml->getNamespaces(true);
|
||||
|
||||
$worksheetID = 1;
|
||||
$xml_ss = $xml->children($namespaces['ss']);
|
||||
$xml_ss = $xml->children(self::NAMESPACES_SS);
|
||||
foreach ($xml_ss->Worksheet as $worksheet) {
|
||||
$worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']);
|
||||
$worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS);
|
||||
|
||||
$tmpInfo = [];
|
||||
$tmpInfo['worksheetName'] = '';
|
||||
@ -231,6 +230,19 @@ class Xml extends BaseReader
|
||||
return $worksheetInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads Spreadsheet from string.
|
||||
*/
|
||||
public function loadSpreadsheetFromString(string $contents): Spreadsheet
|
||||
{
|
||||
// Create new Spreadsheet
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$spreadsheet->removeSheetByIndex(0);
|
||||
|
||||
// Load into this instance
|
||||
return $this->loadIntoExisting($contents, $spreadsheet, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads Spreadsheet from file.
|
||||
*/
|
||||
@ -245,18 +257,20 @@ class Xml extends BaseReader
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads from file into Spreadsheet instance.
|
||||
* Loads from file or contents into Spreadsheet instance.
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return Spreadsheet
|
||||
* @param string $filename file name if useContents is false else file contents
|
||||
*/
|
||||
public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
|
||||
public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, bool $useContents = false): Spreadsheet
|
||||
{
|
||||
if ($useContents) {
|
||||
$this->fileContents = $filename;
|
||||
} else {
|
||||
File::assertFile($filename);
|
||||
if (!$this->canRead($filename)) {
|
||||
throw new Exception($filename . ' is an Invalid Spreadsheet file.');
|
||||
}
|
||||
}
|
||||
|
||||
$xml = $this->trySimpleXMLLoadString($filename);
|
||||
if ($xml === false) {
|
||||
@ -268,14 +282,17 @@ class Xml extends BaseReader
|
||||
(new Properties($spreadsheet))->readProperties($xml, $namespaces);
|
||||
|
||||
$this->styles = (new Style())->parseStyles($xml, $namespaces);
|
||||
if (isset($this->styles['Default'])) {
|
||||
$spreadsheet->getCellXfCollection()[0]->applyFromArray($this->styles['Default']);
|
||||
}
|
||||
|
||||
$worksheetID = 0;
|
||||
$xml_ss = $xml->children($namespaces['ss']);
|
||||
$xml_ss = $xml->children(self::NAMESPACES_SS);
|
||||
|
||||
/** @var null|SimpleXMLElement $worksheetx */
|
||||
foreach ($xml_ss->Worksheet as $worksheetx) {
|
||||
$worksheet = $worksheetx ?? new SimpleXMLElement('<xml></xml>');
|
||||
$worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']);
|
||||
$worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS);
|
||||
|
||||
if (
|
||||
isset($this->loadSheetsOnly, $worksheet_ss['Name']) &&
|
||||
@ -295,11 +312,15 @@ class Xml extends BaseReader
|
||||
// the worksheet name in line with the formula, not the reverse
|
||||
$spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
|
||||
}
|
||||
if (isset($worksheet_ss['Protected'])) {
|
||||
$protection = (string) $worksheet_ss['Protected'] === '1';
|
||||
$spreadsheet->getActiveSheet()->getProtection()->setSheet($protection);
|
||||
}
|
||||
|
||||
// locally scoped defined names
|
||||
if (isset($worksheet->Names[0])) {
|
||||
foreach ($worksheet->Names[0] as $definedName) {
|
||||
$definedName_ss = self::getAttributes($definedName, $namespaces['ss']);
|
||||
$definedName_ss = self::getAttributes($definedName, self::NAMESPACES_SS);
|
||||
$name = (string) $definedName_ss['Name'];
|
||||
$definedValue = (string) $definedName_ss['RefersTo'];
|
||||
$convertedValue = AddressHelper::convertFormulaToA1($definedValue);
|
||||
@ -313,15 +334,35 @@ class Xml extends BaseReader
|
||||
$columnID = 'A';
|
||||
if (isset($worksheet->Table->Column)) {
|
||||
foreach ($worksheet->Table->Column as $columnData) {
|
||||
$columnData_ss = self::getAttributes($columnData, $namespaces['ss']);
|
||||
$columnData_ss = self::getAttributes($columnData, self::NAMESPACES_SS);
|
||||
$colspan = 0;
|
||||
if (isset($columnData_ss['Span'])) {
|
||||
$spanAttr = (string) $columnData_ss['Span'];
|
||||
if (is_numeric($spanAttr)) {
|
||||
$colspan = max(0, (int) $spanAttr);
|
||||
}
|
||||
}
|
||||
if (isset($columnData_ss['Index'])) {
|
||||
$columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']);
|
||||
}
|
||||
$columnWidth = null;
|
||||
if (isset($columnData_ss['Width'])) {
|
||||
$columnWidth = $columnData_ss['Width'];
|
||||
}
|
||||
$columnVisible = null;
|
||||
if (isset($columnData_ss['Hidden'])) {
|
||||
$columnVisible = ((string) $columnData_ss['Hidden']) !== '1';
|
||||
}
|
||||
while ($colspan >= 0) {
|
||||
if (isset($columnWidth)) {
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
|
||||
}
|
||||
if (isset($columnVisible)) {
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setVisible($columnVisible);
|
||||
}
|
||||
++$columnID;
|
||||
--$colspan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,14 +371,18 @@ class Xml extends BaseReader
|
||||
$additionalMergedCells = 0;
|
||||
foreach ($worksheet->Table->Row as $rowData) {
|
||||
$rowHasData = false;
|
||||
$row_ss = self::getAttributes($rowData, $namespaces['ss']);
|
||||
$row_ss = self::getAttributes($rowData, self::NAMESPACES_SS);
|
||||
if (isset($row_ss['Index'])) {
|
||||
$rowID = (int) $row_ss['Index'];
|
||||
}
|
||||
if (isset($row_ss['Hidden'])) {
|
||||
$rowVisible = ((string) $row_ss['Hidden']) !== '1';
|
||||
$spreadsheet->getActiveSheet()->getRowDimension($rowID)->setVisible($rowVisible);
|
||||
}
|
||||
|
||||
$columnID = 'A';
|
||||
foreach ($rowData->Cell as $cell) {
|
||||
$cell_ss = self::getAttributes($cell, $namespaces['ss']);
|
||||
$cell_ss = self::getAttributes($cell, self::NAMESPACES_SS);
|
||||
if (isset($cell_ss['Index'])) {
|
||||
$columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']);
|
||||
}
|
||||
@ -379,7 +424,7 @@ class Xml extends BaseReader
|
||||
$cellData = $cell->Data;
|
||||
$cellValue = (string) $cellData;
|
||||
$type = DataType::TYPE_NULL;
|
||||
$cellData_ss = self::getAttributes($cellData, $namespaces['ss']);
|
||||
$cellData_ss = self::getAttributes($cellData, self::NAMESPACES_SS);
|
||||
if (isset($cellData_ss['Type'])) {
|
||||
$cellDataType = $cellData_ss['Type'];
|
||||
switch ($cellDataType) {
|
||||
@ -437,7 +482,7 @@ class Xml extends BaseReader
|
||||
}
|
||||
|
||||
if (isset($cell->Comment)) {
|
||||
$this->parseCellComment($cell->Comment, $namespaces, $spreadsheet, $columnID, $rowID);
|
||||
$this->parseCellComment($cell->Comment, $spreadsheet, $columnID, $rowID);
|
||||
}
|
||||
|
||||
if (isset($cell_ss['StyleID'])) {
|
||||
@ -466,11 +511,57 @@ class Xml extends BaseReader
|
||||
|
||||
++$rowID;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($namespaces['x'])) {
|
||||
$xmlX = $worksheet->children($namespaces['x']);
|
||||
$dataValidations = new Xml\DataValidations();
|
||||
$dataValidations->loadDataValidations($worksheet, $spreadsheet);
|
||||
$xmlX = $worksheet->children(Namespaces::URN_EXCEL);
|
||||
if (isset($xmlX->WorksheetOptions)) {
|
||||
(new PageSettings($xmlX, $namespaces))->loadPageSettings($spreadsheet);
|
||||
if (isset($xmlX->WorksheetOptions->FreezePanes)) {
|
||||
$freezeRow = $freezeColumn = 1;
|
||||
if (isset($xmlX->WorksheetOptions->SplitHorizontal)) {
|
||||
$freezeRow = (int) $xmlX->WorksheetOptions->SplitHorizontal + 1;
|
||||
}
|
||||
if (isset($xmlX->WorksheetOptions->SplitVertical)) {
|
||||
$freezeColumn = (int) $xmlX->WorksheetOptions->SplitVertical + 1;
|
||||
}
|
||||
$spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow);
|
||||
}
|
||||
(new PageSettings($xmlX))->loadPageSettings($spreadsheet);
|
||||
if (isset($xmlX->WorksheetOptions->TopRowVisible, $xmlX->WorksheetOptions->LeftColumnVisible)) {
|
||||
$leftTopRow = (string) $xmlX->WorksheetOptions->TopRowVisible;
|
||||
$leftTopColumn = (string) $xmlX->WorksheetOptions->LeftColumnVisible;
|
||||
if (is_numeric($leftTopRow) && is_numeric($leftTopColumn)) {
|
||||
$leftTopCoordinate = Coordinate::stringFromColumnIndex((int) $leftTopColumn + 1) . (string) ($leftTopRow + 1);
|
||||
$spreadsheet->getActiveSheet()->setTopLeftCell($leftTopCoordinate);
|
||||
}
|
||||
}
|
||||
$rangeCalculated = false;
|
||||
if (isset($xmlX->WorksheetOptions->Panes->Pane->RangeSelection)) {
|
||||
if (1 === preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $xmlX->WorksheetOptions->Panes->Pane->RangeSelection, $selectionMatches)) {
|
||||
$selectedCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
|
||||
. $selectionMatches[1]
|
||||
. ':'
|
||||
. Coordinate::stringFromColumnIndex((int) $selectionMatches[4])
|
||||
. $selectionMatches[3];
|
||||
$spreadsheet->getActiveSheet()->setSelectedCells($selectedCell);
|
||||
$rangeCalculated = true;
|
||||
}
|
||||
}
|
||||
if (!$rangeCalculated) {
|
||||
if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveRow)) {
|
||||
$activeRow = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveRow;
|
||||
} else {
|
||||
$activeRow = 0;
|
||||
}
|
||||
if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveCol)) {
|
||||
$activeColumn = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveCol;
|
||||
} else {
|
||||
$activeColumn = 0;
|
||||
}
|
||||
if (is_numeric($activeRow) && is_numeric($activeColumn)) {
|
||||
$selectedCell = Coordinate::stringFromColumnIndex((int) $activeColumn + 1) . (string) ($activeRow + 1);
|
||||
$spreadsheet->getActiveSheet()->setSelectedCells($selectedCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -478,10 +569,14 @@ class Xml extends BaseReader
|
||||
}
|
||||
|
||||
// Globally scoped defined names
|
||||
$activeWorksheet = $spreadsheet->setActiveSheetIndex(0);
|
||||
$activeSheetIndex = 0;
|
||||
if (isset($xml->ExcelWorkbook->ActiveSheet)) {
|
||||
$activeSheetIndex = (int) (string) $xml->ExcelWorkbook->ActiveSheet;
|
||||
}
|
||||
$activeWorksheet = $spreadsheet->setActiveSheetIndex($activeSheetIndex);
|
||||
if (isset($xml->Names[0])) {
|
||||
foreach ($xml->Names[0] as $definedName) {
|
||||
$definedName_ss = self::getAttributes($definedName, $namespaces['ss']);
|
||||
$definedName_ss = self::getAttributes($definedName, self::NAMESPACES_SS);
|
||||
$name = (string) $definedName_ss['Name'];
|
||||
$definedValue = (string) $definedName_ss['RefersTo'];
|
||||
$convertedValue = AddressHelper::convertFormulaToA1($definedValue);
|
||||
@ -498,12 +593,11 @@ class Xml extends BaseReader
|
||||
|
||||
protected function parseCellComment(
|
||||
SimpleXMLElement $comment,
|
||||
array $namespaces,
|
||||
Spreadsheet $spreadsheet,
|
||||
string $columnID,
|
||||
int $rowID
|
||||
): void {
|
||||
$commentAttributes = $comment->attributes($namespaces['ss']);
|
||||
$commentAttributes = $comment->attributes(self::NAMESPACES_SS);
|
||||
$author = 'unknown';
|
||||
if (isset($commentAttributes->Author)) {
|
||||
$author = (string) $commentAttributes->Author;
|
||||
|
177
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php
vendored
Normal file
177
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class DataValidations
|
||||
{
|
||||
private const OPERATOR_MAPPINGS = [
|
||||
'between' => DataValidation::OPERATOR_BETWEEN,
|
||||
'equal' => DataValidation::OPERATOR_EQUAL,
|
||||
'greater' => DataValidation::OPERATOR_GREATERTHAN,
|
||||
'greaterorequal' => DataValidation::OPERATOR_GREATERTHANOREQUAL,
|
||||
'less' => DataValidation::OPERATOR_LESSTHAN,
|
||||
'lessorequal' => DataValidation::OPERATOR_LESSTHANOREQUAL,
|
||||
'notbetween' => DataValidation::OPERATOR_NOTBETWEEN,
|
||||
'notequal' => DataValidation::OPERATOR_NOTEQUAL,
|
||||
];
|
||||
|
||||
private const TYPE_MAPPINGS = [
|
||||
'textlength' => DataValidation::TYPE_TEXTLENGTH,
|
||||
];
|
||||
|
||||
private int $thisRow = 0;
|
||||
|
||||
private int $thisColumn = 0;
|
||||
|
||||
private function replaceR1C1(array $matches): string
|
||||
{
|
||||
return AddressHelper::convertToA1($matches[0], $this->thisRow, $this->thisColumn, false);
|
||||
}
|
||||
|
||||
public function loadDataValidations(SimpleXMLElement $worksheet, Spreadsheet $spreadsheet): void
|
||||
{
|
||||
$xmlX = $worksheet->children(Namespaces::URN_EXCEL);
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
/** @var callable */
|
||||
$pregCallback = [$this, 'replaceR1C1'];
|
||||
foreach ($xmlX->DataValidation as $dataValidation) {
|
||||
$cells = [];
|
||||
$validation = new DataValidation();
|
||||
|
||||
// set defaults
|
||||
$validation->setShowDropDown(true);
|
||||
$validation->setShowInputMessage(true);
|
||||
$validation->setShowErrorMessage(true);
|
||||
$validation->setShowDropDown(true);
|
||||
$this->thisRow = 1;
|
||||
$this->thisColumn = 1;
|
||||
|
||||
foreach ($dataValidation as $tagName => $tagValue) {
|
||||
$tagValue = (string) $tagValue;
|
||||
$tagValueLower = strtolower($tagValue);
|
||||
switch ($tagName) {
|
||||
case 'Range':
|
||||
foreach (explode(',', $tagValue) as $range) {
|
||||
$cell = '';
|
||||
if (preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $range, $selectionMatches) === 1) {
|
||||
// range
|
||||
$firstCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
|
||||
. $selectionMatches[1];
|
||||
$cell = $firstCell
|
||||
. ':'
|
||||
. Coordinate::stringFromColumnIndex((int) $selectionMatches[4])
|
||||
. $selectionMatches[3];
|
||||
$this->thisRow = (int) $selectionMatches[1];
|
||||
$this->thisColumn = (int) $selectionMatches[2];
|
||||
$sheet->getCell($firstCell);
|
||||
} elseif (preg_match('/^R(\d+)C(\d+)$/', (string) $range, $selectionMatches) === 1) {
|
||||
// cell
|
||||
$cell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
|
||||
. $selectionMatches[1];
|
||||
$sheet->getCell($cell);
|
||||
$this->thisRow = (int) $selectionMatches[1];
|
||||
$this->thisColumn = (int) $selectionMatches[2];
|
||||
} elseif (preg_match('/^C(\d+)$/', (string) $range, $selectionMatches) === 1) {
|
||||
// column
|
||||
$firstCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[1])
|
||||
. '1';
|
||||
$cell = $firstCell
|
||||
. ':'
|
||||
. Coordinate::stringFromColumnIndex((int) $selectionMatches[1])
|
||||
. ((string) AddressRange::MAX_ROW);
|
||||
$this->thisColumn = (int) $selectionMatches[1];
|
||||
$sheet->getCell($firstCell);
|
||||
} elseif (preg_match('/^R(\d+)$/', (string) $range, $selectionMatches)) {
|
||||
// row
|
||||
$firstCell = 'A'
|
||||
. $selectionMatches[1];
|
||||
$cell = $firstCell
|
||||
. ':'
|
||||
. AddressRange::MAX_COLUMN
|
||||
. $selectionMatches[1];
|
||||
$this->thisRow = (int) $selectionMatches[1];
|
||||
$sheet->getCell($firstCell);
|
||||
}
|
||||
|
||||
$validation->setSqref($cell);
|
||||
$stRange = $sheet->shrinkRangeToFit($cell);
|
||||
$cells = array_merge($cells, Coordinate::extractAllCellReferencesInRange($stRange));
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Type':
|
||||
$validation->setType(self::TYPE_MAPPINGS[$tagValueLower] ?? $tagValueLower);
|
||||
|
||||
break;
|
||||
case 'Qualifier':
|
||||
$validation->setOperator(self::OPERATOR_MAPPINGS[$tagValueLower] ?? $tagValueLower);
|
||||
|
||||
break;
|
||||
case 'InputTitle':
|
||||
$validation->setPromptTitle($tagValue);
|
||||
|
||||
break;
|
||||
case 'InputMessage':
|
||||
$validation->setPrompt($tagValue);
|
||||
|
||||
break;
|
||||
case 'InputHide':
|
||||
$validation->setShowInputMessage(false);
|
||||
|
||||
break;
|
||||
case 'ErrorStyle':
|
||||
$validation->setErrorStyle($tagValueLower);
|
||||
|
||||
break;
|
||||
case 'ErrorTitle':
|
||||
$validation->setErrorTitle($tagValue);
|
||||
|
||||
break;
|
||||
case 'ErrorMessage':
|
||||
$validation->setError($tagValue);
|
||||
|
||||
break;
|
||||
case 'ErrorHide':
|
||||
$validation->setShowErrorMessage(false);
|
||||
|
||||
break;
|
||||
case 'ComboHide':
|
||||
$validation->setShowDropDown(false);
|
||||
|
||||
break;
|
||||
case 'UseBlank':
|
||||
$validation->setAllowBlank(true);
|
||||
|
||||
break;
|
||||
case 'CellRangeList':
|
||||
// FIXME missing FIXME
|
||||
|
||||
break;
|
||||
case 'Min':
|
||||
case 'Value':
|
||||
$tagValue = (string) preg_replace_callback(AddressHelper::R1C1_COORDINATE_REGEX, $pregCallback, $tagValue);
|
||||
$validation->setFormula1($tagValue);
|
||||
|
||||
break;
|
||||
case 'Max':
|
||||
$tagValue = (string) preg_replace_callback(AddressHelper::R1C1_COORDINATE_REGEX, $pregCallback, $tagValue);
|
||||
$validation->setFormula2($tagValue);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($cells as $cell) {
|
||||
$sheet->getCell($cell)->setDataValidation(clone $validation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
|
||||
use SimpleXMLElement;
|
||||
@ -14,9 +15,9 @@ class PageSettings
|
||||
*/
|
||||
private $printSettings;
|
||||
|
||||
public function __construct(SimpleXMLElement $xmlX, array $namespaces)
|
||||
public function __construct(SimpleXMLElement $xmlX)
|
||||
{
|
||||
$printSettings = $this->pageSetup($xmlX, $namespaces, $this->getPrintDefaults());
|
||||
$printSettings = $this->pageSetup($xmlX, $this->getPrintDefaults());
|
||||
$this->printSettings = $this->printSetup($xmlX, $printSettings);
|
||||
}
|
||||
|
||||
@ -56,13 +57,13 @@ class PageSettings
|
||||
];
|
||||
}
|
||||
|
||||
private function pageSetup(SimpleXMLElement $xmlX, array $namespaces, stdClass $printDefaults): stdClass
|
||||
private function pageSetup(SimpleXMLElement $xmlX, stdClass $printDefaults): stdClass
|
||||
{
|
||||
if (isset($xmlX->WorksheetOptions->PageSetup)) {
|
||||
foreach ($xmlX->WorksheetOptions->PageSetup as $pageSetupData) {
|
||||
foreach ($pageSetupData as $pageSetupKey => $pageSetupValue) {
|
||||
/** @scrutinizer ignore-call */
|
||||
$pageSetupAttributes = $pageSetupValue->attributes($namespaces['x']);
|
||||
$pageSetupAttributes = $pageSetupValue->attributes(Namespaces::URN_EXCEL);
|
||||
if ($pageSetupAttributes !== null) {
|
||||
switch ($pageSetupKey) {
|
||||
case 'Layout':
|
||||
|
@ -92,6 +92,10 @@ class Properties
|
||||
case 'Manager':
|
||||
$docProps->setManager($stringValue);
|
||||
|
||||
break;
|
||||
case 'HyperlinkBase':
|
||||
$docProps->setHyperlinkBase($stringValue);
|
||||
|
||||
break;
|
||||
case 'Keywords':
|
||||
$docProps->setKeywords($stringValue);
|
||||
@ -110,17 +114,10 @@ class Properties
|
||||
?SimpleXMLElement $propertyValue,
|
||||
SimpleXMLElement $propertyAttributes
|
||||
): void {
|
||||
$propertyType = DocumentProperties::PROPERTY_TYPE_UNKNOWN;
|
||||
|
||||
switch ((string) $propertyAttributes) {
|
||||
case 'string':
|
||||
$propertyType = DocumentProperties::PROPERTY_TYPE_STRING;
|
||||
$propertyValue = trim((string) $propertyValue);
|
||||
|
||||
break;
|
||||
case 'boolean':
|
||||
$propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN;
|
||||
$propertyValue = (bool) $propertyValue;
|
||||
$propertyValue = (bool) (string) $propertyValue;
|
||||
|
||||
break;
|
||||
case 'integer':
|
||||
@ -134,9 +131,15 @@ class Properties
|
||||
|
||||
break;
|
||||
case 'dateTime.tz':
|
||||
case 'dateTime.iso8601tz':
|
||||
$propertyType = DocumentProperties::PROPERTY_TYPE_DATE;
|
||||
$propertyValue = trim((string) $propertyValue);
|
||||
|
||||
break;
|
||||
default:
|
||||
$propertyType = DocumentProperties::PROPERTY_TYPE_STRING;
|
||||
$propertyValue = trim((string) $propertyValue);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Style\Protection;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class Style
|
||||
@ -30,7 +31,7 @@ class Style
|
||||
$styleID = (string) $style_ss['ID'];
|
||||
$this->styles[$styleID] = $this->styles['Default'] ?? [];
|
||||
|
||||
$alignment = $border = $font = $fill = $numberFormat = [];
|
||||
$alignment = $border = $font = $fill = $numberFormat = $protection = [];
|
||||
|
||||
foreach ($style as $styleType => $styleDatax) {
|
||||
$styleData = self::getSxml($styleDatax);
|
||||
@ -65,10 +66,30 @@ class Style
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Protection':
|
||||
$locked = $hidden = null;
|
||||
$styleAttributesP = $styleData->attributes($namespaces['x']);
|
||||
if (isset($styleAttributes['Protected'])) {
|
||||
$locked = ((bool) (string) $styleAttributes['Protected']) ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED;
|
||||
}
|
||||
if (isset($styleAttributesP['HideFormula'])) {
|
||||
$hidden = ((bool) (string) $styleAttributesP['HideFormula']) ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED;
|
||||
}
|
||||
if ($locked !== null || $hidden !== null) {
|
||||
$protection['protection'] = [];
|
||||
if ($locked !== null) {
|
||||
$protection['protection']['locked'] = $locked;
|
||||
}
|
||||
if ($hidden !== null) {
|
||||
$protection['protection']['hidden'] = $hidden;
|
||||
}
|
||||
}
|
||||
|
||||
$this->styles[$styleID] = array_merge($alignment, $border, $font, $fill, $numberFormat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->styles[$styleID] = array_merge($alignment, $border, $font, $fill, $numberFormat, $protection);
|
||||
}
|
||||
|
||||
return $this->styles;
|
||||
|
@ -56,11 +56,11 @@ class Font extends StyleBase
|
||||
|
||||
break;
|
||||
case 'Bold':
|
||||
$style['font']['bold'] = true;
|
||||
$style['font']['bold'] = $styleAttributeValue === '1';
|
||||
|
||||
break;
|
||||
case 'Italic':
|
||||
$style['font']['italic'] = true;
|
||||
$style['font']['italic'] = $styleAttributeValue === '1';
|
||||
|
||||
break;
|
||||
case 'Underline':
|
||||
|
@ -75,14 +75,11 @@ class ReferenceHelper
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function columnReverseSort($a, $b)
|
||||
public static function columnReverseSort(string $a, string $b)
|
||||
{
|
||||
return -strcasecmp(strlen($a) . $a, strlen($b) . $b);
|
||||
}
|
||||
|
||||
/** @var int */
|
||||
private static $scrutinizer0 = 0;
|
||||
|
||||
/**
|
||||
* Compare two cell addresses
|
||||
* Intended for use as a Callback function for sorting cell addresses by column and row.
|
||||
@ -92,16 +89,16 @@ class ReferenceHelper
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function cellSort($a, $b)
|
||||
public static function cellSort(string $a, string $b)
|
||||
{
|
||||
$ac = $bc = '';
|
||||
$ar = self::$scrutinizer0;
|
||||
$br = 0;
|
||||
/** @scrutinizer be-damned */
|
||||
sscanf($a, '%[A-Z]%d', $ac, $ar);
|
||||
/** @var int $ar */
|
||||
/** @var string $ac */
|
||||
/** @scrutinizer be-damned */
|
||||
sscanf($b, '%[A-Z]%d', $bc, $br);
|
||||
|
||||
$ac = (string) $ac;
|
||||
$bc = (string) $bc;
|
||||
/** @var int $br */
|
||||
/** @var string $bc */
|
||||
if ($ar === $br) {
|
||||
return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
|
||||
}
|
||||
@ -118,16 +115,16 @@ class ReferenceHelper
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function cellReverseSort($a, $b)
|
||||
public static function cellReverseSort(string $a, string $b)
|
||||
{
|
||||
$ac = $bc = '';
|
||||
$ar = self::$scrutinizer0;
|
||||
$br = 0;
|
||||
/** @scrutinizer be-damned */
|
||||
sscanf($a, '%[A-Z]%d', $ac, $ar);
|
||||
/** @var int $ar */
|
||||
/** @var string $ac */
|
||||
/** @scrutinizer be-damned */
|
||||
sscanf($b, '%[A-Z]%d', $bc, $br);
|
||||
|
||||
$ac = (string) $ac;
|
||||
$bc = (string) $bc;
|
||||
/** @var int $br */
|
||||
/** @var string $bc */
|
||||
if ($ar === $br) {
|
||||
return -strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
|
||||
}
|
||||
@ -142,7 +139,7 @@ class ReferenceHelper
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustPageBreaks(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void
|
||||
protected function adjustPageBreaks(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
|
||||
{
|
||||
$aBreaks = $worksheet->getBreaks();
|
||||
($numberOfColumns > 0 || $numberOfRows > 0)
|
||||
@ -171,7 +168,7 @@ class ReferenceHelper
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet that we're editing
|
||||
*/
|
||||
protected function adjustComments($worksheet): void
|
||||
protected function adjustComments(Worksheet $worksheet): void
|
||||
{
|
||||
$aComments = $worksheet->getComments();
|
||||
$aNewComments = []; // the new array of all comments
|
||||
@ -195,7 +192,7 @@ class ReferenceHelper
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustHyperlinks($worksheet, $numberOfColumns, $numberOfRows): void
|
||||
protected function adjustHyperlinks(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
|
||||
{
|
||||
$aHyperlinkCollection = $worksheet->getHyperlinkCollection();
|
||||
($numberOfColumns > 0 || $numberOfRows > 0)
|
||||
@ -220,7 +217,7 @@ class ReferenceHelper
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustConditionalFormatting($worksheet, $numberOfColumns, $numberOfRows): void
|
||||
protected function adjustConditionalFormatting(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
|
||||
{
|
||||
$aStyles = $worksheet->getConditionalStylesCollection();
|
||||
($numberOfColumns > 0 || $numberOfRows > 0)
|
||||
@ -259,7 +256,7 @@ class ReferenceHelper
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustDataValidations(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void
|
||||
protected function adjustDataValidations(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
|
||||
{
|
||||
$aDataValidationCollection = $worksheet->getDataValidationCollection();
|
||||
($numberOfColumns > 0 || $numberOfRows > 0)
|
||||
@ -299,7 +296,7 @@ class ReferenceHelper
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustProtectedCells(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void
|
||||
protected function adjustProtectedCells(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
|
||||
{
|
||||
$aProtectedCells = $worksheet->getProtectedCells();
|
||||
($numberOfColumns > 0 || $numberOfRows > 0)
|
||||
@ -412,7 +409,7 @@ class ReferenceHelper
|
||||
$cellCollection = $worksheet->getCellCollection();
|
||||
$missingCoordinates = array_filter(
|
||||
array_map(function ($row) use ($highestColumn) {
|
||||
return $highestColumn . $row;
|
||||
return "{$highestColumn}{$row}";
|
||||
}, range(1, $highestRow)),
|
||||
function ($coordinate) use ($cellCollection) {
|
||||
return $cellCollection->has($coordinate) === false;
|
||||
@ -453,9 +450,9 @@ class ReferenceHelper
|
||||
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
|
||||
// Formula should be adjusted
|
||||
$worksheet->getCell($newCoordinate)
|
||||
->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle()));
|
||||
->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true));
|
||||
} else {
|
||||
// Formula should not be adjusted
|
||||
// Cell value should not be adjusted
|
||||
$worksheet->getCell($newCoordinate)->setValueExplicit($cell->getValue(), $cell->getDataType());
|
||||
}
|
||||
|
||||
@ -466,7 +463,7 @@ class ReferenceHelper
|
||||
but we do still need to adjust any formulae in those cells */
|
||||
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
|
||||
// Formula should be adjusted
|
||||
$cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle()));
|
||||
$cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -609,7 +606,7 @@ class ReferenceHelper
|
||||
// Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
|
||||
$column = 100000;
|
||||
$row = 10000000 + (int) trim($match[3], '$');
|
||||
$cellIndex = $column . $row;
|
||||
$cellIndex = "{$column}{$row}";
|
||||
|
||||
$newCellTokens[$cellIndex] = preg_quote($toString, '/');
|
||||
$cellTokens[$cellIndex] = '/(?<!\d\$\!)' . preg_quote($fromString, '/') . '(?!\d)/i';
|
||||
@ -634,7 +631,7 @@ class ReferenceHelper
|
||||
// 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($match[3], '$')) + 100000;
|
||||
$row = 10000000;
|
||||
$cellIndex = $column . $row;
|
||||
$cellIndex = "{$column}{$row}";
|
||||
|
||||
$newCellTokens[$cellIndex] = preg_quote($toString, '/');
|
||||
$cellTokens[$cellIndex] = '/(?<![A-Z\$\!])' . preg_quote($fromString, '/') . '(?![A-Z])/i';
|
||||
@ -660,7 +657,7 @@ class ReferenceHelper
|
||||
// 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 = (int) trim($row, '$') + 10000000;
|
||||
$cellIndex = $column . $row;
|
||||
$cellIndex = "{$column}{$row}";
|
||||
|
||||
$newCellTokens[$cellIndex] = preg_quote($toString, '/');
|
||||
$cellTokens[$cellIndex] = '/(?<![A-Z]\$\!)' . preg_quote($fromString, '/') . '(?!\d)/i';
|
||||
@ -917,11 +914,18 @@ class ReferenceHelper
|
||||
$cellAddress = $definedName->getValue();
|
||||
$asFormula = ($cellAddress[0] === '=');
|
||||
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
|
||||
/**
|
||||
* If we delete the entire range that is referenced by a Named Range, MS Excel sets the value to #REF!
|
||||
* PhpSpreadsheet still only does a basic adjustment, so the Named Range will still reference Cells.
|
||||
* Note that this applies only when deleting columns/rows; subsequent insertion won't fix the #REF!
|
||||
* TODO Can we work out a method to identify Named Ranges that cease to be valid, so that we can replace
|
||||
* them with a #REF!
|
||||
*/
|
||||
if ($asFormula === true) {
|
||||
$formula = $this->updateFormulaReferences($cellAddress, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle());
|
||||
$formula = $this->updateFormulaReferences($cellAddress, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true);
|
||||
$definedName->setValue($formula);
|
||||
} else {
|
||||
$definedName->setValue($this->updateCellReference(ltrim($cellAddress, '=')));
|
||||
$definedName->setValue($this->updateCellReference(ltrim($cellAddress, '='), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -929,8 +933,15 @@ class ReferenceHelper
|
||||
private function updateNamedFormula(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
|
||||
{
|
||||
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
|
||||
/**
|
||||
* If we delete the entire range that is referenced by a Named Formula, MS Excel sets the value to #REF!
|
||||
* PhpSpreadsheet still only does a basic adjustment, so the Named Formula will still reference Cells.
|
||||
* Note that this applies only when deleting columns/rows; subsequent insertion won't fix the #REF!
|
||||
* TODO Can we work out a method to identify Named Ranges that cease to be valid, so that we can replace
|
||||
* them with a #REF!
|
||||
*/
|
||||
$formula = $definedName->getValue();
|
||||
$formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle());
|
||||
$formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true);
|
||||
$definedName->setValue($formula);
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class TextElement implements ITextElement
|
||||
}
|
||||
|
||||
/**
|
||||
* Get font.
|
||||
* Get font. For this class, the return value is always null.
|
||||
*
|
||||
* @return null|\PhpOffice\PhpSpreadsheet\Style\Font
|
||||
*/
|
||||
|
@ -155,7 +155,7 @@ class Settings
|
||||
/**
|
||||
* Sets the implementation of cache that should be used for cell collection.
|
||||
*/
|
||||
public static function setCache(CacheInterface $cache): void
|
||||
public static function setCache(?CacheInterface $cache): void
|
||||
{
|
||||
self::$cache = $cache;
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ class Date
|
||||
throw new Exception("Invalid string $value supplied for datatype Date");
|
||||
}
|
||||
|
||||
if (preg_match('/^\\d\\d:\\d\\d:\\d\\d/', $value) == 1) {
|
||||
if (preg_match('/^\\s*\\d?\\d:\\d\\d(:\\d\\d([.]\\d+)?)?\\s*(am|pm)?\\s*$/i', $value) == 1) {
|
||||
$newValue = fmod($newValue, 1.0);
|
||||
}
|
||||
|
||||
|
@ -161,11 +161,15 @@ class File
|
||||
|
||||
if ($zipMember !== '') {
|
||||
$zipfile = "zip://$filename#$zipMember";
|
||||
if (!self::fileExists($zipfile)) {
|
||||
// Has the file been saved with Windoze directory separators rather than unix?
|
||||
$zipfile = "zip://$filename#" . str_replace('/', '\\', $zipMember);
|
||||
if (!self::fileExists($zipfile)) {
|
||||
throw new ReaderException("Could not find zip member $zipfile");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as assertFile, except return true/false and don't throw Exception.
|
||||
@ -186,6 +190,14 @@ class File
|
||||
return self::validateZipFirst4($filename);
|
||||
}
|
||||
|
||||
return self::fileExists("zip://$filename#$zipMember");
|
||||
$zipfile = "zip://$filename#$zipMember";
|
||||
if (self::fileExists($zipfile)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Has the file been saved with Windoze directory separators rather than unix?
|
||||
$zipfile = "zip://$filename#" . str_replace('/', '\\', $zipMember);
|
||||
|
||||
return self::fileExists($zipfile);
|
||||
}
|
||||
}
|
||||
|
@ -380,6 +380,7 @@ class Font
|
||||
$approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX;
|
||||
$columnWidth = 0;
|
||||
if (!$approximate) {
|
||||
try {
|
||||
$columnWidthAdjust = ceil(
|
||||
self::getTextWidthPixelsExact(
|
||||
str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
|
||||
@ -388,7 +389,6 @@ class Font
|
||||
) * 1.07
|
||||
);
|
||||
|
||||
try {
|
||||
// Width of text in pixels excl. padding
|
||||
// and addition because Excel adds some padding, just use approx width of 'n' glyph
|
||||
$columnWidth = self::getTextWidthPixelsExact($cellText, $font, $rotation) + $columnWidthAdjust;
|
||||
@ -453,29 +453,26 @@ class Font
|
||||
$fontName = $font->getName();
|
||||
$fontSize = $font->getSize();
|
||||
|
||||
// Calculate column width in pixels. We assume fixed glyph width. Result varies with font name and size.
|
||||
// Calculate column width in pixels.
|
||||
// We assume fixed glyph width, but count double for "fullwidth" characters.
|
||||
// Result varies with font name and size.
|
||||
switch ($fontName) {
|
||||
case 'Calibri':
|
||||
// value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
|
||||
$columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
|
||||
$columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
|
||||
|
||||
break;
|
||||
case 'Arial':
|
||||
// value 8 was set because of experience in different exports at Arial 10 font.
|
||||
$columnWidth = (int) (8 * StringHelper::countCharacters($columnText));
|
||||
$columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
|
||||
$columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
|
||||
|
||||
break;
|
||||
case 'Verdana':
|
||||
// value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font.
|
||||
$columnWidth = (int) (8 * StringHelper::countCharacters($columnText));
|
||||
$columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
|
||||
$columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
|
||||
|
||||
break;
|
||||
default:
|
||||
// just assume Calibri
|
||||
$columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
|
||||
// value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
|
||||
$columnWidth = (int) (8.26 * StringHelper::countCharactersDbcs($columnText));
|
||||
$columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
|
||||
|
||||
break;
|
||||
@ -564,10 +561,13 @@ class Font
|
||||
if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') {
|
||||
$separator = DIRECTORY_SEPARATOR;
|
||||
}
|
||||
$fontFileAbsolute = preg_match('~^([A-Za-z]:)?[/\\\\]~', $fontFile) === 1;
|
||||
if (!$fontFileAbsolute) {
|
||||
$fontFile = self::$trueTypeFontPath . $separator . $fontFile;
|
||||
}
|
||||
|
||||
// Check if file actually exists
|
||||
if ($checkPath && !file_exists($fontFile)) {
|
||||
if ($checkPath && !file_exists($fontFile) && !$fontFileAbsolute) {
|
||||
$alternateName = $name;
|
||||
if ($index !== 'x' && $fontArray[$name][$index] !== $fontArray[$name]['x']) {
|
||||
// Bold but no italic:
|
||||
|
@ -451,6 +451,18 @@ class StringHelper
|
||||
return mb_strlen($textValue, $encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get character count using mb_strwidth rather than mb_strlen.
|
||||
*
|
||||
* @param string $encoding Encoding
|
||||
*
|
||||
* @return int Character count
|
||||
*/
|
||||
public static function countCharactersDbcs(string $textValue, string $encoding = 'UTF-8'): int
|
||||
{
|
||||
return mb_strwidth($textValue, $encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a substring of a UTF-8 encoded string.
|
||||
*
|
||||
|
@ -105,7 +105,6 @@ class Trend
|
||||
$className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit';
|
||||
//* @phpstan-ignore-next-line
|
||||
$bestFit[$trendMethod] = new $className($yValues, $xValues, $const);
|
||||
//* @phpstan-ignore-next-line
|
||||
$bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit();
|
||||
}
|
||||
if ($trendType != self::TREND_BEST_FIT_NO_POLY) {
|
||||
|
@ -203,6 +203,14 @@ class Spreadsheet implements JsonSerializable
|
||||
*/
|
||||
private $tabRatio = 600;
|
||||
|
||||
/** @var Theme */
|
||||
private $theme;
|
||||
|
||||
public function getTheme(): Theme
|
||||
{
|
||||
return $this->theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* The workbook has macros ?
|
||||
*
|
||||
@ -476,6 +484,7 @@ class Spreadsheet implements JsonSerializable
|
||||
{
|
||||
$this->uniqueID = uniqid('', true);
|
||||
$this->calculationEngine = new Calculation($this);
|
||||
$this->theme = new Theme();
|
||||
|
||||
// Initialise worksheet collection and add one worksheet
|
||||
$this->workSheetCollection = [];
|
||||
@ -1654,4 +1663,26 @@ class Spreadsheet implements JsonSerializable
|
||||
{
|
||||
throw new Exception('Spreadsheet objects cannot be json encoded');
|
||||
}
|
||||
|
||||
public function resetThemeFonts(): void
|
||||
{
|
||||
$majorFontLatin = $this->theme->getMajorFontLatin();
|
||||
$minorFontLatin = $this->theme->getMinorFontLatin();
|
||||
foreach ($this->cellXfCollection as $cellStyleXf) {
|
||||
$scheme = $cellStyleXf->getFont()->getScheme();
|
||||
if ($scheme === 'major') {
|
||||
$cellStyleXf->getFont()->setName($majorFontLatin)->setScheme($scheme);
|
||||
} elseif ($scheme === 'minor') {
|
||||
$cellStyleXf->getFont()->setName($minorFontLatin)->setScheme($scheme);
|
||||
}
|
||||
}
|
||||
foreach ($this->cellStyleXfCollection as $cellStyleXf) {
|
||||
$scheme = $cellStyleXf->getFont()->getScheme();
|
||||
if ($scheme === 'major') {
|
||||
$cellStyleXf->getFont()->setName($majorFontLatin)->setScheme($scheme);
|
||||
} elseif ($scheme === 'minor') {
|
||||
$cellStyleXf->getFont()->setName($minorFontLatin)->setScheme($scheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -362,23 +362,8 @@ class Color extends Supervisor
|
||||
$green = self::getGreen($hexColourValue, false);
|
||||
/** @var int $blue */
|
||||
$blue = self::getBlue($hexColourValue, false);
|
||||
if ($adjustPercentage > 0) {
|
||||
$red += (255 - $red) * $adjustPercentage;
|
||||
$green += (255 - $green) * $adjustPercentage;
|
||||
$blue += (255 - $blue) * $adjustPercentage;
|
||||
} else {
|
||||
$red += $red * $adjustPercentage;
|
||||
$green += $green * $adjustPercentage;
|
||||
$blue += $blue * $adjustPercentage;
|
||||
}
|
||||
|
||||
$rgb = strtoupper(
|
||||
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;
|
||||
return (($rgba) ? 'FF' : '') . RgbTint::rgbAndTintToRgb($red, $green, $blue, $adjustPercentage);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -248,7 +248,7 @@ class Conditional implements IComparable
|
||||
/**
|
||||
* Set Conditions.
|
||||
*
|
||||
* @param bool|float|int|string|(bool|float|int|string)[] $conditions Condition
|
||||
* @param (bool|float|int|string)[]|bool|float|int|string $conditions Condition
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
|
@ -107,6 +107,9 @@ class Font extends Supervisor
|
||||
*/
|
||||
public $colorIndex;
|
||||
|
||||
/** @var string */
|
||||
protected $scheme = '';
|
||||
|
||||
/**
|
||||
* Create a new Font.
|
||||
*
|
||||
@ -231,6 +234,12 @@ class Font extends Supervisor
|
||||
if (isset($styleArray['size'])) {
|
||||
$this->setSize($styleArray['size']);
|
||||
}
|
||||
if (isset($styleArray['chartColor'])) {
|
||||
$this->chartColor = $styleArray['chartColor'];
|
||||
}
|
||||
if (isset($styleArray['scheme'])) {
|
||||
$this->setScheme($styleArray['scheme']);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -278,13 +287,11 @@ class Font extends Supervisor
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Name.
|
||||
* Set Name and turn off Scheme.
|
||||
*
|
||||
* @param string $fontname
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($fontname)
|
||||
public function setName($fontname): self
|
||||
{
|
||||
if ($fontname == '') {
|
||||
$fontname = 'Calibri';
|
||||
@ -296,7 +303,7 @@ class Font extends Supervisor
|
||||
$this->name = $fontname;
|
||||
}
|
||||
|
||||
return $this;
|
||||
return $this->setScheme('');
|
||||
}
|
||||
|
||||
public function setLatin(string $fontname): self
|
||||
@ -634,6 +641,13 @@ class Font extends Supervisor
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setChartColorFromObject(?ChartColor $chartColor): self
|
||||
{
|
||||
$this->chartColor = $chartColor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Underline.
|
||||
*
|
||||
@ -774,6 +788,7 @@ class Font extends Supervisor
|
||||
$this->underline .
|
||||
($this->strikethrough ? 't' : 'f') .
|
||||
$this->color->getHashCode() .
|
||||
$this->scheme .
|
||||
implode(
|
||||
'*',
|
||||
[
|
||||
@ -802,6 +817,7 @@ class Font extends Supervisor
|
||||
$this->exportArray2($exportedArray, 'italic', $this->getItalic());
|
||||
$this->exportArray2($exportedArray, 'latin', $this->getLatin());
|
||||
$this->exportArray2($exportedArray, 'name', $this->getName());
|
||||
$this->exportArray2($exportedArray, 'scheme', $this->getScheme());
|
||||
$this->exportArray2($exportedArray, 'size', $this->getSize());
|
||||
$this->exportArray2($exportedArray, 'strikethrough', $this->getStrikethrough());
|
||||
$this->exportArray2($exportedArray, 'strikeType', $this->getStrikeType());
|
||||
@ -812,4 +828,27 @@ class Font extends Supervisor
|
||||
|
||||
return $exportedArray;
|
||||
}
|
||||
|
||||
public function getScheme(): string
|
||||
{
|
||||
if ($this->isSupervisor) {
|
||||
return $this->getSharedComponent()->getScheme();
|
||||
}
|
||||
|
||||
return $this->scheme;
|
||||
}
|
||||
|
||||
public function setScheme(string $scheme): self
|
||||
{
|
||||
if ($scheme === '' || $scheme === 'major' || $scheme === 'minor') {
|
||||
if ($this->isSupervisor) {
|
||||
$styleArray = $this->getStyleArray(['scheme' => $scheme]);
|
||||
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
|
||||
} else {
|
||||
$this->scheme = $scheme;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xls\Color\BIFF8;
|
||||
use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Color;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
@ -67,14 +68,19 @@ class Formatter
|
||||
// 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO]
|
||||
// 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT]
|
||||
$sectionCount = count($sections);
|
||||
$color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . ')\\]/mui';
|
||||
// Colour could be a named colour, or a numeric index entry in the colour-palette
|
||||
$color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . '|color\\s*(\\d+))\\]/mui';
|
||||
$cond_regex = '/\\[(>|>=|<|<=|=|<>)([+-]?\\d+([.]\\d+)?)\\]/';
|
||||
$colors = ['', '', '', '', ''];
|
||||
$conditionOperations = ['', '', '', '', ''];
|
||||
$conditionComparisonValues = [0, 0, 0, 0, 0];
|
||||
for ($idx = 0; $idx < $sectionCount; ++$idx) {
|
||||
if (preg_match($color_regex, $sections[$idx], $matches)) {
|
||||
if (isset($matches[2])) {
|
||||
$colors[$idx] = '#' . BIFF8::lookup((int) $matches[2] + 7)['rgb'];
|
||||
} else {
|
||||
$colors[$idx] = $matches[0];
|
||||
}
|
||||
$sections[$idx] = (string) preg_replace($color_regex, '', $sections[$idx]);
|
||||
}
|
||||
if (preg_match($cond_regex, $sections[$idx], $matches)) {
|
||||
@ -170,10 +176,11 @@ class Formatter
|
||||
$format = (string) preg_replace('/_.?/ui', ' ', $format);
|
||||
|
||||
// Let's begin inspecting the format and converting the value to a formatted string
|
||||
// Check for date/time characters (not inside quotes)
|
||||
if (
|
||||
(preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format)) &&
|
||||
(preg_match('/0(?![^\[]*\])/miu', $format) === 0)
|
||||
// Check for date/time characters (not inside quotes)
|
||||
(preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format))
|
||||
// A date/time with a decimal time shouldn't have a digit placeholder before the decimal point
|
||||
&& (preg_match('/[0\?#]\.(?![^\[]*\])/miu', $format) === 0)
|
||||
) {
|
||||
// datetime format
|
||||
$value = DateFormatter::format($value, $format);
|
||||
@ -194,8 +201,6 @@ class Formatter
|
||||
$value = $writerInstance->$function($value, $colors);
|
||||
}
|
||||
|
||||
$value = str_replace(chr(0x00), '.', $value);
|
||||
|
||||
return $value;
|
||||
return str_replace(chr(0x00), '.', $value);
|
||||
}
|
||||
}
|
||||
|
@ -89,13 +89,13 @@ class Accounting extends Currency
|
||||
(
|
||||
$this->currencySymbolPosition === self::LEADING_SYMBOL &&
|
||||
$this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING
|
||||
) ? ' ' : '',
|
||||
) ? "\u{a0}" : '',
|
||||
$this->thousandsSeparator ? '#,##' : null,
|
||||
$this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null,
|
||||
(
|
||||
$this->currencySymbolPosition === self::TRAILING_SYMBOL &&
|
||||
$this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING
|
||||
) ? ' ' : '',
|
||||
) ? "\u{a0}" : '',
|
||||
$this->currencySymbolPosition === self::TRAILING_SYMBOL ? $this->formatCurrencyCode() : null
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
|
||||
|
||||
class Date extends DateTimeWizard
|
||||
{
|
||||
/**
|
||||
* Year (4 digits), e.g. 2023.
|
||||
*/
|
||||
public const YEAR_FULL = 'yyyy';
|
||||
|
||||
/**
|
||||
* Year (last 2 digits), e.g. 23.
|
||||
*/
|
||||
public const YEAR_SHORT = 'yy';
|
||||
|
||||
public const MONTH_FIRST_LETTER = 'mmmmm';
|
||||
/**
|
||||
* Month name, long form, e.g. January.
|
||||
*/
|
||||
public const MONTH_NAME_FULL = 'mmmm';
|
||||
/**
|
||||
* Month name, short form, e.g. Jan.
|
||||
*/
|
||||
public const MONTH_NAME_SHORT = 'mmm';
|
||||
/**
|
||||
* Month number with a leading zero if required, e.g. 01.
|
||||
*/
|
||||
public const MONTH_NUMBER_LONG = 'mm';
|
||||
|
||||
/**
|
||||
* Month number without a leading zero, e.g. 1.
|
||||
*/
|
||||
public const MONTH_NUMBER_SHORT = 'm';
|
||||
|
||||
/**
|
||||
* Day of the week, full form, e.g. Tuesday.
|
||||
*/
|
||||
public const WEEKDAY_NAME_LONG = 'dddd';
|
||||
|
||||
/**
|
||||
* Day of the week, short form, e.g. Tue.
|
||||
*/
|
||||
public const WEEKDAY_NAME_SHORT = 'ddd';
|
||||
|
||||
/**
|
||||
* Day number with a leading zero, e.g. 03.
|
||||
*/
|
||||
public const DAY_NUMBER_LONG = 'dd';
|
||||
|
||||
/**
|
||||
* Day number without a leading zero, e.g. 3.
|
||||
*/
|
||||
public const DAY_NUMBER_SHORT = 'd';
|
||||
|
||||
protected const DATE_BLOCKS = [
|
||||
self::YEAR_FULL,
|
||||
self::YEAR_SHORT,
|
||||
self::MONTH_FIRST_LETTER,
|
||||
self::MONTH_NAME_FULL,
|
||||
self::MONTH_NAME_SHORT,
|
||||
self::MONTH_NUMBER_LONG,
|
||||
self::MONTH_NUMBER_SHORT,
|
||||
self::WEEKDAY_NAME_LONG,
|
||||
self::WEEKDAY_NAME_SHORT,
|
||||
self::DAY_NUMBER_LONG,
|
||||
self::DAY_NUMBER_SHORT,
|
||||
];
|
||||
|
||||
public const SEPARATOR_DASH = '-';
|
||||
public const SEPARATOR_DOT = '.';
|
||||
public const SEPARATOR_SLASH = '/';
|
||||
public const SEPARATOR_SPACE_NONBREAKING = "\u{a0}";
|
||||
public const SEPARATOR_SPACE = ' ';
|
||||
|
||||
protected const DATE_DEFAULT = [
|
||||
self::YEAR_FULL,
|
||||
self::MONTH_NUMBER_LONG,
|
||||
self::DAY_NUMBER_LONG,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $separators;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $formatBlocks;
|
||||
|
||||
/**
|
||||
* @param null|string|string[] $separators
|
||||
* If you want to use the same separator for all format blocks, then it can be passed as a string literal;
|
||||
* if you wish to use different separators, then they should be passed as an array.
|
||||
* If you want to use only a single format block, then pass a null as the separator argument
|
||||
*/
|
||||
public function __construct($separators = self::SEPARATOR_DASH, string ...$formatBlocks)
|
||||
{
|
||||
$separators ??= self::SEPARATOR_DASH;
|
||||
$formatBlocks = (count($formatBlocks) === 0) ? self::DATE_DEFAULT : $formatBlocks;
|
||||
|
||||
$this->separators = $this->padSeparatorArray(
|
||||
is_array($separators) ? $separators : [$separators],
|
||||
count($formatBlocks) - 1
|
||||
);
|
||||
$this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
|
||||
}
|
||||
|
||||
private function mapFormatBlocks(string $value): string
|
||||
{
|
||||
// Any date masking codes are returned as lower case values
|
||||
if (in_array(mb_strtolower($value), self::DATE_BLOCKS, true)) {
|
||||
return mb_strtolower($value);
|
||||
}
|
||||
|
||||
// Wrap any string literals in quotes, so that they're clearly defined as string literals
|
||||
return $this->wrapLiteral($value);
|
||||
}
|
||||
|
||||
public function format(): string
|
||||
{
|
||||
return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
|
||||
|
||||
class DateTime extends DateTimeWizard
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $separators;
|
||||
|
||||
/**
|
||||
* @var array<DateTimeWizard|string>
|
||||
*/
|
||||
protected array $formatBlocks;
|
||||
|
||||
/**
|
||||
* @param null|string|string[] $separators
|
||||
* If you want to use only a single format block, then pass a null as the separator argument
|
||||
* @param DateTimeWizard|string ...$formatBlocks
|
||||
*/
|
||||
public function __construct($separators, ...$formatBlocks)
|
||||
{
|
||||
$this->separators = $this->padSeparatorArray(
|
||||
is_array($separators) ? $separators : [$separators],
|
||||
count($formatBlocks) - 1
|
||||
);
|
||||
$this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTimeWizard|string $value
|
||||
*/
|
||||
private function mapFormatBlocks($value): string
|
||||
{
|
||||
// Any date masking codes are returned as lower case values
|
||||
if (is_object($value)) {
|
||||
// We can't explicitly test for Stringable until PHP >= 8.0
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Wrap any string literals in quotes, so that they're clearly defined as string literals
|
||||
return $this->wrapLiteral($value);
|
||||
}
|
||||
|
||||
public function format(): string
|
||||
{
|
||||
return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
|
||||
|
||||
abstract class DateTimeWizard implements Wizard
|
||||
{
|
||||
protected const NO_ESCAPING_NEEDED = "$+-/():!^&'~{}<>= ";
|
||||
|
||||
protected function padSeparatorArray(array $separators, int $count): array
|
||||
{
|
||||
$lastSeparator = array_pop($separators);
|
||||
|
||||
return $separators + array_fill(0, $count, $lastSeparator);
|
||||
}
|
||||
|
||||
protected function escapeSingleCharacter(string $value): string
|
||||
{
|
||||
if (strpos(self::NO_ESCAPING_NEEDED, $value) !== false) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return "\\{$value}";
|
||||
}
|
||||
|
||||
protected function wrapLiteral(string $value): string
|
||||
{
|
||||
if (mb_strlen($value, 'UTF-8') === 1) {
|
||||
return $this->escapeSingleCharacter($value);
|
||||
}
|
||||
|
||||
// Wrap any other string literals in quotes, so that they're clearly defined as string literals
|
||||
return '"' . str_replace('"', '""', $value) . '"';
|
||||
}
|
||||
|
||||
protected function intersperse(string $formatBlock, ?string $separator): string
|
||||
{
|
||||
return "{$formatBlock}{$separator}";
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->format();
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
|
||||
|
||||
class Duration extends DateTimeWizard
|
||||
{
|
||||
public const DAYS_DURATION = 'd';
|
||||
|
||||
/**
|
||||
* Hours as a duration (can exceed 24), e.g. 29.
|
||||
*/
|
||||
public const HOURS_DURATION = '[h]';
|
||||
|
||||
/**
|
||||
* Hours without a leading zero, e.g. 9.
|
||||
*/
|
||||
public const HOURS_SHORT = 'h';
|
||||
|
||||
/**
|
||||
* Hours with a leading zero, e.g. 09.
|
||||
*/
|
||||
public const HOURS_LONG = 'hh';
|
||||
|
||||
/**
|
||||
* Minutes as a duration (can exceed 60), e.g. 109.
|
||||
*/
|
||||
public const MINUTES_DURATION = '[m]';
|
||||
|
||||
/**
|
||||
* Minutes without a leading zero, e.g. 5.
|
||||
*/
|
||||
public const MINUTES_SHORT = 'm';
|
||||
|
||||
/**
|
||||
* Minutes with a leading zero, e.g. 05.
|
||||
*/
|
||||
public const MINUTES_LONG = 'mm';
|
||||
|
||||
/**
|
||||
* Seconds as a duration (can exceed 60), e.g. 129.
|
||||
*/
|
||||
public const SECONDS_DURATION = '[s]';
|
||||
|
||||
/**
|
||||
* Seconds without a leading zero, e.g. 2.
|
||||
*/
|
||||
public const SECONDS_SHORT = 's';
|
||||
|
||||
/**
|
||||
* Seconds with a leading zero, e.g. 02.
|
||||
*/
|
||||
public const SECONDS_LONG = 'ss';
|
||||
|
||||
protected const DURATION_BLOCKS = [
|
||||
self::DAYS_DURATION,
|
||||
self::HOURS_DURATION,
|
||||
self::HOURS_LONG,
|
||||
self::HOURS_SHORT,
|
||||
self::MINUTES_DURATION,
|
||||
self::MINUTES_LONG,
|
||||
self::MINUTES_SHORT,
|
||||
self::SECONDS_DURATION,
|
||||
self::SECONDS_LONG,
|
||||
self::SECONDS_SHORT,
|
||||
];
|
||||
|
||||
protected const DURATION_MASKS = [
|
||||
self::DAYS_DURATION => self::DAYS_DURATION,
|
||||
self::HOURS_DURATION => self::HOURS_SHORT,
|
||||
self::MINUTES_DURATION => self::MINUTES_LONG,
|
||||
self::SECONDS_DURATION => self::SECONDS_LONG,
|
||||
];
|
||||
|
||||
protected const DURATION_DEFAULTS = [
|
||||
self::HOURS_LONG => self::HOURS_DURATION,
|
||||
self::HOURS_SHORT => self::HOURS_DURATION,
|
||||
self::MINUTES_LONG => self::MINUTES_DURATION,
|
||||
self::MINUTES_SHORT => self::MINUTES_DURATION,
|
||||
self::SECONDS_LONG => self::SECONDS_DURATION,
|
||||
self::SECONDS_SHORT => self::SECONDS_DURATION,
|
||||
];
|
||||
|
||||
public const SEPARATOR_COLON = ':';
|
||||
public const SEPARATOR_SPACE_NONBREAKING = "\u{a0}";
|
||||
public const SEPARATOR_SPACE = ' ';
|
||||
|
||||
public const DURATION_DEFAULT = [
|
||||
self::HOURS_DURATION,
|
||||
self::MINUTES_LONG,
|
||||
self::SECONDS_LONG,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $separators;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $formatBlocks;
|
||||
|
||||
protected bool $durationIsSet = false;
|
||||
|
||||
/**
|
||||
* @param null|string|string[] $separators
|
||||
* If you want to use the same separator for all format blocks, then it can be passed as a string literal;
|
||||
* if you wish to use different separators, then they should be passed as an array.
|
||||
* If you want to use only a single format block, then pass a null as the separator argument
|
||||
*/
|
||||
public function __construct($separators = self::SEPARATOR_COLON, string ...$formatBlocks)
|
||||
{
|
||||
$separators ??= self::SEPARATOR_COLON;
|
||||
$formatBlocks = (count($formatBlocks) === 0) ? self::DURATION_DEFAULT : $formatBlocks;
|
||||
|
||||
$this->separators = $this->padSeparatorArray(
|
||||
is_array($separators) ? $separators : [$separators],
|
||||
count($formatBlocks) - 1
|
||||
);
|
||||
$this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
|
||||
|
||||
if ($this->durationIsSet === false) {
|
||||
// We need at least one duration mask, so if none has been set we change the first mask element
|
||||
// to a duration.
|
||||
$this->formatBlocks[0] = self::DURATION_DEFAULTS[mb_strtolower($this->formatBlocks[0])];
|
||||
}
|
||||
}
|
||||
|
||||
private function mapFormatBlocks(string $value): string
|
||||
{
|
||||
// Any duration masking codes are returned as lower case values
|
||||
if (in_array(mb_strtolower($value), self::DURATION_BLOCKS, true)) {
|
||||
if (array_key_exists(mb_strtolower($value), self::DURATION_MASKS)) {
|
||||
if ($this->durationIsSet) {
|
||||
// We should only have a single duration mask, the first defined in the mask set,
|
||||
// so convert any additional duration masks to standard time masks.
|
||||
$value = self::DURATION_MASKS[mb_strtolower($value)];
|
||||
}
|
||||
$this->durationIsSet = true;
|
||||
}
|
||||
|
||||
return mb_strtolower($value);
|
||||
}
|
||||
|
||||
// Wrap any string literals in quotes, so that they're clearly defined as string literals
|
||||
return $this->wrapLiteral($value);
|
||||
}
|
||||
|
||||
public function format(): string
|
||||
{
|
||||
return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
|
||||
|
||||
class Time extends DateTimeWizard
|
||||
{
|
||||
/**
|
||||
* Hours without a leading zero, e.g. 9.
|
||||
*/
|
||||
public const HOURS_SHORT = 'h';
|
||||
|
||||
/**
|
||||
* Hours with a leading zero, e.g. 09.
|
||||
*/
|
||||
public const HOURS_LONG = 'hh';
|
||||
|
||||
/**
|
||||
* Minutes without a leading zero, e.g. 5.
|
||||
*/
|
||||
public const MINUTES_SHORT = 'm';
|
||||
|
||||
/**
|
||||
* Minutes with a leading zero, e.g. 05.
|
||||
*/
|
||||
public const MINUTES_LONG = 'mm';
|
||||
|
||||
/**
|
||||
* Seconds without a leading zero, e.g. 2.
|
||||
*/
|
||||
public const SECONDS_SHORT = 's';
|
||||
|
||||
/**
|
||||
* Seconds with a leading zero, e.g. 02.
|
||||
*/
|
||||
public const SECONDS_LONG = 'ss';
|
||||
|
||||
public const MORNING_AFTERNOON = 'AM/PM';
|
||||
|
||||
protected const TIME_BLOCKS = [
|
||||
self::HOURS_LONG,
|
||||
self::HOURS_SHORT,
|
||||
self::MINUTES_LONG,
|
||||
self::MINUTES_SHORT,
|
||||
self::SECONDS_LONG,
|
||||
self::SECONDS_SHORT,
|
||||
self::MORNING_AFTERNOON,
|
||||
];
|
||||
|
||||
public const SEPARATOR_COLON = ':';
|
||||
public const SEPARATOR_SPACE_NONBREAKING = "\u{a0}";
|
||||
public const SEPARATOR_SPACE = ' ';
|
||||
|
||||
protected const TIME_DEFAULT = [
|
||||
self::HOURS_LONG,
|
||||
self::MINUTES_LONG,
|
||||
self::SECONDS_LONG,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $separators;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $formatBlocks;
|
||||
|
||||
/**
|
||||
* @param null|string|string[] $separators
|
||||
* If you want to use the same separator for all format blocks, then it can be passed as a string literal;
|
||||
* if you wish to use different separators, then they should be passed as an array.
|
||||
* If you want to use only a single format block, then pass a null as the separator argument
|
||||
*/
|
||||
public function __construct($separators = self::SEPARATOR_COLON, string ...$formatBlocks)
|
||||
{
|
||||
$separators ??= self::SEPARATOR_COLON;
|
||||
$formatBlocks = (count($formatBlocks) === 0) ? self::TIME_DEFAULT : $formatBlocks;
|
||||
|
||||
$this->separators = $this->padSeparatorArray(
|
||||
is_array($separators) ? $separators : [$separators],
|
||||
count($formatBlocks) - 1
|
||||
);
|
||||
$this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
|
||||
}
|
||||
|
||||
private function mapFormatBlocks(string $value): string
|
||||
{
|
||||
// Any date masking codes are returned as lower case values
|
||||
// except for AM/PM, which is set to uppercase
|
||||
if (in_array(mb_strtolower($value), self::TIME_BLOCKS, true)) {
|
||||
return mb_strtolower($value);
|
||||
} elseif (mb_strtoupper($value) === self::MORNING_AFTERNOON) {
|
||||
return mb_strtoupper($value);
|
||||
}
|
||||
|
||||
// Wrap any string literals in quotes, so that they're clearly defined as string literals
|
||||
return $this->wrapLiteral($value);
|
||||
}
|
||||
|
||||
public function format(): string
|
||||
{
|
||||
return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
|
||||
}
|
||||
}
|
175
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php
vendored
Normal file
175
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Style;
|
||||
|
||||
/**
|
||||
* Class to handle tint applied to color.
|
||||
* Code borrows heavily from some Python projects.
|
||||
*
|
||||
* @see https://docs.python.org/3/library/colorsys.html
|
||||
* @see https://gist.github.com/Mike-Honey/b36e651e9a7f1d2e1d60ce1c63b9b633
|
||||
*/
|
||||
class RgbTint
|
||||
{
|
||||
private const ONE_THIRD = 1.0 / 3.0;
|
||||
private const ONE_SIXTH = 1.0 / 6.0;
|
||||
private const TWO_THIRD = 2.0 / 3.0;
|
||||
private const RGBMAX = 255.0;
|
||||
/**
|
||||
* MS excel's tint function expects that HLS is base 240.
|
||||
*
|
||||
* @see https://social.msdn.microsoft.com/Forums/en-US/e9d8c136-6d62-4098-9b1b-dac786149f43/excel-color-tint-algorithm-incorrect?forum=os_binaryfile#d3c2ac95-52e0-476b-86f1-e2a697f24969
|
||||
*/
|
||||
private const HLSMAX = 240.0;
|
||||
|
||||
/**
|
||||
* Convert red/green/blue to hue/luminance/saturation.
|
||||
*
|
||||
* @param float $red 0.0 through 1.0
|
||||
* @param float $green 0.0 through 1.0
|
||||
* @param float $blue 0.0 through 1.0
|
||||
*
|
||||
* @return float[]
|
||||
*/
|
||||
private static function rgbToHls(float $red, float $green, float $blue): array
|
||||
{
|
||||
$maxc = max($red, $green, $blue);
|
||||
$minc = min($red, $green, $blue);
|
||||
$luminance = ($minc + $maxc) / 2.0;
|
||||
if ($minc === $maxc) {
|
||||
return [0.0, $luminance, 0.0];
|
||||
}
|
||||
$maxMinusMin = $maxc - $minc;
|
||||
if ($luminance <= 0.5) {
|
||||
$s = $maxMinusMin / ($maxc + $minc);
|
||||
} else {
|
||||
$s = $maxMinusMin / (2.0 - $maxc - $minc);
|
||||
}
|
||||
$rc = ($maxc - $red) / $maxMinusMin;
|
||||
$gc = ($maxc - $green) / $maxMinusMin;
|
||||
$bc = ($maxc - $blue) / $maxMinusMin;
|
||||
if ($red === $maxc) {
|
||||
$h = $bc - $gc;
|
||||
} elseif ($green === $maxc) {
|
||||
$h = 2.0 + $rc - $bc;
|
||||
} else {
|
||||
$h = 4.0 + $gc - $rc;
|
||||
}
|
||||
$h = self::positiveDecimalPart($h / 6.0);
|
||||
|
||||
return [$h, $luminance, $s];
|
||||
}
|
||||
|
||||
/** @var mixed */
|
||||
private static $scrutinizerZeroPointZero = 0.0;
|
||||
|
||||
/**
|
||||
* Convert hue/luminance/saturation to red/green/blue.
|
||||
*
|
||||
* @param float $hue 0.0 through 1.0
|
||||
* @param float $luminance 0.0 through 1.0
|
||||
* @param float $saturation 0.0 through 1.0
|
||||
*
|
||||
* @return float[]
|
||||
*/
|
||||
private static function hlsToRgb($hue, $luminance, $saturation): array
|
||||
{
|
||||
if ($saturation === self::$scrutinizerZeroPointZero) {
|
||||
return [$luminance, $luminance, $luminance];
|
||||
}
|
||||
if ($luminance <= 0.5) {
|
||||
$m2 = $luminance * (1.0 + $saturation);
|
||||
} else {
|
||||
$m2 = $luminance + $saturation - ($luminance * $saturation);
|
||||
}
|
||||
$m1 = 2.0 * $luminance - $m2;
|
||||
|
||||
return [
|
||||
self::vFunction($m1, $m2, $hue + self::ONE_THIRD),
|
||||
self::vFunction($m1, $m2, $hue),
|
||||
self::vFunction($m1, $m2, $hue - self::ONE_THIRD),
|
||||
];
|
||||
}
|
||||
|
||||
private static function vFunction(float $m1, float $m2, float $hue): float
|
||||
{
|
||||
$hue = self::positiveDecimalPart($hue);
|
||||
if ($hue < self::ONE_SIXTH) {
|
||||
return $m1 + ($m2 - $m1) * $hue * 6.0;
|
||||
}
|
||||
if ($hue < 0.5) {
|
||||
return $m2;
|
||||
}
|
||||
if ($hue < self::TWO_THIRD) {
|
||||
return $m1 + ($m2 - $m1) * (self::TWO_THIRD - $hue) * 6.0;
|
||||
}
|
||||
|
||||
return $m1;
|
||||
}
|
||||
|
||||
private static function positiveDecimalPart(float $hue): float
|
||||
{
|
||||
$hue = fmod($hue, 1.0);
|
||||
|
||||
return ($hue >= 0.0) ? $hue : (1.0 + $hue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert red/green/blue to HLSMAX-based hue/luminance/saturation.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
private static function rgbToMsHls(int $red, int $green, int $blue): array
|
||||
{
|
||||
$red01 = $red / self::RGBMAX;
|
||||
$green01 = $green / self::RGBMAX;
|
||||
$blue01 = $blue / self::RGBMAX;
|
||||
[$hue, $luminance, $saturation] = self::rgbToHls($red01, $green01, $blue01);
|
||||
|
||||
return [
|
||||
(int) round($hue * self::HLSMAX),
|
||||
(int) round($luminance * self::HLSMAX),
|
||||
(int) round($saturation * self::HLSMAX),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts HLSMAX based HLS values to rgb values in the range (0,1).
|
||||
*
|
||||
* @return float[]
|
||||
*/
|
||||
private static function msHlsToRgb(int $hue, int $lightness, int $saturation): array
|
||||
{
|
||||
return self::hlsToRgb($hue / self::HLSMAX, $lightness / self::HLSMAX, $saturation / self::HLSMAX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tints HLSMAX based luminance.
|
||||
*
|
||||
* @see http://ciintelligence.blogspot.co.uk/2012/02/converting-excel-theme-color-and-tint.html
|
||||
*/
|
||||
private static function tintLuminance(float $tint, float $luminance): int
|
||||
{
|
||||
if ($tint < 0) {
|
||||
return (int) round($luminance * (1.0 + $tint));
|
||||
}
|
||||
|
||||
return (int) round($luminance * (1.0 - $tint) + (self::HLSMAX - self::HLSMAX * (1.0 - $tint)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return result of tinting supplied rgb as 6 hex digits.
|
||||
*/
|
||||
public static function rgbAndTintToRgb(int $red, int $green, int $blue, float $tint): string
|
||||
{
|
||||
[$hue, $luminance, $saturation] = self::rgbToMsHls($red, $green, $blue);
|
||||
[$red, $green, $blue] = self::msHlsToRgb($hue, self::tintLuminance($tint, $luminance), $saturation);
|
||||
|
||||
return sprintf(
|
||||
'%02X%02X%02X',
|
||||
(int) round($red * self::RGBMAX),
|
||||
(int) round($green * self::RGBMAX),
|
||||
(int) round($blue * self::RGBMAX)
|
||||
);
|
||||
}
|
||||
}
|
@ -2,7 +2,10 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Style;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
class Style extends Supervisor
|
||||
@ -122,7 +125,7 @@ class Style extends Supervisor
|
||||
public function getSharedComponent(): self
|
||||
{
|
||||
$activeSheet = $this->getActiveSheet();
|
||||
$selectedCell = $this->getActiveCell(); // e.g. 'A1'
|
||||
$selectedCell = Functions::trimSheetFromCellReference($this->getActiveCell()); // e.g. 'A1'
|
||||
|
||||
if ($activeSheet->cellExists($selectedCell)) {
|
||||
$xfIndex = $activeSheet->getCell($selectedCell)->getXfIndex();
|
||||
@ -203,8 +206,15 @@ class Style extends Supervisor
|
||||
if ($this->isSupervisor) {
|
||||
$pRange = $this->getSelectedCells();
|
||||
|
||||
// Uppercase coordinate
|
||||
// Uppercase coordinate and strip any Worksheet reference from the selected range
|
||||
$pRange = strtoupper($pRange);
|
||||
if (strpos($pRange, '!') !== false) {
|
||||
$pRangeWorksheet = StringHelper::strToUpper(trim(substr($pRange, 0, (int) strrpos($pRange, '!')), "'"));
|
||||
if ($pRangeWorksheet !== '' && StringHelper::strToUpper($this->getActiveSheet()->getTitle()) !== $pRangeWorksheet) {
|
||||
throw new Exception('Invalid Worksheet for specified Range');
|
||||
}
|
||||
$pRange = strtoupper(Functions::trimSheetFromCellReference($pRange));
|
||||
}
|
||||
|
||||
// Is it a cell range or a single cell?
|
||||
if (strpos($pRange, ':') === false) {
|
||||
|
269
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Theme.php
vendored
Normal file
269
lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Theme.php
vendored
Normal file
@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet;
|
||||
|
||||
class Theme
|
||||
{
|
||||
/** @var string */
|
||||
private $themeColorName = 'Office';
|
||||
|
||||
/** @var string */
|
||||
private $themeFontName = 'Office';
|
||||
|
||||
public const COLOR_SCHEME_2013_PLUS_NAME = 'Office 2013+';
|
||||
public const COLOR_SCHEME_2013_PLUS = [
|
||||
'dk1' => '000000',
|
||||
'lt1' => 'FFFFFF',
|
||||
'dk2' => '44546A',
|
||||
'lt2' => 'E7E6E6',
|
||||
'accent1' => '4472C4',
|
||||
'accent2' => 'ED7D31',
|
||||
'accent3' => 'A5A5A5',
|
||||
'accent4' => 'FFC000',
|
||||
'accent5' => '5B9BD5',
|
||||
'accent6' => '70AD47',
|
||||
'hlink' => '0563C1',
|
||||
'folHlink' => '954F72',
|
||||
];
|
||||
|
||||
public const COLOR_SCHEME_2007_2010_NAME = 'Office 2007-2010';
|
||||
public const COLOR_SCHEME_2007_2010 = [
|
||||
'dk1' => '000000',
|
||||
'lt1' => 'FFFFFF',
|
||||
'dk2' => '1F497D',
|
||||
'lt2' => 'EEECE1',
|
||||
'accent1' => '4F81BD',
|
||||
'accent2' => 'C0504D',
|
||||
'accent3' => '9BBB59',
|
||||
'accent4' => '8064A2',
|
||||
'accent5' => '4BACC6',
|
||||
'accent6' => 'F79646',
|
||||
'hlink' => '0000FF',
|
||||
'folHlink' => '800080',
|
||||
];
|
||||
|
||||
/** @var string[] */
|
||||
private $themeColors = self::COLOR_SCHEME_2007_2010;
|
||||
|
||||
/** @var string */
|
||||
private $majorFontLatin = 'Cambria';
|
||||
|
||||
/** @var string */
|
||||
private $majorFontEastAsian = '';
|
||||
|
||||
/** @var string */
|
||||
private $majorFontComplexScript = '';
|
||||
|
||||
/** @var string */
|
||||
private $minorFontLatin = 'Calibri';
|
||||
|
||||
/** @var string */
|
||||
private $minorFontEastAsian = '';
|
||||
|
||||
/** @var string */
|
||||
private $minorFontComplexScript = '';
|
||||
|
||||
/**
|
||||
* Map of Major (header) fonts to write.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $majorFontSubstitutions = self::FONTS_TIMES_SUBSTITUTIONS;
|
||||
|
||||
/**
|
||||
* Map of Minor (body) fonts to write.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $minorFontSubstitutions = self::FONTS_ARIAL_SUBSTITUTIONS;
|
||||
|
||||
public const FONTS_TIMES_SUBSTITUTIONS = [
|
||||
'Jpan' => 'MS Pゴシック',
|
||||
'Hang' => '맑은 고딕',
|
||||
'Hans' => '宋体',
|
||||
'Hant' => '新細明體',
|
||||
'Arab' => 'Times New Roman',
|
||||
'Hebr' => 'Times New Roman',
|
||||
'Thai' => 'Tahoma',
|
||||
'Ethi' => 'Nyala',
|
||||
'Beng' => 'Vrinda',
|
||||
'Gujr' => 'Shruti',
|
||||
'Khmr' => 'MoolBoran',
|
||||
'Knda' => 'Tunga',
|
||||
'Guru' => 'Raavi',
|
||||
'Cans' => 'Euphemia',
|
||||
'Cher' => 'Plantagenet Cherokee',
|
||||
'Yiii' => 'Microsoft Yi Baiti',
|
||||
'Tibt' => 'Microsoft Himalaya',
|
||||
'Thaa' => 'MV Boli',
|
||||
'Deva' => 'Mangal',
|
||||
'Telu' => 'Gautami',
|
||||
'Taml' => 'Latha',
|
||||
'Syrc' => 'Estrangelo Edessa',
|
||||
'Orya' => 'Kalinga',
|
||||
'Mlym' => 'Kartika',
|
||||
'Laoo' => 'DokChampa',
|
||||
'Sinh' => 'Iskoola Pota',
|
||||
'Mong' => 'Mongolian Baiti',
|
||||
'Viet' => 'Times New Roman',
|
||||
'Uigh' => 'Microsoft Uighur',
|
||||
'Geor' => 'Sylfaen',
|
||||
];
|
||||
|
||||
public const FONTS_ARIAL_SUBSTITUTIONS = [
|
||||
'Jpan' => 'MS Pゴシック',
|
||||
'Hang' => '맑은 고딕',
|
||||
'Hans' => '宋体',
|
||||
'Hant' => '新細明體',
|
||||
'Arab' => 'Arial',
|
||||
'Hebr' => 'Arial',
|
||||
'Thai' => 'Tahoma',
|
||||
'Ethi' => 'Nyala',
|
||||
'Beng' => 'Vrinda',
|
||||
'Gujr' => 'Shruti',
|
||||
'Khmr' => 'DaunPenh',
|
||||
'Knda' => 'Tunga',
|
||||
'Guru' => 'Raavi',
|
||||
'Cans' => 'Euphemia',
|
||||
'Cher' => 'Plantagenet Cherokee',
|
||||
'Yiii' => 'Microsoft Yi Baiti',
|
||||
'Tibt' => 'Microsoft Himalaya',
|
||||
'Thaa' => 'MV Boli',
|
||||
'Deva' => 'Mangal',
|
||||
'Telu' => 'Gautami',
|
||||
'Taml' => 'Latha',
|
||||
'Syrc' => 'Estrangelo Edessa',
|
||||
'Orya' => 'Kalinga',
|
||||
'Mlym' => 'Kartika',
|
||||
'Laoo' => 'DokChampa',
|
||||
'Sinh' => 'Iskoola Pota',
|
||||
'Mong' => 'Mongolian Baiti',
|
||||
'Viet' => 'Arial',
|
||||
'Uigh' => 'Microsoft Uighur',
|
||||
'Geor' => 'Sylfaen',
|
||||
];
|
||||
|
||||
public function getThemeColors(): array
|
||||
{
|
||||
return $this->themeColors;
|
||||
}
|
||||
|
||||
public function setThemeColor(string $key, string $value): self
|
||||
{
|
||||
$this->themeColors[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getThemeColorName(): string
|
||||
{
|
||||
return $this->themeColorName;
|
||||
}
|
||||
|
||||
public function setThemeColorName(string $name, ?array $themeColors = null): self
|
||||
{
|
||||
$this->themeColorName = $name;
|
||||
if ($name === self::COLOR_SCHEME_2007_2010_NAME) {
|
||||
$themeColors = $themeColors ?? self::COLOR_SCHEME_2007_2010;
|
||||
} elseif ($name === self::COLOR_SCHEME_2013_PLUS_NAME) {
|
||||
$themeColors = $themeColors ?? self::COLOR_SCHEME_2013_PLUS;
|
||||
}
|
||||
if ($themeColors !== null) {
|
||||
$this->themeColors = $themeColors;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMajorFontLatin(): string
|
||||
{
|
||||
return $this->majorFontLatin;
|
||||
}
|
||||
|
||||
public function getMajorFontEastAsian(): string
|
||||
{
|
||||
return $this->majorFontEastAsian;
|
||||
}
|
||||
|
||||
public function getMajorFontComplexScript(): string
|
||||
{
|
||||
return $this->majorFontComplexScript;
|
||||
}
|
||||
|
||||
public function getMajorFontSubstitutions(): array
|
||||
{
|
||||
return $this->majorFontSubstitutions;
|
||||
}
|
||||
|
||||
/** @param null|array $substitutions */
|
||||
public function setMajorFontValues(?string $latin, ?string $eastAsian, ?string $complexScript, $substitutions): self
|
||||
{
|
||||
if (!empty($latin)) {
|
||||
$this->majorFontLatin = $latin;
|
||||
}
|
||||
if ($eastAsian !== null) {
|
||||
$this->majorFontEastAsian = $eastAsian;
|
||||
}
|
||||
if ($complexScript !== null) {
|
||||
$this->majorFontComplexScript = $complexScript;
|
||||
}
|
||||
if ($substitutions !== null) {
|
||||
$this->majorFontSubstitutions = $substitutions;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMinorFontLatin(): string
|
||||
{
|
||||
return $this->minorFontLatin;
|
||||
}
|
||||
|
||||
public function getMinorFontEastAsian(): string
|
||||
{
|
||||
return $this->minorFontEastAsian;
|
||||
}
|
||||
|
||||
public function getMinorFontComplexScript(): string
|
||||
{
|
||||
return $this->minorFontComplexScript;
|
||||
}
|
||||
|
||||
public function getMinorFontSubstitutions(): array
|
||||
{
|
||||
return $this->minorFontSubstitutions;
|
||||
}
|
||||
|
||||
/** @param null|array $substitutions */
|
||||
public function setMinorFontValues(?string $latin, ?string $eastAsian, ?string $complexScript, $substitutions): self
|
||||
{
|
||||
if (!empty($latin)) {
|
||||
$this->minorFontLatin = $latin;
|
||||
}
|
||||
if ($eastAsian !== null) {
|
||||
$this->minorFontEastAsian = $eastAsian;
|
||||
}
|
||||
if ($complexScript !== null) {
|
||||
$this->minorFontComplexScript = $complexScript;
|
||||
}
|
||||
if ($substitutions !== null) {
|
||||
$this->minorFontSubstitutions = $substitutions;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getThemeFontName(): string
|
||||
{
|
||||
return $this->themeFontName;
|
||||
}
|
||||
|
||||
public function setThemeFontName(?string $name): self
|
||||
{
|
||||
if (!empty($name)) {
|
||||
$this->themeFontName = $name;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -321,7 +321,7 @@ class AutoFilter
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function filterTestInSimpleDataSet($cellValue, $dataSet)
|
||||
protected static function filterTestInSimpleDataSet($cellValue, $dataSet)
|
||||
{
|
||||
$dataSetValues = $dataSet['filterValues'];
|
||||
$blanks = $dataSet['blanks'];
|
||||
@ -340,7 +340,7 @@ class AutoFilter
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function filterTestInDateGroupSet($cellValue, $dataSet)
|
||||
protected static function filterTestInDateGroupSet($cellValue, $dataSet)
|
||||
{
|
||||
$dateSet = $dataSet['filterValues'];
|
||||
$blanks = $dataSet['blanks'];
|
||||
@ -384,7 +384,7 @@ class AutoFilter
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function filterTestInCustomDataSet($cellValue, $ruleSet)
|
||||
protected static function filterTestInCustomDataSet($cellValue, $ruleSet)
|
||||
{
|
||||
/** @var array[] */
|
||||
$dataSet = $ruleSet['filterRules'];
|
||||
@ -509,7 +509,7 @@ class AutoFilter
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function filterTestInPeriodDateSet($cellValue, $monthSet)
|
||||
protected static function filterTestInPeriodDateSet($cellValue, $monthSet)
|
||||
{
|
||||
// Blank cells are always ignored, so return a FALSE
|
||||
if (($cellValue == '') || ($cellValue === null)) {
|
||||
|
@ -197,21 +197,6 @@ class PageMargins
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement PHP __clone to create a deep clone, not just a shallow copy.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
foreach ($vars as $key => $value) {
|
||||
if (is_object($value)) {
|
||||
$this->$key = clone $value;
|
||||
} else {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromCentimeters(float $value): float
|
||||
{
|
||||
return $value / 2.54;
|
||||
|
@ -885,19 +885,4 @@ class PageSetup
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement PHP __clone to create a deep clone, not just a shallow copy.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
foreach ($vars as $key => $value) {
|
||||
if (is_object($value)) {
|
||||
$this->$key = clone $value;
|
||||
} else {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,19 +175,4 @@ class SheetView
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement PHP __clone to create a deep clone, not just a shallow copy.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
foreach ($vars as $key => $value) {
|
||||
if (is_object($value)) {
|
||||
$this->$key = clone $value;
|
||||
} else {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ class Table
|
||||
|
||||
private function updateStructuredReferencesInCells(Worksheet $worksheet, string $newName): void
|
||||
{
|
||||
$pattern = '/' . preg_quote($this->name) . '\[/mui';
|
||||
$pattern = '/' . preg_quote($this->name, '/') . '\[/mui';
|
||||
|
||||
foreach ($worksheet->getCoordinates(false) as $coordinate) {
|
||||
$cell = $worksheet->getCell($coordinate);
|
||||
@ -196,7 +196,7 @@ class Table
|
||||
|
||||
private function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $newName): void
|
||||
{
|
||||
$pattern = '/' . preg_quote($this->name) . '\[/mui';
|
||||
$pattern = '/' . preg_quote($this->name, '/') . '\[/mui';
|
||||
|
||||
foreach ($spreadsheet->getNamedFormulae() as $namedFormula) {
|
||||
$formula = $namedFormula->getValue();
|
||||
|
@ -225,7 +225,7 @@ class Column
|
||||
|
||||
private static function updateStructuredReferencesInCells(Worksheet $worksheet, string $oldTitle, string $newTitle): void
|
||||
{
|
||||
$pattern = '/\[(@?)' . preg_quote($oldTitle) . '\]/mui';
|
||||
$pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui';
|
||||
|
||||
foreach ($worksheet->getCoordinates(false) as $coordinate) {
|
||||
$cell = $worksheet->getCell($coordinate);
|
||||
@ -241,7 +241,7 @@ class Column
|
||||
|
||||
private static function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $oldTitle, string $newTitle): void
|
||||
{
|
||||
$pattern = '/\[(@?)' . preg_quote($oldTitle) . '\]/mui';
|
||||
$pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui';
|
||||
|
||||
foreach ($spreadsheet->getNamedFormulae() as $namedFormula) {
|
||||
$formula = $namedFormula->getValue();
|
||||
|
@ -53,6 +53,9 @@ class Validations
|
||||
return self::validateCellRange($cellRange);
|
||||
}
|
||||
|
||||
private const SETMAXROW = '${1}1:${2}' . AddressRange::MAX_ROW;
|
||||
private const SETMAXCOL = 'A${1}:' . AddressRange::MAX_COLUMN . '${2}';
|
||||
|
||||
/**
|
||||
* Validate a cell range.
|
||||
*
|
||||
@ -69,7 +72,7 @@ class Validations
|
||||
// or Row ranges like '1:3' to 'A1:XFD3'
|
||||
$addressRange = (string) preg_replace(
|
||||
['/^([A-Z]+):([A-Z]+)$/i', '/^(\\d+):(\\d+)$/'],
|
||||
['${1}1:${2}1048576', 'A${1}:XFD${2}'],
|
||||
[self::SETMAXROW, self::SETMAXCOL],
|
||||
$addressRange
|
||||
);
|
||||
|
||||
|
@ -543,9 +543,18 @@ class Worksheet implements IComparable
|
||||
*/
|
||||
public function getColumnDimensions()
|
||||
{
|
||||
/** @var callable */
|
||||
$callable = [self::class, 'columnDimensionCompare'];
|
||||
uasort($this->columnDimensions, $callable);
|
||||
|
||||
return $this->columnDimensions;
|
||||
}
|
||||
|
||||
private static function columnDimensionCompare(ColumnDimension $a, ColumnDimension $b): int
|
||||
{
|
||||
return $a->getColumnNumeric() - $b->getColumnNumeric();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default column dimension.
|
||||
*
|
||||
@ -1806,9 +1815,15 @@ class Worksheet implements IComparable
|
||||
public function getBreaks()
|
||||
{
|
||||
$breaks = [];
|
||||
/** @var callable */
|
||||
$compareFunction = [self::class, 'compareRowBreaks'];
|
||||
uksort($this->rowBreaks, $compareFunction);
|
||||
foreach ($this->rowBreaks as $break) {
|
||||
$breaks[$break->getCoordinate()] = self::BREAK_ROW;
|
||||
}
|
||||
/** @var callable */
|
||||
$compareFunction = [self::class, 'compareColumnBreaks'];
|
||||
uksort($this->columnBreaks, $compareFunction);
|
||||
foreach ($this->columnBreaks as $break) {
|
||||
$breaks[$break->getCoordinate()] = self::BREAK_COLUMN;
|
||||
}
|
||||
@ -1823,16 +1838,40 @@ class Worksheet implements IComparable
|
||||
*/
|
||||
public function getRowBreaks()
|
||||
{
|
||||
/** @var callable */
|
||||
$compareFunction = [self::class, 'compareRowBreaks'];
|
||||
uksort($this->rowBreaks, $compareFunction);
|
||||
|
||||
return $this->rowBreaks;
|
||||
}
|
||||
|
||||
protected static function compareRowBreaks(string $coordinate1, string $coordinate2): int
|
||||
{
|
||||
$row1 = Coordinate::indexesFromString($coordinate1)[1];
|
||||
$row2 = Coordinate::indexesFromString($coordinate2)[1];
|
||||
|
||||
return $row1 - $row2;
|
||||
}
|
||||
|
||||
protected static function compareColumnBreaks(string $coordinate1, string $coordinate2): int
|
||||
{
|
||||
$column1 = Coordinate::indexesFromString($coordinate1)[0];
|
||||
$column2 = Coordinate::indexesFromString($coordinate2)[0];
|
||||
|
||||
return $column1 - $column2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get row breaks.
|
||||
* Get column breaks.
|
||||
*
|
||||
* @return PageBreak[]
|
||||
*/
|
||||
public function getColumnBreaks()
|
||||
{
|
||||
/** @var callable */
|
||||
$compareFunction = [self::class, 'compareColumnBreaks'];
|
||||
uksort($this->columnBreaks, $compareFunction);
|
||||
|
||||
return $this->columnBreaks;
|
||||
}
|
||||
|
||||
@ -2448,12 +2487,12 @@ class Worksheet implements IComparable
|
||||
/**
|
||||
* Insert a new row, updating all possible related data.
|
||||
*
|
||||
* @param int $before Insert before this one
|
||||
* @param int $numberOfRows Number of rows to insert
|
||||
* @param int $before Insert before this row number
|
||||
* @param int $numberOfRows Number of new rows to insert
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function insertNewRowBefore($before, $numberOfRows = 1)
|
||||
public function insertNewRowBefore(int $before, int $numberOfRows = 1)
|
||||
{
|
||||
if ($before >= 1) {
|
||||
$objReferenceHelper = ReferenceHelper::getInstance();
|
||||
@ -2468,12 +2507,12 @@ class Worksheet implements IComparable
|
||||
/**
|
||||
* Insert a new column, updating all possible related data.
|
||||
*
|
||||
* @param string $before Insert before this one, eg: 'A'
|
||||
* @param int $numberOfColumns Number of columns to insert
|
||||
* @param string $before Insert before this column Name, eg: 'A'
|
||||
* @param int $numberOfColumns Number of new columns to insert
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function insertNewColumnBefore($before, $numberOfColumns = 1)
|
||||
public function insertNewColumnBefore(string $before, int $numberOfColumns = 1)
|
||||
{
|
||||
if (!is_numeric($before)) {
|
||||
$objReferenceHelper = ReferenceHelper::getInstance();
|
||||
@ -2488,12 +2527,12 @@ class Worksheet implements IComparable
|
||||
/**
|
||||
* Insert a new column, updating all possible related data.
|
||||
*
|
||||
* @param int $beforeColumnIndex Insert before this one (numeric column coordinate of the cell)
|
||||
* @param int $numberOfColumns Number of columns to insert
|
||||
* @param int $beforeColumnIndex Insert before this column ID (numeric column coordinate of the cell)
|
||||
* @param int $numberOfColumns Number of new columns to insert
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function insertNewColumnBeforeByIndex($beforeColumnIndex, $numberOfColumns = 1)
|
||||
public function insertNewColumnBeforeByIndex(int $beforeColumnIndex, int $numberOfColumns = 1)
|
||||
{
|
||||
if ($beforeColumnIndex >= 1) {
|
||||
return $this->insertNewColumnBefore(Coordinate::stringFromColumnIndex($beforeColumnIndex), $numberOfColumns);
|
||||
@ -2505,12 +2544,12 @@ class Worksheet implements IComparable
|
||||
/**
|
||||
* Delete a row, updating all possible related data.
|
||||
*
|
||||
* @param int $row Remove starting with this one
|
||||
* @param int $row Remove rows, starting with this row number
|
||||
* @param int $numberOfRows Number of rows to remove
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeRow($row, $numberOfRows = 1)
|
||||
public function removeRow(int $row, int $numberOfRows = 1)
|
||||
{
|
||||
if ($row < 1) {
|
||||
throw new Exception('Rows to be deleted should at least start from row 1.');
|
||||
@ -2561,12 +2600,12 @@ class Worksheet implements IComparable
|
||||
/**
|
||||
* Remove a column, updating all possible related data.
|
||||
*
|
||||
* @param string $column Remove starting with this one, eg: 'A'
|
||||
* @param string $column Remove columns starting with this column name, eg: 'A'
|
||||
* @param int $numberOfColumns Number of columns to remove
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeColumn($column, $numberOfColumns = 1)
|
||||
public function removeColumn(string $column, int $numberOfColumns = 1)
|
||||
{
|
||||
if (is_numeric($column)) {
|
||||
throw new Exception('Column references should not be numeric.');
|
||||
@ -2623,12 +2662,12 @@ class Worksheet implements IComparable
|
||||
/**
|
||||
* Remove a column, updating all possible related data.
|
||||
*
|
||||
* @param int $columnIndex Remove starting with this one (numeric column coordinate of the cell)
|
||||
* @param int $columnIndex Remove starting with this column Index (numeric column coordinate)
|
||||
* @param int $numColumns Number of columns to remove
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeColumnByIndex($columnIndex, $numColumns = 1)
|
||||
public function removeColumnByIndex(int $columnIndex, int $numColumns = 1)
|
||||
{
|
||||
if ($columnIndex >= 1) {
|
||||
return $this->removeColumn(Coordinate::stringFromColumnIndex($columnIndex), $numColumns);
|
||||
@ -2988,21 +3027,58 @@ class Worksheet implements IComparable
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $nullValue
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Calculation\Exception
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function cellToArray(Cell $cell, bool $calculateFormulas, bool $formatData, $nullValue)
|
||||
{
|
||||
$returnValue = $nullValue;
|
||||
|
||||
if ($cell->getValue() !== null) {
|
||||
if ($cell->getValue() instanceof RichText) {
|
||||
$returnValue = $cell->getValue()->getPlainText();
|
||||
} else {
|
||||
$returnValue = ($calculateFormulas) ? $cell->getCalculatedValue() : $cell->getValue();
|
||||
}
|
||||
|
||||
if ($formatData) {
|
||||
$style = $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex());
|
||||
$returnValue = NumberFormat::toFormattedString(
|
||||
$returnValue,
|
||||
$style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create array from a range of cells.
|
||||
*
|
||||
* @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
|
||||
* @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
|
||||
* @param bool $calculateFormulas Should formulas be calculated?
|
||||
* @param bool $formatData Should formatting be applied to cell values?
|
||||
* @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
|
||||
* True - Return rows and columns indexed by their actual row and column IDs
|
||||
*
|
||||
* @return array
|
||||
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
|
||||
* True - Don't return values for rows/columns that are defined as hidden.
|
||||
*/
|
||||
public function rangeToArray($range, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
|
||||
{
|
||||
// Returnvalue
|
||||
public function rangeToArray(
|
||||
string $range,
|
||||
$nullValue = null,
|
||||
bool $calculateFormulas = true,
|
||||
bool $formatData = true,
|
||||
bool $returnCellRef = false,
|
||||
bool $ignoreHidden = false
|
||||
): array {
|
||||
$range = Validations::validateCellOrCellRange($range);
|
||||
|
||||
$returnValue = [];
|
||||
// Identify the range that we need to extract from the worksheet
|
||||
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range);
|
||||
@ -3015,42 +3091,23 @@ class Worksheet implements IComparable
|
||||
// Loop through rows
|
||||
$r = -1;
|
||||
for ($row = $minRow; $row <= $maxRow; ++$row) {
|
||||
$rRef = $returnCellRef ? $row : ++$r;
|
||||
if (($ignoreHidden === true) && ($this->getRowDimension($row)->getVisible() === false)) {
|
||||
continue;
|
||||
}
|
||||
$rowRef = $returnCellRef ? $row : ++$r;
|
||||
$c = -1;
|
||||
// Loop through columns in the current row
|
||||
for ($col = $minCol; $col != $maxCol; ++$col) {
|
||||
$cRef = $returnCellRef ? $col : ++$c;
|
||||
for ($col = $minCol; $col !== $maxCol; ++$col) {
|
||||
if (($ignoreHidden === true) && ($this->getColumnDimension($col)->getVisible() === false)) {
|
||||
continue;
|
||||
}
|
||||
$columnRef = $returnCellRef ? $col : ++$c;
|
||||
// Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen
|
||||
// so we test and retrieve directly against cellCollection
|
||||
$cell = $this->cellCollection->get($col . $row);
|
||||
//if ($this->cellCollection->has($col . $row)) {
|
||||
$cell = $this->cellCollection->get("{$col}{$row}");
|
||||
$returnValue[$rowRef][$columnRef] = $nullValue;
|
||||
if ($cell !== null) {
|
||||
// Cell exists
|
||||
if ($cell->getValue() !== null) {
|
||||
if ($cell->getValue() instanceof RichText) {
|
||||
$returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText();
|
||||
} else {
|
||||
if ($calculateFormulas) {
|
||||
$returnValue[$rRef][$cRef] = $cell->getCalculatedValue();
|
||||
} else {
|
||||
$returnValue[$rRef][$cRef] = $cell->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
if ($formatData) {
|
||||
$style = $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex());
|
||||
$returnValue[$rRef][$cRef] = NumberFormat::toFormattedString(
|
||||
$returnValue[$rRef][$cRef],
|
||||
$style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Cell holds a NULL
|
||||
$returnValue[$rRef][$cRef] = $nullValue;
|
||||
}
|
||||
} else {
|
||||
// Cell doesn't exist
|
||||
$returnValue[$rRef][$cRef] = $nullValue;
|
||||
$returnValue[$rowRef][$columnRef] = $this->cellToArray($cell, $calculateFormulas, $formatData, $nullValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3103,11 +3160,17 @@ class Worksheet implements IComparable
|
||||
* @param bool $formatData Should formatting be applied to cell values?
|
||||
* @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
|
||||
* True - Return rows and columns indexed by their actual row and column IDs
|
||||
*
|
||||
* @return array
|
||||
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
|
||||
* True - Don't return values for rows/columns that are defined as hidden.
|
||||
*/
|
||||
public function namedRangeToArray(string $definedName, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
|
||||
{
|
||||
public function namedRangeToArray(
|
||||
string $definedName,
|
||||
$nullValue = null,
|
||||
bool $calculateFormulas = true,
|
||||
bool $formatData = true,
|
||||
bool $returnCellRef = false,
|
||||
bool $ignoreHidden = false
|
||||
): array {
|
||||
$retVal = [];
|
||||
$namedRange = $this->validateNamedRange($definedName);
|
||||
if ($namedRange !== null) {
|
||||
@ -3115,7 +3178,7 @@ class Worksheet implements IComparable
|
||||
$cellRange = str_replace('$', '', $cellRange);
|
||||
$workSheet = $namedRange->getWorksheet();
|
||||
if ($workSheet !== null) {
|
||||
$retVal = $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
|
||||
$retVal = $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3130,11 +3193,16 @@ class Worksheet implements IComparable
|
||||
* @param bool $formatData Should formatting be applied to cell values?
|
||||
* @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
|
||||
* True - Return rows and columns indexed by their actual row and column IDs
|
||||
*
|
||||
* @return array
|
||||
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
|
||||
* True - Don't return values for rows/columns that are defined as hidden.
|
||||
*/
|
||||
public function toArray($nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
|
||||
{
|
||||
public function toArray(
|
||||
$nullValue = null,
|
||||
bool $calculateFormulas = true,
|
||||
bool $formatData = true,
|
||||
bool $returnCellRef = false,
|
||||
bool $ignoreHidden = false
|
||||
): array {
|
||||
// Garbage collect...
|
||||
$this->garbageCollect();
|
||||
|
||||
@ -3143,7 +3211,7 @@ class Worksheet implements IComparable
|
||||
$maxRow = $this->getHighestRow();
|
||||
|
||||
// Return
|
||||
return $this->rangeToArray('A1:' . $maxCol . $maxRow, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
|
||||
return $this->rangeToArray("A1:{$maxCol}{$maxRow}", $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,9 +7,11 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Document\Properties;
|
||||
use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
||||
use PhpOffice\PhpSpreadsheet\RichText\Run;
|
||||
use PhpOffice\PhpSpreadsheet\Settings;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\File;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Font as SharedFont;
|
||||
@ -342,13 +344,21 @@ class Html extends BaseWriter
|
||||
|
||||
private static function generateMeta(?string $val, string $desc): string
|
||||
{
|
||||
return $val
|
||||
return ($val || $val === '0')
|
||||
? (' <meta name="' . $desc . '" content="' . htmlspecialchars($val, Settings::htmlEntityFlags()) . '" />' . PHP_EOL)
|
||||
: '';
|
||||
}
|
||||
|
||||
public const BODY_LINE = ' <body>' . PHP_EOL;
|
||||
|
||||
private const CUSTOM_TO_META = [
|
||||
Properties::PROPERTY_TYPE_BOOLEAN => 'bool',
|
||||
Properties::PROPERTY_TYPE_DATE => 'date',
|
||||
Properties::PROPERTY_TYPE_FLOAT => 'float',
|
||||
Properties::PROPERTY_TYPE_INTEGER => 'int',
|
||||
Properties::PROPERTY_TYPE_STRING => 'string',
|
||||
];
|
||||
|
||||
/**
|
||||
* Generate HTML header.
|
||||
*
|
||||
@ -374,6 +384,36 @@ class Html extends BaseWriter
|
||||
$html .= self::generateMeta($properties->getCategory(), 'category');
|
||||
$html .= self::generateMeta($properties->getCompany(), 'company');
|
||||
$html .= self::generateMeta($properties->getManager(), 'manager');
|
||||
$html .= self::generateMeta($properties->getLastModifiedBy(), 'lastModifiedBy');
|
||||
$date = Date::dateTimeFromTimestamp((string) $properties->getCreated());
|
||||
$date->setTimeZone(Date::getDefaultOrLocalTimeZone());
|
||||
$html .= self::generateMeta($date->format(DATE_W3C), 'created');
|
||||
$date = Date::dateTimeFromTimestamp((string) $properties->getModified());
|
||||
$date->setTimeZone(Date::getDefaultOrLocalTimeZone());
|
||||
$html .= self::generateMeta($date->format(DATE_W3C), 'modified');
|
||||
|
||||
$customProperties = $properties->getCustomProperties();
|
||||
foreach ($customProperties as $customProperty) {
|
||||
$propertyValue = $properties->getCustomPropertyValue($customProperty);
|
||||
$propertyType = $properties->getCustomPropertyType($customProperty);
|
||||
$propertyQualifier = self::CUSTOM_TO_META[$propertyType] ?? null;
|
||||
if ($propertyQualifier !== null) {
|
||||
if ($propertyType === Properties::PROPERTY_TYPE_BOOLEAN) {
|
||||
$propertyValue = $propertyValue ? '1' : '0';
|
||||
} elseif ($propertyType === Properties::PROPERTY_TYPE_DATE) {
|
||||
$date = Date::dateTimeFromTimestamp((string) $propertyValue);
|
||||
$date->setTimeZone(Date::getDefaultOrLocalTimeZone());
|
||||
$propertyValue = $date->format(DATE_W3C);
|
||||
} else {
|
||||
$propertyValue = (string) $propertyValue;
|
||||
}
|
||||
$html .= self::generateMeta($propertyValue, "custom.$propertyQualifier.$customProperty");
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($properties->getHyperlinkBase())) {
|
||||
$html .= ' <base href="' . $properties->getHyperlinkBase() . '" />' . PHP_EOL;
|
||||
}
|
||||
|
||||
$html .= $includeStyles ? $this->generateStyles(true) : $this->generatePageDeclarations(true);
|
||||
|
||||
@ -693,7 +733,8 @@ class Html extends BaseWriter
|
||||
// max-width: 100% ensures that image doesnt overflow containing cell
|
||||
// width: X sets width of supplied image.
|
||||
// As a result, images bigger than cell will be contained and images smaller will not get stretched
|
||||
$html .= '<img alt="' . $filedesc . '" src="' . $dataUri . '" style="max-width:100%;width:' . $drawing->getWidth() . 'px;" />';
|
||||
$html .= '<img alt="' . $filedesc . '" src="' . $dataUri . '" style="max-width:100%;width:' . $drawing->getWidth() . 'px;left: ' .
|
||||
$drawing->getOffsetX() . 'px; top: ' . $drawing->getOffsetY() . 'px;position: absolute; z-index: 1;" />';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ use PhpOffice\PhpSpreadsheet\Writer\Ods\Settings;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Ods\Styles;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Ods\Thumbnails;
|
||||
use ZipStream\Exception\OverflowException;
|
||||
use ZipStream\Option\Archive;
|
||||
use ZipStream\ZipStream;
|
||||
|
||||
class Ods extends BaseWriter
|
||||
@ -158,11 +157,7 @@ class Ods extends BaseWriter
|
||||
}
|
||||
|
||||
// Create new ZIP stream
|
||||
$options = new Archive();
|
||||
$options->setEnableZip64(false);
|
||||
$options->setOutputStream($this->fileHandle);
|
||||
|
||||
return new ZipStream(null, $options);
|
||||
return ZipStream0::newZipStream($this->fileHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -126,7 +126,16 @@ class Content extends WriterPart
|
||||
$objWriter->writeAttribute('table:name', $spreadsheet->getSheet($sheetIndex)->getTitle());
|
||||
$objWriter->writeAttribute('table:style-name', Style::TABLE_STYLE_PREFIX . (string) ($sheetIndex + 1));
|
||||
$objWriter->writeElement('office:forms');
|
||||
$lastColumn = 0;
|
||||
foreach ($spreadsheet->getSheet($sheetIndex)->getColumnDimensions() as $columnDimension) {
|
||||
$thisColumn = $columnDimension->getColumnNumeric();
|
||||
$emptyColumns = $thisColumn - $lastColumn - 1;
|
||||
if ($emptyColumns > 0) {
|
||||
$objWriter->startElement('table:table-column');
|
||||
$objWriter->writeAttribute('table:number-columns-repeated', (string) $emptyColumns);
|
||||
$objWriter->endElement();
|
||||
}
|
||||
$lastColumn = $thisColumn;
|
||||
$objWriter->startElement('table:table-column');
|
||||
$objWriter->writeAttribute(
|
||||
'table:style-name',
|
||||
|
@ -31,7 +31,6 @@ use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Workbook;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet;
|
||||
use ZipArchive;
|
||||
use ZipStream\Exception\OverflowException;
|
||||
use ZipStream\Option\Archive;
|
||||
use ZipStream\ZipStream;
|
||||
|
||||
class Xlsx extends BaseWriter
|
||||
@ -377,7 +376,7 @@ class Xlsx extends BaseWriter
|
||||
}
|
||||
|
||||
// Add theme to ZIP file
|
||||
$zipContent['xl/theme/theme1.xml'] = $this->getWriterPartTheme()->writeTheme();
|
||||
$zipContent['xl/theme/theme1.xml'] = $this->getWriterPartTheme()->writeTheme($this->spreadSheet);
|
||||
|
||||
// Add string table to ZIP file
|
||||
$zipContent['xl/sharedStrings.xml'] = $this->getWriterPartStringTable()->writeStringTable($this->stringTable);
|
||||
@ -546,11 +545,7 @@ class Xlsx extends BaseWriter
|
||||
|
||||
$this->openFileHandle($filename);
|
||||
|
||||
$options = new Archive();
|
||||
$options->setEnableZip64(false);
|
||||
$options->setOutputStream($this->fileHandle);
|
||||
|
||||
$this->zip = new ZipStream(null, $options);
|
||||
$this->zip = ZipStream0::newZipStream($this->fileHandle);
|
||||
|
||||
$this->addZipFiles($zipContent);
|
||||
|
||||
|
@ -14,6 +14,7 @@ use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\TrendLine;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
|
||||
|
||||
class Chart extends WriterPart
|
||||
@ -109,12 +110,20 @@ class Chart extends WriterPart
|
||||
$objWriter->endElement();
|
||||
|
||||
$objWriter->endElement(); // c:chart
|
||||
if ($chart->getNoFill()) {
|
||||
|
||||
$objWriter->startElement('c:spPr');
|
||||
if ($chart->getNoFill()) {
|
||||
$objWriter->startElement('a:noFill');
|
||||
$objWriter->endElement(); // a:noFill
|
||||
$objWriter->endElement(); // c:spPr
|
||||
}
|
||||
$fillColor = $chart->getFillColor();
|
||||
if ($fillColor->isUsable()) {
|
||||
$this->writeColor($objWriter, $fillColor);
|
||||
}
|
||||
$borderLines = $chart->getBorderLines();
|
||||
$this->writeLineStyles($objWriter, $borderLines);
|
||||
$this->writeEffects($objWriter, $borderLines);
|
||||
$objWriter->endElement(); // c:spPr
|
||||
|
||||
$this->writePrintSettings($objWriter);
|
||||
|
||||
@ -201,6 +210,17 @@ class Chart extends WriterPart
|
||||
$objWriter->writeAttribute('val', ($legend->getOverlay()) ? '1' : '0');
|
||||
$objWriter->endElement();
|
||||
|
||||
$objWriter->startElement('c:spPr');
|
||||
$fillColor = $legend->getFillColor();
|
||||
if ($fillColor->isUsable()) {
|
||||
$this->writeColor($objWriter, $fillColor);
|
||||
}
|
||||
$borderLines = $legend->getBorderLines();
|
||||
$this->writeLineStyles($objWriter, $borderLines);
|
||||
$this->writeEffects($objWriter, $borderLines);
|
||||
$objWriter->endElement(); // c:spPr
|
||||
|
||||
$legendText = $legend->getLegendText();
|
||||
$objWriter->startElement('c:txPr');
|
||||
$objWriter->startElement('a:bodyPr');
|
||||
$objWriter->endElement();
|
||||
@ -213,17 +233,21 @@ class Chart extends WriterPart
|
||||
$objWriter->writeAttribute('rtl', '0');
|
||||
|
||||
$objWriter->startElement('a:defRPr');
|
||||
$objWriter->endElement();
|
||||
$objWriter->endElement();
|
||||
if ($legendText !== null) {
|
||||
$this->writeColor($objWriter, $legendText->getFillColorObject());
|
||||
$this->writeEffects($objWriter, $legendText);
|
||||
}
|
||||
$objWriter->endElement(); // a:defRpr
|
||||
$objWriter->endElement(); // a:pPr
|
||||
|
||||
$objWriter->startElement('a:endParaRPr');
|
||||
$objWriter->writeAttribute('lang', 'en-US');
|
||||
$objWriter->endElement();
|
||||
$objWriter->endElement(); // a:endParaRPr
|
||||
|
||||
$objWriter->endElement();
|
||||
$objWriter->endElement();
|
||||
$objWriter->endElement(); // a:p
|
||||
$objWriter->endElement(); // c:txPr
|
||||
|
||||
$objWriter->endElement();
|
||||
$objWriter->endElement(); // c:legend
|
||||
}
|
||||
|
||||
/**
|
||||
@ -307,19 +331,26 @@ class Chart extends WriterPart
|
||||
$objWriter->startElement('c:hiLowLines');
|
||||
$objWriter->endElement();
|
||||
|
||||
$gapWidth = $plotArea->getGapWidth();
|
||||
$upBars = $plotArea->getUseUpBars();
|
||||
$downBars = $plotArea->getUseDownBars();
|
||||
if ($gapWidth !== null || $upBars || $downBars) {
|
||||
$objWriter->startElement('c:upDownBars');
|
||||
|
||||
if ($gapWidth !== null) {
|
||||
$objWriter->startElement('c:gapWidth');
|
||||
$objWriter->writeAttribute('val', '300');
|
||||
$objWriter->writeAttribute('val', "$gapWidth");
|
||||
$objWriter->endElement();
|
||||
|
||||
}
|
||||
if ($upBars) {
|
||||
$objWriter->startElement('c:upBars');
|
||||
$objWriter->endElement();
|
||||
|
||||
}
|
||||
if ($downBars) {
|
||||
$objWriter->startElement('c:downBars');
|
||||
$objWriter->endElement();
|
||||
|
||||
$objWriter->endElement();
|
||||
}
|
||||
$objWriter->endElement(); // c:upDownBars
|
||||
}
|
||||
}
|
||||
|
||||
// Generate 3 unique numbers to use for axId values
|
||||
@ -428,8 +459,8 @@ class Chart extends WriterPart
|
||||
}
|
||||
$objWriter->endElement(); // c:spPr
|
||||
}
|
||||
$fontColor = $chartLayout->getLabelFontColor();
|
||||
if ($fontColor && $fontColor->isUsable()) {
|
||||
$labelFont = $chartLayout->getLabelFont();
|
||||
if ($labelFont !== null) {
|
||||
$objWriter->startElement('c:txPr');
|
||||
|
||||
$objWriter->startElement('a:bodyPr');
|
||||
@ -445,14 +476,7 @@ class Chart extends WriterPart
|
||||
|
||||
$objWriter->startElement('a:lstStyle');
|
||||
$objWriter->endElement(); // a:lstStyle
|
||||
|
||||
$objWriter->startElement('a:p');
|
||||
$objWriter->startElement('a:pPr');
|
||||
$objWriter->startElement('a:defRPr');
|
||||
$this->writeColor($objWriter, $fontColor);
|
||||
$objWriter->endElement(); // a:defRPr
|
||||
$objWriter->endElement(); // a:pPr
|
||||
$objWriter->endElement(); // a:p
|
||||
$this->writeLabelFont($objWriter, $labelFont, $chartLayout->getLabelEffects());
|
||||
|
||||
$objWriter->endElement(); // c:txPr
|
||||
}
|
||||
@ -608,25 +632,24 @@ class Chart extends WriterPart
|
||||
}
|
||||
|
||||
$textRotation = $yAxis->getAxisOptionsProperty('textRotation');
|
||||
if (is_numeric($textRotation)) {
|
||||
$axisText = $yAxis->getAxisText();
|
||||
|
||||
if ($axisText !== null || is_numeric($textRotation)) {
|
||||
$objWriter->startElement('c:txPr');
|
||||
$objWriter->startElement('a:bodyPr');
|
||||
if (is_numeric($textRotation)) {
|
||||
$objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation));
|
||||
}
|
||||
$objWriter->endElement(); // a:bodyPr
|
||||
$objWriter->startElement('a:lstStyle');
|
||||
$objWriter->endElement(); // a:lstStyle
|
||||
$objWriter->startElement('a:p');
|
||||
$objWriter->startElement('a:pPr');
|
||||
$objWriter->startElement('a:defRPr');
|
||||
$objWriter->endElement(); // a:defRPr
|
||||
$objWriter->endElement(); // a:pPr
|
||||
$objWriter->endElement(); // a:p
|
||||
$this->writeLabelFont($objWriter, ($axisText === null) ? null : $axisText->getFont(), $axisText);
|
||||
$objWriter->endElement(); // c:txPr
|
||||
}
|
||||
|
||||
$objWriter->startElement('c:spPr');
|
||||
$this->writeColor($objWriter, $yAxis->getFillColorObject());
|
||||
$this->writeLineStyles($objWriter, $yAxis);
|
||||
$this->writeLineStyles($objWriter, $yAxis, $yAxis->getNoFill());
|
||||
$this->writeEffects($objWriter, $yAxis);
|
||||
$objWriter->endElement(); // spPr
|
||||
|
||||
@ -826,25 +849,26 @@ class Chart extends WriterPart
|
||||
}
|
||||
|
||||
$textRotation = $xAxis->getAxisOptionsProperty('textRotation');
|
||||
if (is_numeric($textRotation)) {
|
||||
$axisText = $xAxis->getAxisText();
|
||||
|
||||
if ($axisText !== null || is_numeric($textRotation)) {
|
||||
$objWriter->startElement('c:txPr');
|
||||
$objWriter->startElement('a:bodyPr');
|
||||
if (is_numeric($textRotation)) {
|
||||
$objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation));
|
||||
}
|
||||
$objWriter->endElement(); // a:bodyPr
|
||||
$objWriter->startElement('a:lstStyle');
|
||||
$objWriter->endElement(); // a:lstStyle
|
||||
$objWriter->startElement('a:p');
|
||||
$objWriter->startElement('a:pPr');
|
||||
$objWriter->startElement('a:defRPr');
|
||||
$objWriter->endElement(); // a:defRPr
|
||||
$objWriter->endElement(); // a:pPr
|
||||
$objWriter->endElement(); // a:p
|
||||
|
||||
$this->writeLabelFont($objWriter, ($axisText === null) ? null : $axisText->getFont(), $axisText);
|
||||
|
||||
$objWriter->endElement(); // c:txPr
|
||||
}
|
||||
|
||||
$objWriter->startElement('c:spPr');
|
||||
$this->writeColor($objWriter, $xAxis->getFillColorObject());
|
||||
$this->writeLineStyles($objWriter, $xAxis);
|
||||
$this->writeLineStyles($objWriter, $xAxis, $xAxis->getNoFill());
|
||||
$this->writeEffects($objWriter, $xAxis);
|
||||
$objWriter->endElement(); //end spPr
|
||||
|
||||
@ -1055,14 +1079,6 @@ class Chart extends WriterPart
|
||||
$labelFill = $plotLabel->getFillColorObject();
|
||||
$labelFill = ($labelFill instanceof ChartColor) ? $labelFill : null;
|
||||
}
|
||||
if ($plotLabel && $groupType !== DataSeries::TYPE_LINECHART) {
|
||||
$fillColor = $plotLabel->getFillColorObject();
|
||||
if ($fillColor !== null && !is_array($fillColor) && $fillColor->isUsable()) {
|
||||
$objWriter->startElement('c:spPr');
|
||||
$this->writeColor($objWriter, $fillColor);
|
||||
$objWriter->endElement(); // c:spPr
|
||||
}
|
||||
}
|
||||
|
||||
// Values
|
||||
$plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesIdx);
|
||||
@ -1094,6 +1110,12 @@ class Chart extends WriterPart
|
||||
$plotSeriesValues !== false
|
||||
) {
|
||||
$objWriter->startElement('c:spPr');
|
||||
if ($plotLabel && $groupType !== DataSeries::TYPE_LINECHART) {
|
||||
$fillColor = $plotLabel->getFillColorObject();
|
||||
if ($fillColor !== null && !is_array($fillColor) && $fillColor->isUsable()) {
|
||||
$this->writeColor($objWriter, $fillColor);
|
||||
}
|
||||
}
|
||||
$fillObject = $labelFill ?? $plotSeriesValues->getFillColorObject();
|
||||
$callLineStyles = true;
|
||||
if ($fillObject instanceof ChartColor && $fillObject->isUsable()) {
|
||||
@ -1398,7 +1420,7 @@ class Chart extends WriterPart
|
||||
$count = $plotSeriesValues->getPointCount();
|
||||
$source = $plotSeriesValues->getDataSource();
|
||||
$values = $plotSeriesValues->getDataValues();
|
||||
if ($count > 1 || ($count === 1 && "=$source" !== (string) $values[0])) {
|
||||
if ($count > 1 || ($count === 1 && array_key_exists(0, $values) && "=$source" !== (string) $values[0])) {
|
||||
$objWriter->startElement('c:' . $dataType . 'Cache');
|
||||
|
||||
if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
|
||||
@ -1770,4 +1792,51 @@ class Chart extends WriterPart
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function writeLabelFont(XMLWriter $objWriter, ?Font $labelFont, ?Properties $axisText): void
|
||||
{
|
||||
$objWriter->startElement('a:p');
|
||||
$objWriter->startElement('a:pPr');
|
||||
$objWriter->startElement('a:defRPr');
|
||||
if ($labelFont !== null) {
|
||||
$fontSize = $labelFont->getSize();
|
||||
if (is_numeric($fontSize)) {
|
||||
$fontSize *= (($fontSize < 100) ? 100 : 1);
|
||||
$objWriter->writeAttribute('sz', (string) $fontSize);
|
||||
}
|
||||
if ($labelFont->getBold() === true) {
|
||||
$objWriter->writeAttribute('b', '1');
|
||||
}
|
||||
if ($labelFont->getItalic() === true) {
|
||||
$objWriter->writeAttribute('i', '1');
|
||||
}
|
||||
$fontColor = $labelFont->getChartColor();
|
||||
if ($fontColor !== null) {
|
||||
$this->writeColor($objWriter, $fontColor);
|
||||
}
|
||||
}
|
||||
if ($axisText !== null) {
|
||||
$this->writeEffects($objWriter, $axisText);
|
||||
}
|
||||
if ($labelFont !== null) {
|
||||
if (!empty($labelFont->getLatin())) {
|
||||
$objWriter->startElement('a:latin');
|
||||
$objWriter->writeAttribute('typeface', $labelFont->getLatin());
|
||||
$objWriter->endElement();
|
||||
}
|
||||
if (!empty($labelFont->getEastAsian())) {
|
||||
$objWriter->startElement('a:eastAsian');
|
||||
$objWriter->writeAttribute('typeface', $labelFont->getEastAsian());
|
||||
$objWriter->endElement();
|
||||
}
|
||||
if (!empty($labelFont->getComplexScript())) {
|
||||
$objWriter->startElement('a:complexScript');
|
||||
$objWriter->writeAttribute('typeface', $labelFont->getComplexScript());
|
||||
$objWriter->endElement();
|
||||
}
|
||||
}
|
||||
$objWriter->endElement(); // a:defRPr
|
||||
$objWriter->endElement(); // a:pPr
|
||||
$objWriter->endElement(); // a:p
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,9 @@ class DocProps extends WriterPart
|
||||
// SharedDoc
|
||||
$objWriter->writeElement('SharedDoc', 'false');
|
||||
|
||||
// HyperlinkBase
|
||||
$objWriter->writeElement('HyperlinkBase', $spreadsheet->getProperties()->getHyperlinkBase());
|
||||
|
||||
// HyperlinksChanged
|
||||
$objWriter->writeElement('HyperlinksChanged', 'false');
|
||||
|
||||
|
@ -4,7 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
class FunctionPrefix
|
||||
{
|
||||
const XLFNREGEXP = '/(?:_xlfn\.)?((?:_xlws\.)?('
|
||||
const XLFNREGEXP = '/(?:_xlfn\.)?((?:_xlws\.)?\b('
|
||||
// functions added with Excel 2010
|
||||
. 'beta[.]dist'
|
||||
. '|beta[.]inv'
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user