Merge branch 'PHPOffice:master' into Table-for-Xlsx
This commit is contained in:
commit
257c22277c
|
|
@ -26,7 +26,7 @@ $config
|
|||
'combine_consecutive_issets' => true,
|
||||
'combine_consecutive_unsets' => true,
|
||||
'combine_nested_dirname' => true,
|
||||
'comment_to_phpdoc' => true,
|
||||
'comment_to_phpdoc' => false, // interferes with annotations
|
||||
'compact_nullable_typehint' => true,
|
||||
'concat_space' => ['spacing' => 'one'],
|
||||
'constant_case' => true,
|
||||
|
|
@ -171,7 +171,7 @@ $config
|
|||
'phpdoc_separation' => true,
|
||||
'phpdoc_single_line_var_spacing' => true,
|
||||
'phpdoc_summary' => true,
|
||||
'phpdoc_to_comment' => true,
|
||||
'phpdoc_to_comment' => false, // interferes with annotations
|
||||
'phpdoc_to_param_type' => false, // Because experimental, but interesting for one shot use
|
||||
'phpdoc_to_return_type' => false, // idem
|
||||
'phpdoc_trim' => true,
|
||||
|
|
|
|||
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
|||
|
||||
### Added
|
||||
|
||||
- Implementation of the ISREF() information function.
|
||||
- Implementation of the FILTER(), SORT(), SORTBY() and UNIQUE() Lookup/Reference (array) functions
|
||||
- Implementation of the ISREF() Information function.
|
||||
- Added support for reading "formatted" numeric values from Csv files; although default behaviour of reading these values as strings is preserved.
|
||||
|
||||
(i.e a value of "12,345.67" can be read as numeric `1235.67`, not simply as a string `"12,345.67"`, if the `castFormattedNumberToNumeric()` setting is enabled.
|
||||
|
|
@ -17,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
|||
This functionality is locale-aware, using the server's locale settings to identify the thousands and decimal separators.
|
||||
|
||||
- Support for two cell anchor drawing of images. [#2532](https://github.com/PHPOffice/PhpSpreadsheet/pull/2532)
|
||||
- Limited support for Xls Reader to handle Conditional Formatting:
|
||||
|
||||
Ranges and Rules are read, but style is currently limited to font size, weight and color; and to fill style and color.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
@ -28,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
|||
This is determined by the Calculation Engine locale setting.
|
||||
|
||||
(i.e. `"Vrai"` wil be converted to a boolean `true` if the Locale is set to `fr`.)
|
||||
- Allow `psr/simple-cache` 2.x
|
||||
|
||||
### Deprecated
|
||||
|
||||
|
|
@ -39,6 +44,11 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
|||
|
||||
### Fixed
|
||||
|
||||
- Update Conditional Formatting ranges and rule conditions when inserting/deleting rows/columns [Issue #2678](https://github.com/PHPOffice/PhpSpreadsheet/issues/2678) [PR #2689](https://github.com/PHPOffice/PhpSpreadsheet/pull/2689)
|
||||
- Allow `INDIRECT()` to accept row/column ranges as well as cell ranges [PR #2687](https://github.com/PHPOffice/PhpSpreadsheet/pull/2687)
|
||||
- Fix bug when deleting cells with hyperlinks, where the hyperlink was then being "inherited" by whatever cell moved to that cell address.
|
||||
- Fix bug in Conditional Formatting in the Xls Writer that resulted in a broken file when there were multiple conditional ranges in a worksheet.
|
||||
- Fix Conditional Formatting in the Xls Writer to work with rules that contain string literals, cell references and formulae.
|
||||
- Fix for setting Active Sheet to the first loaded worksheet when bookViews element isn't defined [Issue #2666](https://github.com/PHPOffice/PhpSpreadsheet/issues/2666) [PR #2669](https://github.com/PHPOffice/PhpSpreadsheet/pull/2669)
|
||||
- Fixed behaviour of XLSX font style vertical align settings.
|
||||
- Resolved formula translations to handle separators (row and column) for array functions as well as for function argument separators; and cleanly handle nesting levels.
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
"markbaker/matrix": "^3.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/simple-cache": "^1.0"
|
||||
"psr/simple-cache": "^1.0 || ^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9dadb265f548dd4ddbfd2ee97d5a6aa6",
|
||||
"content-hash": "ed42c40a4281d97171980367f24d0a25",
|
||||
"packages": [
|
||||
{
|
||||
"name": "ezyang/htmlpurifier",
|
||||
|
|
|
|||
|
|
@ -2300,16 +2300,6 @@ parameters:
|
|||
count: 2
|
||||
path: src/PhpSpreadsheet/Reader/Xls.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$errorStyle of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataValidation\\:\\:setErrorStyle\\(\\) expects string, int\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Reader/Xls.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$operator of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataValidation\\:\\:setOperator\\(\\) expects string, int\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Reader/Xls.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$showSummaryBelow of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:setShowSummaryBelow\\(\\) expects bool, int given\\.$#"
|
||||
count: 1
|
||||
|
|
@ -2320,11 +2310,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/PhpSpreadsheet/Reader/Xls.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$type of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataValidation\\:\\:setType\\(\\) expects string, int\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Reader/Xls.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$row of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\IReadFilter\\:\\:readCell\\(\\) expects int, string given\\.$#"
|
||||
count: 1
|
||||
|
|
@ -3165,31 +3150,11 @@ parameters:
|
|||
count: 1
|
||||
path: src/PhpSpreadsheet/ReferenceHelper.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$index of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\:\\:setRowIndex\\(\\) expects int, string given\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/ReferenceHelper.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$callback of function uksort expects callable\\(\\(int\\|string\\), \\(int\\|string\\)\\)\\: int, array\\{'self', 'cellReverseSort'\\} given\\.$#"
|
||||
count: 4
|
||||
path: src/PhpSpreadsheet/ReferenceHelper.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$callback of function uksort expects callable\\(\\(int\\|string\\), \\(int\\|string\\)\\)\\: int, array\\{'self', 'cellSort'\\} given\\.$#"
|
||||
count: 4
|
||||
path: src/PhpSpreadsheet/ReferenceHelper.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/ReferenceHelper.php
|
||||
|
||||
-
|
||||
message: "#^Static property PhpOffice\\\\PhpSpreadsheet\\\\ReferenceHelper\\:\\:\\$instance \\(PhpOffice\\\\PhpSpreadsheet\\\\ReferenceHelper\\) in isset\\(\\) is not nullable\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/ReferenceHelper.php
|
||||
|
||||
-
|
||||
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\Run\\:\\:\\$font \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
|
||||
count: 1
|
||||
|
|
@ -4240,26 +4205,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getCell\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Worksheet/BaseDrawing.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getDrawingCollection\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Worksheet/BaseDrawing.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getHashCode\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Worksheet/BaseDrawing.php
|
||||
|
||||
-
|
||||
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\BaseDrawing\\:\\:\\$shadow \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Drawing\\\\Shadow\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Drawing\\\\Shadow\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Worksheet/BaseDrawing.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\CellIterator\\:\\:adjustForExistingOnlyRange\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
|
|
@ -5270,36 +5215,6 @@ parameters:
|
|||
count: 2
|
||||
path: src/PhpSpreadsheet/Writer/Xlsx/DocProps.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$coordinates of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:indexesFromString\\(\\) expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$index of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getChartByIndex\\(\\) expects string, int\\<0, max\\> given\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$chart of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Drawing\\:\\:writeChart\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\|false given\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, int given\\.$#"
|
||||
count: 20
|
||||
path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#"
|
||||
count: 10
|
||||
path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<0, max\\> given\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeUnparsedRelationship\\(\\) has parameter \\$relationship with no type specified\\.$#"
|
||||
count: 1
|
||||
|
|
@ -5554,3 +5469,4 @@ parameters:
|
|||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Xlfn\\:\\:addXlfn\\(\\) should return string but returns string\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
<?php
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as Reader;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as Writer;
|
||||
|
||||
require __DIR__ . '/../Header.php';
|
||||
|
||||
// Create temporary file that will be read
|
||||
$sampleSpreadsheet = require __DIR__ . '/../templates/sampleSpreadsheet.php';
|
||||
$filename = $helper->getTemporaryFilename();
|
||||
$writer = new Xlsx($sampleSpreadsheet);
|
||||
$writer = new Writer($sampleSpreadsheet);
|
||||
$writer->save($filename);
|
||||
|
||||
$inputFileType = IOFactory::identify($filename);
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$reader = new Reader();
|
||||
$sheetList = $reader->listWorksheetNames($filename);
|
||||
$sheetInfo = $reader->listWorksheetInfo($filename);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
// Create new Spreadsheet object
|
||||
use PhpOffice\PhpSpreadsheet\Helper\Dimension;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
|
||||
|
||||
require __DIR__ . '/../Header.php';
|
||||
|
||||
$helper->log('Create new Spreadsheet object');
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->getCell('A1')->setValue('twocell');
|
||||
$sheet->getCell('A2')->setValue('twocell');
|
||||
$sheet->getCell('A3')->setValue('onecell');
|
||||
$sheet->getCell('A6')->setValue('absolute');
|
||||
|
||||
// Add a drawing to the worksheet
|
||||
$helper->log('Add a drawing to the worksheet two-cell anchor not resized');
|
||||
$drawing = new Drawing();
|
||||
$drawing->setName('PhpSpreadsheet');
|
||||
$drawing->setDescription('PhpSpreadsheet');
|
||||
$drawing->setPath(__DIR__ . '/../images/PhpSpreadsheet_logo.png');
|
||||
// anchor type will be two-cell because Coordinates2 is set
|
||||
//$drawing->setAnchorType(Drawing::ANCHORTYPE_TWOCELL);
|
||||
$drawing->setCoordinates('B1');
|
||||
$drawing->setCoordinates2('B1');
|
||||
$drawing->setOffsetX2($drawing->getImageWidth());
|
||||
$drawing->setOffsetY2($drawing->getImageHeight());
|
||||
$drawing->setWorksheet($spreadsheet->getActiveSheet());
|
||||
|
||||
// Add a drawing to the worksheet
|
||||
$helper->log('Add a drawing to the worksheet two-cell anchor resized');
|
||||
$drawing2 = new Drawing();
|
||||
$drawing2->setName('PhpSpreadsheet');
|
||||
$drawing2->setDescription('PhpSpreadsheet');
|
||||
$drawing2->setPath(__DIR__ . '/../images/PhpSpreadsheet_logo.png');
|
||||
// anchor type will be two-cell because Coordinates2 is set
|
||||
//$drawing->setAnchorType(Drawing::ANCHORTYPE_TWOCELL);
|
||||
$drawing2->setCoordinates('C2');
|
||||
$drawing2->setCoordinates2('C2');
|
||||
$drawing2->setOffsetX2($drawing->getImageWidth());
|
||||
$drawing2->setOffsetY2($drawing->getImageHeight());
|
||||
$drawing2->setWorksheet($spreadsheet->getActiveSheet());
|
||||
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth($drawing->getImageWidth(), Dimension::UOM_PIXELS);
|
||||
$spreadsheet->getActiveSheet()->getRowDimension(2)->setRowHeight($drawing->getImageHeight(), Dimension::UOM_PIXELS);
|
||||
|
||||
// Add a drawing to the worksheet one cell anchor
|
||||
$helper->log('Add a drawing to the worksheet one-cell anchor');
|
||||
$drawing3 = new Drawing();
|
||||
$drawing3->setName('PhpSpreadsheet');
|
||||
$drawing3->setDescription('PhpSpreadsheet');
|
||||
$drawing3->setPath(__DIR__ . '/../images/PhpSpreadsheet_logo.png');
|
||||
// anchor type will be one-cell because Coordinates2 is not set
|
||||
//$drawing->setAnchorType(Drawing::ANCHORTYPE_ONECELL);
|
||||
$drawing3->setCoordinates('D3');
|
||||
$drawing3->setWorksheet($spreadsheet->getActiveSheet());
|
||||
|
||||
// Add a drawing to the worksheet
|
||||
$helper->log('Add a drawing to the worksheet two-cell anchor resized absolute');
|
||||
$drawing4 = new Drawing();
|
||||
$drawing4->setName('PhpSpreadsheet');
|
||||
$drawing4->setDescription('PhpSpreadsheet');
|
||||
$drawing4->setPath(__DIR__ . '/../images/PhpSpreadsheet_logo.png');
|
||||
// anchor type will be two-cell because Coordinates2 is set
|
||||
//$drawing->setAnchorType(Drawing::ANCHORTYPE_TWOCELL);
|
||||
$drawing4->setCoordinates('C6');
|
||||
$drawing4->setCoordinates2('C6');
|
||||
$drawing4->setOffsetX2($drawing->getImageWidth());
|
||||
$drawing4->setOffsetY2($drawing->getImageHeight());
|
||||
$drawing4->setWorksheet($spreadsheet->getActiveSheet());
|
||||
$drawing4->setEditAs(Drawing::EDIT_AS_ABSOLUTE);
|
||||
|
||||
//$spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth($drawing->getImageWidth(), Dimension::UOM_PIXELS);
|
||||
$spreadsheet->getActiveSheet()->getRowDimension(6)->setRowHeight($drawing->getImageHeight(), Dimension::UOM_PIXELS);
|
||||
|
||||
$helper->write($spreadsheet, __FILE__, ['Xlsx']);
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
|
@ -71,7 +71,7 @@ $series = new DataSeries(
|
|||
// Set the series in the plot area
|
||||
$plotArea = new PlotArea(null, [$series]);
|
||||
// Set the chart legend
|
||||
$legend = new Legend(Legend::POSITION_TOPRIGHT, null, false);
|
||||
$legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false);
|
||||
|
||||
$title = new Title('Test %age-Stacked Area Chart');
|
||||
$yAxisLabel = new Title('Value ($k)');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
|
@ -74,7 +74,7 @@ $series->setPlotDirection(DataSeries::DIRECTION_BAR);
|
|||
// Set the series in the plot area
|
||||
$plotArea = new PlotArea(null, [$series]);
|
||||
// Set the chart legend
|
||||
$legend = new Legend(Legend::POSITION_RIGHT, null, false);
|
||||
$legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false);
|
||||
|
||||
$title = new Title('Test Chart');
|
||||
$yAxisLabel = new Title('Value ($k)');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
|
@ -74,7 +74,7 @@ $series->setPlotDirection(DataSeries::DIRECTION_COL);
|
|||
// Set the series in the plot area
|
||||
$plotArea = new PlotArea(null, [$series]);
|
||||
// Set the chart legend
|
||||
$legend = new Legend(Legend::POSITION_RIGHT, null, false);
|
||||
$legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false);
|
||||
|
||||
$title = new Title('Test Column Chart');
|
||||
$yAxisLabel = new Title('Value ($k)');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
|
@ -82,7 +82,7 @@ $series->setPlotDirection(DataSeries::DIRECTION_COL);
|
|||
// Set the series in the plot area
|
||||
$plotArea = new PlotArea(null, [$series]);
|
||||
// Set the chart legend
|
||||
$legend = new Legend(Legend::POSITION_BOTTOM, null, false);
|
||||
$legend = new ChartLegend(ChartLegend::POSITION_BOTTOM, null, false);
|
||||
|
||||
$title = new Title('Test Grouped Column Chart');
|
||||
$xAxisLabel = new Title('Financial Period');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
|
@ -128,7 +128,7 @@ $series3 = new DataSeries(
|
|||
// Set the series in the plot area
|
||||
$plotArea = new PlotArea(null, [$series1, $series2, $series3]);
|
||||
// Set the chart legend
|
||||
$legend = new Legend(Legend::POSITION_RIGHT, null, false);
|
||||
$legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false);
|
||||
|
||||
$title = new Title('Average Weather Chart for Crete');
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
|
@ -72,7 +72,7 @@ $series = new DataSeries(
|
|||
// Set the series in the plot area
|
||||
$plotArea = new PlotArea(null, [$series]);
|
||||
// Set the chart legend
|
||||
$legend = new Legend(Legend::POSITION_TOPRIGHT, null, false);
|
||||
$legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false);
|
||||
|
||||
$title = new Title('Test Stacked Line Chart');
|
||||
$yAxisLabel = new Title('Value ($k)');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
|
@ -71,7 +71,7 @@ $series1 = new DataSeries(
|
|||
// Set the series in the plot area
|
||||
$plotArea1 = new PlotArea(null, [$series1]);
|
||||
// Set the chart legend
|
||||
$legend1 = new Legend(Legend::POSITION_TOPRIGHT, null, false);
|
||||
$legend1 = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false);
|
||||
|
||||
$title1 = new Title('Test %age-Stacked Area Chart');
|
||||
$yAxisLabel1 = new Title('Value ($k)');
|
||||
|
|
@ -146,7 +146,7 @@ $series2->setPlotDirection(DataSeries::DIRECTION_COL);
|
|||
// Set the series in the plot area
|
||||
$plotArea2 = new PlotArea(null, [$series2]);
|
||||
// Set the chart legend
|
||||
$legend2 = new Legend(Legend::POSITION_RIGHT, null, false);
|
||||
$legend2 = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false);
|
||||
|
||||
$title2 = new Title('Test Column Chart');
|
||||
$yAxisLabel2 = new Title('Value ($k)');
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
|||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Layout;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
|
@ -73,7 +73,7 @@ $layout1->setShowPercent(true);
|
|||
// Set the series in the plot area
|
||||
$plotArea1 = new PlotArea($layout1, [$series1]);
|
||||
// Set the chart legend
|
||||
$legend1 = new Legend(Legend::POSITION_RIGHT, null, false);
|
||||
$legend1 = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false);
|
||||
|
||||
$title1 = new Title('Test Pie Chart');
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
|||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Layout;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
|
@ -79,7 +79,7 @@ $layout1->setShowPercent(true);
|
|||
// Set the series in the plot area
|
||||
$plotArea1 = new PlotArea($layout1, [$series1]);
|
||||
// Set the chart legend
|
||||
$legend1 = new Legend(Legend::POSITION_RIGHT, null, false);
|
||||
$legend1 = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false);
|
||||
|
||||
$title1 = new Title('Test Pie Chart');
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
|||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Layout;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
|
@ -85,7 +85,7 @@ $layout = new Layout();
|
|||
// Set the series in the plot area
|
||||
$plotArea = new PlotArea($layout, [$series]);
|
||||
// Set the chart legend
|
||||
$legend = new Legend(Legend::POSITION_RIGHT, null, false);
|
||||
$legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false);
|
||||
|
||||
$title = new Title('Test Radar Chart');
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
|
@ -68,7 +68,7 @@ $series = new DataSeries(
|
|||
// Set the series in the plot area
|
||||
$plotArea = new PlotArea(null, [$series]);
|
||||
// Set the chart legend
|
||||
$legend = new Legend(Legend::POSITION_TOPRIGHT, null, false);
|
||||
$legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false);
|
||||
|
||||
$title = new Title('Test Scatter Chart');
|
||||
$yAxisLabel = new Title('Value ($k)');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
|
@ -79,7 +79,7 @@ $series = new DataSeries(
|
|||
// Set the series in the plot area
|
||||
$plotArea = new PlotArea(null, [$series]);
|
||||
// Set the chart legend
|
||||
$legend = new Legend(Legend::POSITION_RIGHT, null, false);
|
||||
$legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false);
|
||||
|
||||
$title = new Title('Test Stock Chart');
|
||||
$xAxisLabel = new Title('Counts');
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ $spreadsheet->getActiveSheet()
|
|||
->setCellValue('A1', 'Literal Value Comparison')
|
||||
->setCellValue('A9', 'Value Comparison with Absolute Cell Reference $H$9')
|
||||
->setCellValue('A17', 'Value Comparison with Relative Cell References')
|
||||
->setCellValue('A23', 'Value Comparison with Formula based on AVERAGE() ± STDEV()');
|
||||
->setCellValue('A23', 'Value Comparison with Formula based on AVERAGE() ± STDEV()')
|
||||
->setCellValue('A30', 'Literal String Value Comparison');
|
||||
|
||||
$dataArray = [
|
||||
[-2, -1, 0, 1, 2],
|
||||
|
|
@ -45,11 +46,18 @@ $betweenDataArray = [
|
|||
[4, 3, 8],
|
||||
];
|
||||
|
||||
$stringArray = [
|
||||
['I'],
|
||||
['Love'],
|
||||
['PHP'],
|
||||
];
|
||||
|
||||
$spreadsheet->getActiveSheet()
|
||||
->fromArray($dataArray, null, 'A2', true)
|
||||
->fromArray($dataArray, null, 'A10', true)
|
||||
->fromArray($betweenDataArray, null, 'A18', true)
|
||||
->fromArray($dataArray, null, 'A24', true)
|
||||
->fromArray($stringArray, null, 'A31', true)
|
||||
->setCellValue('H9', 1);
|
||||
|
||||
// Set title row bold
|
||||
|
|
@ -58,21 +66,31 @@ $spreadsheet->getActiveSheet()->getStyle('A1:E1')->getFont()->setBold(true);
|
|||
$spreadsheet->getActiveSheet()->getStyle('A9:E9')->getFont()->setBold(true);
|
||||
$spreadsheet->getActiveSheet()->getStyle('A17:E17')->getFont()->setBold(true);
|
||||
$spreadsheet->getActiveSheet()->getStyle('A23:E23')->getFont()->setBold(true);
|
||||
$spreadsheet->getActiveSheet()->getStyle('A30:E30')->getFont()->setBold(true);
|
||||
|
||||
// Define some styles for our Conditionals
|
||||
$helper->log('Define some styles for our Conditionals');
|
||||
$yellowStyle = new Style(false, true);
|
||||
$yellowStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getStartColor()->setARGB(Color::COLOR_YELLOW);
|
||||
$yellowStyle->getFill()
|
||||
->getEndColor()->setARGB(Color::COLOR_YELLOW);
|
||||
$yellowStyle->getFont()->setColor(new Color(Color::COLOR_BLUE));
|
||||
$greenStyle = new Style(false, true);
|
||||
$greenStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getStartColor()->setARGB(Color::COLOR_GREEN);
|
||||
$greenStyle->getFill()
|
||||
->getEndColor()->setARGB(Color::COLOR_GREEN);
|
||||
$greenStyle->getFont()->setColor(new Color(Color::COLOR_DARKRED));
|
||||
$redStyle = new Style(false, true);
|
||||
$redStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getStartColor()->setARGB(Color::COLOR_RED);
|
||||
$redStyle->getFill()
|
||||
->getEndColor()->setARGB(Color::COLOR_RED);
|
||||
$redStyle->getFont()->setColor(new Color(Color::COLOR_GREEN));
|
||||
|
||||
// Set conditional formatting rules and styles
|
||||
$helper->log('Define conditional formatting and set styles');
|
||||
|
|
@ -166,6 +184,32 @@ $cellWizard->lessThan('AVERAGE(' . $formulaRange . ')-STDEV(' . $formulaRange .
|
|||
->setStyle($redStyle);
|
||||
$conditionalStyles[] = $cellWizard->getConditional();
|
||||
|
||||
$spreadsheet->getActiveSheet()
|
||||
->getStyle($cellWizard->getCellRange())
|
||||
->setConditionalStyles($conditionalStyles);
|
||||
|
||||
// Set rules for Value Comparison with String Literal
|
||||
$cellRange = 'A31:A33';
|
||||
$formulaRange = implode(
|
||||
':',
|
||||
array_map(
|
||||
[Coordinate::class, 'absoluteCoordinate'],
|
||||
Coordinate::splitRange($cellRange)[0]
|
||||
)
|
||||
);
|
||||
$conditionalStyles = [];
|
||||
$wizardFactory = new Wizard($cellRange);
|
||||
/** @var Wizard\CellValue $cellWizard */
|
||||
$cellWizard = $wizardFactory->newRule(Wizard::CELL_VALUE);
|
||||
|
||||
$cellWizard->equals('LOVE')
|
||||
->setStyle($redStyle);
|
||||
$conditionalStyles[] = $cellWizard->getConditional();
|
||||
|
||||
$cellWizard->equals('PHP')
|
||||
->setStyle($greenStyle);
|
||||
$conditionalStyles[] = $cellWizard->getConditional();
|
||||
|
||||
$spreadsheet->getActiveSheet()
|
||||
->getStyle($cellWizard->getCellRange())
|
||||
->setConditionalStyles($conditionalStyles);
|
||||
|
|
|
|||
|
|
@ -74,14 +74,17 @@ $yellowStyle = new Style(false, true);
|
|||
$yellowStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_YELLOW);
|
||||
$yellowStyle->getFont()->setColor(new Color(Color::COLOR_BLUE));
|
||||
$greenStyle = new Style(false, true);
|
||||
$greenStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_GREEN);
|
||||
$greenStyle->getFont()->setColor(new Color(Color::COLOR_DARKRED));
|
||||
$redStyle = new Style(false, true);
|
||||
$redStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_RED);
|
||||
$redStyle->getFont()->setColor(new Color(Color::COLOR_GREEN));
|
||||
|
||||
// Set conditional formatting rules and styles
|
||||
$helper->log('Define conditional formatting and set styles');
|
||||
|
|
|
|||
|
|
@ -46,10 +46,12 @@ $greenStyle = new Style(false, true);
|
|||
$greenStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_GREEN);
|
||||
$greenStyle->getFont()->setColor(new Color(Color::COLOR_DARKRED));
|
||||
$redStyle = new Style(false, true);
|
||||
$redStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_RED);
|
||||
$redStyle->getFont()->setColor(new Color(Color::COLOR_GREEN));
|
||||
|
||||
// Set conditional formatting rules and styles
|
||||
$helper->log('Define conditional formatting and set styles');
|
||||
|
|
|
|||
|
|
@ -49,10 +49,12 @@ $greenStyle = new Style(false, true);
|
|||
$greenStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_GREEN);
|
||||
$greenStyle->getFont()->setColor(new Color(Color::COLOR_DARKRED));
|
||||
$redStyle = new Style(false, true);
|
||||
$redStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_RED);
|
||||
$redStyle->getFont()->setColor(new Color(Color::COLOR_GREEN));
|
||||
|
||||
// Set conditional formatting rules and styles
|
||||
$helper->log('Define conditional formatting and set styles');
|
||||
|
|
|
|||
|
|
@ -108,12 +108,11 @@ $spreadsheet->getActiveSheet()->getStyle('B1:K1')->getAlignment()->setHorizontal
|
|||
|
||||
// Define some styles for our Conditionals
|
||||
$helper->log('Define some styles for our Conditionals');
|
||||
|
||||
$yellowStyle = new Style(false, true);
|
||||
$yellowStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_YELLOW);
|
||||
$yellowStyle->getNumberFormat()->setFormatCode('ddd dd-mmm-yyyy');
|
||||
$yellowStyle->getFont()->setColor(new Color(Color::COLOR_BLUE));
|
||||
|
||||
// Set conditional formatting rules and styles
|
||||
$helper->log('Define conditional formatting and set styles');
|
||||
|
|
|
|||
|
|
@ -51,14 +51,16 @@ $spreadsheet->getActiveSheet()->getStyle('A1:C1')->getFont()->setBold(true);
|
|||
|
||||
// Define some styles for our Conditionals
|
||||
$helper->log('Define some styles for our Conditionals');
|
||||
$greenStyle = new Style(false, true);
|
||||
$greenStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_GREEN);
|
||||
$yellowStyle = new Style(false, true);
|
||||
$yellowStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_YELLOW);
|
||||
$yellowStyle->getFont()->setColor(new Color(Color::COLOR_BLUE));
|
||||
$greenStyle = new Style(false, true);
|
||||
$greenStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_GREEN);
|
||||
$greenStyle->getFont()->setColor(new Color(Color::COLOR_DARKRED));
|
||||
|
||||
// Set conditional formatting rules and styles
|
||||
$helper->log('Define conditional formatting and set styles');
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ $helper->log('Add data');
|
|||
$spreadsheet->setActiveSheetIndex(0);
|
||||
$spreadsheet->getActiveSheet()
|
||||
->setCellValue('A1', 'Odd/Even Expression Comparison')
|
||||
->setCellValue('A4', 'Note that these functions are not available for Xls files')
|
||||
->setCellValue('A15', 'Sales Grid Expression Comparison')
|
||||
->setCellValue('A25', 'Sales Grid Multiple Expression Comparison');
|
||||
|
||||
|
|
@ -69,14 +70,16 @@ $spreadsheet->getActiveSheet()->getStyle('A25:D26')->getFont()->setBold(true);
|
|||
|
||||
// Define some styles for our Conditionals
|
||||
$helper->log('Define some styles for our Conditionals');
|
||||
$greenStyle = new Style(false, true);
|
||||
$greenStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_GREEN);
|
||||
$yellowStyle = new Style(false, true);
|
||||
$yellowStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_YELLOW);
|
||||
$yellowStyle->getFont()->setColor(new Color(Color::COLOR_BLUE));
|
||||
$greenStyle = new Style(false, true);
|
||||
$greenStyle->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getEndColor()->setARGB(Color::COLOR_GREEN);
|
||||
$greenStyle->getFont()->setColor(new Color(Color::COLOR_DARKRED));
|
||||
|
||||
$greenStyleMoney = clone $greenStyle;
|
||||
$greenStyleMoney->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_ACCOUNTING_USD);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use PhpOffice\PhpSpreadsheet\IOFactory;
|
|||
require __DIR__ . '/../Header.php';
|
||||
|
||||
$inputFileName = __DIR__ . '/sampleData/example1.xls';
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory to identify the format');
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory to identify the format');
|
||||
$spreadsheet = IOFactory::load($inputFileName);
|
||||
$sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
|
||||
var_dump($sheetData);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use PhpOffice\PhpSpreadsheet\Reader\Xls;
|
|||
require __DIR__ . '/../Header.php';
|
||||
$inputFileName = __DIR__ . '/sampleData/example1.xls';
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using ' . Xls::class);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using ' . Xls::class);
|
||||
$reader = new Xls();
|
||||
$spreadsheet = $reader->load($inputFileName);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ require __DIR__ . '/../Header.php';
|
|||
$inputFileType = 'Xls';
|
||||
$inputFileName = __DIR__ . '/sampleData/example1.xls';
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$spreadsheet = $reader->load($inputFileName);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ require __DIR__ . '/../Header.php';
|
|||
$inputFileName = __DIR__ . '/sampleData/example1.xls';
|
||||
|
||||
$inputFileType = IOFactory::identify($inputFileName);
|
||||
$helper->log('File ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' has been identified as an ' . $inputFileType . ' file');
|
||||
$helper->log('File ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' has been identified as an ' . $inputFileType . ' file');
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with the identified reader type');
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with the identified reader type');
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$spreadsheet = $reader->load($inputFileName);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ require __DIR__ . '/../Header.php';
|
|||
$inputFileType = 'Xls';
|
||||
$inputFileName = __DIR__ . '/sampleData/example1.xls';
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$helper->log('Turning Formatting off for Load');
|
||||
$reader->setReadDataOnly(true);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ require __DIR__ . '/../Header.php';
|
|||
$inputFileType = 'Xls';
|
||||
$inputFileName = __DIR__ . '/sampleData/example1.xls';
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$helper->log('Loading all WorkSheets');
|
||||
$reader->setLoadAllSheets();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ $inputFileType = 'Xls';
|
|||
$inputFileName = __DIR__ . '/sampleData/example1.xls';
|
||||
$sheetname = 'Data Sheet #2';
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$helper->log('Loading Sheet "' . $sheetname . '" only');
|
||||
$reader->setLoadSheetsOnly($sheetname);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ $inputFileType = 'Xls';
|
|||
$inputFileName = __DIR__ . '/sampleData/example1.xls';
|
||||
$sheetnames = ['Data Sheet #1', 'Data Sheet #3'];
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$helper->log('Loading Sheet' . ((count($sheetnames) == 1) ? '' : 's') . ' "' . implode('" and "', $sheetnames) . '" only');
|
||||
$reader->setLoadSheetsOnly($sheetnames);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class MyReadFilter implements IReadFilter
|
|||
|
||||
$filterSubset = new MyReadFilter();
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$helper->log('Loading Sheet "' . $sheetname . '" only');
|
||||
$reader->setLoadSheetsOnly($sheetname);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class MyReadFilter implements IReadFilter
|
|||
|
||||
$filterSubset = new MyReadFilter(9, 15, range('G', 'K'));
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$helper->log('Loading Sheet "' . $sheetname . '" only');
|
||||
$reader->setLoadSheetsOnly($sheetname);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class ChunkReadFilter implements IReadFilter
|
|||
}
|
||||
}
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
// Create a new Reader of the type defined in $inputFileType
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class ChunkReadFilter implements IReadFilter
|
|||
}
|
||||
}
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
// Create a new Reader of the type defined in $inputFileType
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
<?php
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Csv;
|
||||
|
||||
require __DIR__ . '/../Header.php';
|
||||
|
||||
$inputFileType = 'Csv';
|
||||
$inputFileNames = [__DIR__ . '/sampleData/example1.csv', __DIR__ . '/sampleData/example2.csv'];
|
||||
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$reader = new Csv();
|
||||
$inputFileName = array_shift($inputFileNames);
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' into WorkSheet #1 using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' into WorkSheet #1 using Csv Reader');
|
||||
$spreadsheet = $reader->load($inputFileName);
|
||||
$spreadsheet->getActiveSheet()->setTitle(pathinfo($inputFileName, PATHINFO_BASENAME));
|
||||
$spreadsheet->getActiveSheet()->setTitle(/** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME));
|
||||
foreach ($inputFileNames as $sheet => $inputFileName) {
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' into WorkSheet #' . ($sheet + 2) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' into WorkSheet #' . ($sheet + 2) . ' using Csv Reader');
|
||||
$reader->setSheetIndex($sheet + 1);
|
||||
$reader->loadIntoExisting($inputFileName, $spreadsheet);
|
||||
$spreadsheet->getActiveSheet()->setTitle(pathinfo($inputFileName, PATHINFO_BASENAME));
|
||||
$spreadsheet->getActiveSheet()->setTitle(/** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME));
|
||||
}
|
||||
|
||||
$helper->log($spreadsheet->getSheetCount() . ' worksheet' . (($spreadsheet->getSheetCount() == 1) ? '' : 's') . ' loaded');
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@
|
|||
|
||||
namespace Samples\Sample14;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Csv;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
require __DIR__ . '/../Header.php';
|
||||
|
||||
$inputFileType = 'Csv';
|
||||
$inputFileName = __DIR__ . '/sampleData/example2.csv';
|
||||
|
||||
/** Define a Read Filter class implementing IReadFilter */
|
||||
|
|
@ -41,9 +40,9 @@ class ChunkReadFilter implements IReadFilter
|
|||
}
|
||||
}
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using Csv reader');
|
||||
// Create a new Reader of the type defined in $inputFileType
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$reader = new Csv();
|
||||
|
||||
// Define how many rows we want to read for each "chunk"
|
||||
$chunkSize = 100;
|
||||
|
|
|
|||
|
|
@ -2,20 +2,19 @@
|
|||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Csv;
|
||||
|
||||
require __DIR__ . '/../Header.php';
|
||||
|
||||
Cell::setValueBinder(new AdvancedValueBinder());
|
||||
|
||||
$inputFileType = 'Csv';
|
||||
$inputFileName = __DIR__ . '/sampleData/example1.tsv';
|
||||
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' into WorkSheet #1 using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$reader = new Csv();
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' into WorkSheet #1 using Csv reader');
|
||||
$reader->setDelimiter("\t");
|
||||
$spreadsheet = $reader->load($inputFileName);
|
||||
$spreadsheet->getActiveSheet()->setTitle(pathinfo($inputFileName, PATHINFO_BASENAME));
|
||||
$spreadsheet->getActiveSheet()->setTitle(/** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME));
|
||||
|
||||
$helper->log($spreadsheet->getSheetCount() . ' worksheet' . (($spreadsheet->getSheetCount() == 1) ? '' : 's') . ' loaded');
|
||||
$loadedSheetNames = $spreadsheet->getSheetNames();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
|
|||
require __DIR__ . '/../Header.php';
|
||||
|
||||
$inputFileName = __DIR__ . '/sampleData/non-existing-file.xls';
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory to identify the format');
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory to identify the format');
|
||||
|
||||
try {
|
||||
$spreadsheet = IOFactory::load($inputFileName);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
<?php
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||
|
||||
require __DIR__ . '/../Header.php';
|
||||
|
||||
$inputFileType = 'Xls';
|
||||
$inputFileName = __DIR__ . '/sampleData/example1.xls';
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' using Xls reader');
|
||||
$reader = new Xls();
|
||||
|
||||
// Read the list of Worksheet Names from the Workbook file
|
||||
$helper->log('Read the list of Worksheets in the WorkBook');
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
<?php
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||
|
||||
require __DIR__ . '/../Header.php';
|
||||
|
||||
$inputFileType = 'Xls';
|
||||
$inputFileName = __DIR__ . '/sampleData/example1.xls';
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' information using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' information using Xls reader');
|
||||
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$reader = new Xls();
|
||||
$worksheetNames = $reader->listWorksheetNames($inputFileName);
|
||||
|
||||
$helper->log('<h3>Worksheet Names</h3>');
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
<?php
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||
|
||||
require __DIR__ . '/../Header.php';
|
||||
|
||||
$inputFileType = 'Xls';
|
||||
$inputFileName = __DIR__ . '/sampleData/example1.xls';
|
||||
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' information using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' information using Xls reader');
|
||||
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$reader = new Xls();
|
||||
$worksheetData = $reader->listWorksheetInfo($inputFileName);
|
||||
|
||||
$helper->log('<h3>Worksheet Information</h3>');
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ $spreadsheet = new Spreadsheet();
|
|||
$aSheet = $spreadsheet->getActiveSheet();
|
||||
|
||||
$gdImage = @imagecreatetruecolor(120, 20);
|
||||
if ($gdImage === false) {
|
||||
throw new \Exception('imagecreatetruecolor failed');
|
||||
}
|
||||
$textColor = imagecolorallocate($gdImage, 255, 255, 255);
|
||||
imagestring($gdImage, 1, 5, 5, 'Created with PhpSpreadsheet', $textColor);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ $inputFileType = 'Csv';
|
|||
$inputFileName = __DIR__ . '/sampleData/longIntegers.csv';
|
||||
|
||||
$reader = IOFactory::createReader($inputFileType);
|
||||
$helper->log('Loading file ' . pathinfo($inputFileName, PATHINFO_BASENAME) . ' into WorkSheet #1 using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
$helper->log('Loading file ' . /** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME) . ' into WorkSheet #1 using IOFactory with a defined reader type of ' . $inputFileType);
|
||||
|
||||
$spreadsheet = $reader->load($inputFileName);
|
||||
$spreadsheet->getActiveSheet()->setTitle(pathinfo($inputFileName, PATHINFO_BASENAME));
|
||||
$spreadsheet->getActiveSheet()->setTitle(/** @scrutinizer ignore-type */ pathinfo($inputFileName, PATHINFO_BASENAME));
|
||||
|
||||
$helper->log($spreadsheet->getSheetCount() . ' worksheet' . (($spreadsheet->getSheetCount() == 1) ? '' : 's') . ' loaded');
|
||||
$loadedSheetNames = $spreadsheet->getSheetNames();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
|
@ -71,7 +71,7 @@ $series->setPlotDirection(DataSeries::DIRECTION_BAR);
|
|||
// Set the series in the plot area
|
||||
$plotArea = new PlotArea(null, [$series]);
|
||||
// Set the chart legend
|
||||
$legend = new Legend(Legend::POSITION_RIGHT, null, false);
|
||||
$legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false);
|
||||
|
||||
$title = new Title('Test Bar Chart');
|
||||
$yAxisLabel = new Title('Value ($k)');
|
||||
|
|
|
|||
|
|
@ -1040,7 +1040,7 @@ class Calculation
|
|||
],
|
||||
'FILTER' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [Functions::class, 'DUMMY'],
|
||||
'functionCall' => [LookupRef\Filter::class, 'filter'],
|
||||
'argumentCount' => '2-3',
|
||||
],
|
||||
'FILTERXML' => [
|
||||
|
|
@ -2282,12 +2282,12 @@ class Calculation
|
|||
],
|
||||
'SORT' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [Functions::class, 'DUMMY'],
|
||||
'argumentCount' => '1+',
|
||||
'functionCall' => [LookupRef\Sort::class, 'sort'],
|
||||
'argumentCount' => '1-4',
|
||||
],
|
||||
'SORTBY' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [Functions::class, 'DUMMY'],
|
||||
'functionCall' => [LookupRef\Sort::class, 'sortBy'],
|
||||
'argumentCount' => '2+',
|
||||
],
|
||||
'SQRT' => [
|
||||
|
|
@ -2583,7 +2583,7 @@ class Calculation
|
|||
],
|
||||
'UNIQUE' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [Functions::class, 'DUMMY'],
|
||||
'functionCall' => [LookupRef\Unique::class, 'unique'],
|
||||
'argumentCount' => '1+',
|
||||
],
|
||||
'UPPER' => [
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class ErrorValue
|
|||
return false;
|
||||
}
|
||||
|
||||
return in_array($value, ExcelError::$errorCodes);
|
||||
return in_array($value, ExcelError::$errorCodes) || $value === ExcelError::CALC();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class ExcelError
|
|||
'num' => '#NUM!',
|
||||
'na' => '#N/A',
|
||||
'gettingdata' => '#GETTING_DATA',
|
||||
'spill' => '#SPILL!',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -45,6 +46,10 @@ class ExcelError
|
|||
++$i;
|
||||
}
|
||||
|
||||
if ($value === self::CALC()) {
|
||||
return 14;
|
||||
}
|
||||
|
||||
return self::NA();
|
||||
}
|
||||
|
||||
|
|
@ -127,10 +132,20 @@ class ExcelError
|
|||
/**
|
||||
* DIV0.
|
||||
*
|
||||
* @return string #Not Yet Implemented
|
||||
* @return string #DIV/0!
|
||||
*/
|
||||
public static function DIV0()
|
||||
{
|
||||
return self::$errorCodes['divisionbyzero'];
|
||||
}
|
||||
|
||||
/**
|
||||
* CALC.
|
||||
*
|
||||
* @return string #Not Yet Implemented
|
||||
*/
|
||||
public static function CALC()
|
||||
{
|
||||
return '#CALC!';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Filter
|
||||
{
|
||||
/**
|
||||
* @param mixed $lookupArray
|
||||
* @param mixed $matchArray
|
||||
* @param mixed $ifEmpty
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function filter($lookupArray, $matchArray, $ifEmpty = null)
|
||||
{
|
||||
if (!is_array($matchArray)) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
$matchArray = self::enumerateArrayKeys($matchArray);
|
||||
|
||||
$result = (Matrix::isColumnVector($matchArray))
|
||||
? self::filterByRow($lookupArray, $matchArray)
|
||||
: self::filterByColumn($lookupArray, $matchArray);
|
||||
|
||||
if (empty($result)) {
|
||||
return $ifEmpty ?? ExcelError::CALC();
|
||||
}
|
||||
|
||||
return array_values(array_map('array_values', $result));
|
||||
}
|
||||
|
||||
private static function enumerateArrayKeys(array $sortArray): array
|
||||
{
|
||||
array_walk(
|
||||
$sortArray,
|
||||
function (&$columns): void {
|
||||
if (is_array($columns)) {
|
||||
$columns = array_values($columns);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return array_values($sortArray);
|
||||
}
|
||||
|
||||
private static function filterByRow(array $lookupArray, array $matchArray): array
|
||||
{
|
||||
$matchArray = array_values(array_column($matchArray, 0));
|
||||
|
||||
return array_filter(
|
||||
array_values($lookupArray),
|
||||
function ($index) use ($matchArray): bool {
|
||||
return (bool) $matchArray[$index];
|
||||
},
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
}
|
||||
|
||||
private static function filterByColumn(array $lookupArray, array $matchArray): array
|
||||
{
|
||||
$lookupArray = Matrix::transpose($lookupArray);
|
||||
|
||||
if (count($matchArray) === 1) {
|
||||
$matchArray = array_pop($matchArray);
|
||||
}
|
||||
|
||||
array_walk(
|
||||
$matchArray,
|
||||
function (&$value): void {
|
||||
$value = [$value];
|
||||
}
|
||||
);
|
||||
|
||||
$result = self::filterByRow($lookupArray, $matchArray);
|
||||
|
||||
return Matrix::transpose($result);
|
||||
}
|
||||
}
|
||||
|
|
@ -32,9 +32,10 @@ class HLookup extends LookupBase
|
|||
}
|
||||
|
||||
$notExactMatch = (bool) ($notExactMatch ?? true);
|
||||
$lookupArray = self::convertLiteralArray($lookupArray);
|
||||
|
||||
try {
|
||||
self::validateLookupArray($lookupArray);
|
||||
$lookupArray = self::convertLiteralArray($lookupArray);
|
||||
$indexNumber = self::validateIndexLookup($lookupArray, $indexNumber);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
|
|
|
|||
|
|
@ -72,11 +72,17 @@ class Indirect
|
|||
|
||||
[$cellAddress, $worksheet, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell);
|
||||
|
||||
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) {
|
||||
$cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress));
|
||||
} elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) {
|
||||
$cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress));
|
||||
}
|
||||
|
||||
[$cellAddress1, $cellAddress2, $cellAddress] = Helpers::extractCellAddresses($cellAddress, $a1, $cell->getWorkSheet(), $sheetName);
|
||||
|
||||
if (
|
||||
(!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress1, $matches)) ||
|
||||
(($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress2, $matches)))
|
||||
(!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress1, $matches)) ||
|
||||
(($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress2, $matches)))
|
||||
) {
|
||||
return ExcelError::REF();
|
||||
}
|
||||
|
|
@ -95,4 +101,22 @@ class Indirect
|
|||
return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null)
|
||||
->extractCellRange($cellAddress, $worksheet, false);
|
||||
}
|
||||
|
||||
private static function handleRowColumnRanges(?Worksheet $worksheet, string $start, string $end): string
|
||||
{
|
||||
// Being lazy, we're only checking a single row/column to get the max
|
||||
if (ctype_digit($start) && $start <= 1048576) {
|
||||
// Max 16,384 columns for Excel2007
|
||||
$endColRef = ($worksheet !== null) ? $worksheet->getHighestDataColumn((int) $start) : 'XFD';
|
||||
|
||||
return "A{$start}:{$endColRef}{$end}";
|
||||
} elseif (ctype_alpha($start) && strlen($start) <= 3) {
|
||||
// Max 1,048,576 rows for Excel2007
|
||||
$endRowRef = ($worksheet !== null) ? $worksheet->getHighestDataRow($start) : 1048576;
|
||||
|
||||
return "{$start}1:{$end}{$endRowRef}";
|
||||
}
|
||||
|
||||
return "{$start}:{$end}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,16 @@ use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
|||
|
||||
abstract class LookupBase
|
||||
{
|
||||
/**
|
||||
* @param mixed $lookup_array
|
||||
*/
|
||||
protected static function validateLookupArray($lookup_array): void
|
||||
{
|
||||
if (!is_array($lookup_array)) {
|
||||
throw new Exception(ExcelError::REF());
|
||||
}
|
||||
}
|
||||
|
||||
protected static function validateIndexLookup(array $lookup_array, $index_number): int
|
||||
{
|
||||
// index_number must be a number greater than or equal to 1
|
||||
|
|
|
|||
|
|
@ -10,6 +10,23 @@ class Matrix
|
|||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* Helper function; NOT an implementation of any Excel Function.
|
||||
*/
|
||||
public static function isColumnVector(array $values): bool
|
||||
{
|
||||
return count($values, COUNT_RECURSIVE) === (count($values, COUNT_NORMAL) * 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function; NOT an implementation of any Excel Function.
|
||||
*/
|
||||
public static function isRowVector(array $values): bool
|
||||
{
|
||||
return count($values, COUNT_RECURSIVE) > 1 &&
|
||||
(count($values, COUNT_NORMAL) === 1 || count($values, COUNT_RECURSIVE) === count($values, COUNT_NORMAL));
|
||||
}
|
||||
|
||||
/**
|
||||
* TRANSPOSE.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,342 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class Sort extends LookupRefValidations
|
||||
{
|
||||
public const ORDER_ASCENDING = 1;
|
||||
public const ORDER_DESCENDING = -1;
|
||||
|
||||
/**
|
||||
* SORT
|
||||
* The SORT function returns a sorted array of the elements in an array.
|
||||
* The returned array is the same shape as the provided array argument.
|
||||
* Both $sortIndex and $sortOrder can be arrays, to provide multi-level sorting.
|
||||
*
|
||||
* @param mixed $sortArray The range of cells being sorted
|
||||
* @param mixed $sortIndex The column or row number within the sortArray to sort on
|
||||
* @param mixed $sortOrder Flag indicating whether to sort ascending or descending
|
||||
* Ascending = 1 (self::ORDER_ASCENDING)
|
||||
* Descending = -1 (self::ORDER_DESCENDING)
|
||||
* @param mixed $byColumn Whether the sort should be determined by row (the default) or by column
|
||||
*
|
||||
* @return mixed The sorted values from the sort range
|
||||
*/
|
||||
public static function sort($sortArray, $sortIndex = 1, $sortOrder = self::ORDER_ASCENDING, $byColumn = false)
|
||||
{
|
||||
if (!is_array($sortArray)) {
|
||||
// Scalars are always returned "as is"
|
||||
return $sortArray;
|
||||
}
|
||||
|
||||
$sortArray = self::enumerateArrayKeys($sortArray);
|
||||
|
||||
$byColumn = (bool) $byColumn;
|
||||
$lookupIndexSize = $byColumn ? count($sortArray) : count($sortArray[0]);
|
||||
|
||||
try {
|
||||
// If $sortIndex and $sortOrder are scalars, then convert them into arrays
|
||||
if (is_scalar($sortIndex)) {
|
||||
$sortIndex = [$sortIndex];
|
||||
$sortOrder = is_scalar($sortOrder) ? [$sortOrder] : $sortOrder;
|
||||
}
|
||||
// but the values of those array arguments still need validation
|
||||
$sortOrder = (empty($sortOrder) ? [self::ORDER_ASCENDING] : $sortOrder);
|
||||
self::validateArrayArgumentsForSort($sortIndex, $sortOrder, $lookupIndexSize);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// We want a simple, enumrated array of arrays where we can reference column by its index number.
|
||||
$sortArray = array_values(array_map('array_values', $sortArray));
|
||||
|
||||
return ($byColumn === true)
|
||||
? self::sortByColumn($sortArray, $sortIndex, $sortOrder)
|
||||
: self::sortByRow($sortArray, $sortIndex, $sortOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* SORTBY
|
||||
* The SORTBY function sorts the contents of a range or array based on the values in a corresponding range or array.
|
||||
* The returned array is the same shape as the provided array argument.
|
||||
* Both $sortIndex and $sortOrder can be arrays, to provide multi-level sorting.
|
||||
*
|
||||
* @param mixed $sortArray The range of cells being sorted
|
||||
* @param mixed $args
|
||||
* At least one additional argument must be provided, The vector or range to sort on
|
||||
* After that, arguments are passed as pairs:
|
||||
* sort order: ascending or descending
|
||||
* Ascending = 1 (self::ORDER_ASCENDING)
|
||||
* Descending = -1 (self::ORDER_DESCENDING)
|
||||
* additional arrays or ranges for multi-level sorting
|
||||
*
|
||||
* @return mixed The sorted values from the sort range
|
||||
*/
|
||||
public static function sortBy($sortArray, ...$args)
|
||||
{
|
||||
if (!is_array($sortArray)) {
|
||||
// Scalars are always returned "as is"
|
||||
return $sortArray;
|
||||
}
|
||||
|
||||
$sortArray = self::enumerateArrayKeys($sortArray);
|
||||
|
||||
$lookupArraySize = count($sortArray);
|
||||
$argumentCount = count($args);
|
||||
|
||||
try {
|
||||
$sortBy = $sortOrder = [];
|
||||
for ($i = 0; $i < $argumentCount; $i += 2) {
|
||||
$sortBy[] = self::validateSortVector($args[$i], $lookupArraySize);
|
||||
$sortOrder[] = self::validateSortOrder($args[$i + 1] ?? self::ORDER_ASCENDING);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return self::processSortBy($sortArray, $sortBy, $sortOrder);
|
||||
}
|
||||
|
||||
private static function enumerateArrayKeys(array $sortArray): array
|
||||
{
|
||||
array_walk(
|
||||
$sortArray,
|
||||
function (&$columns): void {
|
||||
if (is_array($columns)) {
|
||||
$columns = array_values($columns);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return array_values($sortArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $sortIndex
|
||||
* @param mixed $sortOrder
|
||||
*/
|
||||
private static function validateScalarArgumentsForSort(&$sortIndex, &$sortOrder, int $sortArraySize): void
|
||||
{
|
||||
if (is_array($sortIndex) || is_array($sortOrder)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
$sortIndex = self::validatePositiveInt($sortIndex, false);
|
||||
|
||||
if ($sortIndex > $sortArraySize) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
$sortOrder = self::validateSortOrder($sortOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $sortVector
|
||||
*/
|
||||
private static function validateSortVector($sortVector, int $sortArraySize): array
|
||||
{
|
||||
if (!is_array($sortVector)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
// It doesn't matter if it's a row or a column vectors, it works either way
|
||||
$sortVector = Functions::flattenArray($sortVector);
|
||||
if (count($sortVector) !== $sortArraySize) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return $sortVector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $sortOrder
|
||||
*/
|
||||
private static function validateSortOrder($sortOrder): int
|
||||
{
|
||||
$sortOrder = self::validateInt($sortOrder);
|
||||
if (($sortOrder == self::ORDER_ASCENDING || $sortOrder === self::ORDER_DESCENDING) === false) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return $sortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $sortIndex
|
||||
* @param mixed $sortOrder
|
||||
*/
|
||||
private static function validateArrayArgumentsForSort(&$sortIndex, &$sortOrder, int $sortArraySize): void
|
||||
{
|
||||
// It doesn't matter if they're row or column vectors, it works either way
|
||||
$sortIndex = Functions::flattenArray($sortIndex);
|
||||
$sortOrder = Functions::flattenArray($sortOrder);
|
||||
|
||||
if (
|
||||
count($sortOrder) === 0 || count($sortOrder) > $sortArraySize ||
|
||||
(count($sortOrder) > count($sortIndex))
|
||||
) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
if (count($sortIndex) > count($sortOrder)) {
|
||||
// If $sortOrder has fewer elements than $sortIndex, then the last order element is repeated.
|
||||
$sortOrder = array_merge(
|
||||
$sortOrder,
|
||||
array_fill(0, count($sortIndex) - count($sortOrder), array_pop($sortOrder))
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($sortIndex as $key => &$value) {
|
||||
self::validateScalarArgumentsForSort($value, $sortOrder[$key], $sortArraySize);
|
||||
}
|
||||
}
|
||||
|
||||
private static function prepareSortVectorValues(array $sortVector): array
|
||||
{
|
||||
// Strings should be sorted case-insensitive; with booleans converted to locale-strings
|
||||
return array_map(
|
||||
function ($value) {
|
||||
if (is_bool($value)) {
|
||||
return ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||
} elseif (is_string($value)) {
|
||||
return StringHelper::strToLower($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
$sortVector
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array[] $sortIndex
|
||||
* @param int[] $sortOrder
|
||||
*/
|
||||
private static function processSortBy(array $sortArray, array $sortIndex, $sortOrder): array
|
||||
{
|
||||
$sortArguments = [];
|
||||
$sortData = [];
|
||||
foreach ($sortIndex as $index => $sortValues) {
|
||||
$sortData[] = $sortValues;
|
||||
$sortArguments[] = self::prepareSortVectorValues($sortValues);
|
||||
$sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC;
|
||||
}
|
||||
$sortArguments = self::applyPHP7Patch($sortArray, $sortArguments);
|
||||
|
||||
$sortVector = self::executeVectorSortQuery($sortData, $sortArguments);
|
||||
|
||||
return self::sortLookupArrayFromVector($sortArray, $sortVector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $sortIndex
|
||||
* @param int[] $sortOrder
|
||||
*/
|
||||
private static function sortByRow(array $sortArray, array $sortIndex, array $sortOrder): array
|
||||
{
|
||||
$sortVector = self::buildVectorForSort($sortArray, $sortIndex, $sortOrder);
|
||||
|
||||
return self::sortLookupArrayFromVector($sortArray, $sortVector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $sortIndex
|
||||
* @param int[] $sortOrder
|
||||
*/
|
||||
private static function sortByColumn(array $sortArray, array $sortIndex, array $sortOrder): array
|
||||
{
|
||||
$sortArray = Matrix::transpose($sortArray);
|
||||
$result = self::sortByRow($sortArray, $sortIndex, $sortOrder);
|
||||
|
||||
return Matrix::transpose($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $sortIndex
|
||||
* @param int[] $sortOrder
|
||||
*/
|
||||
private static function buildVectorForSort(array $sortArray, array $sortIndex, array $sortOrder): array
|
||||
{
|
||||
$sortArguments = [];
|
||||
$sortData = [];
|
||||
foreach ($sortIndex as $index => $sortIndexValue) {
|
||||
$sortValues = array_column($sortArray, $sortIndexValue - 1);
|
||||
$sortData[] = $sortValues;
|
||||
$sortArguments[] = self::prepareSortVectorValues($sortValues);
|
||||
$sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC;
|
||||
}
|
||||
$sortArguments = self::applyPHP7Patch($sortArray, $sortArguments);
|
||||
|
||||
$sortData = self::executeVectorSortQuery($sortData, $sortArguments);
|
||||
|
||||
return $sortData;
|
||||
}
|
||||
|
||||
private static function executeVectorSortQuery(array $sortData, array $sortArguments): array
|
||||
{
|
||||
$sortData = Matrix::transpose($sortData);
|
||||
|
||||
// We need to set an index that can be retained, as array_multisort doesn't maintain numeric keys.
|
||||
$sortDataIndexed = [];
|
||||
foreach ($sortData as $key => $value) {
|
||||
$sortDataIndexed[Coordinate::stringFromColumnIndex($key + 1)] = $value;
|
||||
}
|
||||
unset($sortData);
|
||||
|
||||
$sortArguments[] = &$sortDataIndexed;
|
||||
|
||||
array_multisort(...$sortArguments);
|
||||
|
||||
// After the sort, we restore the numeric keys that will now be in the correct, sorted order
|
||||
$sortedData = [];
|
||||
foreach (array_keys($sortDataIndexed) as $key) {
|
||||
$sortedData[] = Coordinate::columnIndexFromString($key) - 1;
|
||||
}
|
||||
|
||||
return $sortedData;
|
||||
}
|
||||
|
||||
private static function sortLookupArrayFromVector(array $sortArray, array $sortVector): array
|
||||
{
|
||||
// Building a new array in the correct (sorted) order works; but may be memory heavy for larger arrays
|
||||
$sortedArray = [];
|
||||
foreach ($sortVector as $index) {
|
||||
$sortedArray[] = $sortArray[$index];
|
||||
}
|
||||
|
||||
return $sortedArray;
|
||||
|
||||
// uksort(
|
||||
// $lookupArray,
|
||||
// function (int $a, int $b) use (array $sortVector) {
|
||||
// return $sortVector[$a] <=> $sortVector[$b];
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// return $lookupArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hack to handle PHP 7:
|
||||
* From PHP 8.0.0, If two members compare as equal in a sort, they retain their original order;
|
||||
* but prior to PHP 8.0.0, their relative order in the sorted array was undefined.
|
||||
* MS Excel replicates the PHP 8.0.0 behaviour, retaining the original order of matching elements.
|
||||
* To replicate that behaviour with PHP 7, we add an extra sort based on the row index.
|
||||
*/
|
||||
private static function applyPHP7Patch(array $sortArray, array $sortArguments): array
|
||||
{
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
$sortArguments[] = range(1, count($sortArray));
|
||||
$sortArguments[] = SORT_ASC;
|
||||
}
|
||||
|
||||
return $sortArguments;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class Unique
|
||||
{
|
||||
/**
|
||||
* UNIQUE
|
||||
* The UNIQUE function searches for value either from a one-row or one-column range or from an array.
|
||||
*
|
||||
* @param mixed $lookupVector The range of cells being searched
|
||||
* @param mixed $byColumn Whether the uniqueness should be determined by row (the default) or by column
|
||||
* @param mixed $exactlyOnce Whether the function should return only entries that occur just once in the list
|
||||
*
|
||||
* @return mixed The unique values from the search range
|
||||
*/
|
||||
public static function unique($lookupVector, $byColumn = false, $exactlyOnce = false)
|
||||
{
|
||||
if (!is_array($lookupVector)) {
|
||||
// Scalars are always returned "as is"
|
||||
return $lookupVector;
|
||||
}
|
||||
|
||||
$byColumn = (bool) $byColumn;
|
||||
$exactlyOnce = (bool) $exactlyOnce;
|
||||
|
||||
return ($byColumn === true)
|
||||
? self::uniqueByColumn($lookupVector, $exactlyOnce)
|
||||
: self::uniqueByRow($lookupVector, $exactlyOnce);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
private static function uniqueByRow(array $lookupVector, bool $exactlyOnce)
|
||||
{
|
||||
// When not $byColumn, we count whole rows or values, not individual values
|
||||
// so implode each row into a single string value
|
||||
array_walk(
|
||||
$lookupVector,
|
||||
function (array &$value): void {
|
||||
$value = implode(chr(0x00), $value);
|
||||
}
|
||||
);
|
||||
|
||||
$result = self::countValuesCaseInsensitive($lookupVector);
|
||||
|
||||
if ($exactlyOnce === true) {
|
||||
$result = self::exactlyOnceFilter($result);
|
||||
}
|
||||
|
||||
if (count($result) === 0) {
|
||||
return ExcelError::CALC();
|
||||
}
|
||||
|
||||
$result = array_keys($result);
|
||||
|
||||
// restore rows from their strings
|
||||
array_walk(
|
||||
$result,
|
||||
function (string &$value): void {
|
||||
$value = explode(chr(0x00), $value);
|
||||
}
|
||||
);
|
||||
|
||||
return (count($result) === 1) ? array_pop($result) : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
private static function uniqueByColumn(array $lookupVector, bool $exactlyOnce)
|
||||
{
|
||||
$flattenedLookupVector = Functions::flattenArray($lookupVector);
|
||||
|
||||
if (count($lookupVector, COUNT_RECURSIVE) > count($flattenedLookupVector, COUNT_RECURSIVE) + 1) {
|
||||
// We're looking at a full column check (multiple rows)
|
||||
$transpose = Matrix::transpose($lookupVector);
|
||||
$result = self::uniqueByRow($transpose, $exactlyOnce);
|
||||
|
||||
return (is_array($result)) ? Matrix::transpose($result) : $result;
|
||||
}
|
||||
|
||||
$result = self::countValuesCaseInsensitive($flattenedLookupVector);
|
||||
|
||||
if ($exactlyOnce === true) {
|
||||
$result = self::exactlyOnceFilter($result);
|
||||
}
|
||||
|
||||
if (count($result) === 0) {
|
||||
return ExcelError::CALC();
|
||||
}
|
||||
|
||||
$result = array_keys($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function countValuesCaseInsensitive(array $caseSensitiveLookupValues): array
|
||||
{
|
||||
$caseInsensitiveCounts = array_count_values(
|
||||
array_map(
|
||||
function (string $value) {
|
||||
return StringHelper::strToUpper($value);
|
||||
},
|
||||
$caseSensitiveLookupValues
|
||||
)
|
||||
);
|
||||
|
||||
$caseSensitiveCounts = [];
|
||||
foreach ($caseInsensitiveCounts as $caseInsensitiveKey => $count) {
|
||||
if (is_numeric($caseInsensitiveKey)) {
|
||||
$caseSensitiveCounts[$caseInsensitiveKey] = $count;
|
||||
} else {
|
||||
foreach ($caseSensitiveLookupValues as $caseSensitiveValue) {
|
||||
if ($caseInsensitiveKey === StringHelper::strToUpper($caseSensitiveValue)) {
|
||||
$caseSensitiveCounts[$caseSensitiveValue] = $count;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $caseSensitiveCounts;
|
||||
}
|
||||
|
||||
private static function exactlyOnceFilter(array $values): array
|
||||
{
|
||||
return array_filter(
|
||||
$values,
|
||||
function ($value) {
|
||||
return $value === 1;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ class VLookup extends LookupBase
|
|||
$notExactMatch = (bool) ($notExactMatch ?? true);
|
||||
|
||||
try {
|
||||
self::validateLookupArray($lookupArray);
|
||||
$indexNumber = self::validateIndexLookup($lookupArray, $indexNumber);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
|
||||
class CellReferenceHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $beforeCellAddress;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $beforeColumn;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $beforeRow;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $numberOfColumns;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $numberOfRows;
|
||||
|
||||
public function __construct(string $beforeCellAddress = 'A1', int $numberOfColumns = 0, int $numberOfRows = 0)
|
||||
{
|
||||
$this->beforeCellAddress = str_replace('$', '', $beforeCellAddress);
|
||||
$this->numberOfColumns = $numberOfColumns;
|
||||
$this->numberOfRows = $numberOfRows;
|
||||
|
||||
// Get coordinate of $beforeCellAddress
|
||||
[$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($beforeCellAddress);
|
||||
$this->beforeColumn = (int) Coordinate::columnIndexFromString($beforeColumn);
|
||||
$this->beforeRow = (int) $beforeRow;
|
||||
}
|
||||
|
||||
public function beforeCellAddress(): string
|
||||
{
|
||||
return $this->beforeCellAddress;
|
||||
}
|
||||
|
||||
public function refreshRequired(string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): bool
|
||||
{
|
||||
return $this->beforeCellAddress !== $beforeCellAddress ||
|
||||
$this->numberOfColumns !== $numberOfColumns ||
|
||||
$this->numberOfRows !== $numberOfRows;
|
||||
}
|
||||
|
||||
public function updateCellReference(string $cellReference = 'A1', bool $includeAbsoluteReferences = false): string
|
||||
{
|
||||
if (Coordinate::coordinateIsRange($cellReference)) {
|
||||
throw new Exception('Only single cell references may be passed to this method.');
|
||||
}
|
||||
|
||||
// Get coordinate of $cellReference
|
||||
[$newColumn, $newRow] = Coordinate::coordinateFromString($cellReference);
|
||||
$newColumnIndex = (int) Coordinate::columnIndexFromString(str_replace('$', '', $newColumn));
|
||||
$newRowIndex = (int) str_replace('$', '', $newRow);
|
||||
|
||||
$absoluteColumn = $newColumn[0] === '$' ? '$' : '';
|
||||
$absoluteRow = $newRow[0] === '$' ? '$' : '';
|
||||
// Verify which parts should be updated
|
||||
if ($includeAbsoluteReferences === false) {
|
||||
$updateColumn = (($absoluteColumn !== '$') && $newColumnIndex >= $this->beforeColumn);
|
||||
$updateRow = (($absoluteRow !== '$') && $newRowIndex >= $this->beforeRow);
|
||||
} else {
|
||||
$updateColumn = ($newColumnIndex >= $this->beforeColumn);
|
||||
$updateRow = ($newRowIndex >= $this->beforeRow);
|
||||
}
|
||||
|
||||
// Create new column reference
|
||||
if ($updateColumn) {
|
||||
$newColumn = ($includeAbsoluteReferences === false)
|
||||
? Coordinate::stringFromColumnIndex($newColumnIndex + $this->numberOfColumns)
|
||||
: $absoluteColumn . Coordinate::stringFromColumnIndex($newColumnIndex + $this->numberOfColumns);
|
||||
}
|
||||
|
||||
// Create new row reference
|
||||
if ($updateRow) {
|
||||
$newRow = ($includeAbsoluteReferences === false)
|
||||
? $newRowIndex + $this->numberOfRows
|
||||
: $absoluteRow . (string) ($newRowIndex + $this->numberOfRows);
|
||||
}
|
||||
|
||||
// Return new reference
|
||||
return "{$newColumn}{$newRow}";
|
||||
}
|
||||
|
||||
public function cellAddressInDeleteRange(string $cellAddress): bool
|
||||
{
|
||||
[$cellColumn, $cellRow] = Coordinate::coordinateFromString($cellAddress);
|
||||
$cellColumnIndex = Coordinate::columnIndexFromString($cellColumn);
|
||||
// Is cell within the range of rows/columns if we're deleting
|
||||
if (
|
||||
$this->numberOfRows < 0 &&
|
||||
($cellRow >= ($this->beforeRow + $this->numberOfRows)) &&
|
||||
($cellRow < $this->beforeRow)
|
||||
) {
|
||||
return true;
|
||||
} elseif (
|
||||
$this->numberOfColumns < 0 &&
|
||||
($cellColumnIndex >= ($this->beforeColumn + $this->numberOfColumns)) &&
|
||||
($cellColumnIndex < $this->beforeColumn)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -619,6 +619,7 @@ class Html
|
|||
// Load the HTML file into the DOM object
|
||||
// Note the use of error suppression, because typically this will be an html fragment, so not fully valid markup
|
||||
$prefix = '<?xml encoding="UTF-8">';
|
||||
/** @scrutinizer ignore-unhandled */
|
||||
@$dom->loadHTML($prefix . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
|
||||
// Discard excess white space
|
||||
$dom->preserveWhiteSpace = false;
|
||||
|
|
@ -808,7 +809,7 @@ class Html
|
|||
if (isset($callbacks[$callbackTag])) {
|
||||
$elementHandler = $callbacks[$callbackTag];
|
||||
if (method_exists($this, $elementHandler)) {
|
||||
// @phpstan-ignore-next-line
|
||||
/** @phpstan-ignore-next-line */
|
||||
call_user_func([$this, $elementHandler], $element);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
|||
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\NamedRange;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xls\ConditionalFormatting;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xls\Style\CellFont;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xls\Style\FillPattern;
|
||||
use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\CodePage;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
|
@ -21,6 +23,8 @@ use PhpOffice\PhpSpreadsheet\Shared\Xls as SharedXls;
|
|||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Borders;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Conditional;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Protection;
|
||||
|
|
@ -142,6 +146,8 @@ class Xls extends BaseReader
|
|||
const XLS_TYPE_SHEETLAYOUT = 0x0862;
|
||||
const XLS_TYPE_XFEXT = 0x087d;
|
||||
const XLS_TYPE_PAGELAYOUTVIEW = 0x088b;
|
||||
const XLS_TYPE_CFHEADER = 0x01b0;
|
||||
const XLS_TYPE_CFRULE = 0x01b1;
|
||||
const XLS_TYPE_UNKNOWN = 0xffff;
|
||||
|
||||
// Encryption type
|
||||
|
|
@ -1031,6 +1037,14 @@ class Xls extends BaseReader
|
|||
case self::XLS_TYPE_DATAVALIDATION:
|
||||
$this->readDataValidation();
|
||||
|
||||
break;
|
||||
case self::XLS_TYPE_CFHEADER:
|
||||
$cellRangeAddresses = $this->readCFHeader();
|
||||
|
||||
break;
|
||||
case self::XLS_TYPE_CFRULE:
|
||||
$this->readCFRule($cellRangeAddresses ?? []);
|
||||
|
||||
break;
|
||||
case self::XLS_TYPE_SHEETLAYOUT:
|
||||
$this->readSheetLayout();
|
||||
|
|
@ -4776,57 +4790,11 @@ class Xls extends BaseReader
|
|||
|
||||
// bit: 0-3; mask: 0x0000000F; type
|
||||
$type = (0x0000000F & $options) >> 0;
|
||||
switch ($type) {
|
||||
case 0x00:
|
||||
$type = DataValidation::TYPE_NONE;
|
||||
|
||||
break;
|
||||
case 0x01:
|
||||
$type = DataValidation::TYPE_WHOLE;
|
||||
|
||||
break;
|
||||
case 0x02:
|
||||
$type = DataValidation::TYPE_DECIMAL;
|
||||
|
||||
break;
|
||||
case 0x03:
|
||||
$type = DataValidation::TYPE_LIST;
|
||||
|
||||
break;
|
||||
case 0x04:
|
||||
$type = DataValidation::TYPE_DATE;
|
||||
|
||||
break;
|
||||
case 0x05:
|
||||
$type = DataValidation::TYPE_TIME;
|
||||
|
||||
break;
|
||||
case 0x06:
|
||||
$type = DataValidation::TYPE_TEXTLENGTH;
|
||||
|
||||
break;
|
||||
case 0x07:
|
||||
$type = DataValidation::TYPE_CUSTOM;
|
||||
|
||||
break;
|
||||
}
|
||||
$type = Xls\DataValidationHelper::type($type);
|
||||
|
||||
// bit: 4-6; mask: 0x00000070; error type
|
||||
$errorStyle = (0x00000070 & $options) >> 4;
|
||||
switch ($errorStyle) {
|
||||
case 0x00:
|
||||
$errorStyle = DataValidation::STYLE_STOP;
|
||||
|
||||
break;
|
||||
case 0x01:
|
||||
$errorStyle = DataValidation::STYLE_WARNING;
|
||||
|
||||
break;
|
||||
case 0x02:
|
||||
$errorStyle = DataValidation::STYLE_INFORMATION;
|
||||
|
||||
break;
|
||||
}
|
||||
$errorStyle = Xls\DataValidationHelper::errorStyle($errorStyle);
|
||||
|
||||
// bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)
|
||||
// I have only seen cases where this is 1
|
||||
|
|
@ -4846,39 +4814,10 @@ class Xls extends BaseReader
|
|||
|
||||
// bit: 20-23; mask: 0x00F00000; condition operator
|
||||
$operator = (0x00F00000 & $options) >> 20;
|
||||
switch ($operator) {
|
||||
case 0x00:
|
||||
$operator = DataValidation::OPERATOR_BETWEEN;
|
||||
$operator = Xls\DataValidationHelper::operator($operator);
|
||||
|
||||
break;
|
||||
case 0x01:
|
||||
$operator = DataValidation::OPERATOR_NOTBETWEEN;
|
||||
|
||||
break;
|
||||
case 0x02:
|
||||
$operator = DataValidation::OPERATOR_EQUAL;
|
||||
|
||||
break;
|
||||
case 0x03:
|
||||
$operator = DataValidation::OPERATOR_NOTEQUAL;
|
||||
|
||||
break;
|
||||
case 0x04:
|
||||
$operator = DataValidation::OPERATOR_GREATERTHAN;
|
||||
|
||||
break;
|
||||
case 0x05:
|
||||
$operator = DataValidation::OPERATOR_LESSTHAN;
|
||||
|
||||
break;
|
||||
case 0x06:
|
||||
$operator = DataValidation::OPERATOR_GREATERTHANOREQUAL;
|
||||
|
||||
break;
|
||||
case 0x07:
|
||||
$operator = DataValidation::OPERATOR_LESSTHANOREQUAL;
|
||||
|
||||
break;
|
||||
if ($type === null || $errorStyle === null || $operator === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// offset: 4; size: var; title of the prompt box
|
||||
|
|
@ -7921,4 +7860,230 @@ class Xls extends BaseReader
|
|||
{
|
||||
return $this->mapCellStyleXfIndex;
|
||||
}
|
||||
|
||||
private function readCFHeader(): array
|
||||
{
|
||||
$length = self::getUInt2d($this->data, $this->pos + 2);
|
||||
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
|
||||
|
||||
// move stream pointer forward to next record
|
||||
$this->pos += 4 + $length;
|
||||
|
||||
if ($this->readDataOnly) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// offset: 0; size: 2; Rule Count
|
||||
// $ruleCount = self::getUInt2d($recordData, 0);
|
||||
|
||||
// offset: var; size: var; cell range address list with
|
||||
$cellRangeAddressList = ($this->version == self::XLS_BIFF8)
|
||||
? $this->readBIFF8CellRangeAddressList(substr($recordData, 12))
|
||||
: $this->readBIFF5CellRangeAddressList(substr($recordData, 12));
|
||||
$cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
|
||||
|
||||
return $cellRangeAddresses;
|
||||
}
|
||||
|
||||
private function readCFRule(array $cellRangeAddresses): void
|
||||
{
|
||||
$length = self::getUInt2d($this->data, $this->pos + 2);
|
||||
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
|
||||
|
||||
// move stream pointer forward to next record
|
||||
$this->pos += 4 + $length;
|
||||
|
||||
if ($this->readDataOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
// offset: 0; size: 2; Options
|
||||
$cfRule = self::getUInt2d($recordData, 0);
|
||||
|
||||
// bit: 8-15; mask: 0x00FF; type
|
||||
$type = (0x00FF & $cfRule) >> 0;
|
||||
$type = ConditionalFormatting::type($type);
|
||||
|
||||
// bit: 0-7; mask: 0xFF00; type
|
||||
$operator = (0xFF00 & $cfRule) >> 8;
|
||||
$operator = ConditionalFormatting::operator($operator);
|
||||
|
||||
if ($type === null || $operator === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// offset: 2; size: 2; Size1
|
||||
$size1 = self::getUInt2d($recordData, 2);
|
||||
|
||||
// offset: 4; size: 2; Size2
|
||||
$size2 = self::getUInt2d($recordData, 4);
|
||||
|
||||
// offset: 6; size: 4; Options
|
||||
$options = self::getInt4d($recordData, 6);
|
||||
|
||||
$style = new Style();
|
||||
$this->getCFStyleOptions($options, $style);
|
||||
|
||||
$hasFontRecord = (bool) ((0x04000000 & $options) >> 26);
|
||||
$hasAlignmentRecord = (bool) ((0x08000000 & $options) >> 27);
|
||||
$hasBorderRecord = (bool) ((0x10000000 & $options) >> 28);
|
||||
$hasFillRecord = (bool) ((0x20000000 & $options) >> 29);
|
||||
$hasProtectionRecord = (bool) ((0x40000000 & $options) >> 30);
|
||||
|
||||
$offset = 12;
|
||||
|
||||
if ($hasFontRecord === true) {
|
||||
$fontStyle = substr($recordData, $offset, 118);
|
||||
$this->getCFFontStyle($fontStyle, $style);
|
||||
$offset += 118;
|
||||
}
|
||||
|
||||
if ($hasAlignmentRecord === true) {
|
||||
$alignmentStyle = substr($recordData, $offset, 8);
|
||||
$this->getCFAlignmentStyle($alignmentStyle, $style);
|
||||
$offset += 8;
|
||||
}
|
||||
|
||||
if ($hasBorderRecord === true) {
|
||||
$borderStyle = substr($recordData, $offset, 8);
|
||||
$this->getCFBorderStyle($borderStyle, $style);
|
||||
$offset += 8;
|
||||
}
|
||||
|
||||
if ($hasFillRecord === true) {
|
||||
$fillStyle = substr($recordData, $offset, 4);
|
||||
$this->getCFFillStyle($fillStyle, $style);
|
||||
$offset += 4;
|
||||
}
|
||||
|
||||
if ($hasProtectionRecord === true) {
|
||||
$protectionStyle = substr($recordData, $offset, 4);
|
||||
$this->getCFProtectionStyle($protectionStyle, $style);
|
||||
$offset += 2;
|
||||
}
|
||||
|
||||
$formula1 = $formula2 = null;
|
||||
if ($size1 > 0) {
|
||||
$formula1 = $this->readCFFormula($recordData, $offset, $size1);
|
||||
if ($formula1 === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$offset += $size1;
|
||||
}
|
||||
|
||||
if ($size2 > 0) {
|
||||
$formula2 = $this->readCFFormula($recordData, $offset, $size2);
|
||||
if ($formula2 === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$offset += $size2;
|
||||
}
|
||||
|
||||
$this->setCFRules($cellRangeAddresses, $type, $operator, $formula1, $formula2, $style);
|
||||
}
|
||||
|
||||
private function getCFStyleOptions(int $options, Style $style): void
|
||||
{
|
||||
}
|
||||
|
||||
private function getCFFontStyle(string $options, Style $style): void
|
||||
{
|
||||
$fontSize = self::getInt4d($options, 64);
|
||||
if ($fontSize !== -1) {
|
||||
$style->getFont()->setSize($fontSize / 20); // Convert twips to points
|
||||
}
|
||||
|
||||
$bold = self::getUInt2d($options, 72) === 700; // 400 = normal, 700 = bold
|
||||
$style->getFont()->setBold($bold);
|
||||
|
||||
$color = self::getInt4d($options, 80);
|
||||
|
||||
if ($color !== -1) {
|
||||
$style->getFont()->getColor()->setRGB(Xls\Color::map($color, $this->palette, $this->version)['rgb']);
|
||||
}
|
||||
}
|
||||
|
||||
private function getCFAlignmentStyle(string $options, Style $style): void
|
||||
{
|
||||
}
|
||||
|
||||
private function getCFBorderStyle(string $options, Style $style): void
|
||||
{
|
||||
}
|
||||
|
||||
private function getCFFillStyle(string $options, Style $style): void
|
||||
{
|
||||
$fillPattern = self::getUInt2d($options, 0);
|
||||
// bit: 10-15; mask: 0xFC00; type
|
||||
$fillPattern = (0xFC00 & $fillPattern) >> 10;
|
||||
$fillPattern = FillPattern::lookup($fillPattern);
|
||||
$fillPattern = $fillPattern === Fill::FILL_NONE ? Fill::FILL_SOLID : $fillPattern;
|
||||
|
||||
if ($fillPattern !== Fill::FILL_NONE) {
|
||||
$style->getFill()->setFillType($fillPattern);
|
||||
|
||||
$fillColors = self::getUInt2d($options, 2);
|
||||
|
||||
// bit: 0-6; mask: 0x007F; type
|
||||
$color1 = (0x007F & $fillColors) >> 0;
|
||||
$style->getFill()->getStartColor()->setRGB(Xls\Color::map($color1, $this->palette, $this->version)['rgb']);
|
||||
|
||||
// bit: 7-13; mask: 0x3F80; type
|
||||
$color2 = (0x3F80 & $fillColors) >> 7;
|
||||
$style->getFill()->getEndColor()->setRGB(Xls\Color::map($color2, $this->palette, $this->version)['rgb']);
|
||||
}
|
||||
}
|
||||
|
||||
private function getCFProtectionStyle(string $options, Style $style): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|float|int|string
|
||||
*/
|
||||
private function readCFFormula(string $recordData, int $offset, int $size)
|
||||
{
|
||||
try {
|
||||
$formula = substr($recordData, $offset, $size);
|
||||
$formula = pack('v', $size) . $formula; // prepend the length
|
||||
|
||||
$formula = $this->getFormulaFromStructure($formula);
|
||||
if (is_numeric($formula)) {
|
||||
return (strpos($formula, '.') !== false) ? (float) $formula : (int) $formula;
|
||||
}
|
||||
|
||||
return $formula;
|
||||
} catch (PhpSpreadsheetException $e) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|float|int|string $formula1
|
||||
* @param null|float|int|string $formula2
|
||||
*/
|
||||
private function setCFRules(array $cellRanges, string $type, string $operator, $formula1, $formula2, Style $style): void
|
||||
{
|
||||
foreach ($cellRanges as $cellRange) {
|
||||
$conditional = new Conditional();
|
||||
$conditional->setConditionType($type);
|
||||
$conditional->setOperatorType($operator);
|
||||
if ($formula1 !== null) {
|
||||
$conditional->addCondition($formula1);
|
||||
}
|
||||
if ($formula2 !== null) {
|
||||
$conditional->addCondition($formula2);
|
||||
}
|
||||
$conditional->setStyle($style);
|
||||
|
||||
$conditionalStyles = $this->phpSheet->getStyle($cellRange)->getConditionalStyles();
|
||||
$conditionalStyles[] = $conditional;
|
||||
|
||||
$this->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles);
|
||||
$this->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Style\Conditional;
|
||||
|
||||
class ConditionalFormatting
|
||||
{
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private static $types = [
|
||||
0x01 => Conditional::CONDITION_CELLIS,
|
||||
0x02 => Conditional::CONDITION_EXPRESSION,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private static $operators = [
|
||||
0x00 => Conditional::OPERATOR_NONE,
|
||||
0x01 => Conditional::OPERATOR_BETWEEN,
|
||||
0x02 => Conditional::OPERATOR_NOTBETWEEN,
|
||||
0x03 => Conditional::OPERATOR_EQUAL,
|
||||
0x04 => Conditional::OPERATOR_NOTEQUAL,
|
||||
0x05 => Conditional::OPERATOR_GREATERTHAN,
|
||||
0x06 => Conditional::OPERATOR_LESSTHAN,
|
||||
0x07 => Conditional::OPERATOR_GREATERTHANOREQUAL,
|
||||
0x08 => Conditional::OPERATOR_LESSTHANOREQUAL,
|
||||
];
|
||||
|
||||
public static function type(int $type): ?string
|
||||
{
|
||||
if (isset(self::$types[$type])) {
|
||||
return self::$types[$type];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function operator(int $operator): ?string
|
||||
{
|
||||
if (isset(self::$operators[$operator])) {
|
||||
return self::$operators[$operator];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
|
||||
|
||||
class DataValidationHelper
|
||||
{
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private static $types = [
|
||||
0x00 => DataValidation::TYPE_NONE,
|
||||
0x01 => DataValidation::TYPE_WHOLE,
|
||||
0x02 => DataValidation::TYPE_DECIMAL,
|
||||
0x03 => DataValidation::TYPE_LIST,
|
||||
0x04 => DataValidation::TYPE_DATE,
|
||||
0x05 => DataValidation::TYPE_TIME,
|
||||
0x06 => DataValidation::TYPE_TEXTLENGTH,
|
||||
0x07 => DataValidation::TYPE_CUSTOM,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private static $errorStyles = [
|
||||
0x00 => DataValidation::STYLE_STOP,
|
||||
0x01 => DataValidation::STYLE_WARNING,
|
||||
0x02 => DataValidation::STYLE_INFORMATION,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private static $operators = [
|
||||
0x00 => DataValidation::OPERATOR_BETWEEN,
|
||||
0x01 => DataValidation::OPERATOR_NOTBETWEEN,
|
||||
0x02 => DataValidation::OPERATOR_EQUAL,
|
||||
0x03 => DataValidation::OPERATOR_NOTEQUAL,
|
||||
0x04 => DataValidation::OPERATOR_GREATERTHAN,
|
||||
0x05 => DataValidation::OPERATOR_LESSTHAN,
|
||||
0x06 => DataValidation::OPERATOR_GREATERTHANOREQUAL,
|
||||
0x07 => DataValidation::OPERATOR_LESSTHANOREQUAL,
|
||||
];
|
||||
|
||||
public static function type(int $type): ?string
|
||||
{
|
||||
if (isset(self::$types[$type])) {
|
||||
return self::$types[$type];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function errorStyle(int $errorStyle): ?string
|
||||
{
|
||||
if (isset(self::$errorStyles[$errorStyle])) {
|
||||
return self::$errorStyles[$errorStyle];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function operator(int $operator): ?string
|
||||
{
|
||||
if (isset(self::$operators[$operator])) {
|
||||
return self::$operators[$operator];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ class MD5
|
|||
*/
|
||||
public function add(string $data): void
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
// @phpstan-ignore-next-line
|
||||
$words = array_values(unpack('V16', $data));
|
||||
|
||||
$A = $this->a;
|
||||
|
|
|
|||
|
|
@ -1315,6 +1315,11 @@ class Xlsx extends BaseReader
|
|||
$outerShdw = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw;
|
||||
$hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick;
|
||||
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
|
||||
/** @scrutinizer ignore-call */
|
||||
$editAs = $twoCellAnchor->attributes();
|
||||
if (isset($editAs, $editAs['editAs'])) {
|
||||
$objDrawing->setEditAs($editAs['editAs']);
|
||||
}
|
||||
$objDrawing->setName((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'name'));
|
||||
$objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'descr'));
|
||||
$embedImageKey = (string) self::getArrayItem(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ namespace PhpOffice\PhpSpreadsheet;
|
|||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Conditional;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class ReferenceHelper
|
||||
|
|
@ -19,10 +21,15 @@ class ReferenceHelper
|
|||
/**
|
||||
* Instance of this class.
|
||||
*
|
||||
* @var ReferenceHelper
|
||||
* @var ?ReferenceHelper
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @var CellReferenceHelper
|
||||
*/
|
||||
private $cellReferenceHelper;
|
||||
|
||||
/**
|
||||
* Get an instance of this class.
|
||||
*
|
||||
|
|
@ -30,7 +37,7 @@ class ReferenceHelper
|
|||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (!isset(self::$instance) || (self::$instance === null)) {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
|
|
@ -114,67 +121,32 @@ class ReferenceHelper
|
|||
return ($ar < $br) ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a cell address falls within a defined range of cells.
|
||||
*
|
||||
* @param string $cellAddress Address of the cell we're testing
|
||||
* @param int $beforeRow Number of the row we're inserting/deleting before
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
* @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
|
||||
* @param int $numberOfCols Number of columns to insert/delete (negative values indicate deletion)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function cellAddressInDeleteRange($cellAddress, $beforeRow, $numberOfRows, $beforeColumnIndex, $numberOfCols)
|
||||
{
|
||||
[$cellColumn, $cellRow] = Coordinate::coordinateFromString($cellAddress);
|
||||
$cellColumnIndex = Coordinate::columnIndexFromString($cellColumn);
|
||||
// Is cell within the range of rows/columns if we're deleting
|
||||
if (
|
||||
$numberOfRows < 0 &&
|
||||
($cellRow >= ($beforeRow + $numberOfRows)) &&
|
||||
($cellRow < $beforeRow)
|
||||
) {
|
||||
return true;
|
||||
} elseif (
|
||||
$numberOfCols < 0 &&
|
||||
($cellColumnIndex >= ($beforeColumnIndex + $numberOfCols)) &&
|
||||
($cellColumnIndex < $beforeColumnIndex)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update page breaks when inserting/deleting rows/columns.
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet that we're editing
|
||||
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
|
||||
* @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $beforeRow Number of the row we're inserting/deleting before
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustPageBreaks(Worksheet $worksheet, $beforeCellAddress, $beforeColumnIndex, $numberOfColumns, $beforeRow, $numberOfRows): void
|
||||
protected function adjustPageBreaks(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void
|
||||
{
|
||||
$aBreaks = $worksheet->getBreaks();
|
||||
($numberOfColumns > 0 || $numberOfRows > 0) ?
|
||||
uksort($aBreaks, ['self', 'cellReverseSort']) : uksort($aBreaks, ['self', 'cellSort']);
|
||||
($numberOfColumns > 0 || $numberOfRows > 0)
|
||||
? uksort($aBreaks, [self::class, 'cellReverseSort'])
|
||||
: uksort($aBreaks, [self::class, 'cellSort']);
|
||||
|
||||
foreach ($aBreaks as $key => $value) {
|
||||
if (self::cellAddressInDeleteRange($key, $beforeRow, $numberOfRows, $beforeColumnIndex, $numberOfColumns)) {
|
||||
foreach ($aBreaks as $cellAddress => $value) {
|
||||
if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) {
|
||||
// If we're deleting, then clear any defined breaks that are within the range
|
||||
// of rows/columns that we're deleting
|
||||
$worksheet->setBreak($key, Worksheet::BREAK_NONE);
|
||||
$worksheet->setBreak($cellAddress, Worksheet::BREAK_NONE);
|
||||
} else {
|
||||
// Otherwise update any affected breaks by inserting a new break at the appropriate point
|
||||
// and removing the old affected break
|
||||
$newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
if ($key != $newReference) {
|
||||
$newReference = $this->updateCellReference($cellAddress);
|
||||
if ($cellAddress !== $newReference) {
|
||||
$worksheet->setBreak($newReference, $value)
|
||||
->setBreak($key, Worksheet::BREAK_NONE);
|
||||
->setBreak($cellAddress, Worksheet::BREAK_NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -184,22 +156,17 @@ class ReferenceHelper
|
|||
* Update cell comments when inserting/deleting rows/columns.
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet that we're editing
|
||||
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
|
||||
* @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $beforeRow Number of the row we're inserting/deleting before
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustComments($worksheet, $beforeCellAddress, $beforeColumnIndex, $numberOfColumns, $beforeRow, $numberOfRows): void
|
||||
protected function adjustComments($worksheet): void
|
||||
{
|
||||
$aComments = $worksheet->getComments();
|
||||
$aNewComments = []; // the new array of all comments
|
||||
|
||||
foreach ($aComments as $key => &$value) {
|
||||
foreach ($aComments as $cellAddress => &$value) {
|
||||
// Any comments inside a deleted range will be ignored
|
||||
if (!self::cellAddressInDeleteRange($key, $beforeRow, $numberOfRows, $beforeColumnIndex, $numberOfColumns)) {
|
||||
if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === false) {
|
||||
// Otherwise build a new array of comments indexed by the adjusted cell reference
|
||||
$newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$newReference = $this->updateCellReference($cellAddress);
|
||||
$aNewComments[$newReference] = $value;
|
||||
}
|
||||
}
|
||||
|
|
@ -211,44 +178,85 @@ class ReferenceHelper
|
|||
* Update hyperlinks when inserting/deleting rows/columns.
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet that we're editing
|
||||
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustHyperlinks($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void
|
||||
protected function adjustHyperlinks($worksheet, $numberOfColumns, $numberOfRows): void
|
||||
{
|
||||
$aHyperlinkCollection = $worksheet->getHyperlinkCollection();
|
||||
($numberOfColumns > 0 || $numberOfRows > 0) ?
|
||||
uksort($aHyperlinkCollection, ['self', 'cellReverseSort']) : uksort($aHyperlinkCollection, ['self', 'cellSort']);
|
||||
($numberOfColumns > 0 || $numberOfRows > 0)
|
||||
? uksort($aHyperlinkCollection, [self::class, 'cellReverseSort'])
|
||||
: uksort($aHyperlinkCollection, [self::class, 'cellSort']);
|
||||
|
||||
foreach ($aHyperlinkCollection as $key => $value) {
|
||||
$newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
if ($key != $newReference) {
|
||||
foreach ($aHyperlinkCollection as $cellAddress => $value) {
|
||||
$newReference = $this->updateCellReference($cellAddress);
|
||||
if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) {
|
||||
$worksheet->setHyperlink($cellAddress, null);
|
||||
} elseif ($cellAddress !== $newReference) {
|
||||
$worksheet->setHyperlink($newReference, $value);
|
||||
$worksheet->setHyperlink($key, null);
|
||||
$worksheet->setHyperlink($cellAddress, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update conditional formatting styles when inserting/deleting rows/columns.
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet that we're editing
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustConditionalFormatting($worksheet, $numberOfColumns, $numberOfRows): void
|
||||
{
|
||||
$aStyles = $worksheet->getConditionalStylesCollection();
|
||||
($numberOfColumns > 0 || $numberOfRows > 0)
|
||||
? uksort($aStyles, [self::class, 'cellReverseSort'])
|
||||
: uksort($aStyles, [self::class, 'cellSort']);
|
||||
|
||||
foreach ($aStyles as $cellAddress => $cfRules) {
|
||||
$worksheet->removeConditionalStyles($cellAddress);
|
||||
$newReference = $this->updateCellReference($cellAddress);
|
||||
|
||||
foreach ($cfRules as &$cfRule) {
|
||||
/** @var Conditional $cfRule */
|
||||
$conditions = $cfRule->getConditions();
|
||||
foreach ($conditions as &$condition) {
|
||||
if (is_string($condition)) {
|
||||
$condition = $this->updateFormulaReferences(
|
||||
$condition,
|
||||
$this->cellReferenceHelper->beforeCellAddress(),
|
||||
$numberOfColumns,
|
||||
$numberOfRows,
|
||||
$worksheet->getTitle(),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
$cfRule->setConditions($conditions);
|
||||
}
|
||||
$worksheet->setConditionalStyles($newReference, $cfRules);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data validations when inserting/deleting rows/columns.
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet that we're editing
|
||||
* @param string $before Insert/Delete before this cell address (e.g. 'A1')
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustDataValidations(Worksheet $worksheet, $before, $numberOfColumns, $numberOfRows): void
|
||||
protected function adjustDataValidations(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void
|
||||
{
|
||||
$aDataValidationCollection = $worksheet->getDataValidationCollection();
|
||||
($numberOfColumns > 0 || $numberOfRows > 0) ?
|
||||
uksort($aDataValidationCollection, ['self', 'cellReverseSort']) : uksort($aDataValidationCollection, ['self', 'cellSort']);
|
||||
($numberOfColumns > 0 || $numberOfRows > 0)
|
||||
? uksort($aDataValidationCollection, [self::class, 'cellReverseSort'])
|
||||
: uksort($aDataValidationCollection, [self::class, 'cellSort']);
|
||||
|
||||
foreach ($aDataValidationCollection as $key => $value) {
|
||||
$newReference = $this->updateCellReference($key, $before, $numberOfColumns, $numberOfRows);
|
||||
if ($key != $newReference) {
|
||||
foreach ($aDataValidationCollection as $cellAddress => $value) {
|
||||
$newReference = $this->updateCellReference($cellAddress);
|
||||
if ($cellAddress !== $newReference) {
|
||||
$worksheet->setDataValidation($newReference, $value);
|
||||
$worksheet->setDataValidation($key, null);
|
||||
$worksheet->setDataValidation($cellAddress, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -257,16 +265,13 @@ class ReferenceHelper
|
|||
* Update merged cells when inserting/deleting rows/columns.
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet that we're editing
|
||||
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustMergeCells(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void
|
||||
protected function adjustMergeCells(Worksheet $worksheet): void
|
||||
{
|
||||
$aMergeCells = $worksheet->getMergeCells();
|
||||
$aNewMergeCells = []; // the new array of all merge cells
|
||||
foreach ($aMergeCells as $key => &$value) {
|
||||
$newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
foreach ($aMergeCells as $cellAddress => &$value) {
|
||||
$newReference = $this->updateCellReference($cellAddress);
|
||||
$aNewMergeCells[$newReference] = $newReference;
|
||||
}
|
||||
$worksheet->setMergeCells($aNewMergeCells); // replace the merge cells array
|
||||
|
|
@ -276,20 +281,20 @@ class ReferenceHelper
|
|||
* Update protected cells when inserting/deleting rows/columns.
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet that we're editing
|
||||
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustProtectedCells(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void
|
||||
protected function adjustProtectedCells(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void
|
||||
{
|
||||
$aProtectedCells = $worksheet->getProtectedCells();
|
||||
($numberOfColumns > 0 || $numberOfRows > 0) ?
|
||||
uksort($aProtectedCells, ['self', 'cellReverseSort']) : uksort($aProtectedCells, ['self', 'cellSort']);
|
||||
foreach ($aProtectedCells as $key => $value) {
|
||||
$newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
if ($key != $newReference) {
|
||||
($numberOfColumns > 0 || $numberOfRows > 0)
|
||||
? uksort($aProtectedCells, [self::class, 'cellReverseSort'])
|
||||
: uksort($aProtectedCells, [self::class, 'cellSort']);
|
||||
foreach ($aProtectedCells as $cellAddress => $value) {
|
||||
$newReference = $this->updateCellReference($cellAddress);
|
||||
if ($cellAddress !== $newReference) {
|
||||
$worksheet->protectCells($newReference, $value, true);
|
||||
$worksheet->unprotectCells($key);
|
||||
$worksheet->unprotectCells($cellAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -298,18 +303,15 @@ class ReferenceHelper
|
|||
* Update column dimensions when inserting/deleting rows/columns.
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet that we're editing
|
||||
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustColumnDimensions(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void
|
||||
protected function adjustColumnDimensions(Worksheet $worksheet): void
|
||||
{
|
||||
$aColumnDimensions = array_reverse($worksheet->getColumnDimensions(), true);
|
||||
if (!empty($aColumnDimensions)) {
|
||||
foreach ($aColumnDimensions as $objColumnDimension) {
|
||||
$newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1', $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1');
|
||||
[$newReference] = Coordinate::coordinateFromString($newReference);
|
||||
if ($objColumnDimension->getColumnIndex() != $newReference) {
|
||||
if ($objColumnDimension->getColumnIndex() !== $newReference) {
|
||||
$objColumnDimension->setColumnIndex($newReference);
|
||||
}
|
||||
}
|
||||
|
|
@ -321,20 +323,19 @@ class ReferenceHelper
|
|||
* Update row dimensions when inserting/deleting rows/columns.
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet that we're editing
|
||||
* @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1')
|
||||
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
|
||||
* @param int $beforeRow Number of the row we're inserting/deleting before
|
||||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
*/
|
||||
protected function adjustRowDimensions(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $beforeRow, $numberOfRows): void
|
||||
protected function adjustRowDimensions(Worksheet $worksheet, $beforeRow, $numberOfRows): void
|
||||
{
|
||||
$aRowDimensions = array_reverse($worksheet->getRowDimensions(), true);
|
||||
if (!empty($aRowDimensions)) {
|
||||
foreach ($aRowDimensions as $objRowDimension) {
|
||||
$newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex(), $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex());
|
||||
[, $newReference] = Coordinate::coordinateFromString($newReference);
|
||||
if ($objRowDimension->getRowIndex() != $newReference) {
|
||||
$objRowDimension->setRowIndex($newReference);
|
||||
$newRoweference = (int) $newReference;
|
||||
if ($objRowDimension->getRowIndex() !== $newRoweference) {
|
||||
$objRowDimension->setRowIndex($newRoweference);
|
||||
}
|
||||
}
|
||||
$worksheet->refreshRowDimensions();
|
||||
|
|
@ -358,11 +359,22 @@ class ReferenceHelper
|
|||
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
|
||||
* @param Worksheet $worksheet The worksheet that we're editing
|
||||
*/
|
||||
public function insertNewBefore($beforeCellAddress, $numberOfColumns, $numberOfRows, Worksheet $worksheet): void
|
||||
{
|
||||
public function insertNewBefore(
|
||||
string $beforeCellAddress,
|
||||
int $numberOfColumns,
|
||||
int $numberOfRows,
|
||||
Worksheet $worksheet
|
||||
): void {
|
||||
$remove = ($numberOfColumns < 0 || $numberOfRows < 0);
|
||||
$allCoordinates = $worksheet->getCoordinates();
|
||||
|
||||
if (
|
||||
$this->cellReferenceHelper === null ||
|
||||
$this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows)
|
||||
) {
|
||||
$this->cellReferenceHelper = new CellReferenceHelper($beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
}
|
||||
|
||||
// Get coordinate of $beforeCellAddress
|
||||
[$beforeColumn, $beforeRow] = Coordinate::indexesFromString($beforeCellAddress);
|
||||
|
||||
|
|
@ -372,30 +384,12 @@ class ReferenceHelper
|
|||
|
||||
// 1. Clear column strips if we are removing columns
|
||||
if ($numberOfColumns < 0 && $beforeColumn - 2 + $numberOfColumns > 0) {
|
||||
for ($i = 1; $i <= $highestRow - 1; ++$i) {
|
||||
for ($j = $beforeColumn - 1 + $numberOfColumns; $j <= $beforeColumn - 2; ++$j) {
|
||||
$coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i;
|
||||
$worksheet->removeConditionalStyles($coordinate);
|
||||
if ($worksheet->cellExists($coordinate)) {
|
||||
$worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
|
||||
$worksheet->getCell($coordinate)->setXfIndex(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->clearColumnStrips($highestRow, $beforeColumn, $numberOfColumns, $worksheet);
|
||||
}
|
||||
|
||||
// 2. Clear row strips if we are removing rows
|
||||
if ($numberOfRows < 0 && $beforeRow - 1 + $numberOfRows > 0) {
|
||||
for ($i = $beforeColumn - 1; $i <= Coordinate::columnIndexFromString($highestColumn) - 1; ++$i) {
|
||||
for ($j = $beforeRow + $numberOfRows; $j <= $beforeRow - 1; ++$j) {
|
||||
$coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j;
|
||||
$worksheet->removeConditionalStyles($coordinate);
|
||||
if ($worksheet->cellExists($coordinate)) {
|
||||
$worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
|
||||
$worksheet->getCell($coordinate)->setXfIndex(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->clearRowStrips($highestColumn, $beforeColumn, $beforeRow, $numberOfRows, $worksheet);
|
||||
}
|
||||
|
||||
// Find missing coordinates. This is important when inserting column before the last column
|
||||
|
|
@ -440,7 +434,7 @@ class ReferenceHelper
|
|||
$worksheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex());
|
||||
|
||||
// Insert this cell at its new location
|
||||
if ($cell->getDataType() == DataType::TYPE_FORMULA) {
|
||||
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
|
||||
// Formula should be adjusted
|
||||
$worksheet->getCell($newCoordinate)
|
||||
->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle()));
|
||||
|
|
@ -454,7 +448,7 @@ class ReferenceHelper
|
|||
} else {
|
||||
/* We don't need to update styles for rows/columns before our insertion position,
|
||||
but we do still need to adjust any formulae in those cells */
|
||||
if ($cell->getDataType() == DataType::TYPE_FORMULA) {
|
||||
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
|
||||
// Formula should be adjusted
|
||||
$cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle()));
|
||||
}
|
||||
|
|
@ -466,148 +460,65 @@ class ReferenceHelper
|
|||
$highestRow = $worksheet->getHighestRow();
|
||||
|
||||
if ($numberOfColumns > 0 && $beforeColumn - 2 > 0) {
|
||||
for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
|
||||
// Style
|
||||
$coordinate = Coordinate::stringFromColumnIndex($beforeColumn - 1) . $i;
|
||||
if ($worksheet->cellExists($coordinate)) {
|
||||
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
|
||||
$conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ?
|
||||
$worksheet->getConditionalStyles($coordinate) : false;
|
||||
for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) {
|
||||
$worksheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex);
|
||||
if ($conditionalStyles) {
|
||||
$cloned = [];
|
||||
foreach ($conditionalStyles as $conditionalStyle) {
|
||||
$cloned[] = clone $conditionalStyle;
|
||||
}
|
||||
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($j) . $i, $cloned);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->duplicateStylesByColumn($worksheet, $beforeColumn, $beforeRow, $highestRow, $numberOfColumns);
|
||||
}
|
||||
|
||||
if ($numberOfRows > 0 && $beforeRow - 1 > 0) {
|
||||
for ($i = $beforeColumn; $i <= Coordinate::columnIndexFromString($highestColumn); ++$i) {
|
||||
// Style
|
||||
$coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1);
|
||||
if ($worksheet->cellExists($coordinate)) {
|
||||
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
|
||||
$conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ?
|
||||
$worksheet->getConditionalStyles($coordinate) : false;
|
||||
for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) {
|
||||
$worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
|
||||
if ($conditionalStyles) {
|
||||
$cloned = [];
|
||||
foreach ($conditionalStyles as $conditionalStyle) {
|
||||
$cloned[] = clone $conditionalStyle;
|
||||
}
|
||||
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($i) . $j, $cloned);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->duplicateStylesByRow($worksheet, $beforeColumn, $beforeRow, $highestColumn, $numberOfRows);
|
||||
}
|
||||
|
||||
// Update worksheet: column dimensions
|
||||
$this->adjustColumnDimensions($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$this->adjustColumnDimensions($worksheet);
|
||||
|
||||
// Update worksheet: row dimensions
|
||||
$this->adjustRowDimensions($worksheet, $beforeCellAddress, $numberOfColumns, $beforeRow, $numberOfRows);
|
||||
$this->adjustRowDimensions($worksheet, $beforeRow, $numberOfRows);
|
||||
|
||||
// Update worksheet: page breaks
|
||||
$this->adjustPageBreaks($worksheet, $beforeCellAddress, $beforeColumn, $numberOfColumns, $beforeRow, $numberOfRows);
|
||||
$this->adjustPageBreaks($worksheet, $numberOfColumns, $numberOfRows);
|
||||
|
||||
// Update worksheet: comments
|
||||
$this->adjustComments($worksheet, $beforeCellAddress, $beforeColumn, $numberOfColumns, $beforeRow, $numberOfRows);
|
||||
$this->adjustComments($worksheet);
|
||||
|
||||
// Update worksheet: hyperlinks
|
||||
$this->adjustHyperlinks($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$this->adjustHyperlinks($worksheet, $numberOfColumns, $numberOfRows);
|
||||
|
||||
// Update worksheet: conditional formatting styles
|
||||
$this->adjustConditionalFormatting($worksheet, $numberOfColumns, $numberOfRows);
|
||||
|
||||
// Update worksheet: data validations
|
||||
$this->adjustDataValidations($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$this->adjustDataValidations($worksheet, $numberOfColumns, $numberOfRows);
|
||||
|
||||
// Update worksheet: merge cells
|
||||
$this->adjustMergeCells($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$this->adjustMergeCells($worksheet);
|
||||
|
||||
// Update worksheet: protected cells
|
||||
$this->adjustProtectedCells($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$this->adjustProtectedCells($worksheet, $numberOfColumns, $numberOfRows);
|
||||
|
||||
// Update worksheet: autofilter
|
||||
$autoFilter = $worksheet->getAutoFilter();
|
||||
$autoFilterRange = $autoFilter->getRange();
|
||||
if (!empty($autoFilterRange)) {
|
||||
if ($numberOfColumns != 0) {
|
||||
$autoFilterColumns = $autoFilter->getColumns();
|
||||
if (count($autoFilterColumns) > 0) {
|
||||
$column = '';
|
||||
$row = 0;
|
||||
sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
|
||||
$columnIndex = Coordinate::columnIndexFromString($column);
|
||||
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange);
|
||||
if ($columnIndex <= $rangeEnd[0]) {
|
||||
if ($numberOfColumns < 0) {
|
||||
// If we're actually deleting any columns that fall within the autofilter range,
|
||||
// then we delete any rules for those columns
|
||||
$deleteColumn = $columnIndex + $numberOfColumns - 1;
|
||||
$deleteCount = abs($numberOfColumns);
|
||||
for ($i = 1; $i <= $deleteCount; ++$i) {
|
||||
if (isset($autoFilterColumns[Coordinate::stringFromColumnIndex($deleteColumn + 1)])) {
|
||||
$autoFilter->clearColumn(Coordinate::stringFromColumnIndex($deleteColumn + 1));
|
||||
}
|
||||
++$deleteColumn;
|
||||
}
|
||||
}
|
||||
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
|
||||
|
||||
// Shuffle columns in autofilter range
|
||||
if ($numberOfColumns > 0) {
|
||||
$startColRef = $startCol;
|
||||
$endColRef = $rangeEnd[0];
|
||||
$toColRef = $rangeEnd[0] + $numberOfColumns;
|
||||
|
||||
do {
|
||||
$autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
|
||||
--$endColRef;
|
||||
--$toColRef;
|
||||
} while ($startColRef <= $endColRef);
|
||||
} else {
|
||||
// For delete, we shuffle from beginning to end to avoid overwriting
|
||||
$startColID = Coordinate::stringFromColumnIndex($startCol);
|
||||
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
|
||||
$endColID = Coordinate::stringFromColumnIndex($rangeEnd[0] + 1);
|
||||
do {
|
||||
$autoFilter->shiftColumn($startColID, $toColID);
|
||||
++$startColID;
|
||||
++$toColID;
|
||||
} while ($startColID != $endColID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$worksheet->setAutoFilter($this->updateCellReference($autoFilterRange, $beforeCellAddress, $numberOfColumns, $numberOfRows));
|
||||
}
|
||||
$this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns);
|
||||
|
||||
// Update worksheet: freeze pane
|
||||
if ($worksheet->getFreezePane()) {
|
||||
$splitCell = $worksheet->getFreezePane() ?? '';
|
||||
$topLeftCell = $worksheet->getTopLeftCell() ?? '';
|
||||
|
||||
$splitCell = $this->updateCellReference($splitCell, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$topLeftCell = $this->updateCellReference($topLeftCell, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$splitCell = $this->updateCellReference($splitCell);
|
||||
$topLeftCell = $this->updateCellReference($topLeftCell);
|
||||
|
||||
$worksheet->freezePane($splitCell, $topLeftCell);
|
||||
}
|
||||
|
||||
// Page setup
|
||||
if ($worksheet->getPageSetup()->isPrintAreaSet()) {
|
||||
$worksheet->getPageSetup()->setPrintArea($this->updateCellReference($worksheet->getPageSetup()->getPrintArea(), $beforeCellAddress, $numberOfColumns, $numberOfRows));
|
||||
$worksheet->getPageSetup()->setPrintArea(
|
||||
$this->updateCellReference($worksheet->getPageSetup()->getPrintArea())
|
||||
);
|
||||
}
|
||||
|
||||
// Update worksheet: drawings
|
||||
$aDrawings = $worksheet->getDrawingCollection();
|
||||
foreach ($aDrawings as $objDrawing) {
|
||||
$newReference = $this->updateCellReference($objDrawing->getCoordinates(), $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$newReference = $this->updateCellReference($objDrawing->getCoordinates());
|
||||
if ($objDrawing->getCoordinates() != $newReference) {
|
||||
$objDrawing->setCoordinates($newReference);
|
||||
}
|
||||
|
|
@ -617,7 +528,7 @@ class ReferenceHelper
|
|||
if (count($worksheet->getParent()->getDefinedNames()) > 0) {
|
||||
foreach ($worksheet->getParent()->getDefinedNames() as $definedName) {
|
||||
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
|
||||
$definedName->setValue($this->updateCellReference($definedName->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows));
|
||||
$definedName->setValue($this->updateCellReference($definedName->getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -637,8 +548,21 @@ class ReferenceHelper
|
|||
*
|
||||
* @return string Updated formula
|
||||
*/
|
||||
public function updateFormulaReferences($formula = '', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0, $worksheetName = '')
|
||||
{
|
||||
public function updateFormulaReferences(
|
||||
$formula = '',
|
||||
$beforeCellAddress = 'A1',
|
||||
$numberOfColumns = 0,
|
||||
$numberOfRows = 0,
|
||||
$worksheetName = '',
|
||||
bool $includeAbsoluteReferences = false
|
||||
) {
|
||||
if (
|
||||
$this->cellReferenceHelper === null ||
|
||||
$this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows)
|
||||
) {
|
||||
$this->cellReferenceHelper = new CellReferenceHelper($beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
}
|
||||
|
||||
// Update cell references in the formula
|
||||
$formulaBlocks = explode('"', $formula);
|
||||
$i = false;
|
||||
|
|
@ -648,13 +572,13 @@ class ReferenceHelper
|
|||
$adjustCount = 0;
|
||||
$newCellTokens = $cellTokens = [];
|
||||
// Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5)
|
||||
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
|
||||
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
|
||||
if ($matchCount > 0) {
|
||||
foreach ($matches as $match) {
|
||||
$fromString = ($match[2] > '') ? $match[2] . '!' : '';
|
||||
$fromString .= $match[3] . ':' . $match[4];
|
||||
$modified3 = substr($this->updateCellReference('$A' . $match[3], $beforeCellAddress, $numberOfColumns, $numberOfRows), 2);
|
||||
$modified4 = substr($this->updateCellReference('$A' . $match[4], $beforeCellAddress, $numberOfColumns, $numberOfRows), 2);
|
||||
$modified3 = substr($this->updateCellReference('$A' . $match[3], $includeAbsoluteReferences), 2);
|
||||
$modified4 = substr($this->updateCellReference('$A' . $match[4], $includeAbsoluteReferences), 2);
|
||||
|
||||
if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
|
||||
if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
|
||||
|
|
@ -673,13 +597,13 @@ class ReferenceHelper
|
|||
}
|
||||
}
|
||||
// Search for column ranges (e.g. 'Sheet1'!C:E or C:E) with or without $ absolutes (e.g. $C:E)
|
||||
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
|
||||
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
|
||||
if ($matchCount > 0) {
|
||||
foreach ($matches as $match) {
|
||||
$fromString = ($match[2] > '') ? $match[2] . '!' : '';
|
||||
$fromString .= $match[3] . ':' . $match[4];
|
||||
$modified3 = substr($this->updateCellReference($match[3] . '$1', $beforeCellAddress, $numberOfColumns, $numberOfRows), 0, -2);
|
||||
$modified4 = substr($this->updateCellReference($match[4] . '$1', $beforeCellAddress, $numberOfColumns, $numberOfRows), 0, -2);
|
||||
$modified3 = substr($this->updateCellReference($match[3] . '$1', $includeAbsoluteReferences), 0, -2);
|
||||
$modified4 = substr($this->updateCellReference($match[4] . '$1', $includeAbsoluteReferences), 0, -2);
|
||||
|
||||
if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
|
||||
if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
|
||||
|
|
@ -698,13 +622,13 @@ class ReferenceHelper
|
|||
}
|
||||
}
|
||||
// Search for cell ranges (e.g. 'Sheet1'!A3:C5 or A3:C5) with or without $ absolutes (e.g. $A1:C$5)
|
||||
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
|
||||
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
|
||||
if ($matchCount > 0) {
|
||||
foreach ($matches as $match) {
|
||||
$fromString = ($match[2] > '') ? $match[2] . '!' : '';
|
||||
$fromString .= $match[3] . ':' . $match[4];
|
||||
$modified3 = $this->updateCellReference($match[3], $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$modified4 = $this->updateCellReference($match[4], $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences);
|
||||
$modified4 = $this->updateCellReference($match[4], $includeAbsoluteReferences);
|
||||
|
||||
if ($match[3] . $match[4] !== $modified3 . $modified4) {
|
||||
if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
|
||||
|
|
@ -724,14 +648,14 @@ class ReferenceHelper
|
|||
}
|
||||
}
|
||||
// Search for cell references (e.g. 'Sheet1'!A3 or C5) with or without $ absolutes (e.g. $A1 or C$5)
|
||||
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
|
||||
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
|
||||
|
||||
if ($matchCount > 0) {
|
||||
foreach ($matches as $match) {
|
||||
$fromString = ($match[2] > '') ? $match[2] . '!' : '';
|
||||
$fromString .= $match[3];
|
||||
|
||||
$modified3 = $this->updateCellReference($match[3], $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences);
|
||||
if ($match[3] !== $modified3) {
|
||||
if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
|
||||
$toString = ($match[2] > '') ? $match[2] . '!' : '';
|
||||
|
|
@ -908,13 +832,10 @@ class ReferenceHelper
|
|||
* Update cell reference.
|
||||
*
|
||||
* @param string $cellReference Cell address or range of addresses
|
||||
* @param string $beforeCellAddress Insert before this one
|
||||
* @param int $numberOfColumns Number of columns to increment
|
||||
* @param int $numberOfRows Number of rows to increment
|
||||
*
|
||||
* @return string Updated cell range
|
||||
*/
|
||||
public function updateCellReference($cellReference = 'A1', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0)
|
||||
private function updateCellReference($cellReference = 'A1', bool $includeAbsoluteReferences = false)
|
||||
{
|
||||
// Is it in another worksheet? Will not have to update anything.
|
||||
if (strpos($cellReference, '!') !== false) {
|
||||
|
|
@ -922,10 +843,10 @@ class ReferenceHelper
|
|||
// Is it a range or a single cell?
|
||||
} elseif (!Coordinate::coordinateIsRange($cellReference)) {
|
||||
// Single cell
|
||||
return $this->updateSingleCellReference($cellReference, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
return $this->cellReferenceHelper->updateCellReference($cellReference, $includeAbsoluteReferences);
|
||||
} elseif (Coordinate::coordinateIsRange($cellReference)) {
|
||||
// Range
|
||||
return $this->updateCellRange($cellReference, $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
return $this->updateCellRange($cellReference, $includeAbsoluteReferences);
|
||||
}
|
||||
|
||||
// Return original
|
||||
|
|
@ -948,7 +869,7 @@ class ReferenceHelper
|
|||
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
|
||||
foreach ($sheet->getCoordinates(false) as $coordinate) {
|
||||
$cell = $sheet->getCell($coordinate);
|
||||
if (($cell !== null) && ($cell->getDataType() == DataType::TYPE_FORMULA)) {
|
||||
if (($cell !== null) && ($cell->getDataType() === DataType::TYPE_FORMULA)) {
|
||||
$formula = $cell->getValue();
|
||||
if (strpos($formula, $oldName) !== false) {
|
||||
$formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula);
|
||||
|
|
@ -964,13 +885,10 @@ class ReferenceHelper
|
|||
* Update cell range.
|
||||
*
|
||||
* @param string $cellRange Cell range (e.g. 'B2:D4', 'B:C' or '2:3')
|
||||
* @param string $beforeCellAddress Insert before this one
|
||||
* @param int $numberOfColumns Number of columns to increment
|
||||
* @param int $numberOfRows Number of rows to increment
|
||||
*
|
||||
* @return string Updated cell range
|
||||
*/
|
||||
private function updateCellRange($cellRange = 'A1:A1', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0)
|
||||
private function updateCellRange(string $cellRange = 'A1:A1', bool $includeAbsoluteReferences = false): string
|
||||
{
|
||||
if (!Coordinate::coordinateIsRange($cellRange)) {
|
||||
throw new Exception('Only cell ranges may be passed to this method.');
|
||||
|
|
@ -983,13 +901,15 @@ class ReferenceHelper
|
|||
$jc = count($range[$i]);
|
||||
for ($j = 0; $j < $jc; ++$j) {
|
||||
if (ctype_alpha($range[$i][$j])) {
|
||||
$r = Coordinate::coordinateFromString($this->updateSingleCellReference($range[$i][$j] . '1', $beforeCellAddress, $numberOfColumns, $numberOfRows));
|
||||
$range[$i][$j] = $r[0];
|
||||
$range[$i][$j] = Coordinate::coordinateFromString(
|
||||
$this->cellReferenceHelper->updateCellReference($range[$i][$j] . '1', $includeAbsoluteReferences)
|
||||
)[0];
|
||||
} elseif (ctype_digit($range[$i][$j])) {
|
||||
$r = Coordinate::coordinateFromString($this->updateSingleCellReference('A' . $range[$i][$j], $beforeCellAddress, $numberOfColumns, $numberOfRows));
|
||||
$range[$i][$j] = $r[1];
|
||||
$range[$i][$j] = Coordinate::coordinateFromString(
|
||||
$this->cellReferenceHelper->updateCellReference('A' . $range[$i][$j], $includeAbsoluteReferences)
|
||||
)[1];
|
||||
} else {
|
||||
$range[$i][$j] = $this->updateSingleCellReference($range[$i][$j], $beforeCellAddress, $numberOfColumns, $numberOfRows);
|
||||
$range[$i][$j] = $this->cellReferenceHelper->updateCellReference($range[$i][$j], $includeAbsoluteReferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -998,44 +918,142 @@ class ReferenceHelper
|
|||
return Coordinate::buildRange($range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update single cell reference.
|
||||
*
|
||||
* @param string $cellReference Single cell reference
|
||||
* @param string $beforeCellAddress Insert before this one
|
||||
* @param int $numberOfColumns Number of columns to increment
|
||||
* @param int $numberOfRows Number of rows to increment
|
||||
*
|
||||
* @return string Updated cell reference
|
||||
*/
|
||||
private function updateSingleCellReference($cellReference = 'A1', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0)
|
||||
private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void
|
||||
{
|
||||
if (Coordinate::coordinateIsRange($cellReference)) {
|
||||
throw new Exception('Only single cell references may be passed to this method.');
|
||||
for ($i = 1; $i <= $highestRow - 1; ++$i) {
|
||||
for ($j = $beforeColumn - 1 + $numberOfColumns; $j <= $beforeColumn - 2; ++$j) {
|
||||
$coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i;
|
||||
$worksheet->removeConditionalStyles($coordinate);
|
||||
if ($worksheet->cellExists($coordinate)) {
|
||||
$worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
|
||||
$worksheet->getCell($coordinate)->setXfIndex(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get coordinate of $beforeCellAddress
|
||||
[$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($beforeCellAddress);
|
||||
private function clearRowStrips(string $highestColumn, int $beforeColumn, int $beforeRow, int $numberOfRows, Worksheet $worksheet): void
|
||||
{
|
||||
$lastColumnIndex = Coordinate::columnIndexFromString($highestColumn) - 1;
|
||||
|
||||
// Get coordinate of $cellReference
|
||||
[$newColumn, $newRow] = Coordinate::coordinateFromString($cellReference);
|
||||
|
||||
// Verify which parts should be updated
|
||||
$updateColumn = (($newColumn[0] != '$') && ($beforeColumn[0] != '$') && (Coordinate::columnIndexFromString($newColumn) >= Coordinate::columnIndexFromString($beforeColumn)));
|
||||
$updateRow = (($newRow[0] != '$') && ($beforeRow[0] != '$') && $newRow >= $beforeRow);
|
||||
|
||||
// Create new column reference
|
||||
if ($updateColumn) {
|
||||
$newColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($newColumn) + $numberOfColumns);
|
||||
for ($i = $beforeColumn - 1; $i <= $lastColumnIndex; ++$i) {
|
||||
for ($j = $beforeRow + $numberOfRows; $j <= $beforeRow - 1; ++$j) {
|
||||
$coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j;
|
||||
$worksheet->removeConditionalStyles($coordinate);
|
||||
if ($worksheet->cellExists($coordinate)) {
|
||||
$worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
|
||||
$worksheet->getCell($coordinate)->setXfIndex(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create new row reference
|
||||
if ($updateRow) {
|
||||
$newRow = (int) $newRow + $numberOfRows;
|
||||
private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void
|
||||
{
|
||||
$autoFilter = $worksheet->getAutoFilter();
|
||||
$autoFilterRange = $autoFilter->getRange();
|
||||
if (!empty($autoFilterRange)) {
|
||||
if ($numberOfColumns !== 0) {
|
||||
$autoFilterColumns = $autoFilter->getColumns();
|
||||
if (count($autoFilterColumns) > 0) {
|
||||
$column = '';
|
||||
$row = 0;
|
||||
sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
|
||||
$columnIndex = Coordinate::columnIndexFromString($column);
|
||||
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange);
|
||||
if ($columnIndex <= $rangeEnd[0]) {
|
||||
if ($numberOfColumns < 0) {
|
||||
$this->adjustAutoFilterDeleteRules($columnIndex, $numberOfColumns, $autoFilterColumns, $autoFilter);
|
||||
}
|
||||
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
|
||||
|
||||
// Shuffle columns in autofilter range
|
||||
if ($numberOfColumns > 0) {
|
||||
$this->adjustAutoFilterInsert($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter);
|
||||
} else {
|
||||
$this->adjustAutoFilterDelete($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return new reference
|
||||
return $newColumn . $newRow;
|
||||
$worksheet->setAutoFilter(
|
||||
$this->updateCellReference($autoFilterRange)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function adjustAutoFilterDeleteRules(int $columnIndex, int $numberOfColumns, array $autoFilterColumns, AutoFilter $autoFilter): void
|
||||
{
|
||||
// If we're actually deleting any columns that fall within the autofilter range,
|
||||
// then we delete any rules for those columns
|
||||
$deleteColumn = $columnIndex + $numberOfColumns - 1;
|
||||
$deleteCount = abs($numberOfColumns);
|
||||
|
||||
for ($i = 1; $i <= $deleteCount; ++$i) {
|
||||
$columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1);
|
||||
if (isset($autoFilterColumns[$columnName])) {
|
||||
$autoFilter->clearColumn($columnName);
|
||||
}
|
||||
++$deleteColumn;
|
||||
}
|
||||
}
|
||||
|
||||
private function adjustAutoFilterInsert(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void
|
||||
{
|
||||
$startColRef = $startCol;
|
||||
$endColRef = $rangeEnd;
|
||||
$toColRef = $rangeEnd + $numberOfColumns;
|
||||
|
||||
do {
|
||||
$autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
|
||||
--$endColRef;
|
||||
--$toColRef;
|
||||
} while ($startColRef <= $endColRef);
|
||||
}
|
||||
|
||||
private function adjustAutoFilterDelete(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void
|
||||
{
|
||||
// For delete, we shuffle from beginning to end to avoid overwriting
|
||||
$startColID = Coordinate::stringFromColumnIndex($startCol);
|
||||
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
|
||||
$endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1);
|
||||
|
||||
do {
|
||||
$autoFilter->shiftColumn($startColID, $toColID);
|
||||
++$startColID;
|
||||
++$toColID;
|
||||
} while ($startColID !== $endColID);
|
||||
}
|
||||
|
||||
private function duplicateStylesByColumn(Worksheet $worksheet, int $beforeColumn, int $beforeRow, int $highestRow, int $numberOfColumns): void
|
||||
{
|
||||
$beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1);
|
||||
for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
|
||||
// Style
|
||||
$coordinate = $beforeColumnName . $i;
|
||||
if ($worksheet->cellExists($coordinate)) {
|
||||
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
|
||||
for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) {
|
||||
$worksheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function duplicateStylesByRow(Worksheet $worksheet, int $beforeColumn, int $beforeRow, string $highestColumn, int $numberOfRows): void
|
||||
{
|
||||
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
|
||||
for ($i = $beforeColumn; $i <= $highestColumnIndex; ++$i) {
|
||||
// Style
|
||||
$coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1);
|
||||
if ($worksheet->cellExists($coordinate)) {
|
||||
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
|
||||
for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) {
|
||||
$worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -54,7 +54,9 @@ class XMLWriter extends \XMLWriter
|
|||
public function __destruct()
|
||||
{
|
||||
// Unlink temporary files
|
||||
// There is nothing reasonable to do if unlink fails.
|
||||
if ($this->tempFileName != '') {
|
||||
/** @scrutinizer ignore-unhandled */
|
||||
@unlink($this->tempFileName);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1602,4 +1602,14 @@ class Spreadsheet
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Silliness to mollify Scrutinizer.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function getSharedComponent(): Style
|
||||
{
|
||||
return new Style();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ abstract class WizardAbstract
|
|||
return "{$worksheet}{$column}{$row}";
|
||||
}
|
||||
|
||||
protected static function reverseAdjustCellRef(string $condition, string $cellRange): string
|
||||
public static function reverseAdjustCellRef(string $condition, string $cellRange): string
|
||||
{
|
||||
$conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($cellRange)));
|
||||
[$referenceCell] = $conditionalRange[0];
|
||||
|
|
|
|||
|
|
@ -8,6 +8,22 @@ use PhpOffice\PhpSpreadsheet\IComparable;
|
|||
|
||||
class BaseDrawing implements IComparable
|
||||
{
|
||||
const EDIT_AS_ABSOLUTE = 'absolute';
|
||||
const EDIT_AS_ONECELL = 'onecell';
|
||||
const EDIT_AS_TWOCELL = 'twocell';
|
||||
private const VALID_EDIT_AS = [
|
||||
self::EDIT_AS_ABSOLUTE,
|
||||
self::EDIT_AS_ONECELL,
|
||||
self::EDIT_AS_TWOCELL,
|
||||
];
|
||||
|
||||
/**
|
||||
* The editAs attribute, used only with two cell anchor.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $editAs = '';
|
||||
|
||||
/**
|
||||
* Image counter.
|
||||
*
|
||||
|
|
@ -27,14 +43,14 @@ class BaseDrawing implements IComparable
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* Description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* Worksheet.
|
||||
|
|
@ -48,70 +64,84 @@ class BaseDrawing implements IComparable
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $coordinates;
|
||||
protected $coordinates = 'A1';
|
||||
|
||||
/**
|
||||
* Offset X.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $offsetX;
|
||||
protected $offsetX = 0;
|
||||
|
||||
/**
|
||||
* Offset Y.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $offsetY;
|
||||
protected $offsetY = 0;
|
||||
|
||||
/**
|
||||
* Coordinates2.
|
||||
*
|
||||
* @var null|string
|
||||
* @var string
|
||||
*/
|
||||
protected $coordinates2;
|
||||
protected $coordinates2 = '';
|
||||
|
||||
/**
|
||||
* Offset X2.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $offsetX2;
|
||||
protected $offsetX2 = 0;
|
||||
|
||||
/**
|
||||
* Offset Y2.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $offsetY2;
|
||||
protected $offsetY2 = 0;
|
||||
|
||||
/**
|
||||
* Width.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $width;
|
||||
protected $width = 0;
|
||||
|
||||
/**
|
||||
* Height.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $height;
|
||||
protected $height = 0;
|
||||
|
||||
/**
|
||||
* Pixel width of image. See $width for the size the Drawing will be in the sheet.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $imageWidth = 0;
|
||||
|
||||
/**
|
||||
* Pixel width of image. See $height for the size the Drawing will be in the sheet.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $imageHeight = 0;
|
||||
|
||||
/**
|
||||
* Proportional resize.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $resizeProportional;
|
||||
protected $resizeProportional = true;
|
||||
|
||||
/**
|
||||
* Rotation.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $rotation;
|
||||
protected $rotation = 0;
|
||||
|
||||
/**
|
||||
* Shadow.
|
||||
|
|
@ -132,7 +162,7 @@ class BaseDrawing implements IComparable
|
|||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $type;
|
||||
protected $type = IMAGETYPE_UNKNOWN;
|
||||
|
||||
/**
|
||||
* Create a new BaseDrawing.
|
||||
|
|
@ -140,91 +170,43 @@ class BaseDrawing implements IComparable
|
|||
public function __construct()
|
||||
{
|
||||
// Initialise values
|
||||
$this->name = '';
|
||||
$this->description = '';
|
||||
$this->worksheet = null;
|
||||
$this->coordinates = 'A1';
|
||||
$this->offsetX = 0;
|
||||
$this->offsetY = 0;
|
||||
$this->coordinates2 = null;
|
||||
$this->offsetX2 = 0;
|
||||
$this->offsetY2 = 0;
|
||||
$this->width = 0;
|
||||
$this->height = 0;
|
||||
$this->resizeProportional = true;
|
||||
$this->rotation = 0;
|
||||
$this->shadow = new Drawing\Shadow();
|
||||
$this->type = IMAGETYPE_UNKNOWN;
|
||||
$this->setShadow();
|
||||
|
||||
// Set image index
|
||||
++self::$imageCounter;
|
||||
$this->imageIndex = self::$imageCounter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image index.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getImageIndex()
|
||||
public function getImageIndex(): int
|
||||
{
|
||||
return $this->imageIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Name.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name)
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Description.
|
||||
*
|
||||
* @param string $description
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDescription($description)
|
||||
public function setDescription(string $description): self
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Worksheet.
|
||||
*
|
||||
* @return null|Worksheet
|
||||
*/
|
||||
public function getWorksheet()
|
||||
public function getWorksheet(): ?Worksheet
|
||||
{
|
||||
return $this->worksheet;
|
||||
}
|
||||
|
|
@ -233,16 +215,16 @@ class BaseDrawing implements IComparable
|
|||
* Set Worksheet.
|
||||
*
|
||||
* @param bool $overrideOld If a Worksheet has already been assigned, overwrite it and remove image from old Worksheet?
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setWorksheet(?Worksheet $worksheet = null, $overrideOld = false)
|
||||
public function setWorksheet(?Worksheet $worksheet = null, bool $overrideOld = false): self
|
||||
{
|
||||
if ($this->worksheet === null) {
|
||||
// Add drawing to \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
|
||||
if ($worksheet !== null) {
|
||||
$this->worksheet = $worksheet;
|
||||
$this->worksheet->getCell($this->coordinates);
|
||||
$this->worksheet->getDrawingCollection()->append($this);
|
||||
}
|
||||
} else {
|
||||
if ($overrideOld) {
|
||||
// Remove drawing from old \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
|
||||
|
|
@ -267,168 +249,84 @@ class BaseDrawing implements IComparable
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Coordinates.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCoordinates()
|
||||
public function getCoordinates(): string
|
||||
{
|
||||
return $this->coordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Coordinates.
|
||||
*
|
||||
* @param string $coordinates eg: 'A1'
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCoordinates($coordinates)
|
||||
public function setCoordinates(string $coordinates): self
|
||||
{
|
||||
$this->coordinates = $coordinates;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OffsetX.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getOffsetX()
|
||||
public function getOffsetX(): int
|
||||
{
|
||||
return $this->offsetX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set OffsetX.
|
||||
*
|
||||
* @param int $offsetX
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOffsetX($offsetX)
|
||||
public function setOffsetX(int $offsetX): self
|
||||
{
|
||||
$this->offsetX = $offsetX;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OffsetY.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getOffsetY()
|
||||
public function getOffsetY(): int
|
||||
{
|
||||
return $this->offsetY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Coordinates2.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getCoordinates2()
|
||||
{
|
||||
return $this->coordinates2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Coordinates2.
|
||||
*
|
||||
* @param null|string $coordinates2 eg: 'A1'
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCoordinates2($coordinates2)
|
||||
{
|
||||
$this->coordinates2 = $coordinates2;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OffsetX2.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getOffsetX2()
|
||||
{
|
||||
return $this->offsetX2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set OffsetX2.
|
||||
*
|
||||
* @param int $offsetX2
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOffsetX2($offsetX2)
|
||||
{
|
||||
$this->offsetX2 = $offsetX2;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OffsetY2.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getOffsetY2()
|
||||
{
|
||||
return $this->offsetY2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set OffsetY2.
|
||||
*
|
||||
* @param int $offsetY2
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOffsetY2($offsetY2)
|
||||
{
|
||||
$this->offsetY2 = $offsetY2;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set OffsetY.
|
||||
*
|
||||
* @param int $offsetY
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOffsetY($offsetY)
|
||||
public function setOffsetY(int $offsetY): self
|
||||
{
|
||||
$this->offsetY = $offsetY;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Width.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth()
|
||||
public function getCoordinates2(): string
|
||||
{
|
||||
return $this->coordinates2;
|
||||
}
|
||||
|
||||
public function setCoordinates2(string $coordinates2): self
|
||||
{
|
||||
$this->coordinates2 = $coordinates2;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOffsetX2(): int
|
||||
{
|
||||
return $this->offsetX2;
|
||||
}
|
||||
|
||||
public function setOffsetX2(int $offsetX2): self
|
||||
{
|
||||
$this->offsetX2 = $offsetX2;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOffsetY2(): int
|
||||
{
|
||||
return $this->offsetY2;
|
||||
}
|
||||
|
||||
public function setOffsetY2(int $offsetY2): self
|
||||
{
|
||||
$this->offsetY2 = $offsetY2;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Width.
|
||||
*
|
||||
* @param int $width
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setWidth($width)
|
||||
public function setWidth(int $width): self
|
||||
{
|
||||
// Resize proportional?
|
||||
if ($this->resizeProportional && $width != 0) {
|
||||
|
|
@ -442,24 +340,12 @@ class BaseDrawing implements IComparable
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Height.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight()
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Height.
|
||||
*
|
||||
* @param int $height
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeight($height)
|
||||
public function setHeight(int $height): self
|
||||
{
|
||||
// Resize proportional?
|
||||
if ($this->resizeProportional && $height != 0) {
|
||||
|
|
@ -482,14 +368,9 @@ class BaseDrawing implements IComparable
|
|||
* $objDrawing->setWidthAndHeight(160,120);
|
||||
* </code>
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @author Vincent@luo MSN:kele_100@hotmail.com
|
||||
*/
|
||||
public function setWidthAndHeight($width, $height)
|
||||
public function setWidthAndHeight(int $width, int $height): self
|
||||
{
|
||||
$xratio = $width / ($this->width != 0 ? $this->width : 1);
|
||||
$yratio = $height / ($this->height != 0 ? $this->height : 1);
|
||||
|
|
@ -509,72 +390,38 @@ class BaseDrawing implements IComparable
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ResizeProportional.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getResizeProportional()
|
||||
public function getResizeProportional(): bool
|
||||
{
|
||||
return $this->resizeProportional;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ResizeProportional.
|
||||
*
|
||||
* @param bool $resizeProportional
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setResizeProportional($resizeProportional)
|
||||
public function setResizeProportional(bool $resizeProportional): self
|
||||
{
|
||||
$this->resizeProportional = $resizeProportional;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Rotation.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRotation()
|
||||
public function getRotation(): int
|
||||
{
|
||||
return $this->rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Rotation.
|
||||
*
|
||||
* @param int $rotation
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setRotation($rotation)
|
||||
public function setRotation(int $rotation): self
|
||||
{
|
||||
$this->rotation = $rotation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Shadow.
|
||||
*
|
||||
* @return Drawing\Shadow
|
||||
*/
|
||||
public function getShadow()
|
||||
public function getShadow(): Drawing\Shadow
|
||||
{
|
||||
return $this->shadow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Shadow.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setShadow(?Drawing\Shadow $shadow = null)
|
||||
public function setShadow(?Drawing\Shadow $shadow = null): self
|
||||
{
|
||||
$this->shadow = $shadow;
|
||||
$this->shadow = $shadow ?? new Drawing\Shadow();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
@ -589,7 +436,7 @@ class BaseDrawing implements IComparable
|
|||
return md5(
|
||||
$this->name .
|
||||
$this->description .
|
||||
$this->worksheet->getHashCode() .
|
||||
(($this->worksheet === null) ? '' : $this->worksheet->getHashCode()) .
|
||||
$this->coordinates .
|
||||
$this->offsetX .
|
||||
$this->offsetY .
|
||||
|
|
@ -626,10 +473,7 @@ class BaseDrawing implements IComparable
|
|||
$this->hyperlink = $hyperlink;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|Hyperlink
|
||||
*/
|
||||
public function getHyperlink()
|
||||
public function getHyperlink(): ?Hyperlink
|
||||
{
|
||||
return $this->hyperlink;
|
||||
}
|
||||
|
|
@ -639,15 +483,19 @@ class BaseDrawing implements IComparable
|
|||
*/
|
||||
protected function setSizesAndType(string $path): void
|
||||
{
|
||||
if ($this->width == 0 && $this->height == 0 && $this->type == IMAGETYPE_UNKNOWN) {
|
||||
if ($this->imageWidth === 0 && $this->imageHeight === 0 && $this->type === IMAGETYPE_UNKNOWN) {
|
||||
$imageData = getimagesize($path);
|
||||
|
||||
if (is_array($imageData)) {
|
||||
$this->width = $imageData[0];
|
||||
$this->height = $imageData[1];
|
||||
$this->imageWidth = $imageData[0];
|
||||
$this->imageHeight = $imageData[1];
|
||||
$this->type = $imageData[2];
|
||||
}
|
||||
}
|
||||
if ($this->width === 0 && $this->height === 0) {
|
||||
$this->width = $this->imageWidth;
|
||||
$this->height = $this->imageHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -657,4 +505,31 @@ class BaseDrawing implements IComparable
|
|||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function getImageWidth(): int
|
||||
{
|
||||
return $this->imageWidth;
|
||||
}
|
||||
|
||||
public function getImageHeight(): int
|
||||
{
|
||||
return $this->imageHeight;
|
||||
}
|
||||
|
||||
public function getEditAs(): string
|
||||
{
|
||||
return $this->editAs;
|
||||
}
|
||||
|
||||
public function setEditAs(string $editAs): self
|
||||
{
|
||||
$this->editAs = $editAs;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function validEditAs(): bool
|
||||
{
|
||||
return in_array($this->editAs, self::VALID_EDIT_AS);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ class MemoryDrawing extends BaseDrawing
|
|||
*/
|
||||
private $uniqueName;
|
||||
|
||||
/** @var null|resource */
|
||||
private $alwaysNull;
|
||||
|
||||
/**
|
||||
* Create a new MemoryDrawing.
|
||||
*/
|
||||
|
|
@ -56,6 +59,7 @@ class MemoryDrawing extends BaseDrawing
|
|||
$this->renderingFunction = self::RENDERING_DEFAULT;
|
||||
$this->mimeType = self::MIMETYPE_DEFAULT;
|
||||
$this->uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999));
|
||||
$this->alwaysNull = null;
|
||||
|
||||
// Initialize parent
|
||||
parent::__construct();
|
||||
|
|
@ -64,8 +68,9 @@ class MemoryDrawing extends BaseDrawing
|
|||
public function __destruct()
|
||||
{
|
||||
if ($this->imageResource) {
|
||||
imagedestroy($this->imageResource);
|
||||
$this->imageResource = null;
|
||||
$rslt = @imagedestroy($this->imageResource);
|
||||
// "Fix" for Scrutinizer
|
||||
$this->imageResource = $rslt ? null : $this->alwaysNull;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,8 +35,7 @@ class Row
|
|||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
$this->worksheet = null;
|
||||
$this->worksheet = null; // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2279,16 +2279,18 @@ class Worksheet implements IComparable
|
|||
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
|
||||
$pColumnIndex = Coordinate::columnIndexFromString($column);
|
||||
|
||||
if ($pColumnIndex > $highestColumnIndex) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$holdColumnDimensions = $this->removeColumnDimensions($pColumnIndex, $numberOfColumns);
|
||||
|
||||
$column = Coordinate::stringFromColumnIndex($pColumnIndex + $numberOfColumns);
|
||||
$objReferenceHelper = ReferenceHelper::getInstance();
|
||||
$objReferenceHelper->insertNewBefore($column . '1', -$numberOfColumns, 0, $this);
|
||||
|
||||
$this->columnDimensions = $holdColumnDimensions;
|
||||
|
||||
if ($pColumnIndex > $highestColumnIndex) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$maxPossibleColumnsToBeRemoved = $highestColumnIndex - $pColumnIndex + 1;
|
||||
|
||||
for ($c = 0, $n = min($maxPossibleColumnsToBeRemoved, $numberOfColumns); $c < $n; ++$c) {
|
||||
|
|
@ -2296,8 +2298,6 @@ class Worksheet implements IComparable
|
|||
$highestColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1);
|
||||
}
|
||||
|
||||
$this->columnDimensions = $holdColumnDimensions;
|
||||
|
||||
$this->garbageCollect();
|
||||
|
||||
return $this;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
|
||||
|
||||
class ConditionalHelper
|
||||
{
|
||||
/**
|
||||
* Formula parser.
|
||||
*
|
||||
* @var Parser
|
||||
*/
|
||||
protected $parser;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $condition;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $cellRange;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
protected $tokens;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $size;
|
||||
|
||||
public function __construct(Parser $parser)
|
||||
{
|
||||
$this->parser = $parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $condition
|
||||
*/
|
||||
public function processCondition($condition, string $cellRange): void
|
||||
{
|
||||
$this->condition = $condition;
|
||||
$this->cellRange = $cellRange;
|
||||
|
||||
if (is_int($condition) || is_float($condition)) {
|
||||
$this->size = ($condition <= 65535 ? 3 : 0x0000);
|
||||
$this->tokens = pack('Cv', 0x1E, $condition);
|
||||
} else {
|
||||
try {
|
||||
$formula = Wizard\WizardAbstract::reverseAdjustCellRef((string) $condition, $cellRange);
|
||||
$this->parser->parse($formula);
|
||||
$this->tokens = $this->parser->toReversePolish();
|
||||
$this->size = strlen($this->tokens ?? '');
|
||||
} catch (PhpSpreadsheetException $e) {
|
||||
// In the event of a parser error with a formula value, we set the expression to ptgInt + 0
|
||||
$this->tokens = pack('Cv', 0x1E, 0);
|
||||
$this->size = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function tokens(): ?string
|
||||
{
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
public function size(): int
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
}
|
||||
|
|
@ -539,37 +539,44 @@ class Worksheet extends BIFFwriter
|
|||
$this->writeSheetProtection();
|
||||
$this->writeRangeProtection();
|
||||
|
||||
$arrConditionalStyles = $phpSheet->getConditionalStylesCollection();
|
||||
// Write Conditional Formatting Rules and Styles
|
||||
$this->writeConditionalFormatting();
|
||||
|
||||
$this->storeEof();
|
||||
}
|
||||
|
||||
private function writeConditionalFormatting(): void
|
||||
{
|
||||
$conditionalFormulaHelper = new ConditionalHelper($this->parser);
|
||||
|
||||
$arrConditionalStyles = $this->phpSheet->getConditionalStylesCollection();
|
||||
if (!empty($arrConditionalStyles)) {
|
||||
$arrConditional = [];
|
||||
|
||||
$cfHeaderWritten = false;
|
||||
// Write ConditionalFormattingTable records
|
||||
foreach ($arrConditionalStyles as $cellCoordinate => $conditionalStyles) {
|
||||
$cfHeaderWritten = false;
|
||||
foreach ($conditionalStyles as $conditional) {
|
||||
/** @var Conditional $conditional */
|
||||
if (
|
||||
$conditional->getConditionType() == Conditional::CONDITION_EXPRESSION ||
|
||||
$conditional->getConditionType() == Conditional::CONDITION_CELLIS
|
||||
$conditional->getConditionType() === Conditional::CONDITION_EXPRESSION ||
|
||||
$conditional->getConditionType() === Conditional::CONDITION_CELLIS
|
||||
) {
|
||||
// Write CFHEADER record (only if there are Conditional Styles that we are able to write)
|
||||
if ($cfHeaderWritten === false) {
|
||||
$this->writeCFHeader();
|
||||
$cfHeaderWritten = true;
|
||||
$cfHeaderWritten = $this->writeCFHeader($cellCoordinate, $conditionalStyles);
|
||||
}
|
||||
if (!isset($arrConditional[$conditional->getHashCode()])) {
|
||||
if ($cfHeaderWritten === true && !isset($arrConditional[$conditional->getHashCode()])) {
|
||||
// This hash code has been handled
|
||||
$arrConditional[$conditional->getHashCode()] = true;
|
||||
|
||||
// Write CFRULE record
|
||||
$this->writeCFRule($conditional);
|
||||
$this->writeCFRule($conditionalFormulaHelper, $conditional, $cellCoordinate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->storeEof();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2774,8 +2781,11 @@ class Worksheet extends BIFFwriter
|
|||
/**
|
||||
* Write CFRule Record.
|
||||
*/
|
||||
private function writeCFRule(Conditional $conditional): void
|
||||
{
|
||||
private function writeCFRule(
|
||||
ConditionalHelper $conditionalFormulaHelper,
|
||||
Conditional $conditional,
|
||||
string $cellRange
|
||||
): void {
|
||||
$record = 0x01B1; // Record identifier
|
||||
$type = null; // Type of the CF
|
||||
$operatorType = null; // Comparison operator
|
||||
|
|
@ -2827,21 +2837,23 @@ class Worksheet extends BIFFwriter
|
|||
// $szValue2 : size of the formula data for second value or formula
|
||||
$arrConditions = $conditional->getConditions();
|
||||
$numConditions = count($arrConditions);
|
||||
if ($numConditions == 1) {
|
||||
$szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000);
|
||||
$szValue2 = 0x0000;
|
||||
$operand1 = pack('Cv', 0x1E, $arrConditions[0]);
|
||||
$operand2 = null;
|
||||
} elseif ($numConditions == 2 && ($conditional->getOperatorType() == Conditional::OPERATOR_BETWEEN)) {
|
||||
$szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000);
|
||||
$szValue2 = ($arrConditions[1] <= 65535 ? 3 : 0x0000);
|
||||
$operand1 = pack('Cv', 0x1E, $arrConditions[0]);
|
||||
$operand2 = pack('Cv', 0x1E, $arrConditions[1]);
|
||||
} else {
|
||||
|
||||
$szValue1 = 0x0000;
|
||||
$szValue2 = 0x0000;
|
||||
$operand1 = null;
|
||||
$operand2 = null;
|
||||
|
||||
if ($numConditions === 1) {
|
||||
$conditionalFormulaHelper->processCondition($arrConditions[0], $cellRange);
|
||||
$szValue1 = $conditionalFormulaHelper->size();
|
||||
$operand1 = $conditionalFormulaHelper->tokens();
|
||||
} elseif ($numConditions === 2 && ($conditional->getOperatorType() === Conditional::OPERATOR_BETWEEN)) {
|
||||
$conditionalFormulaHelper->processCondition($arrConditions[0], $cellRange);
|
||||
$szValue1 = $conditionalFormulaHelper->size();
|
||||
$operand1 = $conditionalFormulaHelper->tokens();
|
||||
$conditionalFormulaHelper->processCondition($arrConditions[1], $cellRange);
|
||||
$szValue2 = $conditionalFormulaHelper->size();
|
||||
$operand2 = $conditionalFormulaHelper->tokens();
|
||||
}
|
||||
|
||||
// $flags : Option flags
|
||||
|
|
@ -3127,8 +3139,10 @@ class Worksheet extends BIFFwriter
|
|||
|
||||
/**
|
||||
* Write CFHeader record.
|
||||
*
|
||||
* @param Conditional[] $conditionalStyles
|
||||
*/
|
||||
private function writeCFHeader(): void
|
||||
private function writeCFHeader(string $cellCoordinate, array $conditionalStyles): bool
|
||||
{
|
||||
$record = 0x01B0; // Record identifier
|
||||
$length = 0x0016; // Bytes to follow
|
||||
|
|
@ -3137,13 +3151,9 @@ class Worksheet extends BIFFwriter
|
|||
$numColumnMax = null;
|
||||
$numRowMin = null;
|
||||
$numRowMax = null;
|
||||
|
||||
$arrConditional = [];
|
||||
foreach ($this->phpSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) {
|
||||
foreach ($conditionalStyles as $conditional) {
|
||||
if (
|
||||
$conditional->getConditionType() == Conditional::CONDITION_EXPRESSION ||
|
||||
$conditional->getConditionType() == Conditional::CONDITION_CELLIS
|
||||
) {
|
||||
if (!in_array($conditional->getHashCode(), $arrConditional)) {
|
||||
$arrConditional[] = $conditional->getHashCode();
|
||||
}
|
||||
|
|
@ -3162,8 +3172,11 @@ class Worksheet extends BIFFwriter
|
|||
$numRowMax = (int) $rangeCoordinates[1][1];
|
||||
}
|
||||
}
|
||||
|
||||
if (count($arrConditional) === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$needRedraw = 1;
|
||||
$cellRange = pack('vvvv', $numRowMin - 1, $numRowMax - 1, $numColumnMin - 1, $numColumnMax - 1);
|
||||
|
||||
|
|
@ -3173,6 +3186,8 @@ class Worksheet extends BIFFwriter
|
|||
$data .= pack('v', 0x0001);
|
||||
$data .= $cellRange;
|
||||
$this->append($header . $data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getDataBlockProtection(Conditional $conditional): int
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
|
||||
|
|
@ -56,7 +57,10 @@ class Drawing extends WriterPart
|
|||
// Loop through charts and write the chart position
|
||||
if ($chartCount > 0) {
|
||||
for ($c = 0; $c < $chartCount; ++$c) {
|
||||
$this->writeChart($objWriter, $worksheet->getChartByIndex($c), $c + $i);
|
||||
$chart = $worksheet->getChartByIndex((string) $c);
|
||||
if ($chart !== false) {
|
||||
$this->writeChart($objWriter, $chart, $c + $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -90,16 +94,16 @@ class Drawing extends WriterPart
|
|||
$objWriter->startElement('xdr:twoCellAnchor');
|
||||
|
||||
$objWriter->startElement('xdr:from');
|
||||
$objWriter->writeElement('xdr:col', $tlColRow[0] - 1);
|
||||
$objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['xOffset']));
|
||||
$objWriter->writeElement('xdr:row', $tlColRow[1] - 1);
|
||||
$objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['yOffset']));
|
||||
$objWriter->writeElement('xdr:col', (string) ($tlColRow[0] - 1));
|
||||
$objWriter->writeElement('xdr:colOff', self::stringEmu($tl['xOffset']));
|
||||
$objWriter->writeElement('xdr:row', (string) ($tlColRow[1] - 1));
|
||||
$objWriter->writeElement('xdr:rowOff', self::stringEmu($tl['yOffset']));
|
||||
$objWriter->endElement();
|
||||
$objWriter->startElement('xdr:to');
|
||||
$objWriter->writeElement('xdr:col', $brColRow[0] - 1);
|
||||
$objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['xOffset']));
|
||||
$objWriter->writeElement('xdr:row', $brColRow[1] - 1);
|
||||
$objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['yOffset']));
|
||||
$objWriter->writeElement('xdr:col', (string) ($brColRow[0] - 1));
|
||||
$objWriter->writeElement('xdr:colOff', self::stringEmu($br['xOffset']));
|
||||
$objWriter->writeElement('xdr:row', (string) ($brColRow[1] - 1));
|
||||
$objWriter->writeElement('xdr:rowOff', self::stringEmu($br['yOffset']));
|
||||
$objWriter->endElement();
|
||||
|
||||
$objWriter->startElement('xdr:graphicFrame');
|
||||
|
|
@ -107,7 +111,7 @@ class Drawing extends WriterPart
|
|||
$objWriter->startElement('xdr:nvGraphicFramePr');
|
||||
$objWriter->startElement('xdr:cNvPr');
|
||||
$objWriter->writeAttribute('name', 'Chart ' . $relationId);
|
||||
$objWriter->writeAttribute('id', 1025 * $relationId);
|
||||
$objWriter->writeAttribute('id', (string) (1025 * $relationId));
|
||||
$objWriter->endElement();
|
||||
$objWriter->startElement('xdr:cNvGraphicFramePr');
|
||||
$objWriter->startElement('a:graphicFrameLocks');
|
||||
|
|
@ -153,28 +157,31 @@ class Drawing extends WriterPart
|
|||
public function writeDrawing(XMLWriter $objWriter, BaseDrawing $drawing, $relationId = -1, $hlinkClickId = null): void
|
||||
{
|
||||
if ($relationId >= 0) {
|
||||
$isTwoCellAnchor = $drawing->getCoordinates2() !== null;
|
||||
$isTwoCellAnchor = $drawing->getCoordinates2() !== '';
|
||||
if ($isTwoCellAnchor) {
|
||||
// xdr:twoCellAnchor
|
||||
$objWriter->startElement('xdr:twoCellAnchor');
|
||||
if ($drawing->validEditAs()) {
|
||||
$objWriter->writeAttribute('editAs', $drawing->getEditAs());
|
||||
}
|
||||
// Image location
|
||||
$aCoordinates = Coordinate::indexesFromString($drawing->getCoordinates());
|
||||
$aCoordinates2 = Coordinate::indexesFromString($drawing->getCoordinates2());
|
||||
|
||||
// xdr:from
|
||||
$objWriter->startElement('xdr:from');
|
||||
$objWriter->writeElement('xdr:col', $aCoordinates[0] - 1);
|
||||
$objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetX()));
|
||||
$objWriter->writeElement('xdr:row', $aCoordinates[1] - 1);
|
||||
$objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetY()));
|
||||
$objWriter->writeElement('xdr:col', (string) ($aCoordinates[0] - 1));
|
||||
$objWriter->writeElement('xdr:colOff', self::stringEmu($drawing->getOffsetX()));
|
||||
$objWriter->writeElement('xdr:row', (string) ($aCoordinates[1] - 1));
|
||||
$objWriter->writeElement('xdr:rowOff', self::stringEmu($drawing->getOffsetY()));
|
||||
$objWriter->endElement();
|
||||
|
||||
// xdr:to
|
||||
$objWriter->startElement('xdr:to');
|
||||
$objWriter->writeElement('xdr:col', $aCoordinates2[0] - 1);
|
||||
$objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetX2()));
|
||||
$objWriter->writeElement('xdr:row', $aCoordinates2[1] - 1);
|
||||
$objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetY2()));
|
||||
$objWriter->writeElement('xdr:col', (string) ($aCoordinates2[0] - 1));
|
||||
$objWriter->writeElement('xdr:colOff', self::stringEmu($drawing->getOffsetX2()));
|
||||
$objWriter->writeElement('xdr:row', (string) ($aCoordinates2[1] - 1));
|
||||
$objWriter->writeElement('xdr:rowOff', self::stringEmu($drawing->getOffsetY2()));
|
||||
$objWriter->endElement();
|
||||
} else {
|
||||
// xdr:oneCellAnchor
|
||||
|
|
@ -184,16 +191,16 @@ class Drawing extends WriterPart
|
|||
|
||||
// xdr:from
|
||||
$objWriter->startElement('xdr:from');
|
||||
$objWriter->writeElement('xdr:col', $aCoordinates[0] - 1);
|
||||
$objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetX()));
|
||||
$objWriter->writeElement('xdr:row', $aCoordinates[1] - 1);
|
||||
$objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetY()));
|
||||
$objWriter->writeElement('xdr:col', (string) ($aCoordinates[0] - 1));
|
||||
$objWriter->writeElement('xdr:colOff', self::stringEmu($drawing->getOffsetX()));
|
||||
$objWriter->writeElement('xdr:row', (string) ($aCoordinates[1] - 1));
|
||||
$objWriter->writeElement('xdr:rowOff', self::stringEmu($drawing->getOffsetY()));
|
||||
$objWriter->endElement();
|
||||
|
||||
// xdr:ext
|
||||
$objWriter->startElement('xdr:ext');
|
||||
$objWriter->writeAttribute('cx', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getWidth()));
|
||||
$objWriter->writeAttribute('cy', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getHeight()));
|
||||
$objWriter->writeAttribute('cx', self::stringEmu($drawing->getWidth()));
|
||||
$objWriter->writeAttribute('cy', self::stringEmu($drawing->getHeight()));
|
||||
$objWriter->endElement();
|
||||
}
|
||||
|
||||
|
|
@ -205,7 +212,7 @@ class Drawing extends WriterPart
|
|||
|
||||
// xdr:cNvPr
|
||||
$objWriter->startElement('xdr:cNvPr');
|
||||
$objWriter->writeAttribute('id', $relationId);
|
||||
$objWriter->writeAttribute('id', (string) $relationId);
|
||||
$objWriter->writeAttribute('name', $drawing->getName());
|
||||
$objWriter->writeAttribute('descr', $drawing->getDescription());
|
||||
|
||||
|
|
@ -247,11 +254,11 @@ class Drawing extends WriterPart
|
|||
|
||||
// a:xfrm
|
||||
$objWriter->startElement('a:xfrm');
|
||||
$objWriter->writeAttribute('rot', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($drawing->getRotation()));
|
||||
$objWriter->writeAttribute('rot', (string) SharedDrawing::degreesToAngle($drawing->getRotation()));
|
||||
if ($isTwoCellAnchor) {
|
||||
$objWriter->startElement('a:ext');
|
||||
$objWriter->writeAttribute('cx', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getWidth()));
|
||||
$objWriter->writeAttribute('cy', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getHeight()));
|
||||
$objWriter->writeAttribute('cx', self::stringEmu($drawing->getWidth()));
|
||||
$objWriter->writeAttribute('cy', self::stringEmu($drawing->getHeight()));
|
||||
$objWriter->endElement();
|
||||
}
|
||||
$objWriter->endElement();
|
||||
|
|
@ -271,9 +278,9 @@ class Drawing extends WriterPart
|
|||
|
||||
// a:outerShdw
|
||||
$objWriter->startElement('a:outerShdw');
|
||||
$objWriter->writeAttribute('blurRad', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getShadow()->getBlurRadius()));
|
||||
$objWriter->writeAttribute('dist', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getShadow()->getDistance()));
|
||||
$objWriter->writeAttribute('dir', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($drawing->getShadow()->getDirection()));
|
||||
$objWriter->writeAttribute('blurRad', self::stringEmu($drawing->getShadow()->getBlurRadius()));
|
||||
$objWriter->writeAttribute('dist', self::stringEmu($drawing->getShadow()->getDistance()));
|
||||
$objWriter->writeAttribute('dir', (string) SharedDrawing::degreesToAngle($drawing->getShadow()->getDirection()));
|
||||
$objWriter->writeAttribute('algn', $drawing->getShadow()->getAlignment());
|
||||
$objWriter->writeAttribute('rotWithShape', '0');
|
||||
|
||||
|
|
@ -283,7 +290,7 @@ class Drawing extends WriterPart
|
|||
|
||||
// a:alpha
|
||||
$objWriter->startElement('a:alpha');
|
||||
$objWriter->writeAttribute('val', $drawing->getShadow()->getAlpha() * 1000);
|
||||
$objWriter->writeAttribute('val', (string) ($drawing->getShadow()->getAlpha() * 1000));
|
||||
$objWriter->endElement();
|
||||
|
||||
$objWriter->endElement();
|
||||
|
|
@ -528,4 +535,9 @@ class Drawing extends WriterPart
|
|||
$objWriter->writeAttribute('r:id', 'rId' . $hlinkClickId);
|
||||
$objWriter->endElement();
|
||||
}
|
||||
|
||||
private static function stringEmu(int $pixelValue): string
|
||||
{
|
||||
return (string) SharedDrawing::pixelsToEMU($pixelValue);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class CalculationFunctionListTest extends TestCase
|
|||
* @param array|string $functionCall
|
||||
* @param string $argumentCount
|
||||
*/
|
||||
public function testGetFunctions($category, $functionCall, $argumentCount): void
|
||||
public function testGetFunctions(/** @scrutinizer ignore-unused */ $category, $functionCall, /** @scrutinizer ignore-unused */ $argumentCount): void
|
||||
{
|
||||
self::assertIsCallable($functionCall);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Engineering;
|
|||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ComplexTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerCOMPLEX
|
||||
*
|
||||
|
|
@ -21,7 +15,15 @@ class ComplexTest extends TestCase
|
|||
*/
|
||||
public function testCOMPLEX($expectedResult, ...$args): void
|
||||
{
|
||||
$result = Engineering::COMPLEX(...$args);
|
||||
if (count($args) === 0) {
|
||||
$result = Engineering::COMPLEX();
|
||||
} elseif (count($args) === 1) {
|
||||
$result = Engineering::COMPLEX($args[0]);
|
||||
} elseif (count($args) === 2) {
|
||||
$result = Engineering::COMPLEX($args[0], $args[1]);
|
||||
} else {
|
||||
$result = Engineering::COMPLEX($args[0], $args[1], $args[2]);
|
||||
}
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,16 +4,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Financial;
|
|||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DollarDeTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerDOLLARDE
|
||||
*
|
||||
|
|
@ -21,7 +15,13 @@ class DollarDeTest extends TestCase
|
|||
*/
|
||||
public function testDOLLARDE($expectedResult, ...$args): void
|
||||
{
|
||||
$result = Financial::DOLLARDE(...$args);
|
||||
if (count($args) === 0) {
|
||||
$result = Financial::DOLLARDE();
|
||||
} elseif (count($args) === 1) {
|
||||
$result = Financial::DOLLARDE($args[0]);
|
||||
} else {
|
||||
$result = Financial::DOLLARDE($args[0], $args[1]);
|
||||
}
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1E-8);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,10 @@
|
|||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DollarFrTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerDOLLARFR
|
||||
*
|
||||
|
|
@ -20,7 +14,13 @@ class DollarFrTest extends TestCase
|
|||
*/
|
||||
public function testDOLLARFR($expectedResult, ...$args): void
|
||||
{
|
||||
$result = Financial::DOLLARFR(...$args);
|
||||
if (count($args) === 0) {
|
||||
$result = Financial::DOLLARFR();
|
||||
} elseif (count($args) === 1) {
|
||||
$result = Financial::DOLLARFR($args[0]);
|
||||
} else {
|
||||
$result = Financial::DOLLARFR($args[0], $args[1]);
|
||||
}
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1E-8);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,10 @@
|
|||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class FvTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerFV
|
||||
*
|
||||
|
|
@ -20,7 +14,19 @@ class FvTest extends TestCase
|
|||
*/
|
||||
public function testFV($expectedResult, array $args): void
|
||||
{
|
||||
$result = Financial::FV(...$args);
|
||||
if (count($args) === 0) {
|
||||
$result = Financial::FV();
|
||||
} elseif (count($args) === 1) {
|
||||
$result = Financial::FV($args[0]);
|
||||
} elseif (count($args) === 2) {
|
||||
$result = Financial::FV($args[0], $args[1]);
|
||||
} elseif (count($args) === 3) {
|
||||
$result = Financial::FV($args[0], $args[1], $args[2]);
|
||||
} elseif (count($args) === 4) {
|
||||
$result = Financial::FV($args[0], $args[1], $args[2], $args[3]);
|
||||
} else {
|
||||
$result = Financial::FV($args[0], $args[1], $args[2], $args[3], $args[4]);
|
||||
}
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1E-8);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,10 @@
|
|||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class NPerTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerNPER
|
||||
*
|
||||
|
|
@ -20,7 +14,19 @@ class NPerTest extends TestCase
|
|||
*/
|
||||
public function testNPER($expectedResult, array $args): void
|
||||
{
|
||||
$result = Financial::NPER(...$args);
|
||||
if (count($args) === 0) {
|
||||
$result = Financial::NPER();
|
||||
} elseif (count($args) === 1) {
|
||||
$result = Financial::NPER($args[0]);
|
||||
} elseif (count($args) === 2) {
|
||||
$result = Financial::NPER($args[0], $args[1]);
|
||||
} elseif (count($args) === 3) {
|
||||
$result = Financial::NPER($args[0], $args[1], $args[2]);
|
||||
} elseif (count($args) === 4) {
|
||||
$result = Financial::NPER($args[0], $args[1], $args[2], $args[3]);
|
||||
} else {
|
||||
$result = Financial::NPER($args[0], $args[1], $args[2], $args[3], $args[4]);
|
||||
}
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1E-8);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,10 @@
|
|||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PDurationTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerPDURATION
|
||||
*
|
||||
|
|
@ -20,7 +14,15 @@ class PDurationTest extends TestCase
|
|||
*/
|
||||
public function testPDURATION($expectedResult, array $args): void
|
||||
{
|
||||
$result = Financial::PDURATION(...$args);
|
||||
if (count($args) === 0) {
|
||||
$result = Financial::PDURATION();
|
||||
} elseif (count($args) === 1) {
|
||||
$result = Financial::PDURATION($args[0]);
|
||||
} elseif (count($args) === 2) {
|
||||
$result = Financial::PDURATION($args[0], $args[1]);
|
||||
} else {
|
||||
$result = Financial::PDURATION($args[0], $args[1], $args[2]);
|
||||
}
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1E-8);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,10 @@
|
|||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PmtTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerPMT
|
||||
*
|
||||
|
|
@ -23,7 +17,13 @@ class PmtTest extends TestCase
|
|||
$interestRate = array_shift($args);
|
||||
$numberOfPeriods = array_shift($args);
|
||||
$presentValue = array_shift($args);
|
||||
$result = Financial::PMT($interestRate, $numberOfPeriods, $presentValue, ...$args);
|
||||
if (count($args) === 0) {
|
||||
$result = Financial::PMT($interestRate, $numberOfPeriods, $presentValue);
|
||||
} elseif (count($args) === 1) {
|
||||
$result = Financial::PMT($interestRate, $numberOfPeriods, $presentValue, $args[0]);
|
||||
} else {
|
||||
$result = Financial::PMT($interestRate, $numberOfPeriods, $presentValue, $args[0], $args[1]);
|
||||
}
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1E-8);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,10 @@
|
|||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PvTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerPV
|
||||
*
|
||||
|
|
@ -20,7 +14,19 @@ class PvTest extends TestCase
|
|||
*/
|
||||
public function testPV($expectedResult, array $args): void
|
||||
{
|
||||
$result = Financial::PV(...$args);
|
||||
if (count($args) === 0) {
|
||||
$result = Financial::PV();
|
||||
} elseif (count($args) === 1) {
|
||||
$result = Financial::PV($args[0]);
|
||||
} elseif (count($args) === 2) {
|
||||
$result = Financial::PV($args[0], $args[1]);
|
||||
} elseif (count($args) === 3) {
|
||||
$result = Financial::PV($args[0], $args[1], $args[2]);
|
||||
} elseif (count($args) === 4) {
|
||||
$result = Financial::PV($args[0], $args[1], $args[2], $args[3]);
|
||||
} else {
|
||||
$result = Financial::PV($args[0], $args[1], $args[2], $args[3], $args[4]);
|
||||
}
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1E-8);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,10 @@
|
|||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class RriTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerRRI
|
||||
*
|
||||
|
|
@ -20,7 +14,15 @@ class RriTest extends TestCase
|
|||
*/
|
||||
public function testRRI($expectedResult, array $args): void
|
||||
{
|
||||
$result = Financial::RRI(...$args);
|
||||
if (count($args) === 0) {
|
||||
$result = Financial::RRI();
|
||||
} elseif (count($args) === 1) {
|
||||
$result = Financial::RRI($args[0]);
|
||||
} elseif (count($args) === 2) {
|
||||
$result = Financial::RRI($args[0], $args[1]);
|
||||
} else {
|
||||
$result = Financial::RRI($args[0], $args[1], $args[2]);
|
||||
}
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1E-8);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,13 @@ class UsDollarTest extends TestCase
|
|||
*
|
||||
* @param mixed $expectedResult
|
||||
*/
|
||||
public function testUSDOLLAR($expectedResult, ...$args): void
|
||||
public function testUSDOLLAR($expectedResult, float $amount, ?int $precision = null): void
|
||||
{
|
||||
$result = Dollar::format(...$args);
|
||||
if ($precision === null) {
|
||||
$result = Dollar::format($amount);
|
||||
} else {
|
||||
$result = Dollar::format($amount, $precision);
|
||||
}
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,11 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Logical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Logical;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class IfTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerIF
|
||||
*
|
||||
|
|
@ -20,7 +14,15 @@ class IfTest extends TestCase
|
|||
*/
|
||||
public function testIF($expectedResult, ...$args): void
|
||||
{
|
||||
$result = Logical::statementIf(...$args);
|
||||
if (count($args) === 0) {
|
||||
$result = Logical::statementIf();
|
||||
} elseif (count($args) === 1) {
|
||||
$result = Logical::statementIf($args[0]);
|
||||
} elseif (count($args) === 2) {
|
||||
$result = Logical::statementIf($args[0], $args[1]);
|
||||
} else {
|
||||
$result = Logical::statementIf($args[0], $args[1], $args[2]);
|
||||
}
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,17 +3,11 @@
|
|||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Logical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Logical;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class NotTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerNOT
|
||||
*
|
||||
|
|
@ -21,7 +15,11 @@ class NotTest extends TestCase
|
|||
*/
|
||||
public function testNOT($expectedResult, ...$args): void
|
||||
{
|
||||
$result = Logical::NOT(...$args);
|
||||
if (count($args) === 0) {
|
||||
$result = Logical::NOT();
|
||||
} else {
|
||||
$result = Logical::NOT($args[0]);
|
||||
}
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Filter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class FilterTest extends TestCase
|
||||
{
|
||||
public function testFilterByRow(): void
|
||||
{
|
||||
$criteria = [[true], [false], [false], [false], [true], [false], [false], [false], [false], [false], [false], [true], [false], [false], [false], [true]];
|
||||
$expectedResult = [
|
||||
['East', 'Tom', 'Apple', 6830],
|
||||
['East', 'Fritz', 'Apple', 4394],
|
||||
['South', 'Sal', 'Apple', 1310],
|
||||
['South', 'Hector', 'Apple', 8144],
|
||||
];
|
||||
$result = Filter::filter($this->sampleDataForRow(), $criteria);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function testFilterByColumn(): void
|
||||
{
|
||||
$criteria = [[false, false, true, false, true, false, false, false, true, true]];
|
||||
$expectedResult = [
|
||||
['Betty', 'Charlotte', 'Oliver', 'Zoe'],
|
||||
['B', 'B', 'B', 'B'],
|
||||
[1, 2, 4, 8],
|
||||
];
|
||||
$result = Filter::filter($this->sampleDataForColumn(), $criteria);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function testFilterException(): void
|
||||
{
|
||||
$criteria = 'INVALID';
|
||||
$result = Filter::filter($this->sampleDataForColumn(), $criteria);
|
||||
self::assertSame(ExcelError::VALUE(), $result);
|
||||
}
|
||||
|
||||
public function testFilterEmpty(): void
|
||||
{
|
||||
$criteria = [[false], [false], [false]];
|
||||
$expectedResult = ExcelError::CALC();
|
||||
$result = Filter::filter([[1], [2], [3]], $criteria);
|
||||
self::assertSame($expectedResult, $result);
|
||||
|
||||
$expectedResult = 'Invalid Data';
|
||||
$result = Filter::filter([[1], [2], [3]], $criteria, $expectedResult);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
protected function sampleDataForRow(): array
|
||||
{
|
||||
return [
|
||||
['East', 'Tom', 'Apple', 6830],
|
||||
['West', 'Fred', 'Grape', 5619],
|
||||
['North', 'Amy', 'Pear', 4565],
|
||||
['South', 'Sal', 'Banana', 5323],
|
||||
['East', 'Fritz', 'Apple', 4394],
|
||||
['West', 'Sravan', 'Grape', 7195],
|
||||
['North', 'Xi', 'Pear', 5231],
|
||||
['South', 'Hector', 'Banana', 2427],
|
||||
['East', 'Tom', 'Banana', 4213],
|
||||
['West', 'Fred', 'Pear', 3239],
|
||||
['North', 'Amy', 'Grape', 6420],
|
||||
['South', 'Sal', 'Apple', 1310],
|
||||
['East', 'Fritz', 'Banana', 6274],
|
||||
['West', 'Sravan', 'Pear', 4894],
|
||||
['North', 'Xi', 'Grape', 7580],
|
||||
['South', 'Hector', 'Apple', 8144],
|
||||
];
|
||||
}
|
||||
|
||||
protected function sampleDataForColumn(): array
|
||||
{
|
||||
return [
|
||||
['Aiden', 'Andrew', 'Betty', 'Caden', 'Charlotte', 'Emma', 'Isabella', 'Mason', 'Oliver', 'Zoe'],
|
||||
['A', 'C', 'B', 'A', 'B', 'C', 'A', 'A', 'B', 'B'],
|
||||
[0, 4, 1, 2, 2, 0, 2, 4, 4, 8],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Matrix;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class MatrixHelperFunctionsTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider columnVectorProvider
|
||||
*/
|
||||
public function testIsColumnVector(bool $expectedResult, array $array): void
|
||||
{
|
||||
$result = Matrix::isColumnVector($array);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider rowVectorProvider
|
||||
*/
|
||||
public function testIsRowVector(bool $expectedResult, array $array): void
|
||||
{
|
||||
$result = Matrix::isRowVector($array);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function columnVectorProvider(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
true,
|
||||
[
|
||||
[1], [2], [3],
|
||||
],
|
||||
],
|
||||
[
|
||||
false,
|
||||
[1, 2, 3],
|
||||
],
|
||||
[
|
||||
false,
|
||||
[
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function rowVectorProvider(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
false,
|
||||
[
|
||||
[1], [2], [3],
|
||||
],
|
||||
],
|
||||
[
|
||||
true,
|
||||
[1, 2, 3],
|
||||
],
|
||||
[
|
||||
true,
|
||||
[[1, 2, 3]],
|
||||
],
|
||||
[
|
||||
false,
|
||||
[
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Sort;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SortByTest extends TestCase
|
||||
{
|
||||
public function testSortOnScalar(): void
|
||||
{
|
||||
$value = 'NON-ARRAY';
|
||||
|
||||
$result = Sort::sortBy($value);
|
||||
self::assertSame($value, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerSortWithScalarArgumentErrorReturns
|
||||
*
|
||||
* @param mixed $sortIndex
|
||||
* @param mixed$sortOrder
|
||||
*/
|
||||
public function testSortByWithArgumentErrorReturns($sortIndex, $sortOrder = 1): void
|
||||
{
|
||||
$value = [[1, 2], [3, 4], [5, 6]];
|
||||
|
||||
$result = Sort::sortBy($value, $sortIndex, $sortOrder);
|
||||
self::assertSame(ExcelError::VALUE(), $result);
|
||||
}
|
||||
|
||||
public function providerSortWithScalarArgumentErrorReturns(): array
|
||||
{
|
||||
return [
|
||||
'Non-array sortIndex' => ['A', 1],
|
||||
'Mismatched sortIndex count' => [[1, 2, 3, 4], 1],
|
||||
'Non-numeric sortOrder' => [[1, 2, 3], 'A'],
|
||||
'Invalid negative sortOrder' => [[1, 2, 3], -2],
|
||||
'Zero sortOrder' => [[1, 2, 3], 0],
|
||||
'Invalid positive sortOrder' => [[1, 2, 3], 2],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerSortByRow
|
||||
*/
|
||||
public function testSortByRow(array $expectedResult, array $matrix, ...$args): void
|
||||
{
|
||||
$result = Sort::sortBy($matrix, ...$args);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function providerSortByRow(): array
|
||||
{
|
||||
return [
|
||||
'Simple sort by age' => [
|
||||
[
|
||||
['Fritz', 19],
|
||||
['Xi', 19],
|
||||
['Amy', 22],
|
||||
['Srivan', 39],
|
||||
['Tom', 52],
|
||||
['Fred', 65],
|
||||
['Hector', 66],
|
||||
['Sal', 73],
|
||||
],
|
||||
$this->sampleDataForSimpleSort(),
|
||||
array_column($this->sampleDataForSimpleSort(), 1),
|
||||
],
|
||||
'Simple sort by name' => [
|
||||
[
|
||||
['Amy', 22],
|
||||
['Fred', 65],
|
||||
['Fritz', 19],
|
||||
['Hector', 66],
|
||||
['Sal', 73],
|
||||
['Srivan', 39],
|
||||
['Tom', 52],
|
||||
['Xi', 19],
|
||||
],
|
||||
$this->sampleDataForSimpleSort(),
|
||||
array_column($this->sampleDataForSimpleSort(), 0),
|
||||
],
|
||||
'Row vector' => [
|
||||
[
|
||||
['Amy', 22],
|
||||
['Fred', 65],
|
||||
['Fritz', 19],
|
||||
['Hector', 66],
|
||||
['Sal', 73],
|
||||
['Srivan', 39],
|
||||
['Tom', 52],
|
||||
['Xi', 19],
|
||||
],
|
||||
$this->sampleDataForSimpleSort(),
|
||||
['Tom', 'Fred', 'Amy', 'Sal', 'Fritz', 'Srivan', 'Xi', 'Hector'],
|
||||
],
|
||||
'Column vector' => [
|
||||
[
|
||||
['Amy', 22],
|
||||
['Fred', 65],
|
||||
['Fritz', 19],
|
||||
['Hector', 66],
|
||||
['Sal', 73],
|
||||
['Srivan', 39],
|
||||
['Tom', 52],
|
||||
['Xi', 19],
|
||||
],
|
||||
$this->sampleDataForSimpleSort(),
|
||||
[['Tom'], ['Fred'], ['Amy'], ['Sal'], ['Fritz'], ['Srivan'], ['Xi'], ['Hector']],
|
||||
],
|
||||
'Sort by region asc, name asc' => [
|
||||
[
|
||||
['East', 'Fritz', 19],
|
||||
['East', 'Tom', 52],
|
||||
['North', 'Amy', 22],
|
||||
['North', 'Xi', 19],
|
||||
['South', 'Hector', 66],
|
||||
['South', 'Sal', 73],
|
||||
['West', 'Fred', 65],
|
||||
['West', 'Srivan', 39],
|
||||
],
|
||||
$this->sampleDataForMultiSort(),
|
||||
array_column($this->sampleDataForMultiSort(), 0),
|
||||
Sort::ORDER_ASCENDING,
|
||||
array_column($this->sampleDataForMultiSort(), 1),
|
||||
],
|
||||
'Sort by region asc, age desc' => [
|
||||
[
|
||||
['East', 'Tom', 52],
|
||||
['East', 'Fritz', 19],
|
||||
['North', 'Amy', 22],
|
||||
['North', 'Xi', 19],
|
||||
['South', 'Sal', 73],
|
||||
['South', 'Hector', 66],
|
||||
['West', 'Fred', 65],
|
||||
['West', 'Srivan', 39],
|
||||
],
|
||||
$this->sampleDataForMultiSort(),
|
||||
array_column($this->sampleDataForMultiSort(), 0),
|
||||
Sort::ORDER_ASCENDING,
|
||||
array_column($this->sampleDataForMultiSort(), 2),
|
||||
Sort::ORDER_DESCENDING,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function sampleDataForSimpleSort(): array
|
||||
{
|
||||
return [
|
||||
['Tom', 52],
|
||||
['Fred', 65],
|
||||
['Amy', 22],
|
||||
['Sal', 73],
|
||||
['Fritz', 19],
|
||||
['Srivan', 39],
|
||||
['Xi', 19],
|
||||
['Hector', 66],
|
||||
];
|
||||
}
|
||||
|
||||
private function sampleDataForMultiSort(): array
|
||||
{
|
||||
return [
|
||||
['North', 'Amy', 22],
|
||||
['West', 'Fred', 65],
|
||||
['East', 'Fritz', 19],
|
||||
['South', 'Hector', 66],
|
||||
['South', 'Sal', 73],
|
||||
['West', 'Srivan', 39],
|
||||
['East', 'Tom', 52],
|
||||
['North', 'Xi', 19],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Sort;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SortTest extends TestCase
|
||||
{
|
||||
public function testSortOnScalar(): void
|
||||
{
|
||||
$value = 'NON-ARRAY';
|
||||
|
||||
$result = Sort::sort($value, 1, -1);
|
||||
self::assertSame($value, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerSortWithScalarArgumentErrorReturns
|
||||
*
|
||||
* @param mixed $sortIndex
|
||||
* @param mixed$sortOrder
|
||||
*/
|
||||
public function testSortWithScalarArgumentErrorReturns($sortIndex, $sortOrder = 1): void
|
||||
{
|
||||
$value = [[1, 2], [3, 4], [5, 6]];
|
||||
|
||||
$result = Sort::sort($value, $sortIndex, $sortOrder);
|
||||
self::assertSame(ExcelError::VALUE(), $result);
|
||||
}
|
||||
|
||||
public function providerSortWithScalarArgumentErrorReturns(): array
|
||||
{
|
||||
return [
|
||||
'Negative sortIndex' => [-1, -1],
|
||||
'Non-numeric sortIndex' => ['A', -1],
|
||||
'Zero sortIndex' => [0, -1],
|
||||
'Too high sortIndex' => [3, -1],
|
||||
'Non-numeric sortOrder' => [1, 'A'],
|
||||
'Invalid negative sortOrder' => [1, -2],
|
||||
'Zero sortOrder' => [1, 0],
|
||||
'Invalid positive sortOrder' => [1, 2],
|
||||
'Too many sortOrders (scalar and array)' => [1, [-1, 1]],
|
||||
'Too many sortOrders (both array)' => [[1, 2], [1, 2, 3]],
|
||||
'Zero positive sortIndex in vector' => [[0, 1]],
|
||||
'Too high sortIndex in vector' => [[1, 3]],
|
||||
'Invalid sortOrder in vector' => [[1, 2], [1, -2]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerSortByRow
|
||||
*/
|
||||
public function testSortByRow(array $expectedResult, array $matrix, int $sortIndex, int $sortOrder = Sort::ORDER_ASCENDING): void
|
||||
{
|
||||
$result = Sort::sort($matrix, $sortIndex, $sortOrder);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function providerSortByRow(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
[[142], [378], [404], [445], [483], [622], [650], [691], [783], [961]],
|
||||
$this->sampleDataForRow(),
|
||||
1,
|
||||
],
|
||||
[
|
||||
[[961], [783], [691], [650], [622], [483], [445], [404], [378], [142]],
|
||||
$this->sampleDataForRow(),
|
||||
1,
|
||||
Sort::ORDER_DESCENDING,
|
||||
],
|
||||
[
|
||||
[['Peaches', 25], ['Cherries', 29], ['Grapes', 31], ['Lemons', 34], ['Oranges', 36], ['Apples', 38], ['Pears', 40]],
|
||||
[['Apples', 38], ['Cherries', 29], ['Grapes', 31], ['Lemons', 34], ['Oranges', 36], ['Peaches', 25], ['Pears', 40]],
|
||||
2,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerSortByRowMultiLevel
|
||||
*/
|
||||
public function testSortByRowMultiLevel(array $expectedResult, array $matrix, array $sortIndex, int $sortOrder = Sort::ORDER_ASCENDING): void
|
||||
{
|
||||
$result = Sort::sort($matrix, $sortIndex, $sortOrder);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function providerSortByRowMultiLevel(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
[
|
||||
['East', 'Grapes', 31],
|
||||
['East', 'Lemons', 36],
|
||||
['North', 'Cherries', 29],
|
||||
['North', 'Grapes', 27],
|
||||
['North', 'Peaches', 25],
|
||||
['South', 'Apples', 38],
|
||||
['South', 'Cherries', 28],
|
||||
['South', 'Oranges', 36],
|
||||
['South', 'Pears', 40],
|
||||
['West', 'Apples', 30],
|
||||
['West', 'Lemons', 34],
|
||||
['West', 'Oranges', 25],
|
||||
],
|
||||
$this->sampleDataForMultiRow(),
|
||||
[1, 2],
|
||||
],
|
||||
[
|
||||
[
|
||||
['East', 'Grapes', 31],
|
||||
['East', 'Lemons', 36],
|
||||
['North', 'Peaches', 25],
|
||||
['North', 'Grapes', 27],
|
||||
['North', 'Cherries', 29],
|
||||
['South', 'Cherries', 28],
|
||||
['South', 'Oranges', 36],
|
||||
['South', 'Apples', 38],
|
||||
['South', 'Pears', 40],
|
||||
['West', 'Oranges', 25],
|
||||
['West', 'Apples', 30],
|
||||
['West', 'Lemons', 34],
|
||||
],
|
||||
$this->sampleDataForMultiRow(),
|
||||
[1, 3],
|
||||
],
|
||||
[
|
||||
[
|
||||
['West', 'Apples', 30],
|
||||
['South', 'Apples', 38],
|
||||
['South', 'Cherries', 28],
|
||||
['North', 'Cherries', 29],
|
||||
['North', 'Grapes', 27],
|
||||
['East', 'Grapes', 31],
|
||||
['West', 'Lemons', 34],
|
||||
['East', 'Lemons', 36],
|
||||
['West', 'Oranges', 25],
|
||||
['South', 'Oranges', 36],
|
||||
['North', 'Peaches', 25],
|
||||
['South', 'Pears', 40],
|
||||
],
|
||||
$this->sampleDataForMultiRow(),
|
||||
[2, 3],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerSortByColumn
|
||||
*/
|
||||
public function testSortByColumn(array $expectedResult, array $matrix, int $sortIndex, int $sortOrder): void
|
||||
{
|
||||
$result = Sort::sort($matrix, $sortIndex, $sortOrder, true);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function providerSortByColumn(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
[[142, 378, 404, 445, 483, 622, 650, 691, 783, 961]],
|
||||
$this->sampleDataForColumn(),
|
||||
1,
|
||||
Sort::ORDER_ASCENDING,
|
||||
],
|
||||
[
|
||||
[[961, 783, 691, 650, 622, 483, 445, 404, 378, 142]],
|
||||
$this->sampleDataForColumn(),
|
||||
1,
|
||||
Sort::ORDER_DESCENDING,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function sampleDataForRow(): array
|
||||
{
|
||||
return [
|
||||
[622], [961], [691], [445], [378], [483], [650], [783], [142], [404],
|
||||
];
|
||||
}
|
||||
|
||||
public function sampleDataForMultiRow(): array
|
||||
{
|
||||
return [
|
||||
['South', 'Pears', 40],
|
||||
['South', 'Apples', 38],
|
||||
['South', 'Oranges', 36],
|
||||
['East', 'Lemons', 36],
|
||||
['West', 'Lemons', 34],
|
||||
['East', 'Grapes', 31],
|
||||
['West', 'Apples', 30],
|
||||
['North', 'Cherries', 29],
|
||||
['South', 'Cherries', 28],
|
||||
['North', 'Grapes', 27],
|
||||
['North', 'Peaches', 25],
|
||||
['West', 'Oranges', 25],
|
||||
];
|
||||
}
|
||||
|
||||
public function sampleDataForColumn(): array
|
||||
{
|
||||
return [
|
||||
[622, 961, 691, 445, 378, 483, 650, 783, 142, 404],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class UniqueTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider uniqueTestProvider
|
||||
*/
|
||||
public function testUnique(array $expectedResult, array $lookupRef, bool $byColumn = false, bool $exactlyOnce = false): void
|
||||
{
|
||||
$result = LookupRef\Unique::unique($lookupRef, $byColumn, $exactlyOnce);
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function testUniqueException(): void
|
||||
{
|
||||
$rowLookupData = [
|
||||
['Andrew', 'Brown'],
|
||||
['Betty', 'Johnson'],
|
||||
['Betty', 'Johnson'],
|
||||
['Andrew', 'Brown'],
|
||||
['David', 'White'],
|
||||
['Andrew', 'Brown'],
|
||||
['David', 'White'],
|
||||
];
|
||||
|
||||
$columnLookupData = [
|
||||
['PHP', 'Rocks', 'php', 'rocks'],
|
||||
];
|
||||
|
||||
$result = LookupRef\Unique::unique($rowLookupData, false, true);
|
||||
self::assertEquals(ExcelError::CALC(), $result);
|
||||
|
||||
$result = LookupRef\Unique::unique($columnLookupData, true, true);
|
||||
self::assertEquals(ExcelError::CALC(), $result);
|
||||
}
|
||||
|
||||
public function testUniqueWithScalar(): void
|
||||
{
|
||||
$lookupData = 123;
|
||||
|
||||
$result = LookupRef\Unique::unique($lookupData);
|
||||
self::assertSame($lookupData, $result);
|
||||
}
|
||||
|
||||
public function uniqueTestProvider(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
[['Red'], ['Green'], ['Blue'], ['Orange']],
|
||||
[
|
||||
['Red'],
|
||||
['Green'],
|
||||
['Green'],
|
||||
['Blue'],
|
||||
['Blue'],
|
||||
['Orange'],
|
||||
['Green'],
|
||||
['Blue'],
|
||||
['Red'],
|
||||
],
|
||||
],
|
||||
[
|
||||
[['Red'], ['Green'], ['Blue'], ['Orange']],
|
||||
[
|
||||
['Red'],
|
||||
['Green'],
|
||||
['GrEEn'],
|
||||
['Blue'],
|
||||
['BLUE'],
|
||||
['Orange'],
|
||||
['GReeN'],
|
||||
['blue'],
|
||||
['RED'],
|
||||
],
|
||||
],
|
||||
[
|
||||
['Orange'],
|
||||
[
|
||||
['Red'],
|
||||
['Green'],
|
||||
['Green'],
|
||||
['Blue'],
|
||||
['Blue'],
|
||||
['Orange'],
|
||||
['Green'],
|
||||
['Blue'],
|
||||
['Red'],
|
||||
],
|
||||
false,
|
||||
true,
|
||||
],
|
||||
[
|
||||
['Andrew', 'Betty', 'Robert', 'David'],
|
||||
[['Andrew', 'Betty', 'Robert', 'Andrew', 'Betty', 'Robert', 'David', 'Andrew']],
|
||||
true,
|
||||
],
|
||||
[
|
||||
['David'],
|
||||
[['Andrew', 'Betty', 'Robert', 'Andrew', 'Betty', 'Robert', 'David', 'Andrew']],
|
||||
true,
|
||||
true,
|
||||
],
|
||||
[
|
||||
[1, 1, 2, 2, 3],
|
||||
[[1, 1, 2, 2, 3]],
|
||||
],
|
||||
[
|
||||
[1, 2, 3],
|
||||
[[1, 1, 2, 2, 3]],
|
||||
true,
|
||||
],
|
||||
[
|
||||
[
|
||||
[1, 1, 2, 3],
|
||||
[1, 2, 2, 3],
|
||||
],
|
||||
[
|
||||
[1, 1, 2, 2, 3],
|
||||
[1, 2, 2, 2, 3],
|
||||
],
|
||||
true,
|
||||
],
|
||||
[
|
||||
[
|
||||
['Andrew', 'Brown'],
|
||||
['Betty', 'Johnson'],
|
||||
['David', 'White'],
|
||||
],
|
||||
[
|
||||
['Andrew', 'Brown'],
|
||||
['Betty', 'Johnson'],
|
||||
['Betty', 'Johnson'],
|
||||
['Andrew', 'Brown'],
|
||||
['David', 'White'],
|
||||
['Andrew', 'Brown'],
|
||||
['David', 'White'],
|
||||
],
|
||||
],
|
||||
[
|
||||
[[1.2], [2.1], [2.2], [3.0]],
|
||||
[
|
||||
[1.2],
|
||||
[1.2],
|
||||
[2.1],
|
||||
[2.2],
|
||||
[3.0],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ class RandArrayTest extends AllSetupTeardown
|
|||
|
||||
$result = MathTrig\Random::randArray($rows, $cols, $min, $max, true);
|
||||
self::assertIsArray($result);
|
||||
self::assertCount($rows, $result);
|
||||
self::assertCount($rows, /** @scrutinizer ignore-type */ $result);
|
||||
self::assertIsArray($result[0]);
|
||||
self::assertCount($cols, $result[0]);
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ class RandArrayTest extends AllSetupTeardown
|
|||
|
||||
$result = MathTrig\Random::randArray($rows, $cols, $min, $max, false);
|
||||
self::assertIsArray($result);
|
||||
self::assertCount($rows, $result);
|
||||
self::assertCount($rows, /** @scrutinizer ignore-type */ $result);
|
||||
self::assertIsArray($result[0]);
|
||||
self::assertCount($cols, $result[0]);
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class RandBetweenTest extends AllSetupTeardown
|
|||
self::assertIsArray($result);
|
||||
self::assertCount($expectedRows, $result);
|
||||
self::assertIsArray($result[0]);
|
||||
self::assertCount($expectedColumns, $result[0]);
|
||||
self::assertCount($expectedColumns, /** @scrutinizer ignore-type */ $result[0]);
|
||||
}
|
||||
|
||||
public function providerRandBetweenArray(): array
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\MathTrig;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\MatrixFunctions;
|
||||
|
||||
class SequenceTest extends AllSetupTeardown
|
||||
{
|
||||
|
|
@ -14,7 +14,17 @@ class SequenceTest extends AllSetupTeardown
|
|||
*/
|
||||
public function testSEQUENCE(array $arguments, $expectedResult): void
|
||||
{
|
||||
$result = MathTrig\MatrixFunctions::sequence(...$arguments);
|
||||
if (count($arguments) === 0) {
|
||||
$result = MatrixFunctions::sequence();
|
||||
} elseif (count($arguments) === 1) {
|
||||
$result = MatrixFunctions::sequence($arguments[0]);
|
||||
} elseif (count($arguments) === 2) {
|
||||
$result = MatrixFunctions::sequence($arguments[0], $arguments[1]);
|
||||
} elseif (count($arguments) === 3) {
|
||||
$result = MatrixFunctions::sequence($arguments[0], $arguments[1], $arguments[2]);
|
||||
} else {
|
||||
$result = MatrixFunctions::sequence($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
|
||||
}
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,11 +17,16 @@ class GrowthTest extends TestCase
|
|||
* @dataProvider providerGROWTH
|
||||
*
|
||||
* @param mixed $expectedResult
|
||||
* @param mixed $yValues
|
||||
*/
|
||||
public function testGROWTH($expectedResult, $yValues, ...$args): void
|
||||
public function testGROWTH($expectedResult, array $yValues, array $xValues, ?array $newValues = null, ?bool $const = null): void
|
||||
{
|
||||
$result = Statistical::GROWTH($yValues, ...$args);
|
||||
if ($newValues === null) {
|
||||
$result = Statistical::GROWTH($yValues, $xValues);
|
||||
} elseif ($const === null) {
|
||||
$result = Statistical::GROWTH($yValues, $xValues, $newValues);
|
||||
} else {
|
||||
$result = Statistical::GROWTH($yValues, $xValues, $newValues, $const);
|
||||
}
|
||||
|
||||
self::assertEqualsWithDelta($expectedResult, $result[0], 1E-12);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,16 @@ class TrendTest extends TestCase
|
|||
* @dataProvider providerGROWTH
|
||||
*
|
||||
* @param mixed $expectedResult
|
||||
* @param mixed $yValues
|
||||
*/
|
||||
public function testTREND($expectedResult, $yValues, ...$args): void
|
||||
public function testTREND($expectedResult, array $yValues, array $xValues, ?array $newValues = null, ?bool $const = null): void
|
||||
{
|
||||
$result = Statistical::TREND($yValues, ...$args);
|
||||
if ($newValues === null) {
|
||||
$result = Statistical::TREND($yValues, $xValues);
|
||||
} elseif ($const === null) {
|
||||
$result = Statistical::TREND($yValues, $xValues, $newValues);
|
||||
} else {
|
||||
$result = Statistical::TREND($yValues, $xValues, $newValues, $const);
|
||||
}
|
||||
|
||||
self::assertEqualsWithDelta($expectedResult, $result[0], 1E-12);
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue