!b4b=js3udvxLD(TSTol2
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
KyF}R^|NS3$AaOSU
literal 0
HcmV?d00001
diff --git a/src/PhpSpreadsheet/Chart/Chart.php b/src/PhpSpreadsheet/Chart/Chart.php
index e078b80a6d..b86b19cdd0 100644
--- a/src/PhpSpreadsheet/Chart/Chart.php
+++ b/src/PhpSpreadsheet/Chart/Chart.php
@@ -132,6 +132,16 @@ class Chart
private ChartColor $fillColor;
+ /**
+ * Rendered width in pixels.
+ */
+ private ?float $renderedWidth = null;
+
+ /**
+ * Rendered height in pixels.
+ */
+ private ?float $renderedHeight = null;
+
/**
* Create a new Chart.
* majorGridlines and minorGridlines are deprecated, moved to Axis.
@@ -791,4 +801,28 @@ public function getFillColor(): ChartColor
{
return $this->fillColor;
}
+
+ public function setRenderedWidth(?float $width): self
+ {
+ $this->renderedWidth = $width;
+
+ return $this;
+ }
+
+ public function getRenderedWidth(): ?float
+ {
+ return $this->renderedWidth;
+ }
+
+ public function setRenderedHeight(?float $height): self
+ {
+ $this->renderedHeight = $height;
+
+ return $this;
+ }
+
+ public function getRenderedHeight(): ?float
+ {
+ return $this->renderedHeight;
+ }
}
diff --git a/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php b/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php
index 40ce338d93..27cf3b165d 100644
--- a/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php
+++ b/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php
@@ -26,9 +26,9 @@
*/
abstract class JpGraphRendererBase implements IRenderer
{
- private static $width = 640;
+ private const DEFAULT_WIDTH = 640.0;
- private static $height = 480;
+ private const DEFAULT_HEIGHT = 480.0;
private static $colourSet = [
'mediumpurple1', 'palegreen3', 'gold1', 'cadetblue1',
@@ -70,6 +70,16 @@ public function __construct(Chart $chart)
];
}
+ private function getGraphWidth(): float
+ {
+ return $this->chart->getRenderedWidth() ?? self::DEFAULT_WIDTH;
+ }
+
+ private function getGraphHeight(): float
+ {
+ return $this->chart->getRenderedHeight() ?? self::DEFAULT_HEIGHT;
+ }
+
/**
* This method should be overriden in descendants to do real JpGraph library initialization.
*/
@@ -221,7 +231,7 @@ private function renderLegend(): void
private function renderCartesianPlotArea(string $type = 'textlin'): void
{
- $this->graph = new Graph(self::$width, self::$height);
+ $this->graph = new Graph($this->getGraphWidth(), $this->getGraphHeight());
$this->graph->SetScale($type);
$this->renderTitle();
@@ -258,14 +268,14 @@ private function renderCartesianPlotArea(string $type = 'textlin'): void
private function renderPiePlotArea(): void
{
- $this->graph = new PieGraph(self::$width, self::$height);
+ $this->graph = new PieGraph($this->getGraphWidth(), $this->getGraphHeight());
$this->renderTitle();
}
private function renderRadarPlotArea(): void
{
- $this->graph = new RadarGraph(self::$width, self::$height);
+ $this->graph = new RadarGraph($this->getGraphWidth(), $this->getGraphHeight());
$this->graph->SetScale('lin');
$this->renderTitle();
@@ -460,7 +470,6 @@ private function renderPlotScatter(int $groupID, bool $bubble): void
$dataValuesY[$k] = $k;
}
}
- //var_dump($dataValuesY, $dataValuesX, $bubbleSize);
$seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY);
if ($scatterStyle == 'lineMarker') {
@@ -468,7 +477,7 @@ private function renderPlotScatter(int $groupID, bool $bubble): void
$seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]);
} elseif ($scatterStyle == 'smoothMarker') {
$spline = new Spline($dataValuesY, $dataValuesX);
- [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20);
+ [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * $this->getGraphWidth() / 20);
$lplot = new LinePlot($splineDataX, $splineDataY);
$lplot->SetColor(self::$colourSet[self::$plotColour]);
diff --git a/src/PhpSpreadsheet/Shared/Drawing.php b/src/PhpSpreadsheet/Shared/Drawing.php
index edaad86153..f7ce022c51 100644
--- a/src/PhpSpreadsheet/Shared/Drawing.php
+++ b/src/PhpSpreadsheet/Shared/Drawing.php
@@ -110,7 +110,7 @@ public static function pixelsToPoints($pixelValue): float
/**
* Convert points to pixels.
*
- * @param int $pointValue Value in points
+ * @param float|int $pointValue Value in points
*
* @return int Value in pixels
*/
diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php
index 2b38abfc4e..ecb99219d1 100644
--- a/src/PhpSpreadsheet/Writer/Html.php
+++ b/src/PhpSpreadsheet/Writer/Html.php
@@ -31,6 +31,10 @@
class Html extends BaseWriter
{
+ private const DEFAULT_CELL_WIDTH_POINTS = 42;
+
+ private const DEFAULT_CELL_WIDTH_PIXELS = 56;
+
/**
* Spreadsheet object.
*/
@@ -580,9 +584,9 @@ private function extendRowsForCharts(Worksheet $worksheet, int $row): array
$chartCol = Coordinate::columnIndexFromString($chartTL[0]);
if ($chartTL[1] > $rowMax) {
$rowMax = $chartTL[1];
- if ($chartCol > Coordinate::columnIndexFromString($colMax)) {
- $colMax = $chartTL[0];
- }
+ }
+ if ($chartCol > Coordinate::columnIndexFromString($colMax)) {
+ $colMax = $chartTL[0];
}
}
}
@@ -601,9 +605,9 @@ private function extendRowsForChartsAndImages(Worksheet $worksheet, int $row): s
$imageCol = Coordinate::columnIndexFromString($imageTL[0]);
if ($imageTL[1] > $rowMax) {
$rowMax = $imageTL[1];
- if ($imageCol > Coordinate::columnIndexFromString($colMax)) {
- $colMax = $imageTL[0];
- }
+ }
+ if ($imageCol > Coordinate::columnIndexFromString($colMax)) {
+ $colMax = $imageTL[0];
}
}
@@ -745,7 +749,15 @@ private function writeChartInCell(Worksheet $worksheet, string $coordinates): st
$chartCoordinates = $chart->getTopLeftPosition();
if ($chartCoordinates['cell'] == $coordinates) {
$chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png';
- if (!$chart->render($chartFileName)) {
+ $renderedWidth = $chart->getRenderedWidth();
+ $renderedHeight = $chart->getRenderedHeight();
+ if ($renderedWidth === null || $renderedHeight === null) {
+ $this->adjustRendererPositions($chart, $worksheet);
+ }
+ $renderSuccessful = $chart->render($chartFileName);
+ $chart->setRenderedWidth($renderedWidth);
+ $chart->setRenderedHeight($renderedHeight);
+ if (!$renderSuccessful) {
return '';
}
@@ -770,6 +782,37 @@ private function writeChartInCell(Worksheet $worksheet, string $coordinates): st
return $html;
}
+ private function adjustRendererPositions(Chart $chart, Worksheet $sheet): void
+ {
+ $topLeft = $chart->getTopLeftPosition();
+ $bottomRight = $chart->getBottomRightPosition();
+ $tlCell = $topLeft['cell'];
+ $brCell = $bottomRight['cell'];
+ if ($tlCell !== '' && $brCell !== '') {
+ $tlCoordinate = Coordinate::indexesFromString($tlCell);
+ $brCoordinate = Coordinate::indexesFromString($brCell);
+ $totalHeight = 0.0;
+ $totalWidth = 0.0;
+ $defaultRowHeight = $sheet->getDefaultRowDimension()->getRowHeight();
+ $defaultRowHeight = SharedDrawing::pointsToPixels(($defaultRowHeight >= 0) ? $defaultRowHeight : SharedFont::getDefaultRowHeightByFont($this->defaultFont));
+ if ($tlCoordinate[1] <= $brCoordinate[1] && $tlCoordinate[0] <= $brCoordinate[0]) {
+ for ($row = $tlCoordinate[1]; $row <= $brCoordinate[1]; ++$row) {
+ $height = $sheet->getRowDimension($row)->getRowHeight('pt');
+ $totalHeight += ($height >= 0) ? $height : $defaultRowHeight;
+ }
+ $rightEdge = $brCoordinate[2];
+ ++$rightEdge;
+ for ($column = $tlCoordinate[2]; $column !== $rightEdge; ++$column) {
+ $width = $sheet->getColumnDimension($column)->getWidth();
+ $width = ($width < 0) ? self::DEFAULT_CELL_WIDTH_PIXELS : SharedDrawing::cellDimensionToPixels($sheet->getColumnDimension($column)->getWidth(), $this->defaultFont);
+ $totalWidth += $width;
+ }
+ $chart->setRenderedWidth($totalWidth);
+ $chart->setRenderedHeight($totalHeight);
+ }
+ }
+ }
+
/**
* Generate CSS styles.
*
@@ -844,8 +887,8 @@ private function buildCssPerSheet(Worksheet $sheet, array &$css): void
$highestColumnIndex = Coordinate::columnIndexFromString($sheet->getHighestColumn()) - 1;
$column = -1;
while ($column++ < $highestColumnIndex) {
- $this->columnWidths[$sheetIndex][$column] = 42; // approximation
- $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = '42pt';
+ $this->columnWidths[$sheetIndex][$column] = self::DEFAULT_CELL_WIDTH_POINTS; // approximation
+ $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = self::DEFAULT_CELL_WIDTH_POINTS . 'pt';
}
// col elements, loop through columnDimensions and set width
From 276f7813d5ac3905b010b609f8d31334589ed35b Mon Sep 17 00:00:00 2001
From: Adrian <54487341+AdrianBatista@users.noreply.github.com>
Date: Fri, 17 Nov 2023 11:56:22 -0300
Subject: [PATCH 10/20] check if coordinate is inside range (#3779)
* check if coordinate is inside range
* Added coordinateIsInsideRange tests
* fix coordinateIsInsideRange error throwing
* fix coordinateIsInsideRange error throwing
* add support to worksheet name
* validateReferenceAndGetData type
* change validate and add tests data
* fix tests data reference
* fix absolute reference error
* fix boundaries to get range
* fix scrutinizer erros
* Additional Test Cases
---------
Co-authored-by: oleibman <10341515+oleibman@users.noreply.github.com>
---
src/PhpSpreadsheet/Cell/Coordinate.php | 86 +++++++++++++++++++
.../Cell/CoordinateTest.php | 35 ++++++++
tests/data/Cell/CoordinateIsInsideRange.php | 33 +++++++
.../Cell/CoordinateIsInsideRangeException.php | 8 ++
4 files changed, 162 insertions(+)
create mode 100644 tests/data/Cell/CoordinateIsInsideRange.php
create mode 100644 tests/data/Cell/CoordinateIsInsideRangeException.php
diff --git a/src/PhpSpreadsheet/Cell/Coordinate.php b/src/PhpSpreadsheet/Cell/Coordinate.php
index 564d280a2e..81ad64d4f9 100644
--- a/src/PhpSpreadsheet/Cell/Coordinate.php
+++ b/src/PhpSpreadsheet/Cell/Coordinate.php
@@ -14,6 +14,7 @@
abstract class Coordinate
{
public const A1_COORDINATE_REGEX = '/^(?\$?[A-Z]{1,3})(?\$?\d{1,7})$/i';
+ public const FULL_REFERENCE_REGEX = '/^(?:(?[^!]*)!)?(?(?[$]?[A-Z]{1,3}[$]?\d{1,7})(?:\:(?[$]?[A-Z]{1,3}[$]?\d{1,7}))?)$/i';
/**
* Default range variable constant.
@@ -258,6 +259,91 @@ public static function getRangeBoundaries(string $range)
];
}
+ /**
+ * Check if cell or range reference is valid and return an array with type of reference (cell or range), worksheet (if it was given)
+ * and the coordinate or the first coordinate and second coordinate if it is a range.
+ *
+ * @param string $reference Coordinate or Range (e.g. A1:A1, B2, B:C, 2:3)
+ *
+ * @return array reference data
+ */
+ private static function validateReferenceAndGetData($reference): array
+ {
+ $data = [];
+ preg_match(self::FULL_REFERENCE_REGEX, $reference, $matches);
+ if (count($matches) === 0) {
+ return ['type' => 'invalid'];
+ }
+
+ if (isset($matches['secondCoordinate'])) {
+ $data['type'] = 'range';
+ $data['firstCoordinate'] = str_replace('$', '', $matches['firstCoordinate']);
+ $data['secondCoordinate'] = str_replace('$', '', $matches['secondCoordinate']);
+ } else {
+ $data['type'] = 'coordinate';
+ $data['coordinate'] = str_replace('$', '', $matches['firstCoordinate']);
+ }
+
+ $worksheet = $matches['worksheet'];
+ if ($worksheet !== '') {
+ if (substr($worksheet, 0, 1) === "'" && substr($worksheet, -1, 1) === "'") {
+ $worksheet = substr($worksheet, 1, -1);
+ }
+ $data['worksheet'] = strtolower($worksheet);
+ }
+ $data['localReference'] = str_replace('$', '', $matches['localReference']);
+
+ return $data;
+ }
+
+ /**
+ * Check if coordinate is inside a range.
+ *
+ * @param string $range Cell range, Single Cell, Row/Column Range (e.g. A1:A1, B2, B:C, 2:3)
+ * @param string $coordinate Cell coordinate (e.g. A1)
+ *
+ * @return bool true if coordinate is inside range
+ */
+ public static function coordinateIsInsideRange(string $range, string $coordinate): bool
+ {
+ $rangeData = self::validateReferenceAndGetData($range);
+ if ($rangeData['type'] === 'invalid') {
+ throw new Exception('First argument needs to be a range');
+ }
+
+ $coordinateData = self::validateReferenceAndGetData($coordinate);
+ if ($coordinateData['type'] === 'invalid') {
+ throw new Exception('Second argument needs to be a single coordinate');
+ }
+
+ if (isset($coordinateData['worksheet']) && !isset($rangeData['worksheet'])) {
+ return false;
+ }
+ if (!isset($coordinateData['worksheet']) && isset($rangeData['worksheet'])) {
+ return false;
+ }
+
+ if (isset($coordinateData['worksheet'], $rangeData['worksheet'])) {
+ if ($coordinateData['worksheet'] !== $rangeData['worksheet']) {
+ return false;
+ }
+ }
+
+ $boundaries = self::rangeBoundaries($rangeData['localReference']);
+ $coordinates = self::indexesFromString($coordinateData['localReference']);
+
+ $columnIsInside = $boundaries[0][0] <= $coordinates[0] && $coordinates[0] <= $boundaries[1][0];
+ if (!$columnIsInside) {
+ return false;
+ }
+ $rowIsInside = $boundaries[0][1] <= $coordinates[1] && $coordinates[1] <= $boundaries[1][1];
+ if (!$rowIsInside) {
+ return false;
+ }
+
+ return true;
+ }
+
/**
* Column index from string.
*
diff --git a/tests/PhpSpreadsheetTests/Cell/CoordinateTest.php b/tests/PhpSpreadsheetTests/Cell/CoordinateTest.php
index 1f1d85ee35..e1a527496f 100644
--- a/tests/PhpSpreadsheetTests/Cell/CoordinateTest.php
+++ b/tests/PhpSpreadsheetTests/Cell/CoordinateTest.php
@@ -300,6 +300,41 @@ public static function providerGetRangeBoundaries(): array
return require 'tests/data/CellGetRangeBoundaries.php';
}
+ /**
+ * @dataProvider providerCoordinateIsInsideRange
+ */
+ public static function testCoordinateIsInsideRange(bool $expectedResult, string $range, string $coordinate): void
+ {
+ $result = Coordinate::coordinateIsInsideRange($range, $coordinate);
+ self::assertEquals($result, $expectedResult);
+ }
+
+ public static function providerCoordinateIsInsideRange(): array
+ {
+ return require 'tests/data/Cell/CoordinateIsInsideRange.php';
+ }
+
+ /**
+ * @dataProvider providerCoordinateIsInsideRangeException
+ */
+ public static function testCoordinateIsInsideRangeException(string $expectedResult, string $range, string $coordinate): void
+ {
+ try {
+ Coordinate::coordinateIsInsideRange($range, $coordinate);
+ } catch (\Exception $e) {
+ self::assertInstanceOf(Exception::class, $e);
+ self::assertEquals($e->getMessage(), $expectedResult);
+
+ return;
+ }
+ self::fail('An expected exception has not been raised.');
+ }
+
+ public static function providerCoordinateIsInsideRangeException(): array
+ {
+ return require 'tests/data/Cell/CoordinateIsInsideRangeException.php';
+ }
+
/**
* @dataProvider providerExtractAllCellReferencesInRange
*/
diff --git a/tests/data/Cell/CoordinateIsInsideRange.php b/tests/data/Cell/CoordinateIsInsideRange.php
new file mode 100644
index 0000000000..c71c712307
--- /dev/null
+++ b/tests/data/Cell/CoordinateIsInsideRange.php
@@ -0,0 +1,33 @@
+ [true, 'Sheet!A1:E20', 'sheet!B4'],
+ 'apostrophes 1st sheetname not 2nd' => [true, '\'Sheet\'!A1:E20', 'sheet!B4'],
+ 'apostrophes 2nd sheetname not 1st' => [true, 'Sheet!A1:E20', '\'sheet\'!B4'],
+ [false, 'Sheet!A1:E20', 'Sheet!F36'],
+ [true, 'Sheet!$A$1:$E$20', 'Sheet!$B$4'],
+ [false, 'Sheet!$A$1:$E$20', 'Sheet!$F$36'],
+ [false, 'Sheet!A1:E20', 'B4'],
+ [false, 'Sheet!A1:E20', 'F36'],
+ [false, 'Sheet!$A$1:$E$20', '$B$4'],
+ [false, 'Sheet!$A$1:$E$20', '$F$36'],
+ [false, 'A1:E20', 'Sheet!B4'],
+ [false, 'A1:E20', 'Sheet!F36'],
+ [false, '$A$1:$E$20', 'Sheet!$B$4'],
+ [false, '$A$1:$E$20', 'Sheet!$F$36'],
+ [true, '\'Sheet space\'!A1:E20', '\'Sheet space\'!B4'],
+ [false, '\'Sheet space\'!A1:E20', '\'Sheet space\'!F36'],
+ [true, '\'Sheet space\'!$A$1:$E$20', '\'Sheet space\'!$B$4'],
+ [false, '\'Sheet space\'!$A$1:$E$20', '\'Sheet space\'!$F$36'],
+];
diff --git a/tests/data/Cell/CoordinateIsInsideRangeException.php b/tests/data/Cell/CoordinateIsInsideRangeException.php
new file mode 100644
index 0000000000..2ec8ca56c2
--- /dev/null
+++ b/tests/data/Cell/CoordinateIsInsideRangeException.php
@@ -0,0 +1,8 @@
+
Date: Sat, 18 Nov 2023 15:24:18 +0100
Subject: [PATCH 11/20] Use case insentive comparison to get sheet name (#3791)
---
src/PhpSpreadsheet/Spreadsheet.php | 2 +-
tests/PhpSpreadsheetTests/SpreadsheetTest.php | 17 +++++++++++++++++
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php
index fa8cbcca7a..bc1a103df6 100644
--- a/src/PhpSpreadsheet/Spreadsheet.php
+++ b/src/PhpSpreadsheet/Spreadsheet.php
@@ -701,7 +701,7 @@ public function getSheetByName($worksheetName)
{
$worksheetCount = count($this->workSheetCollection);
for ($i = 0; $i < $worksheetCount; ++$i) {
- if ($this->workSheetCollection[$i]->getTitle() === trim($worksheetName, "'")) {
+ if (strcasecmp($this->workSheetCollection[$i]->getTitle(), trim($worksheetName, "'")) === 0) {
return $this->workSheetCollection[$i];
}
}
diff --git a/tests/PhpSpreadsheetTests/SpreadsheetTest.php b/tests/PhpSpreadsheetTests/SpreadsheetTest.php
index fe4e986439..12fe4abcb1 100644
--- a/tests/PhpSpreadsheetTests/SpreadsheetTest.php
+++ b/tests/PhpSpreadsheetTests/SpreadsheetTest.php
@@ -70,11 +70,22 @@ public function testAddSheetDuplicateTitle(): void
{
$spreadsheet = $this->getSpreadsheet();
$this->expectException(Exception::class);
+ $this->expectExceptionMessage("Workbook already contains a worksheet named 'someSheet2'. Rename this worksheet first.");
$sheet = new Worksheet();
$sheet->setTitle('someSheet2');
$spreadsheet->addSheet($sheet);
}
+ public function testAddSheetDuplicateTitleWithDifferentCase(): void
+ {
+ $spreadsheet = $this->getSpreadsheet();
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessage("Workbook already contains a worksheet named 'SomeSheet2'. Rename this worksheet first.");
+ $sheet = new Worksheet();
+ $sheet->setTitle('SomeSheet2');
+ $spreadsheet->addSheet($sheet);
+ }
+
public function testAddSheetNoAdjustActive(): void
{
$spreadsheet = $this->getSpreadsheet();
@@ -101,6 +112,7 @@ public function testRemoveSheetIndexTooHigh(): void
{
$spreadsheet = $this->getSpreadsheet();
$this->expectException(Exception::class);
+ $this->expectExceptionMessage('You tried to remove a sheet by the out of bounds index: 4. The actual number of sheets is 3.');
$spreadsheet->removeSheetByIndex(4);
}
@@ -126,6 +138,7 @@ public function testGetSheetIndexTooHigh(): void
{
$spreadsheet = $this->getSpreadsheet();
$this->expectException(Exception::class);
+ $this->expectExceptionMessage('Your requested sheet index: 4 is out of bounds. The actual number of sheets is 3.');
$spreadsheet->getSheet(4);
}
@@ -133,6 +146,7 @@ public function testGetIndexNonExistent(): void
{
$spreadsheet = $this->getSpreadsheet();
$this->expectException(Exception::class);
+ $this->expectExceptionMessage('Sheet does not exist.');
$sheet = new Worksheet();
$sheet->setTitle('someSheet4');
$spreadsheet->getIndex($sheet);
@@ -178,6 +192,7 @@ public function testSetActiveSheetIndexTooHigh(): void
{
$spreadsheet = $this->getSpreadsheet();
$this->expectException(Exception::class);
+ $this->expectExceptionMessage('You tried to set a sheet active by the out of bounds index: 4. The actual number of sheets is 3.');
$spreadsheet->setActiveSheetIndex(4);
}
@@ -185,6 +200,7 @@ public function testSetActiveSheetNoSuchName(): void
{
$spreadsheet = $this->getSpreadsheet();
$this->expectException(Exception::class);
+ $this->expectExceptionMessage('Workbook does not contain sheet:unknown');
$spreadsheet->setActiveSheetIndexByName('unknown');
}
@@ -213,6 +229,7 @@ public function testAddExternal(): void
public function testAddExternalDuplicateName(): void
{
$this->expectException(Exception::class);
+ $this->expectExceptionMessage("Workbook already contains a worksheet named 'someSheet1'. Rename the external sheet first.");
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->createSheet()->setTitle('someSheet1');
$sheet->getCell('A1')->setValue(1);
From 5a60ba45abfb975695d4353d34ee1debfe0ef2d2 Mon Sep 17 00:00:00 2001
From: oleibman <10341515+oleibman@users.noreply.github.com>
Date: Tue, 21 Nov 2023 07:06:12 -0800
Subject: [PATCH 12/20] Sheet Background Images (#3795)
* Sheet Background Images
Fix #1649, a 3-year-old issue long marked "stale". Excel supports background images on sheets; now PhpSpreadsheet will as well. Support is limited to Xlsx (read and write) and Html (write only). As far as I can tell, Excel Xml and Gnumeric do not support this, nor, of course, do Csv and Slk; Excel Xls does, but, as usual, how to handle it in BIFF format is a mystery; LibreOffice ODS supports it differently than Excel, and this is just another of many ODS style properties not currently supported by PhpSpreadsheet.
* Update CHANGELOG.md
---
CHANGELOG.md | 3 ++
src/PhpSpreadsheet/Reader/Xlsx.php | 22 ++++++++
src/PhpSpreadsheet/Worksheet/Worksheet.php | 43 +++++++++++++++
src/PhpSpreadsheet/Writer/Html.php | 5 ++
src/PhpSpreadsheet/Writer/Xlsx.php | 2 +-
.../Writer/Xlsx/ContentTypes.php | 7 +++
src/PhpSpreadsheet/Writer/Xlsx/Rels.php | 16 +++++-
src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 44 ++++++++++++---
.../Writer/Html/BackgroundImageTest.php | 31 +++++++++++
.../Writer/Xlsx/BackgroundImageTest.php | 50 ++++++++++++++++++
tests/data/Writer/XLSX/backgroundtest.png | Bin 0 -> 429 bytes
11 files changed, 214 insertions(+), 9 deletions(-)
create mode 100644 tests/PhpSpreadsheetTests/Writer/Html/BackgroundImageTest.php
create mode 100644 tests/PhpSpreadsheetTests/Writer/Xlsx/BackgroundImageTest.php
create mode 100644 tests/data/Writer/XLSX/backgroundtest.png
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6decabdc31..274981073e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Support for Conditional Formatting Color Scale. [PR #3738](https://github.com/PHPOffice/PhpSpreadsheet/pull/3738)
- Support Additional Tags in Helper/Html. [Issue #3751](https://github.com/PHPOffice/PhpSpreadsheet/issues/3751) [PR #3752](https://github.com/PHPOffice/PhpSpreadsheet/pull/3752)
- Writer ODS : Write Border Style for cells [Issue #3690](https://github.com/PHPOffice/PhpSpreadsheet/issues/3690) [PR #3693](https://github.com/PHPOffice/PhpSpreadsheet/pull/3693)
+- Sheet Background Images [Issue #1649](https://github.com/PHPOffice/PhpSpreadsheet/issues/1649) [PR #3795](https://github.com/PHPOffice/PhpSpreadsheet/pull/3795)
+- Check if Coordinate is Inside Range [PR #3779](https://github.com/PHPOffice/PhpSpreadsheet/pull/3779)
### Changed
@@ -72,6 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Theme File Missing but Referenced in Spreadsheet. [Issue #3770](https://github.com/PHPOffice/PhpSpreadsheet/issues/3770) [PR #3772](https://github.com/PHPOffice/PhpSpreadsheet/pull/3772)
- Slk Shared Formulas. [Issue #2267](https://github.com/PHPOffice/PhpSpreadsheet/issues/2267) [PR #3776](https://github.com/PHPOffice/PhpSpreadsheet/pull/3776)
- Html omitting some charts. [Issue #3767](https://github.com/PHPOffice/PhpSpreadsheet/issues/3767) [PR #3771](https://github.com/PHPOffice/PhpSpreadsheet/pull/3771)
+- Case Insensitive Comparison for Sheet Names [PR #3791](https://github.com/PHPOffice/PhpSpreadsheet/pull/3791)
## 1.29.0 - 2023-06-15
diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php
index 99fb3937d5..73ac43afd8 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx.php
@@ -973,6 +973,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
if ($this->readDataOnly === false) {
$this->readAutoFilter($xmlSheetNS, $docSheet);
+ $this->readBackgroundImage($xmlSheetNS, $docSheet, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels');
}
$this->readTables($xmlSheetNS, $docSheet, $dir, $fileWorksheet, $zip, $mainNS);
@@ -2201,6 +2202,27 @@ private function readAutoFilter(
}
}
+ private function readBackgroundImage(
+ SimpleXMLElement $xmlSheet,
+ Worksheet $docSheet,
+ string $relsName
+ ): void {
+ if ($xmlSheet && $xmlSheet->picture) {
+ $id = (string) self::getArrayItem(self::getAttributes($xmlSheet->picture, Namespaces::SCHEMA_OFFICE_DOCUMENT), 'id');
+ $rels = $this->loadZip($relsName);
+ foreach ($rels->Relationship as $rel) {
+ $attrs = $rel->attributes() ?? [];
+ $rid = (string) ($attrs['Id'] ?? '');
+ $target = (string) ($attrs['Target'] ?? '');
+ if ($rid === $id && substr($target, 0, 2) === '..') {
+ $target = 'xl' . substr($target, 2);
+ $content = $this->getFromZipArchive($this->zip, $target);
+ $docSheet->setBackgroundImage($content);
+ }
+ }
+ }
+ }
+
private function readTables(
SimpleXMLElement $xmlSheet,
Worksheet $docSheet,
diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php
index 8ed1d22eb7..89c5458525 100644
--- a/src/PhpSpreadsheet/Worksheet/Worksheet.php
+++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php
@@ -3879,4 +3879,47 @@ private function getXfIndex(string $coordinate): ?int
return $xfIndex;
}
+
+ private string $backgroundImage = '';
+
+ private string $backgroundMime = '';
+
+ private string $backgroundExtension = '';
+
+ public function getBackgroundImage(): string
+ {
+ return $this->backgroundImage;
+ }
+
+ public function getBackgroundMime(): string
+ {
+ return $this->backgroundMime;
+ }
+
+ public function getBackgroundExtension(): string
+ {
+ return $this->backgroundExtension;
+ }
+
+ /**
+ * Set background image.
+ * Used on read/write for Xlsx.
+ * Used on write for Html.
+ *
+ * @param string $backgroundImage Image represented as a string, e.g. results of file_get_contents
+ */
+ public function setBackgroundImage(string $backgroundImage): self
+ {
+ $imageArray = getimagesizefromstring($backgroundImage) ?: ['mime' => ''];
+ $mime = $imageArray['mime'];
+ if ($mime !== '') {
+ $extension = explode('/', $mime);
+ $extension = $extension[1];
+ $this->backgroundImage = $backgroundImage;
+ $this->backgroundMime = $mime;
+ $this->backgroundExtension = $extension;
+ }
+
+ return $this;
+ }
}
diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php
index ecb99219d1..b4547637cf 100644
--- a/src/PhpSpreadsheet/Writer/Html.php
+++ b/src/PhpSpreadsheet/Writer/Html.php
@@ -878,6 +878,11 @@ private function buildCssPerSheet(Worksheet $sheet, array &$css): void
$css["table.sheet$sheetIndex"]['page-break-inside'] = 'avoid';
$css["table.sheet$sheetIndex"]['break-inside'] = 'avoid';
}
+ $picture = $sheet->getBackgroundImage();
+ if ($picture !== '') {
+ $base64 = base64_encode($picture);
+ $css["table.sheet$sheetIndex"]['background-image'] = 'url(data:' . $sheet->getBackgroundMime() . ';base64,' . $base64 . ')';
+ }
// Build styles
// Calculate column widths
diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php
index c18bc85862..e127ccac90 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx.php
@@ -365,7 +365,7 @@ public function save($filename, int $flags = 0): void
// Add worksheet relationships (drawings, ...)
for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
// Add relationships
- $zipContent['xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts, $tableRef1);
+ $zipContent['xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts, $tableRef1, $zipContent);
// Add unparsedLoadedData
$sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName();
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php
index 8faa3ffc2d..3357e657f3 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php
@@ -186,6 +186,13 @@ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = fal
}
}
}
+
+ $bgImage = $spreadsheet->getSheet($i)->getBackgroundImage();
+ $mimeType = $spreadsheet->getSheet($i)->getBackgroundMime();
+ $extension = $spreadsheet->getSheet($i)->getBackgroundExtension();
+ if ($bgImage !== '' && !isset($aMediaContentTypes[$mimeType])) {
+ $this->writeDefaultContentType($objWriter, $extension, $mimeType);
+ }
}
// unparsed defaults
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php
index 977dc9abf6..dce5882ce6 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php
@@ -169,7 +169,7 @@ public function writeWorkbookRelationships(Spreadsheet $spreadsheet)
*
* @return string XML Output
*/
- public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, $worksheetId = 1, $includeCharts = false, $tableRef = 1)
+ public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, $worksheetId = 1, $includeCharts = false, $tableRef = 1, array &$zipContent = [])
{
// Create XML writer
$objWriter = null;
@@ -221,6 +221,20 @@ public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\
);
}
+ $backgroundImage = $worksheet->getBackgroundImage();
+ if ($backgroundImage !== '') {
+ $rId = 'Bg';
+ $uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999));
+ $relPath = "../media/$uniqueName." . $worksheet->getBackgroundExtension();
+ $this->writeRelationship(
+ $objWriter,
+ $rId,
+ Namespaces::IMAGE,
+ $relPath
+ );
+ $zipContent["xl/media/$uniqueName." . $worksheet->getBackgroundExtension()] = $backgroundImage;
+ }
+
// Write hyperlink relationships?
$i = 1;
foreach ($worksheet->getHyperlinkCollection() as $hyperlink) {
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
index 7efaaf9396..de9cc49e7e 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
@@ -139,6 +139,9 @@ public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, array $string
// IgnoredErrors
$this->writeIgnoredErrors($objWriter);
+ // BackgroundImage must come after ignored, before table
+ $this->writeBackgroundImage($objWriter, $worksheet);
+
// Table
$this->writeTable($objWriter, $worksheet);
@@ -1042,6 +1045,9 @@ private function writeAutoFilter(XMLWriter $objWriter, PhpspreadsheetWorksheet $
private function writeTable(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
{
$tableCount = $worksheet->getTableCollection()->count();
+ if ($tableCount === 0) {
+ return;
+ }
$objWriter->startElement('tableParts');
$objWriter->writeAttribute('count', (string) $tableCount);
@@ -1055,6 +1061,18 @@ private function writeTable(XMLWriter $objWriter, PhpspreadsheetWorksheet $works
$objWriter->endElement();
}
+ /**
+ * Write Background Image.
+ */
+ private function writeBackgroundImage(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
+ {
+ if ($worksheet->getBackgroundImage() !== '') {
+ $objWriter->startElement('picture');
+ $objWriter->writeAttribute('r:id', 'rIdBg');
+ $objWriter->endElement();
+ }
+ }
+
/**
* Write PageSetup.
*/
@@ -1098,19 +1116,31 @@ private function writePageSetup(XMLWriter $objWriter, PhpspreadsheetWorksheet $w
private function writeHeaderFooter(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
{
// headerFooter
+ $headerFooter = $worksheet->getHeaderFooter();
+ $oddHeader = $headerFooter->getOddHeader();
+ $oddFooter = $headerFooter->getOddFooter();
+ $evenHeader = $headerFooter->getEvenHeader();
+ $evenFooter = $headerFooter->getEvenFooter();
+ $firstHeader = $headerFooter->getFirstHeader();
+ $firstFooter = $headerFooter->getFirstFooter();
+ if ("$oddHeader$oddFooter$evenHeader$evenFooter$firstHeader$firstFooter" === '') {
+ return;
+ }
+
$objWriter->startElement('headerFooter');
$objWriter->writeAttribute('differentOddEven', ($worksheet->getHeaderFooter()->getDifferentOddEven() ? 'true' : 'false'));
$objWriter->writeAttribute('differentFirst', ($worksheet->getHeaderFooter()->getDifferentFirst() ? 'true' : 'false'));
$objWriter->writeAttribute('scaleWithDoc', ($worksheet->getHeaderFooter()->getScaleWithDocument() ? 'true' : 'false'));
$objWriter->writeAttribute('alignWithMargins', ($worksheet->getHeaderFooter()->getAlignWithMargins() ? 'true' : 'false'));
- $objWriter->writeElement('oddHeader', $worksheet->getHeaderFooter()->getOddHeader());
- $objWriter->writeElement('oddFooter', $worksheet->getHeaderFooter()->getOddFooter());
- $objWriter->writeElement('evenHeader', $worksheet->getHeaderFooter()->getEvenHeader());
- $objWriter->writeElement('evenFooter', $worksheet->getHeaderFooter()->getEvenFooter());
- $objWriter->writeElement('firstHeader', $worksheet->getHeaderFooter()->getFirstHeader());
- $objWriter->writeElement('firstFooter', $worksheet->getHeaderFooter()->getFirstFooter());
- $objWriter->endElement();
+ self::writeElementIf($objWriter, $oddHeader !== '', 'oddHeader', $oddHeader);
+ self::writeElementIf($objWriter, $oddFooter !== '', 'oddFooter', $oddFooter);
+ self::writeElementIf($objWriter, $evenHeader !== '', 'evenHeader', $evenHeader);
+ self::writeElementIf($objWriter, $evenFooter !== '', 'evenFooter', $evenFooter);
+ self::writeElementIf($objWriter, $firstHeader !== '', 'firstHeader', $firstHeader);
+ self::writeElementIf($objWriter, $firstFooter !== '', 'firstFooter', $firstFooter);
+
+ $objWriter->endElement(); // headerFooter
}
/**
diff --git a/tests/PhpSpreadsheetTests/Writer/Html/BackgroundImageTest.php b/tests/PhpSpreadsheetTests/Writer/Html/BackgroundImageTest.php
new file mode 100644
index 0000000000..df73c9cd86
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Html/BackgroundImageTest.php
@@ -0,0 +1,31 @@
+getActiveSheet();
+ $sheet->getCell('A1')->setValue(1);
+ $sheet->getCell('B1')->setValue(2);
+ $sheet->getCell('A2')->setValue(3);
+ $sheet->getCell('B2')->setValue(4);
+ $imageFile = 'tests/data/Writer/XLSX/backgroundtest.png';
+ $image = (string) file_get_contents($imageFile);
+ $sheet->setBackgroundImage($image);
+ self::assertSame('image/png', $sheet->getBackgroundMime());
+ self::assertSame('png', $sheet->getBackgroundExtension());
+ $writer = new Html($spreadsheet);
+ $header = $writer->generateHTMLHeader(true);
+ self::assertStringContainsString('table.sheet0 { background-image:url(data:image/png;base64,', $header);
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/BackgroundImageTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/BackgroundImageTest.php
new file mode 100644
index 0000000000..73a32db3ae
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/BackgroundImageTest.php
@@ -0,0 +1,50 @@
+getActiveSheet();
+ $sheet->getCell('A1')->setValue(1);
+ $sheet->getCell('B1')->setValue(2);
+ $sheet->getCell('A2')->setValue(3);
+ $sheet->getCell('B2')->setValue(4);
+ $imageFile = 'tests/data/Writer/XLSX/backgroundtest.png';
+ $image = (string) file_get_contents($imageFile);
+ $sheet->setBackgroundImage($image);
+ self::assertSame('image/png', $sheet->getBackgroundMime());
+ self::assertSame('png', $sheet->getBackgroundExtension());
+
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
+ $spreadsheet->disconnectWorksheets();
+ $reloadedWorksheet = $reloadedSpreadsheet->getActiveSheet();
+ self::assertSame($image, $reloadedWorksheet->getBackgroundImage());
+ self::assertSame('image/png', $reloadedWorksheet->getBackgroundMime());
+ self::assertSame('png', $reloadedWorksheet->getBackgroundExtension());
+ self::assertSame(2, $reloadedWorksheet->getCell('B1')->getValue());
+ $reloadedSpreadsheet->disconnectWorksheets();
+ }
+
+ public function testInvalidImage(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $sheet->getCell('A1')->setValue(1);
+ $imageFile = __FILE__;
+ $image = (string) file_get_contents($imageFile);
+ self::assertNotSame('', $image);
+ $sheet->setBackgroundImage($image);
+ self::assertSame('', $sheet->getBackgroundImage());
+ self::assertSame('', $sheet->getBackgroundMime());
+ self::assertSame('', $sheet->getBackgroundExtension());
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/data/Writer/XLSX/backgroundtest.png b/tests/data/Writer/XLSX/backgroundtest.png
new file mode 100644
index 0000000000000000000000000000000000000000..3f14e4b3f0b1b3132884e84de4efc5a18278246e
GIT binary patch
literal 429
zcmeAS@N?(olHy`uVBq!ia0vp^DIm_i+ALso$kF{Uk
zIkRO=oSDD++jq-fNRLZ~gQ7H5+GdG%fSakEn?$7t_E4E736{zL
literal 0
HcmV?d00001
From 7fbf5c494fd2a56c78f9cab410cb06eae474e9b1 Mon Sep 17 00:00:00 2001
From: oleibman <10341515+oleibman@users.noreply.github.com>
Date: Thu, 30 Nov 2023 07:31:42 -0800
Subject: [PATCH 13/20] Anticipate Dependabot (#3805)
These changes will be suggested tomorrow. It's better to take care of them in advance.
---
composer.lock | 92 +++++++++----------
phpstan-baseline.neon | 25 -----
.../Calculation/Calculation.php | 2 +-
.../Calculation/FormulaParser.php | 24 ++---
src/PhpSpreadsheet/Worksheet/Validations.php | 8 +-
tests/bootstrap.php | 2 +-
6 files changed, 64 insertions(+), 89 deletions(-)
diff --git a/composer.lock b/composer.lock
index e2e3341b20..a3d742130f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1358,12 +1358,12 @@
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/composer-installer.git",
- "reference": "b49fdb59dd34284bd347435a959f2093894d1ac8"
+ "reference": "eda4086d152f545894f94018cb208f38dc251172"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/b49fdb59dd34284bd347435a959f2093894d1ac8",
- "reference": "b49fdb59dd34284bd347435a959f2093894d1ac8",
+ "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/eda4086d152f545894f94018cb208f38dc251172",
+ "reference": "eda4086d152f545894f94018cb208f38dc251172",
"shasum": ""
},
"require": {
@@ -1428,7 +1428,7 @@
"issues": "https://github.com/PHPCSStandards/composer-installer/issues",
"source": "https://github.com/PHPCSStandards/composer-installer"
},
- "time": "2023-09-22T07:48:18+00:00"
+ "time": "2023-11-26T02:51:26+00:00"
},
{
"name": "doctrine/instantiator",
@@ -1564,50 +1564,50 @@
},
{
"name": "friendsofphp/php-cs-fixer",
- "version": "v3.37.1",
+ "version": "v3.40.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
- "reference": "c3fe76976081ab871aa654e872da588077e19679"
+ "reference": "27d2b3265b5d550ec411b4319967ae7cfddfb2e0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/c3fe76976081ab871aa654e872da588077e19679",
- "reference": "c3fe76976081ab871aa654e872da588077e19679",
+ "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/27d2b3265b5d550ec411b4319967ae7cfddfb2e0",
+ "reference": "27d2b3265b5d550ec411b4319967ae7cfddfb2e0",
"shasum": ""
},
"require": {
- "composer/semver": "^3.3",
+ "composer/semver": "^3.4",
"composer/xdebug-handler": "^3.0.3",
"ext-json": "*",
"ext-tokenizer": "*",
"php": "^7.4 || ^8.0",
"sebastian/diff": "^4.0 || ^5.0",
- "symfony/console": "^5.4 || ^6.0",
- "symfony/event-dispatcher": "^5.4 || ^6.0",
- "symfony/filesystem": "^5.4 || ^6.0",
- "symfony/finder": "^5.4 || ^6.0",
- "symfony/options-resolver": "^5.4 || ^6.0",
- "symfony/polyfill-mbstring": "^1.27",
- "symfony/polyfill-php80": "^1.27",
- "symfony/polyfill-php81": "^1.27",
- "symfony/process": "^5.4 || ^6.0",
- "symfony/stopwatch": "^5.4 || ^6.0"
+ "symfony/console": "^5.4 || ^6.0 || ^7.0",
+ "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0",
+ "symfony/filesystem": "^5.4 || ^6.0 || ^7.0",
+ "symfony/finder": "^5.4 || ^6.0 || ^7.0",
+ "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0",
+ "symfony/polyfill-mbstring": "^1.28",
+ "symfony/polyfill-php80": "^1.28",
+ "symfony/polyfill-php81": "^1.28",
+ "symfony/process": "^5.4 || ^6.0 || ^7.0",
+ "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0"
},
"require-dev": {
"facile-it/paraunit": "^1.3 || ^2.0",
"justinrainbow/json-schema": "^5.2",
- "keradus/cli-executor": "^2.0",
+ "keradus/cli-executor": "^2.1",
"mikey179/vfsstream": "^1.6.11",
- "php-coveralls/php-coveralls": "^2.5.3",
+ "php-coveralls/php-coveralls": "^2.7",
"php-cs-fixer/accessible-object": "^1.1",
- "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2",
- "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1",
- "phpspec/prophecy": "^1.16",
+ "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.4",
+ "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.4",
+ "phpspec/prophecy": "^1.17",
"phpspec/prophecy-phpunit": "^2.0",
- "phpunit/phpunit": "^9.5",
- "symfony/phpunit-bridge": "^6.2.3",
- "symfony/yaml": "^5.4 || ^6.0"
+ "phpunit/phpunit": "^9.6",
+ "symfony/phpunit-bridge": "^6.3.8 || ^7.0",
+ "symfony/yaml": "^5.4 || ^6.0 || ^7.0"
},
"suggest": {
"ext-dom": "For handling output formats in XML",
@@ -1645,7 +1645,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
- "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.37.1"
+ "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.40.0"
},
"funding": [
{
@@ -1653,7 +1653,7 @@
"type": "github"
}
],
- "time": "2023-10-29T20:51:23+00:00"
+ "time": "2023-11-26T09:25:53+00:00"
},
{
"name": "masterminds/html5",
@@ -1770,16 +1770,16 @@
},
{
"name": "mpdf/mpdf",
- "version": "v8.2.0",
+ "version": "v8.2.2",
"source": {
"type": "git",
"url": "https://github.com/mpdf/mpdf.git",
- "reference": "170a236a588d177c2aa7447ce490a030ca68e6f4"
+ "reference": "596a87b876d7793be7be060a8ac13424de120dd5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/mpdf/mpdf/zipball/170a236a588d177c2aa7447ce490a030ca68e6f4",
- "reference": "170a236a588d177c2aa7447ce490a030ca68e6f4",
+ "url": "https://api.github.com/repos/mpdf/mpdf/zipball/596a87b876d7793be7be060a8ac13424de120dd5",
+ "reference": "596a87b876d7793be7be060a8ac13424de120dd5",
"shasum": ""
},
"require": {
@@ -1789,7 +1789,7 @@
"mpdf/psr-log-aware-trait": "^2.0 || ^3.0",
"myclabs/deep-copy": "^1.7",
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
- "php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0 || ~8.2.0",
+ "php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0",
"psr/http-message": "^1.0 || ^2.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"setasign/fpdi": "^2.1"
@@ -1847,7 +1847,7 @@
"type": "custom"
}
],
- "time": "2023-09-01T11:44:52+00:00"
+ "time": "2023-11-07T13:52:14+00:00"
},
{
"name": "mpdf/psr-http-message-shim",
@@ -2371,16 +2371,16 @@
},
{
"name": "phpstan/phpstan",
- "version": "1.10.40",
+ "version": "1.10.46",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d"
+ "reference": "90d3d25c5b98b8068916bbf08ce42d5cb6c54e70"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/93c84b5bf7669920d823631e39904d69b9c7dc5d",
- "reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/90d3d25c5b98b8068916bbf08ce42d5cb6c54e70",
+ "reference": "90d3d25c5b98b8068916bbf08ce42d5cb6c54e70",
"shasum": ""
},
"require": {
@@ -2429,7 +2429,7 @@
"type": "tidelift"
}
],
- "time": "2023-10-30T14:48:31+00:00"
+ "time": "2023-11-28T14:57:26+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
@@ -5327,16 +5327,16 @@
},
{
"name": "theseer/tokenizer",
- "version": "1.2.1",
+ "version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
- "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
+ "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
- "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
+ "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"shasum": ""
},
"require": {
@@ -5365,7 +5365,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
- "source": "https://github.com/theseer/tokenizer/tree/1.2.1"
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.2"
},
"funding": [
{
@@ -5373,7 +5373,7 @@
"type": "github"
}
],
- "time": "2021-07-28T10:34:58+00:00"
+ "time": "2023-11-20T00:12:19+00:00"
}
],
"aliases": [],
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index c7cbf42ca9..c9762340b2 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -1,31 +1,6 @@
parameters:
ignoreErrors:
- -
- message: "#^Cannot call method getTokenSubType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#"
- count: 4
- path: src/PhpSpreadsheet/Calculation/FormulaParser.php
-
- -
- message: "#^Cannot call method getTokenType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#"
- count: 8
- path: src/PhpSpreadsheet/Calculation/FormulaParser.php
-
-
message: "#^Binary operation \"/\" between float and array\\|float\\|int\\|string results in an error\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php
-
- -
- message: "#^Offset 2 does not exist on array\\{int, int, int, int\\}\\|array\\{int, int\\}\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Worksheet/Validations.php
-
- -
- message: "#^Offset 3 does not exist on array\\{int, int, int, int\\}\\|array\\{int, int\\}\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Worksheet/Validations.php
-
- -
- message: "#^Parameter \\#2 \\$value of function ini_set expects string, int given\\.$#"
- count: 1
- path: tests/bootstrap.php
diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php
index d8d0557399..34062a5319 100644
--- a/src/PhpSpreadsheet/Calculation/Calculation.php
+++ b/src/PhpSpreadsheet/Calculation/Calculation.php
@@ -4715,7 +4715,7 @@ private function processTokenStack(mixed $tokens, $cellID = null, ?Cell $cell =
if (($operand2Data = $stack->pop()) === null) {
return $this->raiseFormulaError('Internal error - Operand value missing from stack');
}
- if (($operand1Data = $stack->pop()) === null) {
+ if (($operand1Data = $stack->pop()) === null) { // @phpstan-ignore-line
return $this->raiseFormulaError('Internal error - Operand value missing from stack');
}
diff --git a/src/PhpSpreadsheet/Calculation/FormulaParser.php b/src/PhpSpreadsheet/Calculation/FormulaParser.php
index cffce179c6..0be1ca8ac9 100644
--- a/src/PhpSpreadsheet/Calculation/FormulaParser.php
+++ b/src/PhpSpreadsheet/Calculation/FormulaParser.php
@@ -530,12 +530,12 @@ private function parseToTokens(): void
if ($i == 0) {
$token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX);
} elseif (
- (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION)
- && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
- || (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION)
- && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
- || ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX)
- || ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
+ (($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION)
+ && ($previousToken?->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
+ || (($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION)
+ && ($previousToken?->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
+ || ($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX)
+ || ($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
) {
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
} else {
@@ -551,12 +551,12 @@ private function parseToTokens(): void
if ($i == 0) {
continue;
} elseif (
- (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION)
- && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
- || (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION)
- && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
- || ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX)
- || ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
+ (($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION)
+ && ($previousToken?->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
+ || (($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION)
+ && ($previousToken?->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP))
+ || ($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX)
+ || ($previousToken?->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
) {
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
} else {
diff --git a/src/PhpSpreadsheet/Worksheet/Validations.php b/src/PhpSpreadsheet/Worksheet/Validations.php
index d6e5582809..9ce7652690 100644
--- a/src/PhpSpreadsheet/Worksheet/Validations.php
+++ b/src/PhpSpreadsheet/Worksheet/Validations.php
@@ -81,14 +81,14 @@ public static function validateCellRange(AddressRange|array|string $cellRange):
if (is_array($cellRange)) {
switch (count($cellRange)) {
- case 2:
+ case 4:
$from = [$cellRange[0], $cellRange[1]];
- $to = [$cellRange[0], $cellRange[1]];
+ $to = [$cellRange[2], $cellRange[3]];
break;
- case 4:
+ case 2:
$from = [$cellRange[0], $cellRange[1]];
- $to = [$cellRange[2], $cellRange[3]];
+ $to = [$cellRange[0], $cellRange[1]];
break;
default:
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 4cd23357de..5b48e70f9b 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -32,6 +32,6 @@ function phpunit10ErrorHandler(int $errno, string $errstr, string $filename, int
}
if (!method_exists(\PHPUnit\Framework\TestCase::class, 'setOutputCallback')) {
- ini_set('error_reporting', E_ALL);
+ ini_set('error_reporting', (string) E_ALL);
set_error_handler('phpunit10ErrorHandler');
}
From 009e00981151275ea1cdd521e730c9cbe497cee2 Mon Sep 17 00:00:00 2001
From: oleibman <10341515+oleibman@users.noreply.github.com>
Date: Thu, 30 Nov 2023 08:01:56 -0800
Subject: [PATCH 14/20] Chart Dynamic Title and Special Font Properties (#3800)
* Chart Dynamic Title and Special Font Properties
Fix #3797. Excel allows a Chart Title to be a formula, albeit a very rigidly limited one. It can only be a reference to a single cell, and the worksheet name must be specified, and the column and row must be absolute. Methods are added to Chart/Title to accommodate this (and styling for it). This will be handled for input/output for Xlsx, and for output for Html.
The sample file which was submitted with this issue demonstrated that something else was missing. When setting the font for a chart title in Excel, you can specify all-caps or small-caps, options not available for most cell formatting. These are now added.
The sample file also fell into the category of spreadsheets which lose one or more charts when converted to Html. I have redone the "extend rows and charts" logic in Html Writer. It is now clearer (I hope) and more efficient, and hopefully this problem will not arise again.
* Scrutinizer 50/50
One false positive, one correct "unused parameter".
---
samples/Chart/32_Chart_read_write.php | 2 +-
samples/Chart/37_Chart_dynamic_title.php | 83 ++++++++++
samples/templates/37dynamictitle.xlsx | Bin 0 -> 10153 bytes
src/PhpSpreadsheet/Chart/Title.php | 58 ++++++-
src/PhpSpreadsheet/Helper/Sample.php | 19 ++-
src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 21 ++-
src/PhpSpreadsheet/Settings.php | 5 +
src/PhpSpreadsheet/Style/Font.php | 31 ++++
src/PhpSpreadsheet/Writer/Html.php | 150 +++++++-----------
src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 69 +++++++-
.../Chart/ChartsDynamicTitleTest.php | 141 ++++++++++++++++
11 files changed, 480 insertions(+), 99 deletions(-)
create mode 100644 samples/Chart/37_Chart_dynamic_title.php
create mode 100644 samples/templates/37dynamictitle.xlsx
create mode 100644 tests/PhpSpreadsheetTests/Chart/ChartsDynamicTitleTest.php
diff --git a/samples/Chart/32_Chart_read_write.php b/samples/Chart/32_Chart_read_write.php
index a1f2f54681..42944c0ccb 100644
--- a/samples/Chart/32_Chart_read_write.php
+++ b/samples/Chart/32_Chart_read_write.php
@@ -42,7 +42,7 @@
foreach ($chartNames as $i => $chartName) {
$chart = $worksheet->getChartByName($chartName);
if ($chart->getTitle() !== null) {
- $caption = '"' . implode(' ', $chart->getTitle()->getCaption()) . '"';
+ $caption = '"' . $chart->getTitle()->getCaptionText($spreadsheet) . '"';
} else {
$caption = 'Untitled';
}
diff --git a/samples/Chart/37_Chart_dynamic_title.php b/samples/Chart/37_Chart_dynamic_title.php
new file mode 100644
index 0000000000..b8b801faae
--- /dev/null
+++ b/samples/Chart/37_Chart_dynamic_title.php
@@ -0,0 +1,83 @@
+log('File ' . $inputFileNameShort . ' does not exist');
+
+ continue;
+ }
+ $reader = IOFactory::createReader($inputFileType);
+ $reader->setIncludeCharts(true);
+ $callStartTime = microtime(true);
+ $spreadsheet = $reader->load($inputFileName);
+ $helper->logRead($inputFileType, $inputFileName, $callStartTime);
+
+ $helper->log('Iterate worksheets looking at the charts');
+ foreach ($spreadsheet->getWorksheetIterator() as $worksheet) {
+ $sheetName = $worksheet->getTitle();
+ $worksheet->getCell('A1')->setValue('Changed Title');
+ $helper->log('Worksheet: ' . $sheetName);
+
+ $chartNames = $worksheet->getChartNames();
+ if (empty($chartNames)) {
+ $helper->log(' There are no charts in this worksheet');
+ } else {
+ natsort($chartNames);
+ foreach ($chartNames as $i => $chartName) {
+ $chart = $worksheet->getChartByName($chartName);
+ if ($chart->getTitle() !== null) {
+ $caption = '"' . $chart->getTitle()->getCaptionText($spreadsheet) . '"';
+ } else {
+ $caption = 'Untitled';
+ }
+ $helper->log(' ' . $chartName . ' - ' . $caption);
+ $indentation = str_repeat(' ', strlen($chartName) + 3);
+ $groupCount = $chart->getPlotArea()->getPlotGroupCount();
+ if ($groupCount == 1) {
+ $chartType = $chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType();
+ $helper->log($indentation . ' ' . $chartType);
+ $helper->renderChart($chart, __FILE__, $spreadsheet);
+ } else {
+ $chartTypes = [];
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $chartTypes[] = $chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
+ }
+ $chartTypes = array_unique($chartTypes);
+ if (count($chartTypes) == 1) {
+ $chartType = 'Multiple Plot ' . array_pop($chartTypes);
+ $helper->log($indentation . ' ' . $chartType);
+ $helper->renderChart($chart, __FILE__);
+ } elseif (count($chartTypes) == 0) {
+ $helper->log($indentation . ' *** Type not yet implemented');
+ } else {
+ $helper->log($indentation . ' Combination Chart');
+ $helper->renderChart($chart, __FILE__);
+ }
+ }
+ }
+ }
+ }
+
+ $callStartTime = microtime(true);
+ $helper->write($spreadsheet, $inputFileName, ['Xlsx'], true);
+
+ Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class);
+ $callStartTime = microtime(true);
+ $helper->write($spreadsheet, $inputFileName, ['Html'], true);
+
+ $spreadsheet->disconnectWorksheets();
+ unset($spreadsheet);
+}
diff --git a/samples/templates/37dynamictitle.xlsx b/samples/templates/37dynamictitle.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..6a5215e861439d5ae656790c90964948d86bd559
GIT binary patch
literal 10153
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≀|Sh
z9