Skip to content

Commit c1ad483

Browse files
authored
Merge branch 'master' into issue4182
2 parents d327481 + 27d7920 commit c1ad483

File tree

12 files changed

+249
-35
lines changed

12 files changed

+249
-35
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
2828
- Xls Writer Condtional Rules Applied to Whole Rows or Columns. [Issue #3185](https://github.com/PHPOffice/PhpSpreadsheet/issues/3185) [PR #4152](https://github.com/PHPOffice/PhpSpreadsheet/pull/4152)
2929
- Xlsx Writer Duplicate ContentTypes Entry for Background Image. [Issue #4179](https://github.com/PHPOffice/PhpSpreadsheet/issues/4179) [PR #4180](https://github.com/PHPOffice/PhpSpreadsheet/pull/4180)
3030
- Check strictNullComparison outside of loops. [PR #3347](https://github.com/PHPOffice/PhpSpreadsheet/pull/3347)
31-
- SUMIFS Does Not Require _xlfn. [Issue #4182](https://github.com/PHPOffice/PhpSpreadsheet/issues/4182) [PR #4186](https://github.com/PHPOffice/PhpSpreadsheet/pull/4186)
31+
- SUMIFS Does Not Require xlfn. [Issue #4182](https://github.com/PHPOffice/PhpSpreadsheet/issues/4182) [PR #4186](https://github.com/PHPOffice/PhpSpreadsheet/pull/4186)
3232

3333
## 2024-09-29 - 3.3.0 (no 3.0.\*, 3.1.\*, 3.2.\*)
3434

composer.lock

+5-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/topics/reading-and-writing-to-file.md

+14-14
Original file line numberDiff line numberDiff line change
@@ -1113,19 +1113,19 @@ Flags that are available that can be passed to the Reader in this way include:
11131113

11141114
- $reader::LOAD_WITH_CHARTS
11151115
- $reader::READ_DATA_ONLY
1116-
- $reader::IGNORE_EMPTY_CELLS
1117-
- $reader::SKIP_EMPTY_CELLS (synonym for IGNORE_EMPTY_CELLS)
1118-
1119-
| Readers | LOAD_WITH_CHARTS | READ_DATA_ONLY | IGNORE_EMPTY_CELLS |
1120-
|----------|------------------|----------------|--------------------|
1121-
| Xlsx | YES | YES | YES |
1122-
| Xls | NO | YES | YES |
1123-
| Xml | NO | NO | NO |
1124-
| Ods | NO | YES | NO |
1125-
| Gnumeric | NO | YES | NO |
1126-
| Html | N/A | N/A | N/A |
1127-
| Slk | N/A | NO | NO |
1128-
| Csv | N/A | NO | NO |
1116+
- $reader::IGNORE_EMPTY_CELLS
1117+
- $reader::IGNORE_ROWS_WITH_NO_CELLS
1118+
1119+
| Readers | LOAD_WITH_CHARTS | READ_DATA_ONLY | IGNORE_EMPTY_CELLS | IGNORE_ROWS_WITH_NO_CELLS |
1120+
|----------|------------------|----------------|--------------------|---------------------------|
1121+
| Xlsx | YES | YES | YES | YES |
1122+
| Xls | NO | YES | YES | NO |
1123+
| Xml | NO | NO | NO | NO |
1124+
| Ods | NO | YES | NO | NO |
1125+
| Gnumeric | NO | YES | NO | NO |
1126+
| Html | N/A | N/A | N/A | N/A |
1127+
| Slk | N/A | NO | NO | NO |
1128+
| Csv | N/A | NO | NO | NO |
11291129

11301130
Likewise, when saving a file using a Writer, loaded charts will not be saved unless you explicitly tell the Writer to include them:
11311131

@@ -1162,5 +1162,5 @@ Two or more flags can be passed together using PHP's `|` operator.
11621162

11631163
```php
11641164
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile("myExampleFile.xlsx");
1165-
$reader->load("spreadsheetWithCharts.xlsx", $reader::READ_DATA_ONLY | $reader::SKIP_EMPTY_CELLS);
1165+
$reader->load("spreadsheetWithCharts.xlsx", $reader::READ_DATA_ONLY | $reader::IGNORE_EMPTY_CELLS);
11661166
```

src/PhpSpreadsheet/Reader/BaseReader.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ abstract class BaseReader implements IReader
1919

2020
/**
2121
* Read empty cells?
22-
* Identifies whether the Reader should read data values for cells all cells, or should ignore cells containing
22+
* Identifies whether the Reader should read data values for all cells, or should ignore cells containing
2323
* null value or empty string.
2424
*/
2525
protected bool $readEmptyCells = true;
@@ -166,7 +166,7 @@ protected function processFlags(int $flags): void
166166
if (((bool) ($flags & self::READ_DATA_ONLY)) === true) {
167167
$this->setReadDataOnly(true);
168168
}
169-
if (((bool) ($flags & self::SKIP_EMPTY_CELLS) || (bool) ($flags & self::IGNORE_EMPTY_CELLS)) === true) {
169+
if (((bool) ($flags & self::IGNORE_EMPTY_CELLS)) === true) {
170170
$this->setReadEmptyCells(false);
171171
}
172172
if (((bool) ($flags & self::IGNORE_ROWS_WITH_NO_CELLS)) === true) {

src/PhpSpreadsheet/Reader/IReader.php

+25-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,36 @@
66

77
interface IReader
88
{
9+
/**
10+
* Flag used to load the charts.
11+
*
12+
* This flag is supported only for some formats.
13+
*/
914
public const LOAD_WITH_CHARTS = 1;
1015

16+
/**
17+
* Flag used to read data only, not style or structure information.
18+
*/
1119
public const READ_DATA_ONLY = 2;
1220

13-
public const SKIP_EMPTY_CELLS = 4;
21+
/**
22+
* @deprecated 3.4.0 use IGNORE_EMPTY_CELLS instead.
23+
*/
24+
public const SKIP_EMPTY_CELLS = self::IGNORE_EMPTY_CELLS;
25+
26+
/**
27+
* Flag used to ignore empty cells when reading.
28+
*
29+
* The ignored cells will not be instantiated.
30+
*/
1431
public const IGNORE_EMPTY_CELLS = 4;
1532

33+
/**
34+
* Flag used to ignore rows without cells.
35+
*
36+
* This flag is supported only for some formats.
37+
* This can heavily improve performance for some files.
38+
*/
1639
public const IGNORE_ROWS_WITH_NO_CELLS = 8;
1740

1841
public function __construct();
@@ -119,7 +142,7 @@ public function setReadFilter(IReadFilter $readFilter): self;
119142
* @param int $flags Flags that can change the behaviour of the Writer:
120143
* self::LOAD_WITH_CHARTS Load any charts that are defined (if the Reader supports Charts)
121144
* self::READ_DATA_ONLY Read only data, not style or structure information, from the file
122-
* self::SKIP_EMPTY_CELLS Don't read empty cells (cells that contain a null value,
145+
* self::IGNORE_EMPTY_CELLS Don't read empty cells (cells that contain a null value,
123146
* empty string, or a string containing only whitespace characters)
124147
*/
125148
public function load(string $filename, int $flags = 0): Spreadsheet;

src/PhpSpreadsheet/Worksheet/Worksheet.php

+14-7
Original file line numberDiff line numberDiff line change
@@ -2784,23 +2784,30 @@ public function fromArray(array $source, mixed $nullValue = null, string $startC
27842784
[$startColumn, $startRow] = Coordinate::coordinateFromString($startCell);
27852785

27862786
// Loop through $source
2787-
foreach ($source as $rowData) {
2788-
$currentColumn = $startColumn;
2789-
foreach ($rowData as $cellValue) {
2790-
if ($strictNullComparison) {
2787+
if ($strictNullComparison) {
2788+
foreach ($source as $rowData) {
2789+
$currentColumn = $startColumn;
2790+
foreach ($rowData as $cellValue) {
27912791
if ($cellValue !== $nullValue) {
27922792
// Set cell value
27932793
$this->getCell($currentColumn . $startRow)->setValue($cellValue);
27942794
}
2795-
} else {
2795+
++$currentColumn;
2796+
}
2797+
++$startRow;
2798+
}
2799+
} else {
2800+
foreach ($source as $rowData) {
2801+
$currentColumn = $startColumn;
2802+
foreach ($rowData as $cellValue) {
27962803
if ($cellValue != $nullValue) {
27972804
// Set cell value
27982805
$this->getCell($currentColumn . $startRow)->setValue($cellValue);
27992806
}
2807+
++$currentColumn;
28002808
}
2801-
++$currentColumn;
2809+
++$startRow;
28022810
}
2803-
++$startRow;
28042811
}
28052812

28062813
return $this;

src/PhpSpreadsheet/Writer/Xls/ConditionalHelper.php

+2-2
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

+29-1
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)) {

src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public function writeContentTypes(Spreadsheet $spreadsheet, bool $includeCharts
196196
$bgImage = $spreadsheet->getSheet($i)->getBackgroundImage();
197197
$mimeType = $spreadsheet->getSheet($i)->getBackgroundMime();
198198
$extension = $spreadsheet->getSheet($i)->getBackgroundExtension();
199-
if ($bgImage !== '' && !isset($aMediaContentTypes[$mimeType])) {
199+
if ($bgImage !== '' && !isset($aMediaContentTypes[$extension])) {
200200
$this->writeDefaultContentType($objWriter, $extension, $mimeType);
201201
}
202202
}
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+
}

tests/PhpSpreadsheetTests/Writer/Xlsx/ArrayFunctionsTest.php

+8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ class ArrayFunctionsTest extends TestCase
1515
{
1616
private string $outputFile = '';
1717

18+
protected function tearDown(): void
19+
{
20+
if ($this->outputFile !== '') {
21+
unlink($this->outputFile);
22+
$this->outputFile = '';
23+
}
24+
}
25+
1826
public function testArrayOutput(): void
1927
{
2028
$spreadsheet = new Spreadsheet();

0 commit comments

Comments
 (0)