Merge branch 'master' into TextFunctions-New

This commit is contained in:
Mark Baker 2022-07-28 19:14:06 +02:00 committed by GitHub
commit 345c0ebdfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1439 additions and 185 deletions

View File

@ -34,7 +34,7 @@ If this is an issue with reading a specific spreadsheet file, then it may be app
- [ ] Writer - [ ] Writer
- [ ] Styles - [ ] Styles
- [ ] Data Validations - [ ] Data Validations
- [ ] Formula Calulations - [ ] Formula Calculations
- [ ] Charts - [ ] Charts
- [ ] AutoFilter - [ ] AutoFilter
- [ ] Form Elements - [ ] Form Elements

View File

@ -25,8 +25,35 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Fixed ### Fixed
- 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
### Added
- Add Chart Axis Option textRotation [Issue #2705](https://github.com/PHPOffice/PhpSpreadsheet/issues/2705) [PR #2940](https://github.com/PHPOffice/PhpSpreadsheet/pull/2940)
### Changed
- Nothing - Nothing
### Deprecated
- Nothing
### Removed
- Nothing
### Fixed
- Fix Encoding issue with Html reader (PHP 8.2 deprecation for mb_convert_encoding) [Issue #2942](https://github.com/PHPOffice/PhpSpreadsheet/issues/2942) [PR #2943](https://github.com/PHPOffice/PhpSpreadsheet/pull/2943)
- Additional Chart fixes
- Pie chart with part separated unwantedly [Issue #2506](https://github.com/PHPOffice/PhpSpreadsheet/issues/2506) [PR #2928](https://github.com/PHPOffice/PhpSpreadsheet/pull/2928)
- Chart styling is lost on simple load / save process [Issue #1797](https://github.com/PHPOffice/PhpSpreadsheet/issues/1797) [Issue #2077](https://github.com/PHPOffice/PhpSpreadsheet/issues/2077) [PR #2930](https://github.com/PHPOffice/PhpSpreadsheet/pull/2930)
- Can't create contour chart (surface 2d) [Issue #2931](https://github.com/PHPOffice/PhpSpreadsheet/issues/2931) [PR #2933](https://github.com/PHPOffice/PhpSpreadsheet/pull/2933)
- VLOOKUP Breaks When Array Contains Null Cells [Issue #2934](https://github.com/PHPOffice/PhpSpreadsheet/issues/2934) [PR #2939](https://github.com/PHPOffice/PhpSpreadsheet/pull/2939)
## 1.24.0 - 2022-07-09 ## 1.24.0 - 2022-07-09
Note that this will be the last 1.x branch release before the 2.x release. We will maintain both branches in parallel for a time; but users are requested to update to version 2.0 once that is fully available. Note that this will be the last 1.x branch release before the 2.x release. We will maintain both branches in parallel for a time; but users are requested to update to version 2.0 once that is fully available.

View File

@ -2860,11 +2860,6 @@ parameters:
count: 1 count: 1
path: src/PhpSpreadsheet/Spreadsheet.php path: src/PhpSpreadsheet/Spreadsheet.php
-
message: "#^Comparison operation \"\\<\\=\" between int\\<min, \\-1\\> 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\\.$#" message: "#^Parameter \\#1 \\$worksheet of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getIndex\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet, PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null given\\.$#"
count: 1 count: 1
@ -2875,21 +2870,11 @@ parameters:
count: 1 count: 1
path: src/PhpSpreadsheet/Spreadsheet.php 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\\.$#" message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet and null will always evaluate to false\\.$#"
count: 1 count: 1
path: src/PhpSpreadsheet/Spreadsheet.php 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\\.$#" message: "#^Unreachable statement \\- code above always terminates\\.$#"
count: 1 count: 1

View File

@ -124,6 +124,7 @@ $dataSeriesValues[2]->setScatterLines(false); // points not connected
$xAxis = new Axis(); $xAxis = new Axis();
//$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE ); //$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE );
$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601, true); $xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601, true);
$xAxis->setAxisOption('textRotation', '45');
$yAxis = new Axis(); $yAxis = new Axis();
$yAxis->setLineStyleProperties( $yAxis->setLineStyleProperties(

View File

@ -0,0 +1,192 @@
<?php
use PhpOffice\PhpSpreadsheet\Chart\Axis;
use PhpOffice\PhpSpreadsheet\Chart\Chart;
use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
use PhpOffice\PhpSpreadsheet\Chart\Properties;
use PhpOffice\PhpSpreadsheet\Chart\Title;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
require __DIR__ . '/../Header.php';
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
// changed data to simulate a trend chart - Xaxis are dates; Yaxis are 3 meausurements from each date
$worksheet->fromArray(
[
['', 'metric1', 'metric2', 'metric3'],
['=DATEVALUE("2021-01-01")', 12.1, 15.1, 21.1],
['=DATEVALUE("2021-01-04")', 56.2, 73.2, 86.2],
['=DATEVALUE("2021-01-07")', 52.2, 61.2, 69.2],
['=DATEVALUE("2021-01-10")', 30.2, 32.2, 0.2],
]
);
$worksheet->getStyle('A2:A5')->getNumberFormat()->setFormatCode(Properties::FORMAT_CODE_DATE_ISO8601);
$worksheet->getColumnDimension('A')->setAutoSize(true);
$worksheet->setSelectedCells('A1');
// Set the Labels for each data series we want to plot
// Datatype
// Cell reference for data
// Format Code
// Number of datapoints in series
// Data values
// Data Marker
$dataSeriesLabels = [
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // was 2010
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // was 2011
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // was 2012
];
// Set the X-Axis Labels
// changed from STRING to NUMBER
// added 2 additional x-axis values associated with each of the 3 metrics
// added FORMATE_CODE_NUMBER
$xAxisTickValues = [
//new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$2:$A$5', Properties::FORMAT_CODE_DATE, 4),
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$2:$A$5', Properties::FORMAT_CODE_DATE, 4),
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$2:$A$5', Properties::FORMAT_CODE_DATE, 4),
];
// Set the Data values for each data series we want to plot
// Datatype
// Cell reference for data
// Format Code
// Number of datapoints in series
// Data values
// Data Marker
// added FORMAT_CODE_NUMBER
$dataSeriesValues = [
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', Properties::FORMAT_CODE_NUMBER, 4),
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', Properties::FORMAT_CODE_NUMBER, 4),
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', Properties::FORMAT_CODE_NUMBER, 4),
];
// series 1
// marker details
$dataSeriesValues[0]
->setPointMarker('diamond')
->setPointSize(5)
->getMarkerFillColor()
->setColorProperties('0070C0', null, ChartColor::EXCEL_COLOR_TYPE_RGB);
$dataSeriesValues[0]
->getMarkerBorderColor()
->setColorProperties('002060', null, ChartColor::EXCEL_COLOR_TYPE_RGB);
// line details - smooth line, connected
$dataSeriesValues[0]
->setScatterLines(true)
->setSmoothLine(true)
->setLineColorProperties('accent1', 40, ChartColor::EXCEL_COLOR_TYPE_SCHEME); // value, alpha, type
$dataSeriesValues[0]->setLineStyleProperties(
2.5, // width in points
Properties::LINE_STYLE_COMPOUND_TRIPLE, // compound
Properties::LINE_STYLE_DASH_SQUARE_DOT, // dash
Properties::LINE_STYLE_CAP_SQUARE, // cap
Properties::LINE_STYLE_JOIN_MITER, // join
Properties::LINE_STYLE_ARROW_TYPE_OPEN, // head type
Properties::LINE_STYLE_ARROW_SIZE_4, // head size preset index
Properties::LINE_STYLE_ARROW_TYPE_ARROW, // end type
Properties::LINE_STYLE_ARROW_SIZE_6 // end size preset index
);
// series 2 - straight line - no special effects, connected, straight line
$dataSeriesValues[1] // square fill
->setPointMarker('square')
->setPointSize(6)
->getMarkerBorderColor()
->setColorProperties('accent6', 3, ChartColor::EXCEL_COLOR_TYPE_SCHEME);
$dataSeriesValues[1] // square border
->getMarkerFillColor()
->setColorProperties('0FFF00', null, ChartColor::EXCEL_COLOR_TYPE_RGB);
$dataSeriesValues[1]
->setScatterLines(true)
->setSmoothLine(false)
->setLineColorProperties('FF0000', 80, ChartColor::EXCEL_COLOR_TYPE_RGB);
$dataSeriesValues[1]->setLineWidth(2.0);
// series 3 - markers, no line
$dataSeriesValues[2] // triangle fill
//->setPointMarker('triangle') // let Excel choose shape
->setPointSize(7)
->getMarkerFillColor()
->setColorProperties('FFFF00', null, ChartColor::EXCEL_COLOR_TYPE_RGB);
$dataSeriesValues[2] // triangle border
->getMarkerBorderColor()
->setColorProperties('accent4', null, ChartColor::EXCEL_COLOR_TYPE_SCHEME);
$dataSeriesValues[2]->setScatterLines(false); // points not connected
// Added so that Xaxis shows dates instead of Excel-equivalent-year1900-numbers
$xAxis = new Axis();
//$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE );
$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601, true);
$xAxis->setAxisOption('textRotation', '45');
$xAxis->setAxisOption('hidden', '1');
$yAxis = new Axis();
$yAxis->setLineStyleProperties(
2.5, // width in points
Properties::LINE_STYLE_COMPOUND_SIMPLE,
Properties::LINE_STYLE_DASH_DASH_DOT,
Properties::LINE_STYLE_CAP_FLAT,
Properties::LINE_STYLE_JOIN_BEVEL
);
$yAxis->setLineColorProperties('ffc000', null, ChartColor::EXCEL_COLOR_TYPE_RGB);
$yAxis->setAxisOption('hidden', '1');
// Build the dataseries
$series = new DataSeries(
DataSeries::TYPE_SCATTERCHART, // plotType
null, // plotGrouping (Scatter charts don't have any grouping)
range(0, count($dataSeriesValues) - 1), // plotOrder
$dataSeriesLabels, // plotLabel
$xAxisTickValues, // plotCategory
$dataSeriesValues, // plotValues
null, // plotDirection
false, // smooth line
DataSeries::STYLE_SMOOTHMARKER // plotStyle
);
// Set the series in the plot area
$plotArea = new PlotArea(null, [$series]);
$plotArea->setNoFill(true);
// Set the chart legend
$legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false);
$title = new Title('Test Scatter Trend Chart');
//$yAxisLabel = new Title('Value ($k)');
// Create the chart
$chart = new Chart(
'chart1', // name
$title, // title
$legend, // legend
$plotArea, // plotArea
true, // plotVisibleOnly
DataSeries::EMPTY_AS_GAP, // displayBlanksAs
null, // xAxisLabel
null, //$yAxisLabel, // yAxisLabel
// added xAxis for correct date display
$xAxis, // xAxis
$yAxis, // yAxis
);
$chart->setNoFill(true);
// Set the position where the chart should appear in the worksheet
$chart->setTopLeftPosition('A7');
$chart->setBottomRightPosition('P20');
// Add the chart to the worksheet
$worksheet->addChart($chart);
// Save Excel 2007 file
$filename = $helper->getFilename(__FILE__);
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
$writer->setIncludeCharts(true);
$callStartTime = microtime(true);
$writer->save($filename);
$spreadsheet->disconnectWorksheets();
$helper->logWrite($writer, $filename, $callStartTime);

View File

@ -0,0 +1,130 @@
<?php
use PhpOffice\PhpSpreadsheet\Chart\Chart;
use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
use PhpOffice\PhpSpreadsheet\Chart\Title;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
require __DIR__ . '/../Header.php';
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$worksheet->fromArray(
[
['', 2010, 2011, 2012],
['Q1', 12, 15, 21],
['Q2', 56, 73, 86],
['Q3', 52, 61, 69],
['Q4', 30, 32, 0],
]
);
// Set the Labels for each data series we want to plot
// Datatype
// Cell reference for data
// Format Code
// Number of datapoints in series
// Data values
// Data Marker
$dataSeriesLabels = [
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012
];
// Set the X-Axis Labels
$xAxisTickValues = [
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4
];
// Set the Data values for each data series we want to plot
// Datatype
// Cell reference for data
// Format Code
// Number of datapoints in series
// Data values
// Data Marker
$dataSeriesValues = [
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4),
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4),
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4),
];
// Build the dataseries
$series = new DataSeries(
DataSeries::TYPE_SCATTERCHART, // plotType
null, // plotGrouping (Scatter charts don't have any grouping)
range(0, count($dataSeriesValues) - 1), // plotOrder
$dataSeriesLabels, // plotLabel
$xAxisTickValues, // plotCategory
$dataSeriesValues, // plotValues
null, // plotDirection
null, // smooth line
DataSeries::STYLE_LINEMARKER // plotStyle
);
// Set the series in the plot area
$plotArea = new PlotArea(null, [$series]);
$pos1 = 0; // pos = 0% (extreme low side or lower left corner)
$brightness1 = 0; // 0%
$gsColor1 = new ChartColor();
$gsColor1->setColorProperties('FF0000', 75, 'srgbClr', $brightness1); // red
$gradientStop1 = [$pos1, $gsColor1];
$pos2 = 0.5; // pos = 50% (middle)
$brightness2 = 0.5; // 50%
$gsColor2 = new ChartColor();
$gsColor2->setColorProperties('FFFF00', 50, 'srgbClr', $brightness2); // yellow
$gradientStop2 = [$pos2, $gsColor2];
$pos3 = 1.0; // pos = 100% (extreme high side or upper right corner)
$brightness3 = 0.5; // 50%
$gsColor3 = new ChartColor();
$gsColor3->setColorProperties('00B050', 50, 'srgbClr', $brightness3); // green
$gradientStop3 = [$pos3, $gsColor3];
$gradientFillStops = [
$gradientStop1,
$gradientStop2,
$gradientStop3,
];
$gradientFillAngle = 315.0; // 45deg above horiz
$plotArea->setGradientFillProperties($gradientFillStops, $gradientFillAngle);
// Set the chart legend
$legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false);
$title = new Title('Test Scatter Chart');
$yAxisLabel = new Title('Value ($k)');
// Create the chart
$chart = new Chart(
'chart1', // name
$title, // title
$legend, // legend
$plotArea, // plotArea
true, // plotVisibleOnly
DataSeries::EMPTY_AS_GAP, // displayBlanksAs
null, // xAxisLabel
$yAxisLabel // yAxisLabel
);
// Set the position where the chart should appear in the worksheet
$chart->setTopLeftPosition('A7');
$chart->setBottomRightPosition('H20');
// Add the chart to the worksheet
$worksheet->addChart($chart);
// Save Excel 2007 file
$filename = $helper->getFilename(__FILE__);
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
$writer->setIncludeCharts(true);
$callStartTime = microtime(true);
$writer->save($filename);
$helper->logWrite($writer, $filename, $callStartTime);

Binary file not shown.

Binary file not shown.

View File

@ -573,24 +573,20 @@ class Functions
return (array) $array; return (array) $array;
} }
$arrayValues = []; $flattened = [];
foreach ($array as $value) { $stack = array_values($array);
while ($stack) {
$value = array_shift($stack);
if (is_array($value)) { if (is_array($value)) {
foreach ($value as $val) { array_unshift($stack, ...array_values($value));
if (is_array($val)) {
foreach ($val as $v) {
$arrayValues[] = $v;
}
} else { } else {
$arrayValues[] = $val; $flattened[] = $value;
}
}
} else {
$arrayValues[] = $value;
} }
} }
return $arrayValues; return $flattened;
} }
/** /**

View File

@ -47,7 +47,7 @@ class ErrorValue
return false; return false;
} }
return in_array($value, ExcelError::$errorCodes, true); return in_array($value, ExcelError::ERROR_CODES, true);
} }
/** /**

View File

@ -13,7 +13,7 @@ class ExcelError
* *
* @var array<string, string> * @var array<string, string>
*/ */
public static $errorCodes = [ public const ERROR_CODES = [
'null' => '#NULL!', // 1 'null' => '#NULL!', // 1
'divisionbyzero' => '#DIV/0!', // 2 'divisionbyzero' => '#DIV/0!', // 2
'value' => '#VALUE!', // 3 'value' => '#VALUE!', // 3
@ -30,12 +30,23 @@ class ExcelError
'calculation' => '#CALC!', //14 'calculation' => '#CALC!', //14
]; ];
/**
* List of error codes. Replaced by constant;
* previously it was public and updateable, allowing
* user to make inappropriate alterations.
*
* @deprecated 1.25.0 Use ERROR_CODES constant instead.
*
* @var array<string, string>
*/
public static $errorCodes = self::ERROR_CODES;
/** /**
* @param mixed $value * @param mixed $value
*/ */
public static function throwError($value): string public static function throwError($value): string
{ {
return in_array($value, self::$errorCodes, true) ? $value : self::$errorCodes['value']; return in_array($value, self::ERROR_CODES, true) ? $value : self::ERROR_CODES['value'];
} }
/** /**
@ -52,7 +63,7 @@ class ExcelError
} }
$i = 1; $i = 1;
foreach (self::$errorCodes as $errorCode) { foreach (self::ERROR_CODES as $errorCode) {
if ($value === $errorCode) { if ($value === $errorCode) {
return $i; return $i;
} }
@ -71,7 +82,7 @@ class ExcelError
*/ */
public static function null(): string public static function null(): string
{ {
return self::$errorCodes['null']; return self::ERROR_CODES['null'];
} }
/** /**
@ -83,7 +94,7 @@ class ExcelError
*/ */
public static function NAN(): string public static function NAN(): string
{ {
return self::$errorCodes['num']; return self::ERROR_CODES['num'];
} }
/** /**
@ -95,7 +106,7 @@ class ExcelError
*/ */
public static function REF(): string public static function REF(): string
{ {
return self::$errorCodes['reference']; return self::ERROR_CODES['reference'];
} }
/** /**
@ -111,7 +122,7 @@ class ExcelError
*/ */
public static function NA(): string public static function NA(): string
{ {
return self::$errorCodes['na']; return self::ERROR_CODES['na'];
} }
/** /**
@ -123,7 +134,7 @@ class ExcelError
*/ */
public static function VALUE(): string public static function VALUE(): string
{ {
return self::$errorCodes['value']; return self::ERROR_CODES['value'];
} }
/** /**
@ -135,7 +146,7 @@ class ExcelError
*/ */
public static function NAME(): string public static function NAME(): string
{ {
return self::$errorCodes['name']; return self::ERROR_CODES['name'];
} }
/** /**
@ -145,7 +156,7 @@ class ExcelError
*/ */
public static function DIV0(): string public static function DIV0(): string
{ {
return self::$errorCodes['divisionbyzero']; return self::ERROR_CODES['divisionbyzero'];
} }
/** /**
@ -155,6 +166,6 @@ class ExcelError
*/ */
public static function CALC(): string public static function CALC(): string
{ {
return self::$errorCodes['calculation']; return self::ERROR_CODES['calculation'];
} }
} }

View File

@ -66,7 +66,7 @@ class HLookup extends LookupBase
*/ */
private static function hLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int private static function hLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int
{ {
$lookupLower = StringHelper::strToLower($lookupValue); $lookupLower = StringHelper::strToLower((string) $lookupValue);
$rowNumber = null; $rowNumber = null;
foreach ($lookupArray[$column] as $rowKey => $rowData) { foreach ($lookupArray[$column] as $rowKey => $rowData) {

View File

@ -19,8 +19,16 @@ abstract class LookupBase
protected static function validateIndexLookup(array $lookup_array, $index_number): int protected static function validateIndexLookup(array $lookup_array, $index_number): int
{ {
// index_number must be a number greater than or equal to 1 // index_number must be a number greater than or equal to 1.
if (!is_numeric($index_number) || $index_number < 1) { // Excel results are inconsistent when index is non-numeric.
// VLOOKUP(whatever, whatever, SQRT(-1)) yields NUM error, but
// VLOOKUP(whatever, whatever, cellref) yields REF error
// when cellref is '=SQRT(-1)'. So just try our best here.
// Similar results if string (literal yields VALUE, cellRef REF).
if (!is_numeric($index_number)) {
throw new Exception(ExcelError::throwError($index_number));
}
if ($index_number < 1) {
throw new Exception(ExcelError::VALUE()); throw new Exception(ExcelError::VALUE());
} }

View File

@ -68,8 +68,8 @@ class VLookup extends LookupBase
{ {
reset($a); reset($a);
$firstColumn = key($a); $firstColumn = key($a);
$aLower = StringHelper::strToLower($a[$firstColumn]); $aLower = StringHelper::strToLower((string) $a[$firstColumn]);
$bLower = StringHelper::strToLower($b[$firstColumn]); $bLower = StringHelper::strToLower((string) $b[$firstColumn]);
if ($aLower == $bLower) { if ($aLower == $bLower) {
return 0; return 0;
@ -84,7 +84,7 @@ class VLookup extends LookupBase
*/ */
private static function vLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int private static function vLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int
{ {
$lookupLower = StringHelper::strToLower($lookupValue); $lookupLower = StringHelper::strToLower((string) $lookupValue);
$rowNumber = null; $rowNumber = null;
foreach ($lookupArray as $rowKey => $rowData) { foreach ($lookupArray as $rowKey => $rowData) {

View File

@ -60,6 +60,8 @@ class Axis extends Properties
'axis_labels' => self::AXIS_LABELS_NEXT_TO, 'axis_labels' => self::AXIS_LABELS_NEXT_TO,
'horizontal_crosses' => self::HORIZONTAL_CROSSES_AUTOZERO, 'horizontal_crosses' => self::HORIZONTAL_CROSSES_AUTOZERO,
'horizontal_crosses_value' => null, 'horizontal_crosses_value' => null,
'textRotation' => null,
'hidden' => null,
]; ];
/** /**
@ -136,7 +138,9 @@ class Axis extends Properties
?string $minimum = null, ?string $minimum = null,
?string $maximum = null, ?string $maximum = null,
?string $majorUnit = null, ?string $majorUnit = null,
?string $minorUnit = null ?string $minorUnit = null,
?string $textRotation = null,
?string $hidden = null
): void { ): void {
$this->axisOptions['axis_labels'] = $axisLabels; $this->axisOptions['axis_labels'] = $axisLabels;
$this->setAxisOption('horizontal_crosses_value', $horizontalCrossesValue); $this->setAxisOption('horizontal_crosses_value', $horizontalCrossesValue);
@ -144,11 +148,12 @@ class Axis extends Properties
$this->setAxisOption('orientation', $axisOrientation); $this->setAxisOption('orientation', $axisOrientation);
$this->setAxisOption('major_tick_mark', $majorTmt); $this->setAxisOption('major_tick_mark', $majorTmt);
$this->setAxisOption('minor_tick_mark', $minorTmt); $this->setAxisOption('minor_tick_mark', $minorTmt);
$this->setAxisOption('minor_tick_mark', $minorTmt);
$this->setAxisOption('minimum', $minimum); $this->setAxisOption('minimum', $minimum);
$this->setAxisOption('maximum', $maximum); $this->setAxisOption('maximum', $maximum);
$this->setAxisOption('major_unit', $majorUnit); $this->setAxisOption('major_unit', $majorUnit);
$this->setAxisOption('minor_unit', $minorUnit); $this->setAxisOption('minor_unit', $minorUnit);
$this->setAxisOption('textRotation', $textRotation);
$this->setAxisOption('hidden', $hidden);
} }
/** /**

View File

@ -144,6 +144,9 @@ class Chart
/** @var bool */ /** @var bool */
private $autoTitleDeleted = false; private $autoTitleDeleted = false;
/** @var bool */
private $noFill = false;
/** /**
* Create a new Chart. * Create a new Chart.
* majorGridlines and minorGridlines are deprecated, moved to Axis. * majorGridlines and minorGridlines are deprecated, moved to Axis.
@ -747,4 +750,16 @@ class Chart
return $this; return $this;
} }
public function getNoFill(): bool
{
return $this->noFill;
}
public function setNoFill(bool $noFill): self
{
$this->noFill = $noFill;
return $this;
}
} }

View File

@ -24,15 +24,18 @@ class ChartColor
/** @var ?int */ /** @var ?int */
private $alpha; private $alpha;
/** @var ?int */
private $brightness;
/** /**
* @param string|string[] $value * @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)) { if (is_array($value)) {
$this->setColorPropertiesArray($value); $this->setColorPropertiesArray($value);
} else { } else {
$this->setColorProperties($value, $alpha, $type); $this->setColorProperties($value, $alpha, $type, $brightness);
} }
} }
@ -72,10 +75,23 @@ class ChartColor
return $this; 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 $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 (empty($type) && !empty($color)) {
if (substr($color, 0, 1) === '*') { if (substr($color, 0, 1) === '*') {
@ -99,6 +115,11 @@ class ChartColor
} elseif (is_numeric($alpha)) { } elseif (is_numeric($alpha)) {
$this->setAlpha((int) $alpha); $this->setAlpha((int) $alpha);
} }
if ($brightness === null) {
$this->setBrightness(null);
} elseif (is_numeric($brightness)) {
$this->setBrightness((int) $brightness);
}
return $this; return $this;
} }
@ -108,7 +129,8 @@ class ChartColor
return $this->setColorProperties( return $this->setColorProperties(
$color['value'] ?? '', $color['value'] ?? '',
$color['alpha'] ?? null, $color['alpha'] ?? null,
$color['type'] ?? null $color['type'] ?? null,
$color['brightness'] ?? null
); );
} }
@ -133,6 +155,8 @@ class ChartColor
$retVal = $this->type; $retVal = $this->type;
} elseif ($propertyName === 'alpha') { } elseif ($propertyName === 'alpha') {
$retVal = $this->alpha; $retVal = $this->alpha;
} elseif ($propertyName === 'brightness') {
$retVal = $this->brightness;
} }
return $retVal; return $retVal;

View File

@ -94,7 +94,7 @@ class DataSeries
private $plotCategory = []; private $plotCategory = [];
/** /**
* Smooth Line. * Smooth Line. Must be specified for both DataSeries and DataSeriesValues.
* *
* @var bool * @var bool
*/ */

View File

@ -536,7 +536,7 @@ class DataSeriesValues extends Properties
} }
/** /**
* Smooth Line. * Smooth Line. Must be specified for both DataSeries and DataSeriesValues.
* *
* @var bool * @var bool
*/ */

View File

@ -6,6 +6,30 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class PlotArea 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. * PlotArea Layout.
* *
@ -101,4 +125,42 @@ class PlotArea
$plotSeries->refresh($worksheet); $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;
}
} }

View File

@ -201,7 +201,7 @@ class Html extends BaseReader
/** /**
* Loads Spreadsheet from file. * Loads Spreadsheet from file.
*/ */
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet public function loadSpreadsheetFromFile(string $filename): Spreadsheet
{ {
// Create new Spreadsheet // Create new Spreadsheet
$spreadsheet = new Spreadsheet(); $spreadsheet = new Spreadsheet();
@ -651,7 +651,13 @@ class Html extends BaseReader
// Reload the HTML file into the DOM object // Reload the HTML file into the DOM object
try { try {
$convert = $this->securityScanner->scanFile($filename); $convert = $this->securityScanner->scanFile($filename);
$loaded = $dom->loadHTML($convert); $lowend = "\u{80}";
$highend = "\u{10ffff}";
$regexp = "/[$lowend-$highend]/u";
/** @var callable */
$callback = [self::class, 'replaceNonAscii'];
$convert = preg_replace_callback($regexp, $callback, $convert);
$loaded = ($convert === null) ? false : $dom->loadHTML($convert);
} catch (Throwable $e) { } catch (Throwable $e) {
$loaded = false; $loaded = false;
} }
@ -662,6 +668,11 @@ class Html extends BaseReader
return $this->loadDocument($dom, $spreadsheet); return $this->loadDocument($dom, $spreadsheet);
} }
private static function replaceNonAscii(array $matches): string
{
return '&#' . mb_ord($matches[0], 'UTF-8') . ';';
}
/** /**
* Spreadsheet from content. * Spreadsheet from content.
* *
@ -674,7 +685,13 @@ class Html extends BaseReader
// Reload the HTML file into the DOM object // Reload the HTML file into the DOM object
try { try {
$convert = $this->securityScanner->scan($content); $convert = $this->securityScanner->scan($content);
$loaded = $dom->loadHTML($convert); $lowend = "\u{80}";
$highend = "\u{10ffff}";
$regexp = "/[$lowend-$highend]/u";
/** @var callable */
$callback = [self::class, 'replaceNonAscii'];
$convert = preg_replace_callback($regexp, $callback, $convert);
$loaded = ($convert === null) ? false : $dom->loadHTML($convert);
} catch (Throwable $e) { } catch (Throwable $e) {
$loaded = false; $loaded = false;
} }

View File

@ -73,8 +73,18 @@ class Chart
$xAxis = new Axis(); $xAxis = new Axis();
$yAxis = new Axis(); $yAxis = new Axis();
$autoTitleDeleted = null; $autoTitleDeleted = null;
$chartNoFill = false;
$gradientArray = [];
$gradientLin = null;
foreach ($chartElementsC as $chartElementKey => $chartElement) { foreach ($chartElementsC as $chartElementKey => $chartElement) {
switch ($chartElementKey) { switch ($chartElementKey) {
case 'spPr':
$possibleNoFill = $chartElementsC->spPr->children($this->aNamespace);
if (isset($possibleNoFill->noFill)) {
$chartNoFill = true;
}
break;
case 'chart': case 'chart':
foreach ($chartElement as $chartDetailsKey => $chartDetails) { foreach ($chartElement as $chartDetailsKey => $chartDetails) {
$chartDetailsC = $chartDetails->children($this->cNamespace); $chartDetailsC = $chartDetails->children($this->cNamespace);
@ -95,8 +105,29 @@ class Chart
$plotAreaLayout = $XaxisLabel = $YaxisLabel = null; $plotAreaLayout = $XaxisLabel = $YaxisLabel = null;
$plotSeries = $plotAttributes = []; $plotSeries = $plotAttributes = [];
$catAxRead = false; $catAxRead = false;
$plotNoFill = false;
foreach ($chartDetails as $chartDetailKey => $chartDetail) { foreach ($chartDetails as $chartDetailKey => $chartDetail) {
switch ($chartDetailKey) { 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': case 'layout':
$plotAreaLayout = $this->chartLayoutDetails($chartDetail); $plotAreaLayout = $this->chartLayoutDetails($chartDetail);
@ -288,6 +319,12 @@ class Chart
} }
$plotArea = new PlotArea($plotAreaLayout, $plotSeries); $plotArea = new PlotArea($plotAreaLayout, $plotSeries);
$this->setChartAttributes($plotAreaLayout, $plotAttributes); $this->setChartAttributes($plotAreaLayout, $plotAttributes);
if ($plotNoFill) {
$plotArea->setNoFill(true);
}
if (!empty($gradientArray)) {
$plotArea->setGradientFillProperties($gradientArray, $gradientLin);
}
break; break;
case 'plotVisOnly': 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); $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)) { if (is_bool($autoTitleDeleted)) {
$chart->setAutoTitleDeleted($autoTitleDeleted); $chart->setAutoTitleDeleted($autoTitleDeleted);
} }
@ -1147,6 +1187,7 @@ class Chart
'type' => null, 'type' => null,
'value' => null, 'value' => null,
'alpha' => null, 'alpha' => null,
'brightness' => null,
]; ];
foreach (ChartColor::EXCEL_COLOR_TYPES as $type) { foreach (ChartColor::EXCEL_COLOR_TYPES as $type) {
if (isset($colorXml->$type)) { if (isset($colorXml->$type)) {
@ -1159,6 +1200,13 @@ class Chart
$result['alpha'] = ChartColor::alphaFromXml($alpha); $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; break;
} }
@ -1236,6 +1284,9 @@ class Chart
if (!isset($whichAxis)) { if (!isset($whichAxis)) {
return; return;
} }
if (isset($chartDetail->delete)) {
$whichAxis->setAxisOption('hidden', (string) self::getAttribute($chartDetail->delete, 'val', 'string'));
}
if (isset($chartDetail->numFmt)) { if (isset($chartDetail->numFmt)) {
$whichAxis->setAxisNumberProperties( $whichAxis->setAxisNumberProperties(
(string) self::getAttribute($chartDetail->numFmt, 'formatCode', 'string'), (string) self::getAttribute($chartDetail->numFmt, 'formatCode', 'string'),
@ -1279,5 +1330,15 @@ class Chart
if (isset($chartDetail->minorUnit)) { if (isset($chartDetail->minorUnit)) {
$whichAxis->setAxisOption('minor_unit', (string) self::getAttribute($chartDetail->minorUnit, 'val', 'string')); $whichAxis->setAxisOption('minor_unit', (string) self::getAttribute($chartDetail->minorUnit, 'val', 'string'));
} }
if (isset($chartDetail->txPr)) {
$children = $chartDetail->txPr->children($this->aNamespace);
if (isset($children->bodyPr)) {
/** @var string */
$textRotation = self::getAttribute($children->bodyPr, 'rot', 'string');
if (is_numeric($textRotation)) {
$whichAxis->setAxisOption('textRotation', (string) Properties::xmlToAngle($textRotation));
}
}
}
} }
} }

View File

@ -3,10 +3,13 @@
namespace PhpOffice\PhpSpreadsheet; namespace PhpOffice\PhpSpreadsheet;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation; 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\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Worksheet\Iterator; use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
class Spreadsheet class Spreadsheet
{ {
@ -1120,28 +1123,24 @@ class Spreadsheet
*/ */
public function copy() public function copy()
{ {
$copied = clone $this; $filename = File::temporaryFilename();
$writer = new XlsxWriter($this);
$writer->setIncludeCharts(true);
$writer->save($filename);
$worksheetCount = count($this->workSheetCollection); $reader = new XlsxReader();
for ($i = 0; $i < $worksheetCount; ++$i) { $reader->setIncludeCharts(true);
$this->workSheetCollection[$i] = $this->workSheetCollection[$i]->copy(); $reloadedSpreadsheet = $reader->load($filename);
$this->workSheetCollection[$i]->rebindParent($this); unlink($filename);
return $reloadedSpreadsheet;
} }
return $copied;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone() public function __clone()
{ {
// @phpstan-ignore-next-line throw new Exception(
foreach ($this as $key => $val) { 'Do not use clone on spreadsheet. Use spreadsheet->copy() instead.'
if (is_object($val) || (is_array($val))) { );
$this->{$key} = unserialize(serialize($val));
}
}
} }
/** /**
@ -1562,7 +1561,7 @@ class Spreadsheet
* Workbook window is hidden and cannot be shown in the * Workbook window is hidden and cannot be shown in the
* user interface. * user interface.
* *
* @param string $visibility visibility status of the workbook * @param null|string $visibility visibility status of the workbook
*/ */
public function setVisibility($visibility): void public function setVisibility($visibility): void
{ {
@ -1596,7 +1595,7 @@ class Spreadsheet
*/ */
public function setTabRatio($tabRatio): void public function setTabRatio($tabRatio): void
{ {
if ($tabRatio >= 0 || $tabRatio <= 1000) { if ($tabRatio >= 0 && $tabRatio <= 1000) {
$this->tabRatio = (int) $tabRatio; $this->tabRatio = (int) $tabRatio;
} else { } else {
throw new Exception('Tab ratio must be between 0 and 1000.'); throw new Exception('Tab ratio must be between 0 and 1000.');

View File

@ -72,30 +72,22 @@ class Chart extends WriterPart
$objWriter->endElement(); $objWriter->endElement();
$objWriter->startElement('c:view3D'); $objWriter->startElement('c:view3D');
$rotX = $chart->getRotX(); $surface2D = false;
if (is_int($rotX)) { $plotArea = $chart->getPlotArea();
$objWriter->startElement('c:rotX'); if ($plotArea !== null) {
$objWriter->writeAttribute('val', "$rotX"); $seriesArray = $plotArea->getPlotGroup();
$objWriter->endElement(); foreach ($seriesArray as $series) {
if ($series->getPlotType() === DataSeries::TYPE_SURFACECHART) {
$surface2D = true;
break;
} }
$rotY = $chart->getRotY();
if (is_int($rotY)) {
$objWriter->startElement('c:rotY');
$objWriter->writeAttribute('val', "$rotY");
$objWriter->endElement();
} }
$rAngAx = $chart->getRAngAx();
if (is_int($rAngAx)) {
$objWriter->startElement('c:rAngAx');
$objWriter->writeAttribute('val', "$rAngAx");
$objWriter->endElement();
}
$perspective = $chart->getPerspective();
if (is_int($perspective)) {
$objWriter->startElement('c:perspective');
$objWriter->writeAttribute('val', "$perspective");
$objWriter->endElement();
} }
$this->writeView3D($objWriter, $chart->getRotX(), 'c:rotX', $surface2D, 90);
$this->writeView3D($objWriter, $chart->getRotY(), 'c:rotY', $surface2D);
$this->writeView3D($objWriter, $chart->getRAngAx(), 'c:rAngAx', $surface2D);
$this->writeView3D($objWriter, $chart->getPerspective(), 'c:perspective', $surface2D);
$objWriter->endElement(); // view3D $objWriter->endElement(); // view3D
$this->writePlotArea($objWriter, $chart->getPlotArea(), $chart->getXAxisLabel(), $chart->getYAxisLabel(), $chart->getChartAxisX(), $chart->getChartAxisY()); $this->writePlotArea($objWriter, $chart->getPlotArea(), $chart->getXAxisLabel(), $chart->getYAxisLabel(), $chart->getChartAxisX(), $chart->getChartAxisY());
@ -114,16 +106,34 @@ class Chart extends WriterPart
$objWriter->writeAttribute('val', '0'); $objWriter->writeAttribute('val', '0');
$objWriter->endElement(); $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); $this->writePrintSettings($objWriter);
$objWriter->endElement(); $objWriter->endElement(); // c:chartSpace
// Return // Return
return $objWriter->getData(); return $objWriter->getData();
} }
private function writeView3D(XMLWriter $objWriter, ?int $value, string $tag, bool $surface2D, int $default = 0): void
{
if ($value === null && $surface2D) {
$value = $default;
}
if ($value !== null) {
$objWriter->startElement($tag);
$objWriter->writeAttribute('val', "$value");
$objWriter->endElement();
}
}
/** /**
* Write Chart Title. * Write Chart Title.
*/ */
@ -356,8 +366,35 @@ class Chart extends WriterPart
$this->writeSerAxis($objWriter, $id2, $id3); $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 private function writeDataLabelsBool(XMLWriter $objWriter, string $name, ?bool $value): void
@ -488,7 +525,7 @@ class Chart extends WriterPart
$objWriter->endElement(); // c:scaling $objWriter->endElement(); // c:scaling
$objWriter->startElement('c:delete'); $objWriter->startElement('c:delete');
$objWriter->writeAttribute('val', '0'); $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('hidden') ?? '0');
$objWriter->endElement(); $objWriter->endElement();
$objWriter->startElement('c:axPos'); $objWriter->startElement('c:axPos');
@ -569,6 +606,23 @@ class Chart extends WriterPart
$objWriter->endElement(); $objWriter->endElement();
} }
$textRotation = $yAxis->getAxisOptionsProperty('textRotation');
if (is_numeric($textRotation)) {
$objWriter->startElement('c:txPr');
$objWriter->startElement('a:bodyPr');
$objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation));
$objWriter->endElement(); // a:bodyPr
$objWriter->startElement('a:lstStyle');
$objWriter->endElement(); // a:lstStyle
$objWriter->startElement('a:p');
$objWriter->startElement('a:pPr');
$objWriter->startElement('a:defRPr');
$objWriter->endElement(); // a:defRPr
$objWriter->endElement(); // a:pPr
$objWriter->endElement(); // a:p
$objWriter->endElement(); // c:txPr
}
$objWriter->startElement('c:spPr'); $objWriter->startElement('c:spPr');
$this->writeColor($objWriter, $yAxis->getFillColorObject()); $this->writeColor($objWriter, $yAxis->getFillColorObject());
$this->writeEffects($objWriter, $yAxis); $this->writeEffects($objWriter, $yAxis);
@ -661,7 +715,7 @@ class Chart extends WriterPart
$objWriter->endElement(); // c:scaling $objWriter->endElement(); // c:scaling
$objWriter->startElement('c:delete'); $objWriter->startElement('c:delete');
$objWriter->writeAttribute('val', '0'); $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('hidden') ?? '0');
$objWriter->endElement(); $objWriter->endElement();
$objWriter->startElement('c:axPos'); $objWriter->startElement('c:axPos');
@ -744,6 +798,23 @@ class Chart extends WriterPart
$objWriter->endElement(); $objWriter->endElement();
} }
$textRotation = $xAxis->getAxisOptionsProperty('textRotation');
if (is_numeric($textRotation)) {
$objWriter->startElement('c:txPr');
$objWriter->startElement('a:bodyPr');
$objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation));
$objWriter->endElement(); // a:bodyPr
$objWriter->startElement('a:lstStyle');
$objWriter->endElement(); // a:lstStyle
$objWriter->startElement('a:p');
$objWriter->startElement('a:pPr');
$objWriter->startElement('a:defRPr');
$objWriter->endElement(); // a:defRPr
$objWriter->endElement(); // a:pPr
$objWriter->endElement(); // a:p
$objWriter->endElement(); // c:txPr
}
$objWriter->startElement('c:spPr'); $objWriter->startElement('c:spPr');
$this->writeColor($objWriter, $xAxis->getFillColorObject()); $this->writeColor($objWriter, $xAxis->getFillColorObject());
$this->writeLineStyles($objWriter, $xAxis); $this->writeLineStyles($objWriter, $xAxis);
@ -913,8 +984,8 @@ class Chart extends WriterPart
$objWriter->endElement(); $objWriter->endElement();
} }
if ($plotGroup->getPlotGrouping() !== null) {
$plotGroupingType = $plotGroup->getPlotGrouping(); $plotGroupingType = $plotGroup->getPlotGrouping();
if ($plotGroupingType !== null && $groupType !== DataSeries::TYPE_SURFACECHART && $groupType !== DataSeries::TYPE_SURFACECHART_3D) {
$objWriter->startElement('c:grouping'); $objWriter->startElement('c:grouping');
$objWriter->writeAttribute('val', $plotGroupingType); $objWriter->writeAttribute('val', $plotGroupingType);
$objWriter->endElement(); $objWriter->endElement();
@ -1574,7 +1645,18 @@ class Chart extends WriterPart
if (is_numeric($alpha)) { if (is_numeric($alpha)) {
$objWriter->startElement('a:alpha'); $objWriter->startElement('a:alpha');
$objWriter->writeAttribute('val', ChartColor::alphaToXml((int) $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 $objWriter->endElement(); //a:srgbClr/schemeClr/prstClr
if ($solidFill) { if ($solidFill) {

View File

@ -0,0 +1,34 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PHPUnit\Framework\TestCase;
class ArrayTest extends TestCase
{
public function testMultiDimensionalArrayIsFlattened(): void
{
$array = [
0 => [
0 => [
32 => [
'B' => 'PHP',
],
],
],
1 => [
0 => [
32 => [
'C' => 'Spreadsheet',
],
],
],
];
$values = Functions::flattenArray($array);
self::assertIsNotArray($values[0]);
self::assertIsNotArray($values[1]);
}
}

View File

@ -3,26 +3,42 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef; namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class VLookupTest extends TestCase class VLookupTest extends TestCase
{ {
protected function setUp(): void
{
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
}
/** /**
* @dataProvider providerVLOOKUP * @dataProvider providerVLOOKUP
* *
* @param mixed $expectedResult * @param mixed $expectedResult
* @param mixed $value
* @param mixed $table
* @param mixed $index
*/ */
public function testVLOOKUP($expectedResult, ...$args): void public function testVLOOKUP($expectedResult, $value, $table, $index, ?bool $lookup = null): void
{ {
$result = LookupRef::VLOOKUP(...$args); $spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
if (is_array($table)) {
$sheet->fromArray($table);
$dimension = $sheet->calculateWorksheetDimension();
} else {
$sheet->getCell('A1')->setValue($table);
$dimension = 'A1';
}
if ($lookup === null) {
$lastarg = '';
} else {
$lastarg = $lookup ? ',TRUE' : ',FALSE';
}
$sheet->getCell('Z98')->setValue($value);
$sheet->getCell('Z97')->setValue($index);
$sheet->getCell('Z99')->setValue("=VLOOKUP(Z98,$dimension,Z97$lastarg)");
$result = $sheet->getCell('Z99')->getCalculatedValue();
self::assertEquals($expectedResult, $result); self::assertEquals($expectedResult, $result);
$spreadsheet->disconnectWorksheets();
} }
public function providerVLOOKUP(): array public function providerVLOOKUP(): array

View File

@ -2,6 +2,8 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
class ConcatenateTest extends AllSetupTeardown class ConcatenateTest extends AllSetupTeardown
{ {
/** /**
@ -30,4 +32,27 @@ class ConcatenateTest extends AllSetupTeardown
{ {
return require 'tests/data/Calculation/TextData/CONCATENATE.php'; 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();
}
} }

View File

@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheetTests\Chart; namespace PhpOffice\PhpSpreadsheetTests\Chart;
use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Chart\Properties;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\RichText;
@ -391,6 +392,9 @@ class Charts32ScatterTest extends AbstractFunctional
$chart = $charts[0]; $chart = $charts[0];
self::assertNotNull($chart); self::assertNotNull($chart);
$xAxis = $chart->getChartAxisX();
self::assertEquals(45, $xAxis->getAxisOptionsProperty('textRotation'));
$plotArea = $chart->getPlotArea(); $plotArea = $chart->getPlotArea();
self::assertNotNull($plotArea); self::assertNotNull($plotArea);
$plotSeries = $plotArea->getPlotGroup(); $plotSeries = $plotArea->getPlotGroup();
@ -444,4 +448,88 @@ class Charts32ScatterTest extends AbstractFunctional
$reloadedSpreadsheet->disconnectWorksheets(); $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();
}
} }

View File

@ -0,0 +1,111 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Chart;
use PhpOffice\PhpSpreadsheet\Chart\Chart;
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
use PhpOffice\PhpSpreadsheet\Chart\Title;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
use PHPUnit\Framework\TestCase;
class Issue2931Test extends TestCase
{
public function testSurface(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$dataSeriesLabels = [
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, null, null, 1, ['5-6']),
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, null, null, 1, ['6-7']),
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, null, null, 1, ['7-8']),
];
$xAxisTickValues = [
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, null, null, 9, [1, 2, 3, 4, 5, 6, 7, 8, 9]),
];
$dataSeriesValues = [
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, null, null, 9, [6, 6, 6, 6, 6, 6, 5.9, 6, 6]),
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, null, null, 9, [6, 6, 6, 6.5, 7, 7, 7, 7, 7]),
new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, null, null, 9, [6, 6, 6, 7, 8, 8, 8, 8, 7.9]),
];
$series = new DataSeries(
DataSeries::TYPE_SURFACECHART,
DataSeries::GROUPING_STANDARD, // grouping should not be written for surface chart
range(0, count($dataSeriesValues) - 1),
$dataSeriesLabels,
$xAxisTickValues,
$dataSeriesValues,
null, // plotDirection
false, // smooth line
DataSeries::STYLE_LINEMARKER // plotStyle
);
$plotArea = new PlotArea(null, [$series]);
$legend = new ChartLegend(ChartLegend::POSITION_BOTTOM, null, false);
$title = new Title('График распредления температур в пределах кр');
$chart = new Chart(
'chart2',
$title,
$legend,
$plotArea,
true,
DataSeries::EMPTY_AS_GAP,
);
$chart->setTopLeftPosition('$A$1');
$chart->setBottomRightPosition('$P$20');
$sheet->addChart($chart);
$writer = new XlsxWriter($spreadsheet);
$writer->setIncludeCharts(true);
$writer = new XlsxWriter($spreadsheet);
$writer->setIncludeCharts(true);
$writerChart = new XlsxWriter\Chart($writer);
$data = $writerChart->writeChart($chart);
// rotX etc. should be generated for surfaceChart 2D
// even when unspecified.
$expectedXml2D = [
'<c:view3D><c:rotX val="90"/><c:rotY val="0"/><c:rAngAx val="0"/><c:perspective val="0"/></c:view3D>',
];
$expectedXml3D = [
'<c:view3D/>',
];
$expectedXmlNoX = [
'c:grouping',
];
// confirm that file contains expected tags
foreach ($expectedXml2D as $expected) {
self::assertSame(1, substr_count($data, $expected), $expected);
}
foreach ($expectedXmlNoX as $expected) {
self::assertSame(0, substr_count($data, $expected), $expected);
}
$series->setPlotType(DataSeries::TYPE_SURFACECHART_3D);
$plotArea = new PlotArea(null, [$series]);
$chart->setPlotArea($plotArea);
$writerChart = new XlsxWriter\Chart($writer);
$data = $writerChart->writeChart($chart);
// confirm that file contains expected tags
foreach ($expectedXml3D as $expected) {
self::assertSame(1, substr_count($data, $expected), $expected);
}
foreach ($expectedXmlNoX as $expected) {
self::assertSame(0, substr_count($data, $expected), $expected);
}
$spreadsheet->disconnectWorksheets();
}
}

View File

@ -85,6 +85,18 @@ class DefinedNameTest extends TestCase
self::assertCount(1, $this->spreadsheet->getDefinedNames()); 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 public function testRemoveGlobalDefinedNameWhenDuplicateNames(): void
{ {
$this->spreadsheet->addDefinedName( $this->spreadsheet->addDefinedName(

View File

@ -133,4 +133,10 @@ class NamedFormulaTest extends TestCase
$formula->getValue() $formula->getValue()
); );
} }
public function testRemoveNonExistentNamedFormula(): void
{
self::assertCount(0, $this->spreadsheet->getNamedFormulae());
$this->spreadsheet->removeNamedFormula('Any');
}
} }

View File

@ -133,4 +133,10 @@ class NamedRangeTest extends TestCase
$range->getValue() $range->getValue()
); );
} }
public function testRemoveNonExistentNamedRange(): void
{
self::assertCount(0, $this->spreadsheet->getNamedRanges());
$this->spreadsheet->removeNamedRange('Any');
}
} }

View File

@ -13,7 +13,7 @@ class HtmlImageTest extends TestCase
$html = '<table> $html = '<table>
<tr> <tr>
<td><img src="' . $imagePath . '" alt="test image"></td> <td><img src="' . $imagePath . '" alt="test image voilà"></td>
</tr> </tr>
</table>'; </table>';
$filename = HtmlHelper::createHtml($html); $filename = HtmlHelper::createHtml($html);
@ -24,7 +24,7 @@ class HtmlImageTest extends TestCase
$drawing = $firstSheet->getDrawingCollection()[0]; $drawing = $firstSheet->getDrawingCollection()[0];
self::assertEquals($imagePath, $drawing->getPath()); self::assertEquals($imagePath, $drawing->getPath());
self::assertEquals('A1', $drawing->getCoordinates()); self::assertEquals('A1', $drawing->getCoordinates());
self::assertEquals('test image', $drawing->getName()); self::assertEquals('test image voilà', $drawing->getName());
self::assertEquals('100', $drawing->getWidth()); self::assertEquals('100', $drawing->getWidth());
self::assertEquals('100', $drawing->getHeight()); self::assertEquals('100', $drawing->getHeight());
} }

View File

@ -0,0 +1,36 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Reader\Html;
use PhpOffice\PhpSpreadsheet\Reader\Html;
use PHPUnit\Framework\TestCase;
class Issue2942Test extends TestCase
{
public function testLoadFromString(): void
{
$content = '<table><tbody><tr><td>éàâèî</td></tr></tbody></table>';
$reader = new Html();
$spreadsheet = $reader->loadFromString($content);
$sheet = $spreadsheet->getActiveSheet();
self::assertSame('éàâèî', $sheet->getCell('A1')->getValue());
}
public function testLoadFromFile(): void
{
$file = 'tests/data/Reader/HTML/utf8chars.html';
$reader = new Html();
$spreadsheet = $reader->loadSpreadsheetFromFile($file);
$sheet = $spreadsheet->getActiveSheet();
self::assertSame('Test Utf-8 characters voilà', $sheet->getTitle());
self::assertSame('éàâèî', $sheet->getCell('A1')->getValue());
self::assertSame('αβγδε', $sheet->getCell('B1')->getValue());
self::assertSame('𐐁𐐂𐐃 & だけち', $sheet->getCell('A2')->getValue());
self::assertSame('אבגדה', $sheet->getCell('B2')->getValue());
self::assertSame('𪔀𪔁𪔂', $sheet->getCell('C2')->getValue());
self::assertSame('᠐᠑᠒', $sheet->getCell('A3')->getValue());
self::assertSame('അആ', $sheet->getCell('B3')->getValue());
self::assertSame('กขฃ', $sheet->getCell('C3')->getValue());
self::assertSame('✀✐✠', $sheet->getCell('D3')->getValue());
}
}

View File

@ -24,5 +24,7 @@ class Issue2301Test extends \PHPUnit\Framework\TestCase
self::assertSame('Arial CE', $font->getName()); self::assertSame('Arial CE', $font->getName());
self::assertSame(9.0, $font->getSize()); self::assertSame(9.0, $font->getSize());
self::assertSame('protected', $sheet->getCell('BT10')->getStyle()->getProtection()->getHidden()); self::assertSame('protected', $sheet->getCell('BT10')->getStyle()->getProtection()->getHidden());
$spreadsheet->disconnectWorksheets();
unset($spreadsheet);
} }
} }

View File

@ -44,4 +44,34 @@ class RibbonTest extends AbstractFunctional
self::assertNull($reloadedSpreadsheet->getRibbonBinObjects()); self::assertNull($reloadedSpreadsheet->getRibbonBinObjects());
$reloadedSpreadsheet->disconnectWorksheets(); $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();
}
} }

View File

@ -0,0 +1,209 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests;
use PhpOffice\PhpSpreadsheet\Exception as SSException;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PHPUnit\Framework\TestCase;
class SpreadsheetCoverageTest extends TestCase
{
public function testDocumentProperties(): void
{
$spreadsheet = new Spreadsheet();
$properties = $spreadsheet->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();
}
}

View File

@ -10,15 +10,24 @@ use PHPUnit\Framework\TestCase;
class CellMatcherTest extends TestCase class CellMatcherTest extends TestCase
{ {
/** /**
* @var Spreadsheet * @var ?Spreadsheet
*/ */
protected $spreadsheet; protected $spreadsheet;
protected function setUp(): void protected function loadSpreadsheet(): Spreadsheet
{ {
$filename = 'tests/data/Style/ConditionalFormatting/CellMatcher.xlsx'; $filename = 'tests/data/Style/ConditionalFormatting/CellMatcher.xlsx';
$reader = IOFactory::createReader('Xlsx'); $reader = IOFactory::createReader('Xlsx');
$this->spreadsheet = $reader->load($filename);
return $reader->load($filename);
}
protected function tearDown(): void
{
if ($this->spreadsheet !== null) {
$this->spreadsheet->disconnectWorksheets();
$this->spreadsheet = null;
}
} }
/** /**
@ -26,16 +35,13 @@ class CellMatcherTest extends TestCase
*/ */
public function testBasicCellIsComparison(string $sheetname, string $cellAddress, array $expectedMatches): void public function testBasicCellIsComparison(string $sheetname, string $cellAddress, array $expectedMatches): void
{ {
$this->spreadsheet = $this->loadSpreadsheet();
$worksheet = $this->spreadsheet->getSheetByName($sheetname); $worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) { self::assertNotNull($worksheet, "$sheetname not found in test workbook");
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress); $cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); $cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) { self::assertNotNull($cfRange, "{$cellAddress} is not in a Conditional Format range");
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate()); $cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange); $matcher = new CellMatcher($cell, $cfRange);
@ -82,16 +88,13 @@ class CellMatcherTest extends TestCase
*/ */
public function testRangeCellIsComparison(string $sheetname, string $cellAddress, bool $expectedMatch): void public function testRangeCellIsComparison(string $sheetname, string $cellAddress, bool $expectedMatch): void
{ {
$this->spreadsheet = $this->loadSpreadsheet();
$worksheet = $this->spreadsheet->getSheetByName($sheetname); $worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) { self::assertNotNull($worksheet, "$sheetname not found in test workbook");
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress); $cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); $cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) { self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate()); $cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange); $matcher = new CellMatcher($cell, $cfRange);
@ -128,16 +131,13 @@ class CellMatcherTest extends TestCase
*/ */
public function testCellIsMultipleExpression(string $sheetname, string $cellAddress, array $expectedMatches): void public function testCellIsMultipleExpression(string $sheetname, string $cellAddress, array $expectedMatches): void
{ {
$this->spreadsheet = $this->loadSpreadsheet();
$worksheet = $this->spreadsheet->getSheetByName($sheetname); $worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) { self::assertNotNull($worksheet, "$sheetname not found in test workbook");
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress); $cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); $cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) { self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate()); $cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange); $matcher = new CellMatcher($cell, $cfRange);
@ -167,16 +167,13 @@ class CellMatcherTest extends TestCase
*/ */
public function testCellIsExpression(string $sheetname, string $cellAddress, bool $expectedMatch): void public function testCellIsExpression(string $sheetname, string $cellAddress, bool $expectedMatch): void
{ {
$this->spreadsheet = $this->loadSpreadsheet();
$worksheet = $this->spreadsheet->getSheetByName($sheetname); $worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) { self::assertNotNull($worksheet, "$sheetname not found in test workbook");
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress); $cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); $cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) { self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate()); $cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange); $matcher = new CellMatcher($cell, $cfRange);
@ -216,16 +213,13 @@ class CellMatcherTest extends TestCase
*/ */
public function testTextExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void public function testTextExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void
{ {
$this->spreadsheet = $this->loadSpreadsheet();
$worksheet = $this->spreadsheet->getSheetByName($sheetname); $worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) { self::assertNotNull($worksheet, "$sheetname not found in test workbook");
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress); $cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); $cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) { self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate()); $cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange); $matcher = new CellMatcher($cell, $cfRange);
@ -329,16 +323,13 @@ class CellMatcherTest extends TestCase
*/ */
public function testBlankExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void public function testBlankExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void
{ {
$this->spreadsheet = $this->loadSpreadsheet();
$worksheet = $this->spreadsheet->getSheetByName($sheetname); $worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) { self::assertNotNull($worksheet, "$sheetname not found in test workbook");
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress); $cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); $cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) { self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate()); $cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange); $matcher = new CellMatcher($cell, $cfRange);
@ -365,16 +356,13 @@ class CellMatcherTest extends TestCase
*/ */
public function testErrorExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void public function testErrorExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void
{ {
$this->spreadsheet = $this->loadSpreadsheet();
$worksheet = $this->spreadsheet->getSheetByName($sheetname); $worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) { self::assertNotNull($worksheet, "$sheetname not found in test workbook");
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress); $cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); $cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) { self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate()); $cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange); $matcher = new CellMatcher($cell, $cfRange);
@ -400,16 +388,13 @@ class CellMatcherTest extends TestCase
*/ */
public function testDateOccurringExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void public function testDateOccurringExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void
{ {
$this->spreadsheet = $this->loadSpreadsheet();
$worksheet = $this->spreadsheet->getSheetByName($sheetname); $worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) { self::assertNotNull($worksheet, "$sheetname not found in test workbook");
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress); $cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); $cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) { self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate()); $cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange); $matcher = new CellMatcher($cell, $cfRange);
@ -447,16 +432,13 @@ class CellMatcherTest extends TestCase
*/ */
public function testDuplicatesExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void public function testDuplicatesExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void
{ {
$this->spreadsheet = $this->loadSpreadsheet();
$worksheet = $this->spreadsheet->getSheetByName($sheetname); $worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) { self::assertNotNull($worksheet, "$sheetname not found in test workbook");
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress); $cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); $cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) { self::AssertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate()); $cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange); $matcher = new CellMatcher($cell, $cfRange);
@ -486,16 +468,13 @@ class CellMatcherTest extends TestCase
*/ */
public function testCrossWorksheetExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void public function testCrossWorksheetExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void
{ {
$this->spreadsheet = $this->loadSpreadsheet();
$worksheet = $this->spreadsheet->getSheetByName($sheetname); $worksheet = $this->spreadsheet->getSheetByName($sheetname);
if ($worksheet === null) { self::assertNotNull($worksheet, "$sheetname not found in test workbook");
self::markTestSkipped("{$sheetname} not found in test workbook");
}
$cell = $worksheet->getCell($cellAddress); $cell = $worksheet->getCell($cellAddress);
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate()); $cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
if ($cfRange === null) { self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
}
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate()); $cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
$matcher = new CellMatcher($cell, $cfRange); $matcher = new CellMatcher($cell, $cfRange);

View File

@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheetTests\Style; namespace PhpOffice\PhpSpreadsheetTests\Style;
use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Font;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class FontTest extends TestCase class FontTest extends TestCase
@ -88,4 +89,20 @@ class FontTest extends TestCase
self::assertEquals('Calibri', $font->getName(), 'Null string changed to default'); self::assertEquals('Calibri', $font->getName(), 'Null string changed to default');
$spreadsheet->disconnectWorksheets(); $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);
}
} }

View File

@ -186,4 +186,14 @@ return [
3, 3,
true, true,
], ],
'issue2934' => [
'Red',
102,
[
[null, 102],
[null, 'Red'],
],
2,
false,
],
]; ];

View File

@ -98,7 +98,7 @@ return [
['10y1', 7.0], ['10y1', 7.0],
['10y2', 10.0], ['10y2', 10.0],
], ],
'NaN', -5,
], ],
[ [
'#REF!', '#REF!',
@ -111,9 +111,9 @@ return [
'#REF!', '#REF!',
'10y2', '10y2',
[ [
2.0, [2.0],
7.0, [7.0],
10.0, [10.0],
], ],
2.0, 2.0,
], ],
@ -163,4 +163,34 @@ return [
3, 3,
null, null,
], ],
'issue2934' => [
'Red',
102,
[
[null, null],
[102, 'Red'],
],
2,
false,
],
'string supplied as index' => [
'#VALUE!',
102,
[
[null, null],
[102, 'Red'],
],
'xyz',
false,
],
'num error propagated' => [
'#NUM!',
102,
[
[null, null],
[102, 'Red'],
],
'=SQRT(-1)',
false,
],
]; ];

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<!-- deliberately do not identify charset for this test -->
<title>Test Utf-8 characters voilà</title>
</head>
<body>
<table>
<tbody>
<tr>
<td>éàâèî</td><!-- Latin1 -->
<td>αβγδε</td><!-- Greek -->
</tr>
<tr>
<td>𐐁𐐂𐐃 &amp; だけち</td><!-- Osmanya (not in BMP) and Hiragana -->
<td>אבגדה</td><!-- Hebrew -->
<td>𪔀𪔁𪔂</td><!-- CJK Unified Ideographs Extension B (not in BMP) -->
</tr>
<tr>
<td>᠐᠑᠒</td><!-- Mongolian -->
<td>അആ</td><!-- Malayalam -->
<td>กขฃ</td><!-- Thai -->
<td>✀✐✠</td><!-- Dingbats -->
</tr>
</tbody>
</table>
</body>
</html>