Skip to content

Commit f1101b3

Browse files
committed
Fix if_not_found, and improve match handling for errors
Might still differ from XMATCH in edge cases and string locales
1 parent 764927d commit f1101b3

File tree

2 files changed

+77
-19
lines changed

2 files changed

+77
-19
lines changed

Source/ExcelDna.XFunctions/Functions.cs

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
2+
using System.CodeDom;
23
using System.Collections.Generic;
4+
using System.Globalization;
35
using ExcelDna.Integration;
46
using static ExcelDna.Integration.XlCall;
57

@@ -37,8 +39,12 @@ public static object XLOOKUP(
3739
if (matchResult is ExcelError error)
3840
{
3941
if (error == ExcelError.ExcelErrorNA)
42+
{
4043
if (if_not_found is ExcelMissing)
4144
return ExcelError.ExcelErrorNA; // Default if not found is #N/A
45+
else
46+
return if_not_found;
47+
}
4248

4349
return error; // Might be other error, likely #VALUE
4450
}
@@ -175,6 +181,9 @@ public static object XMATCH(
175181
Description = "specify the search mode to use (optional)\r\n 1 - Search first-to-last (default)\r\n -1 - Search last-to-first\r\n 2 - Binary search (sorted ascending order)\r\n -2 - Binary search (sorted descending order)"
176182
)] object search_mode)
177183
{
184+
if (lookup_value is ExcelError)
185+
return lookup_value; // Seems to be Excel's behaviour
186+
178187
if (lookup_value is object[,])
179188
{
180189
// TODO: Does XMATCH support lookup_value that is an array?
@@ -243,9 +252,10 @@ public static object XMATCH(
243252
}
244253

245254
// Three cases are fine:
255+
// TODO: Actually these are not correct: MATCH and XMATCH do comparisons differently.
246256
// match_mode == 0 (exact_match) and search_mode == 1 (first-to-last) ==> match_type = 0 in MATCH
247257
// match_mode == -1 (exact or next smaller) and search_mode == 2 (sorted ascending) ==> match_type = 1 in MATCH ?????
248-
// match_mode == 1 (exact or next larger) and search_mode == 2 (sorted descending) ==> match_type = -1 in MATCH ?????
258+
// match_mode == 1 (exact or next larger) and search_mode == -2 (sorted descending) ==> match_type = -1 in MATCH ?????
249259

250260
if (match_mode.Equals(0.0) && search_mode.Equals(1.0))
251261
{
@@ -372,34 +382,82 @@ public static object XMATCH(
372382
}
373383

374384
// This comparison function must agree exactly with how Excel compares two values
375-
// I'm avoiding to implment it myself for now - who know what the rules are for strings, errors etc.
385+
// We accept only numbers, strings, bools and errors (no ExcelReference or arrays)
376386
// Instead I base it on a call to MATCH
377387
// If a < b it returns -1
378388
// if a == b it returns 0
379389
// if a > b it returns 1
390+
// if a and b can't be compared, because one is an error, returns -2
380391
#if DEBUG
381-
public // allows checking on a sheet
392+
[ExcelFunction] public // allows checking on a sheet
382393
#endif
383-
static int ExcelCompare(object a, object b)
394+
static int ExcelCompare(object a, object b)
384395
{
385-
// Is a <= b ? (MATCH with match_type -1 will return 1.0 if so, else #N/A error)
386-
object a_leq_b_res = Excel(xlfMatch, a, new object[] { b }, -1.0);
387-
object b_leq_a_res = Excel(xlfMatch, b, new object[] { a }, -1.0);
396+
if (a is ExcelError || b is ExcelError)
397+
return -2;
388398

389-
bool a_leq_b = a_leq_b_res is double;
390-
bool b_leq_a = b_leq_a_res is double;
391-
392-
if (a_leq_b)
399+
if (a is double da)
393400
{
394-
if (b_leq_a)
395-
return 0;
401+
// Not going to worry about NaN etc.
402+
if (b is double db)
403+
return (da < db) ? -1 : (da == db ? 0 : 1);
396404
else
397-
return -1;
405+
return -1; // b is a string or bool, so a < b
398406
}
399-
else
407+
408+
if (a is string sa)
409+
{
410+
if (b is double)
411+
return 1; // a > b
412+
if (b is string sb)
413+
{
414+
var cmp = string.Compare(sa, sb, true);
415+
var result = (cmp < 0) ? -1 : (cmp == 0 ? 0 : 1);
416+
417+
#if DEBUG
418+
// We could call match to avoid dealing with locales etc., until we know how Excel deals with this
419+
int dbg_result = -3;
420+
{
421+
422+
// Is a <= b ? (MATCH with match_type -1 will return 1.0 if so, else #N/A error)
423+
object a_leq_b_res = Excel(xlfMatch, a, new object[] { b }, -1.0);
424+
object b_leq_a_res = Excel(xlfMatch, b, new object[] { a }, -1.0);
425+
426+
bool a_leq_b = a_leq_b_res is double;
427+
bool b_leq_a = b_leq_a_res is double;
428+
429+
if (a_leq_b)
430+
{
431+
if (b_leq_a)
432+
dbg_result = 0;
433+
else
434+
dbg_result = -1;
435+
}
436+
else // !a_leq_b
437+
{
438+
if (b_leq_a)
439+
dbg_result = 1;
440+
else
441+
dbg_result = -2; // Cannot be compared !?
442+
}
443+
}
444+
System.Diagnostics.Debug.Assert(dbg_result == result);
445+
#endif
446+
return result;
447+
}
448+
449+
return -1; // b is bool, so is bigger then a, so a < b
450+
}
451+
452+
if (a is bool ba)
400453
{
401-
return 1;
454+
if (b is bool bb) // False < True
455+
return (!ba && bb) ? -1 : (ba == bb ? 0 : 1);
456+
457+
return 1; // b is something else and smaller than a bool, thus a > b
402458
}
459+
460+
throw new ArgumentOutOfRangeException($"Unexpected comparison between {a} and {b}");
403461
}
404462

405463

@@ -414,7 +472,7 @@ static int ExcelCompare(object a, object b)
414472
// 0 - finds the first value that is exactly equal to lookup_value. NO WILDCARDS.
415473
// -1 - finds the smallest value that is greater than or equal to lookup_value. lookup_array does not need to be sorted.
416474
#if DEBUG
417-
public // allows checking on a sheet
475+
[ExcelFunction] public // allows checking on a sheet
418476
#endif
419477
static object UnsortedMatch(object lookup_value, object lookup_array, int match_type, bool reverse_lookup = false)
420478
{

Source/ExcelDna.XFunctions/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@
3232
// You can specify all the values or you can default the Build and Revision Numbers
3333
// by using the '*' as shown below:
3434
// [assembly: AssemblyVersion("1.0.*")]
35-
[assembly: AssemblyVersion("0.3.0.0")]
36-
[assembly: AssemblyFileVersion("0.3.0.0")]
35+
[assembly: AssemblyVersion("0.4.0.0")]
36+
[assembly: AssemblyFileVersion("0.4.0.0")]

0 commit comments

Comments
 (0)