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
This commit is contained in:
Mark Baker 2021-07-12 03:19:40 +02:00 committed by GitHub
parent 8729a68338
commit 15170cf8cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 152 additions and 2 deletions

View File

@ -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();
}
}
}
}
}
}
}

View File

@ -267,6 +267,11 @@ class Column
return null;
}
public function ruleCount(): int
{
return count($this->ruleset);
}
/**
* Get all AutoFilter Column Rules.
*

View File

@ -0,0 +1,74 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Features\AutoFilter\Xlsx;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule;
use PHPUnit\Framework\TestCase;
class BasicLoadTest extends TestCase
{
public function testLoadAutoFilter(): void
{
$filename = 'tests/data/Features/AutoFilter/Xlsx/AutoFilter_Basic.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());
}
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());
}
}