From 10a69f998372e8a339f81b97b197a7260f890dd2 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 2 Jul 2021 11:37:24 +0200 Subject: [PATCH 1/4] Update matrix library to version 3 --- composer.json | 2 +- composer.lock | 34 ++++++++-------------------------- 2 files changed, 9 insertions(+), 27 deletions(-) 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", From 075cecd268f11ee612e8c0a968ae54385f526c79 Mon Sep 17 00:00:00 2001 From: oleibman Date: Fri, 2 Jul 2021 03:36:54 -0700 Subject: [PATCH 2/4] Xlsx Reader Better Namespace Handling Phase 1 First Bugfix (#2204) See issue #2203. An undotted i uncrossed t. When using namespaces, need to call attributes() to access the attributes before trying to access them directly. Failure to do so in castToFormula caused problem for shared formulas. Surprisingly, this didn't show up in unit tests. Perhaps sharing the same formula between two cells isn't common. It did show up in Chart Samples. I've added a test. I was really inclined to merge this right away. Not to worry - I can control myself. It should be moved fairly quickly nevertheless. --- src/PhpSpreadsheet/Reader/Xlsx.php | 7 ++-- .../Reader/Xlsx/SharedFormulaTest.php | 41 +++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/SharedFormulaTest.php diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 00d86f00..d94a8852 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']); 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(); + } +} From 8729a68338ac23f95595d5a3177d4a6df0ea58f7 Mon Sep 17 00:00:00 2001 From: oleibman Date: Sun, 11 Jul 2021 18:02:47 -0700 Subject: [PATCH 3/4] Xls Reader Handle MACCENTRALEUROPE With or Without Hyphen (#2213) * Xls Reader Handle MACCENTRALEUROPE With or Without Hyphen Fixes issue #549 and https://github.com/Maatwebsite/Laravel-Excel/issues/989 (which is the source of the new test file). Some systems accept MACCENTRALEUROPE as the name for the appropriate encoding, and some accept MAC-CENTRALEUROPE. I fortunately have access to at least one of each type, and have run the tests on each. CodePage.php has an array of translations from codepage number to string. I now allow the value to itself be an array; if so, the code will test each in turn to see if it can be used in iconv. I did not go fishing for other similar problems. If such show up, they can be dealt with in the same manner as this one. I don't really expect others, since this is a problem not merely for Xls, but, even then, it applies only to BIFF5 and earlier. I also moved XlsTest from Reader to Reader/Xls. * Cache Successful Result For Future Use Per suggestion from @MarkBaker --- phpstan-baseline.neon | 15 ------ src/PhpSpreadsheet/Shared/CodePage.php | 19 ++++++- .../Reader/{ => Xls}/XlsTest.php | 47 +++++++++++++++++- .../Shared/CodePageTest.php | 9 +++- tests/data/Reader/XLS/maccentraleurope.xls | Bin 0 -> 20480 bytes tests/data/Shared/CodePage.php | 7 ++- 6 files changed, 76 insertions(+), 21 deletions(-) rename tests/PhpSpreadsheetTests/Reader/{ => Xls}/XlsTest.php (61%) create mode 100644 tests/data/Reader/XLS/maccentraleurope.xls diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c7a45cf5..e9787962 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 @@ -6525,16 +6520,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/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/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/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/Reader/XLS/maccentraleurope.xls b/tests/data/Reader/XLS/maccentraleurope.xls new file mode 100644 index 0000000000000000000000000000000000000000..98607556ee23184827685ca3787bbd060b06222e GIT binary patch literal 20480 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 Date: Mon, 12 Jul 2021 03:19:40 +0200 Subject: [PATCH 4/4] Issue 2216 resolve office365 auto filter structure move (#2218) * Initial adjustments to Xlsx Reader for two possible locations for AutoFiter information, either on the sheet itself for older files, or in the tables/tableX file for more recent files * Refactor AutoFilter Reader logic into separate methods; preparatory work toward the eventual goal of moving it into its own dedicated AutoFilterTables class * Basic unit tests to verify that the Xlsx Reader can read both the older and Office365 variants of the files used to store AutoFilter structure --- src/PhpSpreadsheet/Reader/Xlsx.php | 75 +++++++++++++++++- .../Worksheet/AutoFilter/Column.php | 5 ++ .../AutoFilter/Xlsx/BasicLoadTest.php | 74 +++++++++++++++++ .../AutoFilter/Xlsx/AutoFilter_Basic.xlsx | Bin 0 -> 9912 bytes .../Xlsx/AutoFilter_Basic_Office365.xlsx | Bin 0 -> 11104 bytes 5 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Features/AutoFilter/Xlsx/BasicLoadTest.php create mode 100644 tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic.xlsx create mode 100644 tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic_Office365.xlsx diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index d94a8852..d78b227e 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -336,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 * @@ -821,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) { @@ -1977,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/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/data/Features/AutoFilter/Xlsx/AutoFilter_Basic.xlsx b/tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..030f8db2fd1378eec70ca1ff6f879aed77db36d5 GIT binary patch literal 9912 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 G`1XIA#cXo` literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..24893e7480d12cc5366b57d9c322fb7a2639ad48 GIT binary patch literal 11104 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