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:
parent
8729a68338
commit
15170cf8cd
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -267,6 +267,11 @@ class Column
|
|||
return null;
|
||||
}
|
||||
|
||||
public function ruleCount(): int
|
||||
{
|
||||
return count($this->ruleset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all AutoFilter Column Rules.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue