Skip to content

Commit 8f5c11b

Browse files
authored
Merge pull request PHPOffice#4803 from QDenka/fix/ods-read-filter-repeated-columns
Fix ODS reader column misalignment with read filter and number-columns-repeated
2 parents ff03307 + 6252ff3 commit 8f5c11b

File tree

4 files changed

+73
-12
lines changed

4 files changed

+73
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). Thia is a
3838
- Option to whitelist external images. [PR #4793](https://github.com/PHPOffice/PhpSpreadsheet/pull/4793)
3939
- Handle Unions as Function Arguments. [Issue #4656](https://github.com/PHPOffice/PhpSpreadsheet/issues/4656) [Issue #316](https://github.com/PHPOffice/PhpSpreadsheet/issues/316) [Issue #503](https://github.com/PHPOffice/PhpSpreadsheet/issues/503) [PR #4657](https://github.com/PHPOffice/PhpSpreadsheet/pull/4657)
4040
- Unexpected Behavior of CONCATENATE. [Issue #4061](https://github.com/PHPOffice/PhpSpreadsheet/issues/4061) [PR #4797](https://github.com/PHPOffice/PhpSpreadsheet/pull/4797)
41+
- Ods Reader column misalignment. [Issue #4802](https://github.com/PHPOffice/PhpSpreadsheet/issues/4802) [PR #4803](https://github.com/PHPOffice/PhpSpreadsheet/pull/4803)
4142
- Image Css size in millimeters. [Issue #4800](https://github.com/PHPOffice/PhpSpreadsheet/issues/4800) [PR #4801](https://github.com/PHPOffice/PhpSpreadsheet/pull/4801)
4243

4344
## 2026-01-10 - 5.4.0

src/PhpSpreadsheet/Reader/Ods.php

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -573,18 +573,45 @@ private function processTableRow(
573573
if ($cellData instanceof DOMText) {
574574
continue; // should just be whitespace
575575
}
576+
if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
577+
$colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated');
578+
} else {
579+
$colRepeats = 1;
580+
}
581+
582+
// When a cell has number-columns-repeated, check if ANY column in the
583+
// repeated range passes the read filter. If not, skip the entire group.
584+
// If some columns pass, we need to fall through to the processing block
585+
// which will handle per-column filtering.
576586
if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
577-
if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
578-
$colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated');
579-
} else {
580-
$colRepeats = 1;
587+
if ($colRepeats <= 1) {
588+
StringHelper::stringIncrement($columnID);
589+
590+
continue;
581591
}
582592

593+
// Check if any column within this repeated group passes the filter
594+
$anyColumnPasses = false;
595+
$tempCol = $columnID;
583596
for ($i = 0; $i < $colRepeats; ++$i) {
584-
StringHelper::stringIncrement($columnID);
597+
if ($i > 0) {
598+
StringHelper::stringIncrement($tempCol);
599+
}
600+
if ($this->getReadFilter()->readCell($tempCol, $rowID, $worksheetName)) {
601+
$anyColumnPasses = true;
602+
603+
break;
604+
}
585605
}
586606

587-
continue;
607+
if (!$anyColumnPasses) {
608+
for ($i = 0; $i < $colRepeats; ++$i) {
609+
StringHelper::stringIncrement($columnID);
610+
}
611+
612+
continue;
613+
}
614+
// Fall through to process the cell, with per-column filter checks
588615
}
589616

590617
// Initialize variables
@@ -763,18 +790,16 @@ private function processTableRow(
763790
$cellDataFormula = FormulaTranslator::convertToExcelFormulaValue($cellDataFormula);
764791
}
765792

766-
if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
767-
$colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated');
768-
} else {
769-
$colRepeats = 1;
770-
}
771-
772793
if ($type !== null) { // @phpstan-ignore-line
773794
for ($i = 0; $i < $colRepeats; ++$i) {
774795
if ($i > 0) {
775796
StringHelper::stringIncrement($columnID);
776797
}
777798

799+
if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
800+
continue;
801+
}
802+
778803
if ($type !== DataType::TYPE_NULL) {
779804
for ($rowAdjust = 0; $rowAdjust < $rowRepeats; ++$rowAdjust) {
780805
$rID = $rowID + $rowAdjust;

tests/PhpSpreadsheetTests/Reader/Ods/RepeatedColumnsTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,39 @@ public function readCell(string $columnAddress, int $row, string $worksheetName
3232
self::assertEquals('TestK', $worksheet->getCell('K1')->getValue());
3333
$spreadsheet->disconnectWorksheets();
3434
}
35+
36+
/**
37+
* Test that read filter correctly handles cells with number-columns-repeated
38+
* containing data, where some repeated columns pass the filter and others don't.
39+
*
40+
* @see https://github.com/PHPOffice/PhpSpreadsheet/issues/4802
41+
*/
42+
public function testReadFilterWithRepeatedDataCells(): void
43+
{
44+
// The ODS file has: A=TestA, B-D=empty(repeated=3), E=TestE, F=empty,
45+
// G=TestG, H-J=SameValue(repeated=3), K=TestK
46+
$reader = new Ods();
47+
$reader->setReadFilter(
48+
new class () implements IReadFilter {
49+
public function readCell(string $columnAddress, int $row, string $worksheetName = ''): bool
50+
{
51+
return in_array($columnAddress, ['I', 'K'], true);
52+
}
53+
}
54+
);
55+
$spreadsheet = $reader->load('tests/data/Reader/Ods/RepeatedDataCells.ods');
56+
$worksheet = $spreadsheet->getActiveSheet();
57+
58+
// I1 is part of a repeated cell group (H-J) with value "SameValue"
59+
// The filter should correctly read I1 even though H (first in group) is not in the filter
60+
self::assertEquals('SameValue', $worksheet->getCell('I1')->getValue());
61+
self::assertEquals('TestK', $worksheet->getCell('K1')->getValue());
62+
63+
// Columns not in filter should not have data
64+
self::assertNull($worksheet->getCell('A1')->getValue());
65+
self::assertNull($worksheet->getCell('H1')->getValue());
66+
self::assertNull($worksheet->getCell('J1')->getValue());
67+
68+
$spreadsheet->disconnectWorksheets();
69+
}
3570
}
2.76 KB
Binary file not shown.

0 commit comments

Comments
 (0)