diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 87d933e2..754a463b 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -124,6 +124,37 @@ jobs:
- name: Code style with PHP_CodeSniffer
run: ./vendor/bin/phpcs -q --report=checkstyle | cs2pr
+ versions:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Setup PHP, with composer and extensions
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 7.4
+ extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib
+ coverage: none
+ tools: cs2pr
+
+ - name: Get composer cache directory
+ id: composer-cache
+ run: echo "::set-output name=dir::$(composer config cache-files-dir)"
+
+ - name: Cache composer dependencies
+ uses: actions/cache@v2
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: Install dependencies
+ run: composer install --no-progress --prefer-dist --optimize-autoloader
+
+ - name: Code Version Compatibility check with PHP_CodeSniffer
+ run: ./vendor/bin/phpcs -q --report-width=200 --report=summary,full src/ --standard=PHPCompatibility --runtime-set testVersion 7.2-
+
phpstan:
runs-on: ubuntu-latest
steps:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5fcace6f..aac3a32d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Added
+- Implemented basic AutoFiltering for Ods Reader and Writer [PR #2053](https://github.com/PHPOffice/PhpSpreadsheet/pull/2053)
+- Implemented basic AutoFiltering for Gnumeric Reader [PR #2055](https://github.com/PHPOffice/PhpSpreadsheet/pull/2055)
- Improved support for Row and Column ranges in formulae [Issue #1755](https://github.com/PHPOffice/PhpSpreadsheet/issues/1755) [PR #2028](https://github.com/PHPOffice/PhpSpreadsheet/pull/2028)
+- Implemented URLENCODE() Web Function
- Implemented the CHITEST(), CHISQ.DIST() and CHISQ.INV() and equivalent Statistical functions, for both left- and right-tailed distributions.
- Support for ActiveSheet and SelectedCells in the ODS Reader and Writer. [PR #1908](https://github.com/PHPOffice/PhpSpreadsheet/pull/1908)
- Support for notContainsText Conditional Style in xlsx [Issue #984](https://github.com/PHPOffice/PhpSpreadsheet/issues/984)
@@ -27,7 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Nothing.
### Fixed
-
+- Correctly handle absolute A1 references when converting to R1C1 format [PR #2060](https://github.com/PHPOffice/PhpSpreadsheet/pull/2060)
+- Correct default fill style for conditional without a pattern defined [Issue #2035](https://github.com/PHPOffice/PhpSpreadsheet/issues/2035) [PR #2050](https://github.com/PHPOffice/PhpSpreadsheet/pull/2050)
- Fixed issue where array key check for existince before accessing arrays in Xlsx.php. [PR #1970](https://github.com/PHPOffice/PhpSpreadsheet/pull/1970)
- Fixed issue with quoted strings in number format mask rendered with toFormattedString() [Issue 1972#](https://github.com/PHPOffice/PhpSpreadsheet/issues/1972) [PR #1978](https://github.com/PHPOffice/PhpSpreadsheet/pull/1978)
- Fixed issue with percentage formats in number format mask rendered with toFormattedString() [Issue 1929#](https://github.com/PHPOffice/PhpSpreadsheet/issues/1929) [PR #1928](https://github.com/PHPOffice/PhpSpreadsheet/pull/1928)
diff --git a/composer.json b/composer.json
index 6cd65021..314f21cd 100644
--- a/composer.json
+++ b/composer.json
@@ -53,7 +53,6 @@
},
"require": {
"php": "^7.2 || ^8.0",
- "ext-simplexml": "*",
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
@@ -61,6 +60,7 @@
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
+ "ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
@@ -75,6 +75,7 @@
"psr/simple-cache": "^1.0"
},
"require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"dompdf/dompdf": "^1.0",
"friendsofphp/php-cs-fixer": "^2.18",
"jpgraph/jpgraph": "^4.0",
diff --git a/composer.lock b/composer.lock
index 3670f857..4921cc8f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "3be2673a6367d296c616bf9c34b77953",
+ "content-hash": "9158fcde13425499acaf0da201637737",
"packages": [
{
"name": "ezyang/htmlpurifier",
@@ -802,6 +802,77 @@
],
"time": "2021-03-25T17:01:18+00:00"
},
+ {
+ "name": "dealerdirect/phpcodesniffer-composer-installer",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
+ "reference": "c960cf4629fab7155caca18c038ca7257b7595e3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/c960cf4629fab7155caca18c038ca7257b7595e3",
+ "reference": "c960cf4629fab7155caca18c038ca7257b7595e3",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0 || ^2.0",
+ "php": ">=5.3",
+ "squizlabs/php_codesniffer": "^2.0 || ^3.0 || ^4.0"
+ },
+ "require-dev": {
+ "composer/composer": "*",
+ "enlightn/security-checker": "^1.2",
+ "phpcompatibility/php-compatibility": "^9.0"
+ },
+ "default-branch": true,
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Franck Nijhof",
+ "email": "franck.nijhof@dealerdirect.com",
+ "homepage": "http://www.frenck.nl",
+ "role": "Developer / IT Manager"
+ }
+ ],
+ "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
+ "homepage": "http://www.dealerdirect.com",
+ "keywords": [
+ "PHPCodeSniffer",
+ "PHP_CodeSniffer",
+ "code quality",
+ "codesniffer",
+ "composer",
+ "installer",
+ "phpcs",
+ "plugin",
+ "qa",
+ "quality",
+ "standard",
+ "standards",
+ "style guide",
+ "stylecheck",
+ "tests"
+ ],
+ "support": {
+ "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues",
+ "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer"
+ },
+ "time": "2021-03-14T13:49:41+00:00"
+ },
{
"name": "doctrine/annotations",
"version": "1.12.1",
@@ -5065,12 +5136,13 @@
],
"aliases": [],
"minimum-stability": "stable",
- "stability-flags": [],
+ "stability-flags": {
+ "dealerdirect/phpcodesniffer-composer-installer": 20
+ },
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "^7.2 || ^8.0",
- "ext-simplexml": "*",
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
@@ -5078,6 +5150,7 @@
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
+ "ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
diff --git a/docs/references/features-cross-reference.md b/docs/references/features-cross-reference.md
index 716a3787..c836a99a 100644
--- a/docs/references/features-cross-reference.md
+++ b/docs/references/features-cross-reference.md
@@ -1220,13 +1220,13 @@
| Merged Cells |
- |
- |
- |
- |
+ ✔ |
✔ |
|
- |
+ ✔ |
+ ✔ |
+ N/A |
+ N/A |
|
|
|
@@ -1313,13 +1313,13 @@
● |
● |
|
- |
- |
+ ● |
+ ● |
|
|
● |
● |
- |
+ ● |
|
|
|
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 89355400..037ee997 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -975,11 +975,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php
- -
- message: "#^Parameter \\#3 \\$rowNum of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Matrix\\:\\:extractRowValue\\(\\) expects int, float\\|int\\<0, max\\>\\|string given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php
-
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Matrix\\:\\:extractRowValue\\(\\) has no return typehint specified\\.$#"
count: 1
@@ -2545,71 +2540,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Reader/Csv/Delimiter.php
- -
- message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:\\$referenceHelper has no typehint specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
- -
- message: "#^Parameter \\#1 \\$fp of function fread expects resource, resource\\|false given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
- -
- message: "#^Parameter \\#1 \\$fp of function fclose expects resource, resource\\|false given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
- -
- message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:\\$mappings has no typehint specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
- -
- message: "#^Offset 'No' does not exist on SimpleXMLElement\\|null\\.$#"
- count: 2
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
- -
- message: "#^Offset 'Unit' does not exist on SimpleXMLElement\\|null\\.$#"
- count: 2
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
- -
- message: "#^Offset 'DefaultSizePts' does not exist on SimpleXMLElement\\|null\\.$#"
- count: 2
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseBorderAttributes\\(\\) has no return typehint specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseBorderAttributes\\(\\) has parameter \\$borderAttributes with no typehint specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseRichText\\(\\) has no return typehint specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseRichText\\(\\) has parameter \\$is with no typehint specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseGnumericColour\\(\\) has no return typehint specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Gnumeric\\:\\:parseGnumericColour\\(\\) has parameter \\$gnmColour with no typehint specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Gnumeric.php
-
-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Html\\:\\:\\$rowspan has no typehint specified\\.$#"
count: 1
@@ -2810,21 +2740,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Reader/Ods.php
- -
- message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#"
- count: 7
- path: src/PhpSpreadsheet/Reader/Ods.php
-
- -
- message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:convertToExcelAddressValue\\(\\) should return string but returns string\\|null\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Ods.php
-
- -
- message: "#^Parameter \\#3 \\$formula of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:translateSeparator\\(\\) expects string, string\\|null given\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Reader/Ods.php
-
-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$officeNs has no typehint specified\\.$#"
count: 1
diff --git a/samples/Basic/16_Csv.php b/samples/Basic/16_Csv.php
index 15bbf0d4..137f6469 100644
--- a/samples/Basic/16_Csv.php
+++ b/samples/Basic/16_Csv.php
@@ -38,7 +38,8 @@ $helper->write($spreadsheetFromCSV, __FILE__, ['Xlsx']);
$filenameCSV = $helper->getFilename(__FILE__, 'csv');
/** @var \PhpOffice\PhpSpreadsheet\Writer\Csv $writerCSV */
$writerCSV = new CsvWriter($spreadsheetFromCSV);
-$writerCSV->setExcelCompatibility(true);
+//$writerCSV->setExcelCompatibility(true);
+$writerCSV->setUseBom(true); // because of non-ASCII chars
$callStartTime = microtime(true);
$writerCSV->save($filenameCSV);
diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php b/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php
index d0324964..823d70c6 100644
--- a/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php
+++ b/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php
@@ -11,7 +11,7 @@ class Hyperlink
* HYPERLINK.
*
* Excel Function:
- * =HYPERLINK(linkURL,displayName)
+ * =HYPERLINK(linkURL, [displayName])
*
* @param mixed $linkURL Expect string. Value to check, is also the value returned when no error
* @param mixed $displayName Expect string. Value to return when testValue is an error condition
diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.php b/src/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.php
new file mode 100644
index 00000000..b0739eb3
--- /dev/null
+++ b/src/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.php
@@ -0,0 +1,39 @@
+getMessage();
}
if (!is_array($matrix) || ($rowNum > count($matrix))) {
@@ -69,12 +75,12 @@ class Matrix
return Functions::REF();
}
- if ($columnNum == 0) {
+ if ($columnNum === 0) {
return self::extractRowValue($matrix, $rowKeys, $rowNum);
}
$columnNum = $columnKeys[--$columnNum];
- if ($rowNum == 0) {
+ if ($rowNum === 0) {
return array_map(
function ($value) {
return [$value];
@@ -89,7 +95,7 @@ class Matrix
private static function extractRowValue(array $matrix, array $rowKeys, int $rowNum)
{
- if ($rowNum == 0) {
+ if ($rowNum === 0) {
return $matrix;
}
diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php b/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php
index ab3a9a07..8a3223b1 100644
--- a/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php
+++ b/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php
@@ -51,16 +51,20 @@ class Sum
{
$returnValue = 0;
// Loop through the arguments
- foreach (Functions::flattenArray($args) as $arg) {
+ $aArgs = Functions::flattenArrayIndexed($args);
+ foreach ($aArgs as $k => $arg) {
// Is it a numeric value?
if (is_numeric($arg) || empty($arg)) {
if (is_string($arg)) {
$arg = (int) $arg;
}
$returnValue += $arg;
+ } elseif (is_bool($arg)) {
+ $returnValue += (int) $arg;
} elseif (Functions::isError($arg)) {
return $arg;
- } else {
+ // ignore non-numerics from cell, but fail as literals (except null)
+ } elseif ($arg !== null && !Functions::isCellValue($k)) {
return Functions::VALUE();
}
}
diff --git a/src/PhpSpreadsheet/Calculation/locale/fr/functions b/src/PhpSpreadsheet/Calculation/locale/fr/functions
index 7f40d5fd..b28b4a70 100644
--- a/src/PhpSpreadsheet/Calculation/locale/fr/functions
+++ b/src/PhpSpreadsheet/Calculation/locale/fr/functions
@@ -47,6 +47,7 @@ DVARP = BDVARP ## Calcule la variance pour l’ensemble d’une population d
DATE = DATE ## Renvoie le numéro de série d’une date précise.
DATEVALUE = DATEVAL ## Convertit une date représentée sous forme de texte en numéro de série.
DAY = JOUR ## Convertit un numéro de série en jour du mois.
+DAYS = JOURS ## Calcule le nombre de jours qui séparent deux dates.
DAYS360 = JOURS360 ## Calcule le nombre de jours qui séparent deux dates sur la base d’une année de 360 jours.
EDATE = MOIS.DECALER ## Renvoie le numéro séquentiel de la date qui représente une date spécifiée (l’argument date_départ), corrigée en plus ou en moins du nombre de mois indiqué.
EOMONTH = FIN.MOIS ## Renvoie le numéro séquentiel de la date du dernier jour du mois précédant ou suivant la date_départ du nombre de mois indiqué.
diff --git a/src/PhpSpreadsheet/Cell/AddressHelper.php b/src/PhpSpreadsheet/Cell/AddressHelper.php
index b0e34e25..632c046f 100644
--- a/src/PhpSpreadsheet/Cell/AddressHelper.php
+++ b/src/PhpSpreadsheet/Cell/AddressHelper.php
@@ -102,14 +102,23 @@ class AddressHelper
?int $currentRowNumber = null,
?int $currentColumnNumber = null
): string {
- $validityCheck = preg_match('/^\$?([A-Z]{1,3})\$?(\d{1,7})$/i', $address, $cellReference);
+ $validityCheck = preg_match(Coordinate::A1_COORDINATE_REGEX, $address, $cellReference);
if ($validityCheck === 0) {
throw new Exception('Invalid A1-format Cell Reference');
}
- $columnId = Coordinate::columnIndexFromString($cellReference[1]);
- $rowId = (int) $cellReference[2];
+ $columnId = Coordinate::columnIndexFromString($cellReference['col_ref']);
+ if ($cellReference['absolute_col'] === '$') {
+ // Column must be absolute address
+ $currentColumnNumber = null;
+ }
+
+ $rowId = (int) $cellReference['row_ref'];
+ if ($cellReference['absolute_row'] === '$') {
+ // Row must be absolute address
+ $currentRowNumber = null;
+ }
if ($currentRowNumber !== null) {
if ($rowId === $currentRowNumber) {
diff --git a/src/PhpSpreadsheet/Cell/Coordinate.php b/src/PhpSpreadsheet/Cell/Coordinate.php
index 0b3917f2..58d2573e 100644
--- a/src/PhpSpreadsheet/Cell/Coordinate.php
+++ b/src/PhpSpreadsheet/Cell/Coordinate.php
@@ -13,6 +13,8 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
*/
abstract class Coordinate
{
+ public const A1_COORDINATE_REGEX = '/^(?\$?)(?[A-Z]{1,3})(?\$?)(?\d{1,7})$/i';
+
/**
* Default range variable constant.
*
@@ -29,8 +31,8 @@ abstract class Coordinate
*/
public static function coordinateFromString($pCoordinateString)
{
- if (preg_match('/^([$]?[A-Z]{1,3})([$]?\\d{1,7})$/', $pCoordinateString, $matches)) {
- return [$matches[1], $matches[2]];
+ if (preg_match(self::A1_COORDINATE_REGEX, $pCoordinateString, $matches)) {
+ return [$matches['absolute_col'] . $matches['col_ref'], $matches['absolute_row'] . $matches['row_ref']];
} elseif (self::coordinateIsRange($pCoordinateString)) {
throw new Exception('Cell coordinate string can not be a range of cells');
} elseif ($pCoordinateString == '') {
diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php
index d3cdf1b0..85bae6f8 100644
--- a/src/PhpSpreadsheet/Reader/Gnumeric.php
+++ b/src/PhpSpreadsheet/Reader/Gnumeric.php
@@ -7,25 +7,32 @@ use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\DefinedName;
use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\PageSetup;
use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\Properties;
+use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\Styles;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\ReferenceHelper;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Settings;
-use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
-use PhpOffice\PhpSpreadsheet\Style\Alignment;
-use PhpOffice\PhpSpreadsheet\Style\Border;
-use PhpOffice\PhpSpreadsheet\Style\Borders;
-use PhpOffice\PhpSpreadsheet\Style\Fill;
-use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
use XMLReader;
class Gnumeric extends BaseReader
{
- private const UOM_CONVERSION_POINTS_TO_CENTIMETERS = 0.03527777778;
+ const NAMESPACE_GNM = 'http://www.gnumeric.org/v10.dtd'; // gmr in old sheets
+
+ const NAMESPACE_XSI = 'http://www.w3.org/2001/XMLSchema-instance';
+
+ const NAMESPACE_OFFICE = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0';
+
+ const NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink';
+
+ const NAMESPACE_DC = 'http://purl.org/dc/elements/1.1/';
+
+ const NAMESPACE_META = 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0';
+
+ const NAMESPACE_OOO = 'http://openoffice.org/2004/office';
/**
* Shared Expressions.
@@ -41,15 +48,22 @@ class Gnumeric extends BaseReader
*/
private $spreadsheet;
+ /** @var ReferenceHelper */
private $referenceHelper;
- /**
- * Namespace shared across all functions.
- * It is 'gnm', except for really old sheets which use 'gmr'.
- *
- * @var string
- */
- private $gnm = 'gnm';
+ /** @var array */
+ public static $mappings = [
+ 'dataType' => [
+ '10' => DataType::TYPE_NULL,
+ '20' => DataType::TYPE_BOOL,
+ '30' => DataType::TYPE_NUMERIC, // Integer doesn't exist in Excel
+ '40' => DataType::TYPE_NUMERIC, // Float
+ '50' => DataType::TYPE_ERROR,
+ '60' => DataType::TYPE_STRING,
+ //'70': // Cell Range
+ //'80': // Array
+ ],
+ ];
/**
* Create a new Gnumeric.
@@ -77,16 +91,20 @@ class Gnumeric extends BaseReader
if (function_exists('gzread')) {
// Read signature data (first 3 bytes)
$fh = fopen($pFilename, 'rb');
- $data = fread($fh, 2);
- fclose($fh);
+ if ($fh !== false) {
+ $data = fread($fh, 2);
+ fclose($fh);
+ }
}
- return $data == chr(0x1F) . chr(0x8B);
+ return $data === chr(0x1F) . chr(0x8B);
}
- private static function matchXml(string $name, string $field): bool
+ private static function matchXml(XMLReader $xml, string $expectedLocalName): bool
{
- return 1 === preg_match("/^(gnm|gmr):$field$/", $name);
+ return $xml->namespaceURI === self::NAMESPACE_GNM
+ && $xml->localName === $expectedLocalName
+ && $xml->nodeType === XMLReader::ELEMENT;
}
/**
@@ -106,10 +124,10 @@ class Gnumeric extends BaseReader
$worksheetNames = [];
while ($xml->read()) {
- if (self::matchXml($xml->name, 'SheetName') && $xml->nodeType == XMLReader::ELEMENT) {
+ if (self::matchXml($xml, 'SheetName')) {
$xml->read(); // Move onto the value node
$worksheetNames[] = (string) $xml->value;
- } elseif (self::matchXml($xml->name, 'Sheets')) {
+ } elseif (self::matchXml($xml, 'Sheets')) {
// break out of the loop once we've got our sheet names rather than parse the entire file
break;
}
@@ -135,7 +153,7 @@ class Gnumeric extends BaseReader
$worksheetInfo = [];
while ($xml->read()) {
- if (self::matchXml($xml->name, 'Sheet') && $xml->nodeType == XMLReader::ELEMENT) {
+ if (self::matchXml($xml, 'Sheet')) {
$tmpInfo = [
'worksheetName' => '',
'lastColumnLetter' => 'A',
@@ -145,20 +163,18 @@ class Gnumeric extends BaseReader
];
while ($xml->read()) {
- if ($xml->nodeType == XMLReader::ELEMENT) {
- if (self::matchXml($xml->name, 'Name')) {
- $xml->read(); // Move onto the value node
- $tmpInfo['worksheetName'] = (string) $xml->value;
- } elseif (self::matchXml($xml->name, 'MaxCol')) {
- $xml->read(); // Move onto the value node
- $tmpInfo['lastColumnIndex'] = (int) $xml->value;
- $tmpInfo['totalColumns'] = (int) $xml->value + 1;
- } elseif (self::matchXml($xml->name, 'MaxRow')) {
- $xml->read(); // Move onto the value node
- $tmpInfo['totalRows'] = (int) $xml->value + 1;
+ if (self::matchXml($xml, 'Name')) {
+ $xml->read(); // Move onto the value node
+ $tmpInfo['worksheetName'] = (string) $xml->value;
+ } elseif (self::matchXml($xml, 'MaxCol')) {
+ $xml->read(); // Move onto the value node
+ $tmpInfo['lastColumnIndex'] = (int) $xml->value;
+ $tmpInfo['totalColumns'] = (int) $xml->value + 1;
+ } elseif (self::matchXml($xml, 'MaxRow')) {
+ $xml->read(); // Move onto the value node
+ $tmpInfo['totalRows'] = (int) $xml->value + 1;
- break;
- }
+ break;
}
}
$tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
@@ -188,94 +204,34 @@ class Gnumeric extends BaseReader
return $data;
}
- private static $mappings = [
- 'borderStyle' => [
- '0' => Border::BORDER_NONE,
- '1' => Border::BORDER_THIN,
- '2' => Border::BORDER_MEDIUM,
- '3' => Border::BORDER_SLANTDASHDOT,
- '4' => Border::BORDER_DASHED,
- '5' => Border::BORDER_THICK,
- '6' => Border::BORDER_DOUBLE,
- '7' => Border::BORDER_DOTTED,
- '8' => Border::BORDER_MEDIUMDASHED,
- '9' => Border::BORDER_DASHDOT,
- '10' => Border::BORDER_MEDIUMDASHDOT,
- '11' => Border::BORDER_DASHDOTDOT,
- '12' => Border::BORDER_MEDIUMDASHDOTDOT,
- '13' => Border::BORDER_MEDIUMDASHDOTDOT,
- ],
- 'dataType' => [
- '10' => DataType::TYPE_NULL,
- '20' => DataType::TYPE_BOOL,
- '30' => DataType::TYPE_NUMERIC, // Integer doesn't exist in Excel
- '40' => DataType::TYPE_NUMERIC, // Float
- '50' => DataType::TYPE_ERROR,
- '60' => DataType::TYPE_STRING,
- //'70': // Cell Range
- //'80': // Array
- ],
- 'fillType' => [
- '1' => Fill::FILL_SOLID,
- '2' => Fill::FILL_PATTERN_DARKGRAY,
- '3' => Fill::FILL_PATTERN_MEDIUMGRAY,
- '4' => Fill::FILL_PATTERN_LIGHTGRAY,
- '5' => Fill::FILL_PATTERN_GRAY125,
- '6' => Fill::FILL_PATTERN_GRAY0625,
- '7' => Fill::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe
- '8' => Fill::FILL_PATTERN_DARKVERTICAL, // vertical stripe
- '9' => Fill::FILL_PATTERN_DARKDOWN, // diagonal stripe
- '10' => Fill::FILL_PATTERN_DARKUP, // reverse diagonal stripe
- '11' => Fill::FILL_PATTERN_DARKGRID, // diagoanl crosshatch
- '12' => Fill::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch
- '13' => Fill::FILL_PATTERN_LIGHTHORIZONTAL,
- '14' => Fill::FILL_PATTERN_LIGHTVERTICAL,
- '15' => Fill::FILL_PATTERN_LIGHTUP,
- '16' => Fill::FILL_PATTERN_LIGHTDOWN,
- '17' => Fill::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch
- '18' => Fill::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch
- ],
- 'horizontal' => [
- '1' => Alignment::HORIZONTAL_GENERAL,
- '2' => Alignment::HORIZONTAL_LEFT,
- '4' => Alignment::HORIZONTAL_RIGHT,
- '8' => Alignment::HORIZONTAL_CENTER,
- '16' => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
- '32' => Alignment::HORIZONTAL_JUSTIFY,
- '64' => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
- ],
- 'underline' => [
- '1' => Font::UNDERLINE_SINGLE,
- '2' => Font::UNDERLINE_DOUBLE,
- '3' => Font::UNDERLINE_SINGLEACCOUNTING,
- '4' => Font::UNDERLINE_DOUBLEACCOUNTING,
- ],
- 'vertical' => [
- '1' => Alignment::VERTICAL_TOP,
- '2' => Alignment::VERTICAL_BOTTOM,
- '4' => Alignment::VERTICAL_CENTER,
- '8' => Alignment::VERTICAL_JUSTIFY,
- ],
- ];
-
public static function gnumericMappings(): array
{
- return self::$mappings;
+ return array_merge(self::$mappings, Styles::$mappings);
}
private function processComments(SimpleXMLElement $sheet): void
{
if ((!$this->readDataOnly) && (isset($sheet->Objects))) {
- foreach ($sheet->Objects->children($this->gnm, true) as $key => $comment) {
+ foreach ($sheet->Objects->children(self::NAMESPACE_GNM) as $key => $comment) {
$commentAttributes = $comment->attributes();
// Only comment objects are handled at the moment
if ($commentAttributes->Text) {
- $this->spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound)->setAuthor((string) $commentAttributes->Author)->setText($this->parseRichText((string) $commentAttributes->Text));
+ $this->spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound)
+ ->setAuthor((string) $commentAttributes->Author)
+ ->setText($this->parseRichText((string) $commentAttributes->Text));
}
}
}
}
+ /**
+ * @param mixed $value
+ */
+ private static function testSimpleXml($value): SimpleXMLElement
+ {
+ return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement('');
+ }
+
/**
* Loads Spreadsheet from file.
*
@@ -304,12 +260,10 @@ class Gnumeric extends BaseReader
$gFileData = $this->gzfileGetContents($pFilename);
$xml2 = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions());
- $xml = ($xml2 !== false) ? $xml2 : new SimpleXMLElement('');
- $namespacesMeta = $xml->getNamespaces(true);
- $this->gnm = array_key_exists('gmr', $namespacesMeta) ? 'gmr' : 'gnm';
+ $xml = self::testSimpleXml($xml2);
- $gnmXML = $xml->children($namespacesMeta[$this->gnm]);
- (new Properties($this->spreadsheet))->readProperties($xml, $gnmXML, $namespacesMeta);
+ $gnmXML = $xml->children(self::NAMESPACE_GNM);
+ (new Properties($this->spreadsheet))->readProperties($xml, $gnmXML);
$worksheetID = 0;
foreach ($gnmXML->Sheets->Sheet as $sheet) {
@@ -329,7 +283,7 @@ class Gnumeric extends BaseReader
$this->spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
if (!$this->readDataOnly) {
- (new PageSetup($this->spreadsheet, $this->gnm))
+ (new PageSetup($this->spreadsheet))
->printInformation($sheet)
->sheetMargins($sheet);
}
@@ -382,90 +336,22 @@ class Gnumeric extends BaseReader
if (array_key_exists($vtype, self::$mappings['dataType'])) {
$type = self::$mappings['dataType'][$vtype];
}
- if ($vtype == '20') { // Boolean
+ if ($vtype === '20') { // Boolean
$cell = $cell == 'TRUE';
}
}
$this->spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit((string) $cell, $type);
}
- $this->processComments($sheet);
-
- foreach ($sheet->Styles->StyleRegion as $styleRegion) {
- $styleAttributes = $styleRegion->attributes();
- if (
- ($styleAttributes['startRow'] <= $maxRow) &&
- ($styleAttributes['startCol'] <= $maxCol)
- ) {
- $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1);
- $startRow = $styleAttributes['startRow'] + 1;
-
- $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol'];
- $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1);
-
- $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']);
- $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow;
-
- $styleAttributes = $styleRegion->Style->attributes();
-
- $styleArray = [];
- // We still set the number format mask for date/time values, even if readDataOnly is true
- $formatCode = (string) $styleAttributes['Format'];
- if (Date::isDateTimeFormatCode($formatCode)) {
- $styleArray['numberFormat']['formatCode'] = $formatCode;
- }
- if (!$this->readDataOnly) {
- // If readDataOnly is false, we set all formatting information
- $styleArray['numberFormat']['formatCode'] = $formatCode;
-
- self::addStyle2($styleArray, 'alignment', 'horizontal', $styleAttributes['HAlign']);
- self::addStyle2($styleArray, 'alignment', 'vertical', $styleAttributes['VAlign']);
- $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1';
- $styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes);
- $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1';
- $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0;
-
- $this->addColors($styleArray, $styleAttributes);
-
- $fontAttributes = $styleRegion->Style->Font->attributes();
- $styleArray['font']['name'] = (string) $styleRegion->Style->Font;
- $styleArray['font']['size'] = (int) ($fontAttributes['Unit']);
- $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1';
- $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1';
- $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1';
- self::addStyle2($styleArray, 'font', 'underline', $fontAttributes['Underline']);
-
- switch ($fontAttributes['Script']) {
- case '1':
- $styleArray['font']['superscript'] = true;
-
- break;
- case '-1':
- $styleArray['font']['subscript'] = true;
-
- break;
- }
-
- if (isset($styleRegion->Style->StyleBorder)) {
- $srssb = $styleRegion->Style->StyleBorder;
- $this->addBorderStyle($srssb, $styleArray, 'top');
- $this->addBorderStyle($srssb, $styleArray, 'bottom');
- $this->addBorderStyle($srssb, $styleArray, 'left');
- $this->addBorderStyle($srssb, $styleArray, 'right');
- $this->addBorderDiagonal($srssb, $styleArray);
- }
- if (isset($styleRegion->Style->HyperLink)) {
- // TO DO
- $hyperlink = $styleRegion->Style->HyperLink->attributes();
- }
- }
- $this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
- }
+ if ($sheet->Styles !== null) {
+ (new Styles($this->spreadsheet, $this->readDataOnly))->read($sheet, $maxRow, $maxCol);
}
+ $this->processComments($sheet);
$this->processColumnWidths($sheet, $maxCol);
$this->processRowHeights($sheet, $maxRow);
$this->processMergedCells($sheet);
+ $this->processAutofilter($sheet);
++$worksheetID;
}
@@ -476,32 +362,10 @@ class Gnumeric extends BaseReader
return $this->spreadsheet;
}
- private function addBorderDiagonal(SimpleXMLElement $srssb, array &$styleArray): void
- {
- if (isset($srssb->Diagonal, $srssb->{'Rev-Diagonal'})) {
- $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
- $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH;
- } elseif (isset($srssb->Diagonal)) {
- $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
- $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP;
- } elseif (isset($srssb->{'Rev-Diagonal'})) {
- $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes());
- $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN;
- }
- }
-
- private function addBorderStyle(SimpleXMLElement $srssb, array &$styleArray, string $direction): void
- {
- $ucDirection = ucfirst($direction);
- if (isset($srssb->$ucDirection)) {
- $styleArray['borders'][$direction] = self::parseBorderAttributes($srssb->$ucDirection->attributes());
- }
- }
-
- private function processMergedCells(SimpleXMLElement $sheet): void
+ private function processMergedCells(?SimpleXMLElement $sheet): void
{
// Handle Merged Cells in this worksheet
- if (isset($sheet->MergedRegions)) {
+ if ($sheet !== null && isset($sheet->MergedRegions)) {
foreach ($sheet->MergedRegions->Merge as $mergeCells) {
if (strpos($mergeCells, ':') !== false) {
$this->spreadsheet->getActiveSheet()->mergeCells($mergeCells);
@@ -510,92 +374,144 @@ class Gnumeric extends BaseReader
}
}
- private function processColumnLoop(int $c, int $maxCol, SimpleXMLElement $columnOverride, float $defaultWidth): int
+ private function processAutofilter(?SimpleXMLElement $sheet): void
{
- $columnAttributes = $columnOverride->attributes();
+ if ($sheet !== null && isset($sheet->Filters)) {
+ foreach ($sheet->Filters->Filter as $autofilter) {
+ if ($autofilter !== null) {
+ $attributes = $autofilter->attributes();
+ if (isset($attributes['Area'])) {
+ $this->spreadsheet->getActiveSheet()->setAutoFilter((string) $attributes['Area']);
+ }
+ }
+ }
+ }
+ }
+
+ private function setColumnWidth(int $whichColumn, float $defaultWidth): void
+ {
+ $columnDimension = $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1));
+ if ($columnDimension !== null) {
+ $columnDimension->setWidth($defaultWidth);
+ }
+ }
+
+ private function setColumnInvisible(int $whichColumn): void
+ {
+ $columnDimension = $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1));
+ if ($columnDimension !== null) {
+ $columnDimension->setVisible(false);
+ }
+ }
+
+ private function processColumnLoop(int $whichColumn, int $maxCol, SimpleXMLElement $columnOverride, float $defaultWidth): int
+ {
+ $columnAttributes = self::testSimpleXml($columnOverride->attributes());
$column = $columnAttributes['No'];
$columnWidth = ((float) $columnAttributes['Unit']) / 5.4;
$hidden = (isset($columnAttributes['Hidden'])) && ((string) $columnAttributes['Hidden'] == '1');
$columnCount = (int) ($columnAttributes['Count'] ?? 1);
- while ($c < $column) {
- $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
- ++$c;
+ while ($whichColumn < $column) {
+ $this->setColumnWidth($whichColumn, $defaultWidth);
+ ++$whichColumn;
}
- while (($c < ($column + $columnCount)) && ($c <= $maxCol)) {
- $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($columnWidth);
+ while (($whichColumn < ($column + $columnCount)) && ($whichColumn <= $maxCol)) {
+ $this->setColumnWidth($whichColumn, $columnWidth);
if ($hidden) {
- $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setVisible(false);
+ $this->setColumnInvisible($whichColumn);
}
- ++$c;
+ ++$whichColumn;
}
- return $c;
+ return $whichColumn;
}
- private function processColumnWidths(SimpleXMLElement $sheet, int $maxCol): void
+ private function processColumnWidths(?SimpleXMLElement $sheet, int $maxCol): void
{
- if ((!$this->readDataOnly) && (isset($sheet->Cols))) {
+ if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Cols))) {
// Column Widths
+ $defaultWidth = 0;
$columnAttributes = $sheet->Cols->attributes();
- $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4;
- $c = 0;
- foreach ($sheet->Cols->ColInfo as $columnOverride) {
- $c = $this->processColumnLoop($c, $maxCol, $columnOverride, $defaultWidth);
+ if ($columnAttributes !== null) {
+ $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4;
}
- while ($c <= $maxCol) {
- $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
- ++$c;
+ $whichColumn = 0;
+ foreach ($sheet->Cols->ColInfo as $columnOverride) {
+ $whichColumn = $this->processColumnLoop($whichColumn, $maxCol, $columnOverride, $defaultWidth);
+ }
+ while ($whichColumn <= $maxCol) {
+ $this->setColumnWidth($whichColumn, $defaultWidth);
+ ++$whichColumn;
}
}
}
- private function processRowLoop(int $r, int $maxRow, SimpleXMLElement $rowOverride, float $defaultHeight): int
+ private function setRowHeight(int $whichRow, float $defaultHeight): void
{
- $rowAttributes = $rowOverride->attributes();
+ $rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow);
+ if ($rowDimension !== null) {
+ $rowDimension->setRowHeight($defaultHeight);
+ }
+ }
+
+ private function setRowInvisible(int $whichRow): void
+ {
+ $rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow);
+ if ($rowDimension !== null) {
+ $rowDimension->setVisible(false);
+ }
+ }
+
+ private function processRowLoop(int $whichRow, int $maxRow, SimpleXMLElement $rowOverride, float $defaultHeight): int
+ {
+ $rowAttributes = self::testSimpleXml($rowOverride->attributes());
$row = $rowAttributes['No'];
$rowHeight = (float) $rowAttributes['Unit'];
$hidden = (isset($rowAttributes['Hidden'])) && ((string) $rowAttributes['Hidden'] == '1');
$rowCount = (int) ($rowAttributes['Count'] ?? 1);
- while ($r < $row) {
- ++$r;
- $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight);
+ while ($whichRow < $row) {
+ ++$whichRow;
+ $this->setRowHeight($whichRow, $defaultHeight);
}
- while (($r < ($row + $rowCount)) && ($r < $maxRow)) {
- ++$r;
- $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($rowHeight);
+ while (($whichRow < ($row + $rowCount)) && ($whichRow < $maxRow)) {
+ ++$whichRow;
+ $this->setRowHeight($whichRow, $rowHeight);
if ($hidden) {
- $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setVisible(false);
+ $this->setRowInvisible($whichRow);
}
}
- return $r;
+ return $whichRow;
}
- private function processRowHeights(SimpleXMLElement $sheet, int $maxRow): void
+ private function processRowHeights(?SimpleXMLElement $sheet, int $maxRow): void
{
- if ((!$this->readDataOnly) && (isset($sheet->Rows))) {
+ if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Rows))) {
// Row Heights
+ $defaultHeight = 0;
$rowAttributes = $sheet->Rows->attributes();
- $defaultHeight = (float) $rowAttributes['DefaultSizePts'];
- $r = 0;
+ if ($rowAttributes !== null) {
+ $defaultHeight = (float) $rowAttributes['DefaultSizePts'];
+ }
+ $whichRow = 0;
foreach ($sheet->Rows->RowInfo as $rowOverride) {
- $r = $this->processRowLoop($r, $maxRow, $rowOverride, $defaultHeight);
+ $whichRow = $this->processRowLoop($whichRow, $maxRow, $rowOverride, $defaultHeight);
}
// never executed, I can't figure out any circumstances
// under which it would be executed, and, even if
// such exist, I'm not convinced this is needed.
- //while ($r < $maxRow) {
- // ++$r;
- // $this->spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight);
+ //while ($whichRow < $maxRow) {
+ // ++$whichRow;
+ // $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow)->setRowHeight($defaultHeight);
//}
}
}
- private function processDefinedNames(SimpleXMLElement $gnmXML): void
+ private function processDefinedNames(?SimpleXMLElement $gnmXML): void
{
// Loop through definedNames (global named ranges)
- if (isset($gnmXML->Names)) {
+ if ($gnmXML !== null && isset($gnmXML->Names)) {
foreach ($gnmXML->Names->Name as $definedName) {
$name = (string) $definedName->name;
$value = (string) $definedName->value;
@@ -614,77 +530,11 @@ class Gnumeric extends BaseReader
}
}
- private function calcRotation(SimpleXMLElement $styleAttributes): int
- {
- $rotation = (int) $styleAttributes->Rotation;
- if ($rotation >= 270 && $rotation <= 360) {
- $rotation -= 360;
- }
- $rotation = (abs($rotation) > 90) ? 0 : $rotation;
-
- return $rotation;
- }
-
- private static function addStyle(array &$styleArray, string $key, string $value): void
- {
- if (array_key_exists($value, self::$mappings[$key])) {
- $styleArray[$key] = self::$mappings[$key][$value];
- }
- }
-
- private static function addStyle2(array &$styleArray, string $key1, string $key, string $value): void
- {
- if (array_key_exists($value, self::$mappings[$key])) {
- $styleArray[$key1][$key] = self::$mappings[$key][$value];
- }
- }
-
- private static function parseBorderAttributes($borderAttributes)
- {
- $styleArray = [];
- if (isset($borderAttributes['Color'])) {
- $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']);
- }
-
- self::addStyle($styleArray, 'borderStyle', $borderAttributes['Style']);
-
- return $styleArray;
- }
-
- private function parseRichText($is)
+ private function parseRichText(string $is): RichText
{
$value = new RichText();
$value->createText($is);
return $value;
}
-
- private static function parseGnumericColour($gnmColour)
- {
- [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour);
- $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2);
- $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2);
- $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2);
-
- return $gnmR . $gnmG . $gnmB;
- }
-
- private function addColors(array &$styleArray, SimpleXMLElement $styleAttributes): void
- {
- $RGB = self::parseGnumericColour($styleAttributes['Fore']);
- $styleArray['font']['color']['rgb'] = $RGB;
- $RGB = self::parseGnumericColour($styleAttributes['Back']);
- $shade = (string) $styleAttributes['Shade'];
- if (($RGB != '000000') || ($shade != '0')) {
- $RGB2 = self::parseGnumericColour($styleAttributes['PatternColor']);
- if ($shade == '1') {
- $styleArray['fill']['startColor']['rgb'] = $RGB;
- $styleArray['fill']['endColor']['rgb'] = $RGB2;
- } else {
- $styleArray['fill']['endColor']['rgb'] = $RGB;
- $styleArray['fill']['startColor']['rgb'] = $RGB2;
- }
- self::addStyle2($styleArray, 'fill', 'fillType', $shade);
- }
- }
}
diff --git a/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php b/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php
index 0fe73005..accc2716 100644
--- a/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php
+++ b/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php
@@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
+use PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\PageMargins;
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup as WorksheetPageSetup;
@@ -14,15 +15,9 @@ class PageSetup
*/
private $spreadsheet;
- /**
- * @var string
- */
- private $gnm;
-
- public function __construct(Spreadsheet $spreadsheet, string $gnm)
+ public function __construct(Spreadsheet $spreadsheet)
{
$this->spreadsheet = $spreadsheet;
- $this->gnm = $gnm;
}
public function printInformation(SimpleXMLElement $sheet): self
@@ -68,7 +63,7 @@ class PageSetup
private function buildMarginSet(SimpleXMLElement $sheet, array $marginSet): array
{
- foreach ($sheet->PrintInformation->Margins->children($this->gnm, true) as $key => $margin) {
+ foreach ($sheet->PrintInformation->Margins->children(Gnumeric::NAMESPACE_GNM) as $key => $margin) {
$marginAttributes = $margin->attributes();
$marginSize = ($marginAttributes['Points']) ?? 72; // Default is 72pt
// Convert value in points to inches
diff --git a/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php b/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php
index 16d9c2e0..c466a859 100644
--- a/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php
+++ b/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php
@@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
+use PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use SimpleXMLElement;
@@ -91,74 +92,72 @@ class Properties
}
}
- private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta, array $namespacesMeta): void
+ private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta): void
{
$docProps = $this->spreadsheet->getProperties();
foreach ($officePropertyMeta as $propertyName => $propertyValue) {
- if ($propertyValue === null) {
- continue;
- }
+ if ($propertyValue !== null) {
+ $attributes = $propertyValue->attributes(Gnumeric::NAMESPACE_META);
+ $propertyValue = trim((string) $propertyValue);
+ switch ($propertyName) {
+ case 'keyword':
+ $docProps->setKeywords($propertyValue);
- $attributes = $propertyValue->attributes($namespacesMeta['meta']);
- $propertyValue = trim((string) $propertyValue);
- switch ($propertyName) {
- case 'keyword':
- $docProps->setKeywords($propertyValue);
+ break;
+ case 'initial-creator':
+ $docProps->setCreator($propertyValue);
+ $docProps->setLastModifiedBy($propertyValue);
- break;
- case 'initial-creator':
- $docProps->setCreator($propertyValue);
- $docProps->setLastModifiedBy($propertyValue);
+ break;
+ case 'creation-date':
+ $creationDate = strtotime($propertyValue);
+ $creationDate = $creationDate === false ? time() : $creationDate;
+ $docProps->setCreated($creationDate);
+ $docProps->setModified($creationDate);
- break;
- case 'creation-date':
- $creationDate = strtotime($propertyValue);
- $creationDate = $creationDate === false ? time() : $creationDate;
- $docProps->setCreated($creationDate);
- $docProps->setModified($creationDate);
+ break;
+ case 'user-defined':
+ [, $attrName] = explode(':', $attributes['name']);
+ $this->userDefinedProperties($attrName, $propertyValue);
- break;
- case 'user-defined':
- [, $attrName] = explode(':', $attributes['name']);
- switch ($attrName) {
- case 'publisher':
- $docProps->setCompany($propertyValue);
-
- break;
- case 'category':
- $docProps->setCategory($propertyValue);
-
- break;
- case 'manager':
- $docProps->setManager($propertyValue);
-
- break;
- }
-
- break;
+ break;
+ }
}
}
}
- public function readProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML, array $namespacesMeta): void
+ private function userDefinedProperties(string $attrName, string $propertyValue): void
{
- if (isset($namespacesMeta['office'])) {
- $officeXML = $xml->children($namespacesMeta['office']);
+ $docProps = $this->spreadsheet->getProperties();
+ switch ($attrName) {
+ case 'publisher':
+ $docProps->setCompany($propertyValue);
+
+ break;
+ case 'category':
+ $docProps->setCategory($propertyValue);
+
+ break;
+ case 'manager':
+ $docProps->setManager($propertyValue);
+
+ break;
+ }
+ }
+
+ public function readProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML): void
+ {
+ $officeXML = $xml->children(Gnumeric::NAMESPACE_OFFICE);
+ if (!empty($officeXML)) {
$officeDocXML = $officeXML->{'document-meta'};
$officeDocMetaXML = $officeDocXML->meta;
foreach ($officeDocMetaXML as $officePropertyData) {
- $officePropertyDC = [];
- if (isset($namespacesMeta['dc'])) {
- $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']);
- }
+ $officePropertyDC = $officePropertyData->children(Gnumeric::NAMESPACE_DC);
$this->docPropertiesDC($officePropertyDC);
- $officePropertyMeta = [];
- if (isset($namespacesMeta['meta'])) {
- $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
- }
- $this->docPropertiesMeta($officePropertyMeta, $namespacesMeta);
+ $officePropertyMeta = $officePropertyData->children(Gnumeric::NAMESPACE_META);
+ $this->docPropertiesMeta($officePropertyMeta);
}
} elseif (isset($gnmXML->Summary)) {
$this->docPropertiesOld($gnmXML);
diff --git a/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php b/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php
new file mode 100644
index 00000000..9998448e
--- /dev/null
+++ b/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php
@@ -0,0 +1,282 @@
+ [
+ '0' => Border::BORDER_NONE,
+ '1' => Border::BORDER_THIN,
+ '2' => Border::BORDER_MEDIUM,
+ '3' => Border::BORDER_SLANTDASHDOT,
+ '4' => Border::BORDER_DASHED,
+ '5' => Border::BORDER_THICK,
+ '6' => Border::BORDER_DOUBLE,
+ '7' => Border::BORDER_DOTTED,
+ '8' => Border::BORDER_MEDIUMDASHED,
+ '9' => Border::BORDER_DASHDOT,
+ '10' => Border::BORDER_MEDIUMDASHDOT,
+ '11' => Border::BORDER_DASHDOTDOT,
+ '12' => Border::BORDER_MEDIUMDASHDOTDOT,
+ '13' => Border::BORDER_MEDIUMDASHDOTDOT,
+ ],
+ 'fillType' => [
+ '1' => Fill::FILL_SOLID,
+ '2' => Fill::FILL_PATTERN_DARKGRAY,
+ '3' => Fill::FILL_PATTERN_MEDIUMGRAY,
+ '4' => Fill::FILL_PATTERN_LIGHTGRAY,
+ '5' => Fill::FILL_PATTERN_GRAY125,
+ '6' => Fill::FILL_PATTERN_GRAY0625,
+ '7' => Fill::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe
+ '8' => Fill::FILL_PATTERN_DARKVERTICAL, // vertical stripe
+ '9' => Fill::FILL_PATTERN_DARKDOWN, // diagonal stripe
+ '10' => Fill::FILL_PATTERN_DARKUP, // reverse diagonal stripe
+ '11' => Fill::FILL_PATTERN_DARKGRID, // diagoanl crosshatch
+ '12' => Fill::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch
+ '13' => Fill::FILL_PATTERN_LIGHTHORIZONTAL,
+ '14' => Fill::FILL_PATTERN_LIGHTVERTICAL,
+ '15' => Fill::FILL_PATTERN_LIGHTUP,
+ '16' => Fill::FILL_PATTERN_LIGHTDOWN,
+ '17' => Fill::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch
+ '18' => Fill::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch
+ ],
+ 'horizontal' => [
+ '1' => Alignment::HORIZONTAL_GENERAL,
+ '2' => Alignment::HORIZONTAL_LEFT,
+ '4' => Alignment::HORIZONTAL_RIGHT,
+ '8' => Alignment::HORIZONTAL_CENTER,
+ '16' => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
+ '32' => Alignment::HORIZONTAL_JUSTIFY,
+ '64' => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
+ ],
+ 'underline' => [
+ '1' => Font::UNDERLINE_SINGLE,
+ '2' => Font::UNDERLINE_DOUBLE,
+ '3' => Font::UNDERLINE_SINGLEACCOUNTING,
+ '4' => Font::UNDERLINE_DOUBLEACCOUNTING,
+ ],
+ 'vertical' => [
+ '1' => Alignment::VERTICAL_TOP,
+ '2' => Alignment::VERTICAL_BOTTOM,
+ '4' => Alignment::VERTICAL_CENTER,
+ '8' => Alignment::VERTICAL_JUSTIFY,
+ ],
+ ];
+
+ public function __construct(Spreadsheet $spreadsheet, bool $readDataOnly)
+ {
+ $this->spreadsheet = $spreadsheet;
+ $this->readDataOnly = $readDataOnly;
+ }
+
+ public function read(SimpleXMLElement $sheet, int $maxRow, int $maxCol): void
+ {
+ if ($sheet->Styles->StyleRegion !== null) {
+ $this->readStyles($sheet->Styles->StyleRegion, $maxRow, $maxCol);
+ }
+ }
+
+ private function readStyles(SimpleXMLElement $styleRegion, int $maxRow, int $maxCol): void
+ {
+ foreach ($styleRegion as $style) {
+ if ($style === null) {
+ continue;
+ }
+
+ $styleAttributes = $style->attributes();
+ if ($styleAttributes !== null && ($styleAttributes['startRow'] <= $maxRow) && ($styleAttributes['startCol'] <= $maxCol)) {
+ $cellRange = $this->readStyleRange($styleAttributes, $maxCol, $maxRow);
+
+ $styleAttributes = $style->Style->attributes();
+
+ $styleArray = [];
+ // We still set the number format mask for date/time values, even if readDataOnly is true
+ // so that we can identify whether a float is a float or a date value
+ $formatCode = (string) $styleAttributes['Format'];
+ if (Date::isDateTimeFormatCode($formatCode)) {
+ $styleArray['numberFormat']['formatCode'] = $formatCode;
+ }
+ if ($this->readDataOnly === false && $styleAttributes !== null) {
+ // If readDataOnly is false, we set all formatting information
+ $styleArray['numberFormat']['formatCode'] = $formatCode;
+ $styleArray = $this->readStyle($styleArray, $styleAttributes, $style);
+ }
+ $this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
+ }
+ }
+ }
+
+ private function addBorderDiagonal(SimpleXMLElement $srssb, array &$styleArray): void
+ {
+ if (isset($srssb->Diagonal, $srssb->{'Rev-Diagonal'})) {
+ $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
+ $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH;
+ } elseif (isset($srssb->Diagonal)) {
+ $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
+ $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP;
+ } elseif (isset($srssb->{'Rev-Diagonal'})) {
+ $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes());
+ $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN;
+ }
+ }
+
+ private function addBorderStyle(SimpleXMLElement $srssb, array &$styleArray, string $direction): void
+ {
+ $ucDirection = ucfirst($direction);
+ if (isset($srssb->$ucDirection)) {
+ $styleArray['borders'][$direction] = self::parseBorderAttributes($srssb->$ucDirection->attributes());
+ }
+ }
+
+ private function calcRotation(SimpleXMLElement $styleAttributes): int
+ {
+ $rotation = (int) $styleAttributes->Rotation;
+ if ($rotation >= 270 && $rotation <= 360) {
+ $rotation -= 360;
+ }
+ $rotation = (abs($rotation) > 90) ? 0 : $rotation;
+
+ return $rotation;
+ }
+
+ private static function addStyle(array &$styleArray, string $key, string $value): void
+ {
+ if (array_key_exists($value, self::$mappings[$key])) {
+ $styleArray[$key] = self::$mappings[$key][$value];
+ }
+ }
+
+ private static function addStyle2(array &$styleArray, string $key1, string $key, string $value): void
+ {
+ if (array_key_exists($value, self::$mappings[$key])) {
+ $styleArray[$key1][$key] = self::$mappings[$key][$value];
+ }
+ }
+
+ private static function parseBorderAttributes(?SimpleXMLElement $borderAttributes): array
+ {
+ $styleArray = [];
+ if ($borderAttributes !== null) {
+ if (isset($borderAttributes['Color'])) {
+ $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']);
+ }
+
+ self::addStyle($styleArray, 'borderStyle', $borderAttributes['Style']);
+ }
+
+ return $styleArray;
+ }
+
+ private static function parseGnumericColour(string $gnmColour): string
+ {
+ [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour);
+ $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2);
+ $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2);
+ $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2);
+
+ return $gnmR . $gnmG . $gnmB;
+ }
+
+ private function addColors(array &$styleArray, SimpleXMLElement $styleAttributes): void
+ {
+ $RGB = self::parseGnumericColour($styleAttributes['Fore']);
+ $styleArray['font']['color']['rgb'] = $RGB;
+ $RGB = self::parseGnumericColour($styleAttributes['Back']);
+ $shade = (string) $styleAttributes['Shade'];
+ if (($RGB !== '000000') || ($shade !== '0')) {
+ $RGB2 = self::parseGnumericColour($styleAttributes['PatternColor']);
+ if ($shade === '1') {
+ $styleArray['fill']['startColor']['rgb'] = $RGB;
+ $styleArray['fill']['endColor']['rgb'] = $RGB2;
+ } else {
+ $styleArray['fill']['endColor']['rgb'] = $RGB;
+ $styleArray['fill']['startColor']['rgb'] = $RGB2;
+ }
+ self::addStyle2($styleArray, 'fill', 'fillType', $shade);
+ }
+ }
+
+ private function readStyleRange(SimpleXMLElement $styleAttributes, int $maxCol, int $maxRow): string
+ {
+ $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1);
+ $startRow = $styleAttributes['startRow'] + 1;
+
+ $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol'];
+ $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1);
+
+ $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']);
+ $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow;
+
+ return $cellRange;
+ }
+
+ private function readStyle(array $styleArray, SimpleXMLElement $styleAttributes, SimpleXMLElement $style): array
+ {
+ self::addStyle2($styleArray, 'alignment', 'horizontal', $styleAttributes['HAlign']);
+ self::addStyle2($styleArray, 'alignment', 'vertical', $styleAttributes['VAlign']);
+ $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1';
+ $styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes);
+ $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1';
+ $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0;
+
+ $this->addColors($styleArray, $styleAttributes);
+
+ $fontAttributes = $style->Style->Font->attributes();
+ if ($fontAttributes !== null) {
+ $styleArray['font']['name'] = (string) $style->Style->Font;
+ $styleArray['font']['size'] = (int) ($fontAttributes['Unit']);
+ $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1';
+ $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1';
+ $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1';
+ self::addStyle2($styleArray, 'font', 'underline', $fontAttributes['Underline']);
+
+ switch ($fontAttributes['Script']) {
+ case '1':
+ $styleArray['font']['superscript'] = true;
+
+ break;
+ case '-1':
+ $styleArray['font']['subscript'] = true;
+
+ break;
+ }
+ }
+
+ if (isset($style->Style->StyleBorder)) {
+ $srssb = $style->Style->StyleBorder;
+ $this->addBorderStyle($srssb, $styleArray, 'top');
+ $this->addBorderStyle($srssb, $styleArray, 'bottom');
+ $this->addBorderStyle($srssb, $styleArray, 'left');
+ $this->addBorderStyle($srssb, $styleArray, 'right');
+ $this->addBorderDiagonal($srssb, $styleArray);
+ }
+ if (isset($style->Style->HyperLink)) {
+ // TO DO
+ $hyperlink = $style->Style->HyperLink->attributes();
+ }
+
+ return $styleArray;
+ }
+}
diff --git a/src/PhpSpreadsheet/Reader/Ods.php b/src/PhpSpreadsheet/Reader/Ods.php
index 1a4d7ca3..26151cdc 100644
--- a/src/PhpSpreadsheet/Reader/Ods.php
+++ b/src/PhpSpreadsheet/Reader/Ods.php
@@ -11,8 +11,9 @@ use DOMNode;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
-use PhpOffice\PhpSpreadsheet\DefinedName;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
+use PhpOffice\PhpSpreadsheet\Reader\Ods\AutoFilter;
+use PhpOffice\PhpSpreadsheet\Reader\Ods\DefinedNames;
use PhpOffice\PhpSpreadsheet\Reader\Ods\PageSettings;
use PhpOffice\PhpSpreadsheet\Reader\Ods\Properties as DocumentProperties;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
@@ -22,7 +23,6 @@ use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
-use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Throwable;
use XMLReader;
use ZipArchive;
@@ -304,8 +304,10 @@ class Ods extends BaseReader
$pageSettings->readStyleCrossReferences($dom);
- // Content
+ $autoFilterReader = new AutoFilter($spreadsheet, $tableNs);
+ $definedNameReader = new DefinedNames($spreadsheet, $tableNs);
+ // Content
$spreadsheets = $dom->getElementsByTagNameNS($officeNs, 'body')
->item(0)
->getElementsByTagNameNS($officeNs, 'spreadsheet');
@@ -642,8 +644,8 @@ class Ods extends BaseReader
++$worksheetID;
}
- $this->readDefinedRanges($spreadsheet, $workbookData, $tableNs);
- $this->readDefinedExpressions($spreadsheet, $workbookData, $tableNs);
+ $autoFilterReader->read($workbookData);
+ $definedNameReader->read($workbookData);
}
$spreadsheet->setActiveSheetIndex(0);
@@ -771,26 +773,6 @@ class Ods extends BaseReader
return $value;
}
- private function convertToExcelAddressValue(string $openOfficeAddress): string
- {
- $excelAddress = $openOfficeAddress;
-
- // Cell range 3-d reference
- // As we don't support 3-d ranges, we're just going to take a quick and dirty approach
- // and assume that the second worksheet reference is the same as the first
- $excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+):\$?([^\.]+)\.([^\.]+)/miu', '$1!$2:$4', $excelAddress);
- // Cell range reference in another sheet
- $excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+):\.([^\.]+)/miu', '$1!$2:$3', $excelAddress);
- // Cell reference in another sheet
- $excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+)/miu', '$1!$2', $excelAddress);
- // Cell range reference
- $excelAddress = preg_replace('/\.([^\.]+):\.([^\.]+)/miu', '$1:$2', $excelAddress);
- // Simple cell reference
- $excelAddress = preg_replace('/\.([^\.]+)/miu', '$1', $excelAddress);
-
- return $excelAddress;
- }
-
private function convertToExcelFormulaValue(string $openOfficeFormula): string
{
$temp = explode('"', $openOfficeFormula);
@@ -801,11 +783,13 @@ class Ods extends BaseReader
// Cell range reference in another sheet
$value = preg_replace('/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', '$1!$2:$3', $value);
// Cell reference in another sheet
- $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+)\]/miu', '$1!$2', $value);
+ $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+)\]/miu', '$1!$2', $value ?? '');
// Cell range reference
- $value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/miu', '$1:$2', $value);
+ $value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/miu', '$1:$2', $value ?? '');
// Simple cell reference
- $value = preg_replace('/\[\.([^\.]+)\]/miu', '$1', $value);
+ $value = preg_replace('/\[\.([^\.]+)\]/miu', '$1', $value ?? '');
+ // Convert references to defined names/formulae
+ $value = str_replace('$$', '', $value ?? '');
$value = Calculation::translateSeparator(';', ',', $value, $inBraces);
}
@@ -816,53 +800,4 @@ class Ods extends BaseReader
return $excelFormula;
}
-
- /**
- * Read any Named Ranges that are defined in this spreadsheet.
- */
- private function readDefinedRanges(Spreadsheet $spreadsheet, DOMElement $workbookData, string $tableNs): void
- {
- $namedRanges = $workbookData->getElementsByTagNameNS($tableNs, 'named-range');
- foreach ($namedRanges as $definedNameElement) {
- $definedName = $definedNameElement->getAttributeNS($tableNs, 'name');
- $baseAddress = $definedNameElement->getAttributeNS($tableNs, 'base-cell-address');
- $range = $definedNameElement->getAttributeNS($tableNs, 'cell-range-address');
-
- $baseAddress = $this->convertToExcelAddressValue($baseAddress);
- $range = $this->convertToExcelAddressValue($range);
-
- $this->addDefinedName($spreadsheet, $baseAddress, $definedName, $range);
- }
- }
-
- /**
- * Read any Named Formulae that are defined in this spreadsheet.
- */
- private function readDefinedExpressions(Spreadsheet $spreadsheet, DOMElement $workbookData, string $tableNs): void
- {
- $namedExpressions = $workbookData->getElementsByTagNameNS($tableNs, 'named-expression');
- foreach ($namedExpressions as $definedNameElement) {
- $definedName = $definedNameElement->getAttributeNS($tableNs, 'name');
- $baseAddress = $definedNameElement->getAttributeNS($tableNs, 'base-cell-address');
- $expression = $definedNameElement->getAttributeNS($tableNs, 'expression');
-
- $baseAddress = $this->convertToExcelAddressValue($baseAddress);
- $expression = $this->convertToExcelFormulaValue($expression);
-
- $this->addDefinedName($spreadsheet, $baseAddress, $definedName, $expression);
- }
- }
-
- /**
- * Assess scope and store the Defined Name.
- */
- private function addDefinedName(Spreadsheet $spreadsheet, string $baseAddress, string $definedName, string $value): void
- {
- [$sheetReference] = Worksheet::extractSheetTitle($baseAddress, true);
- $worksheet = $spreadsheet->getSheetByName($sheetReference);
- // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet
- if ($worksheet !== null) {
- $spreadsheet->addDefinedName(DefinedName::createInstance((string) $definedName, $worksheet, $value));
- }
- }
}
diff --git a/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php b/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php
new file mode 100644
index 00000000..bdc8b3ff
--- /dev/null
+++ b/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php
@@ -0,0 +1,45 @@
+readAutoFilters($workbookData);
+ }
+
+ protected function readAutoFilters(DOMElement $workbookData): void
+ {
+ $databases = $workbookData->getElementsByTagNameNS($this->tableNs, 'database-ranges');
+
+ foreach ($databases as $autofilters) {
+ foreach ($autofilters->childNodes as $autofilter) {
+ $autofilterRange = $this->getAttributeValue($autofilter, 'target-range-address');
+ if ($autofilterRange !== null) {
+ $baseAddress = $this->convertToExcelAddressValue($autofilterRange);
+ $this->spreadsheet->getActiveSheet()->setAutoFilter($baseAddress);
+ }
+ }
+ }
+ }
+
+ protected function getAttributeValue(?DOMNode $node, string $attributeName): ?string
+ {
+ if ($node !== null && $node->attributes !== null) {
+ $attribute = $node->attributes->getNamedItemNS(
+ $this->tableNs,
+ $attributeName
+ );
+
+ if ($attribute !== null) {
+ return $attribute->nodeValue;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/PhpSpreadsheet/Reader/Ods/BaseReader.php b/src/PhpSpreadsheet/Reader/Ods/BaseReader.php
new file mode 100644
index 00000000..17e2d4d5
--- /dev/null
+++ b/src/PhpSpreadsheet/Reader/Ods/BaseReader.php
@@ -0,0 +1,77 @@
+spreadsheet = $spreadsheet;
+ $this->tableNs = $tableNs;
+ }
+
+ abstract public function read(DOMElement $workbookData): void;
+
+ protected function convertToExcelAddressValue(string $openOfficeAddress): string
+ {
+ $excelAddress = $openOfficeAddress;
+
+ // Cell range 3-d reference
+ // As we don't support 3-d ranges, we're just going to take a quick and dirty approach
+ // and assume that the second worksheet reference is the same as the first
+ $excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+):\$?([^\.]+)\.([^\.]+)/miu', '$1!$2:$4', $excelAddress);
+ // Cell range reference in another sheet
+ $excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+):\.([^\.]+)/miu', '$1!$2:$3', $excelAddress ?? '');
+ // Cell reference in another sheet
+ $excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+)/miu', '$1!$2', $excelAddress ?? '');
+ // Cell range reference
+ $excelAddress = preg_replace('/\.([^\.]+):\.([^\.]+)/miu', '$1:$2', $excelAddress ?? '');
+ // Simple cell reference
+ $excelAddress = preg_replace('/\.([^\.]+)/miu', '$1', $excelAddress ?? '');
+
+ return $excelAddress ?? '';
+ }
+
+ protected function convertToExcelFormulaValue(string $openOfficeFormula): string
+ {
+ $temp = explode('"', $openOfficeFormula);
+ $tKey = false;
+ foreach ($temp as &$value) {
+ // @var string $value
+ // Only replace in alternate array entries (i.e. non-quoted blocks)
+ if ($tKey = !$tKey) {
+ // Cell range reference in another sheet
+ $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', '$1!$2:$3', $value);
+ // Cell reference in another sheet
+ $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+)\]/miu', '$1!$2', $value ?? '');
+ // Cell range reference
+ $value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/miu', '$1:$2', $value ?? '');
+ // Simple cell reference
+ $value = preg_replace('/\[\.([^\.]+)\]/miu', '$1', $value ?? '');
+ // Convert references to defined names/formulae
+ $value = str_replace('$$', '', $value ?? '');
+
+ $value = Calculation::translateSeparator(';', ',', $value, $inBraces);
+ }
+ }
+
+ // Then rebuild the formula string
+ $excelFormula = implode('"', $temp);
+
+ return $excelFormula;
+ }
+}
diff --git a/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php b/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php
new file mode 100644
index 00000000..6810a3c7
--- /dev/null
+++ b/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php
@@ -0,0 +1,66 @@
+readDefinedRanges($workbookData);
+ $this->readDefinedExpressions($workbookData);
+ }
+
+ /**
+ * Read any Named Ranges that are defined in this spreadsheet.
+ */
+ protected function readDefinedRanges(DOMElement $workbookData): void
+ {
+ $namedRanges = $workbookData->getElementsByTagNameNS($this->tableNs, 'named-range');
+ foreach ($namedRanges as $definedNameElement) {
+ $definedName = $definedNameElement->getAttributeNS($this->tableNs, 'name');
+ $baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address');
+ $range = $definedNameElement->getAttributeNS($this->tableNs, 'cell-range-address');
+
+ $baseAddress = $this->convertToExcelAddressValue($baseAddress);
+ $range = $this->convertToExcelAddressValue($range);
+
+ $this->addDefinedName($baseAddress, $definedName, $range);
+ }
+ }
+
+ /**
+ * Read any Named Formulae that are defined in this spreadsheet.
+ */
+ protected function readDefinedExpressions(DOMElement $workbookData): void
+ {
+ $namedExpressions = $workbookData->getElementsByTagNameNS($this->tableNs, 'named-expression');
+ foreach ($namedExpressions as $definedNameElement) {
+ $definedName = $definedNameElement->getAttributeNS($this->tableNs, 'name');
+ $baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address');
+ $expression = $definedNameElement->getAttributeNS($this->tableNs, 'expression');
+
+ $baseAddress = $this->convertToExcelAddressValue($baseAddress);
+ $expression = substr($expression, strpos($expression, ':=') + 1);
+ $expression = $this->convertToExcelFormulaValue($expression);
+
+ $this->addDefinedName($baseAddress, $definedName, $expression);
+ }
+ }
+
+ /**
+ * Assess scope and store the Defined Name.
+ */
+ private function addDefinedName(string $baseAddress, string $definedName, string $value): void
+ {
+ [$sheetReference] = Worksheet::extractSheetTitle($baseAddress, true);
+ $worksheet = $this->spreadsheet->getSheetByName($sheetReference);
+ // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet
+ if ($worksheet !== null) {
+ $this->spreadsheet->addDefinedName(DefinedName::createInstance((string) $definedName, $worksheet, $value));
+ }
+ }
+}
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php
index 2b0c7016..80c32065 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php
@@ -42,9 +42,12 @@ class Styles extends BaseParserClass
public static function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void
{
- $fontStyle->setName((string) $fontStyleXml->name['val']);
- $fontStyle->setSize((float) $fontStyleXml->sz['val']);
-
+ if (isset($fontStyleXml->name, $fontStyleXml->name['val'])) {
+ $fontStyle->setName((string) $fontStyleXml->name['val']);
+ }
+ if (isset($fontStyleXml->sz, $fontStyleXml->sz['val'])) {
+ $fontStyle->setSize((float) $fontStyleXml->sz['val']);
+ }
if (isset($fontStyleXml->b)) {
$fontStyle->setBold(!isset($fontStyleXml->b['val']) || self::boolean((string) $fontStyleXml->b['val']));
}
@@ -68,8 +71,7 @@ class Styles extends BaseParserClass
$verticalAlign = strtolower((string) $fontStyleXml->vertAlign['val']);
if ($verticalAlign === 'superscript') {
$fontStyle->setSuperscript(true);
- }
- if ($verticalAlign === 'subscript') {
+ } elseif ($verticalAlign === 'subscript') {
$fontStyle->setSubscript(true);
}
}
@@ -103,17 +105,21 @@ class Styles extends BaseParserClass
self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)
);
} elseif ($fillStyleXml->patternFill) {
- $patternType = (string) $fillStyleXml->patternFill['patternType'] != ''
- ? (string) $fillStyleXml->patternFill['patternType']
- : Fill::FILL_NONE;
-
- $fillStyle->setFillType($patternType);
+ $defaultFillStyle = Fill::FILL_NONE;
if ($fillStyleXml->patternFill->fgColor) {
$fillStyle->getStartColor()->setARGB(self::readColor($fillStyleXml->patternFill->fgColor, true));
+ $defaultFillStyle = Fill::FILL_SOLID;
}
if ($fillStyleXml->patternFill->bgColor) {
$fillStyle->getEndColor()->setARGB(self::readColor($fillStyleXml->patternFill->bgColor, true));
+ $defaultFillStyle = Fill::FILL_SOLID;
}
+
+ $patternType = (string) $fillStyleXml->patternFill['patternType'] != ''
+ ? (string) $fillStyleXml->patternFill['patternType']
+ : $defaultFillStyle;
+
+ $fillStyle->setFillType($patternType);
}
}
diff --git a/src/PhpSpreadsheet/Reader/Xml.php b/src/PhpSpreadsheet/Reader/Xml.php
index 282cd528..4ef4efe7 100644
--- a/src/PhpSpreadsheet/Reader/Xml.php
+++ b/src/PhpSpreadsheet/Reader/Xml.php
@@ -284,7 +284,7 @@ class Xml extends BaseReader
$worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']);
if (
- (isset($this->loadSheetsOnly)) && (isset($worksheet_ss['Name'])) &&
+ isset($this->loadSheetsOnly, $worksheet_ss['Name']) &&
(!in_array($worksheet_ss['Name'], $this->loadSheetsOnly))
) {
continue;
diff --git a/src/PhpSpreadsheet/Writer/Ods/AutoFilters.php b/src/PhpSpreadsheet/Writer/Ods/AutoFilters.php
new file mode 100644
index 00000000..cf0450f1
--- /dev/null
+++ b/src/PhpSpreadsheet/Writer/Ods/AutoFilters.php
@@ -0,0 +1,63 @@
+objWriter = $objWriter;
+ $this->spreadsheet = $spreadsheet;
+ }
+
+ public function write(): void
+ {
+ $wrapperWritten = false;
+ $sheetCount = $this->spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $worksheet = $this->spreadsheet->getSheet($i);
+ $autofilter = $worksheet->getAutoFilter();
+ if ($autofilter !== null && !empty($autofilter->getRange())) {
+ if ($wrapperWritten === false) {
+ $this->objWriter->startElement('table:database-ranges');
+ $wrapperWritten = true;
+ }
+ $this->objWriter->startElement('table:database-range');
+ $this->objWriter->writeAttribute('table:orientation', 'column');
+ $this->objWriter->writeAttribute('table:display-filter-buttons', 'true');
+ $this->objWriter->writeAttribute(
+ 'table:target-range-address',
+ $this->formatRange($worksheet, $autofilter)
+ );
+ $this->objWriter->endElement();
+ }
+ }
+
+ if ($wrapperWritten === true) {
+ $this->objWriter->endElement();
+ }
+ }
+
+ protected function formatRange(Worksheet $worksheet, Autofilter $autofilter): string
+ {
+ $title = $worksheet->getTitle();
+ $range = $autofilter->getRange();
+
+ return "'{$title}'.{$range}";
+ }
+}
diff --git a/src/PhpSpreadsheet/Writer/Ods/Content.php b/src/PhpSpreadsheet/Writer/Ods/Content.php
index e4bd1793..a589e549 100644
--- a/src/PhpSpreadsheet/Writer/Ods/Content.php
+++ b/src/PhpSpreadsheet/Writer/Ods/Content.php
@@ -101,6 +101,7 @@ class Content extends WriterPart
$this->writeSheets($objWriter);
+ (new AutoFilters($objWriter, $this->getParentWriter()->getSpreadsheet()))->write();
// Defined names (ranges and formulae)
(new NamedExpressions($objWriter, $this->getParentWriter()->getSpreadsheet(), $this->formulaConvertor))->write();
diff --git a/src/PhpSpreadsheet/Writer/Xls.php b/src/PhpSpreadsheet/Writer/Xls.php
index a1b477bf..4bece774 100644
--- a/src/PhpSpreadsheet/Writer/Xls.php
+++ b/src/PhpSpreadsheet/Writer/Xls.php
@@ -219,7 +219,8 @@ class Xls extends BaseWriter
$arrRootData[] = $OLE_DocumentSummaryInformation;
}
- $root = new Root(time(), time(), $arrRootData);
+ $time = $this->spreadsheet->getProperties()->getModified();
+ $root = new Root($time, $time, $arrRootData);
// save the OLE file
$this->openFileHandle($pFilename);
$root->save($this->fileHandle);
diff --git a/src/PhpSpreadsheet/Writer/Xls/Style/ColorMap.php b/src/PhpSpreadsheet/Writer/Xls/Style/ColorMap.php
index e3a6b206..caf85c04 100644
--- a/src/PhpSpreadsheet/Writer/Xls/Style/ColorMap.php
+++ b/src/PhpSpreadsheet/Writer/Xls/Style/ColorMap.php
@@ -75,6 +75,16 @@ class ColorMap
return self::$colorMap["#{$colorRgb}"];
}
+// TODO Try and map RGB value to nearest colour within the define pallette
+// $red = Color::getRed($colorRgb, false);
+// $green = Color::getGreen($colorRgb, false);
+// $blue = Color::getBlue($colorRgb, false);
+
+// $paletteSpace = 3;
+// $newColor = ($red * $paletteSpace / 256) * ($paletteSpace * $paletteSpace) +
+// ($green * $paletteSpace / 256) * $paletteSpace +
+// ($blue * $paletteSpace / 256);
+
return $defaultIndex;
}
}
diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
index 84844d3d..894ce03a 100644
--- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
+++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
@@ -390,7 +390,13 @@ class Worksheet extends BIFFwriter
// Row dimensions
foreach ($phpSheet->getRowDimensions() as $rowDimension) {
$xfIndex = $rowDimension->getXfIndex() + 15; // there are 15 cellXfs
- $this->writeRow($rowDimension->getRowIndex() - 1, (int) $rowDimension->getRowHeight(), $xfIndex, $rowDimension->getVisible(), $rowDimension->getOutlineLevel());
+ $this->writeRow(
+ $rowDimension->getRowIndex() - 1,
+ (int) $rowDimension->getRowHeight(),
+ $xfIndex,
+ !$rowDimension->getVisible(),
+ $rowDimension->getOutlineLevel()
+ );
}
// Write Cells
@@ -1181,7 +1187,7 @@ class Worksheet extends BIFFwriter
// collapsed. The zero height flag, 0x20, is used to collapse a row.
$grbit |= $level;
- if ($hidden) {
+ if ($hidden === true) {
$grbit |= 0x0030;
}
if ($height !== null) {
@@ -2160,7 +2166,9 @@ class Worksheet extends BIFFwriter
*/
public function insertBitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1): void
{
- $bitmap_array = (is_resource($bitmap) || $bitmap instanceof GdImage ? $this->processBitmapGd($bitmap) : $this->processBitmap($bitmap));
+ $bitmap_array = (is_resource($bitmap) || $bitmap instanceof GdImage
+ ? $this->processBitmapGd($bitmap)
+ : $this->processBitmap($bitmap));
[$width, $height, $size, $data] = $bitmap_array;
// Scale the frame of the image.
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
index eefae529..56b890bb 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
@@ -318,10 +318,10 @@ class Chart extends WriterPart
if ($chartType === DataSeries::TYPE_BUBBLECHART) {
$this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id1, $id2, $catIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines);
} else {
- $this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $yAxis);
+ $this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $xAxis);
}
- $this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines);
+ $this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $yAxis, $majorGridlines, $minorGridlines);
}
$objWriter->endElement();
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
index 2c8e6f58..38dead4f 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
@@ -1236,6 +1236,7 @@ class Worksheet extends WriterPart
$objWriter->writeAttribute('t', 'str');
} elseif (is_bool($calculatedValue)) {
$objWriter->writeAttribute('t', 'b');
+ $calculatedValue = (int) $calculatedValue;
}
// array values are not yet supported
//$attributes = $pCell->getFormulaAttributes();
diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/HyperlinkTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/HyperlinkTest.php
new file mode 100644
index 00000000..e71992ed
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/HyperlinkTest.php
@@ -0,0 +1,51 @@
+getMockBuilder(Cell::class)
+ ->onlyMethods(['getHyperlink'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $cell->method('getHyperlink')
+ ->willReturn($hyperlink);
+
+ $result = LookupRef::HYPERLINK($linkUrl, $description, $cell);
+ if (!is_array($expectedResult)) {
+ self::assertSame($expectedResult, $result);
+ } else {
+ self::assertSame($expectedResult[1], $result);
+ self::assertSame($expectedResult[0], $hyperlink->getUrl());
+ self::assertSame($expectedResult[1], $hyperlink->getTooltip());
+ }
+ }
+
+ public function providerHYPERLINK(): array
+ {
+ return require 'tests/data/Calculation/LookupRef/HYPERLINK.php';
+ }
+
+ public function testHYPERLINKwithoutCell(): void
+ {
+ $result = LookupRef::HYPERLINK('https://phpspreadsheet.readthedocs.io/en/latest/', 'Read the Docs');
+ self::assertSame(Functions::REF(), $result);
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumTest.php
index a9ea7f29..b85f0c90 100644
--- a/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumTest.php
+++ b/tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumTest.php
@@ -26,4 +26,22 @@ class SumTest extends AllSetupTeardown
{
return require 'tests/data/Calculation/MathTrig/SUM.php';
}
+
+ /**
+ * @dataProvider providerSUMLiterals
+ *
+ * @param mixed $expectedResult
+ */
+ public function testSUMLiterals($expectedResult, string $args): void
+ {
+ $sheet = $this->sheet;
+ $sheet->getCell('B1')->setValue("=SUM($args)");
+ $result = $sheet->getCell('B1')->getCalculatedValue();
+ self::assertEqualsWithDelta($expectedResult, $result, 1E-12);
+ }
+
+ public function providerSUMLiterals(): array
+ {
+ return require 'tests/data/Calculation/MathTrig/SUMLITERALS.php';
+ }
}
diff --git a/tests/PhpSpreadsheetTests/Reader/Gnumeric/AutoFilterTest.php b/tests/PhpSpreadsheetTests/Reader/Gnumeric/AutoFilterTest.php
new file mode 100644
index 00000000..18dde473
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Gnumeric/AutoFilterTest.php
@@ -0,0 +1,31 @@
+spreadsheet = $reader->load($filename);
+ }
+
+ public function testAutoFilterRange(): void
+ {
+ $worksheet = $this->spreadsheet->getActiveSheet();
+
+ $autoFilterRange = $worksheet->getAutoFilter()->getRange();
+
+ self::assertSame('A1:D57', $autoFilterRange);
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php b/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php
index e24178e5..9544fc3a 100644
--- a/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Gnumeric/GnumericLoadTest.php
@@ -115,7 +115,8 @@ class GnumericLoadTest extends TestCase
self::assertEquals(Font::UNDERLINE_DOUBLE, $sheet->getCell('A24')->getStyle()->getFont()->getUnderline());
self::assertTrue($sheet->getCell('B23')->getStyle()->getFont()->getSubScript());
self::assertTrue($sheet->getCell('B24')->getStyle()->getFont()->getSuperScript());
- self::assertFalse($sheet->getRowDimension(30)->getVisible());
+ $rowDimension = $sheet->getRowDimension(30);
+ self::assertFalse($rowDimension->getVisible());
}
public function testLoadFilter(): void
diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/AutoFilterTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/AutoFilterTest.php
new file mode 100644
index 00000000..47c7ee6a
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Ods/AutoFilterTest.php
@@ -0,0 +1,31 @@
+spreadsheet = $reader->load($filename);
+ }
+
+ public function testAutoFilterRange(): void
+ {
+ $worksheet = $this->spreadsheet->getActiveSheet();
+
+ $autoFilterRange = $worksheet->getAutoFilter()->getRange();
+
+ self::assertSame('A1:C9', $autoFilterRange);
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/DefinedNamesTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/DefinedNamesTest.php
new file mode 100644
index 00000000..760421ce
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Ods/DefinedNamesTest.php
@@ -0,0 +1,35 @@
+spreadsheet = $reader->load($filename);
+ }
+
+ public function testDefinedNames(): void
+ {
+ $worksheet = $this->spreadsheet->getActiveSheet();
+
+ $firstDefinedNameValue = $worksheet->getCell('First')->getValue();
+ $secondDefinedNameValue = $worksheet->getCell('Second')->getValue();
+ $calculatedFormulaValue = $worksheet->getCell('B2')->getCalculatedValue();
+
+ self::assertSame(3, $firstDefinedNameValue);
+ self::assertSame(4, $secondDefinedNameValue);
+ self::assertSame(12, $calculatedFormulaValue);
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/DefaultFillTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/DefaultFillTest.php
index 88d666b2..dc61b953 100644
--- a/tests/PhpSpreadsheetTests/Reader/Xlsx/DefaultFillTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/DefaultFillTest.php
@@ -1,6 +1,6 @@
getCell('J16')->getStyle()->getFill()->getFillType());
self::assertSame('solid', $sheet->getCell('C2')->getStyle()->getFill()->getFillType());
}
+
+ public function testDefaultConditionalFill(): void
+ {
+ // default fill pattern for a conditional style where the filltype is not defined
+ $filename = 'tests/data/Reader/XLSX/pr2050cf-fill.xlsx';
+ $reader = IOFactory::createReader('Xlsx');
+ $spreadsheet = $reader->load($filename);
+
+ $style = $spreadsheet->getActiveSheet()->getConditionalStyles('A1')[0]->getStyle();
+ self::assertSame('solid', $style->getFill()->getFillType());
+ }
}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/DefaultFontTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/DefaultFontTest.php
new file mode 100644
index 00000000..a4e94804
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/DefaultFontTest.php
@@ -0,0 +1,22 @@
+load($filename);
+
+ $style = $spreadsheet->getActiveSheet()->getConditionalStyles('A1')[0]->getStyle();
+ self::assertSame('9C0006', $style->getFont()->getColor()->getRGB());
+ self::assertNull($style->getFont()->getName());
+ self::assertNull($style->getFont()->getSize());
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Csv/CsvExcelCompatibilityTest.php b/tests/PhpSpreadsheetTests/Writer/Csv/CsvExcelCompatibilityTest.php
new file mode 100644
index 00000000..9b7d16aa
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Csv/CsvExcelCompatibilityTest.php
@@ -0,0 +1,49 @@
+getActiveSheet();
+ $sheet->setCellValue('A1', '1');
+ $sheet->setCellValue('B1', '2');
+ $sheet->setCellValue('C1', '3');
+ $sheet->setCellValue('A2', '4');
+ $sheet->setCellValue('B2', '5');
+ $sheet->setCellValue('C2', '6');
+ $writer = new CsvWriter($spreadsheet);
+ $writer->setExcelCompatibility(true);
+ self::assertSame('', $writer->getOutputEncoding());
+ $filename = File::temporaryFilename();
+ $writer->save($filename);
+ $reader = new CsvReader();
+ $spreadsheet2 = $reader->load($filename);
+ $contents = file_get_contents($filename);
+ unlink($filename);
+ self::assertEquals(1, $spreadsheet2->getActiveSheet()->getCell('A1')->getValue());
+ self::assertEquals(6, $spreadsheet2->getActiveSheet()->getCell('C2')->getValue());
+ self::assertStringContainsString(CsvReader::UTF8_BOM, $contents);
+ self::assertStringContainsString("\r\n", $contents);
+ self::assertStringContainsString('sep=;', $contents);
+ self::assertStringContainsString('"1";"2";"3"', $contents);
+ self::assertStringContainsString('"4";"5";"6"', $contents);
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Ods/AutoFilterTest.php b/tests/PhpSpreadsheetTests/Writer/Ods/AutoFilterTest.php
new file mode 100644
index 00000000..4368ef76
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Ods/AutoFilterTest.php
@@ -0,0 +1,33 @@
+getActiveSheet();
+
+ $dataSet = [
+ ['Year', 'Quarter', 'Sales'],
+ [2020, 'Q1', 100],
+ [2020, 'Q2', 120],
+ [2020, 'Q3', 140],
+ [2020, 'Q4', 160],
+ [2021, 'Q1', 180],
+ [2021, 'Q2', 75],
+ [2021, 'Q3', 0],
+ [2021, 'Q4', 0],
+ ];
+ $worksheet->fromArray($dataSet, null, 'A1');
+ $worksheet->getAutoFilter()->setRange('A1:C9');
+
+ $reloaded = $this->writeAndReload($spreadsheet, 'Ods');
+
+ self::assertSame('A1:C9', $reloaded->getActiveSheet()->getAutoFilter()->getRange());
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Ods/DefinedNamesTest.php b/tests/PhpSpreadsheetTests/Writer/Ods/DefinedNamesTest.php
new file mode 100644
index 00000000..1b2e30b2
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Ods/DefinedNamesTest.php
@@ -0,0 +1,33 @@
+getActiveSheet();
+
+ $dataSet = [
+ [7, 'x', 5],
+ ['=', '=FORMULA'],
+ ];
+ $worksheet->fromArray($dataSet, null, 'A1');
+
+ $spreadsheet->addDefinedName(new NamedRange('FIRST', $worksheet, '$A$1'));
+ $spreadsheet->addDefinedName(new NamedRange('SECOND', $worksheet, '$C$1'));
+ $spreadsheet->addDefinedName(new NamedFormula('FORMULA', $worksheet, '=FIRST*SECOND'));
+
+ $reloaded = $this->writeAndReload($spreadsheet, 'Ods');
+
+ self::assertSame(7, $reloaded->getActiveSheet()->getCell('FIRST')->getValue());
+ self::assertSame(5, $reloaded->getActiveSheet()->getCell('SECOND')->getValue());
+ self::assertSame(35, $reloaded->getActiveSheet()->getCell('B2')->getCalculatedValue());
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/VisibilityTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/VisibilityTest.php
new file mode 100644
index 00000000..7de39328
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Xls/VisibilityTest.php
@@ -0,0 +1,99 @@
+getActiveSheet();
+ foreach ($visibleRows as $row => $visibility) {
+ $worksheet->setCellValue("A{$row}", $row);
+ $worksheet->getRowDimension($row)->setVisible($visibility);
+ }
+
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xls');
+ $reloadedWorksheet = $reloadedSpreadsheet->getActiveSheet();
+ foreach ($visibleRows as $row => $visibility) {
+ self::assertSame($visibility, $reloadedWorksheet->getRowDimension($row)->getVisible());
+ }
+ }
+
+ public function dataProviderRowVisibility(): array
+ {
+ return [
+ [
+ [1 => true, 2 => false, 3 => false, 4 => true, 5 => true, 6 => false],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataProviderColumnVisibility
+ */
+ public function testColumnVisibility(array $visibleColumns): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $worksheet = $spreadsheet->getActiveSheet();
+ foreach ($visibleColumns as $column => $visibility) {
+ $worksheet->setCellValue("{$column}1", $column);
+ $worksheet->getColumnDimension($column)->setVisible($visibility);
+ }
+
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xls');
+ $reloadedWorksheet = $reloadedSpreadsheet->getActiveSheet();
+ foreach ($visibleColumns as $column => $visibility) {
+ self::assertSame($visibility, $reloadedWorksheet->getColumnDimension($column)->getVisible());
+ }
+ }
+
+ public function dataProviderColumnVisibility(): array
+ {
+ return [
+ [
+ ['A' => true, 'B' => false, 'C' => false, 'D' => true, 'E' => true, 'F' => false],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataProviderSheetVisibility
+ */
+ public function testSheetVisibility(array $visibleSheets): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $spreadsheet->removeSheetByIndex(0);
+ foreach ($visibleSheets as $sheetName => $visibility) {
+ $worksheet = $spreadsheet->addSheet(new Worksheet($spreadsheet, $sheetName));
+ $worksheet->setCellValue('A1', $sheetName);
+ $worksheet->setSheetState($visibility);
+ }
+
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xls');
+ foreach ($visibleSheets as $sheetName => $visibility) {
+ $reloadedWorksheet = $reloadedSpreadsheet->getSheetByName($sheetName) ?? new Worksheet();
+ self::assertSame($visibility, $reloadedWorksheet->getSheetState());
+ }
+ }
+
+ public function dataProviderSheetVisibility(): array
+ {
+ return [
+ [
+ [
+ 'Worksheet 1' => Worksheet::SHEETSTATE_HIDDEN,
+ 'Worksheet 2' => Worksheet::SHEETSTATE_VERYHIDDEN,
+ 'Worksheet 3' => Worksheet::SHEETSTATE_VISIBLE,
+ ],
+ ],
+ ];
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue2082Test.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue2082Test.php
new file mode 100644
index 00000000..1f72a382
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue2082Test.php
@@ -0,0 +1,40 @@
+getActiveSheet();
+ $worksheet->fromArray(['A', 'B', 'C', 'D']);
+ $worksheet->getCell('A2')->setValue('=A1<>"A"');
+ $worksheet->getCell('A3')->setValue('=A1="A"');
+ $worksheet->getCell('B2')->setValue('=LEFT(B1, 0)');
+ $worksheet->getCell('B3')->setValue('=B2=""');
+
+ $writer = new Writer($spreadsheet);
+ $writer->save($outputFilename);
+ $zipfile = "zip://$outputFilename#xl/worksheets/sheet1.xml";
+ $contents = file_get_contents($zipfile);
+ unlink($outputFilename);
+ if ($contents === false) {
+ self::fail('Unable to open file');
+ } else {
+ self::assertStringContainsString('A1<>"A"0', $contents);
+ self::assertStringContainsString('A1="A"1', $contents);
+ self::assertStringContainsString('LEFT(B1, 0)', $contents);
+ self::assertStringContainsString('B2=""1', $contents);
+ }
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/VisibilityTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/VisibilityTest.php
new file mode 100644
index 00000000..7e1ca967
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/VisibilityTest.php
@@ -0,0 +1,99 @@
+getActiveSheet();
+ foreach ($visibleRows as $row => $visibility) {
+ $worksheet->setCellValue("A{$row}", $row);
+ $worksheet->getRowDimension($row)->setVisible($visibility);
+ }
+
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
+ $reloadedWorksheet = $reloadedSpreadsheet->getActiveSheet();
+ foreach ($visibleRows as $row => $visibility) {
+ self::assertSame($visibility, $reloadedWorksheet->getRowDimension($row)->getVisible());
+ }
+ }
+
+ public function dataProviderRowVisibility(): array
+ {
+ return [
+ [
+ [1 => false, 2 => false, 3 => true, 4 => false, 5 => true, 6 => false],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataProviderColumnVisibility
+ */
+ public function testColumnVisibility(array $visibleColumns): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $worksheet = $spreadsheet->getActiveSheet();
+ foreach ($visibleColumns as $column => $visibility) {
+ $worksheet->setCellValue("{$column}1", $column);
+ $worksheet->getColumnDimension($column)->setVisible($visibility);
+ }
+
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
+ $reloadedWorksheet = $reloadedSpreadsheet->getActiveSheet();
+ foreach ($visibleColumns as $column => $visibility) {
+ self::assertSame($visibility, $reloadedWorksheet->getColumnDimension($column)->getVisible());
+ }
+ }
+
+ public function dataProviderColumnVisibility(): array
+ {
+ return [
+ [
+ ['A' => false, 'B' => false, 'C' => true, 'D' => false, 'E' => true, 'F' => false],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataProviderSheetVisibility
+ */
+ public function testSheetVisibility(array $visibleSheets): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $spreadsheet->removeSheetByIndex(0);
+ foreach ($visibleSheets as $sheetName => $visibility) {
+ $worksheet = $spreadsheet->addSheet(new Worksheet($spreadsheet, $sheetName));
+ $worksheet->setCellValue('A1', $sheetName);
+ $worksheet->setSheetState($visibility);
+ }
+
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
+ foreach ($visibleSheets as $sheetName => $visibility) {
+ $reloadedWorksheet = $reloadedSpreadsheet->getSheetByName($sheetName) ?? new Worksheet();
+ self::assertSame($visibility, $reloadedWorksheet->getSheetState());
+ }
+ }
+
+ public function dataProviderSheetVisibility(): array
+ {
+ return [
+ [
+ [
+ 'Worksheet 1' => Worksheet::SHEETSTATE_HIDDEN,
+ 'Worksheet 2' => Worksheet::SHEETSTATE_VERYHIDDEN,
+ 'Worksheet 3' => Worksheet::SHEETSTATE_VISIBLE,
+ ],
+ ],
+ ];
+ }
+}
diff --git a/tests/data/Calculation/LookupRef/CHOOSE.php b/tests/data/Calculation/LookupRef/CHOOSE.php
index 06371c79..96c29780 100644
--- a/tests/data/Calculation/LookupRef/CHOOSE.php
+++ b/tests/data/Calculation/LookupRef/CHOOSE.php
@@ -25,4 +25,12 @@ return [
'#VALUE!',
0, 'red', 'blue', 'green', 'brown',
],
+ [
+ '#VALUE!',
+ 'NaN', 'red', 'blue', 'green', 'brown',
+ ],
+ [
+ ['blue', 'purple'],
+ 3, ['red', 'orange'], ['yellow', 'green'], ['blue', 'purple'],
+ ],
];
diff --git a/tests/data/Calculation/LookupRef/HLOOKUP.php b/tests/data/Calculation/LookupRef/HLOOKUP.php
index d2a8a446..61cb7e06 100644
--- a/tests/data/Calculation/LookupRef/HLOOKUP.php
+++ b/tests/data/Calculation/LookupRef/HLOOKUP.php
@@ -328,4 +328,28 @@ return [
2,
false,
],
+ [
+ 0.61,
+ 'Ed',
+ [
+ [null, 'Ann', 'Cara', 'Colin', 'Ed', 'Frank'],
+ ['Math', 0.58, 0.90, 0.67, 0.76, 0.80],
+ ['French', 0.61, 0.71, 0.59, 0.59, 0.76],
+ ['Physics', 0.75, 0.45, 0.39, 0.52, 0.69],
+ ['Bioogy', 0.39, 0.55, 0.77, 0.61, 0.45],
+ ],
+ 5,
+ false,
+ ],
+ [
+ 'Normal Weight',
+ 23.5,
+ [
+ [null, 'Min', 0.0, 18.5, 25.0, 30.0],
+ ['BMI', 'Max', 18.4, 24.9, 29.9, null],
+ [null, 'Body Type', 'Underweight', 'Normal Weight', 'Overweight', 'Obese'],
+ ],
+ 3,
+ true,
+ ],
];
diff --git a/tests/data/Calculation/LookupRef/HYPERLINK.php b/tests/data/Calculation/LookupRef/HYPERLINK.php
new file mode 100644
index 00000000..9a5e4c2e
--- /dev/null
+++ b/tests/data/Calculation/LookupRef/HYPERLINK.php
@@ -0,0 +1,26 @@
+ ['R' => 1]],
],
- [
+ 'Negative Row' => [
'#VALUE!', // Expected
// Input
[
@@ -15,7 +15,7 @@ return [
],
-1,
],
- [
+ 'Row > matrix rows' => [
'#REF!', // Expected
// Input
[
@@ -24,7 +24,25 @@ return [
],
10,
],
- [
+ 'Row is not a number' => [
+ '#VALUE!', // Expected
+ // Input
+ [
+ 20 => ['R' => 1],
+ 21 => ['R' => 2],
+ ],
+ 'NaN',
+ ],
+ 'Row is Error' => [
+ '#N/A', // Expected
+ // Input
+ [
+ 20 => ['R' => 1],
+ 21 => ['R' => 2],
+ ],
+ '#N/A',
+ ],
+ 'Return row 2' => [
[21 => ['R' => 2]], // Expected
// Input
[
@@ -33,7 +51,7 @@ return [
],
2,
],
- [
+ 'Return row 2 from larger matrix' => [
[21 => ['R' => 2, 'S' => 4]], // Expected
// Input
[
@@ -43,17 +61,17 @@ return [
2,
0,
],
- [
+ 'Negative Column' => [
'#VALUE!', // Expected
// Input
[
'20' => ['R' => 1, 'S' => 3],
'21' => ['R' => 2, 'S' => 4],
],
- 2,
+ 0,
-1,
],
- [
+ 'Column > matrix columns' => [
'#REF!', // Expected
// Input
[
@@ -63,15 +81,25 @@ return [
2,
10,
],
- [
- '#REF!', // Expected
+ 'Column is not a number' => [
+ '#VALUE!', // Expected
// Input
[
- '20' => ['R' => 1, 'S' => 3],
- '21' => ['R' => 2, 'S' => 4],
+ 20 => ['R' => 1],
+ 21 => ['R' => 2],
],
- 10,
- 2,
+ 1,
+ 'NaN',
+ ],
+ 'Column is Error' => [
+ '#N/A', // Expected
+ // Input
+ [
+ 20 => ['R' => 1],
+ 21 => ['R' => 2],
+ ],
+ 1,
+ '#N/A',
],
[
4, // Expected
@@ -115,6 +143,15 @@ return [
2,
1,
],
+ [
+ [1 => ['Bananas', 'Pears']],
+ [
+ ['Apples', 'Lemons'],
+ ['Bananas', 'Pears'],
+ ],
+ 2,
+ 0,
+ ],
[
3,
[
diff --git a/tests/data/Calculation/LookupRef/VLOOKUP.php b/tests/data/Calculation/LookupRef/VLOOKUP.php
index a5d6f615..0a059048 100644
--- a/tests/data/Calculation/LookupRef/VLOOKUP.php
+++ b/tests/data/Calculation/LookupRef/VLOOKUP.php
@@ -349,4 +349,35 @@ return [
],
2.0,
],
+ [
+ 3.50,
+ 'Cornflakes',
+ [
+ ['Item Description', 'Price'],
+ ['Tinned Tomatoes', 0.90],
+ ['Tinned Tuna', 1.50],
+ ['Cornflakes', 3.50],
+ ['Shortcake Biscuits', 1.00],
+ ['Toothpaste', 4.10],
+ ['Tinned Baked Beans', 0.99],
+ ['White Sliced Bread', 0.80],
+ ],
+ 2,
+ false,
+ ],
+ [
+ 'E',
+ 0.52,
+ [
+ ['Lower', 'Upper', 'Grade'],
+ [0.00, 0.44, 'F'],
+ [0.45, 0.54, 'E'],
+ [0.55, 0.64, 'D'],
+ [0.65, 0.74, 'C'],
+ [0.75, 0.84, 'B'],
+ [0.85, 1.00, 'A'],
+ ],
+ 3,
+ true,
+ ],
];
diff --git a/tests/data/Calculation/MathTrig/SUM.php b/tests/data/Calculation/MathTrig/SUM.php
index a8219076..0c54613e 100644
--- a/tests/data/Calculation/MathTrig/SUM.php
+++ b/tests/data/Calculation/MathTrig/SUM.php
@@ -4,5 +4,9 @@ return [
[50, 5, 15, 30],
[52, 5, 15, 30, 2],
[53.1, 5.7, 15, 30, 2.4],
- ['#VALUE!', 5.7, 'X', 30, 2.4], // error here conflicts with SUMIF
+ [52.1, 5.7, '14', 30, 2.4],
+ [38.1, 5.7, 'X', 30, 2.4], // error if entered in formula, but not in cell
+ [38.1, 5.7, null, 30, 2.4],
+ [38.1, 5.7, false, 30, 2.4],
+ [39.1, 5.7, true, 30, 2.4],
];
diff --git a/tests/data/Calculation/MathTrig/SUMLITERALS.php b/tests/data/Calculation/MathTrig/SUMLITERALS.php
new file mode 100644
index 00000000..fd184ebd
--- /dev/null
+++ b/tests/data/Calculation/MathTrig/SUMLITERALS.php
@@ -0,0 +1,12 @@
+