Skip to content

Commit ff7195c

Browse files
authored
Merge pull request #4618 from oleibman/blanksheet
Option to Create Blank Sheet If LoadSheetsOnly Doesn't Find Any
2 parents 8da69e5 + 187cac7 commit ff7195c

File tree

12 files changed

+178
-0
lines changed

12 files changed

+178
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). Thia is a
1212
- Add Conditional Formatting with IconSet (Xlsx only). [Issue #4560](https://github.com/PHPOffice/PhpSpreadsheet/issues/4560) [PR #4574](https://github.com/PHPOffice/PhpSpreadsheet/pull/4574)
1313
- Copy cell adjusting formula. [Issue #1203](https://github.com/PHPOffice/PhpSpreadsheet/issues/1203) [PR #4577](https://github.com/PHPOffice/PhpSpreadsheet/pull/4577)
1414
- splitRange and ProtectedRange. [Issue #1457](https://github.com/PHPOffice/PhpSpreadsheet/issues/1457) [PR #4580](https://github.com/PHPOffice/PhpSpreadsheet/pull/4580)
15+
- Option to create blank sheet if LoadSheetsOnly doesn't find any. [PR #4618](https://github.com/PHPOffice/PhpSpreadsheet/pull/4618)
1516

1617
### Removed
1718

@@ -34,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). Thia is a
3435
- Google-only formulas exported from Google Sheets. [Issue #1637](https://github.com/PHPOffice/PhpSpreadsheet/issues/1637) [PR #4579](https://github.com/PHPOffice/PhpSpreadsheet/pull/4579)
3536
- Maximum column width. [PR #4581](https://github.com/PHPOffice/PhpSpreadsheet/pull/4581)
3637
- PrintArea after row/column delete. [Issue #2912](https://github.com/PHPOffice/PhpSpreadsheet/issues/2912) [PR #4598](https://github.com/PHPOffice/PhpSpreadsheet/pull/4598)
38+
- Excel 2007 problem with newlines. [Issue #4619](https://github.com/PHPOffice/PhpSpreadsheet/issues/4619) [PR #4620](https://github.com/PHPOffice/PhpSpreadsheet/pull/4620)
3739

3840
## 2025-08-10 - 5.0.0
3941

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
*/
@@ -173,6 +179,17 @@ public function getAllowExternalImages(): bool
173179
return $this->allowExternalImages;
174180
}
175181

182+
/**
183+
* Create a blank sheet if none are read,
184+
* possibly due to a typo when using LoadSheetsOnly.
185+
*/
186+
public function setCreateBlankSheetIfNoneRead(bool $createBlankSheetIfNoneRead): self
187+
{
188+
$this->createBlankSheetIfNoneRead = $createBlankSheetIfNoneRead;
189+
190+
return $this;
191+
}
192+
176193
public function getSecurityScanner(): ?XmlScanner
177194
{
178195
return $this->securityScanner;
@@ -207,6 +224,9 @@ protected function processFlags(int $flags): void
207224
if (((bool) ($flags & self::DONT_ALLOW_EXTERNAL_IMAGES)) === true) {
208225
$this->setAllowExternalImages(false);
209226
}
227+
if (((bool) ($flags & self::CREATE_BLANK_SHEET_IF_NONE_READ)) === true) {
228+
$this->setCreateBlankSheetIfNoneRead(true);
229+
}
210230
}
211231

212232
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
@@ -269,6 +269,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
269269
(new Properties($this->spreadsheet))->readProperties($xml, $gnmXML);
270270

271271
$worksheetID = 0;
272+
$sheetCreated = false;
272273
foreach ($gnmXML->Sheets->Sheet as $sheetOrNull) {
273274
$sheet = self::testSimpleXml($sheetOrNull);
274275
$worksheetName = (string) $sheet->Name;
@@ -280,6 +281,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
280281

281282
// Create new Worksheet
282283
$this->spreadsheet->createSheet();
284+
$sheetCreated = true;
283285
$this->spreadsheet->setActiveSheetIndex($worksheetID);
284286
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
285287
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
@@ -329,6 +331,9 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
329331
$this->setSelectedCells($sheet);
330332
++$worksheetID;
331333
}
334+
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
335+
$this->spreadsheet->createSheet();
336+
}
332337

333338
$this->processDefinedNames($gnmXML);
334339

src/PhpSpreadsheet/Reader/IReader.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ interface IReader
4141
public const ALLOW_EXTERNAL_IMAGES = 16;
4242
public const DONT_ALLOW_EXTERNAL_IMAGES = 32;
4343

44+
public const CREATE_BLANK_SHEET_IF_NONE_READ = 64;
45+
4446
public function __construct();
4547

4648
/**
@@ -149,6 +151,12 @@ public function setAllowExternalImages(bool $allowExternalImages): self;
149151

150152
public function getAllowExternalImages(): bool;
151153

154+
/**
155+
* Create a blank sheet if none are read,
156+
* possibly due to a typo when using LoadSheetsOnly.
157+
*/
158+
public function setCreateBlankSheetIfNoneRead(bool $createBlankSheetIfNoneRead): self;
159+
152160
/**
153161
* Loads PhpSpreadsheet from file.
154162
*
@@ -161,6 +169,7 @@ public function getAllowExternalImages(): bool;
161169
* self::IGNORE_ROWS_WITH_NO_CELLS Don't load any rows that contain no cells.
162170
* self::ALLOW_EXTERNAL_IMAGES Attempt to fetch images stored outside the spreadsheet.
163171
* self::DONT_ALLOW_EXTERNAL_IMAGES Don't attempt to fetch images stored outside the spreadsheet.
172+
* self::CREATE_BLANK_SHEET_IF_NONE_READ If no sheets are read, create a blank one.
164173
*/
165174
public function load(string $filename, int $flags = 0): Spreadsheet;
166175
}

src/PhpSpreadsheet/Reader/Ods.php

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

320320
$worksheetID = 0;
321+
$sheetCreated = false;
321322
foreach ($tables as $worksheetDataSet) {
322323
/** @var DOMElement $worksheetDataSet */
323324
$worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name');
@@ -335,6 +336,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
335336

336337
// Create sheet
337338
$spreadsheet->createSheet();
339+
$sheetCreated = true;
338340
$spreadsheet->setActiveSheetIndex($worksheetID);
339341

340342
if ($worksheetName || is_numeric($worksheetName)) {
@@ -441,6 +443,9 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
441443
);
442444
++$worksheetID;
443445
}
446+
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
447+
$spreadsheet->createSheet();
448+
}
444449

445450
$autoFilterReader->read($workbookData);
446451
$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
@@ -582,6 +584,9 @@ protected function loadSpreadsheetFromFile2(string $filename, Xls $xls): Spreads
582584
$xls->phpSheet->setSelectedCells($selectedCells);
583585
}
584586
}
587+
if ($xls->createBlankSheetIfNoneRead && !$sheetCreated) {
588+
$xls->spreadsheet->createSheet();
589+
}
585590
if ($xls->activeSheetSet === false) {
586591
$xls->spreadsheet->setActiveSheetIndex(0);
587592
}

src/PhpSpreadsheet/Reader/Xlsx.php

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

785785
$charts = $chartDetails = [];
786786

787+
$sheetCreated = false;
787788
if ($xmlWorkbookNS->sheets) {
788789
foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) {
789790
$eleSheetAttr = self::getAttributes($eleSheet);
@@ -810,6 +811,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
810811

811812
// Load sheet
812813
$docSheet = $excel->createSheet();
814+
$sheetCreated = true;
813815
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet
814816
// references in formula cells... during the load, all formulae should be correct,
815817
// and we're simply bringing the worksheet name in line with the formula, not the
@@ -1897,6 +1899,9 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
18971899
}
18981900
}
18991901
}
1902+
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
1903+
$excel->createSheet();
1904+
}
19001905

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

src/PhpSpreadsheet/Reader/Xml.php

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

310+
$sheetCreated = false;
310311
/** @var null|SimpleXMLElement $worksheetx */
311312
foreach ($xml_ss->Worksheet as $worksheetx) {
312313
$worksheet = $worksheetx ?? new SimpleXMLElement('<xml></xml>');
@@ -321,6 +322,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo
321322

322323
// Create new Worksheet
323324
$spreadsheet->createSheet();
325+
$sheetCreated = true;
324326
$spreadsheet->setActiveSheetIndex($worksheetID);
325327
$worksheetName = '';
326328
if (isset($worksheet_ss['Name'])) {
@@ -668,6 +670,9 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo
668670
}
669671
++$worksheetID;
670672
}
673+
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
674+
$spreadsheet->createSheet();
675+
}
671676

672677
// Globally scoped defined names
673678
$activeSheetIndex = 0;

tests/PhpSpreadsheetTests/Functional/ReadBlankCellsTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ public function testLoadDontReadEmptyCells(): void
4848
$reloadedSpreadsheet->disconnectWorksheets();
4949
}
5050

51+
/**
52+
* Test load file ignoring empty cells.
53+
*/
54+
public function testLoadDontReadEmptyCellsFlag(): void
55+
{
56+
$filename = 'tests/data/Reader/XLSX/blankcell.xlsx';
57+
$reader = new Xlsx();
58+
$reloadedSpreadsheet = $reader->load($filename, Xlsx::IGNORE_EMPTY_CELLS);
59+
self::assertFalse($reloadedSpreadsheet->getActiveSheet()->getCellCollection()->has('B2'));
60+
self::assertFalse($reloadedSpreadsheet->getActiveSheet()->getCellCollection()->has('C2'));
61+
self::assertTrue($reloadedSpreadsheet->getActiveSheet()->getCellCollection()->has('C3'));
62+
$reloadedSpreadsheet->disconnectWorksheets();
63+
}
64+
5165
/**
5266
* Test generate file with some empty cells.
5367
*/
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)