1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef; |
4
|
|
|
|
5
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled; |
6
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\Exception; |
7
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; |
8
|
|
|
use PhpOffice\PhpSpreadsheet\Shared\StringHelper; |
9
|
|
|
|
10
|
|
|
class VLookup extends LookupBase |
11
|
|
|
{ |
12
|
|
|
use ArrayEnabled; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* VLOOKUP |
16
|
|
|
* The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value |
17
|
|
|
* in the same row based on the index_number. |
18
|
|
|
* |
19
|
|
|
* @param mixed $lookupValue The value that you want to match in lookup_array |
20
|
|
|
* @param mixed $lookupArray The range of cells being searched |
21
|
|
|
* @param mixed $indexNumber The column number in table_array from which the matching value must be returned. |
22
|
|
|
* The first column is 1. |
23
|
|
|
* @param mixed $notExactMatch determines if you are looking for an exact match based on lookup_value |
24
|
|
|
* |
25
|
|
|
* @return mixed The value of the found cell |
26
|
|
|
*/ |
27
|
35 |
|
public static function lookup(mixed $lookupValue, mixed $lookupArray, mixed $indexNumber, mixed $notExactMatch = true): mixed |
28
|
|
|
{ |
29
|
35 |
|
if (is_array($lookupValue) || is_array($indexNumber)) { |
30
|
21 |
|
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $indexNumber, $notExactMatch); |
31
|
|
|
} |
32
|
|
|
|
33
|
35 |
|
$notExactMatch = (bool) ($notExactMatch ?? true); |
34
|
|
|
|
35
|
|
|
try { |
36
|
35 |
|
self::validateLookupArray($lookupArray); |
37
|
35 |
|
$indexNumber = self::validateIndexLookup($lookupArray, $indexNumber); |
38
|
3 |
|
} catch (Exception $e) { |
39
|
3 |
|
return $e->getMessage(); |
40
|
|
|
} |
41
|
|
|
|
42
|
32 |
|
$f = array_keys($lookupArray); |
43
|
32 |
|
$firstRow = array_pop($f); |
44
|
32 |
|
if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray[$firstRow]))) { |
45
|
3 |
|
return ExcelError::REF(); |
46
|
|
|
} |
47
|
29 |
|
$columnKeys = array_keys($lookupArray[$firstRow]); |
48
|
29 |
|
$returnColumn = $columnKeys[--$indexNumber]; |
49
|
29 |
|
$firstColumn = array_shift($columnKeys) ?? 1; |
50
|
|
|
|
51
|
29 |
|
if (!$notExactMatch) { |
52
|
|
|
/** @var callable $callable */ |
53
|
9 |
|
$callable = [self::class, 'vlookupSort']; |
54
|
9 |
|
uasort($lookupArray, $callable); |
55
|
|
|
} |
56
|
|
|
|
57
|
29 |
|
$rowNumber = self::vLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch); |
58
|
|
|
|
59
|
29 |
|
if ($rowNumber !== null) { |
60
|
|
|
// return the appropriate value |
61
|
23 |
|
return $lookupArray[$rowNumber][$returnColumn]; |
62
|
|
|
} |
63
|
|
|
|
64
|
6 |
|
return ExcelError::NA(); |
65
|
|
|
} |
66
|
|
|
|
67
|
9 |
|
private static function vlookupSort(array $a, array $b): int |
68
|
|
|
{ |
69
|
9 |
|
reset($a); |
70
|
9 |
|
$firstColumn = key($a); |
71
|
9 |
|
$aLower = StringHelper::strToLower((string) $a[$firstColumn]); |
72
|
9 |
|
$bLower = StringHelper::strToLower((string) $b[$firstColumn]); |
73
|
|
|
|
74
|
9 |
|
if ($aLower == $bLower) { |
75
|
1 |
|
return 0; |
76
|
|
|
} |
77
|
|
|
|
78
|
9 |
|
return ($aLower < $bLower) ? -1 : 1; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @param mixed $lookupValue The value that you want to match in lookup_array |
83
|
|
|
* @param int|string $column |
84
|
|
|
*/ |
85
|
29 |
|
private static function vLookupSearch(mixed $lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int |
86
|
|
|
{ |
87
|
29 |
|
$lookupLower = StringHelper::strToLower((string) $lookupValue); |
88
|
|
|
|
89
|
29 |
|
$rowNumber = null; |
90
|
29 |
|
foreach ($lookupArray as $rowKey => $rowData) { |
91
|
29 |
|
$bothNumeric = self::numeric($lookupValue) && self::numeric($rowData[$column]); |
92
|
29 |
|
$bothNotNumeric = !self::numeric($lookupValue) && !self::numeric($rowData[$column]); |
93
|
29 |
|
$cellDataLower = StringHelper::strToLower((string) $rowData[$column]); |
94
|
|
|
|
95
|
|
|
// break if we have passed possible keys |
96
|
|
|
if ( |
97
|
29 |
|
$notExactMatch |
98
|
29 |
|
&& (($bothNumeric && ($rowData[$column] > $lookupValue)) |
99
|
29 |
|
|| ($bothNotNumeric && ($cellDataLower > $lookupLower))) |
100
|
|
|
) { |
101
|
15 |
|
break; |
102
|
|
|
} |
103
|
|
|
|
104
|
26 |
|
$rowNumber = self::checkMatch( |
105
|
26 |
|
$bothNumeric, |
106
|
26 |
|
$bothNotNumeric, |
107
|
26 |
|
$notExactMatch, |
108
|
26 |
|
$rowKey, |
109
|
26 |
|
$cellDataLower, |
110
|
26 |
|
$lookupLower, |
111
|
26 |
|
$rowNumber |
112
|
26 |
|
); |
113
|
|
|
} |
114
|
|
|
|
115
|
29 |
|
return $rowNumber; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
private static function numeric(mixed $value): bool |
119
|
|
|
{ |
120
|
|
|
return is_int($value) || is_float($value); |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
|