Skip to content

Commit 436d067

Browse files
authored
Option for Readers to create a new blank sheet if none match LoadSheetsOnly list. (#4623)
* Option for Readers to create a new blank sheet if none match LoadSheetsOnly list. Backport of PR #4618. * Update CHANGELOG.md
1 parent 73ca822 commit 436d067

File tree

9 files changed

+133
-0
lines changed

9 files changed

+133
-0
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com)
66
and this project adheres to [Semantic Versioning](https://semver.org). This is always true of the master branch. Some earlier branches, including the branch from which you are reading this file, remain supported and security fixes are applied to them; if the security fix represents a breaking change, it may have to be applied as a minor or patch version.
77

8+
## 2025-09-03 - 3.10.1
9+
10+
### Added
11+
12+
- Option for Readers to create a new blank sheet if none match LoadSheetsOnly list. [PR #4623](https://github.com/PHPOffice/PhpSpreadsheet/pull/4623) Backport of [PR #4618](https://github.com/PHPOffice/PhpSpreadsheet/pull/4618).
13+
14+
### Fixed
15+
16+
- Compatibility changes for Php 8.5. [Issue #4600](https://github.com/PHPOffice/PhpSpreadsheet/issues/4600) [PR #4613](https://github.com/PHPOffice/PhpSpreadsheet/pull/4613) [PR #4595](https://github.com/PHPOffice/PhpSpreadsheet/pull/4595) [PR #4589](https://github.com/PHPOffice/PhpSpreadsheet/pull/4589)
17+
818
## 2025-08-10 - 3.10.0
919

1020
### Breaking Changes

src/PhpSpreadsheet/Reader/BaseReader.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ abstract class BaseReader implements IReader
5454
*/
5555
protected bool $allowExternalImages = false;
5656

57+
/**
58+
* Create a blank sheet if none are read,
59+
* possibly due to a typo when using LoadSheetsOnly.
60+
*/
61+
protected bool $createBlankSheetIfNoneRead = false;
62+
5763
/**
5864
* IReadFilter instance.
5965
*/
@@ -171,6 +177,17 @@ public function getAllowExternalImages(): bool
171177
return $this->allowExternalImages;
172178
}
173179

180+
/**
181+
* Create a blank sheet if none are read,
182+
* possibly due to a typo when using LoadSheetsOnly.
183+
*/
184+
public function setCreateBlankSheetIfNoneRead(bool $createBlankSheetIfNoneRead): self
185+
{
186+
$this->createBlankSheetIfNoneRead = $createBlankSheetIfNoneRead;
187+
188+
return $this;
189+
}
190+
174191
public function getSecurityScanner(): ?XmlScanner
175192
{
176193
return $this->securityScanner;
@@ -205,6 +222,9 @@ protected function processFlags(int $flags): void
205222
if (((bool) ($flags & self::DONT_ALLOW_EXTERNAL_IMAGES)) === true) {
206223
$this->setAllowExternalImages(false);
207224
}
225+
if (((bool) ($flags & self::CREATE_BLANK_SHEET_IF_NONE_READ)) === true) {
226+
$this->setCreateBlankSheetIfNoneRead(true);
227+
}
208228
}
209229

210230
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet

src/PhpSpreadsheet/Reader/Gnumeric.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
254254
(new Properties($this->spreadsheet))->readProperties($xml, $gnmXML);
255255

256256
$worksheetID = 0;
257+
$sheetCreated = false;
257258
foreach ($gnmXML->Sheets->Sheet as $sheetOrNull) {
258259
$sheet = self::testSimpleXml($sheetOrNull);
259260
$worksheetName = (string) $sheet->Name;
@@ -265,6 +266,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
265266

266267
// Create new Worksheet
267268
$this->spreadsheet->createSheet();
269+
$sheetCreated = true;
268270
$this->spreadsheet->setActiveSheetIndex($worksheetID);
269271
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
270272
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
@@ -316,6 +318,9 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
316318
$this->setSelectedCells($sheet);
317319
++$worksheetID;
318320
}
321+
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
322+
$this->spreadsheet->createSheet();
323+
}
319324

320325
$this->processDefinedNames($gnmXML);
321326

src/PhpSpreadsheet/Reader/IReader.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ interface IReader
3838
*/
3939
public const IGNORE_ROWS_WITH_NO_CELLS = 8;
4040

41+
public const CREATE_BLANK_SHEET_IF_NONE_READ = 64;
42+
4143
/**
4244
* Allow external images. Use with caution.
4345
* Improper specification of these within a spreadsheet
@@ -145,6 +147,12 @@ public function setAllowExternalImages(bool $allowExternalImages): self;
145147

146148
public function getAllowExternalImages(): bool;
147149

150+
/**
151+
* Create a blank sheet if none are read,
152+
* possibly due to a typo when using LoadSheetsOnly.
153+
*/
154+
public function setCreateBlankSheetIfNoneRead(bool $createBlankSheetIfNoneRead): self;
155+
148156
/**
149157
* Set read filter.
150158
*
@@ -164,6 +172,7 @@ public function setReadFilter(IReadFilter $readFilter): self;
164172
* self::IGNORE_ROWS_WITH_NO_CELLS Don't load any rows that contain no cells.
165173
* self::ALLOW_EXTERNAL_IMAGES Attempt to fetch images stored outside the spreadsheet.
166174
* self::DONT_ALLOW_EXTERNAL_IMAGES Don't attempt to fetch images stored outside the spreadsheet.
175+
* self::CREATE_BLANK_SHEET_IF_NONE_READ If no sheets are read, create a blank one.
167176
*/
168177
public function load(string $filename, int $flags = 0): Spreadsheet;
169178
}

src/PhpSpreadsheet/Reader/Ods.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
325325
$tables = $workbookData->getElementsByTagNameNS($tableNs, 'table');
326326

327327
$worksheetID = 0;
328+
$sheetCreated = false;
328329
foreach ($tables as $worksheetDataSet) {
329330
/** @var DOMElement $worksheetDataSet */
330331
$worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name');
@@ -342,6 +343,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
342343

343344
// Create sheet
344345
$spreadsheet->createSheet();
346+
$sheetCreated = true;
345347
$spreadsheet->setActiveSheetIndex($worksheetID);
346348

347349
if ($worksheetName || is_numeric($worksheetName)) {
@@ -682,6 +684,9 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
682684
$pageSettings->setPrintSettingsForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName);
683685
++$worksheetID;
684686
}
687+
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
688+
$spreadsheet->createSheet();
689+
}
685690

686691
$autoFilterReader->read($workbookData);
687692
$definedNameReader->read($workbookData);

src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ protected function loadSpreadsheetFromFile2(string $filename, Xls $xls): Spreads
153153

154154
// Parse the individual sheets
155155
$xls->activeSheetSet = false;
156+
$sheetCreated = false;
156157
foreach ($xls->sheets as $sheet) {
157158
$selectedCells = '';
158159
if ($sheet['sheetType'] != 0x00) {
@@ -167,6 +168,7 @@ protected function loadSpreadsheetFromFile2(string $filename, Xls $xls): Spreads
167168

168169
// add sheet to PhpSpreadsheet object
169170
$xls->phpSheet = $xls->spreadsheet->createSheet();
171+
$sheetCreated = true;
170172
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
171173
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
172174
// name in line with the formula, not the reverse
@@ -568,6 +570,9 @@ protected function loadSpreadsheetFromFile2(string $filename, Xls $xls): Spreads
568570
$xls->phpSheet->setSelectedCells($selectedCells);
569571
}
570572
}
573+
if ($xls->createBlankSheetIfNoneRead && !$sheetCreated) {
574+
$xls->spreadsheet->createSheet();
575+
}
571576
if ($xls->activeSheetSet === false) {
572577
$xls->spreadsheet->setActiveSheetIndex(0);
573578
}

src/PhpSpreadsheet/Reader/Xlsx.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
762762

763763
$charts = $chartDetails = [];
764764

765+
$sheetCreated = false;
765766
if ($xmlWorkbookNS->sheets) {
766767
foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) {
767768
$eleSheetAttr = self::getAttributes($eleSheet);
@@ -788,6 +789,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
788789

789790
// Load sheet
790791
$docSheet = $excel->createSheet();
792+
$sheetCreated = true;
791793
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet
792794
// references in formula cells... during the load, all formulae should be correct,
793795
// and we're simply bringing the worksheet name in line with the formula, not the
@@ -1850,6 +1852,9 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
18501852
}
18511853
}
18521854
}
1855+
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
1856+
$excel->createSheet();
1857+
}
18531858

18541859
(new WorkbookView($excel))->viewSettings($xmlWorkbook, $mainNS, $mapSheetId, $this->readDataOnly);
18551860

src/PhpSpreadsheet/Reader/Xml.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo
301301
$worksheetID = 0;
302302
$xml_ss = $xml->children(self::NAMESPACES_SS);
303303

304+
$sheetCreated = false;
304305
/** @var null|SimpleXMLElement $worksheetx */
305306
foreach ($xml_ss->Worksheet as $worksheetx) {
306307
$worksheet = $worksheetx ?? new SimpleXMLElement('<xml></xml>');
@@ -315,6 +316,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo
315316

316317
// Create new Worksheet
317318
$spreadsheet->createSheet();
319+
$sheetCreated = true;
318320
$spreadsheet->setActiveSheetIndex($worksheetID);
319321
$worksheetName = '';
320322
if (isset($worksheet_ss['Name'])) {
@@ -663,6 +665,9 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo
663665
}
664666
++$worksheetID;
665667
}
668+
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
669+
$spreadsheet->createSheet();
670+
}
666671

667672
// Globally scoped defined names
668673
$activeSheetIndex = 0;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Reader;
6+
7+
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
8+
use PhpOffice\PhpSpreadsheet\IOFactory;
9+
use PhpOffice\PhpSpreadsheet\Reader;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use PHPUnit\Framework\TestCase;
12+
13+
class CreateBlankSheetIfNoneReadTest extends TestCase
14+
{
15+
#[DataProvider('providerIdentify')]
16+
public function testExceptionIfNoSheet(string $file, string $expectedName, string $expectedClass): void
17+
{
18+
$this->expectException(SpreadsheetException::class);
19+
$this->expectExceptionMessage('out of bounds index: 0');
20+
$actual = IOFactory::identify($file);
21+
self::assertSame($expectedName, $actual);
22+
$reader = IOFactory::createReaderForFile($file);
23+
self::assertSame($expectedClass, $reader::class);
24+
$sheetlist = ['Unknown sheetname'];
25+
$reader->setLoadSheetsOnly($sheetlist);
26+
$reader->load($file);
27+
}
28+
29+
#[DataProvider('providerIdentify')]
30+
public function testCreateSheetIfNoSheet(string $file, string $expectedName, string $expectedClass): void
31+
{
32+
$actual = IOFactory::identify($file);
33+
self::assertSame($expectedName, $actual);
34+
$reader = IOFactory::createReaderForFile($file);
35+
self::assertSame($expectedClass, $reader::class);
36+
$reader->setCreateBlankSheetIfNoneRead(true);
37+
$sheetlist = ['Unknown sheetname'];
38+
$reader->setLoadSheetsOnly($sheetlist);
39+
$spreadsheet = $reader->load($file);
40+
$sheet = $spreadsheet->getActiveSheet();
41+
self::assertSame('Worksheet', $sheet->getTitle());
42+
self::assertCount(1, $spreadsheet->getAllSheets());
43+
$spreadsheet->disconnectWorksheets();
44+
}
45+
46+
public static function providerIdentify(): array
47+
{
48+
return [
49+
['samples/templates/26template.xlsx', 'Xlsx', Reader\Xlsx::class],
50+
['samples/templates/GnumericTest.gnumeric', 'Gnumeric', Reader\Gnumeric::class],
51+
['samples/templates/30template.xls', 'Xls', Reader\Xls::class],
52+
['samples/templates/OOCalcTest.ods', 'Ods', Reader\Ods::class],
53+
['samples/templates/excel2003.xml', 'Xml', Reader\Xml::class],
54+
];
55+
}
56+
57+
public function testUsingFlage(): void
58+
{
59+
$file = 'samples/templates/26template.xlsx';
60+
$reader = IOFactory::createReaderForFile($file);
61+
$sheetlist = ['Unknown sheetname'];
62+
$reader->setLoadSheetsOnly($sheetlist);
63+
$spreadsheet = $reader->load($file, Reader\BaseReader::CREATE_BLANK_SHEET_IF_NONE_READ);
64+
$sheet = $spreadsheet->getActiveSheet();
65+
self::assertSame('Worksheet', $sheet->getTitle());
66+
self::assertCount(1, $spreadsheet->getAllSheets());
67+
$spreadsheet->disconnectWorksheets();
68+
}
69+
}

0 commit comments

Comments
 (0)