Initial work on implementing Array-enabled for the HLOOKUP() and VLOOKUP() functions (#2611)
* Initial work on implementing Array-enabled for the HLOOKUP() and VLOOKUP() functions * In the MATCH() function, we should also use `evaluateArrayArgumentsIgnore()` because the lookupvalue and matchType arguments can be array arguments, but lookupArray is always a dataset matrix
This commit is contained in:
parent
db21d043fe
commit
c9f948bd91
|
|
@ -765,26 +765,11 @@ parameters:
|
|||
count: 1
|
||||
path: src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\LookupBase\\:\\:checkMatch\\(\\) has parameter \\$notExactMatch with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\LookupBase\\:\\:validateIndexLookup\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\LookupBase\\:\\:validateIndexLookup\\(\\) has parameter \\$index_number with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\LookupBase\\:\\:validateIndexLookup\\(\\) has parameter \\$lookup_array with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Matrix\\:\\:extractRowValue\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
|
|
@ -845,31 +830,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has parameter \\$column with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has parameter \\$lookupArray with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has parameter \\$lookupValue with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has parameter \\$notExactMatch with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vlookupSort\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ trait ArrayEnabled
|
|||
self::$arrayArgumentHelper->initialise($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles array argument processing when the function accepts a single argument that can be an array argument.
|
||||
* Example use for:
|
||||
* DAYOFMONTH() or FACT().
|
||||
*/
|
||||
protected static function evaluateSingleArgumentArray(callable $method, array $values): array
|
||||
{
|
||||
$result = [];
|
||||
|
|
@ -31,6 +36,11 @@ trait ArrayEnabled
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles array argument processing when the function accepts multiple arguments,
|
||||
* and any of them can be an array argument.
|
||||
* Example use for:
|
||||
* ROUND() or DATE().
|
||||
*
|
||||
* @param mixed ...$arguments
|
||||
*/
|
||||
protected static function evaluateArrayArguments(callable $method, ...$arguments): array
|
||||
|
|
@ -42,6 +52,12 @@ trait ArrayEnabled
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles array argument processing when the function accepts multiple arguments,
|
||||
* but only the first few (up to limit) can be an array arguments.
|
||||
* Example use for:
|
||||
* NETWORKDAYS() or CONCATENATE(), where the last argument is a matrix (or a series of values) that need
|
||||
* to be treated as a such rather than as an array arguments.
|
||||
*
|
||||
* @param mixed ...$arguments
|
||||
*/
|
||||
protected static function evaluateArrayArgumentsSubset(callable $method, int $limit, ...$arguments): array
|
||||
|
|
@ -55,6 +71,12 @@ trait ArrayEnabled
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles array argument processing when the function accepts multiple arguments,
|
||||
* but only the last few (from start) can be an array arguments.
|
||||
* Example use for:
|
||||
* Z.TEST() or INDEX(), where the first argument 1 is a matrix that needs to be treated as a dataset
|
||||
* rather than as an array argument.
|
||||
*
|
||||
* @param mixed ...$arguments
|
||||
*/
|
||||
protected static function evaluateArrayArgumentsSubsetFrom(callable $method, int $start, ...$arguments): array
|
||||
|
|
@ -74,4 +96,27 @@ trait ArrayEnabled
|
|||
|
||||
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles array argument processing when the function accepts multiple arguments,
|
||||
* and any of them can be an array argument except for the one specified by ignore.
|
||||
* Example use for:
|
||||
* HLOOKUP() and VLOOKUP(), where argument 1 is a matrix that needs to be treated as a database
|
||||
* rather than as an array argument.
|
||||
*
|
||||
* @param mixed ...$arguments
|
||||
*/
|
||||
protected static function evaluateArrayArgumentsIgnore(callable $method, int $ignore, ...$arguments): array
|
||||
{
|
||||
$leadingArguments = array_slice($arguments, 0, $ignore);
|
||||
$ignoreArgument = array_slice($arguments, $ignore, 1);
|
||||
$trailingArguments = array_slice($arguments, $ignore + 1);
|
||||
|
||||
self::initialiseHelper(array_merge($leadingArguments, [[null]], $trailingArguments));
|
||||
$arguments = self::$arrayArgumentHelper->arguments();
|
||||
|
||||
array_splice($arguments, $ignore, 1, $ignoreArgument);
|
||||
|
||||
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class ExcelMatch
|
|||
public static function MATCH($lookupValue, $lookupArray, $matchType = self::MATCHTYPE_LARGEST_VALUE)
|
||||
{
|
||||
if (is_array($lookupValue)) {
|
||||
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $matchType);
|
||||
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $matchType);
|
||||
}
|
||||
|
||||
$lookupArray = Functions::flattenArray($lookupArray);
|
||||
|
|
|
|||
|
|
@ -2,14 +2,16 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
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 HLookup extends LookupBase
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* HLOOKUP
|
||||
* The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value
|
||||
|
|
@ -25,9 +27,11 @@ class HLookup extends LookupBase
|
|||
*/
|
||||
public static function lookup($lookupValue, $lookupArray, $indexNumber, $notExactMatch = true)
|
||||
{
|
||||
$lookupValue = Functions::flattenSingleValue($lookupValue);
|
||||
$indexNumber = Functions::flattenSingleValue($indexNumber);
|
||||
$notExactMatch = ($notExactMatch === null) ? true : Functions::flattenSingleValue($notExactMatch);
|
||||
if (is_array($lookupValue)) {
|
||||
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $indexNumber, $notExactMatch);
|
||||
}
|
||||
|
||||
$notExactMatch = (bool) ($notExactMatch ?? true);
|
||||
$lookupArray = self::convertLiteralArray($lookupArray);
|
||||
|
||||
try {
|
||||
|
|
@ -44,7 +48,7 @@ class HLookup extends LookupBase
|
|||
|
||||
$firstkey = $f[0] - 1;
|
||||
$returnColumn = $firstkey + $indexNumber;
|
||||
$firstColumn = array_shift($f);
|
||||
$firstColumn = array_shift($f) ?? 1;
|
||||
$rowNumber = self::hLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch);
|
||||
|
||||
if ($rowNumber !== null) {
|
||||
|
|
@ -57,10 +61,9 @@ class HLookup extends LookupBase
|
|||
|
||||
/**
|
||||
* @param mixed $lookupValue The value that you want to match in lookup_array
|
||||
* @param mixed $column The column to look up
|
||||
* @param mixed $notExactMatch determines if you are looking for an exact match based on lookup_value
|
||||
* @param int|string $column
|
||||
*/
|
||||
private static function hLookupSearch($lookupValue, array $lookupArray, $column, $notExactMatch): ?int
|
||||
private static function hLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int
|
||||
{
|
||||
$lookupLower = StringHelper::strToLower($lookupValue);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
|||
|
||||
abstract class LookupBase
|
||||
{
|
||||
protected static function validateIndexLookup($lookup_array, $index_number)
|
||||
protected static function validateIndexLookup(array $lookup_array, $index_number): int
|
||||
{
|
||||
// index_number must be a number greater than or equal to 1
|
||||
if (!is_numeric($index_number) || $index_number < 1) {
|
||||
|
|
@ -25,7 +25,7 @@ abstract class LookupBase
|
|||
protected static function checkMatch(
|
||||
bool $bothNumeric,
|
||||
bool $bothNotNumeric,
|
||||
$notExactMatch,
|
||||
bool $notExactMatch,
|
||||
int $rowKey,
|
||||
string $cellDataLower,
|
||||
string $lookupLower,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class VLookup extends LookupBase
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* VLOOKUP
|
||||
* The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value
|
||||
|
|
@ -24,9 +26,11 @@ class VLookup extends LookupBase
|
|||
*/
|
||||
public static function lookup($lookupValue, $lookupArray, $indexNumber, $notExactMatch = true)
|
||||
{
|
||||
$lookupValue = Functions::flattenSingleValue($lookupValue);
|
||||
$indexNumber = Functions::flattenSingleValue($indexNumber);
|
||||
$notExactMatch = ($notExactMatch === null) ? true : Functions::flattenSingleValue($notExactMatch);
|
||||
if (is_array($lookupValue)) {
|
||||
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $indexNumber, $notExactMatch);
|
||||
}
|
||||
|
||||
$notExactMatch = (bool) ($notExactMatch ?? true);
|
||||
|
||||
try {
|
||||
$indexNumber = self::validateIndexLookup($lookupArray, $indexNumber);
|
||||
|
|
@ -41,7 +45,7 @@ class VLookup extends LookupBase
|
|||
}
|
||||
$columnKeys = array_keys($lookupArray[$firstRow]);
|
||||
$returnColumn = $columnKeys[--$indexNumber];
|
||||
$firstColumn = array_shift($columnKeys);
|
||||
$firstColumn = array_shift($columnKeys) ?? 1;
|
||||
|
||||
if (!$notExactMatch) {
|
||||
uasort($lookupArray, ['self', 'vlookupSort']);
|
||||
|
|
@ -71,7 +75,11 @@ class VLookup extends LookupBase
|
|||
return ($aLower < $bLower) ? -1 : 1;
|
||||
}
|
||||
|
||||
private static function vLookupSearch($lookupValue, $lookupArray, $column, $notExactMatch)
|
||||
/**
|
||||
* @param mixed $lookupValue The value that you want to match in lookup_array
|
||||
* @param int|string $column
|
||||
*/
|
||||
private static function vLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int
|
||||
{
|
||||
$lookupLower = StringHelper::strToLower($lookupValue);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
|
@ -89,4 +90,40 @@ class HLookupTest extends TestCase
|
|||
);
|
||||
self::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerHLookupArray
|
||||
*/
|
||||
public function testHLookupArray(array $expectedResult, string $values, string $database, string $index): void
|
||||
{
|
||||
$calculation = Calculation::getInstance();
|
||||
|
||||
$formula = "=HLOOKUP({$values}, {$database}, {$index}, false)";
|
||||
$result = $calculation->_calculateFormulaValue($formula);
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function providerHLookupArray(): array
|
||||
{
|
||||
return [
|
||||
'row vector #1' => [
|
||||
[[4, 9]],
|
||||
'{"Axles", "Bolts"}',
|
||||
'{"Axles", "Bearings", "Bolts"; 4, 4, 9; 5, 7, 10; 6, 8, 11}',
|
||||
'2',
|
||||
],
|
||||
'row vector #2' => [
|
||||
[[5, 7]],
|
||||
'{"Axles", "Bearings"}',
|
||||
'{"Axles", "Bearings", "Bolts"; 4, 4, 9; 5, 7, 10; 6, 8, 11}',
|
||||
'3',
|
||||
],
|
||||
'row/column vectors' => [
|
||||
[[4, 9], [5, 10]],
|
||||
'{"Axles", "Bolts"}',
|
||||
'{"Axles", "Bearings", "Bolts"; 4, 4, 9; 5, 7, 10; 6, 8, 11}',
|
||||
'{2; 3}',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
|
@ -28,4 +29,28 @@ class VLookupTest extends TestCase
|
|||
{
|
||||
return require 'tests/data/Calculation/LookupRef/VLOOKUP.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerVLookupArray
|
||||
*/
|
||||
public function testVLookupArray(array $expectedResult, string $values, string $database, string $index): void
|
||||
{
|
||||
$calculation = Calculation::getInstance();
|
||||
|
||||
$formula = "=VLOOKUP({$values}, {$database}, {$index}, false)";
|
||||
$result = $calculation->_calculateFormulaValue($formula);
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function providerVLookupArray(): array
|
||||
{
|
||||
return [
|
||||
'row vector' => [
|
||||
[[4.19, 5.77, 4.14]],
|
||||
'{"Orange", "Green", "Red"}',
|
||||
'{"Red", 4.14; "Orange", 4.19; "Yellow", 5.17; "Green", 5.77; "Blue", 6.39}',
|
||||
'2',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class AbsTest extends AllSetupTeardown
|
|||
* @param mixed $expectedResult
|
||||
* @param mixed $number
|
||||
*/
|
||||
public function testRound($expectedResult, $number = 'omitted'): void
|
||||
public function testAbs($expectedResult, $number = 'omitted'): void
|
||||
{
|
||||
$sheet = $this->getSheet();
|
||||
$this->mightHaveException($expectedResult);
|
||||
|
|
|
|||
Loading…
Reference in New Issue