mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-09-12 19:16:45 +00:00
Unit tests for pseudo-functions
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Internal;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
@@ -15,12 +16,13 @@ class ExcelArrayPseudoFunctions
|
||||
$worksheet = $cell->getWorksheet();
|
||||
|
||||
[$referenceWorksheetName, $referenceCellCoordinate] = Worksheet::extractSheetTitle($cellReference, true);
|
||||
|
||||
$result = ($referenceWorksheetName === '')
|
||||
? $worksheet->getCell($referenceCellCoordinate)->getCalculatedValue()
|
||||
$referenceCell = ($referenceWorksheetName === '')
|
||||
? $worksheet->getCell($referenceCellCoordinate)
|
||||
: $worksheet->getParent()
|
||||
->getSheetByName($referenceWorksheetName)
|
||||
->getCell($referenceCellCoordinate)->getCalculatedValue();
|
||||
->getCell($referenceCellCoordinate);
|
||||
|
||||
$result = $referenceCell->getCalculatedValue();
|
||||
|
||||
return [[$result]];
|
||||
}
|
||||
@@ -32,30 +34,50 @@ class ExcelArrayPseudoFunctions
|
||||
$value = $cell->getValue();
|
||||
|
||||
[$referenceWorksheetName, $referenceCellCoordinate] = Worksheet::extractSheetTitle($cellReference, true);
|
||||
$referenceCell = ($referenceWorksheetName === '')
|
||||
? $worksheet->getCell($referenceCellCoordinate)
|
||||
: $worksheet->getParent()
|
||||
->getSheetByName($referenceWorksheetName)
|
||||
->getCell($referenceCellCoordinate);
|
||||
|
||||
// We should always use the sizing for the array formula range from the referenced cell formula
|
||||
$referenceRange = null;
|
||||
if ($referenceCell->isFormula() && $referenceCell->isArrayFormula()) {
|
||||
$referenceRange = $referenceCell->arrayFormulaRange();
|
||||
}
|
||||
|
||||
$calcEngine = Calculation::getInstance($worksheet->getParent());
|
||||
$result = $calcEngine->calculateCellValue(
|
||||
($referenceWorksheetName === '')
|
||||
? $worksheet->getCell($referenceCellCoordinate)
|
||||
: $worksheet->getParent()
|
||||
->getSheetByName($referenceWorksheetName)
|
||||
->getCell($referenceCellCoordinate)
|
||||
);
|
||||
$result = $calcEngine->calculateCellValue($referenceCell);
|
||||
if (!is_array($result)) {
|
||||
$result = [[$result]];
|
||||
}
|
||||
|
||||
// Set the result
|
||||
$worksheet->fromArray(
|
||||
$result,
|
||||
null,
|
||||
$coordinate,
|
||||
true
|
||||
);
|
||||
// Ensure that our array result dimensions match the specified array formula range dimensions,
|
||||
// from the referenced cell, expanding or shrinking it as necessary.
|
||||
if ($referenceRange !== null) {
|
||||
$result = Functions::resizeMatrix(
|
||||
$result,
|
||||
...Coordinate::rangeDimension($referenceRange ?? $coordinate)
|
||||
);
|
||||
}
|
||||
|
||||
// Set the result for our target cell (with spillage)
|
||||
// But if we do write it, we get problems with #SPILL! Errors if the spreadsheet is saved
|
||||
// TODO How are we going to identify and handle a #SPILL! or a #CALC! error?
|
||||
// $worksheet->fromArray(
|
||||
// $result,
|
||||
// null,
|
||||
// $coordinate,
|
||||
// true
|
||||
// );
|
||||
|
||||
// Calculate the array formula range that we should set for our target, based on our target cell coordinate
|
||||
[$col, $row] = Coordinate::indexesFromString($coordinate);
|
||||
$row += count($result) - 1;
|
||||
$col = Coordinate::stringFromColumnIndex($col + count($result[0]) - 1);
|
||||
$formulaAttributes = ['t' => 'array', 'ref' => "{$coordinate}:{$col}{$row}"];
|
||||
|
||||
// fromArray() will reset the value for this cell with the calculation result
|
||||
// Using fromArray() would reset the value for this cell with the calculation result
|
||||
// as well as updating the spillage cells,
|
||||
// so we need to restore this cell to its formula value, attributes, and datatype
|
||||
$cell = $worksheet->getCell($coordinate);
|
||||
|
||||
@@ -2900,12 +2900,13 @@ class Worksheet implements IComparable
|
||||
* @param string $range Range to extract title from
|
||||
* @param bool $returnRange Return range? (see example)
|
||||
*
|
||||
* @return mixed
|
||||
* @return string|string[]
|
||||
*/
|
||||
public static function extractSheetTitle($range, $returnRange = false)
|
||||
{
|
||||
// Sheet title included?
|
||||
if (($sep = strrpos($range, '!')) === false) {
|
||||
$sep = strrpos($range, '!');
|
||||
if ($sep === false) {
|
||||
return $returnRange ? ['', $range] : '';
|
||||
}
|
||||
|
||||
|
||||
@@ -1245,6 +1245,16 @@ class Worksheet extends WriterPart
|
||||
$objWriter->writeElement('v', $cellIsFormula ? $formulaerr : $cellValue);
|
||||
}
|
||||
|
||||
private const CM_SPILLAGE_ARRAY_FUNCTIONS = '/\b(' .
|
||||
'anchorarray|' .
|
||||
'filter|' .
|
||||
'randarray|' .
|
||||
'sequence|' .
|
||||
'sort|' .
|
||||
'sortby|' .
|
||||
'unique' .
|
||||
')\(/ui';
|
||||
|
||||
private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell $cell): void
|
||||
{
|
||||
$calculatedValue = $this->getParentWriter()->getPreCalculateFormulas()
|
||||
@@ -1265,7 +1275,9 @@ class Worksheet extends WriterPart
|
||||
|
||||
$attributes = $cell->getFormulaAttributes();
|
||||
if (($attributes['t'] ?? null) === 'array') {
|
||||
$objWriter->writeAttribute('cm', '1');
|
||||
if (preg_match(self::CM_SPILLAGE_ARRAY_FUNCTIONS, $cellValue) === 1) {
|
||||
$objWriter->writeAttribute('cm', '1');
|
||||
}
|
||||
|
||||
$objWriter->startElement('f');
|
||||
$objWriter->writeAttribute('t', 'array');
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class InternalFunctionsTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider anchorArrayDataProvider
|
||||
*/
|
||||
public function testAnchorArrayFormula(string $reference, string $range, array $expectedResult): void
|
||||
{
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet1 = $spreadsheet->getActiveSheet();
|
||||
$sheet1->setTitle('SheetOne'); // no space in sheet title
|
||||
$sheet2 = $spreadsheet->createSheet();
|
||||
$sheet2->setTitle('Sheet Two'); // space in sheet title
|
||||
|
||||
$sheet1->setCellValue('C3', '=SEQUENCE(3,3,-4)', true, 'C3:E5');
|
||||
$sheet2->setCellValue('C3', '=SEQUENCE(3,3, 9, -1)', true, 'C3:E5');
|
||||
|
||||
$sheet1->setCellValue('A8', "=ANCHORARRAY({$reference})", true, $range);
|
||||
|
||||
$result1 = $sheet1->getCell('A8')->getCalculatedValue(true, true);
|
||||
self::assertSame($expectedResult, $result1);
|
||||
$attributes1 = $sheet1->getCell('A8')->getFormulaAttributes();
|
||||
self::assertSame(['t' => 'array', 'ref' => $range], $attributes1);
|
||||
}
|
||||
|
||||
public function anchorArrayDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'C3',
|
||||
'A8:C10',
|
||||
[[-4, -3, -2], [-1, 0, 1], [2, 3, 4]],
|
||||
],
|
||||
[
|
||||
"'Sheet Two'!C3",
|
||||
'A8:C10',
|
||||
[[9, 8, 7], [6, 5, 4], [3, 2, 1]],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider singleDataProvider
|
||||
*/
|
||||
public function testSingleArrayFormula(string $reference, array $expectedResult): void
|
||||
{
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet1 = $spreadsheet->getActiveSheet();
|
||||
$sheet1->setTitle('SheetOne'); // no space in sheet title
|
||||
$sheet2 = $spreadsheet->createSheet();
|
||||
$sheet2->setTitle('Sheet Two'); // space in sheet title
|
||||
|
||||
$sheet1->setCellValue('C3', '=SEQUENCE(3,3,-4)', true, 'C3:E5');
|
||||
$sheet2->setCellValue('C3', '=SEQUENCE(3,3, 9, -1)', true, 'C3:E5');
|
||||
|
||||
$sheet1->setCellValue('A8', "=SINGLE({$reference})");
|
||||
|
||||
$result1 = $sheet1->getCell('A8')->getCalculatedValue(true, true);
|
||||
self::assertSame($expectedResult, $result1);
|
||||
}
|
||||
|
||||
public function singleDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'C3',
|
||||
[[-4]],
|
||||
],
|
||||
[
|
||||
"'Sheet Two'!C3",
|
||||
[[9]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user