Skip to content

Commit 41ca105

Browse files
committed
Handle Overlapping Ranges
Fix #4318. Also make getConditionalStyles more useful, by adding a non-default parameter so that all rules pertaining to a single-cell coordinate can be returned, and in priority order. By default, just the first matching rule will be returned.
1 parent 56e7422 commit 41ca105

File tree

3 files changed

+83
-22
lines changed

3 files changed

+83
-22
lines changed

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,27 +1415,68 @@ public function getStyle(AddressRange|CellAddress|int|string|array $cellCoordina
14151415
* included in a conditional style range.
14161416
* If a range of cells is specified, then the styles will only be returned if the range matches the entire
14171417
* range of the conditional.
1418+
* @param bool $firstOnly default true, return all matching
1419+
* conditionals ordered by priority if false, first only if true
14181420
*
14191421
* @return Conditional[]
14201422
*/
1421-
public function getConditionalStyles(string $coordinate): array
1423+
public function getConditionalStyles(string $coordinate, bool $firstOnly = true): array
14221424
{
14231425
$coordinate = strtoupper($coordinate);
1424-
if (str_contains($coordinate, ':')) {
1426+
if (preg_match('/[: ,]/', $coordinate) === 1) {
14251427
return $this->conditionalStylesCollection[$coordinate] ?? [];
14261428
}
14271429

1428-
$cell = $this->getCell($coordinate);
1429-
foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) {
1430-
$cellBlocks = explode(',', Coordinate::resolveUnionAndIntersection($conditionalRange));
1431-
foreach ($cellBlocks as $cellBlock) {
1432-
if ($cell->isInRange($cellBlock)) {
1433-
return $this->conditionalStylesCollection[$conditionalRange];
1430+
$conditionalStyles = [];
1431+
foreach ($this->conditionalStylesCollection as $keyStylesOrig => $conditionalRange) {
1432+
$keyStyles = Coordinate::resolveUnionAndIntersection($keyStylesOrig);
1433+
$keyParts = explode(',', $keyStyles);
1434+
foreach ($keyParts as $keyPart) {
1435+
if ($keyPart === $coordinate) {
1436+
if ($firstOnly) {
1437+
return $conditionalRange;
1438+
}
1439+
$conditionalStyles[$keyStylesOrig] = $conditionalRange;
1440+
1441+
break;
1442+
} elseif (str_contains($keyPart, ':')) {
1443+
if (Coordinate::coordinateIsInsideRange($keyPart, $coordinate)) {
1444+
if ($firstOnly) {
1445+
return $conditionalRange;
1446+
}
1447+
$conditionalStyles[$keyStylesOrig] = $conditionalRange;
1448+
1449+
break;
1450+
}
14341451
}
14351452
}
14361453
}
1454+
$outArray = [];
1455+
foreach ($conditionalStyles as $conditionalArray) {
1456+
foreach ($conditionalArray as $conditional) {
1457+
$outArray[] = $conditional;
1458+
}
1459+
}
1460+
usort($outArray, [self::class, 'comparePriority']);
14371461

1438-
return [];
1462+
return $outArray;
1463+
}
1464+
1465+
private static function comparePriority(Conditional $condA, Conditional $condB): int
1466+
{
1467+
$a = $condA->getPriority();
1468+
$b = $condB->getPriority();
1469+
if ($a === $b) {
1470+
return 0;
1471+
}
1472+
if ($a === 0) {
1473+
return 1;
1474+
}
1475+
if ($b === 0) {
1476+
return -1;
1477+
}
1478+
1479+
return ($a < $b) ? -1 : 1;
14391480
}
14401481

14411482
public function getConditionalRange(string $coordinate): ?string
@@ -1465,19 +1506,7 @@ public function getConditionalRange(string $coordinate): ?string
14651506
*/
14661507
public function conditionalStylesExists(string $coordinate): bool
14671508
{
1468-
$coordinate = strtoupper($coordinate);
1469-
if (str_contains($coordinate, ':')) {
1470-
return isset($this->conditionalStylesCollection[$coordinate]);
1471-
}
1472-
1473-
$cell = $this->getCell($coordinate);
1474-
foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) {
1475-
if ($cell->isInRange($conditionalRange)) {
1476-
return true;
1477-
}
1478-
}
1479-
1480-
return false;
1509+
return !empty($this->getConditionalStyles($coordinate));
14811510
}
14821511

14831512
/**
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
6+
7+
use PhpOffice\PhpSpreadsheet\IOFactory;
8+
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
9+
10+
class ConditionalPriority2Test extends AbstractFunctional
11+
{
12+
public function testConditionalPriority(): void
13+
{
14+
$filename = 'tests/data/Reader/XLSX/issue.4318.xlsx';
15+
$reader = IOFactory::createReader('Xlsx');
16+
$spreadsheet = $reader->load($filename);
17+
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
18+
$spreadsheet->disconnectWorksheets();
19+
$sheet = $reloadedSpreadsheet->getActiveSheet();
20+
$rules = $sheet->getConditionalStyles('E5', false);
21+
self::assertCount(4, $rules);
22+
self::assertSame(2, $rules[0]->getPriority());
23+
self::assertSame(['$B$2<2'], $rules[0]->getConditions());
24+
self::assertSame(3, $rules[1]->getPriority());
25+
self::assertSame(['$B$2=""'], $rules[1]->getConditions());
26+
self::assertSame(4, $rules[2]->getPriority());
27+
self::assertSame(['$B$2=3'], $rules[2]->getConditions());
28+
self::assertSame(5, $rules[3]->getPriority());
29+
self::assertSame(['$B$2=2'], $rules[3]->getConditions());
30+
$reloadedSpreadsheet->disconnectWorksheets();
31+
}
32+
}
8.73 KB
Binary file not shown.

0 commit comments

Comments
 (0)