mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-09-15 12:36:32 +00:00
Option to Create Blank Sheet If LoadSheetsOnly Doesn't Find Any
In PhpSpreadsheet Release 1, if the LoadSheetsOnly option was specified, and no sheets matched, a new blank sheet was created. This behavior changed in PhpSpreadsheet Release 2, so that an exception wound up being thrown instead. Although the Release 2 approach seems more sensible to me, it was actually collateral damage from a different change, and was not an intentional result. The difference in behavior is causing a problem for Laravel-Excel. In particular, a PR which would move their supported PhpSpreadsheet release from 1 to 5, is delayed because this change in behavior breaks part of their test suite. See https://github.com/SpartnerNL/Laravel-Excel/pull/4302. We would very much like them to get off release 1. I volunteered to add a compatibility option to the Readers which would emulate the release 1 behavior. The result is this PR. Usage: ```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $reader->setLoadSheetsOnly([list of sheet names]); if (method_exists($reader, 'setCreateBlankSheetIfNoneRead')) { $reader->setCreateBlankSheetIfNoneRead(true); } ``` In addition to Xlsx Reader, the method is available for Xls, Ods, Xml, and Gnumeric.
This commit is contained in:
@@ -54,6 +54,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.
|
||||
*/
|
||||
@@ -173,6 +179,17 @@ abstract class BaseReader implements IReader
|
||||
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;
|
||||
@@ -207,6 +224,9 @@ abstract class BaseReader implements IReader
|
||||
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
|
||||
|
||||
@@ -269,6 +269,7 @@ class Gnumeric extends BaseReader
|
||||
(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;
|
||||
@@ -280,6 +281,7 @@ class Gnumeric extends BaseReader
|
||||
|
||||
// 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
|
||||
@@ -329,6 +331,9 @@ class Gnumeric extends BaseReader
|
||||
$this->setSelectedCells($sheet);
|
||||
++$worksheetID;
|
||||
}
|
||||
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
|
||||
$this->spreadsheet->createSheet();
|
||||
}
|
||||
|
||||
$this->processDefinedNames($gnmXML);
|
||||
|
||||
|
||||
@@ -41,6 +41,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();
|
||||
|
||||
/**
|
||||
@@ -149,6 +151,12 @@ interface IReader
|
||||
|
||||
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.
|
||||
*
|
||||
@@ -161,6 +169,7 @@ interface IReader
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -318,6 +318,7 @@ class Ods extends BaseReader
|
||||
$tables = $workbookData->getElementsByTagNameNS($tableNs, 'table');
|
||||
|
||||
$worksheetID = 0;
|
||||
$sheetCreated = false;
|
||||
foreach ($tables as $worksheetDataSet) {
|
||||
/** @var DOMElement $worksheetDataSet */
|
||||
$worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name');
|
||||
@@ -335,6 +336,7 @@ class Ods extends BaseReader
|
||||
|
||||
// Create sheet
|
||||
$spreadsheet->createSheet();
|
||||
$sheetCreated = true;
|
||||
$spreadsheet->setActiveSheetIndex($worksheetID);
|
||||
|
||||
if ($worksheetName || is_numeric($worksheetName)) {
|
||||
@@ -441,6 +443,9 @@ class Ods extends BaseReader
|
||||
);
|
||||
++$worksheetID;
|
||||
}
|
||||
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
|
||||
$spreadsheet->createSheet();
|
||||
}
|
||||
|
||||
$autoFilterReader->read($workbookData);
|
||||
$definedNameReader->read($workbookData);
|
||||
|
||||
@@ -153,6 +153,7 @@ class LoadSpreadsheet extends Xls
|
||||
|
||||
// Parse the individual sheets
|
||||
$xls->activeSheetSet = false;
|
||||
$sheetCreated = false;
|
||||
foreach ($xls->sheets as $sheet) {
|
||||
$selectedCells = '';
|
||||
if ($sheet['sheetType'] != 0x00) {
|
||||
@@ -167,6 +168,7 @@ class LoadSpreadsheet extends Xls
|
||||
|
||||
// add sheet to PhpSpreadsheet object
|
||||
$xls->phpSheet = $xls->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
|
||||
@@ -582,6 +584,9 @@ class LoadSpreadsheet extends Xls
|
||||
$xls->phpSheet->setSelectedCells($selectedCells);
|
||||
}
|
||||
}
|
||||
if ($xls->createBlankSheetIfNoneRead && !$sheetCreated) {
|
||||
$xls->spreadsheet->createSheet();
|
||||
}
|
||||
if ($xls->activeSheetSet === false) {
|
||||
$xls->spreadsheet->setActiveSheetIndex(0);
|
||||
}
|
||||
|
||||
@@ -784,6 +784,7 @@ class Xlsx extends BaseReader
|
||||
|
||||
$charts = $chartDetails = [];
|
||||
|
||||
$sheetCreated = false;
|
||||
if ($xmlWorkbookNS->sheets) {
|
||||
foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) {
|
||||
$eleSheetAttr = self::getAttributes($eleSheet);
|
||||
@@ -810,6 +811,7 @@ class Xlsx extends BaseReader
|
||||
|
||||
// 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
|
||||
@@ -1897,6 +1899,9 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
|
||||
$excel->createSheet();
|
||||
}
|
||||
|
||||
(new WorkbookView($excel))->viewSettings($xmlWorkbook, $mainNS, $mapSheetId, $this->readDataOnly);
|
||||
|
||||
|
||||
@@ -307,6 +307,7 @@ class Xml extends BaseReader
|
||||
$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>');
|
||||
@@ -321,6 +322,7 @@ class Xml extends BaseReader
|
||||
|
||||
// Create new Worksheet
|
||||
$spreadsheet->createSheet();
|
||||
$sheetCreated = true;
|
||||
$spreadsheet->setActiveSheetIndex($worksheetID);
|
||||
$worksheetName = '';
|
||||
if (isset($worksheet_ss['Name'])) {
|
||||
@@ -668,6 +670,9 @@ class Xml extends BaseReader
|
||||
}
|
||||
++$worksheetID;
|
||||
}
|
||||
if ($this->createBlankSheetIfNoneRead && !$sheetCreated) {
|
||||
$spreadsheet->createSheet();
|
||||
}
|
||||
|
||||
// Globally scoped defined names
|
||||
$activeSheetIndex = 0;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user