From a69c3605cfbe3428ff8a413703db82e562ad513b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A4ntz=20Miccoli?= Date: Mon, 10 Dec 2018 16:12:51 +0100 Subject: [PATCH 1/3] Exact match in VLOOKUP now returns first match (it was inconsistent with spreadsheet software before) --- CHANGELOG.md | 1 + src/PhpSpreadsheet/Calculation/LookupRef.php | 21 ++++++++++++++------ tests/data/Calculation/LookupRef/VLOOKUP.php | 21 ++++++++++++++++++++ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74d7547082..e4bd60e5e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Improve XLSX parsing speed if no readFilter is applied - [#772](https://github.com/PHPOffice/PhpSpreadsheet/issues/772) - Fix column names if read filter calls in XLSX reader skip columns - [#777](https://github.com/PHPOffice/PhpSpreadsheet/pull/777) +- Fix VLOOKUP with exact matches ## [1.5.2] - 2018-11-25 diff --git a/src/PhpSpreadsheet/Calculation/LookupRef.php b/src/PhpSpreadsheet/Calculation/LookupRef.php index 9a2937fb2c..cd2c088e57 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef.php @@ -709,6 +709,7 @@ public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not $rowNumber = $rowValue = false; foreach ($lookup_array as $rowKey => $rowData) { + // break if we have passed possible keys if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) || (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && (strtolower($rowData[$firstColumn]) > strtolower($lookup_value)))) { break; @@ -716,16 +717,24 @@ public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not // remember the last key, but only if datatypes match if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn])) || (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]))) { - $rowNumber = $rowKey; - $rowValue = $rowData[$firstColumn]; + if ($not_exact_match) { + $rowNumber = $rowKey; + $rowValue = $rowData[$firstColumn]; + + continue; + } elseif ((strtolower($rowData[$firstColumn]) == strtolower($lookup_value)) + // Spreadsheets software returns first exact match, + // we have sorted and we might have broken key orders + // we want the first one (by its initial index) + && (($rowNumber == false) || ($rowKey < $rowNumber)) + ) { + $rowNumber = $rowKey; + $rowValue = $rowData[$firstColumn]; + } } } if ($rowNumber !== false) { - if ((!$not_exact_match) && ($rowValue != $lookup_value)) { - // if an exact match is required, we have what we need to return an appropriate response - return Functions::NA(); - } // otherwise return the appropriate value return $lookup_array[$rowNumber][$returnColumn]; } diff --git a/tests/data/Calculation/LookupRef/VLOOKUP.php b/tests/data/Calculation/LookupRef/VLOOKUP.php index 2ef64d06ac..1ba30a4756 100644 --- a/tests/data/Calculation/LookupRef/VLOOKUP.php +++ b/tests/data/Calculation/LookupRef/VLOOKUP.php @@ -291,4 +291,25 @@ 2, true, ], + [ + 5, + 'x', + [ + [ + 'Selection column', + 'Value to retrieve', + ], + ['0', 1], + ['0', 2], + ['0', 3], + ['0', 4], + ['x', 5], + ['x', 6], + ['x', 7], + ['x', 8], + ['x', 9], + ], + 2, + false + ] ]; From 7af72675d14ca24dfb3426964b912b63910f4109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A4ntz=20Miccoli?= Date: Thu, 13 Dec 2018 14:58:51 +0100 Subject: [PATCH 2/3] Replicate the VLOOKUP fix to be an HLOOKUP fix (exact match error) --- src/PhpSpreadsheet/Calculation/LookupRef.php | 26 ++++++++++++++------ tests/data/Calculation/LookupRef/HLOOKUP.php | 10 ++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/PhpSpreadsheet/Calculation/LookupRef.php b/src/PhpSpreadsheet/Calculation/LookupRef.php index cd2c088e57..55539c6738 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef.php @@ -735,7 +735,7 @@ public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not } if ($rowNumber !== false) { - // otherwise return the appropriate value + // return the appropriate value return $lookup_array[$rowNumber][$returnColumn]; } @@ -783,20 +783,30 @@ public static function HLOOKUP($lookup_value, $lookup_array, $index_number, $not } $rowNumber = $rowValue = false; foreach ($lookup_array[$firstColumn] as $rowKey => $rowData) { + // break if we have passed possible keys if ((is_numeric($lookup_value) && is_numeric($rowData) && ($rowData > $lookup_value)) || (!is_numeric($lookup_value) && !is_numeric($rowData) && (strtolower($rowData) > strtolower($lookup_value)))) { break; } - $rowNumber = $rowKey; - $rowValue = $rowData; + + // remember the last key, but only if datatypes match (as in VLOOKUP) + if ((is_numeric($lookup_value) && is_numeric($rowData)) || + (!is_numeric($lookup_value) && !is_numeric($rowData))) { + if ($not_exact_match) { + $rowNumber = $rowKey; + $rowValue = $rowData; + continue; + } elseif ((strtolower($rowData) == strtolower($lookup_value)) + && (($rowNumber == false) || ($rowKey < $rowNumber)) + ) { + $rowNumber = $rowKey; + $rowValue = $rowData; + } + } } if ($rowNumber !== false) { - if ((!$not_exact_match) && ($rowValue != $lookup_value)) { - // if an exact match is required, we have what we need to return an appropriate response - return Functions::NA(); - } - // otherwise return the appropriate value + // otherwise return the appropriate value return $lookup_array[$returnColumn][$rowNumber]; } diff --git a/tests/data/Calculation/LookupRef/HLOOKUP.php b/tests/data/Calculation/LookupRef/HLOOKUP.php index 8733fa784f..25bcea8734 100644 --- a/tests/data/Calculation/LookupRef/HLOOKUP.php +++ b/tests/data/Calculation/LookupRef/HLOOKUP.php @@ -275,4 +275,14 @@ 2, true, ], + [ + 5, + 'x', + [ + ['Selection column', '0', '0', '0', '0', 'x', 'x', 'x', 'x', 'x'], + ['Value to retrieve', 1, 2, 3, 4, 5, 6, 7, 8, 9] + ], + 2, + false + ] ]; From 00c515355538dac0f28ee4e958fd3c71a8b0764d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A4ntz=20Miccoli?= Date: Thu, 13 Dec 2018 14:58:51 +0100 Subject: [PATCH 3/3] Replicate the VLOOKUP fix to be an HLOOKUP fix (exact match error) --- src/PhpSpreadsheet/Calculation/LookupRef.php | 27 ++++++++++++++------ tests/data/Calculation/LookupRef/HLOOKUP.php | 10 ++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/PhpSpreadsheet/Calculation/LookupRef.php b/src/PhpSpreadsheet/Calculation/LookupRef.php index cd2c088e57..0a1053ca50 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef.php @@ -735,7 +735,7 @@ public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not } if ($rowNumber !== false) { - // otherwise return the appropriate value + // return the appropriate value return $lookup_array[$rowNumber][$returnColumn]; } @@ -783,20 +783,31 @@ public static function HLOOKUP($lookup_value, $lookup_array, $index_number, $not } $rowNumber = $rowValue = false; foreach ($lookup_array[$firstColumn] as $rowKey => $rowData) { + // break if we have passed possible keys if ((is_numeric($lookup_value) && is_numeric($rowData) && ($rowData > $lookup_value)) || (!is_numeric($lookup_value) && !is_numeric($rowData) && (strtolower($rowData) > strtolower($lookup_value)))) { break; } - $rowNumber = $rowKey; - $rowValue = $rowData; + + // remember the last key, but only if datatypes match (as in VLOOKUP) + if ((is_numeric($lookup_value) && is_numeric($rowData)) || + (!is_numeric($lookup_value) && !is_numeric($rowData))) { + if ($not_exact_match) { + $rowNumber = $rowKey; + $rowValue = $rowData; + + continue; + } elseif ((strtolower($rowData) == strtolower($lookup_value)) + && (($rowNumber == false) || ($rowKey < $rowNumber)) + ) { + $rowNumber = $rowKey; + $rowValue = $rowData; + } + } } if ($rowNumber !== false) { - if ((!$not_exact_match) && ($rowValue != $lookup_value)) { - // if an exact match is required, we have what we need to return an appropriate response - return Functions::NA(); - } - // otherwise return the appropriate value + // otherwise return the appropriate value return $lookup_array[$returnColumn][$rowNumber]; } diff --git a/tests/data/Calculation/LookupRef/HLOOKUP.php b/tests/data/Calculation/LookupRef/HLOOKUP.php index 8733fa784f..25bcea8734 100644 --- a/tests/data/Calculation/LookupRef/HLOOKUP.php +++ b/tests/data/Calculation/LookupRef/HLOOKUP.php @@ -275,4 +275,14 @@ 2, true, ], + [ + 5, + 'x', + [ + ['Selection column', '0', '0', '0', '0', 'x', 'x', 'x', 'x', 'x'], + ['Value to retrieve', 1, 2, 3, 4, 5, 6, 7, 8, 9] + ], + 2, + false + ] ];