Skip to content

Commit 1e7bb18

Browse files
authored
fix: Ensure unmatched optional capture group is treated as null in parseRowColumnSelection (#578)
Add PREG_UNMATCHED_AS_NULL flag to parseRowColumnSelection
1 parent 239da76 commit 1e7bb18

2 files changed

Lines changed: 52 additions & 4 deletions

File tree

src/FragmentFinder.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -292,18 +292,17 @@ private function parseColumnSelection(string $selection, TabularData $tabularDat
292292
*/
293293
private function parseRowColumnSelection(string $selection): array
294294
{
295-
if (1 !== preg_match(self::REGEXP_ROWS_COLUMNS_SELECTION, $selection, $found)) {
295+
if (1 !== preg_match(self::REGEXP_ROWS_COLUMNS_SELECTION, $selection, $found, PREG_UNMATCHED_AS_NULL)) {
296296
return [-1, 0];
297297
}
298298

299-
$start = $found['start'];
300-
$end = $found['end'] ?? null;
301-
$start = filter_var($start, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
299+
$start = filter_var($found['start'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
302300
if (false === $start) {
303301
return [-1, 0];
304302
}
305303
--$start;
306304

305+
$end = $found['end'];
307306
if (null === $end || '*' === $end) {
308307
return [$start, $end];
309308
}

src/TabularDataReaderTestCase.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,37 @@ public static function provideValidExpressions(): iterable
273273
'expression' => 'cell=48,12',
274274
'expected' => null,
275275
];
276+
277+
yield 'multiple single rows without range endpoint' => [
278+
'expression' => 'row=1;3',
279+
'expected' => [
280+
0 => ['date', 'temperature', 'place'],
281+
1 => ['2011-01-02', '-1', 'Galway'],
282+
],
283+
];
284+
285+
yield 'mixed single row and row range' => [
286+
'expression' => 'row=1;3-5',
287+
'expected' => [
288+
0 => ['date', 'temperature', 'place'],
289+
1 => ['2011-01-02', '-1', 'Galway'],
290+
2 => ['2011-01-03', '0', 'Galway'],
291+
3 => ['2011-01-01', '6', 'Berkeley'],
292+
],
293+
];
294+
295+
yield 'multiple single columns without range endpoint' => [
296+
'expression' => 'col=1;3',
297+
'expected' => [
298+
0 => [0 => 'date', 2 => 'place'],
299+
1 => [0 => '2011-01-01', 2 => 'Galway'],
300+
2 => [0 => '2011-01-02', 2 => 'Galway'],
301+
3 => [0 => '2011-01-03', 2 => 'Galway'],
302+
4 => [0 => '2011-01-01', 2 => 'Berkeley'],
303+
5 => [0 => '2011-01-02', 2 => 'Berkeley'],
304+
6 => [0 => '2011-01-03', 2 => 'Berkeley'],
305+
],
306+
];
276307
}
277308

278309
#[Test]
@@ -323,6 +354,7 @@ public static function provideExpressionWithIgnoredSelections(): iterable
323354
'expression selection is invalid for cell 7' => ['cell=1,0-2,3'],
324355
'expression selection is invalid for row or column 3' => ['row=0-3'],
325356
'expression selection is invalid for row or column 4' => ['row=3-0'],
357+
'all single row selections are out of bounds' => ['row=0;0'],
326358
];
327359
}
328360

@@ -352,6 +384,23 @@ public function it_fails_if_no_row_is_found(): void
352384
$this->tabularDataWithoutHeader()->matchingFirstOrFail('row=42');
353385
}
354386

387+
#[Test]
388+
public function it_throws_when_expression_contains_invalid_single_selection_alongside_valid(): void
389+
{
390+
$this->expectException(FragmentNotFound::class);
391+
392+
$this->tabularDataWithoutHeader()->matchingFirstOrFail('row=0;3');
393+
}
394+
395+
#[Test]
396+
public function it_returns_valid_rows_when_mixed_selection_contains_invalid_single_row(): void
397+
{
398+
$result = $this->tabularDataWithoutHeader()->matchingFirst('row=0;3');
399+
400+
self::assertNotNull($result);
401+
self::assertSame([0 => ['2011-01-02', '-1', 'Galway']], [...$result]);
402+
}
403+
355404
/***************************
356405
* TabularDataReader::map
357406
****************************/

0 commit comments

Comments
 (0)