diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d78534a3e..51defbef9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Xlsx Reader Shared Formula with Boolean Result. Partial solution for [Issue #4280](https://github.com/PHPOffice/PhpSpreadsheet/issues/4280) [PR #4281](https://github.com/PHPOffice/PhpSpreadsheet/pull/4281) - Retitling cloned Worksheets. [Issue #641](https://github.com/PHPOffice/PhpSpreadsheet/issues/641) [PR #4302](https://github.com/PHPOffice/PhpSpreadsheet/pull/4302) +- Extremely limited support for GROUPBY function. Partial response to [Issue #4282](https://github.com/PHPOffice/PhpSpreadsheet/issues/4282) [PR #4283](https://github.com/PHPOffice/PhpSpreadsheet/pull/4283) ## 2024-12-26 - 3.7.0 diff --git a/docs/references/function-list-by-category.md b/docs/references/function-list-by-category.md index 447c97367e..e6a702028f 100644 --- a/docs/references/function-list-by-category.md +++ b/docs/references/function-list-by-category.md @@ -249,6 +249,7 @@ EXPAND | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Choos FILTER | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Filter::filter FORMULATEXT | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Formula::text GETPIVOTDATA | **Not yet Implemented** +GROUPBY | **Not yet Implemented** HLOOKUP | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\HLookup::lookup HYPERLINK | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Hyperlink::set INDEX | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Matrix::index diff --git a/docs/references/function-list-by-name.md b/docs/references/function-list-by-name.md index 868da519a2..3696b95b7c 100644 --- a/docs/references/function-list-by-name.md +++ b/docs/references/function-list-by-name.md @@ -239,6 +239,7 @@ GCD | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpread GEOMEAN | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages\Mean::geometric GESTEP | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering\Compare::GESTEP GETPIVOTDATA | CATEGORY_LOOKUP_AND_REFERENCE | **Not yet Implemented** +GROUPBY | CATEGORY_LOOKUP_AND_REFERENCE | **Not yet Implemented** GROWTH | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Trends::GROWTH ## H diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index c31ea2c749..b256aad3b3 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -1256,6 +1256,11 @@ public static function getExcelConstants(string $key): bool|null 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2+', ], + 'GROUPBY' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3-7', + ], 'GROWTH' => [ 'category' => Category::CATEGORY_STATISTICAL, 'functionCall' => [Statistical\Trends::class, 'GROWTH'], @@ -4601,7 +4606,7 @@ private static function dataTestReference(array &$operandData): mixed private static int $matchIndex10 = 10; /** - * @return array|false + * @return array|false|string */ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell $cell = null) { @@ -5182,6 +5187,9 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell } elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token, $matches)) { // if the token is a named range or formula, evaluate it and push the result onto the stack $definedName = $matches[6]; + if (str_starts_with($definedName, '_xleta')) { + return Functions::NOT_YET_IMPLEMENTED; + } if ($cell === null || $pCellWorksheet === null) { return $this->raiseFormulaError("undefined name '$token'"); } @@ -5214,6 +5222,7 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell } $result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack, $specifiedWorksheet !== ''); + if (isset($storeKey)) { $branchStore[$storeKey] = $result; } diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index cb6b451cd4..6d30ab0f52 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -49,6 +49,8 @@ class Worksheet public const MERGE_CELL_CONTENT_HIDE = 'hide'; public const MERGE_CELL_CONTENT_MERGE = 'merge'; + public const FUNCTION_LIKE_GROUPBY = '/\\b(groupby|_xleta)\\b/i'; // weird new syntax + protected const SHEET_NAME_REQUIRES_NO_QUOTES = '/^[_\p{L}][_\p{L}\p{N}]*$/mui'; /** @@ -3701,7 +3703,9 @@ public function calculateArrays(bool $preCalculateFormulas = true): void $keys = $this->cellCollection->getCoordinates(); foreach ($keys as $key) { if ($this->getCell($key)->getDataType() === DataType::TYPE_FORMULA) { - $this->getCell($key)->getCalculatedValue(); + if (preg_match(self::FUNCTION_LIKE_GROUPBY, $this->getCell($key)->getValue()) !== 1) { + $this->getCell($key)->getCalculatedValue(); + } } } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php b/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php index a563bd9130..16834dc04d 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php @@ -142,6 +142,7 @@ class FunctionPrefix . '|drop' . '|expand' . '|filter' + . '|groupby' . '|hstack' . '|isomitted' . '|lambda' diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 28af258297..929b4f8c09 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -1578,7 +1578,11 @@ private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksh $mappedType = $pCell->getDataType(); if ($mappedType === DataType::TYPE_FORMULA) { if ($this->useDynamicArrays) { - $tempCalc = $pCell->getCalculatedValue(); + if (preg_match(PhpspreadsheetWorksheet::FUNCTION_LIKE_GROUPBY, $cellValue) === 1) { + $tempCalc = []; + } else { + $tempCalc = $pCell->getCalculatedValue(); + } if (is_array($tempCalc)) { $objWriter->writeAttribute('cm', '1'); } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/GroupByLimitedTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/GroupByLimitedTest.php new file mode 100644 index 0000000000..e41b2190ed --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/GroupByLimitedTest.php @@ -0,0 +1,33 @@ +load(self::$testbook); + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + $reloadedSheet = $reloadedSpreadsheet->getActiveSheet(); + self::assertSame(['t' => 'array', 'ref' => 'E3:F7'], $reloadedSheet->getCell('E3')->getFormulaAttributes()); + $expected = [ + ['Design', '$505,000 '], + ['Development', '$346,000 '], + ['Marketing', '$491,000 '], + ['Research', '$573,000 '], + ['Total', '$1,915,000 '], + [null, null], + ]; + self::assertSame($expected, $reloadedSheet->rangeToArray('E3:F8')); + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Reader/XLSX/excel-groupby-one.xlsx b/tests/data/Reader/XLSX/excel-groupby-one.xlsx new file mode 100644 index 0000000000..dbb92b6057 Binary files /dev/null and b/tests/data/Reader/XLSX/excel-groupby-one.xlsx differ