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
|
* @param string $fileName
|
||||||
*
|
*
|
||||||
|
|
@ -821,8 +844,8 @@ class Xlsx extends BaseReader
|
||||||
$this->readSheetProtection($docSheet, $xmlSheet);
|
$this->readSheetProtection($docSheet, $xmlSheet);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) {
|
if ($this->readDataOnly === false) {
|
||||||
(new AutoFilter($docSheet, $xmlSheet))->load();
|
$this->readAutoFilterTables($xmlSheet, $docSheet, $dir, $fileWorksheet, $zip);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ruleCount(): int
|
||||||
|
{
|
||||||
|
return count($this->ruleset);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all AutoFilter Column Rules.
|
* 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