From cadb4523085eab13b3b63072c9b663ffe95cb5a7 Mon Sep 17 00:00:00 2001
From: oleibman <10341515+oleibman@users.noreply.github.com>
Date: Thu, 3 Aug 2023 08:27:06 -0700
Subject: [PATCH 1/5] Xlsx Reader Namespacing for Tables, AutoFilters
Fix #3665. The original issue was the use of an absolute path in the rels file pointing to the comments file. That was easy to take care of, but a bigger problem with the spreadsheet accompanying the problem report was that it used unexpected spacing for AutoFilters and Tables. AutoFilters were already known not to be covered, but Tables appeared after the namespacing changes, but without namespacing support. This PR fixes the absolute path problem and adds namespacing support for Tables and AutoFilters.
Remaining areas which are still namespace unaware, mainly because of the absence of test samples which use them with unexpected namespacing, include conditional formatting (internal or external), sheet view options, sheet protection, unparsed loaded data, data validation (internal or external), alternate content, and header/footer images.
---
src/PhpSpreadsheet/Reader/Xlsx.php | 27 ++++--
src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php | 79 ++++++++++--------
.../Reader/Xlsx/TableReader.php | 51 ++++++-----
.../Reader/Xlsx/Issue3665Test.php | 62 ++++++++++++++
tests/data/Reader/XLSX/issue.3665.xlsx | Bin 0 -> 7639 bytes
5 files changed, 157 insertions(+), 62 deletions(-)
create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/Issue3665Test.php
create mode 100644 tests/data/Reader/XLSX/issue.3665.xlsx
diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php
index 1c33fd6811..368d4ddeaa 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx.php
@@ -793,6 +793,14 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
$docSheet->setTitle((string) $eleSheetAttr['name'], false, false);
$fileWorksheet = (string) $worksheets[$sheetReferenceId];
+ // issue 3665 adds test for /.
+ // This broke XlsxRootZipFilesTest,
+ // but Excel reports an error with that file.
+ // Testing dir for . avoids this problem.
+ // It might be better just to drop the test.
+ if ($fileWorksheet[0] == '/' && $dir !== '.') {
+ $fileWorksheet = substr($fileWorksheet, strlen($dir) + 2);
+ }
$xmlSheet = $this->loadZipNoNamespace("$dir/$fileWorksheet", $mainNS);
$xmlSheetNS = $this->loadZip("$dir/$fileWorksheet", $mainNS);
@@ -980,8 +988,8 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
}
if ($this->readDataOnly === false) {
- $this->readAutoFilter($xmlSheet, $docSheet);
- $this->readTables($xmlSheet, $docSheet, $dir, $fileWorksheet, $zip);
+ $this->readAutoFilter($xmlSheetNS, $docSheet);
+ $this->readTables($xmlSheetNS, $docSheet, $dir, $fileWorksheet, $zip, $mainNS);
}
if ($xmlSheetNS && $xmlSheetNS->mergeCells && $xmlSheetNS->mergeCells->mergeCell && !$this->readDataOnly) {
@@ -2206,10 +2214,14 @@ private function readTables(
Worksheet $docSheet,
string $dir,
string $fileWorksheet,
- ZipArchive $zip
+ ZipArchive $zip,
+ string $namespaceTable
): void {
- if ($xmlSheet && $xmlSheet->tableParts && (int) $xmlSheet->tableParts['count'] > 0) {
- $this->readTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet);
+ if ($xmlSheet && $xmlSheet->tableParts) {
+ $attributes = $xmlSheet->tableParts->attributes() ?? ['count' => 0];
+ if (((int) $attributes['count']) > 0) {
+ $this->readTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet, $namespaceTable);
+ }
}
}
@@ -2218,7 +2230,8 @@ private function readTablesInTablesFile(
string $dir,
string $fileWorksheet,
ZipArchive $zip,
- Worksheet $docSheet
+ Worksheet $docSheet,
+ string $namespaceTable
): void {
foreach ($xmlSheet->tableParts->tablePart as $tablePart) {
$relation = self::getAttributes($tablePart, Namespaces::SCHEMA_OFFICE_DOCUMENT);
@@ -2236,7 +2249,7 @@ private function readTablesInTablesFile(
$relationshipFilePath = File::realpath($relationshipFilePath);
if ($this->fileExistsInArchive($this->zip, $relationshipFilePath)) {
- $tableXml = $this->loadZip($relationshipFilePath);
+ $tableXml = $this->loadZip($relationshipFilePath, $namespaceTable);
(new TableReader($docSheet, $tableXml))->load();
}
}
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
index a6ab4d894b..4538aeb84e 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
@@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
@@ -32,36 +33,39 @@ public function __construct($parent, SimpleXMLElement $worksheetXml)
public function load(): void
{
// Remove all "$" in the auto filter range
- $autoFilterRange = (string) preg_replace('/\$/', '', $this->worksheetXml->autoFilter['ref'] ?? '');
+ $attrs = $this->worksheetXml->autoFilter->attributes() ?? [];
+ $autoFilterRange = (string) preg_replace('/\$/', '', $attrs['ref'] ?? '');
if (strpos($autoFilterRange, ':') !== false) {
- $this->readAutoFilter($autoFilterRange, $this->worksheetXml);
+ $this->readAutoFilter($autoFilterRange);
}
}
- private function readAutoFilter(string $autoFilterRange, SimpleXMLElement $xmlSheet): void
+ private function readAutoFilter(string $autoFilterRange): void
{
$autoFilter = $this->parent->getAutoFilter();
$autoFilter->setRange($autoFilterRange);
- foreach ($xmlSheet->autoFilter->filterColumn as $filterColumn) {
- $column = $autoFilter->getColumnByOffset((int) $filterColumn['colId']);
+ foreach ($this->worksheetXml->autoFilter->filterColumn as $filterColumn) {
+ $attributes = $filterColumn->attributes() ?? [];
+ $column = $autoFilter->getColumnByOffset((int) ($attributes['colId'] ?? 0));
// Check for standard filters
if ($filterColumn->filters) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER);
- $filters = $filterColumn->filters;
+ $filters = Xlsx::testSimpleXml($filterColumn->filters->attributes());
if ((isset($filters['blank'])) && ((int) $filters['blank'] == 1)) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule('', '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
}
// Standard filters are always an OR join, so no join rule needs to be set
// Entries can be either filter elements
- foreach ($filters->filter as $filterRule) {
+ foreach ($filterColumn->filters->filter as $filterRule) {
// Operator is undefined, but always treated as EQUAL
- $column->createRule()->setRule('', (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
+ $attr2 = $filterRule->attributes() ?? ['val' => ''];
+ $column->createRule()->setRule('', (string) $attr2['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
}
// Or Date Group elements
- $this->readDateRangeAutoFilter($filters, $column);
+ $this->readDateRangeAutoFilter($filterColumn->filters, $column);
}
// Check for custom filters
@@ -76,20 +80,23 @@ private function readAutoFilter(string $autoFilterRange, SimpleXMLElement $xmlSh
private function readDateRangeAutoFilter(SimpleXMLElement $filters, Column $column): void
{
- foreach ($filters->dateGroupItem as $dateGroupItem) {
+ foreach ($filters->dateGroupItem as $dateGroupItemx) {
// Operator is undefined, but always treated as EQUAL
- $column->createRule()->setRule(
- '',
- [
- 'year' => (string) $dateGroupItem['year'],
- 'month' => (string) $dateGroupItem['month'],
- 'day' => (string) $dateGroupItem['day'],
- 'hour' => (string) $dateGroupItem['hour'],
- 'minute' => (string) $dateGroupItem['minute'],
- 'second' => (string) $dateGroupItem['second'],
- ],
- (string) $dateGroupItem['dateTimeGrouping']
- )->setRuleType(Rule::AUTOFILTER_RULETYPE_DATEGROUP);
+ $dateGroupItem = $dateGroupItemx->attributes();
+ if ($dateGroupItem !== null) {
+ $column->createRule()->setRule(
+ '',
+ [
+ 'year' => (string) $dateGroupItem['year'],
+ 'month' => (string) $dateGroupItem['month'],
+ 'day' => (string) $dateGroupItem['day'],
+ 'hour' => (string) $dateGroupItem['hour'],
+ 'minute' => (string) $dateGroupItem['minute'],
+ 'second' => (string) $dateGroupItem['second'],
+ ],
+ (string) $dateGroupItem['dateTimeGrouping']
+ )->setRuleType(Rule::AUTOFILTER_RULETYPE_DATEGROUP);
+ }
}
}
@@ -98,15 +105,17 @@ private function readCustomAutoFilter(?SimpleXMLElement $filterColumn, Column $c
if (isset($filterColumn, $filterColumn->customFilters)) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER);
$customFilters = $filterColumn->customFilters;
+ $attributes = $customFilters->attributes();
// Custom filters can an AND or an OR join;
// and there should only ever be one or two entries
- if ((isset($customFilters['and'])) && ((string) $customFilters['and'] === '1')) {
+ if ((isset($attributes['and'])) && ((string) $attributes['and'] === '1')) {
$column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND);
}
foreach ($customFilters->customFilter as $filterRule) {
+ $attr2 = $filterRule->attributes() ?? ['operator' => '', 'val' => ''];
$column->createRule()->setRule(
- (string) $filterRule['operator'],
- (string) $filterRule['val']
+ (string) $attr2['operator'],
+ (string) $attr2['val']
)->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER);
}
}
@@ -119,16 +128,17 @@ private function readDynamicAutoFilter(?SimpleXMLElement $filterColumn, Column $
// We should only ever have one dynamic filter
foreach ($filterColumn->dynamicFilter as $filterRule) {
// Operator is undefined, but always treated as EQUAL
+ $attr2 = $filterRule->attributes() ?? [];
$column->createRule()->setRule(
'',
- (string) $filterRule['val'],
- (string) $filterRule['type']
+ (string) ($attr2['val'] ?? ''),
+ (string) ($attr2['type'] ?? '')
)->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER);
- if (isset($filterRule['val'])) {
- $column->setAttribute('val', (string) $filterRule['val']);
+ if (isset($attr2['val'])) {
+ $column->setAttribute('val', (string) $attr2['val']);
}
- if (isset($filterRule['maxVal'])) {
- $column->setAttribute('maxVal', (string) $filterRule['maxVal']);
+ if (isset($attr2['maxVal'])) {
+ $column->setAttribute('maxVal', (string) $attr2['maxVal']);
}
}
}
@@ -140,15 +150,16 @@ private function readTopTenAutoFilter(?SimpleXMLElement $filterColumn, Column $c
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER);
// We should only ever have one top10 filter
foreach ($filterColumn->top10 as $filterRule) {
+ $attr2 = $filterRule->attributes() ?? [];
$column->createRule()->setRule(
(
- ((isset($filterRule['percent'])) && ((string) $filterRule['percent'] === '1'))
+ ((isset($attr2['percent'])) && ((string) $attr2['percent'] === '1'))
? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT
: Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE
),
- (string) $filterRule['val'],
+ (string) ($attr2['val'] ?? ''),
(
- ((isset($filterRule['top'])) && ((string) $filterRule['top'] === '1'))
+ ((isset($attr2['top'])) && ((string) $attr2['top'] === '1'))
? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP
: Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM
)
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php b/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php
index cf89e995f0..09df0dc7e7 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php
@@ -19,6 +19,9 @@ class TableReader
*/
private $tableXml;
+ /** @var array|SimpleXMLElement */
+ private $tableAttributes;
+
public function __construct(Worksheet $workSheet, SimpleXMLElement $tableXml)
{
$this->worksheet = $workSheet;
@@ -30,28 +33,29 @@ public function __construct(Worksheet $workSheet, SimpleXMLElement $tableXml)
*/
public function load(): void
{
+ $this->tableAttributes = $this->tableXml->attributes() ?? [];
// Remove all "$" in the table range
- $tableRange = (string) preg_replace('/\$/', '', $this->tableXml['ref'] ?? '');
+ $tableRange = (string) preg_replace('/\$/', '', $this->tableAttributes['ref'] ?? '');
if (strpos($tableRange, ':') !== false) {
- $this->readTable($tableRange, $this->tableXml);
+ $this->readTable($tableRange);
}
}
/**
* Read Table from xml.
*/
- private function readTable(string $tableRange, SimpleXMLElement $tableXml): void
+ private function readTable(string $tableRange): void
{
$table = new Table($tableRange);
- $table->setName((string) $tableXml['displayName']);
- $table->setShowHeaderRow((string) $tableXml['headerRowCount'] !== '0');
- $table->setShowTotalsRow((string) $tableXml['totalsRowCount'] === '1');
+ $table->setName((string) ($this->tableAttributes['displayName'] ?? ''));
+ $table->setShowHeaderRow(((string) ($this->tableAttributes['headerRowCount'] ?? '')) !== '0');
+ $table->setShowTotalsRow(((string) ($this->tableAttributes['totalsRowCount'] ?? '')) === '1');
- $this->readTableAutoFilter($table, $tableXml->autoFilter);
- $this->readTableColumns($table, $tableXml->tableColumns);
- $this->readTableStyle($table, $tableXml->tableStyleInfo);
+ $this->readTableAutoFilter($table, $this->tableXml->autoFilter);
+ $this->readTableColumns($table, $this->tableXml->tableColumns);
+ $this->readTableStyle($table, $this->tableXml->tableStyleInfo);
- (new AutoFilter($table, $tableXml))->load();
+ (new AutoFilter($table, $this->tableXml))->load();
$this->worksheet->addTable($table);
}
@@ -67,8 +71,9 @@ private function readTableAutoFilter(Table $table, SimpleXMLElement $autoFilterX
}
foreach ($autoFilterXml->filterColumn as $filterColumn) {
- $column = $table->getColumnByOffset((int) $filterColumn['colId']);
- $column->setShowFilterButton((string) $filterColumn['hiddenButton'] !== '1');
+ $attributes = $filterColumn->attributes() ?? ['colId' => 0, 'hiddenButton' => 0];
+ $column = $table->getColumnByOffset((int) $attributes['colId']);
+ $column->setShowFilterButton(((string) $attributes['hiddenButton']) !== '1');
}
}
@@ -79,15 +84,16 @@ private function readTableColumns(Table $table, SimpleXMLElement $tableColumnsXm
{
$offset = 0;
foreach ($tableColumnsXml->tableColumn as $tableColumn) {
+ $attributes = $tableColumn->attributes() ?? ['totalsRowLabel' => 0, 'totalsRowFunction' => 0];
$column = $table->getColumnByOffset($offset++);
if ($table->getShowTotalsRow()) {
- if ($tableColumn['totalsRowLabel']) {
- $column->setTotalsRowLabel((string) $tableColumn['totalsRowLabel']);
+ if ($attributes['totalsRowLabel']) {
+ $column->setTotalsRowLabel((string) $attributes['totalsRowLabel']);
}
- if ($tableColumn['totalsRowFunction']) {
- $column->setTotalsRowFunction((string) $tableColumn['totalsRowFunction']);
+ if ($attributes['totalsRowFunction']) {
+ $column->setTotalsRowFunction((string) $attributes['totalsRowFunction']);
}
}
@@ -103,11 +109,14 @@ private function readTableColumns(Table $table, SimpleXMLElement $tableColumnsXm
private function readTableStyle(Table $table, SimpleXMLElement $tableStyleInfoXml): void
{
$tableStyle = new TableStyle();
- $tableStyle->setTheme((string) $tableStyleInfoXml['name']);
- $tableStyle->setShowRowStripes((string) $tableStyleInfoXml['showRowStripes'] === '1');
- $tableStyle->setShowColumnStripes((string) $tableStyleInfoXml['showColumnStripes'] === '1');
- $tableStyle->setShowFirstColumn((string) $tableStyleInfoXml['showFirstColumn'] === '1');
- $tableStyle->setShowLastColumn((string) $tableStyleInfoXml['showLastColumn'] === '1');
+ $attributes = $tableStyleInfoXml->attributes();
+ if ($attributes !== null) {
+ $tableStyle->setTheme((string) $attributes['name']);
+ $tableStyle->setShowRowStripes((string) $attributes['showRowStripes'] === '1');
+ $tableStyle->setShowColumnStripes((string) $attributes['showColumnStripes'] === '1');
+ $tableStyle->setShowFirstColumn((string) $attributes['showFirstColumn'] === '1');
+ $tableStyle->setShowLastColumn((string) $attributes['showLastColumn'] === '1');
+ }
$table->setStyle($tableStyle);
}
}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue3665Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue3665Test.php
new file mode 100644
index 0000000000..ba1778698e
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue3665Test.php
@@ -0,0 +1,62 @@
+', $data);
+ self::assertStringContainsString('', $data);
+ }
+
+ $file = 'zip://';
+ $file .= self::$testbook;
+ $file .= '#xl/worksheets/_rels/sheet1.xml.rels';
+ $data = file_get_contents($file);
+ // confirm absolute path as reference
+ if ($data === false) {
+ self::fail('Unable to read rels file');
+ } else {
+ self::assertStringContainsString('Target="/xl/comments1.xml"', $data);
+ }
+ }
+
+ public function testTable(): void
+ {
+ $reader = new Xlsx();
+ $spreadsheet = $reader->load(self::$testbook);
+ $sheet = $spreadsheet->getActiveSheet();
+ $tables = $sheet->getTableCollection();
+ self::assertCount(1, $tables);
+ $table = $tables->offsetGet(0);
+ if ($table === null) {
+ self::fail('Unexpected failure obtaining table');
+ } else {
+ self::assertEquals('Table5', $table->getName());
+ self::assertEquals('A3:M4', $table->getRange());
+ self::assertTrue($table->getAllowFilter());
+ self::assertSame('A3:M4', $table->getAutoFilter()->getRange());
+ }
+ $comment = $sheet->getComment('A3');
+ self::assertSame('Code20', (string) $comment);
+ $comment = $sheet->getComment('B3');
+ self::assertSame('Text100', (string) $comment);
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/data/Reader/XLSX/issue.3665.xlsx b/tests/data/Reader/XLSX/issue.3665.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..ad844bdc18463c2022f57b70df9c961e059442ae
GIT binary patch
literal 7639
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
literal 0
HcmV?d00001
From fe4b418561cea7a3b117e0a453e70d90eece5425 Mon Sep 17 00:00:00 2001
From: oleibman <10341515+oleibman@users.noreply.github.com>
Date: Thu, 3 Aug 2023 09:09:16 -0700
Subject: [PATCH 2/5] Mysterious Warning for Php7.4 Only
Node no longer exists, doesn't affect result. Suppress warning.
---
src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
index 4538aeb84e..594bcc5e0d 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
@@ -34,7 +34,8 @@ public function load(): void
{
// Remove all "$" in the auto filter range
$attrs = $this->worksheetXml->autoFilter->attributes() ?? [];
- $autoFilterRange = (string) preg_replace('/\$/', '', $attrs['ref'] ?? '');
+ // Mysterious 'Node no longer exists' warning for Php7.4 only.
+ $autoFilterRange = (string) @preg_replace('/\$/', '', $attrs['ref'] ?? '');
if (strpos($autoFilterRange, ':') !== false) {
$this->readAutoFilter($autoFilterRange);
}
From 12eeb1a44b4fce54de95cb0b3a371514dfe4c3af Mon Sep 17 00:00:00 2001
From: oleibman <10341515+oleibman@users.noreply.github.com>
Date: Thu, 3 Aug 2023 09:36:43 -0700
Subject: [PATCH 3/5] Scrutinizer
What would a change be without some new false positives?
---
src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php | 6 +++---
src/PhpSpreadsheet/Reader/Xlsx/TableReader.php | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
index 594bcc5e0d..8e80e464e1 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
@@ -47,7 +47,7 @@ private function readAutoFilter(string $autoFilterRange): void
$autoFilter->setRange($autoFilterRange);
foreach ($this->worksheetXml->autoFilter->filterColumn as $filterColumn) {
- $attributes = $filterColumn->attributes() ?? [];
+ $attributes = $filterColumn->/** @scrutinizer ignore-call */ attributes() ?? [];
$column = $autoFilter->getColumnByOffset((int) ($attributes['colId'] ?? 0));
// Check for standard filters
if ($filterColumn->filters) {
@@ -61,7 +61,7 @@ private function readAutoFilter(string $autoFilterRange): void
// Entries can be either filter elements
foreach ($filterColumn->filters->filter as $filterRule) {
// Operator is undefined, but always treated as EQUAL
- $attr2 = $filterRule->attributes() ?? ['val' => ''];
+ $attr2 = $filterRule->/** @scrutinizer ignore-call */ attributes() ?? ['val' => ''];
$column->createRule()->setRule('', (string) $attr2['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
}
@@ -83,7 +83,7 @@ private function readDateRangeAutoFilter(SimpleXMLElement $filters, Column $colu
{
foreach ($filters->dateGroupItem as $dateGroupItemx) {
// Operator is undefined, but always treated as EQUAL
- $dateGroupItem = $dateGroupItemx->attributes();
+ $dateGroupItem = $dateGroupItemx->/** @scrutinizer ignore-call */ attributes();
if ($dateGroupItem !== null) {
$column->createRule()->setRule(
'',
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php b/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php
index 09df0dc7e7..7f8fd85596 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php
@@ -71,7 +71,7 @@ private function readTableAutoFilter(Table $table, SimpleXMLElement $autoFilterX
}
foreach ($autoFilterXml->filterColumn as $filterColumn) {
- $attributes = $filterColumn->attributes() ?? ['colId' => 0, 'hiddenButton' => 0];
+ $attributes = $filterColumn->/** @scrutinizer ignore-call */ attributes() ?? ['colId' => 0, 'hiddenButton' => 0];
$column = $table->getColumnByOffset((int) $attributes['colId']);
$column->setShowFilterButton(((string) $attributes['hiddenButton']) !== '1');
}
@@ -84,7 +84,7 @@ private function readTableColumns(Table $table, SimpleXMLElement $tableColumnsXm
{
$offset = 0;
foreach ($tableColumnsXml->tableColumn as $tableColumn) {
- $attributes = $tableColumn->attributes() ?? ['totalsRowLabel' => 0, 'totalsRowFunction' => 0];
+ $attributes = $tableColumn->/** @scrutinizer ignore-call */ attributes() ?? ['totalsRowLabel' => 0, 'totalsRowFunction' => 0];
$column = $table->getColumnByOffset($offset++);
if ($table->getShowTotalsRow()) {
From 57228002b1fff9e7c5fc2dc099ca9ea4a379b318 Mon Sep 17 00:00:00 2001
From: oleibman <10341515+oleibman@users.noreply.github.com>
Date: Thu, 3 Aug 2023 10:06:46 -0700
Subject: [PATCH 4/5] Scrutinizer
A gift that keeps on giving :-( Now it's deciding that things that it didn't report in a scan from a few minutes ago are worth reporting, even with no relevant code changes.
---
src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
index 8e80e464e1..eeca0b649c 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
@@ -113,7 +113,7 @@ private function readCustomAutoFilter(?SimpleXMLElement $filterColumn, Column $c
$column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND);
}
foreach ($customFilters->customFilter as $filterRule) {
- $attr2 = $filterRule->attributes() ?? ['operator' => '', 'val' => ''];
+ $attr2 = $filterRule->/** @scrutinizer ignore-call */ attributes() ?? ['operator' => '', 'val' => ''];
$column->createRule()->setRule(
(string) $attr2['operator'],
(string) $attr2['val']
From 33ef0bde06cf34a93ae8ff64806da0f4e94b9000 Mon Sep 17 00:00:00 2001
From: oleibman <10341515+oleibman@users.noreply.github.com>
Date: Thu, 3 Aug 2023 10:33:36 -0700
Subject: [PATCH 5/5] Scrutinizer
It is an idiot. Let me see if I can fix one false positive and succeed in guessing where it might report another one, even though it didn't do so with this last run.
---
src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
index eeca0b649c..bc406e652a 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
@@ -129,7 +129,7 @@ private function readDynamicAutoFilter(?SimpleXMLElement $filterColumn, Column $
// We should only ever have one dynamic filter
foreach ($filterColumn->dynamicFilter as $filterRule) {
// Operator is undefined, but always treated as EQUAL
- $attr2 = $filterRule->attributes() ?? [];
+ $attr2 = $filterRule->/** @scrutinizer ignore-call */ attributes() ?? [];
$column->createRule()->setRule(
'',
(string) ($attr2['val'] ?? ''),
@@ -151,7 +151,7 @@ private function readTopTenAutoFilter(?SimpleXMLElement $filterColumn, Column $c
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER);
// We should only ever have one top10 filter
foreach ($filterColumn->top10 as $filterRule) {
- $attr2 = $filterRule->attributes() ?? [];
+ $attr2 = $filterRule->/** @scrutinizer ignore-call */ attributes() ?? [];
$column->createRule()->setRule(
(
((isset($attr2['percent'])) && ((string) $attr2['percent'] === '1'))