Skip to content

Commit 9498c03

Browse files
committed
Xls Writer Conditional Rules Applied to Whole Rows or Columns
Fix #3195. Applying a conditional style to an entire row or column applies the Excel 2007+ limits (16,384 columns and 1,048,576 rows). However, Excel 2003- has different limits (256 columns and 65,536 rows). Trying to use the larger limits in an Xls spreadsheet results in a corrupt file. Xls Writer is changed to adjust ranges appropriately. The issue also mentions that there might be a problem if a spreadsheet contains CF rules for both whole rows and whole columns. I am unable to corroborate this; the code from the new test can be used to generate an Xls file which is usable, and which has both row and column conditional styles. However, testing showed that Xls Writer did not handle conditions correctly for floating point, negative integers, or integers > 65535. These should now be handled correctly.
1 parent f7c183b commit 9498c03

File tree

3 files changed

+117
-3
lines changed

3 files changed

+117
-3
lines changed

src/PhpSpreadsheet/Writer/Xls/ConditionalHelper.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public function processCondition(mixed $condition, string $cellRange): void
3030
$this->condition = $condition;
3131
$this->cellRange = $cellRange;
3232

33-
if (is_int($condition) || is_float($condition)) {
34-
$this->size = ($condition <= 65535 ? 3 : 0x0000);
33+
if (is_int($condition) && $condition >= 0 && $condition <= 65535) {
34+
$this->size = 3;
3535
$this->tokens = pack('Cv', 0x1E, $condition);
3636
} else {
3737
try {

src/PhpSpreadsheet/Writer/Xls/Worksheet.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,31 @@ public function close(): void
489489
$this->storeEof();
490490
}
491491

492+
public const MAX_XLS_COLUMN = 256;
493+
public const MAX_XLS_COLUMN_STRING = 'IV';
494+
public const MAX_XLS_ROW = 65536;
495+
496+
private static function limitRange(string $exploded): string
497+
{
498+
$retVal = '';
499+
$ranges = Coordinate::getRangeBoundaries($exploded);
500+
$firstCol = Coordinate::columnIndexFromString($ranges[0][0]);
501+
$firstRow = (int) $ranges[0][1];
502+
if ($firstCol <= self::MAX_XLS_COLUMN && $firstRow <= self::MAX_XLS_ROW) {
503+
$retVal = $exploded;
504+
if (str_contains($exploded, ':')) {
505+
$lastCol = Coordinate::columnIndexFromString($ranges[1][0]);
506+
$ranges[1][1] = min(self::MAX_XLS_ROW, (int) $ranges[1][1]);
507+
if ($lastCol > self::MAX_XLS_COLUMN) {
508+
$ranges[1][0] = self::MAX_XLS_COLUMN_STRING;
509+
}
510+
$retVal = "{$ranges[0][0]}{$ranges[0][1]}:{$ranges[1][0]}{$ranges[1][1]}";
511+
}
512+
}
513+
514+
return $retVal;
515+
}
516+
492517
private function writeConditionalFormatting(): void
493518
{
494519
$conditionalFormulaHelper = new ConditionalHelper($this->parser);
@@ -497,7 +522,10 @@ private function writeConditionalFormatting(): void
497522
foreach ($this->phpSheet->getConditionalStylesCollection() as $key => $value) {
498523
$keyExplode = explode(',', Coordinate::resolveUnionAndIntersection($key));
499524
foreach ($keyExplode as $exploded) {
500-
$arrConditionalStyles[$exploded] = $value;
525+
$range = self::limitRange($exploded);
526+
if ($range !== '') {
527+
$arrConditionalStyles[$range] = $value;
528+
}
501529
}
502530
}
503531
if (!empty($arrConditionalStyles)) {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xls;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PhpOffice\PhpSpreadsheet\Style\Color;
9+
use PhpOffice\PhpSpreadsheet\Style\Conditional;
10+
use PhpOffice\PhpSpreadsheet\Style\Style;
11+
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
12+
13+
class ConditionalLimitsTest extends AbstractFunctional
14+
{
15+
public function testLimits(): void
16+
{
17+
$spreadsheet = new Spreadsheet();
18+
$sheet = $spreadsheet->getActiveSheet();
19+
$sheet->fromArray(
20+
[
21+
['Cell', 0, null, null, 'Col Rng', -2, -1],
22+
[null, null, null, null, null, 0, 1],
23+
['Cell Rng', -2, -1, 0, null, 2, 3],
24+
[null, 1, 2, 3, null, 4, -1],
25+
[],
26+
['Row Rng'],
27+
[-2, -1, 0],
28+
[1, 2, 3],
29+
],
30+
strictNullComparison: true
31+
);
32+
$redStyle = new Style(false, true);
33+
$redStyle->getFont()->setColor(new Color(Color::COLOR_RED));
34+
35+
$condition1 = new Conditional();
36+
$condition1->setConditionType(Conditional::CONDITION_CELLIS)
37+
->setOperatorType(Conditional::OPERATOR_BETWEEN)
38+
->addCondition(-1)
39+
->addCondition(1)
40+
->setStyle($redStyle);
41+
$conditionalStyles = [$condition1];
42+
$cellRange = 'B1';
43+
$sheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles);
44+
45+
$condition2 = new Conditional();
46+
$condition2->setConditionType(Conditional::CONDITION_CELLIS)
47+
->setOperatorType(Conditional::OPERATOR_BETWEEN)
48+
->addCondition(-1.5)
49+
->addCondition(1.5)
50+
->setStyle($redStyle);
51+
$conditionalStyles = [$condition2];
52+
$cellRange = 'F:G';
53+
$sheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles);
54+
55+
$condition3 = new Conditional();
56+
$condition3->setConditionType(Conditional::CONDITION_CELLIS)
57+
->setOperatorType(Conditional::OPERATOR_BETWEEN)
58+
->addCondition(-1)
59+
->addCondition(70000)
60+
->setStyle($redStyle);
61+
$conditionalStyles = [$condition3];
62+
$cellRange = '7:8';
63+
$sheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles);
64+
65+
$cellRange = 'B3:D4';
66+
$sheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles);
67+
$sheet->setSelectedCells('A1');
68+
$keys = array_keys($sheet->getConditionalStylesCollection());
69+
self::assertSame(['B1', 'F1:G1048576', 'A7:XFD8', 'B3:D4'], $keys);
70+
71+
$robj = $this->writeAndReload($spreadsheet, 'Xls');
72+
$spreadsheet->disconnectWorksheets();
73+
$sheet0 = $robj->getActiveSheet();
74+
$conditionals = $sheet0->getConditionalStylesCollection();
75+
self::assertSame(['B1', 'F1:G65536', 'A7:IV8', 'B3:D4'], array_keys($conditionals));
76+
$b1 = $conditionals['B1'][0];
77+
self::assertSame([-1, 1], $b1->getConditions());
78+
$b1 = $conditionals['F1:G65536'][0];
79+
self::assertSame([-1.5, 1.5], $b1->getConditions());
80+
$b1 = $conditionals['A7:IV8'][0];
81+
self::assertSame([-1, 70000], $b1->getConditions());
82+
$b1 = $conditionals['B3:D4'][0];
83+
self::assertSame([-1, 70000], $b1->getConditions());
84+
$robj->disconnectWorksheets();
85+
}
86+
}

0 commit comments

Comments
 (0)