Merge branch 'master' into TextFunctions-New
This commit is contained in:
commit
345c0ebdfc
|
|
@ -34,7 +34,7 @@ If this is an issue with reading a specific spreadsheet file, then it may be app
|
|||
- [ ] Writer
|
||||
- [ ] Styles
|
||||
- [ ] Data Validations
|
||||
- [ ] Formula Calulations
|
||||
- [ ] Formula Calculations
|
||||
- [ ] Charts
|
||||
- [ ] AutoFilter
|
||||
- [ ] Form Elements
|
||||
|
|
|
|||
27
CHANGELOG.md
27
CHANGELOG.md
|
|
@ -25,8 +25,35 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
|||
|
||||
### 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
|
||||
|
||||
### 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
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -2860,11 +2860,6 @@ parameters:
|
|||
count: 1
|
||||
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\\.$#"
|
||||
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
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ $dataSeriesValues[2]->setScatterLines(false); // points not connected
|
|||
$xAxis = new Axis();
|
||||
//$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE );
|
||||
$xAxis->setAxisNumberProperties(Properties::FORMAT_CODE_DATE_ISO8601, true);
|
||||
$xAxis->setAxisOption('textRotation', '45');
|
||||
|
||||
$yAxis = new Axis();
|
||||
$yAxis->setLineStyleProperties(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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.
Binary file not shown.
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class ErrorValue
|
|||
return false;
|
||||
}
|
||||
|
||||
return in_array($value, ExcelError::$errorCodes, true);
|
||||
return in_array($value, ExcelError::ERROR_CODES, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class ExcelError
|
|||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public static $errorCodes = [
|
||||
public const ERROR_CODES = [
|
||||
'null' => '#NULL!', // 1
|
||||
'divisionbyzero' => '#DIV/0!', // 2
|
||||
'value' => '#VALUE!', // 3
|
||||
|
|
@ -30,12 +30,23 @@ class ExcelError
|
|||
'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
|
||||
*/
|
||||
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;
|
||||
foreach (self::$errorCodes as $errorCode) {
|
||||
foreach (self::ERROR_CODES as $errorCode) {
|
||||
if ($value === $errorCode) {
|
||||
return $i;
|
||||
}
|
||||
|
|
@ -71,7 +82,7 @@ class ExcelError
|
|||
*/
|
||||
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
|
||||
{
|
||||
return self::$errorCodes['num'];
|
||||
return self::ERROR_CODES['num'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -95,7 +106,7 @@ class ExcelError
|
|||
*/
|
||||
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
|
||||
{
|
||||
return self::$errorCodes['na'];
|
||||
return self::ERROR_CODES['na'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -123,7 +134,7 @@ class ExcelError
|
|||
*/
|
||||
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
|
||||
{
|
||||
return self::$errorCodes['name'];
|
||||
return self::ERROR_CODES['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -145,7 +156,7 @@ class ExcelError
|
|||
*/
|
||||
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
|
||||
{
|
||||
return self::$errorCodes['calculation'];
|
||||
return self::ERROR_CODES['calculation'];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class HLookup extends LookupBase
|
|||
*/
|
||||
private static function hLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int
|
||||
{
|
||||
$lookupLower = StringHelper::strToLower($lookupValue);
|
||||
$lookupLower = StringHelper::strToLower((string) $lookupValue);
|
||||
|
||||
$rowNumber = null;
|
||||
foreach ($lookupArray[$column] as $rowKey => $rowData) {
|
||||
|
|
|
|||
|
|
@ -19,8 +19,16 @@ abstract class LookupBase
|
|||
|
||||
protected static function validateIndexLookup(array $lookup_array, $index_number): int
|
||||
{
|
||||
// index_number must be a number greater than or equal to 1
|
||||
if (!is_numeric($index_number) || $index_number < 1) {
|
||||
// index_number must be a number greater than or equal to 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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ class VLookup extends LookupBase
|
|||
{
|
||||
reset($a);
|
||||
$firstColumn = key($a);
|
||||
$aLower = StringHelper::strToLower($a[$firstColumn]);
|
||||
$bLower = StringHelper::strToLower($b[$firstColumn]);
|
||||
$aLower = StringHelper::strToLower((string) $a[$firstColumn]);
|
||||
$bLower = StringHelper::strToLower((string) $b[$firstColumn]);
|
||||
|
||||
if ($aLower == $bLower) {
|
||||
return 0;
|
||||
|
|
@ -84,7 +84,7 @@ class VLookup extends LookupBase
|
|||
*/
|
||||
private static function vLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int
|
||||
{
|
||||
$lookupLower = StringHelper::strToLower($lookupValue);
|
||||
$lookupLower = StringHelper::strToLower((string) $lookupValue);
|
||||
|
||||
$rowNumber = null;
|
||||
foreach ($lookupArray as $rowKey => $rowData) {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ class Axis extends Properties
|
|||
'axis_labels' => self::AXIS_LABELS_NEXT_TO,
|
||||
'horizontal_crosses' => self::HORIZONTAL_CROSSES_AUTOZERO,
|
||||
'horizontal_crosses_value' => null,
|
||||
'textRotation' => null,
|
||||
'hidden' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -136,7 +138,9 @@ class Axis extends Properties
|
|||
?string $minimum = null,
|
||||
?string $maximum = null,
|
||||
?string $majorUnit = null,
|
||||
?string $minorUnit = null
|
||||
?string $minorUnit = null,
|
||||
?string $textRotation = null,
|
||||
?string $hidden = null
|
||||
): void {
|
||||
$this->axisOptions['axis_labels'] = $axisLabels;
|
||||
$this->setAxisOption('horizontal_crosses_value', $horizontalCrossesValue);
|
||||
|
|
@ -144,11 +148,12 @@ class Axis extends Properties
|
|||
$this->setAxisOption('orientation', $axisOrientation);
|
||||
$this->setAxisOption('major_tick_mark', $majorTmt);
|
||||
$this->setAxisOption('minor_tick_mark', $minorTmt);
|
||||
$this->setAxisOption('minor_tick_mark', $minorTmt);
|
||||
$this->setAxisOption('minimum', $minimum);
|
||||
$this->setAxisOption('maximum', $maximum);
|
||||
$this->setAxisOption('major_unit', $majorUnit);
|
||||
$this->setAxisOption('minor_unit', $minorUnit);
|
||||
$this->setAxisOption('textRotation', $textRotation);
|
||||
$this->setAxisOption('hidden', $hidden);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class DataSeries
|
|||
private $plotCategory = [];
|
||||
|
||||
/**
|
||||
* Smooth Line.
|
||||
* Smooth Line. Must be specified for both DataSeries and DataSeriesValues.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -536,7 +536,7 @@ class DataSeriesValues extends Properties
|
|||
}
|
||||
|
||||
/**
|
||||
* Smooth Line.
|
||||
* Smooth Line. Must be specified for both DataSeries and DataSeriesValues.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ class Html extends BaseReader
|
|||
/**
|
||||
* Loads Spreadsheet from file.
|
||||
*/
|
||||
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
|
||||
public function loadSpreadsheetFromFile(string $filename): Spreadsheet
|
||||
{
|
||||
// Create new Spreadsheet
|
||||
$spreadsheet = new Spreadsheet();
|
||||
|
|
@ -651,7 +651,13 @@ class Html extends BaseReader
|
|||
// Reload the HTML file into the DOM object
|
||||
try {
|
||||
$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) {
|
||||
$loaded = false;
|
||||
}
|
||||
|
|
@ -662,6 +668,11 @@ class Html extends BaseReader
|
|||
return $this->loadDocument($dom, $spreadsheet);
|
||||
}
|
||||
|
||||
private static function replaceNonAscii(array $matches): string
|
||||
{
|
||||
return '&#' . mb_ord($matches[0], 'UTF-8') . ';';
|
||||
}
|
||||
|
||||
/**
|
||||
* Spreadsheet from content.
|
||||
*
|
||||
|
|
@ -674,7 +685,13 @@ class Html extends BaseReader
|
|||
// Reload the HTML file into the DOM object
|
||||
try {
|
||||
$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) {
|
||||
$loaded = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
@ -1279,5 +1330,15 @@ class Chart
|
|||
if (isset($chartDetail->minorUnit)) {
|
||||
$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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
|
|
|
|||
|
|
@ -72,30 +72,22 @@ class Chart extends WriterPart
|
|||
$objWriter->endElement();
|
||||
|
||||
$objWriter->startElement('c:view3D');
|
||||
$rotX = $chart->getRotX();
|
||||
if (is_int($rotX)) {
|
||||
$objWriter->startElement('c:rotX');
|
||||
$objWriter->writeAttribute('val', "$rotX");
|
||||
$objWriter->endElement();
|
||||
}
|
||||
$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();
|
||||
$surface2D = false;
|
||||
$plotArea = $chart->getPlotArea();
|
||||
if ($plotArea !== null) {
|
||||
$seriesArray = $plotArea->getPlotGroup();
|
||||
foreach ($seriesArray as $series) {
|
||||
if ($series->getPlotType() === DataSeries::TYPE_SURFACECHART) {
|
||||
$surface2D = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$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
|
||||
|
||||
$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->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();
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
|
|
@ -356,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
|
||||
|
|
@ -488,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');
|
||||
|
|
@ -569,6 +606,23 @@ class Chart extends WriterPart
|
|||
$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');
|
||||
$this->writeColor($objWriter, $yAxis->getFillColorObject());
|
||||
$this->writeEffects($objWriter, $yAxis);
|
||||
|
|
@ -661,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');
|
||||
|
|
@ -744,6 +798,23 @@ class Chart extends WriterPart
|
|||
$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');
|
||||
$this->writeColor($objWriter, $xAxis->getFillColorObject());
|
||||
$this->writeLineStyles($objWriter, $xAxis);
|
||||
|
|
@ -913,8 +984,8 @@ class Chart extends WriterPart
|
|||
$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->writeAttribute('val', $plotGroupingType);
|
||||
$objWriter->endElement();
|
||||
|
|
@ -1574,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) {
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,26 +3,42 @@
|
|||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class VLookupTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerVLOOKUP
|
||||
*
|
||||
* @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);
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
}
|
||||
|
||||
public function providerVLOOKUP(): array
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -391,6 +392,9 @@ class Charts32ScatterTest extends AbstractFunctional
|
|||
$chart = $charts[0];
|
||||
self::assertNotNull($chart);
|
||||
|
||||
$xAxis = $chart->getChartAxisX();
|
||||
self::assertEquals(45, $xAxis->getAxisOptionsProperty('textRotation'));
|
||||
|
||||
$plotArea = $chart->getPlotArea();
|
||||
self::assertNotNull($plotArea);
|
||||
$plotSeries = $plotArea->getPlotGroup();
|
||||
|
|
@ -444,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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -133,4 +133,10 @@ class NamedFormulaTest extends TestCase
|
|||
$formula->getValue()
|
||||
);
|
||||
}
|
||||
|
||||
public function testRemoveNonExistentNamedFormula(): void
|
||||
{
|
||||
self::assertCount(0, $this->spreadsheet->getNamedFormulae());
|
||||
$this->spreadsheet->removeNamedFormula('Any');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,4 +133,10 @@ class NamedRangeTest extends TestCase
|
|||
$range->getValue()
|
||||
);
|
||||
}
|
||||
|
||||
public function testRemoveNonExistentNamedRange(): void
|
||||
{
|
||||
self::assertCount(0, $this->spreadsheet->getNamedRanges());
|
||||
$this->spreadsheet->removeNamedRange('Any');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class HtmlImageTest extends TestCase
|
|||
|
||||
$html = '<table>
|
||||
<tr>
|
||||
<td><img src="' . $imagePath . '" alt="test image"></td>
|
||||
<td><img src="' . $imagePath . '" alt="test image voilà"></td>
|
||||
</tr>
|
||||
</table>';
|
||||
$filename = HtmlHelper::createHtml($html);
|
||||
|
|
@ -24,7 +24,7 @@ class HtmlImageTest extends TestCase
|
|||
$drawing = $firstSheet->getDrawingCollection()[0];
|
||||
self::assertEquals($imagePath, $drawing->getPath());
|
||||
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->getHeight());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -24,5 +24,7 @@ class Issue2301Test extends \PHPUnit\Framework\TestCase
|
|||
self::assertSame('Arial CE', $font->getName());
|
||||
self::assertSame(9.0, $font->getSize());
|
||||
self::assertSame('protected', $sheet->getCell('BT10')->getStyle()->getProtection()->getHidden());
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
unset($spreadsheet);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,15 +10,24 @@ use PHPUnit\Framework\TestCase;
|
|||
class CellMatcherTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Spreadsheet
|
||||
* @var ?Spreadsheet
|
||||
*/
|
||||
protected $spreadsheet;
|
||||
|
||||
protected function setUp(): void
|
||||
protected function loadSpreadsheet(): Spreadsheet
|
||||
{
|
||||
$filename = 'tests/data/Style/ConditionalFormatting/CellMatcher.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
|
||||
{
|
||||
$this->spreadsheet = $this->loadSpreadsheet();
|
||||
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
|
||||
if ($worksheet === null) {
|
||||
self::markTestSkipped("{$sheetname} not found in test workbook");
|
||||
}
|
||||
self::assertNotNull($worksheet, "$sheetname not found in test workbook");
|
||||
$cell = $worksheet->getCell($cellAddress);
|
||||
|
||||
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
|
||||
if ($cfRange === null) {
|
||||
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
|
||||
}
|
||||
self::assertNotNull($cfRange, "{$cellAddress} is not in a Conditional Format range");
|
||||
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
|
||||
|
||||
$matcher = new CellMatcher($cell, $cfRange);
|
||||
|
|
@ -82,16 +88,13 @@ class CellMatcherTest extends TestCase
|
|||
*/
|
||||
public function testRangeCellIsComparison(string $sheetname, string $cellAddress, bool $expectedMatch): void
|
||||
{
|
||||
$this->spreadsheet = $this->loadSpreadsheet();
|
||||
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
|
||||
if ($worksheet === null) {
|
||||
self::markTestSkipped("{$sheetname} not found in test workbook");
|
||||
}
|
||||
self::assertNotNull($worksheet, "$sheetname not found in test workbook");
|
||||
$cell = $worksheet->getCell($cellAddress);
|
||||
|
||||
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
|
||||
if ($cfRange === null) {
|
||||
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
|
||||
}
|
||||
self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
|
||||
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
|
||||
|
||||
$matcher = new CellMatcher($cell, $cfRange);
|
||||
|
|
@ -128,16 +131,13 @@ class CellMatcherTest extends TestCase
|
|||
*/
|
||||
public function testCellIsMultipleExpression(string $sheetname, string $cellAddress, array $expectedMatches): void
|
||||
{
|
||||
$this->spreadsheet = $this->loadSpreadsheet();
|
||||
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
|
||||
if ($worksheet === null) {
|
||||
self::markTestSkipped("{$sheetname} not found in test workbook");
|
||||
}
|
||||
self::assertNotNull($worksheet, "$sheetname not found in test workbook");
|
||||
$cell = $worksheet->getCell($cellAddress);
|
||||
|
||||
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
|
||||
if ($cfRange === null) {
|
||||
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
|
||||
}
|
||||
self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
|
||||
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
|
||||
|
||||
$matcher = new CellMatcher($cell, $cfRange);
|
||||
|
|
@ -167,16 +167,13 @@ class CellMatcherTest extends TestCase
|
|||
*/
|
||||
public function testCellIsExpression(string $sheetname, string $cellAddress, bool $expectedMatch): void
|
||||
{
|
||||
$this->spreadsheet = $this->loadSpreadsheet();
|
||||
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
|
||||
if ($worksheet === null) {
|
||||
self::markTestSkipped("{$sheetname} not found in test workbook");
|
||||
}
|
||||
self::assertNotNull($worksheet, "$sheetname not found in test workbook");
|
||||
$cell = $worksheet->getCell($cellAddress);
|
||||
|
||||
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
|
||||
if ($cfRange === null) {
|
||||
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
|
||||
}
|
||||
self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
|
||||
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
|
||||
|
||||
$matcher = new CellMatcher($cell, $cfRange);
|
||||
|
|
@ -216,16 +213,13 @@ class CellMatcherTest extends TestCase
|
|||
*/
|
||||
public function testTextExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void
|
||||
{
|
||||
$this->spreadsheet = $this->loadSpreadsheet();
|
||||
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
|
||||
if ($worksheet === null) {
|
||||
self::markTestSkipped("{$sheetname} not found in test workbook");
|
||||
}
|
||||
self::assertNotNull($worksheet, "$sheetname not found in test workbook");
|
||||
$cell = $worksheet->getCell($cellAddress);
|
||||
|
||||
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
|
||||
if ($cfRange === null) {
|
||||
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
|
||||
}
|
||||
self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
|
||||
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
|
||||
|
||||
$matcher = new CellMatcher($cell, $cfRange);
|
||||
|
|
@ -329,16 +323,13 @@ class CellMatcherTest extends TestCase
|
|||
*/
|
||||
public function testBlankExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void
|
||||
{
|
||||
$this->spreadsheet = $this->loadSpreadsheet();
|
||||
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
|
||||
if ($worksheet === null) {
|
||||
self::markTestSkipped("{$sheetname} not found in test workbook");
|
||||
}
|
||||
self::assertNotNull($worksheet, "$sheetname not found in test workbook");
|
||||
$cell = $worksheet->getCell($cellAddress);
|
||||
|
||||
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
|
||||
if ($cfRange === null) {
|
||||
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
|
||||
}
|
||||
self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
|
||||
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
|
||||
|
||||
$matcher = new CellMatcher($cell, $cfRange);
|
||||
|
|
@ -365,16 +356,13 @@ class CellMatcherTest extends TestCase
|
|||
*/
|
||||
public function testErrorExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void
|
||||
{
|
||||
$this->spreadsheet = $this->loadSpreadsheet();
|
||||
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
|
||||
if ($worksheet === null) {
|
||||
self::markTestSkipped("{$sheetname} not found in test workbook");
|
||||
}
|
||||
self::assertNotNull($worksheet, "$sheetname not found in test workbook");
|
||||
$cell = $worksheet->getCell($cellAddress);
|
||||
|
||||
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
|
||||
if ($cfRange === null) {
|
||||
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
|
||||
}
|
||||
self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
|
||||
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
|
||||
|
||||
$matcher = new CellMatcher($cell, $cfRange);
|
||||
|
|
@ -400,16 +388,13 @@ class CellMatcherTest extends TestCase
|
|||
*/
|
||||
public function testDateOccurringExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void
|
||||
{
|
||||
$this->spreadsheet = $this->loadSpreadsheet();
|
||||
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
|
||||
if ($worksheet === null) {
|
||||
self::markTestSkipped("{$sheetname} not found in test workbook");
|
||||
}
|
||||
self::assertNotNull($worksheet, "$sheetname not found in test workbook");
|
||||
$cell = $worksheet->getCell($cellAddress);
|
||||
|
||||
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
|
||||
if ($cfRange === null) {
|
||||
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
|
||||
}
|
||||
self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
|
||||
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
|
||||
|
||||
$matcher = new CellMatcher($cell, $cfRange);
|
||||
|
|
@ -447,16 +432,13 @@ class CellMatcherTest extends TestCase
|
|||
*/
|
||||
public function testDuplicatesExpressions(string $sheetname, string $cellAddress, array $expectedMatches): void
|
||||
{
|
||||
$this->spreadsheet = $this->loadSpreadsheet();
|
||||
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
|
||||
if ($worksheet === null) {
|
||||
self::markTestSkipped("{$sheetname} not found in test workbook");
|
||||
}
|
||||
self::assertNotNull($worksheet, "$sheetname not found in test workbook");
|
||||
$cell = $worksheet->getCell($cellAddress);
|
||||
|
||||
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
|
||||
if ($cfRange === null) {
|
||||
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
|
||||
}
|
||||
self::AssertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
|
||||
$cfStyles = $worksheet->getConditionalStyles($cell->getCoordinate());
|
||||
|
||||
$matcher = new CellMatcher($cell, $cfRange);
|
||||
|
|
@ -486,16 +468,13 @@ class CellMatcherTest extends TestCase
|
|||
*/
|
||||
public function testCrossWorksheetExpressions(string $sheetname, string $cellAddress, bool $expectedMatch): void
|
||||
{
|
||||
$this->spreadsheet = $this->loadSpreadsheet();
|
||||
$worksheet = $this->spreadsheet->getSheetByName($sheetname);
|
||||
if ($worksheet === null) {
|
||||
self::markTestSkipped("{$sheetname} not found in test workbook");
|
||||
}
|
||||
self::assertNotNull($worksheet, "$sheetname not found in test workbook");
|
||||
$cell = $worksheet->getCell($cellAddress);
|
||||
|
||||
$cfRange = $worksheet->getConditionalRange($cell->getCoordinate());
|
||||
if ($cfRange === null) {
|
||||
self::markTestSkipped("{$cellAddress} is not in a Conditional Format range");
|
||||
}
|
||||
self::assertNotNull($cfRange, "$cellAddress is not in a Conditional Format range");
|
||||
$cfStyle = $worksheet->getConditionalStyles($cell->getCoordinate());
|
||||
|
||||
$matcher = new CellMatcher($cell, $cfRange);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,4 +186,14 @@ return [
|
|||
3,
|
||||
true,
|
||||
],
|
||||
'issue2934' => [
|
||||
'Red',
|
||||
102,
|
||||
[
|
||||
[null, 102],
|
||||
[null, 'Red'],
|
||||
],
|
||||
2,
|
||||
false,
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ return [
|
|||
['10y1', 7.0],
|
||||
['10y2', 10.0],
|
||||
],
|
||||
'NaN',
|
||||
-5,
|
||||
],
|
||||
[
|
||||
'#REF!',
|
||||
|
|
@ -111,9 +111,9 @@ return [
|
|||
'#REF!',
|
||||
'10y2',
|
||||
[
|
||||
2.0,
|
||||
7.0,
|
||||
10.0,
|
||||
[2.0],
|
||||
[7.0],
|
||||
[10.0],
|
||||
],
|
||||
2.0,
|
||||
],
|
||||
|
|
@ -163,4 +163,34 @@ return [
|
|||
3,
|
||||
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,
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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>𐐁𐐂𐐃 & だけち</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>
|
||||
Loading…
Reference in New Issue