diff --git a/crates/functions/src/criteria.rs b/crates/functions/src/criteria.rs index 20410c8..1784f08 100644 --- a/crates/functions/src/criteria.rs +++ b/crates/functions/src/criteria.rs @@ -168,19 +168,25 @@ fn tokenize(pattern: &str) -> Vec { while let Some(c) = it.next() { match c { '~' => match it.next() { - Some(n) => out.extend(n.to_lowercase().map(Tok::Char)), + Some(n) => out.extend(std::iter::once(lower_char(n)).map(Tok::Char)), None => out.push(Tok::Char('~')), }, '*' => out.push(Tok::Star), '?' => out.push(Tok::Any), - c => out.extend(c.to_lowercase().map(Tok::Char)), + c => out.extend(std::iter::once(lower_char(c)).map(Tok::Char)), } } out } +/// Lowercase one char to exactly one char, so positions in the lowered text match the original +/// (U+0130 `İ` folds to `i`, like `fold` in text.rs). +fn lower_char(c: char) -> char { + c.to_lowercase().next().unwrap_or(c) +} + fn lower_chars(s: &str) -> Vec { - s.chars().flat_map(char::to_lowercase).collect() + s.chars().map(lower_char).collect() } /// Whether the pattern contains an unescaped wildcard. @@ -317,6 +323,8 @@ mod tests { assert_eq!(wildcard_find("b?d", "abxxd bcd", 0), Some(6)); assert_eq!(wildcard_find("c*", "abcd", 0), Some(2)); assert_eq!(wildcard_find("z", "abcd", 0), None); + assert_eq!(wildcard_find("b", "a\u{130}bc", 0), Some(2)); + assert!(wildcard_match("i*", "\u{130}stanbul")); assert!(has_wildcards("a*")); assert!(!has_wildcards("abc")); } diff --git a/crates/functions/src/text.rs b/crates/functions/src/text.rs index 723cd4a..eefb2d5 100644 --- a/crates/functions/src/text.rs +++ b/crates/functions/src/text.rs @@ -1032,6 +1032,11 @@ mod tests { close(ev("SEARCH", vec![t("~*"), t("a*b")]), 2.0); is_err(ev("SEARCH", vec![t("z"), t("abc")]), CellError::Value); is_err(ev("SEARCH", vec![t("a"), t("abc"), n(5.0)]), CellError::Value); + close(ev("SEARCH", vec![t("b"), t("\u{130}b")]), 2.0); + close(ev("SEARCH", vec![t("B"), t("\u{130}b")]), 2.0); + close(ev("SEARCH", vec![t("b?"), t("a\u{130}bc")]), 3.0); + close(ev("SEARCH", vec![t("i"), t("a\u{130}bc")]), 2.0); + is_err(ev("SEARCH", vec![t("b"), t("a\u{130}bc"), n(4.0)]), CellError::Value); assert_eq!(ev("EXACT", vec![t("a"), t("A")]), Value::Bool(false)); assert_eq!(ev("EXACT", vec![t("a"), t("a")]), Value::Bool(true)); }