>6Nt6t;ymmu&S2J?jxPPLYHDuPod2nsKR-z20Xg!(?ezsbIY$dW
zrc}%URBH?l^bwEt>IawW%z8s~=zU&nRoNims0v&j!m{!5*-$onTpK1T(R7bKdvSW_
zk9mKFQ=u*#L)MePtczXl4@-iW7A#&FTT(x2+8PwzF2dv{3K+{&
zz*t84Yb+bu+y5_=fie8&krDUlXC))#4E!1?@Z3$N~l`A$wJe8TBbZ_#wV
zX@rqcIuv&Ks-)v+MVc_1T5qb5SmwD>n6j{Q#9-0CQ!hoqCzjvR3n{IIvJ3FAnTqF|
z{4VnzT3Fx6V_rVQ$XUnbvg@NM6TiOaI-xey{9&t2CcEH+%%g!E0*z|I+A9PeioHCe
zR)(|%z(KJS#a_uWo_({Kv97;N@wSA}8rh}DieHpe*#~t(JUC{VYsTmk9TQ(-!)TQ-
zq&!H4$hoGdVU#uwrP(eKFDZ6X3W^p(dZf*Ew^u9h9Rzq>TsFsu;wV`1sBTFF9|tYL
zW1ocDGtiL{$j+z+7{hw2e$*&jBY!cBjt!>K3jGM_yFHEZSo=i5-_T|c9&{%1hN093
z5mjb=v_7aL=hUO~^A+V0-ovZWo2)8fU$n`swWFOK(&yOkjppnW00eSJ4>CBggFK5(#n{ETk+%G29UK;;>
zPUp8N2nZqEFXR6*weu3^<*deUBtwLM{}cZ)x$zR^We@o`3Nmoh2DZ(rN2!JasM!VSvP%&@UkNL8^IUv7s9{g)?bCzOO%&IsNX2lz_Qd&
z-SV;|^%CKw%>9i}Oa2St|0jB10=`s^zX7W${_fa+(UC6!U#hX+fEAQ~`1n_C_R{+A
z+UK_|2#7Zo2*`gZp_k@=-){bDE=Btn^FKEqMHxsSi+)ajB7^J$iHN}P^SA#8ZMLTE
literal 0
HcmV?d00001
diff --git a/src/PhpSpreadsheet/Chart/Axis.php b/src/PhpSpreadsheet/Chart/Axis.php
index 70ff1b31..bd7082ff 100644
--- a/src/PhpSpreadsheet/Chart/Axis.php
+++ b/src/PhpSpreadsheet/Chart/Axis.php
@@ -61,6 +61,7 @@ class Axis extends Properties
'horizontal_crosses' => self::HORIZONTAL_CROSSES_AUTOZERO,
'horizontal_crosses_value' => null,
'textRotation' => null,
+ 'hidden' => null,
];
/**
@@ -138,7 +139,8 @@ class Axis extends Properties
?string $maximum = null,
?string $majorUnit = null,
?string $minorUnit = null,
- ?string $textRotation = null
+ ?string $textRotation = null,
+ ?string $hidden = null
): void {
$this->axisOptions['axis_labels'] = $axisLabels;
$this->setAxisOption('horizontal_crosses_value', $horizontalCrossesValue);
@@ -151,6 +153,7 @@ class Axis extends Properties
$this->setAxisOption('major_unit', $majorUnit);
$this->setAxisOption('minor_unit', $minorUnit);
$this->setAxisOption('textRotation', $textRotation);
+ $this->setAxisOption('hidden', $hidden);
}
/**
diff --git a/src/PhpSpreadsheet/Chart/Chart.php b/src/PhpSpreadsheet/Chart/Chart.php
index 3f89e6fb..e850f502 100644
--- a/src/PhpSpreadsheet/Chart/Chart.php
+++ b/src/PhpSpreadsheet/Chart/Chart.php
@@ -144,6 +144,9 @@ class Chart
/** @var bool */
private $autoTitleDeleted = false;
+ /** @var bool */
+ private $noFill = false;
+
/**
* Create a new Chart.
* majorGridlines and minorGridlines are deprecated, moved to Axis.
@@ -747,4 +750,16 @@ class Chart
return $this;
}
+
+ public function getNoFill(): bool
+ {
+ return $this->noFill;
+ }
+
+ public function setNoFill(bool $noFill): self
+ {
+ $this->noFill = $noFill;
+
+ return $this;
+ }
}
diff --git a/src/PhpSpreadsheet/Chart/ChartColor.php b/src/PhpSpreadsheet/Chart/ChartColor.php
index 7f87e391..87f31020 100644
--- a/src/PhpSpreadsheet/Chart/ChartColor.php
+++ b/src/PhpSpreadsheet/Chart/ChartColor.php
@@ -24,15 +24,18 @@ class ChartColor
/** @var ?int */
private $alpha;
+ /** @var ?int */
+ private $brightness;
+
/**
* @param string|string[] $value
*/
- public function __construct($value = '', ?int $alpha = null, ?string $type = null)
+ public function __construct($value = '', ?int $alpha = null, ?string $type = null, ?int $brightness = null)
{
if (is_array($value)) {
$this->setColorPropertiesArray($value);
} else {
- $this->setColorProperties($value, $alpha, $type);
+ $this->setColorProperties($value, $alpha, $type, $brightness);
}
}
@@ -72,10 +75,23 @@ class ChartColor
return $this;
}
+ public function getBrightness(): ?int
+ {
+ return $this->brightness;
+ }
+
+ public function setBrightness(?int $brightness): self
+ {
+ $this->brightness = $brightness;
+
+ return $this;
+ }
+
/**
* @param null|float|int|string $alpha
+ * @param null|float|int|string $brightness
*/
- public function setColorProperties(?string $color, $alpha = null, ?string $type = null): self
+ public function setColorProperties(?string $color, $alpha = null, ?string $type = null, $brightness = null): self
{
if (empty($type) && !empty($color)) {
if (substr($color, 0, 1) === '*') {
@@ -99,6 +115,11 @@ class ChartColor
} elseif (is_numeric($alpha)) {
$this->setAlpha((int) $alpha);
}
+ if ($brightness === null) {
+ $this->setBrightness(null);
+ } elseif (is_numeric($brightness)) {
+ $this->setBrightness((int) $brightness);
+ }
return $this;
}
@@ -108,7 +129,8 @@ class ChartColor
return $this->setColorProperties(
$color['value'] ?? '',
$color['alpha'] ?? null,
- $color['type'] ?? null
+ $color['type'] ?? null,
+ $color['brightness'] ?? null
);
}
@@ -133,6 +155,8 @@ class ChartColor
$retVal = $this->type;
} elseif ($propertyName === 'alpha') {
$retVal = $this->alpha;
+ } elseif ($propertyName === 'brightness') {
+ $retVal = $this->brightness;
}
return $retVal;
diff --git a/src/PhpSpreadsheet/Chart/DataSeries.php b/src/PhpSpreadsheet/Chart/DataSeries.php
index 548145e7..5d33e96d 100644
--- a/src/PhpSpreadsheet/Chart/DataSeries.php
+++ b/src/PhpSpreadsheet/Chart/DataSeries.php
@@ -94,7 +94,7 @@ class DataSeries
private $plotCategory = [];
/**
- * Smooth Line.
+ * Smooth Line. Must be specified for both DataSeries and DataSeriesValues.
*
* @var bool
*/
diff --git a/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/src/PhpSpreadsheet/Chart/DataSeriesValues.php
index bc0e04d1..cb5fa742 100644
--- a/src/PhpSpreadsheet/Chart/DataSeriesValues.php
+++ b/src/PhpSpreadsheet/Chart/DataSeriesValues.php
@@ -536,7 +536,7 @@ class DataSeriesValues extends Properties
}
/**
- * Smooth Line.
+ * Smooth Line. Must be specified for both DataSeries and DataSeriesValues.
*
* @var bool
*/
diff --git a/src/PhpSpreadsheet/Chart/PlotArea.php b/src/PhpSpreadsheet/Chart/PlotArea.php
index 4bd49ece..ccde4bb2 100644
--- a/src/PhpSpreadsheet/Chart/PlotArea.php
+++ b/src/PhpSpreadsheet/Chart/PlotArea.php
@@ -6,6 +6,30 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class PlotArea
{
+ /**
+ * No fill in plot area (show Excel gridlines through chart).
+ *
+ * @var bool
+ */
+ private $noFill = false;
+
+ /**
+ * PlotArea Gradient Stop list.
+ * Each entry is a 2-element array.
+ * First is position in %.
+ * Second is ChartColor.
+ *
+ * @var array[]
+ */
+ private $gradientFillStops = [];
+
+ /**
+ * PlotArea Gradient Angle.
+ *
+ * @var ?float
+ */
+ private $gradientFillAngle;
+
/**
* PlotArea Layout.
*
@@ -101,4 +125,42 @@ class PlotArea
$plotSeries->refresh($worksheet);
}
}
+
+ public function setNoFill(bool $noFill): self
+ {
+ $this->noFill = $noFill;
+
+ return $this;
+ }
+
+ public function getNoFill(): bool
+ {
+ return $this->noFill;
+ }
+
+ public function setGradientFillProperties(array $gradientFillStops, ?float $gradientFillAngle): self
+ {
+ $this->gradientFillStops = $gradientFillStops;
+ $this->gradientFillAngle = $gradientFillAngle;
+
+ return $this;
+ }
+
+ /**
+ * Get gradientFillAngle.
+ */
+ public function getGradientFillAngle(): ?float
+ {
+ return $this->gradientFillAngle;
+ }
+
+ /**
+ * Get gradientFillStops.
+ *
+ * @return array
+ */
+ public function getGradientFillStops()
+ {
+ return $this->gradientFillStops;
+ }
}
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
index d49a5238..12ee0ade 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
@@ -73,8 +73,18 @@ class Chart
$xAxis = new Axis();
$yAxis = new Axis();
$autoTitleDeleted = null;
+ $chartNoFill = false;
+ $gradientArray = [];
+ $gradientLin = null;
foreach ($chartElementsC as $chartElementKey => $chartElement) {
switch ($chartElementKey) {
+ case 'spPr':
+ $possibleNoFill = $chartElementsC->spPr->children($this->aNamespace);
+ if (isset($possibleNoFill->noFill)) {
+ $chartNoFill = true;
+ }
+
+ break;
case 'chart':
foreach ($chartElement as $chartDetailsKey => $chartDetails) {
$chartDetailsC = $chartDetails->children($this->cNamespace);
@@ -95,8 +105,29 @@ class Chart
$plotAreaLayout = $XaxisLabel = $YaxisLabel = null;
$plotSeries = $plotAttributes = [];
$catAxRead = false;
+ $plotNoFill = false;
foreach ($chartDetails as $chartDetailKey => $chartDetail) {
switch ($chartDetailKey) {
+ case 'spPr':
+ $possibleNoFill = $chartDetails->spPr->children($this->aNamespace);
+ if (isset($possibleNoFill->noFill)) {
+ $plotNoFill = true;
+ }
+ if (isset($possibleNoFill->gradFill->gsLst)) {
+ foreach ($possibleNoFill->gradFill->gsLst->gs as $gradient) {
+ /** @var float */
+ $pos = self::getAttribute($gradient, 'pos', 'float');
+ $gradientArray[] = [
+ $pos / Properties::PERCENTAGE_MULTIPLIER,
+ new ChartColor($this->readColor($gradient)),
+ ];
+ }
+ }
+ if (isset($possibleNoFill->gradFill->lin)) {
+ $gradientLin = Properties::XmlToAngle((string) self::getAttribute($possibleNoFill->gradFill->lin, 'ang', 'string'));
+ }
+
+ break;
case 'layout':
$plotAreaLayout = $this->chartLayoutDetails($chartDetail);
@@ -288,6 +319,12 @@ class Chart
}
$plotArea = new PlotArea($plotAreaLayout, $plotSeries);
$this->setChartAttributes($plotAreaLayout, $plotAttributes);
+ if ($plotNoFill) {
+ $plotArea->setNoFill(true);
+ }
+ if (!empty($gradientArray)) {
+ $plotArea->setGradientFillProperties($gradientArray, $gradientLin);
+ }
break;
case 'plotVisOnly':
@@ -330,6 +367,9 @@ class Chart
}
}
$chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis);
+ if ($chartNoFill) {
+ $chart->setNoFill(true);
+ }
if (is_bool($autoTitleDeleted)) {
$chart->setAutoTitleDeleted($autoTitleDeleted);
}
@@ -1147,6 +1187,7 @@ class Chart
'type' => null,
'value' => null,
'alpha' => null,
+ 'brightness' => null,
];
foreach (ChartColor::EXCEL_COLOR_TYPES as $type) {
if (isset($colorXml->$type)) {
@@ -1159,6 +1200,13 @@ class Chart
$result['alpha'] = ChartColor::alphaFromXml($alpha);
}
}
+ if (isset($colorXml->$type->lumMod)) {
+ /** @var string */
+ $brightness = self::getAttribute($colorXml->$type->lumMod, 'val', 'string');
+ if (is_numeric($brightness)) {
+ $result['brightness'] = ChartColor::alphaFromXml($brightness);
+ }
+ }
break;
}
@@ -1236,6 +1284,9 @@ class Chart
if (!isset($whichAxis)) {
return;
}
+ if (isset($chartDetail->delete)) {
+ $whichAxis->setAxisOption('hidden', (string) self::getAttribute($chartDetail->delete, 'val', 'string'));
+ }
if (isset($chartDetail->numFmt)) {
$whichAxis->setAxisNumberProperties(
(string) self::getAttribute($chartDetail->numFmt, 'formatCode', 'string'),
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
index d9d96da6..278b64e7 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
@@ -106,11 +106,17 @@ class Chart extends WriterPart
$objWriter->writeAttribute('val', '0');
$objWriter->endElement();
- $objWriter->endElement();
+ $objWriter->endElement(); // c:chart
+ if ($chart->getNoFill()) {
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:noFill');
+ $objWriter->endElement(); // a:noFill
+ $objWriter->endElement(); // c:spPr
+ }
$this->writePrintSettings($objWriter);
- $objWriter->endElement();
+ $objWriter->endElement(); // c:chartSpace
// Return
return $objWriter->getData();
@@ -360,8 +366,35 @@ class Chart extends WriterPart
$this->writeSerAxis($objWriter, $id2, $id3);
}
}
+ $stops = $plotArea->getGradientFillStops();
+ if ($plotArea->getNoFill() || !empty($stops)) {
+ $objWriter->startElement('c:spPr');
+ if ($plotArea->getNoFill()) {
+ $objWriter->startElement('a:noFill');
+ $objWriter->endElement(); // a:noFill
+ }
+ if (!empty($stops)) {
+ $objWriter->startElement('a:gradFill');
+ $objWriter->startElement('a:gsLst');
+ foreach ($stops as $stop) {
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', (string) (Properties::PERCENTAGE_MULTIPLIER * (float) $stop[0]));
+ $this->writeColor($objWriter, $stop[1], false);
+ $objWriter->endElement(); // a:gs
+ }
+ $objWriter->endElement(); // a:gsLst
+ $angle = $plotArea->getGradientFillAngle();
+ if ($angle !== null) {
+ $objWriter->startElement('a:lin');
+ $objWriter->writeAttribute('ang', Properties::angleToXml($angle));
+ $objWriter->endElement(); // a:lin
+ }
+ $objWriter->endElement(); // a:gradFill
+ }
+ $objWriter->endElement(); // c:spPr
+ }
- $objWriter->endElement();
+ $objWriter->endElement(); // c:plotArea
}
private function writeDataLabelsBool(XMLWriter $objWriter, string $name, ?bool $value): void
@@ -492,7 +525,7 @@ class Chart extends WriterPart
$objWriter->endElement(); // c:scaling
$objWriter->startElement('c:delete');
- $objWriter->writeAttribute('val', '0');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('hidden') ?? '0');
$objWriter->endElement();
$objWriter->startElement('c:axPos');
@@ -682,7 +715,7 @@ class Chart extends WriterPart
$objWriter->endElement(); // c:scaling
$objWriter->startElement('c:delete');
- $objWriter->writeAttribute('val', '0');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('hidden') ?? '0');
$objWriter->endElement();
$objWriter->startElement('c:axPos');
@@ -1612,7 +1645,18 @@ class Chart extends WriterPart
if (is_numeric($alpha)) {
$objWriter->startElement('a:alpha');
$objWriter->writeAttribute('val', ChartColor::alphaToXml((int) $alpha));
- $objWriter->endElement();
+ $objWriter->endElement(); // a:alpha
+ }
+ $brightness = $chartColor->getBrightness();
+ if (is_numeric($brightness)) {
+ $brightness = (int) $brightness;
+ $lumOff = 100 - $brightness;
+ $objWriter->startElement('a:lumMod');
+ $objWriter->writeAttribute('val', ChartColor::alphaToXml($brightness));
+ $objWriter->endElement(); // a:lumMod
+ $objWriter->startElement('a:lumOff');
+ $objWriter->writeAttribute('val', ChartColor::alphaToXml($lumOff));
+ $objWriter->endElement(); // a:lumOff
}
$objWriter->endElement(); //a:srgbClr/schemeClr/prstClr
if ($solidFill) {
diff --git a/tests/PhpSpreadsheetTests/Chart/Charts32ScatterTest.php b/tests/PhpSpreadsheetTests/Chart/Charts32ScatterTest.php
index 77b0a9b2..fcde7ae3 100644
--- a/tests/PhpSpreadsheetTests/Chart/Charts32ScatterTest.php
+++ b/tests/PhpSpreadsheetTests/Chart/Charts32ScatterTest.php
@@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheetTests\Chart;
+use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
use PhpOffice\PhpSpreadsheet\Chart\Properties;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
@@ -447,4 +448,88 @@ class Charts32ScatterTest extends AbstractFunctional
$reloadedSpreadsheet->disconnectWorksheets();
}
+
+ public function testScatter9(): void
+ {
+ // gradient testing
+ $file = self::DIRECTORY . '32readwriteScatterChart9.xlsx';
+ $reader = new XlsxReader();
+ $reader->setIncludeCharts(true);
+ $spreadsheet = $reader->load($file);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame(1, $sheet->getChartCount());
+ /** @var callable */
+ $callableReader = [$this, 'readCharts'];
+ /** @var callable */
+ $callableWriter = [$this, 'writeCharts'];
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter);
+ $spreadsheet->disconnectWorksheets();
+
+ $sheet = $reloadedSpreadsheet->getActiveSheet();
+ self::assertSame('Worksheet', $sheet->getTitle());
+ $charts = $sheet->getChartCollection();
+ self::assertCount(1, $charts);
+ $chart = $charts[0];
+ self::assertNotNull($chart);
+ self::assertFalse($chart->getNoFill());
+ $plotArea = $chart->getPlotArea();
+ self::assertNotNull($plotArea);
+ self::assertFalse($plotArea->getNoFill());
+ self::assertEquals(315.0, $plotArea->getGradientFillAngle());
+ $stops = $plotArea->getGradientFillStops();
+ self::assertCount(3, $stops);
+ self::assertEquals(0.43808, $stops[0][0]);
+ self::assertEquals(0, $stops[1][0]);
+ self::assertEquals(0.91, $stops[2][0]);
+ $color = $stops[0][1];
+ self::assertInstanceOf(ChartColor::class, $color);
+ self::assertSame('srgbClr', $color->getType());
+ self::assertSame('CDDBEC', $color->getValue());
+ self::assertNull($color->getAlpha());
+ self::assertSame(20, $color->getBrightness());
+ $color = $stops[1][1];
+ self::assertInstanceOf(ChartColor::class, $color);
+ self::assertSame('srgbClr', $color->getType());
+ self::assertSame('FFC000', $color->getValue());
+ self::assertNull($color->getAlpha());
+ self::assertNull($color->getBrightness());
+ $color = $stops[2][1];
+ self::assertInstanceOf(ChartColor::class, $color);
+ self::assertSame('srgbClr', $color->getType());
+ self::assertSame('00B050', $color->getValue());
+ self::assertNull($color->getAlpha());
+ self::assertSame(4, $color->getBrightness());
+
+ $reloadedSpreadsheet->disconnectWorksheets();
+ }
+
+ public function testScatter10(): void
+ {
+ // nofill for Chart and PlotArea, hidden Axis
+ $file = self::DIRECTORY . '32readwriteScatterChart10.xlsx';
+ $reader = new XlsxReader();
+ $reader->setIncludeCharts(true);
+ $spreadsheet = $reader->load($file);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame(1, $sheet->getChartCount());
+ /** @var callable */
+ $callableReader = [$this, 'readCharts'];
+ /** @var callable */
+ $callableWriter = [$this, 'writeCharts'];
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter);
+ $spreadsheet->disconnectWorksheets();
+
+ $sheet = $reloadedSpreadsheet->getActiveSheet();
+ self::assertSame('Worksheet', $sheet->getTitle());
+ $charts = $sheet->getChartCollection();
+ self::assertCount(1, $charts);
+ $chart = $charts[0];
+ self::assertNotNull($chart);
+ self::assertTrue($chart->getNoFill());
+ $plotArea = $chart->getPlotArea();
+ self::assertNotNull($plotArea);
+ self::assertTrue($plotArea->getNoFill());
+
+ $reloadedSpreadsheet->disconnectWorksheets();
+ }
}
From e460c826067102c8d36e8c61fe5b5bf91532259f Mon Sep 17 00:00:00 2001
From: Jonathan Goode
Date: Thu, 28 Jul 2022 03:29:02 +0100
Subject: [PATCH 10/11] Fully flatten an array (#2956)
* Fully flatten an array
* Provide test coverage for CONCAT combined with INDEX/MATCH
---
CHANGELOG.md | 2 +-
src/PhpSpreadsheet/Calculation/Functions.php | 22 +++++-------
.../Calculation/ArrayTest.php | 34 +++++++++++++++++++
.../Functions/TextData/ConcatenateTest.php | 25 ++++++++++++++
4 files changed, 69 insertions(+), 14 deletions(-)
create mode 100644 tests/PhpSpreadsheetTests/Calculation/ArrayTest.php
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2fd0cc1..7f07303a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Fixed
-- Nothing
+- Fully flatten an array [Issue #2955](https://github.com/PHPOffice/PhpSpreadsheet/issues/2955) [PR #2956](https://github.com/PHPOffice/PhpSpreadsheet/pull/2956)
## 1.24.1 - 2022-07-18
diff --git a/src/PhpSpreadsheet/Calculation/Functions.php b/src/PhpSpreadsheet/Calculation/Functions.php
index ddd3e200..b3d6998c 100644
--- a/src/PhpSpreadsheet/Calculation/Functions.php
+++ b/src/PhpSpreadsheet/Calculation/Functions.php
@@ -573,24 +573,20 @@ class Functions
return (array) $array;
}
- $arrayValues = [];
- foreach ($array as $value) {
+ $flattened = [];
+ $stack = array_values($array);
+
+ while ($stack) {
+ $value = array_shift($stack);
+
if (is_array($value)) {
- foreach ($value as $val) {
- if (is_array($val)) {
- foreach ($val as $v) {
- $arrayValues[] = $v;
- }
- } else {
- $arrayValues[] = $val;
- }
- }
+ array_unshift($stack, ...array_values($value));
} else {
- $arrayValues[] = $value;
+ $flattened[] = $value;
}
}
- return $arrayValues;
+ return $flattened;
}
/**
diff --git a/tests/PhpSpreadsheetTests/Calculation/ArrayTest.php b/tests/PhpSpreadsheetTests/Calculation/ArrayTest.php
new file mode 100644
index 00000000..60c336cf
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Calculation/ArrayTest.php
@@ -0,0 +1,34 @@
+ [
+ 0 => [
+ 32 => [
+ 'B' => 'PHP',
+ ],
+ ],
+ ],
+ 1 => [
+ 0 => [
+ 32 => [
+ 'C' => 'Spreadsheet',
+ ],
+ ],
+ ],
+ ];
+
+ $values = Functions::flattenArray($array);
+
+ self::assertIsNotArray($values[0]);
+ self::assertIsNotArray($values[1]);
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateTest.php
index 6c9a871d..31fb94fa 100644
--- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateTest.php
+++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateTest.php
@@ -2,6 +2,8 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
class ConcatenateTest extends AllSetupTeardown
{
/**
@@ -30,4 +32,27 @@ class ConcatenateTest extends AllSetupTeardown
{
return require 'tests/data/Calculation/TextData/CONCATENATE.php';
}
+
+ public function testConcatenateWithIndexMatch(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $sheet1 = $spreadsheet->getActiveSheet();
+ $sheet1->setTitle('Formula');
+ $sheet1->fromArray(
+ [
+ ['Number', 'Formula'],
+ ['52101293', '=CONCAT(INDEX(Lookup!$B$2, MATCH($A2, Lookup!$A$2, 0)))'],
+ ]
+ );
+ $sheet2 = $spreadsheet->createSheet();
+ $sheet2->setTitle('Lookup');
+ $sheet2->fromArray(
+ [
+ ['Lookup', 'Match'],
+ ['52101293', 'PHP'],
+ ]
+ );
+ self::assertSame('PHP', $sheet1->getCell('B2')->getCalculatedValue());
+ $spreadsheet->disconnectWorksheets();
+ }
}
From c0809b0c6cb2f8ad6e1ce359cfc44219ba7c841d Mon Sep 17 00:00:00 2001
From: oleibman <10341515+oleibman@users.noreply.github.com>
Date: Thu, 28 Jul 2022 07:03:26 -0700
Subject: [PATCH 11/11] Fix Spreadsheet Copy, Disable Clone, Improve Coverage
(#2951)
* Fix Spreadsheet Copy, Disable Clone, Improve Coverage
This PR was supposed to be merely to increase coverage in Spreadsheet. However, in doing so, I discovered that neither clone nor copy worked correctly. Neither had been covered in the test suite. Copy not only did not work, it broke the source spreadsheet as well. I tried to debug and got nowhere; I even tried using myclabs/deep-copy which is already in use in the test suite, but it failed as well. However, write and reload ought to work just fine for copy. It can't be used for clone; however, since copy does what clone ought to do, there's no reason why clone needs to be used, so __clone is changed to throw an exception if attempted.
One other source change was needed, an obvious bug where an if condition uses 'or' when it should use 'and'. Also, one docblock declaration needed a change. Aside from that, the rest of this PR is test cases, and overall coverage passes 89% for the first time.
* Clone is Okay After All
But copy wasn't, changing it to just return clone. Perhaps save and reload will be needed instead at some point, but not yet.
* An Error I Cannot Reproduce
PHP8.1 unit test says error because GdImage can't be serialized. I can't reproduce this error on any of my test systems. I have no idea why GdImage is even involved. Using try/catch to see if it helps.
* Weird Failures in Github
I thought restoring clone was a good idea. That left me in a state where, after one change, copy/clone no longer worked on Github (unable to reproduce on any of my test systems). After a second change, copy worked but clone didn't, again unable to reproduce. So, reverting to original version - copy does save and reload, clone throws exception.
---
phpstan-baseline.neon | 15 --
src/PhpSpreadsheet/Spreadsheet.php | 35 ++-
tests/PhpSpreadsheetTests/DefinedNameTest.php | 12 +
.../PhpSpreadsheetTests/NamedFormulaTest.php | 6 +
tests/PhpSpreadsheetTests/NamedRangeTest.php | 6 +
.../Reader/Xlsx/RibbonTest.php | 30 +++
.../SpreadsheetCoverageTest.php | 209 ++++++++++++++++++
tests/PhpSpreadsheetTests/Style/FontTest.php | 17 ++
8 files changed, 297 insertions(+), 33 deletions(-)
create mode 100644 tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 5ef5522f..024f1fbd 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -2860,11 +2860,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Spreadsheet.php
- -
- message: "#^Comparison operation \"\\<\\=\" between int\\ and 1000 is always true\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Spreadsheet.php
-
-
message: "#^Parameter \\#1 \\$worksheet of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getIndex\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet, PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null given\\.$#"
count: 1
@@ -2875,21 +2870,11 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Spreadsheet.php
- -
- message: "#^Result of \\|\\| is always true\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Spreadsheet.php
-
-
message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet and null will always evaluate to false\\.$#"
count: 1
path: src/PhpSpreadsheet/Spreadsheet.php
- -
- message: "#^Strict comparison using \\=\\=\\= between string and null will always evaluate to false\\.$#"
- count: 1
- path: src/PhpSpreadsheet/Spreadsheet.php
-
-
message: "#^Unreachable statement \\- code above always terminates\\.$#"
count: 1
diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php
index 33b4fe0c..4bb93987 100644
--- a/src/PhpSpreadsheet/Spreadsheet.php
+++ b/src/PhpSpreadsheet/Spreadsheet.php
@@ -3,10 +3,13 @@
namespace PhpOffice\PhpSpreadsheet;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
+use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
class Spreadsheet
{
@@ -1120,28 +1123,24 @@ class Spreadsheet
*/
public function copy()
{
- $copied = clone $this;
+ $filename = File::temporaryFilename();
+ $writer = new XlsxWriter($this);
+ $writer->setIncludeCharts(true);
+ $writer->save($filename);
- $worksheetCount = count($this->workSheetCollection);
- for ($i = 0; $i < $worksheetCount; ++$i) {
- $this->workSheetCollection[$i] = $this->workSheetCollection[$i]->copy();
- $this->workSheetCollection[$i]->rebindParent($this);
- }
+ $reader = new XlsxReader();
+ $reader->setIncludeCharts(true);
+ $reloadedSpreadsheet = $reader->load($filename);
+ unlink($filename);
- return $copied;
+ return $reloadedSpreadsheet;
}
- /**
- * Implement PHP __clone to create a deep clone, not just a shallow copy.
- */
public function __clone()
{
- // @phpstan-ignore-next-line
- foreach ($this as $key => $val) {
- if (is_object($val) || (is_array($val))) {
- $this->{$key} = unserialize(serialize($val));
- }
- }
+ throw new Exception(
+ 'Do not use clone on spreadsheet. Use spreadsheet->copy() instead.'
+ );
}
/**
@@ -1562,7 +1561,7 @@ class Spreadsheet
* Workbook window is hidden and cannot be shown in the
* user interface.
*
- * @param string $visibility visibility status of the workbook
+ * @param null|string $visibility visibility status of the workbook
*/
public function setVisibility($visibility): void
{
@@ -1596,7 +1595,7 @@ class Spreadsheet
*/
public function setTabRatio($tabRatio): void
{
- if ($tabRatio >= 0 || $tabRatio <= 1000) {
+ if ($tabRatio >= 0 && $tabRatio <= 1000) {
$this->tabRatio = (int) $tabRatio;
} else {
throw new Exception('Tab ratio must be between 0 and 1000.');
diff --git a/tests/PhpSpreadsheetTests/DefinedNameTest.php b/tests/PhpSpreadsheetTests/DefinedNameTest.php
index 43eddc8a..82950880 100644
--- a/tests/PhpSpreadsheetTests/DefinedNameTest.php
+++ b/tests/PhpSpreadsheetTests/DefinedNameTest.php
@@ -85,6 +85,18 @@ class DefinedNameTest extends TestCase
self::assertCount(1, $this->spreadsheet->getDefinedNames());
}
+ public function testRemoveGlobalDefinedName(): void
+ {
+ $this->spreadsheet->addDefinedName(
+ DefinedName::createInstance('Any', $this->spreadsheet->getActiveSheet(), '=A1')
+ );
+ self::assertCount(1, $this->spreadsheet->getDefinedNames());
+
+ $this->spreadsheet->removeDefinedName('Any');
+ self::assertCount(0, $this->spreadsheet->getDefinedNames());
+ $this->spreadsheet->removeDefinedName('Other');
+ }
+
public function testRemoveGlobalDefinedNameWhenDuplicateNames(): void
{
$this->spreadsheet->addDefinedName(
diff --git a/tests/PhpSpreadsheetTests/NamedFormulaTest.php b/tests/PhpSpreadsheetTests/NamedFormulaTest.php
index 4c4a6b11..02e9d818 100644
--- a/tests/PhpSpreadsheetTests/NamedFormulaTest.php
+++ b/tests/PhpSpreadsheetTests/NamedFormulaTest.php
@@ -133,4 +133,10 @@ class NamedFormulaTest extends TestCase
$formula->getValue()
);
}
+
+ public function testRemoveNonExistentNamedFormula(): void
+ {
+ self::assertCount(0, $this->spreadsheet->getNamedFormulae());
+ $this->spreadsheet->removeNamedFormula('Any');
+ }
}
diff --git a/tests/PhpSpreadsheetTests/NamedRangeTest.php b/tests/PhpSpreadsheetTests/NamedRangeTest.php
index c72b7b73..402e7eba 100644
--- a/tests/PhpSpreadsheetTests/NamedRangeTest.php
+++ b/tests/PhpSpreadsheetTests/NamedRangeTest.php
@@ -133,4 +133,10 @@ class NamedRangeTest extends TestCase
$range->getValue()
);
}
+
+ public function testRemoveNonExistentNamedRange(): void
+ {
+ self::assertCount(0, $this->spreadsheet->getNamedRanges());
+ $this->spreadsheet->removeNamedRange('Any');
+ }
}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php
index 197ad47f..ab304e7b 100644
--- a/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php
@@ -44,4 +44,34 @@ class RibbonTest extends AbstractFunctional
self::assertNull($reloadedSpreadsheet->getRibbonBinObjects());
$reloadedSpreadsheet->disconnectWorksheets();
}
+
+ /**
+ * Same as above but discard macros.
+ */
+ public function testDiscardMacros(): void
+ {
+ $filename = 'tests/data/Reader/XLSX/ribbon.donotopen.zip';
+ $reader = IOFactory::createReader('Xlsx');
+ $spreadsheet = $reader->load($filename);
+ self::assertTrue($spreadsheet->hasRibbon());
+ $target = $spreadsheet->getRibbonXMLData('target');
+ self::assertSame('customUI/customUI.xml', $target);
+ $data = $spreadsheet->getRibbonXMLData('data');
+ self::assertIsString($data);
+ self::assertSame(1522, strlen($data));
+ $vbaCode = (string) $spreadsheet->getMacrosCode();
+ self::assertSame(13312, strlen($vbaCode));
+ $spreadsheet->discardMacros();
+
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
+ $spreadsheet->disconnectWorksheets();
+ self::assertTrue($reloadedSpreadsheet->hasRibbon());
+ $ribbonData = $reloadedSpreadsheet->getRibbonXmlData();
+ self::assertIsArray($ribbonData);
+ self::assertSame($target, $ribbonData['target'] ?? '');
+ self::assertSame($data, $ribbonData['data'] ?? '');
+ self::assertNull($reloadedSpreadsheet->getMacrosCode());
+ self::assertNull($reloadedSpreadsheet->getRibbonBinObjects());
+ $reloadedSpreadsheet->disconnectWorksheets();
+ }
}
diff --git a/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php b/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php
new file mode 100644
index 00000000..584c53fe
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php
@@ -0,0 +1,209 @@
+getProperties();
+ $properties->setCreator('Anyone');
+ $properties->setTitle('Description');
+ $spreadsheet2 = new Spreadsheet();
+ self::assertNotEquals($properties, $spreadsheet2->getProperties());
+ $properties2 = clone $properties;
+ $spreadsheet2->setProperties($properties2);
+ self::assertEquals($properties, $spreadsheet2->getProperties());
+ $spreadsheet->disconnectWorksheets();
+ $spreadsheet2->disconnectWorksheets();
+ }
+
+ public function testDocumentSecurity(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $security = $spreadsheet->getSecurity();
+ $security->setLockRevision(true);
+ $revisionsPassword = 'revpasswd';
+ $security->setRevisionsPassword($revisionsPassword);
+ $spreadsheet2 = new Spreadsheet();
+ self::assertNotEquals($security, $spreadsheet2->getSecurity());
+ $security2 = clone $security;
+ $spreadsheet2->setSecurity($security2);
+ self::assertEquals($security, $spreadsheet2->getSecurity());
+ $spreadsheet->disconnectWorksheets();
+ $spreadsheet2->disconnectWorksheets();
+ }
+
+ public function testCellXfCollection(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $sheet->getStyle('A1')->getFont()->setName('font1');
+ $sheet->getStyle('A2')->getFont()->setName('font2');
+ $sheet->getStyle('A3')->getFont()->setName('font3');
+ $sheet->getStyle('B1')->getFont()->setName('font1');
+ $sheet->getStyle('B2')->getFont()->setName('font2');
+ $collection = $spreadsheet->getCellXfCollection();
+ self::assertCount(4, $collection);
+ $font1Style = $collection[1];
+ self::assertTrue($spreadsheet->cellXfExists($font1Style));
+ self::assertSame('font1', $spreadsheet->getCellXfCollection()[1]->getFont()->getName());
+ self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName());
+ self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName());
+ self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName());
+ self::assertSame('font1', $sheet->getStyle('B1')->getFont()->getName());
+ self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName());
+
+ $spreadsheet->removeCellXfByIndex(1);
+ self::assertFalse($spreadsheet->cellXfExists($font1Style));
+ self::assertSame('font2', $spreadsheet->getCellXfCollection()[1]->getFont()->getName());
+ self::assertSame('Calibri', $sheet->getStyle('A1')->getFont()->getName());
+ self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName());
+ self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName());
+ self::assertSame('Calibri', $sheet->getStyle('B1')->getFont()->getName());
+ self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName());
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testInvalidRemoveCellXfByIndex(): void
+ {
+ $this->expectException(SSException::class);
+ $this->expectExceptionMessage('CellXf index is out of bounds.');
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $sheet->getStyle('A1')->getFont()->setName('font1');
+ $sheet->getStyle('A2')->getFont()->setName('font2');
+ $sheet->getStyle('A3')->getFont()->setName('font3');
+ $sheet->getStyle('B1')->getFont()->setName('font1');
+ $sheet->getStyle('B2')->getFont()->setName('font2');
+ $spreadsheet->removeCellXfByIndex(5);
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testInvalidRemoveDefaultStyle(): void
+ {
+ $this->expectException(SSException::class);
+ $this->expectExceptionMessage('No default style found for this workbook');
+ // Removing default style probably should be disallowed.
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $spreadsheet->removeCellXfByIndex(0);
+ $style = $spreadsheet->getDefaultStyle();
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testCellStyleXF(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $collection = $spreadsheet->getCellStyleXfCollection();
+ self::assertCount(1, $collection);
+ $styleXf = $collection[0];
+ self::assertSame($styleXf, $spreadsheet->getCellStyleXfByIndex(0));
+ $hash = $styleXf->getHashCode();
+ self::assertSame($styleXf, $spreadsheet->getCellStyleXfByHashCode($hash));
+ self::assertFalse($spreadsheet->getCellStyleXfByHashCode($hash . 'x'));
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testInvalidRemoveCellStyleXfByIndex(): void
+ {
+ $this->expectException(SSException::class);
+ $this->expectExceptionMessage('CellStyleXf index is out of bounds.');
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $spreadsheet->removeCellStyleXfByIndex(5);
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testInvalidFirstSheetIndex(): void
+ {
+ $this->expectException(SSException::class);
+ $this->expectExceptionMessage('First sheet index must be a positive integer.');
+ $spreadsheet = new Spreadsheet();
+ $spreadsheet->setFirstSheetIndex(-1);
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testInvalidVisibility(): void
+ {
+ $this->expectException(SSException::class);
+ $this->expectExceptionMessage('Invalid visibility value.');
+ $spreadsheet = new Spreadsheet();
+ $spreadsheet->setVisibility(Spreadsheet::VISIBILITY_HIDDEN);
+ self::assertSame(Spreadsheet::VISIBILITY_HIDDEN, $spreadsheet->getVisibility());
+ $spreadsheet->setVisibility(null);
+ self::assertSame(Spreadsheet::VISIBILITY_VISIBLE, $spreadsheet->getVisibility());
+ $spreadsheet->setVisibility('badvalue');
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testInvalidTabRatio(): void
+ {
+ $this->expectException(SSException::class);
+ $this->expectExceptionMessage('Tab ratio must be between 0 and 1000.');
+ $spreadsheet = new Spreadsheet();
+ $spreadsheet->setTabRatio(2000);
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testCopy(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $sheet->getStyle('A1')->getFont()->setName('font1');
+ $sheet->getStyle('A2')->getFont()->setName('font2');
+ $sheet->getStyle('A3')->getFont()->setName('font3');
+ $sheet->getStyle('B1')->getFont()->setName('font1');
+ $sheet->getStyle('B2')->getFont()->setName('font2');
+ $sheet->getCell('A1')->setValue('this is a1');
+ $sheet->getCell('A2')->setValue('this is a2');
+ $sheet->getCell('A3')->setValue('this is a3');
+ $sheet->getCell('B1')->setValue('this is b1');
+ $sheet->getCell('B2')->setValue('this is b2');
+ $copied = $spreadsheet->copy();
+ $copysheet = $copied->getActiveSheet();
+ $copysheet->getStyle('A2')->getFont()->setName('font12');
+ $copysheet->getCell('A2')->setValue('this was a2');
+
+ self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName());
+ self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName());
+ self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName());
+ self::assertSame('font1', $sheet->getStyle('B1')->getFont()->getName());
+ self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName());
+ self::assertSame('this is a1', $sheet->getCell('A1')->getValue());
+ self::assertSame('this is a2', $sheet->getCell('A2')->getValue());
+ self::assertSame('this is a3', $sheet->getCell('A3')->getValue());
+ self::assertSame('this is b1', $sheet->getCell('B1')->getValue());
+ self::assertSame('this is b2', $sheet->getCell('B2')->getValue());
+
+ self::assertSame('font1', $copysheet->getStyle('A1')->getFont()->getName());
+ self::assertSame('font12', $copysheet->getStyle('A2')->getFont()->getName());
+ self::assertSame('font3', $copysheet->getStyle('A3')->getFont()->getName());
+ self::assertSame('font1', $copysheet->getStyle('B1')->getFont()->getName());
+ self::assertSame('font2', $copysheet->getStyle('B2')->getFont()->getName());
+ self::assertSame('this is a1', $copysheet->getCell('A1')->getValue());
+ self::assertSame('this was a2', $copysheet->getCell('A2')->getValue());
+ self::assertSame('this is a3', $copysheet->getCell('A3')->getValue());
+ self::assertSame('this is b1', $copysheet->getCell('B1')->getValue());
+ self::assertSame('this is b2', $copysheet->getCell('B2')->getValue());
+
+ $spreadsheet->disconnectWorksheets();
+ $copied->disconnectWorksheets();
+ }
+
+ public function testClone(): void
+ {
+ $this->expectException(SSException::class);
+ $this->expectExceptionMessage('Do not use clone on spreadsheet. Use spreadsheet->copy() instead.');
+ $spreadsheet = new Spreadsheet();
+ $clone = clone $spreadsheet;
+ $spreadsheet->disconnectWorksheets();
+ $clone->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Style/FontTest.php b/tests/PhpSpreadsheetTests/Style/FontTest.php
index 02814afa..6cd4d950 100644
--- a/tests/PhpSpreadsheetTests/Style/FontTest.php
+++ b/tests/PhpSpreadsheetTests/Style/FontTest.php
@@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheetTests\Style;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Font;
use PHPUnit\Framework\TestCase;
class FontTest extends TestCase
@@ -88,4 +89,20 @@ class FontTest extends TestCase
self::assertEquals('Calibri', $font->getName(), 'Null string changed to default');
$spreadsheet->disconnectWorksheets();
}
+
+ public function testUnderlineHash(): void
+ {
+ $font1 = new Font();
+ $font2 = new Font();
+ $font2aHash = $font2->getHashCode();
+ self::assertSame($font1->getHashCode(), $font2aHash);
+ $font2->setUnderlineColor(
+ [
+ 'type' => 'srgbClr',
+ 'value' => 'FF0000',
+ ]
+ );
+ $font2bHash = $font2->getHashCode();
+ self::assertNotEquals($font1->getHashCode(), $font2bHash);
+ }
}