diff --git a/composer.json b/composer.json
index 720039f1..dff99d7e 100644
--- a/composer.json
+++ b/composer.json
@@ -69,7 +69,7 @@
"ezyang/htmlpurifier": "^4.13",
"maennchen/zipstream-php": "^2.1",
"markbaker/complex": "^3.0",
- "markbaker/matrix": "^2.0",
+ "markbaker/matrix": "^3.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/simple-cache": "^1.0"
diff --git a/composer.lock b/composer.lock
index 9bfe87d0..7cbf16c6 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": "475a2da5744e2426d6201ca5d9c56283",
+ "content-hash": "08bcc40376dc4b219b21d172e40c622d",
"packages": [
{
"name": "ezyang/htmlpurifier",
@@ -184,16 +184,16 @@
},
{
"name": "markbaker/matrix",
- "version": "2.1.3",
+ "version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPMatrix.git",
- "reference": "174395a901b5ba0925f1d790fa91bab531074b61"
+ "reference": "c66aefcafb4f6c269510e9ac46b82619a904c576"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/174395a901b5ba0925f1d790fa91bab531074b61",
- "reference": "174395a901b5ba0925f1d790fa91bab531074b61",
+ "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/c66aefcafb4f6c269510e9ac46b82619a904c576",
+ "reference": "c66aefcafb4f6c269510e9ac46b82619a904c576",
"shasum": ""
},
"require": {
@@ -213,25 +213,7 @@
"autoload": {
"psr-4": {
"Matrix\\": "classes/src/"
- },
- "files": [
- "classes/src/Functions/adjoint.php",
- "classes/src/Functions/antidiagonal.php",
- "classes/src/Functions/cofactors.php",
- "classes/src/Functions/determinant.php",
- "classes/src/Functions/diagonal.php",
- "classes/src/Functions/identity.php",
- "classes/src/Functions/inverse.php",
- "classes/src/Functions/minors.php",
- "classes/src/Functions/trace.php",
- "classes/src/Functions/transpose.php",
- "classes/src/Operations/add.php",
- "classes/src/Operations/directsum.php",
- "classes/src/Operations/subtract.php",
- "classes/src/Operations/multiply.php",
- "classes/src/Operations/divideby.php",
- "classes/src/Operations/divideinto.php"
- ]
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -252,9 +234,9 @@
],
"support": {
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
- "source": "https://github.com/MarkBaker/PHPMatrix/tree/2.1.3"
+ "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.0"
},
- "time": "2021-05-25T15:42:17+00:00"
+ "time": "2021-07-01T19:01:15+00:00"
},
{
"name": "myclabs/php-enum",
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index f58e230d..8c689f11 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -3490,11 +3490,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Settings.php
- -
- message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\CodePage\\:\\:\\$pageArray has no typehint specified\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Shared/CodePage.php
-
-
message: "#^Parameter \\#1 \\$dateValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:timestampToExcel\\(\\) expects int, float\\|int\\|string given\\.$#"
count: 1
@@ -6520,16 +6515,6 @@ parameters:
count: 1
path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php
- -
- message: "#^Cannot call method getFormattedValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#"
- count: 1
- path: tests/PhpSpreadsheetTests/Reader/XlsTest.php
-
- -
- message: "#^Cannot call method getCoordinate\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#"
- count: 1
- path: tests/PhpSpreadsheetTests/Reader/XlsTest.php
-
-
message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xlsx\\\\AutoFilterTest\\:\\:getWorksheetInstance\\(\\) has no return typehint specified\\.$#"
count: 1
diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php
index 00d86f00..d78b227e 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx.php
@@ -312,15 +312,16 @@ class Xlsx extends BaseReader
private function castToFormula($c, $r, &$cellDataType, &$value, &$calculatedValue, &$sharedFormulas, $castBaseType): void
{
+ $attr = $c->f->attributes();
$cellDataType = 'f';
$value = "={$c->f}";
$calculatedValue = self::$castBaseType($c);
// Shared formula?
- if (isset($c->f['t']) && strtolower((string) $c->f['t']) == 'shared') {
- $instance = (string) $c->f['si'];
+ if (isset($attr['t']) && strtolower((string) $attr['t']) == 'shared') {
+ $instance = (string) $attr['si'];
- if (!isset($sharedFormulas[(string) $c->f['si']])) {
+ if (!isset($sharedFormulas[(string) $attr['si']])) {
$sharedFormulas[$instance] = ['master' => $r, 'formula' => $value];
} else {
$master = Coordinate::indexesFromString($sharedFormulas[$instance]['master']);
@@ -335,6 +336,29 @@ class Xlsx extends BaseReader
}
}
+ /**
+ * @param string $fileName
+ */
+ private function fileExistsInArchive(ZipArchive $archive, $fileName = ''): bool
+ {
+ // Root-relative paths
+ if (strpos($fileName, '//') !== false) {
+ $fileName = substr($fileName, strpos($fileName, '//') + 1);
+ }
+ $fileName = File::realpath($fileName);
+
+ // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming
+ // so we need to load case-insensitively from the zip file
+
+ // Apache POI fixes
+ $contents = $archive->locateName($fileName, ZipArchive::FL_NOCASE);
+ if ($contents === false) {
+ $contents = $archive->locateName(substr($fileName, 1), ZipArchive::FL_NOCASE);
+ }
+
+ return $contents !== false;
+ }
+
/**
* @param string $fileName
*
@@ -820,8 +844,8 @@ class Xlsx extends BaseReader
$this->readSheetProtection($docSheet, $xmlSheet);
}
- if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) {
- (new AutoFilter($docSheet, $xmlSheet))->load();
+ if ($this->readDataOnly === false) {
+ $this->readAutoFilterTables($xmlSheet, $docSheet, $dir, $fileWorksheet, $zip);
}
if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) {
@@ -1976,4 +2000,52 @@ class Xlsx extends BaseReader
}
}
}
+
+ private function readAutoFilterTables(
+ SimpleXMLElement $xmlSheet,
+ Worksheet $docSheet,
+ string $dir,
+ string $fileWorksheet,
+ ZipArchive $zip
+ ): void {
+ if ($xmlSheet && $xmlSheet->autoFilter) {
+ // In older files, autofilter structure is defined in the worksheet file
+ (new AutoFilter($docSheet, $xmlSheet))->load();
+ } elseif ($xmlSheet && $xmlSheet->tableParts && $xmlSheet->tableParts['count'] > 0) {
+ // But for Office365, MS decided to make it all just a bit more complicated
+ $this->readAutoFilterTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet);
+ }
+ }
+
+ private function readAutoFilterTablesInTablesFile(
+ SimpleXMLElement $xmlSheet,
+ string $dir,
+ string $fileWorksheet,
+ ZipArchive $zip,
+ Worksheet $docSheet
+ ): void {
+ foreach ($xmlSheet->tableParts->tablePart as $tablePart) {
+ $relation = self::getAttributes($tablePart, Namespaces::SCHEMA_OFFICE_DOCUMENT);
+ $tablePartRel = (string) $relation['id'];
+ $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
+
+ if ($zip->locateName($relationsFileName)) {
+ $relsTableReferences = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS);
+ foreach ($relsTableReferences->Relationship as $relationship) {
+ $relationshipAttributes = self::getAttributes($relationship, '');
+
+ if ((string) $relationshipAttributes['Id'] === $tablePartRel) {
+ $relationshipFileName = (string) $relationshipAttributes['Target'];
+ $relationshipFilePath = dirname("$dir/$fileWorksheet") . '/' . $relationshipFileName;
+ $relationshipFilePath = File::realpath($relationshipFilePath);
+
+ if ($this->fileExistsInArchive($this->zip, $relationshipFilePath)) {
+ $autoFilter = $this->loadZip($relationshipFilePath);
+ (new AutoFilter($docSheet, $autoFilter))->load();
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/PhpSpreadsheet/Shared/CodePage.php b/src/PhpSpreadsheet/Shared/CodePage.php
index 1d5d8933..8718a613 100644
--- a/src/PhpSpreadsheet/Shared/CodePage.php
+++ b/src/PhpSpreadsheet/Shared/CodePage.php
@@ -8,6 +8,7 @@ class CodePage
{
public const DEFAULT_CODE_PAGE = 'CP1252';
+ /** @var array */
private static $pageArray = [
0 => 'CP1252', // CodePage is not always correctly set when the xls file was saved by Apple's Numbers program
367 => 'ASCII', // ASCII
@@ -56,7 +57,7 @@ class CodePage
10010 => 'MACROMANIA', // Macintosh Romania
10017 => 'MACUKRAINE', // Macintosh Ukraine
10021 => 'MACTHAI', // Macintosh Thai
- 10029 => 'MACCENTRALEUROPE', // Macintosh Central Europe
+ 10029 => ['MACCENTRALEUROPE', 'MAC-CENTRALEUROPE'], // Macintosh Central Europe
10079 => 'MACICELAND', // Macintosh Icelandic
10081 => 'MACTURKISH', // Macintosh Turkish
10082 => 'MACCROATIAN', // Macintosh Croatian
@@ -65,6 +66,7 @@ class CodePage
//32769 => 'unsupported', // ANSI Latin I (BIFF2-BIFF3)
65000 => 'UTF-7', // Unicode (UTF-7)
65001 => 'UTF-8', // Unicode (UTF-8)
+ 99999 => ['unsupported'], // Unicode (UTF-8)
];
public static function validate(string $codePage): bool
@@ -83,7 +85,20 @@ class CodePage
public static function numberToName(int $codePage): string
{
if (array_key_exists($codePage, self::$pageArray)) {
- return self::$pageArray[$codePage];
+ $value = self::$pageArray[$codePage];
+ if (is_array($value)) {
+ foreach ($value as $encoding) {
+ if (@iconv('UTF-8', $encoding, ' ') !== false) {
+ self::$pageArray[$codePage] = $encoding;
+
+ return $encoding;
+ }
+ }
+
+ throw new PhpSpreadsheetException("Code page $codePage not implemented on this system.");
+ } else {
+ return $value;
+ }
}
if ($codePage == 720 || $codePage == 32769) {
throw new PhpSpreadsheetException("Code page $codePage not supported."); // OEM Arabic
diff --git a/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php b/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php
index 6bedceca..3c4bee47 100644
--- a/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php
+++ b/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php
@@ -267,6 +267,11 @@ class Column
return null;
}
+ public function ruleCount(): int
+ {
+ return count($this->ruleset);
+ }
+
/**
* Get all AutoFilter Column Rules.
*
diff --git a/tests/PhpSpreadsheetTests/Features/AutoFilter/Xlsx/BasicLoadTest.php b/tests/PhpSpreadsheetTests/Features/AutoFilter/Xlsx/BasicLoadTest.php
new file mode 100644
index 00000000..c36e6741
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Features/AutoFilter/Xlsx/BasicLoadTest.php
@@ -0,0 +1,74 @@
+load($filename);
+
+ $worksheet = $spreadsheet->getActiveSheet();
+ self::assertSame('A1:D57', $worksheet->getAutoFilter()->getRange());
+ self::assertSame(2, $worksheet->getAutoFilter()->getColumn('C')->ruleCount());
+ self::assertSame(
+ Rule::AUTOFILTER_COLUMN_RULE_EQUAL,
+ $worksheet->getAutoFilter()->getColumn('C')->getRules()[0]->getOperator()
+ );
+ self::assertSame('UK', $worksheet->getAutoFilter()->getColumn('C')->getRules()[0]->getValue());
+ self::assertSame(
+ Rule::AUTOFILTER_COLUMN_RULE_EQUAL,
+ $worksheet->getAutoFilter()->getColumn('C')->getRules()[1]->getOperator()
+ );
+ self::assertSame('United States', $worksheet->getAutoFilter()->getColumn('C')->getRules()[1]->getValue());
+ self::assertSame(2, $worksheet->getAutoFilter()->getColumn('D')->ruleCount());
+ self::assertSame(
+ Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN,
+ $worksheet->getAutoFilter()->getColumn('D')->getRules()[0]->getOperator()
+ );
+ self::assertSame('650', $worksheet->getAutoFilter()->getColumn('D')->getRules()[0]->getValue());
+ self::assertSame(
+ Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN,
+ $worksheet->getAutoFilter()->getColumn('D')->getRules()[1]->getOperator()
+ );
+ self::assertSame('800', $worksheet->getAutoFilter()->getColumn('D')->getRules()[1]->getValue());
+ }
+
+ public function testLoadOffice365AutoFilter(): void
+ {
+ $filename = 'tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic_Office365.xlsx';
+ $reader = new Xlsx();
+ $spreadsheet = $reader->load($filename);
+
+ $worksheet = $spreadsheet->getActiveSheet();
+ self::assertSame('A1:D57', $worksheet->getAutoFilter()->getRange());
+ self::assertSame(2, $worksheet->getAutoFilter()->getColumn('C')->ruleCount());
+ self::assertSame(
+ Rule::AUTOFILTER_COLUMN_RULE_EQUAL,
+ $worksheet->getAutoFilter()->getColumn('C')->getRules()[0]->getOperator()
+ );
+ self::assertSame('UK', $worksheet->getAutoFilter()->getColumn('C')->getRules()[0]->getValue());
+ self::assertSame(
+ Rule::AUTOFILTER_COLUMN_RULE_EQUAL,
+ $worksheet->getAutoFilter()->getColumn('C')->getRules()[1]->getOperator()
+ );
+ self::assertSame('United States', $worksheet->getAutoFilter()->getColumn('C')->getRules()[1]->getValue());
+ self::assertSame(2, $worksheet->getAutoFilter()->getColumn('D')->ruleCount());
+ self::assertSame(
+ Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN,
+ $worksheet->getAutoFilter()->getColumn('D')->getRules()[0]->getOperator()
+ );
+ self::assertSame('650', $worksheet->getAutoFilter()->getColumn('D')->getRules()[0]->getValue());
+ self::assertSame(
+ Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN,
+ $worksheet->getAutoFilter()->getColumn('D')->getRules()[1]->getOperator()
+ );
+ self::assertSame('800', $worksheet->getAutoFilter()->getColumn('D')->getRules()[1]->getValue());
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/XlsTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/XlsTest.php
similarity index 61%
rename from tests/PhpSpreadsheetTests/Reader/XlsTest.php
rename to tests/PhpSpreadsheetTests/Reader/Xls/XlsTest.php
index 130374b3..94257694 100644
--- a/tests/PhpSpreadsheetTests/Reader/XlsTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xls/XlsTest.php
@@ -1,8 +1,10 @@
load($filename);
self::assertEquals('Title', $spreadsheet->getSheet(0)->getCell('A1')->getValue());
+ $spreadsheet->disconnectWorksheets();
}
/**
@@ -42,6 +45,8 @@ class XlsTest extends AbstractFunctional
self::assertEquals($row, $newrow);
self::assertEquals($sheet->getCell('A1')->getFormattedValue(), $newsheet->getCell('A1')->getFormattedValue());
self::assertEquals($sheet->getCell("$col$row")->getFormattedValue(), $newsheet->getCell("$col$row")->getFormattedValue());
+ $spreadsheet->disconnectWorksheets();
+ $newspreadsheet->disconnectWorksheets();
}
/**
@@ -71,11 +76,49 @@ class XlsTest extends AbstractFunctional
$rowIterator = $sheet->getRowIterator();
foreach ($rowIterator as $row) {
- foreach ($row->getCellIterator() as $cell) {
+ foreach ($row->getCellIterator() as $cellx) {
+ /** @var Cell */
+ $cell = $cellx;
$valOld = $cell->getFormattedValue();
$valNew = $newsheet->getCell($cell->getCoordinate())->getFormattedValue();
self::assertEquals($valOld, $valNew);
}
}
+ $spreadsheet->disconnectWorksheets();
+ $newspreadsheet->disconnectWorksheets();
+ }
+
+ /**
+ * Test load Xls file with MACCENTRALEUROPE encoding, which is implemented
+ * as MAC-CENTRALEUROPE on some systems. Issue #549.
+ */
+ public function testLoadMacCentralEurope(): void
+ {
+ $codePages = CodePage::getEncodings();
+ self::assertIsArray($codePages[10029]);
+ $filename = 'tests/data/Reader/XLS/maccentraleurope.xls';
+ $reader = new Xls();
+ // When no fix applied, spreadsheet fails to load on some systems
+ $spreadsheet = $reader->load($filename);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame('Ładowność', $sheet->getCell('I1')->getValue());
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ /**
+ * First test changes array entry in CodePage.
+ * This test confirms new that new entry is okay.
+ */
+ public function testLoadMacCentralEurope2(): void
+ {
+ $codePages = CodePage::getEncodings();
+ self::assertIsString($codePages[10029]);
+ $filename = 'tests/data/Reader/XLS/maccentraleurope.xls';
+ $reader = new Xls();
+ // When no fix applied, spreadsheet fails to load on some systems
+ $spreadsheet = $reader->load($filename);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame('Ładowność', $sheet->getCell('I1')->getValue());
+ $spreadsheet->disconnectWorksheets();
}
}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/SharedFormulaTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/SharedFormulaTest.php
new file mode 100644
index 00000000..94f8e349
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/SharedFormulaTest.php
@@ -0,0 +1,41 @@
+', $data);
+ self::assertStringContainsString('', $data);
+ }
+ }
+
+ public function testLoadSheetsXlsxChart(): void
+ {
+ $filename = self::$testbook;
+ $reader = IOFactory::createReader('Xlsx');
+ $spreadsheet = $reader->load($filename, IReader::LOAD_WITH_CHARTS);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame('=(RANDBETWEEN(-50,250)+100)*10', $sheet->getCell('D6')->getValue());
+ self::assertSame('=(RANDBETWEEN(-50,250)+100)*10', $sheet->getCell('E6')->getValue());
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Shared/CodePageTest.php b/tests/PhpSpreadsheetTests/Shared/CodePageTest.php
index e7dc7469..af036f4e 100644
--- a/tests/PhpSpreadsheetTests/Shared/CodePageTest.php
+++ b/tests/PhpSpreadsheetTests/Shared/CodePageTest.php
@@ -16,8 +16,15 @@ class CodePageTest extends TestCase
*/
public function testCodePageNumberToName($expectedResult, $codePageIndex): void
{
+ if ($expectedResult === 'exception') {
+ $this->expectException(Exception::class);
+ }
$result = CodePage::numberToName($codePageIndex);
- self::assertEquals($expectedResult, $result);
+ if (is_array($expectedResult)) {
+ self::assertContains($result, $expectedResult);
+ } else {
+ self::assertEquals($expectedResult, $result);
+ }
}
public function providerCodePage(): array
diff --git a/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic.xlsx b/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic.xlsx
new file mode 100644
index 00000000..030f8db2
Binary files /dev/null and b/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic.xlsx differ
diff --git a/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic_Office365.xlsx b/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic_Office365.xlsx
new file mode 100644
index 00000000..24893e74
Binary files /dev/null and b/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic_Office365.xlsx differ
diff --git a/tests/data/Reader/XLS/maccentraleurope.xls b/tests/data/Reader/XLS/maccentraleurope.xls
new file mode 100644
index 00000000..98607556
Binary files /dev/null and b/tests/data/Reader/XLS/maccentraleurope.xls differ
diff --git a/tests/data/Shared/CodePage.php b/tests/data/Shared/CodePage.php
index 82bb23e4..ccb59f24 100644
--- a/tests/data/Shared/CodePage.php
+++ b/tests/data/Shared/CodePage.php
@@ -233,7 +233,7 @@ return [
],
// Macintosh Central Europe
[
- 'MACCENTRALEUROPE',
+ ['MACCENTRALEUROPE', 'MAC-CENTRALEUROPE'],
10029,
],
// Macintosh Icelandic
@@ -271,4 +271,9 @@ return [
'UTF-8',
65001,
],
+ // invalid
+ [
+ 'exception',
+ 99999,
+ ],
];