From 0edd5eff428dcbeed01f172fe3ea7682dd327ae5 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sun, 9 May 2021 23:06:44 +0200 Subject: [PATCH 01/11] First pass code to read pivot tables for Xlsx --- src/PhpSpreadsheet/PivotTable.php | 143 ++++++++++++++++++ .../PivotTable/PivotCacheDefinition.php | 102 +++++++++++++ .../PivotCacheRecords.php | 81 ++++++++++ src/PhpSpreadsheet/Reader/BaseReader.php | 20 +++ src/PhpSpreadsheet/Reader/IReader.php | 22 +++ src/PhpSpreadsheet/Reader/Xlsx.php | 8 + .../Reader/Xlsx/PivotTables.php | 100 ++++++++++++ src/PhpSpreadsheet/Worksheet/Worksheet.php | 31 ++++ 8 files changed, 507 insertions(+) create mode 100644 src/PhpSpreadsheet/PivotTable.php create mode 100644 src/PhpSpreadsheet/PivotTable/PivotCacheDefinition.php create mode 100644 src/PhpSpreadsheet/PivotTable/PivotCacheDefinition/PivotCacheRecords.php create mode 100644 src/PhpSpreadsheet/Reader/Xlsx/PivotTables.php diff --git a/src/PhpSpreadsheet/PivotTable.php b/src/PhpSpreadsheet/PivotTable.php new file mode 100644 index 0000000000..f9d47128cf --- /dev/null +++ b/src/PhpSpreadsheet/PivotTable.php @@ -0,0 +1,143 @@ +setId($id); + $this->setTarget($target); + // patch to correct dxfId="XX" + $this->data = preg_replace('/ dxfId=".."/i', '', $xmlData); + } + + public function setId($id): void + { + $this->id = preg_replace('/^rId/i', '', $id); + } + + public function getId() + { + return $this->id; + } + + public function getName() + { + return $this->name; + } + + public function getTarget() + { + return $this->target; + } + + public function setTarget($target): void + { + $this->target = $target; + $this->name = basename($target); + } + + public function addPivotCacheDefinition(PivotCacheDefinition $PivotCacheDefinition): void + { + $this->pivotCacheDefinitionCollection[] = $PivotCacheDefinition; + } + + public function getXmlData() + { + return $this->data; + } + + public function setWorksheet(?Worksheet $pValue = null) + { + $this->worksheet = $pValue; + + return $this; + } + + /** + * @return PivotCacheDefinition[] + */ + public function getPivotCacheDefinitionCollection(): array + { + return $this->pivotCacheDefinitionCollection; + } + + public static function normalizePath($path): string + { + $parts = []; // Array to build a new path from the good parts + $path = str_replace('\\', '/', $path); // Replace backslashes with forwardslashes + $path = preg_replace('/\/+/', '/', $path); // Combine multiple slashes into a single slash + $segments = explode('/', $path); // Collect path segments + $test = ''; // Initialize testing variable + + foreach ($segments as $segment) { + if ($segment != '.') { + $test = array_pop($parts); + if (null === $test) { + $parts[] = $segment; + } elseif ($segment == '..') { + if ($test == '..') { + $parts[] = $test; + } + if ($test == '..' || $test == '') { + $parts[] = $segment; + } + } else { + $parts[] = $test; + $parts[] = $segment; + } + } + } + + return implode('/', $parts); + } +} diff --git a/src/PhpSpreadsheet/PivotTable/PivotCacheDefinition.php b/src/PhpSpreadsheet/PivotTable/PivotCacheDefinition.php new file mode 100644 index 0000000000..e3806804a2 --- /dev/null +++ b/src/PhpSpreadsheet/PivotTable/PivotCacheDefinition.php @@ -0,0 +1,102 @@ +setId($id); + $this->setTarget($target); + $this->data = $xmlData; + } + + public function getName() + { + return $this->name; + } + + public function getTarget() + { + return $this->target; + } + + public function getId() + { + return $this->id; + } + + public function setId($id): void + { + $this->id = preg_replace('/^rId/i', '', $id); + } + + public function setTarget($target): void + { + $this->target = $target; + $this->name = basename($target); + } + + public function getXmlData() + { + return $this->data; + } + + public function addPivotCacheRecords(PivotCacheRecords $PivotCacheRecords): void + { + $this->pivotCacheRecordsCollection[] = $PivotCacheRecords; + } + + /** + * @return PivotCacheRecords[] + */ + public function getPivotCacheRecordsCollection(): array + { + return $this->pivotCacheRecordsCollection; + } +} diff --git a/src/PhpSpreadsheet/PivotTable/PivotCacheDefinition/PivotCacheRecords.php b/src/PhpSpreadsheet/PivotTable/PivotCacheDefinition/PivotCacheRecords.php new file mode 100644 index 0000000000..2ad13de3f4 --- /dev/null +++ b/src/PhpSpreadsheet/PivotTable/PivotCacheDefinition/PivotCacheRecords.php @@ -0,0 +1,81 @@ +setId($id); + $this->setTarget($target); + $this->data = $xmlData; + } + + public function getName() + { + return $this->name; + } + + public function getTarget() + { + return $this->target; + } + + public function setTarget($target): void + { + $this->target = $target; + $this->name = basename($target); + } + + public function getId() + { + return $this->id; + } + + public function setId($id): void + { + $this->id = preg_replace('/^rId/i', '', $id); + } + + public function getXmlData() + { + return $this->data; + } +} diff --git a/src/PhpSpreadsheet/Reader/BaseReader.php b/src/PhpSpreadsheet/Reader/BaseReader.php index 8034813239..bb3cb9b976 100644 --- a/src/PhpSpreadsheet/Reader/BaseReader.php +++ b/src/PhpSpreadsheet/Reader/BaseReader.php @@ -34,6 +34,14 @@ abstract class BaseReader implements IReader */ protected $includeCharts = false; + /** + * Read Pivottable that are defined in the workbook? + * Identifies whether the Reader should read the definitions for any Pivottable that exist in the workbook;. + * + * @var bool + */ + protected $includePivotTables = false; + /** * Restrict which sheets should be loaded? * This property holds an array of worksheet names to be loaded. If null, then all worksheets will be loaded. @@ -97,6 +105,18 @@ public function setIncludeCharts($pValue) return $this; } + public function getIncludePivotTables() + { + return $this->includePivotTables; + } + + public function setIncludePivotTables($pValue = false) + { + $this->includePivotTables = (bool) $pValue; + + return $this; + } + public function getLoadSheetsOnly() { return $this->loadSheetsOnly; diff --git a/src/PhpSpreadsheet/Reader/IReader.php b/src/PhpSpreadsheet/Reader/IReader.php index a8bd360659..1d67c32270 100644 --- a/src/PhpSpreadsheet/Reader/IReader.php +++ b/src/PhpSpreadsheet/Reader/IReader.php @@ -80,6 +80,28 @@ public function getIncludeCharts(); */ public function setIncludeCharts($pValue); + /** + * Read Pivot Tables in workbook? + * If this is true, then the Reader will include any Pivot Tables that exist in the workbook. + * Note that a ReadDataOnly value of false overrides, and Pivot Tables won't be read regardless of the IncludePivotTables value. + * If false (the default) it will ignore any Pivot Tables defined in the workbook file. + * + * @return bool + */ + public function getIncludePivotTables(); + + /** + * Set read Pivot Tables in workbook + * Set to true, to advise the Reader to include any Pivot Tables that exist in the workbook. + * Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludePivotTables value. + * Set to false (the default) to discard Pivot Tables. + * + * @param bool $pValue + * + * @return IReader + */ + public function setIncludePivotTables($pValue); + /** * Get which sheets to load * Returns either an array of worksheet names (the list of worksheets that should be loaded), or a null diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index f07ac008fa..f923af404d 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -14,6 +14,7 @@ use PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PivotTables; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews; @@ -802,6 +803,13 @@ public function load($pFilename) (new DataValidations($docSheet, $xmlSheet))->load(); } + if (!$this->readDataOnly && $this->includePivotTables) { + if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { + $relsWorksheet = simplexml_load_string($this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')); //~ http://schemas.openxmlformats.org/package/2006/relationships"); + (new PivotTables($docSheet, $zip))->load($relsWorksheet, $dir, $fileWorksheet); + } + } + // unparsed sheet AlternateContent if ($xmlSheet && !$this->readDataOnly) { $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006'); diff --git a/src/PhpSpreadsheet/Reader/Xlsx/PivotTables.php b/src/PhpSpreadsheet/Reader/Xlsx/PivotTables.php new file mode 100644 index 0000000000..f3dc310202 --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xlsx/PivotTables.php @@ -0,0 +1,100 @@ +worksheet = $worksheet; + $this->archive = $archive; + } + + public function load(SimpleXMLElement $relsWorksheet, string $dir, string $fileWorksheet): void + { + foreach ($relsWorksheet->Relationship as $ele) { + if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable') { + $objPivotTable = new PivotTable( + (string) $ele['Id'], + (string) $ele['Target'], + $this->getFromZipArchive(dirname("$dir/$fileWorksheet") . '/' . (string) $ele['Target']) + ); + + $relsPivotTablePath = $dir . '/pivotTables/_rels/' . $objPivotTable->getName() . '.rels'; + if ($this->archive->locateName($relsPivotTablePath)) { + $relsPivotTable = simplexml_load_string($this->getFromZipArchive($relsPivotTablePath)); + foreach ($relsPivotTable->Relationship as $elePT) { + if ($elePT['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition') { + $objPivotCacheDefinition = new PivotCacheDefinition( + (string) $elePT['Id'], + (string) $elePT['Target'], + $this->getFromZipArchive(dirname("$dir/$fileWorksheet") . '/' . (string) $elePT['Target']) + ); + + $relsPivotCachePath = $dir . '/pivotCache/_rels/' . $objPivotCacheDefinition->getName() . '.rels'; + if ($this->archive->locateName($relsPivotCachePath)) { + $relsPivotCache = simplexml_load_string($this->getFromZipArchive($relsPivotCachePath)); + foreach ($relsPivotCache->Relationship as $elePC) { + if ($elePC['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords') { + $objPivotCacheRecords = new PivotCacheRecords( + (string) $elePC['Id'], + (string) $elePC['Target'], + $this->getFromZipArchive(dirname("$dir/$fileWorksheet") . '/' . dirname((string) $elePT['Target']) . '/' . (string) $elePC['Target']) + ); + } + $objPivotCacheDefinition->addPivotCacheRecords($objPivotCacheRecords); + } + } + } + $objPivotTable->addPivotCacheDefinition($objPivotCacheDefinition); + } + } + + $this->worksheet->addPivotTable($objPivotTable); + } + } + } + + /** + * @param string $fileName + * + * @return string + */ + private function getFromZipArchive($fileName = '') + { + // Root-relative paths + if (strpos($fileName, '//') !== false) { + $fileName = substr($fileName, strpos($fileName, '//') + 1); + } + $fileName = File::realpath($fileName); + + // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming + // so we need to load case-insensitively from the zip file + + // Apache POI fixes + $contents = $this->archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE); + if ($contents === false) { + $contents = $this->archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE); + } + + return $contents; + } +} diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 1dbf2a1a12..106ceaacec 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -16,6 +16,7 @@ use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\IComparable; +use PhpOffice\PhpSpreadsheet\PivotTable; use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared; @@ -107,6 +108,13 @@ class Worksheet implements IComparable */ private $chartCollection; + /** + * Collection of PivotTable objects. + * + * @var PivotTable[] + */ + private $pivotTableCollection = []; + /** * Worksheet title. * @@ -2849,6 +2857,29 @@ public function getDataValidationCollection() return $this->dataValidationCollection; } + /** + * Get collection of PivotTables. + * + * @return PivotTable[] + */ + public function getPivotTableCollection() + { + return $this->pivotTableCollection; + } + + public function addPivotTable(?PivotTable $pPivotTable = null, ?int $iPivotTableIndex = null): PivotTable + { + $pPivotTable->setWorksheet($this); + if ($iPivotTableIndex === null) { + $this->pivotTableCollection[] = $pPivotTable; + } else { + // Insert the pivot table at the requested index + array_splice($this->pivotTableCollection, $iPivotTableIndex, 0, [$pPivotTable]); + } + + return $pPivotTable; + } + /** * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet. * From dd6c9f489a18273300049734858bfc8bcdff4678 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Tue, 1 Jun 2021 17:08:00 +0200 Subject: [PATCH 02/11] Use of passing flags with Readers to identify whether speacial features such as loading charts should be enabled; no need to instantiate a reader and manually enable it before loading any more. This is in preparation for supporting new "boolean" Reaer/Writer features, such as pivot tables --- src/PhpSpreadsheet/IOFactory.php | 8 +- src/PhpSpreadsheet/PivotTable.php | 143 ------------------ .../PivotTable/PivotCacheDefinition.php | 102 ------------- .../PivotCacheRecords.php | 81 ---------- src/PhpSpreadsheet/Reader/BaseReader.php | 27 +--- src/PhpSpreadsheet/Reader/Csv.php | 4 +- src/PhpSpreadsheet/Reader/Gnumeric.php | 4 +- src/PhpSpreadsheet/Reader/Html.php | 4 +- src/PhpSpreadsheet/Reader/IReader.php | 28 +--- src/PhpSpreadsheet/Reader/Ods.php | 4 +- src/PhpSpreadsheet/Reader/Slk.php | 4 +- src/PhpSpreadsheet/Reader/Xls.php | 4 +- src/PhpSpreadsheet/Reader/Xlsx.php | 15 +- .../Reader/Xlsx/PivotTables.php | 100 ------------ src/PhpSpreadsheet/Reader/Xml.php | 6 +- src/PhpSpreadsheet/Worksheet/Worksheet.php | 31 ---- 16 files changed, 39 insertions(+), 526 deletions(-) delete mode 100644 src/PhpSpreadsheet/PivotTable.php delete mode 100644 src/PhpSpreadsheet/PivotTable/PivotCacheDefinition.php delete mode 100644 src/PhpSpreadsheet/PivotTable/PivotCacheDefinition/PivotCacheRecords.php delete mode 100644 src/PhpSpreadsheet/Reader/Xlsx/PivotTables.php diff --git a/src/PhpSpreadsheet/IOFactory.php b/src/PhpSpreadsheet/IOFactory.php index 06006edc2e..6ed1fd82f2 100644 --- a/src/PhpSpreadsheet/IOFactory.php +++ b/src/PhpSpreadsheet/IOFactory.php @@ -75,15 +75,15 @@ public static function createReader($readerType) /** * Loads Spreadsheet from file using automatic Reader\IReader resolution. * - * @param string $pFilename The name of the spreadsheet file + * @param string $filename The name of the spreadsheet file * * @return Spreadsheet */ - public static function load($pFilename) + public static function load(string $filename, int $flags = 0) { - $reader = self::createReaderForFile($pFilename); + $reader = self::createReaderForFile($filename); - return $reader->load($pFilename); + return $reader->load($filename, $flags); } /** diff --git a/src/PhpSpreadsheet/PivotTable.php b/src/PhpSpreadsheet/PivotTable.php deleted file mode 100644 index f9d47128cf..0000000000 --- a/src/PhpSpreadsheet/PivotTable.php +++ /dev/null @@ -1,143 +0,0 @@ -setId($id); - $this->setTarget($target); - // patch to correct dxfId="XX" - $this->data = preg_replace('/ dxfId=".."/i', '', $xmlData); - } - - public function setId($id): void - { - $this->id = preg_replace('/^rId/i', '', $id); - } - - public function getId() - { - return $this->id; - } - - public function getName() - { - return $this->name; - } - - public function getTarget() - { - return $this->target; - } - - public function setTarget($target): void - { - $this->target = $target; - $this->name = basename($target); - } - - public function addPivotCacheDefinition(PivotCacheDefinition $PivotCacheDefinition): void - { - $this->pivotCacheDefinitionCollection[] = $PivotCacheDefinition; - } - - public function getXmlData() - { - return $this->data; - } - - public function setWorksheet(?Worksheet $pValue = null) - { - $this->worksheet = $pValue; - - return $this; - } - - /** - * @return PivotCacheDefinition[] - */ - public function getPivotCacheDefinitionCollection(): array - { - return $this->pivotCacheDefinitionCollection; - } - - public static function normalizePath($path): string - { - $parts = []; // Array to build a new path from the good parts - $path = str_replace('\\', '/', $path); // Replace backslashes with forwardslashes - $path = preg_replace('/\/+/', '/', $path); // Combine multiple slashes into a single slash - $segments = explode('/', $path); // Collect path segments - $test = ''; // Initialize testing variable - - foreach ($segments as $segment) { - if ($segment != '.') { - $test = array_pop($parts); - if (null === $test) { - $parts[] = $segment; - } elseif ($segment == '..') { - if ($test == '..') { - $parts[] = $test; - } - if ($test == '..' || $test == '') { - $parts[] = $segment; - } - } else { - $parts[] = $test; - $parts[] = $segment; - } - } - } - - return implode('/', $parts); - } -} diff --git a/src/PhpSpreadsheet/PivotTable/PivotCacheDefinition.php b/src/PhpSpreadsheet/PivotTable/PivotCacheDefinition.php deleted file mode 100644 index e3806804a2..0000000000 --- a/src/PhpSpreadsheet/PivotTable/PivotCacheDefinition.php +++ /dev/null @@ -1,102 +0,0 @@ -setId($id); - $this->setTarget($target); - $this->data = $xmlData; - } - - public function getName() - { - return $this->name; - } - - public function getTarget() - { - return $this->target; - } - - public function getId() - { - return $this->id; - } - - public function setId($id): void - { - $this->id = preg_replace('/^rId/i', '', $id); - } - - public function setTarget($target): void - { - $this->target = $target; - $this->name = basename($target); - } - - public function getXmlData() - { - return $this->data; - } - - public function addPivotCacheRecords(PivotCacheRecords $PivotCacheRecords): void - { - $this->pivotCacheRecordsCollection[] = $PivotCacheRecords; - } - - /** - * @return PivotCacheRecords[] - */ - public function getPivotCacheRecordsCollection(): array - { - return $this->pivotCacheRecordsCollection; - } -} diff --git a/src/PhpSpreadsheet/PivotTable/PivotCacheDefinition/PivotCacheRecords.php b/src/PhpSpreadsheet/PivotTable/PivotCacheDefinition/PivotCacheRecords.php deleted file mode 100644 index 2ad13de3f4..0000000000 --- a/src/PhpSpreadsheet/PivotTable/PivotCacheDefinition/PivotCacheRecords.php +++ /dev/null @@ -1,81 +0,0 @@ -setId($id); - $this->setTarget($target); - $this->data = $xmlData; - } - - public function getName() - { - return $this->name; - } - - public function getTarget() - { - return $this->target; - } - - public function setTarget($target): void - { - $this->target = $target; - $this->name = basename($target); - } - - public function getId() - { - return $this->id; - } - - public function setId($id): void - { - $this->id = preg_replace('/^rId/i', '', $id); - } - - public function getXmlData() - { - return $this->data; - } -} diff --git a/src/PhpSpreadsheet/Reader/BaseReader.php b/src/PhpSpreadsheet/Reader/BaseReader.php index bb3cb9b976..c789421c78 100644 --- a/src/PhpSpreadsheet/Reader/BaseReader.php +++ b/src/PhpSpreadsheet/Reader/BaseReader.php @@ -34,14 +34,6 @@ abstract class BaseReader implements IReader */ protected $includeCharts = false; - /** - * Read Pivottable that are defined in the workbook? - * Identifies whether the Reader should read the definitions for any Pivottable that exist in the workbook;. - * - * @var bool - */ - protected $includePivotTables = false; - /** * Restrict which sheets should be loaded? * This property holds an array of worksheet names to be loaded. If null, then all worksheets will be loaded. @@ -105,18 +97,6 @@ public function setIncludeCharts($pValue) return $this; } - public function getIncludePivotTables() - { - return $this->includePivotTables; - } - - public function setIncludePivotTables($pValue = false) - { - $this->includePivotTables = (bool) $pValue; - - return $this; - } - public function getLoadSheetsOnly() { return $this->loadSheetsOnly; @@ -157,6 +137,13 @@ public function getSecurityScanner() return $this->securityScanner; } + protected function processFlags(int $flags) + { + if (((bool) ($flags & self::LOAD_WITH_CHARTS)) === true) { + $this->setIncludeCharts(true); + } + } + /** * Open file for reading. * diff --git a/src/PhpSpreadsheet/Reader/Csv.php b/src/PhpSpreadsheet/Reader/Csv.php index b7bc0d49b4..43d7b46281 100644 --- a/src/PhpSpreadsheet/Reader/Csv.php +++ b/src/PhpSpreadsheet/Reader/Csv.php @@ -240,8 +240,10 @@ public function listWorksheetInfo(string $pFilename): array * * @return Spreadsheet */ - public function load($pFilename) + public function load(string $pFilename, int $flags = 0) { + $this->processFlags($flags); + // Create new Spreadsheet $spreadsheet = new Spreadsheet(); diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php index 85bae6f8c8..064b05198d 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric.php @@ -239,8 +239,10 @@ private static function testSimpleXml($value): SimpleXMLElement * * @return Spreadsheet */ - public function load($pFilename) + public function load(string $pFilename, int $flags = 0) { + $this->processFlags($flags); + // Create new Spreadsheet $spreadsheet = new Spreadsheet(); $spreadsheet->removeSheetByIndex(0); diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php index b7faac878c..60a1daf7f1 100644 --- a/src/PhpSpreadsheet/Reader/Html.php +++ b/src/PhpSpreadsheet/Reader/Html.php @@ -208,8 +208,10 @@ private static function containsTags($data) * * @return Spreadsheet */ - public function load($pFilename) + public function load(string $pFilename, int $flags = 0) { + $this->processFlags($flags); + // Create new Spreadsheet $spreadsheet = new Spreadsheet(); diff --git a/src/PhpSpreadsheet/Reader/IReader.php b/src/PhpSpreadsheet/Reader/IReader.php index 1d67c32270..6a57f4a059 100644 --- a/src/PhpSpreadsheet/Reader/IReader.php +++ b/src/PhpSpreadsheet/Reader/IReader.php @@ -4,6 +4,8 @@ interface IReader { + public const LOAD_WITH_CHARTS = 1; + /** * IReader constructor. */ @@ -80,28 +82,6 @@ public function getIncludeCharts(); */ public function setIncludeCharts($pValue); - /** - * Read Pivot Tables in workbook? - * If this is true, then the Reader will include any Pivot Tables that exist in the workbook. - * Note that a ReadDataOnly value of false overrides, and Pivot Tables won't be read regardless of the IncludePivotTables value. - * If false (the default) it will ignore any Pivot Tables defined in the workbook file. - * - * @return bool - */ - public function getIncludePivotTables(); - - /** - * Set read Pivot Tables in workbook - * Set to true, to advise the Reader to include any Pivot Tables that exist in the workbook. - * Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludePivotTables value. - * Set to false (the default) to discard Pivot Tables. - * - * @param bool $pValue - * - * @return IReader - */ - public function setIncludePivotTables($pValue); - /** * Get which sheets to load * Returns either an array of worksheet names (the list of worksheets that should be loaded), or a null @@ -147,9 +127,7 @@ public function setReadFilter(IReadFilter $pValue); /** * Loads PhpSpreadsheet from file. * - * @param string $pFilename - * * @return \PhpOffice\PhpSpreadsheet\Spreadsheet */ - public function load($pFilename); + public function load(string $pFilename, int $flags = 0); } diff --git a/src/PhpSpreadsheet/Reader/Ods.php b/src/PhpSpreadsheet/Reader/Ods.php index 760aa10d08..241145763c 100644 --- a/src/PhpSpreadsheet/Reader/Ods.php +++ b/src/PhpSpreadsheet/Reader/Ods.php @@ -235,8 +235,10 @@ public function listWorksheetInfo($pFilename) * * @return Spreadsheet */ - public function load($pFilename) + public function load(string $pFilename, int $flags = 0) { + $this->processFlags($flags); + // Create new Spreadsheet $spreadsheet = new Spreadsheet(); diff --git a/src/PhpSpreadsheet/Reader/Slk.php b/src/PhpSpreadsheet/Reader/Slk.php index c7b6fc82d0..4b4de788ad 100644 --- a/src/PhpSpreadsheet/Reader/Slk.php +++ b/src/PhpSpreadsheet/Reader/Slk.php @@ -201,8 +201,10 @@ public function listWorksheetInfo($pFilename) * * @return Spreadsheet */ - public function load($pFilename) + public function load(string $pFilename, int $flags = 0) { + $this->processFlags($flags); + // Create new Spreadsheet $spreadsheet = new Spreadsheet(); diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index bfdd558304..3886035598 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -626,8 +626,10 @@ public function listWorksheetInfo($pFilename) * * @return Spreadsheet */ - public function load($pFilename) + public function load(string $pFilename, int $flags = 0) { + $this->processFlags($flags); + // Read the OLE file $this->loadOLE($pFilename); diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 2bbcb766c8..2f48dbceee 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -14,7 +14,6 @@ use PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup; -use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PivotTables; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews; @@ -311,13 +310,12 @@ private function getFromZipArchive(ZipArchive $archive, $fileName = '') /** * Loads Spreadsheet from file. * - * @param string $pFilename - * * @return Spreadsheet */ - public function load($pFilename) + public function load(string $pFilename, int $flags = 0) { File::assertFile($pFilename); + $this->processFlags($flags); // Initialisations $excel = new Spreadsheet(); @@ -770,7 +768,7 @@ public function load($pFilename) } } - $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells']; + $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'selectUnlockedCells']; if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) { foreach ($aKeys as $key) { $method = 'set' . ucfirst($key); @@ -803,13 +801,6 @@ public function load($pFilename) (new DataValidations($docSheet, $xmlSheet))->load(); } - if (!$this->readDataOnly && $this->includePivotTables) { - if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { - $relsWorksheet = simplexml_load_string($this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')); //~ http://schemas.openxmlformats.org/package/2006/relationships"); - (new PivotTables($docSheet, $zip))->load($relsWorksheet, $dir, $fileWorksheet); - } - } - // unparsed sheet AlternateContent if ($xmlSheet && !$this->readDataOnly) { $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006'); diff --git a/src/PhpSpreadsheet/Reader/Xlsx/PivotTables.php b/src/PhpSpreadsheet/Reader/Xlsx/PivotTables.php deleted file mode 100644 index f3dc310202..0000000000 --- a/src/PhpSpreadsheet/Reader/Xlsx/PivotTables.php +++ /dev/null @@ -1,100 +0,0 @@ -worksheet = $worksheet; - $this->archive = $archive; - } - - public function load(SimpleXMLElement $relsWorksheet, string $dir, string $fileWorksheet): void - { - foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable') { - $objPivotTable = new PivotTable( - (string) $ele['Id'], - (string) $ele['Target'], - $this->getFromZipArchive(dirname("$dir/$fileWorksheet") . '/' . (string) $ele['Target']) - ); - - $relsPivotTablePath = $dir . '/pivotTables/_rels/' . $objPivotTable->getName() . '.rels'; - if ($this->archive->locateName($relsPivotTablePath)) { - $relsPivotTable = simplexml_load_string($this->getFromZipArchive($relsPivotTablePath)); - foreach ($relsPivotTable->Relationship as $elePT) { - if ($elePT['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition') { - $objPivotCacheDefinition = new PivotCacheDefinition( - (string) $elePT['Id'], - (string) $elePT['Target'], - $this->getFromZipArchive(dirname("$dir/$fileWorksheet") . '/' . (string) $elePT['Target']) - ); - - $relsPivotCachePath = $dir . '/pivotCache/_rels/' . $objPivotCacheDefinition->getName() . '.rels'; - if ($this->archive->locateName($relsPivotCachePath)) { - $relsPivotCache = simplexml_load_string($this->getFromZipArchive($relsPivotCachePath)); - foreach ($relsPivotCache->Relationship as $elePC) { - if ($elePC['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords') { - $objPivotCacheRecords = new PivotCacheRecords( - (string) $elePC['Id'], - (string) $elePC['Target'], - $this->getFromZipArchive(dirname("$dir/$fileWorksheet") . '/' . dirname((string) $elePT['Target']) . '/' . (string) $elePC['Target']) - ); - } - $objPivotCacheDefinition->addPivotCacheRecords($objPivotCacheRecords); - } - } - } - $objPivotTable->addPivotCacheDefinition($objPivotCacheDefinition); - } - } - - $this->worksheet->addPivotTable($objPivotTable); - } - } - } - - /** - * @param string $fileName - * - * @return string - */ - private function getFromZipArchive($fileName = '') - { - // Root-relative paths - if (strpos($fileName, '//') !== false) { - $fileName = substr($fileName, strpos($fileName, '//') + 1); - } - $fileName = File::realpath($fileName); - - // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming - // so we need to load case-insensitively from the zip file - - // Apache POI fixes - $contents = $this->archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE); - if ($contents === false) { - $contents = $this->archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE); - } - - return $contents; - } -} diff --git a/src/PhpSpreadsheet/Reader/Xml.php b/src/PhpSpreadsheet/Reader/Xml.php index 4ef4efe7b5..7f2cbfec52 100644 --- a/src/PhpSpreadsheet/Reader/Xml.php +++ b/src/PhpSpreadsheet/Reader/Xml.php @@ -240,14 +240,16 @@ public function listWorksheetInfo($filename) * * @return Spreadsheet */ - public function load($filename) + public function load(string $pFilename, int $flags = 0) { + $this->processFlags($flags); + // Create new Spreadsheet $spreadsheet = new Spreadsheet(); $spreadsheet->removeSheetByIndex(0); // Load into this instance - return $this->loadIntoExisting($filename, $spreadsheet); + return $this->loadIntoExisting($pFilename, $spreadsheet); } /** diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index e20af68379..0f2602910e 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -16,7 +16,6 @@ use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\IComparable; -use PhpOffice\PhpSpreadsheet\PivotTable; use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared; @@ -108,13 +107,6 @@ class Worksheet implements IComparable */ private $chartCollection; - /** - * Collection of PivotTable objects. - * - * @var PivotTable[] - */ - private $pivotTableCollection = []; - /** * Worksheet title. * @@ -2858,29 +2850,6 @@ public function getDataValidationCollection() return $this->dataValidationCollection; } - /** - * Get collection of PivotTables. - * - * @return PivotTable[] - */ - public function getPivotTableCollection() - { - return $this->pivotTableCollection; - } - - public function addPivotTable(?PivotTable $pPivotTable = null, ?int $iPivotTableIndex = null): PivotTable - { - $pPivotTable->setWorksheet($this); - if ($iPivotTableIndex === null) { - $this->pivotTableCollection[] = $pPivotTable; - } else { - // Insert the pivot table at the requested index - array_splice($this->pivotTableCollection, $iPivotTableIndex, 0, [$pPivotTable]); - } - - return $pPivotTable; - } - /** * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet. * From 5349fe300cd13038e086c9b641c47c03ce10fd27 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Tue, 1 Jun 2021 17:14:04 +0200 Subject: [PATCH 03/11] Use of passing flags with Writers to identify whether speacial features such as loading charts should be enabled; no need to instantiate a writer and manually enable it before loading any more. --- src/PhpSpreadsheet/Writer/BaseWriter.php | 9 ++++++++- src/PhpSpreadsheet/Writer/Csv.php | 4 +++- src/PhpSpreadsheet/Writer/Html.php | 4 +++- src/PhpSpreadsheet/Writer/IWriter.php | 4 +++- src/PhpSpreadsheet/Writer/Ods.php | 4 +++- src/PhpSpreadsheet/Writer/Xls.php | 4 +++- src/PhpSpreadsheet/Writer/Xlsx.php | 4 +++- 7 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/PhpSpreadsheet/Writer/BaseWriter.php b/src/PhpSpreadsheet/Writer/BaseWriter.php index afda5c433e..b9b444008e 100644 --- a/src/PhpSpreadsheet/Writer/BaseWriter.php +++ b/src/PhpSpreadsheet/Writer/BaseWriter.php @@ -6,7 +6,7 @@ abstract class BaseWriter implements IWriter { /** * Write charts that are defined in the workbook? - * Identifies whether the Writer should write definitions for any charts that exist in the PhpSpreadsheet object;. + * Identifies whether the Writer should write definitions for any charts that exist in the PhpSpreadsheet object. * * @var bool */ @@ -94,6 +94,13 @@ public function getDiskCachingDirectory() return $this->diskCachingDirectory; } + protected function processFlags(int $flags) + { + if (((bool) ($flags & self::SAVE_WITH_CHARTS)) === true) { + $this->setIncludeCharts(true); + } + } + /** * Open file handle. * diff --git a/src/PhpSpreadsheet/Writer/Csv.php b/src/PhpSpreadsheet/Writer/Csv.php index 188a83a803..4296cdda89 100644 --- a/src/PhpSpreadsheet/Writer/Csv.php +++ b/src/PhpSpreadsheet/Writer/Csv.php @@ -86,8 +86,10 @@ public function __construct(Spreadsheet $spreadsheet) * * @param resource|string $pFilename */ - public function save($pFilename): void + public function save($pFilename, $flags = 0): void { + $this->processFlags($flags); + // Fetch sheet $sheet = $this->spreadsheet->getSheet($this->sheetIndex); diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index b694d4ef9d..7d7e56c184 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -153,8 +153,10 @@ public function __construct(Spreadsheet $spreadsheet) * * @param resource|string $pFilename */ - public function save($pFilename): void + public function save($pFilename, $flags = 0): void { + $this->processFlags($flags); + // Open file $this->openFileHandle($pFilename); diff --git a/src/PhpSpreadsheet/Writer/IWriter.php b/src/PhpSpreadsheet/Writer/IWriter.php index 5129d65583..48f8c1a943 100644 --- a/src/PhpSpreadsheet/Writer/IWriter.php +++ b/src/PhpSpreadsheet/Writer/IWriter.php @@ -6,6 +6,8 @@ interface IWriter { + public const SAVE_WITH_CHARTS = 1; + /** * IWriter constructor. */ @@ -59,7 +61,7 @@ public function setPreCalculateFormulas($pValue); * * @param resource|string $pFilename Name of the file to save */ - public function save($pFilename); + public function save($pFilename, $flags = 0); /** * Get use disk caching where possible? diff --git a/src/PhpSpreadsheet/Writer/Ods.php b/src/PhpSpreadsheet/Writer/Ods.php index f07ade9a58..870d2ed002 100644 --- a/src/PhpSpreadsheet/Writer/Ods.php +++ b/src/PhpSpreadsheet/Writer/Ods.php @@ -115,12 +115,14 @@ public function getWriterPartThumbnails(): Thumbnails * * @param resource|string $pFilename */ - public function save($pFilename): void + public function save($pFilename, $flags = 0): void { if (!$this->spreadSheet) { throw new WriterException('PhpSpreadsheet object unassigned.'); } + $this->processFlags($flags); + // garbage collect $this->spreadSheet->garbageCollect(); diff --git a/src/PhpSpreadsheet/Writer/Xls.php b/src/PhpSpreadsheet/Writer/Xls.php index 624dc41419..ff95db5e3c 100644 --- a/src/PhpSpreadsheet/Writer/Xls.php +++ b/src/PhpSpreadsheet/Writer/Xls.php @@ -119,8 +119,10 @@ public function __construct(Spreadsheet $spreadsheet) * * @param resource|string $pFilename */ - public function save($pFilename): void + public function save($pFilename, $flags = 0): void { + $this->processFlags($flags); + // garbage collect $this->spreadsheet->garbageCollect(); diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index 27ceb55901..44066bb8d2 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -286,8 +286,10 @@ public function getWriterPartWorksheet(): Worksheet * * @param resource|string $pFilename */ - public function save($pFilename): void + public function save($pFilename, $flags = 0): void { + $this->processFlags($flags); + // garbage collect $this->pathNames = []; $this->spreadSheet->garbageCollect(); From 1d899ebcc55d9a218fa653792e5c8d19251a65c6 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Tue, 1 Jun 2021 17:57:13 +0200 Subject: [PATCH 04/11] Update documentation --- docs/topics/reading-and-writing-to-file.md | 63 +++++++++++++++++++--- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/docs/topics/reading-and-writing-to-file.md b/docs/topics/reading-and-writing-to-file.md index e5c2afd9b8..a91fce58e8 100644 --- a/docs/topics/reading-and-writing-to-file.md +++ b/docs/topics/reading-and-writing-to-file.md @@ -1,8 +1,7 @@ # Reading and writing to file As you already know from the [architecture](./architecture.md#readers-and-writers), -reading and writing to a -persisted storage is not possible using the base PhpSpreadsheet classes. +reading and writing to a persisted storage is not possible using the base PhpSpreadsheet classes. For this purpose, PhpSpreadsheet provides readers and writers, which are implementations of `\PhpOffice\PhpSpreadsheet\Reader\IReader` and `\PhpOffice\PhpSpreadsheet\Writer\IWriter`. @@ -892,8 +891,7 @@ class My_Custom_TCPDF_Writer extends \PhpOffice\PhpSpreadsheet\Writer\Pdf\Tcpdf #### Writing a spreadsheet -Once you have identified the Renderer that you wish to use for PDF -generation, you can write a .pdf file using the following code: +Once you have identified the Renderer that you wish to use for PDF generation, you can write a .pdf file using the following code: ```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf($spreadsheet); @@ -905,8 +903,7 @@ first worksheet by default. #### Write all worksheets -PDF files can contain one or more worksheets. If you want to write all -sheets into a single PDF file, use the following code: +PDF files can contain one or more worksheets. If you want to write all sheets into a single PDF file, use the following code: ```php $writer->writeAllSheets(); @@ -1020,3 +1017,57 @@ $spreadhseet = $reader->loadFromString($secondHtmlString, $spreadsheet); $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xls'); $writer->save('write.xls'); ``` + +## Reader/Writer Flags + +Some Readers and Writers support "Special Features" that need to be explicitly enabled. +An example of this is Charts in a spreadsheet. By default, when you load a spreadsheet that contains Charts, the charts will not be loaded. If all you want to do is read the data in the spreadsheet, then loading charts is an overhead for both speed of loading and memory usage. +However, there are times when you may want to load any charts in the spreadsheet as well as the data. To do so, you need to tell the Reader explicitly to include Charts. + +```php +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile("05featuredemo.xlsx"); +$reader->setIncludeCharts(true); +$reader->load("spreadsheetWithCharts.xlsx"); +``` +Alternatively, you can specify this in the call to load the spreadsheet: +```php +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile("05featuredemo.xlsx"); +$reader->load("spreadsheetWithCharts.xlsx", $reader::LOAD_WITH_CHARTS); +``` + +If you wish to use the IOFactory `load()` method rather than instantiating a specific Reader, then you can still pass flags any flags. + +```php +$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load("setIncludeCharts.xlsx", \PhpOffice\PhpSpreadsheet\Reader\IReader::LOAD_WITH_CHARTS); +``` + +Likewise, when saving a file using a Writer, loaded charts wil not be saved unless you explicitly tell the Writer to include them: + +```php +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$writer->save('mySavedFileWithCharts.xlsx'); +``` + +Currently, the only "Special Feature" that is supported in this way is the inclusion of Charts, and only for certain formats. + +Readers | LOAD_WITH_CHARTS | +---------|------------------| +Xlsx | YES | +Xls | NO | +Xml | NO | +Ods | NO | +Gnumeric | NO | +Html | N/A | +Slk | N/A | +Csv | N/A | + + +Writers | SAVE_WITH_CHARTS | +--------|------------------| +Xlsx | YES | +Xls | NO | +Ods | NO | +Html | YES | +Pdf | YES | +Csv | N/A | From f900ae9382661f34cb6df4872e113000bb647ad1 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Tue, 1 Jun 2021 19:54:08 +0200 Subject: [PATCH 05/11] Ensure that Writer save() signatures match interface definition --- src/PhpSpreadsheet/Writer/Csv.php | 2 +- src/PhpSpreadsheet/Writer/Html.php | 2 +- src/PhpSpreadsheet/Writer/IWriter.php | 2 +- src/PhpSpreadsheet/Writer/Ods.php | 2 +- src/PhpSpreadsheet/Writer/Pdf/Dompdf.php | 2 +- src/PhpSpreadsheet/Writer/Pdf/Mpdf.php | 2 +- src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php | 2 +- src/PhpSpreadsheet/Writer/Xls.php | 2 +- src/PhpSpreadsheet/Writer/Xlsx.php | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/PhpSpreadsheet/Writer/Csv.php b/src/PhpSpreadsheet/Writer/Csv.php index 4296cdda89..79e8e5f1e3 100644 --- a/src/PhpSpreadsheet/Writer/Csv.php +++ b/src/PhpSpreadsheet/Writer/Csv.php @@ -86,7 +86,7 @@ public function __construct(Spreadsheet $spreadsheet) * * @param resource|string $pFilename */ - public function save($pFilename, $flags = 0): void + public function save($pFilename, int $flags = 0): void { $this->processFlags($flags); diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index 7d7e56c184..76c5e72287 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -153,7 +153,7 @@ public function __construct(Spreadsheet $spreadsheet) * * @param resource|string $pFilename */ - public function save($pFilename, $flags = 0): void + public function save($pFilename, int $flags = 0): void { $this->processFlags($flags); diff --git a/src/PhpSpreadsheet/Writer/IWriter.php b/src/PhpSpreadsheet/Writer/IWriter.php index 48f8c1a943..a0361f7421 100644 --- a/src/PhpSpreadsheet/Writer/IWriter.php +++ b/src/PhpSpreadsheet/Writer/IWriter.php @@ -61,7 +61,7 @@ public function setPreCalculateFormulas($pValue); * * @param resource|string $pFilename Name of the file to save */ - public function save($pFilename, $flags = 0); + public function save($pFilename, int $flags = 0): void; /** * Get use disk caching where possible? diff --git a/src/PhpSpreadsheet/Writer/Ods.php b/src/PhpSpreadsheet/Writer/Ods.php index 870d2ed002..141a411950 100644 --- a/src/PhpSpreadsheet/Writer/Ods.php +++ b/src/PhpSpreadsheet/Writer/Ods.php @@ -115,7 +115,7 @@ public function getWriterPartThumbnails(): Thumbnails * * @param resource|string $pFilename */ - public function save($pFilename, $flags = 0): void + public function save($pFilename, int $flags = 0): void { if (!$this->spreadSheet) { throw new WriterException('PhpSpreadsheet object unassigned.'); diff --git a/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php b/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php index 87e8eeb5f6..313b34e805 100644 --- a/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php +++ b/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php @@ -22,7 +22,7 @@ protected function createExternalWriterInstance() * * @param string $pFilename Name of the file to save as */ - public function save($pFilename): void + public function save($pFilename, int $flags = 0): void { $fileHandle = parent::prepareForSave($pFilename); diff --git a/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php b/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php index 56ac693066..0db164b63e 100644 --- a/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php +++ b/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php @@ -24,7 +24,7 @@ protected function createExternalWriterInstance($config) * * @param string $pFilename Name of the file to save as */ - public function save($pFilename): void + public function save($pFilename, int $flags = 0): void { $fileHandle = parent::prepareForSave($pFilename); diff --git a/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php b/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php index 56e917e3f2..76f983a045 100644 --- a/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php +++ b/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php @@ -38,7 +38,7 @@ protected function createExternalWriterInstance($orientation, $unit, $paperSize) * * @param string $pFilename Name of the file to save as */ - public function save($pFilename): void + public function save($pFilename, int $flags = 0): void { $fileHandle = parent::prepareForSave($pFilename); diff --git a/src/PhpSpreadsheet/Writer/Xls.php b/src/PhpSpreadsheet/Writer/Xls.php index ff95db5e3c..fa7827551f 100644 --- a/src/PhpSpreadsheet/Writer/Xls.php +++ b/src/PhpSpreadsheet/Writer/Xls.php @@ -119,7 +119,7 @@ public function __construct(Spreadsheet $spreadsheet) * * @param resource|string $pFilename */ - public function save($pFilename, $flags = 0): void + public function save($pFilename, int $flags = 0): void { $this->processFlags($flags); diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index 44066bb8d2..6f43989fd2 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -286,7 +286,7 @@ public function getWriterPartWorksheet(): Worksheet * * @param resource|string $pFilename */ - public function save($pFilename, $flags = 0): void + public function save($pFilename, int $flags = 0): void { $this->processFlags($flags); From 3e691d8636a90873e458ec4dbd9c6800f4c0efcc Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Tue, 1 Jun 2021 20:44:51 +0200 Subject: [PATCH 06/11] phpcs and phpstan appeasements --- phpstan-baseline.neon | 5 ----- src/PhpSpreadsheet/Reader/BaseReader.php | 2 +- src/PhpSpreadsheet/Reader/Csv.php | 2 -- src/PhpSpreadsheet/Reader/Gnumeric.php | 2 -- src/PhpSpreadsheet/Reader/Html.php | 2 -- src/PhpSpreadsheet/Reader/Ods.php | 2 -- src/PhpSpreadsheet/Reader/Slk.php | 2 -- src/PhpSpreadsheet/Reader/Xls.php | 2 -- src/PhpSpreadsheet/Reader/Xml.php | 2 -- src/PhpSpreadsheet/Writer/BaseWriter.php | 2 +- 10 files changed, 2 insertions(+), 21 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4ee0a9efd3..4d4f719336 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -6245,11 +6245,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Writer/Html.php - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\IWriter\\:\\:save\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/IWriter.php - - message: "#^Negated boolean expression is always false\\.$#" count: 1 diff --git a/src/PhpSpreadsheet/Reader/BaseReader.php b/src/PhpSpreadsheet/Reader/BaseReader.php index c789421c78..1f18484ff7 100644 --- a/src/PhpSpreadsheet/Reader/BaseReader.php +++ b/src/PhpSpreadsheet/Reader/BaseReader.php @@ -137,7 +137,7 @@ public function getSecurityScanner() return $this->securityScanner; } - protected function processFlags(int $flags) + protected function processFlags(int $flags): void { if (((bool) ($flags & self::LOAD_WITH_CHARTS)) === true) { $this->setIncludeCharts(true); diff --git a/src/PhpSpreadsheet/Reader/Csv.php b/src/PhpSpreadsheet/Reader/Csv.php index 43d7b46281..f06de635af 100644 --- a/src/PhpSpreadsheet/Reader/Csv.php +++ b/src/PhpSpreadsheet/Reader/Csv.php @@ -236,8 +236,6 @@ public function listWorksheetInfo(string $pFilename): array /** * Loads Spreadsheet from file. * - * @param string $pFilename - * * @return Spreadsheet */ public function load(string $pFilename, int $flags = 0) diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php index 064b05198d..64c27366dd 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric.php @@ -235,8 +235,6 @@ private static function testSimpleXml($value): SimpleXMLElement /** * Loads Spreadsheet from file. * - * @param string $pFilename - * * @return Spreadsheet */ public function load(string $pFilename, int $flags = 0) diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php index 60a1daf7f1..6e6155c230 100644 --- a/src/PhpSpreadsheet/Reader/Html.php +++ b/src/PhpSpreadsheet/Reader/Html.php @@ -204,8 +204,6 @@ private static function containsTags($data) /** * Loads Spreadsheet from file. * - * @param string $pFilename - * * @return Spreadsheet */ public function load(string $pFilename, int $flags = 0) diff --git a/src/PhpSpreadsheet/Reader/Ods.php b/src/PhpSpreadsheet/Reader/Ods.php index 241145763c..b39fffedf4 100644 --- a/src/PhpSpreadsheet/Reader/Ods.php +++ b/src/PhpSpreadsheet/Reader/Ods.php @@ -231,8 +231,6 @@ public function listWorksheetInfo($pFilename) /** * Loads PhpSpreadsheet from file. * - * @param string $pFilename - * * @return Spreadsheet */ public function load(string $pFilename, int $flags = 0) diff --git a/src/PhpSpreadsheet/Reader/Slk.php b/src/PhpSpreadsheet/Reader/Slk.php index 4b4de788ad..b58fdcba9c 100644 --- a/src/PhpSpreadsheet/Reader/Slk.php +++ b/src/PhpSpreadsheet/Reader/Slk.php @@ -197,8 +197,6 @@ public function listWorksheetInfo($pFilename) /** * Loads PhpSpreadsheet from file. * - * @param string $pFilename - * * @return Spreadsheet */ public function load(string $pFilename, int $flags = 0) diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index 3886035598..a8c097f7e5 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -622,8 +622,6 @@ public function listWorksheetInfo($pFilename) /** * Loads PhpSpreadsheet from file. * - * @param string $pFilename - * * @return Spreadsheet */ public function load(string $pFilename, int $flags = 0) diff --git a/src/PhpSpreadsheet/Reader/Xml.php b/src/PhpSpreadsheet/Reader/Xml.php index 7f2cbfec52..f63bc79870 100644 --- a/src/PhpSpreadsheet/Reader/Xml.php +++ b/src/PhpSpreadsheet/Reader/Xml.php @@ -236,8 +236,6 @@ public function listWorksheetInfo($filename) /** * Loads Spreadsheet from file. * - * @param string $filename - * * @return Spreadsheet */ public function load(string $pFilename, int $flags = 0) diff --git a/src/PhpSpreadsheet/Writer/BaseWriter.php b/src/PhpSpreadsheet/Writer/BaseWriter.php index b9b444008e..3b03cfc37e 100644 --- a/src/PhpSpreadsheet/Writer/BaseWriter.php +++ b/src/PhpSpreadsheet/Writer/BaseWriter.php @@ -94,7 +94,7 @@ public function getDiskCachingDirectory() return $this->diskCachingDirectory; } - protected function processFlags(int $flags) + protected function processFlags(int $flags): void { if (((bool) ($flags & self::SAVE_WITH_CHARTS)) === true) { $this->setIncludeCharts(true); From b89b2931462a85cdbb892ead0f43fdd39200414c Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Tue, 1 Jun 2021 20:56:14 +0200 Subject: [PATCH 07/11] Fon't brea protection keys --- src/PhpSpreadsheet/Reader/Xlsx.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 2f48dbceee..8adb3bc8cf 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -768,7 +768,7 @@ public function load(string $pFilename, int $flags = 0) } } - $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'selectUnlockedCells']; + $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells']; if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) { foreach ($aKeys as $key) { $method = 'set' . ucfirst($key); From eeca2d53583a404020bb28efd15bac654b527f19 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Tue, 1 Jun 2021 21:16:41 +0200 Subject: [PATCH 08/11] More updates to the documentation --- docs/topics/reading-and-writing-to-file.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/topics/reading-and-writing-to-file.md b/docs/topics/reading-and-writing-to-file.md index a91fce58e8..8ee0c78e76 100644 --- a/docs/topics/reading-and-writing-to-file.md +++ b/docs/topics/reading-and-writing-to-file.md @@ -1020,7 +1020,7 @@ $writer->save('write.xls'); ## Reader/Writer Flags -Some Readers and Writers support "Special Features" that need to be explicitly enabled. +Some Readers and Writers support special "Feature Flags" that need to be explicitly enabled. An example of this is Charts in a spreadsheet. By default, when you load a spreadsheet that contains Charts, the charts will not be loaded. If all you want to do is read the data in the spreadsheet, then loading charts is an overhead for both speed of loading and memory usage. However, there are times when you may want to load any charts in the spreadsheet as well as the data. To do so, you need to tell the Reader explicitly to include Charts. @@ -1035,7 +1035,7 @@ $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile("05featuredem $reader->load("spreadsheetWithCharts.xlsx", $reader::LOAD_WITH_CHARTS); ``` -If you wish to use the IOFactory `load()` method rather than instantiating a specific Reader, then you can still pass flags any flags. +If you wish to use the IOFactory `load()` method rather than instantiating a specific Reader, then you can still pass these flags. ```php $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load("setIncludeCharts.xlsx", \PhpOffice\PhpSpreadsheet\Reader\IReader::LOAD_WITH_CHARTS); @@ -1049,7 +1049,13 @@ $writer->setIncludeCharts(true); $writer->save('mySavedFileWithCharts.xlsx'); ``` -Currently, the only "Special Feature" that is supported in this way is the inclusion of Charts, and only for certain formats. +As with the `load()` method, you can also pass flags in the `save()` method: +```php +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->save('mySavedFileWithCharts.xlsx', \PhpOffice\PhpSpreadsheet\Writer\IWriter::SAVE_WITH_CHARTS); +``` + +Currently, the only special "Feature Flag" that is supported in this way is the inclusion of Charts, and only for certain formats. Readers | LOAD_WITH_CHARTS | ---------|------------------| @@ -1071,3 +1077,4 @@ Ods | NO | Html | YES | Pdf | YES | Csv | N/A | + From c28a2885c63a362fcd28ab5d11d91aeb8033b1f0 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Tue, 1 Jun 2021 21:45:58 +0200 Subject: [PATCH 09/11] Add unit test for Reader flags --- tests/PhpSpreadsheetTests/Reader/SheetsXlsxChartTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/PhpSpreadsheetTests/Reader/SheetsXlsxChartTest.php b/tests/PhpSpreadsheetTests/Reader/SheetsXlsxChartTest.php index c4dc328d2d..9c478c4a7e 100644 --- a/tests/PhpSpreadsheetTests/Reader/SheetsXlsxChartTest.php +++ b/tests/PhpSpreadsheetTests/Reader/SheetsXlsxChartTest.php @@ -4,6 +4,7 @@ use PhpOffice\PhpSpreadsheet\Chart\DataSeries; use PhpOffice\PhpSpreadsheet\IOFactory; +use PhpOffice\PhpSpreadsheet\Reader\IReader; use PHPUnit\Framework\TestCase; class SheetsXlsxChartTest extends TestCase @@ -11,8 +12,8 @@ class SheetsXlsxChartTest extends TestCase public function testLoadSheetsXlsxChart(): void { $filename = 'tests/data/Reader/XLSX/sheetsChartsTest.xlsx'; - $reader = IOFactory::createReader('Xlsx')->setIncludeCharts(true); - $spreadsheet = $reader->load($filename); + $reader = IOFactory::createReader('Xlsx'); + $spreadsheet = $reader->load($filename, IReader::LOAD_WITH_CHARTS); $worksheet = $spreadsheet->getActiveSheet(); $charts = $worksheet->getChartCollection(); From 64fdea8aef9e6f7bf8d21284fdcbad2c3bf503c2 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Wed, 2 Jun 2021 13:33:05 +0200 Subject: [PATCH 10/11] Minor updates to documentation --- docs/topics/reading-and-writing-to-file.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/reading-and-writing-to-file.md b/docs/topics/reading-and-writing-to-file.md index 8ee0c78e76..cb1e0e77b9 100644 --- a/docs/topics/reading-and-writing-to-file.md +++ b/docs/topics/reading-and-writing-to-file.md @@ -1031,14 +1031,14 @@ $reader->load("spreadsheetWithCharts.xlsx"); ``` Alternatively, you can specify this in the call to load the spreadsheet: ```php -$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile("05featuredemo.xlsx"); +$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile("spreadsheetWithCharts.xlsx"); $reader->load("spreadsheetWithCharts.xlsx", $reader::LOAD_WITH_CHARTS); ``` If you wish to use the IOFactory `load()` method rather than instantiating a specific Reader, then you can still pass these flags. ```php -$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load("setIncludeCharts.xlsx", \PhpOffice\PhpSpreadsheet\Reader\IReader::LOAD_WITH_CHARTS); +$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load("spreadsheetWithCharts.xlsx", \PhpOffice\PhpSpreadsheet\Reader\IReader::LOAD_WITH_CHARTS); ``` Likewise, when saving a file using a Writer, loaded charts wil not be saved unless you explicitly tell the Writer to include them: From e505e87de9eb2248f1b365a3a3cd0114992225fb Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 4 Jun 2021 13:14:04 +0200 Subject: [PATCH 11/11] Update documentation with details of changes to the StringValueBinder --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e9eec1106..fe278712b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Added -- Nothing. +- Support for passing flags in the Reader `load()` and Writer `save()`methods, and through the IOFactory, to set behaviours. [PR #2136](https://github.com/PHPOffice/PhpSpreadsheet/pull/2136) + - See [documentation](https://phpspreadsheet.readthedocs.io/en/latest/topics/reading-and-writing-to-file/) for details ### Changed