From ef2b5b9e001bc239efedf4578b5f71c14b0ff2ae Mon Sep 17 00:00:00 2001
From: oleibman <10341515+oleibman@users.noreply.github.com>
Date: Fri, 21 Jun 2024 21:55:05 -0700
Subject: [PATCH] Mostly Docs and Tests
Also support for Xml format.
---
docs/topics/recipes.md | 76 ++++++-------
.../Internal/ExcelArrayPseudoFunctions.php | 2 +-
src/PhpSpreadsheet/Reader/Xml.php | 8 ++
src/PhpSpreadsheet/Worksheet/Worksheet.php | 20 ++++
.../Calculation/InternalFunctionsTest.php | 4 +
.../Functional/ArrayFunctionsSpillTest.php | 48 +++++++++
.../Reader/Gnumeric/ArrayFormulaTest.php | 20 +++-
.../Reader/Ods/ArrayFormulaTest.php | 101 ++++++++++++++++++
.../Reader/Xml/ArrayFormulaTest.php | 26 +++++
.../Writer/Ods/ArrayTest.php | 16 ++-
.../Writer/Xlsx/ArrayFunctionsTest.php | 66 +++++++++++-
tests/data/Reader/Ods/ArrayFormulaTest.ods | Bin 0 -> 14534 bytes
tests/data/Reader/Xml/ArrayFormula.xml | 75 +++++++++++++
13 files changed, 410 insertions(+), 52 deletions(-)
create mode 100644 tests/PhpSpreadsheetTests/Reader/Ods/ArrayFormulaTest.php
create mode 100644 tests/PhpSpreadsheetTests/Reader/Xml/ArrayFormulaTest.php
create mode 100644 tests/data/Reader/Ods/ArrayFormulaTest.ods
create mode 100644 tests/data/Reader/Xml/ArrayFormula.xml
diff --git a/docs/topics/recipes.md b/docs/topics/recipes.md
index a1cfebc5e..5344d95a7 100644
--- a/docs/topics/recipes.md
+++ b/docs/topics/recipes.md
@@ -376,7 +376,7 @@ $value = $spreadsheet->getActiveSheet()->getCell('B8')->getCalculatedValue();
### Array Formulas
With version 2.0.3 of PhpSpreadsheet, we've introduced support for Excel "array formulas".
-It is an opt-in feature. You need to enable it with the following code:
+**It is an opt-in feature.** You need to enable it with the following code:
```php
\PhpOffice\PhpSpreadsheet\Calculation\Calculation::setArrayReturnType(
\PhpOffice\PhpSpreadsheet\Calculation\Calculation::RETURN_ARRAY_AS_ARRAY);
@@ -405,15 +405,16 @@ However, we could have specified an alternative formula to calculate that result

-Entering the formula `=SUM(B2:B6*C2:C6)` will calculate the same result; but because it's using arrays, we need to enter it as an "array formula". In MS Excel itself, we'd do this by using `Shift-Ctrl-Enter` rather than simply `Enter` when we define the formula in the formula edit box. MS Excel then shows that this is an array formula in the formula edit box by wrapping it in the `{}` braces (you don't enter these in the formula yourself; MS Excel does it).
-In recent releases of Excel, Shift-Ctrl-Enter is not required, and Excel does not add the braces.
-PhpSpreadsheet will attempt to behave like the recent releases.
+Entering the formula `=SUM(B2:B6*C2:C6)` will calculate the same result; but because it's using arrays, we need to enter it as an "array formula". In MS Excel itself, we'd do this by using `Ctrl-Shift-Enter` rather than simply `Enter` when we define the formula in the formula edit box. MS Excel then shows that this is an array formula in the formula edit box by wrapping it in the `{}` braces (you don't enter these in the formula yourself; MS Excel does it).
+
+**In recent releases of Excel, Ctrl-Shift-Enter is not required, and Excel does not add the braces.
+PhpSpreadsheet will attempt to behave like the recent releases.**
Or to identify the biggest increase in like-for-like sales from one month to the next:

```php
-$spreadsheet->getActiveSheet()->setCellValue('F1','=MAX(B2:B6-C2:C6)', true);
+$spreadsheet->getActiveSheet()->setCellValue('F1','=MAX(B2:B6-C2:C6)');
```
Which tells us that the biggest increase in sales between December and January was 30 more (in this case, 30 more Lemons).
@@ -424,8 +425,8 @@ As an example, consider transposing a grid of data: MS Excel provides the `TRANS

-When we do this in MS Excel, we need to indicate ___all___ the cells that will contain the transposed data from cells `A1` to `D7`. We do this by selecting the cells where we want to display our transposed data either by holding the left mouse button down while we move with the mouse, or pressing `Shift` and using the arrow keys.
-Once we've selected all the cells to hold our data, then we enter the formula `TRANSPOSE(A1:D7)` in the formula edit box, remembering to use `Shift-Ctrl-Enter` to tell MS Excel that this is an array formula.
+When we do this in MS Excel, we used to need to indicate ___all___ the cells that will contain the transposed data from cells `A1` to `D7`. We do this by selecting the cells where we want to display our transposed data either by holding the left mouse button down while we move with the mouse, or pressing `Shift` and using the arrow keys.
+Once we've selected all the cells to hold our data, then we enter the formula `TRANSPOSE(A1:D7)` in the formula edit box, remembering to use `Ctrl-Shift-Enter` to tell MS Excel that this is an array formula. In recent Excel, you can just enter `=TRANSPOSE(A1:D7)` into cell A10.
Note also that we still set this as the formula for the top-left cell of that range, cell `A10`.
@@ -434,66 +435,40 @@ Simply setting an array formula in a cell and specifying the range won't populat
$spreadsheet->getActiveSheet()
->setCellValue(
'A10',
- '=SEQUENCE(3,3)',
- true,
- 'A1:C3'
+ '=SEQUENCE(3,3)'
);
-
// Will return a null, because the formula for A1 hasn't been calculated to populate the spillage area
$result = $spreadsheet->getActiveSheet()->getCell('C3')->getValue();
```
To do that, we need to retrieve the calculated value for the cell.
```php
-$spreadsheet->getActiveSheet()
- ->setCellValue(
- 'A10',
- '=SEQUENCE(3,3)',
- true,
- 'A1:C3'
- );
-
$spreadsheet->getActiveSheet()->getCell('A1')->getCalculatedValue();
-
// Will return 9, because the formula for A1 has now been calculated, and the spillage area is populated
$result = $spreadsheet->getActiveSheet()->getCell('C3')->getValue();
```
-When we call `getCalculatedValue()` for a cell that contains an array formula, PhpSpreadsheet returns the single value that would appear in that cell in MS Excel.
+If returning arrays has been enabled, `getCalculatedValue` will return an array when appropriate, and will populate the spill range. If returning arrays has not been enabled, when we call `getCalculatedValue()` for a cell that contains an array formula, PhpSpreadsheet will return the single value from the topmost leftmost cell, and will leave other cells unchanged.
```php
// Will return integer 1, the value for that cell within the array
$a1result = $spreadsheet->getActiveSheet()->getCell('A1')->getCalculatedValue();
```
-If we want to return the full array, then we need to call `getCalculatedValue()` with an additional argument, a boolean `true` to return the value as an array.
-```php
-// Will return an array [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
-$a1result = $spreadsheet->getActiveSheet()->getCell('A1')->getCalculatedValue(true);
-```
-
---
Excel365 introduced a number of new functions that return arrays of results.
These include the `UNIQUE()`, `SORT()`, `SORTBY()`, `FILTER()`, `SEQUENCE()` and `RANDARRAY()` functions.
While not all of these have been implemented by the Calculation Engine in PhpSpreadsheet, so they cannot all be calculated within your PHP applications, they can still be read from and written to Xlsx files.
-The way these functions are presented in MS Excel itself is slightly different to that of other array functions.
-
The `SEQUENCE()` function generates a series of values (in this case, starting with `-10` and increasing in steps of `2.5`); and here we're telling the formula to populate a 3x3 grid with these values.

-Note that this is visually different to the multi-cell array formulas like `TRANSPOSE()`. When we are positioned in the "spill" range for the grid, MS Excel highlights the area with a blue border; and the formula displayed in the formula editing field isn't wrapped in braces (`{}`).
+Note that this is visually different from using `Ctrl-Shift-Enter` for the formula. When we are positioned in the "spill" range for the grid, MS Excel highlights the area with a blue border; and the formula displayed in the formula editing field isn't wrapped in braces (`{}`).
And if we select any other cell inside the "spill" area other than the top-left cell, the formula in the formula edit field is greyed rather than displayed in black.

-When we enter this formula in MS Excel, we don't need to select the range of cells that it should occupy; nor do we need to enter it using `Ctrl-Shift-Enter`. MS Excel identifies that it is a multi-cell array formula because of the function that it uses, the `SEQUENCE()` function (and if there are nested function calls in the formula, then it must be the outermost functionin the tree).
-
-However, PhpSpreadsheet isn't quite as intelligent (yet) and doesn't parse the formula to identify if it should be treated as an array formula or not; a formula is just a string of characters until it is actually evaluated. If we want to use this function through code, we still need to specify that it is an "array" function with the `$isArrayFormula` argument, and the range of cells that it should cover.
-
-```php
-$spreadsheet->getActiveSheet()->setCellValue('A1','=SEQUENCE(3,3,-10,2.5)', true, 'A1:C3');
-```
+When we enter this formula in MS Excel, we don't need to select the range of cells that it should occupy; nor do we need to enter it using `Ctrl-Shift-Enter`.
### The Spill Operator
@@ -502,19 +477,34 @@ To simplify this, MS Excel has introduced the "Spill" Operator (`#`).

-Using our `SEQUENCE()"`example, where the formula cell is `A1` and the result spills across the range `A1:C3`, we can use the Spill operator `A1#` to reference all the cells in that spillage range.
+Using our `SEQUENCE()`example, where the formula cell is `A1` and the result spills across the range `A1:C3`, we can use the Spill operator `A1#` to reference all the cells in that spillage range.
In this case, we're taking the absolute value of each cell in that range, and adding them together using the `SUM()` function to give us a result of 50.
-PhpSpreadsheet doesn't currently support entry of a formula like this directly; but interally MS Excel implements the Spill Operator as a function (`ANCHORARRAY()`). MS Excel itself doesn't allow you to use this function in a formula, you have to use the "Spill" operator; but PhpSpreadsheet does allow you to use this internal Excel function.
+PhpSpreadsheet supports entry of a formula like this using the Spill operator. Alternatively, MS Excel internally implements the Spill Operator as a function (`ANCHORARRAY()`). MS Excel itself doesn't allow you to use this function in a formula, you have to use the "Spill" operator; but PhpSpreadsheet does allow you to use this internal Excel function. PhpSpreadsheet will convert the spill operator to ANCHORARRAY on write (so it may appear that your formula has changed, but it hasn't really); it is not necessary to convert it back on read.
To create this same function in PhpSpreadsheet, use:
```php
-$spreadsheet->getActiveSheet()->setCellValue('D1','=SUM(ABS(ANCHORARRAY(A1)))', true);
+$spreadsheet->getActiveSheet()->setCellValue('D1','=SUM(ABS(ANCHORARRAY(A1)))');
```
-Note that this does need to be flagged as an array function with the `$isArrayFormula` argument.
When the file is saved, and opened in MS Excel, it will be rendered correctly.
+### The At-sign Operator
+
+If you want to reference just the first cell of an array formula within another formula, you could do so by prefixing it with an at-sign. You can also select the entry in a range which matches the current row in this way; so, if you enter `=@A1:A5` in cell G3, the result will be the value from A3. MS Excel again implements this under the covers by converting to a function SINGLE. PhpSpreadsheet allows the use of the SINGLE function. It does not yet support the at-sign operator, which can have a different meaning in other contexts.
+
+### Updating Cell in Spill Area
+
+Excel prevents you from updating a cell in the spill area. PhpSpreadsheet does not - it seems like it might be quite expensive, needing to reevaluate the entire worksheet with each `setValue`. PhpSpreadsheet does provide a method to be used prior to calling `setValue` if desired.
+```php
+$sheet->setCellValue('A1', '=SORT{7;5;1}');
+$sheet->getCell('A1')->getCalculatedValue(); // populates A1-A3
+$sheet->isCellInSpillRange('A2'); // true
+$sheet->isCellInSpillRange('A3'); // true
+$sheet->isCellInSpillRange('A4'); // false
+$sheet->isCellInSpillRange('A1'); // false
+```
+The last result might be surprising. Excel allows you to alter the formula cell itself, so `isCellInSpillRange` treats the formula cell as not in range. It should also be noted that, if array returns are not enabled, `isCellInSpillRange` will always return `false`.
## Locale Settings for Formulas
@@ -552,7 +542,7 @@ $spreadsheet->getActiveSheet()->setCellValue('B8',$internalFormula);
```
Currently, formula translation only translates the function names, the
-constants TRUE and FALSE, and the function argument separators. Cell addressing using R1C1 formatting is not supported.
+constants TRUE and FALSE (and NULL), Excel error messages, and the function argument separators. Cell addressing using R1C1 formatting is not supported.
At present, the following locale settings are supported:
@@ -576,6 +566,8 @@ Russian | русский язык | ru
Swedish | Svenska | sv
Turkish | Türkçe | tr
+If anybody can provide translations for additional languages, particularly Basque (Euskara), Catalan (Català), Croatian (Hrvatski jezik), Galician (Galego), Greek (Ελληνικά), Slovak (Slovenčina) or Slovenian (Slovenščina); please feel free to volunteer your services, and we'll happily show you what is needed to contribute a new language.
+
## Write a newline character "\n" in a cell (ALT+"Enter")
In Microsoft Office Excel you get a line break in a cell by hitting
diff --git a/src/PhpSpreadsheet/Calculation/Internal/ExcelArrayPseudoFunctions.php b/src/PhpSpreadsheet/Calculation/Internal/ExcelArrayPseudoFunctions.php
index df7ccc5ef..2bccab8da 100644
--- a/src/PhpSpreadsheet/Calculation/Internal/ExcelArrayPseudoFunctions.php
+++ b/src/PhpSpreadsheet/Calculation/Internal/ExcelArrayPseudoFunctions.php
@@ -20,7 +20,7 @@ class ExcelArrayPseudoFunctions
$ourRow = $cell->getRow();
$firstRow = (int) $matches[3];
$lastRow = (int) $matches[6];
- if ($ourRow < $firstRow || $ourRow > $lastRow) {
+ if ($ourRow < $firstRow || $ourRow > $lastRow || $matches[1] !== $matches[4]) {
return ExcelError::VALUE();
}
$referenceCellCoordinate = $matches[1] . $ourRow;
diff --git a/src/PhpSpreadsheet/Reader/Xml.php b/src/PhpSpreadsheet/Reader/Xml.php
index 979e74c5e..e86fcd4c7 100644
--- a/src/PhpSpreadsheet/Reader/Xml.php
+++ b/src/PhpSpreadsheet/Reader/Xml.php
@@ -403,11 +403,16 @@ class Xml extends BaseReader
$columnID = 'A';
foreach ($rowData->Cell as $cell) {
+ $arrayRef = '';
$cell_ss = self::getAttributes($cell, self::NAMESPACES_SS);
if (isset($cell_ss['Index'])) {
$columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']);
}
$cellRange = $columnID . $rowID;
+ if (isset($cell_ss['ArrayRange'])) {
+ $arrayRange = (string) $cell_ss['ArrayRange'];
+ $arrayRef = AddressHelper::convertFormulaToA1($arrayRange, $rowID, Coordinate::columnIndexFromString($columnID));
+ }
if ($this->getReadFilter() !== null) {
if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
@@ -440,6 +445,9 @@ class Xml extends BaseReader
if (isset($cell_ss['Formula'])) {
$cellDataFormula = $cell_ss['Formula'];
$hasCalculatedValue = true;
+ if ($arrayRef !== '') {
+ $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setFormulaAttributes(['t' => 'array', 'ref' => $arrayRef]);
+ }
}
if (isset($cell->Data)) {
$cellData = $cell->Data;
diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php
index 24d0c6365..8119547d3 100644
--- a/src/PhpSpreadsheet/Worksheet/Worksheet.php
+++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php
@@ -3695,4 +3695,24 @@ class Worksheet implements IComparable
}
}
}
+
+ public function isCellInSpillRange(string $coordinate): bool
+ {
+ if (Calculation::getArrayReturnType() !== Calculation::RETURN_ARRAY_AS_ARRAY) {
+ return false;
+ }
+ $this->calculateArrays();
+ $keys = $this->cellCollection->getCoordinates();
+ foreach ($keys as $key) {
+ $attributes = $this->getCell($key)->getFormulaAttributes();
+ if (isset($attributes['ref'])) {
+ if (Coordinate::coordinateIsInsideRange($attributes['ref'], $coordinate)) {
+ // false for first cell in range, true otherwise
+ return $coordinate !== $key;
+ }
+ }
+ }
+
+ return false;
+ }
}
diff --git a/tests/PhpSpreadsheetTests/Calculation/InternalFunctionsTest.php b/tests/PhpSpreadsheetTests/Calculation/InternalFunctionsTest.php
index 9914d7302..d51d9a5fa 100644
--- a/tests/PhpSpreadsheetTests/Calculation/InternalFunctionsTest.php
+++ b/tests/PhpSpreadsheetTests/Calculation/InternalFunctionsTest.php
@@ -113,6 +113,10 @@ class InternalFunctionsTest extends TestCase
'G3:G5',
'#VALUE!',
],
+ 'range which includes current row but spans columns' => [
+ 'F7:G9',
+ '#VALUE!',
+ ],
];
}
}
diff --git a/tests/PhpSpreadsheetTests/Functional/ArrayFunctionsSpillTest.php b/tests/PhpSpreadsheetTests/Functional/ArrayFunctionsSpillTest.php
index 52d4c75a6..408ab9904 100644
--- a/tests/PhpSpreadsheetTests/Functional/ArrayFunctionsSpillTest.php
+++ b/tests/PhpSpreadsheetTests/Functional/ArrayFunctionsSpillTest.php
@@ -37,6 +37,12 @@ class ArrayFunctionsSpillTest extends TestCase
$sheet->setCellValue('B1', '=UNIQUE(A1:A12)');
$expected = [['#SPILL!'], [null], [null], [null], ['OCCUPIED']];
self::assertSame($expected, $sheet->rangeToArray('B1:B5', calculateFormulas: true, formatData: false, reduceArrays: true), 'spill with B5 unchanged');
+ self::assertFalse($sheet->isCellInSpillRange('B1'));
+ self::assertFalse($sheet->isCellInSpillRange('B2'));
+ self::assertFalse($sheet->isCellInSpillRange('B3'));
+ self::assertFalse($sheet->isCellInSpillRange('B4'));
+ self::assertFalse($sheet->isCellInSpillRange('B5'));
+ self::assertFalse($sheet->isCellInSpillRange('Z9'));
$calculation->clearCalculationCache();
$columnArray = [[1], [2], [2], [2], [3], [3], [3], [3], [4], [4], [4], [4]];
@@ -44,6 +50,12 @@ class ArrayFunctionsSpillTest extends TestCase
$sheet->setCellValue('B1', '=UNIQUE(A1:A12)');
$expected = [[1], [2], [3], [4], ['OCCUPIED']];
self::assertSame($expected, $sheet->rangeToArray('B1:B5', calculateFormulas: true, formatData: false, reduceArrays: true), 'fill B1:B4 with B5 unchanged');
+ self::assertFalse($sheet->isCellInSpillRange('B1'));
+ self::assertTrue($sheet->isCellInSpillRange('B2'));
+ self::assertTrue($sheet->isCellInSpillRange('B3'));
+ self::assertTrue($sheet->isCellInSpillRange('B4'));
+ self::assertFalse($sheet->isCellInSpillRange('B5'));
+ self::assertFalse($sheet->isCellInSpillRange('Z9'));
$calculation->clearCalculationCache();
$columnArray = [[1], [3], [3], [3], [3], [3], [3], [3], [3], [3], [3], [3]];
@@ -51,6 +63,12 @@ class ArrayFunctionsSpillTest extends TestCase
$sheet->setCellValue('B1', '=UNIQUE(A1:A12)');
$expected = [[1], [3], [null], [null], ['OCCUPIED']];
self::assertSame($expected, $sheet->rangeToArray('B1:B5', calculateFormulas: true, formatData: false, reduceArrays: true), 'fill B1:B2(changed from prior) set B3:B4 to null B5 unchanged');
+ self::assertFalse($sheet->isCellInSpillRange('B1'));
+ self::assertTrue($sheet->isCellInSpillRange('B2'));
+ self::assertFalse($sheet->isCellInSpillRange('B3'));
+ self::assertFalse($sheet->isCellInSpillRange('B4'));
+ self::assertFalse($sheet->isCellInSpillRange('B5'));
+ self::assertFalse($sheet->isCellInSpillRange('Z9'));
$calculation->clearCalculationCache();
$columnArray = [[1], [2], [3], [3], [3], [3], [3], [3], [3], [3], [3], [3]];
@@ -67,6 +85,36 @@ class ArrayFunctionsSpillTest extends TestCase
self::assertSame($expected, $sheet->rangeToArray('B1:B5', calculateFormulas: true, formatData: false, reduceArrays: true), 'spill clears B2:B4 with B5 unchanged');
$calculation->clearCalculationCache();
+ $sheet->setCellValue('Z1', '=SORT({7;5;1})');
+ $sheet->getCell('Z1')->getCalculatedValue(); // populates Z1-Z3
+ self::assertTrue($sheet->isCellInSpillRange('Z2'));
+ self::assertTrue($sheet->isCellInSpillRange('Z3'));
+ self::assertFalse($sheet->isCellInSpillRange('Z4'));
+ self::assertFalse($sheet->isCellInSpillRange('Z1'));
+
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testNonArrayOutput(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $calculation = Calculation::getInstance($spreadsheet);
+
+ $sheet = $spreadsheet->getActiveSheet();
+ $sheet->setCellValue('B5', 'OCCUPIED');
+
+ $columnArray = [[1], [2], [2], [2], [3], [3], [3], [3], [4], [4], [4], [4]];
+ $sheet->fromArray($columnArray, 'A1');
+ $sheet->setCellValue('B1', '=UNIQUE(A1:A12)');
+ $expected = [[1], [null], [null], [null], ['OCCUPIED']];
+ self::assertSame($expected, $sheet->rangeToArray('B1:B5', calculateFormulas: true, formatData: false, reduceArrays: true), 'only fill B1');
+ self::assertFalse($sheet->isCellInSpillRange('B1'));
+ self::assertFalse($sheet->isCellInSpillRange('B2'));
+ self::assertFalse($sheet->isCellInSpillRange('B3'));
+ self::assertFalse($sheet->isCellInSpillRange('B4'));
+ self::assertFalse($sheet->isCellInSpillRange('B5'));
+ self::assertFalse($sheet->isCellInSpillRange('Z9'));
+
$spreadsheet->disconnectWorksheets();
}
diff --git a/tests/PhpSpreadsheetTests/Reader/Gnumeric/ArrayFormulaTest.php b/tests/PhpSpreadsheetTests/Reader/Gnumeric/ArrayFormulaTest.php
index cba92a6d3..861a2e05e 100644
--- a/tests/PhpSpreadsheetTests/Reader/Gnumeric/ArrayFormulaTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Gnumeric/ArrayFormulaTest.php
@@ -28,7 +28,7 @@ class ArrayFormulaTest extends TestCase
string $cellAddress,
string $expectedRange,
string $expectedFormula,
- array $expectedValue
+ array|float $expectedValue
): void {
$filename = 'tests/data/Reader/Gnumeric/ArrayFormulaTest.gnumeric';
$reader = new Gnumeric();
@@ -37,13 +37,21 @@ class ArrayFormulaTest extends TestCase
$cell = $worksheet->getCell($cellAddress);
self::assertSame(DataType::TYPE_FORMULA, $cell->getDataType());
- self::assertSame(['t' => 'array', 'ref' => $expectedRange], $cell->getFormulaAttributes());
+ if (is_array($expectedValue)) {
+ self::assertSame(['t' => 'array', 'ref' => $expectedRange], $cell->getFormulaAttributes());
+ } else {
+ self::assertEmpty($cell->getFormulaAttributes());
+ }
self::assertSame($expectedFormula, strtoupper($cell->getValue()));
Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
$worksheet->calculateArrays();
$cell = $worksheet->getCell($cellAddress);
self::assertSame($expectedValue, $cell->getCalculatedValue());
- self::assertSame($expectedValue, $worksheet->rangeToArray($expectedRange, formatData: false, reduceArrays: true));
+ if (is_array($expectedValue)) {
+ self::assertSame($expectedValue, $worksheet->rangeToArray($expectedRange, formatData: false, reduceArrays: true));
+ } else {
+ self::assertSame([[$expectedValue]], $worksheet->rangeToArray($expectedRange, formatData: false, reduceArrays: true));
+ }
$spreadsheet->disconnectWorksheets();
}
@@ -62,6 +70,12 @@ class ArrayFormulaTest extends TestCase
'=SIN({-1,0,1,2})',
[[-0.8414709848078965, 0.0, 0.8414709848078965, 0.9092974268256817]],
],
+ [
+ 'G3',
+ 'G3:G3',
+ '=MAX(SIN({-1,0,1,2}))',
+ 0.9092974268256817,
+ ],
[
'D4',
'D4:E5',
diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/ArrayFormulaTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/ArrayFormulaTest.php
new file mode 100644
index 000000000..2b37d3314
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Ods/ArrayFormulaTest.php
@@ -0,0 +1,101 @@
+arrayReturnType = Calculation::getArrayReturnType();
+ }
+
+ protected function tearDown(): void
+ {
+ Calculation::setArrayReturnType($this->arrayReturnType);
+ }
+
+ /**
+ * @dataProvider arrayFormulaReaderProvider
+ */
+ public function testArrayFormulaReader(
+ string $cellAddress,
+ string $expectedRange,
+ string $expectedFormula,
+ array|float $expectedValue
+ ): void {
+ $filename = 'tests/data/Reader/Ods/ArrayFormulaTest.ods';
+ $reader = new Ods();
+ $spreadsheet = $reader->load($filename);
+ $worksheet = $spreadsheet->getActiveSheet();
+
+ $cell = $worksheet->getCell($cellAddress);
+ self::assertSame(DataType::TYPE_FORMULA, $cell->getDataType());
+ self::assertSame(['t' => 'array', 'ref' => $expectedRange], $cell->getFormulaAttributes());
+ self::assertSame($expectedFormula, strtoupper($cell->getValue()));
+ Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
+ $worksheet->calculateArrays();
+ $cell = $worksheet->getCell($cellAddress);
+ self::assertSame($expectedValue, $cell->getCalculatedValue());
+ if (is_array($expectedValue)) {
+ self::assertSame($expectedValue, $worksheet->rangeToArray($expectedRange, formatData: false, reduceArrays: true));
+ } else {
+ self::assertSame([[$expectedValue]], $worksheet->rangeToArray($expectedRange, formatData: false, reduceArrays: true));
+ }
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public static function arrayFormulaReaderProvider(): array
+ {
+ return [
+ [
+ 'B2',
+ 'B2:C3',
+ '={2,3}*{4;5}',
+ [[8, 12], [10, 15]],
+ ],
+ [
+ 'E1',
+ 'E1:H1',
+ '=SIN({-1,0,1,2})',
+ [[-0.8414709848078965, 0.0, 0.8414709848078965, 0.9092974268256817]],
+ ],
+ [
+ 'E3',
+ 'E3:E3',
+ '=MAX(SIN({-1,0,1,2}))',
+ 0.9092974268256817,
+ ],
+ [
+ 'D5',
+ 'D5:E6',
+ '=A5:B5*A5:A6',
+ [[4, 6], [8, 12]],
+ ],
+ [
+ 'D8',
+ 'D8:E9',
+ '=A8:B8*A8:A9',
+ [[9, 12], [15, 20]],
+ ],
+ [
+ 'D11',
+ 'D11:E12',
+ '=A11:B11*A11:A12',
+ [[16, 20], [24, 30]],
+ ],
+ [
+ 'D14',
+ 'D14:E15',
+ '=A14:B14*A14:A15',
+ [[25, 30], [35, 42]],
+ ],
+ ];
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/ArrayFormulaTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/ArrayFormulaTest.php
new file mode 100644
index 000000000..6ba32aa33
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xml/ArrayFormulaTest.php
@@ -0,0 +1,26 @@
+load($filename);
+ $sheet = $spreadsheet->getActiveSheet();
+
+ self::assertSame(DataType::TYPE_FORMULA, $sheet->getCell('B1')->getDataType());
+ self::assertSame(['t' => 'array', 'ref' => 'B1:B3'], $sheet->getCell('B1')->getFormulaAttributes());
+ self::assertSame('=CONCATENATE(A1:A3,"-",C1:C3)', strtoupper($sheet->getCell('B1')->getValue()));
+ self::assertSame('a-1', $sheet->getCell('B1')->getOldCalculatedValue());
+ self::assertSame('b-2', $sheet->getCell('B2')->getValue());
+ self::assertSame('c-3', $sheet->getCell('B3')->getValue());
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Ods/ArrayTest.php b/tests/PhpSpreadsheetTests/Writer/Ods/ArrayTest.php
index 07f945477..281b1c865 100644
--- a/tests/PhpSpreadsheetTests/Writer/Ods/ArrayTest.php
+++ b/tests/PhpSpreadsheetTests/Writer/Ods/ArrayTest.php
@@ -63,12 +63,20 @@ class ArrayTest extends AbstractFunctional
$sheet->getCell('B1')->setValue('=UNIQUE(A1:A3)');
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Ods');
$sheet = $reloadedSpreadsheet->getActiveSheet();
- self::assertEquals('1', $sheet->getCell('A1')->getValue());
- self::assertEquals('1', $sheet->getCell('A2')->getValue());
- self::assertEquals('3', $sheet->getCell('A3')->getValue());
- self::assertEquals('3', $sheet->getCell('B2')->getValue());
+ self::assertSame(1, $sheet->getCell('A1')->getValue());
+ self::assertSame(1, $sheet->getCell('A2')->getValue());
+ self::assertSame(3, $sheet->getCell('A3')->getValue());
+ self::assertSame(3, $sheet->getCell('B2')->getValue());
+ self::assertTrue($sheet->getCell('B1')->isFormula());
+ self::assertSame(1, $sheet->getCell('B1')->getOldCalculatedValue());
self::assertNull($sheet->getCell('B3')->getValue());
self::assertEquals('=UNIQUE(A1:A3)', $sheet->getCell('B1')->getValue());
+ $cellFormulaAttributes = $sheet->getCell('B1')->getFormulaAttributes();
+ self::assertArrayHasKey('t', $cellFormulaAttributes);
+ self::assertSame('array', $cellFormulaAttributes['t']);
+ self::assertArrayHasKey('ref', $cellFormulaAttributes);
+ self::assertSame('B1:B2', $cellFormulaAttributes['ref']);
+ self::assertSame([[1], [3]], $sheet->getCell('B1')->getCalculatedValue());
$spreadsheet->disconnectWorksheets();
$reloadedSpreadsheet->disconnectWorksheets();
}
diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/ArrayFunctionsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/ArrayFunctionsTest.php
index d7265651d..038f8c235 100644
--- a/tests/PhpSpreadsheetTests/Writer/Xlsx/ArrayFunctionsTest.php
+++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/ArrayFunctionsTest.php
@@ -333,10 +333,18 @@ class ArrayFunctionsTest extends TestCase
self::assertSame($expectedUnique, $sheet2->getCell('H1')->getCalculatedValue());
for ($row = 1; $row <= 5; ++$row) {
if ($row > 1) {
- self::assertSame($expectedUnique[$row - 1][0], $sheet2->getCell("H$row")->getCalculatedValue(), "cell H$row");
+ self::assertSame($expectedUnique[$row - 1][0], $sheet2->getCell("H$row")->getValue(), "cell H$row");
+ } else {
+ self::assertTrue($sheet2->getCell("H$row")->isFormula());
+ self::assertSame($expectedUnique[$row - 1][0], $sheet2->getCell("H$row")->getOldCalculatedValue(), "cell H$row");
}
- self::assertSame($expectedUnique[$row - 1][1], $sheet2->getCell("I$row")->getCalculatedValue(), "cell I$row");
+ self::assertSame($expectedUnique[$row - 1][1], $sheet2->getCell("I$row")->getValue(), "cell I$row");
}
+ $cellFormulaAttributes = $sheet2->getCell('H1')->getFormulaAttributes();
+ self::assertArrayHasKey('t', $cellFormulaAttributes);
+ self::assertSame('array', $cellFormulaAttributes['t']);
+ self::assertArrayHasKey('ref', $cellFormulaAttributes);
+ self::assertSame('H1:I5', $cellFormulaAttributes['ref']);
$spreadsheet2->disconnectWorksheets();
}
@@ -374,4 +382,58 @@ class ArrayFunctionsTest extends TestCase
self::assertSame('x', $sheet2->getCell('A3')->getValue());
$spreadsheet2->disconnectWorksheets();
}
+
+ public function testArrayStringOutput(): void
+ {
+ Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $columnArray = [
+ ['item1'],
+ ['item2'],
+ ['item3'],
+ ['item1'],
+ ['item1'],
+ ['item6'],
+ ['item7'],
+ ['item1'],
+ ['item9'],
+ ['item1'],
+ ];
+ $sheet->fromArray($columnArray, 'A1');
+ $sheet->setCellValue('C1', '=UNIQUE(A1:A10)');
+ $writer = new XlsxWriter($spreadsheet);
+ $this->outputFile = File::temporaryFilename();
+ $writer->save($this->outputFile);
+ $spreadsheet->disconnectWorksheets();
+
+ $reader = new XlsxReader();
+ $spreadsheet2 = $reader->load($this->outputFile);
+ $sheet2 = $spreadsheet2->getActiveSheet();
+ $expectedUnique = [
+ ['item1'],
+ ['item2'],
+ ['item3'],
+ ['item6'],
+ ['item7'],
+ ['item9'],
+ ];
+ self::assertCount(6, $expectedUnique);
+ self::assertSame($expectedUnique, $sheet2->getCell('C1')->getCalculatedValue());
+ self::assertSame($expectedUnique[0][0], $sheet2->getCell('C1')->getCalculatedValueString());
+ for ($row = 2; $row <= 6; ++$row) {
+ self::assertSame($expectedUnique[$row - 1][0], $sheet2->getCell("C$row")->getCalculatedValue(), "cell C$row");
+ }
+ $spreadsheet2->disconnectWorksheets();
+
+ $file = 'zip://';
+ $file .= $this->outputFile;
+ $file .= '#xl/worksheets/sheet1.xml';
+ $data = file_get_contents($file);
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } else {
+ self::assertStringContainsString('_xlfn.UNIQUE(A1:A10)item1', $data, '6 results for UNIQUE');
+ }
+ }
}
diff --git a/tests/data/Reader/Ods/ArrayFormulaTest.ods b/tests/data/Reader/Ods/ArrayFormulaTest.ods
new file mode 100644
index 0000000000000000000000000000000000000000..84b5cd02a2e00241ecac09ec974974d129b8ee3d
GIT binary patch
literal 14534
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 i-%q*Qb+sTNBltyQDXLPQjPEiO$R&
z2jB*4ytN6VwJZS5)lpl&Q?qR;by6Aaku;iHzqS&$wg+eLj6Qb-f!L6CqDyzlv(1>1
z?B~Yf`OSNKF|TfSg+tLw6;Tn)&EJ1iiJLnUh&?-_r>u+TI!!
zZP;uaxx*DBZH;03+)p-W0IA!&{Gqx^crv4H>Q?QS8!0cb;%KGdLOSf3Wn50>bLudN
zrTjy+>xnq4He7hX?FG@#UT=Rj*v)+(pvQ{zv)3OW5+MA2GSScdxZm_EwdlXOem{YL
z@H0#0H)-A%|0#Ls&$a*N`*+00@88jXf%7|==Ayn
z$mE}~M87HaK9J~l9?yUC{W0G4XWa5{>br-z&&B&!6!U*`{W0w6XDsY*D!p&E#qY@2
zUyOej74@5O7Vqz4q<*3N684Xp_s`w5P5tM+_5bGlqxSfznSaw1*Wc)ARYhc!pO4|*
Oe=P5njv~*`U;ht;>*eVH
literal 0
HcmV?d00001
diff --git a/tests/data/Reader/Xml/ArrayFormula.xml b/tests/data/Reader/Xml/ArrayFormula.xml
new file mode 100644
index 000000000..602a1b8a8
--- /dev/null
+++ b/tests/data/Reader/Xml/ArrayFormula.xml
@@ -0,0 +1,75 @@
+
+
+
+
+ Owen Leibman
+ Owen Leibman
+ 2024-06-17T19:03:14Z
+ 2024-06-17T19:04:33Z
+ 16.00
+
+
+
+
+
+ 10300
+ 19420
+ 32767
+ 32767
+ False
+ False
+
+
+
+
+
+
+
+ | a |
+ a-1 |
+ 1 |
+
+
+ | b |
+ b-2 |
+ 2 |
+
+
+ | c |
+ c-3 |
+ 3 |
+
+
+
+
+
+
+
+
+
+
+
+ 3
+ 2
+
+
+ False
+ False
+
+
+