Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com)
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.

## 2025-09-03 - 2.4.1

### Added

- Option for Readers to create a new blank sheet if none match LoadSheetsOnly list. [PR #4622](https://github.com/PHPOffice/PhpSpreadsheet/pull/4622) Backport of [PR #4618](https://github.com/PHPOffice/PhpSpreadsheet/pull/4618).

### Fixed

- Compatibility changes for Php 8.5. [Issue #4600](https://github.com/PHPOffice/PhpSpreadsheet/issues/4600) [PR #4614](https://github.com/PHPOffice/PhpSpreadsheet/pull/4614) [PR #4594](https://github.com/PHPOffice/PhpSpreadsheet/pull/4594) [PR #4588](https://github.com/PHPOffice/PhpSpreadsheet/pull/4588)

## 2025-08-10 - 2.4.0

### Breaking Changes
Expand Down
20 changes: 20 additions & 0 deletions src/PhpSpreadsheet/Reader/BaseReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ abstract class BaseReader implements IReader
*/
protected bool $allowExternalImages = false;

/**
* Create a blank sheet if none are read,
* possibly due to a typo when using LoadSheetsOnly.
*/
protected bool $createBlankSheetIfNoneRead = false;

/**
* IReadFilter instance.
*/
Expand Down Expand Up @@ -168,6 +174,17 @@ public function getAllowExternalImages(): bool
return $this->allowExternalImages;
}

/**
* Create a blank sheet if none are read,
* possibly due to a typo when using LoadSheetsOnly.
*/
public function setCreateBlankSheetIfNoneRead(bool $createBlankSheetIfNoneRead): self
{
$this->createBlankSheetIfNoneRead = $createBlankSheetIfNoneRead;

return $this;
}

public function getSecurityScanner(): ?XmlScanner
{
return $this->securityScanner;
Expand Down Expand Up @@ -202,6 +219,9 @@ protected function processFlags(int $flags): void
if (((bool) ($flags & self::DONT_ALLOW_EXTERNAL_IMAGES)) === true) {
$this->setAllowExternalImages(false);
}
if (((bool) ($flags & self::CREATE_BLANK_SHEET_IF_NONE_READ)) === true) {
$this->setCreateBlankSheetIfNoneRead(true);
}
}

protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
Expand Down
5 changes: 5 additions & 0 deletions src/PhpSpreadsheet/Reader/Gnumeric.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
(new Properties($this->spreadsheet))->readProperties($xml, $gnmXML);

$worksheetID = 0;
$sheetCreated = false;
foreach ($gnmXML->Sheets->Sheet as $sheetOrNull) {
$sheet = self::testSimpleXml($sheetOrNull);
$worksheetName = (string) $sheet->Name;
Expand All @@ -264,6 +265,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp

// Create new Worksheet
$this->spreadsheet->createSheet();
$sheetCreated = true;
$this->spreadsheet->setActiveSheetIndex($worksheetID);
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
Expand Down Expand Up @@ -315,6 +317,9 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
$this->setSelectedCells($sheet);
++$worksheetID;
}
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
$this->spreadsheet->createSheet();
}

$this->processDefinedNames($gnmXML);

Expand Down
9 changes: 9 additions & 0 deletions src/PhpSpreadsheet/Reader/IReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ interface IReader
public const ALLOW_EXTERNAL_IMAGES = 16;
public const DONT_ALLOW_EXTERNAL_IMAGES = 32;

public const CREATE_BLANK_SHEET_IF_NONE_READ = 64;

public function __construct();

/**
Expand Down Expand Up @@ -129,6 +131,12 @@ public function setAllowExternalImages(bool $allowExternalImages): self;

public function getAllowExternalImages(): bool;

/**
* Create a blank sheet if none are read,
* possibly due to a typo when using LoadSheetsOnly.
*/
public function setCreateBlankSheetIfNoneRead(bool $createBlankSheetIfNoneRead): self;

/**
* Loads PhpSpreadsheet from file.
*
Expand All @@ -141,6 +149,7 @@ public function getAllowExternalImages(): bool;
* self::IGNORE_ROWS_WITH_NO_CELLS Don't load any rows that contain no cells.
* self::ALLOW_EXTERNAL_IMAGES Attempt to fetch images stored outside the spreadsheet.
* self::DONT_ALLOW_EXTERNAL_IMAGES Don't attempt to fetch images stored outside the spreadsheet.
* self::CREATE_BLANK_SHEET_IF_NONE_READ If no sheets are read, create a blank one.
*/
public function load(string $filename, int $flags = 0): Spreadsheet;
}
5 changes: 5 additions & 0 deletions src/PhpSpreadsheet/Reader/Ods.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
$tables = $workbookData->getElementsByTagNameNS($tableNs, 'table');

$worksheetID = 0;
$sheetCreated = false;
foreach ($tables as $worksheetDataSet) {
/** @var DOMElement $worksheetDataSet */
$worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name');
Expand All @@ -337,6 +338,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp

// Create sheet
$spreadsheet->createSheet();
$sheetCreated = true;
$spreadsheet->setActiveSheetIndex($worksheetID);

if ($worksheetName || is_numeric($worksheetName)) {
Expand Down Expand Up @@ -658,6 +660,9 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
$pageSettings->setPrintSettingsForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName);
++$worksheetID;
}
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
$spreadsheet->createSheet();
}

$autoFilterReader->read($workbookData);
$definedNameReader->read($workbookData);
Expand Down
5 changes: 5 additions & 0 deletions src/PhpSpreadsheet/Reader/Xls.php
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet

// Parse the individual sheets
$this->activeSheetSet = false;
$sheetCreated = false;
foreach ($this->sheets as $sheet) {
$selectedCells = '';
if ($sheet['sheetType'] != 0x00) {
Expand All @@ -714,6 +715,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet

// add sheet to PhpSpreadsheet object
$this->phpSheet = $this->spreadsheet->createSheet();
$sheetCreated = true;
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
// name in line with the formula, not the reverse
Expand Down Expand Up @@ -1115,6 +1117,9 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
$this->phpSheet->setSelectedCells($selectedCells);
}
}
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
$this->spreadsheet->createSheet();
}
if ($this->activeSheetSet === false) {
$this->spreadsheet->setActiveSheetIndex(0);
}
Expand Down
5 changes: 5 additions & 0 deletions src/PhpSpreadsheet/Reader/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet

$charts = $chartDetails = [];

$sheetCreated = false;
if ($xmlWorkbookNS->sheets) {
/** @var SimpleXMLElement $eleSheet */
foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) {
Expand Down Expand Up @@ -777,6 +778,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet

// Load sheet
$docSheet = $excel->createSheet();
$sheetCreated = true;
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet
// references in formula cells... during the load, all formulae should be correct,
// and we're simply bringing the worksheet name in line with the formula, not the
Expand Down Expand Up @@ -1821,6 +1823,9 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
}
}
}
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
$excel->createSheet();
}

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

Expand Down
5 changes: 5 additions & 0 deletions src/PhpSpreadsheet/Reader/Xml.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo
$worksheetID = 0;
$xml_ss = $xml->children(self::NAMESPACES_SS);

$sheetCreated = false;
/** @var null|SimpleXMLElement $worksheetx */
foreach ($xml_ss->Worksheet as $worksheetx) {
$worksheet = $worksheetx ?? new SimpleXMLElement('<xml></xml>');
Expand All @@ -313,6 +314,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo

// Create new Worksheet
$spreadsheet->createSheet();
$sheetCreated = true;
$spreadsheet->setActiveSheetIndex($worksheetID);
$worksheetName = '';
if (isset($worksheet_ss['Name'])) {
Expand Down Expand Up @@ -658,6 +660,9 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo
}
++$worksheetID;
}
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
$spreadsheet->createSheet();
}

// Globally scoped defined names
$activeSheetIndex = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests\Reader;

use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

class CreateBlankSheetIfNoneReadTest extends TestCase
{
#[DataProvider('providerIdentify')]
public function testExceptionIfNoSheet(string $file, string $expectedName, string $expectedClass): void
{
$this->expectException(SpreadsheetException::class);
$this->expectExceptionMessage('out of bounds index: 0');
$actual = IOFactory::identify($file);
self::assertSame($expectedName, $actual);
$reader = IOFactory::createReaderForFile($file);
self::assertSame($expectedClass, $reader::class);
$sheetlist = ['Unknown sheetname'];
$reader->setLoadSheetsOnly($sheetlist);
$reader->load($file);
}

#[DataProvider('providerIdentify')]
public function testCreateSheetIfNoSheet(string $file, string $expectedName, string $expectedClass): void
{
$actual = IOFactory::identify($file);
self::assertSame($expectedName, $actual);
$reader = IOFactory::createReaderForFile($file);
self::assertSame($expectedClass, $reader::class);
$reader->setCreateBlankSheetIfNoneRead(true);
$sheetlist = ['Unknown sheetname'];
$reader->setLoadSheetsOnly($sheetlist);
$spreadsheet = $reader->load($file);
$sheet = $spreadsheet->getActiveSheet();
self::assertSame('Worksheet', $sheet->getTitle());
self::assertCount(1, $spreadsheet->getAllSheets());
$spreadsheet->disconnectWorksheets();
}

public static function providerIdentify(): array
{
return [
['samples/templates/26template.xlsx', 'Xlsx', Reader\Xlsx::class],
['samples/templates/GnumericTest.gnumeric', 'Gnumeric', Reader\Gnumeric::class],
['samples/templates/30template.xls', 'Xls', Reader\Xls::class],
['samples/templates/OOCalcTest.ods', 'Ods', Reader\Ods::class],
['samples/templates/excel2003.xml', 'Xml', Reader\Xml::class],
];
}

public function testUsingFlage(): void
{
$file = 'samples/templates/26template.xlsx';
$reader = IOFactory::createReaderForFile($file);
$sheetlist = ['Unknown sheetname'];
$reader->setLoadSheetsOnly($sheetlist);
$spreadsheet = $reader->load($file, Reader\BaseReader::CREATE_BLANK_SHEET_IF_NONE_READ);
$sheet = $spreadsheet->getActiveSheet();
self::assertSame('Worksheet', $sheet->getTitle());
self::assertCount(1, $spreadsheet->getAllSheets());
$spreadsheet->disconnectWorksheets();
}
}