Sort: case-sensitive order puts lowercase before uppercase

With Case sensitive checked, Sort compared text by code point, so every
uppercase word came before every lowercase one (A, B, a, b). Excel
compares ignoring case first and only then puts lowercase before
uppercase: a, A, b, B.
This commit is contained in:
nihilist-narwhal
2026-10-10 22:52:59 -05:00
parent 9f896c1249
commit 5e22f9b373
2 changed files with 16 additions and 1 deletions
+7 -1
View File
@@ -248,7 +248,7 @@ fn do_sort(s: &mut Session, r: RangeRef, header: bool, keys: Vec<SortKey>, by_co
let pos = |v: &Value| v.as_text().and_then(|t| list.iter().position(|x| x.eq_ignore_ascii_case(t))).unwrap_or(usize::MAX);
pos(va).cmp(&pos(vb))
} else if case && va.is_text() && vb.is_text() {
va.as_text().unwrap_or("").cmp(vb.as_text().unwrap_or(""))
sort_compare(va, vb).then_with(|| case_order(va.as_text().unwrap_or(""), vb.as_text().unwrap_or("")))
} else {
sort_compare(va, vb)
};
@@ -653,6 +653,12 @@ pub fn filter_values(wb: &Workbook, sheet: usize, col: u32) -> Vec<(String, bool
seen.into_iter().map(|((_, t), on)| (t, on)).collect()
}
/// Case-sensitive tie-break for text that is equal ignoring case: as in Excel, lowercase sorts
/// before uppercase at the first letter that differs in case.
fn case_order(a: &str, b: &str) -> Ordering {
a.chars().zip(b.chars()).find(|(x, y)| x != y).map_or(Ordering::Equal, |(x, y)| y.is_lowercase().cmp(&x.is_lowercase()))
}
fn sort_key(v: &Value) -> (u8, i64, String) {
match v {
Value::Number(n) => (0, (n * 1e6) as i64, String::new()),
+9
View File
@@ -606,6 +606,15 @@ fn hidden_formulas_stay_hidden_on_protected_sheets() {
assert_eq!(r["results"][0]["value"], "7");
}
#[test]
fn case_sensitive_sort_puts_lowercase_first() {
let mut s = s();
s.execute("range.setValues", json!({"range": "A1", "values": [["b"], ["B"], ["Ab"], ["a"], ["aB"], ["A"]]})).unwrap();
s.execute("data.sort", json!({"range": "A1:A6", "header": false, "matchCase": true, "keys": [{"column": "A"}]})).unwrap();
let col: Vec<Value> = (1..=6).map(|r| v(&s, &format!("A{r}"))).collect();
assert_eq!(col, ["a", "A", "aB", "Ab", "b", "B"].map(Value::from));
}
#[test]
fn formatting_and_styles() {
let mut s = s();