diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 2043d09c6..9ed1590d2 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -1052,6 +1052,22 @@ class Calculation extends CalculationLocale '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison ]; + /** @param string[] $matches */ + private static function unionForComma(array $matches): string + { + return $matches[2] . str_replace(',', '∪', $matches[3]); + } + + private const UNIONABLE_COMMAS = '/(([,(]|^)\s*)' // comma or open paren or start of string, followed by optional whitespace + . '([(]' // open paren + . self::CALCULATION_REGEXP_CELLREF // cell address + . '(:' . self::CALCULATION_REGEXP_CELLREF . ')?' // optional range address + . '(\s*,\s*' // optioonal whitespace, comma, optional whitespace + . self::CALCULATION_REGEXP_CELLREF // cell address + . '(:' . self::CALCULATION_REGEXP_CELLREF . ')?' // optional range address + . ')+' // one or more occurrences + . '\s*[)])/i'; // optional whitespace, end paren + /** * @return array|false */ @@ -1060,6 +1076,8 @@ class Calculation extends CalculationLocale if (($formula = $this->convertMatrixReferences(trim($formula))) === false) { return false; } + + $formula = preg_replace_callback(self::UNIONABLE_COMMAS, self::unionForComma(...), $formula) ?? $formula; $phpSpreadsheetFunctions = &self::getFunctionsAddress(); // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet), diff --git a/tests/PhpSpreadsheetTests/Calculation/Discussion1950Test.php b/tests/PhpSpreadsheetTests/Calculation/Discussion1950Test.php new file mode 100644 index 000000000..1e1642141 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Discussion1950Test.php @@ -0,0 +1,36 @@ +load($infile); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('=SUM((A1,A2),(B1,B2))', $sheet->getCell('A5')->getValue()); + //$sheet->getCell('A5')->setValue('=SUM((A1∪A2)∪(B1∪B2)))'); + self::assertSame(10, $sheet->getCell('A5')->getCalculatedValue(), 'error out so use old calculated value'); + $spreadsheet->disconnectWorksheets(); + } + + public function testUnexpectedUnion(): void + { + // was failing in a different manner than prior test + $infile = 'tests/data/Reader/Xlsx/issue.4656.d.xlsx'; + $reader = new XlsxReader(); + $spreadsheet = $reader->load($infile); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('=RANK(A1,(A2,A3,A4))', $sheet->getCell('A5')->getValue()); + //$sheet->getCell('A5')->setValue('=RANK(A1,(A2∪A3∪A4))'); + self::assertSame(2, $sheet->getCell('A5')->getCalculatedValue(), 'error out so use old calculated value'); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Chart/Issue316Test.php b/tests/PhpSpreadsheetTests/Chart/Issue316Test.php new file mode 100644 index 000000000..d35757d7c --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/Issue316Test.php @@ -0,0 +1,115 @@ +setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setPreCalculateFormulas(true) + ->setIncludeCharts(true); + } + + public function testUnionFormula(): void + { + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray([ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ]); + + $dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 + ]; + + $xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 + ]; + + $dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, '(Worksheet!$B$2,Worksheet!$B$5)', null, 4), // cell union created problem on write + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), + ]; + + // Build the dataseries + $series = new DataSeries( + DataSeries::TYPE_BARCHART, // plotType + DataSeries::GROUPING_CLUSTERED, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues + ); + + $plotArea = new PlotArea(null, [$series]); + $legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false); + + $title = new Title('Test Bar Chart'); + $yAxisLabel = new Title('Value ($k)'); + + // Create the chart + $chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel // yAxisLabel + ); + + $chart->setTopLeftPosition('A7'); + $chart->setBottomRightPosition('H20'); + + $worksheet->addChart($chart); + + $reloadedSpreadsheet = $this->writeAndReload( + $spreadsheet, + 'Xlsx', + $this->readCharts(...), + $this->writeCharts(...) + ); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getActiveSheet(); + $charts2 = $sheet->getChartCollection(); + self::assertCount(1, $charts2); + $chart2 = $charts2[0]; + self::assertNotNull($chart2); + self::assertSame( + '(Worksheet!$B$2,Worksheet!$B$5)', + $chart2->getPlotArea() + ?->getPlotGroupByIndex(0) + ->getPlotValues()[0] + ->getDataSource() + ); + + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Reader/XLSX/discussion.1950.xlsx b/tests/data/Reader/XLSX/discussion.1950.xlsx new file mode 100644 index 000000000..1f3804475 Binary files /dev/null and b/tests/data/Reader/XLSX/discussion.1950.xlsx differ diff --git a/tests/data/Reader/XLSX/issue.4656.d.xlsx b/tests/data/Reader/XLSX/issue.4656.d.xlsx new file mode 100644 index 000000000..359c543ac Binary files /dev/null and b/tests/data/Reader/XLSX/issue.4656.d.xlsx differ