mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-12 00:43:16 -06:00
Functions: VLOOKUP/HLOOKUP/LOOKUP/XLOOKUP keep an empty target blank (#374)
A looked-up empty cell was turned into the number 0 inside the function, so ISBLANK saw FALSE and & / CONCATENATE inserted "0". The value now stays blank, like INDEX; a bare lookup still displays 0 as a formula's final result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
fc45b7f6d8
commit
691883f99c
@@ -968,3 +968,26 @@ fn trim_references() {
|
||||
t.set("K20", "=SUM(Q1:.Q9)");
|
||||
assert_eq!(t.get("K20"), Value::Error(CellError::Calc));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_of_an_empty_cell_stays_blank() {
|
||||
// #374: B1 (the value for key "k") is empty.
|
||||
let mut t = T::new();
|
||||
t.set("A1", "k");
|
||||
t.set("A2", "j");
|
||||
t.set("B2", "v");
|
||||
for (at, f, want) in [
|
||||
("D1", "=ISBLANK(VLOOKUP(\"k\",A1:B2,2,0))", Value::Bool(true)),
|
||||
("D2", "=\"x\"&VLOOKUP(\"k\",A1:B2,2,0)&\"y\"", Value::from("xy")),
|
||||
("D3", "=CONCATENATE(\"x\",VLOOKUP(\"k\",A1:B2,2,0),\"y\")", Value::from("xy")),
|
||||
("D4", "=ISBLANK(XLOOKUP(\"k\",A1:A2,B1:B2))", Value::Bool(true)),
|
||||
("D5", "=\"x\"&XLOOKUP(\"k\",A1:A2,B1:B2)&\"y\"", Value::from("xy")),
|
||||
("D6", "=ISBLANK(LOOKUP(\"k\",A1:A1,B1:B1))", Value::Bool(true)),
|
||||
("D7", "=ISBLANK(HLOOKUP(\"j\",A2:B3,2,0))", Value::Bool(true)),
|
||||
// A bare lookup still shows 0, like a reference to an empty cell.
|
||||
("D8", "=VLOOKUP(\"k\",A1:B2,2,0)", Value::Number(0.0)),
|
||||
] {
|
||||
t.set(at, f);
|
||||
assert_eq!(t.get(at), want, "{f}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,11 +119,6 @@ fn row(a: &Array, r: usize) -> Vec<Value> {
|
||||
(0..a.cols).map(|c| a.get(r, c).cloned().unwrap_or(Value::Empty)).collect()
|
||||
}
|
||||
|
||||
/// A looked-up cell value: blanks read as 0 like a reference to an empty cell.
|
||||
fn cell_result(v: Value) -> Value {
|
||||
if matches!(v, Value::Empty) { Value::Number(0.0) } else { v }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// VLOOKUP / HLOOKUP / LOOKUP / MATCH
|
||||
|
||||
@@ -146,7 +141,9 @@ fn vh_lookup(args: &[Arg], vertical: bool) -> R<Value> {
|
||||
let pos = if approx { bsearch_last(&x, &keys, false) } else { find_exact(&x, &keys, true, false) };
|
||||
let pos = pos.ok_or(CellError::NA)?;
|
||||
let v = if vertical { table.get(pos, idx) } else { table.get(idx, pos) };
|
||||
Ok(cell_result(v.cloned().unwrap_or(Value::Empty)))
|
||||
// An empty target stays blank, as in Excel: ISBLANK sees it as blank and `&` /
|
||||
// CONCATENATE treat it as ""; only a formula's final result displays 0 (#374).
|
||||
Ok(v.cloned().unwrap_or(Value::Empty))
|
||||
}
|
||||
|
||||
fn vlookup(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||||
@@ -179,18 +176,18 @@ fn lookup(args: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
return v.map(cell_result).ok_or(CellError::NA);
|
||||
return v.ok_or(CellError::NA);
|
||||
}
|
||||
// Array form: search the first row of a wide array, else the first column; return the
|
||||
// matching entry from the last row / column.
|
||||
if la.cols > la.rows {
|
||||
let keys = row(&la, 0);
|
||||
let pos = bsearch_last(&x, &keys, false).ok_or(CellError::NA)?;
|
||||
Ok(cell_result(la.get(la.rows - 1, pos).cloned().unwrap_or(Value::Empty)))
|
||||
Ok(la.get(la.rows - 1, pos).cloned().unwrap_or(Value::Empty))
|
||||
} else {
|
||||
let keys = column(&la, 0);
|
||||
let pos = bsearch_last(&x, &keys, false).ok_or(CellError::NA)?;
|
||||
Ok(cell_result(la.get(pos, la.cols - 1).cloned().unwrap_or(Value::Empty)))
|
||||
Ok(la.get(pos, la.cols - 1).cloned().unwrap_or(Value::Empty))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,13 +308,13 @@ fn xlookup(args: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
|
||||
if vertical {
|
||||
let r = row(&ra, p);
|
||||
if r.len() == 1 {
|
||||
return Ok(cell_result(r.into_iter().next().unwrap_or(Value::Empty)));
|
||||
return Ok(r.into_iter().next().unwrap_or(Value::Empty));
|
||||
}
|
||||
array_val(1, r.len(), r)
|
||||
} else {
|
||||
let c = column(&ra, p);
|
||||
if c.len() == 1 {
|
||||
return Ok(cell_result(c.into_iter().next().unwrap_or(Value::Empty)));
|
||||
return Ok(c.into_iter().next().unwrap_or(Value::Empty));
|
||||
}
|
||||
array_val(c.len(), 1, c)
|
||||
}
|
||||
@@ -581,7 +578,8 @@ mod tests {
|
||||
is_err(ev("VLOOKUP", vec![n(4.0), rf(table()), n(2.0), b(false)]), CellError::NA);
|
||||
is_err(ev("VLOOKUP", vec![n(4.0), rf(table()), n(2.0), empty()]), CellError::NA);
|
||||
close(ev("VLOOKUP", vec![n(3.0), rf(table()), n(3.0), b(false)]), 30.0);
|
||||
close(ev("VLOOKUP", vec![n(5.0), rf(table()), n(3.0), b(false)]), 0.0);
|
||||
// An empty target stays blank (the cell displays 0 only as a final result) (#374).
|
||||
assert_eq!(ev("VLOOKUP", vec![n(5.0), rf(table()), n(3.0), b(false)]), Value::Empty);
|
||||
is_err(ev("VLOOKUP", vec![n(3.0), rf(table()), n(4.0)]), CellError::Ref);
|
||||
is_err(ev("VLOOKUP", vec![n(3.0), rf(table()), n(0.0)]), CellError::Value);
|
||||
close(ev("VLOOKUP", vec![t("b*"), rf(names()), n(2.0), b(false)]), 2.0);
|
||||
|
||||
Reference in New Issue
Block a user