11using System ;
2+ using System . CodeDom ;
23using System . Collections . Generic ;
4+ using System . Globalization ;
35using ExcelDna . Integration ;
46using 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 {
0 commit comments